superpowers-manager 0.1.6 → 0.3.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 +184 -64
- package/bin/superpowers-manager.js +9 -237
- package/dist/adapter-result.js +204 -0
- package/dist/adapter.js +649 -0
- package/dist/atomic.js +142 -0
- package/dist/cli-arguments.js +32 -0
- package/dist/cli.js +293 -0
- package/dist/codex-json.js +89 -0
- package/dist/codex-state.js +78 -0
- package/dist/commands/context.js +1 -0
- package/dist/commands/install.js +376 -0
- package/dist/commands/pin.js +79 -0
- package/dist/commands/prepare.js +527 -0
- package/dist/commands/probe.js +392 -0
- package/dist/commands/track-latest.js +44 -0
- package/dist/commands/uninstall.js +308 -0
- package/dist/commands/unpin.js +95 -0
- package/dist/commands/update.js +138 -0
- package/dist/domain/fingerprint.js +4 -0
- package/dist/domain/refs.js +37 -0
- package/dist/effective-selection.js +123 -0
- package/dist/generated-plugin.js +748 -0
- package/dist/git.js +38 -0
- package/dist/hooks.js +290 -0
- package/dist/lifecycle.js +220 -0
- package/dist/manifest-overlay.js +125 -0
- package/dist/provenance.js +111 -0
- package/dist/python-json-format.js +83 -0
- package/dist/python-json.js +29 -0
- package/dist/python-text.js +74 -0
- package/dist/safe-path.js +137 -0
- package/dist/safety-error.js +10 -0
- package/dist/selection-state-cli.js +85 -0
- package/dist/selection-store.js +172 -0
- package/dist/selection.js +200 -0
- package/dist/status.js +24 -0
- package/dist/strict-json.js +248 -0
- package/dist/upstream-cli.js +110 -0
- package/dist/upstream-version.js +40 -0
- package/dist/upstream.js +253 -0
- package/dist/validate-generated-plugin-cli.js +118 -0
- package/dist/validator.js +317 -0
- package/dist/workspace.js +139 -0
- package/package.json +25 -2
- package/scripts/adapters/codex/adapter +0 -598
- package/scripts/adapters/codex/lib.sh +0 -391
- package/scripts/adapters/codex/validate-generated-plugin.py +0 -314
- package/scripts/core/adapter.sh +0 -92
- package/scripts/core/common.sh +0 -35
- package/scripts/core/lifecycle.sh +0 -127
- package/scripts/core/provenance.sh +0 -135
- package/scripts/core/status.sh +0 -27
- package/scripts/core/upstream.sh +0 -146
- package/scripts/core/validate-adapter-response.py +0 -245
- package/scripts/install +0 -60
- package/scripts/prepare +0 -126
- package/scripts/probe +0 -61
- package/scripts/uninstall +0 -44
- package/scripts/update +0 -31
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ npx superpowers-manager update
|
|
|
19
19
|
Use the official marketplace for the simplest native Codex installation. Use
|
|
20
20
|
Superpowers Manager when you want immediate stable-upstream freshness after a
|
|
21
21
|
user-triggered install/update, per-invocation release or commit selection,
|
|
22
|
-
recorded upstream provenance, diagnostics,
|
|
22
|
+
recorded upstream provenance, diagnostics, upstream-owned Codex hook handling,
|
|
23
23
|
and explicit install/update/probe/uninstall lifecycle control.
|
|
24
24
|
|
|
25
25
|
| Choose | Best fit |
|
|
@@ -39,46 +39,79 @@ It never removes the legacy provider automatically.
|
|
|
39
39
|
|
|
40
40
|
## Requirements and platforms
|
|
41
41
|
|
|
42
|
-
Superpowers Manager requires Node 24+,
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
Superpowers Manager requires Node 24+, and Codex is the only supported
|
|
43
|
+
integration today. Every other requirement is per command, enforced by
|
|
44
|
+
preflight before any dispatch. This table is checked against production by
|
|
45
|
+
`tests/bin/readme-requirements.test.js`; edit it when a command's requirements
|
|
46
|
+
change, in the same pull request.
|
|
47
|
+
|
|
48
|
+
<!-- requirements:begin -->
|
|
49
|
+
| Command | git | Python 3 | Codex CLI |
|
|
50
|
+
|---|---|---|---|
|
|
51
|
+
| `pin` | yes | no | no |
|
|
52
|
+
| `track-latest` | no | no | no |
|
|
53
|
+
| `unpin` | no | no | no |
|
|
54
|
+
| `prepare` | yes | only with SUPERPOWERS_VALIDATOR | no |
|
|
55
|
+
| `probe` | yes | no | yes |
|
|
56
|
+
| `install` | yes | no | yes |
|
|
57
|
+
| `update` | yes | no | yes |
|
|
58
|
+
| `uninstall` | no | no | yes |
|
|
59
|
+
<!-- requirements:end -->
|
|
60
|
+
|
|
61
|
+
See `docs/baseline/behavioral-inventory.md`'s `CLI-PREFLIGHT-01` for the
|
|
62
|
+
behaviour this table describes.
|
|
45
63
|
|
|
46
64
|
macOS and Linux are tested. WSL2 is supported. The native Windows path is
|
|
47
|
-
untested; the launcher looks for Git Bash
|
|
48
|
-
|
|
65
|
+
untested; the launcher no longer looks for Git Bash at all — every command runs
|
|
66
|
+
in-process, so no POSIX shell is discovered or required — but path handling
|
|
67
|
+
between MSYS and Codex remains a known risk area.
|
|
49
68
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
Network needs depend on the saved policy. `pin` verifies its target;
|
|
70
|
+
`track-latest` and invocation overrides resolve from upstream when applied; and
|
|
71
|
+
`prepare`, `install`, and `update` fetch or verify the effective source. A probe
|
|
72
|
+
of a saved exact pin can reuse its recorded identity without upstream access.
|
|
73
|
+
Updates remain user-triggered; the manager does not run automatic or background
|
|
74
|
+
updates.
|
|
53
75
|
|
|
54
76
|
## What it does
|
|
55
77
|
|
|
56
78
|
- Resolves an upstream ref, defaulting to the latest stable `vX.Y.Z` release
|
|
57
79
|
tag.
|
|
58
80
|
- Clones/fetches upstream at that commit and assembles a Codex plugin tree under
|
|
59
|
-
`plugins/superpowers/` (skills, assets, license/readme, and
|
|
60
|
-
|
|
81
|
+
`plugins/superpowers/` (skills, assets, license/readme, manifest, and any
|
|
82
|
+
hook files that the upstream manifest allows).
|
|
61
83
|
- Stamps the generated manifest with a ref-aware manager version ending in
|
|
62
84
|
`+manager.<short-sha>` and writes the upstream provenance to
|
|
63
85
|
`.superpowers-upstream.json`.
|
|
64
|
-
- Validates the generated tree with the manager's shipped
|
|
65
|
-
|
|
66
|
-
|
|
86
|
+
- Validates the generated tree with the manager's shipped contract validator
|
|
87
|
+
before swapping it into place (a failed run never destroys a previously
|
|
88
|
+
generated tree).
|
|
67
89
|
- Registers the `superpowers-manager` marketplace and installs or refreshes
|
|
68
90
|
`superpowers@superpowers-manager` in Codex.
|
|
69
91
|
|
|
70
|
-
The generated plugin carries upstream skills, assets, and
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
92
|
+
The generated plugin carries upstream skills, assets, documentation, and the
|
|
93
|
+
upstream-owned hook policy. When upstream provides a Codex manifest, its
|
|
94
|
+
`hooks` declaration is preserved: exact `{}` suppresses the `hooks/` subtree;
|
|
95
|
+
active declarations conditionally materialize the upstream hook files; and a
|
|
96
|
+
missing `hooks` key or explicit empty array (`[]`) declaration uses Codex's
|
|
97
|
+
default discovery only when upstream ships `hooks/hooks.json`. Manifest-less
|
|
98
|
+
fallback refs use the committed minimal template and generate neither a manifest
|
|
99
|
+
`hooks` key nor a `hooks/` directory.
|
|
100
|
+
The manager never creates its own hooks or mutates Codex trust state.
|
|
101
|
+
|
|
102
|
+
Review upstream hook command definitions with Codex's `/hooks` flow. Codex
|
|
103
|
+
trust applies to a command definition, not to the contents of scripts that the
|
|
104
|
+
definition references. An explicit update of an older pin can therefore change
|
|
105
|
+
the contents of a trusted referenced script, or reveal a newly untrusted
|
|
106
|
+
upstream definition; review it before trusting or running it. This policy
|
|
107
|
+
eliminates latent hook-policy drift.
|
|
76
108
|
|
|
77
109
|
## Runtime architecture
|
|
78
110
|
|
|
79
|
-
- `
|
|
80
|
-
|
|
81
|
-
|
|
111
|
+
- The in-process TypeScript runtime under `src/` owns lifecycle dispatch,
|
|
112
|
+
status, and adapter-result handling;
|
|
113
|
+
see the [in-process adapter result contract](https://github.com/j7an/superpowers-manager/blob/main/docs/adapter-result-contract.md).
|
|
114
|
+
- `src/adapter.ts` owns build, inspection, reconciliation, and Codex mutation.
|
|
82
115
|
- Codex is the only supported adapter today; no public harness selector ships
|
|
83
116
|
yet.
|
|
84
117
|
|
|
@@ -92,10 +125,33 @@ after the built-in check. It receives the candidate plugin root as its only
|
|
|
92
125
|
argument. It cannot replace or bypass built-in validation, and either check
|
|
93
126
|
failing prevents the tree swap and all Codex mutation.
|
|
94
127
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
128
|
+
`SUPERPOWERS_VALIDATOR_EXECUTABLE=/path/to/validator` adds an optional external
|
|
129
|
+
validator after the built-in check. It names an executable, receiving the
|
|
130
|
+
candidate plugin root as its only argument. The manager constructs no shell
|
|
131
|
+
command string and supplies argv directly. On Linux, `execvp` may hand that
|
|
132
|
+
same argv to `/bin/sh` after `ENOEXEC`, and the values are preserved rather
|
|
133
|
+
than re-parsed, so a candidate path containing spaces or shell metacharacters
|
|
134
|
+
is passed through intact and cannot inject a command — on either platform. A
|
|
135
|
+
file with the exec bit set but no valid executable format is handled by the
|
|
136
|
+
platform's own `execvp` fallback, not by the manager: on Linux, `/bin/sh` runs
|
|
137
|
+
it as shell source; on darwin, it is
|
|
138
|
+
rejected outright as not runnable. Give the validator a shebang line or make
|
|
139
|
+
it a real binary, or its behavior will differ by platform. Exit 0 accepts;
|
|
140
|
+
any nonzero exit rejects and prevents the tree swap. The manager stops
|
|
141
|
+
waiting after 30 seconds and keeps at most 64 KiB of each of its output
|
|
142
|
+
streams. It cannot replace or bypass built-in validation, and it is not a
|
|
143
|
+
security boundary: it runs with the manager's own privileges, by design.
|
|
144
|
+
|
|
145
|
+
`SUPERPOWERS_VALIDATOR` is deprecated in favor of `SUPERPOWERS_VALIDATOR_EXECUTABLE`
|
|
146
|
+
and continues to work unchanged. The legacy variable requires `python3`; the
|
|
147
|
+
executable hook does not. Setting both is rejected at preflight, before any
|
|
148
|
+
network or Codex access, rather than silently resolved. Removal of the legacy
|
|
149
|
+
variable is deferred to a future major release.
|
|
150
|
+
|
|
151
|
+
The in-process install command keeps harness-specific validation in the Codex
|
|
152
|
+
adapter: phase 1 prepares the exact candidate first, then the adapter performs
|
|
153
|
+
its Codex and refresh-mode preflight before any Codex mutation. The CLI runs
|
|
154
|
+
its Codex preflight before invoking the lifecycle handler.
|
|
99
155
|
|
|
100
156
|
## Provider ownership
|
|
101
157
|
|
|
@@ -121,6 +177,9 @@ manager will not remove it for you.
|
|
|
121
177
|
|
|
122
178
|
| Command | Codex side effects | Purpose |
|
|
123
179
|
|---|---|---|
|
|
180
|
+
| `npx superpowers-manager pin REF` | None | Resolve and save an exact upstream release tag or full commit |
|
|
181
|
+
| `npx superpowers-manager track-latest` | None | Save a policy that resolves the latest stable release when applied |
|
|
182
|
+
| `npx superpowers-manager unpin` | None | Remove saved selection and return to the packaged fallback policy |
|
|
124
183
|
| `npx superpowers-manager prepare` | None | Resolve upstream, build a staged plugin tree, validate it, and replace the generated tree only on success |
|
|
125
184
|
| `npx superpowers-manager probe` | None | Report requested and resolved refs, desired/generated/installed commits, identity state, and status |
|
|
126
185
|
| `npx superpowers-manager install` | Marketplace and plugin state | Prepare and validate, register the marketplace, install the plugin, and verify installed state |
|
|
@@ -136,43 +195,91 @@ when resulting manager state cannot be verified.
|
|
|
136
195
|
|
|
137
196
|
## Choosing the upstream version
|
|
138
197
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
records the requested ref, resolved ref, and exact commit, but does not persist
|
|
142
|
-
the selection for a later invocation that omits `SUPERPOWERS_REF`.
|
|
198
|
+
Save a tag pin, commit pin, or explicit latest-stable policy for future
|
|
199
|
+
invocations:
|
|
143
200
|
|
|
144
|
-
|
|
145
|
-
|
|
201
|
+
```sh
|
|
202
|
+
npx superpowers-manager pin v6.1.1
|
|
203
|
+
npx superpowers-manager pin 0123456789abcdef0123456789abcdef01234567 # replace with a real upstream commit
|
|
204
|
+
npx superpowers-manager track-latest
|
|
205
|
+
npx superpowers-manager unpin
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The selection commands save intent only; they do not prepare generated content
|
|
209
|
+
or change Codex state. Run `npx superpowers-manager install` for a first
|
|
210
|
+
installation or `npx superpowers-manager update` to apply the saved policy to
|
|
211
|
+
an existing installation. There is no background updater: selection changes
|
|
212
|
+
take effect only through a user-triggered `prepare`, `install`, or `update`.
|
|
213
|
+
|
|
214
|
+
`pin` accepts only an exact `vMAJOR.MINOR.PATCH` tag (optionally with a SemVer
|
|
215
|
+
prerelease suffix) or a full 40-character commit. It resolves and verifies the
|
|
216
|
+
target before saving both the exact commit identity and its source. The saved
|
|
217
|
+
identity is the contract, not the contents of a clone cache: clearing or moving
|
|
218
|
+
`SUPERPOWERS_CACHE_DIR` does not change the selection. `track-latest` saves the
|
|
219
|
+
source and resolves the highest stable `vX.Y.Z` tag each time the policy is
|
|
220
|
+
applied. `unpin` removes only the saved selection and restores the packaged
|
|
221
|
+
`config/upstream-ref` fallback, currently `latest-release`.
|
|
222
|
+
|
|
223
|
+
The user-wide selection file is `selection.json` under the first configured
|
|
224
|
+
location:
|
|
146
225
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
226
|
+
1. `$SUPERPOWERS_CONFIG_DIR`
|
|
227
|
+
2. `$XDG_CONFIG_HOME/superpowers-manager`
|
|
228
|
+
3. `$HOME/.config/superpowers-manager`
|
|
229
|
+
|
|
230
|
+
Each location must be absolute. The ref and source precedence chains are
|
|
231
|
+
independent:
|
|
232
|
+
|
|
233
|
+
- Ref: `SUPERPOWERS_REF`, then saved selection, then `config/upstream-ref`.
|
|
234
|
+
- Source: `SUPERPOWERS_UPSTREAM_URL`, then saved source, then the official
|
|
235
|
+
`https://github.com/obra/superpowers` source.
|
|
236
|
+
|
|
237
|
+
`SUPERPOWERS_REF` is an invocation-only override and is not persisted. Unlike
|
|
238
|
+
an exact saved pin, it accepts stable tags, full commit SHAs, branches, and
|
|
239
|
+
other resolvable upstream refs for that invocation:
|
|
151
240
|
|
|
152
241
|
```sh
|
|
153
|
-
SUPERPOWERS_REF=
|
|
154
|
-
SUPERPOWERS_REF=main npx superpowers-manager probe
|
|
155
|
-
SUPERPOWERS_REF=feature/foo npx superpowers-manager install
|
|
156
|
-
SUPERPOWERS_REF=latest-release npx superpowers-manager update
|
|
242
|
+
SUPERPOWERS_REF=feature/foo npx superpowers-manager probe
|
|
157
243
|
```
|
|
158
244
|
|
|
159
|
-
`
|
|
160
|
-
|
|
161
|
-
|
|
245
|
+
`pin` and `track-latest` bind the current `SUPERPOWERS_UPSTREAM_URL`, or the
|
|
246
|
+
official source when that variable is unset, to the saved intent. An invocation
|
|
247
|
+
may override only the ref or only the source; `probe` exposes
|
|
248
|
+
`selection_origin`, `selection_mode`, `upstream_source_origin`,
|
|
249
|
+
`effective_source`, the saved-selection fields, and a mixed-origin warning in
|
|
250
|
+
human output so that combination remains visible.
|
|
251
|
+
|
|
252
|
+
A saved exact pin lets `probe` determine the desired upstream identity without
|
|
253
|
+
contacting Git, even when the saved source is temporarily unavailable. This is
|
|
254
|
+
not a general offline installation promise: `pin` verifies the requested
|
|
255
|
+
target, `track-latest` must resolve when applied, and `prepare`/`install`/`update`
|
|
256
|
+
must fetch or verify content from the effective source. Codex inspection is
|
|
257
|
+
still required for lifecycle status.
|
|
258
|
+
|
|
259
|
+
HTTP(S) upstream URLs containing userinfo are now rejected, including
|
|
260
|
+
token-only userinfo. This is an intentional compatibility break: use a Git
|
|
261
|
+
credential helper for HTTP(S) authentication or an SSH source instead of
|
|
262
|
+
embedding credentials in the URL.
|
|
263
|
+
|
|
264
|
+
Saved selection does not claim or transfer provider ownership. The manager
|
|
265
|
+
continues to mutate only `superpowers@superpowers-manager` and never removes or
|
|
266
|
+
updates the official provider or any other provider automatically.
|
|
162
267
|
|
|
163
268
|
## How versioning works
|
|
164
269
|
|
|
165
270
|
- **Upstream manifest first:** when upstream provides
|
|
166
271
|
`.codex-plugin/plugin.json`, `prepare` uses it as the generated manifest base
|
|
167
|
-
so future upstream metadata fields
|
|
272
|
+
so future upstream metadata fields, including its hook declaration, are
|
|
273
|
+
preserved by default.
|
|
168
274
|
- **Fallback template:** `plugins/superpowers/.codex-plugin/plugin.template.json`
|
|
169
275
|
is committed as a minimal fallback for older upstream refs that do not ship a
|
|
170
276
|
Codex manifest. It carries the placeholder version
|
|
171
|
-
`0.0.0+manager.template
|
|
277
|
+
`0.0.0+manager.template`, declares no hooks, and produces no `hooks/`
|
|
278
|
+
directory.
|
|
172
279
|
- **Manager overlay:** `prepare` replaces the version with a ref-aware manager
|
|
173
|
-
version
|
|
174
|
-
|
|
175
|
-
|
|
280
|
+
version and forces `skills` to `./skills/`. Unknown upstream manifest fields,
|
|
281
|
+
including `hooks`, remain preserved; the adapter materializes hook files only
|
|
282
|
+
according to the upstream declaration.
|
|
176
283
|
- Stable tags generate release-looking versions such as
|
|
177
284
|
`6.0.3+manager.896224c`; explicit prerelease tags generate versions such as
|
|
178
285
|
`6.1.0-beta.1+manager.abc1234`.
|
|
@@ -202,11 +309,16 @@ persist ref selection or trigger updates.
|
|
|
202
309
|
## Tests
|
|
203
310
|
|
|
204
311
|
```sh
|
|
312
|
+
pnpm install --frozen-lockfile
|
|
313
|
+
pnpm run build
|
|
205
314
|
sh tests/run.sh # Layers 1-3: host-side hermetic checks while iterating
|
|
206
315
|
sh tests/container.sh # Layers 1-4: blocking Docker acceptance command
|
|
207
316
|
sh tests/manual/codex-behavior-probe.sh # optional native-only compatibility residue
|
|
208
317
|
```
|
|
209
318
|
|
|
319
|
+
`pnpm run build` deletes `dist/` before compiling, so every build is a clean
|
|
320
|
+
rebuild and stale artifacts of deleted or renamed modules cannot survive.
|
|
321
|
+
|
|
210
322
|
Layers 1-3 stay offline and hermetic: they use a fake local upstream repo plus
|
|
211
323
|
host-side fixtures, and they perform no mutation of the developer's or runner's
|
|
212
324
|
real Codex state.
|
|
@@ -219,28 +331,36 @@ an isolated container home with networking disabled. That container run may
|
|
|
219
331
|
mutate the throwaway container-local Codex state, but it still performs no
|
|
220
332
|
mutation of the developer's or runner's real Codex state.
|
|
221
333
|
|
|
334
|
+
The container probe uses Codex's `hooks/list` only as compatibility evidence
|
|
335
|
+
for the tested Codex build. It is not a stable Superpowers Manager API.
|
|
336
|
+
|
|
222
337
|
The manual probe is opt-in and covers native-only compatibility residue such as
|
|
223
|
-
path/cache behavior against an intentionally real local
|
|
224
|
-
not part of acceptance. GitHub Actions runs the
|
|
225
|
-
command on pull requests
|
|
338
|
+
path/cache and version-precedence behavior against an intentionally real local
|
|
339
|
+
Codex install. It is not part of acceptance. GitHub Actions runs both the
|
|
340
|
+
toolchain check and the blocking container acceptance command on pull requests
|
|
341
|
+
and pushes to `main`.
|
|
226
342
|
|
|
227
343
|
## Repository layout
|
|
228
344
|
|
|
229
345
|
```
|
|
230
346
|
.agents/plugins/marketplace.json # local marketplace definition (tracked)
|
|
231
|
-
|
|
347
|
+
.github/ # CI, security, dependency, and release workflows (tracked)
|
|
348
|
+
bin/superpowers-manager.js # public thin loader for dist/cli.js
|
|
349
|
+
config/upstream-ref # packaged upstream fallback policy (tracked)
|
|
350
|
+
docs/ # adapter result contract and baseline evidence (tracked)
|
|
351
|
+
package.json pnpm-lock.yaml tsconfig.json # package contract and locked toolchain (tracked)
|
|
232
352
|
plugins/superpowers/
|
|
233
|
-
.codex-plugin/plugin.template.json
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
.cache/upstream/ # upstream clone cache (gitignored)
|
|
353
|
+
.codex-plugin/plugin.template.json # fallback manifest template (tracked)
|
|
354
|
+
generated plugin tree # prepared from upstream (gitignored)
|
|
355
|
+
src/ # in-process TypeScript runtime (tracked)
|
|
356
|
+
cli.ts # CLI parsing, preflight, and command dispatch
|
|
357
|
+
commands/ # lifecycle and selection command handlers
|
|
358
|
+
adapter.ts # Codex integration
|
|
359
|
+
tests/ # hermetic suite and manual Codex probe (tracked)
|
|
360
|
+
migration-inventory/ # frozen historical evidence
|
|
361
|
+
dist/ # generated ESM build tree (gitignored)
|
|
243
362
|
```
|
|
244
363
|
|
|
245
364
|
Everything under `plugins/superpowers/` except the fallback manifest template is
|
|
246
|
-
generated by `prepare` and ignored by Git; re-run
|
|
365
|
+
generated by `npx superpowers-manager prepare` and ignored by Git; re-run that
|
|
366
|
+
command to regenerate it.
|
|
@@ -1,244 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// @ts-check
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
import * as path from 'node:path';
|
|
6
|
-
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { existsSync } from "node:fs";
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
* @typedef {'prepare' | 'probe' | 'install' | 'update' | 'uninstall'} Subcommand
|
|
10
|
-
*/
|
|
6
|
+
const cliUrl = new URL("../dist/cli.js", import.meta.url);
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* @typedef {RunParseResult | HelpParseResult | VersionParseResult | UsageErrorParseResult} ParseResult
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @typedef {{ ok: true, shell: string }} PreflightOk
|
|
22
|
-
* @typedef {{ ok: false, errors: string[] }} PreflightError
|
|
23
|
-
* @typedef {PreflightOk | PreflightError} PreflightResult
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @typedef {{ file: string, argv: string[] }} SpawnDescriptor
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
const SUBCOMMANDS = ['prepare', 'probe', 'install', 'update', 'uninstall'];
|
|
31
|
-
const CODEX_SUBCOMMANDS = ['probe', 'install', 'update', 'uninstall'];
|
|
32
|
-
// Mirrors upstream Superpowers' hooks/run-hook.cmd discovery order.
|
|
33
|
-
const GIT_BASH_CANDIDATES = [
|
|
34
|
-
'C:\\Program Files\\Git\\bin\\bash.exe',
|
|
35
|
-
'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
|
|
36
|
-
];
|
|
37
|
-
|
|
38
|
-
// Walk upward from the bin's physical location to the directory containing
|
|
39
|
-
// package.json. realpathSync first: npm/npx expose the bin through a symlink
|
|
40
|
-
// or shim outside the package root.
|
|
41
|
-
/**
|
|
42
|
-
* @param {string} scriptPath
|
|
43
|
-
* @returns {string | null}
|
|
44
|
-
*/
|
|
45
|
-
function resolvePackageRoot(scriptPath) {
|
|
46
|
-
let dir;
|
|
47
|
-
try {
|
|
48
|
-
dir = path.dirname(fs.realpathSync(scriptPath));
|
|
49
|
-
} catch (err) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
for (;;) {
|
|
53
|
-
if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
|
|
54
|
-
const parent = path.dirname(dir);
|
|
55
|
-
if (parent === dir) return null;
|
|
56
|
-
dir = parent;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* @param {string} moduleFilename
|
|
62
|
-
* @param {string | undefined} argvPath
|
|
63
|
-
* @returns {boolean}
|
|
64
|
-
*/
|
|
65
|
-
function isMain(moduleFilename, argvPath) {
|
|
66
|
-
if (!argvPath) return false;
|
|
67
|
-
return moduleFilename === fs.realpathSync(argvPath);
|
|
8
|
+
if (!existsSync(cliUrl)) {
|
|
9
|
+
console.error(
|
|
10
|
+
"dist/ not built — run `pnpm install --frozen-lockfile && pnpm run build`",
|
|
11
|
+
);
|
|
12
|
+
process.exit(1);
|
|
68
13
|
}
|
|
69
14
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
* @returns {ParseResult}
|
|
73
|
-
*/
|
|
74
|
-
function parseArgs(argv) {
|
|
75
|
-
/** @type {string | undefined} */
|
|
76
|
-
const first = argv[0];
|
|
77
|
-
if (argv.length === 0) return { kind: 'run', cmd: 'update', args: [] };
|
|
78
|
-
if (first === '--help' || first === '-h') return { kind: 'help' };
|
|
79
|
-
if (first === '--version') return { kind: 'version' };
|
|
80
|
-
if (first && SUBCOMMANDS.includes(first)) {
|
|
81
|
-
return { kind: 'run', cmd: /** @type {Subcommand} */ (first), args: argv.slice(1) };
|
|
82
|
-
}
|
|
83
|
-
return { kind: 'usage-error', message: `unknown subcommand: ${first}` };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Search env.PATH for an executable named `name`; on win32 also try PATHEXT
|
|
87
|
-
// extensions. Returns the full path or null.
|
|
88
|
-
/**
|
|
89
|
-
* @param {string} name
|
|
90
|
-
* @param {NodeJS.ProcessEnv} env
|
|
91
|
-
* @param {NodeJS.Platform} platform
|
|
92
|
-
* @returns {string | null}
|
|
93
|
-
*/
|
|
94
|
-
function findTool(name, env, platform) {
|
|
95
|
-
const pathVar = env.PATH || env.Path || '';
|
|
96
|
-
const dirs = pathVar.split(path.delimiter).filter(Boolean);
|
|
97
|
-
const exts = platform === 'win32'
|
|
98
|
-
? (env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';')
|
|
99
|
-
: [''];
|
|
100
|
-
for (const dir of dirs) {
|
|
101
|
-
for (const ext of exts) {
|
|
102
|
-
const candidate = path.join(dir, name + ext.toLowerCase());
|
|
103
|
-
try {
|
|
104
|
-
fs.accessSync(candidate, fs.constants.X_OK);
|
|
105
|
-
return candidate;
|
|
106
|
-
} catch (err) { /* keep looking */ }
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// POSIX: `sh` on PATH. Windows: Git Bash at its standard install paths,
|
|
113
|
-
// then `bash` on PATH.
|
|
114
|
-
/**
|
|
115
|
-
* @param {NodeJS.ProcessEnv} env
|
|
116
|
-
* @param {NodeJS.Platform} platform
|
|
117
|
-
* @returns {string | null}
|
|
118
|
-
*/
|
|
119
|
-
function discoverShell(env, platform) {
|
|
120
|
-
if (platform !== 'win32') return findTool('sh', env, platform);
|
|
121
|
-
for (const candidate of GIT_BASH_CANDIDATES) {
|
|
122
|
-
try {
|
|
123
|
-
fs.accessSync(candidate, fs.constants.X_OK);
|
|
124
|
-
return candidate;
|
|
125
|
-
} catch (err) { /* keep looking */ }
|
|
126
|
-
}
|
|
127
|
-
return findTool('bash', env, platform);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Tool preflight; never touches Codex state. codex is required only for the
|
|
131
|
-
// subcommands that mutate or read Codex.
|
|
132
|
-
/**
|
|
133
|
-
* @param {Subcommand} cmd
|
|
134
|
-
* @param {NodeJS.ProcessEnv} env
|
|
135
|
-
* @param {NodeJS.Platform} platform
|
|
136
|
-
* @returns {PreflightResult}
|
|
137
|
-
*/
|
|
138
|
-
function preflight(cmd, env, platform) {
|
|
139
|
-
const errors = [];
|
|
140
|
-
for (const tool of ['git', 'python3']) {
|
|
141
|
-
if (!findTool(tool, env, platform)) {
|
|
142
|
-
errors.push(`required command not found: ${tool} — install ${tool} and re-run`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
const shell = discoverShell(env, platform);
|
|
146
|
-
if (!shell) {
|
|
147
|
-
errors.push(platform === 'win32'
|
|
148
|
-
? 'no POSIX shell found — install Git for Windows (provides bash) or use WSL2'
|
|
149
|
-
: 'required command not found: sh');
|
|
150
|
-
}
|
|
151
|
-
if (CODEX_SUBCOMMANDS.includes(cmd)) {
|
|
152
|
-
const codexBin = env.SUPERPOWERS_CODEX || 'codex';
|
|
153
|
-
// An explicit override may be a path rather than a PATH-resolvable name.
|
|
154
|
-
const found = codexBin.includes(path.sep)
|
|
155
|
-
? fs.existsSync(codexBin)
|
|
156
|
-
: Boolean(findTool(codexBin, env, platform));
|
|
157
|
-
if (!found) {
|
|
158
|
-
errors.push(`required command not found: ${codexBin} — install the Codex CLI or set SUPERPOWERS_CODEX`);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (errors.length) return { ok: false, errors };
|
|
162
|
-
return { ok: true, shell: /** @type {string} */ (shell) };
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// POSIX executes the script directly (#!/bin/sh shebang); Windows cannot
|
|
166
|
-
// spawn extensionless scripts, so the discovered shell runs the script as
|
|
167
|
-
// its first argument.
|
|
168
|
-
/**
|
|
169
|
-
* @param {Subcommand} cmd
|
|
170
|
-
* @param {string[]} args
|
|
171
|
-
* @param {string} root
|
|
172
|
-
* @param {string} shell
|
|
173
|
-
* @param {NodeJS.Platform} platform
|
|
174
|
-
* @returns {SpawnDescriptor}
|
|
175
|
-
*/
|
|
176
|
-
function buildSpawn(cmd, args, root, shell, platform) {
|
|
177
|
-
const script = path.join(root, 'scripts', cmd);
|
|
178
|
-
if (platform === 'win32') {
|
|
179
|
-
return { file: shell, argv: [script, ...args] };
|
|
180
|
-
}
|
|
181
|
-
return { file: script, argv: args };
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function usage() {
|
|
185
|
-
return [
|
|
186
|
-
'usage: superpowers-manager [prepare|probe|install|update|uninstall] [args...]',
|
|
187
|
-
'',
|
|
188
|
-
' prepare fetch the pinned upstream ref and generate the plugin tree',
|
|
189
|
-
' probe report upstream/generated/installed status (accepts --porcelain)',
|
|
190
|
-
' install register this package root as a Codex marketplace and install the plugin',
|
|
191
|
-
' update probe, then prepare/install only if needed (default when no subcommand)',
|
|
192
|
-
' uninstall remove the manager plugin and marketplace from Codex',
|
|
193
|
-
'',
|
|
194
|
-
'Environment overrides (passed through to the scripts): SUPERPOWERS_REF,',
|
|
195
|
-
'SUPERPOWERS_UPSTREAM_URL, SUPERPOWERS_CODEX, SUPERPOWERS_CACHE_DIR,',
|
|
196
|
-
'SUPERPOWERS_PLUGIN_ROOT, SUPERPOWERS_MANIFEST_TEMPLATE,',
|
|
197
|
-
'SUPERPOWERS_VALIDATOR,',
|
|
198
|
-
'SUPERPOWERS_INSTALLED_SEARCH_ROOT, SUPERPOWERS_INSTALL_REFRESH_MODE',
|
|
199
|
-
].join('\n');
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function main() {
|
|
203
|
-
const parsed = parseArgs(process.argv.slice(2));
|
|
204
|
-
if (parsed.kind === 'help') {
|
|
205
|
-
console.log(usage());
|
|
206
|
-
process.exit(0);
|
|
207
|
-
}
|
|
208
|
-
const root = resolvePackageRoot(import.meta.filename);
|
|
209
|
-
if (!root) {
|
|
210
|
-
console.error('error: cannot resolve the superpowers-manager package root');
|
|
211
|
-
process.exit(1);
|
|
212
|
-
}
|
|
213
|
-
if (parsed.kind === 'version') {
|
|
214
|
-
console.log(JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version);
|
|
215
|
-
process.exit(0);
|
|
216
|
-
}
|
|
217
|
-
if (parsed.kind === 'usage-error') {
|
|
218
|
-
console.error(`error: ${parsed.message}`);
|
|
219
|
-
console.error(usage());
|
|
220
|
-
process.exit(2);
|
|
221
|
-
}
|
|
222
|
-
const pf = preflight(parsed.cmd, process.env, process.platform);
|
|
223
|
-
if (!pf.ok) {
|
|
224
|
-
for (const e of pf.errors) console.error(`error: ${e}`);
|
|
225
|
-
process.exit(1);
|
|
226
|
-
}
|
|
227
|
-
const script = path.join(root, 'scripts', parsed.cmd);
|
|
228
|
-
if (!fs.existsSync(script)) {
|
|
229
|
-
console.error(`error: missing script: ${script}`);
|
|
230
|
-
process.exit(1);
|
|
231
|
-
}
|
|
232
|
-
const spawn = buildSpawn(parsed.cmd, parsed.args, root, pf.shell, process.platform);
|
|
233
|
-
// env is inherited wholesale, so every SUPERPOWERS_* override passes through.
|
|
234
|
-
const res = spawnSync(spawn.file, spawn.argv, { stdio: 'inherit', env: process.env });
|
|
235
|
-
if (res.error) {
|
|
236
|
-
console.error(`error: cannot run ${spawn.file}: ${res.error.message}`);
|
|
237
|
-
process.exit(1);
|
|
238
|
-
}
|
|
239
|
-
process.exit(res.status === null ? 1 : res.status);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export { resolvePackageRoot, isMain, parseArgs, findTool, discoverShell, preflight, buildSpawn, usage };
|
|
243
|
-
|
|
244
|
-
if (isMain(import.meta.filename, process.argv[1])) main();
|
|
15
|
+
const { main } = await import(cliUrl.href);
|
|
16
|
+
main();
|