@zeroroot-ai/zerocool-claude 0.1.0 → 0.1.1

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.
@@ -3,11 +3,17 @@
3
3
  "name": "zerocool",
4
4
  "displayName": "Zerocool (Gibson)",
5
5
  "description": "Gibson platform integration for Claude Code. The session is a live mission. Memory and knowledge in the tenant graph, findings, Gibson tools, delegation. The model stays on your Claude subscription.",
6
- "author": { "name": "ZeroRoot", "url": "https://zeroroot.ai" },
6
+ "author": {
7
+ "name": "ZeroRoot",
8
+ "url": "https://zeroroot.ai"
9
+ },
7
10
  "homepage": "https://github.com/zeroroot-ai/zerocool-plugins",
8
11
  "repository": "https://github.com/zeroroot-ai/zerocool-plugins",
9
12
  "license": "MIT",
10
- "keywords": ["gibson", "security", "knowledge-graph", "mcp"],
11
- "mcpServers": "./.mcp.json",
12
- "hooks": "./hooks/hooks.json"
13
+ "keywords": [
14
+ "gibson",
15
+ "security",
16
+ "knowledge-graph",
17
+ "mcp"
18
+ ]
13
19
  }
@@ -0,0 +1,15 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { type Gibson } from "./session.js";
3
+ /**
4
+ * @zeroroot-ai/zerocool-claude: the MCP server Claude Code starts for a session.
5
+ *
6
+ * The model stays on the user's Claude subscription. This server adds Gibson:
7
+ * one live mission per session, memory and knowledge, findings, tools,
8
+ * delegation. It never routes LLM traffic and never reads an Anthropic key
9
+ * (ADR-0007).
10
+ */
11
+ export declare const log: (line: string) => void;
12
+ export declare function buildServer(env: NodeJS.ProcessEnv, cwd: string): Promise<{
13
+ server: McpServer;
14
+ gibson: Gibson;
15
+ }>;
package/dist/build.js ADDED
@@ -0,0 +1,60 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { ambientBlock } from "./ambient.js";
5
+ import { openGibson } from "./session.js";
6
+ import { stateDir, writeAmbient, writeLive } from "./state.js";
7
+ import { registerComponentizeTool, registerEnrollTool } from "./tools/componentize.js";
8
+ import { registerDelegationTools } from "./tools/delegate.js";
9
+ import { gibsonFindingsBackend, localFindingsBackend, registerFindingTools } from "./tools/findings.js";
10
+ import { registerGibsonTools } from "./tools/gibson-tools.js";
11
+ import { registerMemoryTools } from "./tools/memory.js";
12
+ import { registerStatusTool } from "./tools/status.js";
13
+ /**
14
+ * @zeroroot-ai/zerocool-claude: the MCP server Claude Code starts for a session.
15
+ *
16
+ * The model stays on the user's Claude subscription. This server adds Gibson:
17
+ * one live mission per session, memory and knowledge, findings, tools,
18
+ * delegation. It never routes LLM traffic and never reads an Anthropic key
19
+ * (ADR-0007).
20
+ */
21
+ export const log = (line) => {
22
+ process.stderr.write(`${line}\n`);
23
+ };
24
+ export async function buildServer(env, cwd) {
25
+ const gibson = await openGibson({ env, log });
26
+ const server = new McpServer({ name: "zerocool", version: "0.0.0" });
27
+ registerStatusTool(server, gibson);
28
+ registerComponentizeTool(server, cwd);
29
+ if (!gibson.session) {
30
+ registerFindingTools(server, gibson, localFindingsBackend(env.ZEROCOOL_FINDINGS_LOG ?? join(homedir(), ".zerocool", "findings.jsonl")));
31
+ return { server, gibson };
32
+ }
33
+ registerFindingTools(server, gibson, gibsonFindingsBackend(gibson));
34
+ registerMemoryTools(server, gibson);
35
+ registerEnrollTool(server, gibson.session);
36
+ registerDelegationTools(server, gibson.session);
37
+ const discovery = await registerGibsonTools(server, gibson.session);
38
+ log(discovery.note ? `[zerocool-claude] Gibson tool discovery: ${discovery.note}` : `[zerocool-claude] ${discovery.discovered} Gibson tool(s) registered`);
39
+ // Handoff for the hooks: the ambient block now, the live coordinates while
40
+ // the mission runs. Both fail open.
41
+ const dir = stateDir(env);
42
+ if (gibson.knowledge) {
43
+ const block = await ambientBlock(gibson.knowledge, env.ZEROCOOL_AMBIENT_QUERY ?? "memories, prior findings and security facts for this codebase");
44
+ if (block)
45
+ await writeAmbient(dir, cwd, block).catch((e) => log(`[zerocool-claude] ambient handoff failed: ${e.message}`));
46
+ }
47
+ if (gibson.live) {
48
+ const live = gibson.live;
49
+ const write = () => writeLive(dir, cwd, {
50
+ missionId: live.missionId,
51
+ workId: live.workId,
52
+ endpoint: live.harness.endpoint,
53
+ token: live.harness.token(),
54
+ insecure: env.GIBSON_CALLBACK_INSECURE === "1",
55
+ writtenAt: Date.now(),
56
+ }).catch((e) => log(`[zerocool-claude] live handoff failed: ${e.message}`));
57
+ await write();
58
+ }
59
+ return { server, gibson };
60
+ }
@@ -0,0 +1,21 @@
1
+ import { readFile } from "node:fs/promises";
2
+ /**
3
+ * The hook bin. Claude Code runs it as a separate process per event, with the
4
+ * event on stdin. It never talks to the MCP server. It reads what the server
5
+ * handed off through the state directory (state.ts) and fails open: a missing
6
+ * file means no output, and no output blocks nothing.
7
+ *
8
+ * - SessionStart: inject the ambient knowledge block as additionalContext.
9
+ * - SessionEnd: checkpoint the transcript to the daemon session store under
10
+ * the live mission's task grant (zerocool-plugins#13, the session seam).
11
+ */
12
+ export interface HookInput {
13
+ session_id?: string;
14
+ hook_event_name?: string;
15
+ cwd?: string;
16
+ transcript_path?: string;
17
+ reason?: string;
18
+ }
19
+ export declare function runHook(input: HookInput, env: NodeJS.ProcessEnv, deps?: {
20
+ readFile: typeof readFile;
21
+ }): Promise<string>;
@@ -0,0 +1,51 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { openTaskHarness } from "@zeroroot-ai/sdk";
3
+ import { readAmbient, readLive, stateDir } from "./state.js";
4
+ /** The session store caps a blob at 8 MiB (gibson callback_session_context.go). */
5
+ const MAX_TRANSCRIPT_BYTES = 8 * 1024 * 1024;
6
+ export async function runHook(input, env, deps = { readFile }) {
7
+ const cwd = input.cwd ?? process.cwd();
8
+ const dir = stateDir(env);
9
+ switch (input.hook_event_name) {
10
+ case "SessionStart": {
11
+ const block = await readAmbient(dir, cwd);
12
+ if (!block)
13
+ return "";
14
+ return JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: block } });
15
+ }
16
+ case "SessionEnd": {
17
+ const live = await readLive(dir, cwd);
18
+ if (!live || !input.transcript_path || !input.session_id)
19
+ return "";
20
+ let data;
21
+ try {
22
+ data = await deps.readFile(input.transcript_path);
23
+ }
24
+ catch {
25
+ return "";
26
+ }
27
+ if (data.byteLength > MAX_TRANSCRIPT_BYTES)
28
+ data = data.subarray(data.byteLength - MAX_TRANSCRIPT_BYTES);
29
+ const harness = openTaskHarness({ endpoint: live.endpoint, token: live.token, insecure: live.insecure, renew: false });
30
+ try {
31
+ const res = await harness.client.putSessionContext({
32
+ context: harness.context,
33
+ sessionId: input.session_id,
34
+ data: new Uint8Array(data),
35
+ ifMatch: "",
36
+ });
37
+ if (res.error)
38
+ return JSON.stringify({ systemMessage: `zerocool: session checkpoint refused: ${res.error.message}` });
39
+ }
40
+ catch (e) {
41
+ return JSON.stringify({ systemMessage: `zerocool: session checkpoint failed: ${e.message}` });
42
+ }
43
+ finally {
44
+ harness.stop();
45
+ }
46
+ return "";
47
+ }
48
+ default:
49
+ return "";
50
+ }
51
+ }
package/dist/hook.d.ts CHANGED
@@ -1,23 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { readFile } from "node:fs/promises";
3
- /**
4
- * The hook bin. Claude Code runs it as a separate process per event, with the
5
- * event on stdin. It never talks to the MCP server. It reads what the server
6
- * handed off through the state directory (state.ts) and fails open: a missing
7
- * file means no output, and no output blocks nothing.
8
- *
9
- * - SessionStart: inject the ambient knowledge block as additionalContext.
10
- * - SessionEnd: checkpoint the transcript to the daemon session store under
11
- * the live mission's task grant (zerocool-plugins#13, the session seam).
12
- */
13
- interface HookInput {
14
- session_id?: string;
15
- hook_event_name?: string;
16
- cwd?: string;
17
- transcript_path?: string;
18
- reason?: string;
19
- }
20
- export declare function runHook(input: HookInput, env: NodeJS.ProcessEnv, deps?: {
21
- readFile: typeof readFile;
22
- }): Promise<string>;
23
2
  export {};
package/dist/hook.js CHANGED
@@ -1,72 +1,24 @@
1
1
  #!/usr/bin/env node
2
- import { readFile } from "node:fs/promises";
3
- import { openTaskHarness } from "@zeroroot-ai/sdk";
4
- import { readAmbient, readLive, stateDir } from "./state.js";
5
- /** The session store caps a blob at 8 MiB (gibson callback_session_context.go). */
6
- const MAX_TRANSCRIPT_BYTES = 8 * 1024 * 1024;
7
- export async function runHook(input, env, deps = { readFile }) {
8
- const cwd = input.cwd ?? process.cwd();
9
- const dir = stateDir(env);
10
- switch (input.hook_event_name) {
11
- case "SessionStart": {
12
- const block = await readAmbient(dir, cwd);
13
- if (!block)
14
- return "";
15
- return JSON.stringify({ hookSpecificOutput: { hookEventName: "SessionStart", additionalContext: block } });
16
- }
17
- case "SessionEnd": {
18
- const live = await readLive(dir, cwd);
19
- if (!live || !input.transcript_path || !input.session_id)
20
- return "";
21
- let data;
22
- try {
23
- data = await deps.readFile(input.transcript_path);
24
- }
25
- catch {
26
- return "";
27
- }
28
- if (data.byteLength > MAX_TRANSCRIPT_BYTES)
29
- data = data.subarray(data.byteLength - MAX_TRANSCRIPT_BYTES);
30
- const harness = openTaskHarness({ endpoint: live.endpoint, token: live.token, insecure: live.insecure, renew: false });
31
- try {
32
- const res = await harness.client.putSessionContext({
33
- context: harness.context,
34
- sessionId: input.session_id,
35
- data: new Uint8Array(data),
36
- ifMatch: "",
37
- });
38
- if (res.error)
39
- return JSON.stringify({ systemMessage: `zerocool: session checkpoint refused: ${res.error.message}` });
40
- }
41
- catch (e) {
42
- return JSON.stringify({ systemMessage: `zerocool: session checkpoint failed: ${e.message}` });
43
- }
44
- finally {
45
- harness.stop();
46
- }
47
- return "";
48
- }
49
- default:
50
- return "";
51
- }
52
- }
2
+ import { runHook } from "./hook-run.js";
3
+ /**
4
+ * The hook bin. Runs unconditionally: npm installs it as a symlink named
5
+ * `zerocool-claude-hook`, so an argv[1] guard would never match. Library
6
+ * code lives in hook-run.ts.
7
+ */
53
8
  async function readStdin() {
54
9
  const chunks = [];
55
10
  for await (const c of process.stdin)
56
11
  chunks.push(c);
57
12
  return Buffer.concat(chunks).toString("utf8");
58
13
  }
59
- const invokedDirectly = process.argv[1]?.endsWith("hook.js") ?? false;
60
- if (invokedDirectly) {
61
- readStdin()
62
- .then((raw) => runHook(raw ? JSON.parse(raw) : {}, process.env))
63
- .then((out) => {
64
- if (out)
65
- process.stdout.write(out);
66
- process.exit(0);
67
- })
68
- .catch((e) => {
69
- process.stderr.write(`[zerocool-claude hook] ${e.message}\n`);
70
- process.exit(0); // fail open: a hook error must not block the session
71
- });
72
- }
14
+ readStdin()
15
+ .then((raw) => runHook(raw ? JSON.parse(raw) : {}, process.env))
16
+ .then((out) => {
17
+ if (out)
18
+ process.stdout.write(out);
19
+ process.exit(0);
20
+ })
21
+ .catch((e) => {
22
+ process.stderr.write(`[zerocool-claude hook] ${e.message}\n`);
23
+ process.exit(0); // fail open: a hook error must not block the session
24
+ });
package/dist/index.d.ts CHANGED
@@ -2,4 +2,5 @@ export * from "./mode.js";
2
2
  export * from "./session.js";
3
3
  export * from "./state.js";
4
4
  export * from "./ambient.js";
5
- export { buildServer } from "./server.js";
5
+ export { buildServer } from "./build.js";
6
+ export { runHook } from "./hook-run.js";
package/dist/index.js CHANGED
@@ -4,4 +4,5 @@ export * from "./mode.js";
4
4
  export * from "./session.js";
5
5
  export * from "./state.js";
6
6
  export * from "./ambient.js";
7
- export { buildServer } from "./server.js";
7
+ export { buildServer } from "./build.js";
8
+ export { runHook } from "./hook-run.js";
package/dist/server.d.ts CHANGED
@@ -1,7 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { type Gibson } from "./session.js";
4
- export declare function buildServer(env: NodeJS.ProcessEnv, cwd: string): Promise<{
5
- server: McpServer;
6
- gibson: Gibson;
7
- }>;
2
+ export {};
package/dist/server.js CHANGED
@@ -1,65 +1,14 @@
1
1
  #!/usr/bin/env node
2
- import { homedir } from "node:os";
3
- import { join } from "node:path";
4
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { ambientBlock } from "./ambient.js";
7
- import { openGibson } from "./session.js";
8
- import { clearLive, stateDir, writeAmbient, writeLive } from "./state.js";
9
- import { registerComponentizeTool, registerEnrollTool } from "./tools/componentize.js";
10
- import { registerDelegationTools } from "./tools/delegate.js";
11
- import { gibsonFindingsBackend, localFindingsBackend, registerFindingTools } from "./tools/findings.js";
12
- import { registerGibsonTools } from "./tools/gibson-tools.js";
13
- import { registerMemoryTools } from "./tools/memory.js";
14
- import { describeGibson, registerStatusTool } from "./tools/status.js";
3
+ import { buildServer, log } from "./build.js";
4
+ import { clearLive, stateDir } from "./state.js";
5
+ import { describeGibson } from "./tools/status.js";
15
6
  /**
16
- * @zeroroot-ai/zerocool-claude: the MCP server Claude Code starts for a session.
17
- *
18
- * The model stays on the user's Claude subscription. This server adds Gibson:
19
- * one live mission per session, memory and knowledge, findings, tools,
20
- * delegation. It never routes LLM traffic and never reads an Anthropic key
21
- * (ADR-0007).
7
+ * The MCP server bin. This file runs main() unconditionally: npm installs
8
+ * bins as symlinks named after the bin (`zerocool-claude-mcp`), so any
9
+ * "am I the entry point" check on argv[1] is false there and the process
10
+ * exits before it serves anything. Library code lives in build.ts.
22
11
  */
23
- const log = (line) => {
24
- process.stderr.write(`${line}\n`);
25
- };
26
- export async function buildServer(env, cwd) {
27
- const gibson = await openGibson({ env, log });
28
- const server = new McpServer({ name: "zerocool", version: "0.0.0" });
29
- registerStatusTool(server, gibson);
30
- registerComponentizeTool(server, cwd);
31
- if (!gibson.session) {
32
- registerFindingTools(server, gibson, localFindingsBackend(env.ZEROCOOL_FINDINGS_LOG ?? join(homedir(), ".zerocool", "findings.jsonl")));
33
- return { server, gibson };
34
- }
35
- registerFindingTools(server, gibson, gibsonFindingsBackend(gibson));
36
- registerMemoryTools(server, gibson);
37
- registerEnrollTool(server, gibson.session);
38
- registerDelegationTools(server, gibson.session);
39
- const discovery = await registerGibsonTools(server, gibson.session);
40
- log(discovery.note ? `[zerocool-claude] Gibson tool discovery: ${discovery.note}` : `[zerocool-claude] ${discovery.discovered} Gibson tool(s) registered`);
41
- // Handoff for the hooks: the ambient block now, the live coordinates while
42
- // the mission runs. Both fail open.
43
- const dir = stateDir(env);
44
- if (gibson.knowledge) {
45
- const block = await ambientBlock(gibson.knowledge, env.ZEROCOOL_AMBIENT_QUERY ?? "memories, prior findings and security facts for this codebase");
46
- if (block)
47
- await writeAmbient(dir, cwd, block).catch((e) => log(`[zerocool-claude] ambient handoff failed: ${e.message}`));
48
- }
49
- if (gibson.live) {
50
- const live = gibson.live;
51
- const write = () => writeLive(dir, cwd, {
52
- missionId: live.missionId,
53
- workId: live.workId,
54
- endpoint: live.harness.endpoint,
55
- token: live.harness.token(),
56
- insecure: env.GIBSON_CALLBACK_INSECURE === "1",
57
- writtenAt: Date.now(),
58
- }).catch((e) => log(`[zerocool-claude] live handoff failed: ${e.message}`));
59
- await write();
60
- }
61
- return { server, gibson };
62
- }
63
12
  async function main() {
64
13
  const cwd = process.cwd();
65
14
  const { server, gibson } = await buildServer(process.env, cwd);
@@ -78,10 +27,7 @@ async function main() {
78
27
  process.on(sig, () => void close().finally(() => process.exit(0)));
79
28
  await server.connect(new StdioServerTransport());
80
29
  }
81
- const invokedDirectly = process.argv[1]?.endsWith("server.js") ?? false;
82
- if (invokedDirectly) {
83
- main().catch((e) => {
84
- log(`[zerocool-claude] fatal: ${e.message}`);
85
- process.exit(1);
86
- });
87
- }
30
+ main().catch((e) => {
31
+ log(`[zerocool-claude] fatal: ${e.message}`);
32
+ process.exit(1);
33
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroroot-ai/zerocool-claude",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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": {