superpowers-manager 0.3.0 → 0.4.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/README.md CHANGED
@@ -16,16 +16,21 @@ npx superpowers-manager probe
16
16
  npx superpowers-manager update
17
17
  ```
18
18
 
19
- Use the official marketplace for the simplest native Codex installation. Use
20
- Superpowers Manager when you want immediate stable-upstream freshness after a
21
- user-triggered install/update, per-invocation release or commit selection,
22
- recorded upstream provenance, diagnostics, upstream-owned Codex hook handling,
23
- and explicit install/update/probe/uninstall lifecycle control.
19
+ Use Superpowers Manager to follow the latest stable Superpowers release directly
20
+ from upstream and apply updates with one command. By default, `install` and
21
+ `update` resolve the highest stable `vX.Y.Z` tag when invoked; saved pins and
22
+ invocation overrides take precedence. `probe` reports whether your installation
23
+ matches that selection without changing Codex state. Updates are user-triggered.
24
24
 
25
- | Choose | Best fit |
26
- |---|---|
27
- | Official marketplace | Simplest Codex-native installation and marketplace-managed cadence |
28
- | `superpowers-manager` | Direct stable-upstream freshness when invoked, exact ref selection, provenance, diagnostics, and lifecycle control |
25
+ | Choose | Version source | Best fit |
26
+ |---|---|---|
27
+ | `superpowers-manager` | Latest stable upstream tag by default, or your saved/overridden selection | Follow upstream stable with drift reporting and verified updates |
28
+ | Direct upstream repository | The configured Git ref, or the repository's default branch | Use Codex's native Git marketplace support |
29
+ | `superpowers@openai-curated` | The copy distributed in the curated marketplace | Use the curated integration and its refresh policy |
30
+
31
+ See [Comparing installation routes](#comparing-installation-routes) for commands
32
+ and update behavior. Use one provider at a time; see
33
+ [Provider ownership](#provider-ownership) before switching.
29
34
 
30
35
  ### Moving from `superpowers-wrapper`
31
36
 
@@ -39,10 +44,11 @@ It never removes the legacy provider automatically.
39
44
 
40
45
  ## Requirements and platforms
41
46
 
42
- Superpowers Manager requires Node 24+, and Codex is the only supported
47
+ The installed Superpowers Manager package requires Node >=24. Native source,
48
+ tests, and packaging tooling require Node >=24.12.0. Codex is the only supported
43
49
  integration today. Every other requirement is per command, enforced by
44
50
  preflight before any dispatch. This table is checked against production by
45
- `tests/bin/readme-requirements.test.js`; edit it when a command's requirements
51
+ `tests/bin/readme-requirements.test.ts`; edit it when a command's requirements
46
52
  change, in the same pull request.
47
53
 
48
54
  <!-- requirements:begin -->
@@ -73,6 +79,78 @@ of a saved exact pin can reuse its recorded identity without upstream access.
73
79
  Updates remain user-triggered; the manager does not run automatic or background
74
80
  updates.
75
81
 
82
+ ## Comparing installation routes
83
+
84
+ ### Getting the latest stable release
85
+
86
+ The manager selects the highest stable upstream `vX.Y.Z` tag when its effective
87
+ policy is `latest-release`. It does not require the curated marketplace to
88
+ publish that release first. Saved pins and environment overrides can select a
89
+ different version or source; see [Choosing the upstream version](#choosing-the-upstream-version).
90
+
91
+ The direct repository route uses the Git ref you configure. Adding the repository
92
+ without `--ref` uses its default branch, which is not a latest-stable release
93
+ policy. The curated route uses the
94
+ [copy in `openai/plugins`](https://github.com/openai/plugins/tree/main/plugins/superpowers).
95
+ Refreshing that snapshot does not independently resolve the latest stable tag
96
+ from `obra/superpowers`; curated may match upstream or lag behind it.
97
+
98
+ The native CLI examples below were checked against Codex `0.153.3`. Upstream's
99
+ [marketplace manifest](https://github.com/obra/superpowers/blob/main/.agents/plugins/marketplace.json)
100
+ names its marketplace `superpowers-dev`.
101
+
102
+ ### First install
103
+
104
+ These are alternative routes; do not run both to install duplicate providers.
105
+
106
+ | Step | Superpowers Manager | Direct upstream repository |
107
+ |---|---|---|
108
+ | 1 | `npx superpowers-manager install` | `codex plugin marketplace add https://github.com/obra/superpowers` |
109
+ | 2 | — | `codex plugin add superpowers@superpowers-dev` |
110
+
111
+ The manager follows upstream stable by default. For a specific release on the
112
+ direct route, choose its tag and add `--ref TAG` to the marketplace-add command.
113
+
114
+ ### Staying current
115
+
116
+ | Action | Superpowers Manager | Direct upstream repository |
117
+ |---|---|---|
118
+ | Explicit update | `npx superpowers-manager update` | `codex plugin marketplace upgrade superpowers-dev` |
119
+ | Optional inspection | `npx superpowers-manager probe` | `codex plugin list` |
120
+
121
+ The manager probes first, skips prepare/install when current and update control
122
+ is valid, and verifies the resulting installed state after a refresh. `probe`
123
+ reports requested and resolved refs, desired/generated/installed commits, and
124
+ status. With a saved pin, "current" means matching that pin, not necessarily
125
+ matching the latest upstream release. See [Lifecycle commands](#lifecycle-commands).
126
+
127
+ Native marketplace upgrade follows its configured ref. In the checked Codex
128
+ version, it can skip unchanged snapshots and refreshes installed plugins when
129
+ the marketplace changes; a second `plugin add` is not required for that refresh.
130
+ Codex also has an automatic refresh path for configured Git marketplaces.
131
+ See the [native upgrade implementation](https://github.com/openai/codex/blob/rust-v0.153.3/codex-rs/core-plugins/src/manager.rs).
132
+ `plugin list` shows installed versions; comparing them with upstream stable is
133
+ separate inspection work, not a prerequisite for running an update.
134
+
135
+ ### Changing the selected version
136
+
137
+ Replace `TAG` below with the exact release tag you want. For an already registered
138
+ direct marketplace, remove/add is a CLI route to changing its ref:
139
+
140
+ | Step | Superpowers Manager | Direct upstream repository |
141
+ |---|---|---|
142
+ | 1 | `npx superpowers-manager pin TAG` | `codex plugin marketplace remove superpowers-dev` |
143
+ | 2 | `npx superpowers-manager update` | `codex plugin marketplace add https://github.com/obra/superpowers --ref TAG` |
144
+ | 3 | — | `codex plugin add superpowers@superpowers-dev` |
145
+
146
+ The manager's `pin` saves verified intent without changing Codex state; `update`
147
+ applies it. To resume following stable releases, run
148
+ `npx superpowers-manager track-latest`, then `npx superpowers-manager update`.
149
+ The native sequence changes marketplace configuration immediately. In the checked
150
+ CLI, `marketplace upgrade` has no `--ref` option, and adding the same marketplace
151
+ name with a different ref is rejected. This sequence does not imply that editing
152
+ native configuration is impossible.
153
+
76
154
  ## What it does
77
155
 
78
156
  - Resolves an upstream ref, defaulting to the latest stable `vX.Y.Z` release
@@ -111,6 +189,8 @@ eliminates latent hook-policy drift.
111
189
  - The in-process TypeScript runtime under `src/` owns lifecycle dispatch,
112
190
  status, and adapter-result handling;
113
191
  see the [in-process adapter result contract](https://github.com/j7an/superpowers-manager/blob/main/docs/adapter-result-contract.md).
192
+ - Developers extending that runtime should start with the
193
+ [internal harness interface](https://github.com/j7an/superpowers-manager/blob/main/docs/harness-interface.md).
114
194
  - `src/adapter.ts` owns build, inspection, reconciliation, and Codex mutation.
115
195
  - Codex is the only supported adapter today; no public harness selector ships
116
196
  yet.
@@ -306,46 +386,175 @@ updates the official provider or any other provider automatically.
306
386
  SUPERPOWERS_INSTALL_REFRESH_MODE=remove-add npx superpowers-manager update
307
387
  ```
308
388
 
389
+ ## pnpm updates
390
+
391
+ A weekly Monday workflow (06:00 UTC), also available through manual dispatch,
392
+ opens or refreshes a dedicated pull request updating only `package.json`'s
393
+ `packageManager` pin. It selects a newer, non-deprecated stable pnpm release
394
+ within the pinned major after five days. If the current pin is deprecated,
395
+ the shared updater bypasses that waiting period and discloses it in the PR.
396
+ Major upgrades remain a manual decision; the lockfile is not updated by this
397
+ workflow.
398
+
399
+ The caller forwards only `RELEASE_BOT_PRIVATE_KEY` and uses
400
+ `vars.RELEASE_BOT_APP_ID` for App-authored PRs that can trigger CI. Keep both
401
+ configured. After adopting the caller, verify a manual run's App authentication
402
+ and inspect any resulting PR before treating live integration as validated.
403
+
309
404
  ## Tests
310
405
 
406
+ Install once, run the static gate before submission, and choose the package
407
+ script that matches the scope you are iterating on:
408
+
409
+ ```sh
410
+ pnpm install --frozen-lockfile
411
+ pnpm run check:static
412
+ pnpm test
413
+ pnpm run test:unit
414
+ pnpm run test:integration
415
+ pnpm run test:harness:codex
416
+ pnpm run test:acceptance
417
+ ```
418
+
419
+ The package scripts above are alternative iteration selectors; do not run every
420
+ row sequentially as a substitute for acceptance. `pnpm test` runs all shared
421
+ suites once, while the unit and integration scripts select one shared group.
422
+ `test:harness:codex` runs only the isolated Docker Codex probe. Complete
423
+ acceptance is `pnpm run check:static` followed by `pnpm run test:acceptance`
424
+ with explicit evidence for the package's minimum Node runtime.
425
+
426
+ To compare shared-suite scheduling while iterating, use:
427
+
428
+ ```sh
429
+ sh tests/run.sh --concurrency 1
430
+ sh tests/run.sh --concurrency 2
431
+ ```
432
+
433
+ Every normally completed shared run emits both `run-node-suites: complete
434
+ status=<status>` and `tests/run.sh: complete failed=<count>`. Either sentinel
435
+ may report failure; if either is absent, the run was interrupted and is
436
+ incomplete.
437
+
438
+ Use a Homebrew-managed local pnpm by command name; do not use Corepack.
439
+ `check:static` checks formatting, lints, and typechecks production and tests without
440
+ emitting JavaScript. Run the maintained CLI directly with `node src/cli.ts`.
441
+ Tests and their subprocesses execute TypeScript against `src/` with no checkout
442
+ build. Future production coverage covers `src/`, including `src/cli.ts`; emitted
443
+ distribution files, tests, and packaging tools are outside that source scope.
444
+ No coverage collection is enabled by this migration.
445
+
446
+ On macOS, reuse an existing exact package-minimum Node executable when one is
447
+ already available:
448
+
449
+ ```sh
450
+ export SPW_PACKAGE_NODE=/absolute/path/to/node-v24.0.0
451
+ export SPW_PACKAGE_NODE_VERSION=24.0.0
452
+ test "$("$SPW_PACKAGE_NODE" -p 'process.versions.node')" = "$SPW_PACKAGE_NODE_VERSION"
453
+ ```
454
+
455
+ Otherwise, run this prerequisite setup from the worktree while a supported
456
+ native Node remains on `PATH`. It downloads an official Node archive into a
457
+ new temporary directory and verifies the selected checksum before extraction:
458
+
459
+ ```sh
460
+ spw_runtime_dir=$(mktemp -d)
461
+ SPW_PACKAGE_NODE_VERSION=$(node -p 'const e=require("./package.json").engines.node; const m=/^>=(\d+)$/.exec(e); if(!m) throw Error("unsupported engines.node"); `${m[1]}.0.0`')
462
+ case "$(uname -m)" in
463
+ arm64) spw_node_arch=arm64 ;;
464
+ x86_64) spw_node_arch=x64 ;;
465
+ *) echo "unsupported macOS architecture" >&2; exit 1 ;;
466
+ esac
467
+ spw_node_archive="node-v${SPW_PACKAGE_NODE_VERSION}-darwin-${spw_node_arch}.tar.gz"
468
+ (
469
+ set -eu
470
+ cd "$spw_runtime_dir"
471
+ curl --fail --location --remote-name "https://nodejs.org/dist/v${SPW_PACKAGE_NODE_VERSION}/${spw_node_archive}"
472
+ curl --fail --location --remote-name "https://nodejs.org/dist/v${SPW_PACKAGE_NODE_VERSION}/SHASUMS256.txt"
473
+ rg -F " ${spw_node_archive}" SHASUMS256.txt > selected-sha256.txt
474
+ test "$(wc -l < selected-sha256.txt)" -eq 1
475
+ read -r spw_node_sha spw_selected_archive < selected-sha256.txt
476
+ test "$spw_selected_archive" = "$spw_node_archive"
477
+ shasum -a 256 -c selected-sha256.txt
478
+ tar -xzf "$spw_node_archive"
479
+ ) || exit 1
480
+ SPW_PACKAGE_NODE="$spw_runtime_dir/node-v${SPW_PACKAGE_NODE_VERSION}-darwin-${spw_node_arch}/bin/node"
481
+ export SPW_PACKAGE_NODE SPW_PACKAGE_NODE_VERSION
482
+ ```
483
+
484
+ The native runtime on `PATH` remains unchanged. With the package runtime
485
+ variables exported, complete acceptance is:
486
+
311
487
  ```sh
312
488
  pnpm install --frozen-lockfile
313
- pnpm run build
314
- sh tests/run.sh # Layers 1-3: host-side hermetic checks while iterating
315
- sh tests/container.sh # Layers 1-4: blocking Docker acceptance command
316
- sh tests/manual/codex-behavior-probe.sh # optional native-only compatibility residue
489
+ pnpm run check:static
490
+ pnpm run test:acceptance
491
+ ```
492
+
493
+ After the checks, remove only the temporary directory created by this
494
+ invocation and clear its variables:
495
+
496
+ ```sh
497
+ rm -rf -- "$spw_runtime_dir"
498
+ unset SPW_PACKAGE_NODE SPW_PACKAGE_NODE_VERSION spw_node_arch spw_node_archive spw_runtime_dir
499
+ ```
500
+
501
+ Linux CI provisions the exact package runtime with `actions/setup-node`.
502
+ Downloads happen only during prerequisite setup, never inside the hermetic test
503
+ cases.
504
+
505
+ Packaging compiles only production source into fresh external temporary staging:
506
+
507
+ ```sh
508
+ node tests/tools/pack.ts --out-dir /absolute/existing/temporary/output
317
509
  ```
318
510
 
319
- `pnpm run build` deletes `dist/` before compiling, so every build is a clean
320
- rebuild and stale artifacts of deleted or renamed modules cannot survive.
511
+ Allocate the existing output directory outside the checkout before running this
512
+ command. It returns npm-compatible JSON for one validated tarball and removes its
513
+ own staging. Bare checkout `npm pack` always fails with this command's guidance,
514
+ even if stale `dist/` exists. Maintained or manager-generated JavaScript must not
515
+ remain in the checkout; dependencies and generated upstream plugin content have
516
+ separate ownership. This developer command does not authorize publication; follow
517
+ `RELEASING.md` for the protected release procedure.
321
518
 
322
519
  Layers 1-3 stay offline and hermetic: they use a fake local upstream repo plus
323
520
  host-side fixtures, and they perform no mutation of the developer's or runner's
324
521
  real Codex state.
325
522
 
326
- Layer 4 is the Docker acceptance path. It is the required completion command
327
- because the isolated-container Codex probe graduated from a temporary
328
- nonblocking spike to a blocking acceptance gate. `sh tests/container.sh` runs
329
- the inner `sh tests/run.sh` suite and then the real Codex offline probe inside
330
- an isolated container home with networking disabled. That container run may
331
- mutate the throwaway container-local Codex state, but it still performs no
332
- mutation of the developer's or runner's real Codex state.
523
+ Layer 4 is the Docker acceptance path. `pnpm run test:harness:codex` runs the
524
+ real Codex offline probe inside an isolated container home with networking
525
+ disabled. `pnpm run test:acceptance` composes one host-side shared run with that
526
+ probe. The container may mutate its throwaway container-local Codex state, but
527
+ it performs no mutation of the developer's or runner's real Codex state.
528
+
529
+ Release validation deliberately retains the combined shared-plus-Codex path at
530
+ both native endpoints:
531
+
532
+ ```sh
533
+ SPW_NATIVE_NODE_VERSION=24.12.0 sh tests/container.sh
534
+ SPW_NATIVE_NODE_VERSION=24 sh tests/container.sh
535
+ ```
333
536
 
334
537
  The container probe uses Codex's `hooks/list` only as compatibility evidence
335
538
  for the tested Codex build. It is not a stable Superpowers Manager API.
336
539
 
337
- The manual probe is opt-in and covers native-only compatibility residue such as
540
+ `sh tests/manual/codex-behavior-probe.sh` remains opt-in and covers native-only compatibility residue such as
338
541
  path/cache and version-precedence behavior against an intentionally real local
339
- Codex install. It is not part of acceptance. GitHub Actions runs both the
340
- toolchain check and the blocking container acceptance command on pull requests
341
- and pushes to `main`.
542
+ Codex install. It is not part of acceptance. GitHub Actions runs two focused
543
+ `toolchain` entries and one blocking Codex integration job on pull requests
544
+ and pushes to `main`. The Node 24.12.0 toolchain entry checks native source
545
+ loading, the suite runner/assertion preload, and package-producer success and
546
+ failure. The latest-24 toolchain entry alone runs `pnpm run check:static` and
547
+ the full shared suite with package-minimum evidence. The independent `test` job
548
+ runs only the Codex offline probe on latest Node 24.x. Release acceptance still
549
+ runs the combined shared and Codex paths at both native endpoints, and the
550
+ installed npm executable is tested with Node 24.0.0. These runtime selectors
551
+ are separate from the Homebrew-managed local commands above.
342
552
 
343
553
  ## Repository layout
344
554
 
345
555
  ```
346
556
  .agents/plugins/marketplace.json # local marketplace definition (tracked)
347
557
  .github/ # CI, security, dependency, and release workflows (tracked)
348
- bin/superpowers-manager.js # public thin loader for dist/cli.js
349
558
  config/upstream-ref # packaged upstream fallback policy (tracked)
350
559
  docs/ # adapter result contract and baseline evidence (tracked)
351
560
  package.json pnpm-lock.yaml tsconfig.json # package contract and locked toolchain (tracked)
@@ -353,12 +562,12 @@ plugins/superpowers/
353
562
  .codex-plugin/plugin.template.json # fallback manifest template (tracked)
354
563
  generated plugin tree # prepared from upstream (gitignored)
355
564
  src/ # in-process TypeScript runtime (tracked)
356
- cli.ts # CLI parsing, preflight, and command dispatch
565
+ cli.ts # sole maintained CLI entry, parsing, preflight, dispatch
357
566
  commands/ # lifecycle and selection command handlers
358
567
  adapter.ts # Codex integration
359
568
  tests/ # hermetic suite and manual Codex probe (tracked)
360
569
  migration-inventory/ # frozen historical evidence
361
- dist/ # generated ESM build tree (gitignored)
570
+ tools/pack.ts # external staging and validated package emission
362
571
  ```
363
572
 
364
573
  Everything under `plugins/superpowers/` except the fallback manifest template is
package/dist/adapter.js CHANGED
@@ -197,11 +197,8 @@ async function fileExists(path) {
197
197
  return false;
198
198
  }
199
199
  }
200
- async function runBuild(argv, root, env, log) {
201
- const flags = parseFlags(argv, BUILD_FLAGS);
202
- const upstreamRoot = flags["--upstream-root"];
203
- const candidateRoot = flags["--candidate-root"];
204
- const fallbackManifest = flags["--fallback-manifest"];
200
+ async function runBuild(input, env, log) {
201
+ const { upstreamRoot, candidateRoot, fallbackManifest } = input;
205
202
  if (!isAbsolute(upstreamRoot)) {
206
203
  fail("invalid-arguments", "--upstream-root must be an absolute path");
207
204
  }
@@ -276,7 +273,7 @@ async function runBuild(argv, root, env, log) {
276
273
  }
277
274
  let overlaid;
278
275
  try {
279
- overlaid = applyManifestOverlay(source, flags["--manager-version"], candidateManifest);
276
+ overlaid = applyManifestOverlay(source, input.managerVersion, candidateManifest);
280
277
  }
281
278
  catch (cause) {
282
279
  // applyManifestOverlay's own messages already name the manifest
@@ -321,13 +318,13 @@ async function runBuild(argv, root, env, log) {
321
318
  // on the adapter's surface.
322
319
  const splitValues = [
323
320
  { value: candidateRoot, name: "--plugin-root" },
324
- { value: flags["--requested-ref"], name: "--requested-ref" },
325
- { value: flags["--resolved-ref"], name: "--resolved-ref" },
326
- { value: flags["--commit"], name: "--commit" },
327
- { value: flags["--manager-version"], name: "--manager-version" },
321
+ { value: input.requestedRef, name: "--requested-ref" },
322
+ { value: input.resolvedRef, name: "--resolved-ref" },
323
+ { value: input.commit, name: "--commit" },
324
+ { value: input.managerVersion, name: "--manager-version" },
328
325
  { value: manifestSource, name: "--manifest-source" },
329
326
  {
330
- value: flags["--upstream-manifest-version"],
327
+ value: input.upstreamManifestVersion,
331
328
  name: "--upstream-manifest-version",
332
329
  },
333
330
  ];
@@ -347,12 +344,12 @@ async function runBuild(argv, root, env, log) {
347
344
  errors = await validateGeneratedPlugin({
348
345
  pluginRoot: candidateRoot,
349
346
  source: upstreamSource,
350
- requestedRef: flags["--requested-ref"],
351
- resolvedRef: flags["--resolved-ref"],
352
- commit: flags["--commit"],
353
- manifestVersion: flags["--manager-version"],
347
+ requestedRef: input.requestedRef,
348
+ resolvedRef: input.resolvedRef,
349
+ commit: input.commit,
350
+ manifestVersion: input.managerVersion,
354
351
  manifestSource,
355
- upstreamManifestVersion: flags["--upstream-manifest-version"],
352
+ upstreamManifestVersion: input.upstreamManifestVersion,
356
353
  });
357
354
  }
358
355
  catch {
@@ -377,9 +374,7 @@ async function runBuild(argv, root, env, log) {
377
374
  throw cause;
378
375
  }
379
376
  }
380
- async function runInstall(argv, env, log) {
381
- const flags = parseFlags(argv, ["--package-root"]);
382
- const packageRoot = flags["--package-root"];
377
+ async function runInstall(packageRoot, env, log) {
383
378
  if (!isAbsolute(packageRoot)) {
384
379
  fail("invalid-arguments", "--package-root must be an absolute path");
385
380
  }
@@ -453,23 +448,15 @@ async function runInstall(argv, env, log) {
453
448
  throw cause;
454
449
  }
455
450
  }
456
- async function runUninstall(argv, env, log) {
457
- const flags = parseFlags(argv, ["--plugin-present", "--marketplace-present"]);
458
- const pluginPresent = flags["--plugin-present"];
459
- const marketplacePresent = flags["--marketplace-present"];
460
- if (pluginPresent !== "true" && pluginPresent !== "false") {
461
- fail("invalid-arguments", "--plugin-present must be true or false");
462
- }
463
- if (marketplacePresent !== "true" && marketplacePresent !== "false") {
464
- fail("invalid-arguments", "--marketplace-present must be true or false");
465
- }
451
+ async function runUninstall(input, env, log) {
452
+ const { pluginPresent, marketplacePresent } = input;
466
453
  const codexBin = env.SUPERPOWERS_CODEX || "codex";
467
454
  await requireCodex(codexBin, env);
468
455
  let entered = false;
469
456
  try {
470
457
  return await withWorkspace(tmpdir(), "superpowers-manager.adapter-uninstall.", async () => {
471
458
  entered = true;
472
- if (pluginPresent === "true") {
459
+ if (pluginPresent) {
473
460
  const result = await mutationCommand(log, codexBin, ["plugin", "remove", PLUGIN_ID], env);
474
461
  if (commandFailed(result)) {
475
462
  fail("uninstall-failed", `codex plugin remove failed for ${PLUGIN_ID}`);
@@ -479,7 +466,7 @@ async function runUninstall(argv, env, log) {
479
466
  else {
480
467
  log.appendText("stdout", "plugin not installed; skipping");
481
468
  }
482
- if (marketplacePresent === "true") {
469
+ if (marketplacePresent) {
483
470
  const result = await mutationCommand(log, codexBin, ["plugin", "marketplace", "remove", MARKETPLACE_NAME], env);
484
471
  if (commandFailed(result)) {
485
472
  fail("uninstall-failed", `codex plugin marketplace remove failed for ${MARKETPLACE_NAME}`);
@@ -499,9 +486,7 @@ async function runUninstall(argv, env, log) {
499
486
  throw cause;
500
487
  }
501
488
  }
502
- async function runInspect(argv, env, log) {
503
- const flags = parseFlags(argv, ["--view"]);
504
- const view = flags["--view"];
489
+ async function runInspect(view, env, log) {
505
490
  if (view === "update-control") {
506
491
  return { view: "update-control", update_control: "managed" };
507
492
  }
@@ -610,39 +595,95 @@ async function runInspect(argv, env, log) {
610
595
  throw cause;
611
596
  }
612
597
  }
613
- fail("invalid-arguments", `unsupported inspect view: ${view}`);
598
+ const unsupportedView = view;
599
+ fail("invalid-arguments", `unsupported inspect view: ${unsupportedView}`);
600
+ }
601
+ async function runCodexOperation(operation, context, execute) {
602
+ const env = { ...process.env, ...context.env };
603
+ const log = new AdapterMessageLog();
604
+ try {
605
+ const result = await execute(env, log);
606
+ return successResult(operation, result, log.snapshot());
607
+ }
608
+ catch (cause) {
609
+ if (cause instanceof AdapterFailure) {
610
+ return failureResult(operation, cause.code, cause.message, cause.hints, log.snapshot());
611
+ }
612
+ throw cause;
613
+ }
614
+ }
615
+ export function codexBuild(input, context) {
616
+ return runCodexOperation("build", context, (env, log) => runBuild(input, env, log));
617
+ }
618
+ export function codexInstall(packageRoot, context) {
619
+ return runCodexOperation("install", context, (env, log) => runInstall(packageRoot, env, log));
620
+ }
621
+ export function codexRemove(input, context) {
622
+ return runCodexOperation("uninstall", context, (env, log) => runUninstall(input, env, log));
623
+ }
624
+ export function codexInspect(view, context) {
625
+ return runCodexOperation("inspect", context, (env, log) => runInspect(view, env, log));
614
626
  }
615
627
  export async function runAdapter(argv, context) {
616
628
  const rawOperation = argv[0];
617
629
  const operation = rawOperation || "adapter";
618
630
  const args = argv.slice(1);
619
- const env = { ...process.env, ...context.env };
620
- const log = new AdapterMessageLog();
621
631
  try {
622
- let result;
623
632
  if (rawOperation === undefined || rawOperation.length === 0) {
624
633
  fail("invalid-arguments", "missing adapter operation");
625
634
  }
626
635
  else if (operation === "build") {
627
- result = await runBuild(args, context.root, env, log);
636
+ const flags = parseFlags(args, BUILD_FLAGS);
637
+ return codexBuild({
638
+ upstreamRoot: flags["--upstream-root"],
639
+ candidateRoot: flags["--candidate-root"],
640
+ requestedRef: flags["--requested-ref"],
641
+ resolvedRef: flags["--resolved-ref"],
642
+ commit: flags["--commit"],
643
+ managerVersion: flags["--manager-version"],
644
+ upstreamManifestVersion: flags["--upstream-manifest-version"],
645
+ fallbackManifest: flags["--fallback-manifest"],
646
+ }, context);
628
647
  }
629
648
  else if (operation === "install") {
630
- result = await runInstall(args, env, log);
649
+ const flags = parseFlags(args, ["--package-root"]);
650
+ return codexInstall(flags["--package-root"], context);
631
651
  }
632
652
  else if (operation === "uninstall") {
633
- result = await runUninstall(args, env, log);
653
+ const flags = parseFlags(args, [
654
+ "--plugin-present",
655
+ "--marketplace-present",
656
+ ]);
657
+ const pluginPresent = flags["--plugin-present"];
658
+ const marketplacePresent = flags["--marketplace-present"];
659
+ if (pluginPresent !== "true" && pluginPresent !== "false") {
660
+ fail("invalid-arguments", "--plugin-present must be true or false");
661
+ }
662
+ if (marketplacePresent !== "true" && marketplacePresent !== "false") {
663
+ fail("invalid-arguments", "--marketplace-present must be true or false");
664
+ }
665
+ return codexRemove({
666
+ pluginPresent: pluginPresent === "true",
667
+ marketplacePresent: marketplacePresent === "true",
668
+ }, context);
634
669
  }
635
670
  else if (operation === "inspect") {
636
- result = await runInspect(args, env, log);
671
+ const flags = parseFlags(args, ["--view"]);
672
+ const view = flags["--view"];
673
+ if (view !== "ownership" &&
674
+ view !== "update-control" &&
675
+ view !== "fingerprint") {
676
+ fail("invalid-arguments", `unsupported inspect view: ${view}`);
677
+ }
678
+ return codexInspect(view, context);
637
679
  }
638
680
  else {
639
681
  fail("unsupported-operation", `unsupported adapter operation: ${operation}`);
640
682
  }
641
- return successResult(operation, result, log.snapshot());
642
683
  }
643
684
  catch (cause) {
644
685
  if (cause instanceof AdapterFailure) {
645
- return failureResult(operation, cause.code, cause.message, cause.hints, log.snapshot());
686
+ return failureResult(operation, cause.code, cause.message, cause.hints, []);
646
687
  }
647
688
  throw cause;
648
689
  }