superpowers-manager 0.6.0 → 0.7.1
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 +42 -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 +25 -40
- package/dist/commands/adapter-call.js +28 -0
- package/dist/commands/install.js +25 -105
- package/dist/commands/pin.js +1 -1
- package/dist/commands/prepare.js +37 -81
- package/dist/commands/probe.js +35 -262
- package/dist/commands/track-latest.js +1 -1
- package/dist/commands/uninstall.js +15 -91
- package/dist/commands/update.js +5 -25
- package/dist/effective-selection.js +4 -6
- package/dist/git-tree.js +85 -0
- package/dist/harness-command-result.js +47 -0
- package/dist/harnesses/codex/adapter.js +12 -41
- package/dist/harnesses/codex/conflicts.js +2 -3
- package/dist/harnesses/codex/hooks.js +1 -1
- package/dist/harnesses/codex/json.js +1 -11
- package/dist/harnesses/codex/lifecycle.js +0 -48
- package/dist/harnesses/codex/manifest-overlay.js +1 -1
- package/dist/harnesses/codex/paths.js +14 -29
- package/dist/harnesses/codex/presentation.js +2 -23
- package/dist/harnesses/codex/publication.js +0 -6
- 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 +617 -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/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 +4 -1
- 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/status.js +0 -24
- 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,16 +127,18 @@ 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
|
|
|
128
144
|
Generated Codex manifests preserve upstream JSON number tokens while rejecting non-finite decimal or exponent values.
|
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";
|
|
@@ -47,7 +48,7 @@ const IN_PROCESS_HANDLERS = {
|
|
|
47
48
|
// per adapter call
|
|
48
49
|
// (`git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/adapter.sh:37-44::--response "$response_file" --result "$result_file" \`);
|
|
49
50
|
// the in-process path no longer uses that adapter-response validator.
|
|
50
|
-
// No command has a Python requirement in
|
|
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"],
|
|
@@ -178,38 +179,22 @@ function sharedRequirement(name) {
|
|
|
178
179
|
missingMessage: `required command not found: ${name} — install ${name} and re-run`,
|
|
179
180
|
};
|
|
180
181
|
}
|
|
181
|
-
function
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
pin: forCommand("pin"),
|
|
188
|
-
"track-latest": forCommand("track-latest"),
|
|
189
|
-
unpin: forCommand("unpin"),
|
|
190
|
-
prepare: forCommand("prepare"),
|
|
191
|
-
probe: forCommand("probe"),
|
|
192
|
-
install: forCommand("install"),
|
|
193
|
-
update: forCommand("update"),
|
|
194
|
-
uninstall: forCommand("uninstall"),
|
|
195
|
-
};
|
|
196
|
-
}
|
|
197
|
-
function commandRequirements(env) {
|
|
198
|
-
return Object.fromEntries(SUBCOMMANDS.map((command) => [
|
|
199
|
-
command,
|
|
200
|
-
commandRequirementsFor(env, codexHarness)[command].map((requirement) => requirement.name),
|
|
201
|
-
]));
|
|
182
|
+
function requirementsFor(command, env, adapter) {
|
|
183
|
+
return [
|
|
184
|
+
...SHARED_COMMAND_REQUIREMENTS[command].map(sharedRequirement),
|
|
185
|
+
...adapter.requirements(command, env),
|
|
186
|
+
];
|
|
202
187
|
}
|
|
203
188
|
// Preflight; never touches Codex state. It is the union of two exported
|
|
204
189
|
// accessors: configurationErrors (validator configuration) and
|
|
205
|
-
//
|
|
190
|
+
// requirementsFor (tool availability), both specific to the selected
|
|
206
191
|
// command. No command requires a POSIX shell: slice 4b flipped the last
|
|
207
192
|
// spawned command in-process, so there is no shell to discover.
|
|
208
|
-
function
|
|
193
|
+
function preflight(cmd, env, platform, adapter) {
|
|
209
194
|
const errors = [...configurationErrors(cmd, env)];
|
|
210
195
|
if (errors.length > 0)
|
|
211
196
|
return { ok: false, errors };
|
|
212
|
-
for (const requirement of
|
|
197
|
+
for (const requirement of requirementsFor(cmd, env, adapter)) {
|
|
213
198
|
const found = requirement.lookup === "explicit-path-or-path" &&
|
|
214
199
|
requirement.executable.includes(path.sep)
|
|
215
200
|
? fs.existsSync(requirement.executable)
|
|
@@ -221,9 +206,6 @@ function preflightFor(cmd, env, platform, adapter) {
|
|
|
221
206
|
return { ok: false, errors };
|
|
222
207
|
return { ok: true };
|
|
223
208
|
}
|
|
224
|
-
function preflight(cmd, env, platform) {
|
|
225
|
-
return preflightFor(cmd, env, platform, codexHarness);
|
|
226
|
-
}
|
|
227
209
|
function usage() {
|
|
228
210
|
return [
|
|
229
211
|
"usage: superpowers-manager [command] [args...]",
|
|
@@ -241,19 +223,20 @@ function usage() {
|
|
|
241
223
|
" uninstall remove the manager plugin and marketplace from Codex",
|
|
242
224
|
"",
|
|
243
225
|
"Target one invocation (default: codex; selection commands are shared):",
|
|
244
|
-
" probe --harness
|
|
245
|
-
" prepare --harness
|
|
246
|
-
" install --harness
|
|
247
|
-
" update --harness
|
|
248
|
-
" uninstall --harness
|
|
249
|
-
" --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.",
|
|
250
232
|
"",
|
|
251
233
|
"Environment overrides (used by in-process commands): SUPERPOWERS_REF,",
|
|
252
234
|
"SUPERPOWERS_UPSTREAM_URL, SUPERPOWERS_CODEX, SUPERPOWERS_CACHE_DIR,",
|
|
253
235
|
"SUPERPOWERS_CONFIG_DIR, XDG_CONFIG_HOME,",
|
|
254
236
|
"SUPERPOWERS_PLUGIN_ROOT, SUPERPOWERS_MANIFEST_TEMPLATE,",
|
|
255
237
|
"SUPERPOWERS_VALIDATOR_EXECUTABLE,",
|
|
256
|
-
"SUPERPOWERS_INSTALLED_SEARCH_ROOT, SUPERPOWERS_INSTALL_REFRESH_MODE",
|
|
238
|
+
"SUPERPOWERS_INSTALLED_SEARCH_ROOT, SUPERPOWERS_INSTALL_REFRESH_MODE,",
|
|
239
|
+
"SUPERPOWERS_OPENCODE",
|
|
257
240
|
"",
|
|
258
241
|
"SUPERPOWERS_VALIDATOR is removed; unset it and use",
|
|
259
242
|
"SUPERPOWERS_VALIDATOR_EXECUTABLE with an executable validator.",
|
|
@@ -283,9 +266,11 @@ async function main() {
|
|
|
283
266
|
console.error(usage());
|
|
284
267
|
process.exit(2);
|
|
285
268
|
}
|
|
286
|
-
const status = parsed.options.harness === "
|
|
287
|
-
? await dispatch(
|
|
288
|
-
:
|
|
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);
|
|
289
274
|
process.exit(status);
|
|
290
275
|
}
|
|
291
276
|
async function dispatch(adapter, parsed) {
|
|
@@ -294,7 +279,7 @@ async function dispatch(adapter, parsed) {
|
|
|
294
279
|
console.error("error: cannot resolve the superpowers-manager package root");
|
|
295
280
|
return 1;
|
|
296
281
|
}
|
|
297
|
-
const pf =
|
|
282
|
+
const pf = preflight(parsed.cmd, process.env, process.platform, adapter);
|
|
298
283
|
if (!pf.ok) {
|
|
299
284
|
for (const e of pf.errors)
|
|
300
285
|
console.error(`error: ${e}`);
|
|
@@ -336,7 +321,7 @@ async function dispatch(adapter, parsed) {
|
|
|
336
321
|
}
|
|
337
322
|
return status;
|
|
338
323
|
}
|
|
339
|
-
export { resolvePackageRoot, isMain, parseArgs,
|
|
324
|
+
export { resolvePackageRoot, isMain, parseArgs, requirementsFor, preflight, usage, main, };
|
|
340
325
|
if (isMain(import.meta.filename, process.argv[1])) {
|
|
341
326
|
main().catch((cause) => {
|
|
342
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,7 +106,7 @@ 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:
|
|
109
|
+
// (`src/harnesses/codex/presentation.ts:280::if (inspection.status !== 0 || !inspection.outcome.ok) {`)
|
|
167
110
|
// exists for this result-bearing path. Returning
|
|
168
111
|
// failed() instead reported the adapter's own generic diagnostic and
|
|
169
112
|
// dropped the post-install verification claim -- a mutation had
|
|
@@ -172,7 +115,7 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
172
115
|
// adapter call failed". A ctx.adapter THROW has no result to render.
|
|
173
116
|
// A pending transaction still rolls back before reporting that failure.
|
|
174
117
|
const inspectionFailure = ctx.adapter.presentation.callFailure("post-install", adapterContext);
|
|
175
|
-
const inspected = await invoke(() => ctx.adapter.inspectInstalled(selection, adapterContext), inspectionFailure, outcomes
|
|
118
|
+
const inspected = await invoke(() => ctx.adapter.inspectInstalled(selection, adapterContext), inspectionFailure, outcomes);
|
|
176
119
|
const inspection = inspected.result;
|
|
177
120
|
const verified = inspection !== null &&
|
|
178
121
|
inspection.status === 0 &&
|
|
@@ -185,7 +128,7 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
185
128
|
invalidStatus: `installation ${operation} returned an invalid result; preserve recovery material for manual resolution`,
|
|
186
129
|
}, outcomes, (value) => value === null);
|
|
187
130
|
if (!verified) {
|
|
188
|
-
const restored = await invoke(() => ctx.adapter.inspectInstalled(selection, adapterContext), inspectionFailure, outcomes
|
|
131
|
+
const restored = await invoke(() => ctx.adapter.inspectInstalled(selection, adapterContext), inspectionFailure, outcomes);
|
|
189
132
|
const restoration = restored.ok
|
|
190
133
|
? `installed state after rollback: ${restored.result.outcome.result.kind}; identity=${restored.result.outcome.result.observedIdentity}`
|
|
191
134
|
: "installed state after rollback could not be inspected";
|
|
@@ -216,7 +159,7 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
216
159
|
};
|
|
217
160
|
}
|
|
218
161
|
if (!settlement.ok) {
|
|
219
|
-
const actual = await invoke(() => ctx.adapter.inspectInstalled(selection, adapterContext), inspectionFailure, outcomes
|
|
162
|
+
const actual = await invoke(() => ctx.adapter.inspectInstalled(selection, adapterContext), inspectionFailure, outcomes);
|
|
220
163
|
return {
|
|
221
164
|
kind: "verified",
|
|
222
165
|
outcomes,
|
|
@@ -259,7 +202,7 @@ async function gatherInstallStages(ctx, selection, artifact) {
|
|
|
259
202
|
// Reachable only for mkdtemp failure (nothing collected yet) -- the
|
|
260
203
|
// callback above never throws, so a post-success cleanup failure is
|
|
261
204
|
// already handled by onCleanupFailure and cannot reach here.
|
|
262
|
-
throw new GatherFailure(cause, outcomes);
|
|
205
|
+
throw new GatherFailure("install gather failed", cause, outcomes);
|
|
263
206
|
}
|
|
264
207
|
}
|
|
265
208
|
export async function runInstall(argv, ctx) {
|
|
@@ -276,31 +219,8 @@ async function performInstall(argv, ctx) {
|
|
|
276
219
|
probe = await gatherProbe(ctx);
|
|
277
220
|
}
|
|
278
221
|
catch (cause) {
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
// output: the NOTE line above already left the try, and everything below
|
|
282
|
-
// runs only after this try/catch has resolved.
|
|
283
|
-
//
|
|
284
|
-
// This is a SECOND consumer of gatherProbe's throw channel --
|
|
285
|
-
// `src/commands/probe.ts:469-527::THREE exceptions, all inherited and none a regression:`'s
|
|
286
|
-
// runProbe catch is the first. Because both consumers wrap the identical
|
|
287
|
-
// function, its long comment there enumerates exactly what can reach THIS
|
|
288
|
-
// stream too, including the three foreign-text exceptions at :251-296:
|
|
289
|
-
// 1. :251-262 -- resolveRef splices git's own combined stdout+stderr
|
|
290
|
-
// into its text. Reached on probe's DEFAULT path, which that comment
|
|
291
|
-
// defines as every invocation NOT resolving a saved pin: a 40-hex
|
|
292
|
-
// ref returns a "raw-commit" resolution at
|
|
293
|
-
// `src/upstream.ts:162-164::return { kind: "raw-commit"`
|
|
294
|
-
// before any git call, so it reaches no splice at all.
|
|
295
|
-
// 2. :263-280 -- src/selection-store.ts's read path interpolates the
|
|
296
|
-
// caught error's own message, so Node errno prose can appear.
|
|
297
|
-
// AGENTS.md grandfathers that module's wording.
|
|
298
|
-
// 3. :281-296 -- a SPAWN-level git failure, a different channel from
|
|
299
|
-
// exception 1's exit-status one: on the non-ENOENT arm of
|
|
300
|
-
// `src/git.ts:47-52::if (typeof failure.code === "string") {`, runGit
|
|
301
|
-
// rejects with "cannot run git: " followed by the Node spawn error's
|
|
302
|
-
// own message.
|
|
303
|
-
// 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.
|
|
304
224
|
ctx.stderr.write(`error: ${oneLine(cause)}\n`);
|
|
305
225
|
return 1;
|
|
306
226
|
}
|
package/dist/commands/pin.js
CHANGED
|
@@ -17,7 +17,7 @@ async function attemptPin(requested, ctx) {
|
|
|
17
17
|
// writing, under "Invalid existing state must block overwrite" —
|
|
18
18
|
// readSelectionState's own readOpenedRecord/parseRecordBytes is what
|
|
19
19
|
// validates an existing record, via validateRecord at
|
|
20
|
-
// `src/selection-store.ts:
|
|
20
|
+
// `src/selection-store.ts:99::return validateRecord`). Calling loadSavedSelection here preserves
|
|
21
21
|
// `git show 349fe2ed405b371ec2de1347bb3fc50c6bc15dc4:scripts/pin:26-27::read`'s read-then-resolve-then-write shape, so this command
|
|
22
22
|
// stays fail-closed on its own terms rather than solely by depending on the
|
|
23
23
|
// store's internals.
|