@agnishc/edb-claude-proxy 0.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+ - Initial release: `claude_proxy` tool with streaming JSON output parsing
7
+ - File context injection via `files[]` parameter
8
+ - Configurable tool access and system prompt
9
+ - Collapsed and expanded TUI rendering with markdown support
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agnish Chakraborty
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # @agnishc/edb-claude-proxy
2
+
3
+ A Pi CLI extension that registers a `claude_proxy` tool — lets the pi agent delegate tasks to **Claude Code CLI** running in non-interactive (`--print`) mode.
4
+
5
+ ## Use cases
6
+
7
+ - Code review and security audit
8
+ - Diff analysis and documentation
9
+ - Architectural second opinion
10
+ - Any task where a second model perspective is valuable
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pi install npm:@agnishc/edb-claude-proxy
16
+ ```
17
+
18
+ ## Requirements
19
+
20
+ - `claude` CLI on PATH ([Claude Code](https://docs.anthropic.com/claude/docs/claude-code))
21
+ - Set `CLAUDE_PATH` env var to override the binary location
22
+
23
+ ## Parameters
24
+
25
+ | Parameter | Type | Description |
26
+ |-----------|------|-------------|
27
+ | `prompt` | string | The task for Claude — be specific |
28
+ | `systemPrompt` | string? | Expert persona (e.g. "You are a senior security engineer") |
29
+ | `model` | string? | `sonnet` (default), `opus`, `haiku`, or full model name |
30
+ | `allowedTools` | string[]? | Tools Claude may use. Defaults to `["Read"]` |
31
+ | `files` | string[]? | File paths to inject into Claude's context |
32
+ | `cwd` | string? | Working directory for the Claude process |
33
+
34
+ ## TUI
35
+
36
+ - Collapsed: shows tool calls (read/bash/edit) and a response preview
37
+ - Expanded (`Ctrl+O`): full markdown-rendered response with all tool calls
38
+
39
+ ## License
40
+
41
+ [MIT](LICENSE) © Agnish Chakraborty
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@agnishc/edb-claude-proxy",
3
+ "version": "0.1.0",
4
+ "description": "Pi extension: claude_proxy tool — delegate tasks to Claude Code CLI from within pi",
5
+ "keywords": ["pi-package", "pi-extension", "edb", "claude"],
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "author": "Agnish Chakraborty",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/agnishcc/pi-extention-monorepo.git",
12
+ "directory": "packages/edb-claude-proxy"
13
+ },
14
+ "homepage": "https://github.com/agnishcc/pi-extention-monorepo/tree/main/packages/edb-claude-proxy#readme",
15
+ "bugs": { "url": "https://github.com/agnishcc/pi-extention-monorepo/issues" },
16
+ "publishConfig": { "access": "public" },
17
+ "scripts": { "test": "vitest run" },
18
+ "files": ["src", "README.md", "LICENSE", "CHANGELOG.md"],
19
+ "pi": {
20
+ "extensions": ["./src/index.ts"]
21
+ },
22
+ "peerDependencies": {
23
+ "@mariozechner/pi-coding-agent": "*",
24
+ "@mariozechner/pi-tui": "*",
25
+ "typebox": "*"
26
+ }
27
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { execSync } from "node:child_process";
2
+ import * as fs from "node:fs";
3
+ import * as os from "node:os";
4
+ import * as path from "node:path";
5
+
6
+ // ── CLI discovery ──────────────────────────────────────────────────────────────
7
+
8
+ /** Locate the claude CLI. Checks CLAUDE_PATH env, PATH, then common install dirs. */
9
+ export function findClaudeCli(): string {
10
+ if (process.env.CLAUDE_PATH) {
11
+ try {
12
+ fs.accessSync(process.env.CLAUDE_PATH, fs.constants.X_OK);
13
+ return process.env.CLAUDE_PATH;
14
+ } catch {
15
+ /* fall through */
16
+ }
17
+ }
18
+
19
+ try {
20
+ const resolved = execSync("which claude", {
21
+ encoding: "utf-8",
22
+ stdio: ["ignore", "pipe", "ignore"],
23
+ }).trim();
24
+ if (resolved) return resolved;
25
+ } catch {
26
+ /* fall through */
27
+ }
28
+
29
+ for (const c of [
30
+ path.join(os.homedir(), ".local/bin/claude"),
31
+ "/usr/local/bin/claude",
32
+ "/opt/homebrew/bin/claude",
33
+ ]) {
34
+ try {
35
+ fs.accessSync(c, fs.constants.X_OK);
36
+ return c;
37
+ } catch {
38
+ /* continue */
39
+ }
40
+ }
41
+
42
+ return "claude"; // final fallback — let the OS resolve it
43
+ }
44
+
45
+ // ── File context ───────────────────────────────────────────────────────────────
46
+
47
+ /** Read a file, returning an XML-wrapped content block for Claude's context. */
48
+ export async function readFileForContext(absPath: string): Promise<string> {
49
+ try {
50
+ const content = await fs.promises.readFile(absPath, "utf-8");
51
+ return `<file path="${absPath}">\n${content}\n</file>`;
52
+ } catch (err) {
53
+ return `<file path="${absPath}" error="${(err as Error).message}" />`;
54
+ }
55
+ }
package/src/execute.ts ADDED
@@ -0,0 +1,182 @@
1
+ import { spawn } from "node:child_process";
2
+ import * as path from "node:path";
3
+ import { readFileForContext } from "./cli";
4
+ import type { ClaudeProxyDetails, ToolCallRecord } from "./types";
5
+
6
+ // ── Execute ────────────────────────────────────────────────────────────────────
7
+
8
+ export async function execute(
9
+ claudePath: string,
10
+ params: any,
11
+ signal: AbortSignal | undefined,
12
+ onUpdate: ((update: { content: Array<{ type: "text"; text: string }>; details?: any }) => void) | undefined,
13
+ ctx: any,
14
+ ): Promise<{ content: Array<{ type: string; text: string }>; details: ClaudeProxyDetails; isError: boolean }> {
15
+ const workDir = params.cwd ?? ctx.cwd;
16
+
17
+ // ----- 1. Build context from requested files -----
18
+ let fullPrompt = params.prompt;
19
+ const filesInjected: string[] = [];
20
+
21
+ if (params.files && params.files.length > 0) {
22
+ const parts: string[] = [];
23
+ for (const filePath of params.files) {
24
+ const absPath = path.isAbsolute(filePath) ? filePath : path.join(workDir, filePath);
25
+ filesInjected.push(absPath);
26
+ parts.push(await readFileForContext(absPath));
27
+ }
28
+ fullPrompt = `${parts.join("\n")}\n\n${fullPrompt}`;
29
+ }
30
+
31
+ // ----- 2. Build CLI arguments -----
32
+ const args: string[] = [
33
+ "--print",
34
+ "--output-format",
35
+ "stream-json",
36
+ "--verbose",
37
+ "--include-partial-messages",
38
+ "--input-format",
39
+ "text",
40
+ "--dangerously-skip-permissions",
41
+ "--no-session-persistence",
42
+ ];
43
+
44
+ if (params.model) args.push("--model", params.model);
45
+ if (params.systemPrompt) args.push("--system-prompt", params.systemPrompt);
46
+
47
+ const tools = params.allowedTools ?? ["Read"];
48
+ if (tools.length === 0) {
49
+ args.push("--tools", "");
50
+ } else {
51
+ args.push("--allowed-tools", ...tools);
52
+ }
53
+
54
+ // ----- 3. Spawn Claude -----
55
+ const details: ClaudeProxyDetails = {
56
+ streaming: true,
57
+ toolCalls: [],
58
+ prompt: params.prompt,
59
+ filesInjected: filesInjected.length > 0 ? filesInjected : undefined,
60
+ };
61
+
62
+ const pendingToolCalls = new Map<string, ToolCallRecord>();
63
+ let finalText = "";
64
+ let latestPartialText = "";
65
+ let spawnError = "";
66
+
67
+ const emitUpdate = () => {
68
+ onUpdate?.({
69
+ content: [{ type: "text" as const, text: latestPartialText || "(waiting for Claude…)" }],
70
+ details: { ...details },
71
+ });
72
+ };
73
+
74
+ const exitCode = await new Promise<number>((resolve) => {
75
+ const proc = spawn(claudePath, args, {
76
+ cwd: workDir,
77
+ shell: false,
78
+ stdio: ["pipe", "pipe", "pipe"],
79
+ env: { ...process.env },
80
+ });
81
+
82
+ proc.stdin.write(fullPrompt, "utf-8");
83
+ proc.stdin.end();
84
+
85
+ let buffer = "";
86
+
87
+ const processLine = (line: string) => {
88
+ if (!line.trim()) return;
89
+ let event: Record<string, unknown>;
90
+ try {
91
+ event = JSON.parse(line) as Record<string, unknown>;
92
+ } catch {
93
+ return;
94
+ }
95
+
96
+ switch (event.type) {
97
+ case "system": {
98
+ if ((event as any).session_id) details.sessionId = (event as any).session_id as string;
99
+ break;
100
+ }
101
+ case "assistant": {
102
+ const msg = (event as any).message as any;
103
+ if (!msg?.content) break;
104
+ for (const block of msg.content as any[]) {
105
+ if (block.type === "text") {
106
+ latestPartialText = block.text as string;
107
+ emitUpdate();
108
+ } else if (block.type === "tool_use") {
109
+ const record: ToolCallRecord = { id: block.id, name: block.name, input: block.input ?? {} };
110
+ pendingToolCalls.set(record.id, record);
111
+ details.toolCalls = [...details.toolCalls, record];
112
+ emitUpdate();
113
+ }
114
+ }
115
+ if (msg.model && !details.model) details.model = msg.model as string;
116
+ break;
117
+ }
118
+ case "tool_result": {
119
+ const id = (event as any).tool_use_id as string;
120
+ const record = pendingToolCalls.get(id);
121
+ if (record) {
122
+ const raw = (event as any).content;
123
+ record.result = typeof raw === "string" ? raw : JSON.stringify(raw);
124
+ record.isError = Boolean((event as any).is_error);
125
+ details.toolCalls = details.toolCalls.map((t) => (t.id === id ? { ...record } : t));
126
+ emitUpdate();
127
+ }
128
+ break;
129
+ }
130
+ case "result": {
131
+ finalText = ((event as any).result as string) ?? latestPartialText;
132
+ details.costUsd = (event as any).total_cost_usd as number;
133
+ details.streaming = false;
134
+ if ((event as any).subtype !== "success" || (event as any).is_error) {
135
+ spawnError = ((event as any).error as string) || finalText || "Claude reported an error";
136
+ }
137
+ break;
138
+ }
139
+ }
140
+ };
141
+
142
+ proc.stdout.on("data", (chunk) => {
143
+ buffer += chunk.toString("utf-8");
144
+ const lines = buffer.split("\n");
145
+ buffer = lines.pop() ?? "";
146
+ for (const line of lines) processLine(line);
147
+ });
148
+
149
+ proc.stderr.on("data", (chunk) => {
150
+ spawnError += chunk.toString("utf-8");
151
+ });
152
+
153
+ proc.on("close", (code) => {
154
+ if (buffer.trim()) processLine(buffer);
155
+ details.exitCode = code ?? 0;
156
+ resolve(code ?? 0);
157
+ });
158
+
159
+ proc.on("error", (err) => {
160
+ spawnError = `Failed to spawn claude CLI: ${err.message}\n\nMake sure 'claude' is on your PATH or set the CLAUDE_PATH environment variable.`;
161
+ details.exitCode = 1;
162
+ resolve(1);
163
+ });
164
+
165
+ if (signal) {
166
+ const kill = () => {
167
+ proc.kill("SIGTERM");
168
+ setTimeout(() => {
169
+ if (!proc.killed) proc.kill("SIGKILL");
170
+ }, 5000);
171
+ };
172
+ if (signal.aborted) kill();
173
+ else signal.addEventListener("abort", kill, { once: true });
174
+ }
175
+ });
176
+
177
+ // ----- 4. Return result -----
178
+ const isError = exitCode !== 0 || Boolean(spawnError && !finalText);
179
+ const responseText = isError ? spawnError || "(claude exited with no output)" : finalText || "(no output)";
180
+
181
+ return { content: [{ type: "text" as const, text: responseText }], details, isError };
182
+ }
package/src/format.ts ADDED
@@ -0,0 +1,30 @@
1
+ import * as os from "node:os";
2
+
3
+ // ── Format helpers ─────────────────────────────────────────────────────────────
4
+
5
+ /** Format a tool call for single-line display in the TUI. */
6
+ export function formatToolCall(name: string, input: Record<string, unknown>): string {
7
+ const shortenPath = (p: string) => {
8
+ const home = os.homedir();
9
+ return p.startsWith(home) ? `~${p.slice(home.length)}` : p;
10
+ };
11
+ switch (name) {
12
+ case "Read": {
13
+ const p = shortenPath(String(input.file_path ?? input.path ?? "..."));
14
+ return `read ${p}`;
15
+ }
16
+ case "Bash": {
17
+ const cmd = String(input.command ?? "...");
18
+ return `$ ${cmd.length > 60 ? `${cmd.slice(0, 60)}…` : cmd}`;
19
+ }
20
+ case "Edit":
21
+ case "Write": {
22
+ const p = shortenPath(String(input.file_path ?? input.path ?? "..."));
23
+ return `${name.toLowerCase()} ${p}`;
24
+ }
25
+ default: {
26
+ const s = JSON.stringify(input);
27
+ return `${name} ${s.length > 50 ? `${s.slice(0, 50)}…` : s}`;
28
+ }
29
+ }
30
+ }
package/src/index.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * pi-claude-proxy
3
+ *
4
+ * Registers a `claude_proxy` tool that lets the pi agent delegate tasks
5
+ * to Claude Code CLI running in non-interactive (--print) mode.
6
+ *
7
+ * Typical uses: code review, security audit, diff analysis, documentation,
8
+ * second-opinion reasoning.
9
+ *
10
+ * Requires: `claude` CLI on PATH (Claude Code ≥ 2.x)
11
+ * Optional: set CLAUDE_PATH env var to point to a specific claude binary.
12
+ */
13
+
14
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
15
+ import { Type } from "typebox";
16
+ import { findClaudeCli } from "./cli";
17
+ import { execute } from "./execute";
18
+ import { renderCall, renderResult } from "./render";
19
+
20
+ // ── Extension ──────────────────────────────────────────────────────────────────
21
+
22
+ export default function claudeProxyExtension(pi: ExtensionAPI): void {
23
+ const claudePath = findClaudeCli();
24
+
25
+ pi.registerTool({
26
+ name: "claude_proxy",
27
+ label: "Claude Proxy",
28
+ description: [
29
+ "Delegate a task to Claude Code CLI running in non-interactive mode.",
30
+ "Best for: code review, security audit, diff analysis, documentation, architectural second opinion.",
31
+ "Claude can explore the codebase autonomously via its Read tool (default).",
32
+ "Supply files[] to inject specific files directly into context.",
33
+ ].join(" "),
34
+ promptSnippet:
35
+ "Delegate a task to Claude Code — triggers on 'ask claude', 'check with claude', 'have claude look at', 'get claude to', or requests for code review, security audit, second opinion",
36
+ promptGuidelines: [
37
+ "Use claude_proxy when the user says 'ask claude', 'check with claude', 'have claude look at this', 'get claude to', 'claude should', or any similar phrase that directs a task explicitly to Claude.",
38
+ "Use claude_proxy when the user asks for a code review, security review, architectural review, or wants a Claude second-opinion on a piece of code.",
39
+ "Use claude_proxy with a tailored systemPrompt to give Claude a specific expert persona (e.g. 'You are a senior security engineer').",
40
+ "Pass relevant file paths in the files[] parameter so Claude has precise context without needing to hunt for them.",
41
+ ],
42
+
43
+ parameters: Type.Object({
44
+ prompt: Type.String({
45
+ description:
46
+ "The task or question for Claude. Be specific: include file names, what to look for, expected output format.",
47
+ }),
48
+ systemPrompt: Type.Optional(
49
+ Type.String({
50
+ description:
51
+ "Expert persona / role for Claude. E.g. 'You are a senior security engineer specialising in Node.js. Identify vulnerabilities and rate their severity.'",
52
+ }),
53
+ ),
54
+ model: Type.Optional(
55
+ Type.String({
56
+ description:
57
+ "Claude model alias: 'sonnet' (default), 'opus' (best reasoning), 'haiku' (fast/cheap). Or a full model name.",
58
+ }),
59
+ ),
60
+ allowedTools: Type.Optional(
61
+ Type.Array(Type.String(), {
62
+ description:
63
+ 'Tools Claude may use during its response. Defaults to ["Read"] for safe read-only exploration. Pass [] to disable all tools.',
64
+ }),
65
+ ),
66
+ files: Type.Optional(
67
+ Type.Array(Type.String(), {
68
+ description:
69
+ "File paths (relative to cwd) whose full contents are injected into Claude's context before the prompt.",
70
+ }),
71
+ ),
72
+ cwd: Type.Optional(
73
+ Type.String({
74
+ description: "Working directory for the Claude process. Defaults to pi's current working directory.",
75
+ }),
76
+ ),
77
+ }),
78
+
79
+ async execute(_toolCallId, params, signal, onUpdate, ctx) {
80
+ return execute(claudePath, params, signal, onUpdate as any, ctx) as any;
81
+ },
82
+
83
+ renderCall,
84
+ renderResult,
85
+ });
86
+ }
package/src/render.ts ADDED
@@ -0,0 +1,96 @@
1
+ import { getMarkdownTheme } from "@mariozechner/pi-coding-agent";
2
+ import { Container, Markdown, Spacer, Text } from "@mariozechner/pi-tui";
3
+ import { formatToolCall } from "./format";
4
+ import type { ClaudeProxyDetails } from "./types";
5
+
6
+ // ── TUI rendering ──────────────────────────────────────────────────────────────
7
+
8
+ export function renderCall(args: any, theme: any): any {
9
+ const prompt = args.prompt ?? "";
10
+ const preview = prompt.length > 80 ? `${prompt.slice(0, 80)}…` : prompt;
11
+ const model = args.model ?? "sonnet";
12
+ const toolsLabel = args.allowedTools
13
+ ? (args.allowedTools as string[]).length === 0
14
+ ? "no tools"
15
+ : (args.allowedTools as string[]).join(", ")
16
+ : "Read";
17
+
18
+ let text =
19
+ theme.fg("toolTitle", theme.bold("claude_proxy ")) +
20
+ theme.fg("accent", `[${model}]`) +
21
+ theme.fg("muted", ` tools: ${toolsLabel}`);
22
+
23
+ if (args.systemPrompt) {
24
+ const rolePreview = (args.systemPrompt as string).slice(0, 50);
25
+ text += `\n ${theme.fg("muted", "role: ")}${theme.fg("dim", rolePreview)}`;
26
+ }
27
+
28
+ text += `\n ${theme.fg("dim", preview)}`;
29
+
30
+ if (args.files && (args.files as string[]).length > 0) {
31
+ text += `\n ${theme.fg("muted", "files: ")}${theme.fg("dim", (args.files as string[]).join(", "))}`;
32
+ }
33
+
34
+ return new Text(text, 0, 0);
35
+ }
36
+
37
+ export function renderResult(result: any, { expanded }: any, theme: any): any {
38
+ const details = result.details as ClaudeProxyDetails | undefined;
39
+ const text = result.content[0]?.type === "text" ? result.content[0].text : "(no output)";
40
+
41
+ const metaParts: string[] = [];
42
+ if (details?.model) metaParts.push(details.model);
43
+ if (details?.costUsd) metaParts.push(`$${details.costUsd.toFixed(4)}`);
44
+ const meta = metaParts.map((p) => theme.fg("dim", p)).join(" ");
45
+
46
+ if (result.isError) {
47
+ const errText = details?.streaming ? "(aborted)" : text;
48
+ return new Text(
49
+ theme.fg("error", "✗ ") +
50
+ theme.fg("toolTitle", theme.bold("Claude")) +
51
+ (meta ? ` ${meta}` : "") +
52
+ `\n${theme.fg("error", errText)}`,
53
+ 0,
54
+ 0,
55
+ );
56
+ }
57
+
58
+ const icon = details?.streaming ? theme.fg("warning", "⏳") : theme.fg("success", "✓");
59
+ const headerLine = `${icon} ${theme.fg("toolTitle", theme.bold("Claude"))}${meta ? ` ${meta}` : ""}`;
60
+
61
+ const toolCallLines = (details?.toolCalls ?? []).map(
62
+ (tc) =>
63
+ ` ${theme.fg("muted", "→ ")}${theme.fg("accent", tc.name)}${theme.fg("dim", ` ${formatToolCall(tc.name, tc.input)}`)}${tc.isError ? ` ${theme.fg("error", "[error]")}` : ""}`,
64
+ );
65
+
66
+ if (expanded) {
67
+ const mdTheme = getMarkdownTheme();
68
+ const container = new Container();
69
+ container.addChild(new Text(headerLine, 0, 0));
70
+ if (toolCallLines.length > 0) {
71
+ container.addChild(new Spacer(1));
72
+ container.addChild(new Text(theme.fg("muted", "─── Tool calls ───"), 0, 0));
73
+ for (const tl of toolCallLines) container.addChild(new Text(tl, 0, 0));
74
+ }
75
+ if (text) {
76
+ container.addChild(new Spacer(1));
77
+ container.addChild(new Text(theme.fg("muted", "─── Response ───"), 0, 0));
78
+ container.addChild(new Markdown(text.trim(), 0, 0, mdTheme));
79
+ }
80
+ return container;
81
+ }
82
+
83
+ // Collapsed view
84
+ const previewLines = text.split("\n").slice(0, 6);
85
+ const previewText = previewLines.join("\n") + (text.split("\n").length > 6 ? "\n…" : "");
86
+
87
+ let out = headerLine;
88
+ if (toolCallLines.length > 0) {
89
+ out += `\n${toolCallLines.slice(0, 3).join("\n")}`;
90
+ if (toolCallLines.length > 3) out += `\n ${theme.fg("muted", `… +${toolCallLines.length - 3} more`)}`;
91
+ }
92
+ if (text) out += `\n${theme.fg("toolOutput", previewText)}`;
93
+ out += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
94
+
95
+ return new Text(out, 0, 0);
96
+ }
package/src/types.ts ADDED
@@ -0,0 +1,20 @@
1
+ // ── Types ──────────────────────────────────────────────────────────────────────
2
+
3
+ export interface ToolCallRecord {
4
+ id: string;
5
+ name: string;
6
+ input: Record<string, unknown>;
7
+ result?: string;
8
+ isError?: boolean;
9
+ }
10
+
11
+ export interface ClaudeProxyDetails {
12
+ sessionId?: string;
13
+ model?: string;
14
+ costUsd?: number;
15
+ streaming: boolean;
16
+ toolCalls: ToolCallRecord[];
17
+ exitCode?: number;
18
+ prompt?: string;
19
+ filesInjected?: string[];
20
+ }