Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ npm run dev # open http://localhost:3000

After the UI opens: **Settings** → configure a model provider → open a workspace → click **Sync** → start chatting.

### Optional CLI

```bash
npm link
innoclaw
innoclaw run --prompt "Summarize the current workspace"
```

The CLI uses the same local app runtime as the web UI. When auth is enabled, it auto-starts the app, opens the browser login page on `localhost`, and stores a dedicated CLI session after sign-in. For trusted headless automation, start the app with authentication disabled.

Headless local run mode:

```bash
npm run dev:no-auth
```

> **Security**: InnoClaw includes shell execution and remote job submission capabilities. See [SECURITY.md](SECURITY.md) for deployment hardening and trust boundary documentation.

<details>
Expand Down Expand Up @@ -141,6 +157,7 @@ Go from code inspection to job submission and result analysis. Review repositori
#### 2026-05-12
- **Local User Authentication**: Added built-in account registration, sign-in, sign-out, and persistent session support.
- **Admin User Management**: Added an admin users page to create accounts and manage roles, access, passwords, and ownership.
- **CLI Login Handoff**: `innoclaw` now supports interactive terminal mode, one-shot `run`, JSON `batch`, and browser-to-CLI session handoff.



Expand Down Expand Up @@ -306,4 +323,3 @@ Go from code inspection to job submission and result analysis. Review repositori
- **License** — Apache-2.0, see `LICENSE`
- **Repository** — https://github.com/SpectrAI-Initiative/InnoClaw
- **Docs** — https://SpectrAI-Initiative.github.io/InnoClaw/

176 changes: 130 additions & 46 deletions dev-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,117 @@ cd "$(dirname "$0")"

PORT=3000

if [ -n "${INNOCLAW_DEV_START_TEST_PATH:-}" ]; then
PATH="${INNOCLAW_DEV_START_TEST_PATH}:$PATH"
hash -r
fi

server_responding() {
command -v curl >/dev/null 2>&1 || return 1
command -v node >/dev/null 2>&1 || return 1

local body_file meta status content_type node_status
body_file=$(mktemp) || return 1
meta=$(curl --noproxy "*" -sS -o "$body_file" -w "%{http_code}\n%{content_type}" "http://127.0.0.1:$PORT/api/auth/me" 2>/dev/null) || {
Comment on lines +17 to +19
rm -f "$body_file"
return 1
}
status=$(printf '%s\n' "$meta" | sed -n '1p')
content_type=$(printf '%s\n' "$meta" | sed -n '2p')

case "${content_type,,}" in
*application/json*) ;;
*)
rm -f "$body_file"
return 1
;;
esac
Comment on lines +23 to +32

node - "$status" "$body_file" <<'NODE'
const [status, bodyPath] = process.argv.slice(2);
const { readFileSync } = require("node:fs");

let body;
try {
body = JSON.parse(readFileSync(bodyPath, "utf8"));
} catch {
process.exit(1);
}

if (status === "200" && body && typeof body.authMode === "string" && body.user) {
process.exit(0);
}

if (status === "401" && body?.error === "Unauthorized") {
process.exit(0);
}

process.exit(1);
NODE
node_status=$?
rm -f "$body_file"
return "$node_status"
}

pid_elapsed_seconds() {
ps -p "$1" -o etimes= 2>/dev/null | tr -d ' '
}

pid_workdir() {
readlink "/proc/$1/cwd" 2>/dev/null
}

is_repo_dev_process() {
local pid=$1
local cwd=$(pid_workdir "$pid")
local cmdline=$(ps -p "$pid" -o args= 2>/dev/null)
[ "$cwd" = "$PWD" ] && echo "$cmdline" | grep -qE "(npm run dev|next dev|node.*next)"
}
Comment on lines +64 to +73

if [ "${INNOCLAW_DEV_START_TEST_HOOK:-}" = "1" ]; then
case "${1:-}" in
server_responding)
server_responding
exit $?
;;
*)
echo "Unknown dev-start test hook: ${1:-}" >&2
exit 64
;;
esac
fi

# Check if already running
if [ -f .dev.pid ]; then
PID=$(cat .dev.pid)
if ! echo "$PID" | grep -qE '^[0-9]+$'; then
echo "Invalid PID in .dev.pid, removing file"
rm -f .dev.pid
elif ps -p "$PID" > /dev/null 2>&1; then
echo "Dev server is already running (PID: $PID)"
exit 1
if ! is_repo_dev_process "$PID"; then
PID_CWD=$(pid_workdir "$PID")
echo ".dev.pid points to a live non-server process (PID: $PID${PID_CWD:+, cwd: $PID_CWD}). Removing stale file."
rm -f .dev.pid
elif server_responding; then
echo "Dev server is already running (PID: $PID)"
exit 0
else
PID_AGE=$(pid_elapsed_seconds "$PID")
if [ -n "$PID_AGE" ] && [ "$PID_AGE" -le 30 ]; then
echo "Dev server is still starting (PID: $PID, age: ${PID_AGE}s)"
exit 0
fi

echo "Dev server PID $PID is not healthy. Restarting it..."
kill "$PID" 2>/dev/null
sleep 2
if ps -p "$PID" > /dev/null 2>&1; then
echo "Force killing stale dev server..."
kill -9 "$PID" 2>/dev/null
sleep 1
fi
rm -f .dev.pid
fi
else
rm -f .dev.pid
fi
Expand All @@ -22,63 +124,45 @@ fi
# Check if port is occupied
check_port() {
local port=$1
local pid=$(lsof -t -i:$port 2>/dev/null)
if [ -n "$pid" ]; then
echo "Port $port is occupied by process: $pid"
local process_name=$(ps -p "$pid" -o comm= 2>/dev/null)
echo "Process name: $process_name"
local pids=$(lsof -t -i:$port 2>/dev/null)
if [ -n "$pids" ]; then
echo "Port $port is occupied by process: $pids"
for pid in $pids; do
local process_name=$(ps -p "$pid" -o comm= 2>/dev/null)
local cmdline=$(ps -p "$pid" -o args= 2>/dev/null)
echo "Process $pid: ${process_name:-unknown} ${cmdline:+($cmdline)}"
done
return 0
fi
return 1
}

kill_port_process() {
port_owned_by_pid_file() {
local port=$1
# Only kill node/next processes, not VSCode port forwarding
[ -f .dev.pid ] || return 1
local expected_pid=$(cat .dev.pid)
echo "$expected_pid" | grep -qE '^[0-9]+$' || return 1
is_repo_dev_process "$expected_pid" || return 1

local pids=$(lsof -t -i:$port 2>/dev/null)
if [ -n "$pids" ]; then
for pid in $pids; do
local cmdline=$(ps -p "$pid" -o args= 2>/dev/null)
# Skip VSCode related processes
if echo "$cmdline" | grep -qE "(vscode|code-server|sshd)"; then
echo "Skipping VSCode/SSH process: $pid ($cmdline)"
continue
fi
# Only kill node/next related processes
if echo "$cmdline" | grep -qE "(node|next|npm)"; then
echo "Killing process $pid on port $port ($cmdline)..."
kill "$pid" 2>/dev/null
sleep 2
if ps -p "$pid" > /dev/null 2>&1; then
echo "Force killing..."
kill -9 "$pid" 2>/dev/null
sleep 1
fi
fi
done
if lsof -t -i:$port > /dev/null 2>&1; then
# Check if remaining process is VSCode
local remaining=$(lsof -t -i:$port 2>/dev/null | head -1)
local remaining_cmd=$(ps -p "$remaining" -o args= 2>/dev/null)
if echo "$remaining_cmd" | grep -qE "(vscode|code-server|sshd)"; then
echo "Port $port still used by VSCode (safe to ignore)"
return 0
fi
echo "Failed to free port $port"
return 1
[ -n "$pids" ] || return 1
for pid in $pids; do
if [ "$pid" = "$expected_pid" ]; then
return 0
fi
echo "Port $port is now free"
fi
return 0
done
return 1
}

# Check and resolve port conflict
if check_port $PORT; then
echo "Attempting to free port $PORT..."
if ! kill_port_process $PORT; then
echo "Error: Could not free port $PORT. Please manually resolve."
exit 1
if port_owned_by_pid_file $PORT && server_responding; then
echo "Dev server is already running (PID: $(cat .dev.pid))"
exit 0
fi
echo "Error: Port $PORT is already in use by a process not managed by this repo's .dev.pid."
echo "Stop that process or remove the conflict before running dev-start.sh."
exit 1
fi

# Install dependencies if needed
Expand Down
77 changes: 73 additions & 4 deletions docs/usage/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,72 @@

InnoClaw provides a set of REST API endpoints served by Next.js API routes. All endpoints are under the `/api/` path.

## Auth

### Login

```
POST /api/auth/login
```

Creates a browser session for a local user account.

### Register

```
POST /api/auth/register
```

Creates a local user account and signs the user in.

### Current Session

```
GET /api/auth/me
```

Returns the signed-in user and session expiry, or `401` if the request is unauthenticated.

### CLI Session Handoff

```
POST /api/auth/cli-session
```

Requires an authenticated browser session. Mints a fresh CLI session for the same user and returns the cookie triple needed by the terminal client.

**Request Body:**

```json
{
"nonce": "cli-login-nonce"
}
```

**Response:**

```json
{
"nonce": "cli-login-nonce",
"expiresAt": "2026-06-20T00:00:00.000Z",
"user": {
"id": "user-123",
"email": "user@example.com",
"name": "User",
"role": "user",
"isActive": true,
"lastLoginAt": null,
"createdAt": "2026-05-20T00:00:00.000Z",
"updatedAt": "2026-05-20T00:00:00.000Z"
},
"cookies": {
"innoclaw_session": "token",
"innoclaw_session_expires": "2026-06-20T00:00:00.000Z",
"innoclaw_session_sig": "signature"
}
}
```

## Workspaces

### List Workspaces
Expand All @@ -12,14 +78,16 @@ GET /api/workspaces

Returns all workspaces.

When auth is enabled, this endpoint requires a valid browser or CLI session cookie set. With `AUTH_MODE=disabled`, the trusted no-auth path returns the anonymous admin context.

**Response:**

```json
[
{
"id": "workspace-uuid",
"name": "my-project",
"rootPath": "/data/research/my-project",
"folderPath": "/data/research/my-project",
"createdAt": "2025-01-01T00:00:00.000Z"
}
]
Expand All @@ -35,7 +103,8 @@ POST /api/workspaces

```json
{
"path": "/data/research/my-project"
"name": "my-project",
"folderPath": "/data/research/my-project"
}
```

Expand Down Expand Up @@ -694,8 +763,8 @@ POST /api/git/clone

```json
{
"url": "https://github.com/user/repo.git",
"rootPath": "/data/research"
"repoUrl": "https://github.com/user/repo.git",
"targetFolderName": "repo"
}
```

Expand Down
Loading
Loading