209 lines
7.2 KiB
JavaScript
209 lines
7.2 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { spawnSync } from "node:child_process";
|
|
import { mkdir, readFile, readdir, writeFile } from "node:fs/promises";
|
|
import path from "node:path";
|
|
|
|
function parseArgs(argv) {
|
|
const options = {
|
|
codexBin: "codex",
|
|
outDir: "docs/protocol-snapshots/codex-app-server",
|
|
};
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const arg = argv[index];
|
|
if (arg === "--codex-bin") {
|
|
options.codexBin = argv[index + 1];
|
|
index += 1;
|
|
continue;
|
|
}
|
|
if (arg === "--out-dir") {
|
|
options.outDir = argv[index + 1];
|
|
index += 1;
|
|
continue;
|
|
}
|
|
if (arg === "--help" || arg === "-h") {
|
|
options.help = true;
|
|
}
|
|
}
|
|
return options;
|
|
}
|
|
|
|
function usage() {
|
|
return `Usage: node scripts/codex-app-server-protocol-snapshot.mjs [--codex-bin codex] [--out-dir docs/protocol-snapshots/codex-app-server]`;
|
|
}
|
|
|
|
function run(command, args, options = {}) {
|
|
const result = spawnSync(command, args, {
|
|
cwd: options.cwd || process.cwd(),
|
|
encoding: "utf8",
|
|
maxBuffer: 50 * 1024 * 1024,
|
|
});
|
|
if (result.status !== 0) {
|
|
throw new Error(
|
|
[
|
|
`Command failed: ${command} ${args.join(" ")}`,
|
|
result.stdout?.trim(),
|
|
result.stderr?.trim(),
|
|
]
|
|
.filter(Boolean)
|
|
.join("\n"),
|
|
);
|
|
}
|
|
return result.stdout || "";
|
|
}
|
|
|
|
function parseCodexVersion(raw) {
|
|
const match = String(raw || "").match(/codex(?:-cli)?\s+([^\s]+)/i);
|
|
return match?.[1] || "unknown";
|
|
}
|
|
|
|
async function listFiles(root) {
|
|
const entries = await readdir(root, { withFileTypes: true });
|
|
const files = [];
|
|
for (const entry of entries) {
|
|
const fullPath = path.join(root, entry.name);
|
|
if (entry.isDirectory()) {
|
|
files.push(...(await listFiles(fullPath)));
|
|
} else if (entry.isFile()) {
|
|
files.push(fullPath);
|
|
}
|
|
}
|
|
return files;
|
|
}
|
|
|
|
function extractProtocolMethodsFromText(text) {
|
|
const methods = new Set();
|
|
const pattern = /"([A-Za-z][A-Za-z0-9]*(?:\/[A-Za-z0-9_-]+)+)"/g;
|
|
let match;
|
|
while ((match = pattern.exec(text))) {
|
|
const value = match[1];
|
|
if (/^(thread|turn|item|rawResponseItem|model|modelProvider|experimentalFeature|permissionProfile|process|command|review|account|config|mcpServer|plugin|marketplace|skills|hooks|fs|remoteControl|externalAgentConfig|fuzzyFileSearch|windowsSandbox)\//.test(value)) {
|
|
methods.add(value);
|
|
}
|
|
}
|
|
return methods;
|
|
}
|
|
|
|
async function extractProtocolMethods(root) {
|
|
const methods = new Set();
|
|
for (const filePath of await listFiles(root)) {
|
|
if (!/\.(json|ts)$/.test(filePath)) {
|
|
continue;
|
|
}
|
|
const text = await readFile(filePath, "utf8");
|
|
for (const method of extractProtocolMethodsFromText(text)) {
|
|
methods.add(method);
|
|
}
|
|
}
|
|
return Array.from(methods).sort();
|
|
}
|
|
|
|
function buildSupportMatrix({ helpText, methods }) {
|
|
const methodSet = new Set(methods);
|
|
return {
|
|
stdioTransport: /stdio:\/\//.test(helpText) || /stdio/.test(helpText),
|
|
unixTransport: /unix:\/\//.test(helpText),
|
|
wsTransport: /ws:\/\/|websocket/i.test(helpText),
|
|
wsAuth: /--ws-auth/.test(helpText),
|
|
threadInjectItems: methodSet.has("thread/inject_items"),
|
|
threadRollback: methodSet.has("thread/rollback"),
|
|
threadArchive: methodSet.has("thread/archive"),
|
|
threadUnarchive: methodSet.has("thread/unarchive"),
|
|
threadFork: methodSet.has("thread/fork"),
|
|
threadCompactStart: methodSet.has("thread/compact/start"),
|
|
threadNameSet: methodSet.has("thread/name/set"),
|
|
threadMetadataUpdate: methodSet.has("thread/metadata/update"),
|
|
threadShellCommand: methodSet.has("thread/shellCommand"),
|
|
threadUnsubscribe: methodSet.has("thread/unsubscribe"),
|
|
threadGoal: methodSet.has("thread/goal/set") || methodSet.has("thread/goal/get"),
|
|
turnSteer: methodSet.has("turn/steer"),
|
|
turnInterrupt: methodSet.has("turn/interrupt"),
|
|
commandExec: methodSet.has("command/exec"),
|
|
realtimeThread: methods.some((method) => method.startsWith("thread/realtime/")),
|
|
modelList: methodSet.has("model/list"),
|
|
skillsExtraRoots: methodSet.has("skills/extraRoots/set"),
|
|
hooksList: methodSet.has("hooks/list"),
|
|
pluginInstall: methodSet.has("plugin/install"),
|
|
pluginUninstall: methodSet.has("plugin/uninstall"),
|
|
pluginRead: methodSet.has("plugin/read"),
|
|
pluginSkillRead: methodSet.has("plugin/skill/read"),
|
|
pluginShare:
|
|
methodSet.has("plugin/share/save") ||
|
|
methodSet.has("plugin/share/checkout") ||
|
|
methodSet.has("plugin/share/delete") ||
|
|
methodSet.has("plugin/share/updateTargets") ||
|
|
methodSet.has("plugin/share/list"),
|
|
accountLogin:
|
|
methodSet.has("account/login/start") ||
|
|
methodSet.has("account/login/cancel") ||
|
|
methodSet.has("account/login/completed"),
|
|
accountLogout: methodSet.has("account/logout"),
|
|
accountTokenRefresh: methodSet.has("account/chatgptAuthTokens/refresh"),
|
|
accountAddCreditsNudge: methodSet.has("account/sendAddCreditsNudgeEmail"),
|
|
configValueWrite: methodSet.has("config/value/write"),
|
|
configBatchWrite: methodSet.has("config/batchWrite"),
|
|
configMcpServerReload: methodSet.has("config/mcpServer/reload"),
|
|
skillsConfigWrite: methodSet.has("skills/config/write"),
|
|
commandExecWrite: methodSet.has("command/exec/write"),
|
|
commandExecResize: methodSet.has("command/exec/resize"),
|
|
commandExecTerminate: methodSet.has("command/exec/terminate"),
|
|
fsRead:
|
|
methodSet.has("fs/readFile") ||
|
|
methodSet.has("fs/readDirectory") ||
|
|
methodSet.has("fs/getMetadata"),
|
|
fsWrite:
|
|
methodSet.has("fs/writeFile") ||
|
|
methodSet.has("fs/createDirectory") ||
|
|
methodSet.has("fs/remove") ||
|
|
methodSet.has("fs/copy"),
|
|
fsWatch: methodSet.has("fs/watch") || methodSet.has("fs/unwatch"),
|
|
};
|
|
}
|
|
|
|
async function main() {
|
|
const options = parseArgs(process.argv.slice(2));
|
|
if (options.help) {
|
|
console.log(usage());
|
|
return;
|
|
}
|
|
|
|
const versionRaw = run(options.codexBin, ["--version"]).trim();
|
|
const codexVersion = parseCodexVersion(versionRaw);
|
|
const snapshotDir = path.resolve(options.outDir, codexVersion);
|
|
const schemaDir = path.join(snapshotDir, "json-schema");
|
|
const typescriptDir = path.join(snapshotDir, "typescript");
|
|
|
|
await mkdir(schemaDir, { recursive: true });
|
|
await mkdir(typescriptDir, { recursive: true });
|
|
|
|
const helpText = run(options.codexBin, ["app-server", "--help"]);
|
|
await writeFile(path.join(snapshotDir, "app-server-help.txt"), helpText, "utf8");
|
|
|
|
run(options.codexBin, ["app-server", "generate-json-schema", "--out", schemaDir]);
|
|
run(options.codexBin, ["app-server", "generate-ts", "--out", typescriptDir]);
|
|
|
|
const methods = Array.from(
|
|
new Set([
|
|
...(await extractProtocolMethods(schemaDir)),
|
|
...(await extractProtocolMethods(typescriptDir)),
|
|
]),
|
|
).sort();
|
|
|
|
const manifest = {
|
|
generatedAt: new Date().toISOString(),
|
|
codexVersion,
|
|
codexVersionRaw: versionRaw,
|
|
codexBin: options.codexBin,
|
|
snapshotDir,
|
|
methods,
|
|
supports: buildSupportMatrix({ helpText, methods }),
|
|
};
|
|
await writeFile(path.join(snapshotDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
console.log(JSON.stringify({ ok: true, snapshotDir, codexVersion, methodCount: methods.length }));
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error instanceof Error ? error.message : String(error));
|
|
process.exit(1);
|
|
});
|