claws-code 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/commands/claws-auto.md +90 -0
- package/.claude/commands/claws-bin.md +28 -0
- package/.claude/commands/claws-cleanup.md +28 -0
- package/.claude/commands/claws-do.md +82 -0
- package/.claude/commands/claws-fix.md +40 -0
- package/.claude/commands/claws-goal.md +111 -0
- package/.claude/commands/claws-help.md +54 -0
- package/.claude/commands/claws-plan.md +103 -0
- package/.claude/commands/claws-report.md +29 -0
- package/.claude/commands/claws-status.md +37 -0
- package/.claude/commands/claws-update.md +32 -0
- package/.claude/commands/claws.md +64 -0
- package/.claude/rules/claws-default-behavior.md +76 -0
- package/.claude/settings.json +112 -0
- package/.claude/settings.local.json +19 -0
- package/.claude/skills/claws-auto-engine/SKILL.md +97 -0
- package/.claude/skills/claws-goal-tracker/SKILL.md +106 -0
- package/.claude/skills/claws-prompt-templates/SKILL.md +203 -0
- package/.claude/skills/claws-wave-lead/SKILL.md +126 -0
- package/.claude/skills/claws-wave-subworker/SKILL.md +60 -0
- package/CHANGELOG.md +1949 -0
- package/LICENSE +21 -0
- package/README.md +420 -0
- package/bin/cli.js +84 -0
- package/cli.js +223 -0
- package/docs/ARCHITECTURE.md +511 -0
- package/docs/event-protocol.md +588 -0
- package/docs/features.md +562 -0
- package/docs/guide.md +891 -0
- package/docs/index.html +716 -0
- package/docs/protocol.md +323 -0
- package/extension/.vscodeignore +15 -0
- package/extension/CHANGELOG.md +1906 -0
- package/extension/LICENSE +21 -0
- package/extension/README.md +137 -0
- package/extension/docs/features.md +424 -0
- package/extension/docs/protocol.md +197 -0
- package/extension/esbuild.mjs +25 -0
- package/extension/icon.png +0 -0
- package/extension/native/.metadata.json +10 -0
- package/extension/native/node-pty/LICENSE +69 -0
- package/extension/native/node-pty/README.md +165 -0
- package/extension/native/node-pty/lib/conpty_console_list_agent.js +16 -0
- package/extension/native/node-pty/lib/conpty_console_list_agent.js.map +1 -0
- package/extension/native/node-pty/lib/eventEmitter2.js +47 -0
- package/extension/native/node-pty/lib/eventEmitter2.js.map +1 -0
- package/extension/native/node-pty/lib/index.js +52 -0
- package/extension/native/node-pty/lib/index.js.map +1 -0
- package/extension/native/node-pty/lib/interfaces.js +7 -0
- package/extension/native/node-pty/lib/interfaces.js.map +1 -0
- package/extension/native/node-pty/lib/shared/conout.js +11 -0
- package/extension/native/node-pty/lib/shared/conout.js.map +1 -0
- package/extension/native/node-pty/lib/terminal.js +190 -0
- package/extension/native/node-pty/lib/terminal.js.map +1 -0
- package/extension/native/node-pty/lib/types.js +7 -0
- package/extension/native/node-pty/lib/types.js.map +1 -0
- package/extension/native/node-pty/lib/unixTerminal.js +346 -0
- package/extension/native/node-pty/lib/unixTerminal.js.map +1 -0
- package/extension/native/node-pty/lib/utils.js +39 -0
- package/extension/native/node-pty/lib/utils.js.map +1 -0
- package/extension/native/node-pty/lib/windowsConoutConnection.js +125 -0
- package/extension/native/node-pty/lib/windowsConoutConnection.js.map +1 -0
- package/extension/native/node-pty/lib/windowsPtyAgent.js +320 -0
- package/extension/native/node-pty/lib/windowsPtyAgent.js.map +1 -0
- package/extension/native/node-pty/lib/windowsTerminal.js +199 -0
- package/extension/native/node-pty/lib/windowsTerminal.js.map +1 -0
- package/extension/native/node-pty/lib/worker/conoutSocketWorker.js +22 -0
- package/extension/native/node-pty/lib/worker/conoutSocketWorker.js.map +1 -0
- package/extension/native/node-pty/package.json +64 -0
- package/extension/native/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
- package/extension/native/node-pty/prebuilds/darwin-x64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty/OpenConsole.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty/conpty.dll +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/conpty_console_list.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/winpty-agent.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-arm64/winpty.dll +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty/OpenConsole.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty/conpty.dll +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/conpty_console_list.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/pty.node +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/winpty-agent.exe +0 -0
- package/extension/native/node-pty/prebuilds/win32-x64/winpty.dll +0 -0
- package/extension/package-lock.json +605 -0
- package/extension/package.json +343 -0
- package/extension/scripts/bundle-native.mjs +104 -0
- package/extension/scripts/deploy-dev.mjs +60 -0
- package/extension/src/ansi-strip.ts +52 -0
- package/extension/src/backends/vscode/claws-pty.ts +483 -0
- package/extension/src/backends/vscode/status-bar.ts +99 -0
- package/extension/src/backends/vscode/vscode-backend.ts +282 -0
- package/extension/src/capture-store.ts +125 -0
- package/extension/src/event-log.ts +629 -0
- package/extension/src/event-schemas.ts +478 -0
- package/extension/src/extension.js +492 -0
- package/extension/src/extension.ts +873 -0
- package/extension/src/lifecycle-engine.ts +60 -0
- package/extension/src/lifecycle-rules.ts +171 -0
- package/extension/src/lifecycle-store.ts +506 -0
- package/extension/src/peer-registry.ts +176 -0
- package/extension/src/pipeline-registry.ts +82 -0
- package/extension/src/platform.ts +64 -0
- package/extension/src/protocol.ts +532 -0
- package/extension/src/server-config.ts +98 -0
- package/extension/src/server.ts +2210 -0
- package/extension/src/task-registry.ts +51 -0
- package/extension/src/terminal-backend.ts +211 -0
- package/extension/src/terminal-manager.ts +395 -0
- package/extension/src/topic-registry.ts +70 -0
- package/extension/src/topic-utils.ts +46 -0
- package/extension/src/transport.ts +45 -0
- package/extension/src/uninstall-cleanup.ts +232 -0
- package/extension/src/wave-registry.ts +314 -0
- package/extension/src/websocket-transport.ts +153 -0
- package/extension/tsconfig.json +23 -0
- package/lib/capabilities.js +145 -0
- package/lib/dry-run.js +43 -0
- package/lib/install.js +1018 -0
- package/lib/mcp-setup.js +92 -0
- package/lib/platform.js +240 -0
- package/lib/preflight.js +152 -0
- package/lib/shell-hook.js +343 -0
- package/lib/uninstall.js +162 -0
- package/lib/verify.js +166 -0
- package/mcp_server.js +3529 -0
- package/package.json +48 -0
- package/rules/claws-default-behavior.md +72 -0
- package/scripts/_helpers/atomic-file.mjs +137 -0
- package/scripts/_helpers/fix-repair.js +64 -0
- package/scripts/_helpers/json-safe.mjs +218 -0
- package/scripts/bump-version.sh +84 -0
- package/scripts/codegen/gen-docs.mjs +61 -0
- package/scripts/codegen/gen-json-schema.mjs +62 -0
- package/scripts/codegen/gen-mcp-tools.mjs +358 -0
- package/scripts/codegen/gen-types.mjs +172 -0
- package/scripts/codegen/index.mjs +42 -0
- package/scripts/dev-hooks/check-extension-dirs.js +77 -0
- package/scripts/dev-hooks/check-open-claws-terminals.js +70 -0
- package/scripts/dev-hooks/check-stale-main.js +55 -0
- package/scripts/dev-hooks/check-tag-pushed.js +51 -0
- package/scripts/dev-hooks/check-tag-vs-main.js +56 -0
- package/scripts/dev-vsix-install.sh +60 -0
- package/scripts/fix.sh +702 -0
- package/scripts/gen-client-types.mjs +81 -0
- package/scripts/git-hooks/pre-commit +31 -0
- package/scripts/hooks/lifecycle-state.js +61 -0
- package/scripts/hooks/package.json +4 -0
- package/scripts/hooks/post-tool-use-claws.js +292 -0
- package/scripts/hooks/pre-bash-no-verify-block.js +72 -0
- package/scripts/hooks/pre-tool-use-claws.js +206 -0
- package/scripts/hooks/session-start-claws.js +97 -0
- package/scripts/hooks/stop-claws.js +88 -0
- package/scripts/inject-claude-md.js +205 -0
- package/scripts/inject-dev-hooks.js +96 -0
- package/scripts/inject-global-claude-md.js +140 -0
- package/scripts/inject-settings-hooks.js +370 -0
- package/scripts/install.ps1 +146 -0
- package/scripts/install.sh +1729 -0
- package/scripts/monitor-arm-watch.js +155 -0
- package/scripts/rebuild-node-pty.sh +245 -0
- package/scripts/report.sh +232 -0
- package/scripts/shell-hook.fish +164 -0
- package/scripts/shell-hook.ps1 +33 -0
- package/scripts/shell-hook.sh +232 -0
- package/scripts/stream-events.js +399 -0
- package/scripts/terminal-wrapper.sh +36 -0
- package/scripts/test-enforcement.sh +132 -0
- package/scripts/test-install.sh +174 -0
- package/scripts/test-installer-parity.sh +135 -0
- package/scripts/test-template-enforcement.sh +76 -0
- package/scripts/uninstall.sh +143 -0
- package/scripts/update.sh +337 -0
- package/scripts/verify-release.sh +323 -0
- package/scripts/verify-wrapped.sh +194 -0
- package/templates/CLAUDE.global.md +135 -0
- package/templates/CLAUDE.project.md +37 -0
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claws-code",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Claws — Terminal Control Bridge for VS Code. One command to install.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"claws-code": "./bin/cli.js",
|
|
7
|
+
"claws": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"lib/",
|
|
12
|
+
"mcp_server.js",
|
|
13
|
+
"cli.js",
|
|
14
|
+
"scripts/",
|
|
15
|
+
".claude/",
|
|
16
|
+
"templates/",
|
|
17
|
+
"rules/",
|
|
18
|
+
"docs/",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"CHANGELOG.md",
|
|
22
|
+
"extension/"
|
|
23
|
+
],
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/neunaha/claws"
|
|
27
|
+
},
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"keywords": [
|
|
32
|
+
"claude-code",
|
|
33
|
+
"terminal",
|
|
34
|
+
"orchestration",
|
|
35
|
+
"ai",
|
|
36
|
+
"vscode",
|
|
37
|
+
"mcp"
|
|
38
|
+
],
|
|
39
|
+
"author": "Anish Neunaha",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"scripts": {
|
|
42
|
+
"prepublishOnly": "chmod +x bin/cli.js",
|
|
43
|
+
"test:mcp": "node tests/mcp/worker.test.js",
|
|
44
|
+
"test:installer": "node --test test/installer-platform.test.js",
|
|
45
|
+
"test:installer-full": "node --test test/installer.test.js",
|
|
46
|
+
"test:json-safe": "node --test test/json-safe.test.mjs"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
> **Scope note**: This file is loaded by the ECC plugin's `SessionStart` hook as a system-reminder.
|
|
2
|
+
> It is NOT auto-loaded by stock Claude Code. The canonical imperative rules that every Claude
|
|
3
|
+
> session sees are in the `CLAWS:BEGIN` block injected into `CLAUDE.md` by `scripts/inject-claude-md.js`.
|
|
4
|
+
> This file is a supplement for ECC users — do not rely on it as the sole enforcement layer.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Claws — Terminal Behavior Override
|
|
9
|
+
|
|
10
|
+
> Auto-installed by Claws. This rule OVERRIDES default terminal behavior.
|
|
11
|
+
|
|
12
|
+
## MANDATORY: when the user invokes any /claws command, you MUST use Claws terminals
|
|
13
|
+
|
|
14
|
+
When the user types `/claws-do`, `/claws-go`, or any `/claws-*` command, you are REQUIRED to create visible terminals via the Claws socket. Do NOT fall back to the Bash tool. Do NOT say "this isn't a Claws task." EVERYTHING asked through a /claws command IS a Claws task.
|
|
15
|
+
|
|
16
|
+
**If MCP tools (claws_list, claws_create, etc.) are available**: use them directly.
|
|
17
|
+
|
|
18
|
+
**If MCP tools are NOT loaded** (new session, MCP not registered): use raw socket calls via `node -e` with `require('net').createConnection('.claws/claws.sock')`. The socket commands are documented in every /claws-* slash command file.
|
|
19
|
+
|
|
20
|
+
**If the socket doesn't exist**: tell the user to reload VS Code (Cmd+Shift+P → Developer: Reload Window).
|
|
21
|
+
|
|
22
|
+
## For all other work (no /claws prefix), use this behavior:
|
|
23
|
+
|
|
24
|
+
**DO**: run visible work in Claws terminals — builds, tests, deploys, servers, AI workers, refactoring, audits. The user watches it happen live in their VS Code terminal panel.
|
|
25
|
+
|
|
26
|
+
**DO**: use `claws_worker` when the user asks to "fix", "refactor", "audit", or any complex task. This spawns a Claude Code session with full permissions that the user can watch.
|
|
27
|
+
|
|
28
|
+
**DO**: spawn parallel terminals for independent tasks. "lint + test + build" = 3 terminals, all running simultaneously.
|
|
29
|
+
|
|
30
|
+
**DO**: always create wrapped terminals (`wrapped=true`). Always close them when done.
|
|
31
|
+
|
|
32
|
+
**USE BASH DIRECTLY FOR**: quick lookups — `ls`, `cat`, `git status`, `grep`, file reads. No terminal needed.
|
|
33
|
+
|
|
34
|
+
## Pattern matching — what the user says → what you do
|
|
35
|
+
|
|
36
|
+
"run tests" / "run the build" / "lint the code"
|
|
37
|
+
→ `claws_create` wrapped → `claws_send` the command → `claws_read_log` → report → `claws_close`
|
|
38
|
+
|
|
39
|
+
"run lint, test, and build" / "do these in parallel"
|
|
40
|
+
→ 3x `claws_create` → send all 3 commands → monitor all 3 → close all → aggregate report
|
|
41
|
+
|
|
42
|
+
"fix the bug" / "refactor this" / "audit the code" / "write tests for X"
|
|
43
|
+
→ `claws_worker` with the mission → monitor via `claws_read_log` → report → close
|
|
44
|
+
|
|
45
|
+
"start the server" / "watch for crashes"
|
|
46
|
+
→ `claws_create` wrapped → send start command → monitor → restart if crash detected
|
|
47
|
+
|
|
48
|
+
"spawn a worker" / "launch claude in a terminal"
|
|
49
|
+
→ `claws_worker` — auto-launches Claude Code with `--dangerously-skip-permissions`
|
|
50
|
+
|
|
51
|
+
"what's running" / "show me the terminals"
|
|
52
|
+
→ `claws_list` → format as dashboard
|
|
53
|
+
|
|
54
|
+
"clean up" / "close the workers"
|
|
55
|
+
→ `claws_list` → close all worker terminals → confirm
|
|
56
|
+
|
|
57
|
+
## Terminal naming
|
|
58
|
+
|
|
59
|
+
Always descriptive: `worker-tests`, `worker-lint`, `build-server`, `ai-refactor`, `deploy-staging`.
|
|
60
|
+
Never generic: `terminal`, `shell`, `worker-1`.
|
|
61
|
+
|
|
62
|
+
## Cleanup is non-negotiable
|
|
63
|
+
|
|
64
|
+
Every `claws_create` must have a matching `claws_close`. At the end of any orchestration, run `claws_list` and close anything you own that's still open. Stale terminals are a bug.
|
|
65
|
+
|
|
66
|
+
## Never headless
|
|
67
|
+
|
|
68
|
+
When spawning Claude Code in a worker terminal:
|
|
69
|
+
- ALWAYS: `claude --dangerously-skip-permissions` (interactive, visible TUI)
|
|
70
|
+
- NEVER: `claude -p "..."` (headless, invisible)
|
|
71
|
+
|
|
72
|
+
The user must see every AI worker in their terminal panel.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// Rename-pattern atomic file and directory operations.
|
|
2
|
+
// Used by install.sh (M-09 hooks-copy atomicity, M-01 dotfile backup)
|
|
3
|
+
// and json-safe.mjs (mergeIntoFile).
|
|
4
|
+
// Self-contained — no imports from other _helpers/ modules in L0.
|
|
5
|
+
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
|
|
9
|
+
let _nonce = 0;
|
|
10
|
+
function tmpSuffix() {
|
|
11
|
+
return `${process.pid}-${++_nonce}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Write content to filePath atomically via a tmp → rename pattern.
|
|
16
|
+
* Writes to ${filePath}.claws-tmp.${pid}, fsyncs, then renames over the target.
|
|
17
|
+
* On POSIX the rename is atomic; on Windows it is best-effort (no atomic rename API).
|
|
18
|
+
*
|
|
19
|
+
* @param {string} filePath
|
|
20
|
+
* @param {string | Buffer} content
|
|
21
|
+
* @param {{ mode?: number }} [opts] mode defaults to 0o644
|
|
22
|
+
*/
|
|
23
|
+
export async function writeAtomic(filePath, content, opts = {}) {
|
|
24
|
+
const mode = opts.mode ?? 0o644;
|
|
25
|
+
const tmp = `${filePath}.claws-tmp.${tmpSuffix()}`;
|
|
26
|
+
|
|
27
|
+
let fd;
|
|
28
|
+
try {
|
|
29
|
+
await fs.promises.mkdir(path.dirname(path.resolve(filePath)), { recursive: true });
|
|
30
|
+
fd = await fs.promises.open(tmp, 'w', mode);
|
|
31
|
+
await fd.writeFile(content);
|
|
32
|
+
await fd.sync();
|
|
33
|
+
await fd.close();
|
|
34
|
+
fd = null;
|
|
35
|
+
await fs.promises.rename(tmp, filePath);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (fd) {
|
|
38
|
+
try { await fd.close(); } catch { /* ignore */ }
|
|
39
|
+
fd = null;
|
|
40
|
+
}
|
|
41
|
+
try { await fs.promises.unlink(tmp); } catch { /* best-effort cleanup */ }
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Copy srcDir to destDir atomically via:
|
|
48
|
+
* 1. Copy srcDir → destDir.claws-tmp.${pid}
|
|
49
|
+
* 2. Move existing destDir → destDir.claws-old.${ts}
|
|
50
|
+
* 3. Rename tmp → destDir
|
|
51
|
+
* 4. Remove the moved-aside old dir
|
|
52
|
+
*
|
|
53
|
+
* On failure before step 3, destDir is left untouched (tmp is cleaned up).
|
|
54
|
+
*
|
|
55
|
+
* @param {string} srcDir
|
|
56
|
+
* @param {string} destDir
|
|
57
|
+
*/
|
|
58
|
+
export async function copyDirAtomic(srcDir, destDir) {
|
|
59
|
+
const tmp = `${destDir}.claws-tmp.${tmpSuffix()}`;
|
|
60
|
+
const ts = Date.now();
|
|
61
|
+
|
|
62
|
+
// Step 1: copy into tmp
|
|
63
|
+
try {
|
|
64
|
+
await fs.promises.rm(tmp, { recursive: true, force: true });
|
|
65
|
+
await copyDirRecursive(srcDir, tmp);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
// Clean up partial tmp — destDir untouched
|
|
68
|
+
try { await fs.promises.rm(tmp, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
69
|
+
throw err;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Step 2+3: swap atomically — move old aside, rename tmp into place
|
|
73
|
+
const old = `${destDir}.claws-old.${ts}`;
|
|
74
|
+
let destExisted = false;
|
|
75
|
+
try {
|
|
76
|
+
await fs.promises.access(destDir);
|
|
77
|
+
destExisted = true;
|
|
78
|
+
} catch { /* doesn't exist */ }
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
if (destExisted) {
|
|
82
|
+
await fs.promises.rename(destDir, old);
|
|
83
|
+
}
|
|
84
|
+
await fs.promises.rename(tmp, destDir);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
// Roll back: restore old dir if we moved it, remove tmp
|
|
87
|
+
if (destExisted) {
|
|
88
|
+
try {
|
|
89
|
+
await fs.promises.access(destDir);
|
|
90
|
+
} catch {
|
|
91
|
+
// destDir is gone — put old back
|
|
92
|
+
try { await fs.promises.rename(old, destDir); } catch { /* ignore */ }
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
try { await fs.promises.rm(tmp, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Step 4: remove the moved-aside old dir (best-effort)
|
|
100
|
+
if (destExisted) {
|
|
101
|
+
try { await fs.promises.rm(old, { recursive: true, force: true }); } catch { /* ignore */ }
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Create a timestamped backup of filePath.
|
|
107
|
+
* Backup path: ${filePath}.claws-bak.${ISO-timestamp}[.suffix]
|
|
108
|
+
* Returns the backup path.
|
|
109
|
+
*
|
|
110
|
+
* @param {string} filePath
|
|
111
|
+
* @param {string} [suffix] optional extra suffix appended after the timestamp
|
|
112
|
+
* @returns {Promise<string>}
|
|
113
|
+
* @throws {Error} If filePath does not exist (ENOENT) or is not readable.
|
|
114
|
+
* Layer 2 callers (M-01 dotfile backup) must guard against missing files with
|
|
115
|
+
* a prior fs.access() check or try/catch — backupFile does not silently skip.
|
|
116
|
+
*/
|
|
117
|
+
export async function backupFile(filePath, suffix) {
|
|
118
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
119
|
+
const backupPath = `${filePath}.claws-bak.${ts}${suffix ? '.' + suffix : ''}`;
|
|
120
|
+
await fs.promises.copyFile(filePath, backupPath);
|
|
121
|
+
return backupPath;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ─── internal ────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
async function copyDirRecursive(src, dest) {
|
|
127
|
+
await fs.promises.mkdir(dest, { recursive: true });
|
|
128
|
+
const entries = await fs.promises.readdir(src, { withFileTypes: true });
|
|
129
|
+
await Promise.all(entries.map(entry => {
|
|
130
|
+
const srcPath = path.join(src, entry.name);
|
|
131
|
+
const destPath = path.join(dest, entry.name);
|
|
132
|
+
if (entry.isDirectory()) {
|
|
133
|
+
return copyDirRecursive(srcPath, destPath);
|
|
134
|
+
}
|
|
135
|
+
return fs.promises.copyFile(srcPath, destPath);
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// M-45/M-46: safe repair helper for fix.sh — replaces inline node -e repair
|
|
3
|
+
// scripts that used JSON.parse (silent-reset on malformed, M-02) + writeFileSync
|
|
4
|
+
// (non-atomic, M-30) + embedded paths (injection risk, M-20).
|
|
5
|
+
//
|
|
6
|
+
// Uses json-safe.mjs: abort-on-malformed, atomic write, JSONC-tolerant.
|
|
7
|
+
// Paths passed via env vars — no string interpolation into JS source.
|
|
8
|
+
//
|
|
9
|
+
// Usage (called by fix.sh):
|
|
10
|
+
// CLAWS_REPAIR_TARGET=<path> node fix-repair.js mcp
|
|
11
|
+
// CLAWS_REPAIR_TARGET=<path> node fix-repair.js extensions
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { pathToFileURL } = require('url');
|
|
16
|
+
|
|
17
|
+
const HELPERS_URL = pathToFileURL(path.resolve(__dirname, 'json-safe.mjs')).href;
|
|
18
|
+
|
|
19
|
+
(async () => {
|
|
20
|
+
const { mergeIntoFile } = await import(HELPERS_URL);
|
|
21
|
+
const op = process.argv[2];
|
|
22
|
+
const target = process.env.CLAWS_REPAIR_TARGET;
|
|
23
|
+
|
|
24
|
+
if (!op || !target) {
|
|
25
|
+
console.error('[fix-repair] Usage: CLAWS_REPAIR_TARGET=<path> node fix-repair.js <mcp|extensions>');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let result;
|
|
30
|
+
|
|
31
|
+
if (op === 'mcp') {
|
|
32
|
+
result = await mergeIntoFile(target, (cfg) => {
|
|
33
|
+
if (!cfg.mcpServers) cfg.mcpServers = {};
|
|
34
|
+
cfg.mcpServers.claws = {
|
|
35
|
+
command: 'node',
|
|
36
|
+
args: ['./.claws-bin/mcp_server.js'],
|
|
37
|
+
env: { CLAWS_SOCKET: '.claws/claws.sock' },
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
} else if (op === 'extensions') {
|
|
41
|
+
result = await mergeIntoFile(target, (cfg) => {
|
|
42
|
+
if (!Array.isArray(cfg.recommendations)) cfg.recommendations = [];
|
|
43
|
+
if (!cfg.recommendations.includes('neunaha.claws')) {
|
|
44
|
+
cfg.recommendations.push('neunaha.claws');
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
console.error('[fix-repair] Unknown operation:', op);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!result.ok) {
|
|
53
|
+
console.error(`[fix-repair] ${op} repair failed: ${result.error.message}`);
|
|
54
|
+
if (result.error.backupSavedAt) {
|
|
55
|
+
console.error(' Malformed original backed up to:', result.error.backupSavedAt);
|
|
56
|
+
console.error(' File left unchanged — manual intervention required.');
|
|
57
|
+
}
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
console.log(`[fix-repair] ${op === 'mcp' ? '.mcp.json' : 'extensions.json'} repaired (atomic write)`);
|
|
61
|
+
})().catch(e => {
|
|
62
|
+
console.error('[fix-repair] unexpected error:', e.message);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
});
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// JSONC-tolerant JSON parsing + abort-on-error file merge.
|
|
2
|
+
// Used by install.sh (M-02 .mcp.json) and inject-settings-hooks.js (M-03 settings.json).
|
|
3
|
+
// Both helpers in _helpers/ are self-contained — no cross-imports in L0.
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
export class JsonSafeError extends Error {
|
|
9
|
+
constructor(message, code) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'JsonSafeError';
|
|
12
|
+
this.code = code;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Strip JSONC extensions: // line comments, /* block comments */, and trailing commas.
|
|
17
|
+
// Handles strings correctly — comment sequences inside quoted values are preserved.
|
|
18
|
+
function stripJsonc(input) {
|
|
19
|
+
let result = '';
|
|
20
|
+
let i = 0;
|
|
21
|
+
const len = input.length;
|
|
22
|
+
|
|
23
|
+
while (i < len) {
|
|
24
|
+
const ch = input[i];
|
|
25
|
+
|
|
26
|
+
if (ch === '"') {
|
|
27
|
+
// Consume a JSON string verbatim (including escape sequences).
|
|
28
|
+
result += ch;
|
|
29
|
+
i++;
|
|
30
|
+
while (i < len) {
|
|
31
|
+
const sc = input[i];
|
|
32
|
+
result += sc;
|
|
33
|
+
if (sc === '\\') {
|
|
34
|
+
i++;
|
|
35
|
+
if (i < len) { result += input[i]; i++; }
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (sc === '"') { i++; break; }
|
|
39
|
+
i++;
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (ch === '/' && i + 1 < len && input[i + 1] === '*') {
|
|
45
|
+
// Block comment — skip everything until closing */, preserving newlines
|
|
46
|
+
// so that line/col error positions remain accurate.
|
|
47
|
+
i += 2;
|
|
48
|
+
while (i + 1 < len && !(input[i] === '*' && input[i + 1] === '/')) {
|
|
49
|
+
if (input[i] === '\n') result += '\n';
|
|
50
|
+
i++;
|
|
51
|
+
}
|
|
52
|
+
i += 2; // consume closing */
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (ch === '/' && i + 1 < len && input[i + 1] === '/') {
|
|
57
|
+
// Line comment — skip to end of line, preserve the newline itself.
|
|
58
|
+
i += 2;
|
|
59
|
+
while (i < len && input[i] !== '\n') i++;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
result += ch;
|
|
64
|
+
i++;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Remove trailing commas before ] or } (handles whitespace/newlines between).
|
|
68
|
+
return result.replace(/,(\s*[}\]])/g, '$1');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Compute 1-based line/col from a character position within a string.
|
|
72
|
+
function posToLineCol(str, pos) {
|
|
73
|
+
const before = str.slice(0, pos);
|
|
74
|
+
const lines = before.split('\n');
|
|
75
|
+
return { line: lines.length, col: lines[lines.length - 1].length + 1 };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Parse JSON (optionally JSONC) without throwing.
|
|
80
|
+
* @param {string} input
|
|
81
|
+
* @param {{ allowJsonc?: boolean }} [opts] allowJsonc defaults to true
|
|
82
|
+
* @returns {{ ok: true, data: unknown } | { ok: false, error: { code: string, message: string, line?: number, col?: number, original: string } }}
|
|
83
|
+
*/
|
|
84
|
+
export function parseJsonSafe(input, opts = {}) {
|
|
85
|
+
if (input == null || typeof input !== 'string') {
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: {
|
|
89
|
+
code: 'PARSE_ERROR',
|
|
90
|
+
message: `Expected string, got ${input === null ? 'null' : typeof input}`,
|
|
91
|
+
original: String(input),
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Strip UTF-8 BOM (U+FEFF) that Windows editors sometimes write at file start.
|
|
96
|
+
const normalized = input.charCodeAt(0) === 0xFEFF ? input.slice(1) : input;
|
|
97
|
+
const allowJsonc = opts.allowJsonc !== false;
|
|
98
|
+
const source = allowJsonc ? stripJsonc(normalized) : normalized;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
return { ok: true, data: JSON.parse(source) };
|
|
102
|
+
} catch (e) {
|
|
103
|
+
let line, col;
|
|
104
|
+
// Node's SyntaxError messages include "position N" for the error offset.
|
|
105
|
+
const m = e.message.match(/position (\d+)/);
|
|
106
|
+
if (m) {
|
|
107
|
+
const loc = posToLineCol(source, parseInt(m[1], 10));
|
|
108
|
+
line = loc.line;
|
|
109
|
+
col = loc.col;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
ok: false,
|
|
113
|
+
error: {
|
|
114
|
+
code: 'PARSE_ERROR',
|
|
115
|
+
message: e.message,
|
|
116
|
+
...(line != null && { line, col }),
|
|
117
|
+
original: input,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Per-call nonce for unique tmp filenames across concurrent mergeIntoFile calls (F1).
|
|
124
|
+
let _mergeNonce = 0;
|
|
125
|
+
|
|
126
|
+
// Minimal inline atomic write (rename pattern) — avoids importing atomic-file.mjs
|
|
127
|
+
// so each L0 helper stays self-contained. Layer 2/3 wiring may replace this.
|
|
128
|
+
// Uses pid+nonce for tmp uniqueness (F1) and fsyncs before rename for durability (F2).
|
|
129
|
+
async function writeAtomicInline(filePath, content) {
|
|
130
|
+
const tmp = `${filePath}.claws-tmp.${process.pid}-${++_mergeNonce}`;
|
|
131
|
+
let fd;
|
|
132
|
+
try {
|
|
133
|
+
fd = await fs.promises.open(tmp, 'w', 0o644);
|
|
134
|
+
await fd.writeFile(content);
|
|
135
|
+
await fd.sync();
|
|
136
|
+
await fd.close();
|
|
137
|
+
fd = null;
|
|
138
|
+
await fs.promises.rename(tmp, filePath);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
if (fd) {
|
|
141
|
+
try { await fd.close(); } catch { /* ignore */ }
|
|
142
|
+
fd = null;
|
|
143
|
+
}
|
|
144
|
+
try { await fs.promises.unlink(tmp); } catch { /* best-effort cleanup */ }
|
|
145
|
+
throw err;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Read filePath, parse JSONC, apply mutator, write back atomically.
|
|
151
|
+
* On parse error: saves a timestamped backup and returns ok:false WITHOUT
|
|
152
|
+
* touching the original file (critical: never silently reset to {}).
|
|
153
|
+
*
|
|
154
|
+
* @param {string} filePath
|
|
155
|
+
* @param {(cfg: object) => object | void} mutator return new obj or mutate in place
|
|
156
|
+
* @param {{ allowJsonc?: boolean }} [opts]
|
|
157
|
+
* @returns {Promise<{ ok: true, written: boolean } | { ok: false, error: object }>}
|
|
158
|
+
*/
|
|
159
|
+
export async function mergeIntoFile(filePath, mutator, opts = {}) {
|
|
160
|
+
const allowJsonc = opts.allowJsonc !== false;
|
|
161
|
+
|
|
162
|
+
// Read — absent file is treated as empty object, not an error.
|
|
163
|
+
let raw = '{}';
|
|
164
|
+
try {
|
|
165
|
+
raw = await fs.promises.readFile(filePath, 'utf8');
|
|
166
|
+
} catch (e) {
|
|
167
|
+
if (e.code !== 'ENOENT') {
|
|
168
|
+
return { ok: false, error: { code: 'READ_ERROR', message: e.message } };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const parsed = parseJsonSafe(raw, { allowJsonc });
|
|
173
|
+
|
|
174
|
+
if (!parsed.ok) {
|
|
175
|
+
// Backup the malformed original BEFORE returning — never overwrite it.
|
|
176
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
177
|
+
const backupPath = `${filePath}.claws-bak.${ts}`;
|
|
178
|
+
try {
|
|
179
|
+
await fs.promises.writeFile(backupPath, raw, 'utf8');
|
|
180
|
+
} catch (backupErr) {
|
|
181
|
+
return {
|
|
182
|
+
ok: false,
|
|
183
|
+
error: {
|
|
184
|
+
code: 'PARSE_FAILED',
|
|
185
|
+
message: parsed.error.message,
|
|
186
|
+
backupSavedAt: null,
|
|
187
|
+
backupError: backupErr.message,
|
|
188
|
+
parseError: parsed.error,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
ok: false,
|
|
194
|
+
error: {
|
|
195
|
+
code: 'PARSE_FAILED',
|
|
196
|
+
message: parsed.error.message,
|
|
197
|
+
backupSavedAt: backupPath,
|
|
198
|
+
parseError: parsed.error,
|
|
199
|
+
},
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let cfg = parsed.data;
|
|
204
|
+
const mutatorResult = mutator(cfg);
|
|
205
|
+
if (mutatorResult !== undefined && mutatorResult !== null) {
|
|
206
|
+
cfg = mutatorResult;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const content = JSON.stringify(cfg, null, 2) + '\n';
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
await fs.promises.mkdir(path.dirname(path.resolve(filePath)), { recursive: true });
|
|
213
|
+
await writeAtomicInline(filePath, content);
|
|
214
|
+
return { ok: true, written: true };
|
|
215
|
+
} catch (e) {
|
|
216
|
+
return { ok: false, error: { code: 'WRITE_ERROR', message: e.message } };
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# scripts/bump-version.sh — single source of truth for version bumps.
|
|
4
|
+
#
|
|
5
|
+
# Updates every place where the version is hard-coded:
|
|
6
|
+
# - package.json (root)
|
|
7
|
+
# - extension/package.json
|
|
8
|
+
# - extension/package-lock.json (root + nested "" package)
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# bash scripts/bump-version.sh 0.7.10
|
|
12
|
+
#
|
|
13
|
+
# After running, sync the extension CHANGELOG and refresh .claws-bin if needed:
|
|
14
|
+
# cp CHANGELOG.md extension/CHANGELOG.md
|
|
15
|
+
# cp mcp_server.js .claws-bin/mcp_server.js
|
|
16
|
+
#
|
|
17
|
+
# This script is the only blessed way to change the version. Manual edits to
|
|
18
|
+
# any one file invite drift — and version drift on this codebase has caused
|
|
19
|
+
# real shipping bugs (v0.7.7.1 semver invalidation, v0.7.5 stale lockfile, ...).
|
|
20
|
+
#
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
|
|
23
|
+
if [ $# -ne 1 ]; then
|
|
24
|
+
echo "usage: bash scripts/bump-version.sh <new-version>" >&2
|
|
25
|
+
echo " e.g. bash scripts/bump-version.sh 0.7.10" >&2
|
|
26
|
+
exit 64
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
NEW="$1"
|
|
30
|
+
# SemVer 2.0 strict: MAJOR.MINOR.PATCH only. No fourth segment (the v0.7.7.1 trap).
|
|
31
|
+
if ! echo "$NEW" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
|
|
32
|
+
echo "ERROR: '$NEW' is not a valid SemVer 2.0 version (MAJOR.MINOR.PATCH only)." >&2
|
|
33
|
+
echo " VS Code rejects four-segment versions (this broke v0.7.7.1)." >&2
|
|
34
|
+
exit 65
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# Find the project root — script lives in scripts/ which lives at repo root.
|
|
38
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
|
|
39
|
+
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
|
|
40
|
+
|
|
41
|
+
cd "$ROOT"
|
|
42
|
+
|
|
43
|
+
# Capture old version from root package.json so the report is meaningful.
|
|
44
|
+
OLD="$(node -p "require('./package.json').version")"
|
|
45
|
+
|
|
46
|
+
if [ "$OLD" = "$NEW" ]; then
|
|
47
|
+
echo "version is already $NEW — nothing to do."
|
|
48
|
+
exit 0
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
echo "bumping $OLD → $NEW across all source files…"
|
|
52
|
+
|
|
53
|
+
# Update each file via Node so the JSON stays well-formed (no sed regex edge cases).
|
|
54
|
+
node --no-deprecation -e "
|
|
55
|
+
const fs=require('fs');
|
|
56
|
+
const targets=[
|
|
57
|
+
'package.json',
|
|
58
|
+
'extension/package.json',
|
|
59
|
+
'extension/package-lock.json',
|
|
60
|
+
];
|
|
61
|
+
for (const f of targets) {
|
|
62
|
+
const j=JSON.parse(fs.readFileSync(f,'utf8'));
|
|
63
|
+
if (j.version) j.version='$NEW';
|
|
64
|
+
// package-lock has a nested top-level package entry that ALSO carries a
|
|
65
|
+
// version field — keep it in sync so 'npm install' doesn't re-write a stale
|
|
66
|
+
// value back in.
|
|
67
|
+
if (j.packages && j.packages['']) j.packages[''].version='$NEW';
|
|
68
|
+
fs.writeFileSync(f, JSON.stringify(j,null,2)+'\n');
|
|
69
|
+
console.log(' ✓', f);
|
|
70
|
+
}
|
|
71
|
+
"
|
|
72
|
+
|
|
73
|
+
echo ""
|
|
74
|
+
echo "verify (all 3 should be $NEW):"
|
|
75
|
+
echo " root $(node -p "require('./package.json').version")"
|
|
76
|
+
echo " extension $(node -p "require('./extension/package.json').version")"
|
|
77
|
+
echo " extension lock $(node -p "require('./extension/package-lock.json').version")"
|
|
78
|
+
echo ""
|
|
79
|
+
echo "next steps:"
|
|
80
|
+
echo " 1. Update CHANGELOG.md with [$NEW] section"
|
|
81
|
+
echo " 2. Sync extension CHANGELOG: cp CHANGELOG.md extension/CHANGELOG.md"
|
|
82
|
+
echo " 3. Refresh runtime: cp mcp_server.js .claws-bin/mcp_server.js"
|
|
83
|
+
echo " 4. Run tests: cd extension && npm test"
|
|
84
|
+
echo " 5. Commit + tag: git tag -a v$NEW -m 'v$NEW'"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// gen-docs.mjs — Regenerate the schema reference table in docs/event-protocol.md.
|
|
2
|
+
// Called by index.mjs. Default export is the generator function.
|
|
3
|
+
// Replaces content between <!-- BEGIN GENERATED SCHEMAS --> and
|
|
4
|
+
// <!-- END GENERATED SCHEMAS --> markers. If markers are absent, warns and
|
|
5
|
+
// exits without modifying the file.
|
|
6
|
+
|
|
7
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
|
|
10
|
+
const BEGIN_MARKER = '<!-- BEGIN GENERATED SCHEMAS -->';
|
|
11
|
+
const END_MARKER = '<!-- END GENERATED SCHEMAS -->';
|
|
12
|
+
|
|
13
|
+
const TOPIC_TABLE = `\
|
|
14
|
+
_This section is auto-generated by \`npm run schemas\` in \`extension/\`. Run to update._
|
|
15
|
+
|
|
16
|
+
| Topic Pattern | Schema Name | Key Required Fields |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| \`worker.*.boot\` | \`worker-boot-v1\` | model, role, mission_summary, cwd, terminal_id |
|
|
19
|
+
| \`worker.*.phase\` | \`worker-phase-v1\` | phase, prev, transition_reason, phases_completed |
|
|
20
|
+
| \`worker.*.event\` | \`worker-event-v1\` | kind, severity, message |
|
|
21
|
+
| \`worker.*.heartbeat\` | \`worker-heartbeat-v1\` | current_phase, time_in_phase_ms, tokens_used, cost_usd |
|
|
22
|
+
| \`worker.*.complete\` | \`worker-complete-v1\` | result, summary, artifacts, phases_completed |
|
|
23
|
+
| \`cmd.*.approve\` | \`cmd-approve-v1\` | correlation_id |
|
|
24
|
+
| \`cmd.*.reject\` | \`cmd-reject-v1\` | correlation_id, reason |
|
|
25
|
+
| \`cmd.*.abort\` | \`cmd-abort-v1\` | reason |
|
|
26
|
+
| \`cmd.*.pause\` | \`cmd-pause-v1\` | _(none)_ |
|
|
27
|
+
| \`cmd.*.resume\` | \`cmd-resume-v1\` | _(none)_ |
|
|
28
|
+
| \`cmd.*.set_phase\` | \`cmd-set-phase-v1\` | phase, reason |
|
|
29
|
+
| \`cmd.*.spawn\` | \`cmd-spawn-v1\` | name, mission |
|
|
30
|
+
| \`cmd.*.inject_text\` | \`cmd-inject-text-v1\` | text |
|
|
31
|
+
| \`system.peer.joined\` | \`system-peer-joined-v1\` | peerId, role, peerName, ts |
|
|
32
|
+
| \`system.peer.left\` | \`system-peer-left-v1\` | peerId, role, reason |
|
|
33
|
+
| \`system.peer.stale\` | \`system-peer-stale-v1\` | peerId, last_seen, missed_heartbeats |
|
|
34
|
+
| \`system.gate.fired\` | \`system-gate-fired-v1\` | tool, reason, peerId |
|
|
35
|
+
| \`system.budget.warning\` | \`system-budget-warning-v1\` | current_usd, threshold_usd |
|
|
36
|
+
| \`system.malformed.received\` | \`system-malformed-received-v1\` | from, topic, error |`;
|
|
37
|
+
|
|
38
|
+
export default async function genDocs(repoRoot) {
|
|
39
|
+
const docPath = join(repoRoot, 'docs', 'event-protocol.md');
|
|
40
|
+
let content;
|
|
41
|
+
try {
|
|
42
|
+
content = readFileSync(docPath, 'utf8');
|
|
43
|
+
} catch {
|
|
44
|
+
process.stderr.write(`[codegen/gen-docs] WARNING: docs/event-protocol.md not found — skipping\n`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const beginIdx = content.indexOf(BEGIN_MARKER);
|
|
49
|
+
const endIdx = content.indexOf(END_MARKER);
|
|
50
|
+
|
|
51
|
+
if (beginIdx === -1 || endIdx === -1) {
|
|
52
|
+
process.stderr.write('[codegen/gen-docs] WARNING: markers not found in docs/event-protocol.md — skipping\n');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const before = content.slice(0, beginIdx + BEGIN_MARKER.length);
|
|
57
|
+
const after = content.slice(endIdx);
|
|
58
|
+
const updated = `${before}\n${TOPIC_TABLE}\n${after}`;
|
|
59
|
+
writeFileSync(docPath, updated, 'utf8');
|
|
60
|
+
console.log('[codegen/gen-docs] updated docs/event-protocol.md schema reference table');
|
|
61
|
+
}
|