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,114 +0,0 @@
1
- import { homedir } from 'node:os'
2
- import { Config, Console, Effect, Match, Record, Schema } from 'effect'
3
- import { parse, stringify, type TomlTableWithoutBigInt } from 'smol-toml'
4
- import { CliInvocation, ConfigIo, McpParseError, McpWriteError, SERVER_KEY } from '../Domain.js'
5
- import type { InstallScope } from '../Mcp.js'
6
-
7
- export class Entry extends Schema.Class<Entry>('CodexMcpEntry')({
8
- command: Schema.NonEmptyTrimmedString,
9
- args: Schema.Array(Schema.String),
10
- env: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.String })),
11
- enabled: Schema.Boolean,
12
- }) {}
13
-
14
- function isRecord(value: unknown): value is Record<string, unknown> {
15
- return typeof value === 'object' && value !== null && !Array.isArray(value)
16
- }
17
-
18
- const getMcpServers = Effect.fn(function* (config: TomlTableWithoutBigInt) {
19
- const servers = config.mcp_servers
20
- return isRecord(servers) ? servers : {}
21
- })
22
-
23
- function toStringifyValue(value: TomlTableWithoutBigInt) {
24
- // smol-toml's stringify type accepts any; keep the unsafe boundary local.
25
- return value as unknown as Record<string, unknown>
26
- }
27
-
28
- /**
29
- * Codex MCP descriptor — Codex stores MCP servers in TOML config under
30
- * `[mcp_servers.<name>]`.
31
- *
32
- * - global → `~/.codex/config.toml`
33
- * - project → `<cwd>/.codex/config.toml`
34
- */
35
- export class CodexMcp extends Effect.Service<CodexMcp>()('@expandai/cli/Install/Codex/Mcp', {
36
- dependencies: [ConfigIo.Default, CliInvocation.Default],
37
- effect: Effect.gen(function* () {
38
- const io = yield* ConfigIo
39
- const cli = yield* CliInvocation
40
- const home = yield* Config.string('HOME').pipe(Config.withDefault(homedir()))
41
- const cwd = yield* Config.string('PWD').pipe(Config.withDefault(process.cwd()))
42
-
43
- const pathFor = Effect.fn(function* (scope: InstallScope) {
44
- return yield* Match.value(scope).pipe(
45
- Match.when('global', () => Effect.succeed(`${home}/.codex/config.toml`)),
46
- Match.when('project', () => Effect.succeed(`${cwd}/.codex/config.toml`)),
47
- Match.exhaustive,
48
- )
49
- })
50
-
51
- const readConfig = Effect.fn(function* (file: string) {
52
- const text = yield* io.readText(file)
53
- return yield* Effect.try({
54
- try: () => parse(text, { integersAsBigInt: false }),
55
- catch: (error) => new McpParseError({ path: file, error }),
56
- })
57
- })
58
-
59
- const writeConfig = Effect.fn(function* (file: string, value: TomlTableWithoutBigInt) {
60
- const encoded = yield* Effect.try({
61
- try: () => stringify(toStringifyValue(value)),
62
- catch: (error) => new McpWriteError({ path: file, error }),
63
- })
64
- yield* io.writeText(file, encoded).pipe(Effect.mapError((error) => new McpWriteError({ path: file, error })))
65
- })
66
-
67
- const install = Effect.fn(function* (scope: InstallScope) {
68
- const file = yield* pathFor(scope)
69
- const config = yield* readConfig(file)
70
- const servers = yield* getMcpServers(config)
71
-
72
- const invocation = yield* cli.resolve()
73
- const entryModel = new Entry({
74
- command: invocation.command,
75
- args: [...invocation.args, 'mcp', 'run'],
76
- env: { EXPANDAI_HARNESS: 'codex' },
77
- enabled: true,
78
- })
79
-
80
- const entry: TomlTableWithoutBigInt = {
81
- command: entryModel.command,
82
- args: [...entryModel.args],
83
- env: { ...entryModel.env },
84
- enabled: entryModel.enabled,
85
- }
86
-
87
- yield* writeConfig(file, {
88
- ...config,
89
- mcp_servers: Record.set(servers, SERVER_KEY, entry),
90
- })
91
- yield* Console.log(`Installed expandai MCP server into ${file}`)
92
- })
93
-
94
- const uninstall = Effect.fn(function* (scope: InstallScope) {
95
- const file = yield* pathFor(scope)
96
- const config = yield* readConfig(file)
97
- const servers = yield* getMcpServers(config)
98
- if (!(SERVER_KEY in servers)) {
99
- return
100
- }
101
- yield* writeConfig(file, { ...config, mcp_servers: Record.remove(servers, SERVER_KEY) })
102
- yield* Console.log(`Removed expandai MCP server from ${file}`)
103
- })
104
-
105
- const isInstalled = Effect.fn(function* (scope: InstallScope) {
106
- const file = yield* pathFor(scope)
107
- const config = yield* readConfig(file)
108
- const servers = yield* getMcpServers(config)
109
- return SERVER_KEY in servers
110
- })
111
-
112
- return { install, uninstall, isInstalled, pathFor } as const
113
- }),
114
- }) {}
@@ -1,44 +0,0 @@
1
- import { homedir } from 'node:os'
2
- import { Config, Effect, Match } from 'effect'
3
- import { SkillIo } from '../Domain.js'
4
- import type { InstallScope } from '../Mcp.js'
5
-
6
- /**
7
- * Codex skills use the open agent skills locations:
8
- *
9
- * - global → `~/.agents/skills/<name>/`
10
- * - project → `<cwd>/.agents/skills/<name>/`
11
- */
12
-
13
- export const SKILL_NAME = 'expandai'
14
-
15
- export class CodexSkill extends Effect.Service<CodexSkill>()('@expandai/cli/Install/Codex/Skill', {
16
- dependencies: [SkillIo.Default],
17
- effect: Effect.gen(function* () {
18
- const io = yield* SkillIo
19
- const home = yield* Config.string('HOME').pipe(Config.withDefault(homedir()))
20
- const cwd = yield* Config.string('PWD').pipe(Config.withDefault(process.cwd()))
21
-
22
- const pathFor = Effect.fn(function* (scope: InstallScope) {
23
- return yield* Match.value(scope).pipe(
24
- Match.when('global', () => Effect.succeed(`${home}/.agents/skills/${SKILL_NAME}`)),
25
- Match.when('project', () => Effect.succeed(`${cwd}/.agents/skills/${SKILL_NAME}`)),
26
- Match.exhaustive,
27
- )
28
- })
29
-
30
- const install = Effect.fn(function* (scope: InstallScope) {
31
- return yield* io.install(yield* pathFor(scope))
32
- })
33
-
34
- const uninstall = Effect.fn(function* (scope: InstallScope) {
35
- return yield* io.uninstall(yield* pathFor(scope))
36
- })
37
-
38
- const isCurrent = Effect.fn(function* (scope: InstallScope) {
39
- return yield* io.isCurrent(yield* pathFor(scope))
40
- })
41
-
42
- return { install, uninstall, isCurrent, pathFor } as const
43
- }),
44
- }) {}
@@ -1,85 +0,0 @@
1
- import { homedir } from 'node:os'
2
- import { Config, Console, Effect, Match, Record, Schema } from 'effect'
3
- import { CliInvocation, ConfigIo, McpParseError, McpServersConfigSchema, McpWriteError, SERVER_KEY } from '../Domain.js'
4
- import type { InstallScope } from '../Mcp.js'
5
-
6
- /**
7
- * Cursor MCP descriptor — same `mcpServers` shape as Claude Code, different
8
- * paths.
9
- *
10
- * - global → `~/.cursor/mcp.json`
11
- * - project → `<cwd>/.cursor/mcp.json`
12
- */
13
- export class Entry extends Schema.Class<Entry>('CursorMcpEntry')({
14
- type: Schema.Literal('stdio'),
15
- command: Schema.NonEmptyTrimmedString,
16
- args: Schema.Array(Schema.String),
17
- env: Schema.optional(Schema.Record({ key: Schema.String, value: Schema.String })),
18
- }) {}
19
-
20
- export class CursorMcp extends Effect.Service<CursorMcp>()('@expandai/cli/Install/Cursor/Mcp', {
21
- dependencies: [ConfigIo.Default, CliInvocation.Default],
22
- effect: Effect.gen(function* () {
23
- const io = yield* ConfigIo
24
- const cli = yield* CliInvocation
25
- const home = yield* Config.string('HOME').pipe(Config.withDefault(homedir()))
26
- const cwd = yield* Config.string('PWD').pipe(Config.withDefault(process.cwd()))
27
-
28
- const pathFor = Effect.fn(function* (scope: InstallScope) {
29
- return yield* Match.value(scope).pipe(
30
- Match.when('global', () => Effect.succeed(`${home}/.cursor/mcp.json`)),
31
- Match.when('project', () => Effect.succeed(`${cwd}/.cursor/mcp.json`)),
32
- Match.exhaustive,
33
- )
34
- })
35
-
36
- const readConfig = Effect.fn(function* (file: string) {
37
- return yield* io
38
- .read(file, McpServersConfigSchema)
39
- .pipe(Effect.mapError((error) => new McpParseError({ path: file, error })))
40
- })
41
-
42
- const writeConfig = Effect.fn(function* (file: string, value: unknown) {
43
- yield* io
44
- .write(file, value, McpServersConfigSchema)
45
- .pipe(Effect.mapError((error) => new McpWriteError({ path: file, error })))
46
- })
47
-
48
- const install = Effect.fn(function* (scope: InstallScope) {
49
- const file = yield* pathFor(scope)
50
- const config = yield* readConfig(file)
51
- const invocation = yield* cli.resolve()
52
- yield* writeConfig(file, {
53
- ...config,
54
- mcpServers: {
55
- ...config.mcpServers,
56
- [SERVER_KEY]: new Entry({
57
- type: 'stdio',
58
- command: invocation.command,
59
- args: [...invocation.args, 'mcp', 'run'],
60
- env: { EXPANDAI_HARNESS: 'cursor' },
61
- }),
62
- },
63
- })
64
- yield* Console.log(`Installed expandai MCP server into ${file}`)
65
- })
66
-
67
- const uninstall = Effect.fn(function* (scope: InstallScope) {
68
- const file = yield* pathFor(scope)
69
- const config = yield* readConfig(file)
70
- if (!(SERVER_KEY in config.mcpServers)) {
71
- return
72
- }
73
- yield* writeConfig(file, { ...config, mcpServers: Record.remove(config.mcpServers, SERVER_KEY) })
74
- yield* Console.log(`Removed expandai MCP server from ${file}`)
75
- })
76
-
77
- const isInstalled = Effect.fn(function* (scope: InstallScope) {
78
- const file = yield* pathFor(scope)
79
- const config = yield* readConfig(file)
80
- return SERVER_KEY in config.mcpServers
81
- })
82
-
83
- return { install, uninstall, isInstalled, pathFor } as const
84
- }),
85
- }) {}
@@ -1,51 +0,0 @@
1
- import { homedir } from 'node:os'
2
- import { Config, Effect, Match } from 'effect'
3
- import { SkillIo } from '../Domain.js'
4
- import type { InstallScope } from '../Mcp.js'
5
-
6
- /**
7
- * Cursor skills are auto-loaded from these locations (per
8
- * cursor.com/docs/skills):
9
- *
10
- * - global → `~/.cursor/skills/<name>/`
11
- * - project → `<cwd>/.cursor/skills/<name>/`
12
- *
13
- * Cursor also compat-loads `.claude/skills/`, `.codex/skills/`, `.agents/skills/`
14
- * at both project and user scope, but we ship to Cursor's native paths.
15
- *
16
- * Format is the same SKILL.md + YAML frontmatter open standard as Claude Code
17
- * and OpenCode, so the bundled `skills/expandai/` directory copies in unchanged.
18
- */
19
-
20
- export const SKILL_NAME = 'expandai'
21
-
22
- export class CursorSkill extends Effect.Service<CursorSkill>()('@expandai/cli/Install/Cursor/Skill', {
23
- dependencies: [SkillIo.Default],
24
- effect: Effect.gen(function* () {
25
- const io = yield* SkillIo
26
- const home = yield* Config.string('HOME').pipe(Config.withDefault(homedir()))
27
- const cwd = yield* Config.string('PWD').pipe(Config.withDefault(process.cwd()))
28
-
29
- const pathFor = Effect.fn(function* (scope: InstallScope) {
30
- return yield* Match.value(scope).pipe(
31
- Match.when('global', () => Effect.succeed(`${home}/.cursor/skills/${SKILL_NAME}`)),
32
- Match.when('project', () => Effect.succeed(`${cwd}/.cursor/skills/${SKILL_NAME}`)),
33
- Match.exhaustive,
34
- )
35
- })
36
-
37
- const install = Effect.fn(function* (scope: InstallScope) {
38
- return yield* io.install(yield* pathFor(scope))
39
- })
40
-
41
- const uninstall = Effect.fn(function* (scope: InstallScope) {
42
- return yield* io.uninstall(yield* pathFor(scope))
43
- })
44
-
45
- const isCurrent = Effect.fn(function* (scope: InstallScope) {
46
- return yield* io.isCurrent(yield* pathFor(scope))
47
- })
48
-
49
- return { install, uninstall, isCurrent, pathFor } as const
50
- }),
51
- }) {}
@@ -1,221 +0,0 @@
1
- import { FileSystem, Path } from '@effect/platform'
2
- import { NodeFileSystem, NodePath } from '@effect/platform-node'
3
- import { Array, Config, Effect, Inspectable, Layer, Schema, String } from 'effect'
4
-
5
- export class McpParseError extends Schema.TaggedError<McpParseError>()('McpParseError', {
6
- path: Schema.String,
7
- error: Schema.Defect,
8
- }) {
9
- get message() {
10
- return `Failed to parse MCP config at ${this.path}: ${Inspectable.format(this.error)}`
11
- }
12
- }
13
-
14
- export class McpWriteError extends Schema.TaggedError<McpWriteError>()('McpWriteError', {
15
- path: Schema.String,
16
- error: Schema.Defect,
17
- }) {
18
- get message() {
19
- return `Failed to write MCP config at ${this.path}: ${Inspectable.format(this.error)}`
20
- }
21
- }
22
-
23
- export const Host = Schema.Literal('claude-code', 'cursor', 'opencode', 'codex')
24
- export type Host = typeof Host.Type
25
-
26
- /**
27
- * The agent harness invoking the CLI — every install target plus 'unknown'
28
- * for uninstrumented callers. Installers bake `EXPANDAI_HARNESS=<host>` into
29
- * MCP/hook configs; telemetry reads it back.
30
- */
31
- export const Harness = Schema.Literal(...Host.literals, 'unknown')
32
- export type Harness = typeof Harness.Type
33
-
34
- export const InstallScope = Schema.Literal('project', 'global')
35
- export type InstallScope = typeof InstallScope.Type
36
-
37
- const McpServerMap = Schema.Record({ key: Schema.String, value: Schema.Unknown })
38
-
39
- export const McpServersConfigSchema = Schema.parseJson(
40
- Schema.Struct({ mcpServers: Schema.optionalWith(McpServerMap, { default: () => ({}) }) }, McpServerMap),
41
- { space: 2 },
42
- )
43
-
44
- export const OpencodeConfigSchema = Schema.parseJson(
45
- Schema.Struct({ mcp: Schema.optionalWith(McpServerMap, { default: () => ({}) }) }, McpServerMap),
46
- { space: 2 },
47
- )
48
-
49
- export const SERVER_KEY = 'expandai'
50
-
51
- /** How a host config should launch the CLI: `command` plus leading args. */
52
- export interface CliCommand {
53
- readonly command: string
54
- readonly args: ReadonlyArray<string>
55
- }
56
-
57
- export const DIRECT_CLI: CliCommand = { command: 'expandai', args: [] }
58
- const NPX_CLI: CliCommand = { command: 'npx', args: ['-y', 'expandai'] }
59
-
60
- /**
61
- * Renders a `CliCommand` as the single shell string Claude Code stores for hook
62
- * entries. MCP descriptors keep `command`/`args` separate because they exec
63
- * directly; hooks are run through a shell, so they need one flat line.
64
- */
65
- export function cliShellCommand(cli: CliCommand, args: ReadonlyArray<string>): string {
66
- return [cli.command, ...cli.args, ...args].join(' ')
67
- }
68
-
69
- /**
70
- * Resolves how installed host configs should invoke the expandai CLI. MCP
71
- * entries exec their `command` directly (no shell), so the fallback decision
72
- * happens at install time: prefer the `expandai` binary when it is on PATH,
73
- * otherwise write an `npx -y expandai` invocation so installs performed via
74
- * `npx expandai …` keep working without a global CLI install.
75
- */
76
- const makeCliInvocation = Effect.gen(function* () {
77
- const fs = yield* FileSystem.FileSystem
78
- const path = yield* Path.Path
79
- const searchPath = yield* Config.string('PATH').pipe(Config.withDefault(''))
80
-
81
- const resolve = Effect.fn('CliInvocation.resolve')(function* () {
82
- // ':' is the POSIX PATH delimiter — the install flow already assumes
83
- // POSIX conventions (HOME-based config paths, shell hooks).
84
- const onPath = yield* Effect.exists(Array.filter(searchPath.split(':'), String.isNonEmpty), (dir) =>
85
- fs.exists(path.join(dir, 'expandai')).pipe(Effect.orElseSucceed(() => false)),
86
- )
87
- return onPath ? DIRECT_CLI : NPX_CLI
88
- })
89
-
90
- return { resolve } as const
91
- })
92
-
93
- export class CliInvocation extends Effect.Service<CliInvocation>()('@expandai/cli/Install/CliInvocation', {
94
- dependencies: [NodeFileSystem.layer, NodePath.layer],
95
- effect: makeCliInvocation,
96
- }) {
97
- /** Dependency-open layer so fixtures can supply their own file system and PATH. */
98
- static readonly layer = Layer.effect(CliInvocation, makeCliInvocation.pipe(Effect.map(CliInvocation.make)))
99
- }
100
-
101
- export const readConfigText = Effect.fn(function* (file: string) {
102
- const fs = yield* FileSystem.FileSystem
103
- const exists = yield* fs.exists(file)
104
- if (!exists) {
105
- return '{}'
106
- }
107
- return yield* fs.readFileString(file)
108
- })
109
-
110
- export const writeConfigText = Effect.fn(function* (file: string, text: string) {
111
- const fs = yield* FileSystem.FileSystem
112
- const path = yield* Path.Path
113
- yield* fs.makeDirectory(path.dirname(file), { recursive: true })
114
- yield* fs.writeFileString(file, `${text}\n`)
115
- })
116
-
117
- /**
118
- * Captures `FileSystem` + `Path` once and exposes string-encoded JSON read/
119
- * write methods that don't re-yield them on every call. Use this from inside
120
- * an `Effect.Service` gen block so the install/uninstall methods don't have
121
- * to re-thread `provideService` calls.
122
- */
123
- export class ConfigIo extends Effect.Service<ConfigIo>()('@expandai/cli/Install/ConfigIo', {
124
- dependencies: [NodeFileSystem.layer, NodePath.layer],
125
- effect: Effect.gen(function* () {
126
- const fs = yield* FileSystem.FileSystem
127
- const path = yield* Path.Path
128
-
129
- const read = Effect.fn('ConfigIo.read')(function* <A>(file: string, schema: Schema.Schema<A, string>) {
130
- const text = yield* readConfigText(file).pipe(Effect.provideService(FileSystem.FileSystem, fs))
131
- return yield* Schema.decode(schema)(text)
132
- })
133
-
134
- const write = Effect.fn('ConfigIo.write')(function* <A>(
135
- file: string,
136
- value: unknown,
137
- schema: Schema.Schema<A, string>,
138
- ) {
139
- const encoded = yield* Schema.encodeUnknown(schema)(value)
140
- yield* writeConfigText(file, encoded).pipe(
141
- Effect.provideService(FileSystem.FileSystem, fs),
142
- Effect.provideService(Path.Path, path),
143
- )
144
- })
145
-
146
- const exists = Effect.fn('ConfigIo.exists')(function* (file: string) {
147
- return yield* fs.exists(file)
148
- })
149
-
150
- // Raw text I/O for formats `ConfigIo.read`/`write` can't handle through a
151
- // `Schema.parseJson(...)` schema (e.g. Codex's TOML). Missing file reads
152
- // as an empty string — callers attach their own parser-specific default.
153
- const readText = Effect.fn('ConfigIo.readText')(function* (file: string) {
154
- const present = yield* fs.exists(file)
155
- return present ? yield* fs.readFileString(file) : ''
156
- })
157
-
158
- const writeText = Effect.fn('ConfigIo.writeText')(function* (file: string, text: string) {
159
- yield* writeConfigText(file, text).pipe(
160
- Effect.provideService(FileSystem.FileSystem, fs),
161
- Effect.provideService(Path.Path, path),
162
- )
163
- })
164
-
165
- return { read, write, exists, readText, writeText } as const
166
- }),
167
- }) {}
168
-
169
- /**
170
- * Captures `FileSystem` + `Path` once and exposes skill directory copy/remove
171
- * operations. The bundled `SKILL.md` lives at `<cli>/skills/expandai/` (or
172
- * `<build>/skills/expandai/` in production); this service computes that path
173
- * from its own `import.meta.url`, so per-host skill descriptors only need to
174
- * provide `pathFor` and call `io.install(target)` / `io.uninstall(target)`.
175
- */
176
- export class SkillIo extends Effect.Service<SkillIo>()('@expandai/cli/Install/SkillIo', {
177
- dependencies: [NodeFileSystem.layer, NodePath.layer],
178
- effect: Effect.gen(function* () {
179
- const fs = yield* FileSystem.FileSystem
180
- const path = yield* Path.Path
181
-
182
- const sourceDir = path.resolve(
183
- path.dirname(yield* path.fromFileUrl(new URL(import.meta.url))),
184
- '..',
185
- '..',
186
- 'skills',
187
- 'expandai',
188
- )
189
-
190
- const install = Effect.fn('SkillIo.install')(function* (target: string) {
191
- yield* fs.makeDirectory(path.dirname(target), { recursive: true })
192
- yield* fs.copy(sourceDir, target, { overwrite: true })
193
- return target
194
- })
195
-
196
- const uninstall = Effect.fn('SkillIo.uninstall')(function* (target: string) {
197
- const exists = yield* fs.exists(target)
198
- if (exists) {
199
- yield* fs.remove(target, { recursive: true })
200
- }
201
- return target
202
- })
203
-
204
- /**
205
- * Whether the installed skill matches the bundled one. Presence alone is not
206
- * enough: an upgraded CLI ships new skill text, and a harness whose install is
207
- * gated on "the file exists" would keep serving the old instructions forever.
208
- */
209
- const isCurrent = Effect.fn('SkillIo.isCurrent')(function* (target: string) {
210
- const targetFile = path.join(target, 'SKILL.md')
211
- if (!(yield* fs.exists(targetFile))) return false
212
- const [installed, bundled] = yield* Effect.all([
213
- fs.readFileString(targetFile),
214
- fs.readFileString(path.join(sourceDir, 'SKILL.md')),
215
- ])
216
- return installed === bundled
217
- })
218
-
219
- return { install, uninstall, isCurrent } as const
220
- }),
221
- }) {}
@@ -1,50 +0,0 @@
1
- import { Effect, Match, Schema } from 'effect'
2
- import { ClaudeHook } from './Claude/Hook.js'
3
- import { type InstallScope, Mcp } from './Mcp.js'
4
- import { OpencodeHook } from './Opencode/Hook.js'
5
-
6
- /**
7
- * Hook install dispatcher. Bundles MCP install + per-host hook config:
8
- *
9
- * - claude-code: writes the matcher into `~/.claude/settings.json` (global)
10
- * or `<cwd>/.claude/settings.json` (project), then registers expandai as
11
- * an MCP server at the matching scope.
12
- * - opencode: writes the webfetch plugin file into `.opencode/plugins/`
13
- * (project) or `~/.config/opencode/plugins/` (global), plus registers
14
- * expandai as an MCP server.
15
- *
16
- * Cursor has no hook system per opencode.ai/docs and Cursor's docs, so it's
17
- * absent from this Host union (compare with `Mcp.Host` which includes Cursor).
18
- */
19
- export const HookHost = Schema.Literal('claude-code', 'opencode')
20
- export type HookHost = typeof HookHost.Type
21
-
22
- export class HookInstaller extends Effect.Service<HookInstaller>()('@expandai/cli/Install/Hook', {
23
- dependencies: [Mcp.Default, ClaudeHook.Default, OpencodeHook.Default],
24
- effect: Effect.gen(function* () {
25
- const mcp = yield* Mcp
26
- const claudeHook = yield* ClaudeHook
27
- const opencodeHook = yield* OpencodeHook
28
-
29
- const install = Effect.fn('HookInstaller.install')(function* (host: HookHost, scope: InstallScope) {
30
- // Bundle: MCP install first, then host-specific hook config.
31
- yield* mcp.install(host, scope)
32
- yield* Match.value(host).pipe(
33
- Match.when('claude-code', () => claudeHook.install(scope)),
34
- Match.when('opencode', () => opencodeHook.install(scope)),
35
- Match.exhaustive,
36
- )
37
- })
38
-
39
- const uninstall = Effect.fn('HookInstaller.uninstall')(function* (host: HookHost, scope: InstallScope) {
40
- yield* Match.value(host).pipe(
41
- Match.when('claude-code', () => claudeHook.uninstall(scope)),
42
- Match.when('opencode', () => opencodeHook.uninstall(scope)),
43
- Match.exhaustive,
44
- )
45
- yield* mcp.uninstall(host, scope)
46
- })
47
-
48
- return { install, uninstall } as const
49
- }),
50
- }) {}
@@ -1,71 +0,0 @@
1
- import { Effect, Match } from 'effect'
2
- import { ClaudeMcp } from './Claude/Mcp.js'
3
- import { CodexMcp } from './Codex/Mcp.js'
4
- import { CursorMcp } from './Cursor/Mcp.js'
5
- import type { Host, InstallScope } from './Domain.js'
6
- import { OpencodeMcp } from './Opencode/Mcp.js'
7
-
8
- export { Host, InstallScope, McpParseError, McpWriteError } from './Domain.js'
9
-
10
- /**
11
- * MCP install dispatcher. Per-host descriptors live in `Install/<Host>/Mcp.ts`
12
- * and contribute their path resolution + entry shape; this service handles
13
- * the file I/O (read existing config, merge, write back) and routes by host.
14
- *
15
- * - claude-code (~/.claude.json | <cwd>/.mcp.json) → mcpServers shape
16
- * - cursor (~/.cursor/mcp.json | <cwd>/.cursor/mcp.json) → mcpServers
17
- * - opencode (~/.config/opencode/opencode.json | <cwd>/opencode.json) → opencode `mcp` shape
18
- * - codex (~/.codex/config.toml | <cwd>/.codex/config.toml) → Codex TOML `mcp_servers`
19
- */
20
-
21
- export class Mcp extends Effect.Service<Mcp>()('@expandai/cli/Install/Mcp', {
22
- dependencies: [ClaudeMcp.Default, CursorMcp.Default, OpencodeMcp.Default, CodexMcp.Default],
23
- effect: Effect.gen(function* () {
24
- const claude = yield* ClaudeMcp
25
- const cursor = yield* CursorMcp
26
- const opencode = yield* OpencodeMcp
27
- const codex = yield* CodexMcp
28
-
29
- const pathFor = Effect.fn('Mcp.pathFor')(function* (host: Host, scope: InstallScope) {
30
- return yield* Match.value(host).pipe(
31
- Match.when('claude-code', () => claude.pathFor(scope)),
32
- Match.when('cursor', () => cursor.pathFor(scope)),
33
- Match.when('opencode', () => opencode.pathFor(scope)),
34
- Match.when('codex', () => codex.pathFor(scope)),
35
- Match.exhaustive,
36
- )
37
- })
38
-
39
- const install = Effect.fn('Mcp.install')(function* (host: Host, scope: InstallScope) {
40
- yield* Match.value(host).pipe(
41
- Match.when('claude-code', () => claude.install(scope)),
42
- Match.when('cursor', () => cursor.install(scope)),
43
- Match.when('opencode', () => opencode.install(scope)),
44
- Match.when('codex', () => codex.install(scope)),
45
- Match.exhaustive,
46
- )
47
- })
48
-
49
- const uninstall = Effect.fn('Mcp.uninstall')(function* (host: Host, scope: InstallScope) {
50
- yield* Match.value(host).pipe(
51
- Match.when('claude-code', () => claude.uninstall(scope)),
52
- Match.when('cursor', () => cursor.uninstall(scope)),
53
- Match.when('opencode', () => opencode.uninstall(scope)),
54
- Match.when('codex', () => codex.uninstall(scope)),
55
- Match.exhaustive,
56
- )
57
- })
58
-
59
- const isInstalled = Effect.fn('Mcp.isInstalled')(function* (host: Host, scope: InstallScope) {
60
- return yield* Match.value(host).pipe(
61
- Match.when('claude-code', () => claude.isInstalled(scope)),
62
- Match.when('cursor', () => cursor.isInstalled(scope)),
63
- Match.when('opencode', () => opencode.isInstalled(scope)),
64
- Match.when('codex', () => codex.isInstalled(scope)),
65
- Match.exhaustive,
66
- )
67
- })
68
-
69
- return { install, uninstall, isInstalled, pathFor } as const
70
- }),
71
- }) {}