@amsterdamdatalabs/enact-extensions 0.1.0 → 0.1.3

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 (245) hide show
  1. package/README.md +96 -21
  2. package/dist/index.d.ts +5 -3
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +3 -2
  5. package/dist/index.js.map +1 -1
  6. package/dist/install.d.ts +171 -1
  7. package/dist/install.d.ts.map +1 -1
  8. package/dist/install.js +402 -49
  9. package/dist/install.js.map +1 -1
  10. package/dist/internal/codex.d.ts.map +1 -1
  11. package/dist/internal/codex.js +7 -1
  12. package/dist/internal/codex.js.map +1 -1
  13. package/dist/internal/platform.d.ts +8 -0
  14. package/dist/internal/platform.d.ts.map +1 -1
  15. package/dist/internal/platform.js +46 -2
  16. package/dist/internal/platform.js.map +1 -1
  17. package/dist/provision.d.ts +30 -0
  18. package/dist/provision.d.ts.map +1 -0
  19. package/dist/provision.js +202 -0
  20. package/dist/provision.js.map +1 -0
  21. package/dist/validate/index.d.ts +44 -0
  22. package/dist/validate/index.d.ts.map +1 -1
  23. package/dist/validate/index.js +157 -0
  24. package/dist/validate/index.js.map +1 -1
  25. package/extensions/cmux/.agents/plugin.json +37 -0
  26. package/extensions/cmux/skills/cmux/SKILL.md +82 -0
  27. package/extensions/cmux/skills/cmux/agents/openai.yaml +4 -0
  28. package/extensions/cmux/skills/cmux/references/handles-and-identify.md +35 -0
  29. package/extensions/cmux/skills/cmux/references/panes-surfaces.md +37 -0
  30. package/extensions/cmux/skills/cmux/references/trigger-flash-and-health.md +23 -0
  31. package/extensions/cmux/skills/cmux/references/windows-workspaces.md +31 -0
  32. package/extensions/cmux/skills/cmux-vm-monitor/SKILL.md +122 -0
  33. package/extensions/cmux/skills/cmux-vm-monitor/agents/openai.yaml +4 -0
  34. package/extensions/cmux/skills/cmux-vm-monitor/references/cmux-commands.md +66 -0
  35. package/extensions/cmux/skills/cmux-vm-monitor/scripts/codex_vm_monitor.sh +45 -0
  36. package/extensions/cmux/skills/cmux-workspace/SKILL.md +93 -0
  37. package/extensions/dev-state/.agents/plugin.json +35 -0
  38. package/extensions/dev-state/skills/dev-state-plan-graduation/SKILL.md +194 -0
  39. package/extensions/dev-state/skills/dev-state-plan-graduation/agents/openai.yaml +4 -0
  40. package/extensions/dev-state/skills/dev-state-plan-graduation/references/reference.md +130 -0
  41. package/extensions/devops/.agents/plugin.json +36 -0
  42. package/extensions/devops/skills/azure-devops-cli/SKILL.md +431 -0
  43. package/extensions/devops/skills/azure-devops-cli/agents/openai.yaml +4 -0
  44. package/extensions/devops/skills/ci-pipeline-strategy/SKILL.md +217 -0
  45. package/extensions/devops/skills/ci-pipeline-strategy/agents/openai.yaml +4 -0
  46. package/extensions/enact-context/.agents/plugin.json +40 -0
  47. package/extensions/enact-context/.mcp.json +8 -0
  48. package/extensions/enact-context/README.md +25 -0
  49. package/extensions/enact-context/assets/icon.png +0 -0
  50. package/extensions/enact-context/assets/logo.png +0 -0
  51. package/extensions/enact-context/hooks/hooks.json +115 -0
  52. package/extensions/enact-context/skills/enact-context/SKILL.md +149 -0
  53. package/extensions/enact-context/skills/enact-context/scripts/install.sh +69 -0
  54. package/extensions/enact-factory/.agents/plugin.json +42 -0
  55. package/extensions/enact-factory/.mcp.json +8 -0
  56. package/extensions/enact-factory/assets/icon.png +0 -0
  57. package/extensions/enact-factory/assets/logo.png +0 -0
  58. package/extensions/enact-factory/hooks/user-prompt-submit.mjs +67 -0
  59. package/extensions/enact-factory/skills/testing-strategy/SKILL.md +167 -0
  60. package/extensions/enact-factory/skills/workitem-triage/SKILL.md +22 -0
  61. package/extensions/enact-operator/.agents/plugin.json +57 -0
  62. package/extensions/enact-operator/.app.json +3 -0
  63. package/extensions/enact-operator/.mcp.json +10 -0
  64. package/extensions/enact-operator/_taxonomy.md +86 -0
  65. package/extensions/enact-operator/agents/README.md +5 -0
  66. package/extensions/enact-operator/agents/architect.toml +25 -0
  67. package/extensions/enact-operator/agents/code-reviewer.toml +24 -0
  68. package/extensions/enact-operator/agents/critic.toml +30 -0
  69. package/extensions/enact-operator/agents/executor.toml +24 -0
  70. package/extensions/enact-operator/agents/explore.toml +23 -0
  71. package/extensions/enact-operator/agents/planner.toml +24 -0
  72. package/extensions/enact-operator/agents/verifier.toml +24 -0
  73. package/extensions/enact-operator/assets/icon.png +0 -0
  74. package/extensions/enact-operator/assets/logo.png +0 -0
  75. package/extensions/enact-operator/commands/doctor.md +39 -0
  76. package/extensions/enact-operator/commands/setup.md +51 -0
  77. package/extensions/enact-operator/hooks/hooks.json +146 -0
  78. package/extensions/enact-operator/skills/_variants.md +44 -0
  79. package/extensions/enact-operator/skills/ai-slop-cleaner/SKILL.md +50 -0
  80. package/extensions/enact-operator/skills/analyze/SKILL.md +91 -0
  81. package/extensions/enact-operator/skills/ask/SKILL.md +47 -0
  82. package/extensions/enact-operator/skills/autopilot/SKILL.md +170 -0
  83. package/extensions/enact-operator/skills/autoresearch-goal/SKILL.md +79 -0
  84. package/extensions/enact-operator/skills/cancel/SKILL.md +99 -0
  85. package/extensions/enact-operator/skills/configure-notifications/SKILL.md +77 -0
  86. package/extensions/enact-operator/skills/deep-interview/SKILL.md +80 -0
  87. package/extensions/enact-operator/skills/doctor/SKILL.md +48 -0
  88. package/extensions/enact-operator/skills/hud/SKILL.md +49 -0
  89. package/extensions/enact-operator/skills/hyperplan/SKILL.md +47 -0
  90. package/extensions/enact-operator/skills/plan/SKILL.md +78 -0
  91. package/extensions/enact-operator/skills/ralph/SKILL.md +201 -0
  92. package/extensions/enact-operator/skills/ralph/gemini.md +18 -0
  93. package/extensions/enact-operator/skills/ralplan/SKILL.md +151 -0
  94. package/extensions/enact-operator/skills/remove-deadcode/SKILL.md +45 -0
  95. package/extensions/enact-operator/skills/research/SKILL.md +74 -0
  96. package/extensions/enact-operator/skills/review/SKILL.md +58 -0
  97. package/extensions/enact-operator/skills/security-research/SKILL.md +54 -0
  98. package/extensions/enact-operator/skills/setup/SKILL.md +91 -0
  99. package/extensions/enact-operator/skills/setup/scripts/install.sh +50 -0
  100. package/extensions/enact-operator/skills/skill/SKILL.md +82 -0
  101. package/extensions/enact-operator/skills/tdd/SKILL.md +59 -0
  102. package/extensions/enact-operator/skills/team/SKILL.md +199 -0
  103. package/extensions/enact-operator/skills/trace/SKILL.md +41 -0
  104. package/extensions/enact-operator/skills/ultragoal/SKILL.md +99 -0
  105. package/extensions/enact-operator/skills/ultraqa/SKILL.md +113 -0
  106. package/extensions/enact-operator/skills/ultrawork/SKILL.md +145 -0
  107. package/extensions/enact-operator/skills/ultrawork/planner.md +28 -0
  108. package/extensions/enact-operator/skills/wiki/SKILL.md +41 -0
  109. package/extensions/enact-operator/skills/work-with-workitem/SKILL.md +51 -0
  110. package/extensions/enact-wiki/.agents/plugin.json +42 -0
  111. package/extensions/enact-wiki/.mcp.json +15 -0
  112. package/extensions/enact-wiki/README.md +44 -0
  113. package/extensions/enact-wiki/assets/icon.png +0 -0
  114. package/extensions/enact-wiki/assets/logo.png +0 -0
  115. package/extensions/enact-wiki/skills/document-parser/SKILL.md +17 -0
  116. package/extensions/enact-wiki/skills/document-parser/scripts/parse.sh +60 -0
  117. package/extensions/enact-wiki/skills/document-parser/skill.json +9 -0
  118. package/extensions/enact-wiki/skills/enact-wiki/SKILL.md +30 -0
  119. package/extensions/enact-wiki/skills/enact-wiki/references/ingest.md +62 -0
  120. package/extensions/enact-wiki/skills/enact-wiki/references/manage.md +34 -0
  121. package/extensions/enact-wiki/skills/enact-wiki/references/query.md +59 -0
  122. package/extensions/enact-wiki/skills/search-lab/SKILL.md +57 -0
  123. package/extensions/enact-wiki/skills/search-lab/scripts/analyze.ts +23 -0
  124. package/{plugins/net-revenue-management/.codex-plugin → extensions/net-revenue-management/.agents}/plugin.json +10 -6
  125. package/extensions/plugin-dev/.agents/plugin.json +42 -0
  126. package/extensions/plugin-dev/.mcp.json +3 -0
  127. package/extensions/plugin-dev/agents/agent-creator.md +199 -0
  128. package/extensions/plugin-dev/agents/plugin-validator.md +91 -0
  129. package/extensions/plugin-dev/agents/skill-reviewer.md +212 -0
  130. package/extensions/plugin-dev/commands/_archive/create-marketplace.md +427 -0
  131. package/extensions/plugin-dev/commands/_archive/plugin-dev-guide.md +12 -0
  132. package/extensions/plugin-dev/commands/create-plugin.md +498 -0
  133. package/extensions/plugin-dev/commands/start.md +81 -0
  134. package/extensions/plugin-dev/hooks/hooks.json +3 -0
  135. package/extensions/plugin-dev/skills/agent-development/SKILL.md +641 -0
  136. package/extensions/plugin-dev/skills/agent-development/examples/agent-creation-prompt.md +250 -0
  137. package/extensions/plugin-dev/skills/agent-development/examples/complete-agent-examples.md +461 -0
  138. package/extensions/plugin-dev/skills/agent-development/references/advanced-agent-fields.md +246 -0
  139. package/extensions/plugin-dev/skills/agent-development/references/agent-creation-system-prompt.md +216 -0
  140. package/extensions/plugin-dev/skills/agent-development/references/permission-modes-rules.md +226 -0
  141. package/extensions/plugin-dev/skills/agent-development/references/system-prompt-design.md +464 -0
  142. package/extensions/plugin-dev/skills/agent-development/references/triggering-examples.md +474 -0
  143. package/extensions/plugin-dev/skills/agent-development/scripts/create-agent-skeleton.sh +176 -0
  144. package/extensions/plugin-dev/skills/agent-development/scripts/test-agent-trigger.sh +227 -0
  145. package/extensions/plugin-dev/skills/agent-development/scripts/validate-agent.sh +227 -0
  146. package/extensions/plugin-dev/skills/command-development/SKILL.md +763 -0
  147. package/extensions/plugin-dev/skills/command-development/examples/plugin-commands.md +612 -0
  148. package/extensions/plugin-dev/skills/command-development/examples/simple-commands.md +527 -0
  149. package/extensions/plugin-dev/skills/command-development/references/advanced-workflows.md +762 -0
  150. package/extensions/plugin-dev/skills/command-development/references/documentation-patterns.md +769 -0
  151. package/extensions/plugin-dev/skills/command-development/references/frontmatter-reference.md +508 -0
  152. package/extensions/plugin-dev/skills/command-development/references/interactive-commands.md +966 -0
  153. package/extensions/plugin-dev/skills/command-development/references/marketplace-considerations.md +943 -0
  154. package/extensions/plugin-dev/skills/command-development/references/plugin-features-reference.md +637 -0
  155. package/extensions/plugin-dev/skills/command-development/references/plugin-integration.md +191 -0
  156. package/extensions/plugin-dev/skills/command-development/references/skill-tool.md +447 -0
  157. package/extensions/plugin-dev/skills/command-development/references/testing-strategies.md +723 -0
  158. package/extensions/plugin-dev/skills/command-development/scripts/check-frontmatter.sh +234 -0
  159. package/extensions/plugin-dev/skills/command-development/scripts/validate-command.sh +160 -0
  160. package/extensions/plugin-dev/skills/hook-development/SKILL.md +861 -0
  161. package/extensions/plugin-dev/skills/hook-development/examples/load-context.sh +55 -0
  162. package/extensions/plugin-dev/skills/hook-development/examples/validate-bash.sh +57 -0
  163. package/extensions/plugin-dev/skills/hook-development/examples/validate-write.sh +48 -0
  164. package/extensions/plugin-dev/skills/hook-development/references/advanced.md +871 -0
  165. package/extensions/plugin-dev/skills/hook-development/references/hook-input-schemas.md +145 -0
  166. package/extensions/plugin-dev/skills/hook-development/references/migration.md +392 -0
  167. package/extensions/plugin-dev/skills/hook-development/references/patterns.md +430 -0
  168. package/extensions/plugin-dev/skills/hook-development/scripts/README.md +181 -0
  169. package/extensions/plugin-dev/skills/hook-development/scripts/hook-linter.sh +153 -0
  170. package/extensions/plugin-dev/skills/hook-development/scripts/test-hook.sh +276 -0
  171. package/extensions/plugin-dev/skills/hook-development/scripts/validate-hook-schema.sh +159 -0
  172. package/extensions/plugin-dev/skills/mcp-integration/SKILL.md +775 -0
  173. package/extensions/plugin-dev/skills/mcp-integration/examples/http-server.json +20 -0
  174. package/extensions/plugin-dev/skills/mcp-integration/examples/sse-server.json +19 -0
  175. package/extensions/plugin-dev/skills/mcp-integration/examples/stdio-server.json +38 -0
  176. package/extensions/plugin-dev/skills/mcp-integration/examples/ws-server.json +26 -0
  177. package/extensions/plugin-dev/skills/mcp-integration/references/authentication.md +601 -0
  178. package/extensions/plugin-dev/skills/mcp-integration/references/server-discovery.md +190 -0
  179. package/extensions/plugin-dev/skills/mcp-integration/references/server-types.md +572 -0
  180. package/extensions/plugin-dev/skills/mcp-integration/references/tool-usage.md +623 -0
  181. package/extensions/plugin-dev/skills/plugin-dev-guide/SKILL.md +222 -0
  182. package/extensions/plugin-dev/skills/plugin-structure/SKILL.md +705 -0
  183. package/extensions/plugin-dev/skills/plugin-structure/examples/advanced-plugin.md +774 -0
  184. package/extensions/plugin-dev/skills/plugin-structure/examples/minimal-plugin.md +83 -0
  185. package/extensions/plugin-dev/skills/plugin-structure/examples/standard-plugin.md +611 -0
  186. package/extensions/plugin-dev/skills/plugin-structure/references/advanced-topics.md +289 -0
  187. package/extensions/plugin-dev/skills/plugin-structure/references/component-patterns.md +592 -0
  188. package/extensions/plugin-dev/skills/plugin-structure/references/github-actions.md +233 -0
  189. package/extensions/plugin-dev/skills/plugin-structure/references/headless-ci-mode.md +193 -0
  190. package/extensions/plugin-dev/skills/plugin-structure/references/manifest-reference.md +625 -0
  191. package/extensions/plugin-dev/skills/plugin-structure/references/output-styles.md +116 -0
  192. package/extensions/plugin-dev/skills/skill-development/SKILL.md +564 -0
  193. package/extensions/plugin-dev/skills/skill-development/examples/complete-skill.md +465 -0
  194. package/extensions/plugin-dev/skills/skill-development/examples/frontmatter-templates.md +167 -0
  195. package/extensions/plugin-dev/skills/skill-development/examples/minimal-skill.md +111 -0
  196. package/extensions/plugin-dev/skills/skill-development/references/advanced-frontmatter.md +225 -0
  197. package/extensions/plugin-dev/skills/skill-development/references/commands-vs-skills.md +39 -0
  198. package/extensions/plugin-dev/skills/skill-development/references/skill-creation-workflow.md +379 -0
  199. package/extensions/plugin-dev/skills/skill-development/references/skill-creator-original.md +210 -0
  200. package/package.json +8 -11
  201. package/scripts/enact-extensions.mjs +823 -21
  202. package/scripts/hooks/session-start-drift-check.mjs +58 -0
  203. package/scripts/lib/build-index.mjs +50 -0
  204. package/scripts/lib/bundle-hash.mjs +137 -0
  205. package/scripts/lib/hooks.mjs +741 -0
  206. package/scripts/lib/ledger.mjs +163 -0
  207. package/scripts/lib/list-bundles.mjs +70 -0
  208. package/scripts/lib/outdated.mjs +144 -0
  209. package/scripts/lib/provision-mcp.mjs +16 -0
  210. package/scripts/lib/resolve-bundle.mjs +121 -0
  211. package/scripts/lib/run-install.mjs +402 -38
  212. package/scripts/lib/run-prune.mjs +73 -0
  213. package/scripts/lib/run-sync.mjs +9 -1
  214. package/scripts/lib/run-uninstall.mjs +244 -0
  215. package/scripts/lib/run-update.mjs +152 -0
  216. package/scripts/lib/run-validate.mjs +21 -18
  217. package/scripts/lib/serve.mjs +472 -0
  218. package/scripts/postinstall.mjs +63 -0
  219. package/scripts/setup-enact-context.sh +2 -2
  220. package/scripts/version-bump.sh +463 -0
  221. package/spec/codex.json +1 -11
  222. package/spec/index.json +59 -0
  223. package/web/assets/README.md +111 -0
  224. package/web/assets/logo-full.png +0 -0
  225. package/web/assets/logo-slim.png +0 -0
  226. package/web/assets/tokens/base.css +45 -0
  227. package/web/assets/tokens/colors.css +248 -0
  228. package/web/assets/tokens/effects.css +24 -0
  229. package/web/assets/tokens/fonts.css +8 -0
  230. package/web/assets/tokens/index.css +18 -0
  231. package/web/assets/tokens/spacing.css +50 -0
  232. package/web/index.html +1188 -0
  233. package/.agents/plugins/marketplace.json +0 -20
  234. package/catalog/enact-context.json +0 -9
  235. package/catalog/enact-factory.json +0 -7
  236. package/catalog/enact-operator.json +0 -7
  237. package/catalog/enact-wiki.json +0 -7
  238. package/catalog/net-revenue-management.json +0 -8
  239. package/scripts/rename-supervisor-to-operator.pl +0 -66
  240. package/scripts/sync-manifests.mjs +0 -23
  241. package/scripts/validate-catalog.mjs +0 -37
  242. package/scripts/validate-plugin.mjs +0 -10
  243. /package/{plugins → extensions}/net-revenue-management/.mcp.json +0 -0
  244. /package/{plugins → extensions}/net-revenue-management/skills/net-revenue-risks/SKILL.md +0 -0
  245. /package/{plugins → extensions}/net-revenue-management/skills/net-revenue-scenario/SKILL.md +0 -0
@@ -1,60 +1,333 @@
1
+ import { dirname, join } from "node:path";
2
+ import { homedir } from "node:os";
3
+ import { existsSync, readFileSync } from "node:fs";
1
4
  import {
2
5
  installPluginBundle,
3
6
  installClaudePluginBundle,
4
7
  installCursorPluginBundle,
5
8
  installCodexPluginBundle,
9
+ installSharedPluginBundle,
6
10
  defaultEnactHome,
11
+ defaultSharedHome,
7
12
  } from "../../dist/index.js";
13
+ import { appendEntry } from "./ledger.mjs";
14
+ import { bundleHash } from "./bundle-hash.mjs";
15
+ import { provisionMcp, summarizeProvision } from "./provision-mcp.mjs";
16
+ import { registerPluginHooks, removePluginHooks } from "./hooks.mjs";
17
+ import { assertSupportedHookEvents } from "../../dist/index.js";
8
18
 
9
- export function runInstall(pluginRoot, options = {}) {
10
- const platform = options.platform ?? "codex";
11
-
12
- if (platform === "claude") {
13
- const result = installClaudePluginBundle({
14
- pluginRoot,
15
- claudeHome: options.claudeHome,
16
- marketplaceName: options.marketplaceName,
17
- sync: options.sync,
18
- });
19
- console.log(`installed ${result.name} -> ${result.installedPluginPath}`);
20
- console.log(`registered marketplace ${result.marketplaceName} -> ${result.marketplaceDir}`);
21
- for (const path of result.refreshedPaths) {
22
- console.log(`refreshed ${path}`);
19
+ // scope: "global" (default) -> the agent's default home (~/.codex, ~/.enact/agent, ~/.claude, ~/.cursor, ~/.agents)
20
+ // "local" -> a project-scoped home under the current dir (./.codex, ./.enact/agent, ...)
21
+ // An explicit --<platform>-home always wins over the scope default.
22
+ function resolveHomes(options) {
23
+ const scope = options.scope ?? "global";
24
+ const local = (sub) => join(process.cwd(), sub);
25
+ return {
26
+ scope,
27
+ codexHome: options.codexHome ?? (scope === "local" ? local(".codex") : undefined),
28
+ enactHome: options.enactHome ?? (scope === "local" ? local(".enact/agent") : defaultEnactHome()),
29
+ claudeHome: options.claudeHome ?? (scope === "local" ? local(".claude") : undefined),
30
+ cursorHome: options.cursorHome ?? (scope === "local" ? local(".cursor") : undefined),
31
+ // For shared: sharedHome is the base directory; installSharedPluginBundle appends .agents/skills/<name>
32
+ // Global default: homedir() (skills land at ~/.agents/skills/<name>)
33
+ // Local: cwd (skills land at ./.agents/skills/<name>)
34
+ sharedHome: options.sharedHome ?? (scope === "local" ? process.cwd() : defaultSharedHome()),
35
+ };
36
+ }
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // ALL_PLATFORMS — deterministic order for `--platform all`
40
+ // ---------------------------------------------------------------------------
41
+ const ALL_PLATFORMS = ["codex", "claude", "cursor", "enact"];
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // PLATFORM_TARGETS — data-driven dispatch table
45
+ //
46
+ // Each entry describes how to install for a given platform:
47
+ // homeField — which field of `homes` supplies the target directory
48
+ // install — the function to call; receives (pluginRoot, home, options)
49
+ // log — how to log the result
50
+ // ---------------------------------------------------------------------------
51
+ const PLATFORM_TARGETS = {
52
+ claude: {
53
+ homeField: "claudeHome",
54
+ install(pluginRoot, home, options) {
55
+ return installClaudePluginBundle({
56
+ pluginRoot,
57
+ claudeHome: home,
58
+ marketplaceName: options.marketplaceName,
59
+ sync: options.sync,
60
+ });
61
+ },
62
+ log(result) {
63
+ console.log(`installed ${result.name} -> ${result.installedPluginPath}`);
64
+ console.log(`registered marketplace ${result.marketplaceName} -> ${result.marketplaceDir}`);
65
+ for (const path of result.refreshedPaths) {
66
+ console.log(`refreshed ${path}`);
67
+ }
68
+ },
69
+ },
70
+
71
+ cursor: {
72
+ homeField: "cursorHome",
73
+ install(pluginRoot, home, options) {
74
+ return installCursorPluginBundle({
75
+ pluginRoot,
76
+ cursorHome: home,
77
+ sync: options.sync,
78
+ });
79
+ },
80
+ log(result) {
81
+ console.log(`installed ${result.name} -> ${result.installedPluginPath}`);
82
+ for (const path of result.refreshedPaths) {
83
+ console.log(`refreshed ${path}`);
84
+ }
85
+ },
86
+ },
87
+
88
+ enact: {
89
+ homeField: "enactHome",
90
+ install(pluginRoot, home, options) {
91
+ return installCodexPluginBundle({
92
+ pluginRoot,
93
+ codexHome: home,
94
+ marketplaceName: options.marketplaceName,
95
+ enable: options.enable,
96
+ sync: options.sync,
97
+ });
98
+ },
99
+ log(result) {
100
+ console.log(`installed ${result.pluginConfigKey} -> ${result.configPath}`);
101
+ console.log(`wrote ${result.marketplacePath}`);
102
+ for (const path of result.refreshedPaths) {
103
+ console.log(`refreshed ${path}`);
104
+ }
105
+ },
106
+ },
107
+
108
+ shared: {
109
+ homeField: "sharedHome",
110
+ install(pluginRoot, home, _options) {
111
+ return installSharedPluginBundle({
112
+ pluginRoot,
113
+ sharedHome: home,
114
+ });
115
+ },
116
+ log(result) {
117
+ console.log(`installed ${result.name} skills -> ${result.installedSkillsPath}`);
118
+ },
119
+ },
120
+ };
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // parsePlatforms — expand "all" and split comma-separated platform lists.
124
+ // Returns a deduped array of validated platform name strings.
125
+ // Throws on any unknown platform token (no silent codex fallthrough).
126
+ // ---------------------------------------------------------------------------
127
+ function parsePlatforms(platform) {
128
+ if (!platform || platform === "codex") return ["codex"];
129
+ if (platform === "all") return ALL_PLATFORMS;
130
+
131
+ // Comma-separated: "claude,cursor" → ["claude", "cursor"]. Dedup preserving order.
132
+ const parts = [...new Set(platform.split(",").map((p) => p.trim()).filter(Boolean))];
133
+ if (parts.length === 0) return ["codex"];
134
+
135
+ // Validate each token. "codex" is valid (table fallthrough); "all" is only valid
136
+ // as a standalone value, so it is rejected here when mixed with other names.
137
+ const valid = new Set(["codex", ...Object.keys(PLATFORM_TARGETS)]);
138
+ for (const part of parts) {
139
+ if (!valid.has(part)) {
140
+ throw new Error(
141
+ `Unknown platform: "${part}". Valid: codex, claude, cursor, enact, shared, all`,
142
+ );
23
143
  }
24
- return result;
25
144
  }
145
+ return parts;
146
+ }
26
147
 
27
- if (platform === "cursor") {
28
- const result = installCursorPluginBundle({
29
- pluginRoot,
30
- cursorHome: options.cursorHome,
31
- sync: options.sync,
148
+ // ---------------------------------------------------------------------------
149
+ // Ledger wiring — record successful installs to the install ledger.
150
+ //
151
+ // BEST-EFFORT: every ledger write is wrapped so a failure NEVER throws out of
152
+ // the install. On failure we emit a single-line warning to stderr and continue.
153
+ // ---------------------------------------------------------------------------
154
+
155
+ // The installed-path field varies by platform result shape.
156
+ function installedPathOf(result) {
157
+ return result.installedPluginPath ?? result.installedSkillsPath ?? result.configPath ?? "";
158
+ }
159
+
160
+ function recordLedgerEntry(entry, _homes, options) {
161
+ try {
162
+ // Single global ledger: every install (local or global scope) appends to
163
+ // <home>/.enact/extensions/ledger.jsonl. The scope distinction lives in the
164
+ // entry's `scope` field, not the path. Tests pass an explicit ledgerHome so
165
+ // they never write into the real ~/.enact.
166
+ appendEntry(entry, {
167
+ home: options.ledgerHome ?? homedir(),
32
168
  });
33
- console.log(`installed ${result.name} -> ${result.installedPluginPath}`);
34
- for (const path of result.refreshedPaths) {
35
- console.log(`refreshed ${path}`);
169
+ } catch (err) {
170
+ const msg = err instanceof Error ? err.message : String(err);
171
+ process.stderr.write(`[enact-extensions install] Warning: failed to write install ledger: ${msg}\n`);
172
+ }
173
+ }
174
+
175
+ // ---------------------------------------------------------------------------
176
+ // computeBundleHash — best-effort; never throws out of the install path.
177
+ // Returns the sha256 hex hash of the canonical bundle, or null on failure.
178
+ // The `bundleHashRoot` option (injected by tests) overrides the default
179
+ // pluginRoot so tests can simulate a hash failure without breaking the install.
180
+ // ---------------------------------------------------------------------------
181
+ function computeBundleHash(pluginRoot, options) {
182
+ const root = options.bundleHashRoot ?? pluginRoot;
183
+ try {
184
+ return bundleHash(root);
185
+ } catch (err) {
186
+ const msg = err instanceof Error ? err.message : String(err);
187
+ process.stderr.write(`[enact-extensions install] Warning: cannot compute bundle hash: ${msg}\n`);
188
+ return null;
189
+ }
190
+ }
191
+
192
+ function readJsonIfExists(path) {
193
+ if (!existsSync(path)) return null;
194
+ try {
195
+ return JSON.parse(readFileSync(path, "utf8"));
196
+ } catch {
197
+ return null;
198
+ }
199
+ }
200
+
201
+ function readPluginHookConfig(pluginRoot) {
202
+ const manifest = readJsonIfExists(join(pluginRoot, ".agents", "plugin.json"));
203
+ const hooksPath = typeof manifest?.hooks === "string" ? manifest.hooks : null;
204
+ if (!hooksPath) return null;
205
+ const normalized = hooksPath.replace(/^\.\//, "");
206
+ return readJsonIfExists(join(pluginRoot, normalized));
207
+ }
208
+
209
+ function registerInstalledPluginHooks(pluginRoot, platform, result, homes, options) {
210
+ // codex reads hooks from the installed plugin bundle (no config.toml
211
+ // registration); cursor likewise. claude/enact register into their config.
212
+ if (!["claude", "cursor", "enact"].includes(platform)) return;
213
+ const hookConfig = readPluginHookConfig(pluginRoot);
214
+ const name = result?.name ?? result?.results?.[0]?.name;
215
+ if (!name) return;
216
+
217
+ const hookOpts = {
218
+ cwd: options.cwd ?? pluginRoot,
219
+ claudeHome: platform === "claude" ? homes.claudeHome : undefined,
220
+ cursorHome: platform === "cursor" ? homes.cursorHome : undefined,
221
+ enactHome: platform === "enact" ? homes.enactHome : undefined,
222
+ };
223
+
224
+ // Self-healing reconcile: first remove ALL of THIS plugin's prior hook
225
+ // registrations (orphaned events dropped from the bundle + duplicate entries
226
+ // accumulated by earlier appends), then register the current bundle's set.
227
+ // Removal is marker/command scoped per plugin, so the user's own hooks and
228
+ // other plugins' hooks are never touched.
229
+ removePluginHooks(platform, name, hookOpts);
230
+
231
+ if (!hookConfig) return;
232
+ const hookResult = registerPluginHooks(platform, name, hookConfig, hookOpts);
233
+ if (hookResult.result !== "skipped") {
234
+ console.log(`hooks ${hookResult.result} for ${name} on ${platform} -> ${hookResult.location}`);
235
+ }
236
+ }
237
+
238
+ function removeLegacyCodexPluginHooks(name, result, homes, options) {
239
+ if (!name) return;
240
+
241
+ if (Array.isArray(result?.results)) {
242
+ for (const install of result.results) {
243
+ const codexHome = install.configPath ? dirname(install.configPath) : homes.codexHome;
244
+ const hookResult = removePluginHooks("codex", name, {
245
+ cwd: options.cwd ?? process.cwd(),
246
+ codexHome,
247
+ });
248
+ if (hookResult.result === "removed") {
249
+ console.log(`removed legacy codex hooks for ${name} -> ${hookResult.location}`);
250
+ }
36
251
  }
37
- return result;
252
+ return;
38
253
  }
39
254
 
40
- if (platform === "enact") {
41
- const result = installCodexPluginBundle({
42
- pluginRoot,
43
- codexHome: options.enactHome ?? defaultEnactHome(),
44
- marketplaceName: options.marketplaceName,
45
- enable: options.enable,
46
- sync: options.sync,
47
- });
48
- console.log(`installed ${result.pluginConfigKey} -> ${result.configPath}`);
49
- console.log(`wrote ${result.marketplacePath}`);
50
- for (const path of result.refreshedPaths) {
51
- console.log(`refreshed ${path}`);
255
+ const hookResult = removePluginHooks("codex", name, {
256
+ cwd: options.cwd ?? process.cwd(),
257
+ codexHome: homes.codexHome,
258
+ });
259
+ if (hookResult.result === "removed") {
260
+ console.log(`removed legacy codex hooks for ${name} -> ${hookResult.location}`);
261
+ }
262
+ }
263
+
264
+ // Append one install entry per installed target in a single-platform result.
265
+ // Tolerant of every result shape (single platform, shared, codex multi-home).
266
+ // `hash` is the pre-computed bundle hash (or null) for this install invocation.
267
+ function recordInstall(platform, result, homes, options, hash) {
268
+ const targets = [];
269
+ if (result && Array.isArray(result.results)) {
270
+ // codex (default) multi-home: one entry per codex home.
271
+ for (const install of result.results) {
272
+ targets.push({
273
+ name: install.name,
274
+ version: install.version,
275
+ path: installedPathOf(install),
276
+ home: install.configPath ? join(install.configPath, "..") : homes.codexHome,
277
+ });
52
278
  }
279
+ } else if (result) {
280
+ targets.push({
281
+ name: result.name,
282
+ version: result.version,
283
+ path: installedPathOf(result),
284
+ home: homes[PLATFORM_TARGETS[platform]?.homeField] ?? homes.codexHome,
285
+ });
286
+ }
287
+
288
+ for (const t of targets) {
289
+ if (!t.name) continue;
290
+ recordLedgerEntry(
291
+ {
292
+ action: "install",
293
+ name: t.name,
294
+ version: t.version,
295
+ platform,
296
+ scope: homes.scope,
297
+ home: t.home,
298
+ path: t.path,
299
+ hash,
300
+ marketplaceName: options.marketplaceName ?? "enact-os-plugins",
301
+ },
302
+ homes,
303
+ options,
304
+ );
305
+ }
306
+ }
307
+
308
+ // ---------------------------------------------------------------------------
309
+ // runSinglePlatform — install for one platform; returns the install result.
310
+ // Throws on error (caller catches for partial-failure tracking).
311
+ // `hash` is the pre-computed bundle hash (or null) for this install invocation.
312
+ // ---------------------------------------------------------------------------
313
+ function runSinglePlatform(pluginRoot, platform, homes, options, hash) {
314
+ const target = PLATFORM_TARGETS[platform];
315
+ if (target) {
316
+ const home = homes[target.homeField];
317
+ const result = target.install(pluginRoot, home, options);
318
+ target.log(result);
319
+ recordInstall(platform, result, homes, options, hash);
320
+ registerInstalledPluginHooks(pluginRoot, platform, result, homes, options);
53
321
  return result;
54
322
  }
55
323
 
56
- // codex (default) or explicit --platform codex
57
- const result = installPluginBundle({ pluginRoot, cwd: options.cwd ?? pluginRoot, ...options });
324
+ // codex (default) multi-home discovery via installPluginBundle.
325
+ const result = installPluginBundle({
326
+ pluginRoot,
327
+ cwd: options.cwd ?? pluginRoot,
328
+ ...options,
329
+ codexHome: homes.codexHome,
330
+ });
58
331
  for (const install of result.results) {
59
332
  console.log(`installed ${install.pluginConfigKey} -> ${install.configPath}`);
60
333
  console.log(`wrote ${install.marketplacePath}`);
@@ -62,5 +335,96 @@ export function runInstall(pluginRoot, options = {}) {
62
335
  console.log(`refreshed ${path}`);
63
336
  }
64
337
  }
338
+ recordInstall("codex", result, homes, options, hash);
339
+ removeLegacyCodexPluginHooks(result.results[0]?.name, result, homes, options);
65
340
  return result;
66
341
  }
342
+
343
+ // ---------------------------------------------------------------------------
344
+ // MCP dependency provisioning — best-effort, platform-independent.
345
+ //
346
+ // After a successful install we provision any packages the bundle's declared
347
+ // MCP servers need (uvx → `uv tool install`, npx → `npm install -g`, etc.).
348
+ // This runs ONCE per runInstall call (provisioning is platform-independent; a
349
+ // `--platform all` install still provisions a single time). Provisioning is on
350
+ // by DEFAULT; pass `--no-provision` / { noProvision: true } to skip. A failure
351
+ // here NEVER fails the install — provisionMcp is best-effort by contract.
352
+ // ---------------------------------------------------------------------------
353
+ function runProvision(pluginRoot, options) {
354
+ try {
355
+ const provisionResults = provisionMcp(pluginRoot, {
356
+ noProvision: options.noProvision,
357
+ exec: options.provisionExec, // injectable for tests; undefined → real spawnSync
358
+ });
359
+ for (const line of summarizeProvision(provisionResults)) {
360
+ console.log(`provision: ${line}`);
361
+ }
362
+ return provisionResults;
363
+ } catch (err) {
364
+ // Defence-in-depth: provisionMcp is already best-effort, but never let a
365
+ // provisioning bug fail an otherwise-successful install.
366
+ const msg = err instanceof Error ? err.message : String(err);
367
+ process.stderr.write(`[enact-extensions install] Warning: MCP provisioning errored: ${msg}\n`);
368
+ return [];
369
+ }
370
+ }
371
+
372
+ export function runInstall(pluginRoot, options = {}) {
373
+ // Refuse to install a bundle that declares hook events outside the
374
+ // cross-surface intersection (claude, codex, cursor, enact).
375
+ assertSupportedHookEvents(pluginRoot);
376
+
377
+ const platforms = parsePlatforms(options.platform);
378
+ const homes = resolveHomes(options);
379
+ console.log(`install scope: ${homes.scope}${homes.scope === "local" ? ` (${process.cwd()})` : ""}`);
380
+
381
+ // Compute bundle hash ONCE per runInstall invocation (best-effort, never throws).
382
+ // Applied to every per-platform ledger entry for this bundle.
383
+ const hash = computeBundleHash(pluginRoot, options);
384
+
385
+ // Single-platform path — unchanged behaviour (no .platforms wrapper).
386
+ if (platforms.length === 1) {
387
+ const result = runSinglePlatform(pluginRoot, platforms[0], homes, options, hash);
388
+ // Provision once after a successful install.
389
+ result.provision = runProvision(pluginRoot, options);
390
+ return result;
391
+ }
392
+
393
+ // Multi-platform path — run each sequentially, collect results + errors.
394
+ const platformResults = [];
395
+ let hasError = false;
396
+
397
+ for (const platform of platforms) {
398
+ console.log(`\n[${platform}] installing...`);
399
+ try {
400
+ const result = runSinglePlatform(pluginRoot, platform, homes, options, hash);
401
+ console.log(`[${platform}] ✓ done`);
402
+ platformResults.push({ platform, result, error: null });
403
+ } catch (err) {
404
+ const msg = err instanceof Error ? err.message : String(err);
405
+ console.error(`[${platform}] ✗ failed: ${msg}`);
406
+ platformResults.push({ platform, result: null, error: err });
407
+ hasError = true;
408
+ }
409
+ }
410
+
411
+ // Provision ONCE for the whole multi-platform run (platform-independent), as
412
+ // long as at least one platform installed successfully.
413
+ let provision = [];
414
+ if (platformResults.some((r) => !r.error)) {
415
+ provision = runProvision(pluginRoot, options);
416
+ }
417
+
418
+ if (hasError) {
419
+ const failed = platformResults.filter((r) => r.error).map((r) => r.platform);
420
+ const succeeded = platformResults.filter((r) => !r.error).map((r) => r.platform);
421
+ const summary = `Multi-platform install: ${succeeded.length} succeeded (${succeeded.join(", ")}), ${failed.length} failed (${failed.join(", ")})`;
422
+ console.error(`\n${summary}`);
423
+ const err = new Error(summary);
424
+ err.platforms = platformResults;
425
+ err.provision = provision;
426
+ throw err;
427
+ }
428
+
429
+ return { platforms: platformResults, provision };
430
+ }
@@ -0,0 +1,73 @@
1
+ import { existsSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { cwd as processCwd } from "node:process";
4
+ import { dirname, join, resolve } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import { listInstalled } from "./ledger.mjs";
7
+ import { parsePlatforms, runUninstall } from "./run-uninstall.mjs";
8
+
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const PACKAGE_ROOT = resolve(__dirname, "..", "..");
11
+ const DEFAULT_MARKETPLACE = "enact-os-plugins";
12
+
13
+ function defaultSourceRoots(cwd) {
14
+ const roots = [join(PACKAGE_ROOT, "extensions")];
15
+ const cwdExtensions = join(cwd, "extensions");
16
+ if (resolve(cwdExtensions) !== resolve(roots[0])) roots.push(cwdExtensions);
17
+ return roots;
18
+ }
19
+
20
+ function sourceBundleExists(name, sourceRoots) {
21
+ return sourceRoots.some((root) => existsSync(join(root, name, ".agents", "plugin.json")));
22
+ }
23
+
24
+ function homeOptionForPlatform(platform) {
25
+ switch (platform) {
26
+ case "codex": return "codexHome";
27
+ case "claude": return "claudeHome";
28
+ case "cursor": return "cursorHome";
29
+ case "enact": return "enactHome";
30
+ case "shared": return "sharedHome";
31
+ default: return "codexHome";
32
+ }
33
+ }
34
+
35
+ export function runPrune(options = {}) {
36
+ const cwd = options.cwd ?? processCwd();
37
+ const ledgerHome = options.ledgerHome ?? options.home ?? homedir();
38
+ const marketplaceName = options.marketplaceName ?? DEFAULT_MARKETPLACE;
39
+ const platforms = new Set(parsePlatforms(options.platform ?? "all"));
40
+ const sourceRoots = options.sourceRoots ?? defaultSourceRoots(cwd);
41
+ const installed = listInstalled({ home: ledgerHome });
42
+
43
+ const candidates = installed
44
+ .filter((entry) => platforms.has(entry.platform))
45
+ .filter((entry) => entry.marketplaceName === marketplaceName)
46
+ .filter((entry) => !sourceBundleExists(entry.name, sourceRoots));
47
+
48
+ const pruned = [];
49
+ const failed = [];
50
+
51
+ if (options.dryRun) {
52
+ return { candidates, pruned, failed, dryRun: true };
53
+ }
54
+
55
+ for (const entry of candidates) {
56
+ const homeOption = homeOptionForPlatform(entry.platform);
57
+ try {
58
+ runUninstall(entry.name, {
59
+ platform: entry.platform,
60
+ scope: entry.scope,
61
+ [homeOption]: entry.home,
62
+ marketplaceName,
63
+ ledgerHome,
64
+ version: entry.version,
65
+ });
66
+ pruned.push(entry);
67
+ } catch (err) {
68
+ failed.push({ entry, error: err instanceof Error ? err : new Error(String(err)) });
69
+ }
70
+ }
71
+
72
+ return { candidates, pruned, failed, dryRun: false };
73
+ }
@@ -1,6 +1,10 @@
1
1
  import { readFileSync, existsSync } from "node:fs";
2
2
  import { join } from "node:path";
3
- import { syncPlatformManifests, createEnactManifest } from "../../dist/index.js";
3
+ import {
4
+ syncPlatformManifests,
5
+ createEnactManifest,
6
+ assertSupportedHookEvents,
7
+ } from "../../dist/index.js";
4
8
 
5
9
  export function runSync(pluginRoot, { name } = {}) {
6
10
  const enactPath = join(pluginRoot, ".agents/plugin.json");
@@ -16,6 +20,10 @@ export function runSync(pluginRoot, { name } = {}) {
16
20
  enact = createEnactManifest({ name });
17
21
  }
18
22
 
23
+ // Refuse to project manifests for a bundle that declares hook events outside
24
+ // the cross-surface intersection (claude, codex, cursor, enact).
25
+ assertSupportedHookEvents(pluginRoot);
26
+
19
27
  const results = syncPlatformManifests(pluginRoot, enact);
20
28
  for (const r of results) {
21
29
  console.log(`${r.written ? "wrote" : "skip"} ${r.manifestPath}`);