@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
@@ -0,0 +1,163 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+
5
+ /**
6
+ * Install ledger — a JSONL record of install/uninstall events.
7
+ *
8
+ * There is exactly ONE ledger location — the GLOBAL file:
9
+ * <home>/.enact/extensions/ledger.jsonl (home defaults to os.homedir())
10
+ *
11
+ * The local-vs-global install distinction is tracked PURELY as the `scope`
12
+ * FIELD inside each entry (scope:"local" | scope:"global"); BOTH scopes write
13
+ * to this single global ledger. The `home` override exists so tests never touch
14
+ * the real ~/.enact.
15
+ *
16
+ * Entry shape: { ts, action, name, version, platform, scope, home, path, marketplaceName }
17
+ *
18
+ * All file I/O is best-effort: failures throw a catchable error so callers
19
+ * (the install path) can swallow + warn rather than aborting an install.
20
+ */
21
+
22
+ /**
23
+ * Resolve the on-disk ledger path. Always the single global file.
24
+ * @param {object} [opts]
25
+ * @param {string} [opts.home] - home dir. Defaults to os.homedir().
26
+ * @returns {string}
27
+ */
28
+ export function ledgerPath({ home } = {}) {
29
+ return join(home ?? homedir(), ".enact", "extensions", "ledger.jsonl");
30
+ }
31
+
32
+ /**
33
+ * Append a single entry as one JSON line. Creates the parent dir if missing.
34
+ * `ts` defaults to new Date().toISOString() when absent (an injected ts is kept).
35
+ * Best-effort: throws a catchable error on I/O failure.
36
+ *
37
+ * @param {object} entry
38
+ * @param {object} [opts] - { home }
39
+ */
40
+ export function appendEntry(entry, { home } = {}) {
41
+ const record = { ts: entry.ts ?? new Date().toISOString(), ...entry };
42
+ // Ensure ts wins even if entry has an explicit undefined.
43
+ if (!record.ts) record.ts = new Date().toISOString();
44
+ const path = ledgerPath({ home });
45
+ mkdirSync(dirname(path), { recursive: true });
46
+ appendFileSync(path, `${JSON.stringify(record)}\n`, "utf8");
47
+ }
48
+
49
+ /**
50
+ * Read and parse all entries from the single global ledger.
51
+ * Missing file → []. Malformed lines are skipped with a stderr warning.
52
+ *
53
+ * @param {object} [opts] - { home }
54
+ * @returns {object[]}
55
+ */
56
+ export function readLedger({ home } = {}) {
57
+ const path = ledgerPath({ home });
58
+ if (!existsSync(path)) return [];
59
+ let raw;
60
+ try {
61
+ raw = readFileSync(path, "utf8");
62
+ } catch (err) {
63
+ process.stderr.write(
64
+ `[enact-extensions log] Warning: cannot read ledger ${path}: ${err instanceof Error ? err.message : String(err)}\n`,
65
+ );
66
+ return [];
67
+ }
68
+ const entries = [];
69
+ for (const line of raw.split("\n")) {
70
+ const trimmed = line.trim();
71
+ if (!trimmed) continue;
72
+ try {
73
+ entries.push(JSON.parse(trimmed));
74
+ } catch {
75
+ process.stderr.write(
76
+ `[enact-extensions log] Warning: skipping malformed ledger line in ${path}\n`,
77
+ );
78
+ }
79
+ }
80
+ return entries;
81
+ }
82
+
83
+ /**
84
+ * Thin alias of {@link readLedger}, retained so existing callers keep working.
85
+ * With the single-global ledger there is no merge to perform: global- and
86
+ * local-scope entries are co-located in the one file.
87
+ *
88
+ * @param {object} [opts] - { home }
89
+ * @returns {object[]}
90
+ */
91
+ export function readMerged({ home } = {}) {
92
+ return readLedger({ home });
93
+ }
94
+
95
+ /**
96
+ * Compute the current install state for `name` by replaying install/uninstall
97
+ * events in ts order across the single global ledger. Returns the set of
98
+ * still-installed targets as { platform, scope, home, path }.
99
+ *
100
+ * A target is keyed by platform+scope+home+path so the same plugin installed to
101
+ * multiple platforms/homes is tracked independently.
102
+ *
103
+ * @param {string} name
104
+ * @param {object} [opts] - { home }
105
+ * @returns {{ platform: string, scope: string, home: string, path: string }[]}
106
+ */
107
+ export function findInstalled(name, { home } = {}) {
108
+ const merged = readLedger({ home }).filter((e) => e && e.name === name);
109
+ // Stable sort by ts (string ISO compares chronologically).
110
+ merged.sort((a, b) => String(a.ts ?? "").localeCompare(String(b.ts ?? "")));
111
+
112
+ const state = new Map();
113
+ for (const e of merged) {
114
+ const key = `${e.platform} ${e.scope} ${e.home ?? ""} ${e.path ?? ""}`;
115
+ if (e.action === "install") {
116
+ state.set(key, { platform: e.platform, scope: e.scope, home: e.home, path: e.path });
117
+ } else if (e.action === "uninstall") {
118
+ state.delete(key);
119
+ }
120
+ }
121
+ return [...state.values()];
122
+ }
123
+
124
+ /**
125
+ * Compute the current install state for ALL plugins by replaying install/uninstall
126
+ * events in ts order across the single global ledger. Returns the set of
127
+ * still-installed targets across all plugin names.
128
+ *
129
+ * Each returned entry carries the full ledger fields for the installed surface:
130
+ * { name, version, platform, scope, home, path, hash, marketplaceName }
131
+ *
132
+ * A target is keyed by name+platform+scope+home+path so the same plugin installed
133
+ * to multiple platforms/homes/names is tracked independently.
134
+ *
135
+ * @param {object} [opts] - { home }
136
+ * @returns {{ name: string, version: string, platform: string, scope: string, home: string, path: string, hash: string|null, marketplaceName: string|null }[]}
137
+ */
138
+ export function listInstalled({ home } = {}) {
139
+ const all = readLedger({ home });
140
+ // Stable sort by ts (string ISO compares chronologically).
141
+ all.sort((a, b) => String(a.ts ?? "").localeCompare(String(b.ts ?? "")));
142
+
143
+ const state = new Map();
144
+ for (const e of all) {
145
+ if (!e || !e.name) continue;
146
+ const key = `${e.name} ${e.platform ?? ""} ${e.scope ?? ""} ${e.home ?? ""} ${e.path ?? ""}`;
147
+ if (e.action === "install") {
148
+ state.set(key, {
149
+ name: e.name,
150
+ version: e.version ?? null,
151
+ platform: e.platform,
152
+ scope: e.scope,
153
+ home: e.home,
154
+ path: e.path,
155
+ hash: e.hash ?? null,
156
+ marketplaceName: e.marketplaceName ?? null,
157
+ });
158
+ } else if (e.action === "uninstall") {
159
+ state.delete(key);
160
+ }
161
+ }
162
+ return [...state.values()];
163
+ }
@@ -0,0 +1,70 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+
4
+ /**
5
+ * Enumerate plugin bundles under the given root directories.
6
+ *
7
+ * For each root, scans direct subdirectories for `.agents/plugin.json`.
8
+ * - Skips subdirs without a readable `.agents/plugin.json`.
9
+ * - Warns to stderr on malformed JSON but does NOT throw.
10
+ * - De-duplicates roots by resolved path so the same directory is never scanned twice.
11
+ *
12
+ * @param {string[]} roots - Absolute paths to scan for plugin bundles.
13
+ * @returns {{ name: string, version: string, category: string, description: string, targets: string[], path: string }[]}
14
+ */
15
+ export function listBundles(roots) {
16
+ // De-duplicate roots by resolved absolute path.
17
+ const seen = new Set();
18
+ const uniqueRoots = [];
19
+ for (const r of roots) {
20
+ const abs = resolve(r);
21
+ if (!seen.has(abs)) {
22
+ seen.add(abs);
23
+ uniqueRoots.push(abs);
24
+ }
25
+ }
26
+
27
+ const results = [];
28
+
29
+ for (const root of uniqueRoots) {
30
+ if (!existsSync(root)) continue;
31
+
32
+ let entries;
33
+ try {
34
+ entries = readdirSync(root, { withFileTypes: true });
35
+ } catch {
36
+ // Unreadable root — skip silently.
37
+ continue;
38
+ }
39
+
40
+ for (const entry of entries) {
41
+ if (!entry.isDirectory()) continue;
42
+
43
+ const bundleDir = join(root, entry.name);
44
+ const manifestPath = join(bundleDir, ".agents", "plugin.json");
45
+
46
+ if (!existsSync(manifestPath)) continue;
47
+
48
+ let manifest;
49
+ try {
50
+ manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
51
+ } catch (err) {
52
+ process.stderr.write(
53
+ `[enact-extensions list] Warning: skipping malformed manifest at ${manifestPath}: ${err.message}\n`,
54
+ );
55
+ continue;
56
+ }
57
+
58
+ results.push({
59
+ name: manifest.name ?? entry.name,
60
+ version: manifest.version ?? "unknown",
61
+ category: manifest.interface?.category ?? "",
62
+ description: manifest.description ?? "",
63
+ targets: Array.isArray(manifest.targets) ? manifest.targets : [],
64
+ path: bundleDir,
65
+ });
66
+ }
67
+ }
68
+
69
+ return results;
70
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * outdated.mjs — compute freshness drift between installed plugins and their
3
+ * canonical source bundles.
4
+ *
5
+ * computeOutdated({ home?, cwd? }) → OutdatedEntry[]
6
+ *
7
+ * For each currently-installed (plugin, platform, scope, home) surface from the
8
+ * ledger, resolves the canonical source bundle (extensions/<name>) and classifies:
9
+ *
10
+ * fresh — canonical source exists and its hash matches the ledger's recorded hash.
11
+ * outdated — canonical source exists but hash differs (or ledger hash is null/missing),
12
+ * or the recorded version differs (future: not currently used for the flag,
13
+ * but hash comparison is the primary signal).
14
+ * orphaned — canonical source does NOT exist on disk; cannot refresh.
15
+ *
16
+ * Returns a sorted array of OutdatedEntry objects:
17
+ * { name, platform, scope, home, status, installedHash, currentHash, version }
18
+ *
19
+ * Pure-ish: reads ledger + canonical bundle; no writes.
20
+ */
21
+
22
+ import { homedir } from "node:os";
23
+ import { cwd as processCwd } from "node:process";
24
+ import { bundleHash } from "./bundle-hash.mjs";
25
+ import { listInstalled } from "./ledger.mjs";
26
+ import { resolveBundlePath } from "./resolve-bundle.mjs";
27
+
28
+ /**
29
+ * @typedef {object} OutdatedEntry
30
+ * @property {string} name
31
+ * @property {string} platform
32
+ * @property {string} scope
33
+ * @property {string} home
34
+ * @property {"fresh"|"outdated"|"orphaned"} status
35
+ * @property {string|null} installedHash — hash recorded at install time (may be null)
36
+ * @property {string|null} currentHash — hash of canonical source now (null if orphaned)
37
+ * @property {string|null} version — version recorded in the ledger
38
+ */
39
+
40
+ /**
41
+ * Safely compute the canonical bundle hash for a given bundle root.
42
+ * Returns null on any error (missing manifest, I/O error, etc.).
43
+ *
44
+ * @param {string} bundleRoot
45
+ * @returns {string|null}
46
+ */
47
+ function safeHash(bundleRoot) {
48
+ try {
49
+ return bundleHash(bundleRoot);
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Resolve the canonical bundle path for a plugin name.
57
+ * Returns null when the canonical source cannot be found (so caller can
58
+ * classify as orphaned rather than crashing).
59
+ *
60
+ * @param {string} name
61
+ * @param {string} cwd
62
+ * @returns {string|null}
63
+ */
64
+ function safeResolveBundlePath(name, cwd) {
65
+ try {
66
+ return resolveBundlePath(name, { cwd });
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Compute freshness status for all currently-installed plugin surfaces.
74
+ *
75
+ * @param {object} [opts]
76
+ * @param {string} [opts.home] — ledger home override (defaults to os.homedir())
77
+ * @param {string} [opts.cwd] — working directory for bundle resolution (defaults to process.cwd())
78
+ * @returns {OutdatedEntry[]} sorted by name, then platform, then scope
79
+ */
80
+ export function computeOutdated({ home, cwd } = {}) {
81
+ const resolvedHome = home ?? homedir();
82
+ const resolvedCwd = cwd ?? processCwd();
83
+
84
+ // Get all currently-installed entries (install replayed over uninstall).
85
+ const installed = listInstalled({ home: resolvedHome });
86
+
87
+ /** @type {OutdatedEntry[]} */
88
+ const results = [];
89
+
90
+ for (const entry of installed) {
91
+ const { name, platform, scope, home: entryHome, version, hash: installedHash } = entry;
92
+
93
+ // Resolve canonical source bundle for this plugin name.
94
+ const canonicalPath = safeResolveBundlePath(name, resolvedCwd);
95
+
96
+ if (!canonicalPath) {
97
+ // Canonical source does not exist → orphaned.
98
+ results.push({
99
+ name,
100
+ platform,
101
+ scope,
102
+ home: entryHome,
103
+ status: "orphaned",
104
+ installedHash: installedHash ?? null,
105
+ currentHash: null,
106
+ version: version ?? null,
107
+ });
108
+ continue;
109
+ }
110
+
111
+ // Canonical source exists — compute its current hash.
112
+ const currentHash = safeHash(canonicalPath);
113
+
114
+ let status;
115
+ if (installedHash == null || currentHash == null || installedHash !== currentHash) {
116
+ // No recorded hash, or canonical hash is uncomputable, or hashes differ.
117
+ status = "outdated";
118
+ } else {
119
+ status = "fresh";
120
+ }
121
+
122
+ results.push({
123
+ name,
124
+ platform,
125
+ scope,
126
+ home: entryHome,
127
+ status,
128
+ installedHash: installedHash ?? null,
129
+ currentHash,
130
+ version: version ?? null,
131
+ });
132
+ }
133
+
134
+ // Deterministic sort: name → platform → scope
135
+ results.sort((a, b) => {
136
+ const n = String(a.name ?? "").localeCompare(String(b.name ?? ""));
137
+ if (n !== 0) return n;
138
+ const p = String(a.platform ?? "").localeCompare(String(b.platform ?? ""));
139
+ if (p !== 0) return p;
140
+ return String(a.scope ?? "").localeCompare(String(b.scope ?? ""));
141
+ });
142
+
143
+ return results;
144
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * CLI/runtime wrapper over the typed library provisioning API.
3
+ *
4
+ * The script-layer helpers remain for backwards compatibility with tests and
5
+ * the CLI wrapper, but the source of truth now lives in src/provision.ts and is
6
+ * exported from dist/index.js for in-process consumers like enact-operator.
7
+ */
8
+ import {
9
+ provisionMcp as provisionMcpCore,
10
+ readMcpServers as readMcpServersCore,
11
+ summarizeProvision as summarizeProvisionCore,
12
+ } from "../../dist/index.js";
13
+
14
+ export const readMcpServers = readMcpServersCore;
15
+ export const provisionMcp = provisionMcpCore;
16
+ export const summarizeProvision = summarizeProvisionCore;
@@ -0,0 +1,121 @@
1
+ import { execSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join, resolve, sep } from "node:path";
4
+ import { cwd as processCwd } from "node:process";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+
9
+ /**
10
+ * The package root of enact-extensions (two levels up from scripts/lib/).
11
+ * This is resolved relative to THIS file's location so it works regardless
12
+ * of where the CLI is invoked from.
13
+ */
14
+ const PACKAGE_ROOT = resolve(__dirname, "..", "..");
15
+
16
+ /**
17
+ * Returns true if `dir` looks like a valid plugin bundle root
18
+ * (i.e. it contains .agents/plugin.json).
19
+ */
20
+ function isBundle(dir) {
21
+ return existsSync(join(dir, ".agents", "plugin.json"));
22
+ }
23
+
24
+ /**
25
+ * Determine whether arg looks like a filesystem path (contains a separator)
26
+ * rather than a bare name.
27
+ */
28
+ function isPathLike(arg) {
29
+ return arg.includes("/") || arg.includes(sep);
30
+ }
31
+
32
+ /**
33
+ * Get the npm global root via `npm root -g`. Returns null on failure.
34
+ */
35
+ function getNpmGlobalRoot() {
36
+ try {
37
+ const result = execSync("npm root -g", { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
38
+ return result.trim();
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Resolve a plugin argument to an absolute bundle path.
46
+ *
47
+ * Resolution order:
48
+ * 1. If `arg` is an existing path (absolute, or relative to `cwd`) → return it.
49
+ * 2. Else if `arg` is a bare name (no path separators):
50
+ * a. <packageRoot>/extensions/<arg> (where packageRoot is this CLI's own package)
51
+ * b. <cwd>/extensions/<arg>
52
+ * c. <npmGlobalRoot>/@amsterdamdatalabs/enact-extensions/extensions/<arg>
53
+ * 3. If nothing found → throw a clear error listing all candidates tried.
54
+ *
55
+ * @param {string} arg - The argument from the CLI (bare name or path).
56
+ * @param {{ cwd?: string }} [opts]
57
+ * @returns {string} Resolved absolute path to the bundle root.
58
+ */
59
+ export function resolveBundlePath(arg, opts = {}) {
60
+ const cwd = opts.cwd ?? processCwd();
61
+
62
+ // --- Rule 1: treat as a path if it contains separators, or if it resolves to an existing path ---
63
+ if (isPathLike(arg)) {
64
+ // Path-like: resolve against cwd and return (whether or not it exists — caller will fail later)
65
+ const resolved = resolve(cwd, arg);
66
+ if (existsSync(resolved)) {
67
+ return resolved;
68
+ }
69
+ // Path-like but doesn't exist: throw a specific error (not a bare-name resolution)
70
+ throw new Error(`Plugin path does not exist: ${resolved}\nPass an explicit path instead.`);
71
+ }
72
+
73
+ // Could be an absolute path (bare absolute paths don't contain separators only on Windows,
74
+ // but on Unix an absolute path starts with '/' which IS a separator, so this handles Unix fine).
75
+ // Also catch the case where arg resolves to an existing directory (absolute path without /... unlikely).
76
+ const asAbsolute = resolve(cwd, arg);
77
+ if (existsSync(asAbsolute) && arg !== asAbsolute) {
78
+ // Only when resolving it to absolute gives something different and it exists
79
+ // (e.g. a single-component name that is a direct cwd subdir — unusual on Unix).
80
+ if (isBundle(asAbsolute)) {
81
+ return asAbsolute;
82
+ }
83
+ // It exists but isn't a bundle — fall through to bare-name search.
84
+ // Do NOT return early here; a non-bundle dir that happens to share the name
85
+ // must not silently short-circuit the resolution chain.
86
+ }
87
+
88
+ // --- Rule 2: bare name → candidate search ---
89
+ const candidates = [];
90
+
91
+ // 2a. <packageRoot>/extensions/<arg>
92
+ const repoCandidate = join(PACKAGE_ROOT, "extensions", arg);
93
+ candidates.push(repoCandidate);
94
+ if (isBundle(repoCandidate)) {
95
+ return repoCandidate;
96
+ }
97
+
98
+ // 2b. <cwd>/extensions/<arg>
99
+ const cwdCandidate = join(cwd, "extensions", arg);
100
+ candidates.push(cwdCandidate);
101
+ if (isBundle(cwdCandidate)) {
102
+ return cwdCandidate;
103
+ }
104
+
105
+ // 2c. <npmGlobalRoot>/@amsterdamdatalabs/enact-extensions/extensions/<arg>
106
+ const npmGlobalRoot = getNpmGlobalRoot();
107
+ if (npmGlobalRoot) {
108
+ const globalCandidate = join(npmGlobalRoot, "@amsterdamdatalabs", "enact-extensions", "extensions", arg);
109
+ candidates.push(globalCandidate);
110
+ if (isBundle(globalCandidate)) {
111
+ return globalCandidate;
112
+ }
113
+ }
114
+
115
+ // --- Rule 3: nothing found ---
116
+ const uniqueCandidates = [...new Set(candidates)];
117
+ const candidateList = uniqueCandidates.map((c) => ` - ${c}`).join("\n");
118
+ throw new Error(
119
+ `Plugin "${arg}" not found. Looked in:\n${candidateList}\nPass an explicit path instead.`,
120
+ );
121
+ }