superpowers-manager 0.3.0 → 0.5.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 +63 -325
- package/dist/adapter.js +91 -46
- package/dist/artifact-tree.js +110 -0
- package/dist/atomic.js +103 -7
- package/dist/cli-arguments.js +37 -0
- package/dist/cli.js +97 -40
- package/dist/codex-compatibility.js +126 -0
- package/dist/codex-conflicts.js +99 -0
- package/dist/codex-harness.js +321 -0
- package/dist/codex-json.js +7 -0
- package/dist/codex-prepare.js +255 -0
- package/dist/codex-presentation.js +282 -0
- package/dist/commands/install.js +187 -173
- package/dist/commands/mutation.js +71 -0
- package/dist/commands/prepare.js +87 -188
- package/dist/commands/probe.js +191 -176
- package/dist/commands/uninstall.js +52 -139
- package/dist/commands/update.js +33 -88
- package/dist/generated-plugin.js +3 -3
- package/dist/harness-compatibility.js +13 -0
- package/dist/harness.js +1 -0
- package/dist/lifecycle.js +1 -86
- package/dist/manifest-overlay.js +2 -2
- package/dist/pi-compatibility.js +73 -0
- package/dist/pi-harness.js +63 -0
- package/dist/pi-install.js +495 -0
- package/dist/pi-native.js +81 -0
- package/dist/pi-package.js +153 -0
- package/dist/pi-paths.js +30 -0
- package/dist/pi-prepare.js +111 -0
- package/dist/pi-presentation.js +67 -0
- package/dist/pi-settings.js +171 -0
- package/dist/pi-shared-skills.js +413 -0
- package/dist/pi-state.js +311 -0
- package/dist/resource-lock.js +265 -0
- package/dist/safe-path.js +21 -5
- package/dist/strict-json.js +1 -1
- package/dist/upstream-workspace.js +8 -0
- package/dist/validator.js +3 -2
- package/package.json +19 -18
- package/bin/superpowers-manager.js +0 -16
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as path from "node:path";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
4
|
+
import { extractHarnessOptions, oneLine, UsageError } from "./cli-arguments.js";
|
|
5
|
+
import { codexHarness } from "./codex-harness.js";
|
|
6
|
+
import { piHarness } from "./pi-harness.js";
|
|
7
|
+
import { createResourceCoordinator } from "./resource-lock.js";
|
|
5
8
|
import { runInstall } from "./commands/install.js";
|
|
6
9
|
import { runPin } from "./commands/pin.js";
|
|
7
10
|
import { runPrepare } from "./commands/prepare.js";
|
|
@@ -46,15 +49,15 @@ const IN_PROCESS_HANDLERS = {
|
|
|
46
49
|
// the in-process path has no validator process. It remains CONDITIONAL for
|
|
47
50
|
// `prepare` through commandRequirements(env) below, unchanged from slice 3.4.
|
|
48
51
|
// No command requires a POSIX shell any more.
|
|
49
|
-
const
|
|
52
|
+
const SHARED_COMMAND_REQUIREMENTS = {
|
|
50
53
|
pin: ["git"],
|
|
51
54
|
"track-latest": [],
|
|
52
55
|
unpin: [],
|
|
53
56
|
prepare: ["git"],
|
|
54
|
-
probe: ["git"
|
|
55
|
-
install: ["git"
|
|
56
|
-
update: ["git"
|
|
57
|
-
uninstall: [
|
|
57
|
+
probe: ["git"],
|
|
58
|
+
install: ["git"],
|
|
59
|
+
update: ["git"],
|
|
60
|
+
uninstall: [],
|
|
58
61
|
};
|
|
59
62
|
// Walk upward from the bin's physical location to the directory containing
|
|
60
63
|
// package.json. realpathSync first: npm/npx expose the bin through a symlink
|
|
@@ -84,14 +87,28 @@ function isMain(moduleFilename, argvPath) {
|
|
|
84
87
|
function parseArgs(argv) {
|
|
85
88
|
const first = argv[0];
|
|
86
89
|
if (argv.length === 0)
|
|
87
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
kind: "run",
|
|
92
|
+
cmd: "update",
|
|
93
|
+
args: [],
|
|
94
|
+
options: { harness: "codex", allowExperimental: false },
|
|
95
|
+
};
|
|
88
96
|
if (first === "--help" || first === "-h")
|
|
89
97
|
return { kind: "help" };
|
|
90
98
|
if (first === "--version")
|
|
91
99
|
return { kind: "version" };
|
|
92
100
|
if (first && SUBCOMMANDS.includes(first)) {
|
|
93
101
|
const command = first;
|
|
94
|
-
|
|
102
|
+
let extracted;
|
|
103
|
+
try {
|
|
104
|
+
extracted = extractHarnessOptions(command, argv.slice(1));
|
|
105
|
+
}
|
|
106
|
+
catch (cause) {
|
|
107
|
+
if (cause instanceof UsageError)
|
|
108
|
+
return { kind: "usage-error", message: cause.message };
|
|
109
|
+
throw cause;
|
|
110
|
+
}
|
|
111
|
+
const { args, options } = extracted;
|
|
95
112
|
if (command === "pin" && args.length !== 1) {
|
|
96
113
|
return {
|
|
97
114
|
kind: "usage-error",
|
|
@@ -127,7 +144,7 @@ function parseArgs(argv) {
|
|
|
127
144
|
message: "usage: superpowers-manager probe [--porcelain]",
|
|
128
145
|
};
|
|
129
146
|
}
|
|
130
|
-
return { kind: "run", cmd: command, args };
|
|
147
|
+
return { kind: "run", cmd: command, args, options };
|
|
131
148
|
}
|
|
132
149
|
return { kind: "usage-error", message: `unknown subcommand: ${first}` };
|
|
133
150
|
}
|
|
@@ -160,40 +177,61 @@ function findTool(name, env, platform) {
|
|
|
160
177
|
// rather than inside preflight — an accessor that under-reports what
|
|
161
178
|
// preflight enforces is the blind spot slice 2 closed when it made
|
|
162
179
|
// CLI-PREFLIGHT-01 derive its map from production.
|
|
163
|
-
function
|
|
164
|
-
|
|
165
|
-
|
|
180
|
+
function sharedRequirement(name) {
|
|
181
|
+
return {
|
|
182
|
+
name,
|
|
183
|
+
executable: name,
|
|
184
|
+
lookup: "path",
|
|
185
|
+
missingMessage: `required command not found: ${name} — install ${name} and re-run`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function commandRequirementsFor(env, adapter) {
|
|
189
|
+
const forCommand = (command) => {
|
|
190
|
+
const shared = SHARED_COMMAND_REQUIREMENTS[command].map(sharedRequirement);
|
|
191
|
+
if (command === "prepare" && env.SUPERPOWERS_VALIDATOR) {
|
|
192
|
+
shared.push(sharedRequirement("python3"));
|
|
193
|
+
}
|
|
194
|
+
return [...shared, ...adapter.requirements(command, env)];
|
|
195
|
+
};
|
|
166
196
|
return {
|
|
167
|
-
|
|
168
|
-
|
|
197
|
+
pin: forCommand("pin"),
|
|
198
|
+
"track-latest": forCommand("track-latest"),
|
|
199
|
+
unpin: forCommand("unpin"),
|
|
200
|
+
prepare: forCommand("prepare"),
|
|
201
|
+
probe: forCommand("probe"),
|
|
202
|
+
install: forCommand("install"),
|
|
203
|
+
update: forCommand("update"),
|
|
204
|
+
uninstall: forCommand("uninstall"),
|
|
169
205
|
};
|
|
170
206
|
}
|
|
207
|
+
function commandRequirements(env) {
|
|
208
|
+
return Object.fromEntries(SUBCOMMANDS.map((command) => [
|
|
209
|
+
command,
|
|
210
|
+
commandRequirementsFor(env, codexHarness)[command].map((requirement) => requirement.name),
|
|
211
|
+
]));
|
|
212
|
+
}
|
|
171
213
|
// Preflight; never touches Codex state. It is the union of two exported
|
|
172
214
|
// accessors: configurationErrors (validator configuration) and
|
|
173
215
|
// commandRequirements (tool availability), both specific to the selected
|
|
174
216
|
// command. No command requires a POSIX shell: slice 4b flipped the last
|
|
175
217
|
// spawned command in-process, so there is no shell to discover.
|
|
176
|
-
function
|
|
218
|
+
function preflightFor(cmd, env, platform, adapter) {
|
|
177
219
|
const errors = [...configurationErrors(cmd, env)];
|
|
178
|
-
for (const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (!found) {
|
|
186
|
-
errors.push(`required command not found: ${codexBin} — install the Codex CLI or set SUPERPOWERS_CODEX`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
else if (!findTool(tool, env, platform)) {
|
|
190
|
-
errors.push(`required command not found: ${tool} — install ${tool} and re-run`);
|
|
191
|
-
}
|
|
220
|
+
for (const requirement of commandRequirementsFor(env, adapter)[cmd]) {
|
|
221
|
+
const found = requirement.lookup === "explicit-path-or-path" &&
|
|
222
|
+
requirement.executable.includes(path.sep)
|
|
223
|
+
? fs.existsSync(requirement.executable)
|
|
224
|
+
: Boolean(findTool(requirement.executable, env, platform));
|
|
225
|
+
if (!found)
|
|
226
|
+
errors.push(requirement.missingMessage);
|
|
192
227
|
}
|
|
193
228
|
if (errors.length)
|
|
194
229
|
return { ok: false, errors };
|
|
195
230
|
return { ok: true };
|
|
196
231
|
}
|
|
232
|
+
function preflight(cmd, env, platform) {
|
|
233
|
+
return preflightFor(cmd, env, platform, codexHarness);
|
|
234
|
+
}
|
|
197
235
|
function usage() {
|
|
198
236
|
return [
|
|
199
237
|
"usage: superpowers-manager [command] [args...]",
|
|
@@ -210,6 +248,14 @@ function usage() {
|
|
|
210
248
|
" update probe, then prepare/install only if needed (default when no subcommand)",
|
|
211
249
|
" uninstall remove the manager plugin and marketplace from Codex",
|
|
212
250
|
"",
|
|
251
|
+
"Target one invocation (default: codex; selection commands are shared):",
|
|
252
|
+
" probe --harness pi [--porcelain]",
|
|
253
|
+
" prepare --harness pi",
|
|
254
|
+
" install --harness pi [--allow-experimental]",
|
|
255
|
+
" update --harness pi [--allow-experimental]",
|
|
256
|
+
" uninstall --harness pi",
|
|
257
|
+
" --harness=codex and --harness=pi are also accepted after the command.",
|
|
258
|
+
"",
|
|
213
259
|
"Environment overrides (used by in-process commands): SUPERPOWERS_REF,",
|
|
214
260
|
"SUPERPOWERS_UPSTREAM_URL, SUPERPOWERS_CODEX, SUPERPOWERS_CACHE_DIR,",
|
|
215
261
|
"SUPERPOWERS_CONFIG_DIR, XDG_CONFIG_HOME,",
|
|
@@ -242,11 +288,22 @@ async function main() {
|
|
|
242
288
|
console.error(usage());
|
|
243
289
|
process.exit(2);
|
|
244
290
|
}
|
|
245
|
-
const
|
|
291
|
+
const status = parsed.options.harness === "pi"
|
|
292
|
+
? await dispatch(piHarness, parsed)
|
|
293
|
+
: await dispatch(codexHarness, parsed);
|
|
294
|
+
process.exit(status);
|
|
295
|
+
}
|
|
296
|
+
async function dispatch(adapter, parsed) {
|
|
297
|
+
const root = resolvePackageRoot(import.meta.filename);
|
|
298
|
+
if (!root) {
|
|
299
|
+
console.error("error: cannot resolve the superpowers-manager package root");
|
|
300
|
+
return 1;
|
|
301
|
+
}
|
|
302
|
+
const pf = preflightFor(parsed.cmd, process.env, process.platform, adapter);
|
|
246
303
|
if (!pf.ok) {
|
|
247
304
|
for (const e of pf.errors)
|
|
248
305
|
console.error(`error: ${e}`);
|
|
249
|
-
|
|
306
|
+
return 1;
|
|
250
307
|
}
|
|
251
308
|
// Dispatch is no longer a branch: slice 4b flipped the last spawned command,
|
|
252
309
|
// so every subcommand runs here, and slice 6 deleted the DISPATCH table whose
|
|
@@ -259,17 +316,17 @@ async function main() {
|
|
|
259
316
|
const handler = IN_PROCESS_HANDLERS[parsed.cmd];
|
|
260
317
|
if (!handler) {
|
|
261
318
|
console.error(`error: no in-process handler registered for: ${parsed.cmd}`);
|
|
262
|
-
|
|
319
|
+
return 1;
|
|
263
320
|
}
|
|
264
321
|
const ctx = {
|
|
265
322
|
root,
|
|
266
323
|
env: process.env,
|
|
267
324
|
stdout: process.stdout,
|
|
268
325
|
stderr: process.stderr,
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
//
|
|
272
|
-
adapter
|
|
326
|
+
options: parsed.options,
|
|
327
|
+
coordination: createResourceCoordinator(),
|
|
328
|
+
// The ONLY production binding of a concrete harness implementation.
|
|
329
|
+
adapter,
|
|
273
330
|
};
|
|
274
331
|
let status;
|
|
275
332
|
try {
|
|
@@ -280,11 +337,11 @@ async function main() {
|
|
|
280
337
|
// failures and returns a status code (see src/commands/unpin.ts). This
|
|
281
338
|
// re-emits a subordinate module's own diagnostic if one somehow escapes.
|
|
282
339
|
console.error(`error: ${oneLine(cause)}`);
|
|
283
|
-
|
|
340
|
+
return 1;
|
|
284
341
|
}
|
|
285
|
-
|
|
342
|
+
return status;
|
|
286
343
|
}
|
|
287
|
-
export { resolvePackageRoot, isMain, parseArgs, findTool, commandRequirements, preflight, usage, main, };
|
|
344
|
+
export { resolvePackageRoot, isMain, parseArgs, findTool, commandRequirements, commandRequirementsFor, preflight, preflightFor, usage, dispatch, main, };
|
|
288
345
|
if (isMain(import.meta.filename, process.argv[1])) {
|
|
289
346
|
main().catch((cause) => {
|
|
290
347
|
console.error(`error: ${oneLine(cause)}`);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { validateNativeSkill } from "./artifact-tree.js";
|
|
3
|
+
import { classifyPathNoFollow, assertExistingContained } from "./safe-path.js";
|
|
4
|
+
import { classifyHooks, readManifest } from "./hooks.js";
|
|
5
|
+
import { SEMVER_RE } from "./generated-plugin.js";
|
|
6
|
+
import { validateSource } from "./selection.js";
|
|
7
|
+
import { writeFile } from "node:fs/promises";
|
|
8
|
+
import { ARTIFACT_DIGEST_RE, ARTIFACT_RECEIPT, digestArtifactTree, readArtifactObject, } from "./artifact-tree.js";
|
|
9
|
+
import { COMMIT_RE } from "./domain/refs.js";
|
|
10
|
+
import { validateGeneratedPlugin } from "./generated-plugin.js";
|
|
11
|
+
import { SafetyError } from "./safety-error.js";
|
|
12
|
+
export async function writeCodexAssessment(root, selection, manifestSource) {
|
|
13
|
+
const receipt = {
|
|
14
|
+
schema: 1,
|
|
15
|
+
manager: "superpowers-manager",
|
|
16
|
+
harness: "codex",
|
|
17
|
+
source: selection.effectiveSource,
|
|
18
|
+
commit: selection.desiredCommit,
|
|
19
|
+
generation: "codex-native-skills-v1",
|
|
20
|
+
manifestSource,
|
|
21
|
+
digest: await digestArtifactTree(root),
|
|
22
|
+
};
|
|
23
|
+
await writeFile(join(root, ARTIFACT_RECEIPT), JSON.stringify(receipt) + "\n", { flag: "wx" });
|
|
24
|
+
}
|
|
25
|
+
export async function readCodexAssessment(root) {
|
|
26
|
+
try {
|
|
27
|
+
const receipt = await readArtifactObject(root, join(root, ARTIFACT_RECEIPT));
|
|
28
|
+
const provenance = await readArtifactObject(root, join(root, ".superpowers-upstream.json"));
|
|
29
|
+
if (receipt.schema !== 1 ||
|
|
30
|
+
receipt.manager !== "superpowers-manager" ||
|
|
31
|
+
receipt.harness !== "codex" ||
|
|
32
|
+
receipt.generation !== "codex-native-skills-v1" ||
|
|
33
|
+
typeof receipt.source !== "string" ||
|
|
34
|
+
typeof receipt.commit !== "string" ||
|
|
35
|
+
!COMMIT_RE.test(receipt.commit) ||
|
|
36
|
+
typeof receipt.digest !== "string" ||
|
|
37
|
+
!ARTIFACT_DIGEST_RE.test(receipt.digest) ||
|
|
38
|
+
(receipt.manifestSource !== "upstream" &&
|
|
39
|
+
receipt.manifestSource !== "fallback") ||
|
|
40
|
+
receipt.source !== provenance.source ||
|
|
41
|
+
receipt.commit !== provenance.commit)
|
|
42
|
+
throw new Error("assessment fields");
|
|
43
|
+
validateSource(receipt.source);
|
|
44
|
+
if ((await digestArtifactTree(root)) !== receipt.digest)
|
|
45
|
+
throw new Error("assessment digest");
|
|
46
|
+
const manifest = await readManifest(join(root, ".codex-plugin/plugin.json"));
|
|
47
|
+
if (typeof manifest.version !== "string" ||
|
|
48
|
+
typeof provenance.requested_ref !== "string" ||
|
|
49
|
+
typeof provenance.resolved_ref !== "string" ||
|
|
50
|
+
typeof provenance.upstream_manifest_version !== "string")
|
|
51
|
+
throw new Error("provenance fields");
|
|
52
|
+
const errors = await validateGeneratedPlugin({
|
|
53
|
+
pluginRoot: root,
|
|
54
|
+
source: receipt.source,
|
|
55
|
+
commit: receipt.commit,
|
|
56
|
+
requestedRef: provenance.requested_ref,
|
|
57
|
+
resolvedRef: provenance.resolved_ref,
|
|
58
|
+
upstreamManifestVersion: provenance.upstream_manifest_version,
|
|
59
|
+
manifestVersion: manifest.version,
|
|
60
|
+
manifestSource: receipt.manifestSource,
|
|
61
|
+
});
|
|
62
|
+
if (errors.length)
|
|
63
|
+
throw new Error("generated resources");
|
|
64
|
+
const selection = {
|
|
65
|
+
effectiveSource: receipt.source,
|
|
66
|
+
desiredCommit: receipt.commit,
|
|
67
|
+
requestedRef: provenance.requested_ref,
|
|
68
|
+
resolvedRef: provenance.resolved_ref,
|
|
69
|
+
resolutionKind: "raw-commit",
|
|
70
|
+
selectionOrigin: "package-default",
|
|
71
|
+
selectionMode: "default",
|
|
72
|
+
upstreamSourceOrigin: "package-default",
|
|
73
|
+
saved: {
|
|
74
|
+
saved_mode: "none",
|
|
75
|
+
saved_source: "",
|
|
76
|
+
saved_requested_ref: "",
|
|
77
|
+
saved_resolved_ref: "",
|
|
78
|
+
saved_commit: "",
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
const compatibility = await assessCodexCompatibility(root, selection);
|
|
82
|
+
return {
|
|
83
|
+
root,
|
|
84
|
+
commit: receipt.commit,
|
|
85
|
+
identity: receipt.commit,
|
|
86
|
+
compatibility,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (cause) {
|
|
90
|
+
throw new SafetyError("codex-compatibility", `invalid Codex prepared assessment: ${root}`, { cause });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export async function assessCodexCompatibility(root, selection) {
|
|
94
|
+
try {
|
|
95
|
+
validateSource(selection.effectiveSource);
|
|
96
|
+
await validateNativeSkill(root);
|
|
97
|
+
for (const path of [
|
|
98
|
+
".codex/superpowers-codex",
|
|
99
|
+
".codex/superpowers-bootstrap.md",
|
|
100
|
+
]) {
|
|
101
|
+
if ((await classifyPathNoFollow(join(root, path))) !== "missing")
|
|
102
|
+
throw new Error("legacy bootstrap");
|
|
103
|
+
}
|
|
104
|
+
const path = join(root, ".codex-plugin/plugin.json");
|
|
105
|
+
if ((await classifyPathNoFollow(path)) !== "missing") {
|
|
106
|
+
await assertExistingContained(root, path);
|
|
107
|
+
const manifest = await readManifest(path);
|
|
108
|
+
if (manifest.name !== "superpowers" ||
|
|
109
|
+
typeof manifest.version !== "string" ||
|
|
110
|
+
!SEMVER_RE.test(manifest.version))
|
|
111
|
+
throw new Error("manifest");
|
|
112
|
+
await classifyHooks(manifest, "upstream", root);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
kind: "supported",
|
|
116
|
+
generation: "codex-native-skills-v1",
|
|
117
|
+
reason: "native Codex skill discovery",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return {
|
|
122
|
+
kind: "unsupported",
|
|
123
|
+
reason: `Codex package does not provide compatible native skill discovery: ${root}`,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { lstat, readFile, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { codexInstalledPluginsFromJson, } from "./codex-json.js";
|
|
4
|
+
export const CODEX_MANAGER_PLUGIN_ID = "superpowers@superpowers-manager";
|
|
5
|
+
export const CODEX_LEGACY_PLUGIN_ID = "superpowers@superpowers-wrapper";
|
|
6
|
+
const SUPERPOWERS_PLUGIN_PREFIX = "superpowers@";
|
|
7
|
+
const DISPLAYABLE_PLUGIN_ID = /^superpowers@[A-Za-z0-9][A-Za-z0-9._-]*/;
|
|
8
|
+
const NATIVE_ROUTE = ".agents/skills/superpowers";
|
|
9
|
+
const NATIVE_ROUTE_CONFLICT = `native Codex skills route ~/${NATIVE_ROUTE} has indeterminate activity`;
|
|
10
|
+
function isMissingPath(cause) {
|
|
11
|
+
if (cause === null || typeof cause !== "object" || !("code" in cause)) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return cause.code === "ENOENT" || cause.code === "ENOTDIR";
|
|
15
|
+
}
|
|
16
|
+
function isUnmanagedPluginId(pluginId) {
|
|
17
|
+
if (pluginId === CODEX_MANAGER_PLUGIN_ID ||
|
|
18
|
+
pluginId === CODEX_LEGACY_PLUGIN_ID) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
return (pluginId.startsWith(SUPERPOWERS_PLUGIN_PREFIX) &&
|
|
22
|
+
pluginId.length > SUPERPOWERS_PLUGIN_PREFIX.length);
|
|
23
|
+
}
|
|
24
|
+
function displayablePluginId(pluginId) {
|
|
25
|
+
return pluginId.length <= 256 &&
|
|
26
|
+
DISPLAYABLE_PLUGIN_ID.exec(pluginId)?.[0] === pluginId
|
|
27
|
+
? pluginId
|
|
28
|
+
: "";
|
|
29
|
+
}
|
|
30
|
+
function unmanagedPluginConflict(plugin) {
|
|
31
|
+
if (!isUnmanagedPluginId(plugin.pluginId))
|
|
32
|
+
return "";
|
|
33
|
+
const displayId = displayablePluginId(plugin.pluginId);
|
|
34
|
+
const subject = displayId.length > 0
|
|
35
|
+
? `Codex plugin ${displayId}`
|
|
36
|
+
: "Codex plugin with a non-displayable Superpowers identity";
|
|
37
|
+
if (plugin.installed === true && plugin.enabled === true) {
|
|
38
|
+
return `active ${subject}`;
|
|
39
|
+
}
|
|
40
|
+
if ((plugin.installed === true && plugin.enabled === false) ||
|
|
41
|
+
(plugin.installed === false && plugin.enabled === false)) {
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
return `${subject} has indeterminate activity`;
|
|
45
|
+
}
|
|
46
|
+
async function nativeRouteConflict(ctx) {
|
|
47
|
+
const home = ctx.env?.HOME;
|
|
48
|
+
if (home === undefined || home.length === 0)
|
|
49
|
+
return NATIVE_ROUTE_CONFLICT;
|
|
50
|
+
const route = join(home, ".agents", "skills", "superpowers");
|
|
51
|
+
const asset = join(route, "using-superpowers", "SKILL.md");
|
|
52
|
+
let routeIsSymlink;
|
|
53
|
+
try {
|
|
54
|
+
routeIsSymlink = (await lstat(route)).isSymbolicLink();
|
|
55
|
+
}
|
|
56
|
+
catch (cause) {
|
|
57
|
+
return isMissingPath(cause) ? "" : NATIVE_ROUTE_CONFLICT;
|
|
58
|
+
}
|
|
59
|
+
if (!routeIsSymlink) {
|
|
60
|
+
try {
|
|
61
|
+
await lstat(asset);
|
|
62
|
+
}
|
|
63
|
+
catch (cause) {
|
|
64
|
+
return isMissingPath(cause) ? "" : NATIVE_ROUTE_CONFLICT;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
const resolvedAsset = await realpath(asset);
|
|
69
|
+
if (!(await stat(resolvedAsset)).isFile())
|
|
70
|
+
return NATIVE_ROUTE_CONFLICT;
|
|
71
|
+
const contents = await readFile(resolvedAsset, "utf8");
|
|
72
|
+
const lines = contents.split(/\r?\n/u);
|
|
73
|
+
if (lines[0] !== "---")
|
|
74
|
+
return "";
|
|
75
|
+
const end = lines.indexOf("---", 1);
|
|
76
|
+
if (end < 0 ||
|
|
77
|
+
!lines
|
|
78
|
+
.slice(1, end)
|
|
79
|
+
.some((line) => /^name:[ \t]+using-superpowers[ \t]*$/u.test(line))) {
|
|
80
|
+
return "";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return NATIVE_ROUTE_CONFLICT;
|
|
85
|
+
}
|
|
86
|
+
return NATIVE_ROUTE_CONFLICT;
|
|
87
|
+
}
|
|
88
|
+
export async function inspectCodexConflicts(ctx, installedListing) {
|
|
89
|
+
const conflicts = new Set();
|
|
90
|
+
for (const plugin of codexInstalledPluginsFromJson(installedListing)) {
|
|
91
|
+
const conflict = unmanagedPluginConflict(plugin);
|
|
92
|
+
if (conflict.length > 0)
|
|
93
|
+
conflicts.add(conflict);
|
|
94
|
+
}
|
|
95
|
+
const nativeConflict = await nativeRouteConflict(ctx);
|
|
96
|
+
if (nativeConflict.length > 0)
|
|
97
|
+
conflicts.add(nativeConflict);
|
|
98
|
+
return [...conflicts];
|
|
99
|
+
}
|