verify-packed-package.mjs 2.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. #!/usr/bin/env node
  2. import assert from "node:assert/strict";
  3. import { execFile } from "node:child_process";
  4. import { constants } from "node:fs";
  5. import { access, mkdtemp, rm, stat } from "node:fs/promises";
  6. import { tmpdir } from "node:os";
  7. import { basename, join, resolve } from "node:path";
  8. import { promisify } from "node:util";
  9. import { Client } from "@modelcontextprotocol/sdk/client/index.js";
  10. import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
  11. const execFileAsync = promisify(execFile);
  12. const EXPECTED_TOOLS = ["overview", "list_threads", "inspect_thread", "wait_for_change"];
  13. const DEFAULT_CACHE = "/private/tmp/codex-chief-of-staff-npm-cache";
  14. async function selectedTarball(argv) {
  15. if (argv.length !== 1) {
  16. throw new Error("usage: npm run verify:package -- /absolute/path/to/codex-chief-of-staff-VERSION.tgz");
  17. }
  18. const tarball = resolve(argv[0]);
  19. if (!tarball.endsWith(".tgz")) throw new Error("package path must end in .tgz");
  20. const metadata = await stat(tarball);
  21. if (!metadata.isFile()) throw new Error("package path must identify a readable file");
  22. await access(tarball, constants.R_OK);
  23. return tarball;
  24. }
  25. async function verify() {
  26. const tarball = await selectedTarball(process.argv.slice(2));
  27. const prefix = await mkdtemp(join(tmpdir(), "codex-chief-of-staff-package-"));
  28. const cache = process.env.CODEX_CHIEF_OF_STAFF_NPM_CACHE ?? DEFAULT_CACHE;
  29. let client;
  30. try {
  31. await execFileAsync("npm", [
  32. "install",
  33. "--prefix",
  34. prefix,
  35. "--ignore-scripts",
  36. "--cache",
  37. cache,
  38. tarball,
  39. ], { maxBuffer: 10 * 1024 * 1024 });
  40. const executable = join(prefix, "node_modules", ".bin", "codex-chief-of-staff");
  41. await access(executable, constants.X_OK);
  42. client = new Client({ name: "packed-artifact-verifier", version: "1.0.0" });
  43. const transport = new StdioClientTransport({
  44. command: executable,
  45. env: {
  46. ...process.env,
  47. CODEX_APP_SERVER_URL: process.env.CODEX_APP_SERVER_URL ?? "ws://127.0.0.1:4500",
  48. },
  49. });
  50. await client.connect(transport);
  51. const instructions = client.getInstructions();
  52. assert.match(instructions ?? "", /call overview first/i);
  53. const listed = await client.listTools();
  54. assert.deepEqual(listed.tools.map(({ name }) => name), EXPECTED_TOOLS);
  55. process.stdout.write(`${JSON.stringify({
  56. ok: true,
  57. package: basename(tarball),
  58. executable: "node_modules/.bin/codex-chief-of-staff",
  59. instructions: "verified",
  60. tools: EXPECTED_TOOLS,
  61. })}\n`);
  62. } finally {
  63. if (client !== undefined) await client.close().catch(() => undefined);
  64. await rm(prefix, { recursive: true, force: true });
  65. }
  66. }
  67. verify().catch((error) => {
  68. const message = error instanceof Error ? error.message : String(error);
  69. process.stderr.write(`Packed-package verification failed: ${message}\n`);
  70. process.exitCode = 1;
  71. });