expandai 0.0.2 → 0.0.4

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 (271) hide show
  1. package/README.md +42 -5
  2. package/build/dts/ApiClient.d.ts +128 -194
  3. package/build/dts/ApiClient.d.ts.map +1 -1
  4. package/build/dts/Auth/Domain.d.ts +95 -0
  5. package/build/dts/Auth/Domain.d.ts.map +1 -0
  6. package/build/dts/Auth/ManualAuthorization.d.ts +107 -0
  7. package/build/dts/Auth/ManualAuthorization.d.ts.map +1 -0
  8. package/build/dts/Auth/PendingInit.d.ts +35 -0
  9. package/build/dts/Auth/PendingInit.d.ts.map +1 -0
  10. package/build/dts/Auth/Pkce.d.ts +10 -0
  11. package/build/dts/Auth/Pkce.d.ts.map +1 -0
  12. package/build/dts/Auth/SetupGrantAuthorization.d.ts +34 -0
  13. package/build/dts/Auth/SetupGrantAuthorization.d.ts.map +1 -0
  14. package/build/dts/Authentication.d.ts +10 -9
  15. package/build/dts/Authentication.d.ts.map +1 -1
  16. package/build/dts/BrowserOAuth.d.ts +7 -7
  17. package/build/dts/BrowserOAuth.d.ts.map +1 -1
  18. package/build/dts/CommandPath.d.ts +2 -0
  19. package/build/dts/CommandPath.d.ts.map +1 -0
  20. package/build/dts/Commands/Fetch.d.ts.map +1 -1
  21. package/build/dts/Commands/Hook.d.ts +5 -5
  22. package/build/dts/Commands/Init.d.ts +55 -0
  23. package/build/dts/Commands/Init.d.ts.map +1 -0
  24. package/build/dts/Commands/Mcp.d.ts +6 -6
  25. package/build/dts/Commands/Search.d.ts.map +1 -1
  26. package/build/dts/Commands/Skill.d.ts +4 -4
  27. package/build/dts/Commands/Status.d.ts +27 -0
  28. package/build/dts/Commands/Status.d.ts.map +1 -0
  29. package/build/dts/Commands/Telemetry.d.ts +7 -0
  30. package/build/dts/Commands/Telemetry.d.ts.map +1 -0
  31. package/build/dts/Commands/Uninstall.d.ts +4 -4
  32. package/build/dts/Config.d.ts +108 -17
  33. package/build/dts/Config.d.ts.map +1 -1
  34. package/build/dts/Hook/SnapshotNote.d.ts +8 -4
  35. package/build/dts/Hook/SnapshotNote.d.ts.map +1 -1
  36. package/build/dts/Init/ClaudeAdapter.d.ts +61 -0
  37. package/build/dts/Init/ClaudeAdapter.d.ts.map +1 -0
  38. package/build/dts/Init/ClaudeProfile.d.ts +77 -0
  39. package/build/dts/Init/ClaudeProfile.d.ts.map +1 -0
  40. package/build/dts/Init/Domain.d.ts +139 -0
  41. package/build/dts/Init/Domain.d.ts.map +1 -0
  42. package/build/dts/Init/HarnessAdapter.d.ts +33 -0
  43. package/build/dts/Init/HarnessAdapter.d.ts.map +1 -0
  44. package/build/dts/Init/HarnessDetector.d.ts +19 -0
  45. package/build/dts/Init/HarnessDetector.d.ts.map +1 -0
  46. package/build/dts/Init/HarnessRegistry.d.ts +50 -0
  47. package/build/dts/Init/HarnessRegistry.d.ts.map +1 -0
  48. package/build/dts/Init/MutationTransaction.d.ts +96 -0
  49. package/build/dts/Init/MutationTransaction.d.ts.map +1 -0
  50. package/build/dts/Init/Service.d.ts +72 -0
  51. package/build/dts/Init/Service.d.ts.map +1 -0
  52. package/build/dts/Init/StandardAdapters.d.ts +42 -0
  53. package/build/dts/Init/StandardAdapters.d.ts.map +1 -0
  54. package/build/dts/Init/Status.d.ts +149 -0
  55. package/build/dts/Init/Status.d.ts.map +1 -0
  56. package/build/dts/Install/Claude/Hook.d.ts +57 -29
  57. package/build/dts/Install/Claude/Hook.d.ts.map +1 -1
  58. package/build/dts/Install/Claude/Mcp.d.ts +21 -18
  59. package/build/dts/Install/Claude/Mcp.d.ts.map +1 -1
  60. package/build/dts/Install/Claude/Skill.d.ts +14 -15
  61. package/build/dts/Install/Claude/Skill.d.ts.map +1 -1
  62. package/build/dts/Install/Codex/Mcp.d.ts +14 -8
  63. package/build/dts/Install/Codex/Mcp.d.ts.map +1 -1
  64. package/build/dts/Install/Codex/Skill.d.ts +4 -2
  65. package/build/dts/Install/Codex/Skill.d.ts.map +1 -1
  66. package/build/dts/Install/Cursor/Mcp.d.ts +13 -13
  67. package/build/dts/Install/Cursor/Mcp.d.ts.map +1 -1
  68. package/build/dts/Install/Cursor/Skill.d.ts +4 -2
  69. package/build/dts/Install/Cursor/Skill.d.ts.map +1 -1
  70. package/build/dts/Install/Domain.d.ts +35 -5
  71. package/build/dts/Install/Domain.d.ts.map +1 -1
  72. package/build/dts/Install/Hook.d.ts +4 -4
  73. package/build/dts/Install/Hook.d.ts.map +1 -1
  74. package/build/dts/Install/Mcp.d.ts +5 -5
  75. package/build/dts/Install/Mcp.d.ts.map +1 -1
  76. package/build/dts/Install/Opencode/Hook.d.ts +4 -3
  77. package/build/dts/Install/Opencode/Hook.d.ts.map +1 -1
  78. package/build/dts/Install/Opencode/Mcp.d.ts +11 -11
  79. package/build/dts/Install/Opencode/Mcp.d.ts.map +1 -1
  80. package/build/dts/Install/Opencode/Skill.d.ts +4 -2
  81. package/build/dts/Install/Opencode/Skill.d.ts.map +1 -1
  82. package/build/dts/Install/Skill.d.ts +3 -3
  83. package/build/dts/Install/Uninstall.d.ts +6 -6
  84. package/build/dts/Install/Uninstall.d.ts.map +1 -1
  85. package/build/dts/MarkdownAnsi.d.ts +29 -0
  86. package/build/dts/MarkdownAnsi.d.ts.map +1 -0
  87. package/build/dts/Mcp/ExpandDocs.d.ts +17 -5
  88. package/build/dts/Mcp/ExpandDocs.d.ts.map +1 -1
  89. package/build/dts/Mcp/Server.d.ts +1 -1
  90. package/build/dts/Mcp/Server.d.ts.map +1 -1
  91. package/build/dts/Mcp/tools/Citations.d.ts +100 -71
  92. package/build/dts/Mcp/tools/Citations.d.ts.map +1 -1
  93. package/build/dts/Mcp/tools/Fetch.d.ts +16 -15
  94. package/build/dts/Mcp/tools/Fetch.d.ts.map +1 -1
  95. package/build/dts/SecureFile.d.ts +23 -0
  96. package/build/dts/SecureFile.d.ts.map +1 -0
  97. package/build/dts/Snippets.d.ts +11 -12
  98. package/build/dts/Snippets.d.ts.map +1 -1
  99. package/build/dts/Telemetry.d.ts +30 -0
  100. package/build/dts/Telemetry.d.ts.map +1 -0
  101. package/build/dts/Version.d.ts +2 -0
  102. package/build/dts/Version.d.ts.map +1 -0
  103. package/build/dts/Xdg.d.ts +11 -0
  104. package/build/dts/Xdg.d.ts.map +1 -0
  105. package/build/esm/ApiClient.js +133 -10
  106. package/build/esm/ApiClient.js.map +1 -1
  107. package/build/esm/Auth/Domain.js +56 -0
  108. package/build/esm/Auth/Domain.js.map +1 -0
  109. package/build/esm/Auth/ManualAuthorization.js +253 -0
  110. package/build/esm/Auth/ManualAuthorization.js.map +1 -0
  111. package/build/esm/Auth/PendingInit.js +267 -0
  112. package/build/esm/Auth/PendingInit.js.map +1 -0
  113. package/build/esm/Auth/Pkce.js +23 -0
  114. package/build/esm/Auth/Pkce.js.map +1 -0
  115. package/build/esm/Auth/SetupGrantAuthorization.js +91 -0
  116. package/build/esm/Auth/SetupGrantAuthorization.js.map +1 -0
  117. package/build/esm/Authentication.js +38 -7
  118. package/build/esm/Authentication.js.map +1 -1
  119. package/build/esm/BrowserOAuth.js +7 -10
  120. package/build/esm/BrowserOAuth.js.map +1 -1
  121. package/build/esm/CommandPath.js +31 -0
  122. package/build/esm/CommandPath.js.map +1 -0
  123. package/build/esm/Commands/Fetch.js +30 -6
  124. package/build/esm/Commands/Fetch.js.map +1 -1
  125. package/build/esm/Commands/Init.js +371 -0
  126. package/build/esm/Commands/Init.js.map +1 -0
  127. package/build/esm/Commands/Search.js +6 -3
  128. package/build/esm/Commands/Search.js.map +1 -1
  129. package/build/esm/Commands/Status.js +186 -0
  130. package/build/esm/Commands/Status.js.map +1 -0
  131. package/build/esm/Commands/Telemetry.js +23 -0
  132. package/build/esm/Commands/Telemetry.js.map +1 -0
  133. package/build/esm/Config.js +502 -28
  134. package/build/esm/Config.js.map +1 -1
  135. package/build/esm/Hook/Claude.js +1 -1
  136. package/build/esm/Hook/Claude.js.map +1 -1
  137. package/build/esm/Hook/SnapshotNote.js +54 -7
  138. package/build/esm/Hook/SnapshotNote.js.map +1 -1
  139. package/build/esm/Init/ClaudeAdapter.js +137 -0
  140. package/build/esm/Init/ClaudeAdapter.js.map +1 -0
  141. package/build/esm/Init/ClaudeProfile.js +344 -0
  142. package/build/esm/Init/ClaudeProfile.js.map +1 -0
  143. package/build/esm/Init/Domain.js +118 -0
  144. package/build/esm/Init/Domain.js.map +1 -0
  145. package/build/esm/Init/HarnessAdapter.js +21 -0
  146. package/build/esm/Init/HarnessAdapter.js.map +1 -0
  147. package/build/esm/Init/HarnessDetector.js +65 -0
  148. package/build/esm/Init/HarnessDetector.js.map +1 -0
  149. package/build/esm/Init/HarnessRegistry.js +58 -0
  150. package/build/esm/Init/HarnessRegistry.js.map +1 -0
  151. package/build/esm/Init/MutationTransaction.js +494 -0
  152. package/build/esm/Init/MutationTransaction.js.map +1 -0
  153. package/build/esm/Init/Service.js +319 -0
  154. package/build/esm/Init/Service.js.map +1 -0
  155. package/build/esm/Init/StandardAdapters.js +116 -0
  156. package/build/esm/Init/StandardAdapters.js.map +1 -0
  157. package/build/esm/Init/Status.js +215 -0
  158. package/build/esm/Init/Status.js.map +1 -0
  159. package/build/esm/Install/Claude/Hook.js +52 -64
  160. package/build/esm/Install/Claude/Hook.js.map +1 -1
  161. package/build/esm/Install/Claude/Mcp.js +56 -26
  162. package/build/esm/Install/Claude/Mcp.js.map +1 -1
  163. package/build/esm/Install/Claude/Skill.js +49 -27
  164. package/build/esm/Install/Claude/Skill.js.map +1 -1
  165. package/build/esm/Install/Codex/Mcp.js +9 -4
  166. package/build/esm/Install/Codex/Mcp.js.map +1 -1
  167. package/build/esm/Install/Codex/Skill.js +4 -1
  168. package/build/esm/Install/Codex/Skill.js.map +1 -1
  169. package/build/esm/Install/Cursor/Mcp.js +7 -4
  170. package/build/esm/Install/Cursor/Mcp.js.map +1 -1
  171. package/build/esm/Install/Cursor/Skill.js +4 -1
  172. package/build/esm/Install/Cursor/Skill.js.map +1 -1
  173. package/build/esm/Install/Domain.js +60 -3
  174. package/build/esm/Install/Domain.js.map +1 -1
  175. package/build/esm/Install/Hook.js.map +1 -1
  176. package/build/esm/Install/Mcp.js +1 -1
  177. package/build/esm/Install/Mcp.js.map +1 -1
  178. package/build/esm/Install/Opencode/Hook.js +1 -1
  179. package/build/esm/Install/Opencode/Hook.js.map +1 -1
  180. package/build/esm/Install/Opencode/Mcp.js +6 -3
  181. package/build/esm/Install/Opencode/Mcp.js.map +1 -1
  182. package/build/esm/Install/Opencode/Skill.js +4 -1
  183. package/build/esm/Install/Opencode/Skill.js.map +1 -1
  184. package/build/esm/Install/Opencode/expandai-webfetch.js +27 -9
  185. package/build/esm/Install/Skill.js.map +1 -1
  186. package/build/esm/Install/Uninstall.js +2 -2
  187. package/build/esm/Install/Uninstall.js.map +1 -1
  188. package/build/esm/MarkdownAnsi.js +142 -0
  189. package/build/esm/MarkdownAnsi.js.map +1 -0
  190. package/build/esm/Mcp/ExpandDocs.js +44 -52
  191. package/build/esm/Mcp/ExpandDocs.js.map +1 -1
  192. package/build/esm/Mcp/RequestHeaders.js.map +1 -1
  193. package/build/esm/Mcp/Server.js +2 -1
  194. package/build/esm/Mcp/Server.js.map +1 -1
  195. package/build/esm/Mcp/tools/Citations.js +109 -8
  196. package/build/esm/Mcp/tools/Citations.js.map +1 -1
  197. package/build/esm/Mcp/tools/Fetch.js +36 -12
  198. package/build/esm/Mcp/tools/Fetch.js.map +1 -1
  199. package/build/esm/SecureFile.js +36 -0
  200. package/build/esm/SecureFile.js.map +1 -0
  201. package/build/esm/Snippets.js +12 -1
  202. package/build/esm/Snippets.js.map +1 -1
  203. package/build/esm/Telemetry.js +157 -0
  204. package/build/esm/Telemetry.js.map +1 -0
  205. package/build/esm/Version.js +4 -0
  206. package/build/esm/Version.js.map +1 -0
  207. package/build/esm/Xdg.js +15 -0
  208. package/build/esm/Xdg.js.map +1 -0
  209. package/build/esm/main.js +122 -14
  210. package/build/esm/main.js.map +1 -1
  211. package/build/skills/expandai/SKILL.md +37 -8
  212. package/package.json +4 -4
  213. package/src/ApiClient.ts +206 -13
  214. package/src/Auth/Domain.ts +63 -0
  215. package/src/Auth/ManualAuthorization.ts +296 -0
  216. package/src/Auth/PendingInit.ts +338 -0
  217. package/src/Auth/Pkce.ts +26 -0
  218. package/src/Auth/SetupGrantAuthorization.ts +116 -0
  219. package/src/Authentication.ts +49 -11
  220. package/src/BrowserOAuth.ts +10 -12
  221. package/src/CommandPath.ts +30 -0
  222. package/src/Commands/Fetch.ts +42 -11
  223. package/src/Commands/Init.ts +489 -0
  224. package/src/Commands/Search.ts +8 -3
  225. package/src/Commands/Status.ts +228 -0
  226. package/src/Commands/Telemetry.ts +42 -0
  227. package/src/Config.ts +695 -50
  228. package/src/Hook/Claude.ts +1 -1
  229. package/src/Hook/SnapshotNote.ts +55 -7
  230. package/src/Init/ClaudeAdapter.ts +191 -0
  231. package/src/Init/ClaudeProfile.ts +453 -0
  232. package/src/Init/Domain.ts +153 -0
  233. package/src/Init/HarnessAdapter.ts +35 -0
  234. package/src/Init/HarnessDetector.ts +77 -0
  235. package/src/Init/HarnessRegistry.ts +87 -0
  236. package/src/Init/MutationTransaction.ts +747 -0
  237. package/src/Init/Service.ts +385 -0
  238. package/src/Init/StandardAdapters.ts +141 -0
  239. package/src/Init/Status.ts +253 -0
  240. package/src/Install/Claude/Hook.ts +81 -91
  241. package/src/Install/Claude/Mcp.ts +80 -33
  242. package/src/Install/Claude/Skill.ts +70 -37
  243. package/src/Install/Codex/Mcp.ts +14 -9
  244. package/src/Install/Codex/Skill.ts +9 -5
  245. package/src/Install/Cursor/Mcp.ts +12 -9
  246. package/src/Install/Cursor/Skill.ts +9 -5
  247. package/src/Install/Domain.ts +77 -4
  248. package/src/Install/Hook.ts +3 -3
  249. package/src/Install/Mcp.ts +6 -6
  250. package/src/Install/Opencode/Hook.ts +5 -5
  251. package/src/Install/Opencode/Mcp.ts +11 -8
  252. package/src/Install/Opencode/Skill.ts +9 -5
  253. package/src/Install/Opencode/expandai-webfetch.js +27 -9
  254. package/src/Install/Skill.ts +3 -3
  255. package/src/Install/Uninstall.ts +3 -3
  256. package/src/MarkdownAnsi.ts +162 -0
  257. package/src/Mcp/ExpandDocs.ts +46 -55
  258. package/src/Mcp/Server.ts +2 -1
  259. package/src/Mcp/tools/Citations.ts +117 -8
  260. package/src/Mcp/tools/Fetch.ts +40 -14
  261. package/src/SecureFile.ts +70 -0
  262. package/src/Snippets.ts +19 -12
  263. package/src/Telemetry.ts +186 -0
  264. package/src/Version.ts +3 -0
  265. package/src/Xdg.ts +18 -0
  266. package/src/main.ts +171 -12
  267. package/build/dts/DeviceAuth.d.ts +0 -87
  268. package/build/dts/DeviceAuth.d.ts.map +0 -1
  269. package/build/esm/DeviceAuth.js +0 -127
  270. package/build/esm/DeviceAuth.js.map +0 -1
  271. package/src/DeviceAuth.ts +0 -193
@@ -68,7 +68,7 @@ export class ClaudeHook extends Effect.Service<ClaudeHook>()('@expandai/cli/Hook
68
68
  hookSpecificOutput: {
69
69
  hookEventName: 'PreToolUse',
70
70
  permissionDecision: 'deny',
71
- permissionDecisionReason: `Fetched via expand.ai — page markdown injected as additionalContext.`,
71
+ permissionDecisionReason: `Fetched by expand.ai instead — the captured page follows, with evidence ids for citing it. Do not retry this URL.`,
72
72
  additionalContext: withSnapshotNote(result.markdown, result.meta.snapshotId, result.meta.playground),
73
73
  },
74
74
  }
@@ -1,18 +1,66 @@
1
1
  import { String } from 'effect'
2
2
 
3
3
  /**
4
- * Prepends a short note to fetched markdown carrying the snapshot handle and the public
5
- * playground link, and telling the agent how to search the already-captured page by handle
6
- * (rather than fetching the URL again) and cite specific passages back to the user. Both the
7
- * Claude Code and OpenCode hooks inject this so the surfaced guidance stays identical.
4
+ * Prepends a short note to fetched markdown carrying the snapshot handle, the public
5
+ * playground link, and the citation contract. Both the Claude Code and OpenCode hooks
6
+ * inject this so the surfaced guidance stays identical.
7
+ *
8
+ * The citation rules are stated here rather than only in the onboarding guide or
9
+ * `SKILL.md`: the guide is read once during setup, and a skill may not be loaded in the
10
+ * session that happens to fetch. This note is the only instruction guaranteed to be in
11
+ * context at the moment the page arrives, so an answer that cites nothing — or cites the
12
+ * bare capture URL, which is not auditable — traces back to what is written here.
8
13
  */
14
+ const FRONT_MATTER_PLAYGROUND = /^playground:\s*"?([^"\n]+)"?\s*$/mu
15
+
16
+ /**
17
+ * Citation guidance for `expandai fetch` text output consumed by an agent. The plain
18
+ * command is a third fetch path alongside the hook and the MCP tools — an agent that
19
+ * shells out to it used to receive evidence ids with nothing telling it to cite them,
20
+ * and answered uncited. Humans at a TTY do not need this, so callers gate on that.
21
+ *
22
+ * Returns the markdown unchanged when no playground URL is present, rather than
23
+ * emitting an instruction the reader cannot follow.
24
+ */
25
+ export function withCitationFooter(markdown: string): string {
26
+ const playgroundUrl = FRONT_MATTER_PLAYGROUND.exec(markdown)?.[1]?.trim()
27
+ if (playgroundUrl === undefined || playgroundUrl.length === 0) return markdown
28
+ const footer = String.stripMargin(`
29
+ |---
30
+ |
31
+ |> Cite what you take from this page. Evidence ids are stamped next to the text they
32
+ |> belong to, written as \`{1199}\`. End your answer with:
33
+ |>
34
+ |> **Sources**
35
+ |> [1] [<page title>](${playgroundUrl}?id=<evidence id>)
36
+ |>
37
+ |> One marker per sentence, numbered in order. Never invent an evidence id, and never
38
+ |> reuse an id from different text. A link without \`?id=\` is not a citation, so if the
39
+ |> sentence you want carries no id, quote one that does or say the capture has none —
40
+ |> never fall back to linking the original page.`)
41
+ return `${markdown.trimEnd()}\n\n${footer}\n`
42
+ }
43
+
9
44
  export function withSnapshotNote(markdown: string, snapshotId: string, playgroundUrl: string): string {
10
45
  const note = String.stripMargin(`
11
- |> Fetched via expand.ai (snapshot \`${snapshotId}\`). View or share this capture at
12
- |> ${playgroundUrl}
46
+ |> Fetched via expand.ai (snapshot \`${snapshotId}\`). Capture: ${playgroundUrl}
47
+ |>
48
+ |> Cite every claim you take from this page. The body below stamps an evidence id next
49
+ |> to the text it belongs to, written as \`{1199}\`. For each sentence you use, take the
50
+ |> id nearest that text and end your answer with:
51
+ |>
52
+ |> **Sources**
53
+ |> [1] [<page title>](${playgroundUrl}?id=<evidence id>)
54
+ |>
55
+ |> Number the markers \`[1]\`, \`[2]\`, … in the order they appear, one per sentence.
56
+ |> Never invent an evidence id, and never reuse an id from different text.
57
+ |> A bare capture link without \`?id=\` is not a citation — the reader cannot
58
+ |> check the sentence against the page. If the sentence you want carries no id, quote
59
+ |> one that does, or say the capture has none. Never fall back to the original page URL.
60
+ |>
13
61
  |> To extract specific passages, search the snapshot by handle — call the \`fetch_search\`
14
62
  |> tool with \`snapshotId: "${snapshotId}"\`, or run \`expandai search ${snapshotId} "<query>"\`.
15
- |> Each snippet returns a \`citationUrl\` you can hand the user to audit the exact source.
63
+ |> Each snippet returns a \`citationUrl\` that already carries the right \`?id=\`.
16
64
  |> Do not fetch this URL again.`)
17
65
  return `${note}\n\n---\n\n${markdown}`
18
66
  }
@@ -0,0 +1,191 @@
1
+ import { Command, CommandExecutor } from '@effect/platform'
2
+ import { NodeCommandExecutor } from '@effect/platform-node'
3
+ import { Config, Effect, Layer, Schema, Stream } from 'effect'
4
+ import { CLI_VERSION } from '../Version.js'
5
+ import { CLAUDE_PROFILE_VERSION, ClaudeProfile, type ClaudeProfileInspection } from './ClaudeProfile.js'
6
+ import type { InitScope } from './Domain.js'
7
+ import { HarnessConfigurationError, type HarnessInspection, HarnessInspectionError } from './HarnessAdapter.js'
8
+ import { MutationTransaction } from './MutationTransaction.js'
9
+
10
+ export class IncompatibleClaudeProfileVersionError extends Schema.TaggedError<IncompatibleClaudeProfileVersionError>()(
11
+ 'IncompatibleClaudeProfileVersionError',
12
+ {
13
+ executingVersion: Schema.String,
14
+ requiredVersion: Schema.String,
15
+ },
16
+ ) {
17
+ get message() {
18
+ return `Executing CLI ${this.executingVersion} is incompatible with Claude profile ${this.requiredVersion}`
19
+ }
20
+ }
21
+
22
+ export class ClaudeProfilePolicyBlockedError extends Schema.TaggedError<ClaudeProfilePolicyBlockedError>()(
23
+ 'ClaudeProfilePolicyBlockedError',
24
+ {
25
+ policyBlock: Schema.String,
26
+ },
27
+ ) {
28
+ get message() {
29
+ return `Claude Code policy blocks profile configuration: ${this.policyBlock}`
30
+ }
31
+ }
32
+
33
+ export class ClaudeAdapterOperationError extends Schema.TaggedError<ClaudeAdapterOperationError>()(
34
+ 'ClaudeAdapterOperationError',
35
+ {
36
+ code: Schema.Literal('inspection_failed', 'configuration_failed'),
37
+ },
38
+ ) {
39
+ get message() {
40
+ return this.code === 'inspection_failed'
41
+ ? 'Claude Code profile inspection failed'
42
+ : 'Claude Code profile configuration failed'
43
+ }
44
+ }
45
+
46
+ const PORTABLE_PROBE_TIMEOUT = '5 seconds'
47
+
48
+ interface ClaudeRuntimeProbeContract {
49
+ readonly executingVersion: string
50
+ readonly portableCommandAvailable: Effect.Effect<boolean, unknown>
51
+ }
52
+
53
+ const makeClaudeRuntimeProbe = Effect.gen(function* () {
54
+ const commandExecutor = yield* CommandExecutor.CommandExecutor
55
+ const searchPath = yield* Config.string('PATH').pipe(Config.withDefault(''))
56
+ const portableCommandAvailable = Effect.scoped(
57
+ Effect.gen(function* () {
58
+ const command = Command.make('expandai', '--version').pipe(
59
+ // Version probing must not persist telemetry state or emit a request in
60
+ // the child. Keep the remaining environment intact while making PATH
61
+ // injectable for fixture verification.
62
+ Command.env({ ...process.env, EXPANDAI_NO_TELEMETRY: '1', PATH: searchPath }),
63
+ Command.stdout('pipe'),
64
+ Command.stderr('pipe'),
65
+ )
66
+ const child = yield* commandExecutor.start(command)
67
+ const [stdout, , exitCode] = yield* Effect.all(
68
+ [child.stdout.pipe(Stream.decodeText(), Stream.mkString), child.stderr.pipe(Stream.runDrain), child.exitCode],
69
+ { concurrency: 'unbounded' },
70
+ )
71
+ return exitCode === 0 && stdout.trim() === CLI_VERSION
72
+ }),
73
+ ).pipe(
74
+ // A shim on PATH that never exits would otherwise hang setup forever. This
75
+ // probe only decides a "restart your harness" hint, so a slow answer is
76
+ // worth no more than a missing one. `Effect.scoped` above reaps the child.
77
+ Effect.timeoutFail({
78
+ duration: PORTABLE_PROBE_TIMEOUT,
79
+ onTimeout: () => new Error('expandai --version timed out'),
80
+ }),
81
+ Effect.orElseSucceed(() => false),
82
+ )
83
+ return { executingVersion: CLI_VERSION, portableCommandAvailable } satisfies ClaudeRuntimeProbeContract
84
+ })
85
+
86
+ /** Runtime facts kept separate from persisted harness commands. */
87
+ export class ClaudeRuntimeProbe extends Effect.Service<ClaudeRuntimeProbe>()('@expandai/cli/Init/ClaudeRuntimeProbe', {
88
+ dependencies: [NodeCommandExecutor.layer],
89
+ effect: makeClaudeRuntimeProbe,
90
+ }) {
91
+ /**
92
+ * Not `DefaultWithoutDependencies`: that layer always reports `CLI_VERSION` and
93
+ * always runs the real command probe. Tests need to pin an arbitrary executing
94
+ * version and a precomputed probe result, which only a fixture layer can supply.
95
+ */
96
+ static testLayer(options: { readonly executingVersion: string; readonly portableCommandAvailable: boolean }) {
97
+ return Layer.succeed(
98
+ ClaudeRuntimeProbe,
99
+ ClaudeRuntimeProbe.make({
100
+ executingVersion: options.executingVersion,
101
+ portableCommandAvailable: Effect.succeed(options.portableCommandAvailable),
102
+ }),
103
+ )
104
+ }
105
+ }
106
+
107
+ function projectInspection(
108
+ inspection: ClaudeProfileInspection,
109
+ portableCommandAvailable: boolean,
110
+ changed: boolean,
111
+ ): HarnessInspection {
112
+ const policyBlock = inspection.policyBlocks[0]
113
+ const blocked = policyBlock !== undefined
114
+ const reloadRequired = inspection.configured && !portableCommandAvailable
115
+ return {
116
+ configured: inspection.configured,
117
+ changed,
118
+ components: inspection.components,
119
+ integrationVersion: inspection.profileVersion,
120
+ ...(policyBlock === undefined ? {} : { policyBlock }),
121
+ runtimeState: blocked ? 'blocked' : reloadRequired ? 'reload_required' : 'unverified',
122
+ nextAction: blocked
123
+ ? 'resolve-claude-policy-block'
124
+ : reloadRequired
125
+ ? 'restart-claude-code-current-path-unavailable'
126
+ : inspection.configured
127
+ ? 'invoke-native-webfetch'
128
+ : 'configure-claude-code',
129
+ }
130
+ }
131
+
132
+ const makeClaudeAdapter = Effect.gen(function* () {
133
+ const profile = yield* ClaudeProfile
134
+ const transaction = yield* MutationTransaction
135
+ const runtime = yield* ClaudeRuntimeProbe
136
+
137
+ const inspect = Effect.fn('ClaudeAdapter.inspect')(function* (scope: InitScope) {
138
+ return yield* Effect.gen(function* () {
139
+ const inspection = yield* profile.inspect(scope)
140
+ const portableCommandAvailable = yield* runtime.portableCommandAvailable
141
+ return projectInspection(inspection, portableCommandAvailable, false)
142
+ }).pipe(
143
+ Effect.catchAllDefect(() => new ClaudeAdapterOperationError({ code: 'inspection_failed' })),
144
+ Effect.mapError(
145
+ () =>
146
+ new HarnessInspectionError({
147
+ harness: 'claude-code',
148
+ scope,
149
+ error: new ClaudeAdapterOperationError({ code: 'inspection_failed' }),
150
+ }),
151
+ ),
152
+ )
153
+ })
154
+
155
+ const configure = Effect.fn('ClaudeAdapter.configure')(function* (scope: InitScope) {
156
+ return yield* Effect.gen(function* () {
157
+ if (runtime.executingVersion !== CLAUDE_PROFILE_VERSION) {
158
+ return yield* new IncompatibleClaudeProfileVersionError({
159
+ executingVersion: runtime.executingVersion,
160
+ requiredVersion: CLAUDE_PROFILE_VERSION,
161
+ })
162
+ }
163
+ const portableCommandAvailable = yield* runtime.portableCommandAvailable
164
+ const plan = yield* profile.plan(scope)
165
+ const policyBlock = plan.inspection.policyBlocks[0]
166
+ if (policyBlock !== undefined) return yield* new ClaudeProfilePolicyBlockedError({ policyBlock })
167
+ yield* transaction.run(plan.operations)
168
+ return projectInspection(plan.resultInspection, portableCommandAvailable, plan.changed)
169
+ }).pipe(
170
+ Effect.catchAllDefect(() => new ClaudeAdapterOperationError({ code: 'configuration_failed' })),
171
+ Effect.mapError(
172
+ (error) =>
173
+ new HarnessConfigurationError({
174
+ harness: 'claude-code',
175
+ scope,
176
+ error:
177
+ error instanceof IncompatibleClaudeProfileVersionError || error instanceof ClaudeProfilePolicyBlockedError
178
+ ? error
179
+ : new ClaudeAdapterOperationError({ code: 'configuration_failed' }),
180
+ }),
181
+ ),
182
+ )
183
+ })
184
+
185
+ return { harness: 'claude-code' as const, inspect, configure } as const
186
+ })
187
+
188
+ export class ClaudeAdapter extends Effect.Service<ClaudeAdapter>()('@expandai/cli/Init/ClaudeAdapter', {
189
+ dependencies: [ClaudeProfile.Default, MutationTransaction.Default, ClaudeRuntimeProbe.Default],
190
+ effect: makeClaudeAdapter,
191
+ }) {}