superpowers-manager 0.5.1 → 0.5.2
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 +1 -1
- package/dist/cli.js +1 -1
- package/dist/commands/install.js +16 -6
- package/dist/commands/prepare.js +1 -1
- package/dist/commands/uninstall.js +1 -1
- package/dist/harnesses/codex/adapter.js +147 -18
- package/dist/harnesses/codex/generated-plugin.js +1 -1
- package/dist/harnesses/codex/harness.js +35 -17
- package/dist/harnesses/codex/hooks.js +1 -1
- package/dist/harnesses/codex/json.js +6 -2
- package/dist/harnesses/codex/lifecycle.js +1 -1
- package/dist/harnesses/codex/marketplace.js +148 -0
- package/dist/harnesses/codex/paths.js +97 -0
- package/dist/harnesses/codex/prepare.js +34 -2
- package/dist/harnesses/codex/presentation.js +4 -3
- package/dist/harnesses/codex/publication.js +538 -0
- package/dist/harnesses/codex/recovery.js +338 -0
- package/dist/harnesses/codex/state.js +131 -0
- package/dist/harnesses/codex/stored-state.js +249 -0
- package/dist/provenance.js +3 -3
- package/node_modules/smol-toml/LICENSE +24 -0
- package/node_modules/smol-toml/README.md +418 -0
- package/node_modules/smol-toml/dist/date.d.ts +41 -0
- package/node_modules/smol-toml/dist/date.js +127 -0
- package/node_modules/smol-toml/dist/error.d.ts +38 -0
- package/node_modules/smol-toml/dist/error.js +63 -0
- package/node_modules/smol-toml/dist/extract.js +69 -0
- package/node_modules/smol-toml/dist/index.cjs +734 -0
- package/node_modules/smol-toml/dist/index.d.ts +43 -0
- package/node_modules/smol-toml/dist/index.js +33 -0
- package/node_modules/smol-toml/dist/parse.d.ts +36 -0
- package/node_modules/smol-toml/dist/parse.js +149 -0
- package/node_modules/smol-toml/dist/primitive.js +238 -0
- package/node_modules/smol-toml/dist/stringify.d.ts +31 -0
- package/node_modules/smol-toml/dist/stringify.js +181 -0
- package/node_modules/smol-toml/dist/struct.js +179 -0
- package/node_modules/smol-toml/dist/util.d.ts +38 -0
- package/node_modules/smol-toml/dist/util.js +89 -0
- package/node_modules/smol-toml/package.json +68 -0
- package/package.json +10 -2
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
2
|
+
import { assertNoFollowType, canonicalizeProspectivePath, } from "../../safe-path.js";
|
|
3
|
+
import { SafetyError } from "../../safety-error.js";
|
|
4
|
+
export function codexHome(env, cwd) {
|
|
5
|
+
const configured = env.CODEX_HOME;
|
|
6
|
+
if (configured !== undefined && configured.length > 0) {
|
|
7
|
+
return resolve(cwd, configured);
|
|
8
|
+
}
|
|
9
|
+
const home = env.HOME;
|
|
10
|
+
if (home === undefined || home.length === 0) {
|
|
11
|
+
throw new SafetyError("codex-paths", "cannot determine Codex state root without HOME");
|
|
12
|
+
}
|
|
13
|
+
return join(resolve(cwd, home), ".codex");
|
|
14
|
+
}
|
|
15
|
+
export function codexPaths(env, cwd) {
|
|
16
|
+
const root = codexHome(env, cwd);
|
|
17
|
+
const managerRoot = join(root, "superpowers-manager");
|
|
18
|
+
const marketplaceRoot = join(managerRoot, "marketplace");
|
|
19
|
+
return {
|
|
20
|
+
codexHome: root,
|
|
21
|
+
managerRoot,
|
|
22
|
+
preparedRoot: join(managerRoot, "prepared"),
|
|
23
|
+
marketplaceRoot,
|
|
24
|
+
publishedPluginRoot: join(marketplaceRoot, "plugins", "superpowers"),
|
|
25
|
+
recoveryRoot: join(managerRoot, "recovery"),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function overlaps(a, b) {
|
|
29
|
+
const inside = (root, leaf) => {
|
|
30
|
+
const suffix = relative(root, leaf);
|
|
31
|
+
return (suffix === "" ||
|
|
32
|
+
(!isAbsolute(suffix) && suffix !== ".." && !suffix.startsWith(`..${sep}`)));
|
|
33
|
+
};
|
|
34
|
+
return inside(a, b) || inside(b, a);
|
|
35
|
+
}
|
|
36
|
+
function separationError() {
|
|
37
|
+
return new SafetyError("codex-paths", "preparation overlaps Codex published or recovery storage", { details: { kind: "overlap" } });
|
|
38
|
+
}
|
|
39
|
+
async function inspectRoot(path, root) {
|
|
40
|
+
try {
|
|
41
|
+
await assertNoFollowType(path, ["directory", "missing"]);
|
|
42
|
+
return await canonicalizeProspectivePath(path);
|
|
43
|
+
}
|
|
44
|
+
catch (cause) {
|
|
45
|
+
throw new SafetyError("codex-paths", `cannot inspect ${root} root: ${path}`, { cause, details: { kind: "inspection", root, path } });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function codexPathFailureDetails(cause) {
|
|
49
|
+
if (!(cause instanceof SafetyError) || cause.module !== "codex-paths") {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const details = cause.details;
|
|
53
|
+
if (details === null || typeof details !== "object" || !("kind" in details)) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (details.kind === "overlap")
|
|
57
|
+
return { kind: "overlap" };
|
|
58
|
+
if (details.kind !== "inspection" ||
|
|
59
|
+
!("root" in details) ||
|
|
60
|
+
!("path" in details) ||
|
|
61
|
+
(details.root !== "preparation" &&
|
|
62
|
+
details.root !== "marketplace" &&
|
|
63
|
+
details.root !== "recovery") ||
|
|
64
|
+
typeof details.path !== "string") {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return { kind: "inspection", root: details.root, path: details.path };
|
|
68
|
+
}
|
|
69
|
+
export async function assertCodexPreparationSeparate(preparedRoot, paths) {
|
|
70
|
+
const recovery = await inspectRoot(paths.recoveryRoot, "recovery");
|
|
71
|
+
const marketplace = await inspectRoot(paths.marketplaceRoot, "marketplace");
|
|
72
|
+
let prepared;
|
|
73
|
+
try {
|
|
74
|
+
prepared = await inspectRoot(preparedRoot, "preparation");
|
|
75
|
+
}
|
|
76
|
+
catch (cause) {
|
|
77
|
+
const failure = codexPathFailureDetails(cause);
|
|
78
|
+
if (failure?.kind !== "inspection" || failure.root !== "preparation") {
|
|
79
|
+
throw cause;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const canonical = await canonicalizeProspectivePath(preparedRoot);
|
|
83
|
+
if (overlaps(canonical, marketplace) || overlaps(canonical, recovery)) {
|
|
84
|
+
throw separationError();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
catch (resolutionCause) {
|
|
88
|
+
if (codexPathFailureDetails(resolutionCause)?.kind === "overlap") {
|
|
89
|
+
throw resolutionCause;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw cause;
|
|
93
|
+
}
|
|
94
|
+
if (overlaps(prepared, marketplace) || overlaps(prepared, recovery)) {
|
|
95
|
+
throw separationError();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -9,6 +9,8 @@ import { readGeneratedCommitLenient, readStrictProvenanceField, writeProvenance,
|
|
|
9
9
|
import { classifyPathNoFollow } from "../../safe-path.js";
|
|
10
10
|
import { SafetyError } from "../../safety-error.js";
|
|
11
11
|
import { manifestVersionForRef } from "../../upstream-version.js";
|
|
12
|
+
import { assertCodexPreparationSeparate, codexPathFailureDetails, codexPaths, } from "./paths.js";
|
|
13
|
+
import { readCodexRecovery } from "./recovery.js";
|
|
12
14
|
// Order is inherited from the original prepare command; the first miss wins.
|
|
13
15
|
const REQUIRED_UPSTREAM = [
|
|
14
16
|
{ path: "skills", label: "skills/" },
|
|
@@ -75,7 +77,7 @@ async function copyPathIfPresent(source, destination) {
|
|
|
75
77
|
await owned(`cannot clear candidate path: ${destination}`, () => rm(destination, { recursive: true, force: true }));
|
|
76
78
|
await owned(`cannot copy upstream path into candidate: ${source}`, () => cp(source, destination, { recursive: true, verbatimSymlinks: true }));
|
|
77
79
|
}
|
|
78
|
-
|
|
80
|
+
function resolveFromCwd(value, cwd) {
|
|
79
81
|
return isAbsolute(value) ? value : resolve(cwd, value);
|
|
80
82
|
}
|
|
81
83
|
function manifestTemplate(ctx) {
|
|
@@ -86,11 +88,41 @@ function manifestTemplate(ctx) {
|
|
|
86
88
|
export function codexPreparationLocation(ctx) {
|
|
87
89
|
const env = ctx.env ?? {};
|
|
88
90
|
return {
|
|
89
|
-
destinationRoot:
|
|
91
|
+
destinationRoot: env.SUPERPOWERS_PLUGIN_ROOT !== undefined &&
|
|
92
|
+
env.SUPERPOWERS_PLUGIN_ROOT.length > 0
|
|
93
|
+
? resolveFromCwd(env.SUPERPOWERS_PLUGIN_ROOT, process.cwd())
|
|
94
|
+
: codexPaths(env, process.cwd()).preparedRoot,
|
|
90
95
|
stagingLeaf: "superpowers",
|
|
91
96
|
};
|
|
92
97
|
}
|
|
93
98
|
export async function validateCodexPreparationBeforeFetch(ctx) {
|
|
99
|
+
const paths = codexPaths(ctx.env ?? {}, process.cwd());
|
|
100
|
+
const preparedRoot = codexPreparationLocation(ctx).destinationRoot;
|
|
101
|
+
try {
|
|
102
|
+
await assertCodexPreparationSeparate(preparedRoot, paths);
|
|
103
|
+
}
|
|
104
|
+
catch (cause) {
|
|
105
|
+
const failure = codexPathFailureDetails(cause);
|
|
106
|
+
if (failure?.kind === "inspection") {
|
|
107
|
+
if (failure.root === "recovery") {
|
|
108
|
+
return failureResult("prepare", "recovery-required", `cannot inspect Codex recovery state at ${paths.recoveryRoot}`, [], []);
|
|
109
|
+
}
|
|
110
|
+
return failureResult("prepare", "prepare-failed", failure.root === "marketplace"
|
|
111
|
+
? `cannot inspect Codex marketplace storage at ${paths.marketplaceRoot}`
|
|
112
|
+
: `cannot inspect Codex preparation root at ${preparedRoot}`, [], []);
|
|
113
|
+
}
|
|
114
|
+
return failureResult("prepare", failure?.kind === "overlap" ? "preparation-overlap" : "prepare-failed", failure?.kind === "overlap"
|
|
115
|
+
? `Codex preparation overlaps Codex published or recovery storage: ${preparedRoot}`
|
|
116
|
+
: `cannot validate Codex preparation storage separation at ${preparedRoot}`, [], []);
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
if ((await readCodexRecovery(paths)) !== null) {
|
|
120
|
+
return failureResult("prepare", "recovery-required", `Codex recovery is required before preparation; preserve material at ${paths.recoveryRoot}`, [], []);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return failureResult("prepare", "recovery-required", `cannot inspect Codex recovery state at ${paths.recoveryRoot}`, [], []);
|
|
125
|
+
}
|
|
94
126
|
const template = manifestTemplate(ctx);
|
|
95
127
|
if (!(await regularFileExists(template))) {
|
|
96
128
|
return failureResult("prepare", "missing-template", `missing fallback manifest template: ${template}`, [], []);
|
|
@@ -2,7 +2,7 @@ import { normalizeCodexInstall, normalizeCodexInstalled } from "./harness.js";
|
|
|
2
2
|
import { displaySource } from "../../selection.js";
|
|
3
3
|
// One ordered table drives both formats, preserving the public human and
|
|
4
4
|
// porcelain field order exposed through the harness presentation.
|
|
5
|
-
|
|
5
|
+
function fields(f) {
|
|
6
6
|
return [
|
|
7
7
|
{ key: "harness", label: "harness", value: f.harness },
|
|
8
8
|
{ key: "requested_ref", label: "requested ref", value: f.requestedRef },
|
|
@@ -251,17 +251,18 @@ export const codexPresentation = {
|
|
|
251
251
|
if (inspection.outcome.result.kind === "current") {
|
|
252
252
|
return { stdout: [...stdout, "manager updated"], stderr: [] };
|
|
253
253
|
}
|
|
254
|
+
const missing = inspection.outcome.result.kind === "absent" || observed.length === 0;
|
|
254
255
|
if (receipt.status !== 0 || !receipt.outcome.ok) {
|
|
255
256
|
return {
|
|
256
257
|
stdout,
|
|
257
258
|
stderr: [
|
|
258
|
-
|
|
259
|
+
missing
|
|
259
260
|
? "error: installed manager fingerprint is not detectable after install."
|
|
260
261
|
: "error: installed manager fingerprint does not match the prepared plugin after install.",
|
|
261
262
|
],
|
|
262
263
|
};
|
|
263
264
|
}
|
|
264
|
-
const output =
|
|
265
|
+
const output = missing
|
|
265
266
|
? receipt.outcome.result.missingVerificationOutput
|
|
266
267
|
: receipt.outcome.result.mismatchVerificationOutput;
|
|
267
268
|
return { stdout, stderr: output.stderr };
|