va-claw 0.1.2 → 0.1.4

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.
Files changed (57) hide show
  1. package/README.md +134 -0
  2. package/README.zh-CN.md +478 -0
  3. package/dist/va-claw-bundle.mjs +561 -64
  4. package/docs/comic.svg +264 -0
  5. package/docs/releases/v0.1.3.md +20 -0
  6. package/index.html +269 -8
  7. package/package.json +1 -1
  8. package/packages/cli/dist/.tsbuildinfo +1 -1
  9. package/packages/cli/dist/claw-store.d.ts +38 -0
  10. package/packages/cli/dist/claw-store.d.ts.map +1 -0
  11. package/packages/cli/dist/claw-store.js +139 -0
  12. package/packages/cli/dist/claw-store.js.map +1 -0
  13. package/packages/cli/dist/deps.d.ts.map +1 -1
  14. package/packages/cli/dist/deps.js +2 -1
  15. package/packages/cli/dist/deps.js.map +1 -1
  16. package/packages/cli/dist/handlers.d.ts +20 -0
  17. package/packages/cli/dist/handlers.d.ts.map +1 -1
  18. package/packages/cli/dist/handlers.js +148 -1
  19. package/packages/cli/dist/handlers.js.map +1 -1
  20. package/packages/cli/dist/index.d.ts +2 -2
  21. package/packages/cli/dist/index.d.ts.map +1 -1
  22. package/packages/cli/dist/index.js +2 -2
  23. package/packages/cli/dist/index.js.map +1 -1
  24. package/packages/cli/dist/install-files.d.ts +1 -0
  25. package/packages/cli/dist/install-files.d.ts.map +1 -1
  26. package/packages/cli/dist/install-files.js +3 -0
  27. package/packages/cli/dist/install-files.js.map +1 -1
  28. package/packages/cli/dist/output.d.ts +2 -0
  29. package/packages/cli/dist/output.d.ts.map +1 -1
  30. package/packages/cli/dist/output.js +21 -0
  31. package/packages/cli/dist/output.js.map +1 -1
  32. package/packages/cli/dist/program.d.ts.map +1 -1
  33. package/packages/cli/dist/program.js +40 -1
  34. package/packages/cli/dist/program.js.map +1 -1
  35. package/packages/cli/dist/test-helpers.d.ts.map +1 -1
  36. package/packages/cli/dist/test-helpers.js +1 -0
  37. package/packages/cli/dist/test-helpers.js.map +1 -1
  38. package/packages/cli/dist/types.d.ts +1 -0
  39. package/packages/cli/dist/types.d.ts.map +1 -1
  40. package/packages/daemon/dist/.tsbuildinfo +1 -1
  41. package/packages/daemon/dist/wake-cycle.d.ts +19 -2
  42. package/packages/daemon/dist/wake-cycle.d.ts.map +1 -1
  43. package/packages/daemon/dist/wake-cycle.js +209 -30
  44. package/packages/daemon/dist/wake-cycle.js.map +1 -1
  45. package/packages/identity/dist/.tsbuildinfo +1 -1
  46. package/packages/identity/dist/defaults.d.ts +1 -0
  47. package/packages/identity/dist/defaults.d.ts.map +1 -1
  48. package/packages/identity/dist/defaults.js +6 -0
  49. package/packages/identity/dist/defaults.js.map +1 -1
  50. package/packages/identity/dist/render.d.ts.map +1 -1
  51. package/packages/identity/dist/render.js +15 -0
  52. package/packages/identity/dist/render.js.map +1 -1
  53. package/packages/identity/dist/types.d.ts +1 -0
  54. package/packages/identity/dist/types.d.ts.map +1 -1
  55. package/skills/claw-fleet-protocol.md +58 -0
  56. package/skills/install-va-claw.md +4 -0
  57. package/skills/migrate-to-va-claw.md +141 -0
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // packages/cli/dist/deps.js
4
- import { spawnSync as spawnSync4 } from "node:child_process";
4
+ import { spawnSync as spawnSync3 } from "node:child_process";
5
5
  import { readFile as readFile4 } from "node:fs/promises";
6
6
  import { createInterface as createInterface2 } from "node:readline/promises";
7
7
 
@@ -28,11 +28,13 @@ import { dirname } from "node:path";
28
28
 
29
29
  // packages/identity/dist/defaults.js
30
30
  var DEFAULT_LOOP_INTERVAL = "0 * * * *";
31
+ var DEFAULT_WAKE_TIMEOUT_MS = 3e5;
31
32
  var DEFAULT_CONFIG = {
32
33
  name: "va-claw",
33
34
  persona: "A pragmatic CLI identity that values clarity, rigor, and continuity.",
34
35
  systemPrompt: "You are va-claw. Be direct, honest about uncertainty, and keep actions aligned with the saved identity.",
35
36
  wakePrompt: "Wake up, load the saved identity, and continue from the most recent remembered state.",
37
+ wakeTimeoutMs: DEFAULT_WAKE_TIMEOUT_MS,
36
38
  loopInterval: DEFAULT_LOOP_INTERVAL,
37
39
  channels: {
38
40
  discord: {
@@ -58,6 +60,9 @@ function pickString(value, fallback) {
58
60
  function pickBoolean(value, fallback) {
59
61
  return typeof value === "boolean" ? value : fallback;
60
62
  }
63
+ function pickNumber(value, fallback) {
64
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : fallback;
65
+ }
61
66
  function getDefaultIdentity() {
62
67
  return { ...DEFAULT_CONFIG };
63
68
  }
@@ -73,6 +78,7 @@ function normalizeConfig(input) {
73
78
  persona: pickString(Reflect.get(data, "persona"), base.persona),
74
79
  systemPrompt: pickString(Reflect.get(data, "systemPrompt"), base.systemPrompt),
75
80
  wakePrompt: pickString(Reflect.get(data, "wakePrompt"), base.wakePrompt),
81
+ wakeTimeoutMs: pickNumber(Reflect.get(data, "wakeTimeoutMs"), base.wakeTimeoutMs ?? DEFAULT_WAKE_TIMEOUT_MS),
76
82
  loopInterval: pickString(Reflect.get(data, "loopInterval"), base.loopInterval),
77
83
  channels: {
78
84
  discord: {
@@ -136,6 +142,13 @@ function toClaudeMdSnippet(config) {
136
142
  normalized.persona,
137
143
  "",
138
144
  textBlock("System Prompt:", normalized.systemPrompt),
145
+ "",
146
+ "Operational protocol:",
147
+ "- Check long-running claw status with `va-claw protocol`.",
148
+ "- For readable text use `va-claw protocol --text`.",
149
+ "- Manage claws with `va-claw claw list | add | set | remove | heartbeat`.",
150
+ '- If asked "va/claw \u5728\u5E72\u4EC0\u4E48" / "my claws are doing what" / similar fleet questions, call `va-claw protocol --text` and answer from it.',
151
+ '- If the user asks a non-technical phrasing like "\u73B0\u5728\u90FD\u5728\u5E72\u561B", interpret it as a fleet status request and run `va-claw protocol --text`.',
139
152
  "<!-- va-claw:identity:end -->"
140
153
  ].join("\n");
141
154
  }
@@ -151,7 +164,15 @@ function toCodexSystemPrompt(config) {
151
164
  "Wake prompt:",
152
165
  normalized.wakePrompt,
153
166
  "",
154
- `Loop interval: ${normalized.loopInterval}`
167
+ `Wake timeout (ms): ${normalized.wakeTimeoutMs ?? 3e5}`,
168
+ "",
169
+ `Loop interval: ${normalized.loopInterval}`,
170
+ "",
171
+ "Operational protocol:",
172
+ "Use `va-claw protocol` in terminal to get long-running claw state.",
173
+ 'Use `va-claw protocol --text` when a user asks in plain language, including Chinese phrases like "va/claw \u5728\u5E72\u4EC0\u4E48".',
174
+ "Use `va-claw claw list` and `va-claw claw set <name> --status <status>` to manage claws.",
175
+ "If the user asks what claws are doing, run `va-claw protocol --text` and summarize only from that output."
155
176
  ].join("\n");
156
177
  }
157
178
 
@@ -924,7 +945,10 @@ var E = class {
924
945
  };
925
946
 
926
947
  // packages/daemon/dist/wake-cycle.js
927
- import { spawnSync as spawnSync2 } from "node:child_process";
948
+ import { spawn } from "node:child_process";
949
+ import { appendFile, mkdir as mkdir3 } from "node:fs/promises";
950
+ import { homedir as homedir4 } from "node:os";
951
+ import { dirname as dirname3, resolve as resolve3 } from "node:path";
928
952
 
929
953
  // packages/memory/dist/default-store.js
930
954
  import { homedir as homedir2 } from "node:os";
@@ -1861,46 +1885,211 @@ function looksLikePath(value) {
1861
1885
 
1862
1886
  // packages/daemon/dist/wake-cycle.js
1863
1887
  var DEFAULT_WARN2 = (message) => console.warn(message);
1888
+ var DEFAULT_WAKE_TIMEOUT_MS2 = 3e5;
1889
+ var OUTPUT_TAIL_LIMIT = 2e3;
1890
+ var OUTPUT_BUFFER_LIMIT = 10 * 1024 * 1024;
1891
+ var wakeRunning = false;
1864
1892
  async function runWakeCycle(config, deps = {}) {
1865
- const adapter = await (deps.detect ?? detectCliAdapter)({ warn: deps.warn });
1866
- if (!adapter) {
1893
+ if (wakeRunning) {
1894
+ (deps.warn ?? DEFAULT_WARN2)("[va-claw/daemon] skipping wake: previous wake still running");
1867
1895
  return null;
1868
1896
  }
1869
- const prompt = await resolveWakePrompt(config, deps);
1870
- const spawnOptions = {
1871
- cwd: process.cwd(),
1872
- encoding: "utf8",
1873
- env: {
1874
- ...readProcessEnv(),
1875
- CLAUDECODE: void 0,
1876
- CLAUDE_CODE_SESSION: void 0
1877
- },
1878
- maxBuffer: 10 * 1024 * 1024
1879
- };
1880
- const result = (deps.spawn ?? spawnSync2)(adapter.command, [...adapter.args, prompt], spawnOptions);
1881
- if (result.status !== 0) {
1882
- (deps.warn ?? DEFAULT_WARN2)(`[va-claw/daemon] ${adapter.name} wake failed: ${readStderr(result.stderr)}`);
1897
+ wakeRunning = true;
1898
+ try {
1899
+ return await _runWakeCycleInner(config, deps);
1900
+ } finally {
1901
+ wakeRunning = false;
1902
+ }
1903
+ }
1904
+ async function _runWakeCycleInner(config, deps = {}) {
1905
+ const now = deps.now ?? (() => /* @__PURE__ */ new Date());
1906
+ const startedAt = now();
1907
+ let combinedOutput = "";
1908
+ try {
1909
+ const adapter = await (deps.detect ?? detectCliAdapter)({ warn: deps.warn });
1910
+ if (!adapter) {
1911
+ return null;
1912
+ }
1913
+ const prompt = await resolveWakePrompt(config, deps);
1914
+ const timeoutMs = resolveWakeTimeoutMs(config);
1915
+ const wakeArgs = [...adapter.args, prompt];
1916
+ const wakeOptions = {
1917
+ cwd: process.cwd(),
1918
+ env: {
1919
+ ...readProcessEnv(),
1920
+ CLAUDECODE: void 0,
1921
+ CLAUDE_CODE_SESSION: void 0
1922
+ }
1923
+ };
1924
+ const result = await (deps.executeWake ?? executeWakeProcess)(adapter.command, wakeArgs, wakeOptions, timeoutMs);
1925
+ combinedOutput = result.combinedOutput;
1926
+ const finishedAt = now();
1927
+ const durationMs = finishedAt.getTime() - startedAt.getTime();
1928
+ if (result.exitCode !== 0) {
1929
+ await writeWakeLogSafe({
1930
+ ts: startedAt.toISOString(),
1931
+ duration_ms: Math.max(0, durationMs),
1932
+ exit_code: result.exitCode,
1933
+ output_tail: tailOutput(result.combinedOutput)
1934
+ }, deps);
1935
+ (deps.warn ?? DEFAULT_WARN2)(result.exitCode === "timeout" ? `[va-claw/daemon] ${adapter.name} wake timed out after ${timeoutMs}ms.` : `[va-claw/daemon] ${adapter.name} wake failed: ${readFailureOutput(result.stderr, result.combinedOutput)}`);
1936
+ return null;
1937
+ }
1938
+ await (deps.storeMemory ?? store)(result.stdout, {
1939
+ source: "va-claw-daemon",
1940
+ kind: "wake",
1941
+ cli: adapter.name,
1942
+ identity: config.name,
1943
+ wokeAt: finishedAt.toISOString()
1944
+ });
1945
+ await writeWakeLogSafe({
1946
+ ts: startedAt.toISOString(),
1947
+ duration_ms: Math.max(0, durationMs),
1948
+ exit_code: 0,
1949
+ output_tail: tailOutput(result.combinedOutput)
1950
+ }, deps);
1951
+ return finishedAt;
1952
+ } catch (error) {
1953
+ const failedAt = now();
1954
+ await writeWakeLogSafe({
1955
+ ts: startedAt.toISOString(),
1956
+ duration_ms: Math.max(0, failedAt.getTime() - startedAt.getTime()),
1957
+ exit_code: "crash",
1958
+ output_tail: tailOutput(combinedOutput)
1959
+ }, deps);
1960
+ (deps.warn ?? DEFAULT_WARN2)(`[va-claw/daemon] wake crashed: ${String(error)}`);
1883
1961
  return null;
1884
1962
  }
1885
- const wokeAt = (deps.now ?? (() => /* @__PURE__ */ new Date()))();
1886
- await (deps.storeMemory ?? store)(result.stdout ?? "", {
1887
- source: "va-claw-daemon",
1888
- kind: "wake",
1889
- cli: adapter.name,
1890
- identity: config.name,
1891
- wokeAt: wokeAt.toISOString()
1892
- });
1893
- return wokeAt;
1894
1963
  }
1895
- function readStderr(stderr) {
1896
- if (typeof stderr === "string" && stderr.trim() !== "") {
1897
- return stderr.trim();
1964
+ async function executeWakeProcess(command, args, options, timeoutMs) {
1965
+ let child;
1966
+ try {
1967
+ child = spawn(command, args, {
1968
+ cwd: options.cwd,
1969
+ env: options.env,
1970
+ stdio: ["ignore", "pipe", "pipe"]
1971
+ });
1972
+ } catch (error) {
1973
+ return {
1974
+ combinedOutput: String(error),
1975
+ exitCode: "spawn_error",
1976
+ stderr: String(error),
1977
+ stdout: ""
1978
+ };
1898
1979
  }
1899
- return "unknown error";
1980
+ let stdout2 = "";
1981
+ let stderr = "";
1982
+ let combinedOutput = "";
1983
+ let settled = false;
1984
+ let combinedSize = 0;
1985
+ let stdoutSize = 0;
1986
+ let stderrSize = 0;
1987
+ child.stdout.on("data", (chunk) => {
1988
+ const text2 = String(chunk);
1989
+ if (stdoutSize < OUTPUT_BUFFER_LIMIT) {
1990
+ stdout2 += text2;
1991
+ stdoutSize += text2.length;
1992
+ }
1993
+ if (combinedSize < OUTPUT_BUFFER_LIMIT) {
1994
+ combinedOutput += text2;
1995
+ combinedSize += text2.length;
1996
+ }
1997
+ });
1998
+ child.stderr.on("data", (chunk) => {
1999
+ const text2 = String(chunk);
2000
+ if (stderrSize < OUTPUT_BUFFER_LIMIT) {
2001
+ stderr += text2;
2002
+ stderrSize += text2.length;
2003
+ }
2004
+ if (combinedSize < OUTPUT_BUFFER_LIMIT) {
2005
+ combinedOutput += text2;
2006
+ combinedSize += text2.length;
2007
+ }
2008
+ });
2009
+ return await new Promise((resolve5) => {
2010
+ const timer = setTimeout(() => {
2011
+ if (settled) {
2012
+ return;
2013
+ }
2014
+ settled = true;
2015
+ clearTimeout(timer);
2016
+ child.kill("SIGTERM");
2017
+ const killTimer = setTimeout(() => {
2018
+ try {
2019
+ child.kill("SIGKILL");
2020
+ } catch {
2021
+ }
2022
+ }, 5e3);
2023
+ const timeoutResult = {
2024
+ combinedOutput,
2025
+ exitCode: "timeout",
2026
+ stderr,
2027
+ stdout: stdout2
2028
+ };
2029
+ child.once("close", () => {
2030
+ clearTimeout(killTimer);
2031
+ resolve5(timeoutResult);
2032
+ });
2033
+ }, timeoutMs);
2034
+ child.on("error", (error) => {
2035
+ if (settled) {
2036
+ return;
2037
+ }
2038
+ settled = true;
2039
+ clearTimeout(timer);
2040
+ const message = String(error);
2041
+ stderr = stderr === "" ? message : `${stderr}
2042
+ ${message}`;
2043
+ combinedOutput += message;
2044
+ resolve5({
2045
+ combinedOutput,
2046
+ exitCode: "spawn_error",
2047
+ stderr,
2048
+ stdout: stdout2
2049
+ });
2050
+ });
2051
+ child.on("close", (code) => {
2052
+ if (settled) {
2053
+ return;
2054
+ }
2055
+ settled = true;
2056
+ clearTimeout(timer);
2057
+ resolve5({
2058
+ combinedOutput,
2059
+ exitCode: typeof code === "number" ? code : 1,
2060
+ stderr,
2061
+ stdout: stdout2
2062
+ });
2063
+ });
2064
+ });
1900
2065
  }
1901
2066
  function readProcessEnv() {
1902
2067
  return process.env ?? {};
1903
2068
  }
2069
+ function readFailureOutput(stderr, combinedOutput) {
2070
+ const candidate = stderr.trim() !== "" ? stderr : combinedOutput;
2071
+ const text2 = candidate.trim();
2072
+ return text2 === "" ? "unknown error" : tailOutput(text2, 200);
2073
+ }
2074
+ function resolveWakeTimeoutMs(config) {
2075
+ return typeof config.wakeTimeoutMs === "number" && Number.isFinite(config.wakeTimeoutMs) && config.wakeTimeoutMs > 0 ? config.wakeTimeoutMs : DEFAULT_WAKE_TIMEOUT_MS2;
2076
+ }
2077
+ function tailOutput(text2, limit = OUTPUT_TAIL_LIMIT) {
2078
+ return text2.length <= limit ? text2 : text2.slice(-limit);
2079
+ }
2080
+ async function writeWakeLogSafe(entry, deps) {
2081
+ try {
2082
+ await (deps.writeWakeLog ?? writeWakeLog)(entry);
2083
+ } catch (error) {
2084
+ (deps.warn ?? DEFAULT_WARN2)(`[va-claw/daemon] failed to write wake.log: ${String(error)}`);
2085
+ }
2086
+ }
2087
+ async function writeWakeLog(entry) {
2088
+ const logPath = resolve3(homedir4(), ".va-claw", "wake.log");
2089
+ await mkdir3(dirname3(logPath), { recursive: true });
2090
+ await appendFile(logPath, `${JSON.stringify(entry)}
2091
+ `, "utf8");
2092
+ }
1904
2093
  async function resolveWakePrompt(config, deps) {
1905
2094
  try {
1906
2095
  const skills = await (deps.listSkills ?? listSkills)();
@@ -1970,29 +2159,29 @@ async function getDaemonStatus() {
1970
2159
  }
1971
2160
 
1972
2161
  // packages/daemon/dist/service.js
1973
- import { mkdir as mkdir3, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
1974
- import { dirname as dirname4 } from "node:path";
1975
- import { spawnSync as spawnSync3 } from "node:child_process";
2162
+ import { mkdir as mkdir4, rm as rm2, writeFile as writeFile3 } from "node:fs/promises";
2163
+ import { dirname as dirname5 } from "node:path";
2164
+ import { spawnSync as spawnSync2 } from "node:child_process";
1976
2165
 
1977
2166
  // packages/daemon/dist/service-files.js
1978
- import { homedir as homedir4 } from "node:os";
1979
- import { dirname as dirname3, join as join4, resolve as resolve3 } from "node:path";
2167
+ import { homedir as homedir5 } from "node:os";
2168
+ import { dirname as dirname4, join as join4, resolve as resolve4 } from "node:path";
1980
2169
  import { fileURLToPath } from "node:url";
1981
2170
  function createServiceDefinition(type) {
1982
- const packageRoot = resolve3(dirname3(fileURLToPath(import.meta.url)), "..");
1983
- const repoRoot = resolve3(packageRoot, "../..");
2171
+ const packageRoot = resolve4(dirname4(fileURLToPath(import.meta.url)), "..");
2172
+ const repoRoot = resolve4(packageRoot, "../..");
1984
2173
  const runnerPath = join4(packageRoot, "dist", "runner.js");
1985
2174
  const nodePath = process.execPath;
1986
2175
  if (type === "launchd") {
1987
2176
  return {
1988
- path: join4(homedir4(), "Library", "LaunchAgents", "com.va-claw.daemon.plist"),
2177
+ path: join4(homedir5(), "Library", "LaunchAgents", "com.va-claw.daemon.plist"),
1989
2178
  content: renderLaunchdPlist(repoRoot, [nodePath, runnerPath]),
1990
2179
  command: "launchctl",
1991
- args: ["load", join4(homedir4(), "Library", "LaunchAgents", "com.va-claw.daemon.plist")]
2180
+ args: ["load", join4(homedir5(), "Library", "LaunchAgents", "com.va-claw.daemon.plist")]
1992
2181
  };
1993
2182
  }
1994
2183
  return {
1995
- path: join4(homedir4(), ".config", "systemd", "user", "va-claw.service"),
2184
+ path: join4(homedir5(), ".config", "systemd", "user", "va-claw.service"),
1996
2185
  content: renderSystemdUnit(repoRoot, [nodePath, runnerPath]),
1997
2186
  command: "systemctl",
1998
2187
  args: ["--user", "enable", "--now", "va-claw.service"]
@@ -2002,7 +2191,7 @@ function createUninstallCommand(type) {
2002
2191
  if (type === "launchd") {
2003
2192
  return {
2004
2193
  command: "launchctl",
2005
- args: ["unload", join4(homedir4(), "Library", "LaunchAgents", "com.va-claw.daemon.plist")]
2194
+ args: ["unload", join4(homedir5(), "Library", "LaunchAgents", "com.va-claw.daemon.plist")]
2006
2195
  };
2007
2196
  }
2008
2197
  return {
@@ -2057,7 +2246,7 @@ function quoteSystemdArg(value) {
2057
2246
  // packages/daemon/dist/service.js
2058
2247
  async function installDaemonService(type) {
2059
2248
  const definition = createServiceDefinition(type);
2060
- await mkdir3(dirname4(definition.path), { recursive: true });
2249
+ await mkdir4(dirname5(definition.path), { recursive: true });
2061
2250
  await writeFile3(definition.path, definition.content, "utf8");
2062
2251
  if (type === "systemd") {
2063
2252
  runCommand("systemctl", ["--user", "daemon-reload"]);
@@ -2074,7 +2263,7 @@ async function uninstallDaemonService(type) {
2074
2263
  }
2075
2264
  }
2076
2265
  function runCommand(command, args, allowFailure = false) {
2077
- const result = spawnSync3(command, args, { encoding: "utf8" });
2266
+ const result = spawnSync2(command, args, { encoding: "utf8" });
2078
2267
  if (allowFailure || result.status === 0) {
2079
2268
  return;
2080
2269
  }
@@ -2083,9 +2272,9 @@ function runCommand(command, args, allowFailure = false) {
2083
2272
  }
2084
2273
 
2085
2274
  // packages/cli/dist/install-files.js
2086
- import { access as access2, mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4 } from "node:fs/promises";
2087
- import { homedir as homedir5 } from "node:os";
2088
- import { dirname as dirname5, join as join5 } from "node:path";
2275
+ import { access as access2, mkdir as mkdir5, readFile as readFile3, writeFile as writeFile4 } from "node:fs/promises";
2276
+ import { homedir as homedir6 } from "node:os";
2277
+ import { dirname as dirname6, join as join5 } from "node:path";
2089
2278
  var CLAUDE_MARKERS = {
2090
2279
  start: "<!-- va-claw:identity:start -->",
2091
2280
  end: "<!-- va-claw:identity:end -->"
@@ -2094,15 +2283,18 @@ var CODEX_MARKERS = {
2094
2283
  start: "<!-- va-claw:codex:start -->",
2095
2284
  end: "<!-- va-claw:codex:end -->"
2096
2285
  };
2097
- function resolveClaudeMdPath(home = homedir5()) {
2286
+ function resolveClaudeMdPath(home = homedir6()) {
2098
2287
  return join5(home, ".claude", "CLAUDE.md");
2099
2288
  }
2100
- function resolveCodexInstructionsPath(home = homedir5()) {
2289
+ function resolveCodexInstructionsPath(home = homedir6()) {
2101
2290
  return join5(home, ".codex", "instructions.md");
2102
2291
  }
2103
- function resolveMemoryDbPath(home = homedir5()) {
2292
+ function resolveMemoryDbPath(home = homedir6()) {
2104
2293
  return join5(home, ".va-claw", "memory.db");
2105
2294
  }
2295
+ function resolveClawRegistryPath(home = homedir6()) {
2296
+ return join5(home, ".va-claw", "claws.json");
2297
+ }
2106
2298
  async function fileExists(path) {
2107
2299
  try {
2108
2300
  await access2(path);
@@ -2114,7 +2306,7 @@ async function fileExists(path) {
2114
2306
  async function upsertManagedBlock(path, block, markers) {
2115
2307
  const current = (await readOptionalFile(path)).trim();
2116
2308
  const next = appendManagedBlock(current, block, markers);
2117
- await mkdir4(dirname5(path), { recursive: true });
2309
+ await mkdir5(dirname6(path), { recursive: true });
2118
2310
  await writeFile4(path, next, "utf8");
2119
2311
  }
2120
2312
  async function removeManagedBlock(path, markers) {
@@ -2123,7 +2315,7 @@ async function removeManagedBlock(path, markers) {
2123
2315
  return;
2124
2316
  }
2125
2317
  const next = stripManagedBlock(current, markers).trim();
2126
- await mkdir4(dirname5(path), { recursive: true });
2318
+ await mkdir5(dirname6(path), { recursive: true });
2127
2319
  await writeFile4(path, next === "" ? "" : `${next}
2128
2320
  `, "utf8");
2129
2321
  }
@@ -2165,8 +2357,9 @@ function createDefaultCliDeps() {
2165
2357
  codexPath: resolveCodexInstructionsPath(),
2166
2358
  configPath: DEFAULT_CONFIG_PATH,
2167
2359
  memoryDbPath: resolveMemoryDbPath(),
2360
+ clawRegistryPath: resolveClawRegistryPath(),
2168
2361
  platform: process.platform,
2169
- spawnSync: spawnSync4,
2362
+ spawnSync: spawnSync3,
2170
2363
  stdout: process.stdout,
2171
2364
  stderr: process.stderr,
2172
2365
  fileExists,
@@ -2269,6 +2462,25 @@ function formatSkills(skills) {
2269
2462
  ` path: ${skill.path}`
2270
2463
  ].join("\n")).join("\n\n");
2271
2464
  }
2465
+ function formatClawDefinitions(claws) {
2466
+ if (claws.length === 0) {
2467
+ return "No claws registered.";
2468
+ }
2469
+ return claws.map((claw) => {
2470
+ const lines = [
2471
+ `${claw.name} [${claw.status}]`,
2472
+ ` goal: ${claw.goal || "(no goal)"}`,
2473
+ ` cli: ${claw.cliCommand || "va-claw"}`,
2474
+ ` tags: ${claw.tags.join(", ") || "(none)"}`,
2475
+ ` note: ${claw.note || "(none)"}`,
2476
+ ` updatedAt: ${claw.updatedAt}`
2477
+ ];
2478
+ if (claw.lastSeenAt) {
2479
+ lines.push(` lastSeenAt: ${claw.lastSeenAt}`);
2480
+ }
2481
+ return lines.join("\n");
2482
+ }).join("\n\n");
2483
+ }
2272
2484
  function writeLine(stream, message) {
2273
2485
  stream.write(`${message}
2274
2486
  `);
@@ -2276,7 +2488,7 @@ function writeLine(stream, message) {
2276
2488
 
2277
2489
  // packages/cli/dist/wait.js
2278
2490
  async function waitForStopSignal(stop) {
2279
- await new Promise((resolve4, reject) => {
2491
+ await new Promise((resolve5, reject) => {
2280
2492
  let stopping = false;
2281
2493
  const cleanup = () => {
2282
2494
  process.off("SIGINT", onSignal);
@@ -2289,7 +2501,7 @@ async function waitForStopSignal(stop) {
2289
2501
  stopping = true;
2290
2502
  void stop().then(() => {
2291
2503
  cleanup();
2292
- resolve4();
2504
+ resolve5();
2293
2505
  }, (error) => {
2294
2506
  cleanup();
2295
2507
  reject(error);
@@ -2420,7 +2632,7 @@ function parseMetadata2(raw) {
2420
2632
  }
2421
2633
 
2422
2634
  // packages/cli/dist/platform.js
2423
- import { homedir as homedir6 } from "node:os";
2635
+ import { homedir as homedir7 } from "node:os";
2424
2636
  import { join as join6 } from "node:path";
2425
2637
  function detectServiceType(platform) {
2426
2638
  if (platform === "darwin") {
@@ -2431,27 +2643,162 @@ function detectServiceType(platform) {
2431
2643
  }
2432
2644
  throw new Error(`Unsupported platform for va-claw daemon service: ${platform}`);
2433
2645
  }
2434
- function probeServiceRunning(type, spawnSync5) {
2646
+ function probeServiceRunning(type, spawnSync4) {
2435
2647
  if (type === "launchd") {
2436
- return spawnSync5("launchctl", ["list", "com.va-claw.daemon"], { encoding: "utf8" }).status === 0;
2648
+ return spawnSync4("launchctl", ["list", "com.va-claw.daemon"], { encoding: "utf8" }).status === 0;
2437
2649
  }
2438
- const result = spawnSync5("systemctl", ["--user", "is-active", "va-claw.service"], {
2650
+ const result = spawnSync4("systemctl", ["--user", "is-active", "va-claw.service"], {
2439
2651
  encoding: "utf8"
2440
2652
  });
2441
2653
  return result.status === 0 && result.stdout.trim() === "active";
2442
2654
  }
2443
- function stopInstalledService(type, spawnSync5) {
2655
+ function stopInstalledService(type, spawnSync4) {
2444
2656
  if (type === "launchd") {
2445
- spawnSync5("launchctl", ["unload", resolveLaunchdPath()], { encoding: "utf8" });
2657
+ spawnSync4("launchctl", ["unload", resolveLaunchdPath()], { encoding: "utf8" });
2446
2658
  return;
2447
2659
  }
2448
- spawnSync5("systemctl", ["--user", "stop", "va-claw.service"], { encoding: "utf8" });
2660
+ spawnSync4("systemctl", ["--user", "stop", "va-claw.service"], { encoding: "utf8" });
2449
2661
  }
2450
2662
  function resolveLaunchdPath() {
2451
- return join6(homedir6(), "Library", "LaunchAgents", "com.va-claw.daemon.plist");
2663
+ return join6(homedir7(), "Library", "LaunchAgents", "com.va-claw.daemon.plist");
2664
+ }
2665
+
2666
+ // packages/cli/dist/claw-store.js
2667
+ import { mkdir as mkdir6, readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
2668
+ import { dirname as dirname7 } from "node:path";
2669
+ var DEFAULT_CLAW_STATUS = "idle";
2670
+ var KNOWN_STATUSES = /* @__PURE__ */ new Set(["running", "working", "idle", "waiting", "error", "offline", "stopped"]);
2671
+ var REGISTRY_VERSION = 1;
2672
+ var DEFAULT_NOW = () => /* @__PURE__ */ new Date();
2673
+ function isClawStatus(value) {
2674
+ return KNOWN_STATUSES.has(value ?? "");
2675
+ }
2676
+ function pickString2(value, fallback = "") {
2677
+ return typeof value === "string" ? value.trim() : fallback;
2678
+ }
2679
+ function pickTags(value) {
2680
+ if (!Array.isArray(value)) {
2681
+ return [];
2682
+ }
2683
+ return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter((entry) => entry.length > 0);
2684
+ }
2685
+ function normalizeStatus(value) {
2686
+ return isClawStatus(typeof value === "string" ? value : void 0) ? value : DEFAULT_CLAW_STATUS;
2687
+ }
2688
+ function validateClawStatus(value) {
2689
+ if (typeof value !== "string") {
2690
+ return null;
2691
+ }
2692
+ const normalized = value.trim().toLowerCase();
2693
+ return isClawStatus(normalized) ? normalized : null;
2694
+ }
2695
+ function normalizeClawDefinition(raw) {
2696
+ if (!raw || typeof raw !== "object") {
2697
+ return null;
2698
+ }
2699
+ const name = pickString2(Reflect.get(raw, "name"));
2700
+ if (name === "") {
2701
+ return null;
2702
+ }
2703
+ const createdAt = pickString2(Reflect.get(raw, "createdAt"), (/* @__PURE__ */ new Date(0)).toISOString());
2704
+ const updatedAt = pickString2(Reflect.get(raw, "updatedAt"), createdAt);
2705
+ return {
2706
+ name,
2707
+ goal: pickString2(Reflect.get(raw, "goal")),
2708
+ status: normalizeStatus(Reflect.get(raw, "status")),
2709
+ cliCommand: pickString2(Reflect.get(raw, "cliCommand"), "va-claw"),
2710
+ note: pickString2(Reflect.get(raw, "note")),
2711
+ tags: pickTags(Reflect.get(raw, "tags")),
2712
+ lastSeenAt: pickString2(Reflect.get(raw, "lastSeenAt"), "").trim() || void 0,
2713
+ createdAt,
2714
+ updatedAt
2715
+ };
2716
+ }
2717
+ function normalizeClawRegistry(raw) {
2718
+ if (!raw || typeof raw !== "object") {
2719
+ return { version: REGISTRY_VERSION, claws: [] };
2720
+ }
2721
+ const version = typeof Reflect.get(raw, "version") === "number" ? Reflect.get(raw, "version") : REGISTRY_VERSION;
2722
+ const rawClaws = Reflect.get(raw, "claws");
2723
+ const claws = Array.isArray(rawClaws) ? rawClaws.map(normalizeClawDefinition).filter(Boolean) : [];
2724
+ return { version: Number(version) > 0 ? Number(version) : REGISTRY_VERSION, claws };
2725
+ }
2726
+ async function listClaws(registryPath) {
2727
+ const registry = await loadClawRegistry(registryPath);
2728
+ return [...registry.claws];
2729
+ }
2730
+ async function registerClaw(registryPath, input, now = DEFAULT_NOW) {
2731
+ const registry = await loadClawRegistry(registryPath);
2732
+ const exists2 = registry.claws.some((entry) => entry.name === input.name);
2733
+ if (exists2) {
2734
+ throw new Error(`Claw already exists: ${input.name}`);
2735
+ }
2736
+ const timestamp = now().toISOString();
2737
+ const claw = {
2738
+ name: input.name,
2739
+ goal: pickString2(input.goal),
2740
+ status: input.status ?? DEFAULT_CLAW_STATUS,
2741
+ cliCommand: pickString2(input.cliCommand, "va-claw"),
2742
+ note: pickString2(input.note),
2743
+ tags: Array.isArray(input.tags) ? input.tags.filter((tag) => tag.trim() !== "") : [],
2744
+ createdAt: timestamp,
2745
+ updatedAt: timestamp
2746
+ };
2747
+ registry.claws = [claw, ...registry.claws];
2748
+ await saveClawRegistry(registryPath, registry);
2749
+ return claw;
2750
+ }
2751
+ async function updateClaw(registryPath, name, updates, now = DEFAULT_NOW) {
2752
+ const registry = await loadClawRegistry(registryPath);
2753
+ const index = registry.claws.findIndex((entry) => entry.name === name);
2754
+ if (index < 0) {
2755
+ return null;
2756
+ }
2757
+ const nowIso = now().toISOString();
2758
+ const current = registry.claws[index];
2759
+ const next = {
2760
+ ...current,
2761
+ goal: updates.goal ?? current.goal,
2762
+ status: updates.status ?? current.status,
2763
+ cliCommand: updates.cliCommand ?? current.cliCommand,
2764
+ note: updates.note ?? current.note,
2765
+ tags: Array.isArray(updates.tags) ? updates.tags.filter((tag) => tag.trim() !== "") : current.tags,
2766
+ lastSeenAt: updates.seen ? nowIso : current.lastSeenAt,
2767
+ updatedAt: nowIso
2768
+ };
2769
+ registry.claws[index] = next;
2770
+ await saveClawRegistry(registryPath, registry);
2771
+ return next;
2772
+ }
2773
+ async function removeClaw(registryPath, name) {
2774
+ const registry = await loadClawRegistry(registryPath);
2775
+ const before = registry.claws.length;
2776
+ registry.claws = registry.claws.filter((entry) => entry.name !== name);
2777
+ if (registry.claws.length === before) {
2778
+ return false;
2779
+ }
2780
+ await saveClawRegistry(registryPath, registry);
2781
+ return true;
2782
+ }
2783
+ async function loadClawRegistry(registryPath) {
2784
+ try {
2785
+ const rawText = await readFile5(registryPath, "utf8");
2786
+ return normalizeClawRegistry(JSON.parse(rawText));
2787
+ } catch (error) {
2788
+ if (typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT") {
2789
+ return { version: REGISTRY_VERSION, claws: [] };
2790
+ }
2791
+ throw error;
2792
+ }
2793
+ }
2794
+ async function saveClawRegistry(registryPath, registry) {
2795
+ await mkdir6(dirname7(registryPath), { recursive: true });
2796
+ await writeFile5(registryPath, `${JSON.stringify({ ...registry, version: REGISTRY_VERSION }, null, 2)}
2797
+ `, "utf8");
2452
2798
  }
2453
2799
 
2454
2800
  // packages/cli/dist/handlers.js
2801
+ var CLAW_FLEET_PROTOCOL_SKILL_URL = "https://raw.githubusercontent.com/Vadaski/va-claw/main/skills/claw-fleet-protocol.md";
2455
2802
  async function runInstall(target, deps) {
2456
2803
  const installTarget = normalizeInstallTarget(target);
2457
2804
  const config = await deps.fileExists(deps.configPath) ? await deps.loadIdentity() : await deps.runInstallWizard();
@@ -2468,6 +2815,10 @@ async function runInstall(target, deps) {
2468
2815
  const serviceType = detectServiceType(deps.platform);
2469
2816
  await deps.installDaemonService(serviceType);
2470
2817
  summary.push(`Daemon service: ${serviceType}`);
2818
+ const fleetSkillName = await installFleetProtocolSkill(deps);
2819
+ if (fleetSkillName) {
2820
+ summary.push(`Claw fleet protocol skill: ${fleetSkillName}`);
2821
+ }
2471
2822
  for (const line of summary) {
2472
2823
  writeLine(deps.stdout, line);
2473
2824
  }
@@ -2500,6 +2851,144 @@ async function runStatus(deps) {
2500
2851
  writeLine(deps.stdout, `Last wake: ${lastWakeAt ?? "never"}`);
2501
2852
  writeLine(deps.stdout, `Memory entries: ${memoryCount}`);
2502
2853
  }
2854
+ async function runClawStatus(deps) {
2855
+ const clawStatus = await buildProtocolReport(deps);
2856
+ writeLine(deps.stdout, formatClawDefinitions(clawStatus.claws));
2857
+ writeLine(deps.stdout, `Daemon running: ${clawStatus.runtime.running ? "yes" : "no"} | service: ${clawStatus.runtime.serviceRunning ? "running" : "stopped"} | wakeCount: ${clawStatus.runtime.wakeCount}`);
2858
+ }
2859
+ async function runClawList(deps) {
2860
+ const claws = await listClaws(deps.clawRegistryPath);
2861
+ writeLine(deps.stdout, formatClawDefinitions(claws));
2862
+ }
2863
+ async function runClawAdd(name, options, deps) {
2864
+ const normalizedName = name.trim();
2865
+ if (normalizedName === "") {
2866
+ throw new Error("Claw name cannot be empty.");
2867
+ }
2868
+ const status = options.status ? validateClawStatus(options.status) : null;
2869
+ if (options.status && status === null) {
2870
+ throw new Error(`Invalid claw status: ${options.status}`);
2871
+ }
2872
+ const claw = await registerClaw(deps.clawRegistryPath, {
2873
+ name: normalizedName,
2874
+ goal: options.goal,
2875
+ status: status ?? void 0,
2876
+ cliCommand: options.cliCommand,
2877
+ note: options.note,
2878
+ tags: options.tags ? splitCommaList(options.tags) : []
2879
+ });
2880
+ writeLine(deps.stdout, formatClawDefinitions([claw]));
2881
+ }
2882
+ async function runClawUpdate(name, options, deps) {
2883
+ const status = options.status ? validateClawStatus(options.status) : null;
2884
+ const normalizedName = name.trim();
2885
+ if (normalizedName === "") {
2886
+ throw new Error("Claw name cannot be empty.");
2887
+ }
2888
+ if (options.status && status === null) {
2889
+ throw new Error(`Invalid claw status: ${options.status}`);
2890
+ }
2891
+ const patch = {
2892
+ goal: options.goal,
2893
+ status: status ?? void 0,
2894
+ cliCommand: options.cliCommand,
2895
+ note: options.note,
2896
+ tags: options.tags ? splitCommaList(options.tags) : void 0,
2897
+ seen: options.seen === "1" || options.seen === "true" || options.seen === "yes"
2898
+ };
2899
+ const claw = await updateClaw(deps.clawRegistryPath, normalizedName, patch);
2900
+ if (!claw) {
2901
+ writeLine(deps.stdout, `Claw not found: ${name}`);
2902
+ return;
2903
+ }
2904
+ writeLine(deps.stdout, formatClawDefinitions([claw]));
2905
+ }
2906
+ async function runClawRemove(name, deps) {
2907
+ const normalizedName = name.trim();
2908
+ if (normalizedName === "") {
2909
+ throw new Error("Claw name cannot be empty.");
2910
+ }
2911
+ const removed = await removeClaw(deps.clawRegistryPath, normalizedName);
2912
+ writeLine(deps.stdout, removed ? `Removed claw: ${normalizedName}` : `Claw not found: ${normalizedName}`);
2913
+ }
2914
+ async function runClawHeartbeat(name, deps) {
2915
+ const normalizedName = name.trim();
2916
+ if (normalizedName === "") {
2917
+ throw new Error("Claw name cannot be empty.");
2918
+ }
2919
+ const claw = await updateClaw(deps.clawRegistryPath, normalizedName, { seen: true, status: "running" });
2920
+ if (!claw) {
2921
+ writeLine(deps.stdout, `Claw not found: ${name}`);
2922
+ return;
2923
+ }
2924
+ writeLine(deps.stdout, formatClawDefinitions([claw]));
2925
+ }
2926
+ async function runProtocol(deps, textMode = false) {
2927
+ const report = await buildProtocolReport(deps);
2928
+ if (textMode) {
2929
+ writeLine(deps.stdout, `protocol: ${report.protocol}`);
2930
+ writeLine(deps.stdout, `timestamp: ${report.timestamp}`);
2931
+ writeLine(deps.stdout, `daemon.running: ${report.runtime.running}`);
2932
+ writeLine(deps.stdout, `daemon.serviceRunning: ${report.runtime.serviceRunning}`);
2933
+ writeLine(deps.stdout, `daemon.discord: ${report.runtime.discord}`);
2934
+ writeLine(deps.stdout, `daemon.wakeCount: ${report.runtime.wakeCount}`);
2935
+ writeLine(deps.stdout, `daemon.lastWakeAt: ${report.runtime.lastWakeAt ?? "never"}`);
2936
+ writeLine(deps.stdout, `memory.entries: ${report.memory.entries}`);
2937
+ writeLine(deps.stdout, `memory.lastWakeAt: ${report.memory.lastWakeAt ?? "never"}`);
2938
+ writeLine(deps.stdout, "");
2939
+ writeLine(deps.stdout, "Claws:");
2940
+ writeLine(deps.stdout, formatClawDefinitions(report.claws));
2941
+ return;
2942
+ }
2943
+ writeLine(deps.stdout, JSON.stringify(report, null, 2));
2944
+ }
2945
+ async function buildProtocolReport(deps) {
2946
+ const runtime = await deps.getDaemonStatus();
2947
+ const serviceType = safeDetectServiceType(deps.platform);
2948
+ const serviceRunning = serviceType ? probeServiceRunning(serviceType, deps.spawnSync) : false;
2949
+ const runtimeLastWakeAt = runtime.lastWakeAt?.toISOString() ?? null;
2950
+ const fallbackLastWakeAt = await findLastWakeAt(deps.memoryDbPath, deps.fileExists);
2951
+ const memoryCount = await countMemoryEntries(deps.memoryDbPath, deps.fileExists);
2952
+ const claws = await listClaws(deps.clawRegistryPath);
2953
+ return {
2954
+ protocol: "va-claw-claw-protocol-1",
2955
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2956
+ runtime: {
2957
+ running: runtime.running,
2958
+ serviceRunning,
2959
+ discord: runtime.discord,
2960
+ wakeCount: runtime.wakeCount,
2961
+ lastWakeAt: runtimeLastWakeAt ?? fallbackLastWakeAt
2962
+ },
2963
+ memory: {
2964
+ entries: memoryCount,
2965
+ lastWakeAt: runtimeLastWakeAt ?? fallbackLastWakeAt
2966
+ },
2967
+ claws: claws.map((claw) => ({
2968
+ name: claw.name,
2969
+ goal: claw.goal,
2970
+ status: claw.status,
2971
+ cliCommand: claw.cliCommand,
2972
+ note: claw.note,
2973
+ tags: claw.tags,
2974
+ lastSeenAt: claw.lastSeenAt,
2975
+ createdAt: claw.createdAt,
2976
+ updatedAt: claw.updatedAt
2977
+ }))
2978
+ };
2979
+ }
2980
+ async function installFleetProtocolSkill(deps) {
2981
+ try {
2982
+ const response = await fetch(CLAW_FLEET_PROTOCOL_SKILL_URL);
2983
+ if (!response.ok) {
2984
+ return null;
2985
+ }
2986
+ const content = await response.text();
2987
+ return await deps.skillInstall(content, "claw-fleet-protocol");
2988
+ } catch {
2989
+ return null;
2990
+ }
2991
+ }
2503
2992
  async function runMemorySearch(query, deps) {
2504
2993
  writeLine(deps.stdout, formatMemoryEntries(await deps.memorySearch(query, 10)));
2505
2994
  }
@@ -2769,6 +3258,7 @@ function createCliProgram(deps = createDefaultCliDeps()) {
2769
3258
  program.command("stop").description("Stop the daemon.").action(async () => runStop(deps));
2770
3259
  program.command("status").description("Show daemon and memory status.").action(async () => runStatus(deps));
2771
3260
  program.command("uninstall").description("Remove daemon service and injected prompts.").action(async () => runUninstall(deps));
3261
+ program.command("protocol").description("Emit a machine-readable protocol summary for agent-facing integrations.").option("--text", "Print a human-readable protocol summary instead of JSON.").action(async (options) => runProtocol(deps, Boolean(options.text)));
2772
3262
  const memory = program.command("memory").description("Memory operations.");
2773
3263
  memory.command("search").description("Search memory.").argument("<query>").action(async (query) => runMemorySearch(query, deps));
2774
3264
  memory.command("memorize").description("Store or update a memory entry by key.").argument("<key>").argument("<essence>").option("--tags <tags>", "Comma-separated tags.").option("--details <details>", "Details text.").option("--importance <importance>", "Importance from 0 to 1.").action((key, essence, options) => runMemoryMemorize(key, essence, options, deps));
@@ -2798,6 +3288,13 @@ function createCliProgram(deps = createDefaultCliDeps()) {
2798
3288
  slack.command("setup").description("Configure Slack bot credentials.").option("--bot-token <token>", "Slack bot token").option("--app-token <token>", "Slack app token").option("--cli-command <command>", "CLI command to invoke for each message").action(async (options) => runSlackChannelSetup(options.botToken, options.appToken, options.cliCommand, deps));
2799
3289
  slack.command("start").description("Start the Slack channel in the foreground.").action(async () => runSlackChannelStart(deps));
2800
3290
  slack.command("status").description("Show Slack channel status.").action(async () => runSlackChannelStatus(deps));
3291
+ const claw = program.command("claw").description("Long-running claw operations.");
3292
+ claw.command("status").description("Show claw status and daemon summary.").action(async () => runClawStatus(deps));
3293
+ claw.command("list").description("List all registered claws.").action(async () => runClawList(deps));
3294
+ claw.command("add").description("Register a long-running claw.").argument("<name>").option("--goal <goal>", "Describe what this claw is responsible for.").option("--status <status>", "running | working | idle | waiting | error | offline | stopped.").option("--cli-command <command>", "Command to execute this claw's actions (default: va-claw).").option("--note <note>", "Single-line note for this claw.").option("--tags <tags>", "Comma-separated tags.").action((name, options) => runClawAdd(name, options, deps));
3295
+ claw.command("set").description("Update a claw's state.").argument("<name>").option("--goal <goal>", "New goal for this claw.").option("--status <status>", "running | working | idle | waiting | error | offline | stopped.").option("--cli-command <command>", "Update command used for this claw.").option("--note <note>", "Update note.").option("--tags <tags>", "Replace tags, comma-separated.").option("--seen", "Mark claw as alive now.").action((name, options) => runClawUpdate(name, { ...options, seen: options.seen ? "true" : void 0 }, deps));
3296
+ claw.command("heartbeat").description("Mark a claw as active and update lastSeenAt.").argument("<name>").action((name) => runClawHeartbeat(name, deps));
3297
+ claw.command("remove").description("Unregister a claw.").argument("<name>").action((name) => runClawRemove(name, deps));
2801
3298
  return program;
2802
3299
  }
2803
3300
  async function runCli(argv = process.argv, deps) {