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.
Files changed (246) hide show
  1. package/README.md +35 -16
  2. package/dist/cli-arguments.js +2 -1
  3. package/dist/cli.js +12 -9
  4. package/dist/harnesses/hermes/harness.js +36 -0
  5. package/dist/harnesses/hermes/install.js +235 -0
  6. package/dist/harnesses/hermes/native.js +40 -0
  7. package/dist/harnesses/hermes/observe.js +178 -0
  8. package/dist/harnesses/hermes/paths.js +61 -0
  9. package/dist/harnesses/hermes/prepare.js +78 -0
  10. package/dist/harnesses/hermes/presentation.js +23 -0
  11. package/dist/harnesses/hermes/state.js +144 -0
  12. package/dist/snapshot-prepare.js +2 -0
  13. package/node_modules/yaml/LICENSE +13 -0
  14. package/node_modules/yaml/README.md +172 -0
  15. package/node_modules/yaml/bin.mjs +11 -0
  16. package/node_modules/yaml/browser/dist/compose/compose-collection.js +88 -0
  17. package/node_modules/yaml/browser/dist/compose/compose-doc.js +43 -0
  18. package/node_modules/yaml/browser/dist/compose/compose-node.js +109 -0
  19. package/node_modules/yaml/browser/dist/compose/compose-scalar.js +86 -0
  20. package/node_modules/yaml/browser/dist/compose/composer.js +219 -0
  21. package/node_modules/yaml/browser/dist/compose/resolve-block-map.js +115 -0
  22. package/node_modules/yaml/browser/dist/compose/resolve-block-scalar.js +198 -0
  23. package/node_modules/yaml/browser/dist/compose/resolve-block-seq.js +49 -0
  24. package/node_modules/yaml/browser/dist/compose/resolve-end.js +37 -0
  25. package/node_modules/yaml/browser/dist/compose/resolve-flow-collection.js +207 -0
  26. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +225 -0
  27. package/node_modules/yaml/browser/dist/compose/resolve-props.js +146 -0
  28. package/node_modules/yaml/browser/dist/compose/util-contains-newline.js +34 -0
  29. package/node_modules/yaml/browser/dist/compose/util-empty-scalar-position.js +26 -0
  30. package/node_modules/yaml/browser/dist/compose/util-flow-indent-check.js +15 -0
  31. package/node_modules/yaml/browser/dist/compose/util-map-includes.js +13 -0
  32. package/node_modules/yaml/browser/dist/doc/Document.js +335 -0
  33. package/node_modules/yaml/browser/dist/doc/anchors.js +71 -0
  34. package/node_modules/yaml/browser/dist/doc/applyReviver.js +55 -0
  35. package/node_modules/yaml/browser/dist/doc/createNode.js +88 -0
  36. package/node_modules/yaml/browser/dist/doc/directives.js +176 -0
  37. package/node_modules/yaml/browser/dist/errors.js +57 -0
  38. package/node_modules/yaml/browser/dist/index.js +17 -0
  39. package/node_modules/yaml/browser/dist/log.js +11 -0
  40. package/node_modules/yaml/browser/dist/nodes/Alias.js +116 -0
  41. package/node_modules/yaml/browser/dist/nodes/Collection.js +147 -0
  42. package/node_modules/yaml/browser/dist/nodes/Node.js +38 -0
  43. package/node_modules/yaml/browser/dist/nodes/Pair.js +36 -0
  44. package/node_modules/yaml/browser/dist/nodes/Scalar.js +24 -0
  45. package/node_modules/yaml/browser/dist/nodes/YAMLMap.js +144 -0
  46. package/node_modules/yaml/browser/dist/nodes/YAMLSeq.js +113 -0
  47. package/node_modules/yaml/browser/dist/nodes/addPairToJSMap.js +63 -0
  48. package/node_modules/yaml/browser/dist/nodes/identity.js +36 -0
  49. package/node_modules/yaml/browser/dist/nodes/toJS.js +37 -0
  50. package/node_modules/yaml/browser/dist/parse/cst-scalar.js +214 -0
  51. package/node_modules/yaml/browser/dist/parse/cst-stringify.js +61 -0
  52. package/node_modules/yaml/browser/dist/parse/cst-visit.js +97 -0
  53. package/node_modules/yaml/browser/dist/parse/cst.js +98 -0
  54. package/node_modules/yaml/browser/dist/parse/lexer.js +721 -0
  55. package/node_modules/yaml/browser/dist/parse/line-counter.js +39 -0
  56. package/node_modules/yaml/browser/dist/parse/parser.js +975 -0
  57. package/node_modules/yaml/browser/dist/public-api.js +102 -0
  58. package/node_modules/yaml/browser/dist/schema/Schema.js +37 -0
  59. package/node_modules/yaml/browser/dist/schema/common/map.js +17 -0
  60. package/node_modules/yaml/browser/dist/schema/common/null.js +15 -0
  61. package/node_modules/yaml/browser/dist/schema/common/seq.js +17 -0
  62. package/node_modules/yaml/browser/dist/schema/common/string.js +14 -0
  63. package/node_modules/yaml/browser/dist/schema/core/bool.js +19 -0
  64. package/node_modules/yaml/browser/dist/schema/core/float.js +43 -0
  65. package/node_modules/yaml/browser/dist/schema/core/int.js +38 -0
  66. package/node_modules/yaml/browser/dist/schema/core/schema.js +23 -0
  67. package/node_modules/yaml/browser/dist/schema/json/schema.js +62 -0
  68. package/node_modules/yaml/browser/dist/schema/tags.js +96 -0
  69. package/node_modules/yaml/browser/dist/schema/yaml-1.1/binary.js +58 -0
  70. package/node_modules/yaml/browser/dist/schema/yaml-1.1/bool.js +26 -0
  71. package/node_modules/yaml/browser/dist/schema/yaml-1.1/float.js +46 -0
  72. package/node_modules/yaml/browser/dist/schema/yaml-1.1/int.js +71 -0
  73. package/node_modules/yaml/browser/dist/schema/yaml-1.1/merge.js +67 -0
  74. package/node_modules/yaml/browser/dist/schema/yaml-1.1/omap.js +74 -0
  75. package/node_modules/yaml/browser/dist/schema/yaml-1.1/pairs.js +78 -0
  76. package/node_modules/yaml/browser/dist/schema/yaml-1.1/schema.js +39 -0
  77. package/node_modules/yaml/browser/dist/schema/yaml-1.1/set.js +93 -0
  78. package/node_modules/yaml/browser/dist/schema/yaml-1.1/timestamp.js +101 -0
  79. package/node_modules/yaml/browser/dist/stringify/foldFlowLines.js +146 -0
  80. package/node_modules/yaml/browser/dist/stringify/stringify.js +129 -0
  81. package/node_modules/yaml/browser/dist/stringify/stringifyCollection.js +153 -0
  82. package/node_modules/yaml/browser/dist/stringify/stringifyComment.js +20 -0
  83. package/node_modules/yaml/browser/dist/stringify/stringifyDocument.js +85 -0
  84. package/node_modules/yaml/browser/dist/stringify/stringifyNumber.js +25 -0
  85. package/node_modules/yaml/browser/dist/stringify/stringifyPair.js +150 -0
  86. package/node_modules/yaml/browser/dist/stringify/stringifyString.js +336 -0
  87. package/node_modules/yaml/browser/dist/util.js +11 -0
  88. package/node_modules/yaml/browser/dist/visit.js +233 -0
  89. package/node_modules/yaml/browser/index.js +5 -0
  90. package/node_modules/yaml/browser/package.json +3 -0
  91. package/node_modules/yaml/dist/cli.d.ts +8 -0
  92. package/node_modules/yaml/dist/cli.mjs +201 -0
  93. package/node_modules/yaml/dist/compose/compose-collection.d.ts +11 -0
  94. package/node_modules/yaml/dist/compose/compose-collection.js +90 -0
  95. package/node_modules/yaml/dist/compose/compose-doc.d.ts +7 -0
  96. package/node_modules/yaml/dist/compose/compose-doc.js +45 -0
  97. package/node_modules/yaml/dist/compose/compose-node.d.ts +29 -0
  98. package/node_modules/yaml/dist/compose/compose-node.js +112 -0
  99. package/node_modules/yaml/dist/compose/compose-scalar.d.ts +5 -0
  100. package/node_modules/yaml/dist/compose/compose-scalar.js +88 -0
  101. package/node_modules/yaml/dist/compose/composer.d.ts +63 -0
  102. package/node_modules/yaml/dist/compose/composer.js +224 -0
  103. package/node_modules/yaml/dist/compose/resolve-block-map.d.ts +6 -0
  104. package/node_modules/yaml/dist/compose/resolve-block-map.js +117 -0
  105. package/node_modules/yaml/dist/compose/resolve-block-scalar.d.ts +11 -0
  106. package/node_modules/yaml/dist/compose/resolve-block-scalar.js +200 -0
  107. package/node_modules/yaml/dist/compose/resolve-block-seq.d.ts +6 -0
  108. package/node_modules/yaml/dist/compose/resolve-block-seq.js +51 -0
  109. package/node_modules/yaml/dist/compose/resolve-end.d.ts +6 -0
  110. package/node_modules/yaml/dist/compose/resolve-end.js +39 -0
  111. package/node_modules/yaml/dist/compose/resolve-flow-collection.d.ts +7 -0
  112. package/node_modules/yaml/dist/compose/resolve-flow-collection.js +209 -0
  113. package/node_modules/yaml/dist/compose/resolve-flow-scalar.d.ts +10 -0
  114. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +227 -0
  115. package/node_modules/yaml/dist/compose/resolve-props.d.ts +23 -0
  116. package/node_modules/yaml/dist/compose/resolve-props.js +148 -0
  117. package/node_modules/yaml/dist/compose/util-contains-newline.d.ts +2 -0
  118. package/node_modules/yaml/dist/compose/util-contains-newline.js +36 -0
  119. package/node_modules/yaml/dist/compose/util-empty-scalar-position.d.ts +2 -0
  120. package/node_modules/yaml/dist/compose/util-empty-scalar-position.js +28 -0
  121. package/node_modules/yaml/dist/compose/util-flow-indent-check.d.ts +3 -0
  122. package/node_modules/yaml/dist/compose/util-flow-indent-check.js +17 -0
  123. package/node_modules/yaml/dist/compose/util-map-includes.d.ts +4 -0
  124. package/node_modules/yaml/dist/compose/util-map-includes.js +15 -0
  125. package/node_modules/yaml/dist/doc/Document.d.ts +141 -0
  126. package/node_modules/yaml/dist/doc/Document.js +337 -0
  127. package/node_modules/yaml/dist/doc/anchors.d.ts +24 -0
  128. package/node_modules/yaml/dist/doc/anchors.js +76 -0
  129. package/node_modules/yaml/dist/doc/applyReviver.d.ts +9 -0
  130. package/node_modules/yaml/dist/doc/applyReviver.js +57 -0
  131. package/node_modules/yaml/dist/doc/createNode.d.ts +17 -0
  132. package/node_modules/yaml/dist/doc/createNode.js +90 -0
  133. package/node_modules/yaml/dist/doc/directives.d.ts +49 -0
  134. package/node_modules/yaml/dist/doc/directives.js +178 -0
  135. package/node_modules/yaml/dist/errors.d.ts +21 -0
  136. package/node_modules/yaml/dist/errors.js +62 -0
  137. package/node_modules/yaml/dist/index.d.ts +25 -0
  138. package/node_modules/yaml/dist/index.js +50 -0
  139. package/node_modules/yaml/dist/log.d.ts +3 -0
  140. package/node_modules/yaml/dist/log.js +19 -0
  141. package/node_modules/yaml/dist/nodes/Alias.d.ts +29 -0
  142. package/node_modules/yaml/dist/nodes/Alias.js +118 -0
  143. package/node_modules/yaml/dist/nodes/Collection.d.ts +73 -0
  144. package/node_modules/yaml/dist/nodes/Collection.js +151 -0
  145. package/node_modules/yaml/dist/nodes/Node.d.ts +53 -0
  146. package/node_modules/yaml/dist/nodes/Node.js +40 -0
  147. package/node_modules/yaml/dist/nodes/Pair.d.ts +22 -0
  148. package/node_modules/yaml/dist/nodes/Pair.js +39 -0
  149. package/node_modules/yaml/dist/nodes/Scalar.d.ts +47 -0
  150. package/node_modules/yaml/dist/nodes/Scalar.js +27 -0
  151. package/node_modules/yaml/dist/nodes/YAMLMap.d.ts +53 -0
  152. package/node_modules/yaml/dist/nodes/YAMLMap.js +147 -0
  153. package/node_modules/yaml/dist/nodes/YAMLSeq.d.ts +60 -0
  154. package/node_modules/yaml/dist/nodes/YAMLSeq.js +115 -0
  155. package/node_modules/yaml/dist/nodes/addPairToJSMap.d.ts +4 -0
  156. package/node_modules/yaml/dist/nodes/addPairToJSMap.js +65 -0
  157. package/node_modules/yaml/dist/nodes/identity.d.ts +23 -0
  158. package/node_modules/yaml/dist/nodes/identity.js +53 -0
  159. package/node_modules/yaml/dist/nodes/toJS.d.ts +29 -0
  160. package/node_modules/yaml/dist/nodes/toJS.js +39 -0
  161. package/node_modules/yaml/dist/options.d.ts +350 -0
  162. package/node_modules/yaml/dist/parse/cst-scalar.d.ts +64 -0
  163. package/node_modules/yaml/dist/parse/cst-scalar.js +218 -0
  164. package/node_modules/yaml/dist/parse/cst-stringify.d.ts +8 -0
  165. package/node_modules/yaml/dist/parse/cst-stringify.js +63 -0
  166. package/node_modules/yaml/dist/parse/cst-visit.d.ts +39 -0
  167. package/node_modules/yaml/dist/parse/cst-visit.js +99 -0
  168. package/node_modules/yaml/dist/parse/cst.d.ts +109 -0
  169. package/node_modules/yaml/dist/parse/cst.js +112 -0
  170. package/node_modules/yaml/dist/parse/lexer.d.ts +87 -0
  171. package/node_modules/yaml/dist/parse/lexer.js +723 -0
  172. package/node_modules/yaml/dist/parse/line-counter.d.ts +22 -0
  173. package/node_modules/yaml/dist/parse/line-counter.js +41 -0
  174. package/node_modules/yaml/dist/parse/parser.d.ts +84 -0
  175. package/node_modules/yaml/dist/parse/parser.js +980 -0
  176. package/node_modules/yaml/dist/public-api.d.ts +44 -0
  177. package/node_modules/yaml/dist/public-api.js +107 -0
  178. package/node_modules/yaml/dist/schema/Schema.d.ts +17 -0
  179. package/node_modules/yaml/dist/schema/Schema.js +39 -0
  180. package/node_modules/yaml/dist/schema/common/map.d.ts +2 -0
  181. package/node_modules/yaml/dist/schema/common/map.js +19 -0
  182. package/node_modules/yaml/dist/schema/common/null.d.ts +4 -0
  183. package/node_modules/yaml/dist/schema/common/null.js +17 -0
  184. package/node_modules/yaml/dist/schema/common/seq.d.ts +2 -0
  185. package/node_modules/yaml/dist/schema/common/seq.js +19 -0
  186. package/node_modules/yaml/dist/schema/common/string.d.ts +2 -0
  187. package/node_modules/yaml/dist/schema/common/string.js +16 -0
  188. package/node_modules/yaml/dist/schema/core/bool.d.ts +4 -0
  189. package/node_modules/yaml/dist/schema/core/bool.js +21 -0
  190. package/node_modules/yaml/dist/schema/core/float.d.ts +4 -0
  191. package/node_modules/yaml/dist/schema/core/float.js +47 -0
  192. package/node_modules/yaml/dist/schema/core/int.d.ts +4 -0
  193. package/node_modules/yaml/dist/schema/core/int.js +42 -0
  194. package/node_modules/yaml/dist/schema/core/schema.d.ts +1 -0
  195. package/node_modules/yaml/dist/schema/core/schema.js +25 -0
  196. package/node_modules/yaml/dist/schema/json/schema.d.ts +2 -0
  197. package/node_modules/yaml/dist/schema/json/schema.js +64 -0
  198. package/node_modules/yaml/dist/schema/json-schema.d.ts +69 -0
  199. package/node_modules/yaml/dist/schema/tags.d.ts +48 -0
  200. package/node_modules/yaml/dist/schema/tags.js +99 -0
  201. package/node_modules/yaml/dist/schema/types.d.ts +92 -0
  202. package/node_modules/yaml/dist/schema/yaml-1.1/binary.d.ts +2 -0
  203. package/node_modules/yaml/dist/schema/yaml-1.1/binary.js +70 -0
  204. package/node_modules/yaml/dist/schema/yaml-1.1/bool.d.ts +7 -0
  205. package/node_modules/yaml/dist/schema/yaml-1.1/bool.js +29 -0
  206. package/node_modules/yaml/dist/schema/yaml-1.1/float.d.ts +4 -0
  207. package/node_modules/yaml/dist/schema/yaml-1.1/float.js +50 -0
  208. package/node_modules/yaml/dist/schema/yaml-1.1/int.d.ts +5 -0
  209. package/node_modules/yaml/dist/schema/yaml-1.1/int.js +76 -0
  210. package/node_modules/yaml/dist/schema/yaml-1.1/merge.d.ts +9 -0
  211. package/node_modules/yaml/dist/schema/yaml-1.1/merge.js +71 -0
  212. package/node_modules/yaml/dist/schema/yaml-1.1/omap.d.ts +22 -0
  213. package/node_modules/yaml/dist/schema/yaml-1.1/omap.js +77 -0
  214. package/node_modules/yaml/dist/schema/yaml-1.1/pairs.d.ts +10 -0
  215. package/node_modules/yaml/dist/schema/yaml-1.1/pairs.js +82 -0
  216. package/node_modules/yaml/dist/schema/yaml-1.1/schema.d.ts +1 -0
  217. package/node_modules/yaml/dist/schema/yaml-1.1/schema.js +41 -0
  218. package/node_modules/yaml/dist/schema/yaml-1.1/set.d.ts +28 -0
  219. package/node_modules/yaml/dist/schema/yaml-1.1/set.js +96 -0
  220. package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.d.ts +6 -0
  221. package/node_modules/yaml/dist/schema/yaml-1.1/timestamp.js +105 -0
  222. package/node_modules/yaml/dist/stringify/foldFlowLines.d.ts +34 -0
  223. package/node_modules/yaml/dist/stringify/foldFlowLines.js +151 -0
  224. package/node_modules/yaml/dist/stringify/stringify.d.ts +21 -0
  225. package/node_modules/yaml/dist/stringify/stringify.js +132 -0
  226. package/node_modules/yaml/dist/stringify/stringifyCollection.d.ts +17 -0
  227. package/node_modules/yaml/dist/stringify/stringifyCollection.js +155 -0
  228. package/node_modules/yaml/dist/stringify/stringifyComment.d.ts +10 -0
  229. package/node_modules/yaml/dist/stringify/stringifyComment.js +24 -0
  230. package/node_modules/yaml/dist/stringify/stringifyDocument.d.ts +4 -0
  231. package/node_modules/yaml/dist/stringify/stringifyDocument.js +87 -0
  232. package/node_modules/yaml/dist/stringify/stringifyNumber.d.ts +2 -0
  233. package/node_modules/yaml/dist/stringify/stringifyNumber.js +27 -0
  234. package/node_modules/yaml/dist/stringify/stringifyPair.d.ts +3 -0
  235. package/node_modules/yaml/dist/stringify/stringifyPair.js +152 -0
  236. package/node_modules/yaml/dist/stringify/stringifyString.d.ts +9 -0
  237. package/node_modules/yaml/dist/stringify/stringifyString.js +338 -0
  238. package/node_modules/yaml/dist/test-events.d.ts +4 -0
  239. package/node_modules/yaml/dist/test-events.js +134 -0
  240. package/node_modules/yaml/dist/util.d.ts +16 -0
  241. package/node_modules/yaml/dist/util.js +28 -0
  242. package/node_modules/yaml/dist/visit.d.ts +102 -0
  243. package/node_modules/yaml/dist/visit.js +236 -0
  244. package/node_modules/yaml/package.json +97 -0
  245. package/node_modules/yaml/util.js +2 -0
  246. 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 Claude Code. 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.
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, or `--harness claude-code` for Claude Code. 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.
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 four harnesses share upstream selection, while each has independent prepared and installed state. Omitting `--harness` selects Codex.
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 Claude Code; run the selected harness's `install` or `update` to apply that choice. `unpin` restores the packaged fallback policy.
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`, or `--harness claude-code` with each targeted lifecycle command.
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), and [Claude Code marketplace registration](https://github.com/j7an/superpowers-manager/blob/main/docs/claude-code.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
 
@@ -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, and --harness=claude-code are also accepted after the command.",
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 === "claude-code"
256
- ? await dispatch(claudeCodeHarness, parsed, root)
257
- : parsed.options.harness === "opencode"
258
- ? await dispatch(openCodeHarness, parsed, root)
259
- : parsed.options.harness === "pi"
260
- ? await dispatch(piHarness, parsed, root)
261
- : await dispatch(codexHarness, parsed, root);
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
+ }