luckerr 0.41.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.
Files changed (156) hide show
  1. package/README.md +267 -0
  2. package/README.zh-CN.md +237 -0
  3. package/dashboard/app.css +3022 -0
  4. package/dashboard/dist/app.js +30137 -0
  5. package/dashboard/dist/app.js.map +1 -0
  6. package/dashboard/dist/vendor-hljs.css +10 -0
  7. package/dashboard/dist/vendor-uplot.css +1 -0
  8. package/dashboard/index.html +19 -0
  9. package/data/deepseek-tokenizer.json.gz +0 -0
  10. package/dist/cli/acp-EOOAI4F5.js +712 -0
  11. package/dist/cli/acp-EOOAI4F5.js.map +1 -0
  12. package/dist/cli/chat-7J6GJXL2.js +51 -0
  13. package/dist/cli/chat-7J6GJXL2.js.map +1 -0
  14. package/dist/cli/chunk-2425HK6U.js +54 -0
  15. package/dist/cli/chunk-2425HK6U.js.map +1 -0
  16. package/dist/cli/chunk-25T6CVUP.js +172 -0
  17. package/dist/cli/chunk-25T6CVUP.js.map +1 -0
  18. package/dist/cli/chunk-2UQP6H6T.js +31 -0
  19. package/dist/cli/chunk-2UQP6H6T.js.map +1 -0
  20. package/dist/cli/chunk-56OAJILV.js +47 -0
  21. package/dist/cli/chunk-56OAJILV.js.map +1 -0
  22. package/dist/cli/chunk-5FTI4KXH.js +150 -0
  23. package/dist/cli/chunk-5FTI4KXH.js.map +1 -0
  24. package/dist/cli/chunk-5TWQD73O.js +2846 -0
  25. package/dist/cli/chunk-5TWQD73O.js.map +1 -0
  26. package/dist/cli/chunk-653BOCMK.js +40 -0
  27. package/dist/cli/chunk-653BOCMK.js.map +1 -0
  28. package/dist/cli/chunk-6ALJTWWQ.js +2663 -0
  29. package/dist/cli/chunk-6ALJTWWQ.js.map +1 -0
  30. package/dist/cli/chunk-6DRKA2IL.js +341 -0
  31. package/dist/cli/chunk-6DRKA2IL.js.map +1 -0
  32. package/dist/cli/chunk-6LV63NJV.js +634 -0
  33. package/dist/cli/chunk-6LV63NJV.js.map +1 -0
  34. package/dist/cli/chunk-74EX7SUH.js +25293 -0
  35. package/dist/cli/chunk-74EX7SUH.js.map +1 -0
  36. package/dist/cli/chunk-74U5RKTX.js +60611 -0
  37. package/dist/cli/chunk-74U5RKTX.js.map +1 -0
  38. package/dist/cli/chunk-ANJSUESV.js +143 -0
  39. package/dist/cli/chunk-ANJSUESV.js.map +1 -0
  40. package/dist/cli/chunk-DB2Z3DKZ.js +54 -0
  41. package/dist/cli/chunk-DB2Z3DKZ.js.map +1 -0
  42. package/dist/cli/chunk-DDIH3ZAA.js +400 -0
  43. package/dist/cli/chunk-DDIH3ZAA.js.map +1 -0
  44. package/dist/cli/chunk-ELN3Z3B2.js +621 -0
  45. package/dist/cli/chunk-ELN3Z3B2.js.map +1 -0
  46. package/dist/cli/chunk-F6BSQJGV.js +200 -0
  47. package/dist/cli/chunk-F6BSQJGV.js.map +1 -0
  48. package/dist/cli/chunk-FET2UAG5.js +246 -0
  49. package/dist/cli/chunk-FET2UAG5.js.map +1 -0
  50. package/dist/cli/chunk-FFJ342IJ.js +190 -0
  51. package/dist/cli/chunk-FFJ342IJ.js.map +1 -0
  52. package/dist/cli/chunk-GB3247B6.js +130 -0
  53. package/dist/cli/chunk-GB3247B6.js.map +1 -0
  54. package/dist/cli/chunk-HC2J4U3G.js +373 -0
  55. package/dist/cli/chunk-HC2J4U3G.js.map +1 -0
  56. package/dist/cli/chunk-HRUZAIHQ.js +42 -0
  57. package/dist/cli/chunk-HRUZAIHQ.js.map +1 -0
  58. package/dist/cli/chunk-J3ZJFUDL.js +308 -0
  59. package/dist/cli/chunk-J3ZJFUDL.js.map +1 -0
  60. package/dist/cli/chunk-J5XJHLWM.js +55 -0
  61. package/dist/cli/chunk-J5XJHLWM.js.map +1 -0
  62. package/dist/cli/chunk-JFGLMRZ6.js +160 -0
  63. package/dist/cli/chunk-JFGLMRZ6.js.map +1 -0
  64. package/dist/cli/chunk-JMBMLOBP.js +26 -0
  65. package/dist/cli/chunk-JMBMLOBP.js.map +1 -0
  66. package/dist/cli/chunk-JMWHXZEL.js +551 -0
  67. package/dist/cli/chunk-JMWHXZEL.js.map +1 -0
  68. package/dist/cli/chunk-KEQGPJBO.js +209 -0
  69. package/dist/cli/chunk-KEQGPJBO.js.map +1 -0
  70. package/dist/cli/chunk-M4K6U37F.js +232 -0
  71. package/dist/cli/chunk-M4K6U37F.js.map +1 -0
  72. package/dist/cli/chunk-MIJI2WMN.js +95 -0
  73. package/dist/cli/chunk-MIJI2WMN.js.map +1 -0
  74. package/dist/cli/chunk-MPAO3JNR.js +128 -0
  75. package/dist/cli/chunk-MPAO3JNR.js.map +1 -0
  76. package/dist/cli/chunk-PZOFBEDC.js +873 -0
  77. package/dist/cli/chunk-PZOFBEDC.js.map +1 -0
  78. package/dist/cli/chunk-RAILYQLN.js +46 -0
  79. package/dist/cli/chunk-RAILYQLN.js.map +1 -0
  80. package/dist/cli/chunk-RR35VQVT.js +90 -0
  81. package/dist/cli/chunk-RR35VQVT.js.map +1 -0
  82. package/dist/cli/chunk-RRA7VPW4.js +417 -0
  83. package/dist/cli/chunk-RRA7VPW4.js.map +1 -0
  84. package/dist/cli/chunk-RU36QVN3.js +452 -0
  85. package/dist/cli/chunk-RU36QVN3.js.map +1 -0
  86. package/dist/cli/chunk-RUBIINXR.js +1819 -0
  87. package/dist/cli/chunk-RUBIINXR.js.map +1 -0
  88. package/dist/cli/chunk-S4XVGLRW.js +499 -0
  89. package/dist/cli/chunk-S4XVGLRW.js.map +1 -0
  90. package/dist/cli/chunk-TUK7OWJA.js +51 -0
  91. package/dist/cli/chunk-TUK7OWJA.js.map +1 -0
  92. package/dist/cli/chunk-VALDDV76.js +580 -0
  93. package/dist/cli/chunk-VALDDV76.js.map +1 -0
  94. package/dist/cli/chunk-WQOGPYGN.js +11390 -0
  95. package/dist/cli/chunk-WQOGPYGN.js.map +1 -0
  96. package/dist/cli/chunk-WREKDFXT.js +34320 -0
  97. package/dist/cli/chunk-WREKDFXT.js.map +1 -0
  98. package/dist/cli/chunk-Y7XQU2EL.js +270 -0
  99. package/dist/cli/chunk-Y7XQU2EL.js.map +1 -0
  100. package/dist/cli/chunk-YBVCZJU4.js +54 -0
  101. package/dist/cli/chunk-YBVCZJU4.js.map +1 -0
  102. package/dist/cli/chunk-YLIHDXUQ.js +749 -0
  103. package/dist/cli/chunk-YLIHDXUQ.js.map +1 -0
  104. package/dist/cli/chunk-YV5XXFD7.js +767 -0
  105. package/dist/cli/chunk-YV5XXFD7.js.map +1 -0
  106. package/dist/cli/chunk-ZRCNIYRQ.js +101 -0
  107. package/dist/cli/chunk-ZRCNIYRQ.js.map +1 -0
  108. package/dist/cli/code-CRKVCMFZ.js +155 -0
  109. package/dist/cli/code-CRKVCMFZ.js.map +1 -0
  110. package/dist/cli/commands-QLMD3T7B.js +356 -0
  111. package/dist/cli/commands-QLMD3T7B.js.map +1 -0
  112. package/dist/cli/commit-53PP32NC.js +293 -0
  113. package/dist/cli/commit-53PP32NC.js.map +1 -0
  114. package/dist/cli/desktop-R6W5CLJ5.js +1046 -0
  115. package/dist/cli/desktop-R6W5CLJ5.js.map +1 -0
  116. package/dist/cli/devtools-YECO25QO.js +3719 -0
  117. package/dist/cli/devtools-YECO25QO.js.map +1 -0
  118. package/dist/cli/diff-LYNRCJZE.js +166 -0
  119. package/dist/cli/diff-LYNRCJZE.js.map +1 -0
  120. package/dist/cli/doctor-5IBP4R5J.js +28 -0
  121. package/dist/cli/doctor-5IBP4R5J.js.map +1 -0
  122. package/dist/cli/events-QN6KLN2V.js +340 -0
  123. package/dist/cli/events-QN6KLN2V.js.map +1 -0
  124. package/dist/cli/index.js +3500 -0
  125. package/dist/cli/index.js.map +1 -0
  126. package/dist/cli/mcp-FGKEH7RG.js +277 -0
  127. package/dist/cli/mcp-FGKEH7RG.js.map +1 -0
  128. package/dist/cli/mcp-browse-YCND4NWT.js +178 -0
  129. package/dist/cli/mcp-browse-YCND4NWT.js.map +1 -0
  130. package/dist/cli/mcp-inspect-V34J3VX5.js +143 -0
  131. package/dist/cli/mcp-inspect-V34J3VX5.js.map +1 -0
  132. package/dist/cli/package.json +3 -0
  133. package/dist/cli/prompt-I775PNKT.js +16 -0
  134. package/dist/cli/prompt-I775PNKT.js.map +1 -0
  135. package/dist/cli/prune-sessions-KGIIYD3P.js +44 -0
  136. package/dist/cli/prune-sessions-KGIIYD3P.js.map +1 -0
  137. package/dist/cli/replay-RDXLUAOE.js +292 -0
  138. package/dist/cli/replay-RDXLUAOE.js.map +1 -0
  139. package/dist/cli/run-RCAC2RYW.js +223 -0
  140. package/dist/cli/run-RCAC2RYW.js.map +1 -0
  141. package/dist/cli/server-FFU6TLYJ.js +3658 -0
  142. package/dist/cli/server-FFU6TLYJ.js.map +1 -0
  143. package/dist/cli/sessions-QT26MQAE.js +107 -0
  144. package/dist/cli/sessions-QT26MQAE.js.map +1 -0
  145. package/dist/cli/setup-VV4WKXHV.js +767 -0
  146. package/dist/cli/setup-VV4WKXHV.js.map +1 -0
  147. package/dist/cli/stats-JVZPQWAN.js +15 -0
  148. package/dist/cli/stats-JVZPQWAN.js.map +1 -0
  149. package/dist/cli/update-KYI3OVJP.js +15 -0
  150. package/dist/cli/update-KYI3OVJP.js.map +1 -0
  151. package/dist/cli/version-ANYORXTI.js +34 -0
  152. package/dist/cli/version-ANYORXTI.js.map +1 -0
  153. package/dist/index.d.ts +2557 -0
  154. package/dist/index.js +15000 -0
  155. package/dist/index.js.map +1 -0
  156. package/package.json +106 -0
@@ -0,0 +1,270 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __cr } from 'node:module'; if (typeof globalThis.require === 'undefined') { globalThis.require = __cr(import.meta.url); }
3
+
4
+ // src/memory/session.ts
5
+ import { execFileSync } from "child_process";
6
+ import {
7
+ appendFileSync,
8
+ chmodSync,
9
+ existsSync,
10
+ mkdirSync,
11
+ readFileSync,
12
+ readdirSync,
13
+ renameSync,
14
+ statSync,
15
+ unlinkSync,
16
+ writeFileSync
17
+ } from "fs";
18
+ import { homedir } from "os";
19
+ import { dirname, join } from "path";
20
+ function detectGitBranch(cwd) {
21
+ try {
22
+ const out = execFileSync("git", ["branch", "--show-current"], {
23
+ cwd,
24
+ stdio: ["ignore", "pipe", "ignore"],
25
+ timeout: 800,
26
+ encoding: "utf8"
27
+ }).trim();
28
+ return out || void 0;
29
+ } catch {
30
+ return void 0;
31
+ }
32
+ }
33
+ function sessionsDir() {
34
+ return join(homedir(), ".luckerr", "sessions");
35
+ }
36
+ function sessionPath(name) {
37
+ return join(sessionsDir(), `${sanitizeName(name)}.jsonl`);
38
+ }
39
+ function sanitizeName(name) {
40
+ const cleaned = name.replace(/[^\w\-\u4e00-\u9fa5]/g, "_").slice(0, 64);
41
+ return cleaned || "default";
42
+ }
43
+ function timestampSuffix() {
44
+ return (/* @__PURE__ */ new Date()).toISOString().replace(/[^\d]/g, "").slice(0, 12);
45
+ }
46
+ function freshSessionName(currentName) {
47
+ const base = currentName ? currentName.replace(/-\d{12,14}$/, "") : "default";
48
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[^\d]/g, "").slice(0, 14);
49
+ return `${base || "default"}-${stamp}`;
50
+ }
51
+ function findSessionsByPrefix(prefix) {
52
+ const dir = sessionsDir();
53
+ if (!existsSync(dir)) return [];
54
+ try {
55
+ const files = readdirSync(dir).filter((f) => f.endsWith(".jsonl") && !f.endsWith(".events.jsonl") && f.startsWith(prefix)).sort().reverse();
56
+ return files.map((f) => f.replace(/\.jsonl$/, ""));
57
+ } catch {
58
+ return [];
59
+ }
60
+ }
61
+ function resolveSession(sessionName, forceNew, forceResume) {
62
+ let resolved = sessionName;
63
+ let preview;
64
+ if (sessionName && forceNew) {
65
+ resolved = `${sessionName}-${timestampSuffix()}`;
66
+ } else if (sessionName && !forceResume) {
67
+ let sessionToCheck = sessionName;
68
+ const prefixed = findSessionsByPrefix(`${sessionName}-`);
69
+ if (prefixed.length > 0) {
70
+ sessionToCheck = prefixed[0];
71
+ }
72
+ const prior = loadSessionMessages(sessionToCheck);
73
+ if (prior.length > 0) {
74
+ resolved = sessionToCheck;
75
+ const p = sessionPath(sessionToCheck);
76
+ const mtime = existsSync(p) ? statSync(p).mtime : /* @__PURE__ */ new Date();
77
+ preview = { messageCount: prior.length, lastActive: mtime };
78
+ }
79
+ } else if (sessionName && forceResume) {
80
+ const prefixed = findSessionsByPrefix(`${sessionName}-`);
81
+ if (prefixed.length > 0) {
82
+ resolved = prefixed[0];
83
+ }
84
+ }
85
+ return { resolved, preview };
86
+ }
87
+ function loadSessionMessages(name) {
88
+ const path = sessionPath(name);
89
+ if (!existsSync(path)) return [];
90
+ try {
91
+ const raw = readFileSync(path, "utf8");
92
+ const out = [];
93
+ for (const line of raw.split(/\r?\n/)) {
94
+ const trimmed = line.trim();
95
+ if (!trimmed) continue;
96
+ try {
97
+ const msg = JSON.parse(trimmed);
98
+ if (msg && typeof msg === "object" && "role" in msg) out.push(msg);
99
+ } catch {
100
+ }
101
+ }
102
+ return out;
103
+ } catch {
104
+ return [];
105
+ }
106
+ }
107
+ function appendSessionMessage(name, message) {
108
+ const path = sessionPath(name);
109
+ mkdirSync(dirname(path), { recursive: true });
110
+ appendFileSync(path, `${JSON.stringify(message)}
111
+ `, "utf8");
112
+ try {
113
+ chmodSync(path, 384);
114
+ } catch {
115
+ }
116
+ }
117
+ function listSessions() {
118
+ const dir = sessionsDir();
119
+ if (!existsSync(dir)) return [];
120
+ try {
121
+ const files = readdirSync(dir).filter(
122
+ (f) => f.endsWith(".jsonl") && !f.endsWith(".events.jsonl")
123
+ );
124
+ return files.map((file) => {
125
+ const path = join(dir, file);
126
+ const stat = statSync(path);
127
+ const name = file.replace(/\.jsonl$/, "");
128
+ const messageCount = countLines(path);
129
+ return {
130
+ name,
131
+ path,
132
+ size: stat.size,
133
+ messageCount,
134
+ mtime: stat.mtime,
135
+ meta: loadSessionMeta(name)
136
+ };
137
+ }).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
138
+ } catch {
139
+ return [];
140
+ }
141
+ }
142
+ function listSessionsForWorkspace(workspace) {
143
+ return listSessions().filter((s) => s.meta.workspace === workspace);
144
+ }
145
+ function metaPath(name) {
146
+ return join(sessionsDir(), `${sanitizeName(name)}.meta.json`);
147
+ }
148
+ function loadSessionMeta(name) {
149
+ const p = metaPath(name);
150
+ if (!existsSync(p)) return {};
151
+ try {
152
+ const raw = JSON.parse(readFileSync(p, "utf8"));
153
+ return raw && typeof raw === "object" ? raw : {};
154
+ } catch {
155
+ return {};
156
+ }
157
+ }
158
+ function patchSessionMeta(name, patch) {
159
+ const cur = loadSessionMeta(name);
160
+ const next = { ...cur, ...patch };
161
+ const p = metaPath(name);
162
+ mkdirSync(dirname(p), { recursive: true });
163
+ writeFileSync(p, JSON.stringify(next), "utf8");
164
+ try {
165
+ chmodSync(p, 384);
166
+ } catch {
167
+ }
168
+ return next;
169
+ }
170
+ function renameSession(oldName, newName) {
171
+ const safeOld = sanitizeName(oldName);
172
+ const safeNew = sanitizeName(newName);
173
+ if (safeOld === safeNew) return false;
174
+ const oldJsonl = sessionPath(oldName);
175
+ const newJsonl = sessionPath(newName);
176
+ if (!existsSync(oldJsonl) || existsSync(newJsonl)) return false;
177
+ renameSync(oldJsonl, newJsonl);
178
+ for (const ext of [".events.jsonl", ".meta.json", ".pending.json", ".plan.json"]) {
179
+ const oldP = oldJsonl.replace(/\.jsonl$/, ext);
180
+ const newP = newJsonl.replace(/\.jsonl$/, ext);
181
+ if (existsSync(oldP)) {
182
+ try {
183
+ renameSync(oldP, newP);
184
+ } catch {
185
+ }
186
+ }
187
+ }
188
+ return true;
189
+ }
190
+ function pruneStaleSessions(daysOld = 90) {
191
+ const cutoff = Date.now() - daysOld * 24 * 60 * 60 * 1e3;
192
+ const deleted = [];
193
+ for (const s of listSessions()) {
194
+ if (s.mtime.getTime() < cutoff) {
195
+ if (deleteSession(s.name)) deleted.push(s.name);
196
+ }
197
+ }
198
+ return deleted;
199
+ }
200
+ function deleteSession(name) {
201
+ const path = sessionPath(name);
202
+ try {
203
+ unlinkSync(path);
204
+ for (const ext of [".events.jsonl", ".pending.json", ".meta.json", ".plan.json"]) {
205
+ const sidecar = path.replace(/\.jsonl$/, ext);
206
+ try {
207
+ unlinkSync(sidecar);
208
+ } catch {
209
+ }
210
+ }
211
+ return true;
212
+ } catch {
213
+ return false;
214
+ }
215
+ }
216
+ function rewriteSession(name, messages) {
217
+ const path = sessionPath(name);
218
+ mkdirSync(dirname(path), { recursive: true });
219
+ const body = messages.map((m) => JSON.stringify(m)).join("\n");
220
+ writeFileSync(path, body ? `${body}
221
+ ` : "", "utf8");
222
+ try {
223
+ chmodSync(path, 384);
224
+ } catch {
225
+ }
226
+ }
227
+ function archiveSession(name) {
228
+ const path = sessionPath(name);
229
+ if (!existsSync(path)) return null;
230
+ try {
231
+ if (statSync(path).size === 0) return null;
232
+ } catch {
233
+ return null;
234
+ }
235
+ for (let attempt = 0; attempt < 5; attempt++) {
236
+ const target = `${name}__archive_${timestampSuffix()}${attempt > 0 ? `_${attempt}` : ""}`;
237
+ if (renameSession(name, target)) return target;
238
+ }
239
+ return null;
240
+ }
241
+ function countLines(path) {
242
+ try {
243
+ const raw = readFileSync(path, "utf8");
244
+ return raw.split(/\r?\n/).filter((l) => l.trim()).length;
245
+ } catch {
246
+ return 0;
247
+ }
248
+ }
249
+
250
+ export {
251
+ detectGitBranch,
252
+ sessionsDir,
253
+ sessionPath,
254
+ sanitizeName,
255
+ timestampSuffix,
256
+ freshSessionName,
257
+ resolveSession,
258
+ loadSessionMessages,
259
+ appendSessionMessage,
260
+ listSessions,
261
+ listSessionsForWorkspace,
262
+ loadSessionMeta,
263
+ patchSessionMeta,
264
+ renameSession,
265
+ pruneStaleSessions,
266
+ deleteSession,
267
+ rewriteSession,
268
+ archiveSession
269
+ };
270
+ //# sourceMappingURL=chunk-Y7XQU2EL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/memory/session.ts"],"sourcesContent":["/** JSONL append-only message log under `~/.luckerr/sessions/`; concurrent-write safe. */\n\nimport { execFileSync } from \"node:child_process\";\nimport {\n appendFileSync,\n chmodSync,\n existsSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n renameSync,\n statSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport type { ChatMessage } from \"../types.js\";\n\n/** Best-effort git branch sniff; returns undefined if not a git repo or git missing. */\nexport function detectGitBranch(cwd: string): string | undefined {\n try {\n const out = execFileSync(\"git\", [\"branch\", \"--show-current\"], {\n cwd,\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 800,\n encoding: \"utf8\",\n }).trim();\n return out || undefined;\n } catch {\n return undefined;\n }\n}\n\nexport interface SessionInfo {\n name: string;\n path: string;\n size: number;\n messageCount: number;\n mtime: Date;\n meta: SessionMeta;\n}\n\nexport interface SessionMeta {\n branch?: string;\n summary?: string;\n totalCostUsd?: number;\n turnCount?: number;\n /** Absolute path of the workspace root the session was created/used in. */\n workspace?: string;\n /** Wallet currency at last save — used to format `totalCostUsd` in the picker without re-fetching balance. */\n balanceCurrency?: string;\n /** Cumulative cache hit / miss tokens across the session — survives resume so /status cache% isn't 0 on a fresh boot. */\n cacheHitTokens?: number;\n cacheMissTokens?: number;\n /** Last turn's promptTokens — lets /status render the context bar before the next turn fires. */\n lastPromptTokens?: number;\n}\n\nexport function sessionsDir(): string {\n return join(homedir(), \".luckerr\", \"sessions\");\n}\n\nexport function sessionPath(name: string): string {\n return join(sessionsDir(), `${sanitizeName(name)}.jsonl`);\n}\n\nexport function sanitizeName(name: string): string {\n const cleaned = name.replace(/[^\\w\\-\\u4e00-\\u9fa5]/g, \"_\").slice(0, 64);\n return cleaned || \"default\";\n}\n\n/** Sortable timestamp `YYYYMMDDHHmm` — used as a session-name suffix. */\nexport function timestampSuffix(): string {\n return new Date().toISOString().replace(/[^\\d]/g, \"\").slice(0, 12);\n}\n\n/** Unique name for an in-app \"new session\" — strips a trailing 12/14-digit timestamp from the current name and re-stamps with seconds precision so back-to-back clicks don't collide. */\nexport function freshSessionName(currentName: string | undefined): string {\n const base = currentName ? currentName.replace(/-\\d{12,14}$/, \"\") : \"default\";\n const stamp = new Date().toISOString().replace(/[^\\d]/g, \"\").slice(0, 14);\n return `${base || \"default\"}-${stamp}`;\n}\n\n/** Names of `.jsonl` sessions starting with `prefix`, newest-first by filename. */\nexport function findSessionsByPrefix(prefix: string): string[] {\n const dir = sessionsDir();\n if (!existsSync(dir)) return [];\n try {\n const files = readdirSync(dir)\n .filter((f) => f.endsWith(\".jsonl\") && !f.endsWith(\".events.jsonl\") && f.startsWith(prefix))\n .sort()\n .reverse();\n return files.map((f) => f.replace(/\\.jsonl$/, \"\"));\n } catch {\n return [];\n }\n}\n\nexport interface SessionPreview {\n messageCount: number;\n lastActive: Date;\n}\n\n/** Resolve launch-time session: forceNew → timestamped suffix; else latest `${name}-*` if any, else base. Preview returned only on the default branch when messages exist. */\nexport function resolveSession(\n sessionName: string | undefined,\n forceNew?: boolean,\n forceResume?: boolean,\n): { resolved: string | undefined; preview: SessionPreview | undefined } {\n let resolved = sessionName;\n let preview: SessionPreview | undefined;\n\n if (sessionName && forceNew) {\n resolved = `${sessionName}-${timestampSuffix()}`;\n } else if (sessionName && !forceResume) {\n let sessionToCheck = sessionName;\n const prefixed = findSessionsByPrefix(`${sessionName}-`);\n if (prefixed.length > 0) {\n sessionToCheck = prefixed[0]!;\n }\n const prior = loadSessionMessages(sessionToCheck);\n if (prior.length > 0) {\n resolved = sessionToCheck;\n const p = sessionPath(sessionToCheck);\n const mtime = existsSync(p) ? statSync(p).mtime : new Date();\n preview = { messageCount: prior.length, lastActive: mtime };\n }\n } else if (sessionName && forceResume) {\n const prefixed = findSessionsByPrefix(`${sessionName}-`);\n if (prefixed.length > 0) {\n resolved = prefixed[0]!;\n }\n }\n\n return { resolved, preview };\n}\n\nexport function loadSessionMessages(name: string): ChatMessage[] {\n const path = sessionPath(name);\n if (!existsSync(path)) return [];\n try {\n const raw = readFileSync(path, \"utf8\");\n const out: ChatMessage[] = [];\n for (const line of raw.split(/\\r?\\n/)) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n try {\n const msg = JSON.parse(trimmed) as ChatMessage;\n if (msg && typeof msg === \"object\" && \"role\" in msg) out.push(msg);\n } catch {\n /* skip malformed line */\n }\n }\n return out;\n } catch {\n return [];\n }\n}\n\nexport function appendSessionMessage(name: string, message: ChatMessage): void {\n const path = sessionPath(name);\n mkdirSync(dirname(path), { recursive: true });\n appendFileSync(path, `${JSON.stringify(message)}\\n`, \"utf8\");\n try {\n chmodSync(path, 0o600);\n } catch {\n /* chmod not supported on this platform */\n }\n}\n\nexport function listSessions(): SessionInfo[] {\n const dir = sessionsDir();\n if (!existsSync(dir)) return [];\n try {\n // Exclude `.events.jsonl` sidecars — they share the .jsonl suffix.\n const files = readdirSync(dir).filter(\n (f) => f.endsWith(\".jsonl\") && !f.endsWith(\".events.jsonl\"),\n );\n return files\n .map((file) => {\n const path = join(dir, file);\n const stat = statSync(path);\n const name = file.replace(/\\.jsonl$/, \"\");\n const messageCount = countLines(path);\n return {\n name,\n path,\n size: stat.size,\n messageCount,\n mtime: stat.mtime,\n meta: loadSessionMeta(name),\n };\n })\n .sort((a, b) => b.mtime.getTime() - a.mtime.getTime());\n } catch {\n return [];\n }\n}\n\n/** Strict match — legacy sessions without meta.workspace are hidden; resume by name still works. */\nexport function listSessionsForWorkspace(workspace: string): SessionInfo[] {\n return listSessions().filter((s) => s.meta.workspace === workspace);\n}\n\nfunction metaPath(name: string): string {\n return join(sessionsDir(), `${sanitizeName(name)}.meta.json`);\n}\n\nexport function loadSessionMeta(name: string): SessionMeta {\n const p = metaPath(name);\n if (!existsSync(p)) return {};\n try {\n const raw = JSON.parse(readFileSync(p, \"utf8\")) as SessionMeta;\n return raw && typeof raw === \"object\" ? raw : {};\n } catch {\n return {};\n }\n}\n\nexport function patchSessionMeta(name: string, patch: Partial<SessionMeta>): SessionMeta {\n const cur = loadSessionMeta(name);\n const next: SessionMeta = { ...cur, ...patch };\n const p = metaPath(name);\n mkdirSync(dirname(p), { recursive: true });\n writeFileSync(p, JSON.stringify(next), \"utf8\");\n try {\n chmodSync(p, 0o600);\n } catch {\n /* chmod not supported */\n }\n return next;\n}\n\n/** Renames the JSONL plus all known sidecars together; returns false if target already exists. */\nexport function renameSession(oldName: string, newName: string): boolean {\n const safeOld = sanitizeName(oldName);\n const safeNew = sanitizeName(newName);\n if (safeOld === safeNew) return false;\n const oldJsonl = sessionPath(oldName);\n const newJsonl = sessionPath(newName);\n if (!existsSync(oldJsonl) || existsSync(newJsonl)) return false;\n renameSync(oldJsonl, newJsonl);\n for (const ext of [\".events.jsonl\", \".meta.json\", \".pending.json\", \".plan.json\"]) {\n const oldP = oldJsonl.replace(/\\.jsonl$/, ext);\n const newP = newJsonl.replace(/\\.jsonl$/, ext);\n if (existsSync(oldP)) {\n try {\n renameSync(oldP, newP);\n } catch {\n /* sidecar rename failed — leave the jsonl rename in place */\n }\n }\n }\n return true;\n}\n\n/** Best-effort: per-file delete errors are swallowed so partial pruning still finishes. */\nexport function pruneStaleSessions(daysOld = 90): string[] {\n const cutoff = Date.now() - daysOld * 24 * 60 * 60 * 1000;\n const deleted: string[] = [];\n for (const s of listSessions()) {\n if (s.mtime.getTime() < cutoff) {\n if (deleteSession(s.name)) deleted.push(s.name);\n }\n }\n return deleted;\n}\n\nexport function deleteSession(name: string): boolean {\n const path = sessionPath(name);\n try {\n unlinkSync(path);\n for (const ext of [\".events.jsonl\", \".pending.json\", \".meta.json\", \".plan.json\"]) {\n const sidecar = path.replace(/\\.jsonl$/, ext);\n try {\n unlinkSync(sidecar);\n } catch {\n /* expected when the sidecar doesn't exist */\n }\n }\n return true;\n } catch {\n return false;\n }\n}\n\n/** Non-atomic truncate+write window is acceptable — a concurrent crash leaves the session file empty, same end state as the user deleting it. */\nexport function rewriteSession(name: string, messages: ChatMessage[]): void {\n const path = sessionPath(name);\n mkdirSync(dirname(path), { recursive: true });\n const body = messages.map((m) => JSON.stringify(m)).join(\"\\n\");\n writeFileSync(path, body ? `${body}\\n` : \"\", \"utf8\");\n try {\n chmodSync(path, 0o600);\n } catch {\n /* chmod not supported */\n }\n}\n\n/** Rotate the live jsonl + sidecars to `<name>__archive_<ts>` so /new doesn't destroy history. Returns the archive name, or null if there was nothing to archive. */\nexport function archiveSession(name: string): string | null {\n const path = sessionPath(name);\n if (!existsSync(path)) return null;\n try {\n if (statSync(path).size === 0) return null;\n } catch {\n return null;\n }\n for (let attempt = 0; attempt < 5; attempt++) {\n const target = `${name}__archive_${timestampSuffix()}${attempt > 0 ? `_${attempt}` : \"\"}`;\n if (renameSession(name, target)) return target;\n }\n return null;\n}\n\nfunction countLines(path: string): number {\n try {\n const raw = readFileSync(path, \"utf8\");\n return raw.split(/\\r?\\n/).filter((l) => l.trim()).length;\n } catch {\n return 0;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire as __cr } from 'node:module'; if (typeof globalThis.require === 'undefined') { globalThis.require = __cr(import.meta.url); }
3
+ import {
4
+ Box_default,
5
+ Text,
6
+ require_react
7
+ } from "./chunk-WREKDFXT.js";
8
+ import {
9
+ t
10
+ } from "./chunk-5TWQD73O.js";
11
+ import {
12
+ __toESM
13
+ } from "./chunk-TUK7OWJA.js";
14
+
15
+ // src/cli/ui/RecordView.tsx
16
+ var import_react = __toESM(require_react(), 1);
17
+ function RecordView({ rec, compact = false }) {
18
+ const toolArgsMax = compact ? 120 : 200;
19
+ const toolContentMax = compact ? 200 : 400;
20
+ if (rec.role === "user") {
21
+ const content = rec.content.includes("\n") ? rec.content.split("\n").join("\n ") : rec.content;
22
+ return /* @__PURE__ */ import_react.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react.default.createElement(Text, { bold: true, color: "cyan" }, t("recordView.userPrefix")), /* @__PURE__ */ import_react.default.createElement(Text, null, content));
23
+ }
24
+ if (rec.role === "assistant_final") {
25
+ return /* @__PURE__ */ import_react.default.createElement(Box_default, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ import_react.default.createElement(Box_default, null, /* @__PURE__ */ import_react.default.createElement(Text, { bold: true, color: "green" }, t("recordView.assistant")), rec.cost !== void 0 ? /* @__PURE__ */ import_react.default.createElement(Text, { dimColor: true }, " $", rec.cost.toFixed(6)) : null, rec.usage ? /* @__PURE__ */ import_react.default.createElement(CacheBadge, { usage: rec.usage }) : null), rec.content ? /* @__PURE__ */ import_react.default.createElement(Text, null, rec.content) : /* @__PURE__ */ import_react.default.createElement(Text, { dimColor: true, italic: true }, t("recordView.toolCallOnly")));
26
+ }
27
+ if (rec.role === "tool") {
28
+ return /* @__PURE__ */ import_react.default.createElement(Box_default, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ import_react.default.createElement(Text, { color: "yellow" }, t("recordView.toolPrefix"), rec.tool ?? "?", ">"), rec.args ? /* @__PURE__ */ import_react.default.createElement(Text, { dimColor: true }, t("recordView.argsLabel"), truncate(rec.args, toolArgsMax)) : null, /* @__PURE__ */ import_react.default.createElement(Text, { dimColor: true }, t("recordView.resultArrow"), truncate(rec.content, toolContentMax)));
29
+ }
30
+ if (rec.role === "error") {
31
+ return /* @__PURE__ */ import_react.default.createElement(Box_default, { marginTop: 1 }, /* @__PURE__ */ import_react.default.createElement(Text, { color: "red", bold: true }, t("recordView.error")), /* @__PURE__ */ import_react.default.createElement(Text, { color: "red" }, rec.error ?? rec.content));
32
+ }
33
+ if (rec.role === "done" || rec.role === "assistant_delta") {
34
+ return null;
35
+ }
36
+ return /* @__PURE__ */ import_react.default.createElement(Box_default, null, /* @__PURE__ */ import_react.default.createElement(Text, { dimColor: true }, "[", rec.role, "] ", rec.content));
37
+ }
38
+ function CacheBadge({ usage }) {
39
+ const hit = usage.prompt_cache_hit_tokens ?? 0;
40
+ const miss = usage.prompt_cache_miss_tokens ?? 0;
41
+ const total = hit + miss;
42
+ if (total === 0) return null;
43
+ const pct = hit / total * 100;
44
+ const color = pct >= 70 ? "green" : pct >= 40 ? "yellow" : "red";
45
+ return /* @__PURE__ */ import_react.default.createElement(Text, null, /* @__PURE__ */ import_react.default.createElement(Text, { dimColor: true }, t("recordView.cache")), /* @__PURE__ */ import_react.default.createElement(Text, { color }, pct.toFixed(1), "%"));
46
+ }
47
+ function truncate(s, max) {
48
+ return s.length <= max ? s : `${s.slice(0, max)}${t("recordView.truncateExtra", { extra: s.length - max })}`;
49
+ }
50
+
51
+ export {
52
+ RecordView
53
+ };
54
+ //# sourceMappingURL=chunk-YBVCZJU4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/cli/ui/RecordView.tsx"],"sourcesContent":["/** Shared renderer for a single TranscriptRecord — used by ReplayApp and DiffApp. */\n\nimport { Box, Text } from \"ink\";\nimport React from \"react\";\nimport { t } from \"../../i18n/index.js\";\nimport type { TranscriptRecord } from \"../../transcript/log.js\";\n\nexport interface RecordViewProps {\n rec: TranscriptRecord;\n /**\n * When rendering side-by-side in diff mode, shorter truncation limits\n * keep long tool results from dominating the pane. Passes through\n * untouched when undefined.\n */\n compact?: boolean;\n}\n\nexport function RecordView({ rec, compact = false }: RecordViewProps) {\n const toolArgsMax = compact ? 120 : 200;\n const toolContentMax = compact ? 200 : 400;\n\n if (rec.role === \"user\") {\n // Continuation indent of 6 spaces matches the `you › ` prefix width\n // so wrapped multi-line user messages align under the body text\n // instead of jumping to column 0.\n const content = rec.content.includes(\"\\n\")\n ? rec.content.split(\"\\n\").join(\"\\n \")\n : rec.content;\n return (\n <Box marginTop={1}>\n <Text bold color=\"cyan\">\n {t(\"recordView.userPrefix\")}\n </Text>\n <Text>{content}</Text>\n </Box>\n );\n }\n if (rec.role === \"assistant_final\") {\n return (\n <Box flexDirection=\"column\" marginTop={1}>\n <Box>\n <Text bold color=\"green\">\n {t(\"recordView.assistant\")}\n </Text>\n {rec.cost !== undefined ? (\n <Text dimColor>\n {\" $\"}\n {rec.cost.toFixed(6)}\n </Text>\n ) : null}\n {rec.usage ? <CacheBadge usage={rec.usage} /> : null}\n </Box>\n {rec.content ? (\n <Text>{rec.content}</Text>\n ) : (\n <Text dimColor italic>\n {t(\"recordView.toolCallOnly\")}\n </Text>\n )}\n </Box>\n );\n }\n if (rec.role === \"tool\") {\n return (\n <Box flexDirection=\"column\" marginTop={1}>\n <Text color=\"yellow\">\n {t(\"recordView.toolPrefix\")}\n {rec.tool ?? \"?\"}\n {\">\"}\n </Text>\n {rec.args ? (\n <Text dimColor>\n {t(\"recordView.argsLabel\")}\n {truncate(rec.args, toolArgsMax)}\n </Text>\n ) : null}\n <Text dimColor>\n {t(\"recordView.resultArrow\")}\n {truncate(rec.content, toolContentMax)}\n </Text>\n </Box>\n );\n }\n if (rec.role === \"error\") {\n return (\n <Box marginTop={1}>\n <Text color=\"red\" bold>\n {t(\"recordView.error\")}\n </Text>\n <Text color=\"red\">{rec.error ?? rec.content}</Text>\n </Box>\n );\n }\n if (rec.role === \"done\" || rec.role === \"assistant_delta\") {\n // Noise in replay; skip.\n return null;\n }\n return (\n <Box>\n <Text dimColor>\n [{rec.role}] {rec.content}\n </Text>\n </Box>\n );\n}\n\nfunction CacheBadge({ usage }: { usage: NonNullable<TranscriptRecord[\"usage\"]> }) {\n const hit = usage.prompt_cache_hit_tokens ?? 0;\n const miss = usage.prompt_cache_miss_tokens ?? 0;\n const total = hit + miss;\n if (total === 0) return null;\n const pct = (hit / total) * 100;\n const color = pct >= 70 ? \"green\" : pct >= 40 ? \"yellow\" : \"red\";\n return (\n <Text>\n <Text dimColor>{t(\"recordView.cache\")}</Text>\n <Text color={color}>{pct.toFixed(1)}%</Text>\n </Text>\n );\n}\n\nfunction truncate(s: string, max: number): string {\n return s.length <= max\n ? s\n : `${s.slice(0, max)}${t(\"recordView.truncateExtra\", { extra: s.length - max })}`;\n}\n"],"mappings":"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","names":["React"]}