superpowers-manager 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +340 -66
  2. package/dist/adapter-result.js +204 -0
  3. package/dist/adapter.js +690 -0
  4. package/dist/atomic.js +142 -0
  5. package/dist/cli-arguments.js +32 -0
  6. package/dist/cli.js +313 -0
  7. package/dist/codex-harness.js +271 -0
  8. package/dist/codex-json.js +89 -0
  9. package/dist/codex-prepare.js +199 -0
  10. package/dist/codex-presentation.js +259 -0
  11. package/dist/codex-state.js +78 -0
  12. package/dist/commands/context.js +1 -0
  13. package/dist/commands/install.js +291 -0
  14. package/dist/commands/pin.js +79 -0
  15. package/dist/commands/prepare.js +420 -0
  16. package/dist/commands/probe.js +257 -0
  17. package/dist/commands/track-latest.js +44 -0
  18. package/dist/commands/uninstall.js +217 -0
  19. package/dist/commands/unpin.js +95 -0
  20. package/dist/commands/update.js +79 -0
  21. package/dist/domain/fingerprint.js +4 -0
  22. package/dist/domain/refs.js +37 -0
  23. package/dist/effective-selection.js +123 -0
  24. package/dist/generated-plugin.js +748 -0
  25. package/dist/git.js +38 -0
  26. package/dist/harness.js +1 -0
  27. package/dist/hooks.js +290 -0
  28. package/dist/lifecycle.js +135 -0
  29. package/dist/manifest-overlay.js +125 -0
  30. package/dist/provenance.js +111 -0
  31. package/dist/python-json-format.js +83 -0
  32. package/dist/python-json.js +29 -0
  33. package/dist/python-text.js +74 -0
  34. package/dist/safe-path.js +137 -0
  35. package/dist/safety-error.js +10 -0
  36. package/dist/selection-state-cli.js +85 -0
  37. package/dist/selection-store.js +172 -0
  38. package/dist/selection.js +200 -0
  39. package/dist/status.js +24 -0
  40. package/dist/strict-json.js +248 -0
  41. package/dist/upstream-cli.js +110 -0
  42. package/dist/upstream-version.js +40 -0
  43. package/dist/upstream.js +253 -0
  44. package/dist/validate-generated-plugin-cli.js +118 -0
  45. package/dist/validator.js +317 -0
  46. package/dist/workspace.js +139 -0
  47. package/package.json +25 -4
  48. package/bin/superpowers-manager.js +0 -290
  49. package/scripts/adapters/codex/adapter +0 -603
  50. package/scripts/adapters/codex/lib.sh +0 -449
  51. package/scripts/adapters/codex/validate-generated-plugin.py +0 -314
  52. package/scripts/core/adapter.sh +0 -103
  53. package/scripts/core/common.sh +0 -59
  54. package/scripts/core/lifecycle.sh +0 -141
  55. package/scripts/core/provenance.sh +0 -135
  56. package/scripts/core/selection-state.py +0 -414
  57. package/scripts/core/selection.sh +0 -168
  58. package/scripts/core/status.sh +0 -27
  59. package/scripts/core/upstream.sh +0 -280
  60. package/scripts/core/validate-adapter-response.py +0 -284
  61. package/scripts/install +0 -59
  62. package/scripts/pin +0 -51
  63. package/scripts/prepare +0 -117
  64. package/scripts/probe +0 -82
  65. package/scripts/track-latest +0 -30
  66. package/scripts/uninstall +0 -35
  67. package/scripts/unpin +0 -41
  68. package/scripts/update +0 -34
@@ -1,290 +0,0 @@
1
- #!/usr/bin/env node
2
- // @ts-check
3
-
4
- import * as fs from 'node:fs';
5
- import * as path from 'node:path';
6
- import { spawnSync } from 'node:child_process';
7
-
8
- /**
9
- * @typedef {'pin' | 'track-latest' | 'unpin' | 'prepare' | 'probe' | 'install' | 'update' | 'uninstall'} Subcommand
10
- */
11
-
12
- /**
13
- * @typedef {{ kind: 'run', cmd: Subcommand, args: string[] }} RunParseResult
14
- * @typedef {{ kind: 'help' }} HelpParseResult
15
- * @typedef {{ kind: 'version' }} VersionParseResult
16
- * @typedef {{ kind: 'usage-error', message: string }} UsageErrorParseResult
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 = [
31
- 'pin', 'track-latest', 'unpin', 'prepare', 'probe', 'install', 'update', 'uninstall',
32
- ];
33
- const PIN_TAG_RE = /^v(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)(?:-(?:(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?$/;
34
- const PIN_COMMIT_RE = /^[0-9A-Fa-f]{40}$/;
35
- /** @type {Record<Subcommand, string[]>} */
36
- const COMMAND_REQUIREMENTS = {
37
- pin: ['git', 'python3'],
38
- 'track-latest': ['python3'],
39
- unpin: [],
40
- prepare: ['git', 'python3'],
41
- probe: ['git', 'python3', 'codex'],
42
- install: ['git', 'python3', 'codex'],
43
- update: ['git', 'python3', 'codex'],
44
- uninstall: ['python3', 'codex'],
45
- };
46
- // Mirrors upstream Superpowers' hooks/run-hook.cmd discovery order.
47
- const GIT_BASH_CANDIDATES = [
48
- 'C:\\Program Files\\Git\\bin\\bash.exe',
49
- 'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
50
- ];
51
-
52
- // Walk upward from the bin's physical location to the directory containing
53
- // package.json. realpathSync first: npm/npx expose the bin through a symlink
54
- // or shim outside the package root.
55
- /**
56
- * @param {string} scriptPath
57
- * @returns {string | null}
58
- */
59
- function resolvePackageRoot(scriptPath) {
60
- let dir;
61
- try {
62
- dir = path.dirname(fs.realpathSync(scriptPath));
63
- } catch (err) {
64
- return null;
65
- }
66
- for (;;) {
67
- if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
68
- const parent = path.dirname(dir);
69
- if (parent === dir) return null;
70
- dir = parent;
71
- }
72
- }
73
-
74
- /**
75
- * @param {string} moduleFilename
76
- * @param {string | undefined} argvPath
77
- * @returns {boolean}
78
- */
79
- function isMain(moduleFilename, argvPath) {
80
- if (!argvPath) return false;
81
- return moduleFilename === fs.realpathSync(argvPath);
82
- }
83
-
84
- /**
85
- * @param {string[]} argv
86
- * @returns {ParseResult}
87
- */
88
- function parseArgs(argv) {
89
- /** @type {string | undefined} */
90
- const first = argv[0];
91
- if (argv.length === 0) return { kind: 'run', cmd: 'update', args: [] };
92
- if (first === '--help' || first === '-h') return { kind: 'help' };
93
- if (first === '--version') return { kind: 'version' };
94
- if (first && SUBCOMMANDS.includes(first)) {
95
- const args = argv.slice(1);
96
- if (first === 'pin' && args.length !== 1) {
97
- return { kind: 'usage-error', message: 'usage: superpowers-manager pin REF' };
98
- }
99
- if (first === 'pin' && !PIN_TAG_RE.test(args[0]) && !PIN_COMMIT_RE.test(args[0])) {
100
- return {
101
- kind: 'usage-error',
102
- message: 'pin REF must be an exact v-prefixed SemVer tag or full 40-hex commit',
103
- };
104
- }
105
- if ((first === 'track-latest' || first === 'unpin') && args.length !== 0) {
106
- return { kind: 'usage-error', message: `usage: superpowers-manager ${first}` };
107
- }
108
- return { kind: 'run', cmd: /** @type {Subcommand} */ (first), args };
109
- }
110
- return { kind: 'usage-error', message: `unknown subcommand: ${first}` };
111
- }
112
-
113
- // Search env.PATH for an executable named `name`; on win32 also try PATHEXT
114
- // extensions. Returns the full path or null.
115
- /**
116
- * @param {string} name
117
- * @param {NodeJS.ProcessEnv} env
118
- * @param {NodeJS.Platform} platform
119
- * @returns {string | null}
120
- */
121
- function findTool(name, env, platform) {
122
- const pathVar = env.PATH || env.Path || '';
123
- const dirs = pathVar.split(path.delimiter).filter(Boolean);
124
- const exts = platform === 'win32'
125
- ? (env.PATHEXT || '.EXE;.CMD;.BAT;.COM').split(';')
126
- : [''];
127
- for (const dir of dirs) {
128
- for (const ext of exts) {
129
- const candidate = path.join(dir, name + ext.toLowerCase());
130
- try {
131
- fs.accessSync(candidate, fs.constants.X_OK);
132
- return candidate;
133
- } catch (err) { /* keep looking */ }
134
- }
135
- }
136
- return null;
137
- }
138
-
139
- // POSIX: `sh` on PATH. Windows: Git Bash at its standard install paths,
140
- // then `bash` on PATH.
141
- /**
142
- * @param {NodeJS.ProcessEnv} env
143
- * @param {NodeJS.Platform} platform
144
- * @returns {string | null}
145
- */
146
- function discoverShell(env, platform) {
147
- if (platform !== 'win32') return findTool('sh', env, platform);
148
- for (const candidate of GIT_BASH_CANDIDATES) {
149
- try {
150
- fs.accessSync(candidate, fs.constants.X_OK);
151
- return candidate;
152
- } catch (err) { /* keep looking */ }
153
- }
154
- return findTool('bash', env, platform);
155
- }
156
-
157
- /**
158
- * @returns {Record<Subcommand, string[]>}
159
- */
160
- function commandRequirements() {
161
- return COMMAND_REQUIREMENTS;
162
- }
163
-
164
- // Tool preflight; never touches Codex state. Requirements are specific to the
165
- // selected command, while a POSIX shell remains mandatory for every command.
166
- /**
167
- * @param {Subcommand} cmd
168
- * @param {NodeJS.ProcessEnv} env
169
- * @param {NodeJS.Platform} platform
170
- * @returns {PreflightResult}
171
- */
172
- function preflight(cmd, env, platform) {
173
- const errors = [];
174
- for (const tool of COMMAND_REQUIREMENTS[cmd]) {
175
- if (tool === 'codex') {
176
- const codexBin = env.SUPERPOWERS_CODEX || 'codex';
177
- // An explicit override may be a path rather than a PATH-resolvable name.
178
- const found = codexBin.includes(path.sep)
179
- ? fs.existsSync(codexBin)
180
- : Boolean(findTool(codexBin, env, platform));
181
- if (!found) {
182
- errors.push(`required command not found: ${codexBin} — install the Codex CLI or set SUPERPOWERS_CODEX`);
183
- }
184
- } else if (!findTool(tool, env, platform)) {
185
- errors.push(`required command not found: ${tool} — install ${tool} and re-run`);
186
- }
187
- }
188
- const shell = discoverShell(env, platform);
189
- if (!shell) {
190
- errors.push(platform === 'win32'
191
- ? 'no POSIX shell found — install Git for Windows (provides bash) or use WSL2'
192
- : 'required command not found: sh');
193
- }
194
- if (errors.length) return { ok: false, errors };
195
- return { ok: true, shell: /** @type {string} */ (shell) };
196
- }
197
-
198
- // POSIX executes the script directly (#!/bin/sh shebang); Windows cannot
199
- // spawn extensionless scripts, so the discovered shell runs the script as
200
- // its first argument.
201
- /**
202
- * @param {Subcommand} cmd
203
- * @param {string[]} args
204
- * @param {string} root
205
- * @param {string} shell
206
- * @param {NodeJS.Platform} platform
207
- * @returns {SpawnDescriptor}
208
- */
209
- function buildSpawn(cmd, args, root, shell, platform) {
210
- const script = path.join(root, 'scripts', cmd);
211
- if (platform === 'win32') {
212
- return { file: shell, argv: [script, ...args] };
213
- }
214
- return { file: script, argv: args };
215
- }
216
-
217
- function usage() {
218
- return [
219
- 'usage: superpowers-manager [command] [args...]',
220
- '',
221
- 'Selection commands (save intent only; they do not prepare or install it):',
222
- ' pin REF save an exact upstream release tag or commit',
223
- ' track-latest save selection of the latest stable upstream release',
224
- ' unpin remove the saved selection and return to the packaged fallback',
225
- '',
226
- 'Apply and lifecycle commands:',
227
- ' prepare fetch the pinned upstream ref and generate the plugin tree',
228
- ' probe report upstream/generated/installed status (accepts --porcelain)',
229
- ' install register this package root as a Codex marketplace and install the plugin',
230
- ' update probe, then prepare/install only if needed (default when no subcommand)',
231
- ' uninstall remove the manager plugin and marketplace from Codex',
232
- '',
233
- 'Environment overrides (passed through to the scripts): SUPERPOWERS_REF,',
234
- 'SUPERPOWERS_UPSTREAM_URL, SUPERPOWERS_CODEX, SUPERPOWERS_CACHE_DIR,',
235
- 'SUPERPOWERS_CONFIG_DIR, XDG_CONFIG_HOME,',
236
- 'SUPERPOWERS_PLUGIN_ROOT, SUPERPOWERS_MANIFEST_TEMPLATE,',
237
- 'SUPERPOWERS_VALIDATOR,',
238
- 'SUPERPOWERS_INSTALLED_SEARCH_ROOT, SUPERPOWERS_INSTALL_REFRESH_MODE',
239
- '',
240
- 'Selection state uses SUPERPOWERS_CONFIG_DIR when set; otherwise it uses',
241
- '$XDG_CONFIG_HOME/superpowers-manager, then $HOME/.config/superpowers-manager.',
242
- ].join('\n');
243
- }
244
-
245
- function main() {
246
- const parsed = parseArgs(process.argv.slice(2));
247
- if (parsed.kind === 'help') {
248
- console.log(usage());
249
- process.exit(0);
250
- }
251
- const root = resolvePackageRoot(import.meta.filename);
252
- if (!root) {
253
- console.error('error: cannot resolve the superpowers-manager package root');
254
- process.exit(1);
255
- }
256
- if (parsed.kind === 'version') {
257
- console.log(JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).version);
258
- process.exit(0);
259
- }
260
- if (parsed.kind === 'usage-error') {
261
- console.error(`error: ${parsed.message}`);
262
- console.error(usage());
263
- process.exit(2);
264
- }
265
- const pf = preflight(parsed.cmd, process.env, process.platform);
266
- if (!pf.ok) {
267
- for (const e of pf.errors) console.error(`error: ${e}`);
268
- process.exit(1);
269
- }
270
- const script = path.join(root, 'scripts', parsed.cmd);
271
- if (!fs.existsSync(script)) {
272
- console.error(`error: missing script: ${script}`);
273
- process.exit(1);
274
- }
275
- const spawn = buildSpawn(parsed.cmd, parsed.args, root, pf.shell, process.platform);
276
- // env is inherited wholesale, so every SUPERPOWERS_* override passes through.
277
- const res = spawnSync(spawn.file, spawn.argv, { stdio: 'inherit', env: process.env });
278
- if (res.error) {
279
- console.error(`error: cannot run ${spawn.file}: ${res.error.message}`);
280
- process.exit(1);
281
- }
282
- process.exit(res.status === null ? 1 : res.status);
283
- }
284
-
285
- export {
286
- resolvePackageRoot, isMain, parseArgs, findTool, discoverShell,
287
- commandRequirements, preflight, buildSpawn, usage,
288
- };
289
-
290
- if (isMain(import.meta.filename, process.argv[1])) main();