superpowers-manager 0.5.0 → 0.5.2
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 +39 -9
- package/dist/artifact-tree.js +2 -1
- package/dist/cli.js +3 -3
- package/dist/commands/install.js +18 -8
- package/dist/commands/prepare.js +5 -5
- package/dist/commands/probe.js +4 -4
- package/dist/commands/uninstall.js +3 -3
- package/dist/commands/update.js +1 -1
- package/dist/domain/refs.js +4 -0
- package/dist/{adapter.js → harnesses/codex/adapter.js} +156 -27
- package/dist/{codex-compatibility.js → harnesses/codex/compatibility.js} +7 -7
- package/dist/{codex-conflicts.js → harnesses/codex/conflicts.js} +1 -1
- package/dist/{generated-plugin.js → harnesses/codex/generated-plugin.js} +8 -74
- package/dist/{codex-harness.js → harnesses/codex/harness.js} +39 -21
- package/dist/{hooks.js → harnesses/codex/hooks.js} +7 -7
- package/dist/{codex-json.js → harnesses/codex/json.js} +8 -4
- package/dist/{lifecycle.js → harnesses/codex/lifecycle.js} +2 -2
- package/dist/{manifest-overlay.js → harnesses/codex/manifest-overlay.js} +4 -4
- package/dist/harnesses/codex/marketplace.js +148 -0
- package/dist/harnesses/codex/paths.js +97 -0
- package/dist/{codex-prepare.js → harnesses/codex/prepare.js} +41 -9
- package/dist/{codex-presentation.js → harnesses/codex/presentation.js} +7 -6
- package/dist/harnesses/codex/publication.js +538 -0
- package/dist/harnesses/codex/recovery.js +338 -0
- package/dist/harnesses/codex/state.js +209 -0
- package/dist/harnesses/codex/stored-state.js +249 -0
- package/dist/{pi-compatibility.js → harnesses/pi/compatibility.js} +3 -3
- package/dist/{pi-harness.js → harnesses/pi/harness.js} +7 -7
- package/dist/{pi-install.js → harnesses/pi/install.js} +8 -8
- package/dist/{pi-native.js → harnesses/pi/native.js} +4 -4
- package/dist/{pi-package.js → harnesses/pi/package.js} +6 -6
- package/dist/{pi-prepare.js → harnesses/pi/prepare.js} +6 -6
- package/dist/{pi-settings.js → harnesses/pi/settings.js} +3 -3
- package/dist/{pi-shared-skills.js → harnesses/pi/shared-skills.js} +1 -1
- package/dist/{pi-state.js → harnesses/pi/state.js} +8 -8
- package/dist/provenance.js +3 -3
- package/dist/python-text.js +16 -0
- package/dist/skill-validation.js +52 -0
- package/dist/validate-generated-plugin-cli.js +1 -1
- package/node_modules/smol-toml/LICENSE +24 -0
- package/node_modules/smol-toml/README.md +418 -0
- package/node_modules/smol-toml/dist/date.d.ts +41 -0
- package/node_modules/smol-toml/dist/date.js +127 -0
- package/node_modules/smol-toml/dist/error.d.ts +38 -0
- package/node_modules/smol-toml/dist/error.js +63 -0
- package/node_modules/smol-toml/dist/extract.js +69 -0
- package/node_modules/smol-toml/dist/index.cjs +734 -0
- package/node_modules/smol-toml/dist/index.d.ts +43 -0
- package/node_modules/smol-toml/dist/index.js +33 -0
- package/node_modules/smol-toml/dist/parse.d.ts +36 -0
- package/node_modules/smol-toml/dist/parse.js +149 -0
- package/node_modules/smol-toml/dist/primitive.js +238 -0
- package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
- package/node_modules/smol-toml/dist/stringify.js +181 -0
- package/node_modules/smol-toml/dist/struct.js +179 -0
- package/node_modules/smol-toml/dist/util.d.ts +38 -0
- package/node_modules/smol-toml/dist/util.js +89 -0
- package/node_modules/smol-toml/package.json +68 -0
- package/package.json +13 -4
- package/dist/codex-state.js +0 -78
- /package/dist/{pi-paths.js → harnesses/pi/paths.js} +0 -0
- /package/dist/{pi-presentation.js → harnesses/pi/presentation.js} +0 -0
package/README.md
CHANGED
|
@@ -1,9 +1,45 @@
|
|
|
1
1
|
# Superpowers Manager
|
|
2
2
|
|
|
3
|
-
Install
|
|
3
|
+
Install [`obra/superpowers`](https://github.com/obra/superpowers) directly from upstream for Codex and the Pi coding agent. Stay on a known-good release or commit, try a branch, and upgrade when you choose—without waiting for a marketplace copy to catch up.
|
|
4
4
|
|
|
5
5
|
> Unofficial community integration. Not affiliated with the `obra/superpowers` maintainers.
|
|
6
6
|
|
|
7
|
+
## Choose what you run
|
|
8
|
+
|
|
9
|
+
Start with the latest stable release by default, or choose the upstream version that works for you. There is no background updater.
|
|
10
|
+
|
|
11
|
+
| What you want | How to choose it |
|
|
12
|
+
|---|---|
|
|
13
|
+
| Stay on a known-good version | `pin` a release tag or full 40-character commit SHA |
|
|
14
|
+
| Try an upstream fix before its release | Set `SUPERPOWERS_REF` to a branch or other resolvable ref for that invocation |
|
|
15
|
+
| Return to the latest stable release | Save `track-latest`, then explicitly install or update |
|
|
16
|
+
|
|
17
|
+
For example, save a release pin and install it for your agent:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
npx superpowers-manager pin v6.1.1
|
|
21
|
+
npx superpowers-manager install --harness codex
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Use `--harness pi` for Pi. The pin is shared by both agents, but each installation changes only when you run its `install` or `update` command. A saved pin keeps subsequent installs and updates on that version unless you change or override the selection.
|
|
25
|
+
|
|
26
|
+
To try a branch, replace `feature/foo` with an existing upstream branch:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
SUPERPOWERS_REF=feature/foo npx superpowers-manager install --harness codex
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
This override applies only to that command and leaves your saved selection unchanged; the installed version remains until you explicitly change it. Branches can move. Persistent `pin` accepts exact version tags (including prereleases) and full commit SHAs.
|
|
33
|
+
|
|
34
|
+
When you are ready to return to the latest stable release:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npx superpowers-manager track-latest
|
|
38
|
+
npx superpowers-manager update --harness codex
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
See [version selection](https://github.com/j7an/superpowers-manager/blob/main/docs/usage.md#choosing-a-source) for source overrides, precedence, and `unpin`.
|
|
42
|
+
|
|
7
43
|
## Start with your agent
|
|
8
44
|
|
|
9
45
|
The two harnesses share upstream selection, while each has independent prepared and installed state. Omitting `--harness` selects Codex.
|
|
@@ -20,7 +56,7 @@ Codex installs the Manager-owned marketplace plugin after preparation and
|
|
|
20
56
|
validation. Review upstream hook definitions with Codex's `/hooks` flow before
|
|
21
57
|
trusting or running them; updates can change a referenced script or surface a
|
|
22
58
|
new definition. The manager neither creates hooks nor changes Codex trust state.
|
|
23
|
-
See the [Codex reference](https://github.com/j7an/superpowers-manager/blob/main/docs/codex.md).
|
|
59
|
+
See the [Codex reference](https://github.com/j7an/superpowers-manager/blob/main/docs/codex.md#durable-storage-migration-and-recovery) for durable storage, migration, and recovery.
|
|
24
60
|
|
|
25
61
|
### Pi
|
|
26
62
|
|
|
@@ -53,7 +89,7 @@ The manager never removes another Superpowers provider. See the [ownership guida
|
|
|
53
89
|
|
|
54
90
|
## Shared selection and lifecycle
|
|
55
91
|
|
|
56
|
-
|
|
92
|
+
The [selection commands above](#choose-what-you-run) save shared upstream intent. They do not activate Codex or Pi; run the selected harness's `install` or `update` to apply that choice. `unpin` restores the packaged fallback policy.
|
|
57
93
|
|
|
58
94
|
| Command | Purpose |
|
|
59
95
|
|---|---|
|
|
@@ -65,12 +101,6 @@ Use `pin`, `track-latest`, or `unpin` to save shared upstream intent. They do no
|
|
|
65
101
|
|
|
66
102
|
Use `--harness codex` or `--harness pi` with each targeted lifecycle command.
|
|
67
103
|
|
|
68
|
-
```sh
|
|
69
|
-
npx superpowers-manager pin v6.1.1
|
|
70
|
-
npx superpowers-manager track-latest
|
|
71
|
-
npx superpowers-manager unpin
|
|
72
|
-
```
|
|
73
|
-
|
|
74
104
|
```text
|
|
75
105
|
shared selection -> prepare / inspect / activate Codex
|
|
76
106
|
-> prepare / inspect / activate Pi
|
package/dist/artifact-tree.js
CHANGED
|
@@ -4,7 +4,8 @@ import { join } from "node:path";
|
|
|
4
4
|
import { assertExistingContained, assertNoFollowType, assertSymlinkTargetContained, } from "./safe-path.js";
|
|
5
5
|
import { SafetyError } from "./safety-error.js";
|
|
6
6
|
import { parseStrictJson } from "./strict-json.js";
|
|
7
|
-
import { compareByCodePoint
|
|
7
|
+
import { compareByCodePoint } from "./python-text.js";
|
|
8
|
+
import { DEFAULT_FS_DEPS, validateSkillFrontmatter, } from "./skill-validation.js";
|
|
8
9
|
export const ARTIFACT_RECEIPT = ".superpowers-manager.json";
|
|
9
10
|
export const ARTIFACT_DIGEST_RE = /^[a-f0-9]{64}$/;
|
|
10
11
|
const DECODER = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { extractHarnessOptions, oneLine, UsageError } from "./cli-arguments.js";
|
|
5
|
-
import { codexHarness } from "./codex
|
|
6
|
-
import { piHarness } from "./pi
|
|
5
|
+
import { codexHarness } from "./harnesses/codex/harness.js";
|
|
6
|
+
import { piHarness } from "./harnesses/pi/harness.js";
|
|
7
7
|
import { createResourceCoordinator } from "./resource-lock.js";
|
|
8
8
|
import { runInstall } from "./commands/install.js";
|
|
9
9
|
import { runPin } from "./commands/pin.js";
|
|
@@ -341,7 +341,7 @@ async function dispatch(adapter, parsed) {
|
|
|
341
341
|
}
|
|
342
342
|
return status;
|
|
343
343
|
}
|
|
344
|
-
export { resolvePackageRoot, isMain, parseArgs,
|
|
344
|
+
export { resolvePackageRoot, isMain, parseArgs, commandRequirements, commandRequirementsFor, preflight, usage, main, };
|
|
345
345
|
if (isMain(import.meta.filename, process.argv[1])) {
|
|
346
346
|
main().catch((cause) => {
|
|
347
347
|
console.error(`error: ${oneLine(cause)}`);
|
package/dist/commands/install.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Ports scripts/install. The shell sourced common.sh, provenance.sh,
|
|
2
2
|
// status.sh, lifecycle.sh and adapter.sh; the predicates now live in
|
|
3
|
-
// src/lifecycle.ts, the generated-metadata read lives in src/provenance.ts,
|
|
3
|
+
// src/harnesses/codex/lifecycle.ts, the generated-metadata read lives in src/provenance.ts,
|
|
4
4
|
// and the adapter arrives through ctx.adapter.
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { oneLine } from "../cli-arguments.js";
|
|
@@ -163,7 +163,7 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
163
163
|
// is what turns a failed inspection into "error: installed manager
|
|
164
164
|
// fingerprint inspection failed after install."
|
|
165
165
|
// renderInstallVerification's failed-inspection arm
|
|
166
|
-
// (`src/codex
|
|
166
|
+
// (`src/harnesses/codex/presentation.ts:329::if (inspection.status !== 0 || !inspection.outcome.ok) {`)
|
|
167
167
|
// exists for this result-bearing path; the lifecycle compatibility
|
|
168
168
|
// export delegates through the same arm. Returning
|
|
169
169
|
// failed() instead reported the adapter's own generic diagnostic and
|
|
@@ -190,18 +190,28 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
190
190
|
const restoration = restored.ok
|
|
191
191
|
? `installed state after rollback: ${restored.result.outcome.result.kind}; identity=${restored.result.outcome.result.observedIdentity}`
|
|
192
192
|
: "installed state after rollback could not be inspected";
|
|
193
|
-
const
|
|
193
|
+
const verificationOutput = inspection === null
|
|
194
|
+
? null
|
|
195
|
+
: ctx.adapter.presentation.renderInstallVerification(selection.desiredCommit, install.result, inspection);
|
|
196
|
+
const inspectionMessage = verificationOutput === null && !inspected.ok
|
|
197
|
+
? inspected.message
|
|
198
|
+
: null;
|
|
199
|
+
const settlementMessage = !settlement.ok
|
|
194
200
|
? settlement.message
|
|
195
|
-
:
|
|
196
|
-
? inspected.message
|
|
197
|
-
: "installation could not be verified";
|
|
201
|
+
: null;
|
|
198
202
|
return {
|
|
199
203
|
kind: "verified",
|
|
200
204
|
outcomes,
|
|
201
205
|
status: 1,
|
|
202
|
-
stdout: [],
|
|
206
|
+
stdout: verificationOutput?.stdout ?? [],
|
|
203
207
|
stderr: [
|
|
204
|
-
...(
|
|
208
|
+
...(verificationOutput?.stderr ?? []),
|
|
209
|
+
...(inspectionMessage === null
|
|
210
|
+
? []
|
|
211
|
+
: [`error: ${inspectionMessage}`]),
|
|
212
|
+
...(settlementMessage === null
|
|
213
|
+
? []
|
|
214
|
+
: [`error: ${settlementMessage}`]),
|
|
205
215
|
restoration,
|
|
206
216
|
],
|
|
207
217
|
};
|
package/dist/commands/prepare.js
CHANGED
|
@@ -14,7 +14,7 @@ import { runWithMutation } from "./mutation.js";
|
|
|
14
14
|
// Every message this module writes is hand-written here. The cause is attached
|
|
15
15
|
// for debuggability and never reaches a stream: oneLine (src/cli-arguments.ts)
|
|
16
16
|
// reads .message only. Same arrangement as hookError
|
|
17
|
-
// (`src/hooks.ts:44::function hookError`).
|
|
17
|
+
// (`src/harnesses/codex/hooks.ts:44::function hookError`).
|
|
18
18
|
function prepareError(message, cause) {
|
|
19
19
|
return new SafetyError("prepare", message, { cause });
|
|
20
20
|
}
|
|
@@ -341,8 +341,8 @@ async function performPrepare(argv, ctx) {
|
|
|
341
341
|
// failure (its cause is the shared runner's (src/validator.ts) captured
|
|
342
342
|
// spawn error, which oneLine never reads -- it takes .message only);
|
|
343
343
|
// readManifest's three hookError messages
|
|
344
|
-
// (`src/hooks.ts:113-138::readManifest`), pinned by
|
|
345
|
-
// `tests/unit/hooks.test.ts:95::void test("readManifest diagnostics` as carrying no reader vocabulary or
|
|
344
|
+
// (`src/harnesses/codex/hooks.ts:113-138::readManifest`), pinned by
|
|
345
|
+
// `tests/unit/harnesses/codex/hooks.test.ts:95::void test("readManifest diagnostics` as carrying no reader vocabulary or
|
|
346
346
|
// errno; and SafetyErrors from gitSafeSource, writeProvenance, and
|
|
347
347
|
// withWorkspace.
|
|
348
348
|
//
|
|
@@ -382,7 +382,7 @@ async function performPrepare(argv, ctx) {
|
|
|
382
382
|
// only, so it bounds how much of that text lands, not what it may
|
|
383
383
|
// contain.
|
|
384
384
|
//
|
|
385
|
-
// runAdapter's closing `throw cause` (src/adapter.ts) does NOT arrive
|
|
385
|
+
// runAdapter's closing `throw cause` (src/harnesses/codex/adapter.ts) does NOT arrive
|
|
386
386
|
// here -- the call site catches it and converts it to a hand-written
|
|
387
387
|
// message per AGENTS.md's reader-diagnostics rule.
|
|
388
388
|
//
|
|
@@ -418,7 +418,7 @@ async function performPrepare(argv, ctx) {
|
|
|
418
418
|
// completed before cleanup ran, so it is not being reported as unverified
|
|
419
419
|
// -- but something did still go wrong, and AGENTS.md's fail-closed rule
|
|
420
420
|
// extends to it. Mirrors
|
|
421
|
-
// `src/commands/install.ts:
|
|
421
|
+
// `src/commands/install.ts:562-570::if (cleanupWarning`.
|
|
422
422
|
ctx.stderr.write(`error: ${cleanupWarning}\n`);
|
|
423
423
|
return 1;
|
|
424
424
|
}
|
package/dist/commands/probe.js
CHANGED
|
@@ -11,7 +11,7 @@ export const PROBE_USAGE = "error: usage: superpowers-manager probe [--porcelain
|
|
|
11
11
|
// silent diagnostics regression, not a simplification.
|
|
12
12
|
//
|
|
13
13
|
// Interpolating error.message and each hint is the sanctioned form (AGENTS.md):
|
|
14
|
-
// src/adapter.ts has 52 `fail()` sites and none interpolates a caught error's
|
|
14
|
+
// src/harnesses/codex/adapter.ts has 52 `fail()` sites and none interpolates a caught error's
|
|
15
15
|
// message, so the callee owns every failure reachable on this path. Those two
|
|
16
16
|
// writes now live in writeAdapterFailure (src/adapter-result.ts), which
|
|
17
17
|
// validates all three strings before the first of them reaches the stream.
|
|
@@ -121,7 +121,7 @@ function validControl(value) {
|
|
|
121
121
|
// evidence and report it as success.
|
|
122
122
|
//
|
|
123
123
|
// It does still THROW for a non-AdapterFailure cause (runAdapter's closing
|
|
124
|
-
// `throw cause`, src/adapter.ts). That is caught here rather than in runProbe's
|
|
124
|
+
// `throw cause`, src/harnesses/codex/adapter.ts). That is caught here rather than in runProbe's
|
|
125
125
|
// outer catch, because the two need different diagnostics -- see spec §3.3a.
|
|
126
126
|
//
|
|
127
127
|
// Reached through ctx.adapter, not a direct module-level dependency on the
|
|
@@ -377,9 +377,9 @@ export async function runProbe(argv, ctx) {
|
|
|
377
377
|
// which catches every failure and returns "".
|
|
378
378
|
//
|
|
379
379
|
// A non-AdapterFailure re-thrown by runAdapter's closing `throw cause`
|
|
380
|
-
// (src/adapter.ts) does NOT reach here: inspect() catches it and converts
|
|
380
|
+
// (src/harnesses/codex/adapter.ts) does NOT reach here: inspect() catches it and converts
|
|
381
381
|
// it to a hand-written message per AGENTS.md's reader-diagnostics rule -- a
|
|
382
|
-
// rethrown cause is exactly the failure src/adapter.ts declined to own, so
|
|
382
|
+
// rethrown cause is exactly the failure src/harnesses/codex/adapter.ts declined to own, so
|
|
383
383
|
// its text must never reach this stream. See §3.3a.
|
|
384
384
|
//
|
|
385
385
|
// gatherProbe performs no writes of its own, so this catch cannot also be
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Ports scripts/uninstall. The shell sourced common.sh, provenance.sh,
|
|
2
|
-
// lifecycle.sh and adapter.sh; the predicates now live in src/lifecycle.ts
|
|
2
|
+
// lifecycle.sh and adapter.sh; the predicates now live in src/harnesses/codex/lifecycle.ts
|
|
3
3
|
// and the adapter arrives through ctx.adapter.
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { oneLine } from "../cli-arguments.js";
|
|
@@ -126,7 +126,7 @@ async function gatherUninstall(ctx) {
|
|
|
126
126
|
// Reachable only for mkdtemp failure, with nothing collected yet: this
|
|
127
127
|
// callback never throws -- every ctx.adapter throw is already caught
|
|
128
128
|
// inside invoke(), and presenceFlag/verifyUninstalledResources/
|
|
129
|
-
// reportLegacyState are pure (src/lifecycle.ts's header comment) -- so a
|
|
129
|
+
// reportLegacyState are pure (src/harnesses/codex/lifecycle.ts's header comment) -- so a
|
|
130
130
|
// post-success cleanup failure is handled by onCleanupFailure above and
|
|
131
131
|
// cannot reach here. Wrapping with `outcomes` anyway keeps the class
|
|
132
132
|
// total over its declared contract rather than assuming the callback's
|
|
@@ -163,7 +163,7 @@ async function performUninstall(argv, ctx) {
|
|
|
163
163
|
// blindly.
|
|
164
164
|
//
|
|
165
165
|
// A cause outside ctx.adapter's AdapterFailure guard
|
|
166
|
-
// (`src/adapter.ts:
|
|
166
|
+
// (`src/harnesses/codex/adapter.ts:1081::async function runCodexOperation<T = JsonValue>(`) does NOT
|
|
167
167
|
// reach here: invoke() catches it inside gatherUninstall and converts it
|
|
168
168
|
// to a hand-written message carried as UninstallOutcome data, exactly as
|
|
169
169
|
// src/commands/probe.ts's inspect() does for the same cause.
|
package/dist/commands/update.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Ports scripts/update. The shell sourced common.sh and lifecycle.sh; the
|
|
2
|
-
// predicates now live in src/lifecycle.ts. Unlike install and uninstall,
|
|
2
|
+
// predicates now live in src/harnesses/codex/lifecycle.ts. Unlike install and uninstall,
|
|
3
3
|
// update issues no ctx.adapter call of its own: every adapter interaction
|
|
4
4
|
// this module needs already happens inside gatherProbe, runPrepare or
|
|
5
5
|
// runInstall, each of which owns its own §4.2a-conformant invoke() gate. That
|
package/dist/domain/refs.js
CHANGED
|
@@ -3,6 +3,10 @@ const SEMVER_BASE_SOURCE = "(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0-9]*)\\.(?:0|[1-9][0
|
|
|
3
3
|
"(?:\\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?";
|
|
4
4
|
export const SEMVER_BASE_RE = new RegExp(`^${SEMVER_BASE_SOURCE}$`);
|
|
5
5
|
export const TAG_RE = new RegExp(`^v${SEMVER_BASE_SOURCE}$`);
|
|
6
|
+
// The Python's own SEMVER_RE (:15-22). Deliberately not SEMVER_BASE_SOURCE
|
|
7
|
+
// from this module, which omits the `+build` component; widening the shared
|
|
8
|
+
// constant would change TAG_RE and is out of scope.
|
|
9
|
+
export const SEMVER_RE = /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
6
10
|
export const COMMIT_RE = /^[0-9a-f]{40}$/;
|
|
7
11
|
export const COMMIT_INPUT_RE = /^[0-9A-Fa-f]{40}$/;
|
|
8
12
|
export function isTagRef(value) {
|
|
@@ -4,21 +4,29 @@ import { constants } from "node:fs";
|
|
|
4
4
|
import { access, copyFile, mkdir, readFile, realpath, stat, writeFile, } from "node:fs/promises";
|
|
5
5
|
import { tmpdir } from "node:os";
|
|
6
6
|
import { delimiter, isAbsolute, join } from "node:path";
|
|
7
|
-
import { AdapterMessageLog, failureResult, successResult, } from "
|
|
8
|
-
import { activePluginVersionFromJson, installedListingHas, marketplaceRootFromJson, } from "./
|
|
9
|
-
import { CODEX_LEGACY_PLUGIN_ID, CODEX_MANAGER_PLUGIN_ID, inspectCodexConflicts, } from "./
|
|
10
|
-
import { oneLine } from "
|
|
11
|
-
import { installedCommitFromRoot, installedRootForVersion, pathsEqual, } from "./
|
|
7
|
+
import { AdapterMessageLog, failureResult, successResult, } from "../../adapter-result.js";
|
|
8
|
+
import { activePluginVersionFromJson, codexInstalledPluginsFromJson, installedListingHas, marketplaceRootFromJson, } from "./json.js";
|
|
9
|
+
import { CODEX_LEGACY_PLUGIN_ID, CODEX_MANAGER_PLUGIN_ID, inspectCodexConflicts, } from "./conflicts.js";
|
|
10
|
+
import { oneLine } from "../../cli-arguments.js";
|
|
11
|
+
import { installedCommitFromRoot, installedRootForVersion, pathsEqual, } from "./state.js";
|
|
12
|
+
import { codexHome } from "./paths.js";
|
|
13
|
+
import { readCodexStoredState } from "./stored-state.js";
|
|
12
14
|
import { validateGeneratedPlugin } from "./generated-plugin.js";
|
|
13
15
|
import { classifyHooks, materializeHooks, readManifest, } from "./hooks.js";
|
|
14
16
|
import { applyManifestOverlay } from "./manifest-overlay.js";
|
|
15
|
-
import { readCodexBuildSource } from "
|
|
16
|
-
import { isAcceptedSplitValue } from "
|
|
17
|
-
import { withWorkspace, workspaceRemovalFailure } from "
|
|
17
|
+
import { readCodexBuildSource } from "../../provenance.js";
|
|
18
|
+
import { isAcceptedSplitValue } from "../../validate-generated-plugin-cli.js";
|
|
19
|
+
import { withWorkspace, workspaceRemovalFailure } from "../../workspace.js";
|
|
18
20
|
const PLUGIN_ID = CODEX_MANAGER_PLUGIN_ID;
|
|
19
21
|
const MARKETPLACE_NAME = "superpowers-manager";
|
|
20
22
|
const LEGACY_PLUGIN_ID = CODEX_LEGACY_PLUGIN_ID;
|
|
21
23
|
const LEGACY_MARKETPLACE_NAME = "superpowers-wrapper";
|
|
24
|
+
export function codexInstallRefreshMode(env) {
|
|
25
|
+
const refreshMode = env.SUPERPOWERS_INSTALL_REFRESH_MODE || "add-only";
|
|
26
|
+
return refreshMode === "add-only" || refreshMode === "remove-add"
|
|
27
|
+
? refreshMode
|
|
28
|
+
: null;
|
|
29
|
+
}
|
|
22
30
|
class AdapterFailure extends Error {
|
|
23
31
|
code;
|
|
24
32
|
hints;
|
|
@@ -89,12 +97,20 @@ function runCommand(file, args, env) {
|
|
|
89
97
|
});
|
|
90
98
|
});
|
|
91
99
|
}
|
|
92
|
-
// Exported only so tests/unit/adapter.test.
|
|
100
|
+
// Exported only so tests/unit/harnesses/codex/adapter.test.ts can assert the env scrub
|
|
93
101
|
// directly. No production caller uses this name.
|
|
94
102
|
export { runCommand as runCommandForTest };
|
|
95
103
|
function commandFailed(result) {
|
|
96
104
|
return result.status !== 0;
|
|
97
105
|
}
|
|
106
|
+
async function storedStateAfterListingFailure(env, code, message) {
|
|
107
|
+
try {
|
|
108
|
+
return await readCodexStoredState(env, process.cwd());
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
fail(code, message);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
98
114
|
// Exported only so its unit test can reach it. No integration test can cover
|
|
99
115
|
// it: on Linux glibc, `execvp`'s ENOEXEC falls back to `/bin/sh`, so the spawn
|
|
100
116
|
// still succeeds, and every other candidate errno is already peeled off by an
|
|
@@ -281,7 +297,7 @@ async function runBuild(input, env, log) {
|
|
|
281
297
|
// path — three are the frozen CPython wording, and the fourth (the
|
|
282
298
|
// numeric-overflow diagnostic, which has no CPython oracle wording
|
|
283
299
|
// to match) now carries the path via its own rewrap in
|
|
284
|
-
// src/manifest-overlay.ts. Emit as-is, with no added prefix — a
|
|
300
|
+
// src/harnesses/codex/manifest-overlay.ts. Emit as-is, with no added prefix — a
|
|
285
301
|
// prefix here would double up the path these messages already
|
|
286
302
|
// name.
|
|
287
303
|
log.appendText("stderr", oneLine(cause));
|
|
@@ -383,25 +399,27 @@ async function runInstall(packageRoot, env, log) {
|
|
|
383
399
|
fail("invalid-arguments", `package root not found: ${packageRoot}`);
|
|
384
400
|
}
|
|
385
401
|
const codexBin = env.SUPERPOWERS_CODEX || "codex";
|
|
386
|
-
const refreshMode = env
|
|
402
|
+
const refreshMode = codexInstallRefreshMode(env);
|
|
387
403
|
await requireCodex(codexBin, env);
|
|
388
|
-
if (refreshMode
|
|
389
|
-
fail("invalid-arguments", `unsupported SUPERPOWERS_INSTALL_REFRESH_MODE: ${
|
|
404
|
+
if (refreshMode === null) {
|
|
405
|
+
fail("invalid-arguments", `unsupported SUPERPOWERS_INSTALL_REFRESH_MODE: ${env.SUPERPOWERS_INSTALL_REFRESH_MODE}`);
|
|
390
406
|
}
|
|
391
407
|
let entered = false;
|
|
392
408
|
try {
|
|
393
409
|
return await withWorkspace(tmpdir(), "superpowers-manager.adapter-install.", async () => {
|
|
394
410
|
entered = true;
|
|
395
411
|
const marketplaceList = await listingCommand(log, codexBin, ["plugin", "marketplace", "list", "--json"], env);
|
|
396
|
-
if (commandFailed(marketplaceList)) {
|
|
397
|
-
fail("install-failed", `cannot list Codex marketplaces via '${codexBin} plugin marketplace list --json'`);
|
|
398
|
-
}
|
|
399
412
|
let registeredRoot;
|
|
400
|
-
|
|
401
|
-
registeredRoot =
|
|
413
|
+
if (commandFailed(marketplaceList)) {
|
|
414
|
+
registeredRoot = (await storedStateAfterListingFailure(env, "install-failed", `cannot list Codex marketplaces via '${codexBin} plugin marketplace list --json'`)).managerMarketplaceRoot;
|
|
402
415
|
}
|
|
403
|
-
|
|
404
|
-
|
|
416
|
+
else {
|
|
417
|
+
try {
|
|
418
|
+
registeredRoot = marketplaceRootFromJson(marketplaceList.stdout, MARKETPLACE_NAME);
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
fail("install-failed", `cannot parse output of '${codexBin} plugin marketplace list --json'`);
|
|
422
|
+
}
|
|
405
423
|
}
|
|
406
424
|
if (registeredRoot.length === 0) {
|
|
407
425
|
const added = await mutationCommand(log, codexBin, ["plugin", "marketplace", "add", packageRoot], env);
|
|
@@ -543,16 +561,34 @@ async function runInspect(view, context, env, log) {
|
|
|
543
561
|
entered = true;
|
|
544
562
|
const plugins = await listingCommand(log, codexBin, ["plugin", "list", "--json"], env);
|
|
545
563
|
if (commandFailed(plugins)) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
564
|
+
const stored = await storedStateAfterListingFailure(env, "inspect-failed", `cannot list Codex plugins via '${codexBin} plugin list --json'`);
|
|
565
|
+
const conflicts = await inspectCodexConflicts({ root: context.root, env }, stored.installedListingJson);
|
|
566
|
+
const managerPresent = stored.managerPluginPresent ||
|
|
567
|
+
stored.managerMarketplaceRoot.length > 0;
|
|
568
|
+
const legacyPresent = stored.legacyPluginPresent ||
|
|
569
|
+
stored.legacyMarketplaceRoot !== null;
|
|
570
|
+
return {
|
|
571
|
+
view: "ownership",
|
|
572
|
+
resources: {
|
|
573
|
+
plugin: stored.managerPluginPresent,
|
|
574
|
+
marketplace: true,
|
|
575
|
+
},
|
|
576
|
+
legacy_resources: {
|
|
577
|
+
plugin: stored.legacyPluginPresent,
|
|
578
|
+
marketplace: stored.legacyMarketplaceRoot !== null,
|
|
579
|
+
},
|
|
580
|
+
identity_state: managerPresent
|
|
581
|
+
? legacyPresent
|
|
582
|
+
? "both"
|
|
583
|
+
: "manager"
|
|
584
|
+
: legacyPresent
|
|
585
|
+
? "legacy"
|
|
586
|
+
: "neither",
|
|
587
|
+
conflicts: [...conflicts],
|
|
588
|
+
};
|
|
551
589
|
}
|
|
552
590
|
let managerPlugin;
|
|
553
591
|
let legacyPlugin;
|
|
554
|
-
let managerMarketplace;
|
|
555
|
-
let legacyMarketplace;
|
|
556
592
|
let conflicts;
|
|
557
593
|
try {
|
|
558
594
|
managerPlugin = installedListingHas(plugins.stdout, "installed", "pluginId", PLUGIN_ID);
|
|
@@ -562,6 +598,35 @@ async function runInspect(view, context, env, log) {
|
|
|
562
598
|
catch {
|
|
563
599
|
fail("inspect-failed", `cannot parse output of '${codexBin} plugin list --json'`);
|
|
564
600
|
}
|
|
601
|
+
const marketplaces = await listingCommand(log, codexBin, ["plugin", "marketplace", "list", "--json"], env);
|
|
602
|
+
if (commandFailed(marketplaces)) {
|
|
603
|
+
const stored = await storedStateAfterListingFailure(env, "inspect-failed", `cannot list Codex marketplaces via '${codexBin} plugin marketplace list --json'`);
|
|
604
|
+
const conflicts = await inspectCodexConflicts({ root: context.root, env }, stored.installedListingJson);
|
|
605
|
+
const managerPresent = true;
|
|
606
|
+
const legacyPresent = stored.legacyPluginPresent ||
|
|
607
|
+
stored.legacyMarketplaceRoot !== null;
|
|
608
|
+
return {
|
|
609
|
+
view: "ownership",
|
|
610
|
+
resources: {
|
|
611
|
+
plugin: stored.managerPluginPresent,
|
|
612
|
+
marketplace: true,
|
|
613
|
+
},
|
|
614
|
+
legacy_resources: {
|
|
615
|
+
plugin: stored.legacyPluginPresent,
|
|
616
|
+
marketplace: stored.legacyMarketplaceRoot !== null,
|
|
617
|
+
},
|
|
618
|
+
identity_state: managerPresent
|
|
619
|
+
? legacyPresent
|
|
620
|
+
? "both"
|
|
621
|
+
: "manager"
|
|
622
|
+
: legacyPresent
|
|
623
|
+
? "legacy"
|
|
624
|
+
: "neither",
|
|
625
|
+
conflicts: [...conflicts],
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
let managerMarketplace;
|
|
629
|
+
let legacyMarketplace;
|
|
565
630
|
try {
|
|
566
631
|
managerMarketplace = installedListingHas(marketplaces.stdout, "marketplaces", "name", MARKETPLACE_NAME);
|
|
567
632
|
legacyMarketplace = installedListingHas(marketplaces.stdout, "marketplaces", "name", LEGACY_MARKETPLACE_NAME);
|
|
@@ -628,6 +693,70 @@ export function codexRemove(input, context) {
|
|
|
628
693
|
export function codexInspect(view, context) {
|
|
629
694
|
return runCodexOperation("inspect", context, (env, log) => runInspect(view, context, env, log));
|
|
630
695
|
}
|
|
696
|
+
export function codexReadNativeState(context) {
|
|
697
|
+
return runCodexOperation("native-state", context, async (env, log) => {
|
|
698
|
+
const codexBin = env.SUPERPOWERS_CODEX || "codex";
|
|
699
|
+
await requireCodex(codexBin, env);
|
|
700
|
+
const plugins = await listingCommand(log, codexBin, ["plugin", "list", "--json"], env);
|
|
701
|
+
if (commandFailed(plugins)) {
|
|
702
|
+
const stored = await storedStateAfterListingFailure(env, "inspect-failed", `cannot list Codex plugins via '${codexBin} plugin list --json'`);
|
|
703
|
+
return {
|
|
704
|
+
marketplaceRoot: stored.managerMarketplaceRoot,
|
|
705
|
+
pluginPresent: stored.managerPluginPresent,
|
|
706
|
+
pluginEnabled: stored.managerPluginEnabled,
|
|
707
|
+
activeVersion: null,
|
|
708
|
+
activeRoot: null,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
let manager;
|
|
712
|
+
let activeVersion;
|
|
713
|
+
try {
|
|
714
|
+
const managers = codexInstalledPluginsFromJson(plugins.stdout).filter((plugin) => plugin.pluginId === PLUGIN_ID);
|
|
715
|
+
if (managers.length > 1)
|
|
716
|
+
fail("inspect-failed", "Codex manager plugin appears more than once");
|
|
717
|
+
manager = managers[0];
|
|
718
|
+
activeVersion =
|
|
719
|
+
manager === undefined
|
|
720
|
+
? ""
|
|
721
|
+
: activePluginVersionFromJson(plugins.stdout, PLUGIN_ID);
|
|
722
|
+
}
|
|
723
|
+
catch (cause) {
|
|
724
|
+
if (cause instanceof AdapterFailure)
|
|
725
|
+
throw cause;
|
|
726
|
+
fail("inspect-failed", `cannot parse output of '${codexBin} plugin list --json'`);
|
|
727
|
+
}
|
|
728
|
+
const marketplaces = await listingCommand(log, codexBin, ["plugin", "marketplace", "list", "--json"], env);
|
|
729
|
+
if (commandFailed(marketplaces)) {
|
|
730
|
+
const stored = await storedStateAfterListingFailure(env, "inspect-failed", `cannot list Codex marketplaces via '${codexBin} plugin marketplace list --json'`);
|
|
731
|
+
return {
|
|
732
|
+
marketplaceRoot: stored.managerMarketplaceRoot,
|
|
733
|
+
pluginPresent: stored.managerPluginPresent,
|
|
734
|
+
pluginEnabled: stored.managerPluginEnabled,
|
|
735
|
+
activeVersion: null,
|
|
736
|
+
activeRoot: null,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
let marketplaceRoot;
|
|
740
|
+
try {
|
|
741
|
+
marketplaceRoot = marketplaceRootFromJson(marketplaces.stdout, MARKETPLACE_NAME, true);
|
|
742
|
+
}
|
|
743
|
+
catch (cause) {
|
|
744
|
+
if (cause instanceof AdapterFailure)
|
|
745
|
+
throw cause;
|
|
746
|
+
fail("inspect-failed", `cannot parse output of '${codexBin} plugin marketplace list --json'`);
|
|
747
|
+
}
|
|
748
|
+
const searchRoot = env.SUPERPOWERS_INSTALLED_SEARCH_ROOT || codexHome(env, process.cwd());
|
|
749
|
+
return {
|
|
750
|
+
marketplaceRoot: marketplaceRoot || null,
|
|
751
|
+
pluginPresent: manager !== undefined,
|
|
752
|
+
pluginEnabled: manager?.enabled === true,
|
|
753
|
+
activeVersion: activeVersion || null,
|
|
754
|
+
activeRoot: activeVersion
|
|
755
|
+
? installedRootForVersion(searchRoot, MARKETPLACE_NAME, "superpowers", activeVersion)
|
|
756
|
+
: null,
|
|
757
|
+
};
|
|
758
|
+
});
|
|
759
|
+
}
|
|
631
760
|
export async function runAdapter(argv, context) {
|
|
632
761
|
const rawOperation = argv[0];
|
|
633
762
|
const operation = rawOperation || "adapter";
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
|
-
import { validateNativeSkill } from "
|
|
3
|
-
import { classifyPathNoFollow, assertExistingContained } from "
|
|
2
|
+
import { validateNativeSkill } from "../../artifact-tree.js";
|
|
3
|
+
import { classifyPathNoFollow, assertExistingContained, } from "../../safe-path.js";
|
|
4
4
|
import { classifyHooks, readManifest } from "./hooks.js";
|
|
5
|
-
import { SEMVER_RE } from "
|
|
6
|
-
import { validateSource } from "
|
|
5
|
+
import { SEMVER_RE } from "../../domain/refs.js";
|
|
6
|
+
import { validateSource } from "../../selection.js";
|
|
7
7
|
import { writeFile } from "node:fs/promises";
|
|
8
|
-
import { ARTIFACT_DIGEST_RE, ARTIFACT_RECEIPT, digestArtifactTree, readArtifactObject, } from "
|
|
9
|
-
import { COMMIT_RE } from "
|
|
8
|
+
import { ARTIFACT_DIGEST_RE, ARTIFACT_RECEIPT, digestArtifactTree, readArtifactObject, } from "../../artifact-tree.js";
|
|
9
|
+
import { COMMIT_RE } from "../../domain/refs.js";
|
|
10
10
|
import { validateGeneratedPlugin } from "./generated-plugin.js";
|
|
11
|
-
import { SafetyError } from "
|
|
11
|
+
import { SafetyError } from "../../safety-error.js";
|
|
12
12
|
export async function writeCodexAssessment(root, selection, manifestSource) {
|
|
13
13
|
const receipt = {
|
|
14
14
|
schema: 1,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import { codexInstalledPluginsFromJson, } from "./
|
|
3
|
+
import { codexInstalledPluginsFromJson, } from "./json.js";
|
|
4
4
|
export const CODEX_MANAGER_PLUGIN_ID = "superpowers@superpowers-manager";
|
|
5
5
|
export const CODEX_LEGACY_PLUGIN_ID = "superpowers@superpowers-wrapper";
|
|
6
6
|
const SUPERPOWERS_PLUGIN_PREFIX = "superpowers@";
|