@zeroroot-ai/zerocool-claude 0.2.1 → 0.3.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/dist/build.js CHANGED
@@ -12,7 +12,7 @@ import { openGibson } from "./session.js";
12
12
  import { stateDir, writeAmbient, writeLive } from "./state.js";
13
13
  import { registerComponentizeTool, registerEnrollTool } from "./tools/componentize.js";
14
14
  import { registerDelegationTools } from "./tools/delegate.js";
15
- import { gibsonFindingsBackend, localFindingsBackend, registerFindingTools } from "./tools/findings.js";
15
+ import { gibsonFindingsBackend, localFindingsBackend, registerFindingTools, taskFindingsBackend } from "./tools/findings.js";
16
16
  import { registerGibsonTools } from "./tools/gibson-tools.js";
17
17
  import { registerMemoryTools } from "./tools/memory.js";
18
18
  import { failure, text } from "./tools/result.js";
@@ -49,7 +49,7 @@ function toolTable(server) {
49
49
  }
50
50
  export async function buildServer(env, cwd, deps = {}) {
51
51
  const settings = await loadSettings(env);
52
- let gibson = await openGibson({ settings, log, ...deps.open });
52
+ let gibson = await openGibson({ settings, log, env, ...deps.open });
53
53
  const server = new McpServer({ name: "zerocool", version: "0.0.0" });
54
54
  let posture = [];
55
55
  const surface = {
@@ -64,12 +64,23 @@ export async function buildServer(env, cwd, deps = {}) {
64
64
  };
65
65
  registerStatusTool(server, () => gibson);
66
66
  registerComponentizeTool(server, cwd);
67
- registerConnectTools(server, surface, env, cwd, deps);
67
+ // A sandboxed dispatch is fully decided by its launch; there is nothing to
68
+ // log in to or connect, and no host key to write.
69
+ if (gibson.mode !== "task")
70
+ registerConnectTools(server, surface, env, cwd, deps);
68
71
  posture = await collect(server, () => registerPosture(server, gibson, env, cwd));
69
72
  return surface;
70
73
  }
71
74
  /** The tools a posture carries, plus the handoff files for the hooks. */
72
75
  async function registerPosture(server, gibson, env, cwd) {
76
+ if (gibson.mode === "task" && gibson.live) {
77
+ // A sandboxed dispatch holds one grant and nothing else: the tools that
78
+ // run under it, and no component-side surface (enroll, delegation,
79
+ // tool discovery) because there is no component client.
80
+ registerFindingTools(server, gibson, taskFindingsBackend(gibson.live.harness));
81
+ registerMemoryTools(server, gibson);
82
+ return;
83
+ }
73
84
  if (!gibson.session) {
74
85
  registerFindingTools(server, gibson, localFindingsBackend(env.ZEROCOOL_FINDINGS_LOG ?? join(homedir(), ".zerocool", "findings.jsonl")));
75
86
  return;
@@ -186,7 +197,7 @@ function registerConnectTools(server, surface, env, cwd, deps) {
186
197
  });
187
198
  const previous = surface.current();
188
199
  await previous.close().catch(() => { });
189
- const next = await openGibson({ settings: s, log, ...deps.open });
200
+ const next = await openGibson({ settings: s, log, env, ...deps.open });
190
201
  await surface.upgrade(next);
191
202
  const body = describeGibson(next);
192
203
  return next.mode === "live" ? text("connected", body) : failure("connected, but not live", body);
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Run Claude Code headless and read what it reports.
3
+ *
4
+ * `claude -p --output-format stream-json --verbose` writes one JSON event per
5
+ * line: a `system/init` first (tools, MCP servers, plugin errors), then
6
+ * `assistant` and `user` messages, and a final `result` with the text, the
7
+ * cost estimate, the turn count and the session id. The API key comes from
8
+ * `ANTHROPIC_API_KEY` in the environment: headless mode never reads a
9
+ * subscription login (code.claude.com/docs/en/headless).
10
+ */
11
+ export interface ClaudeRunOptions {
12
+ goal: string;
13
+ cwd: string;
14
+ model?: string;
15
+ maxTurns?: number;
16
+ maxBudgetUsd?: number;
17
+ appendSystemPrompt?: string;
18
+ /** MCP servers as `--mcp-config` JSON. */
19
+ mcpServers?: Record<string, {
20
+ command: string;
21
+ args?: string[];
22
+ env?: Record<string, string>;
23
+ }>;
24
+ /** Tool patterns that run without a prompt, e.g. "mcp__gibson__*". */
25
+ allowedTools?: string[];
26
+ timeoutMs?: number;
27
+ env?: NodeJS.ProcessEnv;
28
+ bin?: string;
29
+ onEvent?: (line: string) => void;
30
+ }
31
+ export interface ClaudeRunResult {
32
+ text: string;
33
+ sessionId: string;
34
+ isError: boolean;
35
+ numTurns: number;
36
+ costUsd: number;
37
+ /** MCP servers the session refused or failed to start, from system/init. */
38
+ mcpServerErrors: string[];
39
+ events: number;
40
+ }
41
+ /** Build the argv for one headless run. */
42
+ export declare function claudeArgs(opts: ClaudeRunOptions): string[];
43
+ /** Parse the NDJSON stream into the run result. */
44
+ export declare function parseClaudeEvents(stdout: string): ClaudeRunResult;
45
+ /** Spawn claude and collect its stream. Rejects on a non-zero exit or timeout. */
46
+ export declare function runClaude(opts: ClaudeRunOptions): Promise<ClaudeRunResult>;
@@ -0,0 +1,118 @@
1
+ import { spawn } from "node:child_process";
2
+ /** Build the argv for one headless run. */
3
+ export function claudeArgs(opts) {
4
+ const args = ["-p", opts.goal, "--output-format", "stream-json", "--verbose", "--no-session-persistence"];
5
+ // The sandbox is the permission boundary (gibson ADR-0016). Claude Code
6
+ // refuses this mode as root, so the image runs as an unprivileged user.
7
+ args.push("--permission-mode", "bypassPermissions");
8
+ if (opts.model)
9
+ args.push("--model", opts.model);
10
+ if (opts.maxTurns)
11
+ args.push("--max-turns", String(opts.maxTurns));
12
+ if (opts.maxBudgetUsd)
13
+ args.push("--max-budget-usd", String(opts.maxBudgetUsd));
14
+ if (opts.appendSystemPrompt)
15
+ args.push("--append-system-prompt", opts.appendSystemPrompt);
16
+ if (opts.mcpServers && Object.keys(opts.mcpServers).length > 0) {
17
+ args.push("--mcp-config", JSON.stringify({ mcpServers: opts.mcpServers }), "--strict-mcp-config");
18
+ }
19
+ if (opts.allowedTools && opts.allowedTools.length > 0)
20
+ args.push("--allowedTools", opts.allowedTools.join(","));
21
+ return args;
22
+ }
23
+ /** Parse the NDJSON stream into the run result. */
24
+ export function parseClaudeEvents(stdout) {
25
+ const out = { text: "", sessionId: "", isError: false, numTurns: 0, costUsd: 0, mcpServerErrors: [], events: 0 };
26
+ const chunks = [];
27
+ let sawResult = false;
28
+ for (const line of stdout.split("\n")) {
29
+ const trimmed = line.trim();
30
+ if (!trimmed)
31
+ continue;
32
+ let ev;
33
+ try {
34
+ ev = JSON.parse(trimmed);
35
+ }
36
+ catch {
37
+ continue; // a stray log line, not an event
38
+ }
39
+ out.events += 1;
40
+ if (ev.session_id)
41
+ out.sessionId = ev.session_id;
42
+ if (ev.type === "system" && ev.subtype === "init") {
43
+ for (const e of ev.mcp_server_errors ?? [])
44
+ out.mcpServerErrors.push(`${e.name ?? "?"}: ${e.type ?? ""} ${e.message ?? ""}`.trim());
45
+ for (const s of ev.mcp_servers ?? [])
46
+ if (s.status && s.status !== "connected")
47
+ out.mcpServerErrors.push(`${s.name ?? "?"}: ${s.status}`);
48
+ }
49
+ else if (ev.type === "assistant") {
50
+ for (const c of ev.message?.content ?? [])
51
+ if (c.type === "text" && c.text)
52
+ chunks.push(c.text);
53
+ }
54
+ else if (ev.type === "result") {
55
+ sawResult = true;
56
+ out.isError = !!ev.is_error;
57
+ out.numTurns = ev.num_turns ?? 0;
58
+ out.costUsd = ev.total_cost_usd ?? 0;
59
+ if (typeof ev.result === "string" && ev.result)
60
+ out.text = ev.result;
61
+ }
62
+ }
63
+ if (!out.text)
64
+ out.text = chunks.join("");
65
+ if (!sawResult && out.events > 0)
66
+ out.isError = true;
67
+ return out;
68
+ }
69
+ /** Spawn claude and collect its stream. Rejects on a non-zero exit or timeout. */
70
+ export async function runClaude(opts) {
71
+ const bin = opts.bin ?? opts.env?.ZEROCOOL_CLAUDE_BIN ?? process.env.ZEROCOOL_CLAUDE_BIN ?? "claude";
72
+ const args = claudeArgs(opts);
73
+ return await new Promise((resolve, reject) => {
74
+ const child = spawn(bin, args, { cwd: opts.cwd, env: { ...process.env, ...opts.env }, stdio: ["ignore", "pipe", "pipe"] });
75
+ const out = [];
76
+ const err = [];
77
+ let buf = "";
78
+ let timer;
79
+ if (opts.timeoutMs && opts.timeoutMs > 0) {
80
+ timer = setTimeout(() => {
81
+ child.kill("SIGTERM");
82
+ reject(new Error(`claude run timed out after ${opts.timeoutMs}ms`));
83
+ }, opts.timeoutMs);
84
+ }
85
+ child.stdout.on("data", (d) => {
86
+ const s = d.toString();
87
+ out.push(s);
88
+ if (opts.onEvent) {
89
+ buf += s;
90
+ let i;
91
+ while ((i = buf.indexOf("\n")) >= 0) {
92
+ const line = buf.slice(0, i);
93
+ buf = buf.slice(i + 1);
94
+ if (line.trim())
95
+ opts.onEvent(line);
96
+ }
97
+ }
98
+ });
99
+ child.stderr.on("data", (d) => err.push(d.toString()));
100
+ child.on("error", (e) => {
101
+ if (timer)
102
+ clearTimeout(timer);
103
+ reject(new Error(`cannot run ${bin}: ${e.message}`));
104
+ });
105
+ child.on("exit", (code) => {
106
+ if (timer)
107
+ clearTimeout(timer);
108
+ if (buf.trim() && opts.onEvent)
109
+ opts.onEvent(buf);
110
+ const result = parseClaudeEvents(out.join(""));
111
+ if (code !== 0) {
112
+ reject(new Error(`claude exited ${code}: ${(err.join("").trim() || result.text || "no output").slice(0, 2000)}`));
113
+ return;
114
+ }
115
+ resolve(result);
116
+ });
117
+ });
118
+ }
package/dist/mode.d.ts CHANGED
@@ -8,12 +8,16 @@
8
8
  * component grant. No memory writes: a memory is a World observation and
9
9
  * the World is written under a mission (gibson ADR-0012).
10
10
  * - `live`: checked in, and this session is a mission (gibson#1593, decision 9).
11
+ * - `task`: a sandboxed dispatch (gibson ADR-0016). The launch injected the
12
+ * per-dispatch grant and the task; there is no check-in and no live
13
+ * mission to create, the mission already exists and this run is one of
14
+ * its nodes. Reads and writes use that grant.
11
15
  *
12
16
  * The postures are one path with fewer tools at each step down, the same
13
17
  * fail-open shape as the opencode plugin: a coding agent that cannot reach its
14
18
  * platform is still a working coding agent.
15
19
  */
16
- export type Mode = "standalone" | "component" | "live";
20
+ export type Mode = "standalone" | "component" | "live" | "task";
17
21
  export interface ModeDecision {
18
22
  mode: Mode;
19
23
  /** One line a person can act on. Empty when nothing is missing. */
@@ -0,0 +1,26 @@
1
+ import { type SandboxDispatch } from "@zeroroot-ai/sdk";
2
+ import { runClaude, type ClaudeRunResult } from "./claude-run.js";
3
+ /**
4
+ * The sandboxed-dispatch bin (gibson ADR-0016, #1621). gibson launches this
5
+ * image once per mission run with the task, the per-dispatch grant and the
6
+ * callback endpoint in the environment, and the tenant's own Anthropic key
7
+ * as ANTHROPIC_API_KEY (the manifest's `credentials`). It runs Claude Code
8
+ * headless with the Gibson MCP server attached in its task posture, streams
9
+ * the NDJSON events to stdout for the live console, and exits 0 when the
10
+ * run completed. Findings and memories reach the World through the
11
+ * callbacks; there is no result blob.
12
+ */
13
+ export interface SandboxRunOptions {
14
+ env: NodeJS.ProcessEnv;
15
+ cwd: string;
16
+ run?: typeof runClaude;
17
+ onEvent?: (line: string) => void;
18
+ }
19
+ /** Everything the child claude and its MCP server need from the launch. */
20
+ export declare function sandboxEnv(d: SandboxDispatch, env: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
21
+ /** A `.js` path runs under node; anything else is a bin on PATH. */
22
+ export declare function mcpServerSpec(bin: string): {
23
+ command: string;
24
+ args?: string[];
25
+ };
26
+ export declare function runSandbox(opts: SandboxRunOptions): Promise<ClaudeRunResult>;
@@ -0,0 +1,42 @@
1
+ import { readSandboxDispatch, SANDBOX_ENV } from "@zeroroot-ai/sdk";
2
+ import { runClaude } from "./claude-run.js";
3
+ /** Everything the child claude and its MCP server need from the launch. */
4
+ export function sandboxEnv(d, env) {
5
+ return {
6
+ ...env,
7
+ [SANDBOX_ENV.grant]: d.grant,
8
+ [SANDBOX_ENV.callbackEndpoint]: d.callbackEndpoint,
9
+ [SANDBOX_ENV.missionId]: d.missionId,
10
+ [SANDBOX_ENV.missionRunId]: d.missionRunId,
11
+ [SANDBOX_ENV.agentRunId]: d.agentRunId,
12
+ ...(d.traceId ? { [SANDBOX_ENV.traceId]: d.traceId } : {}),
13
+ };
14
+ }
15
+ /** A `.js` path runs under node; anything else is a bin on PATH. */
16
+ export function mcpServerSpec(bin) {
17
+ return bin.endsWith(".js") ? { command: "node", args: [bin] } : { command: bin };
18
+ }
19
+ export async function runSandbox(opts) {
20
+ const d = readSandboxDispatch(opts.env);
21
+ if (!opts.env.ANTHROPIC_API_KEY) {
22
+ throw new Error("ANTHROPIC_API_KEY is not set. Headless Claude Code runs on an API key only; the catalog manifest " +
23
+ "declares it under credentials and the launch injects the tenant's own (gibson#1621, decision 12).");
24
+ }
25
+ const env = sandboxEnv(d, opts.env);
26
+ const run = opts.run ?? runClaude;
27
+ return await run({
28
+ goal: d.goal,
29
+ cwd: opts.cwd,
30
+ ...(opts.env.ZEROCOOL_CLAUDE_MODEL ? { model: opts.env.ZEROCOOL_CLAUDE_MODEL } : {}),
31
+ maxTurns: Number(opts.env.ZEROCOOL_CLAUDE_MAX_TURNS ?? 200),
32
+ ...(opts.env.ZEROCOOL_CLAUDE_MAX_BUDGET_USD ? { maxBudgetUsd: Number(opts.env.ZEROCOOL_CLAUDE_MAX_BUDGET_USD) } : {}),
33
+ appendSystemPrompt: "You run inside a Gibson mission sandbox. Record every real security finding with submit_finding, " +
34
+ "keep facts worth carrying to later runs with remember, and read prior context with recall. " +
35
+ "Work until the goal is met or clearly unreachable, then state the outcome.",
36
+ mcpServers: { gibson: mcpServerSpec(opts.env.ZEROCOOL_CLAUDE_MCP_BIN ?? "zerocool-claude-mcp") },
37
+ allowedTools: ["mcp__gibson__*"],
38
+ timeoutMs: Number(opts.env.ZEROCOOL_CLAUDE_TIMEOUT_MS ?? 0),
39
+ env,
40
+ ...(opts.onEvent ? { onEvent: opts.onEvent } : {}),
41
+ });
42
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { runSandbox } from "./sandbox-run.js";
3
+ /**
4
+ * The sandboxed-dispatch bin. Runs unconditionally: npm installs it as a
5
+ * symlink named zerocool-claude-dispatch. Library code lives in sandbox-run.ts.
6
+ */
7
+ async function main() {
8
+ const log = (l) => process.stderr.write(`[zerocool-claude-dispatch] ${l}\n`);
9
+ let result;
10
+ try {
11
+ result = await runSandbox({
12
+ env: process.env,
13
+ cwd: process.env.ZEROCOOL_WORKSPACE ?? process.cwd(),
14
+ onEvent: (line) => process.stdout.write(`${line}\n`),
15
+ });
16
+ }
17
+ catch (e) {
18
+ log(`run failed: ${e.message}`);
19
+ process.exit(1);
20
+ return;
21
+ }
22
+ if (result.mcpServerErrors.length > 0)
23
+ log(`MCP: ${result.mcpServerErrors.join("; ")}`);
24
+ log(`done turns=${result.numTurns} cost_usd=${result.costUsd.toFixed(4)} session=${result.sessionId || "-"} error=${result.isError}`);
25
+ process.exit(result.isError ? 1 : 0);
26
+ }
27
+ main().catch((e) => {
28
+ process.stderr.write(`[zerocool-claude-dispatch] fatal: ${e.message}\n`);
29
+ process.exit(1);
30
+ });
package/dist/session.d.ts CHANGED
@@ -18,6 +18,8 @@ export interface Gibson {
18
18
  export interface OpenGibsonOptions {
19
19
  settings: Settings;
20
20
  log: (line: string) => void;
21
+ /** The process environment, for the sandboxed-dispatch contract. */
22
+ env?: NodeJS.ProcessEnv;
21
23
  /** Test seams. */
22
24
  connect?: typeof connectGibson;
23
25
  start?: typeof startLiveMission;
package/dist/session.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { existsSync } from "node:fs";
2
- import { componentKnowledge, connectGibson, startLiveMission, taskKnowledge, } from "@zeroroot-ai/sdk";
2
+ import { componentKnowledge, connectGibson, readSandboxDispatch, sandboxHarness, SANDBOX_ENV, startLiveMission, taskKnowledge, } from "@zeroroot-ai/sdk";
3
3
  import { decideMode } from "./mode.js";
4
4
  import { trustCA } from "./tls.js";
5
5
  export { DEFAULT_AGENT_NAME } from "./config.js";
@@ -9,6 +9,10 @@ export { DEFAULT_AGENT_NAME } from "./config.js";
9
9
  */
10
10
  export async function openGibson(opts) {
11
11
  const { settings, log } = opts;
12
+ const env = opts.env ?? {};
13
+ if (env[SANDBOX_ENV.grant] && env[SANDBOX_ENV.callbackEndpoint]) {
14
+ return openSandboxTask(env, settings, log, opts.trust);
15
+ }
12
16
  const { hostKeyPath, agentName } = settings;
13
17
  const hostKeyExists = (opts.hostKeyExists ?? existsSync)(hostKeyPath);
14
18
  const decision = decideMode({ platformURL: settings.platformURL, bootstrapToken: settings.bootstrapToken, hostKeyExists, targetId: settings.targetId }, hostKeyPath);
@@ -101,3 +105,34 @@ export async function openGibson(opts) {
101
105
  },
102
106
  };
103
107
  }
108
+ /**
109
+ * The task posture: this process is a sandboxed dispatch. The launch already
110
+ * decided everything; the plugin only opens the task harness from the grant
111
+ * and serves the tools that run under it. No host key, no check-in, no
112
+ * mission creation, and nothing to fail open to: a sandbox without a
113
+ * usable grant has no reason to exist, so this throws.
114
+ */
115
+ async function openSandboxTask(env, settings, log, trust) {
116
+ const d = readSandboxDispatch(env);
117
+ if (settings.caCertPath)
118
+ await (trust ?? trustCA)(settings.caCertPath).catch((e) => log(`[zerocool-claude] CA not loaded: ${e.message}`));
119
+ const harness = sandboxHarness(d, { insecure: settings.callbackInsecure });
120
+ const live = {
121
+ missionId: d.missionId,
122
+ workId: d.missionRunId,
123
+ harness,
124
+ // The sandbox reports completion by exiting; the launcher owns the node.
125
+ end: async () => harness.stop(),
126
+ };
127
+ log(`[zerocool-claude] task posture: mission ${d.missionId} run ${d.missionRunId}; reads and writes use the dispatch grant`);
128
+ return {
129
+ mode: "task",
130
+ reason: "",
131
+ agentName: settings.agentName,
132
+ hostKeyPath: settings.hostKeyPath,
133
+ settings,
134
+ live,
135
+ knowledge: taskKnowledge(harness),
136
+ close: async () => harness.stop(),
137
+ };
138
+ }
@@ -1,5 +1,5 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
- import { type Finding } from "@zeroroot-ai/sdk";
2
+ import { type Finding, type TaskHarness } from "@zeroroot-ai/sdk";
3
3
  import type { Gibson } from "../session.js";
4
4
  export interface FindingsBackend {
5
5
  submit(f: Finding): Promise<string>;
@@ -7,6 +7,12 @@ export interface FindingsBackend {
7
7
  }
8
8
  /** Platform backend: the finding lands in the tenant knowledge graph. */
9
9
  export declare function gibsonFindingsBackend(g: Gibson): FindingsBackend;
10
+ /**
11
+ * Task backend: the callback service's typed SubmitFinding, under the
12
+ * dispatch grant. Fields map from the SDK's JSON finding onto
13
+ * gibson.types.v1.Finding; the daemon assigns the id and the mission.
14
+ */
15
+ export declare function taskFindingsBackend(harness: TaskHarness): FindingsBackend;
10
16
  /** Standalone backend: an append-only JSONL log. */
11
17
  export declare function localFindingsBackend(path: string): FindingsBackend;
12
18
  /**
@@ -9,6 +9,41 @@ export function gibsonFindingsBackend(g) {
9
9
  const component = g.session.clients.component;
10
10
  return { submit: (f) => submitFinding(component, f), describe: () => "the tenant Gibson graph" };
11
11
  }
12
+ /**
13
+ * Task backend: the callback service's typed SubmitFinding, under the
14
+ * dispatch grant. Fields map from the SDK's JSON finding onto
15
+ * gibson.types.v1.Finding; the daemon assigns the id and the mission.
16
+ */
17
+ export function taskFindingsBackend(harness) {
18
+ const severity = (s) => ({ critical: 1, high: 2, medium: 3, low: 4, info: 5 })[s] ?? 0;
19
+ return {
20
+ submit: async (f) => {
21
+ const res = await harness.client.submitFinding({
22
+ context: harness.context,
23
+ finding: {
24
+ id: f.id,
25
+ missionId: f.mission_id,
26
+ agentName: f.agent_name,
27
+ title: f.title,
28
+ description: f.description,
29
+ category: f.category,
30
+ severity: severity(f.severity),
31
+ confidence: f.confidence,
32
+ remediation: f.remediation ?? "",
33
+ targetId: f.target_id ?? "",
34
+ tags: f.tags ?? [],
35
+ evidence: (f.evidence ?? []).map((e) => ({ type: e.type, title: e.title, content: e.content })),
36
+ },
37
+ });
38
+ if (res.error)
39
+ throw new Error(`SubmitFinding refused: ${res.error.message}`);
40
+ // The callback acknowledges without an id; the finding keeps the one
41
+ // newFinding minted, which is what the daemon stored.
42
+ return f.id;
43
+ },
44
+ describe: () => "the tenant Gibson graph (dispatch grant)",
45
+ };
46
+ }
12
47
  /** Standalone backend: an append-only JSONL log. */
13
48
  export function localFindingsBackend(path) {
14
49
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroroot-ai/zerocool-claude",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Claude Code plugin: Gibson platform integration (zerocool). The session is a live mission. Memory, knowledge, findings, tools, delegation. The model stays on your Claude subscription.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,12 +26,13 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@modelcontextprotocol/sdk": "^1.30.0",
29
- "@zeroroot-ai/sdk": "^0.6.1",
29
+ "@zeroroot-ai/sdk": "^0.7.0",
30
30
  "zod": "^4.4.3"
31
31
  },
32
32
  "bin": {
33
33
  "zerocool-claude-mcp": "dist/server.js",
34
- "zerocool-claude-hook": "dist/hook.js"
34
+ "zerocool-claude-hook": "dist/hook.js",
35
+ "zerocool-claude-dispatch": "dist/sandbox.js"
35
36
  },
36
37
  "publishConfig": {
37
38
  "access": "public"