@clanker-cleanroom/claude 0.2.5

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/lib/index.cjs ADDED
@@ -0,0 +1,28 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_binding = require("./binding-C2kSQy-B.cjs");
3
+ let clanker_cleanroom = require("clanker-cleanroom");
4
+ let node_path = require("node:path");
5
+ let node_url = require("node:url");
6
+ //#region src/agent.ts
7
+ const claudeAgent = clanker_cleanroom.Agent.fromBinding(require_binding.claudeBinding);
8
+ //#endregion
9
+ //#region src/package-root.ts
10
+ /** Absolute path to this package root (parent of `src/` or `lib/`). */
11
+ const PACKAGE_ROOT = (0, node_url.fileURLToPath)(new URL("..", require("url").pathToFileURL(__filename).href));
12
+ /** Folder of `*.Dockerfile` files for this agent. */
13
+ const DOCKER_DIR = (0, node_path.join)(PACKAGE_ROOT, "docker");
14
+ //#endregion
15
+ exports.CLAUDE_API_KEY_ENV = require_binding.CLAUDE_API_KEY_ENV;
16
+ exports.CLAUDE_CONTAINER_CREDENTIALS_PATH = require_binding.CLAUDE_CONTAINER_CREDENTIALS_PATH;
17
+ exports.CLAUDE_IMAGE = require_binding.CLAUDE_IMAGE;
18
+ exports.CLAUDE_OAUTH_TOKEN_ENV = require_binding.CLAUDE_OAUTH_TOKEN_ENV;
19
+ exports.DOCKER_DIR = DOCKER_DIR;
20
+ exports.PACKAGE_ROOT = PACKAGE_ROOT;
21
+ exports.adaptClaudeEvents = require_binding.adaptClaudeEvents;
22
+ exports.claudeAgent = claudeAgent;
23
+ exports.claudeBinding = require_binding.claudeBinding;
24
+ exports.credentialsEnv = require_binding.credentialsEnv;
25
+ exports.defaultClaudeHostCredentialsFile = require_binding.defaultClaudeHostCredentialsFile;
26
+ exports.resolveClaudeCredentials = require_binding.resolveClaudeCredentials;
27
+
28
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs","names":["Agent","claudeBinding","fileURLToPath","join"],"sources":["../src/agent.ts","../src/package-root.ts"],"sourcesContent":["import { Agent } from \"clanker-cleanroom\";\n\nimport { claudeBinding } from \"./binding.js\";\n\nexport const claudeAgent = Agent.fromBinding(claudeBinding);\n","import { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** Absolute path to this package root (parent of `src/` or `lib/`). */\nexport const PACKAGE_ROOT = fileURLToPath(new URL(\"..\", import.meta.url));\n\n/** Folder of `*.Dockerfile` files for this agent. */\nexport const DOCKER_DIR = join(PACKAGE_ROOT, \"docker\");\n"],"mappings":";;;;;;AAIA,MAAa,cAAcA,kBAAAA,MAAM,YAAYC,gBAAAA,aAAa;;;;ACA1D,MAAa,gBAAA,GAAeC,SAAAA,cAAAA,CAAc,IAAI,IAAI,MAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,IAAqB,CAAC;;AAGxE,MAAa,cAAA,GAAaC,UAAAA,KAAAA,CAAK,cAAc,QAAQ"}
@@ -0,0 +1,52 @@
1
+ import { Agent, AgentBinding, TrajectoryEvent } from "clanker-cleanroom";
2
+ //#region src/constants.d.ts
3
+ declare const CLAUDE_IMAGE = "clanker-cleanroom/claude";
4
+ declare const CLAUDE_CONTAINER_CREDENTIALS_PATH = "/home/agent/.claude/.credentials.json";
5
+ /** Long-lived OAuth token from `claude setup-token` (Claude subscription). */
6
+ declare const CLAUDE_OAUTH_TOKEN_ENV = "CLAUDE_CODE_OAUTH_TOKEN";
7
+ /** Anthropic API key (pay-as-you-go). */
8
+ declare const CLAUDE_API_KEY_ENV = "ANTHROPIC_API_KEY";
9
+ /** Linux hosts persist OAuth credentials here; macOS uses the Keychain instead. */
10
+ declare const defaultClaudeHostCredentialsFile: (home: string) => string;
11
+ //#endregion
12
+ //#region src/binding.d.ts
13
+ declare const claudeBinding: AgentBinding;
14
+ //#endregion
15
+ //#region src/credentials.d.ts
16
+ /**
17
+ * How Claude Code authenticates inside the container. Env-backed kinds are forwarded
18
+ * to `docker run` by name (never on argv); the file kind is bind-mounted read-only.
19
+ * See `constants.ts` for which host setups produce each.
20
+ */
21
+ type ClaudeCredentials = {
22
+ kind: "oauth-token";
23
+ token: string;
24
+ } | {
25
+ kind: "api-key";
26
+ apiKey: string;
27
+ } | {
28
+ kind: "credentials-file";
29
+ file: string;
30
+ };
31
+ /** Env OAuth token, then env API key, then a readable host credentials file. */
32
+ declare function resolveClaudeCredentials(options?: {
33
+ env?: NodeJS.ProcessEnv;
34
+ home?: string;
35
+ }): Promise<ClaudeCredentials>;
36
+ /** Secret values for the docker CLI process, keyed by env names forwarded via envPassthrough. */
37
+ declare function credentialsEnv(credentials: ClaudeCredentials): Record<string, string>;
38
+ //#endregion
39
+ //#region src/agent.d.ts
40
+ declare const claudeAgent: Agent;
41
+ //#endregion
42
+ //#region src/trajectory.d.ts
43
+ declare function adaptClaudeEvents(rawEvents: readonly unknown[]): TrajectoryEvent[];
44
+ //#endregion
45
+ //#region src/package-root.d.ts
46
+ /** Absolute path to this package root (parent of `src/` or `lib/`). */
47
+ declare const PACKAGE_ROOT: string;
48
+ /** Folder of `*.Dockerfile` files for this agent. */
49
+ declare const DOCKER_DIR: string;
50
+ //#endregion
51
+ export { CLAUDE_API_KEY_ENV, CLAUDE_CONTAINER_CREDENTIALS_PATH, CLAUDE_IMAGE, CLAUDE_OAUTH_TOKEN_ENV, type ClaudeCredentials, DOCKER_DIR, PACKAGE_ROOT, adaptClaudeEvents, claudeAgent, claudeBinding, credentialsEnv, defaultClaudeHostCredentialsFile, resolveClaudeCredentials };
52
+ //# sourceMappingURL=index.d.cts.map
@@ -0,0 +1,52 @@
1
+ import { Agent, AgentBinding, TrajectoryEvent } from "clanker-cleanroom";
2
+ //#region src/constants.d.ts
3
+ declare const CLAUDE_IMAGE = "clanker-cleanroom/claude";
4
+ declare const CLAUDE_CONTAINER_CREDENTIALS_PATH = "/home/agent/.claude/.credentials.json";
5
+ /** Long-lived OAuth token from `claude setup-token` (Claude subscription). */
6
+ declare const CLAUDE_OAUTH_TOKEN_ENV = "CLAUDE_CODE_OAUTH_TOKEN";
7
+ /** Anthropic API key (pay-as-you-go). */
8
+ declare const CLAUDE_API_KEY_ENV = "ANTHROPIC_API_KEY";
9
+ /** Linux hosts persist OAuth credentials here; macOS uses the Keychain instead. */
10
+ declare const defaultClaudeHostCredentialsFile: (home: string) => string;
11
+ //#endregion
12
+ //#region src/binding.d.ts
13
+ declare const claudeBinding: AgentBinding;
14
+ //#endregion
15
+ //#region src/credentials.d.ts
16
+ /**
17
+ * How Claude Code authenticates inside the container. Env-backed kinds are forwarded
18
+ * to `docker run` by name (never on argv); the file kind is bind-mounted read-only.
19
+ * See `constants.ts` for which host setups produce each.
20
+ */
21
+ type ClaudeCredentials = {
22
+ kind: "oauth-token";
23
+ token: string;
24
+ } | {
25
+ kind: "api-key";
26
+ apiKey: string;
27
+ } | {
28
+ kind: "credentials-file";
29
+ file: string;
30
+ };
31
+ /** Env OAuth token, then env API key, then a readable host credentials file. */
32
+ declare function resolveClaudeCredentials(options?: {
33
+ env?: NodeJS.ProcessEnv;
34
+ home?: string;
35
+ }): Promise<ClaudeCredentials>;
36
+ /** Secret values for the docker CLI process, keyed by env names forwarded via envPassthrough. */
37
+ declare function credentialsEnv(credentials: ClaudeCredentials): Record<string, string>;
38
+ //#endregion
39
+ //#region src/agent.d.ts
40
+ declare const claudeAgent: Agent;
41
+ //#endregion
42
+ //#region src/trajectory.d.ts
43
+ declare function adaptClaudeEvents(rawEvents: readonly unknown[]): TrajectoryEvent[];
44
+ //#endregion
45
+ //#region src/package-root.d.ts
46
+ /** Absolute path to this package root (parent of `src/` or `lib/`). */
47
+ declare const PACKAGE_ROOT: string;
48
+ /** Folder of `*.Dockerfile` files for this agent. */
49
+ declare const DOCKER_DIR: string;
50
+ //#endregion
51
+ export { CLAUDE_API_KEY_ENV, CLAUDE_CONTAINER_CREDENTIALS_PATH, CLAUDE_IMAGE, CLAUDE_OAUTH_TOKEN_ENV, type ClaudeCredentials, DOCKER_DIR, PACKAGE_ROOT, adaptClaudeEvents, claudeAgent, claudeBinding, credentialsEnv, defaultClaudeHostCredentialsFile, resolveClaudeCredentials };
52
+ //# sourceMappingURL=index.d.mts.map
package/lib/index.mjs ADDED
@@ -0,0 +1,16 @@
1
+ import { a as CLAUDE_API_KEY_ENV, c as CLAUDE_OAUTH_TOKEN_ENV, i as resolveClaudeCredentials, l as defaultClaudeHostCredentialsFile, n as adaptClaudeEvents, o as CLAUDE_CONTAINER_CREDENTIALS_PATH, r as credentialsEnv, s as CLAUDE_IMAGE, t as claudeBinding } from "./binding-DAPBfelH.mjs";
2
+ import { Agent } from "clanker-cleanroom";
3
+ import { join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ //#region src/agent.ts
6
+ const claudeAgent = Agent.fromBinding(claudeBinding);
7
+ //#endregion
8
+ //#region src/package-root.ts
9
+ /** Absolute path to this package root (parent of `src/` or `lib/`). */
10
+ const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
11
+ /** Folder of `*.Dockerfile` files for this agent. */
12
+ const DOCKER_DIR = join(PACKAGE_ROOT, "docker");
13
+ //#endregion
14
+ export { CLAUDE_API_KEY_ENV, CLAUDE_CONTAINER_CREDENTIALS_PATH, CLAUDE_IMAGE, CLAUDE_OAUTH_TOKEN_ENV, DOCKER_DIR, PACKAGE_ROOT, adaptClaudeEvents, claudeAgent, claudeBinding, credentialsEnv, defaultClaudeHostCredentialsFile, resolveClaudeCredentials };
15
+
16
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/agent.ts","../src/package-root.ts"],"sourcesContent":["import { Agent } from \"clanker-cleanroom\";\n\nimport { claudeBinding } from \"./binding.js\";\n\nexport const claudeAgent = Agent.fromBinding(claudeBinding);\n","import { join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** Absolute path to this package root (parent of `src/` or `lib/`). */\nexport const PACKAGE_ROOT = fileURLToPath(new URL(\"..\", import.meta.url));\n\n/** Folder of `*.Dockerfile` files for this agent. */\nexport const DOCKER_DIR = join(PACKAGE_ROOT, \"docker\");\n"],"mappings":";;;;;AAIA,MAAa,cAAc,MAAM,YAAY,aAAa;;;;ACA1D,MAAa,eAAe,cAAc,IAAI,IAAI,MAAM,YAAY,GAAG,CAAC;;AAGxE,MAAa,aAAa,KAAK,cAAc,QAAQ"}
@@ -0,0 +1,6 @@
1
+ const require_binding = require("./binding-C2kSQy-B.cjs");
2
+ //#region src/register.ts
3
+ (0, require("clanker-cleanroom").registerBinding)("claude", require_binding.claudeBinding);
4
+ //#endregion
5
+
6
+ //# sourceMappingURL=register.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.cjs","names":["registerBinding","claudeBinding"],"sources":["../src/register.ts"],"sourcesContent":["import { registerBinding } from \"clanker-cleanroom\";\n\nimport { claudeBinding } from \"./binding.js\";\n\nregisterBinding(\"claude\", claudeBinding);\n"],"mappings":";;CAIAA,8BAAAA,CAAAA,CAAAA,gBAAAA,CAAgB,UAAUC,gBAAAA,aAAa"}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,8 @@
1
+ import { t as claudeBinding } from "./binding-DAPBfelH.mjs";
2
+ import { registerBinding } from "clanker-cleanroom";
3
+ //#region src/register.ts
4
+ registerBinding("claude", claudeBinding);
5
+ //#endregion
6
+ export {};
7
+
8
+ //# sourceMappingURL=register.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.mjs","names":[],"sources":["../src/register.ts"],"sourcesContent":["import { registerBinding } from \"clanker-cleanroom\";\n\nimport { claudeBinding } from \"./binding.js\";\n\nregisterBinding(\"claude\", claudeBinding);\n"],"mappings":";;;AAIA,gBAAgB,UAAU,aAAa"}
package/package.json ADDED
@@ -0,0 +1,136 @@
1
+ {
2
+ "name": "@clanker-cleanroom/claude",
3
+ "version": "0.2.5",
4
+ "description": "Claude Code agent binding for clanker-cleanroom",
5
+ "keywords": [
6
+ "agent",
7
+ "claude",
8
+ "claude-code",
9
+ "cleanroom",
10
+ "docker"
11
+ ],
12
+ "homepage": "https://github.com/improving/agent-gwt#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/improving/agent-gwt/issues"
15
+ },
16
+ "license": "MIT",
17
+ "author": "Eric Siebeneich <eric.siebeneich@improving.com> (https://github.com/improving)",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/improving/agent-gwt.git",
21
+ "directory": "packages/clanker-cleanroom-claude"
22
+ },
23
+ "files": [
24
+ "lib",
25
+ "src",
26
+ "docker",
27
+ "!**/*.spec.ts"
28
+ ],
29
+ "type": "module",
30
+ "main": "./lib/index.cjs",
31
+ "module": "./lib/index.mjs",
32
+ "types": "./lib/index.d.cts",
33
+ "exports": {
34
+ ".": {
35
+ "import": {
36
+ "types": "./lib/index.d.mts",
37
+ "default": "./lib/index.mjs"
38
+ },
39
+ "require": {
40
+ "types": "./lib/index.d.cts",
41
+ "default": "./lib/index.cjs"
42
+ }
43
+ },
44
+ "./register": {
45
+ "import": {
46
+ "types": "./lib/register.d.mts",
47
+ "default": "./lib/register.mjs"
48
+ },
49
+ "require": {
50
+ "types": "./lib/register.d.cts",
51
+ "default": "./lib/register.cjs"
52
+ }
53
+ },
54
+ "./package.json": "./package.json"
55
+ },
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "wireit": {
60
+ "build": {
61
+ "command": "vp pack",
62
+ "files": [
63
+ "src/**/*.ts",
64
+ "tsconfig.json",
65
+ "vite.config.ts",
66
+ "!src/**/*.spec.ts"
67
+ ],
68
+ "output": [
69
+ "lib/**"
70
+ ],
71
+ "dependencies": [
72
+ "../clanker-cleanroom:build"
73
+ ]
74
+ },
75
+ "lint": {
76
+ "command": "vp lint src",
77
+ "files": [
78
+ "src/**/*.ts",
79
+ "tsconfig.json",
80
+ "vite.config.ts"
81
+ ],
82
+ "dependencies": [
83
+ "../clanker-cleanroom:build"
84
+ ]
85
+ },
86
+ "test": {
87
+ "command": "vitest run",
88
+ "files": [
89
+ "src/**/*.ts",
90
+ "vite.config.ts"
91
+ ],
92
+ "dependencies": [
93
+ "../clanker-cleanroom:build"
94
+ ]
95
+ },
96
+ "test:coverage": {
97
+ "command": "vitest run --coverage",
98
+ "files": [
99
+ "src/**/*.ts",
100
+ "vite.config.ts"
101
+ ],
102
+ "output": [
103
+ "coverage/**"
104
+ ],
105
+ "dependencies": [
106
+ "../clanker-cleanroom:build"
107
+ ]
108
+ },
109
+ "prepublishOnly": {
110
+ "dependencies": [
111
+ "lint",
112
+ "test",
113
+ "build"
114
+ ]
115
+ }
116
+ },
117
+ "dependencies": {
118
+ "clanker-cleanroom": "0.2.5"
119
+ },
120
+ "devDependencies": {
121
+ "@types/node": "^26.2.0",
122
+ "@vitest/coverage-v8": "4.1.11",
123
+ "typescript": "^7.0.2",
124
+ "vite": "npm:@voidzero-dev/vite-plus-core@0.3.0",
125
+ "vite-plus": "0.3.0",
126
+ "vitest": "4.1.11",
127
+ "vitest-gwt": "4.1.4",
128
+ "wireit": "^0.14.13"
129
+ },
130
+ "scripts": {
131
+ "build": "wireit",
132
+ "test": "wireit",
133
+ "lint": "wireit",
134
+ "test:coverage": "wireit"
135
+ }
136
+ }
package/src/agent.ts ADDED
@@ -0,0 +1,5 @@
1
+ import { Agent } from "clanker-cleanroom";
2
+
3
+ import { claudeBinding } from "./binding.js";
4
+
5
+ export const claudeAgent = Agent.fromBinding(claudeBinding);
package/src/binding.ts ADDED
@@ -0,0 +1,112 @@
1
+ import {
2
+ asRecord,
3
+ parseAgentJsonOutput,
4
+ readTokenCount,
5
+ type AgentBinding,
6
+ type AgentRunResult,
7
+ type DockerVolumeMount,
8
+ } from "clanker-cleanroom";
9
+
10
+ import { CLAUDE_CONTAINER_CREDENTIALS_PATH, CLAUDE_IMAGE } from "./constants.js";
11
+ import { credentialsEnv, resolveClaudeCredentials } from "./credentials.js";
12
+ import { adaptClaudeEvents } from "./trajectory.js";
13
+
14
+ export const claudeBinding: AgentBinding = {
15
+ image: CLAUDE_IMAGE,
16
+ displayName: "Claude",
17
+ trajectoryKind: "claude",
18
+ adaptEvents: adaptClaudeEvents,
19
+ command: ({ prompt, model }) => {
20
+ const claudeArgs = [
21
+ "claude",
22
+ "-p",
23
+ "--output-format",
24
+ "stream-json",
25
+ "--verbose",
26
+ "--dangerously-skip-permissions",
27
+ ];
28
+ if (model !== undefined && model !== "") {
29
+ claudeArgs.push("--model", model);
30
+ }
31
+ claudeArgs.push("--", prompt);
32
+ return claudeArgs;
33
+ },
34
+ prepare: async () => {
35
+ const credentials = await resolveClaudeCredentials();
36
+ const volumes: DockerVolumeMount[] = [];
37
+ if (credentials.kind === "credentials-file") {
38
+ volumes.push({
39
+ host: credentials.file,
40
+ container: CLAUDE_CONTAINER_CREDENTIALS_PATH,
41
+ mode: "ro",
42
+ });
43
+ }
44
+ return {
45
+ volumes,
46
+ env: credentialsEnv(credentials),
47
+ };
48
+ },
49
+ parseResult: parseClaudeResult,
50
+ describeFailure: describeClaudeFailure,
51
+ };
52
+
53
+ function parseClaudeResult(trajectory: string): AgentRunResult {
54
+ const parsed = parseAgentJsonOutput(trajectory);
55
+ if (isErrorResult(parsed)) {
56
+ throw new Error(`Claude agent reported an error: ${describeErrorResult(parsed)}`);
57
+ }
58
+
59
+ const record = asRecord(parsed);
60
+ const usage = asRecord(record?.usage);
61
+
62
+ return {
63
+ durationMs: readTokenCount(record?.duration_ms),
64
+ costUsd: readTokenCount(record?.total_cost_usd),
65
+ usage: {
66
+ inputTokens: readTokenCount(usage?.input_tokens ?? usage?.inputTokens),
67
+ outputTokens: readTokenCount(usage?.output_tokens ?? usage?.outputTokens),
68
+ cacheReadTokens: readTokenCount(usage?.cache_read_input_tokens ?? usage?.cacheReadTokens),
69
+ cacheWriteTokens: readTokenCount(
70
+ usage?.cache_creation_input_tokens ?? usage?.cacheWriteTokens,
71
+ ),
72
+ },
73
+ };
74
+ }
75
+
76
+ function describeClaudeFailure(trajectory: string): string | undefined {
77
+ try {
78
+ const parsed = parseAgentJsonOutput(trajectory);
79
+ return isErrorResult(parsed) ? describeErrorResult(parsed) : undefined;
80
+ } catch {
81
+ return undefined;
82
+ }
83
+ }
84
+
85
+ type ClaudeErrorResult = {
86
+ is_error: true;
87
+ result: string;
88
+ subtype?: string;
89
+ terminal_reason?: string;
90
+ };
91
+
92
+ function isErrorResult(value: unknown): value is ClaudeErrorResult {
93
+ return (
94
+ typeof value === "object" &&
95
+ value !== null &&
96
+ (value as { is_error?: unknown }).is_error === true
97
+ );
98
+ }
99
+
100
+ function describeErrorResult(result: ClaudeErrorResult): string {
101
+ const message = String(result.result);
102
+
103
+ if (result.terminal_reason !== undefined && result.terminal_reason !== "completed") {
104
+ return `${result.terminal_reason}: ${message}`;
105
+ }
106
+
107
+ if (result.subtype !== undefined && result.subtype !== "success") {
108
+ return `${result.subtype}: ${message}`;
109
+ }
110
+
111
+ return message;
112
+ }
@@ -0,0 +1,13 @@
1
+ import { CONTAINER_HOME } from "clanker-cleanroom";
2
+
3
+ export const CLAUDE_IMAGE = "clanker-cleanroom/claude";
4
+ export const CLAUDE_CONTAINER_CREDENTIALS_PATH = `${CONTAINER_HOME}/.claude/.credentials.json`;
5
+
6
+ /** Long-lived OAuth token from `claude setup-token` (Claude subscription). */
7
+ export const CLAUDE_OAUTH_TOKEN_ENV = "CLAUDE_CODE_OAUTH_TOKEN";
8
+ /** Anthropic API key (pay-as-you-go). */
9
+ export const CLAUDE_API_KEY_ENV = "ANTHROPIC_API_KEY";
10
+
11
+ /** Linux hosts persist OAuth credentials here; macOS uses the Keychain instead. */
12
+ export const defaultClaudeHostCredentialsFile = (home: string): string =>
13
+ `${home}/.claude/.credentials.json`;
@@ -0,0 +1,61 @@
1
+ import { access } from "node:fs/promises";
2
+ import { constants as fsConstants } from "node:fs";
3
+ import { homedir } from "node:os";
4
+
5
+ import {
6
+ CLAUDE_API_KEY_ENV,
7
+ CLAUDE_OAUTH_TOKEN_ENV,
8
+ defaultClaudeHostCredentialsFile,
9
+ } from "./constants.js";
10
+
11
+ /**
12
+ * How Claude Code authenticates inside the container. Env-backed kinds are forwarded
13
+ * to `docker run` by name (never on argv); the file kind is bind-mounted read-only.
14
+ * See `constants.ts` for which host setups produce each.
15
+ */
16
+ export type ClaudeCredentials =
17
+ | { kind: "oauth-token"; token: string }
18
+ | { kind: "api-key"; apiKey: string }
19
+ | { kind: "credentials-file"; file: string };
20
+
21
+ /** Env OAuth token, then env API key, then a readable host credentials file. */
22
+ export async function resolveClaudeCredentials(
23
+ options: { env?: NodeJS.ProcessEnv; home?: string } = {},
24
+ ): Promise<ClaudeCredentials> {
25
+ const env = options.env ?? process.env;
26
+ const home = options.home ?? homedir();
27
+
28
+ const token = env[CLAUDE_OAUTH_TOKEN_ENV];
29
+ if (token !== undefined && token !== "") {
30
+ return { kind: "oauth-token", token };
31
+ }
32
+
33
+ const apiKey = env[CLAUDE_API_KEY_ENV];
34
+ if (apiKey !== undefined && apiKey !== "") {
35
+ return { kind: "api-key", apiKey };
36
+ }
37
+
38
+ const file = defaultClaudeHostCredentialsFile(home);
39
+ try {
40
+ await access(file, fsConstants.R_OK);
41
+ } catch {
42
+ throw new Error(
43
+ `Claude Code credentials not found. Set ${CLAUDE_OAUTH_TOKEN_ENV} (run \`claude setup-token\` on the host) ` +
44
+ `or ${CLAUDE_API_KEY_ENV}, or provide ${file} (Linux hosts; macOS keeps credentials in the Keychain).`,
45
+ );
46
+ }
47
+
48
+ return { kind: "credentials-file", file };
49
+ }
50
+
51
+ /** Secret values for the docker CLI process, keyed by env names forwarded via envPassthrough. */
52
+ export function credentialsEnv(credentials: ClaudeCredentials): Record<string, string> {
53
+ switch (credentials.kind) {
54
+ case "oauth-token":
55
+ return { [CLAUDE_OAUTH_TOKEN_ENV]: credentials.token };
56
+ case "api-key":
57
+ return { [CLAUDE_API_KEY_ENV]: credentials.apiKey };
58
+ case "credentials-file":
59
+ return {};
60
+ }
61
+ }
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ export {
2
+ CLAUDE_API_KEY_ENV,
3
+ CLAUDE_CONTAINER_CREDENTIALS_PATH,
4
+ CLAUDE_IMAGE,
5
+ CLAUDE_OAUTH_TOKEN_ENV,
6
+ defaultClaudeHostCredentialsFile,
7
+ } from "./constants.js";
8
+ export { claudeBinding } from "./binding.js";
9
+ export { resolveClaudeCredentials, credentialsEnv, type ClaudeCredentials } from "./credentials.js";
10
+ export { claudeAgent } from "./agent.js";
11
+ export { adaptClaudeEvents } from "./trajectory.js";
12
+ export { DOCKER_DIR, PACKAGE_ROOT } from "./package-root.js";
@@ -0,0 +1,8 @@
1
+ import { join } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ /** Absolute path to this package root (parent of `src/` or `lib/`). */
5
+ export const PACKAGE_ROOT = fileURLToPath(new URL("..", import.meta.url));
6
+
7
+ /** Folder of `*.Dockerfile` files for this agent. */
8
+ export const DOCKER_DIR = join(PACKAGE_ROOT, "docker");
@@ -0,0 +1,5 @@
1
+ import { registerBinding } from "clanker-cleanroom";
2
+
3
+ import { claudeBinding } from "./binding.js";
4
+
5
+ registerBinding("claude", claudeBinding);
@@ -0,0 +1,119 @@
1
+ import {
2
+ asRecord,
3
+ extractTextContent,
4
+ readString,
5
+ readTokenCount,
6
+ type TrajectoryEvent,
7
+ } from "clanker-cleanroom";
8
+
9
+ export function adaptClaudeEvents(rawEvents: readonly unknown[]): TrajectoryEvent[] {
10
+ const events: TrajectoryEvent[] = [];
11
+
12
+ for (const raw of rawEvents) {
13
+ const record = asRecord(raw);
14
+ if (record === undefined) {
15
+ events.push({ kind: "other", type: "unknown", raw });
16
+ continue;
17
+ }
18
+
19
+ const type = readString(record.type) ?? "unknown";
20
+
21
+ switch (type) {
22
+ case "system":
23
+ events.push({
24
+ kind: "system",
25
+ sessionId: readString(record.session_id),
26
+ model: readString(record.model),
27
+ raw,
28
+ });
29
+ break;
30
+ case "user":
31
+ events.push(...adaptClaudeUser(record, raw));
32
+ break;
33
+ case "assistant":
34
+ events.push(...adaptClaudeAssistant(record, raw));
35
+ break;
36
+ case "result":
37
+ events.push({
38
+ kind: "result",
39
+ text: readString(record.result) ?? "",
40
+ isError: record.is_error === true,
41
+ durationMs: readTokenCount(record.duration_ms),
42
+ costUsd: readTokenCount(record.total_cost_usd),
43
+ raw,
44
+ });
45
+ break;
46
+ default:
47
+ events.push({ kind: "other", type, raw });
48
+ }
49
+ }
50
+
51
+ return events;
52
+ }
53
+
54
+ function adaptClaudeAssistant(
55
+ record: Record<string, unknown>,
56
+ raw: unknown,
57
+ ): TrajectoryEvent[] {
58
+ const events: TrajectoryEvent[] = [];
59
+ const text = extractTextContent(record.message);
60
+ if (text !== "") {
61
+ events.push({ kind: "assistant", text, raw });
62
+ }
63
+
64
+ for (const block of contentBlocks(record.message)) {
65
+ if (block.type !== "tool_use") {
66
+ continue;
67
+ }
68
+ events.push({
69
+ kind: "tool",
70
+ phase: "started",
71
+ callId: readString(block.id) ?? "",
72
+ name: readString(block.name) ?? "unknown",
73
+ args: block.input ?? null,
74
+ result: null,
75
+ raw,
76
+ });
77
+ }
78
+
79
+ if (events.length === 0) {
80
+ events.push({ kind: "assistant", text: "", raw });
81
+ }
82
+
83
+ return events;
84
+ }
85
+
86
+ function adaptClaudeUser(record: Record<string, unknown>, raw: unknown): TrajectoryEvent[] {
87
+ const events: TrajectoryEvent[] = [];
88
+ const text = extractTextContent(record.message);
89
+ const toolResults: TrajectoryEvent[] = [];
90
+
91
+ for (const block of contentBlocks(record.message)) {
92
+ if (block.type !== "tool_result") {
93
+ continue;
94
+ }
95
+ toolResults.push({
96
+ kind: "tool",
97
+ phase: "completed",
98
+ callId: readString(block.tool_use_id) ?? "",
99
+ name: "tool_result",
100
+ args: null,
101
+ result: block.content ?? null,
102
+ raw,
103
+ });
104
+ }
105
+
106
+ if (text !== "" || toolResults.length === 0) {
107
+ events.push({ kind: "user", text, raw });
108
+ }
109
+ events.push(...toolResults);
110
+ return events;
111
+ }
112
+
113
+ function contentBlocks(message: unknown): Record<string, unknown>[] {
114
+ const content = asRecord(message)?.content;
115
+ if (!Array.isArray(content)) {
116
+ return [];
117
+ }
118
+ return content.map((block) => asRecord(block)).filter((block) => block !== undefined);
119
+ }