claude-simple 0.2.10 → 0.2.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-simple",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "claude-simple": "server.js",
package/public/index.html CHANGED
@@ -82,7 +82,7 @@
82
82
  <script src="/vendor/marked.min.js"></script>
83
83
  <link id="hljs-theme" rel="stylesheet" href="/vendor/highlight-dark.min.css">
84
84
  <script src="/vendor/highlight.min.js"></script>
85
- <link rel="stylesheet" href="style.css?v=ui-11">
85
+ <link rel="stylesheet" href="style.css?v=ui-12">
86
86
  </head>
87
87
  <body class="h-dvh overflow-hidden flex flex-col bg-base-100 text-base-content">
88
88
  <div id="root" class="flex flex-col flex-1 overflow-hidden"></div>
@@ -98,6 +98,6 @@
98
98
  }
99
99
  }, 10000);
100
100
  </script>
101
- <script type="module" src="app.js?v=ui-11"></script>
101
+ <script type="module" src="app.js?v=ui-12"></script>
102
102
  </body>
103
103
  </html>
@@ -82,10 +82,14 @@ export const AppShell = (opts = {}) => {
82
82
  <button id="btn-share-session" class="btn btn-ghost btn-xs border border-base-300" title="复制当前会话分享链接">分享</button>
83
83
  <button id="btn-refresh-conn" class="btn btn-ghost btn-xs border border-base-300" title="重新连接并同步">↻</button>
84
84
  <button id="btn-pwa-install" class="btn btn-ghost btn-xs border border-base-300 hidden" title="安装到主屏幕">安装</button>
85
- <button id="btn-meta-agent" class="btn btn-ghost btn-xs border border-primary/40 text-primary" title="Meta Agent — 报告 / 问答 / VLM"><span class="hidden sm:inline">✦ Agent</span><span class="sm:hidden">✦</span></button>
86
- <button id="btn-new-session" class="btn btn-ghost btn-xs border border-base-300" title="New session"><span class="hidden sm:inline">+ New</span><span class="sm:hidden">+</span></button>
87
- <button id="btn-theme" class="btn btn-ghost btn-xs border border-base-300">🌙</button>
88
- <button id="btn-settings" class="btn btn-ghost btn-xs border border-base-300">⚙️</button>
85
+ <button id="btn-meta-agent" class="btn btn-ghost btn-xs border border-primary/40 text-primary topbar-icon-btn" title="Meta Agent — 报告 / 问答 / VLM" aria-label="打开 Meta Agent">
86
+ <svg class="topbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="m12 2 1.6 6.4L20 10l-6.4 1.6L12 18l-1.6-6.4L4 10l6.4-1.6L12 2Z"/><path d="m19 16 .7 2.3L22 19l-2.3.7L19 22l-.7-2.3L16 19l2.3-.7L19 16Z"/></svg><span class="topbar-icon-label">Agent</span>
87
+ </button>
88
+ <button id="btn-new-session" class="btn btn-ghost btn-xs border border-base-300 topbar-icon-btn" title="New session" aria-label="新建会话">
89
+ <svg class="topbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><circle cx="12" cy="12" r="8"/><path d="M12 8v8M8 12h8"/></svg><span class="topbar-icon-label">New</span>
90
+ </button>
91
+ <button id="btn-theme" class="btn btn-ghost btn-xs border border-base-300 topbar-icon-btn" title="切换主题" aria-label="切换主题"><svg class="topbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><path d="M20 15.5A8 8 0 0 1 8.5 4 8 8 0 1 0 20 15.5Z"/></svg></button>
92
+ <button id="btn-settings" class="btn btn-ghost btn-xs border border-base-300 topbar-icon-btn" title="设置" aria-label="设置"><svg class="topbar-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" aria-hidden="true"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .34 1.88l.06.06-2.12 2.12-.06-.06A1.7 1.7 0 0 0 15.74 18a1.7 1.7 0 0 0-1.02 1.56V20h-3v-.44A1.7 1.7 0 0 0 10.7 18a1.7 1.7 0 0 0-1.88.34l-.06.06-2.12-2.12.06-.06A1.7 1.7 0 0 0 7.04 14.3 1.7 1.7 0 0 0 5.5 13.28H5v-3h.5a1.7 1.7 0 0 0 1.54-1.02A1.7 1.7 0 0 0 6.7 7.38l-.06-.06L8.76 5.2l.06.06A1.7 1.7 0 0 0 10.7 5a1.7 1.7 0 0 0 1.02-1.56V3h3v.44A1.7 1.7 0 0 0 15.74 5a1.7 1.7 0 0 0 1.88-.34l.06-.06 2.12 2.12-.06.06a1.7 1.7 0 0 0-.34 1.88 1.7 1.7 0 0 0 1.56 1.02h.5v3h-.5A1.7 1.7 0 0 0 19.4 15Z"/></svg></button>
89
93
  <button id="btn-logout" class="btn btn-ghost btn-xs border border-base-300">Sign out</button>
90
94
  </div>
91
95
  </div>
package/public/style.css CHANGED
@@ -1141,6 +1141,18 @@ html.pwa-standalone .ma-panel {
1141
1141
  #recent-menu.recent-menu-open {
1142
1142
  display: block; /* beat .hidden if both race */
1143
1143
  }
1144
+ .topbar-icon-btn {
1145
+ gap: .35rem;
1146
+ min-width: 2rem;
1147
+ padding-inline: .45rem;
1148
+ }
1149
+ .topbar-icon {
1150
+ width: 1rem;
1151
+ height: 1rem;
1152
+ display: block;
1153
+ flex: 0 0 auto;
1154
+ }
1155
+ .topbar-icon-label { display: inline; }
1144
1156
  @media (max-width: 640px) {
1145
1157
  #recent-menu {
1146
1158
  min-width: 0;
@@ -1161,6 +1173,7 @@ html.pwa-standalone .ma-panel {
1161
1173
  #btn-pwa-install {
1162
1174
  display: none !important;
1163
1175
  }
1176
+ .topbar-icon-label { display: none; }
1164
1177
  #topbar-project {
1165
1178
  font-size: 11px;
1166
1179
  max-width: 30vw;
@@ -910,6 +910,14 @@ 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) {
917
+ const home = geminiHome || path.join(os.homedir(), '.gemini');
918
+ return path.join(home, 'antigravity-cli', 'brain', sessionId, '.system_generated', 'logs', 'transcript.jsonl');
919
+ }
920
+
913
921
  function geminiPathFromUri(uri) {
914
922
  if (!uri || typeof uri !== 'string') return null;
915
923
  try { return decodeURIComponent(uri.replace(/^file:\/\//, '')); } catch { return null; }
@@ -1030,10 +1038,46 @@ async function findGeminiJournal(sessionId, geminiHome) {
1030
1038
  try { await fs.access(file); return file; } catch { return null; }
1031
1039
  }
1032
1040
 
1041
+ async function findGeminiNativeTranscript(sessionId, geminiHome) {
1042
+ const file = geminiNativeTranscriptPath(sessionId, geminiHome);
1043
+ try { await fs.access(file); return file; } catch { return null; }
1044
+ }
1045
+
1046
+ function geminiUserText(content) {
1047
+ const text = String(content || '').trim();
1048
+ // Native rows wrap the visible user request in transport metadata. Keep
1049
+ // the chat transcript readable while retaining a safe fallback for old rows.
1050
+ const request = text.match(/<USER_REQUEST>\s*([\s\S]*?)\s*<\/USER_REQUEST>/i);
1051
+ return (request?.[1] || text).trim();
1052
+ }
1053
+
1054
+ /** Parse agy's native brain transcript into the common UI history shape. */
1055
+ export async function parseGeminiNativeMessages(filePath) {
1056
+ const messages = [];
1057
+ try {
1058
+ const stream = fsSync.createReadStream(filePath);
1059
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
1060
+ for await (const line of rl) {
1061
+ let d; try { d = JSON.parse(line); } catch { continue; }
1062
+ if (d.status && d.status !== 'DONE') continue;
1063
+ const ts = Date.parse(d.created_at || '') || undefined;
1064
+ if (d.source === 'USER_EXPLICIT' && d.type === 'USER_INPUT') {
1065
+ const content = geminiUserText(d.content);
1066
+ if (content) messages.push({ role: 'user', type: 'text', content, ts });
1067
+ } else if (d.source === 'MODEL' && ['GENERIC', 'PLANNER_RESPONSE'].includes(d.type)) {
1068
+ const content = String(d.content || '').trim();
1069
+ if (content) messages.push({ role: 'assistant', type: 'text', content, ts });
1070
+ }
1071
+ }
1072
+ } catch {}
1073
+ return messages;
1074
+ }
1075
+
1033
1076
  async function loadGeminiContext(sessionId, geminiHome) {
1034
- const file = await findGeminiJournal(sessionId, geminiHome);
1035
- if (!file) return { note: 'This Gemini conversation predates UI journaling; its native transcript is not exposed by agy.' };
1036
- const messages = await parseGeminiMessages(file);
1077
+ const native = await findGeminiNativeTranscript(sessionId, geminiHome);
1078
+ const file = native || await findGeminiJournal(sessionId, geminiHome);
1079
+ if (!file) return { note: 'This Gemini conversation has no readable transcript on this machine.' };
1080
+ const messages = native ? await parseGeminiNativeMessages(native) : await parseGeminiMessages(file);
1037
1081
  const usages = messages.filter(m => m.type === 'token_usage').map(m => m.usage || {});
1038
1082
  const total = { input_tokens: 0, output_tokens: 0, reasoning_tokens: 0, cache_read_input_tokens: 0, total_tokens: 0 };
1039
1083
  for (const u of usages) for (const key of Object.keys(total)) total[key] += Number(u[key]) || 0;
@@ -1384,16 +1428,17 @@ export async function loadSessionMessages(sessionId, agent, { claudeConfigDirs,
1384
1428
  return { messages: await parseGeminiMessages(file), context: await loadCursorContext(sessionId, cursorHome), agent: 'cursor' };
1385
1429
  }
1386
1430
  if (a === 'gemini') {
1387
- const file = await findGeminiJournal(sessionId, geminiHome);
1431
+ const native = await findGeminiNativeTranscript(sessionId, geminiHome);
1432
+ const file = native || await findGeminiJournal(sessionId, geminiHome);
1388
1433
  if (!file) {
1389
1434
  const native = (await scanGeminiSessions(geminiHome)).some(s => s.sessionId === sessionId);
1390
1435
  if (!native) return null;
1391
- // agy's native transcript is a private protobuf SQLite format. The
1392
- // conversation can still be resumed normally; UI-created turns gain a
1393
- // readable journal from that point onward.
1394
1436
  return { messages: [], context: await loadGeminiContext(sessionId, geminiHome), agent: 'gemini' };
1395
1437
  }
1396
- return { messages: await parseGeminiMessages(file), context: await loadGeminiContext(sessionId, geminiHome), agent: 'gemini' };
1438
+ return {
1439
+ messages: native ? await parseGeminiNativeMessages(native) : await parseGeminiMessages(file),
1440
+ context: await loadGeminiContext(sessionId, geminiHome), agent: 'gemini',
1441
+ };
1397
1442
  }
1398
1443
  if (a === 'codex') {
1399
1444
  const file = await findCodexSessionFile(sessionId, codexHome);