expandai 0.0.2 → 0.0.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 (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 +78 -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 +85 -25
  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 +380 -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 +182 -50
  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 +26 -3
  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 +496 -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 +234 -81
  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
@@ -5,6 +5,7 @@
5
5
  import * as Generated from '@expandai/sdk/Generated';
6
6
  import { citationUrl, playgroundBase, resolvePlaygroundHost } from '@expandai/sdk/Playground';
7
7
  import { Schema as S } from 'effect';
8
+ export const FetchInvocationPath = S.Literal('cli-recovery', 'mcp-fetch-search');
8
9
  export class CitedSnippet extends S.Class('CitedSnippet')({
9
10
  type: S.optionalWith(S.Literal('text'), { nullable: true, default: () => 'text' }),
10
11
  source: S.Literal('markdown', 'appendix', 'statejson'),
@@ -17,6 +18,85 @@ export class CitedSnippet extends S.Class('CitedSnippet')({
17
18
  }),
18
19
  }) {
19
20
  }
21
+ /**
22
+ * Carried in the tool result itself, not only in `SKILL.md`. A harness may not load
23
+ * skills at all, and the hook's snapshot note never reaches this path — so without
24
+ * this the model receives perfect `citationUrl`s and no instruction to use them, and
25
+ * answers with no citation. Observed on OpenCode before this was added.
26
+ */
27
+ export const CITATION_INSTRUCTIONS = 'Cite every claim you take from this result. Append the ready-made `citationBlock` from this ' +
28
+ 'result to the end of your answer. Delete every entry whose marker you did not actually place ' +
29
+ 'in your text, and renumber what remains from [1] — an entry with no matching marker is not a ' +
30
+ 'citation, it is clutter. Do not ' +
31
+ 'retype the URLs, do not substitute the page you fetched, and do not build a link yourself — ' +
32
+ "the citation points at expand.ai's stored capture, not at the original site. Never invent an " +
33
+ 'evidence id and never reuse one from different text. A link without `?id=` is not a citation.';
34
+ const CitationInstructionsField = S.optionalWith(S.String, { default: () => CITATION_INSTRUCTIONS }).annotations({
35
+ description: 'How to cite this result back to the user. Follow it exactly.',
36
+ });
37
+ const CitationBlockField = S.optionalWith(S.String, { default: () => '' }).annotations({
38
+ description: 'Ready-to-append citation block, one entry per citable snippet. Copy it to the end of your ' +
39
+ 'answer, keeping only the entries whose markers you placed in your text and renumbering ' +
40
+ 'from [1]. You may also correct the page title.',
41
+ });
42
+ /**
43
+ * Prebuilds the citation markdown instead of asking the model to assemble it.
44
+ * Selecting `citationUrl` out of a JSON result and pasting it correctly is the step
45
+ * weaker models get wrong — observed repeatedly on OpenCode, which substituted the
46
+ * fetched page URL for the capture link. Copying one block has no such failure mode.
47
+ */
48
+ const MAX_CITATION_MARKERS = 3;
49
+ const EVIDENCE_MARKER = /\{(\d{1,6})\}/gu;
50
+ /**
51
+ * Citation URLs for a plain fetch with no search. The markdown still stamps evidence
52
+ * ids, so a citation is always possible — without this a `fetch` called without a
53
+ * query produced an empty block and the model answered uncited.
54
+ */
55
+ function markdownCitationUrls(markdown, snapshotId, host) {
56
+ const urls = [];
57
+ for (const match of markdown.matchAll(EVIDENCE_MARKER)) {
58
+ const id = Number(match[1]);
59
+ if (!Number.isFinite(id))
60
+ continue;
61
+ const url = citationUrl(snapshotId, { evidenceId: id }, host);
62
+ if (!urls.includes(url))
63
+ urls.push(url);
64
+ if (urls.length >= MAX_CITATION_MARKERS)
65
+ break;
66
+ }
67
+ return urls;
68
+ }
69
+ /**
70
+ * `statejson` snippets are extracted page state — arrays of routes, config blobs — not
71
+ * prose anyone can quote. Offering one as a citable source produced a `Sources` entry
72
+ * pointing at a list of URLs, so only quotable text earns a citation.
73
+ */
74
+ function isQuotable(snippet) {
75
+ return snippet.source !== 'statejson';
76
+ }
77
+ function citationBlock(sourceLabel, snippets, fallback = []) {
78
+ const urls = [];
79
+ for (const snippet of snippets) {
80
+ if (!isQuotable(snippet))
81
+ continue;
82
+ if (snippet.citationUrl.length > 0 && !urls.includes(snippet.citationUrl))
83
+ urls.push(snippet.citationUrl);
84
+ }
85
+ if (urls.length === 0)
86
+ urls.push(...fallback);
87
+ if (urls.length === 0)
88
+ return '';
89
+ // One linked source line per marker.
90
+ // Grouping all the sources first and all the links after loses every link when a
91
+ // model copies only the opening lines — observed on OpenCode.
92
+ const lines = ['**Sources**'];
93
+ urls.slice(0, MAX_CITATION_MARKERS).forEach((url, index) => {
94
+ if (index > 0)
95
+ lines.push('');
96
+ lines.push(`[${index + 1}] [${sourceLabel}](${url})`);
97
+ });
98
+ return lines.join('\n');
99
+ }
20
100
  export class CitedSearchResult extends S.Class('CitedSearchResult')({
21
101
  query: S.String,
22
102
  snippets: S.Array(CitedSnippet),
@@ -24,17 +104,22 @@ export class CitedSearchResult extends S.Class('CitedSearchResult')({
24
104
  }) {
25
105
  }
26
106
  export class CitedObjectModeResult extends S.Class('CitedObjectModeResult')({
107
+ invocationPath: S.Literal('mcp-fetch-search'),
108
+ citationInstructions: CitationInstructionsField,
109
+ citationBlock: CitationBlockField,
27
110
  meta: Generated.FetchObjectModeMeta,
28
111
  markdown: S.String,
29
112
  json: S.Array(Generated.FetchObjectModeEvidence),
30
113
  data: S.optionalWith(S.Struct({
31
114
  assets: S.optionalWith(S.Array(Generated.FetchAssetReference), { nullable: true }),
32
- captureTelemetry: S.optionalWith(Generated.CaptureTelemetryInfo, { nullable: true }),
33
115
  search: S.optionalWith(CitedSearchResult, { nullable: true }),
34
116
  }), { nullable: true }),
35
117
  }) {
36
118
  }
37
119
  export class CitedSnapshotSearchResult extends S.Class('CitedSnapshotSearchResult')({
120
+ invocationPath: FetchInvocationPath,
121
+ citationInstructions: CitationInstructionsField,
122
+ citationBlock: CitationBlockField,
38
123
  snapshotId: S.String.pipe(S.minLength(1)),
39
124
  response: Generated.ResponseInfo,
40
125
  search: CitedSearchResult,
@@ -50,7 +135,7 @@ function toCitedSnippet(snapshotId, host, snippet) {
50
135
  json: snippet.json,
51
136
  score: snippet.score,
52
137
  location: snippet.location,
53
- citationUrl: citationUrl(snapshotId, snippet.source, snippet.location, host),
138
+ citationUrl: citationUrl(snapshotId, snippet.location, host),
54
139
  });
55
140
  }
56
141
  function toCitedSearch(snapshotId, host, search) {
@@ -66,25 +151,41 @@ export function enrichObjectModeResult(result, fallbackHost) {
66
151
  // it is somehow not a parseable URL.
67
152
  const host = resolvePlaygroundHost(result.meta.playground, fallbackHost);
68
153
  const data = result.data;
154
+ const citedSearch = data?.search == null ? undefined : toCitedSearch(result.meta.snapshotId, host, data.search);
155
+ const block = citationBlock(result.meta.url, citedSearch?.snippets ?? [], markdownCitationUrls(result.markdown, result.meta.snapshotId, host));
69
156
  return new CitedObjectModeResult({
157
+ invocationPath: 'mcp-fetch-search',
158
+ citationInstructions: CITATION_INSTRUCTIONS,
159
+ citationBlock: block,
70
160
  meta: result.meta,
71
- markdown: result.markdown,
161
+ // The block is appended to the content, not just offered as a sibling field.
162
+ // A model summarising `markdown` carries an inline footer through; a separate
163
+ // JSON field it has to notice and copy gets dropped — repeatedly, on OpenCode.
164
+ markdown: block.length === 0 ? result.markdown : `${result.markdown.trimEnd()}\n\n---\n\n${block}\n`,
72
165
  json: result.json,
73
166
  data: data == null
74
167
  ? data
75
168
  : {
76
169
  assets: data.assets,
77
- captureTelemetry: data.captureTelemetry,
78
- search: data.search == null ? data.search : toCitedSearch(result.meta.snapshotId, host, data.search),
170
+ search: citedSearch,
79
171
  },
80
172
  });
81
173
  }
82
- /** Add `citationUrl` to each snippet of a snapshot-search result (no server `meta.playground`). */
83
- export function enrichSnapshotSearchResult(result, host) {
174
+ /** Add `citationUrl` to each snippet of a snapshot-search result. */
175
+ export function enrichSnapshotSearchResult(result, fallbackHost, invocationPath = 'mcp-fetch-search') {
176
+ // `playground` is server-resolved (staging-safe), exactly like `meta.playground`
177
+ // above; fall back to the configured host only if it is somehow not a parseable
178
+ // URL. The playground is a separate deployment from the API, so the server is
179
+ // the only component that can say where its snapshots are publicly served.
180
+ const host = resolvePlaygroundHost(result.playground, fallbackHost);
181
+ const citedSearch = toCitedSearch(result.snapshotId, host, result.search);
84
182
  return new CitedSnapshotSearchResult({
183
+ invocationPath,
184
+ citationInstructions: CITATION_INSTRUCTIONS,
185
+ citationBlock: citationBlock(result.response.url, citedSearch.snippets),
85
186
  snapshotId: result.snapshotId,
86
187
  response: result.response,
87
- search: toCitedSearch(result.snapshotId, host, result.search),
188
+ search: citedSearch,
88
189
  durationMs: result.durationMs,
89
190
  playground: playgroundBase(result.snapshotId, host),
90
191
  });
@@ -1 +1 @@
1
- {"version":3,"file":"Citations.js","sourceRoot":"","sources":["../../../../src/Mcp/tools/Citations.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Citations.js","sourceRoot":"","sources":["../../../../src/Mcp/tools/Citations.ts"],"names":[],"mappings":"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"}
@@ -2,6 +2,7 @@ import { Tool } from '@effect/ai';
2
2
  import { Effect, Schema, String } from 'effect';
3
3
  import { ApiClient } from '../../ApiClient.js';
4
4
  import { ConfigManager } from '../../Config.js';
5
+ import { Telemetry } from '../../Telemetry.js';
5
6
  import { CitedObjectModeResult, CitedSnapshotSearchResult, enrichObjectModeResult, enrichSnapshotSearchResult, } from './Citations.js';
6
7
  export class FetchSearchParams extends Schema.Class('FetchSearchParams')({
7
8
  query: Schema.NonEmptyTrimmedString.annotations({
@@ -63,11 +64,15 @@ export const FetchTool = Tool.make('fetch', {
63
64
  description: String.stripMargin(`
64
65
  |Fetch and extract content from any URL.
65
66
  |
67
+ |You MUST cite what you take from the result. The result carries a ready-made
68
+ |'citationBlock' — append it verbatim to the end of your answer. Do not retype it, do not
69
+ |substitute the URL you fetched, and do not build a link yourself: the citation points at
70
+ |expand.ai's stored capture, not at the original site. Drop only the markers you did not use.
71
+ |A 'Verify it' link without '?id=' is not a citation. Never invent or reuse an evidence id.
72
+ |
66
73
  |Returns object-mode output with markdown, front-matter metadata, and optional extracted JSON.
67
74
  |When 'search' is set, the response also carries ranked snippets under data.search.snippets.
68
- |Each snippet has a 'citationUrl' — a public playground link auditing that exact passage — and,
69
- |for State JSON sources, the extracted 'json' value. Return the citationUrl to the user so they
70
- |can verify the source.`),
75
+ |For State JSON sources each snippet also carries the extracted 'json' value.`),
71
76
  parameters: FetchParams.fields,
72
77
  success: CitedObjectModeResult,
73
78
  failure: Schema.Defect,
@@ -79,10 +84,15 @@ export const FetchSearchTool = Tool.make('fetch_search', {
79
84
  description: String.stripMargin(`
80
85
  |Search a previous fetch by snapshotId without starting a new capture.
81
86
  |
82
- |Returns ranked snippets from stored markdown, appendix, or State JSON artifacts. Each snippet
83
- |has a 'citationUrl' — a public playground link auditing that exact passage — and, for State JSON
84
- |sources, the extracted 'json' value. The result also includes a whole-snapshot 'playground' link.
85
- |Return the citationUrl to the user so they can verify the source.`),
87
+ |You MUST cite what you take from the result. The result carries a ready-made
88
+ |'citationBlock' — append it verbatim to the end of your answer. Do not retype it, do not
89
+ |substitute the URL you fetched, and do not build a link yourself: the citation points at
90
+ |expand.ai's stored capture, not at the original site. Drop only the markers you did not use.
91
+ |A 'Verify it' link without '?id=' is not a citation. Never invent or reuse an evidence id.
92
+ |
93
+ |Returns ranked snippets from stored markdown, appendix, or State JSON artifacts. For State JSON
94
+ |sources each snippet also carries the extracted 'json' value. The result also includes a
95
+ |whole-snapshot 'playground' link.`),
86
96
  parameters: FetchSnapshotSearchParams.fields,
87
97
  success: CitedSnapshotSearchResult,
88
98
  failure: Schema.Defect,
@@ -91,13 +101,21 @@ export const FetchSearchTool = Tool.make('fetch_search', {
91
101
  .annotate(Tool.Readonly, true)
92
102
  .annotate(Tool.Destructive, false);
93
103
  export class Fetch extends Effect.Service()('@expandai/cli/Mcp/Fetch', {
94
- dependencies: [ApiClient.Default, ConfigManager.Default],
104
+ dependencies: [ApiClient.Default, ConfigManager.Default, Telemetry.Default],
95
105
  scoped: Effect.gen(function* () {
96
106
  const client = yield* ApiClient;
97
107
  const configManager = yield* ConfigManager;
108
+ const telemetry = yield* Telemetry;
98
109
  const playgroundHost = yield* configManager.getPlaygroundHost();
99
110
  const fetch = Effect.fn('Fetch.fetch')(function* ({ url, search, includeMeta, includeAppendix, includeJson, }) {
100
- yield* Effect.logDebug(`Fetching: ${url}`).pipe(Effect.annotateLogs({ search: search?.query, includeMeta, includeAppendix, includeJson }));
111
+ yield* Effect.logDebug('MCP fetch started').pipe(Effect.annotateLogs({
112
+ operation: 'fetch',
113
+ hasSearch: search !== undefined,
114
+ includeMeta,
115
+ includeAppendix,
116
+ includeJson,
117
+ }));
118
+ yield* telemetry.capture('mcp_tool_called', { tool: 'fetch', has_search: search !== undefined });
101
119
  const includeParam = includeAppendix && includeJson
102
120
  ? 'appendix,statejson'
103
121
  : includeAppendix
@@ -118,11 +136,17 @@ export class Fetch extends Effect.Service()('@expandai/cli/Mcp/Fetch', {
118
136
  search,
119
137
  },
120
138
  });
121
- yield* Effect.logDebug(`Fetched successfully: ${result.meta.url}`);
139
+ yield* Effect.logDebug('MCP fetch completed').pipe(Effect.annotateLogs({
140
+ operation: 'fetch',
141
+ hasSearch: search !== undefined,
142
+ searchResultCount: result.data?.search?.snippets.length ?? 0,
143
+ jsonEvidenceCount: result.json.length,
144
+ }));
122
145
  return enrichObjectModeResult(result, playgroundHost);
123
146
  });
124
147
  const fetchSearch = Effect.fn('Fetch.fetchSearch')(function* ({ snapshotId, search, includeMarkdown, includeAppendix, includeJson, }) {
125
- yield* Effect.logDebug(`Searching fetch snapshot: ${snapshotId}`).pipe(Effect.annotateLogs({ search: search.query, includeMarkdown, includeAppendix, includeJson }));
148
+ yield* Effect.logDebug('MCP snapshot search started').pipe(Effect.annotateLogs({ operation: 'fetch_search', includeMarkdown, includeAppendix, includeJson }));
149
+ yield* telemetry.capture('mcp_tool_called', { tool: 'fetch_search', has_search: true });
126
150
  const result = yield* client.fetchFetchSearch({
127
151
  snapshotId,
128
152
  search,
@@ -132,7 +156,7 @@ export class Fetch extends Effect.Service()('@expandai/cli/Mcp/Fetch', {
132
156
  appendix: includeAppendix,
133
157
  },
134
158
  });
135
- return enrichSnapshotSearchResult(result, playgroundHost);
159
+ return enrichSnapshotSearchResult(result, playgroundHost, 'mcp-fetch-search');
136
160
  });
137
161
  return { fetch, fetchSearch };
138
162
  }),
@@ -1 +1 @@
1
- {"version":3,"file":"Fetch.js","sourceRoot":"","sources":["../../../../src/Mcp/tools/Fetch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Fetch.js","sourceRoot":"","sources":["../../../../src/Mcp/tools/Fetch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { Effect, Exit, Option } from 'effect';
2
+ /** Creates a file exclusively and binds its identity to the opened handle before writing bytes. */
3
+ export function createExclusiveFile(fileSystem, targetPath, contents, mode) {
4
+ return Effect.scoped(Effect.gen(function* () {
5
+ const file = yield* fileSystem.open(targetPath, { flag: 'wx', mode });
6
+ const info = yield* file.stat;
7
+ const identity = {
8
+ dev: info.dev,
9
+ ino: Option.getOrUndefined(info.ino),
10
+ mode: info.mode,
11
+ type: info.type,
12
+ };
13
+ const writeExit = yield* Effect.exit(file.writeAll(new TextEncoder().encode(contents)).pipe(Effect.zipRight(file.sync)));
14
+ if (Exit.isFailure(writeExit))
15
+ return { identity, writeExit, pathMatchesHandle: false };
16
+ // Validate while the creating handle is still open. In particular, Windows
17
+ // may reuse a deleted file's numeric identifier immediately after its final
18
+ // handle closes, making a later pathname-only comparison ambiguous.
19
+ const pathInfo = yield* fileSystem.stat(targetPath);
20
+ const pathMatchesHandle = matchesCreatedFile(identity, {
21
+ exists: true,
22
+ dev: pathInfo.dev,
23
+ ino: Option.getOrUndefined(pathInfo.ino),
24
+ type: pathInfo.type,
25
+ });
26
+ return { identity, writeExit, pathMatchesHandle };
27
+ }));
28
+ }
29
+ /** Compares a pathname snapshot with identity captured from the creating file handle. */
30
+ export function matchesCreatedFile(identity, pathIdentity) {
31
+ return (pathIdentity.exists &&
32
+ pathIdentity.dev === identity.dev &&
33
+ pathIdentity.ino === identity.ino &&
34
+ pathIdentity.type === identity.type);
35
+ }
36
+ //# sourceMappingURL=SecureFile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SecureFile.js","sourceRoot":"","sources":["../../src/SecureFile.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAe7C,mGAAmG;AACnG,MAAM,UAAU,mBAAmB,CAClC,UAAiC,EACjC,UAAkB,EAClB,QAAgB,EAChB,IAAY;IAEZ,OAAO,MAAM,CAAC,MAAM,CACnB,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;QACnB,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QACrE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;QAC7B,MAAM,QAAQ,GAAwB;YACrC,GAAG,EAAE,IAAI,CAAC,GAAG;YACb,GAAG,EAAE,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC;YACpC,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;SACf,CAAA;QACD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACnC,IAAI,CAAC,QAAQ,CAAC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAClF,CAAA;QACD,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;YAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,iBAAiB,EAAE,KAAK,EAAW,CAAA;QAEhG,2EAA2E;QAC3E,4EAA4E;QAC5E,oEAAoE;QACpE,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;QACnD,MAAM,iBAAiB,GAAG,kBAAkB,CAAC,QAAQ,EAAE;YACtD,MAAM,EAAE,IAAI;YACZ,GAAG,EAAE,QAAQ,CAAC,GAAG;YACjB,GAAG,EAAE,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,GAAG,CAAC;YACxC,IAAI,EAAE,QAAQ,CAAC,IAAI;SACnB,CAAC,CAAA;QACF,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,iBAAiB,EAAW,CAAA;IAC3D,CAAC,CAAC,CACF,CAAA;AACF,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,kBAAkB,CACjC,QAA6B,EAC7B,YAKC;IAED,OAAO,CACN,YAAY,CAAC,MAAM;QACnB,YAAY,CAAC,GAAG,KAAK,QAAQ,CAAC,GAAG;QACjC,YAAY,CAAC,GAAG,KAAK,QAAQ,CAAC,GAAG;QACjC,YAAY,CAAC,IAAI,KAAK,QAAQ,CAAC,IAAI,CACnC,CAAA;AACF,CAAC"}
@@ -1,6 +1,17 @@
1
1
  import { citationUrl } from '@expandai/sdk/Playground';
2
2
  import { Schema } from 'effect';
3
3
  const encodeJson = Schema.encodeSync(Schema.parseJson(Schema.Unknown, { space: 2 }));
4
+ /**
5
+ * Attaches the public citation link to each snippet. JSON consumers get the same
6
+ * auditable URL the text renderer prints in its header — without it, a caller has
7
+ * the snapshot and the evidence id but no host to build a link against.
8
+ */
9
+ export function withCitationUrls(snapshotId, host, snippets) {
10
+ return snippets.map((snippet) => ({
11
+ ...snippet,
12
+ citationUrl: citationUrl(snapshotId, snippet.location, host),
13
+ }));
14
+ }
4
15
  /**
5
16
  * Render search snippets as auditable text blocks: a header carrying the source, score, and
6
17
  * citation link, the snippet text, and — for State JSON snippets — the extracted JSON value.
@@ -8,7 +19,7 @@ const encodeJson = Schema.encodeSync(Schema.parseJson(Schema.Unknown, { space: 2
8
19
  export function renderSnippets(snapshotId, host, snippets) {
9
20
  return snippets
10
21
  .map((snippet) => {
11
- const url = citationUrl(snapshotId, snippet.source, snippet.location, host);
22
+ const url = citationUrl(snapshotId, snippet.location, host);
12
23
  const header = `[${snippet.source} · score ${snippet.score.toFixed(2)}] ${url}`;
13
24
  const json = snippet.json == null ? '' : `\n\n${encodeJson(snippet.json)}`;
14
25
  return `${header}\n\n${snippet.text}${json}`;
@@ -1 +1 @@
1
- {"version":3,"file":"Snippets.js","sourceRoot":"","sources":["../../src/Snippets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAC3E,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;AAepF;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,IAAY,EAAE,QAA0C;IAC1G,OAAO,QAAQ;SACb,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAChB,MAAM,GAAG,GAAG,WAAW,CAAC,UAAU,EAAE,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAC3E,MAAM,MAAM,GAAG,IAAI,OAAO,CAAC,MAAM,YAAY,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAA;QAC/E,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAA;QAC1E,OAAO,GAAG,MAAM,OAAO,OAAO,CAAC,IAAI,GAAG,IAAI,EAAE,CAAA;IAC7C,CAAC,CAAC;SACD,IAAI,CAAC,aAAa,CAAC,CAAA;AACtB,CAAC"}
1
+ {"version":3,"file":"Snippets.js","sourceRoot":"","sources":["../../src/Snippets.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAE/B,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;AAKpF;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAC/B,UAAkB,EAClB,IAAY,EACZ,QAA0B;IAE1B,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACjC,GAAG,OAAO;QACV,WAAW,EAAE,WAAW,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC;KAC5D,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,UAAkB,EAAE,IAAY,EAAE,QAA0C;IAC1G,OAAO,QAAQ;SACb,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAChB,MAAM,GAAG,GAAG,WAAW,CAAC,UAAU,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAC3D,MAAM,MAAM,GAAG,IAAI,OAAO,CAAC,MAAM,YAAY,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAA;QAC/E,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAA;QAC1E,OAAO,GAAG,MAAM,OAAO,OAAO,CAAC,IAAI,GAAG,IAAI,EAAE,CAAA;IAC7C,CAAC,CAAC;SACD,IAAI,CAAC,aAAa,CAAC,CAAA;AACtB,CAAC"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Anonymous usage telemetry (PostHog). Events carry a persisted machine UUID,
3
+ * the invoking harness, and coarse feature usage — never URLs, queries,
4
+ * keys, or tokens.
5
+ *
6
+ * Delivery is fire-and-forget: each capture forks into a FiberSet and `flush`
7
+ * caps shutdown at one second, so telemetry can never block a command, delay
8
+ * an MCP/hook payload, or write to stdout (stdout is a protocol surface).
9
+ *
10
+ * Settings live in the config file (`telemetry.enabled` / `distinctId` /
11
+ * `noticeShown`). `DO_NOT_TRACK` / `EXPANDAI_NO_TELEMETRY`, or a non-default
12
+ * effective API URL (env or config file — dev traffic), disable capture per
13
+ * invocation without touching the file. A config file that fails to parse
14
+ * also disables telemetry for the invocation and is never written to.
15
+ */
16
+ import { randomUUID } from 'node:crypto';
17
+ import { HttpClient, HttpClientRequest } from '@effect/platform';
18
+ import { NodeHttpClient } from '@effect/platform-node';
19
+ import { Config, Console, Effect, FiberSet, Layer, Option } from 'effect';
20
+ import { ConfigManager, DEFAULT_API_URL, envFlag, envLiteral } from './Config.js';
21
+ import { Harness } from './Install/Domain.js';
22
+ import { CLI_VERSION } from './Version.js';
23
+ // Every field reads leniently (envFlag / envLiteral / plain strings) so a
24
+ // malformed env var can never fail service construction — telemetry must
25
+ // silently degrade, not crash the CLI.
26
+ export const TelemetryEnvironment = Config.all({
27
+ // PostHog project write-only key — safe to embed as the default (it ships
28
+ // in every browser bundle that uses posthog-js) and only permits ingestion.
29
+ apiKey: Config.string('POSTHOG_API_KEY').pipe(Config.withDefault('phc_sffcV4skT33mMKmo0BenNoE7t7AghUrOugyW9qSx43B')),
30
+ host: Config.string('POSTHOG_HOST').pipe(Config.withDefault('https://us.i.posthog.com')),
31
+ doNotTrack: envFlag('DO_NOT_TRACK'),
32
+ noTelemetry: envFlag('EXPANDAI_NO_TELEMETRY'),
33
+ apiUrl: Config.string('EXPAND_API_URL').pipe(Config.option),
34
+ // Baked into MCP/hook configs at install time; wins over ambient signals.
35
+ // An unrecognized value (e.g. written by a newer installer) reads as None.
36
+ harnessOverride: envLiteral('EXPANDAI_HARNESS', Harness),
37
+ claudeCode: envFlag('CLAUDECODE'),
38
+ cursor: Config.string('CURSOR_TRACE_ID').pipe(Config.option, Config.map(Option.isSome)),
39
+ codex: Config.string('CODEX_RUN_ID').pipe(Config.option, Config.map(Option.isSome)),
40
+ });
41
+ function detectHarness(environment) {
42
+ if (Option.isSome(environment.harnessOverride))
43
+ return environment.harnessOverride.value;
44
+ if (environment.claudeCode)
45
+ return 'claude-code';
46
+ if (environment.cursor)
47
+ return 'cursor';
48
+ if (environment.codex)
49
+ return 'codex';
50
+ return 'unknown';
51
+ }
52
+ const generateDistinctId = Effect.sync(() => randomUUID());
53
+ // Lenient normalization for the non-default-API comparison: `new URL()` adds
54
+ // the trailing slash a bare origin string lacks, and a value that doesn't
55
+ // parse compares as non-default — never crash construction, and treat unknown
56
+ // endpoints as non-production traffic.
57
+ function normalizeApiUrl(url) {
58
+ try {
59
+ return new URL(url).toString();
60
+ }
61
+ catch {
62
+ return `unparseable:${url}`;
63
+ }
64
+ }
65
+ export class Telemetry extends Effect.Service()('@expandai/cli/Telemetry', {
66
+ dependencies: [ConfigManager.Default, NodeHttpClient.layer],
67
+ scoped: Effect.gen(function* () {
68
+ const configManager = yield* ConfigManager;
69
+ const httpClient = yield* HttpClient.HttpClient;
70
+ const fibers = yield* FiberSet.make();
71
+ const environment = yield* TelemetryEnvironment;
72
+ // EXPAND_API_KEY overrides the config file for requests (see ConfigManager),
73
+ // so it must win here too or env-key users all report 'none'.
74
+ const envApiKey = yield* Config.string('EXPAND_API_KEY').pipe(Config.option, Config.map(Option.isSome));
75
+ // The config file is read best-effort: telemetry must work (or silently
76
+ // no-op) before login and survive an unreadable config. A parse failure
77
+ // disables capture for the invocation — an unreadable file must never be
78
+ // mistaken for an empty one, and never be written to.
79
+ const { config, parseFailed } = yield* configManager.readConfigFile();
80
+ const settings = config.telemetry;
81
+ // Same env-over-file precedence as ConfigManager: a self-hosted apiUrl in
82
+ // the config file is dev traffic too. Deliberately not Config.url /
83
+ // getApiUrl — those fail on a malformed env value, and telemetry must
84
+ // degrade instead of crashing the CLI.
85
+ const effectiveApiUrl = Option.firstSomeOf([
86
+ environment.apiUrl,
87
+ Option.map(Option.fromNullable(config.apiUrl), (url) => url.toString()),
88
+ ]);
89
+ const nonDefaultApi = Option.match(effectiveApiUrl, {
90
+ onNone: () => false,
91
+ onSome: (url) => normalizeApiUrl(url) !== normalizeApiUrl(DEFAULT_API_URL),
92
+ });
93
+ const enabled = !environment.doNotTrack &&
94
+ !environment.noTelemetry &&
95
+ !nonDefaultApi &&
96
+ !parseFailed &&
97
+ settings?.enabled !== false;
98
+ const harness = detectHarness(environment);
99
+ const authMethod = envApiKey
100
+ ? 'api-key'
101
+ : config.authMethod?._tag === 'ApiKey'
102
+ ? 'api-key'
103
+ : config.authMethod?._tag === 'OAuth'
104
+ ? 'oauth'
105
+ : 'none';
106
+ const persistNewDistinctId = Effect.fnUntraced(function* () {
107
+ const generated = yield* generateDistinctId;
108
+ // Best-effort persistence — an unwritable config yields a per-run id.
109
+ yield* configManager.updateConfig({ telemetry: { ...settings, distinctId: generated } }).pipe(Effect.ignore);
110
+ return generated;
111
+ });
112
+ // Disabled runs never write: DO_NOT_TRACK users don't get an id persisted,
113
+ // and after a parse failure a write would clobber the original file.
114
+ const distinctId = settings?.distinctId ?? (enabled ? yield* persistNewDistinctId() : yield* generateDistinctId);
115
+ const capture = Effect.fn('Telemetry.capture')(function* (event, properties) {
116
+ if (!enabled)
117
+ return;
118
+ const post = HttpClientRequest.post(`${environment.host}/i/v0/e`).pipe(HttpClientRequest.bodyJson({
119
+ api_key: environment.apiKey,
120
+ event,
121
+ distinct_id: distinctId,
122
+ timestamp: new Date().toISOString(),
123
+ properties: {
124
+ $lib: 'expandai-cli',
125
+ $lib_version: CLI_VERSION,
126
+ version: CLI_VERSION,
127
+ harness,
128
+ auth_method: authMethod,
129
+ ...properties,
130
+ },
131
+ }));
132
+ yield* FiberSet.run(fibers, post.pipe(Effect.flatMap((request) => httpClient.execute(request)), Effect.scoped, Effect.timeout('2 seconds'), Effect.ignore));
133
+ });
134
+ // Cap delivery at flush time so short-lived processes get events out
135
+ // without ever holding the process open noticeably.
136
+ const flush = FiberSet.awaitEmpty(fibers).pipe(Effect.timeout('1 second'), Effect.ignore);
137
+ const showNoticeIfNeeded = Effect.fn('Telemetry.showNoticeIfNeeded')(function* () {
138
+ if (!enabled || settings?.noticeShown === true)
139
+ return;
140
+ yield* Console.error('expandai collects anonymous usage data to improve the CLI. Opt out with `expandai telemetry disable` or EXPANDAI_NO_TELEMETRY=1.');
141
+ yield* configManager
142
+ .updateConfig({ telemetry: { ...settings, distinctId, noticeShown: true } })
143
+ .pipe(Effect.ignore);
144
+ });
145
+ return { capture, flush, showNoticeIfNeeded, enabled, distinctId };
146
+ }),
147
+ }) {
148
+ /** A side-effect-free telemetry service for commands whose contract is read-only. */
149
+ static DISABLED = Layer.succeed(Telemetry, Telemetry.make({
150
+ capture: () => Effect.void,
151
+ flush: Effect.void,
152
+ showNoticeIfNeeded: () => Effect.void,
153
+ enabled: false,
154
+ distinctId: 'disabled',
155
+ }));
156
+ }
157
+ //# sourceMappingURL=Telemetry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Telemetry.js","sourceRoot":"","sources":["../../src/Telemetry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ // Single source of truth for the CLI version. Keep in sync with package.json —
2
+ // the compiled binary cannot read package.json at runtime.
3
+ export const CLI_VERSION = '0.0.3';
4
+ //# sourceMappingURL=Version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Version.js","sourceRoot":"","sources":["../../src/Version.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,2DAA2D;AAC3D,MAAM,CAAC,MAAM,WAAW,GAAG,OAAO,CAAA"}
@@ -0,0 +1,15 @@
1
+ import { homedir } from 'node:os';
2
+ import { Path } from '@effect/platform';
3
+ import { Config, Effect } from 'effect';
4
+ /**
5
+ * XDG base-directory lookup, shared by everything that persists CLI state so
6
+ * the fallback rules cannot drift apart. The spec treats an unset and an empty
7
+ * variable identically: fall back to `$HOME/.config`. Without the empty check,
8
+ * `XDG_CONFIG_HOME=` resolves paths relative to the working directory — which
9
+ * would drop credentials inside whatever project the CLI happens to run in.
10
+ */
11
+ export const xdgConfigDirectory = Effect.gen(function* () {
12
+ const path = yield* Path.Path;
13
+ return yield* Config.string('XDG_CONFIG_HOME').pipe(Config.withDefault(''), Config.map((directory) => (directory.trim() === '' ? path.join(homedir(), '.config') : directory)));
14
+ });
15
+ //# sourceMappingURL=Xdg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Xdg.js","sourceRoot":"","sources":["../../src/Xdg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAEvC;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACrD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAA;IAC7B,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAClD,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,EACtB,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAClG,CAAA;AACF,CAAC,CAAC,CAAA"}