claude-simple 0.2.23 → 0.2.24

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/index.html CHANGED
@@ -4,13 +4,13 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
6
6
  <title>Claude Code</title>
7
- <link rel="stylesheet" href="/style.0.2.23.min.css">
7
+ <link rel="stylesheet" href="/style.0.2.24.min.css">
8
8
  <link rel="stylesheet" href="/highlight.min.css">
9
9
  <script src="/marked.min.js"></script>
10
10
  <script src="/highlight.min.js"></script>
11
11
  </head>
12
12
  <body class="h-dvh overflow-hidden flex flex-col bg-base-100 text-base-content">
13
13
  <div id="root" class="flex flex-col flex-1 overflow-hidden"></div>
14
- <script type="module" src="/app.0.2.23.min.js"></script>
14
+ <script type="module" src="/app.0.2.24.min.js"></script>
15
15
  </body>
16
16
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-simple",
3
- "version": "0.2.23",
3
+ "version": "0.2.24",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude-simple": "server.js",
@@ -14,6 +14,36 @@ function profileKey(options) {
14
14
  return `${options.model || ''}\u001f${options.effort || ''}`;
15
15
  }
16
16
 
17
+ // JSON-RPC 2.0 error.code must be an integer. Node fs/spawn errors use string
18
+ // codes like "ENOENT"; Grok's Rust client rejects those and logs
19
+ // `invalid type: string "ENOENT", expected i32`.
20
+ export function jsonRpcErrorCode(error) {
21
+ const code = error?.code;
22
+ if (typeof code === 'number' && Number.isInteger(code)) return code;
23
+ return -32000;
24
+ }
25
+
26
+ /** ACP paths must be absolute; resolve relatives against the session cwd, not the edge process cwd. */
27
+ export function resolveFsPath(filePath, cwd) {
28
+ const raw = String(filePath || '').trim();
29
+ if (!raw) {
30
+ const err = new Error('path is required');
31
+ err.code = -32602;
32
+ throw err;
33
+ }
34
+ return path.resolve(cwd || process.cwd(), raw);
35
+ }
36
+
37
+ export function selectPermissionOption(options) {
38
+ const list = Array.isArray(options) ? options : [];
39
+ const allow = list.find(o => o?.kind === 'allow_always')
40
+ || list.find(o => o?.kind === 'allow_once')
41
+ || list.find(o => typeof o?.kind === 'string' && String(o.kind).startsWith('allow'))
42
+ || list[0];
43
+ if (!allow?.optionId) return { outcome: 'cancelled' };
44
+ return { outcome: 'selected', optionId: allow.optionId };
45
+ }
46
+
17
47
  function envFromParams(envList) {
18
48
  const env = { ...process.env };
19
49
  if (!Array.isArray(envList)) return env;
@@ -164,6 +194,7 @@ class GrokACPClient {
164
194
  this.child = spawn(cliPath || 'grok', args, { stdio: ['pipe', 'pipe', 'pipe'] });
165
195
  this.pending = new Map();
166
196
  this.listeners = new Map();
197
+ this.sessionCwd = new Map();
167
198
  // High ids avoid colliding with agent-originated request ids (often 0..N).
168
199
  this.nextID = 100000;
169
200
  this.terminals = new TerminalHost();
@@ -198,13 +229,19 @@ class GrokACPClient {
198
229
  return init;
199
230
  }
200
231
 
232
+ fsCwd(params) {
233
+ return (params?.sessionId && this.sessionCwd.get(params.sessionId)) || process.cwd();
234
+ }
235
+
201
236
  async handleClientRequest(msg) {
202
237
  const method = msg.method;
203
238
  const params = msg.params || {};
204
239
  try {
205
240
  let result = {};
206
- if (method === 'fs/read_text_file') {
207
- const filePath = String(params.path || '');
241
+ if (method === 'session/request_permission') {
242
+ result = { outcome: selectPermissionOption(params.options) };
243
+ } else if (method === 'fs/read_text_file') {
244
+ const filePath = resolveFsPath(params.path, this.fsCwd(params));
208
245
  const content = await fs.promises.readFile(filePath, 'utf8');
209
246
  const limit = Number.isFinite(params.limit) ? params.limit : null;
210
247
  const line = Number.isFinite(params.line) ? params.line : null;
@@ -217,11 +254,13 @@ class GrokACPClient {
217
254
  result = { content };
218
255
  }
219
256
  } else if (method === 'fs/write_text_file') {
220
- const filePath = String(params.path || '');
257
+ const filePath = resolveFsPath(params.path, this.fsCwd(params));
258
+ const content = params.content == null ? '' : String(params.content);
221
259
  await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
222
- await fs.promises.writeFile(filePath, params.content ?? '', 'utf8');
260
+ await fs.promises.writeFile(filePath, content, 'utf8');
223
261
  result = {};
224
262
  } else if (method === 'terminal/create') {
263
+ if (!params.cwd) params = { ...params, cwd: this.fsCwd(params) };
225
264
  result = this.terminals.create(params);
226
265
  } else if (method === 'terminal/output') {
227
266
  result = this.terminals.output(params.terminalId);
@@ -244,7 +283,10 @@ class GrokACPClient {
244
283
  this.write({
245
284
  jsonrpc: '2.0',
246
285
  id: msg.id,
247
- error: { code: error.code || -32000, message: error.message || 'Client request failed' },
286
+ error: {
287
+ code: jsonRpcErrorCode(error),
288
+ message: error.message || 'Client request failed',
289
+ },
248
290
  });
249
291
  }
250
292
  }
@@ -291,20 +333,23 @@ class GrokACPClient {
291
333
 
292
334
  async ensureSession(options) {
293
335
  await this.ready;
336
+ const cwd = options.cwd || process.cwd();
294
337
  if (options.sessionId) {
295
338
  try {
296
- await this.request('session/load', { sessionId: options.sessionId, cwd: options.cwd, mcpServers: [] });
339
+ await this.request('session/load', { sessionId: options.sessionId, cwd, mcpServers: [] });
297
340
  } catch {
298
- await this.request('session/resume', { sessionId: options.sessionId, cwd: options.cwd, mcpServers: [] });
341
+ await this.request('session/resume', { sessionId: options.sessionId, cwd, mcpServers: [] });
299
342
  }
343
+ this.sessionCwd.set(options.sessionId, cwd);
300
344
  return options.sessionId;
301
345
  }
302
346
  const result = await this.request('session/new', {
303
- cwd: options.cwd || process.cwd(),
347
+ cwd,
304
348
  mcpServers: [],
305
349
  _meta: { yoloMode: true },
306
350
  });
307
351
  if (!result.sessionId) throw new Error('Grok ACP did not return a session id');
352
+ this.sessionCwd.set(result.sessionId, cwd);
308
353
  return result.sessionId;
309
354
  }
310
355
  }
@@ -910,12 +910,16 @@ function geminiJournalPath(sessionId, geminiHome) {
910
910
  return path.join(geminiUiSessionsRoot(geminiHome), `${sessionId}.jsonl`);
911
911
  }
912
912
 
913
- // agy keeps the actual native transcript here. This is plain JSONL (unlike
914
- // its conversation SQLite database) and is available for conversations made
915
- // in the Antigravity app as well as print-mode conversations created by us.
916
- function geminiNativeTranscriptPath(sessionId, geminiHome) {
913
+ // agy keeps both a compact transport log and a lossless transcript. The
914
+ // compact log replaces large fields with `<truncated N bytes>`; always prefer
915
+ // the full file when it is available so chat history never loses message text.
916
+ function geminiNativeTranscriptPaths(sessionId, geminiHome) {
917
917
  const home = geminiHome || path.join(os.homedir(), '.gemini');
918
- return path.join(home, 'antigravity-cli', 'brain', sessionId, '.system_generated', 'logs', 'transcript.jsonl');
918
+ const logs = path.join(home, 'antigravity-cli', 'brain', sessionId, '.system_generated', 'logs');
919
+ return [
920
+ path.join(logs, 'transcript_full.jsonl'),
921
+ path.join(logs, 'transcript.jsonl'),
922
+ ];
919
923
  }
920
924
 
921
925
  function geminiPathFromUri(uri) {
@@ -1039,8 +1043,10 @@ async function findGeminiJournal(sessionId, geminiHome) {
1039
1043
  }
1040
1044
 
1041
1045
  async function findGeminiNativeTranscript(sessionId, geminiHome) {
1042
- const file = geminiNativeTranscriptPath(sessionId, geminiHome);
1043
- try { await fs.access(file); return file; } catch { return null; }
1046
+ for (const file of geminiNativeTranscriptPaths(sessionId, geminiHome)) {
1047
+ try { await fs.access(file); return file; } catch { /* try the compact fallback */ }
1048
+ }
1049
+ return null;
1044
1050
  }
1045
1051
 
1046
1052
  function geminiUserText(content) {
@@ -1571,7 +1577,8 @@ export async function loadSessionJsonl(sessionId, agent, { claudeConfigDirs, cod
1571
1577
  let file;
1572
1578
  if (a === 'codex') file = await findCodexSessionFile(sessionId, codexHome);
1573
1579
  else if (a === 'cursor') file = await findCursorJournal(sessionId, cursorHome);
1574
- else if (a === 'gemini') file = await findGeminiJournal(sessionId, geminiHome);
1580
+ else if (a === 'gemini') file = await findGeminiNativeTranscript(sessionId, geminiHome)
1581
+ || await findGeminiJournal(sessionId, geminiHome);
1575
1582
  else if (a === 'grok') {
1576
1583
  const dir = await findGrokSessionDir(sessionId, grokHome);
1577
1584
  file = dir ? path.join(dir, 'chat_history.jsonl') : null;
File without changes