linkshell-cli 0.2.92 → 0.2.94
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/src/runtime/acp/agent-session.js +21 -9
- package/dist/cli/src/runtime/acp/agent-session.js.map +1 -1
- package/dist/cli/src/runtime/acp/agent-workspace.js +21 -9
- package/dist/cli/src/runtime/acp/agent-workspace.js.map +1 -1
- package/dist/cli/src/runtime/acp/claude-stream-json-client.d.ts +50 -0
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js +295 -0
- package/dist/cli/src/runtime/acp/claude-stream-json-client.js.map +1 -0
- package/dist/cli/src/runtime/acp/provider-resolver.d.ts +1 -1
- package/dist/cli/src/runtime/acp/provider-resolver.js +3 -3
- package/dist/cli/src/runtime/acp/provider-resolver.js.map +1 -1
- package/dist/cli/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/runtime/acp/agent-session.ts +22 -10
- package/src/runtime/acp/agent-workspace.ts +24 -12
- package/src/runtime/acp/claude-stream-json-client.ts +372 -0
- package/src/runtime/acp/provider-resolver.ts +4 -4
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
type Envelope,
|
|
6
6
|
} from "@linkshell/protocol";
|
|
7
7
|
import { AcpClient } from "./acp-client.js";
|
|
8
|
+
import { ClaudeStreamJsonClient } from "./claude-stream-json-client.js";
|
|
8
9
|
import type { AgentProtocol, AgentProvider } from "./provider-resolver.js";
|
|
9
10
|
import { resolveAgentCommand } from "./provider-resolver.js";
|
|
10
11
|
|
|
@@ -657,7 +658,7 @@ function providerLabel(provider: AgentProvider): string {
|
|
|
657
658
|
}
|
|
658
659
|
|
|
659
660
|
export class AgentWorkspaceProxy {
|
|
660
|
-
private clients = new Map<AgentProvider, AcpClient>();
|
|
661
|
+
private clients = new Map<AgentProvider, AcpClient | ClaudeStreamJsonClient>();
|
|
661
662
|
private agentProtocols = new Map<AgentProvider, AgentProtocol>();
|
|
662
663
|
private initialized = false;
|
|
663
664
|
private status: AgentStatus = "unavailable";
|
|
@@ -753,7 +754,7 @@ export class AgentWorkspaceProxy {
|
|
|
753
754
|
this.clients.clear();
|
|
754
755
|
}
|
|
755
756
|
|
|
756
|
-
private clientForProvider(provider: AgentProvider): AcpClient | undefined {
|
|
757
|
+
private clientForProvider(provider: AgentProvider): AcpClient | ClaudeStreamJsonClient | undefined {
|
|
757
758
|
return this.clients.get(provider);
|
|
758
759
|
}
|
|
759
760
|
|
|
@@ -772,7 +773,7 @@ export class AgentWorkspaceProxy {
|
|
|
772
773
|
this.sendCapabilities();
|
|
773
774
|
}
|
|
774
775
|
|
|
775
|
-
private async ensureProviderClient(provider: AgentProvider): Promise<AcpClient | undefined> {
|
|
776
|
+
private async ensureProviderClient(provider: AgentProvider): Promise<AcpClient | ClaudeStreamJsonClient | undefined> {
|
|
776
777
|
const existing = this.clients.get(provider);
|
|
777
778
|
if (existing) return existing;
|
|
778
779
|
|
|
@@ -789,15 +790,26 @@ export class AgentWorkspaceProxy {
|
|
|
789
790
|
|
|
790
791
|
try {
|
|
791
792
|
this.agentProtocols.set(provider, resolved.protocol);
|
|
792
|
-
const
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
793
|
+
const isClaudeStreamJson = resolved.protocol === "claude-stream-json";
|
|
794
|
+
const client = isClaudeStreamJson
|
|
795
|
+
? new ClaudeStreamJsonClient({
|
|
796
|
+
command: resolved.command,
|
|
797
|
+
protocol: resolved.protocol,
|
|
798
|
+
framing: resolved.framing,
|
|
799
|
+
cwd: this.input.cwd,
|
|
800
|
+
onNotification: (method, params) => this.handleNotification(method, params),
|
|
801
|
+
onRequest: (method, params) => this.handleRequest(method, params),
|
|
802
|
+
onExit: (message) => this.handleProviderExit(provider, message),
|
|
803
|
+
})
|
|
804
|
+
: new AcpClient({
|
|
805
|
+
command: resolved.command,
|
|
806
|
+
protocol: resolved.protocol,
|
|
807
|
+
framing: resolved.framing,
|
|
808
|
+
cwd: this.input.cwd,
|
|
809
|
+
onNotification: (method, params) => this.handleNotification(method, params),
|
|
810
|
+
onRequest: (method, params) => this.handleRequest(method, params),
|
|
811
|
+
onExit: (message) => this.handleProviderExit(provider, message),
|
|
812
|
+
});
|
|
801
813
|
await client.initialize();
|
|
802
814
|
this.clients.set(provider, client);
|
|
803
815
|
this.status = "idle";
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { readdirSync, existsSync } from "node:fs";
|
|
5
|
+
import { join, basename, relative, resolve } from "node:path";
|
|
6
|
+
import type { AgentFraming, AgentProtocol } from "./provider-resolver.js";
|
|
7
|
+
|
|
8
|
+
type AgentPermissionMode = "read_only" | "workspace_write" | "full_access";
|
|
9
|
+
|
|
10
|
+
interface ClaudeStreamEvent {
|
|
11
|
+
type: string;
|
|
12
|
+
subtype?: string;
|
|
13
|
+
message?: Record<string, unknown>;
|
|
14
|
+
session_id?: string;
|
|
15
|
+
parent_tool_use_id?: string | null;
|
|
16
|
+
uuid?: string;
|
|
17
|
+
[key: string]: unknown;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ClaudeContentBlock {
|
|
21
|
+
type: string;
|
|
22
|
+
text?: string;
|
|
23
|
+
thinking?: string;
|
|
24
|
+
id?: string;
|
|
25
|
+
name?: string;
|
|
26
|
+
input?: Record<string, unknown>;
|
|
27
|
+
tool_use_id?: string;
|
|
28
|
+
content?: string;
|
|
29
|
+
is_error?: boolean;
|
|
30
|
+
signature?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Hash a directory path the same way Claude Code does for project storage
|
|
34
|
+
function projectHash(cwd: string): string {
|
|
35
|
+
return (
|
|
36
|
+
"-" +
|
|
37
|
+
resolve(cwd)
|
|
38
|
+
.replace(/\/$/, "")
|
|
39
|
+
.replace(/\//g, "-")
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function id(prefix: string): string {
|
|
44
|
+
return `${prefix}-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class ClaudeStreamJsonClient {
|
|
48
|
+
private child: ChildProcessWithoutNullStreams | undefined;
|
|
49
|
+
private claudeSessionId: string | undefined;
|
|
50
|
+
private pendingCancel = false;
|
|
51
|
+
private messageBuffer = "";
|
|
52
|
+
|
|
53
|
+
constructor(
|
|
54
|
+
private readonly input: {
|
|
55
|
+
command: string;
|
|
56
|
+
protocol: AgentProtocol;
|
|
57
|
+
framing: AgentFraming;
|
|
58
|
+
cwd: string;
|
|
59
|
+
onNotification: (method: string, params: unknown) => void;
|
|
60
|
+
onRequest: (method: string, params: unknown) => Promise<unknown> | unknown;
|
|
61
|
+
onExit: (message: string) => void;
|
|
62
|
+
},
|
|
63
|
+
) {}
|
|
64
|
+
|
|
65
|
+
async initialize(): Promise<unknown> {
|
|
66
|
+
// No persistent process to start — we'll capture session on first prompt.
|
|
67
|
+
// But we can verify the binary exists by running a quick --help.
|
|
68
|
+
try {
|
|
69
|
+
const { execSync } = await import("node:child_process");
|
|
70
|
+
execSync("claude --version", { stdio: "ignore", timeout: 5000 });
|
|
71
|
+
} catch {
|
|
72
|
+
throw new Error("Claude Code CLI not found or not executable");
|
|
73
|
+
}
|
|
74
|
+
return { status: "ok" };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async newSession(input: { cwd: string; mcpServers?: unknown }): Promise<unknown> {
|
|
78
|
+
// Start a dry-run prompt to get a session_id, then cancel.
|
|
79
|
+
// Actually, we just store that there's no session yet — the real session
|
|
80
|
+
// will be created on the first prompt() call.
|
|
81
|
+
this.claudeSessionId = undefined;
|
|
82
|
+
return { sessionId: undefined, status: "ready" };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async loadSession(input: { sessionId: string; cwd: string; mcpServers?: unknown }): Promise<unknown> {
|
|
86
|
+
this.claudeSessionId = input.sessionId;
|
|
87
|
+
return { sessionId: input.sessionId, status: "loaded" };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async prompt(input: {
|
|
91
|
+
sessionId?: string;
|
|
92
|
+
content: unknown[];
|
|
93
|
+
clientMessageId: string;
|
|
94
|
+
model?: string;
|
|
95
|
+
reasoningEffort?: string;
|
|
96
|
+
permissionMode?: AgentPermissionMode;
|
|
97
|
+
cwd: string;
|
|
98
|
+
}): Promise<unknown> {
|
|
99
|
+
if (this.child) {
|
|
100
|
+
this.stop();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
this.pendingCancel = false;
|
|
104
|
+
|
|
105
|
+
// Build Claude args
|
|
106
|
+
const args: string[] = [
|
|
107
|
+
"--print",
|
|
108
|
+
"--output-format", "stream-json",
|
|
109
|
+
"--input-format", "stream-json",
|
|
110
|
+
"--verbose",
|
|
111
|
+
"--permission-mode", "bypassPermissions",
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
// Use stored session for --continue or --resume
|
|
115
|
+
if (input.sessionId || this.claudeSessionId) {
|
|
116
|
+
const sid = input.sessionId ?? this.claudeSessionId;
|
|
117
|
+
if (sid) {
|
|
118
|
+
args.push("--resume", sid);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (input.model) {
|
|
123
|
+
args.push("--model", input.model);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Build the user message
|
|
127
|
+
const contentBlocks = (input.content as Array<{ type: string; text?: string; data?: string; mimeType?: string }>).map(
|
|
128
|
+
(block) => {
|
|
129
|
+
if (block.type === "image" && block.data) {
|
|
130
|
+
return { type: "image", source: { type: "base64", media_type: block.mimeType ?? "image/png", data: block.data } };
|
|
131
|
+
}
|
|
132
|
+
return { type: "text", text: block.text ?? "" };
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const userMessage = {
|
|
137
|
+
type: "user",
|
|
138
|
+
message: {
|
|
139
|
+
role: "user",
|
|
140
|
+
content: contentBlocks,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return new Promise((resolve, reject) => {
|
|
145
|
+
const cwd = input.cwd ?? this.input.cwd;
|
|
146
|
+
const child = spawn("claude", args, {
|
|
147
|
+
cwd,
|
|
148
|
+
env: process.env,
|
|
149
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
this.child = child;
|
|
153
|
+
let settled = false;
|
|
154
|
+
|
|
155
|
+
const finish = (err: Error | null, result: unknown) => {
|
|
156
|
+
if (settled) return;
|
|
157
|
+
settled = true;
|
|
158
|
+
this.child = undefined;
|
|
159
|
+
if (err) {
|
|
160
|
+
this.input.onExit(err.message);
|
|
161
|
+
reject(err);
|
|
162
|
+
} else {
|
|
163
|
+
resolve(result);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Send the prompt
|
|
168
|
+
child.stdin.write(JSON.stringify(userMessage) + "\n");
|
|
169
|
+
child.stdin.end();
|
|
170
|
+
|
|
171
|
+
// Read stdout line by line (stream-json is newline-delimited)
|
|
172
|
+
const rl = createInterface({ input: child.stdout, crlfDelay: Infinity });
|
|
173
|
+
let currentToolId: string | undefined;
|
|
174
|
+
|
|
175
|
+
rl.on("line", (line: string) => {
|
|
176
|
+
if (this.pendingCancel) {
|
|
177
|
+
child.kill("SIGTERM");
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let event: ClaudeStreamEvent;
|
|
182
|
+
try {
|
|
183
|
+
event = JSON.parse(line);
|
|
184
|
+
} catch {
|
|
185
|
+
return; // skip unparseable lines
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
switch (event.type) {
|
|
189
|
+
case "system": {
|
|
190
|
+
if (event.subtype === "init") {
|
|
191
|
+
// Capture session ID
|
|
192
|
+
if (event.session_id) {
|
|
193
|
+
this.claudeSessionId = event.session_id;
|
|
194
|
+
}
|
|
195
|
+
// Send as initialized notification — workspace needs this
|
|
196
|
+
const initParams: Record<string, unknown> = {
|
|
197
|
+
sessionId: event.session_id,
|
|
198
|
+
cwd: event.cwd ?? cwd,
|
|
199
|
+
model: event.model,
|
|
200
|
+
};
|
|
201
|
+
if (event.tools) initParams.tools = event.tools;
|
|
202
|
+
if (event.mcp_servers) initParams.mcpServers = event.mcp_servers;
|
|
203
|
+
this.input.onNotification("initialized", initParams);
|
|
204
|
+
}
|
|
205
|
+
// Hook events and other system messages are informational, skip
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
case "assistant": {
|
|
210
|
+
const message = event.message;
|
|
211
|
+
if (!message) break;
|
|
212
|
+
const content = (message.content ?? []) as ClaudeContentBlock[];
|
|
213
|
+
|
|
214
|
+
for (const block of content) {
|
|
215
|
+
switch (block.type) {
|
|
216
|
+
case "thinking":
|
|
217
|
+
this.input.onNotification("item/started", {
|
|
218
|
+
sessionId: this.claudeSessionId,
|
|
219
|
+
item: {
|
|
220
|
+
id: event.uuid ?? id("thinking"),
|
|
221
|
+
type: "thinking",
|
|
222
|
+
text: block.thinking,
|
|
223
|
+
status: "completed",
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
break;
|
|
227
|
+
|
|
228
|
+
case "text":
|
|
229
|
+
this.input.onNotification("item/agentMessage/delta", {
|
|
230
|
+
sessionId: this.claudeSessionId,
|
|
231
|
+
itemId: message.id ?? event.uuid ?? id("msg"),
|
|
232
|
+
delta: block.text,
|
|
233
|
+
});
|
|
234
|
+
break;
|
|
235
|
+
|
|
236
|
+
case "tool_use": {
|
|
237
|
+
currentToolId = block.id;
|
|
238
|
+
const toolName = block.name ?? "tool";
|
|
239
|
+
this.input.onNotification("item/started", {
|
|
240
|
+
sessionId: this.claudeSessionId,
|
|
241
|
+
item: {
|
|
242
|
+
id: block.id ?? id("tool"),
|
|
243
|
+
type: toolName === "Bash" ? "commandExecution" : toolName === "Write" || toolName === "Edit" ? "fileChange" : "toolCall",
|
|
244
|
+
toolName: block.name,
|
|
245
|
+
tool: block.name,
|
|
246
|
+
input: block.input,
|
|
247
|
+
command: block.input?.command as string | undefined,
|
|
248
|
+
cwd: block.input?.cwd as string | undefined ?? cwd,
|
|
249
|
+
status: "running",
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
case "tool_result":
|
|
256
|
+
// tool_result comes in user messages, not assistant
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
case "user": {
|
|
264
|
+
// User messages in stream-json contain tool results
|
|
265
|
+
const message = event.message;
|
|
266
|
+
if (!message) break;
|
|
267
|
+
const content = (message.content ?? []) as ClaudeContentBlock[];
|
|
268
|
+
|
|
269
|
+
for (const block of content) {
|
|
270
|
+
if (block.type === "tool_result") {
|
|
271
|
+
const toolId = block.tool_use_id ?? currentToolId;
|
|
272
|
+
const isError = block.is_error === true;
|
|
273
|
+
this.input.onNotification("item/completed", {
|
|
274
|
+
sessionId: this.claudeSessionId,
|
|
275
|
+
item: {
|
|
276
|
+
id: toolId ?? id("tool"),
|
|
277
|
+
type: "toolCall",
|
|
278
|
+
status: isError ? "failed" : "completed",
|
|
279
|
+
output: typeof block.content === "string" ? block.content : JSON.stringify(block.content),
|
|
280
|
+
aggregatedOutput: typeof block.content === "string" ? block.content : undefined,
|
|
281
|
+
isError,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
case "result": {
|
|
290
|
+
// Turn complete
|
|
291
|
+
const isError = event.subtype === "error" || event.is_error === true;
|
|
292
|
+
this.input.onNotification("turn/completed", {
|
|
293
|
+
sessionId: this.claudeSessionId,
|
|
294
|
+
stopReason: event.stop_reason ?? (isError ? "error" : "end_turn"),
|
|
295
|
+
durationMs: event.duration_ms,
|
|
296
|
+
totalCostUsd: event.total_cost_usd,
|
|
297
|
+
usage: event.usage,
|
|
298
|
+
isError,
|
|
299
|
+
});
|
|
300
|
+
finish(null, { sessionId: this.claudeSessionId, status: isError ? "error" : "completed" });
|
|
301
|
+
break;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
child.stderr.on("data", (chunk: Buffer) => {
|
|
307
|
+
const trimmed = chunk.toString().trim();
|
|
308
|
+
if (trimmed) process.stderr.write(`[claude:stderr] ${trimmed}\n`);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
child.on("error", (err) => {
|
|
312
|
+
finish(err, undefined);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
child.on("exit", (code, signal) => {
|
|
316
|
+
if (!settled) {
|
|
317
|
+
finish(
|
|
318
|
+
new Error(`Claude exited unexpectedly (code=${code ?? "null"}, signal=${signal ?? "null"})`),
|
|
319
|
+
undefined,
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
cancel(input: { sessionId?: string; turnId?: string }): void {
|
|
327
|
+
this.pendingCancel = true;
|
|
328
|
+
if (this.child && !this.child.killed) {
|
|
329
|
+
this.child.kill("SIGTERM");
|
|
330
|
+
}
|
|
331
|
+
this.child = undefined;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
respondPermission(input: { sessionId?: string; requestId: string; outcome: "allow" | "deny"; optionId?: string }): void {
|
|
335
|
+
// In bypassPermissions mode, permission requests don't occur.
|
|
336
|
+
// If we later switch to hook-based permissions, we'd send the response via a hook.
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async listSessions(): Promise<unknown> {
|
|
340
|
+
const home = homedir();
|
|
341
|
+
const projectDir = join(home, ".claude", "projects", projectHash(this.input.cwd));
|
|
342
|
+
|
|
343
|
+
if (!existsSync(projectDir)) {
|
|
344
|
+
return { sessions: [] };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const sessions: Array<{ id: string; cwd: string; lastModified: number }> = [];
|
|
348
|
+
try {
|
|
349
|
+
for (const entry of readdirSync(projectDir)) {
|
|
350
|
+
if (entry.endsWith(".jsonl")) {
|
|
351
|
+
const sessionId = entry.replace(".jsonl", "");
|
|
352
|
+
sessions.push({
|
|
353
|
+
id: sessionId,
|
|
354
|
+
cwd: this.input.cwd,
|
|
355
|
+
lastModified: 0, // would need fs.statSync for accurate time
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
} catch {
|
|
360
|
+
// directory read failed
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return { sessions };
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
stop(): void {
|
|
367
|
+
if (this.child && !this.child.killed) {
|
|
368
|
+
this.child.kill("SIGTERM");
|
|
369
|
+
}
|
|
370
|
+
this.child = undefined;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
4
|
|
|
5
5
|
export type AgentProvider = "codex" | "claude" | "custom";
|
|
6
|
-
export type AgentProtocol = "acp" | "codex-app-server";
|
|
6
|
+
export type AgentProtocol = "acp" | "codex-app-server" | "claude-stream-json";
|
|
7
7
|
export type AgentFraming = "content-length" | "newline";
|
|
8
8
|
|
|
9
9
|
export interface AgentCommandConfig {
|
|
@@ -40,9 +40,9 @@ export function resolveAgentCommand(input: {
|
|
|
40
40
|
if (input.provider === "claude") {
|
|
41
41
|
return {
|
|
42
42
|
provider: "claude",
|
|
43
|
-
command: "claude --
|
|
44
|
-
protocol: "
|
|
45
|
-
framing: "
|
|
43
|
+
command: "claude --print --output-format stream-json --input-format stream-json --verbose --permission-mode bypassPermissions",
|
|
44
|
+
protocol: "claude-stream-json",
|
|
45
|
+
framing: "newline",
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
|