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
package/src/Telemetry.ts DELETED
@@ -1,186 +0,0 @@
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
-
24
- // Every field reads leniently (envFlag / envLiteral / plain strings) so a
25
- // malformed env var can never fail service construction — telemetry must
26
- // silently degrade, not crash the CLI.
27
- export const TelemetryEnvironment = Config.all({
28
- // PostHog project write-only key — safe to embed as the default (it ships
29
- // in every browser bundle that uses posthog-js) and only permits ingestion.
30
- apiKey: Config.string('POSTHOG_API_KEY').pipe(Config.withDefault('phc_sffcV4skT33mMKmo0BenNoE7t7AghUrOugyW9qSx43B')),
31
- host: Config.string('POSTHOG_HOST').pipe(Config.withDefault('https://us.i.posthog.com')),
32
- doNotTrack: envFlag('DO_NOT_TRACK'),
33
- noTelemetry: envFlag('EXPANDAI_NO_TELEMETRY'),
34
- apiUrl: Config.string('EXPAND_API_URL').pipe(Config.option),
35
- // Baked into MCP/hook configs at install time; wins over ambient signals.
36
- // An unrecognized value (e.g. written by a newer installer) reads as None.
37
- harnessOverride: envLiteral('EXPANDAI_HARNESS', Harness),
38
- claudeCode: envFlag('CLAUDECODE'),
39
- cursor: Config.string('CURSOR_TRACE_ID').pipe(Config.option, Config.map(Option.isSome)),
40
- codex: Config.string('CODEX_RUN_ID').pipe(Config.option, Config.map(Option.isSome)),
41
- })
42
-
43
- type TelemetryEnvironment = Config.Config.Success<typeof TelemetryEnvironment>
44
-
45
- function detectHarness(environment: TelemetryEnvironment): Harness {
46
- if (Option.isSome(environment.harnessOverride)) return environment.harnessOverride.value
47
- if (environment.claudeCode) return 'claude-code'
48
- if (environment.cursor) return 'cursor'
49
- if (environment.codex) return 'codex'
50
- return 'unknown'
51
- }
52
-
53
- const generateDistinctId = Effect.sync(() => randomUUID())
54
-
55
- // Lenient normalization for the non-default-API comparison: `new URL()` adds
56
- // the trailing slash a bare origin string lacks, and a value that doesn't
57
- // parse compares as non-default — never crash construction, and treat unknown
58
- // endpoints as non-production traffic.
59
- function normalizeApiUrl(url: string): string {
60
- try {
61
- return new URL(url).toString()
62
- } catch {
63
- return `unparseable:${url}`
64
- }
65
- }
66
-
67
- export class Telemetry extends Effect.Service<Telemetry>()('@expandai/cli/Telemetry', {
68
- dependencies: [ConfigManager.Default, NodeHttpClient.layer],
69
- scoped: Effect.gen(function* () {
70
- const configManager = yield* ConfigManager
71
- const httpClient = yield* HttpClient.HttpClient
72
- const fibers = yield* FiberSet.make()
73
-
74
- const environment = yield* TelemetryEnvironment
75
- // EXPAND_API_KEY overrides the config file for requests (see ConfigManager),
76
- // so it must win here too or env-key users all report 'none'.
77
- const envApiKey = yield* Config.string('EXPAND_API_KEY').pipe(Config.option, Config.map(Option.isSome))
78
-
79
- // The config file is read best-effort: telemetry must work (or silently
80
- // no-op) before login and survive an unreadable config. A parse failure
81
- // disables capture for the invocation — an unreadable file must never be
82
- // mistaken for an empty one, and never be written to.
83
- const { config, parseFailed } = yield* configManager.readConfigFile()
84
- const settings = config.telemetry
85
-
86
- // Same env-over-file precedence as ConfigManager: a self-hosted apiUrl in
87
- // the config file is dev traffic too. Deliberately not Config.url /
88
- // getApiUrl — those fail on a malformed env value, and telemetry must
89
- // degrade instead of crashing the CLI.
90
- const effectiveApiUrl = Option.firstSomeOf([
91
- environment.apiUrl,
92
- Option.map(Option.fromNullable(config.apiUrl), (url) => url.toString()),
93
- ])
94
- const nonDefaultApi = Option.match(effectiveApiUrl, {
95
- onNone: () => false,
96
- onSome: (url) => normalizeApiUrl(url) !== normalizeApiUrl(DEFAULT_API_URL),
97
- })
98
-
99
- const enabled =
100
- !environment.doNotTrack &&
101
- !environment.noTelemetry &&
102
- !nonDefaultApi &&
103
- !parseFailed &&
104
- settings?.enabled !== false
105
-
106
- const harness = detectHarness(environment)
107
- const authMethod = envApiKey
108
- ? 'api-key'
109
- : config.authMethod?._tag === 'ApiKey'
110
- ? 'api-key'
111
- : config.authMethod?._tag === 'OAuth'
112
- ? 'oauth'
113
- : 'none'
114
-
115
- const persistNewDistinctId = Effect.fnUntraced(function* () {
116
- const generated = yield* generateDistinctId
117
- // Best-effort persistence — an unwritable config yields a per-run id.
118
- yield* configManager.updateConfig({ telemetry: { ...settings, distinctId: generated } }).pipe(Effect.ignore)
119
- return generated
120
- })
121
-
122
- // Disabled runs never write: DO_NOT_TRACK users don't get an id persisted,
123
- // and after a parse failure a write would clobber the original file.
124
- const distinctId = settings?.distinctId ?? (enabled ? yield* persistNewDistinctId() : yield* generateDistinctId)
125
-
126
- const capture = Effect.fn('Telemetry.capture')(function* (
127
- event: string,
128
- properties: Record<string, string | number | boolean>,
129
- ) {
130
- if (!enabled) return
131
- const post = HttpClientRequest.post(`${environment.host}/i/v0/e`).pipe(
132
- HttpClientRequest.bodyJson({
133
- api_key: environment.apiKey,
134
- event,
135
- distinct_id: distinctId,
136
- timestamp: new Date().toISOString(),
137
- properties: {
138
- $lib: 'expandai-cli',
139
- $lib_version: CLI_VERSION,
140
- version: CLI_VERSION,
141
- harness,
142
- auth_method: authMethod,
143
- ...properties,
144
- },
145
- }),
146
- )
147
- yield* FiberSet.run(
148
- fibers,
149
- post.pipe(
150
- Effect.flatMap((request) => httpClient.execute(request)),
151
- Effect.scoped,
152
- Effect.timeout('2 seconds'),
153
- Effect.ignore,
154
- ),
155
- )
156
- })
157
-
158
- // Cap delivery at flush time so short-lived processes get events out
159
- // without ever holding the process open noticeably.
160
- const flush = FiberSet.awaitEmpty(fibers).pipe(Effect.timeout('1 second'), Effect.ignore)
161
-
162
- const showNoticeIfNeeded = Effect.fn('Telemetry.showNoticeIfNeeded')(function* () {
163
- if (!enabled || settings?.noticeShown === true) return
164
- yield* Console.error(
165
- 'expandai collects anonymous usage data to improve the CLI. Opt out with `expandai telemetry disable` or EXPANDAI_NO_TELEMETRY=1.',
166
- )
167
- yield* configManager
168
- .updateConfig({ telemetry: { ...settings, distinctId, noticeShown: true } })
169
- .pipe(Effect.ignore)
170
- })
171
-
172
- return { capture, flush, showNoticeIfNeeded, enabled, distinctId } as const
173
- }),
174
- }) {
175
- /** A side-effect-free telemetry service for commands whose contract is read-only. */
176
- static readonly DISABLED = Layer.succeed(
177
- Telemetry,
178
- Telemetry.make({
179
- capture: () => Effect.void,
180
- flush: Effect.void,
181
- showNoticeIfNeeded: () => Effect.void,
182
- enabled: false,
183
- distinctId: 'disabled',
184
- }),
185
- )
186
- }
package/src/Version.ts DELETED
@@ -1,3 +0,0 @@
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.4'
package/src/Xdg.ts DELETED
@@ -1,18 +0,0 @@
1
- import { homedir } from 'node:os'
2
- import { Path } from '@effect/platform'
3
- import { Config, Effect } from 'effect'
4
-
5
- /**
6
- * XDG base-directory lookup, shared by everything that persists CLI state so
7
- * the fallback rules cannot drift apart. The spec treats an unset and an empty
8
- * variable identically: fall back to `$HOME/.config`. Without the empty check,
9
- * `XDG_CONFIG_HOME=` resolves paths relative to the working directory — which
10
- * would drop credentials inside whatever project the CLI happens to run in.
11
- */
12
- export const xdgConfigDirectory = Effect.gen(function* () {
13
- const path = yield* Path.Path
14
- return yield* Config.string('XDG_CONFIG_HOME').pipe(
15
- Config.withDefault(''),
16
- Config.map((directory) => (directory.trim() === '' ? path.join(homedir(), '.config') : directory)),
17
- )
18
- })
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from './Mcp/Server.js'
package/src/main.ts DELETED
@@ -1,305 +0,0 @@
1
- #!/usr/bin/env node
2
- import { CliConfig, Command, ValidationError } from '@effect/cli'
3
- import { Terminal } from '@effect/platform'
4
- import { NodeContext, NodeRuntime } from '@effect/platform-node'
5
- import { Cause, Config, Console, Effect, Layer, Logger, LogLevel, Option, Schema, String } from 'effect'
6
- import { ManualAuthorization } from './Auth/ManualAuthorization.js'
7
- import { PendingInitStore } from './Auth/PendingInit.js'
8
- import { SetupGrantAuthorization } from './Auth/SetupGrantAuthorization.js'
9
- import { Authentication, LoginCommand, LogoutCommand, WhoamiCommand } from './Authentication.js'
10
- import { commandFromArgv } from './CommandPath.js'
11
- import { FetchCommand } from './Commands/Fetch.js'
12
- import { AgentHookCommand, HumanHookCommand } from './Commands/Hook.js'
13
- import {
14
- AgentInitCommand,
15
- HumanInitCommand,
16
- InitTerminalInteraction,
17
- preflightAgentInitArgv,
18
- resolveInitExecutionMode,
19
- } from './Commands/Init.js'
20
- import { InstallSubCommand, UninstallSubCommand } from './Commands/Mcp.js'
21
- import { McpRunCommand } from './Commands/McpRun.js'
22
- import { SearchCommand } from './Commands/Search.js'
23
- import { SkillCommand } from './Commands/Skill.js'
24
- import {
25
- AgentStatusCommand,
26
- HumanStatusCommand,
27
- preflightAgentStatusArgv,
28
- resolveStatusExecutionMode,
29
- StatusTerminalInteraction,
30
- } from './Commands/Status.js'
31
- import { TelemetryCommand } from './Commands/Telemetry.js'
32
- import { UninstallCommand } from './Commands/Uninstall.js'
33
- import { ConfigManager, envFlag, envLiteral } from './Config.js'
34
- import { ClaudeHook } from './Hook/Claude.js'
35
- import { OpencodeHook } from './Hook/Opencode.js'
36
- import { HarnessDetector } from './Init/HarnessDetector.js'
37
- import { HarnessRegistry } from './Init/HarnessRegistry.js'
38
- import { InitService } from './Init/Service.js'
39
- import { StatusService } from './Init/Status.js'
40
- import { HookInstaller } from './Install/Hook.js'
41
- import { Mcp } from './Install/Mcp.js'
42
- import { Skill as SkillInstaller } from './Install/Skill.js'
43
- import { Uninstall } from './Install/Uninstall.js'
44
- import { Telemetry } from './Telemetry.js'
45
- import { CLI_VERSION } from './Version.js'
46
-
47
- /**
48
- * Where this process is running:
49
- * - 'human': interactive terminal use. Setup commands (`mcp install`, `skill install`) are visible.
50
- * - 'agent': an agent host (Claude Code / Cursor / Codex) or the expandai skill is invoking
51
- * the CLI. Setup commands are hidden because the integration is already wired up.
52
- * - 'mcp': the binary is serving as the MCP transport. MCP only runs in agents.
53
- */
54
-
55
- // envFlag / envLiteral read leniently: a malformed value (CLAUDECODE=banana,
56
- // EXPANDAI_CLI_CONTEXT=bogus) must degrade instead of crashing the CLI —
57
- // Config.option / Config.withDefault only absorb missing data, not invalid.
58
- const EnvironmentSignals = Config.all({
59
- override: envLiteral('EXPANDAI_CLI_CONTEXT', Schema.Literal('human', 'agent')),
60
- mcpTransport: Config.string('MCP_TRANSPORT').pipe(Config.option, Config.map(Option.isSome)),
61
- claudeCode: envFlag('CLAUDECODE'),
62
- cursor: Config.string('CURSOR_TRACE_ID').pipe(Config.option, Config.map(Option.isSome)),
63
- codex: Config.string('CODEX_RUN_ID').pipe(Config.option, Config.map(Option.isSome)),
64
- })
65
-
66
- const detectContext = Effect.fnUntraced(function* (command: string) {
67
- const initMode = resolveInitExecutionMode({
68
- argv: process.argv,
69
- environment: process.env,
70
- stdinIsTty: process.stdin.isTTY === true,
71
- stdoutIsTty: process.stdout.isTTY === true,
72
- })
73
- if (initMode !== undefined) return initMode === 'machine' ? ('agent' as const) : ('human' as const)
74
- const statusMode = resolveStatusExecutionMode({
75
- argv: process.argv,
76
- environment: process.env,
77
- stdinIsTty: process.stdin.isTTY === true,
78
- stdoutIsTty: process.stdout.isTTY === true,
79
- })
80
- if (statusMode !== undefined) return statusMode === 'machine' ? ('agent' as const) : ('human' as const)
81
-
82
- const signals = yield* EnvironmentSignals
83
-
84
- // Installed MCP entries invoke `expandai mcp run` without any transport env
85
- // var, so the resolved command is the reliable signal; MCP_TRANSPORT stays
86
- // as a secondary hint for hand-written configs that set it.
87
- const mcp = command === 'mcp run' || signals.mcpTransport ? Option.some('mcp' as const) : Option.none<'mcp'>()
88
- // Authentication.logout uses both stdout and stdin TTY checks because Prompt
89
- // reads from stdin; for context detection a non-TTY stdout alone is enough
90
- // to identify a non-interactive caller.
91
- const agent =
92
- signals.claudeCode || signals.cursor || signals.codex || !process.stdout.isTTY
93
- ? Option.some('agent' as const)
94
- : Option.none<'agent'>()
95
-
96
- return Option.firstSomeOf([mcp, signals.override, agent]).pipe(Option.getOrElse(() => 'human' as const))
97
- })
98
-
99
- const HumanMcp = Command.make('mcp').pipe(
100
- Command.withDescription('Run or install the expandai MCP server'),
101
- Command.withSubcommands([McpRunCommand, InstallSubCommand, UninstallSubCommand]),
102
- )
103
-
104
- const AgentMcp = Command.make('mcp').pipe(
105
- Command.withDescription('Run the expandai MCP server'),
106
- Command.withSubcommands([McpRunCommand]),
107
- )
108
-
109
- const InitServiceLive = InitService.DefaultWithoutDependencies.pipe(
110
- Layer.provide(
111
- Layer.mergeAll(
112
- Authentication.Default,
113
- ManualAuthorization.Default,
114
- SetupGrantAuthorization.Default,
115
- HarnessRegistry.Default,
116
- ),
117
- ),
118
- )
119
-
120
- const StatusServiceLive = StatusService.DefaultWithoutDependencies.pipe(
121
- Layer.provide(Layer.mergeAll(ConfigManager.Default, PendingInitStore.Default, HarnessRegistry.Default)),
122
- )
123
-
124
- const HumanCli = Command.make('expandai').pipe(
125
- Command.withDescription('expand.ai CLI and MCP server'),
126
- Command.withSubcommands([
127
- FetchCommand,
128
- SearchCommand,
129
- LoginCommand,
130
- WhoamiCommand,
131
- LogoutCommand,
132
- HumanInitCommand,
133
- HumanStatusCommand,
134
- UninstallCommand,
135
- HumanMcp,
136
- SkillCommand,
137
- HumanHookCommand,
138
- TelemetryCommand,
139
- ]),
140
- Command.provide(
141
- Layer.mergeAll(
142
- ConfigManager.Default,
143
- Authentication.Default,
144
- Mcp.Default,
145
- HookInstaller.Default,
146
- SkillInstaller.Default,
147
- Uninstall.Default,
148
- ClaudeHook.Default,
149
- OpencodeHook.Default,
150
- InitServiceLive,
151
- HarnessDetector.Default,
152
- InitTerminalInteraction.Default,
153
- StatusServiceLive,
154
- StatusTerminalInteraction.Default,
155
- ),
156
- ),
157
- )
158
-
159
- const AgentCli = Command.make('expandai').pipe(
160
- Command.withDescription('expand.ai CLI and MCP server'),
161
- Command.withSubcommands([
162
- FetchCommand,
163
- SearchCommand,
164
- LoginCommand,
165
- WhoamiCommand,
166
- LogoutCommand,
167
- AgentInitCommand,
168
- AgentStatusCommand,
169
- // The consent step tells the user `expandai uninstall` is how to undo
170
- // setup — including removing the machine-wide `WebFetch` deny rule. That
171
- // text is read by an agent, so the command has to exist in this tree too;
172
- // it was human-only, and an agent running it got "Invalid subcommand".
173
- UninstallCommand,
174
- AgentMcp,
175
- AgentHookCommand,
176
- TelemetryCommand,
177
- ]),
178
- Command.provide(
179
- Layer.mergeAll(
180
- ConfigManager.Default,
181
- Authentication.Default,
182
- ClaudeHook.Default,
183
- OpencodeHook.Default,
184
- Mcp.Default,
185
- HookInstaller.Default,
186
- SkillInstaller.Default,
187
- Uninstall.Default,
188
- InitServiceLive,
189
- HarnessDetector.Default,
190
- StatusServiceLive,
191
- ),
192
- ),
193
- )
194
-
195
- const LoggerLive = Layer.unwrapEffect(
196
- Effect.gen(function* () {
197
- const isDebug = yield* Config.boolean('DEBUG').pipe(Config.withDefault(false))
198
- // stdout is a protocol/data surface (hook JSON payloads, fetched
199
- // markdown) — a log line ahead of the payload corrupts it for the
200
- // consumer, so diagnostics go to stderr.
201
- return Layer.mergeAll(
202
- Logger.replace(Logger.defaultLogger, Logger.prettyLogger({ stderr: true })),
203
- Logger.minimumLogLevel(isDebug ? LogLevel.All : LogLevel.Info),
204
- )
205
- }),
206
- )
207
-
208
- // Status is a read-only diagnostic contract, and `init --detect` documents itself
209
- // as changing nothing — the guide prints "This changes nothing:" directly above
210
- // it. Select the no-op service before layer acquisition so Telemetry.Default
211
- // cannot persist a distinct id, repair config permissions, display a notice, or
212
- // send a request. A brand-new HOME came back from `--detect` with a config.json
213
- // holding a telemetry id, which is a write however small. Other commands retain
214
- // the ordinary telemetry service unchanged.
215
- const writesNothing =
216
- commandFromArgv(process.argv) === 'status' ||
217
- (commandFromArgv(process.argv) === 'init' && process.argv.includes('--detect'))
218
- const TelemetryLive = writesNothing ? Telemetry.DISABLED : Telemetry.Default
219
- const Env = Layer.mergeAll(NodeContext.layer, CliConfig.layer({ showBuiltIns: false }), LoggerLive, TelemetryLive)
220
-
221
- const errorTagFromCause = (cause: Cause.Cause<unknown>): string =>
222
- Cause.failureOption(cause).pipe(
223
- Option.match({
224
- onNone: () => 'Defect',
225
- onSome: (error) =>
226
- typeof error === 'object' && error !== null && '_tag' in error && typeof error._tag === 'string'
227
- ? error._tag
228
- : 'UnknownError',
229
- }),
230
- )
231
-
232
- function startupOnboardingPreflightError(): string | undefined {
233
- const initMode = resolveInitExecutionMode({
234
- argv: process.argv,
235
- environment: process.env,
236
- stdinIsTty: process.stdin.isTTY === true,
237
- stdoutIsTty: process.stdout.isTTY === true,
238
- })
239
- if (initMode === 'machine') return preflightAgentInitArgv(process.argv)
240
- const statusMode = resolveStatusExecutionMode({
241
- argv: process.argv,
242
- environment: process.env,
243
- stdinIsTty: process.stdin.isTTY === true,
244
- stdoutIsTty: process.stdout.isTTY === true,
245
- })
246
- return statusMode === 'machine' ? preflightAgentStatusArgv(process.argv) : undefined
247
- }
248
-
249
- const program = Effect.gen(function* () {
250
- const command = commandFromArgv(process.argv)
251
- const context = yield* detectContext(command)
252
- const telemetry = yield* Telemetry
253
- if (context === 'human') {
254
- yield* telemetry.showNoticeIfNeeded()
255
- }
256
- yield* telemetry.capture('cli_command_run', { command, context })
257
- const cli = context === 'human' ? HumanCli : AgentCli
258
- return yield* Command.run(cli, { name: 'expand.ai CLI', version: CLI_VERSION })(process.argv)
259
- }).pipe(
260
- Effect.catchAllCause((cause) => {
261
- const failure = Cause.failureOption(cause)
262
- // Ctrl-C during a `Prompt` surfaces as a `QuitException` failure rather
263
- // than a pure interrupt; treat it the same as an interrupt so we exit
264
- // cleanly instead of printing a blank "Error: " (QuitException has no message).
265
- if (Cause.isInterruptedOnly(cause) || (Option.isSome(failure) && Terminal.isQuitException(failure.value))) {
266
- return Effect.void
267
- }
268
- // `@effect/cli` already printed a help/usage message before failing with
269
- // a `ValidationError`; we only need to propagate the non-zero exit.
270
- const print = Option.match(failure, {
271
- onSome: (error) =>
272
- ValidationError.isValidationError(error) ? Effect.void : Console.error(`Error: ${(error as Error).message}`),
273
- onNone: () =>
274
- Console.error(
275
- String.stripMargin(`
276
- |Unexpected failure:
277
- |${Cause.pretty(cause)}`),
278
- ),
279
- })
280
- const debug = Effect.logDebug('Error cause').pipe(Effect.annotateLogs({ cause: Cause.pretty(cause) }))
281
- const trackFailure = Effect.flatMap(Telemetry, (telemetry) =>
282
- telemetry.capture('cli_command_failed', {
283
- command: commandFromArgv(process.argv),
284
- error_tag: errorTagFromCause(cause),
285
- }),
286
- )
287
- return print.pipe(Effect.zipRight(debug), Effect.zipRight(trackFailure), Effect.zipRight(Effect.failCause(cause)))
288
- }),
289
- // Deliver any pending telemetry before the process exits; capped at 1s by
290
- // Telemetry.flush so it can never hold a short-lived invocation open.
291
- Effect.ensuring(Effect.flatMap(Telemetry, (telemetry) => telemetry.flush)),
292
- Effect.provide(Env),
293
- )
294
-
295
- // disablePrettyLogger: runMain's own pretty logger writes to stdout, which
296
- // would put diagnostics back on the protocol/data stream LoggerLive keeps clean.
297
- const preflightError = startupOnboardingPreflightError()
298
- if (preflightError !== undefined) {
299
- // This validation must finish before the Effect runtime acquires Telemetry,
300
- // otherwise an invalid machine command could still create local state.
301
- process.stderr.write(`${preflightError}\n`)
302
- process.exitCode = 2
303
- } else {
304
- NodeRuntime.runMain(program, { disableErrorReporting: true, disablePrettyLogger: true })
305
- }