superpowers-manager 0.9.1 → 0.10.1
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 +35 -16
- package/dist/cli-arguments.js +2 -1
- package/dist/cli.js +12 -9
- package/dist/commands/adapter-call.js +0 -11
- package/dist/commands/install.js +153 -171
- package/dist/commands/prepare.js +1 -1
- package/dist/commands/uninstall.js +49 -64
- package/dist/harnesses/hermes/harness.js +36 -0
- package/dist/harnesses/hermes/install.js +235 -0
- package/dist/harnesses/hermes/native.js +40 -0
- package/dist/harnesses/hermes/observe.js +178 -0
- package/dist/harnesses/hermes/paths.js +48 -0
- package/dist/harnesses/hermes/prepare.js +78 -0
- package/dist/harnesses/hermes/presentation.js +23 -0
- package/dist/harnesses/hermes/state.js +144 -0
- package/dist/harnesses/opencode/paths.js +8 -14
- package/dist/snapshot-prepare.js +2 -0
- package/node_modules/yaml/LICENSE +13 -0
- package/node_modules/yaml/README.md +172 -0
- package/node_modules/yaml/bin.mjs +11 -0
- package/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
- package/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
- package/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
- package/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
- package/node_modules/yaml/browser/dist/compose/composer.js +219 -0
- package/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
- package/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
- package/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
- package/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
- package/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
- package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +225 -0
- package/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
- package/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
- package/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
- package/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
- package/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
- package/node_modules/yaml/browser/dist/doc/Document.js +335 -0
- package/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
- package/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
- package/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
- package/node_modules/yaml/browser/dist/doc/directives.js +176 -0
- package/node_modules/yaml/browser/dist/errors.js +57 -0
- package/node_modules/yaml/browser/dist/index.js +17 -0
- package/node_modules/yaml/browser/dist/log.js +11 -0
- package/node_modules/yaml/browser/dist/nodes/Alias.js +116 -0
- package/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
- package/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
- package/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
- package/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
- package/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
- package/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
- package/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
- package/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
- package/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
- package/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
- package/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
- package/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
- package/node_modules/yaml/browser/dist/parse/cst.js +98 -0
- package/node_modules/yaml/browser/dist/parse/lexer.js +721 -0
- package/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
- package/node_modules/yaml/browser/dist/parse/parser.js +975 -0
- package/node_modules/yaml/browser/dist/public-api.js +102 -0
- package/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
- package/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
- package/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
- package/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
- package/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
- package/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
- package/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
- package/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
- package/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
- package/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
- package/node_modules/yaml/browser/dist/schema/tags.js +96 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +67 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
- package/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
- package/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
- package/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +25 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
- package/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
- package/node_modules/yaml/browser/dist/util.js +11 -0
- package/node_modules/yaml/browser/dist/visit.js +233 -0
- package/node_modules/yaml/browser/index.js +5 -0
- package/node_modules/yaml/browser/package.json +3 -0
- package/node_modules/yaml/dist/cli.d.ts +8 -0
- package/node_modules/yaml/dist/cli.mjs +201 -0
- package/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
- package/node_modules/yaml/dist/compose/compose-collection.js +90 -0
- package/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
- package/node_modules/yaml/dist/compose/compose-doc.js +45 -0
- package/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
- package/node_modules/yaml/dist/compose/compose-node.js +112 -0
- package/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
- package/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
- package/node_modules/yaml/dist/compose/composer.d.ts +63 -0
- package/node_modules/yaml/dist/compose/composer.js +224 -0
- package/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
- package/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
- package/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
- package/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
- package/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
- package/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
- package/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
- package/node_modules/yaml/dist/compose/resolve-end.js +39 -0
- package/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
- package/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
- package/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
- package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
- package/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
- package/node_modules/yaml/dist/compose/resolve-props.js +148 -0
- package/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
- package/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
- package/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
- package/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
- package/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
- package/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
- package/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
- package/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
- package/node_modules/yaml/dist/doc/Document.d.ts +141 -0
- package/node_modules/yaml/dist/doc/Document.js +337 -0
- package/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
- package/node_modules/yaml/dist/doc/anchors.js +76 -0
- package/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
- package/node_modules/yaml/dist/doc/applyReviver.js +57 -0
- package/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
- package/node_modules/yaml/dist/doc/createNode.js +90 -0
- package/node_modules/yaml/dist/doc/directives.d.ts +49 -0
- package/node_modules/yaml/dist/doc/directives.js +178 -0
- package/node_modules/yaml/dist/errors.d.ts +21 -0
- package/node_modules/yaml/dist/errors.js +62 -0
- package/node_modules/yaml/dist/index.d.ts +25 -0
- package/node_modules/yaml/dist/index.js +50 -0
- package/node_modules/yaml/dist/log.d.ts +3 -0
- package/node_modules/yaml/dist/log.js +19 -0
- package/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
- package/node_modules/yaml/dist/nodes/Alias.js +118 -0
- package/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
- package/node_modules/yaml/dist/nodes/Collection.js +151 -0
- package/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
- package/node_modules/yaml/dist/nodes/Node.js +40 -0
- package/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
- package/node_modules/yaml/dist/nodes/Pair.js +39 -0
- package/node_modules/yaml/dist/nodes/Scalar.d.ts +47 -0
- package/node_modules/yaml/dist/nodes/Scalar.js +27 -0
- package/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
- package/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
- package/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
- package/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
- package/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
- package/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
- package/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
- package/node_modules/yaml/dist/nodes/identity.js +53 -0
- package/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
- package/node_modules/yaml/dist/nodes/toJS.js +39 -0
- package/node_modules/yaml/dist/options.d.ts +350 -0
- package/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
- package/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
- package/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
- package/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
- package/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
- package/node_modules/yaml/dist/parse/cst-visit.js +99 -0
- package/node_modules/yaml/dist/parse/cst.d.ts +109 -0
- package/node_modules/yaml/dist/parse/cst.js +112 -0
- package/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
- package/node_modules/yaml/dist/parse/lexer.js +723 -0
- package/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
- package/node_modules/yaml/dist/parse/line-counter.js +41 -0
- package/node_modules/yaml/dist/parse/parser.d.ts +84 -0
- package/node_modules/yaml/dist/parse/parser.js +980 -0
- package/node_modules/yaml/dist/public-api.d.ts +44 -0
- package/node_modules/yaml/dist/public-api.js +107 -0
- package/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
- package/node_modules/yaml/dist/schema/Schema.js +39 -0
- package/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
- package/node_modules/yaml/dist/schema/common/map.js +19 -0
- package/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
- package/node_modules/yaml/dist/schema/common/null.js +17 -0
- package/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
- package/node_modules/yaml/dist/schema/common/seq.js +19 -0
- package/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
- package/node_modules/yaml/dist/schema/common/string.js +16 -0
- package/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
- package/node_modules/yaml/dist/schema/core/bool.js +21 -0
- package/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
- package/node_modules/yaml/dist/schema/core/float.js +47 -0
- package/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
- package/node_modules/yaml/dist/schema/core/int.js +42 -0
- package/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
- package/node_modules/yaml/dist/schema/core/schema.js +25 -0
- package/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
- package/node_modules/yaml/dist/schema/json/schema.js +64 -0
- package/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
- package/node_modules/yaml/dist/schema/tags.d.ts +48 -0
- package/node_modules/yaml/dist/schema/tags.js +99 -0
- package/node_modules/yaml/dist/schema/types.d.ts +92 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
- package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
- package/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
- package/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
- package/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
- package/node_modules/yaml/dist/stringify/stringify.js +132 -0
- package/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
- package/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
- package/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
- package/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
- package/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
- package/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
- package/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
- package/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
- package/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
- package/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
- package/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
- package/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
- package/node_modules/yaml/dist/test-events.d.ts +4 -0
- package/node_modules/yaml/dist/test-events.js +134 -0
- package/node_modules/yaml/dist/util.d.ts +16 -0
- package/node_modules/yaml/dist/util.js +28 -0
- package/node_modules/yaml/dist/visit.d.ts +102 -0
- package/node_modules/yaml/dist/visit.js +236 -0
- package/node_modules/yaml/package.json +97 -0
- package/node_modules/yaml/util.js +2 -0
- package/package.json +13 -7
|
@@ -1,91 +1,76 @@
|
|
|
1
1
|
import { tmpdir } from "node:os";
|
|
2
2
|
import { oneLine } from "../cli-arguments.js";
|
|
3
3
|
import { withWorkspaceReporting, } from "../workspace.js";
|
|
4
|
-
import {
|
|
4
|
+
import { invoke, writeOutput } from "./adapter-call.js";
|
|
5
5
|
import { runWithMutation } from "./mutation.js";
|
|
6
6
|
import { replayOutcome } from "./probe.js";
|
|
7
|
-
// Collect outcomes without writing so output failures
|
|
8
|
-
// inspection failures.
|
|
9
|
-
|
|
7
|
+
// Collect outcomes into the caller's array without writing, so output failures
|
|
8
|
+
// cannot be classified as inspection failures. The caller replays them after
|
|
9
|
+
// gathering, including when gathering throws.
|
|
10
|
+
async function gatherUninstall(ctx, outcomes) {
|
|
10
11
|
const parent = ctx.env.TMPDIR ?? tmpdir();
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const after = await invoke(() => ctx.adapter.inspectOwnership(adapterContext), afterFailure, outcomes);
|
|
39
|
-
if (!after.ok)
|
|
40
|
-
return failed(after.message);
|
|
41
|
-
const ownership = after.result.outcome.result;
|
|
42
|
-
if (ownership.removalVerification.kind === "blocked") {
|
|
43
|
-
return {
|
|
44
|
-
status: 1,
|
|
45
|
-
outcomes,
|
|
46
|
-
message: null,
|
|
47
|
-
output: ownership.removalVerification.output,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
12
|
+
return await withWorkspaceReporting(parent, "superpowers-manager.uninstall.", async (workspace) => {
|
|
13
|
+
const env = { ...ctx.env, TMPDIR: workspace };
|
|
14
|
+
const failed = (message) => ({
|
|
15
|
+
status: 1,
|
|
16
|
+
message,
|
|
17
|
+
output: null,
|
|
18
|
+
});
|
|
19
|
+
const adapterContext = { root: ctx.root, env };
|
|
20
|
+
// Stage 1: inspect ownership, before removal.
|
|
21
|
+
const beforeFailure = ctx.adapter.presentation.callFailure("remove-ownership", adapterContext);
|
|
22
|
+
const before = await invoke(() => ctx.adapter.inspectOwnership(adapterContext), beforeFailure, outcomes);
|
|
23
|
+
if (!before.ok)
|
|
24
|
+
return failed(before.message);
|
|
25
|
+
// Stage 2: remove, passing the private input through untouched.
|
|
26
|
+
const removalInput = before.result.outcome.result.removalInput;
|
|
27
|
+
const removeFailure = ctx.adapter.presentation.callFailure("remove", adapterContext, removalInput);
|
|
28
|
+
const removed = await invoke(() => ctx.adapter.remove(removalInput, adapterContext), removeFailure, outcomes);
|
|
29
|
+
if (!removed.ok)
|
|
30
|
+
return failed(removed.message);
|
|
31
|
+
// Stage 3: inspect ownership again. Everything below reads this
|
|
32
|
+
// post-removal state, not the inspection before removal.
|
|
33
|
+
const afterFailure = ctx.adapter.presentation.callFailure("post-remove", adapterContext);
|
|
34
|
+
const after = await invoke(() => ctx.adapter.inspectOwnership(adapterContext), afterFailure, outcomes);
|
|
35
|
+
if (!after.ok)
|
|
36
|
+
return failed(after.message);
|
|
37
|
+
const ownership = after.result.outcome.result;
|
|
38
|
+
if (ownership.removalVerification.kind === "blocked") {
|
|
50
39
|
return {
|
|
51
|
-
status:
|
|
52
|
-
|
|
53
|
-
output:
|
|
40
|
+
status: 1,
|
|
41
|
+
message: null,
|
|
42
|
+
output: ownership.removalVerification.output,
|
|
54
43
|
};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// cleanupWarning. Wrapping with `outcomes` anyway keeps the class total
|
|
62
|
-
// over its declared contract rather than assuming the callback's purity at
|
|
63
|
-
// the throw site.
|
|
64
|
-
throw new GatherFailure("uninstall gather failed", cause, outcomes);
|
|
65
|
-
}
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
status: 0,
|
|
47
|
+
output: ctx.adapter.presentation.renderRemovalCompletion(ownership, removalInput),
|
|
48
|
+
};
|
|
49
|
+
});
|
|
66
50
|
}
|
|
67
51
|
export async function runUninstall(argv, ctx) {
|
|
68
52
|
// argv is ignored: scripts/uninstall never reads "$@".
|
|
69
53
|
return await runWithMutation("uninstall", ctx, async (scoped) => performUninstall(scoped));
|
|
70
54
|
}
|
|
71
55
|
async function performUninstall(ctx) {
|
|
56
|
+
// Owned here, not in gatherUninstall, so a gather throw still replays every
|
|
57
|
+
// outcome collected before it.
|
|
58
|
+
const outcomes = [];
|
|
72
59
|
let run;
|
|
73
60
|
try {
|
|
74
|
-
run = await gatherUninstall(ctx);
|
|
61
|
+
run = await gatherUninstall(ctx, outcomes);
|
|
75
62
|
}
|
|
76
63
|
catch (cause) {
|
|
77
64
|
// Replay collected outcomes before reporting a gather failure. A
|
|
78
65
|
// post-success cleanup failure arrives as cleanupWarning, not here.
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const inner = cause instanceof GatherFailure ? cause.inner : cause;
|
|
83
|
-
ctx.stderr.write(`error: ${oneLine(inner)}\n`);
|
|
66
|
+
for (const each of outcomes)
|
|
67
|
+
replayOutcome(each, ctx);
|
|
68
|
+
ctx.stderr.write(`error: ${oneLine(cause)}\n`);
|
|
84
69
|
return 1;
|
|
85
70
|
}
|
|
86
71
|
const { value: outcome, cleanupWarning } = run;
|
|
87
72
|
// Replay before reporting a command-level result on either path.
|
|
88
|
-
for (const each of
|
|
73
|
+
for (const each of outcomes)
|
|
89
74
|
replayOutcome(each, ctx);
|
|
90
75
|
let status;
|
|
91
76
|
if (outcome.status === 1) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { displayPath } from "../../validator.js";
|
|
2
|
+
import { installHermes, removeHermes } from "./install.js";
|
|
3
|
+
import { hermesPaths } from "./paths.js";
|
|
4
|
+
import { hermesPreparationLocation, inspectHermesPrepared, prepareHermesCandidate, readHermesPrepared, validateHermesPreparationBeforeFetch, } from "./prepare.js";
|
|
5
|
+
import { hermesPresentation } from "./presentation.js";
|
|
6
|
+
import { inspectHermesControl, inspectHermesInstalled, inspectHermesOwnership, } from "./state.js";
|
|
7
|
+
function requirements(command, env) {
|
|
8
|
+
if (command !== "install" && command !== "update" && command !== "uninstall")
|
|
9
|
+
return [];
|
|
10
|
+
const executable = env.SUPERPOWERS_HERMES || "hermes";
|
|
11
|
+
return [
|
|
12
|
+
{
|
|
13
|
+
name: "hermes",
|
|
14
|
+
executable,
|
|
15
|
+
lookup: "explicit-path-or-path",
|
|
16
|
+
missingMessage: `required command not found: ${displayPath(executable)} — install Hermes Agent or set SUPERPOWERS_HERMES`,
|
|
17
|
+
},
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
export const hermesHarness = {
|
|
21
|
+
preparationLocation: hermesPreparationLocation,
|
|
22
|
+
mutationRoots: async (ctx) => [
|
|
23
|
+
hermesPaths(ctx.env ?? {}, process.cwd()).hermesHome,
|
|
24
|
+
],
|
|
25
|
+
validatePreparationBeforeFetch: validateHermesPreparationBeforeFetch,
|
|
26
|
+
prepareCandidate: prepareHermesCandidate,
|
|
27
|
+
inspectPrepared: inspectHermesPrepared,
|
|
28
|
+
readPrepared: readHermesPrepared,
|
|
29
|
+
inspectOwnership: inspectHermesOwnership,
|
|
30
|
+
inspectUpdateControl: inspectHermesControl,
|
|
31
|
+
inspectInstalled: inspectHermesInstalled,
|
|
32
|
+
install: installHermes,
|
|
33
|
+
remove: removeHermes,
|
|
34
|
+
requirements,
|
|
35
|
+
presentation: hermesPresentation,
|
|
36
|
+
};
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { cp, mkdir, mkdtemp, rename, rm } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { failureResult, successResult, } from "../../adapter-result.js";
|
|
4
|
+
import { assertNoFollowType } from "../../safe-path.js";
|
|
5
|
+
import { observeSnapshot } from "../../snapshot-package.js";
|
|
6
|
+
import { displayPath } from "../../validator.js";
|
|
7
|
+
import { runHermes } from "./native.js";
|
|
8
|
+
import { projectPluginsEnabled, readHermesStatus } from "./observe.js";
|
|
9
|
+
import { assertHermesStorageSafe, hermesPaths, } from "./paths.js";
|
|
10
|
+
import { readHermesPackageAssessment, readHermesReceipt } from "./prepare.js";
|
|
11
|
+
import { leftoverHermesPublication, observeHermesOwnership, otherSuperpowersManifests, } from "./state.js";
|
|
12
|
+
const DEFAULTS = { run: runHermes };
|
|
13
|
+
// Only hand-written diagnostics and bounded native exit statuses enter this type.
|
|
14
|
+
class HermesStep extends Error {
|
|
15
|
+
}
|
|
16
|
+
async function native(run, args, ctx) {
|
|
17
|
+
const result = await run(args, ctx);
|
|
18
|
+
if (!result.outcome.ok || result.status !== 0) {
|
|
19
|
+
const reported = result.outcome.ok
|
|
20
|
+
? undefined
|
|
21
|
+
: /^Hermes command exited with status ([0-9]{1,3})$/.exec(result.outcome.error.message)?.[1];
|
|
22
|
+
const status = reported !== undefined && Number(reported) <= 255
|
|
23
|
+
? ` (exit status ${reported})`
|
|
24
|
+
: "";
|
|
25
|
+
throw new HermesStep(`hermes ${args.join(" ")} did not complete${status}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async function checkPublicationState(paths) {
|
|
29
|
+
const leftovers = await leftoverHermesPublication(paths);
|
|
30
|
+
if (leftovers.length > 0)
|
|
31
|
+
throw new HermesStep(`Hermes recovery required; inspect leftover publication material at ${displayPath(leftovers[0])}`);
|
|
32
|
+
const conflicts = await otherSuperpowersManifests(paths);
|
|
33
|
+
if (conflicts.length > 0)
|
|
34
|
+
throw new HermesStep(`Hermes discovers another plugin named superpowers at ${displayPath(conflicts[0])}`);
|
|
35
|
+
}
|
|
36
|
+
async function requireSnapshot(root, digest) {
|
|
37
|
+
const snapshot = await observeSnapshot(root, readHermesReceipt);
|
|
38
|
+
if (snapshot.kind !== "owned" || snapshot.digest !== digest)
|
|
39
|
+
throw new HermesStep("the Hermes snapshot changed during publication");
|
|
40
|
+
}
|
|
41
|
+
async function verifyRemoved(paths, ctx) {
|
|
42
|
+
if ((await observeHermesOwnership(paths)).kind !== "absent" ||
|
|
43
|
+
(await readHermesStatus(paths, ctx.env ?? {})) !== "not enabled")
|
|
44
|
+
throw new HermesStep("Hermes still lists Superpowers or its plugin directory is still present after removal");
|
|
45
|
+
}
|
|
46
|
+
async function rollback(publication, identity, previouslyEnabled, paths, ctx, run) {
|
|
47
|
+
await assertHermesStorageSafe(paths);
|
|
48
|
+
await assertNoFollowType(publication.container, ["directory"]);
|
|
49
|
+
const status = await readHermesStatus(paths, ctx.env ?? {});
|
|
50
|
+
if (publication.priorDigest !== undefined) {
|
|
51
|
+
if (publication.movedBackup) {
|
|
52
|
+
await requireSnapshot(publication.backup, publication.priorDigest);
|
|
53
|
+
if (publication.published) {
|
|
54
|
+
await requireSnapshot(paths.pluginRoot, identity);
|
|
55
|
+
await assertNoFollowType(publication.stage, ["missing"]);
|
|
56
|
+
await rename(paths.pluginRoot, publication.stage);
|
|
57
|
+
publication.published = false;
|
|
58
|
+
}
|
|
59
|
+
await assertNoFollowType(paths.pluginRoot, ["missing"]);
|
|
60
|
+
await rename(publication.backup, paths.pluginRoot);
|
|
61
|
+
publication.movedBackup = false;
|
|
62
|
+
}
|
|
63
|
+
await requireSnapshot(paths.pluginRoot, publication.priorDigest);
|
|
64
|
+
if (!previouslyEnabled && status === "enabled") {
|
|
65
|
+
if ((await otherSuperpowersManifests(paths)).length > 0)
|
|
66
|
+
throw new HermesStep("Hermes discovers another plugin named superpowers during rollback");
|
|
67
|
+
await native(run, ["plugins", "disable", "superpowers"], ctx);
|
|
68
|
+
if ((await readHermesStatus(paths, ctx.env ?? {})) !== "disabled")
|
|
69
|
+
throw new HermesStep("Hermes still enables Superpowers after rollback");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else if (publication.published) {
|
|
73
|
+
await requireSnapshot(paths.pluginRoot, identity);
|
|
74
|
+
if ((await otherSuperpowersManifests(paths)).length > 0)
|
|
75
|
+
throw new HermesStep("Hermes discovers another plugin named superpowers during rollback");
|
|
76
|
+
await native(run, ["plugins", "remove", "superpowers"], ctx);
|
|
77
|
+
await verifyRemoved(paths, ctx);
|
|
78
|
+
publication.published = false;
|
|
79
|
+
}
|
|
80
|
+
await rm(publication.container, { recursive: true, force: false });
|
|
81
|
+
}
|
|
82
|
+
async function settle(operation, message, action) {
|
|
83
|
+
try {
|
|
84
|
+
await action();
|
|
85
|
+
return successResult(operation, null, []);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return failureResult(operation, "settlement-failed", message, [], []);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
export async function installHermes(artifact, ctx, deps = DEFAULTS) {
|
|
92
|
+
const operation = "install-hermes";
|
|
93
|
+
const paths = hermesPaths(ctx.env ?? {}, process.cwd());
|
|
94
|
+
let publication;
|
|
95
|
+
let previouslyEnabled = false;
|
|
96
|
+
try {
|
|
97
|
+
const ownership = await observeHermesOwnership(paths);
|
|
98
|
+
if (ownership.kind === "foreign")
|
|
99
|
+
throw new HermesStep("refusing to replace a foreign Hermes plugin directory; remove it manually");
|
|
100
|
+
await checkPublicationState(paths);
|
|
101
|
+
if (projectPluginsEnabled(ctx.env ?? {}))
|
|
102
|
+
throw new HermesStep("HERMES_ENABLE_PROJECT_PLUGINS is set; unset it before installing Superpowers");
|
|
103
|
+
if (artifact.root !== paths.preparedRoot)
|
|
104
|
+
throw new HermesStep("unexpected Hermes prepared root");
|
|
105
|
+
const assessment = await readHermesPackageAssessment(artifact.root);
|
|
106
|
+
if (assessment.receipt.digest !== artifact.identity ||
|
|
107
|
+
assessment.receipt.commit !== artifact.commit ||
|
|
108
|
+
assessment.compatibility.kind !== "supported")
|
|
109
|
+
throw new HermesStep("the Hermes prepared artifact changed before activation");
|
|
110
|
+
previouslyEnabled =
|
|
111
|
+
(await readHermesStatus(paths, ctx.env ?? {})) === "enabled";
|
|
112
|
+
await mkdir(paths.pluginsRoot, { recursive: true });
|
|
113
|
+
await assertHermesStorageSafe(paths);
|
|
114
|
+
const container = await mkdtemp(join(paths.managerRoot, "publish."));
|
|
115
|
+
const pending = {
|
|
116
|
+
container,
|
|
117
|
+
stage: join(container, "stage"),
|
|
118
|
+
backup: join(container, "backup"),
|
|
119
|
+
priorDigest: ownership.kind === "owned" ? ownership.digest : undefined,
|
|
120
|
+
movedBackup: false,
|
|
121
|
+
published: false,
|
|
122
|
+
};
|
|
123
|
+
publication = pending;
|
|
124
|
+
await cp(artifact.root, pending.stage, {
|
|
125
|
+
recursive: true,
|
|
126
|
+
verbatimSymlinks: true,
|
|
127
|
+
force: false,
|
|
128
|
+
errorOnExist: true,
|
|
129
|
+
});
|
|
130
|
+
const staged = await readHermesPackageAssessment(pending.stage);
|
|
131
|
+
if (staged.receipt.digest !== artifact.identity ||
|
|
132
|
+
staged.receipt.commit !== artifact.commit ||
|
|
133
|
+
staged.compatibility.kind !== "supported")
|
|
134
|
+
throw new HermesStep("the staged Hermes artifact changed");
|
|
135
|
+
// Keep backups outside plugins so Hermes discovery sees only the live copy.
|
|
136
|
+
if (ownership.kind === "owned") {
|
|
137
|
+
await requireSnapshot(paths.pluginRoot, ownership.digest);
|
|
138
|
+
await rename(paths.pluginRoot, pending.backup);
|
|
139
|
+
pending.movedBackup = true;
|
|
140
|
+
}
|
|
141
|
+
else
|
|
142
|
+
await assertNoFollowType(paths.pluginRoot, ["missing"]);
|
|
143
|
+
await rename(pending.stage, paths.pluginRoot);
|
|
144
|
+
pending.published = true;
|
|
145
|
+
if (!previouslyEnabled)
|
|
146
|
+
await native(deps.run, ["plugins", "enable", "superpowers"], ctx);
|
|
147
|
+
let settled = false;
|
|
148
|
+
return successResult(operation, {
|
|
149
|
+
missingVerificationOutput: {
|
|
150
|
+
stdout: [],
|
|
151
|
+
stderr: [
|
|
152
|
+
"error: the Manager Hermes plugin is absent after activation",
|
|
153
|
+
],
|
|
154
|
+
},
|
|
155
|
+
mismatchVerificationOutput: {
|
|
156
|
+
stdout: [],
|
|
157
|
+
stderr: [
|
|
158
|
+
"error: the installed Hermes plugin does not match the prepared artifact; run `hermes plugins list` to inspect load errors",
|
|
159
|
+
],
|
|
160
|
+
},
|
|
161
|
+
transaction: {
|
|
162
|
+
finalize: () => settle(operation, `Hermes activation succeeded but backup cleanup failed at ${displayPath(container)}`, async () => {
|
|
163
|
+
if (settled)
|
|
164
|
+
return;
|
|
165
|
+
await assertHermesStorageSafe(paths);
|
|
166
|
+
await assertNoFollowType(container, ["directory"]);
|
|
167
|
+
await requireSnapshot(paths.pluginRoot, artifact.identity);
|
|
168
|
+
if (pending.movedBackup)
|
|
169
|
+
await requireSnapshot(pending.backup, pending.priorDigest);
|
|
170
|
+
await rm(container, { recursive: true, force: false });
|
|
171
|
+
settled = true;
|
|
172
|
+
}),
|
|
173
|
+
rollback: () => settle(operation, `Hermes rollback did not complete; the previous state may not be restored — inspect ${displayPath(container)}`, async () => {
|
|
174
|
+
if (settled)
|
|
175
|
+
return;
|
|
176
|
+
await rollback(pending, artifact.identity, previouslyEnabled, paths, ctx, deps.run);
|
|
177
|
+
settled = true;
|
|
178
|
+
}),
|
|
179
|
+
},
|
|
180
|
+
}, []);
|
|
181
|
+
}
|
|
182
|
+
catch (cause) {
|
|
183
|
+
const message = cause instanceof HermesStep
|
|
184
|
+
? cause.message
|
|
185
|
+
: "cannot activate the Manager-owned Hermes installation";
|
|
186
|
+
let restored = true;
|
|
187
|
+
if (publication !== undefined) {
|
|
188
|
+
try {
|
|
189
|
+
if (publication.movedBackup || publication.published)
|
|
190
|
+
await rollback(publication, artifact.identity, previouslyEnabled, paths, ctx, deps.run);
|
|
191
|
+
else {
|
|
192
|
+
await assertNoFollowType(publication.container, ["directory"]);
|
|
193
|
+
await rm(publication.container, { recursive: true, force: false });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
restored = false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return failureResult(operation, "install-failed", restored
|
|
201
|
+
? message
|
|
202
|
+
: `${message}; the previous Hermes state may not be restored — inspect ${displayPath(publication.container)}`, [], []);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
export async function removeHermes(input, ctx, deps = DEFAULTS) {
|
|
206
|
+
const operation = "remove-hermes";
|
|
207
|
+
const paths = hermesPaths(ctx.env ?? {}, process.cwd());
|
|
208
|
+
try {
|
|
209
|
+
const ownership = await observeHermesOwnership(paths);
|
|
210
|
+
if (input.ownership === "foreign" || ownership.kind === "foreign")
|
|
211
|
+
throw new HermesStep("refusing to remove a foreign Hermes plugin directory; remove it manually");
|
|
212
|
+
await checkPublicationState(paths);
|
|
213
|
+
const listed = (await readHermesStatus(paths, ctx.env ?? {})) !== "not enabled";
|
|
214
|
+
if (input.ownership !== ownership.kind || input.listed !== listed)
|
|
215
|
+
return failureResult(operation, "stale-ownership", "Hermes plugin state changed before removal; inspect it and retry", [], []);
|
|
216
|
+
if (ownership.kind === "absent") {
|
|
217
|
+
if (listed)
|
|
218
|
+
throw new HermesStep("refusing to remove an unverified Hermes activation entry without an owned plugin directory");
|
|
219
|
+
return successResult(operation, null, []);
|
|
220
|
+
}
|
|
221
|
+
await native(deps.run, ["plugins", "remove", "superpowers"], ctx);
|
|
222
|
+
try {
|
|
223
|
+
await verifyRemoved(paths, ctx);
|
|
224
|
+
}
|
|
225
|
+
catch {
|
|
226
|
+
return failureResult(operation, "removal-unverified", "Hermes still lists Superpowers or its plugin directory is still present after removal", [], []);
|
|
227
|
+
}
|
|
228
|
+
return successResult(operation, null, []);
|
|
229
|
+
}
|
|
230
|
+
catch (cause) {
|
|
231
|
+
return failureResult(operation, "remove-failed", cause instanceof HermesStep
|
|
232
|
+
? cause.message
|
|
233
|
+
: "cannot remove the Manager-owned Hermes plugin", [], []);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { basename, dirname } from "node:path";
|
|
2
|
+
import { failureResult, } from "../../adapter-result.js";
|
|
3
|
+
import { runIsolatedNative, } from "../../harness-command-result.js";
|
|
4
|
+
import { BOUNDED_EXECUTABLE, runValidator } from "../../validator.js";
|
|
5
|
+
import { hermesPaths } from "./paths.js";
|
|
6
|
+
export async function runHermes(args, ctx, execute = runValidator) {
|
|
7
|
+
const invalidHome = () => failureResult("hermes-command", "invalid-home", "cannot resolve HERMES_HOME for Hermes command", [], []);
|
|
8
|
+
let home;
|
|
9
|
+
try {
|
|
10
|
+
home = hermesPaths(ctx.env ?? {}, process.cwd()).hermesHome;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return invalidHome();
|
|
14
|
+
}
|
|
15
|
+
let homeChanged = false;
|
|
16
|
+
const result = await runIsolatedNative("Hermes", (ctx.env ?? {}).SUPERPOWERS_HERMES || "hermes", ctx, async (executable, workspace, env, invocationCwd) => {
|
|
17
|
+
const childEnv = {
|
|
18
|
+
...process.env,
|
|
19
|
+
...env,
|
|
20
|
+
HERMES_HOME: home,
|
|
21
|
+
TMPDIR: workspace,
|
|
22
|
+
};
|
|
23
|
+
try {
|
|
24
|
+
if (hermesPaths(childEnv, invocationCwd).hermesHome !== home)
|
|
25
|
+
throw new Error("cannot resolve HERMES_HOME: child home changed");
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
homeChanged = true;
|
|
29
|
+
throw new Error("cannot resolve HERMES_HOME for Hermes command");
|
|
30
|
+
}
|
|
31
|
+
return await execute([
|
|
32
|
+
executable,
|
|
33
|
+
...(basename(dirname(home)) === "profiles"
|
|
34
|
+
? []
|
|
35
|
+
: ["--profile", "default"]),
|
|
36
|
+
...args,
|
|
37
|
+
], BOUNDED_EXECUTABLE, childEnv, workspace, invocationCwd);
|
|
38
|
+
});
|
|
39
|
+
return homeChanged ? invalidHome() : result;
|
|
40
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { readdir, realpath, stat } from "node:fs/promises";
|
|
2
|
+
import { basename, join } from "node:path";
|
|
3
|
+
import { parseDocument } from "yaml";
|
|
4
|
+
import { readArtifactFile } from "../../artifact-tree.js";
|
|
5
|
+
import { compareByCodePoint } from "../../python-text.js";
|
|
6
|
+
import { assertNoFollowType, classifyPathNoFollow, isAbsenceError, } from "../../safe-path.js";
|
|
7
|
+
import { SafetyError } from "../../safety-error.js";
|
|
8
|
+
const DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
9
|
+
function mapping(value) {
|
|
10
|
+
return (value !== null &&
|
|
11
|
+
typeof value === "object" &&
|
|
12
|
+
Object.getPrototypeOf(value) === Object.prototype);
|
|
13
|
+
}
|
|
14
|
+
function parseYaml(text, uniqueKeys = true) {
|
|
15
|
+
const document = parseDocument(text, { uniqueKeys, merge: true });
|
|
16
|
+
if (document.errors.length > 0 ||
|
|
17
|
+
document.warnings.some((warning) => warning.code === "TAG_RESOLVE_FAILED"))
|
|
18
|
+
throw new Error("YAML document");
|
|
19
|
+
return document.toJS();
|
|
20
|
+
}
|
|
21
|
+
async function configuration(root) {
|
|
22
|
+
await assertNoFollowType(root, ["directory", "missing"]);
|
|
23
|
+
const path = join(root, "config.yaml");
|
|
24
|
+
if ((await classifyPathNoFollow(path)) === "missing")
|
|
25
|
+
return null;
|
|
26
|
+
const value = parseYaml(DECODER.decode(await readArtifactFile(root, path))) ?? {};
|
|
27
|
+
if (!mapping(value))
|
|
28
|
+
throw new Error("configuration mapping");
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
async function managedDirectory(env, systemManagedDir) {
|
|
32
|
+
const configured = env.HERMES_MANAGED_DIR?.trim();
|
|
33
|
+
for (const path of configured
|
|
34
|
+
? [configured, systemManagedDir]
|
|
35
|
+
: [systemManagedDir]) {
|
|
36
|
+
const kind = await classifyPathNoFollow(path);
|
|
37
|
+
if (kind === "directory")
|
|
38
|
+
return path;
|
|
39
|
+
if (kind === "symlink")
|
|
40
|
+
throw new Error("managed directory symlink");
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
function activationList(value) {
|
|
45
|
+
if (!Array.isArray(value))
|
|
46
|
+
return [];
|
|
47
|
+
if (value.some((item) => (item !== null && typeof item === "object") ||
|
|
48
|
+
(typeof item === "string" && item.includes("${"))))
|
|
49
|
+
throw new SafetyError("hermes-observe", "cannot determine Hermes plugin activation");
|
|
50
|
+
return value;
|
|
51
|
+
}
|
|
52
|
+
export async function readHermesStatus(paths, env, systemManagedDir = "/etc/hermes") {
|
|
53
|
+
let user;
|
|
54
|
+
try {
|
|
55
|
+
user = await configuration(paths.hermesHome);
|
|
56
|
+
}
|
|
57
|
+
catch (cause) {
|
|
58
|
+
throw new SafetyError("hermes-observe", "cannot inspect Hermes configuration", { cause });
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const directory = await managedDirectory(env, systemManagedDir);
|
|
62
|
+
const managed = directory === null ? null : await configuration(directory);
|
|
63
|
+
const plugins = managed?.plugins;
|
|
64
|
+
if (plugins !== undefined && plugins !== null && !mapping(plugins))
|
|
65
|
+
throw new Error("managed plugins shape");
|
|
66
|
+
if (mapping(plugins) &&
|
|
67
|
+
(Object.hasOwn(plugins, "enabled") || Object.hasOwn(plugins, "disabled")))
|
|
68
|
+
throw new Error("managed activation");
|
|
69
|
+
}
|
|
70
|
+
catch (cause) {
|
|
71
|
+
throw new SafetyError("hermes-observe", "cannot determine Hermes plugin activation", { cause });
|
|
72
|
+
}
|
|
73
|
+
const plugins = mapping(user?.plugins) ? user.plugins : {};
|
|
74
|
+
const enabled = activationList(plugins.enabled);
|
|
75
|
+
const disabled = activationList(plugins.disabled);
|
|
76
|
+
return disabled.includes("superpowers")
|
|
77
|
+
? "disabled"
|
|
78
|
+
: enabled.includes("superpowers")
|
|
79
|
+
? "enabled"
|
|
80
|
+
: "not enabled";
|
|
81
|
+
}
|
|
82
|
+
const FOREIGN_MANIFEST_DIRECTORIES = new Set([
|
|
83
|
+
".claude-plugin",
|
|
84
|
+
".codex-plugin",
|
|
85
|
+
".cursor-plugin",
|
|
86
|
+
".devin-plugin",
|
|
87
|
+
".kimi-plugin",
|
|
88
|
+
]);
|
|
89
|
+
export async function findSuperpowersManifests(paths) {
|
|
90
|
+
try {
|
|
91
|
+
if ((await classifyPathNoFollow(paths.pluginsRoot)) === "missing")
|
|
92
|
+
return [];
|
|
93
|
+
const found = [];
|
|
94
|
+
async function scan(root, physicalRoot, depth) {
|
|
95
|
+
await assertNoFollowType(physicalRoot, ["directory"]);
|
|
96
|
+
for (const name of (await readdir(physicalRoot)).sort(compareByCodePoint)) {
|
|
97
|
+
if ((name.startsWith("__") && name.endsWith("__")) ||
|
|
98
|
+
FOREIGN_MANIFEST_DIRECTORIES.has(name))
|
|
99
|
+
continue;
|
|
100
|
+
const child = join(root, name);
|
|
101
|
+
const physicalChild = join(physicalRoot, name);
|
|
102
|
+
const kind = await classifyPathNoFollow(physicalChild);
|
|
103
|
+
if (kind === "symlink") {
|
|
104
|
+
if (child === paths.pluginRoot)
|
|
105
|
+
throw new Error("owned plugin directory symlink");
|
|
106
|
+
try {
|
|
107
|
+
if (!(await stat(physicalChild)).isDirectory())
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
catch (cause) {
|
|
111
|
+
if (isAbsenceError(cause))
|
|
112
|
+
continue;
|
|
113
|
+
throw cause;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
else if (kind !== "directory")
|
|
117
|
+
continue;
|
|
118
|
+
const directory = await realpath(physicalChild);
|
|
119
|
+
let manifest = null;
|
|
120
|
+
for (const file of ["plugin.yaml", "plugin.yml", "plugin.json"]) {
|
|
121
|
+
if ((await classifyPathNoFollow(join(directory, file))) !== "missing") {
|
|
122
|
+
manifest = file;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (manifest === null) {
|
|
127
|
+
if (depth === 0)
|
|
128
|
+
await scan(child, directory, 1);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
const bytes = await readArtifactFile(directory, join(directory, manifest), 64 * 1024);
|
|
132
|
+
let value;
|
|
133
|
+
try {
|
|
134
|
+
const text = DECODER.decode(bytes);
|
|
135
|
+
if (manifest === "plugin.json")
|
|
136
|
+
value = JSON.parse(text);
|
|
137
|
+
else {
|
|
138
|
+
const parsed = parseYaml(text, false);
|
|
139
|
+
value =
|
|
140
|
+
parsed === null ||
|
|
141
|
+
parsed === undefined ||
|
|
142
|
+
parsed === false ||
|
|
143
|
+
parsed === 0 ||
|
|
144
|
+
parsed === "" ||
|
|
145
|
+
(Array.isArray(parsed) && parsed.length === 0) ||
|
|
146
|
+
(parsed instanceof Set && parsed.size === 0) ||
|
|
147
|
+
(parsed instanceof Map && parsed.size === 0) ||
|
|
148
|
+
(Buffer.isBuffer(parsed) && parsed.length === 0)
|
|
149
|
+
? {}
|
|
150
|
+
: parsed;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (!mapping(value))
|
|
157
|
+
continue;
|
|
158
|
+
const pluginName = Object.hasOwn(value, "name")
|
|
159
|
+
? value.name
|
|
160
|
+
: manifest === "plugin.json"
|
|
161
|
+
? undefined
|
|
162
|
+
: basename(child);
|
|
163
|
+
if (pluginName === "superpowers")
|
|
164
|
+
found.push(child);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
await scan(paths.pluginsRoot, paths.pluginsRoot, 0);
|
|
168
|
+
return found.sort(compareByCodePoint);
|
|
169
|
+
}
|
|
170
|
+
catch (cause) {
|
|
171
|
+
throw new SafetyError("hermes-observe", "cannot inspect Hermes plugins", {
|
|
172
|
+
cause,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
export function projectPluginsEnabled(env) {
|
|
177
|
+
return ["1", "true", "yes", "on"].includes((env.HERMES_ENABLE_PROJECT_PLUGINS ?? "").trim().toLowerCase());
|
|
178
|
+
}
|