@cubicj/codex-watch 0.2.0 → 0.2.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/bin/codex-watch +57 -47
- package/package.json +1 -1
package/bin/codex-watch
CHANGED
|
@@ -3,6 +3,7 @@ const fs = require("fs");
|
|
|
3
3
|
const path = require("path");
|
|
4
4
|
const os = require("os");
|
|
5
5
|
const childProcess = require("child_process");
|
|
6
|
+
const crypto = require("node:crypto");
|
|
6
7
|
const { StringDecoder } = require("node:string_decoder");
|
|
7
8
|
|
|
8
9
|
process.stdout.on("error", (error) => {
|
|
@@ -58,11 +59,11 @@ if (args.includes("--help") || args.includes("-h")) {
|
|
|
58
59
|
const once = args.includes("--once");
|
|
59
60
|
const repoRoot = resolveRepoRoot();
|
|
60
61
|
const slug = makeSlug(repoRoot);
|
|
62
|
+
const stateDirName = makeStateDirName(repoRoot, slug);
|
|
61
63
|
const stateRoot = process.env.CLAUDE_PLUGIN_DATA
|
|
62
64
|
? path.join(process.env.CLAUDE_PLUGIN_DATA, "state")
|
|
63
65
|
: path.join(os.homedir(), ".claude/plugins/data/codex-openai-codex/state");
|
|
64
66
|
const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
|
|
65
|
-
const stateDirPattern = new RegExp("^" + escapeRegExp(slug) + "-[0-9a-f]{6,}$");
|
|
66
67
|
|
|
67
68
|
let currentJob = null;
|
|
68
69
|
let logOffset = 0;
|
|
@@ -92,6 +93,9 @@ if (once) {
|
|
|
92
93
|
printHeader(job);
|
|
93
94
|
printSessionInfo(job);
|
|
94
95
|
renderWholeLog(job);
|
|
96
|
+
if (isTerminalStatus(job.status)) {
|
|
97
|
+
printCompletionBanner(job, job.displayedCreatedAt);
|
|
98
|
+
}
|
|
95
99
|
process.exit(0);
|
|
96
100
|
}
|
|
97
101
|
|
|
@@ -115,8 +119,13 @@ function makeSlug(root) {
|
|
|
115
119
|
return path.basename(root).replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "workspace";
|
|
116
120
|
}
|
|
117
121
|
|
|
118
|
-
function
|
|
119
|
-
|
|
122
|
+
function makeStateDirName(root, rootSlug) {
|
|
123
|
+
let canonical = root;
|
|
124
|
+
try {
|
|
125
|
+
canonical = fs.realpathSync.native(root);
|
|
126
|
+
} catch (_error) {}
|
|
127
|
+
const hash = crypto.createHash("sha256").update(canonical).digest("hex").slice(0, 16);
|
|
128
|
+
return rootSlug + "-" + hash;
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
function safeReadDir(dir, options) {
|
|
@@ -144,49 +153,43 @@ function readJson(file) {
|
|
|
144
153
|
}
|
|
145
154
|
|
|
146
155
|
function findNewestJob() {
|
|
147
|
-
const
|
|
156
|
+
const jobsDir = path.join(stateRoot, stateDirName, "jobs");
|
|
157
|
+
const jobEntries = safeReadDir(jobsDir, { withFileTypes: true });
|
|
148
158
|
const candidates = [];
|
|
149
159
|
|
|
150
|
-
for (const
|
|
151
|
-
if (!
|
|
160
|
+
for (const jobEntry of jobEntries) {
|
|
161
|
+
if (!jobEntry.isFile() || !jobEntry.name.endsWith(".json")) {
|
|
152
162
|
continue;
|
|
153
163
|
}
|
|
154
164
|
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
status: typeof meta.status === "string" ? meta.status : "unknown",
|
|
179
|
-
phase: typeof meta.phase === "string" ? meta.phase : "unknown",
|
|
180
|
-
summary: typeof meta.summary === "string" ? meta.summary : "",
|
|
181
|
-
threadId: typeof meta.threadId === "string" ? meta.threadId : "",
|
|
182
|
-
write: typeof meta.write === "boolean" ? meta.write : false
|
|
183
|
-
});
|
|
184
|
-
}
|
|
165
|
+
const id = path.basename(jobEntry.name, ".json");
|
|
166
|
+
const jsonPath = path.join(jobsDir, jobEntry.name);
|
|
167
|
+
const logPath = path.join(jobsDir, id + ".log");
|
|
168
|
+
const meta = readJson(jsonPath);
|
|
169
|
+
const stat = safeStat(jsonPath);
|
|
170
|
+
const fallbackCreatedAt = stat ? stat.mtime.toISOString() : "";
|
|
171
|
+
const createdAt = typeof meta.createdAt === "string" && meta.createdAt ? meta.createdAt : fallbackCreatedAt;
|
|
172
|
+
|
|
173
|
+
candidates.push({
|
|
174
|
+
id,
|
|
175
|
+
jsonPath,
|
|
176
|
+
logPath,
|
|
177
|
+
createdAt,
|
|
178
|
+
displayedCreatedAt: typeof meta.createdAt === "string" && meta.createdAt ? meta.createdAt : createdAt,
|
|
179
|
+
startedAt: typeof meta.startedAt === "string" ? meta.startedAt : "",
|
|
180
|
+
updatedAt: typeof meta.updatedAt === "string" ? meta.updatedAt : "",
|
|
181
|
+
completedAt: typeof meta.completedAt === "string" ? meta.completedAt : "",
|
|
182
|
+
status: typeof meta.status === "string" ? meta.status : "unknown",
|
|
183
|
+
phase: typeof meta.phase === "string" ? meta.phase : "unknown",
|
|
184
|
+
summary: typeof meta.summary === "string" ? meta.summary : "",
|
|
185
|
+
threadId: typeof meta.threadId === "string" ? meta.threadId : "",
|
|
186
|
+
write: typeof meta.write === "boolean" ? meta.write : false
|
|
187
|
+
});
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
candidates.sort((a, b) => {
|
|
188
191
|
if (a.createdAt === b.createdAt) {
|
|
189
|
-
return
|
|
192
|
+
return b.id.localeCompare(a.id);
|
|
190
193
|
}
|
|
191
194
|
return a.createdAt < b.createdAt ? 1 : -1;
|
|
192
195
|
});
|
|
@@ -214,7 +217,10 @@ function scanForNewJob() {
|
|
|
214
217
|
return;
|
|
215
218
|
}
|
|
216
219
|
|
|
217
|
-
if (
|
|
220
|
+
if (
|
|
221
|
+
newest.createdAt > currentJob.createdAt ||
|
|
222
|
+
(newest.createdAt === currentJob.createdAt && newest.id.localeCompare(currentJob.id) > 0)
|
|
223
|
+
) {
|
|
218
224
|
console.log(color("dim", "──────── switched to " + newest.id + " ────────"));
|
|
219
225
|
switchToJob(newest);
|
|
220
226
|
}
|
|
@@ -402,14 +408,14 @@ function readAvailableLog() {
|
|
|
402
408
|
|
|
403
409
|
function checkJobCompletion() {
|
|
404
410
|
if (completionBannerPrinted) {
|
|
405
|
-
return
|
|
411
|
+
return;
|
|
406
412
|
}
|
|
407
413
|
|
|
408
414
|
let meta = null;
|
|
409
415
|
try {
|
|
410
416
|
meta = JSON.parse(fs.readFileSync(currentJob.jsonPath, "utf8"));
|
|
411
417
|
} catch (_error) {
|
|
412
|
-
return
|
|
418
|
+
return;
|
|
413
419
|
}
|
|
414
420
|
|
|
415
421
|
const status = typeof meta.status === "string" ? meta.status : "unknown";
|
|
@@ -418,21 +424,25 @@ function checkJobCompletion() {
|
|
|
418
424
|
currentJob.startedAt = typeof meta.startedAt === "string" ? meta.startedAt : "";
|
|
419
425
|
printSessionInfo(currentJob);
|
|
420
426
|
if (!isTerminalStatus(status)) {
|
|
421
|
-
return
|
|
427
|
+
return;
|
|
422
428
|
}
|
|
423
429
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
430
|
+
printCompletionBanner(meta, currentJob.displayedCreatedAt);
|
|
431
|
+
completionBannerPrinted = true;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function printCompletionBanner(job, fallbackCreatedAt) {
|
|
435
|
+
const status = typeof job.status === "string" ? job.status : "unknown";
|
|
436
|
+
const createdAt = typeof job.createdAt === "string" && job.createdAt ? job.createdAt : fallbackCreatedAt;
|
|
437
|
+
const startedAt = typeof job.startedAt === "string" && job.startedAt ? job.startedAt : createdAt;
|
|
438
|
+
const updatedAt = typeof job.updatedAt === "string" && job.updatedAt ? job.updatedAt : "";
|
|
439
|
+
const completedAt = typeof job.completedAt === "string" && job.completedAt ? job.completedAt : updatedAt;
|
|
428
440
|
const elapsed = formatElapsed(startedAt, completedAt);
|
|
429
441
|
if (status === "completed") {
|
|
430
442
|
console.log(color("green", "✓ completed · " + elapsed));
|
|
431
443
|
} else {
|
|
432
444
|
console.log(color("red", "✗ " + status + " · " + elapsed));
|
|
433
445
|
}
|
|
434
|
-
completionBannerPrinted = true;
|
|
435
|
-
return true;
|
|
436
446
|
}
|
|
437
447
|
|
|
438
448
|
function isTerminalStatus(status) {
|