@zeroroot-ai/zerocool-claude 0.5.2 → 0.6.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-plugin/plugin.json +1 -1
- package/.mcp.json +1 -1
- package/dist/index.d.ts +1 -7
- package/dist/index.js +6 -8
- package/dist/state.d.ts +13 -10
- package/dist/state.js +4 -13
- package/package.json +4 -8
- package/dist/ambient.d.ts +0 -7
- package/dist/ambient.js +0 -18
- package/dist/build.d.ts +0 -32
- package/dist/build.js +0 -218
- package/dist/claude-run.d.ts +0 -46
- package/dist/claude-run.js +0 -118
- package/dist/cli.d.ts +0 -88
- package/dist/cli.js +0 -186
- package/dist/config.d.ts +0 -44
- package/dist/config.js +0 -53
- package/dist/mode.d.ts +0 -32
- package/dist/mode.js +0 -21
- package/dist/sandbox-run.d.ts +0 -26
- package/dist/sandbox-run.js +0 -42
- package/dist/sandbox.d.ts +0 -2
- package/dist/sandbox.js +0 -30
- package/dist/server.d.ts +0 -2
- package/dist/server.js +0 -34
- package/dist/session.d.ts +0 -34
- package/dist/session.js +0 -142
- package/dist/tls.d.ts +0 -1
- package/dist/tls.js +0 -19
- package/dist/tools/componentize.d.ts +0 -18
- package/dist/tools/componentize.js +0 -80
- package/dist/tools/delegate.d.ts +0 -4
- package/dist/tools/delegate.js +0 -108
- package/dist/tools/findings.d.ts +0 -24
- package/dist/tools/findings.js +0 -104
- package/dist/tools/gibson-tools.d.ts +0 -20
- package/dist/tools/gibson-tools.js +0 -89
- package/dist/tools/memory.d.ts +0 -14
- package/dist/tools/memory.js +0 -96
- package/dist/tools/result.d.ts +0 -5
- package/dist/tools/result.js +0 -8
- package/dist/tools/status.d.ts +0 -5
- package/dist/tools/status.js +0 -21
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "zerocool",
|
|
4
4
|
"displayName": "Zerocool (Gibson)",
|
|
5
|
-
"description": "Gibson
|
|
5
|
+
"description": "Gibson for Claude Code. The model stays on your Claude login.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ZeroRoot",
|
|
8
8
|
"url": "https://zeroroot.ai"
|
package/.mcp.json
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
export * from "./mode.js";
|
|
2
|
-
export * from "./session.js";
|
|
3
1
|
export * from "./state.js";
|
|
4
|
-
export
|
|
5
|
-
export * from "./config.js";
|
|
6
|
-
export * from "./cli.js";
|
|
7
|
-
export { buildServer } from "./build.js";
|
|
8
|
-
export { runHook } from "./hook-run.js";
|
|
2
|
+
export { runHook, type HookInput } from "./hook-run.js";
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
// @zeroroot-ai/zerocool-claude:
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
// @zeroroot-ai/zerocool-claude: the Claude Code host adapter (ADR-0008).
|
|
2
|
+
//
|
|
3
|
+
// The tools are not here. They come from the Gibson MCP server, which the
|
|
4
|
+
// bundle's `.mcp.json` names. This package is the bundle around it: the
|
|
5
|
+
// plugin manifest, the marketplace entry, and two hooks. The only bin is
|
|
6
|
+
// the hook.
|
|
5
7
|
export * from "./state.js";
|
|
6
|
-
export * from "./ambient.js";
|
|
7
|
-
export * from "./config.js";
|
|
8
|
-
export * from "./cli.js";
|
|
9
|
-
export { buildServer } from "./build.js";
|
|
10
8
|
export { runHook } from "./hook-run.js";
|
package/dist/state.d.ts
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The handoff
|
|
2
|
+
* The handoff from the Gibson MCP server to this plugin's hooks.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* knowledge block for SessionStart, and the live-mission
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* A hook runs as its own process and cannot reach the server's session, so
|
|
5
|
+
* the server writes one small file per working directory in `~/.zerocool/`:
|
|
6
|
+
* the ambient knowledge block for SessionStart, and the live-mission
|
|
7
|
+
* coordinates for SessionEnd (ADR-0007's consequence, now served by the
|
|
8
|
+
* server, ADR-0008).
|
|
9
|
+
*
|
|
10
|
+
* This file is the read half of that format, kept here because a hook must
|
|
11
|
+
* not depend on the server package: the format is the contract, not the
|
|
12
|
+
* import. It matches `@zeroroot-ai/gibson-mcp`'s `state.ts` exactly, and the
|
|
13
|
+
* key is a hash of the working directory.
|
|
10
14
|
*/
|
|
11
15
|
export interface LiveState {
|
|
12
16
|
missionId: string;
|
|
@@ -19,8 +23,7 @@ export interface LiveState {
|
|
|
19
23
|
}
|
|
20
24
|
export declare function stateDir(env?: NodeJS.ProcessEnv): string;
|
|
21
25
|
export declare function keyFor(cwd: string): string;
|
|
22
|
-
|
|
26
|
+
/** The block SessionStart injects. Empty when the server wrote none. */
|
|
23
27
|
export declare function readAmbient(dir: string, cwd: string): Promise<string>;
|
|
24
|
-
|
|
28
|
+
/** The live mission SessionEnd checkpoints under. Undefined when there is none. */
|
|
25
29
|
export declare function readLive(dir: string, cwd: string): Promise<LiveState | undefined>;
|
|
26
|
-
export declare function clearLive(dir: string, cwd: string): Promise<void>;
|
package/dist/state.js
CHANGED
|
@@ -1,17 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
export function stateDir(env = process.env) {
|
|
6
|
-
return env.ZEROCOOL_STATE_DIR ?? join(homedir(), ".zerocool"
|
|
6
|
+
return env.ZEROCOOL_STATE_DIR ?? join(homedir(), ".zerocool");
|
|
7
7
|
}
|
|
8
8
|
export function keyFor(cwd) {
|
|
9
9
|
return createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
13
|
-
await writeFile(join(dir, `ambient-${keyFor(cwd)}.md`), block, { encoding: "utf8", mode: 0o600 });
|
|
14
|
-
}
|
|
11
|
+
/** The block SessionStart injects. Empty when the server wrote none. */
|
|
15
12
|
export async function readAmbient(dir, cwd) {
|
|
16
13
|
try {
|
|
17
14
|
return await readFile(join(dir, `ambient-${keyFor(cwd)}.md`), "utf8");
|
|
@@ -20,10 +17,7 @@ export async function readAmbient(dir, cwd) {
|
|
|
20
17
|
return "";
|
|
21
18
|
}
|
|
22
19
|
}
|
|
23
|
-
|
|
24
|
-
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
25
|
-
await writeFile(join(dir, `live-${keyFor(cwd)}.json`), JSON.stringify(state), { encoding: "utf8", mode: 0o600 });
|
|
26
|
-
}
|
|
20
|
+
/** The live mission SessionEnd checkpoints under. Undefined when there is none. */
|
|
27
21
|
export async function readLive(dir, cwd) {
|
|
28
22
|
try {
|
|
29
23
|
return JSON.parse(await readFile(join(dir, `live-${keyFor(cwd)}.json`), "utf8"));
|
|
@@ -32,6 +26,3 @@ export async function readLive(dir, cwd) {
|
|
|
32
26
|
return undefined;
|
|
33
27
|
}
|
|
34
28
|
}
|
|
35
|
-
export async function clearLive(dir, cwd) {
|
|
36
|
-
await rm(join(dir, `live-${keyFor(cwd)}.json`), { force: true });
|
|
37
|
-
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zeroroot-ai/zerocool-claude",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Claude Code
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "Claude Code host adapter for Gibson (zerocool): the plugin bundle around the Gibson MCP server, plus SessionStart and SessionEnd hooks. The model stays on your Claude login.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -25,14 +25,10 @@
|
|
|
25
25
|
"test": "tsc -p tsconfig.test.json && node --test \"dist-test/**/*.test.js\""
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@
|
|
29
|
-
"@zeroroot-ai/sdk": "^0.9.0",
|
|
30
|
-
"zod": "^4.4.3"
|
|
28
|
+
"@zeroroot-ai/sdk": "^0.9.0"
|
|
31
29
|
},
|
|
32
30
|
"bin": {
|
|
33
|
-
"zerocool-claude-
|
|
34
|
-
"zerocool-claude-hook": "dist/hook.js",
|
|
35
|
-
"zerocool-claude-dispatch": "dist/sandbox.js"
|
|
31
|
+
"zerocool-claude-hook": "dist/hook.js"
|
|
36
32
|
},
|
|
37
33
|
"publishConfig": {
|
|
38
34
|
"access": "public"
|
package/dist/ambient.d.ts
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type KnowledgeSource } from "@zeroroot-ai/sdk";
|
|
2
|
-
/**
|
|
3
|
-
* One GraphRAG lookup per session, injected by the SessionStart hook. A
|
|
4
|
-
* knowledge failure never breaks a start: the agent works without prior
|
|
5
|
-
* context, it just works less well.
|
|
6
|
-
*/
|
|
7
|
-
export declare function ambientBlock(knowledge: KnowledgeSource, seedQuery: string): Promise<string>;
|
package/dist/ambient.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { formatKnowledgeForPrompt } from "@zeroroot-ai/sdk";
|
|
2
|
-
/** How many hits the ambient block carries. Small: it is prompt overhead on every session. */
|
|
3
|
-
const AMBIENT_LIMIT = 5;
|
|
4
|
-
/**
|
|
5
|
-
* One GraphRAG lookup per session, injected by the SessionStart hook. A
|
|
6
|
-
* knowledge failure never breaks a start: the agent works without prior
|
|
7
|
-
* context, it just works less well.
|
|
8
|
-
*/
|
|
9
|
-
export async function ambientBlock(knowledge, seedQuery) {
|
|
10
|
-
try {
|
|
11
|
-
const hits = await knowledge.query({ text: seedQuery, topK: AMBIENT_LIMIT });
|
|
12
|
-
const body = formatKnowledgeForPrompt(hits);
|
|
13
|
-
return body ? `Prior context from the Gibson knowledge graph (recall for more):\n${body}` : "";
|
|
14
|
-
}
|
|
15
|
-
catch {
|
|
16
|
-
return "";
|
|
17
|
-
}
|
|
18
|
-
}
|
package/dist/build.d.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
import { type CliOptions } from "./cli.js";
|
|
3
|
-
import { type Gibson, type OpenGibsonOptions } from "./session.js";
|
|
4
|
-
/**
|
|
5
|
-
* @zeroroot-ai/zerocool-claude: the MCP server Claude Code starts for a session.
|
|
6
|
-
*
|
|
7
|
-
* The model stays on the user's Claude subscription. This server adds Gibson:
|
|
8
|
-
* one live mission per session, memory and knowledge, findings, tools,
|
|
9
|
-
* delegation. It never routes LLM traffic and never reads an Anthropic key
|
|
10
|
-
* (ADR-0007).
|
|
11
|
-
*
|
|
12
|
-
* The connection is made in the session, not before it. `gibson_login` starts
|
|
13
|
-
* the CLI device flow and returns the URL and code. `gibson_connect` enrolls
|
|
14
|
-
* through the CLI session, trusts the private CA, checks in, picks the
|
|
15
|
-
* target, starts the live mission and swaps the tool set live. The settings
|
|
16
|
-
* it learns are persisted, so later sessions connect on their own.
|
|
17
|
-
*/
|
|
18
|
-
export declare const log: (line: string) => void;
|
|
19
|
-
export interface BuildDeps {
|
|
20
|
-
open?: Pick<OpenGibsonOptions, "connect" | "start" | "hostKeyExists" | "trust">;
|
|
21
|
-
cli?: CliOptions;
|
|
22
|
-
}
|
|
23
|
-
export interface Surface {
|
|
24
|
-
server: McpServer;
|
|
25
|
-
/** The current connection. Changes on gibson_connect. */
|
|
26
|
-
current(): Gibson;
|
|
27
|
-
/** Replace the connection and re-register the posture's tools. */
|
|
28
|
-
upgrade(next: Gibson): Promise<void>;
|
|
29
|
-
}
|
|
30
|
-
export declare function buildServer(env: NodeJS.ProcessEnv, cwd: string, deps?: BuildDeps): Promise<Surface>;
|
|
31
|
-
/** What a coding workspace is, as a URL: its git origin, else the directory. */
|
|
32
|
-
export declare function workspaceURL(cwd: string): Promise<string>;
|
package/dist/build.js
DELETED
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
|
-
import { existsSync } from "node:fs";
|
|
3
|
-
import { promisify } from "node:util";
|
|
4
|
-
import { homedir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
|
-
import { z } from "zod";
|
|
8
|
-
import { ambientBlock } from "./ambient.js";
|
|
9
|
-
import { createTarget, enrollIdentity, listTargets, startLogin } from "./cli.js";
|
|
10
|
-
import { loadSettings, writeConfig } from "./config.js";
|
|
11
|
-
import { openGibson } from "./session.js";
|
|
12
|
-
import { stateDir, writeAmbient, writeLive } from "./state.js";
|
|
13
|
-
import { registerComponentizeTool, registerEnrollTool } from "./tools/componentize.js";
|
|
14
|
-
import { registerDelegationTools } from "./tools/delegate.js";
|
|
15
|
-
import { gibsonFindingsBackend, localFindingsBackend, registerFindingTools, taskFindingsBackend } from "./tools/findings.js";
|
|
16
|
-
import { registerGibsonTools } from "./tools/gibson-tools.js";
|
|
17
|
-
import { registerMemoryTools } from "./tools/memory.js";
|
|
18
|
-
import { failure, text } from "./tools/result.js";
|
|
19
|
-
import { describeGibson, registerStatusTool } from "./tools/status.js";
|
|
20
|
-
/**
|
|
21
|
-
* @zeroroot-ai/zerocool-claude: the MCP server Claude Code starts for a session.
|
|
22
|
-
*
|
|
23
|
-
* The model stays on the user's Claude subscription. This server adds Gibson:
|
|
24
|
-
* one live mission per session, memory and knowledge, findings, tools,
|
|
25
|
-
* delegation. It never routes LLM traffic and never reads an Anthropic key
|
|
26
|
-
* (ADR-0007).
|
|
27
|
-
*
|
|
28
|
-
* The connection is made in the session, not before it. `gibson_login` starts
|
|
29
|
-
* the CLI device flow and returns the URL and code. `gibson_connect` enrolls
|
|
30
|
-
* through the CLI session, trusts the private CA, checks in, picks the
|
|
31
|
-
* target, starts the live mission and swaps the tool set live. The settings
|
|
32
|
-
* it learns are persisted, so later sessions connect on their own.
|
|
33
|
-
*/
|
|
34
|
-
export const log = (line) => {
|
|
35
|
-
process.stderr.write(`${line}\n`);
|
|
36
|
-
};
|
|
37
|
-
/** Snapshot of the tools a posture registered, so an upgrade can drop them. */
|
|
38
|
-
function collect(server, register) {
|
|
39
|
-
const before = new Set(Object.keys(toolTable(server)));
|
|
40
|
-
return Promise.resolve(register()).then(() => {
|
|
41
|
-
const after = toolTable(server);
|
|
42
|
-
return Object.keys(after)
|
|
43
|
-
.filter((n) => !before.has(n))
|
|
44
|
-
.map((n) => after[n]);
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
function toolTable(server) {
|
|
48
|
-
return server._registeredTools;
|
|
49
|
-
}
|
|
50
|
-
export async function buildServer(env, cwd, deps = {}) {
|
|
51
|
-
const settings = await loadSettings(env);
|
|
52
|
-
let gibson = await openGibson({ settings, log, env, ...deps.open });
|
|
53
|
-
const server = new McpServer({ name: "zerocool", version: "0.0.0" });
|
|
54
|
-
let posture = [];
|
|
55
|
-
const surface = {
|
|
56
|
-
server,
|
|
57
|
-
current: () => gibson,
|
|
58
|
-
upgrade: async (next) => {
|
|
59
|
-
for (const t of posture)
|
|
60
|
-
t.remove();
|
|
61
|
-
posture = await collect(server, () => registerPosture(server, next, env, cwd));
|
|
62
|
-
gibson = next;
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
registerStatusTool(server, () => gibson);
|
|
66
|
-
registerComponentizeTool(server, cwd);
|
|
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);
|
|
71
|
-
posture = await collect(server, () => registerPosture(server, gibson, env, cwd));
|
|
72
|
-
return surface;
|
|
73
|
-
}
|
|
74
|
-
/** The tools a posture carries, plus the handoff files for the hooks. */
|
|
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
|
-
}
|
|
84
|
-
if (!gibson.session) {
|
|
85
|
-
registerFindingTools(server, gibson, localFindingsBackend(env.ZEROCOOL_FINDINGS_LOG ?? join(homedir(), ".zerocool", "findings.jsonl")));
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
registerFindingTools(server, gibson, gibsonFindingsBackend(gibson));
|
|
89
|
-
registerMemoryTools(server, gibson);
|
|
90
|
-
registerEnrollTool(server, gibson.session);
|
|
91
|
-
registerDelegationTools(server, gibson.session);
|
|
92
|
-
try {
|
|
93
|
-
const discovery = await registerGibsonTools(server, gibson.session);
|
|
94
|
-
log(discovery.note ? `[zerocool-claude] Gibson tool discovery: ${discovery.note}` : `[zerocool-claude] ${discovery.discovered} Gibson tool(s) registered`);
|
|
95
|
-
}
|
|
96
|
-
catch (e) {
|
|
97
|
-
// Discovery is a convenience. The generic gibson_call_tool is registered
|
|
98
|
-
// before discovery runs, so a failure here costs the per-tool wrappers only.
|
|
99
|
-
log(`[zerocool-claude] Gibson tool discovery failed: ${e.message}`);
|
|
100
|
-
}
|
|
101
|
-
const dir = stateDir(env);
|
|
102
|
-
if (gibson.knowledge) {
|
|
103
|
-
const block = await ambientBlock(gibson.knowledge, env.ZEROCOOL_AMBIENT_QUERY ?? "memories, prior findings and security facts for this codebase");
|
|
104
|
-
if (block)
|
|
105
|
-
await writeAmbient(dir, cwd, block).catch((e) => log(`[zerocool-claude] ambient handoff failed: ${e.message}`));
|
|
106
|
-
}
|
|
107
|
-
if (gibson.live) {
|
|
108
|
-
const live = gibson.live;
|
|
109
|
-
await writeLive(dir, cwd, {
|
|
110
|
-
missionId: live.missionId,
|
|
111
|
-
workId: live.workId,
|
|
112
|
-
endpoint: live.harness.endpoint,
|
|
113
|
-
token: live.harness.token(),
|
|
114
|
-
insecure: gibson.settings.callbackInsecure,
|
|
115
|
-
writtenAt: Date.now(),
|
|
116
|
-
}).catch((e) => log(`[zerocool-claude] live handoff failed: ${e.message}`));
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
/** gibson_login and gibson_connect: the in-session path to a platform. */
|
|
120
|
-
function registerConnectTools(server, surface, env, cwd, deps) {
|
|
121
|
-
const cli = (s) => ({ ...deps.cli, env, gibsonURL: s.platformURL, tenant: s.tenant });
|
|
122
|
-
server.registerTool("gibson_login", {
|
|
123
|
-
description: "Sign in to a Gibson platform as the person running this session. Starts the gibson CLI device " +
|
|
124
|
-
"flow and returns a URL and a code. The person opens the URL, confirms the code, and then calls " +
|
|
125
|
-
"gibson_connect. Needed once per host, and again when the login session expires.",
|
|
126
|
-
inputSchema: {
|
|
127
|
-
platform_url: z.string().optional().describe("Platform URL, e.g. https://api.example.com. Defaults to the last one used."),
|
|
128
|
-
},
|
|
129
|
-
}, async (args) => {
|
|
130
|
-
const s = surface.current().settings;
|
|
131
|
-
try {
|
|
132
|
-
const p = await startLogin({ ...deps.cli, env, gibsonURL: args.platform_url ?? s.platformURL });
|
|
133
|
-
return text("sign in", `Open ${p.url} and confirm the code ${p.code}. The CLI waits for the approval. When it is approved, call gibson_connect.`);
|
|
134
|
-
}
|
|
135
|
-
catch (e) {
|
|
136
|
-
return failure("gibson_login failed", e.message);
|
|
137
|
-
}
|
|
138
|
-
});
|
|
139
|
-
server.registerTool("gibson_connect", {
|
|
140
|
-
description: "Connect this session to a Gibson platform without a restart: enroll this host through the gibson " +
|
|
141
|
-
"CLI login session if needed, check in, pick the target, start the live mission, and add the " +
|
|
142
|
-
"platform tools (remember, recall, world_view, Gibson tools, delegation). The settings are kept, " +
|
|
143
|
-
"so later sessions connect on their own. Call gibson_status afterwards.",
|
|
144
|
-
inputSchema: {
|
|
145
|
-
platform_url: z.string().optional().describe("Platform URL. Defaults to the gibson CLI login session or the saved config."),
|
|
146
|
-
target_id: z.string().optional().describe("Target id for the live mission. Defaults to the only target in the tenant."),
|
|
147
|
-
create_target: z.string().optional().describe("Create a target with this name when none exists, and use it."),
|
|
148
|
-
target_url: z.string().optional().describe("URL of the target to create. Defaults to the workspace's git origin, or file://<cwd>."),
|
|
149
|
-
bootstrap_token: z.string().optional().describe("One-time enrollment token. Only when the CLI cannot mint one."),
|
|
150
|
-
ca_cert_path: z.string().optional().describe("Path to a private CA to trust. Defaults to the CLI session's CA."),
|
|
151
|
-
callback_insecure: z.boolean().optional().describe("Dial the callback endpoint without TLS. Local daemons only."),
|
|
152
|
-
},
|
|
153
|
-
}, async (args) => {
|
|
154
|
-
const base = await loadSettings(env);
|
|
155
|
-
const s = {
|
|
156
|
-
...base,
|
|
157
|
-
platformURL: args.platform_url ?? base.platformURL,
|
|
158
|
-
targetId: args.target_id ?? base.targetId,
|
|
159
|
-
caCertPath: args.ca_cert_path ?? base.caCertPath,
|
|
160
|
-
callbackInsecure: args.callback_insecure ?? base.callbackInsecure,
|
|
161
|
-
bootstrapToken: args.bootstrap_token ?? base.bootstrapToken,
|
|
162
|
-
};
|
|
163
|
-
if (!s.platformURL) {
|
|
164
|
-
return failure("no platform", "Pass platform_url, or call gibson_login first so the CLI session names the platform.");
|
|
165
|
-
}
|
|
166
|
-
const hostKeyExists = (deps.open?.hostKeyExists ?? existsSync)(s.hostKeyPath);
|
|
167
|
-
try {
|
|
168
|
-
if (!hostKeyExists && !s.bootstrapToken) {
|
|
169
|
-
s.bootstrapToken = await enrollIdentity(s.agentName, cli(s));
|
|
170
|
-
}
|
|
171
|
-
if (!s.targetId) {
|
|
172
|
-
if (args.create_target) {
|
|
173
|
-
s.targetId = await createTarget(args.create_target, args.target_url ?? (await workspaceURL(cwd)), cli(s));
|
|
174
|
-
}
|
|
175
|
-
else {
|
|
176
|
-
const targets = await listTargets(cli(s));
|
|
177
|
-
if (targets.length === 1)
|
|
178
|
-
s.targetId = targets[0].id;
|
|
179
|
-
else if (targets.length === 0) {
|
|
180
|
-
return failure("no target", "The tenant has no target. Call gibson_connect again with create_target set to a name for this workspace.");
|
|
181
|
-
}
|
|
182
|
-
else {
|
|
183
|
-
return failure("choose a target", `The tenant has ${targets.length} targets. Call gibson_connect again with target_id:\n${targets.map((t) => `- ${t.id} ${t.name} (${t.type}, ${t.status})`).join("\n")}`);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
catch (e) {
|
|
189
|
-
return failure("gibson_connect failed", e.message);
|
|
190
|
-
}
|
|
191
|
-
await writeConfig(env, {
|
|
192
|
-
platformURL: s.platformURL,
|
|
193
|
-
targetId: s.targetId,
|
|
194
|
-
...(s.caCertPath ? { caCertPath: s.caCertPath } : {}),
|
|
195
|
-
...(s.daemonURL ? { daemonURL: s.daemonURL } : {}),
|
|
196
|
-
callbackInsecure: s.callbackInsecure,
|
|
197
|
-
});
|
|
198
|
-
const previous = surface.current();
|
|
199
|
-
await previous.close().catch(() => { });
|
|
200
|
-
const next = await openGibson({ settings: s, log, env, ...deps.open });
|
|
201
|
-
await surface.upgrade(next);
|
|
202
|
-
const body = describeGibson(next);
|
|
203
|
-
return next.mode === "live" ? text("connected", body) : failure("connected, but not live", body);
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
/** What a coding workspace is, as a URL: its git origin, else the directory. */
|
|
207
|
-
export async function workspaceURL(cwd) {
|
|
208
|
-
try {
|
|
209
|
-
const { stdout } = await promisify(execFile)("git", ["-C", cwd, "remote", "get-url", "origin"]);
|
|
210
|
-
const remote = stdout.trim();
|
|
211
|
-
if (remote)
|
|
212
|
-
return remote;
|
|
213
|
-
}
|
|
214
|
-
catch {
|
|
215
|
-
// not a git checkout, or no origin
|
|
216
|
-
}
|
|
217
|
-
return `file://${cwd}`;
|
|
218
|
-
}
|
package/dist/claude-run.d.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
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>;
|
package/dist/claude-run.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
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
|
-
}
|