merge: fix/codesign-adhoc
This commit is contained in:
@@ -19,6 +19,8 @@
|
|||||||
- macOS Debug: add app log verbosity and rolling file log toggle for swift-log-backed app logs.
|
- macOS Debug: add app log verbosity and rolling file log toggle for swift-log-backed app logs.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
- macOS codesign: skip hardened runtime for ad-hoc signing and avoid empty options args (#70) — thanks @petter-b
|
||||||
|
- Docs: add manual OAuth setup for remote/headless deployments (#67) — thanks @wstock
|
||||||
- Docs/agent tools: clarify that browser `wait` should be avoided by default and used only in exceptional cases.
|
- Docs/agent tools: clarify that browser `wait` should be avoided by default and used only in exceptional cases.
|
||||||
- Browser tools: `upload` supports auto-click refs, direct `inputRef`/`element` file inputs, and emits input/change after `setFiles` so JS-heavy sites pick up attachments.
|
- Browser tools: `upload` supports auto-click refs, direct `inputRef`/`element` file inputs, and emits input/change after `setFiles` so JS-heavy sites pick up attachments.
|
||||||
- Browser tools: harden CDP readiness (HTTP + WS), retry CDP connects, and auto-restart the clawd browser when the socket handshake stalls.
|
- Browser tools: harden CDP readiness (HTTP + WS), retry CDP connects, and auto-restart the clawd browser when the socket handshake stalls.
|
||||||
|
|||||||
171
docs/wizard.md
Normal file
171
docs/wizard.md
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
---
|
||||||
|
summary: "CLI onboarding wizard spec (gateway + workspace + skills + daemon)"
|
||||||
|
read_when:
|
||||||
|
- Designing or implementing the onboarding wizard
|
||||||
|
- Changing gateway install/setup flow
|
||||||
|
---
|
||||||
|
|
||||||
|
# Onboarding Wizard (CLI)
|
||||||
|
|
||||||
|
Goal: single interactive flow to set up Clawdis Gateway + workspace + skills on a new machine.
|
||||||
|
Uses `@clack/prompts` for arrow-key selection and step UX.
|
||||||
|
|
||||||
|
Scope: **Local gateway only**. Remote mode is **info-only** (no config writes).
|
||||||
|
|
||||||
|
## Entry points
|
||||||
|
|
||||||
|
- `clawdis onboard` (primary)
|
||||||
|
- `clawdis setup --wizard` (alias)
|
||||||
|
|
||||||
|
## Non-interactive mode
|
||||||
|
|
||||||
|
`--non-interactive` + flags to skip prompts. `--json` outputs a machine summary.
|
||||||
|
|
||||||
|
## Preflight
|
||||||
|
|
||||||
|
- Runtime: Node >=22 (reuse `runtime-guard`).
|
||||||
|
- Detect existing files:
|
||||||
|
- config: `~/.clawdis/clawdis.json`
|
||||||
|
- creds: `~/.clawdis/credentials/`
|
||||||
|
- sessions: `~/.clawdis/sessions/`
|
||||||
|
- workspace: `~/clawd` (or configured)
|
||||||
|
- Detect available package managers: `npm`, `pnpm`, `bun`.
|
||||||
|
- Detect optional tools: `brew`, `uv`, `go`.
|
||||||
|
|
||||||
|
If config exists:
|
||||||
|
- Prompt: **Keep / Modify / Reset**
|
||||||
|
|
||||||
|
Reset uses `trash` (never `rm`).
|
||||||
|
|
||||||
|
## Flow (interactive)
|
||||||
|
|
||||||
|
1) **Mode**
|
||||||
|
- Local (full wizard)
|
||||||
|
- Remote (info-only; no config writes)
|
||||||
|
|
||||||
|
2) **Model/Auth (local only)**
|
||||||
|
- Anthropic OAuth (recommended)
|
||||||
|
- API key
|
||||||
|
- Minimax M2.1 (LM Studio; recommended local model)
|
||||||
|
- Skip
|
||||||
|
|
||||||
|
3) **Workspace + config**
|
||||||
|
- Default workspace: `~/clawd`
|
||||||
|
- Writes `agent.workspace` into `~/.clawdis/clawdis.json`
|
||||||
|
- Ensures sessions dir exists
|
||||||
|
|
||||||
|
4) **Gateway config**
|
||||||
|
- Port (default 18789)
|
||||||
|
- Bind: loopback | lan | tailnet | auto
|
||||||
|
- Auth: token | password | off
|
||||||
|
- Tailscale: off | serve | funnel
|
||||||
|
|
||||||
|
5) **Daemon install (local only)**
|
||||||
|
- macOS: LaunchAgent
|
||||||
|
- Linux: systemd user unit
|
||||||
|
- Windows: Scheduled Task
|
||||||
|
|
||||||
|
6) **Health**
|
||||||
|
- Start/restart daemon
|
||||||
|
- `clawdis health` summary
|
||||||
|
|
||||||
|
7) **Skills (recommended)**
|
||||||
|
- Read from `buildWorkspaceSkillStatus`
|
||||||
|
- Show eligible vs missing requirements
|
||||||
|
- Offer installs via preferred installer
|
||||||
|
- Allow skip
|
||||||
|
|
||||||
|
8) **Finish**
|
||||||
|
- Summary + next steps
|
||||||
|
- Reminder: iOS/Android/macOS node apps add canvas/camera/screen/system features.
|
||||||
|
|
||||||
|
## Remote mode (info-only)
|
||||||
|
|
||||||
|
- Explain where gateway runs.
|
||||||
|
- Show required steps on gateway host:
|
||||||
|
- `clawdis setup`
|
||||||
|
- `clawdis gateway-daemon ...`
|
||||||
|
- OAuth file: `~/.clawdis/credentials/oauth.json`
|
||||||
|
- Workspace: `~/clawd`
|
||||||
|
- No local config changes.
|
||||||
|
|
||||||
|
## Config writes
|
||||||
|
|
||||||
|
Wizard writes:
|
||||||
|
- `~/.clawdis/clawdis.json`
|
||||||
|
- `agent.workspace`
|
||||||
|
- `agent.model` + `models.providers` (if Minimax selected)
|
||||||
|
- `skills.install.nodeManager` (npm | pnpm | bun)
|
||||||
|
- `skills.entries.<key>.env` / `.apiKey` (if set in skills step)
|
||||||
|
|
||||||
|
## Minimax M2.1 (LM Studio) config snippet
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
agent: {
|
||||||
|
model: "Minimax",
|
||||||
|
allowedModels: [
|
||||||
|
"anthropic/claude-opus-4-5",
|
||||||
|
"lmstudio/minimax-m2.1-gs32"
|
||||||
|
],
|
||||||
|
modelAliases: {
|
||||||
|
Opus: "anthropic/claude-opus-4-5",
|
||||||
|
Minimax: "lmstudio/minimax-m2.1-gs32"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
models: {
|
||||||
|
mode: "merge",
|
||||||
|
providers: {
|
||||||
|
lmstudio: {
|
||||||
|
baseUrl: "http://127.0.0.1:1234/v1",
|
||||||
|
apiKey: "lmstudio",
|
||||||
|
api: "openai-responses",
|
||||||
|
models: [
|
||||||
|
{
|
||||||
|
id: "minimax-m2.1-gs32",
|
||||||
|
name: "MiniMax M2.1 GS32",
|
||||||
|
reasoning: false,
|
||||||
|
input: ["text"],
|
||||||
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
|
contextWindow: 196608,
|
||||||
|
maxTokens: 8192
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Skills install preferences
|
||||||
|
|
||||||
|
Prompt for node manager:
|
||||||
|
- npm
|
||||||
|
- pnpm
|
||||||
|
- bun
|
||||||
|
|
||||||
|
Writes:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
skills: {
|
||||||
|
install: {
|
||||||
|
nodeManager: "npm" // npm | pnpm | bun
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reset scope (decision required)
|
||||||
|
|
||||||
|
Options:
|
||||||
|
- A) Config only (`~/.clawdis/clawdis.json`)
|
||||||
|
- B) Config + credentials + sessions
|
||||||
|
- C) Full reset: config + credentials + sessions + workspace
|
||||||
|
|
||||||
|
Wizard should clearly list what will be removed and use `trash`.
|
||||||
|
|
||||||
|
## Open questions
|
||||||
|
|
||||||
|
- Confirm “Remote = info-only” is final.
|
||||||
|
- Confirm reset scope default (A/B/C).
|
||||||
@@ -127,6 +127,11 @@
|
|||||||
"vitest": "^4.0.16",
|
"vitest": "^4.0.16",
|
||||||
"wireit": "^0.14.12"
|
"wireit": "^0.14.12"
|
||||||
},
|
},
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {
|
||||||
|
"@sinclair/typebox": "0.34.45"
|
||||||
|
}
|
||||||
|
},
|
||||||
"vitest": {
|
"vitest": {
|
||||||
"coverage": {
|
"coverage": {
|
||||||
"provider": "v8",
|
"provider": "v8",
|
||||||
|
|||||||
5
pnpm-lock.yaml
generated
5
pnpm-lock.yaml
generated
@@ -4,6 +4,9 @@ settings:
|
|||||||
autoInstallPeers: true
|
autoInstallPeers: true
|
||||||
excludeLinksFromLockfile: false
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
overrides:
|
||||||
|
'@sinclair/typebox': 0.34.45
|
||||||
|
|
||||||
patchedDependencies:
|
patchedDependencies:
|
||||||
'@mariozechner/pi-ai':
|
'@mariozechner/pi-ai':
|
||||||
hash: bf3e904ebaad236b8c3bb48c7d1150a1463735e783acaab6d15d6cd381b43832
|
hash: bf3e904ebaad236b8c3bb48c7d1150a1463735e783acaab6d15d6cd381b43832
|
||||||
@@ -29,7 +32,7 @@ importers:
|
|||||||
specifier: ^0.30.2
|
specifier: ^0.30.2
|
||||||
version: 0.30.2(ws@8.18.3)(zod@4.2.1)
|
version: 0.30.2(ws@8.18.3)(zod@4.2.1)
|
||||||
'@sinclair/typebox':
|
'@sinclair/typebox':
|
||||||
specifier: ^0.34.45
|
specifier: 0.34.45
|
||||||
version: 0.34.45
|
version: 0.34.45
|
||||||
'@whiskeysockets/baileys':
|
'@whiskeysockets/baileys':
|
||||||
specifier: 7.0.0-rc.9
|
specifier: 7.0.0-rc.9
|
||||||
|
|||||||
@@ -83,7 +83,10 @@ case "$TIMESTAMP_MODE" in
|
|||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
options_args=("--options" "runtime")
|
options_args=()
|
||||||
|
if [[ "$IDENTITY" != "-" ]]; then
|
||||||
|
options_args=("--options" "runtime")
|
||||||
|
fi
|
||||||
timestamp_args=("$timestamp_arg")
|
timestamp_args=("$timestamp_arg")
|
||||||
|
|
||||||
cat > "$ENT_TMP_BASE" <<'PLIST'
|
cat > "$ENT_TMP_BASE" <<'PLIST'
|
||||||
@@ -157,12 +160,12 @@ xattr -cr "$APP_BUNDLE" 2>/dev/null || true
|
|||||||
sign_item() {
|
sign_item() {
|
||||||
local target="$1"
|
local target="$1"
|
||||||
local entitlements="$2"
|
local entitlements="$2"
|
||||||
codesign --force "${options_args[@]}" "${timestamp_args[@]}" --entitlements "$entitlements" --sign "$IDENTITY" "$target"
|
codesign --force ${options_args+"${options_args[@]}"} "${timestamp_args[@]}" --entitlements "$entitlements" --sign "$IDENTITY" "$target"
|
||||||
}
|
}
|
||||||
|
|
||||||
sign_plain_item() {
|
sign_plain_item() {
|
||||||
local target="$1"
|
local target="$1"
|
||||||
codesign --force "${options_args[@]}" "${timestamp_args[@]}" --sign "$IDENTITY" "$target"
|
codesign --force ${options_args+"${options_args[@]}"} "${timestamp_args[@]}" --sign "$IDENTITY" "$target"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Sign main binary
|
# Sign main binary
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
|
import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
|
||||||
import { randomUUID } from "node:crypto";
|
import { randomUUID } from "node:crypto";
|
||||||
import type { AgentTool, AgentToolResult } from "@mariozechner/pi-ai";
|
import type { AgentTool, AgentToolResult } from "@mariozechner/pi-ai";
|
||||||
import { StringEnum } from "@mariozechner/pi-ai";
|
|
||||||
import { Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -31,6 +30,18 @@ const DEFAULT_MAX_OUTPUT = clampNumber(
|
|||||||
150_000,
|
150_000,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const stringEnum = (
|
||||||
|
values: readonly string[],
|
||||||
|
options?: Parameters<typeof Type.Union>[1],
|
||||||
|
) =>
|
||||||
|
Type.Union(
|
||||||
|
values.map((value) => Type.Literal(value)) as [
|
||||||
|
ReturnType<typeof Type.Literal>,
|
||||||
|
...ReturnType<typeof Type.Literal>[],
|
||||||
|
],
|
||||||
|
options,
|
||||||
|
);
|
||||||
|
|
||||||
export type BashToolDefaults = {
|
export type BashToolDefaults = {
|
||||||
backgroundMs?: number;
|
backgroundMs?: number;
|
||||||
timeoutSec?: number;
|
timeoutSec?: number;
|
||||||
@@ -60,7 +71,7 @@ const bashSchema = Type.Object({
|
|||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
stdinMode: Type.Optional(
|
stdinMode: Type.Optional(
|
||||||
StringEnum(["pipe", "pty"] as const, {
|
stringEnum(["pipe", "pty"] as const, {
|
||||||
description: "Only pipe is supported",
|
description: "Only pipe is supported",
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@@ -83,7 +94,8 @@ export type BashToolDetails =
|
|||||||
|
|
||||||
export function createBashTool(
|
export function createBashTool(
|
||||||
defaults?: BashToolDefaults,
|
defaults?: BashToolDefaults,
|
||||||
): AgentTool<typeof bashSchema, BashToolDetails> {
|
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-ai uses a different module instance.
|
||||||
|
): AgentTool<any, BashToolDetails> {
|
||||||
const defaultBackgroundMs = clampNumber(
|
const defaultBackgroundMs = clampNumber(
|
||||||
defaults?.backgroundMs ?? readEnvInt("PI_BASH_YIELD_MS"),
|
defaults?.backgroundMs ?? readEnvInt("PI_BASH_YIELD_MS"),
|
||||||
20_000,
|
20_000,
|
||||||
@@ -329,7 +341,7 @@ export function createBashTool(
|
|||||||
export const bashTool = createBashTool();
|
export const bashTool = createBashTool();
|
||||||
|
|
||||||
const processSchema = Type.Object({
|
const processSchema = Type.Object({
|
||||||
action: StringEnum(
|
action: stringEnum(
|
||||||
["list", "poll", "log", "write", "kill", "clear", "remove"] as const,
|
["list", "poll", "log", "write", "kill", "clear", "remove"] as const,
|
||||||
{
|
{
|
||||||
description: "Process action",
|
description: "Process action",
|
||||||
@@ -346,7 +358,8 @@ const processSchema = Type.Object({
|
|||||||
|
|
||||||
export function createProcessTool(
|
export function createProcessTool(
|
||||||
defaults?: ProcessToolDefaults,
|
defaults?: ProcessToolDefaults,
|
||||||
): AgentTool<typeof processSchema> {
|
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-ai uses a different module instance.
|
||||||
|
): AgentTool<any> {
|
||||||
if (defaults?.cleanupMs !== undefined) {
|
if (defaults?.cleanupMs !== undefined) {
|
||||||
setJobTtlMs(defaults.cleanupMs);
|
setJobTtlMs(defaults.cleanupMs);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import crypto from "node:crypto";
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
|
|
||||||
import type { AgentTool, AgentToolResult } from "@mariozechner/pi-ai";
|
import type { AgentTool, AgentToolResult } from "@mariozechner/pi-ai";
|
||||||
import { type TSchema, Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
import {
|
import {
|
||||||
browserCloseTab,
|
browserCloseTab,
|
||||||
browserFocusTab,
|
browserFocusTab,
|
||||||
@@ -45,7 +45,8 @@ import { callGateway } from "../gateway/call.js";
|
|||||||
import { detectMime } from "../media/mime.js";
|
import { detectMime } from "../media/mime.js";
|
||||||
import { sanitizeToolResultImages } from "./tool-images.js";
|
import { sanitizeToolResultImages } from "./tool-images.js";
|
||||||
|
|
||||||
type AnyAgentTool = AgentTool<TSchema, unknown>;
|
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-ai uses a different module instance.
|
||||||
|
type AnyAgentTool = AgentTool<any, unknown>;
|
||||||
|
|
||||||
const DEFAULT_GATEWAY_URL = "ws://127.0.0.1:18789";
|
const DEFAULT_GATEWAY_URL = "ws://127.0.0.1:18789";
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { AgentTool, AgentToolResult } from "@mariozechner/pi-ai";
|
import type { AgentTool, AgentToolResult } from "@mariozechner/pi-ai";
|
||||||
import { codingTools, readTool } from "@mariozechner/pi-coding-agent";
|
import { codingTools, readTool } from "@mariozechner/pi-coding-agent";
|
||||||
import { type TSchema, Type } from "@sinclair/typebox";
|
import { Type } from "@sinclair/typebox";
|
||||||
|
|
||||||
import { detectMime } from "../media/mime.js";
|
import { detectMime } from "../media/mime.js";
|
||||||
import { startWebLoginWithQr, waitForWebLogin } from "../web/login-qr.js";
|
import { startWebLoginWithQr, waitForWebLogin } from "../web/login-qr.js";
|
||||||
@@ -103,7 +103,8 @@ async function normalizeReadImageResult(
|
|||||||
return { ...result, content: nextContent };
|
return { ...result, content: nextContent };
|
||||||
}
|
}
|
||||||
|
|
||||||
type AnyAgentTool = AgentTool<TSchema, unknown>;
|
// biome-ignore lint/suspicious/noExplicitAny: TypeBox schema type from pi-ai uses a different module instance.
|
||||||
|
type AnyAgentTool = AgentTool<any, unknown>;
|
||||||
|
|
||||||
function extractEnumValues(schema: unknown): unknown[] | undefined {
|
function extractEnumValues(schema: unknown): unknown[] | undefined {
|
||||||
if (!schema || typeof schema !== "object") return undefined;
|
if (!schema || typeof schema !== "object") return undefined;
|
||||||
@@ -204,7 +205,7 @@ function normalizeToolParameters(tool: AnyAgentTool): AnyAgentTool {
|
|||||||
: {}),
|
: {}),
|
||||||
additionalProperties:
|
additionalProperties:
|
||||||
"additionalProperties" in schema ? schema.additionalProperties : true,
|
"additionalProperties" in schema ? schema.additionalProperties : true,
|
||||||
} as unknown as TSchema,
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ describe("getHealthSnapshot", () => {
|
|||||||
testConfig = { telegram: { tokenFile } };
|
testConfig = { telegram: { tokenFile } };
|
||||||
testStore = {};
|
testStore = {};
|
||||||
vi.stubEnv("TELEGRAM_BOT_TOKEN", "");
|
vi.stubEnv("TELEGRAM_BOT_TOKEN", "");
|
||||||
vi.stubEnv("DISCORD_BOT_TOKEN", "");
|
|
||||||
|
|
||||||
const calls: string[] = [];
|
const calls: string[] = [];
|
||||||
vi.stubGlobal(
|
vi.stubGlobal(
|
||||||
|
|||||||
Reference in New Issue
Block a user