expandai 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/README.md +42 -5
  2. package/build/dts/ApiClient.d.ts +128 -194
  3. package/build/dts/ApiClient.d.ts.map +1 -1
  4. package/build/dts/Auth/Domain.d.ts +95 -0
  5. package/build/dts/Auth/Domain.d.ts.map +1 -0
  6. package/build/dts/Auth/ManualAuthorization.d.ts +107 -0
  7. package/build/dts/Auth/ManualAuthorization.d.ts.map +1 -0
  8. package/build/dts/Auth/PendingInit.d.ts +35 -0
  9. package/build/dts/Auth/PendingInit.d.ts.map +1 -0
  10. package/build/dts/Auth/Pkce.d.ts +10 -0
  11. package/build/dts/Auth/Pkce.d.ts.map +1 -0
  12. package/build/dts/Auth/SetupGrantAuthorization.d.ts +34 -0
  13. package/build/dts/Auth/SetupGrantAuthorization.d.ts.map +1 -0
  14. package/build/dts/Authentication.d.ts +10 -9
  15. package/build/dts/Authentication.d.ts.map +1 -1
  16. package/build/dts/BrowserOAuth.d.ts +7 -7
  17. package/build/dts/BrowserOAuth.d.ts.map +1 -1
  18. package/build/dts/CommandPath.d.ts +2 -0
  19. package/build/dts/CommandPath.d.ts.map +1 -0
  20. package/build/dts/Commands/Fetch.d.ts.map +1 -1
  21. package/build/dts/Commands/Hook.d.ts +5 -5
  22. package/build/dts/Commands/Init.d.ts +55 -0
  23. package/build/dts/Commands/Init.d.ts.map +1 -0
  24. package/build/dts/Commands/Mcp.d.ts +6 -6
  25. package/build/dts/Commands/Search.d.ts.map +1 -1
  26. package/build/dts/Commands/Skill.d.ts +4 -4
  27. package/build/dts/Commands/Status.d.ts +27 -0
  28. package/build/dts/Commands/Status.d.ts.map +1 -0
  29. package/build/dts/Commands/Telemetry.d.ts +7 -0
  30. package/build/dts/Commands/Telemetry.d.ts.map +1 -0
  31. package/build/dts/Commands/Uninstall.d.ts +4 -4
  32. package/build/dts/Config.d.ts +108 -17
  33. package/build/dts/Config.d.ts.map +1 -1
  34. package/build/dts/Hook/SnapshotNote.d.ts +8 -4
  35. package/build/dts/Hook/SnapshotNote.d.ts.map +1 -1
  36. package/build/dts/Init/ClaudeAdapter.d.ts +61 -0
  37. package/build/dts/Init/ClaudeAdapter.d.ts.map +1 -0
  38. package/build/dts/Init/ClaudeProfile.d.ts +78 -0
  39. package/build/dts/Init/ClaudeProfile.d.ts.map +1 -0
  40. package/build/dts/Init/Domain.d.ts +139 -0
  41. package/build/dts/Init/Domain.d.ts.map +1 -0
  42. package/build/dts/Init/HarnessAdapter.d.ts +33 -0
  43. package/build/dts/Init/HarnessAdapter.d.ts.map +1 -0
  44. package/build/dts/Init/HarnessDetector.d.ts +19 -0
  45. package/build/dts/Init/HarnessDetector.d.ts.map +1 -0
  46. package/build/dts/Init/HarnessRegistry.d.ts +50 -0
  47. package/build/dts/Init/HarnessRegistry.d.ts.map +1 -0
  48. package/build/dts/Init/MutationTransaction.d.ts +96 -0
  49. package/build/dts/Init/MutationTransaction.d.ts.map +1 -0
  50. package/build/dts/Init/Service.d.ts +72 -0
  51. package/build/dts/Init/Service.d.ts.map +1 -0
  52. package/build/dts/Init/StandardAdapters.d.ts +42 -0
  53. package/build/dts/Init/StandardAdapters.d.ts.map +1 -0
  54. package/build/dts/Init/Status.d.ts +149 -0
  55. package/build/dts/Init/Status.d.ts.map +1 -0
  56. package/build/dts/Install/Claude/Hook.d.ts +85 -25
  57. package/build/dts/Install/Claude/Hook.d.ts.map +1 -1
  58. package/build/dts/Install/Claude/Mcp.d.ts +21 -18
  59. package/build/dts/Install/Claude/Mcp.d.ts.map +1 -1
  60. package/build/dts/Install/Claude/Skill.d.ts +14 -15
  61. package/build/dts/Install/Claude/Skill.d.ts.map +1 -1
  62. package/build/dts/Install/Codex/Mcp.d.ts +14 -8
  63. package/build/dts/Install/Codex/Mcp.d.ts.map +1 -1
  64. package/build/dts/Install/Codex/Skill.d.ts +4 -2
  65. package/build/dts/Install/Codex/Skill.d.ts.map +1 -1
  66. package/build/dts/Install/Cursor/Mcp.d.ts +13 -13
  67. package/build/dts/Install/Cursor/Mcp.d.ts.map +1 -1
  68. package/build/dts/Install/Cursor/Skill.d.ts +4 -2
  69. package/build/dts/Install/Cursor/Skill.d.ts.map +1 -1
  70. package/build/dts/Install/Domain.d.ts +35 -5
  71. package/build/dts/Install/Domain.d.ts.map +1 -1
  72. package/build/dts/Install/Hook.d.ts +4 -4
  73. package/build/dts/Install/Hook.d.ts.map +1 -1
  74. package/build/dts/Install/Mcp.d.ts +5 -5
  75. package/build/dts/Install/Mcp.d.ts.map +1 -1
  76. package/build/dts/Install/Opencode/Hook.d.ts +4 -3
  77. package/build/dts/Install/Opencode/Hook.d.ts.map +1 -1
  78. package/build/dts/Install/Opencode/Mcp.d.ts +11 -11
  79. package/build/dts/Install/Opencode/Mcp.d.ts.map +1 -1
  80. package/build/dts/Install/Opencode/Skill.d.ts +4 -2
  81. package/build/dts/Install/Opencode/Skill.d.ts.map +1 -1
  82. package/build/dts/Install/Skill.d.ts +3 -3
  83. package/build/dts/Install/Uninstall.d.ts +6 -6
  84. package/build/dts/Install/Uninstall.d.ts.map +1 -1
  85. package/build/dts/MarkdownAnsi.d.ts +29 -0
  86. package/build/dts/MarkdownAnsi.d.ts.map +1 -0
  87. package/build/dts/Mcp/ExpandDocs.d.ts +17 -5
  88. package/build/dts/Mcp/ExpandDocs.d.ts.map +1 -1
  89. package/build/dts/Mcp/Server.d.ts +1 -1
  90. package/build/dts/Mcp/Server.d.ts.map +1 -1
  91. package/build/dts/Mcp/tools/Citations.d.ts +100 -71
  92. package/build/dts/Mcp/tools/Citations.d.ts.map +1 -1
  93. package/build/dts/Mcp/tools/Fetch.d.ts +16 -15
  94. package/build/dts/Mcp/tools/Fetch.d.ts.map +1 -1
  95. package/build/dts/SecureFile.d.ts +23 -0
  96. package/build/dts/SecureFile.d.ts.map +1 -0
  97. package/build/dts/Snippets.d.ts +11 -12
  98. package/build/dts/Snippets.d.ts.map +1 -1
  99. package/build/dts/Telemetry.d.ts +30 -0
  100. package/build/dts/Telemetry.d.ts.map +1 -0
  101. package/build/dts/Version.d.ts +2 -0
  102. package/build/dts/Version.d.ts.map +1 -0
  103. package/build/dts/Xdg.d.ts +11 -0
  104. package/build/dts/Xdg.d.ts.map +1 -0
  105. package/build/esm/ApiClient.js +133 -10
  106. package/build/esm/ApiClient.js.map +1 -1
  107. package/build/esm/Auth/Domain.js +56 -0
  108. package/build/esm/Auth/Domain.js.map +1 -0
  109. package/build/esm/Auth/ManualAuthorization.js +253 -0
  110. package/build/esm/Auth/ManualAuthorization.js.map +1 -0
  111. package/build/esm/Auth/PendingInit.js +267 -0
  112. package/build/esm/Auth/PendingInit.js.map +1 -0
  113. package/build/esm/Auth/Pkce.js +23 -0
  114. package/build/esm/Auth/Pkce.js.map +1 -0
  115. package/build/esm/Auth/SetupGrantAuthorization.js +91 -0
  116. package/build/esm/Auth/SetupGrantAuthorization.js.map +1 -0
  117. package/build/esm/Authentication.js +38 -7
  118. package/build/esm/Authentication.js.map +1 -1
  119. package/build/esm/BrowserOAuth.js +7 -10
  120. package/build/esm/BrowserOAuth.js.map +1 -1
  121. package/build/esm/CommandPath.js +31 -0
  122. package/build/esm/CommandPath.js.map +1 -0
  123. package/build/esm/Commands/Fetch.js +30 -6
  124. package/build/esm/Commands/Fetch.js.map +1 -1
  125. package/build/esm/Commands/Init.js +371 -0
  126. package/build/esm/Commands/Init.js.map +1 -0
  127. package/build/esm/Commands/Search.js +6 -3
  128. package/build/esm/Commands/Search.js.map +1 -1
  129. package/build/esm/Commands/Status.js +186 -0
  130. package/build/esm/Commands/Status.js.map +1 -0
  131. package/build/esm/Commands/Telemetry.js +23 -0
  132. package/build/esm/Commands/Telemetry.js.map +1 -0
  133. package/build/esm/Config.js +502 -28
  134. package/build/esm/Config.js.map +1 -1
  135. package/build/esm/Hook/Claude.js +1 -1
  136. package/build/esm/Hook/Claude.js.map +1 -1
  137. package/build/esm/Hook/SnapshotNote.js +54 -7
  138. package/build/esm/Hook/SnapshotNote.js.map +1 -1
  139. package/build/esm/Init/ClaudeAdapter.js +137 -0
  140. package/build/esm/Init/ClaudeAdapter.js.map +1 -0
  141. package/build/esm/Init/ClaudeProfile.js +380 -0
  142. package/build/esm/Init/ClaudeProfile.js.map +1 -0
  143. package/build/esm/Init/Domain.js +118 -0
  144. package/build/esm/Init/Domain.js.map +1 -0
  145. package/build/esm/Init/HarnessAdapter.js +21 -0
  146. package/build/esm/Init/HarnessAdapter.js.map +1 -0
  147. package/build/esm/Init/HarnessDetector.js +65 -0
  148. package/build/esm/Init/HarnessDetector.js.map +1 -0
  149. package/build/esm/Init/HarnessRegistry.js +58 -0
  150. package/build/esm/Init/HarnessRegistry.js.map +1 -0
  151. package/build/esm/Init/MutationTransaction.js +494 -0
  152. package/build/esm/Init/MutationTransaction.js.map +1 -0
  153. package/build/esm/Init/Service.js +319 -0
  154. package/build/esm/Init/Service.js.map +1 -0
  155. package/build/esm/Init/StandardAdapters.js +116 -0
  156. package/build/esm/Init/StandardAdapters.js.map +1 -0
  157. package/build/esm/Init/Status.js +215 -0
  158. package/build/esm/Init/Status.js.map +1 -0
  159. package/build/esm/Install/Claude/Hook.js +182 -50
  160. package/build/esm/Install/Claude/Hook.js.map +1 -1
  161. package/build/esm/Install/Claude/Mcp.js +56 -26
  162. package/build/esm/Install/Claude/Mcp.js.map +1 -1
  163. package/build/esm/Install/Claude/Skill.js +49 -27
  164. package/build/esm/Install/Claude/Skill.js.map +1 -1
  165. package/build/esm/Install/Codex/Mcp.js +9 -4
  166. package/build/esm/Install/Codex/Mcp.js.map +1 -1
  167. package/build/esm/Install/Codex/Skill.js +4 -1
  168. package/build/esm/Install/Codex/Skill.js.map +1 -1
  169. package/build/esm/Install/Cursor/Mcp.js +7 -4
  170. package/build/esm/Install/Cursor/Mcp.js.map +1 -1
  171. package/build/esm/Install/Cursor/Skill.js +4 -1
  172. package/build/esm/Install/Cursor/Skill.js.map +1 -1
  173. package/build/esm/Install/Domain.js +60 -3
  174. package/build/esm/Install/Domain.js.map +1 -1
  175. package/build/esm/Install/Hook.js.map +1 -1
  176. package/build/esm/Install/Mcp.js +1 -1
  177. package/build/esm/Install/Mcp.js.map +1 -1
  178. package/build/esm/Install/Opencode/Hook.js +1 -1
  179. package/build/esm/Install/Opencode/Hook.js.map +1 -1
  180. package/build/esm/Install/Opencode/Mcp.js +6 -3
  181. package/build/esm/Install/Opencode/Mcp.js.map +1 -1
  182. package/build/esm/Install/Opencode/Skill.js +4 -1
  183. package/build/esm/Install/Opencode/Skill.js.map +1 -1
  184. package/build/esm/Install/Opencode/expandai-webfetch.js +27 -9
  185. package/build/esm/Install/Skill.js.map +1 -1
  186. package/build/esm/Install/Uninstall.js +2 -2
  187. package/build/esm/Install/Uninstall.js.map +1 -1
  188. package/build/esm/MarkdownAnsi.js +142 -0
  189. package/build/esm/MarkdownAnsi.js.map +1 -0
  190. package/build/esm/Mcp/ExpandDocs.js +44 -52
  191. package/build/esm/Mcp/ExpandDocs.js.map +1 -1
  192. package/build/esm/Mcp/RequestHeaders.js.map +1 -1
  193. package/build/esm/Mcp/Server.js +2 -1
  194. package/build/esm/Mcp/Server.js.map +1 -1
  195. package/build/esm/Mcp/tools/Citations.js +109 -8
  196. package/build/esm/Mcp/tools/Citations.js.map +1 -1
  197. package/build/esm/Mcp/tools/Fetch.js +36 -12
  198. package/build/esm/Mcp/tools/Fetch.js.map +1 -1
  199. package/build/esm/SecureFile.js +36 -0
  200. package/build/esm/SecureFile.js.map +1 -0
  201. package/build/esm/Snippets.js +12 -1
  202. package/build/esm/Snippets.js.map +1 -1
  203. package/build/esm/Telemetry.js +157 -0
  204. package/build/esm/Telemetry.js.map +1 -0
  205. package/build/esm/Version.js +4 -0
  206. package/build/esm/Version.js.map +1 -0
  207. package/build/esm/Xdg.js +15 -0
  208. package/build/esm/Xdg.js.map +1 -0
  209. package/build/esm/main.js +122 -14
  210. package/build/esm/main.js.map +1 -1
  211. package/build/skills/expandai/SKILL.md +26 -3
  212. package/package.json +4 -4
  213. package/src/ApiClient.ts +206 -13
  214. package/src/Auth/Domain.ts +63 -0
  215. package/src/Auth/ManualAuthorization.ts +296 -0
  216. package/src/Auth/PendingInit.ts +338 -0
  217. package/src/Auth/Pkce.ts +26 -0
  218. package/src/Auth/SetupGrantAuthorization.ts +116 -0
  219. package/src/Authentication.ts +49 -11
  220. package/src/BrowserOAuth.ts +10 -12
  221. package/src/CommandPath.ts +30 -0
  222. package/src/Commands/Fetch.ts +42 -11
  223. package/src/Commands/Init.ts +489 -0
  224. package/src/Commands/Search.ts +8 -3
  225. package/src/Commands/Status.ts +228 -0
  226. package/src/Commands/Telemetry.ts +42 -0
  227. package/src/Config.ts +695 -50
  228. package/src/Hook/Claude.ts +1 -1
  229. package/src/Hook/SnapshotNote.ts +55 -7
  230. package/src/Init/ClaudeAdapter.ts +191 -0
  231. package/src/Init/ClaudeProfile.ts +496 -0
  232. package/src/Init/Domain.ts +153 -0
  233. package/src/Init/HarnessAdapter.ts +35 -0
  234. package/src/Init/HarnessDetector.ts +77 -0
  235. package/src/Init/HarnessRegistry.ts +87 -0
  236. package/src/Init/MutationTransaction.ts +747 -0
  237. package/src/Init/Service.ts +385 -0
  238. package/src/Init/StandardAdapters.ts +141 -0
  239. package/src/Init/Status.ts +253 -0
  240. package/src/Install/Claude/Hook.ts +234 -81
  241. package/src/Install/Claude/Mcp.ts +80 -33
  242. package/src/Install/Claude/Skill.ts +70 -37
  243. package/src/Install/Codex/Mcp.ts +14 -9
  244. package/src/Install/Codex/Skill.ts +9 -5
  245. package/src/Install/Cursor/Mcp.ts +12 -9
  246. package/src/Install/Cursor/Skill.ts +9 -5
  247. package/src/Install/Domain.ts +77 -4
  248. package/src/Install/Hook.ts +3 -3
  249. package/src/Install/Mcp.ts +6 -6
  250. package/src/Install/Opencode/Hook.ts +5 -5
  251. package/src/Install/Opencode/Mcp.ts +11 -8
  252. package/src/Install/Opencode/Skill.ts +9 -5
  253. package/src/Install/Opencode/expandai-webfetch.js +27 -9
  254. package/src/Install/Skill.ts +3 -3
  255. package/src/Install/Uninstall.ts +3 -3
  256. package/src/MarkdownAnsi.ts +162 -0
  257. package/src/Mcp/ExpandDocs.ts +46 -55
  258. package/src/Mcp/Server.ts +2 -1
  259. package/src/Mcp/tools/Citations.ts +117 -8
  260. package/src/Mcp/tools/Fetch.ts +40 -14
  261. package/src/SecureFile.ts +70 -0
  262. package/src/Snippets.ts +19 -12
  263. package/src/Telemetry.ts +186 -0
  264. package/src/Version.ts +3 -0
  265. package/src/Xdg.ts +18 -0
  266. package/src/main.ts +171 -12
  267. package/build/dts/DeviceAuth.d.ts +0 -87
  268. package/build/dts/DeviceAuth.d.ts.map +0 -1
  269. package/build/esm/DeviceAuth.js +0 -127
  270. package/build/esm/DeviceAuth.js.map +0 -1
  271. package/src/DeviceAuth.ts +0 -193
package/build/esm/main.js CHANGED
@@ -3,21 +3,34 @@ import { CliConfig, Command, ValidationError } from '@effect/cli';
3
3
  import { Terminal } from '@effect/platform';
4
4
  import { NodeContext, NodeRuntime } from '@effect/platform-node';
5
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';
6
9
  import { Authentication, LoginCommand, LogoutCommand, WhoamiCommand } from './Authentication.js';
10
+ import { commandFromArgv } from './CommandPath.js';
7
11
  import { FetchCommand } from './Commands/Fetch.js';
8
12
  import { AgentHookCommand, HumanHookCommand } from './Commands/Hook.js';
13
+ import { AgentInitCommand, HumanInitCommand, InitTerminalInteraction, preflightAgentInitArgv, resolveInitExecutionMode, } from './Commands/Init.js';
9
14
  import { InstallSubCommand, UninstallSubCommand } from './Commands/Mcp.js';
10
15
  import { McpRunCommand } from './Commands/McpRun.js';
11
16
  import { SearchCommand } from './Commands/Search.js';
12
17
  import { SkillCommand } from './Commands/Skill.js';
18
+ import { AgentStatusCommand, HumanStatusCommand, preflightAgentStatusArgv, resolveStatusExecutionMode, StatusTerminalInteraction, } from './Commands/Status.js';
19
+ import { TelemetryCommand } from './Commands/Telemetry.js';
13
20
  import { UninstallCommand } from './Commands/Uninstall.js';
14
- import { ConfigManager } from './Config.js';
21
+ import { ConfigManager, envFlag, envLiteral } from './Config.js';
15
22
  import { ClaudeHook } from './Hook/Claude.js';
16
23
  import { OpencodeHook } from './Hook/Opencode.js';
24
+ import { HarnessDetector } from './Init/HarnessDetector.js';
25
+ import { HarnessRegistry } from './Init/HarnessRegistry.js';
26
+ import { InitService } from './Init/Service.js';
27
+ import { StatusService } from './Init/Status.js';
17
28
  import { HookInstaller } from './Install/Hook.js';
18
29
  import { Mcp } from './Install/Mcp.js';
19
30
  import { Skill as SkillInstaller } from './Install/Skill.js';
20
31
  import { Uninstall } from './Install/Uninstall.js';
32
+ import { Telemetry } from './Telemetry.js';
33
+ import { CLI_VERSION } from './Version.js';
21
34
  /**
22
35
  * Where this process is running:
23
36
  * - 'human': interactive terminal use. Setup commands (`mcp install`, `skill install`) are visible.
@@ -25,16 +38,38 @@ import { Uninstall } from './Install/Uninstall.js';
25
38
  * the CLI. Setup commands are hidden because the integration is already wired up.
26
39
  * - 'mcp': the binary is serving as the MCP transport. MCP only runs in agents.
27
40
  */
41
+ // envFlag / envLiteral read leniently: a malformed value (CLAUDECODE=banana,
42
+ // EXPANDAI_CLI_CONTEXT=bogus) must degrade instead of crashing the CLI —
43
+ // Config.option / Config.withDefault only absorb missing data, not invalid.
28
44
  const EnvironmentSignals = Config.all({
29
- override: Schema.Config('EXPANDAI_CLI_CONTEXT', Schema.Literal('human', 'agent')).pipe(Config.option),
45
+ override: envLiteral('EXPANDAI_CLI_CONTEXT', Schema.Literal('human', 'agent')),
30
46
  mcpTransport: Config.string('MCP_TRANSPORT').pipe(Config.option, Config.map(Option.isSome)),
31
- claudeCode: Config.boolean('CLAUDECODE').pipe(Config.withDefault(false)),
47
+ claudeCode: envFlag('CLAUDECODE'),
32
48
  cursor: Config.string('CURSOR_TRACE_ID').pipe(Config.option, Config.map(Option.isSome)),
33
49
  codex: Config.string('CODEX_RUN_ID').pipe(Config.option, Config.map(Option.isSome)),
34
50
  });
35
- const detectContext = Effect.gen(function* () {
51
+ const detectContext = Effect.fnUntraced(function* (command) {
52
+ const initMode = resolveInitExecutionMode({
53
+ argv: process.argv,
54
+ environment: process.env,
55
+ stdinIsTty: process.stdin.isTTY === true,
56
+ stdoutIsTty: process.stdout.isTTY === true,
57
+ });
58
+ if (initMode !== undefined)
59
+ return initMode === 'machine' ? 'agent' : 'human';
60
+ const statusMode = resolveStatusExecutionMode({
61
+ argv: process.argv,
62
+ environment: process.env,
63
+ stdinIsTty: process.stdin.isTTY === true,
64
+ stdoutIsTty: process.stdout.isTTY === true,
65
+ });
66
+ if (statusMode !== undefined)
67
+ return statusMode === 'machine' ? 'agent' : 'human';
36
68
  const signals = yield* EnvironmentSignals;
37
- const mcp = signals.mcpTransport ? Option.some('mcp') : Option.none();
69
+ // Installed MCP entries invoke `expandai mcp run` without any transport env
70
+ // var, so the resolved command is the reliable signal; MCP_TRANSPORT stays
71
+ // as a secondary hint for hand-written configs that set it.
72
+ const mcp = command === 'mcp run' || signals.mcpTransport ? Option.some('mcp') : Option.none();
38
73
  // Authentication.logout uses both stdout and stdin TTY checks because Prompt
39
74
  // reads from stdin; for context detection a non-TTY stdout alone is enough
40
75
  // to identify a non-interactive caller.
@@ -45,35 +80,90 @@ const detectContext = Effect.gen(function* () {
45
80
  });
46
81
  const HumanMcp = Command.make('mcp').pipe(Command.withDescription('Run or install the expandai MCP server'), Command.withSubcommands([McpRunCommand, InstallSubCommand, UninstallSubCommand]));
47
82
  const AgentMcp = Command.make('mcp').pipe(Command.withDescription('Run the expandai MCP server'), Command.withSubcommands([McpRunCommand]));
83
+ const InitServiceLive = InitService.DefaultWithoutDependencies.pipe(Layer.provide(Layer.mergeAll(Authentication.Default, ManualAuthorization.Default, SetupGrantAuthorization.Default, HarnessRegistry.Default)));
84
+ const StatusServiceLive = StatusService.DefaultWithoutDependencies.pipe(Layer.provide(Layer.mergeAll(ConfigManager.Default, PendingInitStore.Default, HarnessRegistry.Default)));
48
85
  const HumanCli = Command.make('expandai').pipe(Command.withDescription('expand.ai CLI and MCP server'), Command.withSubcommands([
49
86
  FetchCommand,
50
87
  SearchCommand,
51
88
  LoginCommand,
52
89
  WhoamiCommand,
53
90
  LogoutCommand,
91
+ HumanInitCommand,
92
+ HumanStatusCommand,
54
93
  UninstallCommand,
55
94
  HumanMcp,
56
95
  SkillCommand,
57
96
  HumanHookCommand,
58
- ]), Command.provide(Layer.mergeAll(ConfigManager.Default, Authentication.Default, Mcp.Default, HookInstaller.Default, SkillInstaller.Default, Uninstall.Default, ClaudeHook.Default, OpencodeHook.Default)));
97
+ TelemetryCommand,
98
+ ]), Command.provide(Layer.mergeAll(ConfigManager.Default, Authentication.Default, Mcp.Default, HookInstaller.Default, SkillInstaller.Default, Uninstall.Default, ClaudeHook.Default, OpencodeHook.Default, InitServiceLive, HarnessDetector.Default, InitTerminalInteraction.Default, StatusServiceLive, StatusTerminalInteraction.Default)));
59
99
  const AgentCli = Command.make('expandai').pipe(Command.withDescription('expand.ai CLI and MCP server'), Command.withSubcommands([
60
100
  FetchCommand,
61
101
  SearchCommand,
62
102
  LoginCommand,
63
103
  WhoamiCommand,
64
104
  LogoutCommand,
105
+ AgentInitCommand,
106
+ AgentStatusCommand,
107
+ // The consent step tells the user `expandai uninstall` is how to undo
108
+ // setup — including removing the machine-wide `WebFetch` deny rule. That
109
+ // text is read by an agent, so the command has to exist in this tree too;
110
+ // it was human-only, and an agent running it got "Invalid subcommand".
111
+ UninstallCommand,
65
112
  AgentMcp,
66
113
  AgentHookCommand,
67
- ]), Command.provide(Layer.mergeAll(ConfigManager.Default, Authentication.Default, ClaudeHook.Default, OpencodeHook.Default)));
114
+ TelemetryCommand,
115
+ ]), Command.provide(Layer.mergeAll(ConfigManager.Default, Authentication.Default, ClaudeHook.Default, OpencodeHook.Default, Mcp.Default, HookInstaller.Default, SkillInstaller.Default, Uninstall.Default, InitServiceLive, HarnessDetector.Default, StatusServiceLive)));
68
116
  const LoggerLive = Layer.unwrapEffect(Effect.gen(function* () {
69
117
  const isDebug = yield* Config.boolean('DEBUG').pipe(Config.withDefault(false));
70
- return Logger.minimumLogLevel(isDebug ? LogLevel.All : LogLevel.Info);
118
+ // stdout is a protocol/data surface (hook JSON payloads, fetched
119
+ // markdown) — a log line ahead of the payload corrupts it for the
120
+ // consumer, so diagnostics go to stderr.
121
+ return Layer.mergeAll(Logger.replace(Logger.defaultLogger, Logger.prettyLogger({ stderr: true })), Logger.minimumLogLevel(isDebug ? LogLevel.All : LogLevel.Info));
122
+ }));
123
+ // Status is a read-only diagnostic contract, and `init --detect` documents itself
124
+ // as changing nothing — the guide prints "This changes nothing:" directly above
125
+ // it. Select the no-op service before layer acquisition so Telemetry.Default
126
+ // cannot persist a distinct id, repair config permissions, display a notice, or
127
+ // send a request. A brand-new HOME came back from `--detect` with a config.json
128
+ // holding a telemetry id, which is a write however small. Other commands retain
129
+ // the ordinary telemetry service unchanged.
130
+ const writesNothing = commandFromArgv(process.argv) === 'status' ||
131
+ (commandFromArgv(process.argv) === 'init' && process.argv.includes('--detect'));
132
+ const TelemetryLive = writesNothing ? Telemetry.DISABLED : Telemetry.Default;
133
+ const Env = Layer.mergeAll(NodeContext.layer, CliConfig.layer({ showBuiltIns: false }), LoggerLive, TelemetryLive);
134
+ const errorTagFromCause = (cause) => Cause.failureOption(cause).pipe(Option.match({
135
+ onNone: () => 'Defect',
136
+ onSome: (error) => typeof error === 'object' && error !== null && '_tag' in error && typeof error._tag === 'string'
137
+ ? error._tag
138
+ : 'UnknownError',
71
139
  }));
72
- const Env = Layer.mergeAll(NodeContext.layer, CliConfig.layer({ showBuiltIns: false }), LoggerLive);
140
+ function startupOnboardingPreflightError() {
141
+ const initMode = resolveInitExecutionMode({
142
+ argv: process.argv,
143
+ environment: process.env,
144
+ stdinIsTty: process.stdin.isTTY === true,
145
+ stdoutIsTty: process.stdout.isTTY === true,
146
+ });
147
+ if (initMode === 'machine')
148
+ return preflightAgentInitArgv(process.argv);
149
+ const statusMode = resolveStatusExecutionMode({
150
+ argv: process.argv,
151
+ environment: process.env,
152
+ stdinIsTty: process.stdin.isTTY === true,
153
+ stdoutIsTty: process.stdout.isTTY === true,
154
+ });
155
+ return statusMode === 'machine' ? preflightAgentStatusArgv(process.argv) : undefined;
156
+ }
73
157
  const program = Effect.gen(function* () {
74
- const context = yield* detectContext;
158
+ const command = commandFromArgv(process.argv);
159
+ const context = yield* detectContext(command);
160
+ const telemetry = yield* Telemetry;
161
+ if (context === 'human') {
162
+ yield* telemetry.showNoticeIfNeeded();
163
+ }
164
+ yield* telemetry.capture('cli_command_run', { command, context });
75
165
  const cli = context === 'human' ? HumanCli : AgentCli;
76
- return yield* Command.run(cli, { name: 'expand.ai CLI', version: '0.0.1' })(process.argv);
166
+ return yield* Command.run(cli, { name: 'expand.ai CLI', version: CLI_VERSION })(process.argv);
77
167
  }).pipe(Effect.catchAllCause((cause) => {
78
168
  const failure = Cause.failureOption(cause);
79
169
  // Ctrl-C during a `Prompt` surfaces as a `QuitException` failure rather
@@ -91,7 +181,25 @@ const program = Effect.gen(function* () {
91
181
  |${Cause.pretty(cause)}`)),
92
182
  });
93
183
  const debug = Effect.logDebug('Error cause').pipe(Effect.annotateLogs({ cause: Cause.pretty(cause) }));
94
- return print.pipe(Effect.zipRight(debug), Effect.zipRight(Effect.failCause(cause)));
95
- }), Effect.provide(Env));
96
- NodeRuntime.runMain(program, { disableErrorReporting: true });
184
+ const trackFailure = Effect.flatMap(Telemetry, (telemetry) => telemetry.capture('cli_command_failed', {
185
+ command: commandFromArgv(process.argv),
186
+ error_tag: errorTagFromCause(cause),
187
+ }));
188
+ return print.pipe(Effect.zipRight(debug), Effect.zipRight(trackFailure), Effect.zipRight(Effect.failCause(cause)));
189
+ }),
190
+ // Deliver any pending telemetry before the process exits; capped at 1s by
191
+ // Telemetry.flush so it can never hold a short-lived invocation open.
192
+ Effect.ensuring(Effect.flatMap(Telemetry, (telemetry) => telemetry.flush)), Effect.provide(Env));
193
+ // disablePrettyLogger: runMain's own pretty logger writes to stdout, which
194
+ // would put diagnostics back on the protocol/data stream LoggerLive keeps clean.
195
+ const preflightError = startupOnboardingPreflightError();
196
+ if (preflightError !== undefined) {
197
+ // This validation must finish before the Effect runtime acquires Telemetry,
198
+ // otherwise an invalid machine command could still create local state.
199
+ process.stderr.write(`${preflightError}\n`);
200
+ process.exitCode = 2;
201
+ }
202
+ else {
203
+ NodeRuntime.runMain(program, { disableErrorReporting: true, disablePrettyLogger: true });
204
+ }
97
205
  //# sourceMappingURL=main.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACxG,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAChG,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACvE,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AACtC,OAAO,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAElD;;;;;;GAMG;AAEH,MAAM,kBAAkB,GAAG,MAAM,CAAC,GAAG,CAAC;IACrC,QAAQ,EAAE,MAAM,CAAC,MAAM,CAAC,sBAAsB,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC;IACrG,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3F,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;IACxE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvF,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;CACnF,CAAC,CAAA;AAEF,MAAM,aAAa,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACzC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,kBAAkB,CAAA;IAEzC,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAc,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAS,CAAA;IACrF,6EAA6E;IAC7E,2EAA2E;IAC3E,wCAAwC;IACxC,MAAM,KAAK,GACV,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;QAC7E,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAgB,CAAC;QAC/B,CAAC,CAAC,MAAM,CAAC,IAAI,EAAW,CAAA;IAE1B,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,OAAgB,CAAC,CAAC,CAAA;AACzG,CAAC,CAAC,CAAA;AAEF,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CACxC,OAAO,CAAC,eAAe,CAAC,wCAAwC,CAAC,EACjE,OAAO,CAAC,eAAe,CAAC,CAAC,aAAa,EAAE,iBAAiB,EAAE,mBAAmB,CAAC,CAAC,CAChF,CAAA;AAED,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CACxC,OAAO,CAAC,eAAe,CAAC,6BAA6B,CAAC,EACtD,OAAO,CAAC,eAAe,CAAC,CAAC,aAAa,CAAC,CAAC,CACxC,CAAA;AAED,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAC7C,OAAO,CAAC,eAAe,CAAC,8BAA8B,CAAC,EACvD,OAAO,CAAC,eAAe,CAAC;IACvB,YAAY;IACZ,aAAa;IACb,YAAY;IACZ,aAAa;IACb,aAAa;IACb,gBAAgB;IAChB,QAAQ;IACR,YAAY;IACZ,gBAAgB;CAChB,CAAC,EACF,OAAO,CAAC,OAAO,CACd,KAAK,CAAC,QAAQ,CACb,aAAa,CAAC,OAAO,EACrB,cAAc,CAAC,OAAO,EACtB,GAAG,CAAC,OAAO,EACX,aAAa,CAAC,OAAO,EACrB,cAAc,CAAC,OAAO,EACtB,SAAS,CAAC,OAAO,EACjB,UAAU,CAAC,OAAO,EAClB,YAAY,CAAC,OAAO,CACpB,CACD,CACD,CAAA;AAED,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAC7C,OAAO,CAAC,eAAe,CAAC,8BAA8B,CAAC,EACvD,OAAO,CAAC,eAAe,CAAC;IACvB,YAAY;IACZ,aAAa;IACb,YAAY;IACZ,aAAa;IACb,aAAa;IACb,QAAQ;IACR,gBAAgB;CAChB,CAAC,EACF,OAAO,CAAC,OAAO,CACd,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,OAAO,EAAE,cAAc,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,YAAY,CAAC,OAAO,CAAC,CACvG,CACD,CAAA;AAED,MAAM,UAAU,GAAG,KAAK,CAAC,YAAY,CACpC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;IAC9E,OAAO,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;AACtE,CAAC,CAAC,CACF,CAAA;AAED,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,CAAC,CAAA;AAEnG,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnC,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,aAAa,CAAA;IACpC,MAAM,GAAG,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAA;IACrD,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAC1F,CAAC,CAAC,CAAC,IAAI,CACN,MAAM,CAAC,aAAa,CAAC,CAAC,KAAK,EAAE,EAAE;IAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAC1C,wEAAwE;IACxE,sEAAsE;IACtE,gFAAgF;IAChF,IAAI,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QAC3G,OAAO,MAAM,CAAC,IAAI,CAAA;IACnB,CAAC;IACD,yEAAyE;IACzE,oEAAoE;IACpE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE;QACnC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CACjB,eAAe,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,UAAW,KAAe,CAAC,OAAO,EAAE,CAAC;QAC7G,MAAM,EAAE,GAAG,EAAE,CACZ,OAAO,CAAC,KAAK,CACZ,MAAM,CAAC,WAAW,CAAC;;SAEf,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAC1B;KACF,CAAC,CAAA;IACF,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;IACtG,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AACpF,CAAC,CAAC,EACF,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CACnB,CAAA;AAED,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAAC,CAAA"}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAChE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AACxG,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,mCAAmC,CAAA;AAC3E,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAChG,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACvE,OAAO,EACN,gBAAgB,EAChB,gBAAgB,EAChB,uBAAuB,EACvB,sBAAsB,EACtB,wBAAwB,GACxB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EACN,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,0BAA0B,EAC1B,yBAAyB,GACzB,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC1D,OAAO,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAA;AACtC,OAAO,EAAE,KAAK,IAAI,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE1C;;;;;;GAMG;AAEH,6EAA6E;AAC7E,yEAAyE;AACzE,4EAA4E;AAC5E,MAAM,kBAAkB,GAAG,MAAM,CAAC,GAAG,CAAC;IACrC,QAAQ,EAAE,UAAU,CAAC,sBAAsB,EAAE,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAC9E,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC3F,UAAU,EAAE,OAAO,CAAC,YAAY,CAAC;IACjC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvF,KAAK,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;CACnF,CAAC,CAAA;AAEF,MAAM,aAAa,GAAG,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,OAAe;IACjE,MAAM,QAAQ,GAAG,wBAAwB,CAAC;QACzC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,WAAW,EAAE,OAAO,CAAC,GAAG;QACxB,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI;QACxC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI;KAC1C,CAAC,CAAA;IACF,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAE,OAAiB,CAAC,CAAC,CAAE,OAAiB,CAAA;IACnG,MAAM,UAAU,GAAG,0BAA0B,CAAC;QAC7C,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,WAAW,EAAE,OAAO,CAAC,GAAG;QACxB,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI;QACxC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI;KAC1C,CAAC,CAAA;IACF,IAAI,UAAU,KAAK,SAAS;QAAE,OAAO,UAAU,KAAK,SAAS,CAAC,CAAC,CAAE,OAAiB,CAAC,CAAC,CAAE,OAAiB,CAAA;IAEvG,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,kBAAkB,CAAA;IAEzC,4EAA4E;IAC5E,2EAA2E;IAC3E,4DAA4D;IAC5D,MAAM,GAAG,GAAG,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAc,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAS,CAAA;IAC9G,6EAA6E;IAC7E,2EAA2E;IAC3E,wCAAwC;IACxC,MAAM,KAAK,GACV,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;QAC7E,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,OAAgB,CAAC;QAC/B,CAAC,CAAC,MAAM,CAAC,IAAI,EAAW,CAAA;IAE1B,OAAO,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,OAAgB,CAAC,CAAC,CAAA;AACzG,CAAC,CAAC,CAAA;AAEF,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CACxC,OAAO,CAAC,eAAe,CAAC,wCAAwC,CAAC,EACjE,OAAO,CAAC,eAAe,CAAC,CAAC,aAAa,EAAE,iBAAiB,EAAE,mBAAmB,CAAC,CAAC,CAChF,CAAA;AAED,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CACxC,OAAO,CAAC,eAAe,CAAC,6BAA6B,CAAC,EACtD,OAAO,CAAC,eAAe,CAAC,CAAC,aAAa,CAAC,CAAC,CACxC,CAAA;AAED,MAAM,eAAe,GAAG,WAAW,CAAC,0BAA0B,CAAC,IAAI,CAClE,KAAK,CAAC,OAAO,CACZ,KAAK,CAAC,QAAQ,CACb,cAAc,CAAC,OAAO,EACtB,mBAAmB,CAAC,OAAO,EAC3B,uBAAuB,CAAC,OAAO,EAC/B,eAAe,CAAC,OAAO,CACvB,CACD,CACD,CAAA;AAED,MAAM,iBAAiB,GAAG,aAAa,CAAC,0BAA0B,CAAC,IAAI,CACtE,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,OAAO,EAAE,gBAAgB,CAAC,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,CACvG,CAAA;AAED,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAC7C,OAAO,CAAC,eAAe,CAAC,8BAA8B,CAAC,EACvD,OAAO,CAAC,eAAe,CAAC;IACvB,YAAY;IACZ,aAAa;IACb,YAAY;IACZ,aAAa;IACb,aAAa;IACb,gBAAgB;IAChB,kBAAkB;IAClB,gBAAgB;IAChB,QAAQ;IACR,YAAY;IACZ,gBAAgB;IAChB,gBAAgB;CAChB,CAAC,EACF,OAAO,CAAC,OAAO,CACd,KAAK,CAAC,QAAQ,CACb,aAAa,CAAC,OAAO,EACrB,cAAc,CAAC,OAAO,EACtB,GAAG,CAAC,OAAO,EACX,aAAa,CAAC,OAAO,EACrB,cAAc,CAAC,OAAO,EACtB,SAAS,CAAC,OAAO,EACjB,UAAU,CAAC,OAAO,EAClB,YAAY,CAAC,OAAO,EACpB,eAAe,EACf,eAAe,CAAC,OAAO,EACvB,uBAAuB,CAAC,OAAO,EAC/B,iBAAiB,EACjB,yBAAyB,CAAC,OAAO,CACjC,CACD,CACD,CAAA;AAED,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAC7C,OAAO,CAAC,eAAe,CAAC,8BAA8B,CAAC,EACvD,OAAO,CAAC,eAAe,CAAC;IACvB,YAAY;IACZ,aAAa;IACb,YAAY;IACZ,aAAa;IACb,aAAa;IACb,gBAAgB;IAChB,kBAAkB;IAClB,sEAAsE;IACtE,yEAAyE;IACzE,0EAA0E;IAC1E,uEAAuE;IACvE,gBAAgB;IAChB,QAAQ;IACR,gBAAgB;IAChB,gBAAgB;CAChB,CAAC,EACF,OAAO,CAAC,OAAO,CACd,KAAK,CAAC,QAAQ,CACb,aAAa,CAAC,OAAO,EACrB,cAAc,CAAC,OAAO,EACtB,UAAU,CAAC,OAAO,EAClB,YAAY,CAAC,OAAO,EACpB,GAAG,CAAC,OAAO,EACX,aAAa,CAAC,OAAO,EACrB,cAAc,CAAC,OAAO,EACtB,SAAS,CAAC,OAAO,EACjB,eAAe,EACf,eAAe,CAAC,OAAO,EACvB,iBAAiB,CACjB,CACD,CACD,CAAA;AAED,MAAM,UAAU,GAAG,KAAK,CAAC,YAAY,CACpC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnB,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAA;IAC9E,iEAAiE;IACjE,kEAAkE;IAClE,yCAAyC;IACzC,OAAO,KAAK,CAAC,QAAQ,CACpB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAC3E,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAC9D,CAAA;AACF,CAAC,CAAC,CACF,CAAA;AAED,kFAAkF;AAClF,gFAAgF;AAChF,6EAA6E;AAC7E,gFAAgF;AAChF,gFAAgF;AAChF,gFAAgF;AAChF,4CAA4C;AAC5C,MAAM,aAAa,GAClB,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,QAAQ;IAC1C,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAA;AAChF,MAAM,aAAa,GAAG,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,CAAA;AAC5E,MAAM,GAAG,GAAG,KAAK,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,aAAa,CAAC,CAAA;AAElH,MAAM,iBAAiB,GAAG,CAAC,KAA2B,EAAU,EAAE,CACjE,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,IAAI,CAC9B,MAAM,CAAC,KAAK,CAAC;IACZ,MAAM,EAAE,GAAG,EAAE,CAAC,QAAQ;IACtB,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CACjB,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ;QAC/F,CAAC,CAAC,KAAK,CAAC,IAAI;QACZ,CAAC,CAAC,cAAc;CAClB,CAAC,CACF,CAAA;AAEF,SAAS,+BAA+B;IACvC,MAAM,QAAQ,GAAG,wBAAwB,CAAC;QACzC,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,WAAW,EAAE,OAAO,CAAC,GAAG;QACxB,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI;QACxC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI;KAC1C,CAAC,CAAA;IACF,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,sBAAsB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IACvE,MAAM,UAAU,GAAG,0BAA0B,CAAC;QAC7C,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,WAAW,EAAE,OAAO,CAAC,GAAG;QACxB,UAAU,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI;QACxC,WAAW,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI;KAC1C,CAAC,CAAA;IACF,OAAO,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,wBAAwB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACrF,CAAC;AAED,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IACnC,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,CAAA;IAC7C,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,SAAS,CAAA;IAClC,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;QACzB,KAAK,CAAC,CAAC,SAAS,CAAC,kBAAkB,EAAE,CAAA;IACtC,CAAC;IACD,KAAK,CAAC,CAAC,SAAS,CAAC,OAAO,CAAC,iBAAiB,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAA;IACjE,MAAM,GAAG,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAA;IACrD,OAAO,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;AAC9F,CAAC,CAAC,CAAC,IAAI,CACN,MAAM,CAAC,aAAa,CAAC,CAAC,KAAK,EAAE,EAAE;IAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IAC1C,wEAAwE;IACxE,sEAAsE;IACtE,gFAAgF;IAChF,IAAI,KAAK,CAAC,iBAAiB,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,QAAQ,CAAC,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC;QAC3G,OAAO,MAAM,CAAC,IAAI,CAAA;IACnB,CAAC;IACD,yEAAyE;IACzE,oEAAoE;IACpE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE;QACnC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CACjB,eAAe,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,UAAW,KAAe,CAAC,OAAO,EAAE,CAAC;QAC7G,MAAM,EAAE,GAAG,EAAE,CACZ,OAAO,CAAC,KAAK,CACZ,MAAM,CAAC,WAAW,CAAC;;SAEf,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAC1B;KACF,CAAC,CAAA;IACF,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;IACtG,MAAM,YAAY,GAAG,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,SAAS,EAAE,EAAE,CAC5D,SAAS,CAAC,OAAO,CAAC,oBAAoB,EAAE;QACvC,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC;QACtC,SAAS,EAAE,iBAAiB,CAAC,KAAK,CAAC;KACnC,CAAC,CACF,CAAA;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AACnH,CAAC,CAAC;AACF,0EAA0E;AAC1E,sEAAsE;AACtE,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAC1E,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CACnB,CAAA;AAED,2EAA2E;AAC3E,iFAAiF;AACjF,MAAM,cAAc,GAAG,+BAA+B,EAAE,CAAA;AACxD,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;IAClC,4EAA4E;IAC5E,uEAAuE;IACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,cAAc,IAAI,CAAC,CAAA;IAC3C,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;AACrB,CAAC;KAAM,CAAC;IACP,WAAW,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,qBAAqB,EAAE,IAAI,EAAE,mBAAmB,EAAE,IAAI,EAAE,CAAC,CAAA;AACzF,CAAC"}
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  name: expandai
3
3
  description: Fetch any URL as clean markdown using the `expandai` CLI. Use when reading documentation, articles, GitHub READMEs, blog posts, or any web page — the output is rendered from the live page so it handles SPAs and bot-protected sites that plain `curl` does not.
4
+ metadata:
5
+ expandai-profile-version: 0.0.2
4
6
  ---
5
7
 
6
8
  # expandai
@@ -49,9 +51,29 @@ prefer `expandai search` (or `fetch_search`), which reuses the existing capture.
49
51
 
50
52
  ## Citations
51
53
 
52
- Search results include enough location data to audit the cited passage. In text output,
53
- `expandai search` and `expandai fetch --search` print a public playground citation link above
54
- each snippet. MCP tool results expose `citationUrl` fields directly.
54
+ Cite every claim you take from a fetched page. This is not optional, and it applies however
55
+ the page arrived — `expandai fetch`, `expandai search`, the MCP tools, or the WebFetch hook.
56
+
57
+ Captured markdown stamps an evidence id next to the text it belongs to, written as `{1199}`.
58
+ For each sentence you use, take the id nearest that text and end your answer with:
59
+
60
+ ```
61
+ **Sources**
62
+ [1] [<page title>](<playground>?id=<evidence id>)
63
+ ```
64
+
65
+ Number the markers `[1]`, `[2]`, … in the order they appear, one per sentence. Search snippets
66
+ and MCP results carry a ready-made `citationUrl` with the right `?id=` already on it — prefer it
67
+ verbatim.
68
+
69
+ If the sentence you want carries no evidence id, do not fall back to linking the original page — that looks like a citation and is not one. Quote a nearby sentence that does carry an id, or say plainly that the capture has no id for it.
70
+
71
+ The link points at expand.ai's stored capture, never at the original site. Do not
72
+ build it by appending `?id=` to the page you fetched — copy the capture URL, or the snippet's
73
+ `citationUrl`, character for character.
74
+
75
+ Never invent an evidence id, and never reuse an id from different text. A bare capture link
76
+ without `?id=` is not a citation: the reader cannot check the sentence against the page.
55
77
 
56
78
  ## Common patterns
57
79
 
@@ -68,5 +90,6 @@ expandai fetch https://docs.example.com/api --format json
68
90
 
69
91
  ## Failure modes
70
92
 
93
+ - `command not found: expandai` — install the CLI globally with npm, restart Claude Code so it inherits the updated PATH, then retry.
71
94
  - Exit code non-zero with `Not authenticated` — the user has not run `expandai login`. Tell them to run it; do not attempt to authenticate yourself.
72
95
  - Empty markdown output — try `--format json` to inspect the response shape.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expandai",
3
3
  "type": "module",
4
- "version": "0.0.2",
4
+ "version": "0.0.3",
5
5
  "private": false,
6
6
  "description": "Unified CLI and MCP server for ExpandAI",
7
7
  "author": "ExpandAI",
@@ -30,13 +30,13 @@
30
30
  "import": "./build/esm/index.js"
31
31
  },
32
32
  "./*": {
33
- "types": "./build/dts/*.d.ts",
33
+ "types": "./src/*.ts",
34
34
  "import": "./build/esm/*.js"
35
35
  }
36
36
  },
37
37
  "scripts": {
38
38
  "dev": "tsx --watch src/main.ts",
39
- "build": "tsc -b tsconfig.build.json && cp -R skills build/skills && cp src/Install/Opencode/expandai-webfetch.js build/esm/Install/Opencode/expandai-webfetch.js && chmod +x build/esm/main.js",
39
+ "build": "tsc -b tsconfig.build.json && rm -rf build/skills && cp -R skills build/skills && cp src/Install/Opencode/expandai-webfetch.js build/esm/Install/Opencode/expandai-webfetch.js && chmod +x build/esm/main.js",
40
40
  "build:bin": "bun ./scripts/build.ts --single",
41
41
  "build:bin:all": "bun ./scripts/build.ts",
42
42
  "check": "tsc -b tsconfig.json",
@@ -54,7 +54,7 @@
54
54
  "@effect/cli": "0.75.1",
55
55
  "@effect/platform": "0.96.1",
56
56
  "@effect/platform-node": "0.106.0",
57
- "@expandai/sdk": "0.17.2",
57
+ "@expandai/sdk": "0.18.0",
58
58
  "effect": "3.21.2",
59
59
  "open": "^11.0.0",
60
60
  "smol-toml": "^1.6.1"
package/src/ApiClient.ts CHANGED
@@ -4,10 +4,76 @@ import { fetchObjectModeJson, fetchSnapshotSearch } from '@expandai/sdk/FetchJso
4
4
  import * as Generated from '@expandai/sdk/Generated'
5
5
  import { decodeError, unexpectedStatus } from '@expandai/sdk/HttpClientHelpers'
6
6
  import { Data, DateTime, Duration, Effect, flow, Match, Option, Redacted, Schema } from 'effect'
7
+ import { CLI_AUTHORIZATION_CLIENT_ID } from './Auth/Domain.js'
7
8
  import { BrowserOAuthService } from './BrowserOAuth.js'
8
9
  import { ConfigManager, OAuthConfig, OAuthTokenExpired } from './Config.js'
9
10
  import { CurrentRequestHeaders } from './Mcp/RequestHeaders.js'
10
11
 
12
+ export class DeviceAuthorizationCreated extends Schema.Class<DeviceAuthorizationCreated>('DeviceAuthorizationCreated')({
13
+ deviceCode: Schema.Redacted(Schema.NonEmptyString),
14
+ userCode: Schema.NonEmptyString,
15
+ // `Schema.URL` decodes to a `URL`; the wire form stays a string. The CLI
16
+ // hands this to the user to open, so a malformed `appUrls.app` should fail
17
+ // here rather than in a browser.
18
+ verificationUri: Schema.URL,
19
+ // Whole seconds. A zero or negative `expiresIn` is a window that has already
20
+ // closed, so it must be positive.
21
+ expiresIn: Schema.Int.pipe(Schema.positive()),
22
+ // `interval` is RFC 8628's *minimum* wait between polls, so zero is
23
+ // meaningful — poll without delay. Only negative is nonsense, and it would
24
+ // make `nextPollAt` drift backwards forever.
25
+ interval: Schema.Int.pipe(Schema.nonNegative()),
26
+ }) {}
27
+
28
+ const ManualAuthorizationTokenResponse = Schema.Struct({
29
+ access_token: Schema.Redacted(Schema.NonEmptyString),
30
+ refresh_token: Schema.Redacted(Schema.NonEmptyString),
31
+ token_type: Schema.NonEmptyString,
32
+ expires_in: Schema.Number.pipe(Schema.positive()),
33
+ scope: Schema.optional(Schema.String),
34
+ })
35
+
36
+ export type ManualAuthorizationTokenResponse = typeof ManualAuthorizationTokenResponse.Type
37
+
38
+ /**
39
+ * RFC 8628 §3.5 poll outcomes. These are results, not errors: an agent-driven
40
+ * CLI returns immediately on every one of them, and only a transport failure
41
+ * is exceptional.
42
+ */
43
+ export type DevicePollOutcome =
44
+ | { readonly _tag: 'Tokens'; readonly token: ManualAuthorizationTokenResponse }
45
+ | { readonly _tag: 'Pending' }
46
+ | { readonly _tag: 'SlowDown' }
47
+ | { readonly _tag: 'Denied' }
48
+ | { readonly _tag: 'Expired' }
49
+ | { readonly _tag: 'Invalid' }
50
+ /**
51
+ * The server refused for a reason a fresh code cannot fix — a misconfigured
52
+ * client, resource, or scope. Distinct from `Invalid` because the recovery is
53
+ * opposite: `Invalid` means start over, this means stop and report. Carries
54
+ * the server's own `error` so the message names the actual cause instead of
55
+ * guessing.
56
+ */
57
+ | { readonly _tag: 'Refused'; readonly error: string; readonly description?: string }
58
+
59
+ const ManualAuthorizationOperation = Schema.Literal('create', 'poll')
60
+
61
+ export class ManualAuthorizationApiError extends Schema.TaggedError<ManualAuthorizationApiError>()(
62
+ 'ManualAuthorizationApiError',
63
+ {
64
+ operation: ManualAuthorizationOperation,
65
+ kind: Schema.Literal('request_failed', 'rejected', 'invalid_response'),
66
+ status: Schema.optional(Schema.Number),
67
+ recoverable: Schema.Boolean,
68
+ },
69
+ ) {
70
+ get message() {
71
+ return this.kind === 'rejected'
72
+ ? `Manual authorization ${this.operation} was rejected. Restart authorization if the problem persists.`
73
+ : `Manual authorization ${this.operation} could not be completed safely. Try again.`
74
+ }
75
+ }
76
+
11
77
  /**
12
78
  * Single user-facing error for any failure crossing the API boundary. The
13
79
  * generated client raises a half-dozen status-specific tagged classes plus
@@ -56,18 +122,18 @@ function getUnsupportedFields(value: unknown): string[] {
56
122
  return Array.isArray(unsupportedFields) ? unsupportedFields.filter((field) => typeof field === 'string') : []
57
123
  }
58
124
 
59
- const formatIssue = Effect.fn('ApiClient.formatIssue')(function* ({ path, message }: Issue) {
125
+ function formatIssue({ path, message }: Issue): string {
60
126
  const prefix = path.map((segment) => (typeof segment === 'object' ? segment.key : String(segment))).join('.')
61
127
  return prefix ? `${prefix}: ${message}` : message
62
- })
128
+ }
63
129
 
64
- const formatApiFailure = Effect.fn('ApiClient.formatApiFailure')(function* (error: ClientError) {
130
+ function formatApiFailure(error: ClientError): string {
65
131
  switch (getStringField(error, '_tag')) {
66
132
  case 'HttpApiDecodeError': {
67
133
  const issues = getIssueList(error)
68
- return yield* issues.length === 0
69
- ? Effect.succeed(getStringField(error, 'message') ?? 'Invalid request')
70
- : Effect.forEach(issues, formatIssue).pipe(Effect.map((parts) => parts.join('; ')))
134
+ return issues.length === 0
135
+ ? (getStringField(error, 'message') ?? 'Invalid request')
136
+ : issues.map(formatIssue).join('; ')
71
137
  }
72
138
  case 'AuthFailed':
73
139
  return `Authentication failed (${getStringField(error, 'reason') ?? 'unknown'}). Run \`expandai login\` to re-authenticate.`
@@ -88,14 +154,139 @@ const formatApiFailure = Effect.fn('ApiClient.formatApiFailure')(function* (erro
88
154
  default:
89
155
  return error instanceof Error ? error.message : (getStringField(error, 'message') ?? 'Expand API request failed.')
90
156
  }
91
- })
157
+ }
92
158
 
93
159
  const liftApiError = <A, R>(self: Effect.Effect<A, ClientError, R>) =>
94
- self.pipe(
95
- Effect.catchAll((error) =>
96
- formatApiFailure(error).pipe(Effect.flatMap((detail) => Effect.fail(new ApiRequestFailed({ detail })))),
97
- ),
98
- )
160
+ self.pipe(Effect.catchAll((error) => new ApiRequestFailed({ detail: formatApiFailure(error) })))
161
+
162
+ /** Public, unauthenticated HTTP boundary for device-flow CLI authorization. */
163
+ export class ManualAuthorizationApiClient extends Effect.Service<ManualAuthorizationApiClient>()(
164
+ '@expandai/cli/ManualAuthorizationApiClient',
165
+ {
166
+ dependencies: [NodeHttpClient.layer, ConfigManager.Default],
167
+ effect: Effect.gen(function* () {
168
+ const httpClient = yield* HttpClient.HttpClient
169
+ const configManager = yield* ConfigManager
170
+
171
+ const apiError = (
172
+ operation: typeof ManualAuthorizationOperation.Type,
173
+ kind: 'request_failed' | 'rejected' | 'invalid_response',
174
+ status?: number,
175
+ ) =>
176
+ new ManualAuthorizationApiError({
177
+ operation,
178
+ kind,
179
+ status,
180
+ recoverable: kind !== 'invalid_response',
181
+ })
182
+
183
+ const create = Effect.fn('ManualAuthorizationApiClient.create')(function* (input: {
184
+ readonly harness: 'agent-harnesses'
185
+ readonly scope: 'project' | 'global'
186
+ readonly platform: string
187
+ readonly machineLabel: string
188
+ readonly cliVersion: string
189
+ }) {
190
+ const apiUrl = yield* configManager.getApiUrl()
191
+ const request = yield* HttpClientRequest.post(new URL('/v1/cli-authorization/device', apiUrl)).pipe(
192
+ HttpClientRequest.bodyJson(input),
193
+ Effect.mapError(() => apiError('create', 'request_failed')),
194
+ )
195
+ return yield* httpClient.execute(request).pipe(
196
+ Effect.flatMap((response) =>
197
+ response.status >= 200 && response.status < 300
198
+ ? HttpClientResponse.schemaBodyJson(DeviceAuthorizationCreated)(response).pipe(
199
+ Effect.mapError(() => apiError('create', 'invalid_response')),
200
+ )
201
+ : Effect.fail(apiError('create', 'rejected', response.status)),
202
+ ),
203
+ Effect.catchAll((error) =>
204
+ error instanceof ManualAuthorizationApiError
205
+ ? Effect.fail(error)
206
+ : Effect.fail(apiError('create', 'request_failed')),
207
+ ),
208
+ )
209
+ })
210
+
211
+ const PollErrorBody = Schema.Struct({
212
+ error: Schema.String,
213
+ error_description: Schema.optional(Schema.String),
214
+ })
215
+
216
+ /**
217
+ * RFC 6749/8628 errors that describe the *request* rather than the state
218
+ * of this particular code. Re-minting produces a code that fails
219
+ * identically, so treating these as "expired" is what turns a
220
+ * misconfiguration into an endless approve loop.
221
+ */
222
+ const PERMANENT_REFUSALS = new Set([
223
+ 'invalid_target',
224
+ 'invalid_client',
225
+ 'invalid_scope',
226
+ 'unsupported_grant_type',
227
+ 'unauthorized_client',
228
+ 'invalid_request',
229
+ ])
230
+
231
+ /**
232
+ * One poll of the OAuth token endpoint (RFC 8628). Form-encoded, and
233
+ * always against `/oauth2/token`: `/device/token` is the first-party
234
+ * session path and the server rejects our client there by design.
235
+ */
236
+ const poll = Effect.fn('ManualAuthorizationApiClient.poll')(function* (deviceCode: Redacted.Redacted<string>) {
237
+ const apiUrl = yield* configManager.getApiUrl()
238
+ const request = HttpClientRequest.post(new URL('/api/auth/oauth2/token', apiUrl)).pipe(
239
+ HttpClientRequest.bodyUrlParams({
240
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
241
+ device_code: Redacted.value(deviceCode),
242
+ client_id: CLI_AUTHORIZATION_CLIENT_ID,
243
+ }),
244
+ )
245
+ return yield* httpClient.execute(request).pipe(
246
+ Effect.flatMap(
247
+ Effect.fnUntraced(function* (response) {
248
+ if (response.status >= 200 && response.status < 300) {
249
+ const token = yield* HttpClientResponse.schemaBodyJson(ManualAuthorizationTokenResponse)(response).pipe(
250
+ Effect.mapError(() => apiError('poll', 'invalid_response')),
251
+ )
252
+ return { _tag: 'Tokens', token } satisfies DevicePollOutcome
253
+ }
254
+ if (response.status >= 500) return yield* apiError('poll', 'rejected', response.status)
255
+ const body = yield* HttpClientResponse.schemaBodyJson(PollErrorBody)(response).pipe(
256
+ Effect.mapError(() => apiError('poll', 'invalid_response')),
257
+ )
258
+ switch (body.error) {
259
+ case 'authorization_pending':
260
+ return { _tag: 'Pending' } satisfies DevicePollOutcome
261
+ case 'slow_down':
262
+ return { _tag: 'SlowDown' } satisfies DevicePollOutcome
263
+ case 'access_denied':
264
+ return { _tag: 'Denied' } satisfies DevicePollOutcome
265
+ case 'expired_token':
266
+ return { _tag: 'Expired' } satisfies DevicePollOutcome
267
+ default:
268
+ return PERMANENT_REFUSALS.has(body.error)
269
+ ? ({
270
+ _tag: 'Refused',
271
+ error: body.error,
272
+ ...(body.error_description === undefined ? {} : { description: body.error_description }),
273
+ } satisfies DevicePollOutcome)
274
+ : ({ _tag: 'Invalid' } satisfies DevicePollOutcome)
275
+ }
276
+ }),
277
+ ),
278
+ Effect.catchAll((error) =>
279
+ error instanceof ManualAuthorizationApiError
280
+ ? Effect.fail(error)
281
+ : Effect.fail(apiError('poll', 'request_failed')),
282
+ ),
283
+ )
284
+ })
285
+
286
+ return { create, poll } as const
287
+ }),
288
+ },
289
+ ) {}
99
290
 
100
291
  /**
101
292
  * Resolves outgoing API auth per call. When the current request scope carries
@@ -126,13 +317,14 @@ export class ApiClient extends Effect.Service<ApiClient>()('@expandai/cli/ApiCli
126
317
  const refreshOAuthToken = Effect.fn('ApiClient.refreshOAuthToken')(function* ({
127
318
  refreshToken,
128
319
  expiresAt,
320
+ clientId,
129
321
  }: OAuthAuthMethod) {
130
322
  if (refreshToken === undefined) {
131
323
  return yield* new OAuthTokenExpired({ expiredAt: expiresAt })
132
324
  }
133
325
 
134
326
  const now = yield* DateTime.now
135
- const token = yield* browserOAuth.refreshToken(Redacted.value(refreshToken))
327
+ const token = yield* browserOAuth.refreshToken(Redacted.value(refreshToken), clientId)
136
328
  if (token === undefined) {
137
329
  return yield* new OAuthTokenExpired({ expiredAt: expiresAt })
138
330
  }
@@ -143,6 +335,7 @@ export class ApiClient extends Effect.Service<ApiClient>()('@expandai/cli/ApiCli
143
335
  accessToken: Redacted.make(token.access_token),
144
336
  refreshToken: nextRefreshToken,
145
337
  expiresAt: refreshedExpiresAt,
338
+ clientId,
146
339
  }),
147
340
  })
148
341