@fclef819/cdx 0.1.0 → 0.1.1
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/README.md +8 -1
- package/bin/cdx.js +136 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,9 +30,16 @@ Each line is:
|
|
|
30
30
|
|
|
31
31
|
- `cdx` to select or create a session
|
|
32
32
|
- `cdx here` to use `.cdx` from the current directory without parent search
|
|
33
|
+
- `cdx new` to create a new session without the selection UI
|
|
34
|
+
- `cdx new here` or `cdx here new` to create a new session using `.cdx` from the current directory
|
|
33
35
|
- `cdx rm` to remove a session from `.cdx`
|
|
34
|
-
- `cdx
|
|
36
|
+
- `cdx init` to create an empty `.cdx` in the current directory
|
|
37
|
+
- `cdx add <uuid> <label>` to add a session to `.cdx`
|
|
38
|
+
- `cdx add <uuid>` to add a session and prompt for the label
|
|
39
|
+
- `cdx add` to add a session and prompt for uuid and label
|
|
35
40
|
- `cdx -h`, `cdx --help`, or `cdx help` to show help
|
|
41
|
+
- `cdx -V` or `cdx --version` to show version
|
|
42
|
+
- `cdx -v` or `cdx --verbose` to show verbose logs
|
|
36
43
|
|
|
37
44
|
## Install (npm)
|
|
38
45
|
|
package/bin/cdx.js
CHANGED
|
@@ -11,11 +11,16 @@ const CODEX_HOME = path.join(process.env.HOME || "", ".codex");
|
|
|
11
11
|
const HISTORY_PATH = path.join(CODEX_HOME, "history.jsonl");
|
|
12
12
|
const SESSIONS_DIR = path.join(CODEX_HOME, "sessions");
|
|
13
13
|
|
|
14
|
-
function
|
|
14
|
+
function logVerbose(message, enabled) {
|
|
15
|
+
if (enabled) console.log(message);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function findCdxFile(startDir, verbose) {
|
|
15
19
|
let dir = path.resolve(startDir);
|
|
16
20
|
while (true) {
|
|
17
21
|
const candidate = path.join(dir, CDX_FILENAME);
|
|
18
22
|
if (fs.existsSync(candidate)) {
|
|
23
|
+
logVerbose(`Found .cdx at: ${candidate}`, verbose);
|
|
19
24
|
return { dir, filePath: candidate };
|
|
20
25
|
}
|
|
21
26
|
const parent = path.dirname(dir);
|
|
@@ -46,16 +51,19 @@ function sanitizeLabel(label) {
|
|
|
46
51
|
return label.replace(/[\t\n\r]+/g, " ").trim();
|
|
47
52
|
}
|
|
48
53
|
|
|
49
|
-
function appendEntry(filePath, entry) {
|
|
54
|
+
function appendEntry(filePath, entry, verbose) {
|
|
50
55
|
const line = `${entry.uuid}\t${entry.label}\n`;
|
|
56
|
+
logVerbose(`Appending entry to ${filePath}`, verbose);
|
|
51
57
|
fs.appendFileSync(filePath, line, "utf8");
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
function writeEntries(filePath, entries) {
|
|
60
|
+
function writeEntries(filePath, entries, verbose) {
|
|
55
61
|
if (!entries.length) {
|
|
62
|
+
logVerbose(`Removing empty .cdx at ${filePath}`, verbose);
|
|
56
63
|
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
|
|
57
64
|
return;
|
|
58
65
|
}
|
|
66
|
+
logVerbose(`Writing ${entries.length} entries to ${filePath}`, verbose);
|
|
59
67
|
const content = entries.map((e) => `${e.uuid}\t${e.label}`).join("\n") + "\n";
|
|
60
68
|
fs.writeFileSync(filePath, content, "utf8");
|
|
61
69
|
}
|
|
@@ -95,17 +103,18 @@ function readSessionIdFromFile(filePath) {
|
|
|
95
103
|
return extractIdFromFilename(filePath);
|
|
96
104
|
}
|
|
97
105
|
|
|
98
|
-
function getLatestSessionSnapshot() {
|
|
106
|
+
function getLatestSessionSnapshot(verbose) {
|
|
99
107
|
const files = [];
|
|
100
108
|
collectSessionFiles(SESSIONS_DIR, files);
|
|
101
109
|
if (!files.length) return null;
|
|
102
110
|
files.sort((a, b) => a.mtimeMs - b.mtimeMs);
|
|
103
111
|
const latest = files[files.length - 1];
|
|
104
112
|
const id = readSessionIdFromFile(latest.path);
|
|
113
|
+
logVerbose(`Latest session file: ${latest.path}`, verbose);
|
|
105
114
|
return { id, mtimeMs: latest.mtimeMs, path: latest.path };
|
|
106
115
|
}
|
|
107
116
|
|
|
108
|
-
function getLastHistorySessionId() {
|
|
117
|
+
function getLastHistorySessionId(verbose) {
|
|
109
118
|
if (!fs.existsSync(HISTORY_PATH)) return null;
|
|
110
119
|
const lines = fs.readFileSync(HISTORY_PATH, "utf8").trim().split("\n");
|
|
111
120
|
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
@@ -116,10 +125,11 @@ function getLastHistorySessionId() {
|
|
|
116
125
|
// ignore malformed lines
|
|
117
126
|
}
|
|
118
127
|
}
|
|
128
|
+
logVerbose("No session_id found in history.jsonl", verbose);
|
|
119
129
|
return null;
|
|
120
130
|
}
|
|
121
131
|
|
|
122
|
-
function getNewestHistorySessionIdSince(previousId) {
|
|
132
|
+
function getNewestHistorySessionIdSince(previousId, verbose) {
|
|
123
133
|
if (!fs.existsSync(HISTORY_PATH)) return null;
|
|
124
134
|
const lines = fs.readFileSync(HISTORY_PATH, "utf8").trim().split("\n");
|
|
125
135
|
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
@@ -132,10 +142,12 @@ function getNewestHistorySessionIdSince(previousId) {
|
|
|
132
142
|
// ignore malformed lines
|
|
133
143
|
}
|
|
134
144
|
}
|
|
145
|
+
logVerbose("No new session_id found in history.jsonl", verbose);
|
|
135
146
|
return null;
|
|
136
147
|
}
|
|
137
148
|
|
|
138
|
-
function runCodex(args, cwd) {
|
|
149
|
+
function runCodex(args, cwd, verbose) {
|
|
150
|
+
logVerbose(`Running codex ${args.join(" ")}`.trim(), verbose);
|
|
139
151
|
const check = spawnSync("codex", ["--version"], { stdio: "ignore" });
|
|
140
152
|
if (check.error && check.error.code === "ENOENT") {
|
|
141
153
|
console.error(
|
|
@@ -180,24 +192,36 @@ async function promptLabel() {
|
|
|
180
192
|
return response.label;
|
|
181
193
|
}
|
|
182
194
|
|
|
195
|
+
async function promptUuid() {
|
|
196
|
+
const response = await prompts({
|
|
197
|
+
type: "text",
|
|
198
|
+
name: "uuid",
|
|
199
|
+
message: "Session UUID",
|
|
200
|
+
validate: (value) => (value && value.trim() ? true : "UUID is required")
|
|
201
|
+
});
|
|
202
|
+
return response.uuid;
|
|
203
|
+
}
|
|
204
|
+
|
|
183
205
|
async function runDefault(startDir, options) {
|
|
184
|
-
const found = options.here ? null : findCdxFile(startDir);
|
|
206
|
+
const found = options.here ? null : findCdxFile(startDir, options.verbose);
|
|
185
207
|
const workDir = found ? found.dir : startDir;
|
|
186
208
|
const cdxPath = found ? found.filePath : path.join(startDir, CDX_FILENAME);
|
|
187
209
|
const entries = loadEntries(found?.filePath);
|
|
188
210
|
|
|
189
211
|
console.log(`.cdx: ${cdxPath}`);
|
|
190
|
-
const selection =
|
|
212
|
+
const selection = options.forceNew
|
|
213
|
+
? { type: "new" }
|
|
214
|
+
: await selectSession(entries);
|
|
191
215
|
if (!selection) return;
|
|
192
216
|
|
|
193
217
|
if (selection.type === "new") {
|
|
194
218
|
const labelInput = await promptLabel();
|
|
195
219
|
if (!labelInput) return;
|
|
196
220
|
const label = sanitizeLabel(labelInput);
|
|
197
|
-
const previousHistoryId = getLastHistorySessionId();
|
|
198
|
-
const previousSession = getLatestSessionSnapshot();
|
|
199
|
-
runCodex([], workDir);
|
|
200
|
-
const latestSession = getLatestSessionSnapshot();
|
|
221
|
+
const previousHistoryId = getLastHistorySessionId(options.verbose);
|
|
222
|
+
const previousSession = getLatestSessionSnapshot(options.verbose);
|
|
223
|
+
runCodex([], workDir, options.verbose);
|
|
224
|
+
const latestSession = getLatestSessionSnapshot(options.verbose);
|
|
201
225
|
let newId = null;
|
|
202
226
|
if (
|
|
203
227
|
latestSession &&
|
|
@@ -208,31 +232,28 @@ async function runDefault(startDir, options) {
|
|
|
208
232
|
) {
|
|
209
233
|
newId = latestSession.id;
|
|
210
234
|
} else if (previousHistoryId) {
|
|
211
|
-
newId = getNewestHistorySessionIdSince(previousHistoryId);
|
|
235
|
+
newId = getNewestHistorySessionIdSince(previousHistoryId, options.verbose);
|
|
212
236
|
}
|
|
213
237
|
if (!newId) {
|
|
214
238
|
console.error("Could not determine new session UUID; not updating .cdx.");
|
|
215
239
|
return;
|
|
216
240
|
}
|
|
217
|
-
appendEntry(cdxPath, { uuid: newId, label });
|
|
241
|
+
appendEntry(cdxPath, { uuid: newId, label }, options.verbose);
|
|
218
242
|
return;
|
|
219
243
|
}
|
|
220
244
|
|
|
221
245
|
if (selection.type === "resume") {
|
|
222
|
-
runCodex(["resume", selection.entry.uuid], workDir);
|
|
246
|
+
runCodex(["resume", selection.entry.uuid], workDir, options.verbose);
|
|
223
247
|
}
|
|
224
248
|
}
|
|
225
249
|
|
|
226
|
-
async function runRemove(startDir,
|
|
227
|
-
const found =
|
|
228
|
-
const targetPath = found ? found.filePath : path.join(startDir, CDX_FILENAME);
|
|
229
|
-
const targetDir = found ? found.dir : startDir;
|
|
250
|
+
async function runRemove(startDir, verbose) {
|
|
251
|
+
const found = findCdxFile(startDir, verbose);
|
|
230
252
|
if (!found) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
253
|
+
console.log("No .cdx file found.");
|
|
254
|
+
return;
|
|
235
255
|
}
|
|
256
|
+
const targetPath = found.filePath;
|
|
236
257
|
const entries = loadEntries(targetPath);
|
|
237
258
|
if (!entries.length) {
|
|
238
259
|
console.log("No sessions to remove.");
|
|
@@ -252,7 +273,48 @@ async function runRemove(startDir, options) {
|
|
|
252
273
|
|
|
253
274
|
if (!response.selection) return;
|
|
254
275
|
const remaining = entries.filter((entry) => entry.uuid !== response.selection);
|
|
255
|
-
writeEntries(targetPath, remaining);
|
|
276
|
+
writeEntries(targetPath, remaining, verbose);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function printAddHelp() {
|
|
280
|
+
console.log(`Usage:
|
|
281
|
+
cdx add <uuid> <label>
|
|
282
|
+
cdx add <uuid>
|
|
283
|
+
cdx add`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function runInit(startDir, verbose) {
|
|
287
|
+
const cdxPath = path.join(startDir, CDX_FILENAME);
|
|
288
|
+
if (fs.existsSync(cdxPath)) {
|
|
289
|
+
console.log(".cdx already exists in the current directory.");
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
fs.writeFileSync(cdxPath, "", "utf8");
|
|
293
|
+
logVerbose(`Created .cdx at ${cdxPath}`, verbose);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function runAdd(startDir, args, verbose) {
|
|
297
|
+
if (args.length > 3 || (args.length === 2 && args[1] === "")) {
|
|
298
|
+
printAddHelp();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const found = findCdxFile(startDir, verbose);
|
|
303
|
+
const cdxPath = found ? found.filePath : path.join(startDir, CDX_FILENAME);
|
|
304
|
+
let uuid = args[1];
|
|
305
|
+
let label = args[2];
|
|
306
|
+
|
|
307
|
+
if (!uuid) {
|
|
308
|
+
uuid = await promptUuid();
|
|
309
|
+
}
|
|
310
|
+
if (!uuid) return;
|
|
311
|
+
|
|
312
|
+
if (!label) {
|
|
313
|
+
label = await promptLabel();
|
|
314
|
+
}
|
|
315
|
+
if (!label) return;
|
|
316
|
+
|
|
317
|
+
appendEntry(cdxPath, { uuid: uuid.trim(), label: sanitizeLabel(label) }, verbose);
|
|
256
318
|
}
|
|
257
319
|
|
|
258
320
|
function printHelp() {
|
|
@@ -261,16 +323,37 @@ function printHelp() {
|
|
|
261
323
|
Usage:
|
|
262
324
|
cdx
|
|
263
325
|
cdx here
|
|
326
|
+
cdx new
|
|
327
|
+
cdx new here
|
|
328
|
+
cdx here new
|
|
264
329
|
cdx rm
|
|
265
|
-
cdx
|
|
266
|
-
cdx
|
|
330
|
+
cdx init
|
|
331
|
+
cdx add <uuid> <label>
|
|
332
|
+
cdx add <uuid>
|
|
333
|
+
cdx add
|
|
334
|
+
cdx -V
|
|
335
|
+
cdx --version
|
|
336
|
+
cdx -v
|
|
337
|
+
cdx --verbose
|
|
267
338
|
|
|
268
339
|
Notes:
|
|
269
340
|
- "here" skips parent directory search and uses .cdx in the current directory
|
|
270
341
|
- the selected .cdx path is shown before session selection
|
|
342
|
+
- use -h, --help, or help to show this message
|
|
343
|
+
- use -v or --verbose to show debug logs
|
|
271
344
|
`);
|
|
272
345
|
}
|
|
273
346
|
|
|
347
|
+
function printVersion() {
|
|
348
|
+
try {
|
|
349
|
+
const pkgPath = path.join(__dirname, "..", "package.json");
|
|
350
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
351
|
+
console.log(pkg.version || "unknown");
|
|
352
|
+
} catch {
|
|
353
|
+
console.log("unknown");
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
274
357
|
async function main() {
|
|
275
358
|
const args = process.argv.slice(2);
|
|
276
359
|
const subcommand = args[0];
|
|
@@ -280,23 +363,45 @@ async function main() {
|
|
|
280
363
|
args.includes("-h") ||
|
|
281
364
|
args.includes("--help") ||
|
|
282
365
|
args.includes("help");
|
|
366
|
+
const wantsVersion = args.includes("-V") || args.includes("--version");
|
|
367
|
+
const verbose = args.includes("-v") || args.includes("--verbose");
|
|
283
368
|
|
|
284
369
|
if (wantsHelp) {
|
|
285
370
|
printHelp();
|
|
286
371
|
return;
|
|
287
372
|
}
|
|
288
373
|
|
|
289
|
-
if (
|
|
290
|
-
|
|
374
|
+
if (wantsVersion) {
|
|
375
|
+
printVersion();
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (subcommand === "rm") {
|
|
380
|
+
await runRemove(startDir, verbose);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (subcommand === "add") {
|
|
385
|
+
await runAdd(startDir, args, verbose);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (subcommand === "init") {
|
|
390
|
+
runInit(startDir, verbose);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (subcommand === "new" || (here && args.includes("new"))) {
|
|
395
|
+
await runDefault(startDir, { here, forceNew: true, verbose });
|
|
291
396
|
return;
|
|
292
397
|
}
|
|
293
398
|
|
|
294
399
|
if (subcommand === "here" || here) {
|
|
295
|
-
await runDefault(startDir, { here: true });
|
|
400
|
+
await runDefault(startDir, { here: true, verbose });
|
|
296
401
|
return;
|
|
297
402
|
}
|
|
298
403
|
|
|
299
|
-
await runDefault(startDir, { here: false });
|
|
404
|
+
await runDefault(startDir, { here: false, verbose });
|
|
300
405
|
}
|
|
301
406
|
|
|
302
407
|
main().catch((err) => {
|