ytimesy 0.1.0 → 0.1.2
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/LICENSE +21 -0
- package/README.md +76 -15
- package/dist/ytimesy.mjs +425 -425
- package/package.json +35 -5
package/dist/ytimesy.mjs
CHANGED
|
@@ -9,7 +9,260 @@ import { randomBytes } from "node:crypto";
|
|
|
9
9
|
import fs16 from "node:fs";
|
|
10
10
|
import path20 from "node:path";
|
|
11
11
|
|
|
12
|
-
//
|
|
12
|
+
// src/api.ts
|
|
13
|
+
var ApiError = class extends Error {
|
|
14
|
+
constructor(message, status, path21) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.status = status;
|
|
17
|
+
this.path = path21;
|
|
18
|
+
}
|
|
19
|
+
status;
|
|
20
|
+
path;
|
|
21
|
+
};
|
|
22
|
+
var TIMEOUT_MS = 12e4;
|
|
23
|
+
async function errorMessage(response) {
|
|
24
|
+
const text = (await response.text().catch(() => "")).trim();
|
|
25
|
+
try {
|
|
26
|
+
const json = JSON.parse(text);
|
|
27
|
+
if (typeof json.error === "string") return json.error;
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
return text.slice(0, 500) || `HTTP ${response.status}`;
|
|
31
|
+
}
|
|
32
|
+
var ImportApi = class {
|
|
33
|
+
constructor(base, token = null, userAgent = "ytimesy-cli") {
|
|
34
|
+
this.base = base;
|
|
35
|
+
this.token = token;
|
|
36
|
+
this.userAgent = userAgent;
|
|
37
|
+
}
|
|
38
|
+
base;
|
|
39
|
+
token;
|
|
40
|
+
userAgent;
|
|
41
|
+
setToken(token) {
|
|
42
|
+
this.token = token;
|
|
43
|
+
}
|
|
44
|
+
async request(path21, init) {
|
|
45
|
+
const url = new URL(path21, this.base).toString();
|
|
46
|
+
let response;
|
|
47
|
+
try {
|
|
48
|
+
response = await fetch(url, {
|
|
49
|
+
method: init.method,
|
|
50
|
+
body: init.body,
|
|
51
|
+
headers: {
|
|
52
|
+
"user-agent": this.userAgent,
|
|
53
|
+
...init.contentType ? { "content-type": init.contentType } : {},
|
|
54
|
+
...this.token ? { authorization: `Bearer ${this.token}` } : {}
|
|
55
|
+
},
|
|
56
|
+
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
57
|
+
});
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new ApiError(
|
|
60
|
+
`Could not reach ${new URL(this.base).host}: ${error.message}`,
|
|
61
|
+
0,
|
|
62
|
+
path21
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
if (!response.ok)
|
|
66
|
+
throw new ApiError(await errorMessage(response), response.status, path21);
|
|
67
|
+
return await response.json();
|
|
68
|
+
}
|
|
69
|
+
json(path21, body) {
|
|
70
|
+
return this.request(path21, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
body: JSON.stringify(body),
|
|
73
|
+
contentType: "application/json"
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
start(body) {
|
|
77
|
+
return this.json("/api/local-import/start", body);
|
|
78
|
+
}
|
|
79
|
+
plan(groups) {
|
|
80
|
+
return this.json("/api/local-import/plan", {
|
|
81
|
+
groups
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/** A project for a repository the ytimesy app reaches (409: it cannot see it yet). */
|
|
85
|
+
createProject(body) {
|
|
86
|
+
return this.json(
|
|
87
|
+
"/api/local-import/create-project",
|
|
88
|
+
body
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
secrets(body) {
|
|
92
|
+
return this.json("/api/local-import/secrets", body);
|
|
93
|
+
}
|
|
94
|
+
session(body) {
|
|
95
|
+
return this.json("/api/local-import/sessions", body);
|
|
96
|
+
}
|
|
97
|
+
transcript(uploadPath, archive) {
|
|
98
|
+
return this.request(uploadPath, {
|
|
99
|
+
method: "PUT",
|
|
100
|
+
body: new Blob([new Uint8Array(archive)]),
|
|
101
|
+
contentType: "application/gzip"
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
finish(body) {
|
|
105
|
+
return this.json("/api/local-import/finish", body);
|
|
106
|
+
}
|
|
107
|
+
cancel() {
|
|
108
|
+
return this.json("/api/local-import/cancel", {});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// src/env-file.ts
|
|
113
|
+
import fs from "node:fs";
|
|
114
|
+
import path from "node:path";
|
|
115
|
+
var ENV_FILES = [".env", ".env.local"];
|
|
116
|
+
var NAME = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
|
|
117
|
+
function unescapeDouble(text) {
|
|
118
|
+
return text.replace(
|
|
119
|
+
/\\([nrt"\\$])/g,
|
|
120
|
+
(_, char) => char === "n" ? "\n" : char === "r" ? "\r" : char === "t" ? " " : char
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
function parseEnvText(text) {
|
|
124
|
+
const variables = /* @__PURE__ */ new Map();
|
|
125
|
+
const source = text.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
|
|
126
|
+
let index = 0;
|
|
127
|
+
while (index < source.length) {
|
|
128
|
+
const lineEnd = source.indexOf("\n", index);
|
|
129
|
+
const end = lineEnd === -1 ? source.length : lineEnd;
|
|
130
|
+
const line = source.slice(index, end);
|
|
131
|
+
const match = /^\s*(?:export\s+)?([^=\s]+)\s*=\s*/.exec(line);
|
|
132
|
+
if (!match || line.trimStart().startsWith("#")) {
|
|
133
|
+
index = end + 1;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const name = match[1];
|
|
137
|
+
const valueStart = index + match[0].length;
|
|
138
|
+
const quote = source[valueStart];
|
|
139
|
+
let value;
|
|
140
|
+
if (quote === '"' || quote === "'" || quote === "`") {
|
|
141
|
+
const close = source.indexOf(quote, valueStart + 1);
|
|
142
|
+
let at = close;
|
|
143
|
+
while (quote === '"' && at !== -1) {
|
|
144
|
+
let slashes = 0;
|
|
145
|
+
for (let back = at - 1; source[back] === "\\"; back--) slashes++;
|
|
146
|
+
if (slashes % 2 === 0) break;
|
|
147
|
+
at = source.indexOf(quote, at + 1);
|
|
148
|
+
}
|
|
149
|
+
if (at === -1) {
|
|
150
|
+
value = line.slice(match[0].length).trim();
|
|
151
|
+
index = end + 1;
|
|
152
|
+
} else {
|
|
153
|
+
const raw = source.slice(valueStart + 1, at);
|
|
154
|
+
value = quote === '"' ? unescapeDouble(raw) : raw;
|
|
155
|
+
const after = source.indexOf("\n", at);
|
|
156
|
+
index = after === -1 ? source.length : after + 1;
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
value = line.slice(match[0].length).replace(/(^|\s)#.*$/, "").trim();
|
|
160
|
+
index = end + 1;
|
|
161
|
+
}
|
|
162
|
+
if (NAME.test(name)) variables.set(name, value);
|
|
163
|
+
}
|
|
164
|
+
return [...variables].map(([name, value]) => ({ name, value }));
|
|
165
|
+
}
|
|
166
|
+
function readEnvFiles(folder) {
|
|
167
|
+
const variables = [];
|
|
168
|
+
for (const source of ENV_FILES) {
|
|
169
|
+
let text;
|
|
170
|
+
try {
|
|
171
|
+
const file = path.join(folder, source);
|
|
172
|
+
if (!fs.statSync(file).isFile()) continue;
|
|
173
|
+
text = fs.readFileSync(file, "utf8");
|
|
174
|
+
} catch {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
for (const variable of parseEnvText(text))
|
|
178
|
+
variables.push({ ...variable, source });
|
|
179
|
+
}
|
|
180
|
+
return variables;
|
|
181
|
+
}
|
|
182
|
+
var TOO_COMMON = /^(?:true|false|yes|no|null|none|on|off|development|production|staging|test|localhost|127\.0\.0\.1|0\.0\.0\.0|\d+(?:\.\d+)?)$/i;
|
|
183
|
+
function redactionValues(variables) {
|
|
184
|
+
const values = /* @__PURE__ */ new Set();
|
|
185
|
+
for (const { value } of variables) {
|
|
186
|
+
const trimmed = value.trim();
|
|
187
|
+
if (trimmed.length >= 8 && !TOO_COMMON.test(trimmed)) values.add(trimmed);
|
|
188
|
+
if (value.length >= 8 && value !== trimmed && !TOO_COMMON.test(value))
|
|
189
|
+
values.add(value);
|
|
190
|
+
}
|
|
191
|
+
return [...values];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// src/github-cli.ts
|
|
195
|
+
import { execFileSync } from "node:child_process";
|
|
196
|
+
var GithubCliError = class extends Error {
|
|
197
|
+
};
|
|
198
|
+
function gh(args, env) {
|
|
199
|
+
return execFileSync("gh", args, {
|
|
200
|
+
env: {
|
|
201
|
+
...env,
|
|
202
|
+
GH_PROMPT_DISABLED: "1",
|
|
203
|
+
NO_COLOR: "1"
|
|
204
|
+
},
|
|
205
|
+
encoding: "utf8",
|
|
206
|
+
timeout: 6e4,
|
|
207
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
function githubCliStatus(env) {
|
|
211
|
+
try {
|
|
212
|
+
gh(["--version"], env);
|
|
213
|
+
} catch {
|
|
214
|
+
return { ready: false, reason: "missing" };
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
gh(["auth", "status", "--hostname", "github.com"], env);
|
|
218
|
+
return { ready: true };
|
|
219
|
+
} catch {
|
|
220
|
+
return { ready: false, reason: "signed-out" };
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
function githubCliNextStep(status) {
|
|
224
|
+
const otherwise = "or create the repository at https://github.com/new, make it this folder's origin remote (git init first if needed) and re-run without --create-repo";
|
|
225
|
+
return status.ready ? "" : status.reason === "missing" ? `Install the GitHub CLI (https://cli.github.com) and run \`gh auth login\`, then re-run the same command; ${otherwise}.` : `Run \`gh auth login\` to sign in to GitHub, then re-run the same command; ${otherwise}.`;
|
|
226
|
+
}
|
|
227
|
+
function createPrivateRepository(name, env) {
|
|
228
|
+
let output;
|
|
229
|
+
try {
|
|
230
|
+
output = gh(
|
|
231
|
+
[
|
|
232
|
+
"api",
|
|
233
|
+
"--method",
|
|
234
|
+
"POST",
|
|
235
|
+
"user/repos",
|
|
236
|
+
"-f",
|
|
237
|
+
`name=${name}`,
|
|
238
|
+
"-F",
|
|
239
|
+
"private=true"
|
|
240
|
+
],
|
|
241
|
+
env
|
|
242
|
+
);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
const failure = error;
|
|
245
|
+
const text = `${String(failure.stdout ?? "")} ${String(failure.stderr ?? "")}`;
|
|
246
|
+
if (/name already exists/i.test(text))
|
|
247
|
+
throw new GithubCliError(
|
|
248
|
+
`A repository named ${name} already exists on your GitHub account.`
|
|
249
|
+
);
|
|
250
|
+
throw new GithubCliError(
|
|
251
|
+
`gh could not create ${name}: ${text.trim().split("\n").slice(-2).join(" ") || error.message}`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
const created = JSON.parse(output);
|
|
255
|
+
if (!created.full_name)
|
|
256
|
+
throw new GithubCliError(`gh did not return the new repository ${name}.`);
|
|
257
|
+
return {
|
|
258
|
+
fullName: created.full_name,
|
|
259
|
+
defaultBranch: created.default_branch || "main",
|
|
260
|
+
cloneUrl: created.clone_url || `https://github.com/${created.full_name}.git`,
|
|
261
|
+
sshUrl: created.ssh_url || `git@github.com:${created.full_name}.git`
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/local-agents.ts
|
|
13
266
|
var LOCAL_AGENT_NAMES = {
|
|
14
267
|
claude: "Claude Code",
|
|
15
268
|
codex: "Codex",
|
|
@@ -20,9 +273,9 @@ var LOCAL_AGENT_NAMES = {
|
|
|
20
273
|
cursor: "Cursor"
|
|
21
274
|
};
|
|
22
275
|
|
|
23
|
-
//
|
|
24
|
-
import
|
|
25
|
-
import
|
|
276
|
+
// src/local-sessions/io.ts
|
|
277
|
+
import fs2 from "node:fs";
|
|
278
|
+
import path2 from "node:path";
|
|
26
279
|
import zlib from "node:zlib";
|
|
27
280
|
var NEWLINE = 10;
|
|
28
281
|
var DEFAULT_CHUNK_BYTES = 256 * 1024;
|
|
@@ -51,14 +304,14 @@ function asArray(value) {
|
|
|
51
304
|
}
|
|
52
305
|
function fileMtimeMs(file) {
|
|
53
306
|
try {
|
|
54
|
-
return
|
|
307
|
+
return fs2.statSync(file).mtimeMs;
|
|
55
308
|
} catch {
|
|
56
309
|
return void 0;
|
|
57
310
|
}
|
|
58
311
|
}
|
|
59
312
|
function listDirectory(dir) {
|
|
60
313
|
try {
|
|
61
|
-
return
|
|
314
|
+
return fs2.readdirSync(dir, { withFileTypes: true });
|
|
62
315
|
} catch {
|
|
63
316
|
return [];
|
|
64
317
|
}
|
|
@@ -66,24 +319,24 @@ function listDirectory(dir) {
|
|
|
66
319
|
function readHeadText(file, maxBytes) {
|
|
67
320
|
let fd;
|
|
68
321
|
try {
|
|
69
|
-
fd =
|
|
322
|
+
fd = fs2.openSync(file, "r");
|
|
70
323
|
const buffer = Buffer.alloc(maxBytes);
|
|
71
|
-
const bytesRead =
|
|
324
|
+
const bytesRead = fs2.readSync(fd, buffer, 0, maxBytes, 0);
|
|
72
325
|
const head = buffer.subarray(0, bytesRead);
|
|
73
326
|
if (!isCompressedFile(file)) return head.toString("utf8");
|
|
74
327
|
return zstdPrefix(head).subarray(0, maxBytes).toString("utf8");
|
|
75
328
|
} catch {
|
|
76
329
|
return "";
|
|
77
330
|
} finally {
|
|
78
|
-
if (fd !== void 0)
|
|
331
|
+
if (fd !== void 0) fs2.closeSync(fd);
|
|
79
332
|
}
|
|
80
333
|
}
|
|
81
334
|
function readCompressedHead(file, compressedBytes) {
|
|
82
335
|
let fd;
|
|
83
336
|
try {
|
|
84
|
-
fd =
|
|
337
|
+
fd = fs2.openSync(file, "r");
|
|
85
338
|
const buffer = Buffer.alloc(compressedBytes);
|
|
86
|
-
const bytesRead =
|
|
339
|
+
const bytesRead = fs2.readSync(fd, buffer, 0, compressedBytes, 0);
|
|
87
340
|
return {
|
|
88
341
|
text: zstdPrefix(buffer.subarray(0, bytesRead)).toString("utf8"),
|
|
89
342
|
compressedRead: bytesRead
|
|
@@ -91,7 +344,7 @@ function readCompressedHead(file, compressedBytes) {
|
|
|
91
344
|
} catch {
|
|
92
345
|
return { text: "", compressedRead: 0 };
|
|
93
346
|
} finally {
|
|
94
|
-
if (fd !== void 0)
|
|
347
|
+
if (fd !== void 0) fs2.closeSync(fd);
|
|
95
348
|
}
|
|
96
349
|
}
|
|
97
350
|
function isCompressedFile(file) {
|
|
@@ -116,7 +369,7 @@ function decompressedFile(file) {
|
|
|
116
369
|
const decompress = zlib.zstdDecompressSync;
|
|
117
370
|
if (!decompress) return void 0;
|
|
118
371
|
try {
|
|
119
|
-
const data = decompress(
|
|
372
|
+
const data = decompress(fs2.readFileSync(file));
|
|
120
373
|
decompressedCache = { key, data };
|
|
121
374
|
return data;
|
|
122
375
|
} catch {
|
|
@@ -141,14 +394,14 @@ function openByteSource(file) {
|
|
|
141
394
|
}
|
|
142
395
|
let fd;
|
|
143
396
|
try {
|
|
144
|
-
fd =
|
|
397
|
+
fd = fs2.openSync(file, "r");
|
|
145
398
|
} catch {
|
|
146
399
|
return void 0;
|
|
147
400
|
}
|
|
148
401
|
return {
|
|
149
|
-
size:
|
|
150
|
-
read: (target, length, position) =>
|
|
151
|
-
close: () =>
|
|
402
|
+
size: fs2.fstatSync(fd).size,
|
|
403
|
+
read: (target, length, position) => fs2.readSync(fd, target, 0, length, position),
|
|
404
|
+
close: () => fs2.closeSync(fd)
|
|
152
405
|
};
|
|
153
406
|
}
|
|
154
407
|
function readFirstLine(file) {
|
|
@@ -231,23 +484,23 @@ function* linesBackward(file, chunkBytes = DEFAULT_CHUNK_BYTES) {
|
|
|
231
484
|
}
|
|
232
485
|
}
|
|
233
486
|
function canonicalPath(input) {
|
|
234
|
-
const resolved =
|
|
487
|
+
const resolved = path2.resolve(input);
|
|
235
488
|
try {
|
|
236
|
-
return
|
|
489
|
+
return fs2.realpathSync.native(resolved);
|
|
237
490
|
} catch {
|
|
238
491
|
return resolved;
|
|
239
492
|
}
|
|
240
493
|
}
|
|
241
494
|
function isPathInside(candidate, root) {
|
|
242
495
|
if (candidate === root) return true;
|
|
243
|
-
const prefix = root.endsWith(
|
|
496
|
+
const prefix = root.endsWith(path2.sep) ? root : root + path2.sep;
|
|
244
497
|
return candidate.startsWith(prefix);
|
|
245
498
|
}
|
|
246
499
|
function findGitRoot(start) {
|
|
247
500
|
let current = canonicalPath(start);
|
|
248
501
|
while (true) {
|
|
249
|
-
if (
|
|
250
|
-
const parent =
|
|
502
|
+
if (fs2.existsSync(path2.join(current, ".git"))) return current;
|
|
503
|
+
const parent = path2.dirname(current);
|
|
251
504
|
if (parent === current) return void 0;
|
|
252
505
|
current = parent;
|
|
253
506
|
}
|
|
@@ -256,17 +509,17 @@ function fileUrlToPath(value) {
|
|
|
256
509
|
return value.startsWith("file://") ? decodeURIComponent(value.slice("file://".length)) : value;
|
|
257
510
|
}
|
|
258
511
|
|
|
259
|
-
//
|
|
260
|
-
import
|
|
512
|
+
// src/local-sessions/machine.ts
|
|
513
|
+
import fs9 from "node:fs";
|
|
261
514
|
import os9 from "node:os";
|
|
262
|
-
import
|
|
515
|
+
import path12 from "node:path";
|
|
263
516
|
import { performance } from "node:perf_hooks";
|
|
264
517
|
|
|
265
|
-
//
|
|
518
|
+
// src/local-sessions/claude.ts
|
|
266
519
|
import os from "node:os";
|
|
267
|
-
import
|
|
520
|
+
import path3 from "node:path";
|
|
268
521
|
|
|
269
|
-
//
|
|
522
|
+
// src/local-sessions/tool-calls.ts
|
|
270
523
|
var MAX_TOOL_OUTPUT_CHARS = 3e3;
|
|
271
524
|
var MAX_TOOL_INPUT_CHARS = 8e3;
|
|
272
525
|
var SHELL_TOOLS = /* @__PURE__ */ new Set([
|
|
@@ -458,7 +711,7 @@ function setToolOutput(call, output) {
|
|
|
458
711
|
if (text) call.output = truncateText(text, MAX_TOOL_OUTPUT_CHARS);
|
|
459
712
|
}
|
|
460
713
|
|
|
461
|
-
//
|
|
714
|
+
// src/local-sessions/transcript.ts
|
|
462
715
|
var BUDGET_SLACK_RATIO = 0.25;
|
|
463
716
|
var TURN_ALIGNMENT_LIMIT_CHARS = 4e5;
|
|
464
717
|
function visitTranscriptRecords(file, scan, mode, visit) {
|
|
@@ -634,7 +887,7 @@ function titleFromPrompt(prompt, maxChars = 80) {
|
|
|
634
887
|
return `${(lastSpace > maxChars / 2 ? cut.slice(0, lastSpace) : cut).trimEnd()}\u2026`;
|
|
635
888
|
}
|
|
636
889
|
|
|
637
|
-
//
|
|
890
|
+
// src/local-sessions/claude.ts
|
|
638
891
|
var HEAD_SCAN_LINES = 40;
|
|
639
892
|
var MAX_FILES_VERIFIED_PER_DIRECTORY = 5;
|
|
640
893
|
var INJECTED_USER_TEXT_PREFIXES = [
|
|
@@ -652,7 +905,7 @@ var INJECTED_USER_TEXT_PREFIXES = [
|
|
|
652
905
|
"Caveat: The messages below were generated by the user while running local commands"
|
|
653
906
|
];
|
|
654
907
|
function claudeProjectsDirectory(home = os.homedir()) {
|
|
655
|
-
return
|
|
908
|
+
return path3.join(home, ".claude", "projects");
|
|
656
909
|
}
|
|
657
910
|
function encodeClaudeProjectDirectory(cwd) {
|
|
658
911
|
return cwd.replace(/[^A-Za-z0-9]/g, "-");
|
|
@@ -682,9 +935,9 @@ function discoverClaudeSessions(repoRoot, home = os.homedir(), limit = 1) {
|
|
|
682
935
|
if (!entry.isDirectory()) continue;
|
|
683
936
|
if (entry.name !== encodedRoot && !entry.name.startsWith(`${encodedRoot}-`))
|
|
684
937
|
continue;
|
|
685
|
-
const directory =
|
|
938
|
+
const directory = path3.join(projectsDirectory, entry.name);
|
|
686
939
|
const files = listDirectory(directory).filter((file) => file.isFile() && file.name.endsWith(".jsonl")).map((file) => {
|
|
687
|
-
const location =
|
|
940
|
+
const location = path3.join(directory, file.name);
|
|
688
941
|
return {
|
|
689
942
|
location,
|
|
690
943
|
id: file.name.slice(0, -".jsonl".length),
|
|
@@ -894,42 +1147,42 @@ function claudeSessionRefForFile(file) {
|
|
|
894
1147
|
if (!cwd) return void 0;
|
|
895
1148
|
return {
|
|
896
1149
|
tool: "claude",
|
|
897
|
-
id:
|
|
1150
|
+
id: path3.basename(file, ".jsonl"),
|
|
898
1151
|
cwd,
|
|
899
1152
|
updatedAt: fileMtimeMs(file) ?? 0,
|
|
900
1153
|
location: file
|
|
901
1154
|
};
|
|
902
1155
|
}
|
|
903
1156
|
|
|
904
|
-
//
|
|
1157
|
+
// src/local-sessions/codex.ts
|
|
905
1158
|
import os2 from "node:os";
|
|
906
|
-
import
|
|
1159
|
+
import path5 from "node:path";
|
|
907
1160
|
|
|
908
|
-
//
|
|
909
|
-
import
|
|
910
|
-
import
|
|
1161
|
+
// src/local-sessions/git.ts
|
|
1162
|
+
import fs3 from "node:fs";
|
|
1163
|
+
import path4 from "node:path";
|
|
911
1164
|
function readText(file) {
|
|
912
1165
|
try {
|
|
913
|
-
return
|
|
1166
|
+
return fs3.readFileSync(file, "utf8");
|
|
914
1167
|
} catch {
|
|
915
1168
|
return void 0;
|
|
916
1169
|
}
|
|
917
1170
|
}
|
|
918
1171
|
function gitDirectoryOf(root) {
|
|
919
|
-
const dotGit =
|
|
1172
|
+
const dotGit = path4.join(root, ".git");
|
|
920
1173
|
let stat;
|
|
921
1174
|
try {
|
|
922
|
-
stat =
|
|
1175
|
+
stat = fs3.statSync(dotGit);
|
|
923
1176
|
} catch {
|
|
924
1177
|
return void 0;
|
|
925
1178
|
}
|
|
926
1179
|
if (stat.isDirectory()) return dotGit;
|
|
927
1180
|
const match = /^gitdir:\s*(.+)$/m.exec(readText(dotGit) ?? "");
|
|
928
|
-
return match?.[1] ?
|
|
1181
|
+
return match?.[1] ? path4.resolve(root, match[1].trim()) : void 0;
|
|
929
1182
|
}
|
|
930
1183
|
function commonDirectoryOf(gitDirectory) {
|
|
931
|
-
const pointer = readText(
|
|
932
|
-
return pointer ? canonicalPath(
|
|
1184
|
+
const pointer = readText(path4.join(gitDirectory, "commondir"))?.trim();
|
|
1185
|
+
return pointer ? canonicalPath(path4.resolve(gitDirectory, pointer)) : canonicalPath(gitDirectory);
|
|
933
1186
|
}
|
|
934
1187
|
function originUrlFromConfig(config) {
|
|
935
1188
|
let inOrigin = false;
|
|
@@ -971,7 +1224,7 @@ function normalizeRemote(url) {
|
|
|
971
1224
|
return `${host.toLowerCase()}/${cleaned}`;
|
|
972
1225
|
}
|
|
973
1226
|
function branchOf(gitDirectory) {
|
|
974
|
-
const head = readText(
|
|
1227
|
+
const head = readText(path4.join(gitDirectory, "HEAD"))?.trim();
|
|
975
1228
|
const match = head ? /^ref:\s*refs\/heads\/(.+)$/.exec(head) : null;
|
|
976
1229
|
return match?.[1];
|
|
977
1230
|
}
|
|
@@ -988,7 +1241,7 @@ function gitIdentity(folder) {
|
|
|
988
1241
|
}
|
|
989
1242
|
const commonDir = commonDirectoryOf(gitDirectory);
|
|
990
1243
|
const remoteUrl = originUrlFromConfig(
|
|
991
|
-
readText(
|
|
1244
|
+
readText(path4.join(commonDir, "config")) ?? ""
|
|
992
1245
|
);
|
|
993
1246
|
const identity = {
|
|
994
1247
|
root,
|
|
@@ -1006,7 +1259,7 @@ function sameRepository(a, b) {
|
|
|
1006
1259
|
return a.remote !== void 0 && a.remote === b.remote;
|
|
1007
1260
|
}
|
|
1008
1261
|
|
|
1009
|
-
//
|
|
1262
|
+
// src/local-sessions/codex.ts
|
|
1010
1263
|
var HEAD_BYTES_FOR_CWD = 512 * 1024;
|
|
1011
1264
|
var ITEM_EVENT_PROBE_BYTES = 1024 * 1024;
|
|
1012
1265
|
var BARE_RESPONSE_ITEM_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -1029,8 +1282,8 @@ var INJECTED_USER_PREFIXES = [
|
|
|
1029
1282
|
var IDE_CONTEXT_PREFIX = "# Context from my IDE";
|
|
1030
1283
|
var IDE_REQUEST_HEADING = "## My request for Codex:";
|
|
1031
1284
|
function codexSessionsDirectory(home = os2.homedir()) {
|
|
1032
|
-
const codexHome = process.env.CODEX_HOME?.trim() ||
|
|
1033
|
-
return
|
|
1285
|
+
const codexHome = process.env.CODEX_HOME?.trim() || path5.join(home, ".codex");
|
|
1286
|
+
return path5.join(codexHome, "sessions");
|
|
1034
1287
|
}
|
|
1035
1288
|
function readCodexSessionMeta(file) {
|
|
1036
1289
|
const firstLine = readFirstLine(file);
|
|
@@ -1059,7 +1312,7 @@ function rolloutIdFromFileName(file) {
|
|
|
1059
1312
|
const match = /([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl(?:\.zst)?$/.exec(
|
|
1060
1313
|
file
|
|
1061
1314
|
);
|
|
1062
|
-
return match?.[1] ??
|
|
1315
|
+
return match?.[1] ?? path5.basename(file).replace(/\.jsonl(?:\.zst)?$/, "");
|
|
1063
1316
|
}
|
|
1064
1317
|
function isRolloutFileName(name) {
|
|
1065
1318
|
return name.endsWith(".jsonl") || name.endsWith(".jsonl.zst");
|
|
@@ -1068,7 +1321,7 @@ function listCodexRolloutFiles(sessionsDirectory) {
|
|
|
1068
1321
|
const files = [];
|
|
1069
1322
|
const walk = (directory, depth) => {
|
|
1070
1323
|
for (const entry of listDirectory(directory)) {
|
|
1071
|
-
const full =
|
|
1324
|
+
const full = path5.join(directory, entry.name);
|
|
1072
1325
|
if (entry.isDirectory() && depth < 3) walk(full, depth + 1);
|
|
1073
1326
|
else if (entry.isFile() && isRolloutFileName(entry.name))
|
|
1074
1327
|
files.push({ file: full, mtime: fileMtimeMs(full) ?? 0 });
|
|
@@ -1089,7 +1342,7 @@ function codexSessionRefForFile(file, mtime = fileMtimeMs(file) ?? 0) {
|
|
|
1089
1342
|
};
|
|
1090
1343
|
}
|
|
1091
1344
|
function codexWorktreesDirectory(home = os2.homedir()) {
|
|
1092
|
-
return
|
|
1345
|
+
return path5.join(path5.dirname(codexSessionsDirectory(home)), "worktrees");
|
|
1093
1346
|
}
|
|
1094
1347
|
function codexWorktreeMatches(cwd, file, repo, worktreesDirectory) {
|
|
1095
1348
|
if (!repo || !isPathInside(cwd, worktreesDirectory)) return false;
|
|
@@ -1233,7 +1486,7 @@ function parseCallOutput(output) {
|
|
|
1233
1486
|
return { text };
|
|
1234
1487
|
}
|
|
1235
1488
|
function relativeTo(base, file) {
|
|
1236
|
-
return base && !
|
|
1489
|
+
return base && !path5.isAbsolute(file) ? path5.join(base, file) : file;
|
|
1237
1490
|
}
|
|
1238
1491
|
function toolCallFromItem(item) {
|
|
1239
1492
|
const status = asString(item.status);
|
|
@@ -1473,13 +1726,13 @@ function readCodexSession(ref, options = {}) {
|
|
|
1473
1726
|
};
|
|
1474
1727
|
}
|
|
1475
1728
|
|
|
1476
|
-
//
|
|
1729
|
+
// src/local-sessions/cursor-agent.ts
|
|
1477
1730
|
import crypto from "node:crypto";
|
|
1478
|
-
import
|
|
1731
|
+
import fs4 from "node:fs";
|
|
1479
1732
|
import os3 from "node:os";
|
|
1480
|
-
import
|
|
1733
|
+
import path6 from "node:path";
|
|
1481
1734
|
|
|
1482
|
-
//
|
|
1735
|
+
// src/local-sessions/cursor-blobs.ts
|
|
1483
1736
|
function decodeProtobuf(buffer) {
|
|
1484
1737
|
const fields = [];
|
|
1485
1738
|
let offset = 0;
|
|
@@ -1655,7 +1908,7 @@ function* blobMessageEvents(message) {
|
|
|
1655
1908
|
}
|
|
1656
1909
|
}
|
|
1657
1910
|
|
|
1658
|
-
//
|
|
1911
|
+
// src/local-sessions/events.ts
|
|
1659
1912
|
function turnsFromEvents(events, activityOnly = false) {
|
|
1660
1913
|
const assembler = new TurnAssembler(activityOnly);
|
|
1661
1914
|
const pending = [];
|
|
@@ -1705,7 +1958,7 @@ function firstUserText(events) {
|
|
|
1705
1958
|
return void 0;
|
|
1706
1959
|
}
|
|
1707
1960
|
|
|
1708
|
-
//
|
|
1961
|
+
// src/local-sessions/sqlite.ts
|
|
1709
1962
|
var SQLITE_MIN_NODE = "22.13";
|
|
1710
1963
|
function loadSqlite() {
|
|
1711
1964
|
try {
|
|
@@ -1719,22 +1972,22 @@ function sqliteAvailable() {
|
|
|
1719
1972
|
return loadSqlite() !== void 0;
|
|
1720
1973
|
}
|
|
1721
1974
|
|
|
1722
|
-
//
|
|
1975
|
+
// src/local-sessions/cursor-agent.ts
|
|
1723
1976
|
var PLACEHOLDER_TITLES = /* @__PURE__ */ new Set(["New Agent", "New Chat", "New chat"]);
|
|
1724
1977
|
function cursorConfigDirectory(home = os3.homedir()) {
|
|
1725
1978
|
const override = process.env.CURSOR_CONFIG_DIR?.trim();
|
|
1726
1979
|
if (override) return override;
|
|
1727
1980
|
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
1728
|
-
return xdg ?
|
|
1981
|
+
return xdg ? path6.join(xdg, "cursor") : path6.join(home, ".cursor");
|
|
1729
1982
|
}
|
|
1730
1983
|
function cursorChatsDirectory(home = os3.homedir()) {
|
|
1731
|
-
return
|
|
1984
|
+
return path6.join(cursorConfigDirectory(home), "chats");
|
|
1732
1985
|
}
|
|
1733
1986
|
function cursorChatFolderHash(cwd) {
|
|
1734
|
-
return crypto.createHash("md5").update(
|
|
1987
|
+
return crypto.createHash("md5").update(path6.resolve(cwd)).digest("hex");
|
|
1735
1988
|
}
|
|
1736
1989
|
function openSqliteReadOnly(file) {
|
|
1737
|
-
if (!
|
|
1990
|
+
if (!fs4.existsSync(file)) return void 0;
|
|
1738
1991
|
const sqlite = loadSqlite();
|
|
1739
1992
|
if (!sqlite) return void 0;
|
|
1740
1993
|
try {
|
|
@@ -1746,7 +1999,7 @@ function openSqliteReadOnly(file) {
|
|
|
1746
1999
|
function readChatMeta(chatDirectory) {
|
|
1747
2000
|
let text;
|
|
1748
2001
|
try {
|
|
1749
|
-
text =
|
|
2002
|
+
text = fs4.readFileSync(path6.join(chatDirectory, "meta.json"), "utf8");
|
|
1750
2003
|
} catch {
|
|
1751
2004
|
return void 0;
|
|
1752
2005
|
}
|
|
@@ -1798,7 +2051,7 @@ function rootWorkspace(storeFile) {
|
|
|
1798
2051
|
}
|
|
1799
2052
|
}
|
|
1800
2053
|
function cursorAgentSessionRefForDirectory(chatDirectory, folderCwd) {
|
|
1801
|
-
const storeFile =
|
|
2054
|
+
const storeFile = path6.join(chatDirectory, "store.db");
|
|
1802
2055
|
const storeMtime = fileMtimeMs(storeFile);
|
|
1803
2056
|
if (storeMtime === void 0) return void 0;
|
|
1804
2057
|
const meta = readChatMeta(chatDirectory);
|
|
@@ -1807,7 +2060,7 @@ function cursorAgentSessionRefForDirectory(chatDirectory, folderCwd) {
|
|
|
1807
2060
|
if (!cwd) return void 0;
|
|
1808
2061
|
return {
|
|
1809
2062
|
tool: "cursor-agent",
|
|
1810
|
-
id:
|
|
2063
|
+
id: path6.basename(chatDirectory),
|
|
1811
2064
|
cwd,
|
|
1812
2065
|
updatedAt: meta?.updatedAtMs ?? storeMtime,
|
|
1813
2066
|
location: chatDirectory
|
|
@@ -1821,11 +2074,11 @@ function listCursorAgentSessionRefs(home = os3.homedir(), candidates = []) {
|
|
|
1821
2074
|
const refs = [];
|
|
1822
2075
|
for (const folder of listDirectory(chats)) {
|
|
1823
2076
|
if (!folder.isDirectory()) continue;
|
|
1824
|
-
const folderDirectory =
|
|
2077
|
+
const folderDirectory = path6.join(chats, folder.name);
|
|
1825
2078
|
for (const chat of listDirectory(folderDirectory)) {
|
|
1826
2079
|
if (!chat.isDirectory()) continue;
|
|
1827
2080
|
const ref = cursorAgentSessionRefForDirectory(
|
|
1828
|
-
|
|
2081
|
+
path6.join(folderDirectory, chat.name),
|
|
1829
2082
|
byHash.get(folder.name)
|
|
1830
2083
|
);
|
|
1831
2084
|
if (ref) refs.push(ref);
|
|
@@ -1838,7 +2091,7 @@ function discoverCursorAgentSessions(repoRoot, home = os3.homedir(), limit = 1)
|
|
|
1838
2091
|
return listCursorAgentSessionRefs(home, [repoRoot, root]).filter((ref) => isPathInside(canonicalPath(ref.cwd), root)).slice(0, limit);
|
|
1839
2092
|
}
|
|
1840
2093
|
function cursorAgentConversation(chatDirectory) {
|
|
1841
|
-
const database = openSqliteReadOnly(
|
|
2094
|
+
const database = openSqliteReadOnly(path6.join(chatDirectory, "store.db"));
|
|
1842
2095
|
if (!database)
|
|
1843
2096
|
return { events: [], unparsed: 0, unsupported: "store.db not readable" };
|
|
1844
2097
|
try {
|
|
@@ -1901,7 +2154,7 @@ function readCursorAgentSession(ref, options = {}) {
|
|
|
1901
2154
|
};
|
|
1902
2155
|
}
|
|
1903
2156
|
function cursorAgentMessageCount(chatDirectory) {
|
|
1904
|
-
const database = openSqliteReadOnly(
|
|
2157
|
+
const database = openSqliteReadOnly(path6.join(chatDirectory, "store.db"));
|
|
1905
2158
|
if (!database) return void 0;
|
|
1906
2159
|
try {
|
|
1907
2160
|
const rootId = asString(readStoreMeta(database)?.latestRootBlobId);
|
|
@@ -1913,28 +2166,28 @@ function cursorAgentMessageCount(chatDirectory) {
|
|
|
1913
2166
|
}
|
|
1914
2167
|
}
|
|
1915
2168
|
|
|
1916
|
-
//
|
|
1917
|
-
import
|
|
2169
|
+
// src/local-sessions/cursor-app.ts
|
|
2170
|
+
import fs5 from "node:fs";
|
|
1918
2171
|
import os4 from "node:os";
|
|
1919
|
-
import
|
|
2172
|
+
import path7 from "node:path";
|
|
1920
2173
|
var NEWEST_KNOWN_COMPOSER_VERSION = 18;
|
|
1921
2174
|
var SHELL_TOOLS2 = /* @__PURE__ */ new Set(["run_terminal_command_v2", "run_terminal_cmd"]);
|
|
1922
2175
|
function cursorUserDirectories(home = os4.homedir()) {
|
|
1923
2176
|
const candidates = [
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
process.env.XDG_CONFIG_HOME?.trim() ||
|
|
2177
|
+
path7.join(home, "Library", "Application Support", "Cursor", "User"),
|
|
2178
|
+
path7.join(
|
|
2179
|
+
process.env.XDG_CONFIG_HOME?.trim() || path7.join(home, ".config"),
|
|
1927
2180
|
"Cursor",
|
|
1928
2181
|
"User"
|
|
1929
2182
|
),
|
|
1930
|
-
|
|
1931
|
-
process.env.APPDATA?.trim() ||
|
|
2183
|
+
path7.join(
|
|
2184
|
+
process.env.APPDATA?.trim() || path7.join(home, "AppData", "Roaming"),
|
|
1932
2185
|
"Cursor",
|
|
1933
2186
|
"User"
|
|
1934
2187
|
)
|
|
1935
2188
|
];
|
|
1936
2189
|
return [...new Set(candidates)].filter(
|
|
1937
|
-
(directory) =>
|
|
2190
|
+
(directory) => fs5.existsSync(path7.join(directory, "globalStorage", "state.vscdb"))
|
|
1938
2191
|
);
|
|
1939
2192
|
}
|
|
1940
2193
|
function uriToFolder(uri) {
|
|
@@ -1947,7 +2200,7 @@ function uriToFolder(uri) {
|
|
|
1947
2200
|
}
|
|
1948
2201
|
function readJson(file) {
|
|
1949
2202
|
try {
|
|
1950
|
-
return parseJsonObject(
|
|
2203
|
+
return parseJsonObject(fs5.readFileSync(file, "utf8"));
|
|
1951
2204
|
} catch {
|
|
1952
2205
|
return void 0;
|
|
1953
2206
|
}
|
|
@@ -1959,19 +2212,19 @@ function workspaceFolder(workspaceJson) {
|
|
|
1959
2212
|
if (!workspaceFile) return void 0;
|
|
1960
2213
|
const first = asObject(asArray(readJson(workspaceFile)?.folders)[0]);
|
|
1961
2214
|
const relative = asString(first?.path) ?? uriToFolder(asString(first?.uri));
|
|
1962
|
-
return relative ?
|
|
2215
|
+
return relative ? path7.resolve(path7.dirname(workspaceFile), relative) : void 0;
|
|
1963
2216
|
}
|
|
1964
2217
|
function listCursorWorkspaces(userDirectory) {
|
|
1965
|
-
const storage =
|
|
2218
|
+
const storage = path7.join(userDirectory, "workspaceStorage");
|
|
1966
2219
|
const workspaces = [];
|
|
1967
2220
|
for (const entry of listDirectory(storage)) {
|
|
1968
2221
|
if (!entry.isDirectory()) continue;
|
|
1969
|
-
const directory =
|
|
1970
|
-
const json = readJson(
|
|
2222
|
+
const directory = path7.join(storage, entry.name);
|
|
2223
|
+
const json = readJson(path7.join(directory, "workspace.json"));
|
|
1971
2224
|
workspaces.push({
|
|
1972
2225
|
id: entry.name,
|
|
1973
2226
|
folder: json ? workspaceFolder(json) : void 0,
|
|
1974
|
-
stateFile:
|
|
2227
|
+
stateFile: path7.join(directory, "state.vscdb")
|
|
1975
2228
|
});
|
|
1976
2229
|
}
|
|
1977
2230
|
return workspaces;
|
|
@@ -2024,7 +2277,7 @@ SELECT id,
|
|
|
2024
2277
|
FROM composer`;
|
|
2025
2278
|
function listCursorComposers(userDirectory, options = {}) {
|
|
2026
2279
|
const database = openSqliteReadOnly(
|
|
2027
|
-
|
|
2280
|
+
path7.join(userDirectory, "globalStorage", "state.vscdb")
|
|
2028
2281
|
);
|
|
2029
2282
|
if (!database)
|
|
2030
2283
|
return {
|
|
@@ -2118,7 +2371,7 @@ function listCursorComposers(userDirectory, options = {}) {
|
|
|
2118
2371
|
}
|
|
2119
2372
|
}
|
|
2120
2373
|
function composerRef(userDirectory, composer) {
|
|
2121
|
-
const stateFile =
|
|
2374
|
+
const stateFile = path7.join(userDirectory, "globalStorage", "state.vscdb");
|
|
2122
2375
|
return {
|
|
2123
2376
|
tool: "cursor",
|
|
2124
2377
|
id: composer.composerId,
|
|
@@ -2355,32 +2608,32 @@ function readCursorSession(ref, options = {}) {
|
|
|
2355
2608
|
};
|
|
2356
2609
|
}
|
|
2357
2610
|
|
|
2358
|
-
//
|
|
2611
|
+
// src/local-sessions/gemini.ts
|
|
2359
2612
|
import crypto2 from "node:crypto";
|
|
2360
|
-
import
|
|
2613
|
+
import fs6 from "node:fs";
|
|
2361
2614
|
import os5 from "node:os";
|
|
2362
|
-
import
|
|
2615
|
+
import path8 from "node:path";
|
|
2363
2616
|
var SHELL_TOOL = "run_shell_command";
|
|
2364
2617
|
function geminiDirectory(home = os5.homedir()) {
|
|
2365
2618
|
const override = process.env.GEMINI_CLI_HOME?.trim();
|
|
2366
|
-
return
|
|
2619
|
+
return path8.join(override || home, ".gemini");
|
|
2367
2620
|
}
|
|
2368
2621
|
function geminiTempDirectory(home = os5.homedir()) {
|
|
2369
|
-
return
|
|
2622
|
+
return path8.join(geminiDirectory(home), "tmp");
|
|
2370
2623
|
}
|
|
2371
2624
|
function geminiProjectHash(projectRoot) {
|
|
2372
2625
|
return crypto2.createHash("sha256").update(projectRoot).digest("hex");
|
|
2373
2626
|
}
|
|
2374
2627
|
function readText2(file) {
|
|
2375
2628
|
try {
|
|
2376
|
-
return
|
|
2629
|
+
return fs6.readFileSync(file, "utf8");
|
|
2377
2630
|
} catch {
|
|
2378
2631
|
return void 0;
|
|
2379
2632
|
}
|
|
2380
2633
|
}
|
|
2381
2634
|
function registeredProjects(home) {
|
|
2382
2635
|
const registry = parseJsonObject(
|
|
2383
|
-
readText2(
|
|
2636
|
+
readText2(path8.join(geminiDirectory(home), "projects.json")) ?? ""
|
|
2384
2637
|
);
|
|
2385
2638
|
const bySlug = /* @__PURE__ */ new Map();
|
|
2386
2639
|
for (const [projectPath, slug] of Object.entries(
|
|
@@ -2399,7 +2652,7 @@ function listGeminiProjects(home = os5.homedir(), candidates = []) {
|
|
|
2399
2652
|
for (const candidate of [...bySlug.values(), ...candidates]) {
|
|
2400
2653
|
for (const variant of /* @__PURE__ */ new Set([
|
|
2401
2654
|
candidate,
|
|
2402
|
-
|
|
2655
|
+
path8.resolve(candidate),
|
|
2403
2656
|
canonicalPath(candidate)
|
|
2404
2657
|
]))
|
|
2405
2658
|
byHash.set(geminiProjectHash(variant), canonicalPath(variant));
|
|
@@ -2410,8 +2663,8 @@ function listGeminiProjects(home = os5.homedir(), candidates = []) {
|
|
|
2410
2663
|
const projects = [];
|
|
2411
2664
|
for (const entry of listDirectory(temp)) {
|
|
2412
2665
|
if (!entry.isDirectory() || entry.name === "bin") continue;
|
|
2413
|
-
const directory =
|
|
2414
|
-
const marker = readText2(
|
|
2666
|
+
const directory = path8.join(temp, entry.name);
|
|
2667
|
+
const marker = readText2(path8.join(directory, ".project_root"))?.trim();
|
|
2415
2668
|
const registered = bySlug.get(entry.name);
|
|
2416
2669
|
let cwd = marker || (registered ? canonicalPath(registered) : void 0);
|
|
2417
2670
|
if (!cwd && /^[0-9a-f]{64}$/.test(entry.name)) cwd = hashLookup(entry.name);
|
|
@@ -2440,25 +2693,25 @@ function readGeminiSessionHead(file) {
|
|
|
2440
2693
|
};
|
|
2441
2694
|
}
|
|
2442
2695
|
function sessionFiles(project) {
|
|
2443
|
-
const chats =
|
|
2696
|
+
const chats = path8.join(project.directory, "chats");
|
|
2444
2697
|
return listDirectory(chats).filter(
|
|
2445
2698
|
(entry) => entry.isFile() && entry.name.startsWith("session-") && (entry.name.endsWith(".jsonl") || entry.name.endsWith(".json"))
|
|
2446
|
-
).map((entry) =>
|
|
2699
|
+
).map((entry) => path8.join(chats, entry.name));
|
|
2447
2700
|
}
|
|
2448
2701
|
function geminiSessionRefForFile(file, cwd) {
|
|
2449
2702
|
const head = readGeminiSessionHead(file);
|
|
2450
2703
|
if (!head || head.kind === "subagent") return void 0;
|
|
2451
2704
|
return {
|
|
2452
2705
|
tool: "gemini",
|
|
2453
|
-
id: head.sessionId ??
|
|
2706
|
+
id: head.sessionId ?? path8.basename(file).replace(/^session-|\.jsonl?$/g, ""),
|
|
2454
2707
|
cwd,
|
|
2455
2708
|
updatedAt: fileMtimeMs(file) ?? 0,
|
|
2456
2709
|
location: file
|
|
2457
2710
|
};
|
|
2458
2711
|
}
|
|
2459
2712
|
function promptLogRefs(project, cwd) {
|
|
2460
|
-
const logFile =
|
|
2461
|
-
if (!
|
|
2713
|
+
const logFile = path8.join(project.directory, "logs.json");
|
|
2714
|
+
if (!fs6.existsSync(logFile)) return [];
|
|
2462
2715
|
let entries;
|
|
2463
2716
|
try {
|
|
2464
2717
|
entries = asArray(JSON.parse(readText2(logFile) ?? "[]"));
|
|
@@ -2467,7 +2720,7 @@ function promptLogRefs(project, cwd) {
|
|
|
2467
2720
|
}
|
|
2468
2721
|
const recorded = new Set(
|
|
2469
2722
|
sessionFiles(project).map(
|
|
2470
|
-
(file) =>
|
|
2723
|
+
(file) => path8.basename(file).replace(/\.jsonl?$/, "").slice(-8)
|
|
2471
2724
|
)
|
|
2472
2725
|
);
|
|
2473
2726
|
const latest = /* @__PURE__ */ new Map();
|
|
@@ -2665,7 +2918,7 @@ function readJsonFile(file) {
|
|
|
2665
2918
|
}
|
|
2666
2919
|
function parseGeminiSession(ref) {
|
|
2667
2920
|
const file = ref.location;
|
|
2668
|
-
const base =
|
|
2921
|
+
const base = path8.basename(file);
|
|
2669
2922
|
if (base === "logs.json") {
|
|
2670
2923
|
const events2 = [];
|
|
2671
2924
|
for (const raw of asArray(readJsonFile(file))) {
|
|
@@ -2725,12 +2978,12 @@ function readGeminiSession(ref, options = {}) {
|
|
|
2725
2978
|
};
|
|
2726
2979
|
}
|
|
2727
2980
|
|
|
2728
|
-
//
|
|
2729
|
-
import
|
|
2981
|
+
// src/local-sessions/grok.ts
|
|
2982
|
+
import fs7 from "node:fs";
|
|
2730
2983
|
import os6 from "node:os";
|
|
2731
|
-
import
|
|
2984
|
+
import path9 from "node:path";
|
|
2732
2985
|
function grokSessionsDirectory(home = os6.homedir()) {
|
|
2733
|
-
return
|
|
2986
|
+
return path9.join(home, ".grok", "sessions");
|
|
2734
2987
|
}
|
|
2735
2988
|
function decodeDirectoryName(name) {
|
|
2736
2989
|
try {
|
|
@@ -2740,7 +2993,7 @@ function decodeDirectoryName(name) {
|
|
|
2740
2993
|
}
|
|
2741
2994
|
}
|
|
2742
2995
|
function sessionUpdatedAt(sessionDirectory) {
|
|
2743
|
-
const times = ["updates.jsonl", "chat_history.jsonl"].map((name) => fileMtimeMs(
|
|
2996
|
+
const times = ["updates.jsonl", "chat_history.jsonl"].map((name) => fileMtimeMs(path9.join(sessionDirectory, name))).filter((time) => time !== void 0);
|
|
2744
2997
|
return times.length > 0 ? Math.max(...times) : void 0;
|
|
2745
2998
|
}
|
|
2746
2999
|
function discoverGrokSessions(repoRoot, home = os6.homedir(), limit = 1) {
|
|
@@ -2750,12 +3003,12 @@ function discoverGrokSessions(repoRoot, home = os6.homedir(), limit = 1) {
|
|
|
2750
3003
|
for (const cwdEntry of listDirectory(sessionsDirectory)) {
|
|
2751
3004
|
if (!cwdEntry.isDirectory()) continue;
|
|
2752
3005
|
const cwd = decodeDirectoryName(cwdEntry.name);
|
|
2753
|
-
if (!cwd || !
|
|
3006
|
+
if (!cwd || !path9.isAbsolute(cwd) || !isPathInside(canonicalPath(cwd), root))
|
|
2754
3007
|
continue;
|
|
2755
|
-
const cwdDirectory =
|
|
3008
|
+
const cwdDirectory = path9.join(sessionsDirectory, cwdEntry.name);
|
|
2756
3009
|
for (const sessionEntry of listDirectory(cwdDirectory)) {
|
|
2757
3010
|
if (!sessionEntry.isDirectory()) continue;
|
|
2758
|
-
const location =
|
|
3011
|
+
const location = path9.join(cwdDirectory, sessionEntry.name);
|
|
2759
3012
|
const updatedAt = sessionUpdatedAt(location);
|
|
2760
3013
|
if (updatedAt === void 0) continue;
|
|
2761
3014
|
refs.push({
|
|
@@ -2817,12 +3070,12 @@ function applyToolUpdate(assembler, update) {
|
|
|
2817
3070
|
}
|
|
2818
3071
|
function readGrokSession(ref, options = {}) {
|
|
2819
3072
|
const summary = readSummary(ref.location);
|
|
2820
|
-
const updatesFile =
|
|
3073
|
+
const updatesFile = path9.join(ref.location, "updates.jsonl");
|
|
2821
3074
|
const assembler = new TurnAssembler(options.activityOnly);
|
|
2822
3075
|
let recap;
|
|
2823
3076
|
let firstPrompt;
|
|
2824
3077
|
let result;
|
|
2825
|
-
if (
|
|
3078
|
+
if (fs7.existsSync(updatesFile)) {
|
|
2826
3079
|
let assistantChunks = "";
|
|
2827
3080
|
let assistantAt;
|
|
2828
3081
|
const flushAssistant = () => {
|
|
@@ -2866,7 +3119,7 @@ function readGrokSession(ref, options = {}) {
|
|
|
2866
3119
|
);
|
|
2867
3120
|
flushAssistant();
|
|
2868
3121
|
} else {
|
|
2869
|
-
const chatFile =
|
|
3122
|
+
const chatFile = path9.join(ref.location, "chat_history.jsonl");
|
|
2870
3123
|
result = visitTranscriptRecords(
|
|
2871
3124
|
chatFile,
|
|
2872
3125
|
chatHistoryScan,
|
|
@@ -2925,7 +3178,7 @@ function readGrokSession(ref, options = {}) {
|
|
|
2925
3178
|
function readSummary(sessionDirectory) {
|
|
2926
3179
|
try {
|
|
2927
3180
|
return parseJsonObject(
|
|
2928
|
-
|
|
3181
|
+
fs7.readFileSync(path9.join(sessionDirectory, "summary.json"), "utf8")
|
|
2929
3182
|
);
|
|
2930
3183
|
} catch {
|
|
2931
3184
|
return void 0;
|
|
@@ -2933,33 +3186,33 @@ function readSummary(sessionDirectory) {
|
|
|
2933
3186
|
}
|
|
2934
3187
|
function grokSessionRefForDirectory(sessionDirectory) {
|
|
2935
3188
|
const summary = readSummary(sessionDirectory);
|
|
2936
|
-
const cwd = asString(asObject(summary?.info)?.cwd) ?? decodeDirectoryName(
|
|
3189
|
+
const cwd = asString(asObject(summary?.info)?.cwd) ?? decodeDirectoryName(path9.basename(path9.dirname(sessionDirectory)));
|
|
2937
3190
|
const updatedAt = sessionUpdatedAt(sessionDirectory);
|
|
2938
3191
|
if (!cwd || updatedAt === void 0) return void 0;
|
|
2939
3192
|
return {
|
|
2940
3193
|
tool: "grok",
|
|
2941
|
-
id:
|
|
3194
|
+
id: path9.basename(sessionDirectory),
|
|
2942
3195
|
cwd,
|
|
2943
3196
|
updatedAt,
|
|
2944
3197
|
location: sessionDirectory
|
|
2945
3198
|
};
|
|
2946
3199
|
}
|
|
2947
3200
|
|
|
2948
|
-
//
|
|
2949
|
-
import
|
|
3201
|
+
// src/local-sessions/opencode.ts
|
|
3202
|
+
import fs8 from "node:fs";
|
|
2950
3203
|
import os7 from "node:os";
|
|
2951
|
-
import
|
|
3204
|
+
import path10 from "node:path";
|
|
2952
3205
|
var NEW_SESSION_PLACEHOLDER = /^New session - \d{4}-\d{2}-\d{2}/;
|
|
2953
3206
|
function opencodeDataDirectory(home = os7.homedir()) {
|
|
2954
3207
|
const xdg = process.env.XDG_DATA_HOME?.trim();
|
|
2955
|
-
return
|
|
3208
|
+
return path10.join(xdg || path10.join(home, ".local", "share"), "opencode");
|
|
2956
3209
|
}
|
|
2957
3210
|
function opencodeDatabasePath(home = os7.homedir()) {
|
|
2958
3211
|
const override = process.env.OPENCODE_DB?.trim();
|
|
2959
|
-
return override ||
|
|
3212
|
+
return override || path10.join(opencodeDataDirectory(home), "opencode.db");
|
|
2960
3213
|
}
|
|
2961
3214
|
function openOpencodeDatabase(databasePath) {
|
|
2962
|
-
if (!
|
|
3215
|
+
if (!fs8.existsSync(databasePath)) return void 0;
|
|
2963
3216
|
const sqlite = loadSqlite();
|
|
2964
3217
|
if (!sqlite) return void 0;
|
|
2965
3218
|
try {
|
|
@@ -2978,7 +3231,7 @@ function sessionColumns(database) {
|
|
|
2978
3231
|
}
|
|
2979
3232
|
}
|
|
2980
3233
|
function likePrefix(directory) {
|
|
2981
|
-
return `${directory.replace(/[\\%_]/g, (character) => `\\${character}`)}${
|
|
3234
|
+
return `${directory.replace(/[\\%_]/g, (character) => `\\${character}`)}${path10.sep}%`;
|
|
2982
3235
|
}
|
|
2983
3236
|
function discoverInDatabase(database, root, rawRoot, databasePath, limit) {
|
|
2984
3237
|
const columns = sessionColumns(database);
|
|
@@ -3002,24 +3255,24 @@ function discoverInDatabase(database, root, rawRoot, databasePath, limit) {
|
|
|
3002
3255
|
}
|
|
3003
3256
|
}
|
|
3004
3257
|
function legacyStorageDirectory(home) {
|
|
3005
|
-
return
|
|
3258
|
+
return path10.join(opencodeDataDirectory(home), "storage");
|
|
3006
3259
|
}
|
|
3007
3260
|
function readJsonFile2(file) {
|
|
3008
3261
|
try {
|
|
3009
|
-
return parseJsonObject(
|
|
3262
|
+
return parseJsonObject(fs8.readFileSync(file, "utf8"));
|
|
3010
3263
|
} catch {
|
|
3011
3264
|
return void 0;
|
|
3012
3265
|
}
|
|
3013
3266
|
}
|
|
3014
3267
|
function discoverInLegacyStorage(root, home) {
|
|
3015
|
-
const sessionsDirectory =
|
|
3268
|
+
const sessionsDirectory = path10.join(legacyStorageDirectory(home), "session");
|
|
3016
3269
|
let newest;
|
|
3017
3270
|
for (const project of listDirectory(sessionsDirectory)) {
|
|
3018
3271
|
if (!project.isDirectory()) continue;
|
|
3019
|
-
const projectDirectory =
|
|
3272
|
+
const projectDirectory = path10.join(sessionsDirectory, project.name);
|
|
3020
3273
|
for (const entry of listDirectory(projectDirectory)) {
|
|
3021
3274
|
if (!entry.name.endsWith(".json")) continue;
|
|
3022
|
-
const file =
|
|
3275
|
+
const file = path10.join(projectDirectory, entry.name);
|
|
3023
3276
|
const session = readJsonFile2(file);
|
|
3024
3277
|
const directory = asString(session?.directory);
|
|
3025
3278
|
if (!session || !directory || asString(session.parentID)) continue;
|
|
@@ -3047,7 +3300,7 @@ function discoverOpencodeSessions(repoRoot, home = os7.homedir(), limit = 1) {
|
|
|
3047
3300
|
return discoverInDatabase(
|
|
3048
3301
|
database,
|
|
3049
3302
|
root,
|
|
3050
|
-
|
|
3303
|
+
path10.resolve(repoRoot),
|
|
3051
3304
|
databasePath,
|
|
3052
3305
|
limit
|
|
3053
3306
|
);
|
|
@@ -3055,7 +3308,7 @@ function discoverOpencodeSessions(repoRoot, home = os7.homedir(), limit = 1) {
|
|
|
3055
3308
|
database.close();
|
|
3056
3309
|
}
|
|
3057
3310
|
}
|
|
3058
|
-
if (
|
|
3311
|
+
if (fs8.existsSync(databasePath)) return [];
|
|
3059
3312
|
return discoverInLegacyStorage(root, home);
|
|
3060
3313
|
}
|
|
3061
3314
|
var OpencodeTurnBuilder = class {
|
|
@@ -3135,14 +3388,14 @@ function* databaseMessages(database, sessionId, toolPartsOnly) {
|
|
|
3135
3388
|
}
|
|
3136
3389
|
function* legacyMessages(home, sessionId) {
|
|
3137
3390
|
const storage = legacyStorageDirectory(home);
|
|
3138
|
-
const messageDirectory =
|
|
3391
|
+
const messageDirectory = path10.join(storage, "message", sessionId);
|
|
3139
3392
|
const messageFiles = listDirectory(messageDirectory).filter((entry) => entry.name.endsWith(".json")).map((entry) => entry.name).sort();
|
|
3140
3393
|
for (const name of messageFiles) {
|
|
3141
|
-
const data = readJsonFile2(
|
|
3394
|
+
const data = readJsonFile2(path10.join(messageDirectory, name));
|
|
3142
3395
|
if (!data) continue;
|
|
3143
3396
|
const messageId = asString(data.id) ?? name.slice(0, -5);
|
|
3144
|
-
const partDirectory =
|
|
3145
|
-
const parts = listDirectory(partDirectory).filter((entry) => entry.name.endsWith(".json")).map((entry) => entry.name).sort().map((partName) => readJsonFile2(
|
|
3397
|
+
const partDirectory = path10.join(storage, "part", messageId);
|
|
3398
|
+
const parts = listDirectory(partDirectory).filter((entry) => entry.name.endsWith(".json")).map((entry) => entry.name).sort().map((partName) => readJsonFile2(path10.join(partDirectory, partName))).filter((part) => part !== void 0);
|
|
3146
3399
|
yield { data, parts, createdAt: asNumber(asObject(data.time)?.created) };
|
|
3147
3400
|
}
|
|
3148
3401
|
}
|
|
@@ -3204,9 +3457,9 @@ function readOpencodeSession(ref, options = {}, home = os7.homedir()) {
|
|
|
3204
3457
|
};
|
|
3205
3458
|
}
|
|
3206
3459
|
|
|
3207
|
-
//
|
|
3460
|
+
// src/local-sessions/pi.ts
|
|
3208
3461
|
import os8 from "node:os";
|
|
3209
|
-
import
|
|
3462
|
+
import path11 from "node:path";
|
|
3210
3463
|
var MAX_FILES_VERIFIED_PER_DIRECTORY2 = 5;
|
|
3211
3464
|
var RELEVANT_ENTRY_TYPES = /* @__PURE__ */ new Set([
|
|
3212
3465
|
"session",
|
|
@@ -3216,7 +3469,7 @@ var RELEVANT_ENTRY_TYPES = /* @__PURE__ */ new Set([
|
|
|
3216
3469
|
]);
|
|
3217
3470
|
function piSessionsDirectory(home = os8.homedir()) {
|
|
3218
3471
|
const agentDirectory = process.env.PI_CODING_AGENT_DIR?.trim();
|
|
3219
|
-
return agentDirectory ?
|
|
3472
|
+
return agentDirectory ? path11.join(agentDirectory, "sessions") : path11.join(home, ".pi", "agent", "sessions");
|
|
3220
3473
|
}
|
|
3221
3474
|
function encodePiSessionDirectory(cwd) {
|
|
3222
3475
|
const withoutLeadingSeparator = cwd.replace(/^[/\\]/, "");
|
|
@@ -3238,7 +3491,7 @@ function piSessionRefForFile(file) {
|
|
|
3238
3491
|
if (!header2?.cwd) return void 0;
|
|
3239
3492
|
return {
|
|
3240
3493
|
tool: "pi",
|
|
3241
|
-
id: header2.id ??
|
|
3494
|
+
id: header2.id ?? path11.basename(file, ".jsonl"),
|
|
3242
3495
|
cwd: header2.cwd,
|
|
3243
3496
|
updatedAt: fileMtimeMs(file) ?? 0,
|
|
3244
3497
|
location: file
|
|
@@ -3254,8 +3507,8 @@ function discoverPiSessions(repoRoot, home = os8.homedir(), limit = 1) {
|
|
|
3254
3507
|
if (!entry.isDirectory()) continue;
|
|
3255
3508
|
if (entry.name !== encodedRoot && !(entry.name.startsWith(childPrefix) && entry.name.endsWith("--")))
|
|
3256
3509
|
continue;
|
|
3257
|
-
const directory =
|
|
3258
|
-
const files = listDirectory(directory).filter((file) => file.isFile() && file.name.endsWith(".jsonl")).map((file) =>
|
|
3510
|
+
const directory = path11.join(sessionsDirectory, entry.name);
|
|
3511
|
+
const files = listDirectory(directory).filter((file) => file.isFile() && file.name.endsWith(".jsonl")).map((file) => path11.join(directory, file.name)).map((file) => ({ file, mtime: fileMtimeMs(file) ?? 0 })).sort((a, b) => b.mtime - a.mtime).slice(0, Math.max(MAX_FILES_VERIFIED_PER_DIRECTORY2, limit));
|
|
3259
3512
|
let found = 0;
|
|
3260
3513
|
for (const { file } of files) {
|
|
3261
3514
|
const ref = piSessionRefForFile(file);
|
|
@@ -3396,7 +3649,7 @@ function readPiSession(ref, options = {}) {
|
|
|
3396
3649
|
};
|
|
3397
3650
|
}
|
|
3398
3651
|
|
|
3399
|
-
//
|
|
3652
|
+
// src/local-sessions/types.ts
|
|
3400
3653
|
var AGENT_TOOLS = [
|
|
3401
3654
|
"claude",
|
|
3402
3655
|
"codex",
|
|
@@ -3408,14 +3661,14 @@ var AGENT_TOOLS = [
|
|
|
3408
3661
|
"cursor"
|
|
3409
3662
|
];
|
|
3410
3663
|
|
|
3411
|
-
//
|
|
3664
|
+
// src/local-sessions/machine.ts
|
|
3412
3665
|
var HEAD_BYTES = 256 * 1024;
|
|
3413
3666
|
var TITLE_TAIL_BYTES = 64 * 1024;
|
|
3414
3667
|
function sqliteStoresPresent(home) {
|
|
3415
3668
|
const present = [];
|
|
3416
|
-
if (
|
|
3669
|
+
if (fs9.existsSync(opencodeDatabasePath(home))) present.push("opencode");
|
|
3417
3670
|
if (cursorUserDirectories(home).length) present.push("cursor");
|
|
3418
|
-
if (
|
|
3671
|
+
if (fs9.existsSync(cursorChatsDirectory(home))) present.push("cursor-agent");
|
|
3419
3672
|
return present;
|
|
3420
3673
|
}
|
|
3421
3674
|
function sqliteWarning(tools, nodeVersion = process.versions.node) {
|
|
@@ -3431,7 +3684,7 @@ function sqliteWarning(tools, nodeVersion = process.versions.node) {
|
|
|
3431
3684
|
}
|
|
3432
3685
|
function fileSize(file) {
|
|
3433
3686
|
try {
|
|
3434
|
-
return
|
|
3687
|
+
return fs9.statSync(file).size;
|
|
3435
3688
|
} catch {
|
|
3436
3689
|
return 0;
|
|
3437
3690
|
}
|
|
@@ -3472,7 +3725,7 @@ function estimate(count, sample) {
|
|
|
3472
3725
|
return { count: Math.round(count / sample.coverage), exact: false };
|
|
3473
3726
|
}
|
|
3474
3727
|
function gitOf(cwd) {
|
|
3475
|
-
if (!cwd || !
|
|
3728
|
+
if (!cwd || !path12.isAbsolute(cwd)) return "none";
|
|
3476
3729
|
const identity = gitIdentity(cwd);
|
|
3477
3730
|
if (!identity) return "none";
|
|
3478
3731
|
return {
|
|
@@ -3487,10 +3740,10 @@ function claudeSessions(home) {
|
|
|
3487
3740
|
const projects = claudeProjectsDirectory(home);
|
|
3488
3741
|
for (const project of listDirectory(projects)) {
|
|
3489
3742
|
if (!project.isDirectory()) continue;
|
|
3490
|
-
const directory =
|
|
3743
|
+
const directory = path12.join(projects, project.name);
|
|
3491
3744
|
for (const entry of listDirectory(directory)) {
|
|
3492
3745
|
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
3493
|
-
const file =
|
|
3746
|
+
const file = path12.join(directory, entry.name);
|
|
3494
3747
|
const ref = claudeSessionRefForFile(file);
|
|
3495
3748
|
if (!ref) continue;
|
|
3496
3749
|
const size = fileSize(file);
|
|
@@ -3545,13 +3798,13 @@ function claudeSessions(home) {
|
|
|
3545
3798
|
}
|
|
3546
3799
|
function codexThreadNames(home) {
|
|
3547
3800
|
const names = /* @__PURE__ */ new Map();
|
|
3548
|
-
const index =
|
|
3549
|
-
|
|
3801
|
+
const index = path12.join(
|
|
3802
|
+
path12.dirname(codexSessionsDirectory(home)),
|
|
3550
3803
|
"session_index.jsonl"
|
|
3551
3804
|
);
|
|
3552
3805
|
let text = "";
|
|
3553
3806
|
try {
|
|
3554
|
-
text =
|
|
3807
|
+
text = fs9.readFileSync(index, "utf8");
|
|
3555
3808
|
} catch {
|
|
3556
3809
|
return names;
|
|
3557
3810
|
}
|
|
@@ -3647,7 +3900,7 @@ function opencodeSessions(home) {
|
|
|
3647
3900
|
function jsonlFiles(directory, depth) {
|
|
3648
3901
|
const files = [];
|
|
3649
3902
|
for (const entry of listDirectory(directory)) {
|
|
3650
|
-
const full =
|
|
3903
|
+
const full = path12.join(directory, entry.name);
|
|
3651
3904
|
if (entry.isFile() && entry.name.endsWith(".jsonl")) files.push(full);
|
|
3652
3905
|
else if (entry.isDirectory() && depth > 0)
|
|
3653
3906
|
files.push(...jsonlFiles(full, depth - 1));
|
|
@@ -3693,15 +3946,15 @@ function grokSessions(home) {
|
|
|
3693
3946
|
const root = grokSessionsDirectory(home);
|
|
3694
3947
|
for (const cwdEntry of listDirectory(root)) {
|
|
3695
3948
|
if (!cwdEntry.isDirectory()) continue;
|
|
3696
|
-
for (const sessionEntry of listDirectory(
|
|
3949
|
+
for (const sessionEntry of listDirectory(path12.join(root, cwdEntry.name))) {
|
|
3697
3950
|
if (!sessionEntry.isDirectory()) continue;
|
|
3698
|
-
const directory =
|
|
3951
|
+
const directory = path12.join(root, cwdEntry.name, sessionEntry.name);
|
|
3699
3952
|
const ref = grokSessionRefForDirectory(directory);
|
|
3700
3953
|
if (!ref) continue;
|
|
3701
|
-
const history =
|
|
3702
|
-
const updates =
|
|
3954
|
+
const history = path12.join(directory, "chat_history.jsonl");
|
|
3955
|
+
const updates = path12.join(directory, "updates.jsonl");
|
|
3703
3956
|
const size = fileSize(history) + fileSize(updates);
|
|
3704
|
-
const source =
|
|
3957
|
+
const source = fs9.existsSync(history) ? history : updates;
|
|
3705
3958
|
const sample = headSample(source, fileSize(source));
|
|
3706
3959
|
let messages = 0;
|
|
3707
3960
|
let previousKind;
|
|
@@ -3735,7 +3988,7 @@ function grokSessions(home) {
|
|
|
3735
3988
|
function geminiSessions(home, candidates) {
|
|
3736
3989
|
return listGeminiSessionRefs(home, candidates).map((ref) => {
|
|
3737
3990
|
const size = fileSize(ref.location);
|
|
3738
|
-
if (
|
|
3991
|
+
if (path12.basename(ref.location) === "logs.json")
|
|
3739
3992
|
return {
|
|
3740
3993
|
tool: "gemini",
|
|
3741
3994
|
id: ref.id,
|
|
@@ -3794,7 +4047,7 @@ function cursorAgentSessions(home, candidates) {
|
|
|
3794
4047
|
let title;
|
|
3795
4048
|
try {
|
|
3796
4049
|
const meta = parseJsonObject(
|
|
3797
|
-
|
|
4050
|
+
fs9.readFileSync(path12.join(ref.location, "meta.json"), "utf8")
|
|
3798
4051
|
);
|
|
3799
4052
|
title = asString(meta?.title)?.trim() || void 0;
|
|
3800
4053
|
} catch {
|
|
@@ -3809,7 +4062,7 @@ function cursorAgentSessions(home, candidates) {
|
|
|
3809
4062
|
updatedAt: ref.updatedAt,
|
|
3810
4063
|
messageCount: count ?? 0,
|
|
3811
4064
|
messageCountExact: count !== void 0,
|
|
3812
|
-
sizeBytes: fileSize(
|
|
4065
|
+
sizeBytes: fileSize(path12.join(ref.location, "store.db")),
|
|
3813
4066
|
sizeExact: true,
|
|
3814
4067
|
ref
|
|
3815
4068
|
};
|
|
@@ -3819,7 +4072,7 @@ var CURSOR_AVERAGE_BUBBLE_BYTES = 6800;
|
|
|
3819
4072
|
function cursorAppSessions(home) {
|
|
3820
4073
|
const sessions = [];
|
|
3821
4074
|
for (const userDirectory of cursorUserDirectories(home)) {
|
|
3822
|
-
const stateFile =
|
|
4075
|
+
const stateFile = path12.join(userDirectory, "globalStorage", "state.vscdb");
|
|
3823
4076
|
for (const composer of listCursorComposers(userDirectory).composers) {
|
|
3824
4077
|
if (!isListedComposer(composer)) continue;
|
|
3825
4078
|
const ref = {
|
|
@@ -3900,259 +4153,6 @@ function discoverAllSessions(options = {}) {
|
|
|
3900
4153
|
};
|
|
3901
4154
|
}
|
|
3902
4155
|
|
|
3903
|
-
// src/api.ts
|
|
3904
|
-
var ApiError = class extends Error {
|
|
3905
|
-
constructor(message, status, path21) {
|
|
3906
|
-
super(message);
|
|
3907
|
-
this.status = status;
|
|
3908
|
-
this.path = path21;
|
|
3909
|
-
}
|
|
3910
|
-
status;
|
|
3911
|
-
path;
|
|
3912
|
-
};
|
|
3913
|
-
var TIMEOUT_MS = 12e4;
|
|
3914
|
-
async function errorMessage(response) {
|
|
3915
|
-
const text = (await response.text().catch(() => "")).trim();
|
|
3916
|
-
try {
|
|
3917
|
-
const json = JSON.parse(text);
|
|
3918
|
-
if (typeof json.error === "string") return json.error;
|
|
3919
|
-
} catch {
|
|
3920
|
-
}
|
|
3921
|
-
return text.slice(0, 500) || `HTTP ${response.status}`;
|
|
3922
|
-
}
|
|
3923
|
-
var ImportApi = class {
|
|
3924
|
-
constructor(base, token = null, userAgent = "ytimesy-cli") {
|
|
3925
|
-
this.base = base;
|
|
3926
|
-
this.token = token;
|
|
3927
|
-
this.userAgent = userAgent;
|
|
3928
|
-
}
|
|
3929
|
-
base;
|
|
3930
|
-
token;
|
|
3931
|
-
userAgent;
|
|
3932
|
-
setToken(token) {
|
|
3933
|
-
this.token = token;
|
|
3934
|
-
}
|
|
3935
|
-
async request(path21, init) {
|
|
3936
|
-
const url = new URL(path21, this.base).toString();
|
|
3937
|
-
let response;
|
|
3938
|
-
try {
|
|
3939
|
-
response = await fetch(url, {
|
|
3940
|
-
method: init.method,
|
|
3941
|
-
body: init.body,
|
|
3942
|
-
headers: {
|
|
3943
|
-
"user-agent": this.userAgent,
|
|
3944
|
-
...init.contentType ? { "content-type": init.contentType } : {},
|
|
3945
|
-
...this.token ? { authorization: `Bearer ${this.token}` } : {}
|
|
3946
|
-
},
|
|
3947
|
-
signal: AbortSignal.timeout(TIMEOUT_MS)
|
|
3948
|
-
});
|
|
3949
|
-
} catch (error) {
|
|
3950
|
-
throw new ApiError(
|
|
3951
|
-
`Could not reach ${new URL(this.base).host}: ${error.message}`,
|
|
3952
|
-
0,
|
|
3953
|
-
path21
|
|
3954
|
-
);
|
|
3955
|
-
}
|
|
3956
|
-
if (!response.ok)
|
|
3957
|
-
throw new ApiError(await errorMessage(response), response.status, path21);
|
|
3958
|
-
return await response.json();
|
|
3959
|
-
}
|
|
3960
|
-
json(path21, body) {
|
|
3961
|
-
return this.request(path21, {
|
|
3962
|
-
method: "POST",
|
|
3963
|
-
body: JSON.stringify(body),
|
|
3964
|
-
contentType: "application/json"
|
|
3965
|
-
});
|
|
3966
|
-
}
|
|
3967
|
-
start(body) {
|
|
3968
|
-
return this.json("/api/local-import/start", body);
|
|
3969
|
-
}
|
|
3970
|
-
plan(groups) {
|
|
3971
|
-
return this.json("/api/local-import/plan", {
|
|
3972
|
-
groups
|
|
3973
|
-
});
|
|
3974
|
-
}
|
|
3975
|
-
/** A project for a repository the ytimesy app reaches (409: it cannot see it yet). */
|
|
3976
|
-
createProject(body) {
|
|
3977
|
-
return this.json(
|
|
3978
|
-
"/api/local-import/create-project",
|
|
3979
|
-
body
|
|
3980
|
-
);
|
|
3981
|
-
}
|
|
3982
|
-
secrets(body) {
|
|
3983
|
-
return this.json("/api/local-import/secrets", body);
|
|
3984
|
-
}
|
|
3985
|
-
session(body) {
|
|
3986
|
-
return this.json("/api/local-import/sessions", body);
|
|
3987
|
-
}
|
|
3988
|
-
transcript(uploadPath, archive) {
|
|
3989
|
-
return this.request(uploadPath, {
|
|
3990
|
-
method: "PUT",
|
|
3991
|
-
body: new Blob([new Uint8Array(archive)]),
|
|
3992
|
-
contentType: "application/gzip"
|
|
3993
|
-
});
|
|
3994
|
-
}
|
|
3995
|
-
finish(body) {
|
|
3996
|
-
return this.json("/api/local-import/finish", body);
|
|
3997
|
-
}
|
|
3998
|
-
cancel() {
|
|
3999
|
-
return this.json("/api/local-import/cancel", {});
|
|
4000
|
-
}
|
|
4001
|
-
};
|
|
4002
|
-
|
|
4003
|
-
// src/env-file.ts
|
|
4004
|
-
import fs9 from "node:fs";
|
|
4005
|
-
import path12 from "node:path";
|
|
4006
|
-
var ENV_FILES = [".env", ".env.local"];
|
|
4007
|
-
var NAME = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
|
|
4008
|
-
function unescapeDouble(text) {
|
|
4009
|
-
return text.replace(
|
|
4010
|
-
/\\([nrt"\\$])/g,
|
|
4011
|
-
(_, char) => char === "n" ? "\n" : char === "r" ? "\r" : char === "t" ? " " : char
|
|
4012
|
-
);
|
|
4013
|
-
}
|
|
4014
|
-
function parseEnvText(text) {
|
|
4015
|
-
const variables = /* @__PURE__ */ new Map();
|
|
4016
|
-
const source = text.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
|
|
4017
|
-
let index = 0;
|
|
4018
|
-
while (index < source.length) {
|
|
4019
|
-
const lineEnd = source.indexOf("\n", index);
|
|
4020
|
-
const end = lineEnd === -1 ? source.length : lineEnd;
|
|
4021
|
-
const line = source.slice(index, end);
|
|
4022
|
-
const match = /^\s*(?:export\s+)?([^=\s]+)\s*=\s*/.exec(line);
|
|
4023
|
-
if (!match || line.trimStart().startsWith("#")) {
|
|
4024
|
-
index = end + 1;
|
|
4025
|
-
continue;
|
|
4026
|
-
}
|
|
4027
|
-
const name = match[1];
|
|
4028
|
-
const valueStart = index + match[0].length;
|
|
4029
|
-
const quote = source[valueStart];
|
|
4030
|
-
let value;
|
|
4031
|
-
if (quote === '"' || quote === "'" || quote === "`") {
|
|
4032
|
-
const close = source.indexOf(quote, valueStart + 1);
|
|
4033
|
-
let at = close;
|
|
4034
|
-
while (quote === '"' && at !== -1) {
|
|
4035
|
-
let slashes = 0;
|
|
4036
|
-
for (let back = at - 1; source[back] === "\\"; back--) slashes++;
|
|
4037
|
-
if (slashes % 2 === 0) break;
|
|
4038
|
-
at = source.indexOf(quote, at + 1);
|
|
4039
|
-
}
|
|
4040
|
-
if (at === -1) {
|
|
4041
|
-
value = line.slice(match[0].length).trim();
|
|
4042
|
-
index = end + 1;
|
|
4043
|
-
} else {
|
|
4044
|
-
const raw = source.slice(valueStart + 1, at);
|
|
4045
|
-
value = quote === '"' ? unescapeDouble(raw) : raw;
|
|
4046
|
-
const after = source.indexOf("\n", at);
|
|
4047
|
-
index = after === -1 ? source.length : after + 1;
|
|
4048
|
-
}
|
|
4049
|
-
} else {
|
|
4050
|
-
value = line.slice(match[0].length).replace(/(^|\s)#.*$/, "").trim();
|
|
4051
|
-
index = end + 1;
|
|
4052
|
-
}
|
|
4053
|
-
if (NAME.test(name)) variables.set(name, value);
|
|
4054
|
-
}
|
|
4055
|
-
return [...variables].map(([name, value]) => ({ name, value }));
|
|
4056
|
-
}
|
|
4057
|
-
function readEnvFiles(folder) {
|
|
4058
|
-
const variables = [];
|
|
4059
|
-
for (const source of ENV_FILES) {
|
|
4060
|
-
let text;
|
|
4061
|
-
try {
|
|
4062
|
-
const file = path12.join(folder, source);
|
|
4063
|
-
if (!fs9.statSync(file).isFile()) continue;
|
|
4064
|
-
text = fs9.readFileSync(file, "utf8");
|
|
4065
|
-
} catch {
|
|
4066
|
-
continue;
|
|
4067
|
-
}
|
|
4068
|
-
for (const variable of parseEnvText(text))
|
|
4069
|
-
variables.push({ ...variable, source });
|
|
4070
|
-
}
|
|
4071
|
-
return variables;
|
|
4072
|
-
}
|
|
4073
|
-
var TOO_COMMON = /^(?:true|false|yes|no|null|none|on|off|development|production|staging|test|localhost|127\.0\.0\.1|0\.0\.0\.0|\d+(?:\.\d+)?)$/i;
|
|
4074
|
-
function redactionValues(variables) {
|
|
4075
|
-
const values = /* @__PURE__ */ new Set();
|
|
4076
|
-
for (const { value } of variables) {
|
|
4077
|
-
const trimmed = value.trim();
|
|
4078
|
-
if (trimmed.length >= 8 && !TOO_COMMON.test(trimmed)) values.add(trimmed);
|
|
4079
|
-
if (value.length >= 8 && value !== trimmed && !TOO_COMMON.test(value))
|
|
4080
|
-
values.add(value);
|
|
4081
|
-
}
|
|
4082
|
-
return [...values];
|
|
4083
|
-
}
|
|
4084
|
-
|
|
4085
|
-
// src/github-cli.ts
|
|
4086
|
-
import { execFileSync } from "node:child_process";
|
|
4087
|
-
var GithubCliError = class extends Error {
|
|
4088
|
-
};
|
|
4089
|
-
function gh(args, env) {
|
|
4090
|
-
return execFileSync("gh", args, {
|
|
4091
|
-
env: {
|
|
4092
|
-
...env,
|
|
4093
|
-
GH_PROMPT_DISABLED: "1",
|
|
4094
|
-
NO_COLOR: "1"
|
|
4095
|
-
},
|
|
4096
|
-
encoding: "utf8",
|
|
4097
|
-
timeout: 6e4,
|
|
4098
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
4099
|
-
});
|
|
4100
|
-
}
|
|
4101
|
-
function githubCliStatus(env) {
|
|
4102
|
-
try {
|
|
4103
|
-
gh(["--version"], env);
|
|
4104
|
-
} catch {
|
|
4105
|
-
return { ready: false, reason: "missing" };
|
|
4106
|
-
}
|
|
4107
|
-
try {
|
|
4108
|
-
gh(["auth", "status", "--hostname", "github.com"], env);
|
|
4109
|
-
return { ready: true };
|
|
4110
|
-
} catch {
|
|
4111
|
-
return { ready: false, reason: "signed-out" };
|
|
4112
|
-
}
|
|
4113
|
-
}
|
|
4114
|
-
function githubCliNextStep(status) {
|
|
4115
|
-
const otherwise = "or create the repository at https://github.com/new, make it this folder's origin remote (git init first if needed) and re-run without --create-repo";
|
|
4116
|
-
return status.ready ? "" : status.reason === "missing" ? `Install the GitHub CLI (https://cli.github.com) and run \`gh auth login\`, then re-run the same command; ${otherwise}.` : `Run \`gh auth login\` to sign in to GitHub, then re-run the same command; ${otherwise}.`;
|
|
4117
|
-
}
|
|
4118
|
-
function createPrivateRepository(name, env) {
|
|
4119
|
-
let output;
|
|
4120
|
-
try {
|
|
4121
|
-
output = gh(
|
|
4122
|
-
[
|
|
4123
|
-
"api",
|
|
4124
|
-
"--method",
|
|
4125
|
-
"POST",
|
|
4126
|
-
"user/repos",
|
|
4127
|
-
"-f",
|
|
4128
|
-
`name=${name}`,
|
|
4129
|
-
"-F",
|
|
4130
|
-
"private=true"
|
|
4131
|
-
],
|
|
4132
|
-
env
|
|
4133
|
-
);
|
|
4134
|
-
} catch (error) {
|
|
4135
|
-
const failure = error;
|
|
4136
|
-
const text = `${String(failure.stdout ?? "")} ${String(failure.stderr ?? "")}`;
|
|
4137
|
-
if (/name already exists/i.test(text))
|
|
4138
|
-
throw new GithubCliError(
|
|
4139
|
-
`A repository named ${name} already exists on your GitHub account.`
|
|
4140
|
-
);
|
|
4141
|
-
throw new GithubCliError(
|
|
4142
|
-
`gh could not create ${name}: ${text.trim().split("\n").slice(-2).join(" ") || error.message}`
|
|
4143
|
-
);
|
|
4144
|
-
}
|
|
4145
|
-
const created = JSON.parse(output);
|
|
4146
|
-
if (!created.full_name)
|
|
4147
|
-
throw new GithubCliError(`gh did not return the new repository ${name}.`);
|
|
4148
|
-
return {
|
|
4149
|
-
fullName: created.full_name,
|
|
4150
|
-
defaultBranch: created.default_branch || "main",
|
|
4151
|
-
cloneUrl: created.clone_url || `https://github.com/${created.full_name}.git`,
|
|
4152
|
-
sshUrl: created.ssh_url || `git@github.com:${created.full_name}.git`
|
|
4153
|
-
};
|
|
4154
|
-
}
|
|
4155
|
-
|
|
4156
4156
|
// src/repo-snapshot.ts
|
|
4157
4157
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4158
4158
|
import fs11 from "node:fs";
|
|
@@ -4700,10 +4700,10 @@ import fs14 from "node:fs";
|
|
|
4700
4700
|
import os14 from "node:os";
|
|
4701
4701
|
import path18 from "node:path";
|
|
4702
4702
|
|
|
4703
|
-
//
|
|
4703
|
+
// src/local-sessions/index.ts
|
|
4704
4704
|
import os13 from "node:os";
|
|
4705
4705
|
|
|
4706
|
-
//
|
|
4706
|
+
// src/local-sessions/caller.ts
|
|
4707
4707
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
4708
4708
|
import fs12 from "node:fs";
|
|
4709
4709
|
import os11 from "node:os";
|
|
@@ -4955,7 +4955,7 @@ function trimImportTail(turns, options) {
|
|
|
4955
4955
|
return result;
|
|
4956
4956
|
}
|
|
4957
4957
|
|
|
4958
|
-
//
|
|
4958
|
+
// src/local-sessions/handoff-helpers.ts
|
|
4959
4959
|
import path16 from "node:path";
|
|
4960
4960
|
var DIGEST_HEADER = "[Summary of earlier context]";
|
|
4961
4961
|
var DIGEST_MAX_TASK_CHARS = 240;
|
|
@@ -5120,7 +5120,7 @@ function trimToBudget(turns, budgetChars) {
|
|
|
5120
5120
|
return result;
|
|
5121
5121
|
}
|
|
5122
5122
|
|
|
5123
|
-
//
|
|
5123
|
+
// src/local-sessions/full-transcript.ts
|
|
5124
5124
|
import fs13 from "node:fs";
|
|
5125
5125
|
import os12 from "node:os";
|
|
5126
5126
|
import path17 from "node:path";
|
|
@@ -5904,7 +5904,7 @@ function exportFullTranscript(ref, options) {
|
|
|
5904
5904
|
};
|
|
5905
5905
|
}
|
|
5906
5906
|
|
|
5907
|
-
//
|
|
5907
|
+
// src/local-sessions/index.ts
|
|
5908
5908
|
var discoverers = {
|
|
5909
5909
|
claude: discoverClaudeSessions,
|
|
5910
5910
|
codex: discoverCodexSessions,
|
|
@@ -6758,7 +6758,7 @@ function chosenSecrets(envVariables, options) {
|
|
|
6758
6758
|
}
|
|
6759
6759
|
function nextCommand(options, scope, extra = []) {
|
|
6760
6760
|
const words = [
|
|
6761
|
-
"npx -y ytimesy continue --code",
|
|
6761
|
+
"npx -y ytimesy@latest continue --code",
|
|
6762
6762
|
shellWord(options.code),
|
|
6763
6763
|
"--scope",
|
|
6764
6764
|
scope,
|
|
@@ -7526,7 +7526,7 @@ async function execute(input) {
|
|
|
7526
7526
|
}
|
|
7527
7527
|
|
|
7528
7528
|
// src/cli.ts
|
|
7529
|
-
var VERSION = true ? "0.1.
|
|
7529
|
+
var VERSION = true ? "0.1.2" : "0.0.0-dev";
|
|
7530
7530
|
var HELP = `ytimesy \u2014 move your local coding-agent sessions to ytimesy
|
|
7531
7531
|
|
|
7532
7532
|
Usage:
|