superpowers-manager 0.9.1 → 0.10.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 +35 -16
- package/dist/cli-arguments.js +2 -1
- package/dist/cli.js +12 -9
- 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 +61 -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/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
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Superpowers Manager
|
|
2
2
|
|
|
3
|
-
Install [`obra/superpowers`](https://github.com/obra/superpowers) directly from upstream for Codex, the Pi coding agent, OpenCode, and
|
|
3
|
+
Install [`obra/superpowers`](https://github.com/obra/superpowers) directly from upstream for Codex, the Pi coding agent, OpenCode, Claude Code, and Hermes Agent. Stay on a known-good release or commit, try a branch, and upgrade when you choose—without waiting for a marketplace copy to catch up.
|
|
4
4
|
|
|
5
5
|
> Unofficial community integration. Not affiliated with the `obra/superpowers` maintainers.
|
|
6
6
|
|
|
@@ -21,7 +21,7 @@ npx superpowers-manager pin v6.1.1
|
|
|
21
21
|
npx superpowers-manager install --harness codex
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
Use `--harness pi` for Pi, `--harness opencode` for OpenCode,
|
|
24
|
+
Use `--harness pi` for Pi, `--harness opencode` for OpenCode, `--harness claude-code` for Claude Code, or `--harness hermes` for Hermes Agent. The pin is shared by all harnesses, but each installation changes only when you run its `install` or `update` command. A saved pin keeps subsequent installs and updates on that version unless you change or override the selection.
|
|
25
25
|
|
|
26
26
|
To try a branch, replace `feature/foo` with an existing upstream branch:
|
|
27
27
|
|
|
@@ -42,7 +42,7 @@ See [version selection](https://github.com/j7an/superpowers-manager/blob/main/do
|
|
|
42
42
|
|
|
43
43
|
## Start with your agent
|
|
44
44
|
|
|
45
|
-
The
|
|
45
|
+
The five harnesses share upstream selection, while each has independent prepared and installed state. Omitting `--harness` selects Codex.
|
|
46
46
|
|
|
47
47
|
### Codex
|
|
48
48
|
|
|
@@ -97,6 +97,18 @@ Restart Claude Code or run `/reload-plugins` after an install, activating update
|
|
|
97
97
|
or removal. See the [Claude Code reference](https://github.com/j7an/superpowers-manager/blob/main/docs/claude-code.md)
|
|
98
98
|
for conflicts, hooks, and recovery.
|
|
99
99
|
|
|
100
|
+
### Hermes Agent
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
npx superpowers-manager install --harness hermes
|
|
104
|
+
npx superpowers-manager probe --harness hermes
|
|
105
|
+
npx superpowers-manager update --harness hermes
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Restart Hermes sessions after an install, activating update, or removal. See
|
|
109
|
+
the [Hermes Agent reference](https://github.com/j7an/superpowers-manager/blob/main/docs/hermes.md)
|
|
110
|
+
for plugin layout, compatibility, route switching, and recovery.
|
|
111
|
+
|
|
100
112
|
To remove a Manager-owned installation, choose one harness explicitly:
|
|
101
113
|
|
|
102
114
|
### Uninstall Codex
|
|
@@ -123,24 +135,31 @@ npx superpowers-manager uninstall --harness opencode
|
|
|
123
135
|
npx superpowers-manager uninstall --harness claude-code
|
|
124
136
|
```
|
|
125
137
|
|
|
138
|
+
### Uninstall Hermes Agent
|
|
139
|
+
|
|
140
|
+
```sh
|
|
141
|
+
npx superpowers-manager uninstall --harness hermes
|
|
142
|
+
```
|
|
143
|
+
|
|
126
144
|
The manager never removes another Superpowers provider. See the [ownership guidance](https://github.com/j7an/superpowers-manager/blob/main/docs/usage.md#provider-ownership) before switching providers.
|
|
127
145
|
|
|
128
146
|
## Shared selection and lifecycle
|
|
129
147
|
|
|
130
|
-
The [selection commands above](#choose-what-you-run) save shared upstream intent. They do not activate Codex, Pi, OpenCode, or
|
|
148
|
+
The [selection commands above](#choose-what-you-run) save shared upstream intent. They do not activate Codex, Pi, OpenCode, Claude Code, or Hermes Agent; run the selected harness's `install` or `update` to apply that choice. `unpin` restores the packaged fallback policy.
|
|
131
149
|
|
|
132
150
|
See [targeted lifecycle commands](https://github.com/j7an/superpowers-manager/blob/main/docs/usage.md#targeted-lifecycle) for preparation, inspection, installation, updates, and removal.
|
|
133
151
|
|
|
134
|
-
Use `--harness codex`, `--harness pi`, `--harness opencode`,
|
|
152
|
+
Use `--harness codex`, `--harness pi`, `--harness opencode`, `--harness claude-code`, or `--harness hermes` with each targeted lifecycle command.
|
|
135
153
|
|
|
136
154
|
```text
|
|
137
155
|
shared selection -> prepare / inspect / activate Codex
|
|
138
156
|
-> prepare / inspect / activate Pi
|
|
139
157
|
-> prepare / inspect / activate OpenCode
|
|
140
158
|
-> prepare / inspect / activate Claude Code
|
|
159
|
+
-> prepare / inspect / activate Hermes Agent
|
|
141
160
|
```
|
|
142
161
|
|
|
143
|
-
The [usage reference](https://github.com/j7an/superpowers-manager/blob/main/docs/usage.md) covers version selection, precedence, offline behavior, `prepare`, validators, and every lifecycle command. The harness references cover [Codex registration and hooks](https://github.com/j7an/superpowers-manager/blob/main/docs/codex.md), [Pi snapshots and recovery](https://github.com/j7an/superpowers-manager/blob/main/docs/pi.md), [OpenCode snapshots](https://github.com/j7an/superpowers-manager/blob/main/docs/opencode.md),
|
|
162
|
+
The [usage reference](https://github.com/j7an/superpowers-manager/blob/main/docs/usage.md) covers version selection, precedence, offline behavior, `prepare`, validators, and every lifecycle command. The harness references cover [Codex registration and hooks](https://github.com/j7an/superpowers-manager/blob/main/docs/codex.md), [Pi snapshots and recovery](https://github.com/j7an/superpowers-manager/blob/main/docs/pi.md), [OpenCode snapshots](https://github.com/j7an/superpowers-manager/blob/main/docs/opencode.md), [Claude Code marketplace registration](https://github.com/j7an/superpowers-manager/blob/main/docs/claude-code.md), and [Hermes Agent plugin support](https://github.com/j7an/superpowers-manager/blob/main/docs/hermes.md).
|
|
144
163
|
|
|
145
164
|
## Requirements and platforms
|
|
146
165
|
|
|
@@ -148,16 +167,16 @@ The installed package requires Node >=24. Native source, tests, and packaging to
|
|
|
148
167
|
|
|
149
168
|
<!-- requirements:begin -->
|
|
150
169
|
|
|
151
|
-
| Command | git | Codex CLI (default) | Pi CLI (`--harness pi`) | OpenCode CLI (`--harness opencode`) | Claude Code CLI (`--harness claude-code`) |
|
|
152
|
-
| -------------- | --- | ------------------- | ----------------------- | ----------------------------------- | ----------------------------------------- |
|
|
153
|
-
| `pin` | yes | no | no | no | no |
|
|
154
|
-
| `track-latest` | no | no | no | no | no |
|
|
155
|
-
| `unpin` | no | no | no | no | no |
|
|
156
|
-
| `prepare` | yes | no | no | no | no |
|
|
157
|
-
| `probe` | yes | yes | no | no | yes |
|
|
158
|
-
| `install` | yes | yes | yes | yes | yes |
|
|
159
|
-
| `update` | yes | yes | yes | yes | yes |
|
|
160
|
-
| `uninstall` | no | yes | yes | no | yes |
|
|
170
|
+
| Command | git | Codex CLI (default) | Pi CLI (`--harness pi`) | OpenCode CLI (`--harness opencode`) | Claude Code CLI (`--harness claude-code`) | Hermes Agent CLI (`--harness hermes`) |
|
|
171
|
+
| -------------- | --- | ------------------- | ----------------------- | ----------------------------------- | ----------------------------------------- | ------------------------------------- |
|
|
172
|
+
| `pin` | yes | no | no | no | no | no |
|
|
173
|
+
| `track-latest` | no | no | no | no | no | no |
|
|
174
|
+
| `unpin` | no | no | no | no | no | no |
|
|
175
|
+
| `prepare` | yes | no | no | no | no | no |
|
|
176
|
+
| `probe` | yes | yes | no | no | yes | no |
|
|
177
|
+
| `install` | yes | yes | yes | yes | yes | yes |
|
|
178
|
+
| `update` | yes | yes | yes | yes | yes | yes |
|
|
179
|
+
| `uninstall` | no | yes | yes | no | yes | yes |
|
|
161
180
|
|
|
162
181
|
<!-- requirements:end -->
|
|
163
182
|
|
package/dist/cli-arguments.js
CHANGED
|
@@ -23,7 +23,8 @@ export function extractHarnessOptions(command, argv) {
|
|
|
23
23
|
if (value !== "codex" &&
|
|
24
24
|
value !== "pi" &&
|
|
25
25
|
value !== "opencode" &&
|
|
26
|
-
value !== "claude-code"
|
|
26
|
+
value !== "claude-code" &&
|
|
27
|
+
value !== "hermes")
|
|
27
28
|
throw new UsageError(`unknown harness: ${value}`);
|
|
28
29
|
harness = value;
|
|
29
30
|
}
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import { extractHarnessOptions, oneLine, UsageError } from "./cli-arguments.js";
|
|
5
5
|
import { codexHarness } from "./harnesses/codex/harness.js";
|
|
6
6
|
import { claudeCodeHarness } from "./harnesses/claude-code/harness.js";
|
|
7
|
+
import { hermesHarness } from "./harnesses/hermes/harness.js";
|
|
7
8
|
import { piHarness } from "./harnesses/pi/harness.js";
|
|
8
9
|
import { openCodeHarness } from "./harnesses/opencode/harness.js";
|
|
9
10
|
import { createResourceCoordinator } from "./resource-lock.js";
|
|
@@ -214,7 +215,7 @@ function usage() {
|
|
|
214
215
|
" install --harness opencode [--allow-experimental]",
|
|
215
216
|
" update --harness opencode [--allow-experimental]",
|
|
216
217
|
" uninstall --harness opencode",
|
|
217
|
-
" --harness=codex, --harness=pi, --harness=opencode,
|
|
218
|
+
" --harness=codex, --harness=pi, --harness=opencode, --harness=claude-code, and --harness=hermes are also accepted after the command.",
|
|
218
219
|
"",
|
|
219
220
|
"Environment overrides (used by in-process commands): SUPERPOWERS_REF,",
|
|
220
221
|
"SUPERPOWERS_UPSTREAM_URL, SUPERPOWERS_CODEX, SUPERPOWERS_CACHE_DIR,",
|
|
@@ -222,7 +223,7 @@ function usage() {
|
|
|
222
223
|
"SUPERPOWERS_PLUGIN_ROOT, SUPERPOWERS_MANIFEST_TEMPLATE,",
|
|
223
224
|
"SUPERPOWERS_VALIDATOR_EXECUTABLE,",
|
|
224
225
|
"SUPERPOWERS_INSTALLED_SEARCH_ROOT, SUPERPOWERS_INSTALL_REFRESH_MODE,",
|
|
225
|
-
"SUPERPOWERS_OPENCODE, SUPERPOWERS_CLAUDE_CODE, CLAUDE_CONFIG_DIR",
|
|
226
|
+
"SUPERPOWERS_OPENCODE, SUPERPOWERS_CLAUDE_CODE, CLAUDE_CONFIG_DIR, SUPERPOWERS_HERMES, HERMES_HOME",
|
|
226
227
|
"",
|
|
227
228
|
"SUPERPOWERS_VALIDATOR is removed; unset it and use",
|
|
228
229
|
"SUPERPOWERS_VALIDATOR_EXECUTABLE with an executable validator.",
|
|
@@ -252,13 +253,15 @@ async function main() {
|
|
|
252
253
|
console.error(usage());
|
|
253
254
|
process.exit(2);
|
|
254
255
|
}
|
|
255
|
-
const status = parsed.options.harness === "
|
|
256
|
-
? await dispatch(
|
|
257
|
-
: parsed.options.harness === "
|
|
258
|
-
? await dispatch(
|
|
259
|
-
: parsed.options.harness === "
|
|
260
|
-
? await dispatch(
|
|
261
|
-
:
|
|
256
|
+
const status = parsed.options.harness === "hermes"
|
|
257
|
+
? await dispatch(hermesHarness, parsed, root)
|
|
258
|
+
: parsed.options.harness === "claude-code"
|
|
259
|
+
? await dispatch(claudeCodeHarness, parsed, root)
|
|
260
|
+
: parsed.options.harness === "opencode"
|
|
261
|
+
? await dispatch(openCodeHarness, parsed, root)
|
|
262
|
+
: parsed.options.harness === "pi"
|
|
263
|
+
? await dispatch(piHarness, parsed, root)
|
|
264
|
+
: await dispatch(codexHarness, parsed, root);
|
|
262
265
|
process.exit(status);
|
|
263
266
|
}
|
|
264
267
|
async function dispatch(adapter, parsed, root) {
|
|
@@ -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
|
+
}
|