expandai 0.0.4 → 0.0.6

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 (321) hide show
  1. package/README.md +2 -2
  2. package/build/{esm/Install/Opencode → assets/opencode}/expandai-webfetch.js +22 -4
  3. package/build/dts/Mcp/RequestHeaders.d.ts +0 -1
  4. package/build/dts/Mcp/Server.d.ts +1 -2
  5. package/build/dts/index.d.ts +0 -1
  6. package/build/esm/index.js +193 -2
  7. package/build/esm/main.js +691 -200
  8. package/build/skills/expandai/SKILL.md +100 -8
  9. package/package.json +20 -12
  10. package/build/dts/ApiClient.d.ts +0 -165
  11. package/build/dts/ApiClient.d.ts.map +0 -1
  12. package/build/dts/Auth/Domain.d.ts +0 -95
  13. package/build/dts/Auth/Domain.d.ts.map +0 -1
  14. package/build/dts/Auth/ManualAuthorization.d.ts +0 -107
  15. package/build/dts/Auth/ManualAuthorization.d.ts.map +0 -1
  16. package/build/dts/Auth/PendingInit.d.ts +0 -35
  17. package/build/dts/Auth/PendingInit.d.ts.map +0 -1
  18. package/build/dts/Auth/Pkce.d.ts +0 -10
  19. package/build/dts/Auth/Pkce.d.ts.map +0 -1
  20. package/build/dts/Auth/SetupGrantAuthorization.d.ts +0 -34
  21. package/build/dts/Auth/SetupGrantAuthorization.d.ts.map +0 -1
  22. package/build/dts/Authentication.d.ts +0 -24
  23. package/build/dts/Authentication.d.ts.map +0 -1
  24. package/build/dts/BrowserOAuth.d.ts +0 -99
  25. package/build/dts/BrowserOAuth.d.ts.map +0 -1
  26. package/build/dts/CommandPath.d.ts +0 -2
  27. package/build/dts/CommandPath.d.ts.map +0 -1
  28. package/build/dts/Commands/Fetch.d.ts +0 -30
  29. package/build/dts/Commands/Fetch.d.ts.map +0 -1
  30. package/build/dts/Commands/Hook.d.ts +0 -17
  31. package/build/dts/Commands/Hook.d.ts.map +0 -1
  32. package/build/dts/Commands/Init.d.ts +0 -55
  33. package/build/dts/Commands/Init.d.ts.map +0 -1
  34. package/build/dts/Commands/Mcp.d.ts +0 -11
  35. package/build/dts/Commands/Mcp.d.ts.map +0 -1
  36. package/build/dts/Commands/McpRun.d.ts +0 -5
  37. package/build/dts/Commands/McpRun.d.ts.map +0 -1
  38. package/build/dts/Commands/Search.d.ts +0 -21
  39. package/build/dts/Commands/Search.d.ts.map +0 -1
  40. package/build/dts/Commands/Skill.d.ts +0 -12
  41. package/build/dts/Commands/Skill.d.ts.map +0 -1
  42. package/build/dts/Commands/Status.d.ts +0 -27
  43. package/build/dts/Commands/Status.d.ts.map +0 -1
  44. package/build/dts/Commands/Telemetry.d.ts +0 -7
  45. package/build/dts/Commands/Telemetry.d.ts.map +0 -1
  46. package/build/dts/Commands/Uninstall.d.ts +0 -9
  47. package/build/dts/Commands/Uninstall.d.ts.map +0 -1
  48. package/build/dts/Config.d.ts +0 -196
  49. package/build/dts/Config.d.ts.map +0 -1
  50. package/build/dts/Hook/Claude.d.ts +0 -13
  51. package/build/dts/Hook/Claude.d.ts.map +0 -1
  52. package/build/dts/Hook/Errors.d.ts +0 -20
  53. package/build/dts/Hook/Errors.d.ts.map +0 -1
  54. package/build/dts/Hook/Opencode.d.ts +0 -13
  55. package/build/dts/Hook/Opencode.d.ts.map +0 -1
  56. package/build/dts/Hook/SnapshotNote.d.ts +0 -12
  57. package/build/dts/Hook/SnapshotNote.d.ts.map +0 -1
  58. package/build/dts/Init/ClaudeAdapter.d.ts +0 -61
  59. package/build/dts/Init/ClaudeAdapter.d.ts.map +0 -1
  60. package/build/dts/Init/ClaudeProfile.d.ts +0 -77
  61. package/build/dts/Init/ClaudeProfile.d.ts.map +0 -1
  62. package/build/dts/Init/Domain.d.ts +0 -139
  63. package/build/dts/Init/Domain.d.ts.map +0 -1
  64. package/build/dts/Init/HarnessAdapter.d.ts +0 -33
  65. package/build/dts/Init/HarnessAdapter.d.ts.map +0 -1
  66. package/build/dts/Init/HarnessDetector.d.ts +0 -19
  67. package/build/dts/Init/HarnessDetector.d.ts.map +0 -1
  68. package/build/dts/Init/HarnessRegistry.d.ts +0 -50
  69. package/build/dts/Init/HarnessRegistry.d.ts.map +0 -1
  70. package/build/dts/Init/MutationTransaction.d.ts +0 -96
  71. package/build/dts/Init/MutationTransaction.d.ts.map +0 -1
  72. package/build/dts/Init/Service.d.ts +0 -72
  73. package/build/dts/Init/Service.d.ts.map +0 -1
  74. package/build/dts/Init/StandardAdapters.d.ts +0 -42
  75. package/build/dts/Init/StandardAdapters.d.ts.map +0 -1
  76. package/build/dts/Init/Status.d.ts +0 -149
  77. package/build/dts/Init/Status.d.ts.map +0 -1
  78. package/build/dts/Install/Claude/Hook.d.ts +0 -88
  79. package/build/dts/Install/Claude/Hook.d.ts.map +0 -1
  80. package/build/dts/Install/Claude/Mcp.d.ts +0 -48
  81. package/build/dts/Install/Claude/Mcp.d.ts.map +0 -1
  82. package/build/dts/Install/Claude/Skill.d.ts +0 -23
  83. package/build/dts/Install/Claude/Skill.d.ts.map +0 -1
  84. package/build/dts/Install/Codex/Mcp.d.ts +0 -45
  85. package/build/dts/Install/Codex/Mcp.d.ts.map +0 -1
  86. package/build/dts/Install/Codex/Skill.d.ts +0 -22
  87. package/build/dts/Install/Codex/Skill.d.ts.map +0 -1
  88. package/build/dts/Install/Cursor/Mcp.d.ts +0 -45
  89. package/build/dts/Install/Cursor/Mcp.d.ts.map +0 -1
  90. package/build/dts/Install/Cursor/Skill.d.ts +0 -29
  91. package/build/dts/Install/Cursor/Skill.d.ts.map +0 -1
  92. package/build/dts/Install/Domain.d.ts +0 -103
  93. package/build/dts/Install/Domain.d.ts.map +0 -1
  94. package/build/dts/Install/Hook.d.ts +0 -30
  95. package/build/dts/Install/Hook.d.ts.map +0 -1
  96. package/build/dts/Install/Mcp.d.ts +0 -28
  97. package/build/dts/Install/Mcp.d.ts.map +0 -1
  98. package/build/dts/Install/Opencode/Hook.d.ts +0 -24
  99. package/build/dts/Install/Opencode/Hook.d.ts.map +0 -1
  100. package/build/dts/Install/Opencode/Mcp.d.ts +0 -47
  101. package/build/dts/Install/Opencode/Mcp.d.ts.map +0 -1
  102. package/build/dts/Install/Opencode/Skill.d.ts +0 -26
  103. package/build/dts/Install/Opencode/Skill.d.ts.map +0 -1
  104. package/build/dts/Install/Skill.d.ts +0 -32
  105. package/build/dts/Install/Skill.d.ts.map +0 -1
  106. package/build/dts/Install/Uninstall.d.ts +0 -32
  107. package/build/dts/Install/Uninstall.d.ts.map +0 -1
  108. package/build/dts/MarkdownAnsi.d.ts +0 -29
  109. package/build/dts/MarkdownAnsi.d.ts.map +0 -1
  110. package/build/dts/Mcp/ExpandDocs.d.ts +0 -19
  111. package/build/dts/Mcp/ExpandDocs.d.ts.map +0 -1
  112. package/build/dts/Mcp/RequestHeaders.d.ts.map +0 -1
  113. package/build/dts/Mcp/Server.d.ts.map +0 -1
  114. package/build/dts/Mcp/Toolkit.d.ts +0 -3
  115. package/build/dts/Mcp/Toolkit.d.ts.map +0 -1
  116. package/build/dts/Mcp/tools/Citations.d.ts +0 -266
  117. package/build/dts/Mcp/tools/Citations.d.ts.map +0 -1
  118. package/build/dts/Mcp/tools/Fetch.d.ts +0 -168
  119. package/build/dts/Mcp/tools/Fetch.d.ts.map +0 -1
  120. package/build/dts/SecureFile.d.ts +0 -23
  121. package/build/dts/SecureFile.d.ts.map +0 -1
  122. package/build/dts/Snippets.d.ts +0 -18
  123. package/build/dts/Snippets.d.ts.map +0 -1
  124. package/build/dts/Telemetry.d.ts +0 -30
  125. package/build/dts/Telemetry.d.ts.map +0 -1
  126. package/build/dts/Version.d.ts +0 -2
  127. package/build/dts/Version.d.ts.map +0 -1
  128. package/build/dts/Xdg.d.ts +0 -11
  129. package/build/dts/Xdg.d.ts.map +0 -1
  130. package/build/dts/index.d.ts.map +0 -1
  131. package/build/dts/main.d.ts +0 -3
  132. package/build/dts/main.d.ts.map +0 -1
  133. package/build/esm/ApiClient.js +0 -305
  134. package/build/esm/ApiClient.js.map +0 -1
  135. package/build/esm/Auth/Domain.js +0 -56
  136. package/build/esm/Auth/Domain.js.map +0 -1
  137. package/build/esm/Auth/ManualAuthorization.js +0 -253
  138. package/build/esm/Auth/ManualAuthorization.js.map +0 -1
  139. package/build/esm/Auth/PendingInit.js +0 -267
  140. package/build/esm/Auth/PendingInit.js.map +0 -1
  141. package/build/esm/Auth/Pkce.js +0 -23
  142. package/build/esm/Auth/Pkce.js.map +0 -1
  143. package/build/esm/Auth/SetupGrantAuthorization.js +0 -91
  144. package/build/esm/Auth/SetupGrantAuthorization.js.map +0 -1
  145. package/build/esm/Authentication.js +0 -143
  146. package/build/esm/Authentication.js.map +0 -1
  147. package/build/esm/BrowserOAuth.js +0 -251
  148. package/build/esm/BrowserOAuth.js.map +0 -1
  149. package/build/esm/CommandPath.js +0 -31
  150. package/build/esm/CommandPath.js.map +0 -1
  151. package/build/esm/Commands/Fetch.js +0 -119
  152. package/build/esm/Commands/Fetch.js.map +0 -1
  153. package/build/esm/Commands/Hook.js +0 -20
  154. package/build/esm/Commands/Hook.js.map +0 -1
  155. package/build/esm/Commands/Init.js +0 -371
  156. package/build/esm/Commands/Init.js.map +0 -1
  157. package/build/esm/Commands/Mcp.js +0 -19
  158. package/build/esm/Commands/Mcp.js.map +0 -1
  159. package/build/esm/Commands/McpRun.js +0 -15
  160. package/build/esm/Commands/McpRun.js.map +0 -1
  161. package/build/esm/Commands/Search.js +0 -46
  162. package/build/esm/Commands/Search.js.map +0 -1
  163. package/build/esm/Commands/Skill.js +0 -17
  164. package/build/esm/Commands/Skill.js.map +0 -1
  165. package/build/esm/Commands/Status.js +0 -186
  166. package/build/esm/Commands/Status.js.map +0 -1
  167. package/build/esm/Commands/Telemetry.js +0 -23
  168. package/build/esm/Commands/Telemetry.js.map +0 -1
  169. package/build/esm/Commands/Uninstall.js +0 -28
  170. package/build/esm/Commands/Uninstall.js.map +0 -1
  171. package/build/esm/Config.js +0 -619
  172. package/build/esm/Config.js.map +0 -1
  173. package/build/esm/Hook/Claude.js +0 -74
  174. package/build/esm/Hook/Claude.js.map +0 -1
  175. package/build/esm/Hook/Errors.js +0 -18
  176. package/build/esm/Hook/Errors.js.map +0 -1
  177. package/build/esm/Hook/Opencode.js +0 -50
  178. package/build/esm/Hook/Opencode.js.map +0 -1
  179. package/build/esm/Hook/SnapshotNote.js +0 -65
  180. package/build/esm/Hook/SnapshotNote.js.map +0 -1
  181. package/build/esm/Init/ClaudeAdapter.js +0 -137
  182. package/build/esm/Init/ClaudeAdapter.js.map +0 -1
  183. package/build/esm/Init/ClaudeProfile.js +0 -344
  184. package/build/esm/Init/ClaudeProfile.js.map +0 -1
  185. package/build/esm/Init/Domain.js +0 -118
  186. package/build/esm/Init/Domain.js.map +0 -1
  187. package/build/esm/Init/HarnessAdapter.js +0 -21
  188. package/build/esm/Init/HarnessAdapter.js.map +0 -1
  189. package/build/esm/Init/HarnessDetector.js +0 -65
  190. package/build/esm/Init/HarnessDetector.js.map +0 -1
  191. package/build/esm/Init/HarnessRegistry.js +0 -58
  192. package/build/esm/Init/HarnessRegistry.js.map +0 -1
  193. package/build/esm/Init/MutationTransaction.js +0 -494
  194. package/build/esm/Init/MutationTransaction.js.map +0 -1
  195. package/build/esm/Init/Service.js +0 -319
  196. package/build/esm/Init/Service.js.map +0 -1
  197. package/build/esm/Init/StandardAdapters.js +0 -116
  198. package/build/esm/Init/StandardAdapters.js.map +0 -1
  199. package/build/esm/Init/Status.js +0 -215
  200. package/build/esm/Init/Status.js.map +0 -1
  201. package/build/esm/Install/Claude/Hook.js +0 -77
  202. package/build/esm/Install/Claude/Hook.js.map +0 -1
  203. package/build/esm/Install/Claude/Mcp.js +0 -101
  204. package/build/esm/Install/Claude/Mcp.js.map +0 -1
  205. package/build/esm/Install/Claude/Skill.js +0 -56
  206. package/build/esm/Install/Claude/Skill.js.map +0 -1
  207. package/build/esm/Install/Codex/Mcp.js +0 -97
  208. package/build/esm/Install/Codex/Mcp.js.map +0 -1
  209. package/build/esm/Install/Codex/Skill.js +0 -33
  210. package/build/esm/Install/Codex/Skill.js.map +0 -1
  211. package/build/esm/Install/Cursor/Mcp.js +0 -74
  212. package/build/esm/Install/Cursor/Mcp.js.map +0 -1
  213. package/build/esm/Install/Cursor/Skill.js +0 -40
  214. package/build/esm/Install/Cursor/Skill.js.map +0 -1
  215. package/build/esm/Install/Domain.js +0 -162
  216. package/build/esm/Install/Domain.js.map +0 -1
  217. package/build/esm/Install/Hook.js +0 -38
  218. package/build/esm/Install/Hook.js.map +0 -1
  219. package/build/esm/Install/Mcp.js +0 -40
  220. package/build/esm/Install/Mcp.js.map +0 -1
  221. package/build/esm/Install/Opencode/Hook.js +0 -44
  222. package/build/esm/Install/Opencode/Hook.js.map +0 -1
  223. package/build/esm/Install/Opencode/Mcp.js +0 -76
  224. package/build/esm/Install/Opencode/Mcp.js.map +0 -1
  225. package/build/esm/Install/Opencode/Skill.js +0 -37
  226. package/build/esm/Install/Opencode/Skill.js.map +0 -1
  227. package/build/esm/Install/Skill.js +0 -41
  228. package/build/esm/Install/Skill.js.map +0 -1
  229. package/build/esm/Install/Uninstall.js +0 -49
  230. package/build/esm/Install/Uninstall.js.map +0 -1
  231. package/build/esm/MarkdownAnsi.js +0 -142
  232. package/build/esm/MarkdownAnsi.js.map +0 -1
  233. package/build/esm/Mcp/ExpandDocs.js +0 -56
  234. package/build/esm/Mcp/ExpandDocs.js.map +0 -1
  235. package/build/esm/Mcp/RequestHeaders.js +0 -13
  236. package/build/esm/Mcp/RequestHeaders.js.map +0 -1
  237. package/build/esm/Mcp/Server.js +0 -12
  238. package/build/esm/Mcp/Server.js.map +0 -1
  239. package/build/esm/Mcp/Toolkit.js +0 -16
  240. package/build/esm/Mcp/Toolkit.js.map +0 -1
  241. package/build/esm/Mcp/tools/Citations.js +0 -193
  242. package/build/esm/Mcp/tools/Citations.js.map +0 -1
  243. package/build/esm/Mcp/tools/Fetch.js +0 -165
  244. package/build/esm/Mcp/tools/Fetch.js.map +0 -1
  245. package/build/esm/SecureFile.js +0 -36
  246. package/build/esm/SecureFile.js.map +0 -1
  247. package/build/esm/Snippets.js +0 -29
  248. package/build/esm/Snippets.js.map +0 -1
  249. package/build/esm/Telemetry.js +0 -157
  250. package/build/esm/Telemetry.js.map +0 -1
  251. package/build/esm/Version.js +0 -4
  252. package/build/esm/Version.js.map +0 -1
  253. package/build/esm/Xdg.js +0 -15
  254. package/build/esm/Xdg.js.map +0 -1
  255. package/build/esm/index.js.map +0 -1
  256. package/build/esm/main.js.map +0 -1
  257. package/src/ApiClient.ts +0 -450
  258. package/src/Auth/Domain.ts +0 -63
  259. package/src/Auth/ManualAuthorization.ts +0 -296
  260. package/src/Auth/PendingInit.ts +0 -338
  261. package/src/Auth/Pkce.ts +0 -26
  262. package/src/Auth/SetupGrantAuthorization.ts +0 -116
  263. package/src/Authentication.ts +0 -202
  264. package/src/BrowserOAuth.ts +0 -359
  265. package/src/CommandPath.ts +0 -30
  266. package/src/Commands/Fetch.ts +0 -188
  267. package/src/Commands/Hook.ts +0 -48
  268. package/src/Commands/Init.ts +0 -489
  269. package/src/Commands/Mcp.ts +0 -30
  270. package/src/Commands/McpRun.ts +0 -25
  271. package/src/Commands/Search.ts +0 -97
  272. package/src/Commands/Skill.ts +0 -30
  273. package/src/Commands/Status.ts +0 -228
  274. package/src/Commands/Telemetry.ts +0 -42
  275. package/src/Commands/Uninstall.ts +0 -38
  276. package/src/Config.ts +0 -840
  277. package/src/Hook/Claude.ts +0 -81
  278. package/src/Hook/Errors.ts +0 -19
  279. package/src/Hook/Opencode.ts +0 -58
  280. package/src/Hook/SnapshotNote.ts +0 -66
  281. package/src/Init/ClaudeAdapter.ts +0 -191
  282. package/src/Init/ClaudeProfile.ts +0 -453
  283. package/src/Init/Domain.ts +0 -153
  284. package/src/Init/HarnessAdapter.ts +0 -35
  285. package/src/Init/HarnessDetector.ts +0 -77
  286. package/src/Init/HarnessRegistry.ts +0 -87
  287. package/src/Init/MutationTransaction.ts +0 -747
  288. package/src/Init/Service.ts +0 -385
  289. package/src/Init/StandardAdapters.ts +0 -141
  290. package/src/Init/Status.ts +0 -253
  291. package/src/Install/Claude/Hook.ts +0 -123
  292. package/src/Install/Claude/Mcp.ts +0 -129
  293. package/src/Install/Claude/Skill.ts +0 -77
  294. package/src/Install/Codex/Mcp.ts +0 -114
  295. package/src/Install/Codex/Skill.ts +0 -44
  296. package/src/Install/Cursor/Mcp.ts +0 -85
  297. package/src/Install/Cursor/Skill.ts +0 -51
  298. package/src/Install/Domain.ts +0 -221
  299. package/src/Install/Hook.ts +0 -50
  300. package/src/Install/Mcp.ts +0 -71
  301. package/src/Install/Opencode/Hook.ts +0 -55
  302. package/src/Install/Opencode/Mcp.ts +0 -87
  303. package/src/Install/Opencode/Skill.ts +0 -48
  304. package/src/Install/Opencode/expandai-webfetch.d.ts +0 -23
  305. package/src/Install/Opencode/expandai-webfetch.js +0 -56
  306. package/src/Install/Skill.ts +0 -57
  307. package/src/Install/Uninstall.ts +0 -82
  308. package/src/MarkdownAnsi.ts +0 -162
  309. package/src/Mcp/ExpandDocs.ts +0 -62
  310. package/src/Mcp/RequestHeaders.ts +0 -16
  311. package/src/Mcp/Server.ts +0 -15
  312. package/src/Mcp/Toolkit.ts +0 -21
  313. package/src/Mcp/tools/Citations.ts +0 -219
  314. package/src/Mcp/tools/Fetch.ts +0 -203
  315. package/src/SecureFile.ts +0 -70
  316. package/src/Snippets.ts +0 -39
  317. package/src/Telemetry.ts +0 -186
  318. package/src/Version.ts +0 -3
  319. package/src/Xdg.ts +0 -18
  320. package/src/index.ts +0 -1
  321. package/src/main.ts +0 -305
@@ -1,21 +0,0 @@
1
- import { McpServer, Toolkit } from '@effect/ai'
2
- import { Effect, Layer } from 'effect'
3
- import { ApiClient } from '../ApiClient.js'
4
- import { Fetch, FetchSearchTool, FetchTool } from './tools/Fetch.js'
5
-
6
- const toolkit = Toolkit.make(FetchTool, FetchSearchTool)
7
-
8
- const ToolkitLayer = toolkit
9
- .toLayer(
10
- Effect.gen(function* () {
11
- const fetchService = yield* Fetch
12
-
13
- return toolkit.of({
14
- fetch: fetchService.fetch,
15
- fetch_search: fetchService.fetchSearch,
16
- })
17
- }),
18
- )
19
- .pipe(Layer.provide(Fetch.Default), Layer.provide(ApiClient.Default))
20
-
21
- export const ExpandToolKit = McpServer.toolkit(toolkit).pipe(Layer.provide(ToolkitLayer))
@@ -1,219 +0,0 @@
1
- // MCP-facing result wrappers that add a ready-made `citationUrl` to every search snippet, so the
2
- // agent can hand the user an auditable playground link without assembling URLs itself. Wraps the
3
- // SDK's `json`-recovering decode types (`@expandai/sdk/FetchJson`) and reuses generated leaf
4
- // schemas; `Generated.ts` is left untouched.
5
-
6
- import type { FetchObjectModeResultWithJson, FetchSnapshotSearchResultWithJson } from '@expandai/sdk/FetchJson'
7
- import * as Generated from '@expandai/sdk/Generated'
8
- import { citationUrl, playgroundBase, resolvePlaygroundHost } from '@expandai/sdk/Playground'
9
- import { Schema as S } from 'effect'
10
-
11
- export const FetchInvocationPath = S.Literal('cli-recovery', 'mcp-fetch-search')
12
- export type FetchInvocationPath = typeof FetchInvocationPath.Type
13
-
14
- export class CitedSnippet extends S.Class<CitedSnippet>('CitedSnippet')({
15
- type: S.optionalWith(S.Literal('text'), { nullable: true, default: () => 'text' as const }),
16
- source: S.Literal('markdown', 'appendix', 'statejson'),
17
- text: S.String,
18
- json: S.optionalWith(S.Unknown, { nullable: true }),
19
- score: S.Number.pipe(S.greaterThanOrEqualTo(0), S.lessThanOrEqualTo(1)),
20
- location: S.optionalWith(Generated.FetchSearchSnippetLocation, { nullable: true }),
21
- citationUrl: S.String.annotations({
22
- description: 'Public playground link auditing this exact snippet. Return it to the user verbatim.',
23
- }),
24
- }) {}
25
-
26
- /**
27
- * Carried in the tool result itself, not only in `SKILL.md`. A harness may not load
28
- * skills at all, and the hook's snapshot note never reaches this path — so without
29
- * this the model receives perfect `citationUrl`s and no instruction to use them, and
30
- * answers with no citation. Observed on OpenCode before this was added.
31
- */
32
- export const CITATION_INSTRUCTIONS =
33
- 'Cite every claim you take from this result. Append the ready-made `citationBlock` from this ' +
34
- 'result to the end of your answer. Delete every entry whose marker you did not actually place ' +
35
- 'in your text, and renumber what remains from [1] — an entry with no matching marker is not a ' +
36
- 'citation, it is clutter. Do not ' +
37
- 'retype the URLs, do not substitute the page you fetched, and do not build a link yourself — ' +
38
- "the citation points at expand.ai's stored capture, not at the original site. Never invent an " +
39
- 'evidence id and never reuse one from different text. A link without `?id=` is not a citation.'
40
-
41
- const CitationInstructionsField = S.optionalWith(S.String, { default: () => CITATION_INSTRUCTIONS }).annotations({
42
- description: 'How to cite this result back to the user. Follow it exactly.',
43
- })
44
-
45
- const CitationBlockField = S.optionalWith(S.String, { default: () => '' }).annotations({
46
- description:
47
- 'Ready-to-append citation block, one entry per citable snippet. Copy it to the end of your ' +
48
- 'answer, keeping only the entries whose markers you placed in your text and renumbering ' +
49
- 'from [1]. You may also correct the page title.',
50
- })
51
-
52
- /**
53
- * Prebuilds the citation markdown instead of asking the model to assemble it.
54
- * Selecting `citationUrl` out of a JSON result and pasting it correctly is the step
55
- * weaker models get wrong — observed repeatedly on OpenCode, which substituted the
56
- * fetched page URL for the capture link. Copying one block has no such failure mode.
57
- */
58
- const MAX_CITATION_MARKERS = 3
59
- const EVIDENCE_MARKER = /\{(\d{1,6})\}/gu
60
-
61
- /**
62
- * Citation URLs for a plain fetch with no search. The markdown still stamps evidence
63
- * ids, so a citation is always possible — without this a `fetch` called without a
64
- * query produced an empty block and the model answered uncited.
65
- */
66
- function markdownCitationUrls(markdown: string, snapshotId: string, host: string): string[] {
67
- const urls: string[] = []
68
- for (const match of markdown.matchAll(EVIDENCE_MARKER)) {
69
- const id = Number(match[1])
70
- if (!Number.isFinite(id)) continue
71
- const url = citationUrl(snapshotId, { evidenceId: id }, host)
72
- if (!urls.includes(url)) urls.push(url)
73
- if (urls.length >= MAX_CITATION_MARKERS) break
74
- }
75
- return urls
76
- }
77
-
78
- /**
79
- * `statejson` snippets are extracted page state — arrays of routes, config blobs — not
80
- * prose anyone can quote. Offering one as a citable source produced a `Sources` entry
81
- * pointing at a list of URLs, so only quotable text earns a citation.
82
- */
83
- function isQuotable(snippet: CitedSnippet): boolean {
84
- return snippet.source !== 'statejson'
85
- }
86
-
87
- function citationBlock(sourceLabel: string, snippets: ReadonlyArray<CitedSnippet>, fallback: string[] = []): string {
88
- const urls: string[] = []
89
- for (const snippet of snippets) {
90
- if (!isQuotable(snippet)) continue
91
- if (snippet.citationUrl.length > 0 && !urls.includes(snippet.citationUrl)) urls.push(snippet.citationUrl)
92
- }
93
- if (urls.length === 0) urls.push(...fallback)
94
- if (urls.length === 0) return ''
95
- // One linked source line per marker.
96
- // Grouping all the sources first and all the links after loses every link when a
97
- // model copies only the opening lines — observed on OpenCode.
98
- const lines = ['**Sources**']
99
- urls.slice(0, MAX_CITATION_MARKERS).forEach((url, index) => {
100
- if (index > 0) lines.push('')
101
- lines.push(`[${index + 1}] [${sourceLabel}](${url})`)
102
- })
103
- return lines.join('\n')
104
- }
105
-
106
- export class CitedSearchResult extends S.Class<CitedSearchResult>('CitedSearchResult')({
107
- query: S.String,
108
- snippets: S.Array(CitedSnippet),
109
- meta: S.optionalWith(Generated.FetchSearchMeta, { nullable: true }),
110
- }) {}
111
-
112
- export class CitedObjectModeResult extends S.Class<CitedObjectModeResult>('CitedObjectModeResult')({
113
- invocationPath: S.Literal('mcp-fetch-search'),
114
- citationInstructions: CitationInstructionsField,
115
- citationBlock: CitationBlockField,
116
- meta: Generated.FetchObjectModeMeta,
117
- markdown: S.String,
118
- json: S.Array(Generated.FetchObjectModeEvidence),
119
- data: S.optionalWith(
120
- S.Struct({
121
- assets: S.optionalWith(S.Array(Generated.FetchAssetReference), { nullable: true }),
122
- search: S.optionalWith(CitedSearchResult, { nullable: true }),
123
- }),
124
- { nullable: true },
125
- ),
126
- }) {}
127
-
128
- export class CitedSnapshotSearchResult extends S.Class<CitedSnapshotSearchResult>('CitedSnapshotSearchResult')({
129
- invocationPath: FetchInvocationPath,
130
- citationInstructions: CitationInstructionsField,
131
- citationBlock: CitationBlockField,
132
- snapshotId: S.String.pipe(S.minLength(1)),
133
- response: Generated.ResponseInfo,
134
- search: CitedSearchResult,
135
- durationMs: S.Number.pipe(S.greaterThanOrEqualTo(0)),
136
- playground: S.String.annotations({ description: 'Public playground link for the whole snapshot.' }),
137
- }) {}
138
-
139
- type SearchResultWithJson = NonNullable<NonNullable<FetchObjectModeResultWithJson['data']>['search']>
140
- type SnippetWithJson = SearchResultWithJson['snippets'][number]
141
-
142
- function toCitedSnippet(snapshotId: string, host: string, snippet: SnippetWithJson): CitedSnippet {
143
- return new CitedSnippet({
144
- type: snippet.type,
145
- source: snippet.source,
146
- text: snippet.text,
147
- json: snippet.json,
148
- score: snippet.score,
149
- location: snippet.location,
150
- citationUrl: citationUrl(snapshotId, snippet.location, host),
151
- })
152
- }
153
-
154
- function toCitedSearch(snapshotId: string, host: string, search: SearchResultWithJson): CitedSearchResult {
155
- return new CitedSearchResult({
156
- query: search.query,
157
- snippets: search.snippets.map((snippet) => toCitedSnippet(snapshotId, host, snippet)),
158
- meta: search.meta,
159
- })
160
- }
161
-
162
- /** Add `citationUrl` to each snippet of an object-mode fetch result. */
163
- export function enrichObjectModeResult(
164
- result: FetchObjectModeResultWithJson,
165
- fallbackHost: string,
166
- ): CitedObjectModeResult {
167
- // `meta.playground` is server-resolved (staging-safe); fall back to the configured host only if
168
- // it is somehow not a parseable URL.
169
- const host = resolvePlaygroundHost(result.meta.playground, fallbackHost)
170
- const data = result.data
171
- const citedSearch = data?.search == null ? undefined : toCitedSearch(result.meta.snapshotId, host, data.search)
172
- const block = citationBlock(
173
- result.meta.url,
174
- citedSearch?.snippets ?? [],
175
- markdownCitationUrls(result.markdown, result.meta.snapshotId, host),
176
- )
177
- return new CitedObjectModeResult({
178
- invocationPath: 'mcp-fetch-search',
179
- citationInstructions: CITATION_INSTRUCTIONS,
180
- citationBlock: block,
181
- meta: result.meta,
182
- // The block is appended to the content, not just offered as a sibling field.
183
- // A model summarising `markdown` carries an inline footer through; a separate
184
- // JSON field it has to notice and copy gets dropped — repeatedly, on OpenCode.
185
- markdown: block.length === 0 ? result.markdown : `${result.markdown.trimEnd()}\n\n---\n\n${block}\n`,
186
- json: result.json,
187
- data:
188
- data == null
189
- ? data
190
- : {
191
- assets: data.assets,
192
- search: citedSearch,
193
- },
194
- })
195
- }
196
-
197
- /** Add `citationUrl` to each snippet of a snapshot-search result. */
198
- export function enrichSnapshotSearchResult(
199
- result: FetchSnapshotSearchResultWithJson,
200
- fallbackHost: string,
201
- invocationPath: FetchInvocationPath = 'mcp-fetch-search',
202
- ): CitedSnapshotSearchResult {
203
- // `playground` is server-resolved (staging-safe), exactly like `meta.playground`
204
- // above; fall back to the configured host only if it is somehow not a parseable
205
- // URL. The playground is a separate deployment from the API, so the server is
206
- // the only component that can say where its snapshots are publicly served.
207
- const host = resolvePlaygroundHost(result.playground, fallbackHost)
208
- const citedSearch = toCitedSearch(result.snapshotId, host, result.search)
209
- return new CitedSnapshotSearchResult({
210
- invocationPath,
211
- citationInstructions: CITATION_INSTRUCTIONS,
212
- citationBlock: citationBlock(result.response.url, citedSearch.snippets),
213
- snapshotId: result.snapshotId,
214
- response: result.response,
215
- search: citedSearch,
216
- durationMs: result.durationMs,
217
- playground: playgroundBase(result.snapshotId, host),
218
- })
219
- }
@@ -1,203 +0,0 @@
1
- import { Tool } from '@effect/ai'
2
- import { Effect, Schema, String } from 'effect'
3
- import { ApiClient } from '../../ApiClient.js'
4
- import { ConfigManager } from '../../Config.js'
5
- import { Telemetry } from '../../Telemetry.js'
6
- import {
7
- CitedObjectModeResult,
8
- CitedSnapshotSearchResult,
9
- enrichObjectModeResult,
10
- enrichSnapshotSearchResult,
11
- } from './Citations.js'
12
-
13
- export class FetchSearchParams extends Schema.Class<FetchSearchParams>('FetchSearchParams')({
14
- query: Schema.NonEmptyTrimmedString.annotations({
15
- description: 'Search query. When provided, fetch returns search results only.',
16
- }),
17
- maxResults: Schema.optionalWith(Schema.Int.pipe(Schema.between(1, 50)), { default: () => 5 }).annotations({
18
- description: 'Maximum snippets to return.',
19
- default: 5,
20
- }),
21
- minScore: Schema.optionalWith(Schema.Number.pipe(Schema.between(0, 1)), { default: () => 0 }).annotations({
22
- description: 'Minimum snippet relevance score from 0 to 1.',
23
- default: 0,
24
- }),
25
- }) {}
26
-
27
- export class FetchParams extends Schema.Class<FetchParams>('FetchParams')({
28
- url: Schema.String.annotations({
29
- description: 'The URL to fetch content from',
30
- }),
31
- search: Schema.optional(FetchSearchParams).annotations({
32
- description: 'Semantic search configuration. When provided, the API returns snippets under data.search.snippets.',
33
- }),
34
- includeMeta: Schema.optionalWith(Schema.Boolean, { default: () => false }).annotations({
35
- description: 'Include page meta tags (title, description, Open Graph)',
36
- default: false,
37
- }),
38
- includeAppendix: Schema.optionalWith(Schema.Boolean, { default: () => false }).annotations({
39
- description: 'Include extracted links and sidebar content',
40
- default: false,
41
- }),
42
- includeJson: Schema.optionalWith(Schema.Boolean, { default: () => true }).annotations({
43
- description: 'Include JSON extracted from network responses and DOM',
44
- default: true,
45
- }),
46
- }) {}
47
-
48
- export class FetchSnapshotSearchParams extends Schema.Class<FetchSnapshotSearchParams>('FetchSnapshotSearchParams')({
49
- snapshotId: Schema.NonEmptyString.annotations({
50
- description: 'Snapshot ID returned by a previous fetch response.',
51
- }),
52
- search: FetchSearchParams.annotations({
53
- description: 'Semantic search configuration. Returns snippets from stored fetch artifacts.',
54
- }),
55
- includeMarkdown: Schema.optionalWith(Schema.Boolean, { default: () => true }).annotations({
56
- description: 'Search the stored markdown corpus.',
57
- default: true,
58
- }),
59
- includeAppendix: Schema.optionalWith(Schema.Boolean, { default: () => false }).annotations({
60
- description: 'Search extracted appendix content.',
61
- default: false,
62
- }),
63
- includeJson: Schema.optionalWith(Schema.Boolean, { default: () => true }).annotations({
64
- description: 'Search stored State JSON.',
65
- default: true,
66
- }),
67
- }) {}
68
-
69
- export const FetchTool = Tool.make('fetch', {
70
- description: String.stripMargin(`
71
- |Fetch and extract content from any URL.
72
- |
73
- |You MUST cite what you take from the result. The result carries a ready-made
74
- |'citationBlock' — append it verbatim to the end of your answer. Do not retype it, do not
75
- |substitute the URL you fetched, and do not build a link yourself: the citation points at
76
- |expand.ai's stored capture, not at the original site. Drop only the markers you did not use.
77
- |A 'Verify it' link without '?id=' is not a citation. Never invent or reuse an evidence id.
78
- |
79
- |Returns object-mode output with markdown, front-matter metadata, and optional extracted JSON.
80
- |When 'search' is set, the response also carries ranked snippets under data.search.snippets.
81
- |For State JSON sources each snippet also carries the extracted 'json' value.`),
82
- parameters: FetchParams.fields,
83
- success: CitedObjectModeResult,
84
- failure: Schema.Defect,
85
- failureMode: 'return',
86
- })
87
- .annotate(Tool.Readonly, true)
88
- .annotate(Tool.Destructive, false)
89
-
90
- export const FetchSearchTool = Tool.make('fetch_search', {
91
- description: String.stripMargin(`
92
- |Search a previous fetch by snapshotId without starting a new capture.
93
- |
94
- |You MUST cite what you take from the result. The result carries a ready-made
95
- |'citationBlock' — append it verbatim to the end of your answer. Do not retype it, do not
96
- |substitute the URL you fetched, and do not build a link yourself: the citation points at
97
- |expand.ai's stored capture, not at the original site. Drop only the markers you did not use.
98
- |A 'Verify it' link without '?id=' is not a citation. Never invent or reuse an evidence id.
99
- |
100
- |Returns ranked snippets from stored markdown, appendix, or State JSON artifacts. For State JSON
101
- |sources each snippet also carries the extracted 'json' value. The result also includes a
102
- |whole-snapshot 'playground' link.`),
103
- parameters: FetchSnapshotSearchParams.fields,
104
- success: CitedSnapshotSearchResult,
105
- failure: Schema.Defect,
106
- failureMode: 'return',
107
- })
108
- .annotate(Tool.Readonly, true)
109
- .annotate(Tool.Destructive, false)
110
-
111
- export interface FetchService {
112
- readonly fetch: (params: FetchParams) => Effect.Effect<CitedObjectModeResult, unknown>
113
- readonly fetchSearch: (params: FetchSnapshotSearchParams) => Effect.Effect<CitedSnapshotSearchResult, unknown>
114
- }
115
-
116
- export class Fetch extends Effect.Service<Fetch>()('@expandai/cli/Mcp/Fetch', {
117
- dependencies: [ApiClient.Default, ConfigManager.Default, Telemetry.Default],
118
- scoped: Effect.gen(function* () {
119
- const client = yield* ApiClient
120
- const configManager = yield* ConfigManager
121
- const telemetry = yield* Telemetry
122
- const playgroundHost = yield* configManager.getPlaygroundHost()
123
-
124
- const fetch: FetchService['fetch'] = Effect.fn('Fetch.fetch')(function* ({
125
- url,
126
- search,
127
- includeMeta,
128
- includeAppendix,
129
- includeJson,
130
- }: FetchParams) {
131
- yield* Effect.logDebug('MCP fetch started').pipe(
132
- Effect.annotateLogs({
133
- operation: 'fetch',
134
- hasSearch: search !== undefined,
135
- includeMeta,
136
- includeAppendix,
137
- includeJson,
138
- }),
139
- )
140
- yield* telemetry.capture('mcp_tool_called', { tool: 'fetch', has_search: search !== undefined })
141
-
142
- const includeParam =
143
- includeAppendix && includeJson
144
- ? 'appendix,statejson'
145
- : includeAppendix
146
- ? 'appendix'
147
- : includeJson
148
- ? undefined
149
- : ''
150
- const result = yield* client.fetchFetchJson({
151
- params: { include: includeParam },
152
- payload: {
153
- url,
154
- include: {
155
- markdown: true,
156
- meta: includeMeta,
157
- json: includeJson,
158
- appendix: includeAppendix,
159
- },
160
- search,
161
- },
162
- })
163
-
164
- yield* Effect.logDebug('MCP fetch completed').pipe(
165
- Effect.annotateLogs({
166
- operation: 'fetch',
167
- hasSearch: search !== undefined,
168
- searchResultCount: result.data?.search?.snippets.length ?? 0,
169
- jsonEvidenceCount: result.json.length,
170
- }),
171
- )
172
-
173
- return enrichObjectModeResult(result, playgroundHost)
174
- })
175
-
176
- const fetchSearch: FetchService['fetchSearch'] = Effect.fn('Fetch.fetchSearch')(function* ({
177
- snapshotId,
178
- search,
179
- includeMarkdown,
180
- includeAppendix,
181
- includeJson,
182
- }: FetchSnapshotSearchParams) {
183
- yield* Effect.logDebug('MCP snapshot search started').pipe(
184
- Effect.annotateLogs({ operation: 'fetch_search', includeMarkdown, includeAppendix, includeJson }),
185
- )
186
- yield* telemetry.capture('mcp_tool_called', { tool: 'fetch_search', has_search: true })
187
-
188
- const result = yield* client.fetchFetchSearch({
189
- snapshotId,
190
- search,
191
- include: {
192
- markdown: includeMarkdown,
193
- json: includeJson,
194
- appendix: includeAppendix,
195
- },
196
- })
197
-
198
- return enrichSnapshotSearchResult(result, playgroundHost, 'mcp-fetch-search')
199
- })
200
-
201
- return { fetch, fetchSearch } satisfies FetchService
202
- }),
203
- }) {}
package/src/SecureFile.ts DELETED
@@ -1,70 +0,0 @@
1
- import { FileSystem, Error as PlatformError } from '@effect/platform'
2
- import { Effect, Exit, Option } from 'effect'
3
-
4
- export interface CreatedFileIdentity {
5
- readonly dev: number
6
- readonly ino?: number
7
- readonly mode: number
8
- readonly type: FileSystem.File.Type
9
- }
10
-
11
- export interface ExclusiveFileCreation {
12
- readonly identity: CreatedFileIdentity
13
- readonly writeExit: Exit.Exit<void, PlatformError.PlatformError>
14
- readonly pathMatchesHandle: boolean
15
- }
16
-
17
- /** Creates a file exclusively and binds its identity to the opened handle before writing bytes. */
18
- export function createExclusiveFile(
19
- fileSystem: FileSystem.FileSystem,
20
- targetPath: string,
21
- contents: string,
22
- mode: number,
23
- ): Effect.Effect<ExclusiveFileCreation, PlatformError.PlatformError> {
24
- return Effect.scoped(
25
- Effect.gen(function* () {
26
- const file = yield* fileSystem.open(targetPath, { flag: 'wx', mode })
27
- const info = yield* file.stat
28
- const identity: CreatedFileIdentity = {
29
- dev: info.dev,
30
- ino: Option.getOrUndefined(info.ino),
31
- mode: info.mode,
32
- type: info.type,
33
- }
34
- const writeExit = yield* Effect.exit(
35
- file.writeAll(new TextEncoder().encode(contents)).pipe(Effect.zipRight(file.sync)),
36
- )
37
- if (Exit.isFailure(writeExit)) return { identity, writeExit, pathMatchesHandle: false } as const
38
-
39
- // Validate while the creating handle is still open. In particular, Windows
40
- // may reuse a deleted file's numeric identifier immediately after its final
41
- // handle closes, making a later pathname-only comparison ambiguous.
42
- const pathInfo = yield* fileSystem.stat(targetPath)
43
- const pathMatchesHandle = matchesCreatedFile(identity, {
44
- exists: true,
45
- dev: pathInfo.dev,
46
- ino: Option.getOrUndefined(pathInfo.ino),
47
- type: pathInfo.type,
48
- })
49
- return { identity, writeExit, pathMatchesHandle } as const
50
- }),
51
- )
52
- }
53
-
54
- /** Compares a pathname snapshot with identity captured from the creating file handle. */
55
- export function matchesCreatedFile(
56
- identity: CreatedFileIdentity,
57
- pathIdentity: {
58
- readonly exists: boolean
59
- readonly dev?: number
60
- readonly ino?: number
61
- readonly type?: FileSystem.File.Type
62
- },
63
- ): boolean {
64
- return (
65
- pathIdentity.exists &&
66
- pathIdentity.dev === identity.dev &&
67
- pathIdentity.ino === identity.ino &&
68
- pathIdentity.type === identity.type
69
- )
70
- }
package/src/Snippets.ts DELETED
@@ -1,39 +0,0 @@
1
- import type { FetchObjectModeResultWithJson } from '@expandai/sdk/FetchJson'
2
- import { citationUrl } from '@expandai/sdk/Playground'
3
- import { Schema } from 'effect'
4
-
5
- const encodeJson = Schema.encodeSync(Schema.parseJson(Schema.Unknown, { space: 2 }))
6
-
7
- type SearchResultWithJson = NonNullable<NonNullable<FetchObjectModeResultWithJson['data']>['search']>
8
- type RenderableSnippet = SearchResultWithJson['snippets'][number]
9
-
10
- /**
11
- * Attaches the public citation link to each snippet. JSON consumers get the same
12
- * auditable URL the text renderer prints in its header — without it, a caller has
13
- * the snapshot and the evidence id but no host to build a link against.
14
- */
15
- export function withCitationUrls<A extends RenderableSnippet>(
16
- snapshotId: string,
17
- host: string,
18
- snippets: ReadonlyArray<A>,
19
- ): Array<A & { readonly citationUrl: string }> {
20
- return snippets.map((snippet) => ({
21
- ...snippet,
22
- citationUrl: citationUrl(snapshotId, snippet.location, host),
23
- }))
24
- }
25
-
26
- /**
27
- * Render search snippets as auditable text blocks: a header carrying the source, score, and
28
- * citation link, the snippet text, and — for State JSON snippets — the extracted JSON value.
29
- */
30
- export function renderSnippets(snapshotId: string, host: string, snippets: ReadonlyArray<RenderableSnippet>): string {
31
- return snippets
32
- .map((snippet) => {
33
- const url = citationUrl(snapshotId, snippet.location, host)
34
- const header = `[${snippet.source} · score ${snippet.score.toFixed(2)}] ${url}`
35
- const json = snippet.json == null ? '' : `\n\n${encodeJson(snippet.json)}`
36
- return `${header}\n\n${snippet.text}${json}`
37
- })
38
- .join('\n\n---\n\n')
39
- }