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
package/dist/atomic.js ADDED
@@ -0,0 +1,142 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { constants, lstat, open, readFile, rename, rm, unlink, } from "node:fs/promises";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { SafetyError } from "./safety-error.js";
5
+ export async function atomicWriteFile(path, bytes, options) {
6
+ const renamePath = options.hooks?.rename ?? rename;
7
+ let handle;
8
+ let temporary;
9
+ let phase = "pre-replacement";
10
+ try {
11
+ for (let attempt = 0; attempt < 100; attempt += 1) {
12
+ const suffix = randomBytes(8).toString("hex");
13
+ const candidate = join(dirname(path), `.${basename(path)}.tmp.${process.pid}.${suffix}`);
14
+ try {
15
+ handle = await open(candidate, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
16
+ temporary = candidate;
17
+ break;
18
+ }
19
+ catch (cause) {
20
+ if (cause instanceof Error &&
21
+ "code" in cause &&
22
+ cause.code === "EEXIST") {
23
+ continue;
24
+ }
25
+ throw cause;
26
+ }
27
+ }
28
+ if (handle === undefined || temporary === undefined) {
29
+ throw new Error("cannot create unique temporary file");
30
+ }
31
+ await handle.chmod(0o600);
32
+ await handle.writeFile(bytes);
33
+ await handle.sync();
34
+ await handle.close();
35
+ handle = undefined;
36
+ await options.validate(temporary);
37
+ await renamePath(temporary, path);
38
+ temporary = undefined;
39
+ phase = "post-replacement";
40
+ await options.hooks?.afterReplace?.(path);
41
+ await fsyncDirectoryBestEffort(dirname(path));
42
+ }
43
+ catch (cause) {
44
+ let finalBytes;
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 });
50
+ }
51
+ finally {
52
+ if (handle !== undefined) {
53
+ await handle.close().catch(() => { });
54
+ }
55
+ if (temporary !== undefined) {
56
+ await unlink(temporary).catch(() => { });
57
+ }
58
+ }
59
+ }
60
+ async function fsyncDirectoryBestEffort(directory) {
61
+ let handle;
62
+ try {
63
+ handle = await open(directory, constants.O_RDONLY);
64
+ await handle.sync();
65
+ }
66
+ catch {
67
+ // The frozen baseline treats directory fsync as best effort.
68
+ }
69
+ finally {
70
+ await handle?.close().catch(() => { });
71
+ }
72
+ }
73
+ async function exists(path) {
74
+ try {
75
+ await lstat(path);
76
+ return true;
77
+ }
78
+ catch (cause) {
79
+ if (cause instanceof Error &&
80
+ "code" in cause &&
81
+ cause.code === "ENOENT") {
82
+ return false;
83
+ }
84
+ throw cause;
85
+ }
86
+ }
87
+ async function chooseBackup(live) {
88
+ for (let attempt = 0; attempt < 100; attempt += 1) {
89
+ const candidate = join(dirname(live), `.${basename(live)}.bak.${process.pid}.${randomBytes(8).toString("hex")}`);
90
+ if (!(await exists(candidate)))
91
+ return candidate;
92
+ }
93
+ throw new SafetyError("atomic", "cannot choose unique backup path");
94
+ }
95
+ export async function atomicReplaceDir(candidate, live, options = {}) {
96
+ const renamePath = options.hooks?.rename ?? rename;
97
+ const removePath = options.hooks?.rm ?? rm;
98
+ let backupCreated = false;
99
+ let phase = "pre-replacement";
100
+ try {
101
+ const backup = await chooseBackup(live);
102
+ if (await exists(live)) {
103
+ await renamePath(live, backup);
104
+ backupCreated = true;
105
+ }
106
+ try {
107
+ await renamePath(candidate, live);
108
+ phase = "post-replacement";
109
+ }
110
+ catch (cause) {
111
+ if (!backupCreated) {
112
+ await removePath(candidate, { recursive: true, force: true });
113
+ throw new SafetyError("atomic", "directory activation failed with no prior tree", { cause, details: { phase: "pre-replacement" } });
114
+ }
115
+ try {
116
+ await renamePath(backup, live);
117
+ }
118
+ catch (rollbackCause) {
119
+ await removePath(candidate, { recursive: true, force: true }).catch(() => { });
120
+ throw new SafetyError("atomic", `directory activation and rollback failed; backup preserved at ${backup}`, {
121
+ cause: new AggregateError([cause, rollbackCause]),
122
+ details: { phase: "pre-replacement" },
123
+ });
124
+ }
125
+ await removePath(candidate, { recursive: true, force: true });
126
+ throw new SafetyError("atomic", "directory activation failed; previous tree restored", { cause, details: { phase: "pre-replacement" } });
127
+ }
128
+ if (backupCreated) {
129
+ try {
130
+ await removePath(backup, { recursive: true, force: true });
131
+ }
132
+ catch (cause) {
133
+ throw new SafetyError("atomic", `directory replacement succeeded but backup cleanup failed at ${backup}`, { cause, details: { phase: "post-replacement" } });
134
+ }
135
+ }
136
+ }
137
+ catch (cause) {
138
+ if (cause instanceof SafetyError)
139
+ throw cause;
140
+ throw new SafetyError("atomic", "directory replacement failed", { cause, details: { phase } });
141
+ }
142
+ }
@@ -0,0 +1,32 @@
1
+ export class UsageError extends Error {
2
+ }
3
+ export function parseFlags(argv, names) {
4
+ const allowed = new Set(names);
5
+ const result = {};
6
+ for (let index = 0; index < argv.length; index += 1) {
7
+ const token = argv[index];
8
+ if (!token.startsWith("--"))
9
+ throw new UsageError(`unexpected argument: ${token}`);
10
+ const separator = token.indexOf("=");
11
+ const name = separator === -1 ? token.slice(2) : token.slice(2, separator);
12
+ if (!allowed.has(name))
13
+ throw new UsageError(`unknown option: --${name}`);
14
+ const value = separator === -1 ? argv[index + 1] : token.slice(separator + 1);
15
+ if (value === undefined || (separator === -1 && value.startsWith("--"))) {
16
+ throw new UsageError(`option --${name} requires a value`);
17
+ }
18
+ result[name] = value;
19
+ if (separator === -1)
20
+ index += 1;
21
+ }
22
+ for (const name of names) {
23
+ if (!Object.hasOwn(result, name)) {
24
+ throw new UsageError(`required option is missing: --${name}`);
25
+ }
26
+ }
27
+ return result;
28
+ }
29
+ export function oneLine(value) {
30
+ const message = value instanceof Error ? value.message : String(value);
31
+ return message.replace(/[\r\n]+/g, " ");
32
+ }
package/dist/cli.js ADDED
@@ -0,0 +1,313 @@
1
+ #!/usr/bin/env node
2
+ import * as fs from "node:fs";
3
+ import * as path from "node:path";
4
+ import { oneLine } from "./cli-arguments.js";
5
+ import { codexHarness } from "./codex-harness.js";
6
+ import { runInstall } from "./commands/install.js";
7
+ import { runPin } from "./commands/pin.js";
8
+ import { runPrepare } from "./commands/prepare.js";
9
+ import { runProbe } from "./commands/probe.js";
10
+ import { runTrackLatest } from "./commands/track-latest.js";
11
+ import { runUninstall } from "./commands/uninstall.js";
12
+ import { runUnpin } from "./commands/unpin.js";
13
+ import { runUpdate } from "./commands/update.js";
14
+ import { COMMIT_INPUT_RE, TAG_RE } from "./domain/refs.js";
15
+ import { configurationErrors } from "./validator.js";
16
+ const SUBCOMMANDS = [
17
+ "pin",
18
+ "track-latest",
19
+ "unpin",
20
+ "prepare",
21
+ "probe",
22
+ "install",
23
+ "update",
24
+ "uninstall",
25
+ ];
26
+ // Keyed by Subcommand itself. Until slice 6 this was keyed by a mapped type
27
+ // that filtered Subcommand through a DISPATCH table of "spawn" | "in-process"
28
+ // literals; with every command in-process that filter was the identity, so the
29
+ // table, its mode union and the mapped type were one indirection with nothing
30
+ // left to discriminate. The guarantee is unchanged and is the whole protection
31
+ // — tests/bin/bin-dispatch.test.js records that decision: a Subcommand added
32
+ // without a handler registered here is a compile error, not a runtime surprise.
33
+ const IN_PROCESS_HANDLERS = {
34
+ pin: runPin,
35
+ "track-latest": runTrackLatest,
36
+ unpin: runUnpin,
37
+ prepare: runPrepare,
38
+ probe: runProbe,
39
+ install: runInstall,
40
+ update: runUpdate,
41
+ uninstall: runUninstall,
42
+ };
43
+ // `python3` left install, update and uninstall at slice 4b's flip. It was
44
+ // required because `spw_invoke_adapter` ran validate-adapter-response.py once
45
+ // per adapter call
46
+ // (`git show ad56569a4c161e7b122967442e2b026eeb6395f6:scripts/core/adapter.sh:37-44::--response "$response_file" --result "$result_file" \`);
47
+ // the in-process path has no validator process. It remains CONDITIONAL for
48
+ // `prepare` through commandRequirements(env) below, unchanged from slice 3.4.
49
+ // No command requires a POSIX shell any more.
50
+ const SHARED_COMMAND_REQUIREMENTS = {
51
+ pin: ["git"],
52
+ "track-latest": [],
53
+ unpin: [],
54
+ prepare: ["git"],
55
+ probe: ["git"],
56
+ install: ["git"],
57
+ update: ["git"],
58
+ uninstall: [],
59
+ };
60
+ // Walk upward from the bin's physical location to the directory containing
61
+ // package.json. realpathSync first: npm/npx expose the bin through a symlink
62
+ // or shim outside the package root.
63
+ function resolvePackageRoot(scriptPath) {
64
+ let dir;
65
+ try {
66
+ dir = path.dirname(fs.realpathSync(scriptPath));
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ for (;;) {
72
+ if (fs.existsSync(path.join(dir, "package.json")))
73
+ return dir;
74
+ const parent = path.dirname(dir);
75
+ if (parent === dir)
76
+ return null;
77
+ dir = parent;
78
+ }
79
+ }
80
+ function isMain(moduleFilename, argvPath) {
81
+ if (!argvPath)
82
+ return false;
83
+ return moduleFilename === fs.realpathSync(argvPath);
84
+ }
85
+ function parseArgs(argv) {
86
+ const first = argv[0];
87
+ if (argv.length === 0)
88
+ return { kind: "run", cmd: "update", args: [] };
89
+ if (first === "--help" || first === "-h")
90
+ return { kind: "help" };
91
+ if (first === "--version")
92
+ return { kind: "version" };
93
+ if (first && SUBCOMMANDS.includes(first)) {
94
+ const command = first;
95
+ const args = argv.slice(1);
96
+ if (command === "pin" && args.length !== 1) {
97
+ return {
98
+ kind: "usage-error",
99
+ message: "usage: superpowers-manager pin REF",
100
+ };
101
+ }
102
+ if (command === "pin" &&
103
+ !TAG_RE.test(args[0]) &&
104
+ !COMMIT_INPUT_RE.test(args[0])) {
105
+ return {
106
+ kind: "usage-error",
107
+ message: "pin REF must be an exact v-prefixed SemVer tag or full 40-hex commit",
108
+ };
109
+ }
110
+ if ((command === "track-latest" || command === "unpin") &&
111
+ args.length !== 0) {
112
+ return {
113
+ kind: "usage-error",
114
+ message: `usage: superpowers-manager ${command}`,
115
+ };
116
+ }
117
+ // Arity lives HERE, not only in src/commands/probe.ts, so `probe` gets the
118
+ // same shape as every other CLI-owned usage error: `error: <msg>` plus the
119
+ // full usage block, exit 2, decided before preflight. Leaving it to the
120
+ // handler alone made `probe --porcelaine` print no usage block, and made
121
+ // the identical input exit 1 on a machine without `codex` because preflight
122
+ // ran first. `PROBE_USAGE` stays as the same unreachable-from-CLI duplicate
123
+ // that track-latest and unpin already carry.
124
+ if (command === "probe" &&
125
+ !(args.length === 0 || (args.length === 1 && args[0] === "--porcelain"))) {
126
+ return {
127
+ kind: "usage-error",
128
+ message: "usage: superpowers-manager probe [--porcelain]",
129
+ };
130
+ }
131
+ return { kind: "run", cmd: command, args };
132
+ }
133
+ return { kind: "usage-error", message: `unknown subcommand: ${first}` };
134
+ }
135
+ // Search env.PATH for an executable named `name`; on win32 also try PATHEXT
136
+ // extensions. Returns the full path or null.
137
+ function findTool(name, env, platform) {
138
+ const pathVar = env.PATH || env.Path || "";
139
+ const dirs = pathVar.split(path.delimiter).filter(Boolean);
140
+ const exts = platform === "win32"
141
+ ? (env.PATHEXT || ".EXE;.CMD;.BAT;.COM").split(";")
142
+ : [""];
143
+ for (const dir of dirs) {
144
+ for (const ext of exts) {
145
+ const candidate = path.join(dir, name + ext.toLowerCase());
146
+ try {
147
+ fs.accessSync(candidate, fs.constants.X_OK);
148
+ return candidate;
149
+ }
150
+ catch {
151
+ /* keep looking */
152
+ }
153
+ }
154
+ }
155
+ return null;
156
+ }
157
+ // python3 is required by `prepare` only when SUPERPOWERS_VALIDATOR names one:
158
+ // after the port, that optional spawn (runValidator, in src/validator.ts,
159
+ // called from src/commands/prepare.ts) is Python's only remaining consumer
160
+ // on the prepare path. The conditional lives here, in the accessor preflight reads,
161
+ // rather than inside preflight — an accessor that under-reports what
162
+ // preflight enforces is the blind spot slice 2 closed when it made
163
+ // CLI-PREFLIGHT-01 derive its map from production.
164
+ function sharedRequirement(name) {
165
+ return {
166
+ name,
167
+ executable: name,
168
+ lookup: "path",
169
+ missingMessage: `required command not found: ${name} — install ${name} and re-run`,
170
+ };
171
+ }
172
+ function commandRequirementsFor(env, adapter) {
173
+ const forCommand = (command) => {
174
+ const shared = SHARED_COMMAND_REQUIREMENTS[command].map(sharedRequirement);
175
+ if (command === "prepare" && env.SUPERPOWERS_VALIDATOR) {
176
+ shared.push(sharedRequirement("python3"));
177
+ }
178
+ return [...shared, ...adapter.requirements(command, env)];
179
+ };
180
+ return {
181
+ pin: forCommand("pin"),
182
+ "track-latest": forCommand("track-latest"),
183
+ unpin: forCommand("unpin"),
184
+ prepare: forCommand("prepare"),
185
+ probe: forCommand("probe"),
186
+ install: forCommand("install"),
187
+ update: forCommand("update"),
188
+ uninstall: forCommand("uninstall"),
189
+ };
190
+ }
191
+ function commandRequirements(env) {
192
+ return Object.fromEntries(SUBCOMMANDS.map((command) => [
193
+ command,
194
+ commandRequirementsFor(env, codexHarness)[command].map((requirement) => requirement.name),
195
+ ]));
196
+ }
197
+ // Preflight; never touches Codex state. It is the union of two exported
198
+ // accessors: configurationErrors (validator configuration) and
199
+ // commandRequirements (tool availability), both specific to the selected
200
+ // command. No command requires a POSIX shell: slice 4b flipped the last
201
+ // spawned command in-process, so there is no shell to discover.
202
+ function preflightFor(cmd, env, platform, adapter) {
203
+ const errors = [...configurationErrors(cmd, env)];
204
+ for (const requirement of commandRequirementsFor(env, adapter)[cmd]) {
205
+ const found = requirement.lookup === "explicit-path-or-path" &&
206
+ requirement.executable.includes(path.sep)
207
+ ? fs.existsSync(requirement.executable)
208
+ : Boolean(findTool(requirement.executable, env, platform));
209
+ if (!found)
210
+ errors.push(requirement.missingMessage);
211
+ }
212
+ if (errors.length)
213
+ return { ok: false, errors };
214
+ return { ok: true };
215
+ }
216
+ function preflight(cmd, env, platform) {
217
+ return preflightFor(cmd, env, platform, codexHarness);
218
+ }
219
+ function usage() {
220
+ return [
221
+ "usage: superpowers-manager [command] [args...]",
222
+ "",
223
+ "Selection commands (save intent only; they do not prepare or install it):",
224
+ " pin REF save an exact upstream release tag or commit",
225
+ " track-latest save selection of the latest stable upstream release",
226
+ " unpin remove the saved selection and return to the packaged fallback",
227
+ "",
228
+ "Apply and lifecycle commands:",
229
+ " prepare fetch the pinned upstream ref and generate the plugin tree",
230
+ " probe report upstream/generated/installed status (accepts --porcelain)",
231
+ " install register this package root as a Codex marketplace and install the plugin",
232
+ " update probe, then prepare/install only if needed (default when no subcommand)",
233
+ " uninstall remove the manager plugin and marketplace from Codex",
234
+ "",
235
+ "Environment overrides (used by in-process commands): SUPERPOWERS_REF,",
236
+ "SUPERPOWERS_UPSTREAM_URL, SUPERPOWERS_CODEX, SUPERPOWERS_CACHE_DIR,",
237
+ "SUPERPOWERS_CONFIG_DIR, XDG_CONFIG_HOME,",
238
+ "SUPERPOWERS_PLUGIN_ROOT, SUPERPOWERS_MANIFEST_TEMPLATE,",
239
+ "SUPERPOWERS_VALIDATOR, SUPERPOWERS_VALIDATOR_EXECUTABLE,",
240
+ "SUPERPOWERS_INSTALLED_SEARCH_ROOT, SUPERPOWERS_INSTALL_REFRESH_MODE",
241
+ "",
242
+ "Selection state uses SUPERPOWERS_CONFIG_DIR when set; otherwise it uses",
243
+ "$XDG_CONFIG_HOME/superpowers-manager, then $HOME/.config/superpowers-manager.",
244
+ ].join("\n");
245
+ }
246
+ async function main() {
247
+ const parsed = parseArgs(process.argv.slice(2));
248
+ if (parsed.kind === "help") {
249
+ console.log(usage());
250
+ process.exit(0);
251
+ }
252
+ const root = resolvePackageRoot(import.meta.filename);
253
+ if (!root) {
254
+ console.error("error: cannot resolve the superpowers-manager package root");
255
+ process.exit(1);
256
+ }
257
+ if (parsed.kind === "version") {
258
+ console.log(JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"))
259
+ .version);
260
+ process.exit(0);
261
+ }
262
+ if (parsed.kind === "usage-error") {
263
+ console.error(`error: ${parsed.message}`);
264
+ console.error(usage());
265
+ process.exit(2);
266
+ }
267
+ const pf = preflight(parsed.cmd, process.env, process.platform);
268
+ if (!pf.ok) {
269
+ for (const e of pf.errors)
270
+ console.error(`error: ${e}`);
271
+ process.exit(1);
272
+ }
273
+ // Dispatch is no longer a branch: slice 4b flipped the last spawned command,
274
+ // so every subcommand runs here, and slice 6 deleted the DISPATCH table whose
275
+ // only remaining job was narrowing this registry's key type. `parsed.cmd` is
276
+ // a Subcommand and the registry is keyed by Subcommand, so no cast is needed.
277
+ // The exhaustiveness check on IN_PROCESS_HANDLERS makes an unregistered
278
+ // handler a compile error; the `!handler` guard below is the runtime backstop
279
+ // for that guarantee. It is unreachable through production code, and no
280
+ // fixture reaches it either.
281
+ const handler = IN_PROCESS_HANDLERS[parsed.cmd];
282
+ if (!handler) {
283
+ console.error(`error: no in-process handler registered for: ${parsed.cmd}`);
284
+ process.exit(1);
285
+ }
286
+ const ctx = {
287
+ root,
288
+ env: process.env,
289
+ stdout: process.stdout,
290
+ stderr: process.stderr,
291
+ // The ONLY production binding of a concrete harness implementation.
292
+ adapter: codexHarness,
293
+ };
294
+ let status;
295
+ try {
296
+ status = await handler(parsed.args, ctx);
297
+ }
298
+ catch (cause) {
299
+ // Belt-and-suspenders: every registered handler already catches its own
300
+ // failures and returns a status code (see src/commands/unpin.ts). This
301
+ // re-emits a subordinate module's own diagnostic if one somehow escapes.
302
+ console.error(`error: ${oneLine(cause)}`);
303
+ process.exit(1);
304
+ }
305
+ process.exit(status);
306
+ }
307
+ export { resolvePackageRoot, isMain, parseArgs, findTool, commandRequirements, commandRequirementsFor, preflight, preflightFor, usage, main, };
308
+ if (isMain(import.meta.filename, process.argv[1])) {
309
+ main().catch((cause) => {
310
+ console.error(`error: ${oneLine(cause)}`);
311
+ process.exit(1);
312
+ });
313
+ }