codex-agent-view 0.2.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.
- package/.agents/plugins/marketplace.json +20 -0
- package/.codex-plugin/plugin.json +34 -0
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +327 -0
- package/assets/logo-dark.svg +13 -0
- package/assets/logo.svg +13 -0
- package/bin/codex-agent-view.mjs +489 -0
- package/hooks/hooks.json +62 -0
- package/package.json +59 -0
- package/public/app.js +637 -0
- package/public/index.html +137 -0
- package/public/styles.css +821 -0
- package/scripts/capture-hook.mjs +143 -0
- package/scripts/send-hook.mjs +64 -0
- package/skills/codex-agent-view/SKILL.md +21 -0
- package/src/core/index.mjs +3 -0
- package/src/core/monitor-store.mjs +332 -0
- package/src/core/normalize-hook-payload.mjs +146 -0
- package/src/runtime/config.mjs +111 -0
- package/src/runtime/server.mjs +203 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
cp,
|
|
5
|
+
lstat,
|
|
6
|
+
mkdir,
|
|
7
|
+
readFile,
|
|
8
|
+
rm,
|
|
9
|
+
rmdir,
|
|
10
|
+
writeFile,
|
|
11
|
+
} from "node:fs/promises";
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
import { dirname, join, resolve } from "node:path";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
|
|
17
|
+
import { startMonitorServer } from "../src/runtime/server.mjs";
|
|
18
|
+
import {
|
|
19
|
+
DEFAULT_PORT,
|
|
20
|
+
readRuntimeInfo,
|
|
21
|
+
removeRuntimeInfo,
|
|
22
|
+
runtimeDirectory,
|
|
23
|
+
runtimeFile,
|
|
24
|
+
} from "../src/runtime/config.mjs";
|
|
25
|
+
|
|
26
|
+
const PACKAGE_ROOT = resolve(fileURLToPath(new URL("../", import.meta.url)));
|
|
27
|
+
const PLUGIN_ID = "codex-agent-view@codex-agent-view";
|
|
28
|
+
const MARKETPLACE_NAME = "codex-agent-view";
|
|
29
|
+
const BUNDLE_MARKER = ".codex-agent-view-owned.json";
|
|
30
|
+
const BUNDLE_MARKER_SCHEMA_VERSION = 1;
|
|
31
|
+
const INSTALL_ENTRIES = [
|
|
32
|
+
".agents",
|
|
33
|
+
".codex-plugin",
|
|
34
|
+
"assets",
|
|
35
|
+
"bin",
|
|
36
|
+
"hooks",
|
|
37
|
+
"public",
|
|
38
|
+
"scripts",
|
|
39
|
+
"skills",
|
|
40
|
+
"src",
|
|
41
|
+
"LICENSE",
|
|
42
|
+
"NOTICE",
|
|
43
|
+
"README.md",
|
|
44
|
+
"package.json",
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
async function packageVersion() {
|
|
48
|
+
return JSON.parse(await readFile(join(PACKAGE_ROOT, "package.json"), "utf8")).version;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function printHelp() {
|
|
52
|
+
process.stdout.write(`Codex Agent View
|
|
53
|
+
|
|
54
|
+
Usage:
|
|
55
|
+
codex-agent-view start [--port <port>] [--no-open]
|
|
56
|
+
codex-agent-view status [--json]
|
|
57
|
+
codex-agent-view doctor [--json]
|
|
58
|
+
codex-agent-view install
|
|
59
|
+
codex-agent-view uninstall [--purge]
|
|
60
|
+
codex-agent-view --version
|
|
61
|
+
|
|
62
|
+
The monitor is read-only and binds only to 127.0.0.1.
|
|
63
|
+
`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function optionValue(args, name) {
|
|
67
|
+
const index = args.indexOf(name);
|
|
68
|
+
return index === -1 ? undefined : args[index + 1];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function run(command, args, { allowFailure = false } = {}) {
|
|
72
|
+
return new Promise((resolvePromise, reject) => {
|
|
73
|
+
const child = spawn(command, args, {
|
|
74
|
+
env: process.env,
|
|
75
|
+
shell: false,
|
|
76
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
77
|
+
});
|
|
78
|
+
let stdout = "";
|
|
79
|
+
let stderr = "";
|
|
80
|
+
child.stdout.setEncoding("utf8");
|
|
81
|
+
child.stderr.setEncoding("utf8");
|
|
82
|
+
child.stdout.on("data", (chunk) => {
|
|
83
|
+
stdout += chunk;
|
|
84
|
+
});
|
|
85
|
+
child.stderr.on("data", (chunk) => {
|
|
86
|
+
stderr += chunk;
|
|
87
|
+
});
|
|
88
|
+
child.once("error", reject);
|
|
89
|
+
child.once("close", (code) => {
|
|
90
|
+
const result = { code: code ?? 1, stderr, stdout };
|
|
91
|
+
if (!allowFailure && result.code !== 0) {
|
|
92
|
+
reject(new Error(stderr.trim() || `${command} exited with ${result.code}`));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
resolvePromise(result);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function openBrowser(url) {
|
|
101
|
+
const command =
|
|
102
|
+
process.platform === "darwin"
|
|
103
|
+
? ["open", [url]]
|
|
104
|
+
: process.platform === "win32"
|
|
105
|
+
? ["cmd", ["/c", "start", "", url]]
|
|
106
|
+
: ["xdg-open", [url]];
|
|
107
|
+
const child = spawn(command[0], command[1], {
|
|
108
|
+
detached: true,
|
|
109
|
+
shell: false,
|
|
110
|
+
stdio: "ignore",
|
|
111
|
+
});
|
|
112
|
+
child.unref();
|
|
113
|
+
child.on("error", () => {});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function start(args) {
|
|
117
|
+
const requestedPort = optionValue(args, "--port");
|
|
118
|
+
const port = requestedPort === undefined ? DEFAULT_PORT : Number(requestedPort);
|
|
119
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
120
|
+
throw new Error("--port must be an integer from 0 to 65535");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const runtime = await inspectRuntime();
|
|
124
|
+
if (runtime.kind === "unknown") {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`refusing to replace unrecognized runtime file at ${runtimeFile()}; move it explicitly and retry`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (runtime.kind === "valid" && (await runtimeResponds(runtime.info))) {
|
|
130
|
+
throw new Error("a Codex Agent View monitor is already running; stop it before starting another");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const monitor = await startMonitorServer({ port });
|
|
134
|
+
process.stdout.write(`Codex Agent View is running at ${monitor.url}\n`);
|
|
135
|
+
process.stdout.write("Press Ctrl+C to stop the in-memory monitor.\n");
|
|
136
|
+
if (!args.includes("--no-open")) {
|
|
137
|
+
openBrowser(monitor.url);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let stopping = false;
|
|
141
|
+
const stop = async () => {
|
|
142
|
+
if (stopping) return;
|
|
143
|
+
stopping = true;
|
|
144
|
+
await monitor.close();
|
|
145
|
+
process.exitCode = 0;
|
|
146
|
+
};
|
|
147
|
+
process.once("SIGINT", stop);
|
|
148
|
+
process.once("SIGTERM", stop);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function fetchState() {
|
|
152
|
+
const runtime = await readRuntimeInfo();
|
|
153
|
+
const response = await fetch(`http://${runtime.host}:${runtime.port}/api/state`, {
|
|
154
|
+
headers: { authorization: `Bearer ${runtime.token}` },
|
|
155
|
+
signal: AbortSignal.timeout(1_500),
|
|
156
|
+
});
|
|
157
|
+
if (!response.ok) {
|
|
158
|
+
throw new Error(`monitor returned HTTP ${response.status}`);
|
|
159
|
+
}
|
|
160
|
+
return response.json();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
async function status(args) {
|
|
164
|
+
const snapshot = await fetchState();
|
|
165
|
+
if (args.includes("--json")) {
|
|
166
|
+
process.stdout.write(`${JSON.stringify(snapshot, null, 2)}\n`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const sessions = Array.isArray(snapshot.sessions) ? snapshot.sessions : [];
|
|
170
|
+
const agents = sessions.reduce(
|
|
171
|
+
(count, session) => count + (Array.isArray(session.agents) ? session.agents.length : 0),
|
|
172
|
+
0,
|
|
173
|
+
);
|
|
174
|
+
process.stdout.write(`${sessions.length} task(s), ${agents} subagent(s) observed.\n`);
|
|
175
|
+
for (const session of sessions) {
|
|
176
|
+
process.stdout.write(`- ${session.session_id}: ${session.status} (${session.agents.length} agents)\n`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function doctor(args) {
|
|
181
|
+
const codex = await run("codex", ["--version"], { allowFailure: true });
|
|
182
|
+
let monitor = { ok: false, message: "not running" };
|
|
183
|
+
try {
|
|
184
|
+
const snapshot = await fetchState();
|
|
185
|
+
monitor = { ok: true, sessions: snapshot.sessions?.length || 0 };
|
|
186
|
+
} catch (error) {
|
|
187
|
+
monitor = { ok: false, message: error.message };
|
|
188
|
+
}
|
|
189
|
+
const plugins = await run("codex", ["plugin", "list", "--json"], {
|
|
190
|
+
allowFailure: true,
|
|
191
|
+
});
|
|
192
|
+
let installed = false;
|
|
193
|
+
try {
|
|
194
|
+
const parsed = JSON.parse(plugins.stdout);
|
|
195
|
+
installed = parsed.installed?.some((entry) => entry.pluginId === PLUGIN_ID) || false;
|
|
196
|
+
} catch {}
|
|
197
|
+
const report = {
|
|
198
|
+
codex: { ok: codex.code === 0, version: codex.stdout.trim() || null },
|
|
199
|
+
monitor,
|
|
200
|
+
plugin: { installed },
|
|
201
|
+
runtime_directory: runtimeDirectory(),
|
|
202
|
+
};
|
|
203
|
+
if (args.includes("--json")) {
|
|
204
|
+
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
process.stdout.write(`Codex CLI: ${report.codex.ok ? report.codex.version : "not available"}\n`);
|
|
208
|
+
process.stdout.write(`Plugin: ${installed ? "installed" : "not installed"}\n`);
|
|
209
|
+
process.stdout.write(`Monitor: ${monitor.ok ? "running" : monitor.message}\n`);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function pathExists(path) {
|
|
213
|
+
try {
|
|
214
|
+
return await lstat(path);
|
|
215
|
+
} catch (error) {
|
|
216
|
+
if (error?.code === "ENOENT") return null;
|
|
217
|
+
throw error;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function assertRuntimeRootIsNotSymlink(root) {
|
|
222
|
+
const stats = await pathExists(root);
|
|
223
|
+
if (stats?.isSymbolicLink()) {
|
|
224
|
+
throw new Error(`refusing symbolic link runtime directory at ${root}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function readJsonRegularFile(path) {
|
|
229
|
+
const stats = await pathExists(path);
|
|
230
|
+
if (!stats?.isFile() || stats.isSymbolicLink()) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function inspectRuntime() {
|
|
241
|
+
const path = runtimeFile();
|
|
242
|
+
const stats = await pathExists(path);
|
|
243
|
+
if (!stats) {
|
|
244
|
+
return { kind: "absent", path };
|
|
245
|
+
}
|
|
246
|
+
if (!stats.isFile() || stats.isSymbolicLink()) {
|
|
247
|
+
return { kind: "unknown", path };
|
|
248
|
+
}
|
|
249
|
+
try {
|
|
250
|
+
return { info: await readRuntimeInfo(), kind: "valid", path };
|
|
251
|
+
} catch {
|
|
252
|
+
return { kind: "unknown", path };
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function runtimeResponds(runtime) {
|
|
257
|
+
try {
|
|
258
|
+
await fetch(`http://${runtime.host}:${runtime.port}/api/state`, {
|
|
259
|
+
headers: { authorization: `Bearer ${runtime.token}` },
|
|
260
|
+
signal: AbortSignal.timeout(1_500),
|
|
261
|
+
});
|
|
262
|
+
return true;
|
|
263
|
+
} catch {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function inspectPluginBundle(destination) {
|
|
269
|
+
const stats = await pathExists(destination);
|
|
270
|
+
if (!stats) {
|
|
271
|
+
return { kind: "absent" };
|
|
272
|
+
}
|
|
273
|
+
if (!stats.isDirectory() || stats.isSymbolicLink()) {
|
|
274
|
+
return { kind: "unmanaged" };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const marker = await readJsonRegularFile(join(destination, BUNDLE_MARKER));
|
|
278
|
+
const manifest = await readJsonRegularFile(
|
|
279
|
+
join(destination, ".codex-plugin", "plugin.json"),
|
|
280
|
+
);
|
|
281
|
+
if (
|
|
282
|
+
marker?.schema_version !== BUNDLE_MARKER_SCHEMA_VERSION ||
|
|
283
|
+
marker?.package !== MARKETPLACE_NAME ||
|
|
284
|
+
marker?.plugin_id !== PLUGIN_ID ||
|
|
285
|
+
manifest?.name !== MARKETPLACE_NAME
|
|
286
|
+
) {
|
|
287
|
+
return { kind: "unmanaged" };
|
|
288
|
+
}
|
|
289
|
+
return { kind: "managed" };
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function unmanagedBundleError(destination) {
|
|
293
|
+
return new Error(
|
|
294
|
+
`refusing to replace or remove unmanaged directory at ${destination}; its files were preserved`,
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function removeManagedPluginBundle(destination) {
|
|
299
|
+
const bundle = await inspectPluginBundle(destination);
|
|
300
|
+
if (bundle.kind === "unmanaged") {
|
|
301
|
+
throw unmanagedBundleError(destination);
|
|
302
|
+
}
|
|
303
|
+
if (bundle.kind === "managed") {
|
|
304
|
+
await rm(destination, { force: true, recursive: true });
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function copyPluginBundle(destination) {
|
|
309
|
+
const root = runtimeDirectory();
|
|
310
|
+
if (dirname(destination) !== root || destination === root) {
|
|
311
|
+
throw new Error("refusing unsafe plugin destination");
|
|
312
|
+
}
|
|
313
|
+
await assertRuntimeRootIsNotSymlink(root);
|
|
314
|
+
const existing = await inspectPluginBundle(destination);
|
|
315
|
+
if (existing.kind === "unmanaged") {
|
|
316
|
+
throw unmanagedBundleError(destination);
|
|
317
|
+
}
|
|
318
|
+
if (existing.kind === "managed") {
|
|
319
|
+
await rm(destination, { force: true, recursive: true });
|
|
320
|
+
}
|
|
321
|
+
await mkdir(destination, { mode: 0o700, recursive: true });
|
|
322
|
+
for (const entry of INSTALL_ENTRIES) {
|
|
323
|
+
const source = join(PACKAGE_ROOT, entry);
|
|
324
|
+
if (!(await pathExists(source))) continue;
|
|
325
|
+
await cp(source, join(destination, entry), {
|
|
326
|
+
errorOnExist: true,
|
|
327
|
+
force: false,
|
|
328
|
+
recursive: true,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
await writeFile(
|
|
332
|
+
join(destination, BUNDLE_MARKER),
|
|
333
|
+
`${JSON.stringify(
|
|
334
|
+
{
|
|
335
|
+
package: MARKETPLACE_NAME,
|
|
336
|
+
plugin_id: PLUGIN_ID,
|
|
337
|
+
schema_version: BUNDLE_MARKER_SCHEMA_VERSION,
|
|
338
|
+
},
|
|
339
|
+
null,
|
|
340
|
+
2,
|
|
341
|
+
)}\n`,
|
|
342
|
+
{ encoding: "utf8", flag: "wx", mode: 0o600 },
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async function configuredMarketplaces() {
|
|
347
|
+
const result = await run("codex", ["plugin", "marketplace", "list", "--json"]);
|
|
348
|
+
return JSON.parse(result.stdout).marketplaces || [];
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
async function install() {
|
|
352
|
+
const destination = join(runtimeDirectory(), "marketplace");
|
|
353
|
+
const marketplaces = await configuredMarketplaces();
|
|
354
|
+
const existing = marketplaces.find((entry) => entry.name === MARKETPLACE_NAME);
|
|
355
|
+
if (existing && resolve(existing.root) !== resolve(destination)) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
`marketplace ${MARKETPLACE_NAME} already points to ${existing.root}; remove it explicitly before installing this npm bundle`,
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
await copyPluginBundle(destination);
|
|
361
|
+
if (!existing) {
|
|
362
|
+
await run("codex", ["plugin", "marketplace", "add", destination, "--json"]);
|
|
363
|
+
}
|
|
364
|
+
await run("codex", ["plugin", "add", PLUGIN_ID, "--json"]);
|
|
365
|
+
process.stdout.write(`Installed ${PLUGIN_ID} from ${destination}.\n`);
|
|
366
|
+
process.stdout.write("Review and trust the hook in the CLI /hooks screen, restart Codex, then start a new task.\n");
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function isBroadRuntimeRoot(root) {
|
|
370
|
+
const normalized = resolve(root);
|
|
371
|
+
return (
|
|
372
|
+
dirname(normalized) === normalized ||
|
|
373
|
+
normalized === resolve(homedir()) ||
|
|
374
|
+
normalized === resolve(process.cwd()) ||
|
|
375
|
+
normalized === PACKAGE_ROOT
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function purgeStaleRuntime(preflight) {
|
|
380
|
+
if (preflight.kind !== "valid") {
|
|
381
|
+
return preflight.kind === "unknown";
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const current = await inspectRuntime();
|
|
385
|
+
if (current.kind !== "valid" || current.info.token !== preflight.info.token) {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
if (await runtimeResponds(current.info)) {
|
|
389
|
+
throw new Error("the Codex Agent View monitor started during uninstall; runtime data was preserved");
|
|
390
|
+
}
|
|
391
|
+
await removeRuntimeInfo(current.info.token);
|
|
392
|
+
return false;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function removeRuntimeRootIfEmpty(root) {
|
|
396
|
+
if (isBroadRuntimeRoot(root)) {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
try {
|
|
400
|
+
await rmdir(root);
|
|
401
|
+
return true;
|
|
402
|
+
} catch (error) {
|
|
403
|
+
if (
|
|
404
|
+
error?.code === "ENOENT" ||
|
|
405
|
+
error?.code === "ENOTEMPTY" ||
|
|
406
|
+
error?.code === "EEXIST" ||
|
|
407
|
+
error?.code === "ENOTDIR" ||
|
|
408
|
+
error?.code === "EBUSY"
|
|
409
|
+
) {
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
throw error;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function uninstall(args) {
|
|
417
|
+
const root = runtimeDirectory();
|
|
418
|
+
const bundle = join(root, "marketplace");
|
|
419
|
+
if (dirname(bundle) !== root || bundle === root) {
|
|
420
|
+
throw new Error("refusing unsafe plugin destination");
|
|
421
|
+
}
|
|
422
|
+
await assertRuntimeRootIsNotSymlink(root);
|
|
423
|
+
const bundlePreflight = await inspectPluginBundle(bundle);
|
|
424
|
+
if (bundlePreflight.kind === "unmanaged") {
|
|
425
|
+
throw unmanagedBundleError(bundle);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const purge = args.includes("--purge");
|
|
429
|
+
const runtimePreflight = purge ? await inspectRuntime() : { kind: "absent" };
|
|
430
|
+
if (
|
|
431
|
+
runtimePreflight.kind === "valid" &&
|
|
432
|
+
(await runtimeResponds(runtimePreflight.info))
|
|
433
|
+
) {
|
|
434
|
+
throw new Error(
|
|
435
|
+
"the Codex Agent View monitor is running; stop it before uninstalling with --purge",
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
await run("codex", ["plugin", "remove", PLUGIN_ID, "--json"], { allowFailure: true });
|
|
440
|
+
await run("codex", ["plugin", "marketplace", "remove", MARKETPLACE_NAME, "--json"], {
|
|
441
|
+
allowFailure: true,
|
|
442
|
+
});
|
|
443
|
+
await removeManagedPluginBundle(bundle);
|
|
444
|
+
if (purge) {
|
|
445
|
+
const preservedRuntimeFile = await purgeStaleRuntime(runtimePreflight);
|
|
446
|
+
const removedRoot = await removeRuntimeRootIfEmpty(root);
|
|
447
|
+
if (removedRoot) {
|
|
448
|
+
process.stdout.write(`Removed plugin, marketplace, and runtime data from ${root}.\n`);
|
|
449
|
+
} else {
|
|
450
|
+
process.stdout.write(
|
|
451
|
+
`Removed owned plugin and stale runtime files. Unrelated or unrecognized files at ${root} were preserved; review them before manual removal.\n`,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
if (preservedRuntimeFile) {
|
|
455
|
+
process.stdout.write(
|
|
456
|
+
`The unrecognized runtime file at ${runtimeFile()} was preserved.\n`,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
} else {
|
|
460
|
+
process.stdout.write("Removed plugin and marketplace bundle. Runtime data was preserved.\n");
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async function main() {
|
|
465
|
+
const [command = "help", ...args] = process.argv.slice(2);
|
|
466
|
+
if (command === "--version" || command === "-v") {
|
|
467
|
+
process.stdout.write(`${await packageVersion()}\n`);
|
|
468
|
+
} else if (command === "start") {
|
|
469
|
+
await start(args);
|
|
470
|
+
} else if (command === "status") {
|
|
471
|
+
await status(args);
|
|
472
|
+
} else if (command === "doctor") {
|
|
473
|
+
await doctor(args);
|
|
474
|
+
} else if (command === "install") {
|
|
475
|
+
await install(args);
|
|
476
|
+
} else if (command === "uninstall") {
|
|
477
|
+
await uninstall(args);
|
|
478
|
+
} else if (command === "help" || command === "--help" || command === "-h") {
|
|
479
|
+
printHelp();
|
|
480
|
+
} else {
|
|
481
|
+
printHelp();
|
|
482
|
+
throw new Error(`unknown command: ${command}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
main().catch((error) => {
|
|
487
|
+
process.stderr.write(`codex-agent-view: ${error.message}\n`);
|
|
488
|
+
process.exitCode = 1;
|
|
489
|
+
});
|
package/hooks/hooks.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Capture privacy-minimized Codex lifecycle payloads for the local companion monitor.",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SubagentStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${PLUGIN_ROOT}/scripts/send-hook.mjs\"",
|
|
10
|
+
"timeout": 5,
|
|
11
|
+
"statusMessage": "Recording subagent start"
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"SubagentStop": [
|
|
17
|
+
{
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "node \"${PLUGIN_ROOT}/scripts/send-hook.mjs\"",
|
|
22
|
+
"timeout": 5,
|
|
23
|
+
"statusMessage": "Recording subagent stop"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
"PreToolUse": [
|
|
29
|
+
{
|
|
30
|
+
"hooks": [
|
|
31
|
+
{
|
|
32
|
+
"type": "command",
|
|
33
|
+
"command": "node \"${PLUGIN_ROOT}/scripts/send-hook.mjs\"",
|
|
34
|
+
"timeout": 5
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"PostToolUse": [
|
|
40
|
+
{
|
|
41
|
+
"hooks": [
|
|
42
|
+
{
|
|
43
|
+
"type": "command",
|
|
44
|
+
"command": "node \"${PLUGIN_ROOT}/scripts/send-hook.mjs\"",
|
|
45
|
+
"timeout": 5
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
49
|
+
],
|
|
50
|
+
"PermissionRequest": [
|
|
51
|
+
{
|
|
52
|
+
"hooks": [
|
|
53
|
+
{
|
|
54
|
+
"type": "command",
|
|
55
|
+
"command": "node \"${PLUGIN_ROOT}/scripts/send-hook.mjs\"",
|
|
56
|
+
"timeout": 5
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "codex-agent-view",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Local read-only companion monitor for Codex parent tasks and subagents.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/core/index.mjs",
|
|
7
|
+
"exports": "./src/core/index.mjs",
|
|
8
|
+
"bin": {
|
|
9
|
+
"codex-agent-view": "bin/codex-agent-view.mjs"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
".agents/",
|
|
13
|
+
".codex-plugin/",
|
|
14
|
+
"assets/",
|
|
15
|
+
"bin/",
|
|
16
|
+
"hooks/",
|
|
17
|
+
"public/",
|
|
18
|
+
"scripts/capture-hook.mjs",
|
|
19
|
+
"scripts/send-hook.mjs",
|
|
20
|
+
"skills/",
|
|
21
|
+
"src/",
|
|
22
|
+
"README.md",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"NOTICE"
|
|
25
|
+
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"test": "node --test",
|
|
28
|
+
"validate:plugin": "node scripts/validate-plugin.mjs",
|
|
29
|
+
"check": "npm test && npm run validate:plugin && npm pack --dry-run --cache ./node_modules/.cache/npm",
|
|
30
|
+
"prepublishOnly": "npm run check",
|
|
31
|
+
"start": "node bin/codex-agent-view.mjs start",
|
|
32
|
+
"doctor": "node bin/codex-agent-view.mjs doctor",
|
|
33
|
+
"install:plugin": "node bin/codex-agent-view.mjs install",
|
|
34
|
+
"uninstall:plugin": "node bin/codex-agent-view.mjs uninstall"
|
|
35
|
+
},
|
|
36
|
+
"keywords": [
|
|
37
|
+
"codex",
|
|
38
|
+
"plugin",
|
|
39
|
+
"subagent",
|
|
40
|
+
"monitor"
|
|
41
|
+
],
|
|
42
|
+
"author": "Junho Yoon",
|
|
43
|
+
"license": "Apache-2.0",
|
|
44
|
+
"homepage": "https://github.com/JunhoYoon95/codex-agent-view#readme",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/JunhoYoon95/codex-agent-view.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/JunhoYoon95/codex-agent-view/issues"
|
|
51
|
+
},
|
|
52
|
+
"packageManager": "npm@11.13.0",
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18"
|
|
58
|
+
}
|
|
59
|
+
}
|