@punkcode/cli 0.1.8 → 0.1.9
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.js +259 -77
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,24 @@ import { execaSync as execaSync2 } from "execa";
|
|
|
12
12
|
|
|
13
13
|
// src/lib/claude-sdk.ts
|
|
14
14
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
15
|
+
import { readdir, readFile } from "fs/promises";
|
|
16
|
+
import { join } from "path";
|
|
17
|
+
import { homedir } from "os";
|
|
18
|
+
|
|
19
|
+
// src/utils/logger.ts
|
|
20
|
+
import pino from "pino";
|
|
21
|
+
var level = process.env.LOG_LEVEL ?? "info";
|
|
22
|
+
var format = process.env.PUNK_LOG_FORMAT ?? (process.stdout.isTTY ? "pretty" : "json");
|
|
23
|
+
var transport = format === "pretty" ? pino.transport({
|
|
24
|
+
target: "pino-pretty",
|
|
25
|
+
options: { colorize: true }
|
|
26
|
+
}) : void 0;
|
|
27
|
+
var logger = pino({ level }, transport);
|
|
28
|
+
function createChildLogger(bindings) {
|
|
29
|
+
return logger.child(bindings);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// src/lib/claude-sdk.ts
|
|
15
33
|
async function* promptWithImages(text, images, sessionId) {
|
|
16
34
|
yield {
|
|
17
35
|
type: "user",
|
|
@@ -33,32 +51,100 @@ async function* promptWithImages(text, images, sessionId) {
|
|
|
33
51
|
session_id: sessionId
|
|
34
52
|
};
|
|
35
53
|
}
|
|
36
|
-
async function
|
|
54
|
+
async function loadGlobalSkills(cwd) {
|
|
55
|
+
const claudeDir = join(homedir(), ".claude");
|
|
56
|
+
const skills = [];
|
|
57
|
+
async function collectSkillsFromDir(dir) {
|
|
58
|
+
const result = [];
|
|
59
|
+
try {
|
|
60
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
|
|
63
|
+
try {
|
|
64
|
+
const md = await readFile(join(dir, entry.name, "SKILL.md"), "utf-8");
|
|
65
|
+
const fmMatch = md.match(/^---\n([\s\S]*?)(\n---|\n*$)/);
|
|
66
|
+
if (!fmMatch) continue;
|
|
67
|
+
const fm = fmMatch[1];
|
|
68
|
+
const nameMatch = fm.match(/^name:\s*(.+)$/m);
|
|
69
|
+
if (!nameMatch) continue;
|
|
70
|
+
let description = "";
|
|
71
|
+
const descMatch = fm.match(/^description:\s*(.+)$/m);
|
|
72
|
+
if (descMatch) {
|
|
73
|
+
description = descMatch[1].trim();
|
|
74
|
+
} else {
|
|
75
|
+
const blockMatch = fm.match(/^description:\s*\n((?:[ \t]+.+\n?)+)/m);
|
|
76
|
+
if (blockMatch) {
|
|
77
|
+
description = blockMatch[1].replace(/^[ \t]+/gm, "").trim().replace(/\n/g, " ");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
result.push({ name: nameMatch[1].trim(), description });
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
}
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
const globalSkills = await collectSkillsFromDir(join(claudeDir, "skills"));
|
|
89
|
+
const projectSkills = cwd ? await collectSkillsFromDir(join(cwd, ".claude", "skills")) : [];
|
|
90
|
+
const projectNames = new Set(projectSkills.map((s) => s.name));
|
|
91
|
+
for (const s of globalSkills) {
|
|
92
|
+
if (!projectNames.has(s.name)) {
|
|
93
|
+
skills.push(s);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
skills.push(...projectSkills);
|
|
97
|
+
try {
|
|
98
|
+
const settings = JSON.parse(await readFile(join(claudeDir, "settings.json"), "utf-8"));
|
|
99
|
+
const plugins = settings.enabledPlugins;
|
|
100
|
+
if (plugins && typeof plugins === "object") {
|
|
101
|
+
for (const [key, enabled] of Object.entries(plugins)) {
|
|
102
|
+
if (!enabled) continue;
|
|
103
|
+
const [name, source] = key.split("@");
|
|
104
|
+
if (!name) continue;
|
|
105
|
+
let description = "";
|
|
106
|
+
if (source) {
|
|
107
|
+
try {
|
|
108
|
+
const cacheDir = join(claudeDir, "plugins", "cache", source, name);
|
|
109
|
+
const versions = await readdir(cacheDir);
|
|
110
|
+
const latest = versions.filter((v) => !v.startsWith(".")).sort().pop();
|
|
111
|
+
if (latest) {
|
|
112
|
+
const md = await readFile(join(cacheDir, latest, "skills", name, "SKILL.md"), "utf-8");
|
|
113
|
+
const descMatch = md.match(/^description:\s*(.+)$/m);
|
|
114
|
+
if (descMatch) description = descMatch[1].trim();
|
|
115
|
+
}
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
skills.push({ name, description });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
return skills;
|
|
125
|
+
}
|
|
126
|
+
async function getProjectCommands(workingDirectory) {
|
|
37
127
|
const q = query({
|
|
38
|
-
prompt: "",
|
|
128
|
+
prompt: "/load-session-info",
|
|
39
129
|
options: {
|
|
40
|
-
|
|
41
|
-
...
|
|
130
|
+
persistSession: false,
|
|
131
|
+
...workingDirectory && { cwd: workingDirectory }
|
|
42
132
|
}
|
|
43
133
|
});
|
|
44
134
|
try {
|
|
45
|
-
const [
|
|
46
|
-
q.initializationResult(),
|
|
135
|
+
const [commands, skills] = await Promise.all([
|
|
47
136
|
q.supportedCommands(),
|
|
48
|
-
|
|
137
|
+
loadGlobalSkills(workingDirectory)
|
|
49
138
|
]);
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
claudeCodeVersion: "",
|
|
60
|
-
permissionMode: "default"
|
|
61
|
-
};
|
|
139
|
+
const slashCommands = commands.map((c) => ({ name: c.name, description: c.description }));
|
|
140
|
+
const knownNames = new Set(slashCommands.map((c) => c.name));
|
|
141
|
+
for (const skill of skills) {
|
|
142
|
+
if (!knownNames.has(skill.name)) {
|
|
143
|
+
slashCommands.push(skill);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
logger.info({ commands: slashCommands.length }, "Project commands retrieved");
|
|
147
|
+
return slashCommands;
|
|
62
148
|
} finally {
|
|
63
149
|
q.close();
|
|
64
150
|
}
|
|
@@ -80,7 +166,7 @@ function runClaude(options, callbacks) {
|
|
|
80
166
|
...opts.disallowedTools && { disallowedTools: opts.disallowedTools },
|
|
81
167
|
...opts.maxTurns && { maxTurns: opts.maxTurns },
|
|
82
168
|
systemPrompt: opts.systemPrompt ?? { type: "preset", preset: "claude_code" },
|
|
83
|
-
...options.
|
|
169
|
+
...options.workingDirectory && { cwd: options.workingDirectory },
|
|
84
170
|
...options.sessionId && { resume: options.sessionId },
|
|
85
171
|
maxThinkingTokens: 1e4,
|
|
86
172
|
includePartialMessages: true,
|
|
@@ -179,25 +265,27 @@ function runClaude(options, callbacks) {
|
|
|
179
265
|
case "system": {
|
|
180
266
|
const sys = message;
|
|
181
267
|
if (sys.subtype === "init" && callbacks.onSessionCreated) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
268
|
+
const initCommands = (sys.slash_commands ?? []).map((cmd) => ({ name: cmd, description: "" }));
|
|
269
|
+
const globalSkills = await loadGlobalSkills(sys.cwd);
|
|
270
|
+
const knownNames = new Set(initCommands.map((c) => c.name));
|
|
271
|
+
for (const skill of globalSkills) {
|
|
272
|
+
if (!knownNames.has(skill.name)) {
|
|
273
|
+
initCommands.push(skill);
|
|
187
274
|
}
|
|
188
|
-
} catch {
|
|
189
275
|
}
|
|
190
|
-
|
|
276
|
+
const sessionInfo = {
|
|
191
277
|
sessionId: sys.session_id ?? "",
|
|
192
278
|
tools: sys.tools ?? [],
|
|
193
|
-
slashCommands,
|
|
279
|
+
slashCommands: initCommands,
|
|
194
280
|
skills: sys.skills ?? [],
|
|
195
281
|
mcpServers: sys.mcp_servers ?? [],
|
|
196
282
|
model: sys.model ?? "",
|
|
197
|
-
|
|
283
|
+
workingDirectory: sys.cwd ?? "",
|
|
198
284
|
claudeCodeVersion: sys.claude_code_version ?? "",
|
|
199
285
|
permissionMode: sys.permissionMode ?? "default"
|
|
200
|
-
}
|
|
286
|
+
};
|
|
287
|
+
logger.info({ sessionId: sessionInfo.sessionId, commands: sessionInfo.slashCommands.length }, "New chat session info");
|
|
288
|
+
callbacks.onSessionCreated(sessionInfo);
|
|
201
289
|
}
|
|
202
290
|
break;
|
|
203
291
|
}
|
|
@@ -273,7 +361,7 @@ function collectDeviceInfo(deviceId, customName, customTags) {
|
|
|
273
361
|
arch: process.arch,
|
|
274
362
|
username: os.userInfo().username,
|
|
275
363
|
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
276
|
-
|
|
364
|
+
defaultWorkingDirectory: process.cwd(),
|
|
277
365
|
model: getModel(),
|
|
278
366
|
cpuModel: cpus.length > 0 ? cpus[0].model : "Unknown",
|
|
279
367
|
memoryGB: Math.round(os.totalmem() / 1024 ** 3),
|
|
@@ -385,10 +473,10 @@ function getModel() {
|
|
|
385
473
|
}
|
|
386
474
|
|
|
387
475
|
// src/lib/session.ts
|
|
388
|
-
import { readdir, readFile, stat, open } from "fs/promises";
|
|
389
|
-
import { join } from "path";
|
|
390
|
-
import { homedir } from "os";
|
|
391
|
-
var CLAUDE_DIR =
|
|
476
|
+
import { readdir as readdir2, readFile as readFile2, stat, open } from "fs/promises";
|
|
477
|
+
import { join as join2 } from "path";
|
|
478
|
+
import { homedir as homedir2 } from "os";
|
|
479
|
+
var CLAUDE_DIR = join2(homedir2(), ".claude", "projects");
|
|
392
480
|
function pathToProjectDir(dir) {
|
|
393
481
|
return dir.replace(/\//g, "-");
|
|
394
482
|
}
|
|
@@ -396,14 +484,14 @@ async function loadSession(sessionId) {
|
|
|
396
484
|
const sessionFile = `${sessionId}.jsonl`;
|
|
397
485
|
let projectDirs;
|
|
398
486
|
try {
|
|
399
|
-
projectDirs = await
|
|
487
|
+
projectDirs = await readdir2(CLAUDE_DIR);
|
|
400
488
|
} catch {
|
|
401
489
|
return null;
|
|
402
490
|
}
|
|
403
491
|
for (const projectDir of projectDirs) {
|
|
404
|
-
const sessionPath =
|
|
492
|
+
const sessionPath = join2(CLAUDE_DIR, projectDir, sessionFile);
|
|
405
493
|
try {
|
|
406
|
-
const content = await
|
|
494
|
+
const content = await readFile2(sessionPath, "utf-8");
|
|
407
495
|
return parseSessionFile(content);
|
|
408
496
|
} catch {
|
|
409
497
|
}
|
|
@@ -420,17 +508,17 @@ async function listSessions(workingDirectory) {
|
|
|
420
508
|
if (workingDirectory) {
|
|
421
509
|
projectDirs = [pathToProjectDir(workingDirectory)];
|
|
422
510
|
} else {
|
|
423
|
-
projectDirs = await
|
|
511
|
+
projectDirs = await readdir2(CLAUDE_DIR);
|
|
424
512
|
}
|
|
425
513
|
} catch {
|
|
426
514
|
return [];
|
|
427
515
|
}
|
|
428
516
|
const candidates = [];
|
|
429
517
|
for (const projectDir of projectDirs) {
|
|
430
|
-
const projectPath =
|
|
518
|
+
const projectPath = join2(CLAUDE_DIR, projectDir);
|
|
431
519
|
let files;
|
|
432
520
|
try {
|
|
433
|
-
files = await
|
|
521
|
+
files = await readdir2(projectPath);
|
|
434
522
|
} catch {
|
|
435
523
|
continue;
|
|
436
524
|
}
|
|
@@ -440,8 +528,10 @@ async function listSessions(workingDirectory) {
|
|
|
440
528
|
if (!isValidSessionUUID(sessionId)) continue;
|
|
441
529
|
candidates.push({
|
|
442
530
|
sessionId,
|
|
443
|
-
|
|
444
|
-
|
|
531
|
+
// When a workingDirectory is provided, return the original path so it
|
|
532
|
+
// matches the device's defaultWorkingDirectory on the mobile side.
|
|
533
|
+
project: workingDirectory ?? projectDir,
|
|
534
|
+
filePath: join2(projectPath, file)
|
|
445
535
|
});
|
|
446
536
|
}
|
|
447
537
|
}
|
|
@@ -695,23 +785,8 @@ function parseSessionFile(content) {
|
|
|
695
785
|
|
|
696
786
|
// src/lib/context.ts
|
|
697
787
|
import { execa } from "execa";
|
|
698
|
-
|
|
699
|
-
// src/utils/logger.ts
|
|
700
|
-
import pino from "pino";
|
|
701
|
-
var level = process.env.LOG_LEVEL ?? "info";
|
|
702
|
-
var format = process.env.PUNK_LOG_FORMAT ?? (process.stdout.isTTY ? "pretty" : "json");
|
|
703
|
-
var transport = format === "pretty" ? pino.transport({
|
|
704
|
-
target: "pino-pretty",
|
|
705
|
-
options: { colorize: true }
|
|
706
|
-
}) : void 0;
|
|
707
|
-
var logger = pino({ level }, transport);
|
|
708
|
-
function createChildLogger(bindings) {
|
|
709
|
-
return logger.child(bindings);
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// src/lib/context.ts
|
|
713
788
|
var log = createChildLogger({ component: "context" });
|
|
714
|
-
async function getContext(sessionId,
|
|
789
|
+
async function getContext(sessionId, workingDirectory) {
|
|
715
790
|
let stdout;
|
|
716
791
|
try {
|
|
717
792
|
const result = await execa("claude", [
|
|
@@ -723,7 +798,7 @@ async function getContext(sessionId, cwd) {
|
|
|
723
798
|
sessionId,
|
|
724
799
|
"/context"
|
|
725
800
|
], {
|
|
726
|
-
cwd:
|
|
801
|
+
cwd: workingDirectory || process.cwd(),
|
|
727
802
|
timeout: 3e4,
|
|
728
803
|
stdin: "ignore"
|
|
729
804
|
});
|
|
@@ -902,6 +977,54 @@ async function refreshIdToken() {
|
|
|
902
977
|
return updated.idToken;
|
|
903
978
|
}
|
|
904
979
|
|
|
980
|
+
// src/lib/sleep-inhibitor.ts
|
|
981
|
+
import { spawn } from "child_process";
|
|
982
|
+
function preventIdleSleep() {
|
|
983
|
+
const platform = process.platform;
|
|
984
|
+
let child = null;
|
|
985
|
+
if (platform === "darwin") {
|
|
986
|
+
child = spawn("caffeinate", ["-i", "-w", String(process.pid)], {
|
|
987
|
+
stdio: "ignore",
|
|
988
|
+
detached: false
|
|
989
|
+
});
|
|
990
|
+
} else if (platform === "linux") {
|
|
991
|
+
child = spawn(
|
|
992
|
+
"systemd-inhibit",
|
|
993
|
+
[
|
|
994
|
+
"--what=idle",
|
|
995
|
+
"--who=punk-connect",
|
|
996
|
+
"--why=Device connected for remote access",
|
|
997
|
+
"cat"
|
|
998
|
+
],
|
|
999
|
+
{ stdio: ["pipe", "ignore", "ignore"], detached: false }
|
|
1000
|
+
);
|
|
1001
|
+
} else if (platform === "win32") {
|
|
1002
|
+
const script = `
|
|
1003
|
+
$sig = '[DllImport("kernel32.dll")] public static extern uint SetThreadExecutionState(uint esFlags);';
|
|
1004
|
+
$t = Add-Type -MemberDefinition $sig -Name WinAPI -Namespace Punk -PassThru;
|
|
1005
|
+
while($true) {
|
|
1006
|
+
$t::SetThreadExecutionState(0x80000001) | Out-Null;
|
|
1007
|
+
try { $null = Read-Host } catch { break };
|
|
1008
|
+
Start-Sleep -Seconds 30;
|
|
1009
|
+
}`.trim();
|
|
1010
|
+
child = spawn("powershell", ["-NoProfile", "-Command", script], {
|
|
1011
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
1012
|
+
detached: false
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
child?.unref();
|
|
1016
|
+
child?.on("error", () => {
|
|
1017
|
+
});
|
|
1018
|
+
return {
|
|
1019
|
+
release: () => {
|
|
1020
|
+
if (child && !child.killed) {
|
|
1021
|
+
child.kill();
|
|
1022
|
+
child = null;
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
|
|
905
1028
|
// src/commands/connect.ts
|
|
906
1029
|
async function connect(server, options) {
|
|
907
1030
|
logger.info("Checking prerequisites...");
|
|
@@ -937,6 +1060,13 @@ async function connect(server, options) {
|
|
|
937
1060
|
reconnectionDelayMax: 5e3
|
|
938
1061
|
});
|
|
939
1062
|
const activeSessions = /* @__PURE__ */ new Map();
|
|
1063
|
+
const sleepLock = preventIdleSleep();
|
|
1064
|
+
logger.info("Sleep inhibitor active");
|
|
1065
|
+
const heartbeatInterval = setInterval(() => {
|
|
1066
|
+
if (socket.connected) {
|
|
1067
|
+
socket.emit("heartbeat");
|
|
1068
|
+
}
|
|
1069
|
+
}, 3e4);
|
|
940
1070
|
socket.on("connect", () => {
|
|
941
1071
|
logger.info("Connected");
|
|
942
1072
|
const deviceInfo = collectDeviceInfo(deviceId, options.name, options.tag);
|
|
@@ -966,15 +1096,17 @@ async function connect(server, options) {
|
|
|
966
1096
|
handleGetContext(socket, msg);
|
|
967
1097
|
}
|
|
968
1098
|
});
|
|
969
|
-
socket.on("get-
|
|
970
|
-
if (msg.type === "get-
|
|
971
|
-
|
|
1099
|
+
socket.on("get-commands", (msg) => {
|
|
1100
|
+
if (msg.type === "get-commands") {
|
|
1101
|
+
handleGetCommands(socket, msg);
|
|
972
1102
|
}
|
|
973
1103
|
});
|
|
974
1104
|
socket.on("cancel", (msg) => {
|
|
975
1105
|
handleCancel(msg.id, activeSessions);
|
|
976
1106
|
});
|
|
977
1107
|
socket.on("permission-response", (msg) => {
|
|
1108
|
+
const log2 = createChildLogger({ sessionId: msg.requestId });
|
|
1109
|
+
log2.info({ toolUseId: msg.toolUseId, allowed: msg.allow }, msg.allow ? "Permission accepted" : "Permission denied");
|
|
978
1110
|
const session = activeSessions.get(msg.requestId);
|
|
979
1111
|
if (session) {
|
|
980
1112
|
session.resolvePermission(msg.toolUseId, msg.allow, msg.answers, msg.feedback);
|
|
@@ -995,7 +1127,9 @@ async function connect(server, options) {
|
|
|
995
1127
|
socket.emit("register", collectDeviceInfo(deviceId, options.name, options.tag));
|
|
996
1128
|
});
|
|
997
1129
|
socket.on("connect_error", (err) => {
|
|
998
|
-
|
|
1130
|
+
const { reason, ...detail } = formatConnectionError(err);
|
|
1131
|
+
logger.error(detail, `Connection error: ${reason}`);
|
|
1132
|
+
logger.debug({ err }, "Connection error (raw)");
|
|
999
1133
|
});
|
|
1000
1134
|
const refreshInterval = setInterval(async () => {
|
|
1001
1135
|
try {
|
|
@@ -1006,6 +1140,8 @@ async function connect(server, options) {
|
|
|
1006
1140
|
}, 50 * 60 * 1e3);
|
|
1007
1141
|
const cleanup = () => {
|
|
1008
1142
|
clearInterval(refreshInterval);
|
|
1143
|
+
clearInterval(heartbeatInterval);
|
|
1144
|
+
sleepLock.release();
|
|
1009
1145
|
for (const session of activeSessions.values()) {
|
|
1010
1146
|
session.abort();
|
|
1011
1147
|
}
|
|
@@ -1016,6 +1152,15 @@ async function connect(server, options) {
|
|
|
1016
1152
|
cleanup();
|
|
1017
1153
|
process.exit(0);
|
|
1018
1154
|
});
|
|
1155
|
+
process.on("SIGTERM", () => {
|
|
1156
|
+
logger.info("Shutting down...");
|
|
1157
|
+
cleanup();
|
|
1158
|
+
process.exit(0);
|
|
1159
|
+
});
|
|
1160
|
+
process.on("SIGCONT", () => {
|
|
1161
|
+
logger.info("Resumed from sleep, reconnecting...");
|
|
1162
|
+
socket.disconnect().connect();
|
|
1163
|
+
});
|
|
1019
1164
|
await new Promise(() => {
|
|
1020
1165
|
});
|
|
1021
1166
|
}
|
|
@@ -1026,15 +1171,52 @@ function buildUrl(server) {
|
|
|
1026
1171
|
}
|
|
1027
1172
|
return url.origin + url.pathname;
|
|
1028
1173
|
}
|
|
1174
|
+
function formatConnectionError(err) {
|
|
1175
|
+
const errRecord = err;
|
|
1176
|
+
const description = errRecord.description;
|
|
1177
|
+
const message = description?.message ?? description?.error?.message ?? err.message;
|
|
1178
|
+
const result = { message };
|
|
1179
|
+
let reason = "unknown";
|
|
1180
|
+
if (errRecord.type === "TransportError" && description) {
|
|
1181
|
+
const target = description.target;
|
|
1182
|
+
const req = target?._req;
|
|
1183
|
+
const res = req?.res;
|
|
1184
|
+
const statusCode = res?.statusCode;
|
|
1185
|
+
if (statusCode) {
|
|
1186
|
+
result.statusCode = statusCode;
|
|
1187
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
1188
|
+
reason = "authentication failed";
|
|
1189
|
+
} else if (statusCode >= 500) {
|
|
1190
|
+
reason = "server unavailable";
|
|
1191
|
+
} else {
|
|
1192
|
+
reason = `server responded ${statusCode}`;
|
|
1193
|
+
}
|
|
1194
|
+
} else if (/ENOTFOUND|ECONNREFUSED|EAI_AGAIN/.test(message)) {
|
|
1195
|
+
reason = "server unreachable";
|
|
1196
|
+
} else {
|
|
1197
|
+
reason = "transport error";
|
|
1198
|
+
}
|
|
1199
|
+
} else if (err.message === "timeout") {
|
|
1200
|
+
reason = "timed out";
|
|
1201
|
+
} else if (errRecord.data) {
|
|
1202
|
+
result.data = errRecord.data;
|
|
1203
|
+
reason = "rejected by server";
|
|
1204
|
+
} else if (err.message.includes("v2.x")) {
|
|
1205
|
+
reason = "server version mismatch";
|
|
1206
|
+
} else {
|
|
1207
|
+
reason = "failed";
|
|
1208
|
+
}
|
|
1209
|
+
return { reason, ...result };
|
|
1210
|
+
}
|
|
1029
1211
|
function send(socket, event, msg) {
|
|
1030
1212
|
socket.emit(event, msg);
|
|
1031
1213
|
}
|
|
1032
1214
|
function handlePrompt(socket, msg, activeSessions) {
|
|
1033
|
-
const { id, prompt: prompt2, sessionId,
|
|
1215
|
+
const { id, prompt: prompt2, sessionId, workingDirectory, images, options } = msg;
|
|
1034
1216
|
const log2 = createChildLogger({ sessionId: id });
|
|
1035
1217
|
log2.info({ prompt: prompt2.slice(0, 80) }, "Session started");
|
|
1036
1218
|
const handle = runClaude(
|
|
1037
|
-
{ prompt: prompt2, sessionId,
|
|
1219
|
+
{ prompt: prompt2, sessionId, workingDirectory, images, options },
|
|
1038
1220
|
{
|
|
1039
1221
|
onSessionCreated: (info) => {
|
|
1040
1222
|
send(socket, "response", { type: "session_created", data: info, requestId: id });
|
|
@@ -1066,7 +1248,7 @@ function handlePrompt(socket, msg, activeSessions) {
|
|
|
1066
1248
|
log2.error({ error: message }, "Session error");
|
|
1067
1249
|
},
|
|
1068
1250
|
onPermissionRequest: (req) => {
|
|
1069
|
-
log2.info({ toolName: req.toolName }, "Permission
|
|
1251
|
+
log2.info({ toolUseId: req.toolUseId, toolName: req.toolName }, "Permission requested");
|
|
1070
1252
|
socket.emit("permission-request", {
|
|
1071
1253
|
requestId: id,
|
|
1072
1254
|
toolUseId: req.toolUseId,
|
|
@@ -1109,26 +1291,26 @@ async function handleLoadSession(socket, msg) {
|
|
|
1109
1291
|
log2.warn("Session not found");
|
|
1110
1292
|
}
|
|
1111
1293
|
}
|
|
1112
|
-
async function
|
|
1113
|
-
const { id,
|
|
1114
|
-
const log2 = createChildLogger({
|
|
1115
|
-
log2.info("Getting
|
|
1294
|
+
async function handleGetCommands(socket, msg) {
|
|
1295
|
+
const { id, workingDirectory } = msg;
|
|
1296
|
+
const log2 = createChildLogger({ requestId: id });
|
|
1297
|
+
log2.info("Getting commands...");
|
|
1116
1298
|
try {
|
|
1117
|
-
const
|
|
1118
|
-
send(socket, "response", { type: "
|
|
1119
|
-
log2.info("
|
|
1299
|
+
const commands = await getProjectCommands(workingDirectory);
|
|
1300
|
+
send(socket, "response", { type: "commands", commands, requestId: id });
|
|
1301
|
+
log2.info({ count: commands.length }, "Commands retrieved");
|
|
1120
1302
|
} catch (err) {
|
|
1121
1303
|
const message = err instanceof Error ? err.message : String(err);
|
|
1122
1304
|
send(socket, "response", { type: "error", message, requestId: id });
|
|
1123
|
-
log2.error({ err }, "
|
|
1305
|
+
log2.error({ err }, "Commands error");
|
|
1124
1306
|
}
|
|
1125
1307
|
}
|
|
1126
1308
|
async function handleGetContext(socket, msg) {
|
|
1127
|
-
const { id, sessionId,
|
|
1309
|
+
const { id, sessionId, workingDirectory } = msg;
|
|
1128
1310
|
const log2 = createChildLogger({ sessionId });
|
|
1129
1311
|
log2.info("Getting context...");
|
|
1130
1312
|
try {
|
|
1131
|
-
const data = await getContext(sessionId,
|
|
1313
|
+
const data = await getContext(sessionId, workingDirectory);
|
|
1132
1314
|
send(socket, "response", { type: "context", data, requestId: id });
|
|
1133
1315
|
log2.info({ totalTokens: data.totalTokens }, "Context retrieved");
|
|
1134
1316
|
} catch (err) {
|