superpowers-manager 0.5.2 → 0.7.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 +44 -26
- package/dist/adapter-result.js +5 -9
- package/dist/atomic.js +4 -14
- package/dist/cli-arguments.js +1 -27
- package/dist/cli.js +32 -52
- package/dist/commands/adapter-call.js +28 -0
- package/dist/commands/install.js +26 -107
- package/dist/commands/mutation.js +7 -0
- package/dist/commands/pin.js +1 -1
- package/dist/commands/prepare.js +39 -129
- package/dist/commands/probe.js +35 -264
- package/dist/commands/track-latest.js +1 -1
- package/dist/commands/uninstall.js +16 -92
- package/dist/commands/update.js +5 -25
- package/dist/effective-selection.js +7 -9
- package/dist/git-tree.js +85 -0
- package/dist/harness-command-result.js +47 -0
- package/dist/harnesses/codex/adapter.js +231 -510
- package/dist/harnesses/codex/compatibility.js +2 -17
- package/dist/harnesses/codex/conflicts.js +2 -3
- package/dist/harnesses/codex/harness.js +18 -263
- package/dist/harnesses/codex/hooks.js +1 -1
- package/dist/harnesses/codex/json.js +1 -11
- package/dist/harnesses/codex/lifecycle.js +80 -72
- package/dist/harnesses/codex/manifest-overlay.js +23 -18
- package/dist/harnesses/codex/paths.js +14 -29
- package/dist/harnesses/codex/presentation.js +8 -37
- package/dist/harnesses/codex/publication.js +13 -37
- package/dist/harnesses/codex/recovery.js +5 -5
- package/dist/harnesses/codex/stored-state.js +1 -1
- package/dist/harnesses/opencode/config.js +256 -0
- package/dist/harnesses/opencode/discovery.js +587 -0
- package/dist/harnesses/opencode/harness.js +37 -0
- package/dist/harnesses/opencode/install.js +601 -0
- package/dist/harnesses/opencode/native.js +57 -0
- package/dist/harnesses/opencode/package.js +89 -0
- package/dist/harnesses/opencode/paths.js +38 -0
- package/dist/harnesses/opencode/prepare.js +30 -0
- package/dist/harnesses/opencode/presentation.js +28 -0
- package/dist/harnesses/opencode/state.js +174 -0
- package/dist/harnesses/pi/compatibility.js +5 -37
- package/dist/harnesses/pi/install.js +7 -5
- package/dist/harnesses/pi/native.js +3 -47
- package/dist/harnesses/pi/package.js +5 -107
- package/dist/harnesses/pi/prepare.js +13 -104
- package/dist/harnesses/pi/presentation.js +23 -66
- package/dist/harnesses/pi/shared-skills.js +28 -39
- package/dist/harnesses/pi/state.js +7 -27
- package/dist/provenance.js +2 -2
- package/dist/python-json-format.js +0 -69
- package/dist/python-json.js +2 -27
- package/dist/python-text.js +9 -69
- package/dist/safe-path.js +2 -2
- package/dist/selection-store.js +2 -22
- package/dist/snapshot-package.js +78 -0
- package/dist/snapshot-prepare.js +99 -0
- package/dist/snapshot-presentation.js +44 -0
- package/dist/validator.js +39 -73
- package/node_modules/jsonc-parser/CHANGELOG.md +76 -0
- package/node_modules/jsonc-parser/LICENSE.md +21 -0
- package/node_modules/jsonc-parser/README.md +364 -0
- package/node_modules/jsonc-parser/SECURITY.md +41 -0
- package/node_modules/jsonc-parser/lib/esm/impl/edit.js +185 -0
- package/node_modules/jsonc-parser/lib/esm/impl/format.js +261 -0
- package/node_modules/jsonc-parser/lib/esm/impl/parser.js +659 -0
- package/node_modules/jsonc-parser/lib/esm/impl/scanner.js +443 -0
- package/node_modules/jsonc-parser/lib/esm/impl/string-intern.js +29 -0
- package/node_modules/jsonc-parser/lib/esm/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/esm/main.js +178 -0
- package/node_modules/jsonc-parser/lib/umd/impl/edit.js +201 -0
- package/node_modules/jsonc-parser/lib/umd/impl/format.js +275 -0
- package/node_modules/jsonc-parser/lib/umd/impl/parser.js +682 -0
- package/node_modules/jsonc-parser/lib/umd/impl/scanner.js +456 -0
- package/node_modules/jsonc-parser/lib/umd/impl/string-intern.js +42 -0
- package/node_modules/jsonc-parser/lib/umd/main.d.ts +351 -0
- package/node_modules/jsonc-parser/lib/umd/main.js +194 -0
- package/node_modules/jsonc-parser/package.json +37 -0
- package/package.json +8 -4
- package/dist/domain/fingerprint.js +0 -4
- package/dist/selection-state-cli.js +0 -85
- package/dist/status.js +0 -24
- package/dist/upstream-cli.js +0 -110
- package/dist/validate-generated-plugin-cli.js +0 -118
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Superpowers Manager
|
|
2
2
|
|
|
3
|
-
Install [`obra/superpowers`](https://github.com/obra/superpowers) directly from upstream for Codex
|
|
3
|
+
Install [`obra/superpowers`](https://github.com/obra/superpowers) directly from upstream for Codex, the Pi coding agent, and OpenCode. 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
|
|
|
@@ -8,11 +8,11 @@ Install [`obra/superpowers`](https://github.com/obra/superpowers) directly from
|
|
|
8
8
|
|
|
9
9
|
Start with the latest stable release by default, or choose the upstream version that works for you. There is no background updater.
|
|
10
10
|
|
|
11
|
-
| What you want
|
|
12
|
-
|
|
13
|
-
| Stay on a known-good version
|
|
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
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
|
|
15
|
+
| Return to the latest stable release | Save `track-latest`, then explicitly install or update |
|
|
16
16
|
|
|
17
17
|
For example, save a release pin and install it for your agent:
|
|
18
18
|
|
|
@@ -21,7 +21,7 @@ npx superpowers-manager pin v6.1.1
|
|
|
21
21
|
npx superpowers-manager install --harness codex
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Use `--harness pi` for Pi. The pin is shared by
|
|
24
|
+
Use `--harness pi` for Pi or `--harness opencode` for OpenCode. The pin is shared by all harnesses, 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
25
|
|
|
26
26
|
To try a branch, replace `feature/foo` with an existing upstream branch:
|
|
27
27
|
|
|
@@ -42,7 +42,7 @@ See [version selection](https://github.com/j7an/superpowers-manager/blob/main/do
|
|
|
42
42
|
|
|
43
43
|
## Start with your agent
|
|
44
44
|
|
|
45
|
-
The
|
|
45
|
+
The three harnesses share upstream selection, while each has independent prepared and installed state. Omitting `--harness` selects Codex.
|
|
46
46
|
|
|
47
47
|
### Codex
|
|
48
48
|
|
|
@@ -71,6 +71,19 @@ after a successful install, activating update, or removal. See the
|
|
|
71
71
|
[Pi reference](https://github.com/j7an/superpowers-manager/blob/main/docs/pi.md)
|
|
72
72
|
for compatibility, experimental opt-in, and recovery behavior.
|
|
73
73
|
|
|
74
|
+
### OpenCode
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
npx superpowers-manager install --harness opencode
|
|
78
|
+
npx superpowers-manager probe --harness opencode
|
|
79
|
+
npx superpowers-manager update --harness opencode
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
OpenCode installs a separately validated, Manager-owned frozen snapshot. Restart
|
|
83
|
+
OpenCode after a successful install, activating update, or removal. See the
|
|
84
|
+
[OpenCode reference](https://github.com/j7an/superpowers-manager/blob/main/docs/opencode.md)
|
|
85
|
+
for global configuration, conflicts, and recovery behavior.
|
|
86
|
+
|
|
74
87
|
To remove a Manager-owned installation, choose one harness explicitly:
|
|
75
88
|
|
|
76
89
|
### Uninstall Codex
|
|
@@ -85,25 +98,26 @@ npx superpowers-manager uninstall --harness codex
|
|
|
85
98
|
npx superpowers-manager uninstall --harness pi
|
|
86
99
|
```
|
|
87
100
|
|
|
101
|
+
### Uninstall OpenCode
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
npx superpowers-manager uninstall --harness opencode
|
|
105
|
+
```
|
|
106
|
+
|
|
88
107
|
The manager never removes another Superpowers provider. See the [ownership guidance](https://github.com/j7an/superpowers-manager/blob/main/docs/usage.md#provider-ownership) before switching providers.
|
|
89
108
|
|
|
90
109
|
## Shared selection and lifecycle
|
|
91
110
|
|
|
92
|
-
The [selection commands above](#choose-what-you-run) save shared upstream intent. They do not activate Codex or
|
|
111
|
+
The [selection commands above](#choose-what-you-run) save shared upstream intent. They do not activate Codex, Pi, or OpenCode; run the selected harness's `install` or `update` to apply that choice. `unpin` restores the packaged fallback policy.
|
|
93
112
|
|
|
94
|
-
|
|
95
|
-
|---|---|
|
|
96
|
-
| `prepare` | Build and validate the selected harness's prepared output |
|
|
97
|
-
| `install` | Apply the shared selection to the selected harness |
|
|
98
|
-
| `probe` | Inspect the selected harness without mutation |
|
|
99
|
-
| `update` | Explicitly refresh the selected harness when needed and verify it |
|
|
100
|
-
| `uninstall` | Remove only Manager-owned state from the selected harness |
|
|
113
|
+
See [targeted lifecycle commands](https://github.com/j7an/superpowers-manager/blob/main/docs/usage.md#targeted-lifecycle) for preparation, inspection, installation, updates, and removal.
|
|
101
114
|
|
|
102
|
-
Use `--harness codex
|
|
115
|
+
Use `--harness codex`, `--harness pi`, or `--harness opencode` with each targeted lifecycle command.
|
|
103
116
|
|
|
104
117
|
```text
|
|
105
118
|
shared selection -> prepare / inspect / activate Codex
|
|
106
119
|
-> prepare / inspect / activate Pi
|
|
120
|
+
-> prepare / inspect / activate OpenCode
|
|
107
121
|
```
|
|
108
122
|
|
|
109
123
|
The [usage reference](https://github.com/j7an/superpowers-manager/blob/main/docs/usage.md) covers version selection, precedence, offline behavior, `prepare`, validators, and every lifecycle command. The harness references cover [Codex registration and hooks](https://github.com/j7an/superpowers-manager/blob/main/docs/codex.md) and [Pi snapshots and recovery](https://github.com/j7an/superpowers-manager/blob/main/docs/pi.md).
|
|
@@ -113,18 +127,22 @@ The [usage reference](https://github.com/j7an/superpowers-manager/blob/main/docs
|
|
|
113
127
|
The installed package requires Node >=24. Native source, tests, and packaging tooling require Node >=24.12.0. Every other requirement is command-specific and is checked before dispatch. This table is derived from production by `tests/bin/readme-requirements.test.ts`.
|
|
114
128
|
|
|
115
129
|
<!-- requirements:begin -->
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
123
|
-
| `
|
|
124
|
-
| `
|
|
125
|
-
| `
|
|
130
|
+
|
|
131
|
+
| Command | git | Codex CLI (default) | Pi CLI (`--harness pi`) | OpenCode CLI (`--harness opencode`) |
|
|
132
|
+
| -------------- | --- | ------------------- | ----------------------- | ----------------------------------- |
|
|
133
|
+
| `pin` | yes | no | no | no |
|
|
134
|
+
| `track-latest` | no | no | no | no |
|
|
135
|
+
| `unpin` | no | no | no | no |
|
|
136
|
+
| `prepare` | yes | no | no | no |
|
|
137
|
+
| `probe` | yes | yes | no | no |
|
|
138
|
+
| `install` | yes | yes | yes | yes |
|
|
139
|
+
| `update` | yes | yes | yes | yes |
|
|
140
|
+
| `uninstall` | no | yes | yes | no |
|
|
141
|
+
|
|
126
142
|
<!-- requirements:end -->
|
|
127
143
|
|
|
144
|
+
Generated Codex manifests preserve upstream JSON number tokens while rejecting non-finite decimal or exponent values.
|
|
145
|
+
|
|
128
146
|
macOS and Linux are tested. WSL2 is supported for the established Codex path; Pi in WSL2 and native Windows remain untested. The manager runs in-process and does not require Git Bash. The [Pi reference](https://github.com/j7an/superpowers-manager/blob/main/docs/pi.md#runtime-compatibility) explains its runtime qualification and admission checks.
|
|
129
147
|
|
|
130
148
|
Native test versions qualify integration mechanisms; they are not runtime allowlists, and an untested runtime is not automatically certified as compatible.
|
package/dist/adapter-result.js
CHANGED
|
@@ -133,6 +133,9 @@ export function failureResult(operation, code, message, hints, messages) {
|
|
|
133
133
|
},
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
|
+
export function inspectionFailure(operation, subject) {
|
|
137
|
+
return failureResult(operation, "invalid-state", `cannot inspect ${subject}`, [], []);
|
|
138
|
+
}
|
|
136
139
|
export function hasTerminalControl(value) {
|
|
137
140
|
for (const character of value) {
|
|
138
141
|
const code = character.codePointAt(0);
|
|
@@ -148,10 +151,8 @@ export function hasTerminalControl(value) {
|
|
|
148
151
|
// every command replays through -- wrote these three strings to the terminal
|
|
149
152
|
// unfiltered.
|
|
150
153
|
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
// member, and D8b requires the thrown message to name the failing one. So this
|
|
154
|
-
// scans with the predicate underneath it instead.
|
|
154
|
+
// This scans with the predicate underneath it because D8b requires the thrown
|
|
155
|
+
// message to name the failing member.
|
|
155
156
|
//
|
|
156
157
|
// Order is code, then message, then hints by ascending index, so the thrown
|
|
157
158
|
// message is a function of the outcome rather than of iteration order.
|
|
@@ -197,8 +198,3 @@ export function writeAdapterFailure(ctx, outcome) {
|
|
|
197
198
|
ctx.stderr.write(`hint: ${hint}\n`);
|
|
198
199
|
}
|
|
199
200
|
}
|
|
200
|
-
export function requireProtocolString(value) {
|
|
201
|
-
if (hasTerminalControl(value)) {
|
|
202
|
-
throw new Error("protocol strings must not contain terminal control characters");
|
|
203
|
-
}
|
|
204
|
-
}
|
package/dist/atomic.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { randomBytes } from "node:crypto";
|
|
2
|
-
import { constants, lstat, open,
|
|
2
|
+
import { constants, lstat, open, rename, rm, unlink } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join, resolve } from "node:path";
|
|
4
|
+
import { isErrno } from "./safe-path.js";
|
|
4
5
|
import { SafetyError } from "./safety-error.js";
|
|
5
6
|
export async function atomicWriteFile(path, bytes, options) {
|
|
6
7
|
const renamePath = options.hooks?.rename ?? rename;
|
|
7
8
|
let handle;
|
|
8
9
|
let temporary;
|
|
9
|
-
let phase = "pre-replacement";
|
|
10
10
|
try {
|
|
11
11
|
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
12
12
|
const suffix = randomBytes(8).toString("hex");
|
|
@@ -36,17 +36,10 @@ export async function atomicWriteFile(path, bytes, options) {
|
|
|
36
36
|
await options.validate(temporary);
|
|
37
37
|
await renamePath(temporary, path);
|
|
38
38
|
temporary = undefined;
|
|
39
|
-
phase = "post-replacement";
|
|
40
|
-
await options.hooks?.afterReplace?.(path);
|
|
41
39
|
await fsyncDirectoryBestEffort(dirname(path));
|
|
42
40
|
}
|
|
43
41
|
catch (cause) {
|
|
44
|
-
|
|
45
|
-
if (phase === "post-replacement") {
|
|
46
|
-
finalBytes = await readFile(path).catch(() => undefined);
|
|
47
|
-
}
|
|
48
|
-
const details = finalBytes === undefined ? { phase } : { phase, finalBytes };
|
|
49
|
-
throw new SafetyError("atomic", `atomic file write failed during ${phase}`, { cause, details });
|
|
42
|
+
throw new SafetyError("atomic", "atomic file write failed during pre-replacement", { cause, details: { phase: "pre-replacement" } });
|
|
50
43
|
}
|
|
51
44
|
finally {
|
|
52
45
|
if (handle !== undefined) {
|
|
@@ -76,11 +69,8 @@ async function exists(path) {
|
|
|
76
69
|
return true;
|
|
77
70
|
}
|
|
78
71
|
catch (cause) {
|
|
79
|
-
if (cause
|
|
80
|
-
"code" in cause &&
|
|
81
|
-
cause.code === "ENOENT") {
|
|
72
|
+
if (isErrno(cause, "ENOENT"))
|
|
82
73
|
return false;
|
|
83
|
-
}
|
|
84
74
|
throw cause;
|
|
85
75
|
}
|
|
86
76
|
}
|
package/dist/cli-arguments.js
CHANGED
|
@@ -20,7 +20,7 @@ export function extractHarnessOptions(command, argv) {
|
|
|
20
20
|
: token.slice("--harness=".length);
|
|
21
21
|
if (!value || value.startsWith("--"))
|
|
22
22
|
throw new UsageError("option --harness requires a value");
|
|
23
|
-
if (value !== "codex" && value !== "pi")
|
|
23
|
+
if (value !== "codex" && value !== "pi" && value !== "opencode")
|
|
24
24
|
throw new UsageError(`unknown harness: ${value}`);
|
|
25
25
|
harness = value;
|
|
26
26
|
}
|
|
@@ -37,32 +37,6 @@ export function extractHarnessOptions(command, argv) {
|
|
|
37
37
|
}
|
|
38
38
|
return { options: { harness, allowExperimental }, args };
|
|
39
39
|
}
|
|
40
|
-
export function parseFlags(argv, names) {
|
|
41
|
-
const allowed = new Set(names);
|
|
42
|
-
const result = {};
|
|
43
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
44
|
-
const token = argv[index];
|
|
45
|
-
if (!token.startsWith("--"))
|
|
46
|
-
throw new UsageError(`unexpected argument: ${token}`);
|
|
47
|
-
const separator = token.indexOf("=");
|
|
48
|
-
const name = separator === -1 ? token.slice(2) : token.slice(2, separator);
|
|
49
|
-
if (!allowed.has(name))
|
|
50
|
-
throw new UsageError(`unknown option: --${name}`);
|
|
51
|
-
const value = separator === -1 ? argv[index + 1] : token.slice(separator + 1);
|
|
52
|
-
if (value === undefined || (separator === -1 && value.startsWith("--"))) {
|
|
53
|
-
throw new UsageError(`option --${name} requires a value`);
|
|
54
|
-
}
|
|
55
|
-
result[name] = value;
|
|
56
|
-
if (separator === -1)
|
|
57
|
-
index += 1;
|
|
58
|
-
}
|
|
59
|
-
for (const name of names) {
|
|
60
|
-
if (!Object.hasOwn(result, name)) {
|
|
61
|
-
throw new UsageError(`required option is missing: --${name}`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return result;
|
|
65
|
-
}
|
|
66
40
|
export function oneLine(value) {
|
|
67
41
|
const message = value instanceof Error ? value.message : String(value);
|
|
68
42
|
return message.replace(/[\r\n]+/g, " ");
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import { extractHarnessOptions, oneLine, UsageError } from "./cli-arguments.js";
|
|
5
5
|
import { codexHarness } from "./harnesses/codex/harness.js";
|
|
6
6
|
import { piHarness } from "./harnesses/pi/harness.js";
|
|
7
|
+
import { openCodeHarness } from "./harnesses/opencode/harness.js";
|
|
7
8
|
import { createResourceCoordinator } from "./resource-lock.js";
|
|
8
9
|
import { runInstall } from "./commands/install.js";
|
|
9
10
|
import { runPin } from "./commands/pin.js";
|
|
@@ -46,8 +47,8 @@ const IN_PROCESS_HANDLERS = {
|
|
|
46
47
|
// required because `spw_invoke_adapter` ran validate-adapter-response.py once
|
|
47
48
|
// per adapter call
|
|
48
49
|
// (`git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/adapter.sh:37-44::--response "$response_file" --result "$result_file" \`);
|
|
49
|
-
// the in-process path
|
|
50
|
-
//
|
|
50
|
+
// the in-process path no longer uses that adapter-response validator.
|
|
51
|
+
// No command has a Python requirement in requirementsFor().
|
|
51
52
|
// No command requires a POSIX shell any more.
|
|
52
53
|
const SHARED_COMMAND_REQUIREMENTS = {
|
|
53
54
|
pin: ["git"],
|
|
@@ -170,13 +171,6 @@ function findTool(name, env, platform) {
|
|
|
170
171
|
}
|
|
171
172
|
return null;
|
|
172
173
|
}
|
|
173
|
-
// python3 is required by `prepare` only when SUPERPOWERS_VALIDATOR names one:
|
|
174
|
-
// after the port, that optional spawn (runValidator, in src/validator.ts,
|
|
175
|
-
// called from src/commands/prepare.ts) is Python's only remaining consumer
|
|
176
|
-
// on the prepare path. The conditional lives here, in the accessor preflight reads,
|
|
177
|
-
// rather than inside preflight — an accessor that under-reports what
|
|
178
|
-
// preflight enforces is the blind spot slice 2 closed when it made
|
|
179
|
-
// CLI-PREFLIGHT-01 derive its map from production.
|
|
180
174
|
function sharedRequirement(name) {
|
|
181
175
|
return {
|
|
182
176
|
name,
|
|
@@ -185,39 +179,22 @@ function sharedRequirement(name) {
|
|
|
185
179
|
missingMessage: `required command not found: ${name} — install ${name} and re-run`,
|
|
186
180
|
};
|
|
187
181
|
}
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}
|
|
194
|
-
return [...shared, ...adapter.requirements(command, env)];
|
|
195
|
-
};
|
|
196
|
-
return {
|
|
197
|
-
pin: forCommand("pin"),
|
|
198
|
-
"track-latest": forCommand("track-latest"),
|
|
199
|
-
unpin: forCommand("unpin"),
|
|
200
|
-
prepare: forCommand("prepare"),
|
|
201
|
-
probe: forCommand("probe"),
|
|
202
|
-
install: forCommand("install"),
|
|
203
|
-
update: forCommand("update"),
|
|
204
|
-
uninstall: forCommand("uninstall"),
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
function commandRequirements(env) {
|
|
208
|
-
return Object.fromEntries(SUBCOMMANDS.map((command) => [
|
|
209
|
-
command,
|
|
210
|
-
commandRequirementsFor(env, codexHarness)[command].map((requirement) => requirement.name),
|
|
211
|
-
]));
|
|
182
|
+
function requirementsFor(command, env, adapter) {
|
|
183
|
+
return [
|
|
184
|
+
...SHARED_COMMAND_REQUIREMENTS[command].map(sharedRequirement),
|
|
185
|
+
...adapter.requirements(command, env),
|
|
186
|
+
];
|
|
212
187
|
}
|
|
213
188
|
// Preflight; never touches Codex state. It is the union of two exported
|
|
214
189
|
// accessors: configurationErrors (validator configuration) and
|
|
215
|
-
//
|
|
190
|
+
// requirementsFor (tool availability), both specific to the selected
|
|
216
191
|
// command. No command requires a POSIX shell: slice 4b flipped the last
|
|
217
192
|
// spawned command in-process, so there is no shell to discover.
|
|
218
|
-
function
|
|
193
|
+
function preflight(cmd, env, platform, adapter) {
|
|
219
194
|
const errors = [...configurationErrors(cmd, env)];
|
|
220
|
-
|
|
195
|
+
if (errors.length > 0)
|
|
196
|
+
return { ok: false, errors };
|
|
197
|
+
for (const requirement of requirementsFor(cmd, env, adapter)) {
|
|
221
198
|
const found = requirement.lookup === "explicit-path-or-path" &&
|
|
222
199
|
requirement.executable.includes(path.sep)
|
|
223
200
|
? fs.existsSync(requirement.executable)
|
|
@@ -229,9 +206,6 @@ function preflightFor(cmd, env, platform, adapter) {
|
|
|
229
206
|
return { ok: false, errors };
|
|
230
207
|
return { ok: true };
|
|
231
208
|
}
|
|
232
|
-
function preflight(cmd, env, platform) {
|
|
233
|
-
return preflightFor(cmd, env, platform, codexHarness);
|
|
234
|
-
}
|
|
235
209
|
function usage() {
|
|
236
210
|
return [
|
|
237
211
|
"usage: superpowers-manager [command] [args...]",
|
|
@@ -249,19 +223,23 @@ function usage() {
|
|
|
249
223
|
" uninstall remove the manager plugin and marketplace from Codex",
|
|
250
224
|
"",
|
|
251
225
|
"Target one invocation (default: codex; selection commands are shared):",
|
|
252
|
-
" probe --harness
|
|
253
|
-
" prepare --harness
|
|
254
|
-
" install --harness
|
|
255
|
-
" update --harness
|
|
256
|
-
" uninstall --harness
|
|
257
|
-
" --harness=codex and --harness=
|
|
226
|
+
" probe --harness opencode [--porcelain]",
|
|
227
|
+
" prepare --harness opencode",
|
|
228
|
+
" install --harness opencode [--allow-experimental]",
|
|
229
|
+
" update --harness opencode [--allow-experimental]",
|
|
230
|
+
" uninstall --harness opencode",
|
|
231
|
+
" --harness=codex, --harness=pi, and --harness=opencode are also accepted after the command.",
|
|
258
232
|
"",
|
|
259
233
|
"Environment overrides (used by in-process commands): SUPERPOWERS_REF,",
|
|
260
234
|
"SUPERPOWERS_UPSTREAM_URL, SUPERPOWERS_CODEX, SUPERPOWERS_CACHE_DIR,",
|
|
261
235
|
"SUPERPOWERS_CONFIG_DIR, XDG_CONFIG_HOME,",
|
|
262
236
|
"SUPERPOWERS_PLUGIN_ROOT, SUPERPOWERS_MANIFEST_TEMPLATE,",
|
|
263
|
-
"
|
|
264
|
-
"SUPERPOWERS_INSTALLED_SEARCH_ROOT, SUPERPOWERS_INSTALL_REFRESH_MODE",
|
|
237
|
+
"SUPERPOWERS_VALIDATOR_EXECUTABLE,",
|
|
238
|
+
"SUPERPOWERS_INSTALLED_SEARCH_ROOT, SUPERPOWERS_INSTALL_REFRESH_MODE,",
|
|
239
|
+
"SUPERPOWERS_OPENCODE",
|
|
240
|
+
"",
|
|
241
|
+
"SUPERPOWERS_VALIDATOR is removed; unset it and use",
|
|
242
|
+
"SUPERPOWERS_VALIDATOR_EXECUTABLE with an executable validator.",
|
|
265
243
|
"",
|
|
266
244
|
"Selection state uses SUPERPOWERS_CONFIG_DIR when set; otherwise it uses",
|
|
267
245
|
"$XDG_CONFIG_HOME/superpowers-manager, then $HOME/.config/superpowers-manager.",
|
|
@@ -288,9 +266,11 @@ async function main() {
|
|
|
288
266
|
console.error(usage());
|
|
289
267
|
process.exit(2);
|
|
290
268
|
}
|
|
291
|
-
const status = parsed.options.harness === "
|
|
292
|
-
? await dispatch(
|
|
293
|
-
:
|
|
269
|
+
const status = parsed.options.harness === "opencode"
|
|
270
|
+
? await dispatch(openCodeHarness, parsed)
|
|
271
|
+
: parsed.options.harness === "pi"
|
|
272
|
+
? await dispatch(piHarness, parsed)
|
|
273
|
+
: await dispatch(codexHarness, parsed);
|
|
294
274
|
process.exit(status);
|
|
295
275
|
}
|
|
296
276
|
async function dispatch(adapter, parsed) {
|
|
@@ -299,7 +279,7 @@ async function dispatch(adapter, parsed) {
|
|
|
299
279
|
console.error("error: cannot resolve the superpowers-manager package root");
|
|
300
280
|
return 1;
|
|
301
281
|
}
|
|
302
|
-
const pf =
|
|
282
|
+
const pf = preflight(parsed.cmd, process.env, process.platform, adapter);
|
|
303
283
|
if (!pf.ok) {
|
|
304
284
|
for (const e of pf.errors)
|
|
305
285
|
console.error(`error: ${e}`);
|
|
@@ -341,7 +321,7 @@ async function dispatch(adapter, parsed) {
|
|
|
341
321
|
}
|
|
342
322
|
return status;
|
|
343
323
|
}
|
|
344
|
-
export { resolvePackageRoot, isMain, parseArgs,
|
|
324
|
+
export { resolvePackageRoot, isMain, parseArgs, requirementsFor, preflight, usage, main, };
|
|
345
325
|
if (isMain(import.meta.filename, process.argv[1])) {
|
|
346
326
|
main().catch((cause) => {
|
|
347
327
|
console.error(`error: ${oneLine(cause)}`);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export async function callAdapter(call, failure) {
|
|
2
|
+
let result;
|
|
3
|
+
try {
|
|
4
|
+
result = await call();
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return { ok: false, result: null, message: failure.unexpected };
|
|
8
|
+
}
|
|
9
|
+
const outcome = result.outcome;
|
|
10
|
+
if (result.status !== 0 || !outcome.ok)
|
|
11
|
+
return {
|
|
12
|
+
ok: false,
|
|
13
|
+
result,
|
|
14
|
+
message: outcome.ok ? failure.invalidStatus : null,
|
|
15
|
+
};
|
|
16
|
+
return { ok: true, result: { status: result.status, outcome } };
|
|
17
|
+
}
|
|
18
|
+
// Preserve outcomes collected before gathering throws so command catches can
|
|
19
|
+
// replay them before reporting the underlying failure.
|
|
20
|
+
export class GatherFailure extends Error {
|
|
21
|
+
inner;
|
|
22
|
+
outcomes;
|
|
23
|
+
constructor(message, inner, outcomes) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.inner = inner;
|
|
26
|
+
this.outcomes = outcomes;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/commands/install.js
CHANGED
|
@@ -1,92 +1,35 @@
|
|
|
1
|
-
// Ports scripts/install. The shell sourced common.sh, provenance.sh,
|
|
2
|
-
// status.sh, lifecycle.sh and adapter.sh; the predicates now live in
|
|
3
|
-
// src/harnesses/codex/lifecycle.ts, the generated-metadata read lives in src/provenance.ts,
|
|
4
|
-
// and the adapter arrives through ctx.adapter.
|
|
5
1
|
import { tmpdir } from "node:os";
|
|
6
2
|
import { oneLine } from "../cli-arguments.js";
|
|
7
3
|
import { withWorkspace, workspaceRemovalFailure } from "../workspace.js";
|
|
8
|
-
import {
|
|
4
|
+
import { GatherFailure, callAdapter, } from "./adapter-call.js";
|
|
5
|
+
import { gatherProbe, replayOutcome } from "./probe.js";
|
|
9
6
|
import { runPrepare } from "./prepare.js";
|
|
10
7
|
import { runWithMutation } from "./mutation.js";
|
|
11
8
|
import { activationBlock } from "../harness-compatibility.js";
|
|
12
|
-
function writeOutput(output, ctx) {
|
|
9
|
+
export function writeOutput(output, ctx) {
|
|
13
10
|
for (const line of output.stdout)
|
|
14
11
|
ctx.stdout.write(`${line}\n`);
|
|
15
12
|
for (const line of output.stderr)
|
|
16
13
|
ctx.stderr.write(`${line}\n`);
|
|
17
14
|
}
|
|
18
|
-
async function invoke(call, failure, outcomes,
|
|
19
|
-
let result;
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
!Array.isArray(result.outcome.messages) ||
|
|
28
|
-
!result.outcome.messages.every((message) => message &&
|
|
29
|
-
(message.channel === "stdout" || message.channel === "stderr") &&
|
|
30
|
-
typeof message.text === "string") ||
|
|
31
|
-
(result.outcome.ok
|
|
32
|
-
? result.outcome.error !== null || !valid(result.outcome.result)
|
|
33
|
-
: !result.outcome.error ||
|
|
34
|
-
typeof result.outcome.error.code !== "string" ||
|
|
35
|
-
typeof result.outcome.error.message !== "string" ||
|
|
36
|
-
!Array.isArray(result.outcome.error.hints) ||
|
|
37
|
-
!result.outcome.error.hints.every((hint) => typeof hint === "string"))) {
|
|
38
|
-
return { ok: false, message: failure.invalidStatus, result: null };
|
|
15
|
+
async function invoke(call, failure, outcomes, acceptValue) {
|
|
16
|
+
let result = await callAdapter(call, failure);
|
|
17
|
+
if (result.ok && acceptValue !== undefined) {
|
|
18
|
+
try {
|
|
19
|
+
if (!acceptValue(result.result.outcome.result))
|
|
20
|
+
result = { ok: false, result: null, message: failure.invalidStatus };
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
result = { ok: false, result: null, message: failure.invalidStatus };
|
|
39
24
|
}
|
|
40
25
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
outcomes.push(result.outcome);
|
|
45
|
-
const outcome = result.outcome;
|
|
46
|
-
if (result.status !== 0 || !outcome.ok) {
|
|
47
|
-
return {
|
|
48
|
-
ok: false,
|
|
49
|
-
message: outcome.ok ? failure.invalidStatus : null,
|
|
50
|
-
result,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
return {
|
|
54
|
-
ok: true,
|
|
55
|
-
result: { status: result.status, outcome },
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
function validReceipt(value) {
|
|
59
|
-
return (value !== null &&
|
|
60
|
-
typeof value === "object" &&
|
|
61
|
-
validOutput(value.missingVerificationOutput) &&
|
|
62
|
-
validOutput(value.mismatchVerificationOutput));
|
|
63
|
-
}
|
|
64
|
-
// withWorkspace can throw AFTER its callback has already returned a fully
|
|
65
|
-
// computed StageOutcome: a post-success cleanup failure discards that return
|
|
66
|
-
// value entirely and rejects instead, UNLESS an `onCleanupFailure` reporter is
|
|
67
|
-
// supplied -- which gatherInstallStages below does, precisely so this class
|
|
68
|
-
// stays reserved for mkdtemp failure (nothing collected yet) and the
|
|
69
|
-
// callback's own throw (never reachable here; see gatherInstallStages).
|
|
70
|
-
// Carries the outcomes collected so far so runInstall's catch can still
|
|
71
|
-
// replay them instead of discarding them with a bare re-throw. Same shape as
|
|
72
|
-
// src/commands/uninstall.ts's GatherFailure, duplicated for the same reason
|
|
73
|
-
// invoke() is: no shared dependency between the two modules.
|
|
74
|
-
class GatherFailure extends Error {
|
|
75
|
-
inner;
|
|
76
|
-
outcomes;
|
|
77
|
-
constructor(inner, outcomes) {
|
|
78
|
-
super("install gather failed");
|
|
79
|
-
this.inner = inner;
|
|
80
|
-
this.outcomes = outcomes;
|
|
81
|
-
}
|
|
26
|
+
if (result.result !== null)
|
|
27
|
+
outcomes.push(result.result.outcome);
|
|
28
|
+
return result;
|
|
82
29
|
}
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
// writes of its own -- same EPIPE-avoidance shape as gatherProbe and
|
|
86
|
-
// src/commands/uninstall.ts's gatherUninstall.
|
|
30
|
+
// Collect outcomes without writing so output failures cannot be classified as
|
|
31
|
+
// inspection failures. Replay collected outcomes after gathering completes.
|
|
87
32
|
async function gatherInstallStages(ctx, selection, artifact) {
|
|
88
|
-
// `git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/install:38::tmp_parent=` -- ${TMPDIR:-/tmp}. Matches
|
|
89
|
-
// src/commands/uninstall.ts's gatherUninstall.
|
|
90
33
|
const parent = ctx.env.TMPDIR ?? tmpdir();
|
|
91
34
|
const outcomes = [];
|
|
92
35
|
let cleanupWarning = null;
|
|
@@ -133,7 +76,7 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
133
76
|
if (activationFailure !== null)
|
|
134
77
|
return failed(activationFailure);
|
|
135
78
|
const installFailure = ctx.adapter.presentation.callFailure("install", adapterContext);
|
|
136
|
-
const install = await invoke(() => ctx.adapter.install(artifact, adapterContext), installFailure, outcomes
|
|
79
|
+
const install = await invoke(() => ctx.adapter.install(artifact, adapterContext), installFailure, outcomes);
|
|
137
80
|
if (!install.ok)
|
|
138
81
|
return failed(install.message);
|
|
139
82
|
let transaction;
|
|
@@ -163,9 +106,8 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
163
106
|
// is what turns a failed inspection into "error: installed manager
|
|
164
107
|
// fingerprint inspection failed after install."
|
|
165
108
|
// renderInstallVerification's failed-inspection arm
|
|
166
|
-
// (`src/harnesses/codex/presentation.ts:
|
|
167
|
-
// exists for this result-bearing path
|
|
168
|
-
// export delegates through the same arm. Returning
|
|
109
|
+
// (`src/harnesses/codex/presentation.ts:280::if (inspection.status !== 0 || !inspection.outcome.ok) {`)
|
|
110
|
+
// exists for this result-bearing path. Returning
|
|
169
111
|
// failed() instead reported the adapter's own generic diagnostic and
|
|
170
112
|
// dropped the post-install verification claim -- a mutation had
|
|
171
113
|
// already been issued at stage 3,
|
|
@@ -173,7 +115,7 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
173
115
|
// adapter call failed". A ctx.adapter THROW has no result to render.
|
|
174
116
|
// A pending transaction still rolls back before reporting that failure.
|
|
175
117
|
const inspectionFailure = ctx.adapter.presentation.callFailure("post-install", adapterContext);
|
|
176
|
-
const inspected = await invoke(() => ctx.adapter.inspectInstalled(selection, adapterContext), inspectionFailure, outcomes
|
|
118
|
+
const inspected = await invoke(() => ctx.adapter.inspectInstalled(selection, adapterContext), inspectionFailure, outcomes);
|
|
177
119
|
const inspection = inspected.result;
|
|
178
120
|
const verified = inspection !== null &&
|
|
179
121
|
inspection.status === 0 &&
|
|
@@ -186,7 +128,7 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
186
128
|
invalidStatus: `installation ${operation} returned an invalid result; preserve recovery material for manual resolution`,
|
|
187
129
|
}, outcomes, (value) => value === null);
|
|
188
130
|
if (!verified) {
|
|
189
|
-
const restored = await invoke(() => ctx.adapter.inspectInstalled(selection, adapterContext), inspectionFailure, outcomes
|
|
131
|
+
const restored = await invoke(() => ctx.adapter.inspectInstalled(selection, adapterContext), inspectionFailure, outcomes);
|
|
190
132
|
const restoration = restored.ok
|
|
191
133
|
? `installed state after rollback: ${restored.result.outcome.result.kind}; identity=${restored.result.outcome.result.observedIdentity}`
|
|
192
134
|
: "installed state after rollback could not be inspected";
|
|
@@ -217,7 +159,7 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
217
159
|
};
|
|
218
160
|
}
|
|
219
161
|
if (!settlement.ok) {
|
|
220
|
-
const actual = await invoke(() => ctx.adapter.inspectInstalled(selection, adapterContext), inspectionFailure, outcomes
|
|
162
|
+
const actual = await invoke(() => ctx.adapter.inspectInstalled(selection, adapterContext), inspectionFailure, outcomes);
|
|
221
163
|
return {
|
|
222
164
|
kind: "verified",
|
|
223
165
|
outcomes,
|
|
@@ -260,7 +202,7 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
260
202
|
// Reachable only for mkdtemp failure (nothing collected yet) -- the
|
|
261
203
|
// callback above never throws, so a post-success cleanup failure is
|
|
262
204
|
// already handled by onCleanupFailure and cannot reach here.
|
|
263
|
-
throw new GatherFailure(cause, outcomes);
|
|
205
|
+
throw new GatherFailure("install gather failed", cause, outcomes);
|
|
264
206
|
}
|
|
265
207
|
}
|
|
266
208
|
export async function runInstall(argv, ctx) {
|
|
@@ -277,31 +219,8 @@ async function performInstall(argv, ctx) {
|
|
|
277
219
|
probe = await gatherProbe(ctx);
|
|
278
220
|
}
|
|
279
221
|
catch (cause) {
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
// output: the NOTE line above already left the try, and everything below
|
|
283
|
-
// runs only after this try/catch has resolved.
|
|
284
|
-
//
|
|
285
|
-
// This is a SECOND consumer of gatherProbe's throw channel --
|
|
286
|
-
// `src/commands/probe.ts:469-527::THREE exceptions, all inherited and none a regression:`'s
|
|
287
|
-
// runProbe catch is the first. Because both consumers wrap the identical
|
|
288
|
-
// function, its long comment there enumerates exactly what can reach THIS
|
|
289
|
-
// stream too, including the three foreign-text exceptions at :251-296:
|
|
290
|
-
// 1. :251-262 -- resolveRef splices git's own combined stdout+stderr
|
|
291
|
-
// into its text. Reached on probe's DEFAULT path, which that comment
|
|
292
|
-
// defines as every invocation NOT resolving a saved pin: a 40-hex
|
|
293
|
-
// ref returns a "raw-commit" resolution at
|
|
294
|
-
// `src/upstream.ts:162-164::return { kind: "raw-commit"`
|
|
295
|
-
// before any git call, so it reaches no splice at all.
|
|
296
|
-
// 2. :263-280 -- src/selection-store.ts's read path interpolates the
|
|
297
|
-
// caught error's own message, so Node errno prose can appear.
|
|
298
|
-
// AGENTS.md grandfathers that module's wording.
|
|
299
|
-
// 3. :281-296 -- a SPAWN-level git failure, a different channel from
|
|
300
|
-
// exception 1's exit-status one: on the non-ENOENT arm of
|
|
301
|
-
// `src/git.ts:47-52::if (typeof failure.code === "string") {`, runGit
|
|
302
|
-
// rejects with "cannot run git: " followed by the Node spawn error's
|
|
303
|
-
// own message.
|
|
304
|
-
// Not repeated in full here; read it there.
|
|
222
|
+
// Gathering does not write, so this catch cannot misclassify an output
|
|
223
|
+
// failure. oneLine() bounds any inherited diagnostic to one line.
|
|
305
224
|
ctx.stderr.write(`error: ${oneLine(cause)}\n`);
|
|
306
225
|
return 1;
|
|
307
226
|
}
|