agent-relay 1.6.0 → 2.0.4

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 (1074) hide show
  1. package/.cursor/mcp.json +11 -0
  2. package/.gitleaks.toml +26 -0
  3. package/.mcp.json +11 -0
  4. package/.nvmrc +1 -1
  5. package/ARCHITECTURE.md +10 -10
  6. package/CHANGELOG.md +38 -0
  7. package/LICENSE +185 -17
  8. package/README.md +43 -5
  9. package/SESSION_HANDOFF.md +67 -0
  10. package/bin/relay-pty-darwin-arm64 +0 -0
  11. package/bin/relay-pty-darwin-x64 +0 -0
  12. package/bin/relay-pty-linux-x64 +0 -0
  13. package/deploy/workspace/entrypoint.sh +83 -11
  14. package/deploy/workspace/git-credential-relay +152 -27
  15. package/deploy/workspace/git-credential-relay.test.sh +230 -0
  16. package/dist/dashboard/out/404.html +1 -1
  17. package/dist/dashboard/out/_next/static/chunks/116-a883fca163f3a5bc.js +1 -0
  18. package/dist/dashboard/out/_next/static/chunks/320-900169c942e31422.js +1 -0
  19. package/dist/dashboard/out/_next/static/chunks/631-af51bad94027527a.js +1 -0
  20. package/dist/dashboard/out/_next/static/chunks/766-2aea80818f7eb0d8.js +1 -0
  21. package/dist/dashboard/out/_next/static/chunks/891-5cb1513eeb97a891.js +1 -0
  22. package/dist/dashboard/out/_next/static/chunks/app/app/page-2e525b1dcc790967.js +1 -0
  23. package/dist/dashboard/out/_next/static/chunks/app/page-4e64923d73c35bc9.js +1 -0
  24. package/dist/dashboard/out/_next/static/chunks/app/providers/page-e65a0010da6ea5be.js +1 -0
  25. package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-84161c802b020a1f.js +1 -0
  26. package/dist/dashboard/out/_next/static/css/99c2552394077586.css +1 -0
  27. package/dist/dashboard/out/app/onboarding.html +1 -1
  28. package/dist/dashboard/out/app/onboarding.txt +1 -1
  29. package/dist/dashboard/out/app.html +1 -1
  30. package/dist/dashboard/out/app.txt +2 -2
  31. package/dist/dashboard/out/cloud/link.html +1 -1
  32. package/dist/dashboard/out/cloud/link.txt +2 -2
  33. package/dist/dashboard/out/connect-repos.html +1 -1
  34. package/dist/dashboard/out/connect-repos.txt +1 -1
  35. package/dist/dashboard/out/history.html +1 -1
  36. package/dist/dashboard/out/history.txt +2 -2
  37. package/dist/dashboard/out/index.html +1 -1
  38. package/dist/dashboard/out/index.txt +2 -2
  39. package/dist/dashboard/out/login.html +2 -2
  40. package/dist/dashboard/out/login.txt +1 -1
  41. package/dist/dashboard/out/metrics.html +1 -1
  42. package/dist/dashboard/out/metrics.txt +2 -2
  43. package/dist/dashboard/out/pricing.html +3 -3
  44. package/dist/dashboard/out/pricing.txt +2 -2
  45. package/dist/dashboard/out/providers/setup/claude.html +1 -1
  46. package/dist/dashboard/out/providers/setup/claude.txt +2 -2
  47. package/dist/dashboard/out/providers/setup/codex.html +1 -1
  48. package/dist/dashboard/out/providers/setup/codex.txt +2 -2
  49. package/dist/dashboard/out/providers/setup/cursor.html +1 -0
  50. package/dist/dashboard/out/providers/setup/cursor.txt +8 -0
  51. package/dist/dashboard/out/providers.html +1 -1
  52. package/dist/dashboard/out/providers.txt +2 -2
  53. package/dist/dashboard/out/signup.html +2 -2
  54. package/dist/dashboard/out/signup.txt +1 -1
  55. package/dist/src/bridge/index.d.ts +8 -0
  56. package/dist/src/bridge/index.js +8 -0
  57. package/dist/src/cli/index.js +3091 -0
  58. package/dist/src/cloud/index.d.ts +8 -0
  59. package/dist/src/cloud/index.js +8 -0
  60. package/dist/src/config/relay-config.d.ts +5 -0
  61. package/dist/src/config/relay-config.js +5 -0
  62. package/dist/src/continuity/index.d.ts +5 -0
  63. package/dist/src/continuity/index.js +5 -0
  64. package/dist/src/daemon/index.d.ts +8 -0
  65. package/dist/src/daemon/index.js +9 -0
  66. package/dist/src/dashboard-server/index.d.ts +8 -0
  67. package/dist/src/dashboard-server/index.js +8 -0
  68. package/dist/src/hooks/index.d.ts +10 -0
  69. package/dist/src/hooks/index.js +10 -0
  70. package/dist/src/index.d.ts +13 -0
  71. package/dist/src/index.js +16 -0
  72. package/dist/src/memory/index.d.ts +5 -0
  73. package/dist/src/memory/index.js +5 -0
  74. package/dist/src/policy/index.d.ts +5 -0
  75. package/dist/src/policy/index.js +5 -0
  76. package/dist/src/protocol/index.d.ts +8 -0
  77. package/dist/src/protocol/index.js +8 -0
  78. package/dist/src/resiliency/index.d.ts +5 -0
  79. package/dist/src/resiliency/index.js +5 -0
  80. package/dist/src/shared/cli-auth-config.d.ts +5 -0
  81. package/dist/src/shared/cli-auth-config.js +5 -0
  82. package/dist/src/state/index.d.ts +5 -0
  83. package/dist/src/state/index.js +5 -0
  84. package/dist/src/storage/index.d.ts +8 -0
  85. package/dist/src/storage/index.js +8 -0
  86. package/dist/src/trajectory/index.d.ts +5 -0
  87. package/dist/src/trajectory/index.js +5 -0
  88. package/dist/src/utils/index.d.ts +5 -0
  89. package/dist/src/utils/index.js +5 -0
  90. package/dist/src/wrapper/index.d.ts +8 -0
  91. package/dist/src/wrapper/index.js +11 -0
  92. package/package.json +64 -19
  93. package/packages/api-types/dist/index.d.ts +21 -0
  94. package/packages/api-types/dist/index.js +22 -0
  95. package/packages/api-types/dist/schemas/agent.d.ts +259 -0
  96. package/packages/api-types/dist/schemas/agent.js +102 -0
  97. package/packages/api-types/dist/schemas/api.d.ts +290 -0
  98. package/packages/api-types/dist/schemas/api.js +162 -0
  99. package/packages/api-types/dist/schemas/decision.d.ts +230 -0
  100. package/packages/api-types/dist/schemas/decision.js +104 -0
  101. package/packages/api-types/dist/schemas/fleet.d.ts +615 -0
  102. package/packages/api-types/dist/schemas/fleet.js +71 -0
  103. package/packages/api-types/dist/schemas/history.d.ts +180 -0
  104. package/packages/api-types/dist/schemas/history.js +72 -0
  105. package/packages/api-types/dist/schemas/index.d.ts +14 -0
  106. package/packages/api-types/dist/schemas/index.js +22 -0
  107. package/packages/api-types/dist/schemas/message.d.ts +456 -0
  108. package/packages/api-types/dist/schemas/message.js +88 -0
  109. package/packages/api-types/dist/schemas/session.d.ts +60 -0
  110. package/packages/api-types/dist/schemas/session.js +36 -0
  111. package/packages/api-types/dist/schemas/task.d.ts +111 -0
  112. package/packages/api-types/dist/schemas/task.js +64 -0
  113. package/packages/api-types/package.json +61 -0
  114. package/packages/api-types/scripts/generate-openapi.ts +106 -0
  115. package/packages/bridge/dist/index.d.ts +8 -0
  116. package/packages/bridge/dist/index.js +9 -0
  117. package/packages/bridge/dist/multi-project-client.d.ts +99 -0
  118. package/packages/bridge/dist/multi-project-client.js +389 -0
  119. package/packages/bridge/dist/shadow-cli.js +75 -0
  120. package/packages/bridge/dist/spawner.d.ts +210 -0
  121. package/packages/bridge/dist/spawner.js +1278 -0
  122. package/packages/bridge/dist/types.d.ts +131 -0
  123. package/packages/bridge/dist/utils.d.ts +15 -0
  124. package/packages/bridge/dist/utils.js +60 -0
  125. package/packages/bridge/package.json +45 -0
  126. package/packages/cloud/dist/api/admin.js +225 -0
  127. package/packages/cloud/dist/api/billing.js +564 -0
  128. package/packages/cloud/dist/api/cli-pty-runner.d.ts +53 -0
  129. package/packages/cloud/dist/api/cli-pty-runner.js +193 -0
  130. package/packages/cloud/dist/api/codex-auth-helper.js +327 -0
  131. package/packages/cloud/dist/api/consensus.js +261 -0
  132. package/packages/cloud/dist/api/coordinators.js +750 -0
  133. package/packages/cloud/dist/api/daemons.js +535 -0
  134. package/packages/cloud/dist/api/generic-webhooks.js +129 -0
  135. package/packages/cloud/dist/api/github-app.js +223 -0
  136. package/packages/cloud/dist/api/monitoring.js +578 -0
  137. package/packages/cloud/dist/api/nango-auth.js +674 -0
  138. package/packages/cloud/dist/api/onboarding.d.ts +15 -0
  139. package/packages/cloud/dist/api/onboarding.js +679 -0
  140. package/packages/cloud/dist/api/policy.js +229 -0
  141. package/packages/cloud/dist/api/provider-env.d.ts +14 -0
  142. package/packages/cloud/dist/api/provider-env.js +75 -0
  143. package/packages/cloud/dist/api/providers.js +564 -0
  144. package/packages/cloud/dist/api/repos.js +577 -0
  145. package/packages/cloud/dist/api/sessions.d.ts +11 -0
  146. package/packages/cloud/dist/api/sessions.js +302 -0
  147. package/packages/cloud/dist/api/teams.js +281 -0
  148. package/packages/cloud/dist/api/test-helpers.js +745 -0
  149. package/packages/cloud/dist/api/workspaces.js +1799 -0
  150. package/packages/cloud/dist/billing/plans.js +245 -0
  151. package/packages/cloud/dist/config.d.ts +5 -0
  152. package/packages/cloud/dist/config.js +5 -0
  153. package/packages/cloud/dist/db/drizzle.d.ts +256 -0
  154. package/packages/cloud/dist/db/drizzle.js +1286 -0
  155. package/packages/cloud/dist/db/schema.d.ts +4873 -0
  156. package/packages/cloud/dist/db/schema.js +620 -0
  157. package/packages/cloud/dist/index.d.ts +11 -0
  158. package/packages/cloud/dist/index.js +38 -0
  159. package/packages/cloud/dist/provisioner/index.d.ts +207 -0
  160. package/packages/cloud/dist/provisioner/index.js +2114 -0
  161. package/packages/cloud/dist/server.js +1924 -0
  162. package/packages/cloud/dist/services/index.d.ts +17 -0
  163. package/packages/cloud/dist/services/index.js +25 -0
  164. package/packages/cloud/dist/services/intro-expiration.d.ts +60 -0
  165. package/packages/cloud/dist/services/intro-expiration.js +252 -0
  166. package/packages/cloud/dist/services/nango.d.ts +201 -0
  167. package/packages/cloud/dist/services/nango.js +392 -0
  168. package/packages/cloud/dist/services/persistence.d.ts +131 -0
  169. package/packages/cloud/dist/shims/consensus.d.ts +23 -0
  170. package/packages/cloud/dist/shims/consensus.js +5 -0
  171. package/packages/cloud/package.json +60 -0
  172. package/packages/config/dist/bridge-config.d.ts +52 -0
  173. package/packages/config/dist/bridge-config.js +143 -0
  174. package/packages/config/dist/bridge-utils.d.ts +30 -0
  175. package/packages/config/dist/bridge-utils.js +54 -0
  176. package/packages/config/dist/cli-auth-config.js +391 -0
  177. package/packages/config/dist/cloud-config.d.ts +75 -0
  178. package/packages/config/dist/cloud-config.js +109 -0
  179. package/packages/config/dist/index.d.ts +13 -0
  180. package/packages/config/dist/index.js +13 -0
  181. package/packages/config/dist/project-namespace.d.ts +77 -0
  182. package/packages/config/dist/project-namespace.js +288 -0
  183. package/packages/config/dist/relay-config.d.ts +33 -0
  184. package/packages/config/dist/relay-config.js +33 -0
  185. package/packages/config/dist/relay-file-writer.d.ts +200 -0
  186. package/packages/config/dist/relay-file-writer.js +407 -0
  187. package/packages/config/dist/schemas.d.ts +672 -0
  188. package/packages/config/dist/schemas.js +180 -0
  189. package/packages/config/dist/shadow-config.d.ts +87 -0
  190. package/packages/config/dist/trajectory-config.d.ts +102 -0
  191. package/packages/config/dist/trajectory-config.js +185 -0
  192. package/packages/config/package.json +103 -0
  193. package/packages/continuity/dist/index.d.ts +9 -0
  194. package/packages/continuity/dist/index.js +9 -0
  195. package/packages/continuity/dist/types.d.ts +180 -0
  196. package/packages/continuity/dist/types.js +2 -0
  197. package/packages/continuity/package.json +37 -0
  198. package/packages/daemon/dist/agent-manager.d.ts +134 -0
  199. package/packages/daemon/dist/agent-manager.js +578 -0
  200. package/packages/daemon/dist/agent-registry.js +213 -0
  201. package/packages/daemon/dist/api.d.ts +106 -0
  202. package/packages/daemon/dist/api.js +876 -0
  203. package/packages/daemon/dist/channel-membership-store.d.ts +55 -0
  204. package/packages/daemon/dist/channel-membership-store.js +176 -0
  205. package/packages/daemon/dist/cli-auth.d.ts +89 -0
  206. package/packages/daemon/dist/cli-auth.js +792 -0
  207. package/packages/daemon/dist/cloud-sync.d.ts +150 -0
  208. package/packages/daemon/dist/cloud-sync.js +446 -0
  209. package/packages/daemon/dist/connection.d.ts +130 -0
  210. package/packages/daemon/dist/connection.js +438 -0
  211. package/packages/daemon/dist/consensus-integration.js +371 -0
  212. package/packages/daemon/dist/delivery-tracker.d.ts +34 -0
  213. package/packages/daemon/dist/delivery-tracker.js +104 -0
  214. package/packages/daemon/dist/enhanced-features.d.ts +118 -0
  215. package/packages/daemon/dist/enhanced-features.js +176 -0
  216. package/packages/daemon/dist/index.d.ts +31 -0
  217. package/packages/daemon/dist/index.js +37 -0
  218. package/packages/daemon/dist/migrations/index.d.ts +73 -0
  219. package/packages/daemon/dist/migrations/index.js +241 -0
  220. package/packages/daemon/dist/orchestrator.d.ts +217 -0
  221. package/packages/daemon/dist/orchestrator.js +1143 -0
  222. package/packages/daemon/dist/relay-ledger.d.ts +261 -0
  223. package/packages/daemon/dist/relay-ledger.js +532 -0
  224. package/packages/daemon/dist/relay-watchdog.d.ts +125 -0
  225. package/packages/daemon/dist/relay-watchdog.js +611 -0
  226. package/packages/daemon/dist/repo-manager.js +384 -0
  227. package/packages/daemon/dist/router.d.ts +370 -0
  228. package/packages/daemon/dist/router.js +1437 -0
  229. package/packages/daemon/dist/server.d.ts +174 -0
  230. package/packages/daemon/dist/server.js +1001 -0
  231. package/packages/daemon/dist/spawn-manager.d.ts +78 -0
  232. package/packages/daemon/dist/spawn-manager.js +165 -0
  233. package/packages/daemon/dist/sync-queue.d.ts +116 -0
  234. package/packages/daemon/dist/sync-queue.js +361 -0
  235. package/packages/daemon/dist/types.d.ts +133 -0
  236. package/packages/daemon/dist/workspace-manager.js +314 -0
  237. package/packages/daemon/package.json +57 -0
  238. package/packages/dashboard/README.md +48 -0
  239. package/packages/dashboard/dist/health-worker-manager.d.ts +62 -0
  240. package/packages/dashboard/dist/health-worker-manager.js +144 -0
  241. package/packages/dashboard/dist/health-worker.d.ts +9 -0
  242. package/packages/dashboard/dist/health-worker.js +79 -0
  243. package/packages/dashboard/dist/index.d.ts +20 -0
  244. package/packages/dashboard/dist/index.js +19 -0
  245. package/packages/dashboard/dist/metrics.d.ts +105 -0
  246. package/packages/dashboard/dist/metrics.js +193 -0
  247. package/packages/dashboard/dist/needs-attention.d.ts +24 -0
  248. package/packages/dashboard/dist/needs-attention.js +78 -0
  249. package/packages/dashboard/dist/server.d.ts +25 -0
  250. package/packages/dashboard/dist/server.js +5179 -0
  251. package/packages/dashboard/dist/start.d.ts +6 -0
  252. package/packages/dashboard/dist/start.js +13 -0
  253. package/packages/dashboard/dist/types/threading.d.ts +8 -0
  254. package/packages/dashboard/dist/types/threading.js +2 -0
  255. package/packages/dashboard/dist/user-bridge.d.ts +154 -0
  256. package/packages/dashboard/dist/user-bridge.js +372 -0
  257. package/packages/dashboard/package.json +64 -0
  258. package/packages/dashboard/ui/app/app/onboarding/page.tsx +394 -0
  259. package/packages/dashboard/ui/app/app/page.tsx +667 -0
  260. package/packages/dashboard/ui/app/apple-icon.png +0 -0
  261. package/packages/dashboard/ui/app/cloud/link/page.tsx +464 -0
  262. package/packages/dashboard/ui/app/connect-repos/page.tsx +410 -0
  263. package/packages/dashboard/ui/app/favicon.png +0 -0
  264. package/packages/dashboard/ui/app/globals.css +59 -0
  265. package/packages/dashboard/ui/app/history/page.tsx +658 -0
  266. package/packages/dashboard/ui/app/layout.tsx +25 -0
  267. package/packages/dashboard/ui/app/login/page.tsx +280 -0
  268. package/packages/dashboard/ui/app/metrics/page.tsx +751 -0
  269. package/packages/dashboard/ui/app/page.tsx +59 -0
  270. package/packages/dashboard/ui/app/pricing/page.tsx +7 -0
  271. package/packages/dashboard/ui/app/providers/page.tsx +193 -0
  272. package/packages/dashboard/ui/app/providers/setup/[provider]/ProviderSetupClient.tsx +148 -0
  273. package/packages/dashboard/ui/app/providers/setup/[provider]/constants.ts +35 -0
  274. package/packages/dashboard/ui/app/providers/setup/[provider]/page.tsx +42 -0
  275. package/packages/dashboard/ui/app/signup/page.tsx +343 -0
  276. package/packages/dashboard/ui/index.ts +49 -0
  277. package/packages/dashboard/ui/landing/LandingPage.tsx +713 -0
  278. package/packages/dashboard/ui/landing/PricingPage.tsx +559 -0
  279. package/packages/dashboard/ui/landing/index.ts +6 -0
  280. package/packages/dashboard/ui/landing/styles.css +2850 -0
  281. package/packages/dashboard/ui/lib/agent-merge.ts +35 -0
  282. package/packages/dashboard/ui/lib/api.ts +1155 -0
  283. package/packages/dashboard/ui/lib/cloudApi.ts +876 -0
  284. package/packages/dashboard/ui/lib/colors.ts +218 -0
  285. package/packages/dashboard/ui/lib/hierarchy.ts +242 -0
  286. package/packages/dashboard/ui/lib/stuckDetection.ts +142 -0
  287. package/packages/dashboard/ui/next-env.d.ts +5 -0
  288. package/packages/dashboard/ui/next.config.js +41 -0
  289. package/packages/dashboard/ui/package-lock.json +2882 -0
  290. package/packages/dashboard/ui/package.json +33 -0
  291. package/packages/dashboard/ui/postcss.config.js +5 -0
  292. package/packages/dashboard/ui/react-components/ActivityFeed.tsx +216 -0
  293. package/packages/dashboard/ui/react-components/AddWorkspaceModal.tsx +170 -0
  294. package/packages/dashboard/ui/react-components/AgentCard.tsx +587 -0
  295. package/packages/dashboard/ui/react-components/AgentList.tsx +411 -0
  296. package/packages/dashboard/ui/react-components/AgentProfilePanel.tsx +564 -0
  297. package/packages/dashboard/ui/react-components/App.tsx +3447 -0
  298. package/packages/dashboard/ui/react-components/BillingPanel.tsx +922 -0
  299. package/packages/dashboard/ui/react-components/BillingResult.tsx +447 -0
  300. package/packages/dashboard/ui/react-components/BroadcastComposer.tsx +690 -0
  301. package/packages/dashboard/ui/react-components/ChannelAdminPanel.tsx +773 -0
  302. package/packages/dashboard/ui/react-components/ChannelBrowser.tsx +385 -0
  303. package/packages/dashboard/ui/react-components/ChannelChat.tsx +307 -0
  304. package/packages/dashboard/ui/react-components/ChannelSidebar.tsx +399 -0
  305. package/packages/dashboard/ui/react-components/CloudSessionProvider.tsx +130 -0
  306. package/packages/dashboard/ui/react-components/CommandPalette.tsx +815 -0
  307. package/packages/dashboard/ui/react-components/ConfirmationDialog.tsx +133 -0
  308. package/packages/dashboard/ui/react-components/ConversationHistory.tsx +518 -0
  309. package/packages/dashboard/ui/react-components/CoordinatorPanel.tsx +944 -0
  310. package/packages/dashboard/ui/react-components/DecisionQueue.tsx +717 -0
  311. package/packages/dashboard/ui/react-components/DirectMessageView.tsx +164 -0
  312. package/packages/dashboard/ui/react-components/FileAutocomplete.tsx +368 -0
  313. package/packages/dashboard/ui/react-components/FleetOverview.tsx +278 -0
  314. package/packages/dashboard/ui/react-components/LogViewer.tsx +310 -0
  315. package/packages/dashboard/ui/react-components/LogViewerPanel.tsx +482 -0
  316. package/packages/dashboard/ui/react-components/Logo.tsx +284 -0
  317. package/packages/dashboard/ui/react-components/MentionAutocomplete.tsx +384 -0
  318. package/packages/dashboard/ui/react-components/MessageList.tsx +649 -0
  319. package/packages/dashboard/ui/react-components/MessageSenderName.tsx +91 -0
  320. package/packages/dashboard/ui/react-components/MessageStatusIndicator.tsx +142 -0
  321. package/packages/dashboard/ui/react-components/NewConversationModal.tsx +400 -0
  322. package/packages/dashboard/ui/react-components/NotificationToast.tsx +488 -0
  323. package/packages/dashboard/ui/react-components/OnlineUsersIndicator.tsx +164 -0
  324. package/packages/dashboard/ui/react-components/Pagination.tsx +124 -0
  325. package/packages/dashboard/ui/react-components/PricingPlans.tsx +386 -0
  326. package/packages/dashboard/ui/react-components/ProjectList.tsx +625 -0
  327. package/packages/dashboard/ui/react-components/ProviderAuthFlow.tsx +843 -0
  328. package/packages/dashboard/ui/react-components/ProviderConnectionList.tsx +363 -0
  329. package/packages/dashboard/ui/react-components/ProvisioningProgress.tsx +730 -0
  330. package/packages/dashboard/ui/react-components/RepoAccessPanel.tsx +392 -0
  331. package/packages/dashboard/ui/react-components/ServerCard.tsx +202 -0
  332. package/packages/dashboard/ui/react-components/SessionExpiredModal.tsx +128 -0
  333. package/packages/dashboard/ui/react-components/SpawnModal.tsx +794 -0
  334. package/packages/dashboard/ui/react-components/TaskAssignmentUI.tsx +375 -0
  335. package/packages/dashboard/ui/react-components/TerminalProviderSetup.tsx +608 -0
  336. package/packages/dashboard/ui/react-components/ThemeProvider.tsx +325 -0
  337. package/packages/dashboard/ui/react-components/ThinkingIndicator.tsx +231 -0
  338. package/packages/dashboard/ui/react-components/ThreadList.tsx +198 -0
  339. package/packages/dashboard/ui/react-components/ThreadPanel.tsx +346 -0
  340. package/packages/dashboard/ui/react-components/TrajectoryViewer.tsx +698 -0
  341. package/packages/dashboard/ui/react-components/TypingIndicator.tsx +69 -0
  342. package/packages/dashboard/ui/react-components/UsageBanner.tsx +231 -0
  343. package/packages/dashboard/ui/react-components/UserProfilePanel.tsx +233 -0
  344. package/packages/dashboard/ui/react-components/WorkspaceContext.tsx +107 -0
  345. package/packages/dashboard/ui/react-components/WorkspaceSelector.tsx +234 -0
  346. package/packages/dashboard/ui/react-components/WorkspaceStatusIndicator.tsx +370 -0
  347. package/packages/dashboard/ui/react-components/XTermInteractive.tsx +510 -0
  348. package/packages/dashboard/ui/react-components/XTermLogViewer.tsx +719 -0
  349. package/packages/dashboard/ui/react-components/channels/ChannelDialogs.tsx +1411 -0
  350. package/packages/dashboard/ui/react-components/channels/ChannelHeader.tsx +317 -0
  351. package/packages/dashboard/ui/react-components/channels/ChannelMessageList.tsx +463 -0
  352. package/packages/dashboard/ui/react-components/channels/ChannelViewV1.tsx +146 -0
  353. package/packages/dashboard/ui/react-components/channels/MessageInput.tsx +288 -0
  354. package/packages/dashboard/ui/react-components/channels/SearchInput.tsx +172 -0
  355. package/packages/dashboard/ui/react-components/channels/SearchResults.tsx +336 -0
  356. package/packages/dashboard/ui/react-components/channels/api.ts +697 -0
  357. package/packages/dashboard/ui/react-components/channels/index.ts +76 -0
  358. package/packages/dashboard/ui/react-components/channels/mockApi.ts +344 -0
  359. package/packages/dashboard/ui/react-components/channels/types.ts +566 -0
  360. package/packages/dashboard/ui/react-components/hooks/index.ts +57 -0
  361. package/packages/dashboard/ui/react-components/hooks/useAgentLogs.ts +394 -0
  362. package/packages/dashboard/ui/react-components/hooks/useAgents.ts +127 -0
  363. package/packages/dashboard/ui/react-components/hooks/useBroadcastDedup.ts +86 -0
  364. package/packages/dashboard/ui/react-components/hooks/useChannelAdmin.ts +329 -0
  365. package/packages/dashboard/ui/react-components/hooks/useChannelBrowser.ts +239 -0
  366. package/packages/dashboard/ui/react-components/hooks/useChannelCommands.ts +138 -0
  367. package/packages/dashboard/ui/react-components/hooks/useChannels.ts +328 -0
  368. package/packages/dashboard/ui/react-components/hooks/useDebounce.ts +29 -0
  369. package/packages/dashboard/ui/react-components/hooks/useDirectMessage.ts +141 -0
  370. package/packages/dashboard/ui/react-components/hooks/useMessages.ts +309 -0
  371. package/packages/dashboard/ui/react-components/hooks/useOrchestrator.ts +364 -0
  372. package/packages/dashboard/ui/react-components/hooks/usePinnedAgents.ts +140 -0
  373. package/packages/dashboard/ui/react-components/hooks/usePresence.ts +340 -0
  374. package/packages/dashboard/ui/react-components/hooks/useRecentRepos.ts +130 -0
  375. package/packages/dashboard/ui/react-components/hooks/useSession.ts +209 -0
  376. package/packages/dashboard/ui/react-components/hooks/useTrajectory.ts +265 -0
  377. package/packages/dashboard/ui/react-components/hooks/useWebSocket.ts +169 -0
  378. package/packages/dashboard/ui/react-components/hooks/useWorkspaceMembers.ts +120 -0
  379. package/packages/dashboard/ui/react-components/hooks/useWorkspaceRepos.ts +73 -0
  380. package/packages/dashboard/ui/react-components/hooks/useWorkspaceStatus.ts +237 -0
  381. package/packages/dashboard/ui/react-components/index.ts +81 -0
  382. package/packages/dashboard/ui/react-components/layout/Header.tsx +355 -0
  383. package/packages/dashboard/ui/react-components/layout/RepoContextHeader.tsx +361 -0
  384. package/packages/dashboard/ui/react-components/layout/Sidebar.archive.test.tsx +126 -0
  385. package/packages/dashboard/ui/react-components/layout/Sidebar.test.tsx +691 -0
  386. package/packages/dashboard/ui/react-components/layout/Sidebar.tsx +930 -0
  387. package/packages/dashboard/ui/react-components/layout/index.ts +7 -0
  388. package/packages/dashboard/ui/react-components/settings/BillingSettingsPanel.tsx +564 -0
  389. package/packages/dashboard/ui/react-components/settings/SettingsPage.tsx +544 -0
  390. package/packages/dashboard/ui/react-components/settings/TeamSettingsPanel.tsx +560 -0
  391. package/packages/dashboard/ui/react-components/settings/WorkspaceSettingsPanel.tsx +1329 -0
  392. package/packages/dashboard/ui/react-components/settings/index.ts +11 -0
  393. package/packages/dashboard/ui/react-components/settings/types.ts +53 -0
  394. package/packages/dashboard/ui/react-components/utils/messageFormatting.tsx +370 -0
  395. package/packages/dashboard/ui/tailwind.config.js +148 -0
  396. package/packages/dashboard/ui/types/index.ts +304 -0
  397. package/packages/dashboard/ui/types/threading.ts +7 -0
  398. package/packages/dashboard/ui-dist/404.html +1 -0
  399. package/packages/dashboard/ui-dist/_next/static/0AsOfRemPXJmtynCKT-rx/_buildManifest.js +1 -0
  400. package/packages/dashboard/ui-dist/_next/static/0AsOfRemPXJmtynCKT-rx/_ssgManifest.js +1 -0
  401. package/packages/dashboard/ui-dist/_next/static/72btMIJ64BCAB4UgVkpaq/_buildManifest.js +1 -0
  402. package/packages/dashboard/ui-dist/_next/static/72btMIJ64BCAB4UgVkpaq/_ssgManifest.js +1 -0
  403. package/packages/dashboard/ui-dist/_next/static/chunks/116-a883fca163f3a5bc.js +1 -0
  404. package/packages/dashboard/ui-dist/_next/static/chunks/117-c8afed19e821a35d.js +2 -0
  405. package/packages/dashboard/ui-dist/_next/static/chunks/282-980c2eb8fff20123.js +1 -0
  406. package/packages/dashboard/ui-dist/_next/static/chunks/320-900169c942e31422.js +1 -0
  407. package/packages/dashboard/ui-dist/_next/static/chunks/532-bace199897eeab37.js +9 -0
  408. package/packages/dashboard/ui-dist/_next/static/chunks/631-af51bad94027527a.js +1 -0
  409. package/packages/dashboard/ui-dist/_next/static/chunks/648-acb2ff9f77cbfbd3.js +1 -0
  410. package/packages/dashboard/ui-dist/_next/static/chunks/677-7323947c23b35979.js +1 -0
  411. package/packages/dashboard/ui-dist/_next/static/chunks/766-2aea80818f7eb0d8.js +1 -0
  412. package/packages/dashboard/ui-dist/_next/static/chunks/83-4f08122d4e7e79a6.js +1 -0
  413. package/packages/dashboard/ui-dist/_next/static/chunks/847-f1f467060f32afff.js +1 -0
  414. package/packages/dashboard/ui-dist/_next/static/chunks/891-5cb1513eeb97a891.js +1 -0
  415. package/packages/dashboard/ui-dist/_next/static/chunks/app/_not-found/page-60501fddbafba9dc.js +1 -0
  416. package/packages/dashboard/ui-dist/_next/static/chunks/app/app/onboarding/page-9914652442f7e4fb.js +1 -0
  417. package/packages/dashboard/ui-dist/_next/static/chunks/app/app/onboarding/page-f746f29e01fffc43.js +1 -0
  418. package/packages/dashboard/ui-dist/_next/static/chunks/app/app/page-2e525b1dcc790967.js +1 -0
  419. package/packages/dashboard/ui-dist/_next/static/chunks/app/app/page-44813aa26ad19681.js +1 -0
  420. package/packages/dashboard/ui-dist/_next/static/chunks/app/cloud/link/page-5011ae044b90449d.js +1 -0
  421. package/packages/dashboard/ui-dist/_next/static/chunks/app/cloud/link/page-fa1d5842aa90e8a6.js +1 -0
  422. package/packages/dashboard/ui-dist/_next/static/chunks/app/connect-repos/page-03ac6f35a6654ea6.js +1 -0
  423. package/packages/dashboard/ui-dist/_next/static/chunks/app/connect-repos/page-113060009ef35bc2.js +1 -0
  424. package/packages/dashboard/ui-dist/_next/static/chunks/app/history/page-9965d2483011b846.js +1 -0
  425. package/packages/dashboard/ui-dist/_next/static/chunks/app/history/page-b2ce7c96ed0931da.js +1 -0
  426. package/packages/dashboard/ui-dist/_next/static/chunks/app/layout-6b91e33784c20610.js +1 -0
  427. package/packages/dashboard/ui-dist/_next/static/chunks/app/layout-c0d118c0f92d969c.js +1 -0
  428. package/packages/dashboard/ui-dist/_next/static/chunks/app/login/page-6ec54eee75877971.js +1 -0
  429. package/packages/dashboard/ui-dist/_next/static/chunks/app/login/page-a0ca6f7ca6a100b8.js +1 -0
  430. package/packages/dashboard/ui-dist/_next/static/chunks/app/metrics/page-1e37ef8e73940b40.js +1 -0
  431. package/packages/dashboard/ui-dist/_next/static/chunks/app/metrics/page-bf2cb1e5915bc92d.js +1 -0
  432. package/packages/dashboard/ui-dist/_next/static/chunks/app/page-4e64923d73c35bc9.js +1 -0
  433. package/packages/dashboard/ui-dist/_next/static/chunks/app/page-7993778218818ace.js +1 -0
  434. package/packages/dashboard/ui-dist/_next/static/chunks/app/pricing/page-0efa024c28ba4597.js +1 -0
  435. package/packages/dashboard/ui-dist/_next/static/chunks/app/pricing/page-9db3ebdfa567a7c9.js +1 -0
  436. package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/page-bcf46064ac4474ce.js +1 -0
  437. package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/page-e65a0010da6ea5be.js +1 -0
  438. package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/setup/[provider]/page-4dbe33f0f7691b7c.js +1 -0
  439. package/packages/dashboard/ui-dist/_next/static/chunks/app/providers/setup/[provider]/page-84161c802b020a1f.js +1 -0
  440. package/packages/dashboard/ui-dist/_next/static/chunks/app/signup/page-18a4665665f6be11.js +1 -0
  441. package/packages/dashboard/ui-dist/_next/static/chunks/app/signup/page-1ede2205b58649ca.js +1 -0
  442. package/packages/dashboard/ui-dist/_next/static/chunks/e868780c-48e5f147c90a3a41.js +18 -0
  443. package/packages/dashboard/ui-dist/_next/static/chunks/fd9d1056-609918ca7b6280bb.js +1 -0
  444. package/packages/dashboard/ui-dist/_next/static/chunks/framework-f66176bb897dc684.js +1 -0
  445. package/packages/dashboard/ui-dist/_next/static/chunks/main-5a40a5ae29646e1b.js +1 -0
  446. package/packages/dashboard/ui-dist/_next/static/chunks/main-app-6e8e8d3ef4e0192a.js +1 -0
  447. package/packages/dashboard/ui-dist/_next/static/chunks/main-app-fdbeb09028f57c9f.js +1 -0
  448. package/packages/dashboard/ui-dist/_next/static/chunks/pages/_app-72b849fbd24ac258.js +1 -0
  449. package/packages/dashboard/ui-dist/_next/static/chunks/pages/_error-7ba65e1336b92748.js +1 -0
  450. package/packages/dashboard/ui-dist/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  451. package/packages/dashboard/ui-dist/_next/static/chunks/webpack-1cdd8ed57114d5e1.js +1 -0
  452. package/packages/dashboard/ui-dist/_next/static/clUN2n0bz9HCjKI0qlOxU/_buildManifest.js +1 -0
  453. package/packages/dashboard/ui-dist/_next/static/clUN2n0bz9HCjKI0qlOxU/_ssgManifest.js +1 -0
  454. package/packages/dashboard/ui-dist/_next/static/css/4034f236dd1a3178.css +1 -0
  455. package/packages/dashboard/ui-dist/_next/static/css/99c2552394077586.css +1 -0
  456. package/packages/dashboard/ui-dist/alt-logos/agent-relay-logo-128.png +0 -0
  457. package/packages/dashboard/ui-dist/alt-logos/agent-relay-logo-256.png +0 -0
  458. package/packages/dashboard/ui-dist/alt-logos/agent-relay-logo-32.png +0 -0
  459. package/packages/dashboard/ui-dist/alt-logos/agent-relay-logo-512.png +0 -0
  460. package/packages/dashboard/ui-dist/alt-logos/agent-relay-logo-64.png +0 -0
  461. package/packages/dashboard/ui-dist/alt-logos/agent-relay-logo.svg +45 -0
  462. package/packages/dashboard/ui-dist/alt-logos/logo.svg +38 -0
  463. package/packages/dashboard/ui-dist/alt-logos/monogram-logo-128.png +0 -0
  464. package/packages/dashboard/ui-dist/alt-logos/monogram-logo-256.png +0 -0
  465. package/packages/dashboard/ui-dist/alt-logos/monogram-logo-32.png +0 -0
  466. package/packages/dashboard/ui-dist/alt-logos/monogram-logo-512.png +0 -0
  467. package/packages/dashboard/ui-dist/alt-logos/monogram-logo-64.png +0 -0
  468. package/packages/dashboard/ui-dist/alt-logos/monogram-logo.svg +38 -0
  469. package/packages/dashboard/ui-dist/app/onboarding.html +1 -0
  470. package/packages/dashboard/ui-dist/app/onboarding.txt +7 -0
  471. package/packages/dashboard/ui-dist/app.html +1 -0
  472. package/packages/dashboard/ui-dist/app.txt +7 -0
  473. package/packages/dashboard/ui-dist/apple-icon.png +0 -0
  474. package/packages/dashboard/ui-dist/cloud/link.html +1 -0
  475. package/packages/dashboard/ui-dist/cloud/link.txt +7 -0
  476. package/packages/dashboard/ui-dist/connect-repos.html +1 -0
  477. package/packages/dashboard/ui-dist/connect-repos.txt +7 -0
  478. package/packages/dashboard/ui-dist/history.html +1 -0
  479. package/packages/dashboard/ui-dist/history.txt +7 -0
  480. package/packages/dashboard/ui-dist/index.html +1 -0
  481. package/packages/dashboard/ui-dist/index.txt +7 -0
  482. package/packages/dashboard/ui-dist/login.html +5 -0
  483. package/packages/dashboard/ui-dist/login.txt +7 -0
  484. package/packages/dashboard/ui-dist/metrics.html +1 -0
  485. package/packages/dashboard/ui-dist/metrics.txt +7 -0
  486. package/packages/dashboard/ui-dist/pricing.html +13 -0
  487. package/packages/dashboard/ui-dist/pricing.txt +7 -0
  488. package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -0
  489. package/packages/dashboard/ui-dist/providers/setup/claude.txt +8 -0
  490. package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -0
  491. package/packages/dashboard/ui-dist/providers/setup/codex.txt +8 -0
  492. package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -0
  493. package/packages/dashboard/ui-dist/providers/setup/cursor.txt +8 -0
  494. package/packages/dashboard/ui-dist/providers.html +1 -0
  495. package/packages/dashboard/ui-dist/providers.txt +7 -0
  496. package/packages/dashboard/ui-dist/signup.html +6 -0
  497. package/packages/dashboard/ui-dist/signup.txt +7 -0
  498. package/packages/dashboard-server/dist/health-worker-manager.d.ts +62 -0
  499. package/packages/dashboard-server/dist/health-worker-manager.js +144 -0
  500. package/packages/dashboard-server/dist/health-worker.d.ts +9 -0
  501. package/packages/dashboard-server/dist/health-worker.js +79 -0
  502. package/packages/dashboard-server/dist/index.d.ts +18 -0
  503. package/packages/dashboard-server/dist/index.js +17 -0
  504. package/packages/dashboard-server/dist/server.js +5099 -0
  505. package/packages/dashboard-server/dist/start.js +13 -0
  506. package/packages/dashboard-server/dist/types/threading.d.ts +8 -0
  507. package/packages/dashboard-server/dist/types/threading.js +2 -0
  508. package/packages/dashboard-server/dist/user-bridge.d.ts +154 -0
  509. package/packages/dashboard-server/dist/user-bridge.js +372 -0
  510. package/packages/dashboard-server/package.json +55 -0
  511. package/packages/hooks/dist/browser.d.ts +2 -0
  512. package/packages/hooks/dist/browser.js +3 -0
  513. package/packages/hooks/dist/index.d.ts +11 -0
  514. package/packages/hooks/dist/index.js +11 -0
  515. package/packages/hooks/dist/registry.js +476 -0
  516. package/packages/hooks/dist/trajectory-hooks.js +183 -0
  517. package/packages/hooks/dist/types.d.ts +285 -0
  518. package/packages/hooks/dist/types.js +10 -0
  519. package/packages/hooks/package.json +57 -0
  520. package/packages/mcp/LICENSE +190 -0
  521. package/packages/mcp/README.md +330 -0
  522. package/packages/mcp/SPEC.md +1922 -0
  523. package/packages/mcp/STAFFING_PLAN.md +294 -0
  524. package/packages/mcp/dist/bin.d.ts +12 -0
  525. package/packages/mcp/dist/bin.js +127 -0
  526. package/packages/mcp/dist/client.d.ts +68 -0
  527. package/packages/mcp/dist/client.js +115 -0
  528. package/packages/mcp/dist/cloud.d.ts +108 -0
  529. package/packages/mcp/dist/cloud.js +328 -0
  530. package/packages/mcp/dist/errors.d.ts +27 -0
  531. package/packages/mcp/dist/errors.js +48 -0
  532. package/packages/mcp/dist/index.d.ts +10 -0
  533. package/packages/mcp/dist/index.js +16 -0
  534. package/packages/mcp/dist/install-cli.d.ts +35 -0
  535. package/packages/mcp/dist/install-cli.js +157 -0
  536. package/packages/mcp/dist/install.d.ts +101 -0
  537. package/packages/mcp/dist/install.js +398 -0
  538. package/packages/mcp/dist/prompts/index.d.ts +2 -0
  539. package/packages/mcp/dist/prompts/index.js +2 -0
  540. package/packages/mcp/dist/prompts/protocol.d.ts +11 -0
  541. package/packages/mcp/dist/prompts/protocol.js +168 -0
  542. package/packages/mcp/dist/resources/agents.d.ts +11 -0
  543. package/packages/mcp/dist/resources/agents.js +17 -0
  544. package/packages/mcp/dist/resources/inbox.d.ts +11 -0
  545. package/packages/mcp/dist/resources/inbox.js +17 -0
  546. package/packages/mcp/dist/resources/index.d.ts +4 -0
  547. package/packages/mcp/dist/resources/index.js +4 -0
  548. package/packages/mcp/dist/resources/project.d.ts +11 -0
  549. package/packages/mcp/dist/resources/project.js +21 -0
  550. package/packages/mcp/dist/server.d.ts +19 -0
  551. package/packages/mcp/dist/server.js +215 -0
  552. package/packages/mcp/dist/simple.d.ts +173 -0
  553. package/packages/mcp/dist/simple.js +120 -0
  554. package/packages/mcp/dist/tools/index.d.ts +10 -0
  555. package/packages/mcp/dist/tools/index.js +10 -0
  556. package/packages/mcp/dist/tools/relay-health.d.ts +23 -0
  557. package/packages/mcp/dist/tools/relay-health.js +138 -0
  558. package/packages/mcp/dist/tools/relay-inbox.d.ts +26 -0
  559. package/packages/mcp/dist/tools/relay-inbox.js +58 -0
  560. package/packages/mcp/dist/tools/relay-logs.d.ts +20 -0
  561. package/packages/mcp/dist/tools/relay-logs.js +88 -0
  562. package/packages/mcp/dist/tools/relay-metrics.d.ts +20 -0
  563. package/packages/mcp/dist/tools/relay-metrics.js +135 -0
  564. package/packages/mcp/dist/tools/relay-release.d.ts +20 -0
  565. package/packages/mcp/dist/tools/relay-release.js +44 -0
  566. package/packages/mcp/dist/tools/relay-send.d.ts +29 -0
  567. package/packages/mcp/dist/tools/relay-send.js +71 -0
  568. package/packages/mcp/dist/tools/relay-spawn.d.ts +36 -0
  569. package/packages/mcp/dist/tools/relay-spawn.js +73 -0
  570. package/packages/mcp/dist/tools/relay-status.d.ts +11 -0
  571. package/packages/mcp/dist/tools/relay-status.js +43 -0
  572. package/packages/mcp/dist/tools/relay-who.d.ts +20 -0
  573. package/packages/mcp/dist/tools/relay-who.js +47 -0
  574. package/packages/mcp/package.json +69 -0
  575. package/packages/memory/dist/memory-hooks.d.ts +60 -0
  576. package/packages/memory/package.json +40 -0
  577. package/packages/policy/dist/agent-policy.js +665 -0
  578. package/packages/policy/dist/index.d.ts +12 -0
  579. package/packages/policy/dist/index.js +12 -0
  580. package/packages/policy/package.json +40 -0
  581. package/packages/protocol/dist/channels.d.ts +137 -0
  582. package/packages/protocol/dist/channels.js +154 -0
  583. package/packages/protocol/dist/framing.d.ts +80 -0
  584. package/packages/protocol/dist/framing.js +206 -0
  585. package/packages/protocol/dist/index.d.ts +5 -0
  586. package/packages/protocol/dist/index.js +5 -0
  587. package/packages/protocol/dist/relay-pty-schemas.d.ts +258 -0
  588. package/packages/protocol/dist/types.d.ts +341 -0
  589. package/packages/protocol/dist/types.js +8 -0
  590. package/packages/protocol/package.json +61 -0
  591. package/packages/resiliency/dist/memory-monitor.js +599 -0
  592. package/packages/resiliency/dist/provider-context.d.ts +100 -0
  593. package/packages/resiliency/package.json +38 -0
  594. package/packages/sdk/README.md +288 -0
  595. package/packages/sdk/dist/client.d.ts +277 -0
  596. package/packages/sdk/dist/client.js +802 -0
  597. package/packages/sdk/dist/discovery.d.ts +29 -0
  598. package/packages/sdk/dist/discovery.js +126 -0
  599. package/packages/sdk/dist/index.d.ts +33 -0
  600. package/packages/sdk/dist/index.js +38 -0
  601. package/packages/sdk/dist/protocol/framing.d.ts +80 -0
  602. package/packages/sdk/dist/protocol/framing.js +206 -0
  603. package/packages/sdk/dist/protocol/index.d.ts +6 -0
  604. package/packages/sdk/dist/protocol/index.js +6 -0
  605. package/packages/sdk/dist/protocol/types.d.ts +341 -0
  606. package/packages/sdk/dist/protocol/types.js +8 -0
  607. package/packages/sdk/dist/standalone.d.ts +87 -0
  608. package/packages/sdk/dist/standalone.js +127 -0
  609. package/packages/sdk/package.json +80 -0
  610. package/packages/spawner/API.md +256 -0
  611. package/packages/spawner/dist/index.d.ts +8 -0
  612. package/packages/spawner/dist/index.js +8 -0
  613. package/packages/spawner/dist/types.d.ts +552 -0
  614. package/packages/spawner/dist/types.js +193 -0
  615. package/packages/spawner/package.json +47 -0
  616. package/packages/state/dist/agent-state.js +120 -0
  617. package/packages/state/dist/index.d.ts +8 -0
  618. package/packages/state/dist/index.js +8 -0
  619. package/packages/state/package.json +37 -0
  620. package/packages/storage/dist/adapter.d.ts +156 -0
  621. package/packages/storage/dist/batched-sqlite-adapter.d.ts +75 -0
  622. package/packages/storage/dist/batched-sqlite-adapter.js +189 -0
  623. package/packages/storage/dist/index.d.ts +5 -0
  624. package/packages/storage/dist/index.js +6 -0
  625. package/packages/storage/dist/sqlite-adapter.d.ts +113 -0
  626. package/packages/storage/dist/sqlite-adapter.js +752 -0
  627. package/packages/storage/package.json +74 -0
  628. package/packages/trajectory/dist/index.d.ts +2 -0
  629. package/packages/trajectory/dist/index.js +2 -0
  630. package/packages/trajectory/dist/integration.js +987 -0
  631. package/packages/trajectory/package.json +40 -0
  632. package/packages/user-directory/dist/index.d.ts +7 -0
  633. package/packages/user-directory/dist/index.js +7 -0
  634. package/packages/user-directory/dist/user-directory.d.ts +121 -0
  635. package/packages/user-directory/dist/user-directory.js +267 -0
  636. package/packages/user-directory/package.json +40 -0
  637. package/packages/utils/dist/command-resolver.js +80 -0
  638. package/packages/utils/dist/error-tracking.d.ts +103 -0
  639. package/packages/utils/dist/error-tracking.js +149 -0
  640. package/packages/utils/dist/index.d.ts +9 -0
  641. package/packages/utils/dist/index.js +9 -0
  642. package/packages/utils/dist/model-mapping.d.ts +28 -0
  643. package/packages/utils/dist/model-mapping.js +55 -0
  644. package/packages/utils/package.json +80 -0
  645. package/packages/wrapper/dist/__fixtures__/claude-outputs.d.ts +49 -0
  646. package/packages/wrapper/dist/__fixtures__/claude-outputs.js +443 -0
  647. package/packages/wrapper/dist/__fixtures__/codex-outputs.d.ts +9 -0
  648. package/packages/wrapper/dist/__fixtures__/codex-outputs.js +94 -0
  649. package/packages/wrapper/dist/__fixtures__/gemini-outputs.d.ts +19 -0
  650. package/packages/wrapper/dist/__fixtures__/gemini-outputs.js +144 -0
  651. package/packages/wrapper/dist/__fixtures__/index.d.ts +68 -0
  652. package/packages/wrapper/dist/__fixtures__/index.js +44 -0
  653. package/packages/wrapper/dist/base-wrapper.d.ts +249 -0
  654. package/packages/wrapper/dist/base-wrapper.js +614 -0
  655. package/packages/wrapper/dist/client.d.ts +254 -0
  656. package/packages/wrapper/dist/client.js +801 -0
  657. package/packages/wrapper/dist/id-generator.d.ts +35 -0
  658. package/packages/wrapper/dist/id-generator.js +60 -0
  659. package/packages/wrapper/dist/idle-detector.d.ts +110 -0
  660. package/packages/wrapper/dist/idle-detector.js +304 -0
  661. package/packages/wrapper/dist/index.d.ts +37 -0
  662. package/packages/wrapper/dist/index.js +47 -0
  663. package/packages/wrapper/dist/parser.d.ts +236 -0
  664. package/packages/wrapper/dist/parser.js +1238 -0
  665. package/packages/wrapper/dist/relay-pty-orchestrator.d.ts +409 -0
  666. package/packages/wrapper/dist/relay-pty-orchestrator.js +1930 -0
  667. package/packages/wrapper/dist/shared.d.ts +226 -0
  668. package/packages/wrapper/dist/shared.js +381 -0
  669. package/packages/wrapper/dist/stuck-detector.d.ts +161 -0
  670. package/packages/wrapper/dist/stuck-detector.js +402 -0
  671. package/packages/wrapper/dist/tmux-wrapper.d.ts +352 -0
  672. package/packages/wrapper/dist/tmux-wrapper.js +1800 -0
  673. package/packages/wrapper/dist/trajectory-integration.d.ts +292 -0
  674. package/packages/wrapper/dist/trajectory-integration.js +979 -0
  675. package/packages/wrapper/dist/wrapper-types.d.ts +41 -0
  676. package/packages/wrapper/dist/wrapper-types.js +7 -0
  677. package/packages/wrapper/package.json +63 -0
  678. package/scripts/setup-stripe-products.ts +312 -0
  679. package/scripts/stress-test-orchestrator-integration.mts +1366 -0
  680. package/scripts/stress-test-orchestrator.mjs +584 -0
  681. package/scripts/stress-test-relay-pty.sh +452 -0
  682. package/scripts/verify-schema.js +1 -1
  683. package/turbo.json +37 -0
  684. package/bin/relay-pty +0 -0
  685. package/dist/bridge/config.d.ts +0 -41
  686. package/dist/bridge/config.js +0 -143
  687. package/dist/bridge/index.d.ts +0 -10
  688. package/dist/bridge/index.js +0 -10
  689. package/dist/bridge/multi-project-client.d.ts +0 -99
  690. package/dist/bridge/multi-project-client.js +0 -389
  691. package/dist/bridge/shadow-cli.js +0 -75
  692. package/dist/bridge/shadow-config.d.ts +0 -87
  693. package/dist/bridge/spawner.d.ts +0 -186
  694. package/dist/bridge/spawner.js +0 -920
  695. package/dist/bridge/types.d.ts +0 -129
  696. package/dist/bridge/utils.d.ts +0 -30
  697. package/dist/bridge/utils.js +0 -54
  698. package/dist/cli/index.js +0 -2784
  699. package/dist/cloud/api/admin.js +0 -225
  700. package/dist/cloud/api/billing.js +0 -564
  701. package/dist/cloud/api/cli-pty-runner.d.ts +0 -54
  702. package/dist/cloud/api/cli-pty-runner.js +0 -119
  703. package/dist/cloud/api/codex-auth-helper.js +0 -327
  704. package/dist/cloud/api/consensus.js +0 -259
  705. package/dist/cloud/api/coordinators.js +0 -749
  706. package/dist/cloud/api/daemons.js +0 -535
  707. package/dist/cloud/api/generic-webhooks.js +0 -129
  708. package/dist/cloud/api/github-app.js +0 -223
  709. package/dist/cloud/api/monitoring.js +0 -578
  710. package/dist/cloud/api/nango-auth.js +0 -658
  711. package/dist/cloud/api/onboarding.d.ts +0 -15
  712. package/dist/cloud/api/onboarding.js +0 -666
  713. package/dist/cloud/api/policy.js +0 -229
  714. package/dist/cloud/api/provider-env.d.ts +0 -5
  715. package/dist/cloud/api/provider-env.js +0 -27
  716. package/dist/cloud/api/providers.js +0 -511
  717. package/dist/cloud/api/repos.js +0 -576
  718. package/dist/cloud/api/teams.js +0 -279
  719. package/dist/cloud/api/test-helpers.js +0 -745
  720. package/dist/cloud/api/workspaces.js +0 -1783
  721. package/dist/cloud/billing/plans.js +0 -245
  722. package/dist/cloud/config.d.ts +0 -75
  723. package/dist/cloud/config.js +0 -109
  724. package/dist/cloud/db/drizzle.d.ts +0 -246
  725. package/dist/cloud/db/drizzle.js +0 -1249
  726. package/dist/cloud/db/schema.d.ts +0 -4854
  727. package/dist/cloud/db/schema.js +0 -610
  728. package/dist/cloud/index.d.ts +0 -11
  729. package/dist/cloud/index.js +0 -38
  730. package/dist/cloud/provisioner/index.d.ts +0 -207
  731. package/dist/cloud/provisioner/index.js +0 -2069
  732. package/dist/cloud/server.js +0 -1599
  733. package/dist/cloud/services/index.d.ts +0 -17
  734. package/dist/cloud/services/index.js +0 -25
  735. package/dist/cloud/services/intro-expiration.d.ts +0 -55
  736. package/dist/cloud/services/intro-expiration.js +0 -211
  737. package/dist/cloud/services/nango.d.ts +0 -199
  738. package/dist/cloud/services/nango.js +0 -382
  739. package/dist/cloud/services/persistence.d.ts +0 -131
  740. package/dist/config/relay-config.d.ts +0 -23
  741. package/dist/config/relay-config.js +0 -23
  742. package/dist/continuity/index.d.ts +0 -45
  743. package/dist/continuity/index.js +0 -48
  744. package/dist/continuity/types.d.ts +0 -180
  745. package/dist/continuity/types.js +0 -9
  746. package/dist/daemon/agent-manager.d.ts +0 -134
  747. package/dist/daemon/agent-manager.js +0 -564
  748. package/dist/daemon/agent-registry.js +0 -213
  749. package/dist/daemon/api.d.ts +0 -83
  750. package/dist/daemon/api.js +0 -780
  751. package/dist/daemon/channel-membership-store.d.ts +0 -48
  752. package/dist/daemon/channel-membership-store.js +0 -149
  753. package/dist/daemon/cli-auth.d.ts +0 -82
  754. package/dist/daemon/cli-auth.js +0 -700
  755. package/dist/daemon/cloud-sync.d.ts +0 -150
  756. package/dist/daemon/cloud-sync.js +0 -424
  757. package/dist/daemon/connection.d.ts +0 -130
  758. package/dist/daemon/connection.js +0 -438
  759. package/dist/daemon/consensus-integration.js +0 -371
  760. package/dist/daemon/delivery-tracker.d.ts +0 -34
  761. package/dist/daemon/delivery-tracker.js +0 -104
  762. package/dist/daemon/enhanced-features.d.ts +0 -118
  763. package/dist/daemon/enhanced-features.js +0 -178
  764. package/dist/daemon/index.d.ts +0 -14
  765. package/dist/daemon/index.js +0 -17
  766. package/dist/daemon/orchestrator.d.ts +0 -157
  767. package/dist/daemon/orchestrator.js +0 -792
  768. package/dist/daemon/repo-manager.js +0 -384
  769. package/dist/daemon/router.d.ts +0 -358
  770. package/dist/daemon/router.js +0 -1333
  771. package/dist/daemon/server.d.ts +0 -159
  772. package/dist/daemon/server.js +0 -788
  773. package/dist/daemon/services/browser-testing.d.ts +0 -88
  774. package/dist/daemon/services/browser-testing.js +0 -244
  775. package/dist/daemon/services/container-spawner.d.ts +0 -135
  776. package/dist/daemon/services/container-spawner.js +0 -313
  777. package/dist/daemon/sync-queue.d.ts +0 -116
  778. package/dist/daemon/sync-queue.js +0 -361
  779. package/dist/daemon/types.d.ts +0 -131
  780. package/dist/daemon/user-directory.d.ts +0 -111
  781. package/dist/daemon/user-directory.js +0 -233
  782. package/dist/daemon/workspace-manager.js +0 -314
  783. package/dist/dashboard/out/_next/static/chunks/116-eacf84a131b80db9.js +0 -1
  784. package/dist/dashboard/out/_next/static/chunks/64-f4268c2ac6f4d7d4.js +0 -1
  785. package/dist/dashboard/out/_next/static/chunks/766-aa7c8c9900ff5f53.js +0 -1
  786. package/dist/dashboard/out/_next/static/chunks/891-a024fbe4b619cf6f.js +0 -1
  787. package/dist/dashboard/out/_next/static/chunks/app/app/page-ffad986adfcc8b31.js +0 -1
  788. package/dist/dashboard/out/_next/static/chunks/app/page-671037943b2f2e43.js +0 -1
  789. package/dist/dashboard/out/_next/static/chunks/app/providers/page-57cbd738c6a73859.js +0 -1
  790. package/dist/dashboard/out/_next/static/chunks/app/providers/setup/[provider]/page-5ab0854472b402b0.js +0 -1
  791. package/dist/dashboard/out/_next/static/css/8f9ed310f454e5a5.css +0 -1
  792. package/dist/dashboard-server/server.js +0 -4806
  793. package/dist/dashboard-server/start.js +0 -13
  794. package/dist/dashboard-server/user-bridge.d.ts +0 -138
  795. package/dist/dashboard-server/user-bridge.js +0 -348
  796. package/dist/hooks/index.d.ts +0 -10
  797. package/dist/hooks/index.js +0 -10
  798. package/dist/hooks/registry.js +0 -476
  799. package/dist/hooks/trajectory-hooks.js +0 -183
  800. package/dist/hooks/types.d.ts +0 -284
  801. package/dist/hooks/types.js +0 -8
  802. package/dist/index.d.ts +0 -13
  803. package/dist/index.js +0 -16
  804. package/dist/memory/memory-hooks.d.ts +0 -60
  805. package/dist/policy/agent-policy.js +0 -665
  806. package/dist/protocol/channels.d.ts +0 -211
  807. package/dist/protocol/channels.js +0 -154
  808. package/dist/protocol/framing.d.ts +0 -94
  809. package/dist/protocol/framing.js +0 -240
  810. package/dist/protocol/index.d.ts +0 -4
  811. package/dist/protocol/index.js +0 -4
  812. package/dist/protocol/relay-pty-schemas.d.ts +0 -209
  813. package/dist/protocol/types.d.ts +0 -168
  814. package/dist/protocol/types.js +0 -6
  815. package/dist/resiliency/memory-monitor.js +0 -593
  816. package/dist/resiliency/provider-context.d.ts +0 -100
  817. package/dist/shared/cli-auth-config.js +0 -320
  818. package/dist/state/agent-state.js +0 -120
  819. package/dist/storage/adapter.d.ts +0 -154
  820. package/dist/storage/batched-sqlite-adapter.d.ts +0 -71
  821. package/dist/storage/batched-sqlite-adapter.js +0 -183
  822. package/dist/storage/sqlite-adapter.d.ts +0 -107
  823. package/dist/storage/sqlite-adapter.js +0 -717
  824. package/dist/trajectory/config.d.ts +0 -102
  825. package/dist/trajectory/config.js +0 -185
  826. package/dist/trajectory/index.d.ts +0 -8
  827. package/dist/trajectory/index.js +0 -8
  828. package/dist/trajectory/integration.js +0 -987
  829. package/dist/utils/command-resolver.js +0 -76
  830. package/dist/utils/index.d.ts +0 -4
  831. package/dist/utils/index.js +0 -4
  832. package/dist/utils/project-namespace.d.ts +0 -70
  833. package/dist/utils/project-namespace.js +0 -216
  834. package/dist/wrapper/base-wrapper.d.ts +0 -217
  835. package/dist/wrapper/base-wrapper.js +0 -538
  836. package/dist/wrapper/client.d.ts +0 -199
  837. package/dist/wrapper/client.js +0 -677
  838. package/dist/wrapper/idle-detector.d.ts +0 -102
  839. package/dist/wrapper/idle-detector.js +0 -279
  840. package/dist/wrapper/index.d.ts +0 -4
  841. package/dist/wrapper/index.js +0 -7
  842. package/dist/wrapper/parser.d.ts +0 -230
  843. package/dist/wrapper/parser.js +0 -1178
  844. package/dist/wrapper/pty-wrapper.d.ts +0 -343
  845. package/dist/wrapper/pty-wrapper.js +0 -1593
  846. package/dist/wrapper/relay-pty-orchestrator.d.ts +0 -296
  847. package/dist/wrapper/relay-pty-orchestrator.js +0 -1088
  848. package/dist/wrapper/shared.d.ts +0 -168
  849. package/dist/wrapper/shared.js +0 -291
  850. package/dist/wrapper/stuck-detector.d.ts +0 -101
  851. package/dist/wrapper/stuck-detector.js +0 -228
  852. package/dist/wrapper/tmux-wrapper.d.ts +0 -344
  853. package/dist/wrapper/tmux-wrapper.js +0 -1711
  854. /package/dist/dashboard/out/_next/static/{BffXAqxm-_rUlj2mAnK26 → 72btMIJ64BCAB4UgVkpaq}/_buildManifest.js +0 -0
  855. /package/dist/dashboard/out/_next/static/{BffXAqxm-_rUlj2mAnK26 → 72btMIJ64BCAB4UgVkpaq}/_ssgManifest.js +0 -0
  856. /package/dist/dashboard/out/_next/static/chunks/app/cloud/link/{page-cfeb437f08a12ed9.js → page-5011ae044b90449d.js} +0 -0
  857. /package/dist/dashboard/out/_next/static/chunks/app/history/{page-240f91e8b06ba8ac.js → page-b2ce7c96ed0931da.js} +0 -0
  858. /package/dist/dashboard/out/_next/static/chunks/app/metrics/{page-82938ab8fcf44694.js → page-bf2cb1e5915bc92d.js} +0 -0
  859. /package/dist/{cli → src/cli}/index.d.ts +0 -0
  860. /package/dist/{health-worker-manager.d.ts → src/health-worker-manager.d.ts} +0 -0
  861. /package/dist/{health-worker-manager.js → src/health-worker-manager.js} +0 -0
  862. /package/dist/{health-worker.d.ts → src/health-worker.d.ts} +0 -0
  863. /package/dist/{health-worker.js → src/health-worker.js} +0 -0
  864. /package/{dist/bridge → packages/bridge/dist}/shadow-cli.d.ts +0 -0
  865. /package/{dist/bridge → packages/bridge/dist}/types.js +0 -0
  866. /package/{dist/cloud → packages/cloud/dist}/api/admin.d.ts +0 -0
  867. /package/{dist/cloud → packages/cloud/dist}/api/auth.d.ts +0 -0
  868. /package/{dist/cloud → packages/cloud/dist}/api/auth.js +0 -0
  869. /package/{dist/cloud → packages/cloud/dist}/api/billing.d.ts +0 -0
  870. /package/{dist/cloud → packages/cloud/dist}/api/codex-auth-helper.d.ts +0 -0
  871. /package/{dist/cloud → packages/cloud/dist}/api/consensus.d.ts +0 -0
  872. /package/{dist/cloud → packages/cloud/dist}/api/coordinators.d.ts +0 -0
  873. /package/{dist/cloud → packages/cloud/dist}/api/daemons.d.ts +0 -0
  874. /package/{dist/cloud → packages/cloud/dist}/api/generic-webhooks.d.ts +0 -0
  875. /package/{dist/cloud → packages/cloud/dist}/api/git.d.ts +0 -0
  876. /package/{dist/cloud → packages/cloud/dist}/api/git.js +0 -0
  877. /package/{dist/cloud → packages/cloud/dist}/api/github-app.d.ts +0 -0
  878. /package/{dist/cloud → packages/cloud/dist}/api/middleware/planLimits.d.ts +0 -0
  879. /package/{dist/cloud → packages/cloud/dist}/api/middleware/planLimits.js +0 -0
  880. /package/{dist/cloud → packages/cloud/dist}/api/monitoring.d.ts +0 -0
  881. /package/{dist/cloud → packages/cloud/dist}/api/nango-auth.d.ts +0 -0
  882. /package/{dist/cloud → packages/cloud/dist}/api/policy.d.ts +0 -0
  883. /package/{dist/cloud → packages/cloud/dist}/api/providers.d.ts +0 -0
  884. /package/{dist/cloud → packages/cloud/dist}/api/repos.d.ts +0 -0
  885. /package/{dist/cloud → packages/cloud/dist}/api/teams.d.ts +0 -0
  886. /package/{dist/cloud → packages/cloud/dist}/api/test-helpers.d.ts +0 -0
  887. /package/{dist/cloud → packages/cloud/dist}/api/usage.d.ts +0 -0
  888. /package/{dist/cloud → packages/cloud/dist}/api/usage.js +0 -0
  889. /package/{dist/cloud → packages/cloud/dist}/api/webhooks.d.ts +0 -0
  890. /package/{dist/cloud → packages/cloud/dist}/api/webhooks.js +0 -0
  891. /package/{dist/cloud → packages/cloud/dist}/api/workspaces.d.ts +0 -0
  892. /package/{dist/cloud → packages/cloud/dist}/billing/index.d.ts +0 -0
  893. /package/{dist/cloud → packages/cloud/dist}/billing/index.js +0 -0
  894. /package/{dist/cloud → packages/cloud/dist}/billing/plans.d.ts +0 -0
  895. /package/{dist/cloud → packages/cloud/dist}/billing/service.d.ts +0 -0
  896. /package/{dist/cloud → packages/cloud/dist}/billing/service.js +0 -0
  897. /package/{dist/cloud → packages/cloud/dist}/billing/types.d.ts +0 -0
  898. /package/{dist/cloud → packages/cloud/dist}/billing/types.js +0 -0
  899. /package/{dist/cloud → packages/cloud/dist}/db/bulk-ingest.d.ts +0 -0
  900. /package/{dist/cloud → packages/cloud/dist}/db/bulk-ingest.js +0 -0
  901. /package/{dist/cloud → packages/cloud/dist}/db/index.d.ts +0 -0
  902. /package/{dist/cloud → packages/cloud/dist}/db/index.js +0 -0
  903. /package/{dist/cloud → packages/cloud/dist}/server.d.ts +0 -0
  904. /package/{dist/cloud → packages/cloud/dist}/services/auto-scaler.d.ts +0 -0
  905. /package/{dist/cloud → packages/cloud/dist}/services/auto-scaler.js +0 -0
  906. /package/{dist/cloud → packages/cloud/dist}/services/capacity-manager.d.ts +0 -0
  907. /package/{dist/cloud → packages/cloud/dist}/services/capacity-manager.js +0 -0
  908. /package/{dist/cloud → packages/cloud/dist}/services/ci-agent-spawner.d.ts +0 -0
  909. /package/{dist/cloud → packages/cloud/dist}/services/ci-agent-spawner.js +0 -0
  910. /package/{dist/cloud → packages/cloud/dist}/services/cloud-message-bus.d.ts +0 -0
  911. /package/{dist/cloud → packages/cloud/dist}/services/cloud-message-bus.js +0 -0
  912. /package/{dist/cloud → packages/cloud/dist}/services/compute-enforcement.d.ts +0 -0
  913. /package/{dist/cloud → packages/cloud/dist}/services/compute-enforcement.js +0 -0
  914. /package/{dist/cloud → packages/cloud/dist}/services/coordinator.d.ts +0 -0
  915. /package/{dist/cloud → packages/cloud/dist}/services/coordinator.js +0 -0
  916. /package/{dist/cloud → packages/cloud/dist}/services/mention-handler.d.ts +0 -0
  917. /package/{dist/cloud → packages/cloud/dist}/services/mention-handler.js +0 -0
  918. /package/{dist/cloud → packages/cloud/dist}/services/persistence.js +0 -0
  919. /package/{dist/cloud → packages/cloud/dist}/services/planLimits.d.ts +0 -0
  920. /package/{dist/cloud → packages/cloud/dist}/services/planLimits.js +0 -0
  921. /package/{dist/cloud → packages/cloud/dist}/services/presence-registry.d.ts +0 -0
  922. /package/{dist/cloud → packages/cloud/dist}/services/presence-registry.js +0 -0
  923. /package/{dist/cloud → packages/cloud/dist}/services/scaling-orchestrator.d.ts +0 -0
  924. /package/{dist/cloud → packages/cloud/dist}/services/scaling-orchestrator.js +0 -0
  925. /package/{dist/cloud → packages/cloud/dist}/services/scaling-policy.d.ts +0 -0
  926. /package/{dist/cloud → packages/cloud/dist}/services/scaling-policy.js +0 -0
  927. /package/{dist/cloud → packages/cloud/dist}/services/ssh-security.d.ts +0 -0
  928. /package/{dist/cloud → packages/cloud/dist}/services/ssh-security.js +0 -0
  929. /package/{dist/cloud → packages/cloud/dist}/services/workspace-keepalive.d.ts +0 -0
  930. /package/{dist/cloud → packages/cloud/dist}/services/workspace-keepalive.js +0 -0
  931. /package/{dist/cloud → packages/cloud/dist}/webhooks/index.d.ts +0 -0
  932. /package/{dist/cloud → packages/cloud/dist}/webhooks/index.js +0 -0
  933. /package/{dist/cloud → packages/cloud/dist}/webhooks/parsers/github.d.ts +0 -0
  934. /package/{dist/cloud → packages/cloud/dist}/webhooks/parsers/github.js +0 -0
  935. /package/{dist/cloud → packages/cloud/dist}/webhooks/parsers/index.d.ts +0 -0
  936. /package/{dist/cloud → packages/cloud/dist}/webhooks/parsers/index.js +0 -0
  937. /package/{dist/cloud → packages/cloud/dist}/webhooks/parsers/linear.d.ts +0 -0
  938. /package/{dist/cloud → packages/cloud/dist}/webhooks/parsers/linear.js +0 -0
  939. /package/{dist/cloud → packages/cloud/dist}/webhooks/parsers/slack.d.ts +0 -0
  940. /package/{dist/cloud → packages/cloud/dist}/webhooks/parsers/slack.js +0 -0
  941. /package/{dist/cloud → packages/cloud/dist}/webhooks/responders/github.d.ts +0 -0
  942. /package/{dist/cloud → packages/cloud/dist}/webhooks/responders/github.js +0 -0
  943. /package/{dist/cloud → packages/cloud/dist}/webhooks/responders/index.d.ts +0 -0
  944. /package/{dist/cloud → packages/cloud/dist}/webhooks/responders/index.js +0 -0
  945. /package/{dist/cloud → packages/cloud/dist}/webhooks/responders/linear.d.ts +0 -0
  946. /package/{dist/cloud → packages/cloud/dist}/webhooks/responders/linear.js +0 -0
  947. /package/{dist/cloud → packages/cloud/dist}/webhooks/responders/slack.d.ts +0 -0
  948. /package/{dist/cloud → packages/cloud/dist}/webhooks/responders/slack.js +0 -0
  949. /package/{dist/cloud → packages/cloud/dist}/webhooks/router.d.ts +0 -0
  950. /package/{dist/cloud → packages/cloud/dist}/webhooks/router.js +0 -0
  951. /package/{dist/cloud → packages/cloud/dist}/webhooks/rules-engine.d.ts +0 -0
  952. /package/{dist/cloud → packages/cloud/dist}/webhooks/rules-engine.js +0 -0
  953. /package/{dist/cloud → packages/cloud/dist}/webhooks/types.d.ts +0 -0
  954. /package/{dist/cloud → packages/cloud/dist}/webhooks/types.js +0 -0
  955. /package/{dist/utils → packages/config/dist}/agent-config.d.ts +0 -0
  956. /package/{dist/utils → packages/config/dist}/agent-config.js +0 -0
  957. /package/{dist/shared → packages/config/dist}/cli-auth-config.d.ts +0 -0
  958. /package/{dist/bridge → packages/config/dist}/shadow-config.js +0 -0
  959. /package/{dist/bridge → packages/config/dist}/teams-config.d.ts +0 -0
  960. /package/{dist/bridge → packages/config/dist}/teams-config.js +0 -0
  961. /package/{dist/continuity → packages/continuity/dist}/formatter.d.ts +0 -0
  962. /package/{dist/continuity → packages/continuity/dist}/formatter.js +0 -0
  963. /package/{dist/continuity → packages/continuity/dist}/handoff-store.d.ts +0 -0
  964. /package/{dist/continuity → packages/continuity/dist}/handoff-store.js +0 -0
  965. /package/{dist/continuity → packages/continuity/dist}/ledger-store.d.ts +0 -0
  966. /package/{dist/continuity → packages/continuity/dist}/ledger-store.js +0 -0
  967. /package/{dist/continuity → packages/continuity/dist}/manager.d.ts +0 -0
  968. /package/{dist/continuity → packages/continuity/dist}/manager.js +0 -0
  969. /package/{dist/continuity → packages/continuity/dist}/parser.d.ts +0 -0
  970. /package/{dist/continuity → packages/continuity/dist}/parser.js +0 -0
  971. /package/{dist/daemon → packages/daemon/dist}/agent-registry.d.ts +0 -0
  972. /package/{dist/daemon → packages/daemon/dist}/agent-signing.d.ts +0 -0
  973. /package/{dist/daemon → packages/daemon/dist}/agent-signing.js +0 -0
  974. /package/{dist/daemon → packages/daemon/dist}/auth.d.ts +0 -0
  975. /package/{dist/daemon → packages/daemon/dist}/auth.js +0 -0
  976. /package/{dist/daemon → packages/daemon/dist}/consensus-integration.d.ts +0 -0
  977. /package/{dist/daemon → packages/daemon/dist}/consensus.d.ts +0 -0
  978. /package/{dist/daemon → packages/daemon/dist}/consensus.js +0 -0
  979. /package/{dist/daemon → packages/daemon/dist}/rate-limiter.d.ts +0 -0
  980. /package/{dist/daemon → packages/daemon/dist}/rate-limiter.js +0 -0
  981. /package/{dist/daemon → packages/daemon/dist}/registry.d.ts +0 -0
  982. /package/{dist/daemon → packages/daemon/dist}/registry.js +0 -0
  983. /package/{dist/daemon → packages/daemon/dist}/repo-manager.d.ts +0 -0
  984. /package/{dist/daemon → packages/daemon/dist}/types.js +0 -0
  985. /package/{dist/daemon → packages/daemon/dist}/workspace-manager.d.ts +0 -0
  986. /package/{dist/dashboard-server → packages/dashboard-server/dist}/metrics.d.ts +0 -0
  987. /package/{dist/dashboard-server → packages/dashboard-server/dist}/metrics.js +0 -0
  988. /package/{dist/dashboard-server → packages/dashboard-server/dist}/needs-attention.d.ts +0 -0
  989. /package/{dist/dashboard-server → packages/dashboard-server/dist}/needs-attention.js +0 -0
  990. /package/{dist/dashboard-server → packages/dashboard-server/dist}/server.d.ts +0 -0
  991. /package/{dist/dashboard-server → packages/dashboard-server/dist}/start.d.ts +0 -0
  992. /package/{dist/hooks → packages/hooks/dist}/emitter.d.ts +0 -0
  993. /package/{dist/hooks → packages/hooks/dist}/emitter.js +0 -0
  994. /package/{dist/hooks → packages/hooks/dist}/inbox-check/hook.d.ts +0 -0
  995. /package/{dist/hooks → packages/hooks/dist}/inbox-check/hook.js +0 -0
  996. /package/{dist/hooks → packages/hooks/dist}/inbox-check/index.d.ts +0 -0
  997. /package/{dist/hooks → packages/hooks/dist}/inbox-check/index.js +0 -0
  998. /package/{dist/hooks → packages/hooks/dist}/inbox-check/types.d.ts +0 -0
  999. /package/{dist/hooks → packages/hooks/dist}/inbox-check/types.js +0 -0
  1000. /package/{dist/hooks → packages/hooks/dist}/inbox-check/utils.d.ts +0 -0
  1001. /package/{dist/hooks → packages/hooks/dist}/inbox-check/utils.js +0 -0
  1002. /package/{dist/hooks → packages/hooks/dist}/registry.d.ts +0 -0
  1003. /package/{dist/hooks → packages/hooks/dist}/trajectory-hooks.d.ts +0 -0
  1004. /package/{dist/memory → packages/memory/dist}/adapters/index.d.ts +0 -0
  1005. /package/{dist/memory → packages/memory/dist}/adapters/index.js +0 -0
  1006. /package/{dist/memory → packages/memory/dist}/adapters/inmemory.d.ts +0 -0
  1007. /package/{dist/memory → packages/memory/dist}/adapters/inmemory.js +0 -0
  1008. /package/{dist/memory → packages/memory/dist}/adapters/supermemory.d.ts +0 -0
  1009. /package/{dist/memory → packages/memory/dist}/adapters/supermemory.js +0 -0
  1010. /package/{dist/memory → packages/memory/dist}/context-compaction.d.ts +0 -0
  1011. /package/{dist/memory → packages/memory/dist}/context-compaction.js +0 -0
  1012. /package/{dist/memory → packages/memory/dist}/factory.d.ts +0 -0
  1013. /package/{dist/memory → packages/memory/dist}/factory.js +0 -0
  1014. /package/{dist/memory → packages/memory/dist}/index.d.ts +0 -0
  1015. /package/{dist/memory → packages/memory/dist}/index.js +0 -0
  1016. /package/{dist/memory → packages/memory/dist}/memory-hooks.js +0 -0
  1017. /package/{dist/memory → packages/memory/dist}/service.d.ts +0 -0
  1018. /package/{dist/memory → packages/memory/dist}/service.js +0 -0
  1019. /package/{dist/memory → packages/memory/dist}/types.d.ts +0 -0
  1020. /package/{dist/memory → packages/memory/dist}/types.js +0 -0
  1021. /package/{dist/policy → packages/policy/dist}/agent-policy.d.ts +0 -0
  1022. /package/{dist/policy → packages/policy/dist}/cloud-policy-fetcher.d.ts +0 -0
  1023. /package/{dist/policy → packages/policy/dist}/cloud-policy-fetcher.js +0 -0
  1024. /package/{dist/utils → packages/protocol/dist}/id-generator.d.ts +0 -0
  1025. /package/{dist/utils → packages/protocol/dist}/id-generator.js +0 -0
  1026. /package/{dist/protocol → packages/protocol/dist}/relay-pty-schemas.js +0 -0
  1027. /package/{dist/resiliency → packages/resiliency/dist}/context-persistence.d.ts +0 -0
  1028. /package/{dist/resiliency → packages/resiliency/dist}/context-persistence.js +0 -0
  1029. /package/{dist/resiliency → packages/resiliency/dist}/crash-insights.d.ts +0 -0
  1030. /package/{dist/resiliency → packages/resiliency/dist}/crash-insights.js +0 -0
  1031. /package/{dist/resiliency → packages/resiliency/dist}/gossip-health.d.ts +0 -0
  1032. /package/{dist/resiliency → packages/resiliency/dist}/gossip-health.js +0 -0
  1033. /package/{dist/resiliency → packages/resiliency/dist}/health-monitor.d.ts +0 -0
  1034. /package/{dist/resiliency → packages/resiliency/dist}/health-monitor.js +0 -0
  1035. /package/{dist/resiliency → packages/resiliency/dist}/index.d.ts +0 -0
  1036. /package/{dist/resiliency → packages/resiliency/dist}/index.js +0 -0
  1037. /package/{dist/resiliency → packages/resiliency/dist}/leader-watchdog.d.ts +0 -0
  1038. /package/{dist/resiliency → packages/resiliency/dist}/leader-watchdog.js +0 -0
  1039. /package/{dist/resiliency → packages/resiliency/dist}/logger.d.ts +0 -0
  1040. /package/{dist/resiliency → packages/resiliency/dist}/logger.js +0 -0
  1041. /package/{dist/resiliency → packages/resiliency/dist}/memory-monitor.d.ts +0 -0
  1042. /package/{dist/resiliency → packages/resiliency/dist}/metrics.d.ts +0 -0
  1043. /package/{dist/resiliency → packages/resiliency/dist}/metrics.js +0 -0
  1044. /package/{dist/resiliency → packages/resiliency/dist}/provider-context.js +0 -0
  1045. /package/{dist/resiliency → packages/resiliency/dist}/stateless-lead.d.ts +0 -0
  1046. /package/{dist/resiliency → packages/resiliency/dist}/stateless-lead.js +0 -0
  1047. /package/{dist/resiliency → packages/resiliency/dist}/supervisor.d.ts +0 -0
  1048. /package/{dist/resiliency → packages/resiliency/dist}/supervisor.js +0 -0
  1049. /package/{dist/state → packages/state/dist}/agent-state.d.ts +0 -0
  1050. /package/{dist/storage → packages/storage/dist}/adapter.js +0 -0
  1051. /package/{dist/storage → packages/storage/dist}/dead-letter-queue.d.ts +0 -0
  1052. /package/{dist/storage → packages/storage/dist}/dead-letter-queue.js +0 -0
  1053. /package/{dist/storage → packages/storage/dist}/dlq-adapter.d.ts +0 -0
  1054. /package/{dist/storage → packages/storage/dist}/dlq-adapter.js +0 -0
  1055. /package/{dist/trajectory → packages/trajectory/dist}/integration.d.ts +0 -0
  1056. /package/{dist/utils → packages/utils/dist}/command-resolver.d.ts +0 -0
  1057. /package/{dist/utils → packages/utils/dist}/git-remote.d.ts +0 -0
  1058. /package/{dist/utils → packages/utils/dist}/git-remote.js +0 -0
  1059. /package/{dist/utils → packages/utils/dist}/logger.d.ts +0 -0
  1060. /package/{dist/utils → packages/utils/dist}/logger.js +0 -0
  1061. /package/{dist/utils → packages/utils/dist}/name-generator.d.ts +0 -0
  1062. /package/{dist/utils → packages/utils/dist}/name-generator.js +0 -0
  1063. /package/{dist/utils → packages/utils/dist}/precompiled-patterns.d.ts +0 -0
  1064. /package/{dist/utils → packages/utils/dist}/precompiled-patterns.js +0 -0
  1065. /package/{dist/utils → packages/utils/dist}/update-checker.d.ts +0 -0
  1066. /package/{dist/utils → packages/utils/dist}/update-checker.js +0 -0
  1067. /package/{dist/wrapper → packages/wrapper/dist}/auth-detection.d.ts +0 -0
  1068. /package/{dist/wrapper → packages/wrapper/dist}/auth-detection.js +0 -0
  1069. /package/{dist/wrapper → packages/wrapper/dist}/inbox.d.ts +0 -0
  1070. /package/{dist/wrapper → packages/wrapper/dist}/inbox.js +0 -0
  1071. /package/{dist/wrapper → packages/wrapper/dist}/prompt-composer.d.ts +0 -0
  1072. /package/{dist/wrapper → packages/wrapper/dist}/prompt-composer.js +0 -0
  1073. /package/{dist/utils → packages/wrapper/dist}/tmux-resolver.d.ts +0 -0
  1074. /package/{dist/utils → packages/wrapper/dist}/tmux-resolver.js +0 -0
@@ -0,0 +1,3091 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Agent Relay CLI
4
+ *
5
+ * Commands:
6
+ * relay claude - Start daemon + Dashboard coordinator with Claude
7
+ * relay codex - Start daemon + Dasbboard coordinator with Codex
8
+ * relay create-agent <cmd> - Wrap agent with real-time messaging
9
+ * relay create-agent -n Name cmd - Wrap with specific agent name
10
+ * relay up - Start daemon + dashboard
11
+ * relay read <id> - Read full message by ID
12
+ * relay agents - List connected agents
13
+ * relay who - Show currently active agents
14
+ */
15
+ import { Command } from 'commander';
16
+ import { config as dotenvConfig } from 'dotenv';
17
+ import { Daemon } from '@agent-relay/daemon';
18
+ import { RelayClient } from '@agent-relay/sdk';
19
+ import { RelayPtyOrchestrator, getTmuxPath } from '@agent-relay/wrapper';
20
+ import { AgentSpawner, readWorkersMetadata, getWorkerLogsDir, selectShadowCli } from '@agent-relay/bridge';
21
+ import { generateAgentName, checkForUpdatesInBackground, checkForUpdates } from '@agent-relay/utils';
22
+ import { getShadowForAgent } from '@agent-relay/config';
23
+ import fs from 'node:fs';
24
+ import path from 'node:path';
25
+ import { promisify } from 'node:util';
26
+ import { exec } from 'node:child_process';
27
+ import { fileURLToPath } from 'node:url';
28
+ dotenvConfig();
29
+ const DEFAULT_DASHBOARD_PORT = process.env.AGENT_RELAY_DASHBOARD_PORT || '3888';
30
+ // Read version from package.json
31
+ const __filename = fileURLToPath(import.meta.url);
32
+ const __dirname = path.dirname(__filename);
33
+ // Find package.json by walking up from current directory
34
+ // Works for both src/cli/ and dist/src/cli/ locations
35
+ function findPackageJson(startDir) {
36
+ let dir = startDir;
37
+ while (dir !== path.dirname(dir)) {
38
+ const candidate = path.join(dir, 'package.json');
39
+ if (fs.existsSync(candidate)) {
40
+ return candidate;
41
+ }
42
+ dir = path.dirname(dir);
43
+ }
44
+ throw new Error('Could not find package.json');
45
+ }
46
+ const packageJsonPath = findPackageJson(__dirname);
47
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
48
+ const VERSION = packageJson.version;
49
+ const execAsync = promisify(exec);
50
+ // Check for updates in background (non-blocking)
51
+ // Only show notification for interactive commands, not when wrapping agents or running update
52
+ const interactiveCommands = ['up', 'down', 'status', 'agents', 'who', 'version', '--version', '-V', '--help', '-h', 'create-agent', 'claude', 'codex'];
53
+ const shouldCheckUpdates = process.argv.length > 2 &&
54
+ interactiveCommands.includes(process.argv[2]);
55
+ if (shouldCheckUpdates) {
56
+ checkForUpdatesInBackground(VERSION);
57
+ }
58
+ const program = new Command();
59
+ function pidFilePathForSocket(socketPath) {
60
+ return `${socketPath}.pid`;
61
+ }
62
+ program
63
+ .name('agent-relay')
64
+ .description('Agent-to-agent messaging')
65
+ .version(VERSION, '-V, --version', 'Output the version number');
66
+ // create-agent - Wrap agent with real-time messaging (requires TTY)
67
+ // For programmatic spawning from scripts, use 'agent-relay spawn' instead (no TTY required)
68
+ program
69
+ .command('create-agent')
70
+ .description('Wrap an agent with real-time messaging (requires TTY, use "spawn" for scripts)')
71
+ .option('-n, --name <name>', 'Agent name (auto-generated if not set)')
72
+ .option('-d, --debug', 'Enable debug output')
73
+ .option('--prefix <pattern>', 'Relay prefix pattern (default: ->relay:)')
74
+ .option('--dashboard-port <port>', 'Dashboard port for spawn/release API (auto-detected if not set)')
75
+ .option('--shadow <name>', 'Spawn a shadow agent with this name that monitors the primary')
76
+ .option('--shadow-role <role>', 'Shadow role: reviewer, auditor, or triggers (comma-separated: SESSION_END,CODE_WRITTEN,REVIEW_REQUEST,EXPLICIT_ASK,ALL_MESSAGES)')
77
+ .option('--skip-instructions', 'Skip initial instruction injection (use with --append-system-prompt)')
78
+ .argument('<command...>', 'Command to wrap (e.g., claude)')
79
+ .action(async (commandParts, options) => {
80
+ const { ensureProjectDir } = await import('@agent-relay/config');
81
+ const { findAgentConfig, isClaudeCli, buildClaudeArgs } = await import('@agent-relay/config');
82
+ // ensureProjectDir creates .agent-relay/ and adds to .gitignore on first run
83
+ const paths = ensureProjectDir();
84
+ const [mainCommand, ...commandArgs] = commandParts;
85
+ const agentName = options.name ?? generateAgentName();
86
+ console.error(`Agent: ${agentName}`);
87
+ console.error(`Project: ${paths.projectId}`);
88
+ // Auto-detect agent config and inject --model/--agent for Claude CLI
89
+ let finalArgs = commandArgs;
90
+ if (isClaudeCli(mainCommand)) {
91
+ const config = findAgentConfig(agentName, paths.projectRoot);
92
+ if (config) {
93
+ console.error(`Agent config: ${config.configPath}`);
94
+ if (config.model) {
95
+ console.error(`Model: ${config.model}`);
96
+ }
97
+ finalArgs = buildClaudeArgs(agentName, commandArgs, paths.projectRoot);
98
+ }
99
+ }
100
+ // Determine dashboard port for spawn/release API
101
+ // Priority: CLI flag > env var > auto-detect default port
102
+ let dashboardPort;
103
+ if (options.dashboardPort) {
104
+ dashboardPort = parseInt(options.dashboardPort, 10);
105
+ }
106
+ else {
107
+ // Try to detect if dashboard is running at common ports
108
+ const portsToTry = [
109
+ parseInt(DEFAULT_DASHBOARD_PORT, 10),
110
+ 3889, 3890, 3891, // Common fallback ports when default is in use
111
+ ];
112
+ for (const port of portsToTry) {
113
+ try {
114
+ const response = await fetch(`http://localhost:${port}/api/health`, {
115
+ method: 'GET',
116
+ signal: AbortSignal.timeout(300), // Quick timeout for detection
117
+ });
118
+ if (response.ok) {
119
+ const health = await response.json();
120
+ if (health.status === 'healthy') {
121
+ dashboardPort = port;
122
+ console.error(`Dashboard detected: http://localhost:${dashboardPort}`);
123
+ break;
124
+ }
125
+ }
126
+ }
127
+ catch {
128
+ // Try next port
129
+ }
130
+ }
131
+ }
132
+ // Create spawner as fallback for direct spawn (if dashboard API not available)
133
+ const spawner = new AgentSpawner(paths.projectRoot, undefined, dashboardPort);
134
+ const wrapper = new RelayPtyOrchestrator({
135
+ name: agentName,
136
+ command: mainCommand,
137
+ args: finalArgs,
138
+ socketPath: paths.socketPath,
139
+ cwd: paths.projectRoot,
140
+ relayPrefix: options.prefix,
141
+ skipInstructions: options.skipInstructions,
142
+ streamLogs: true,
143
+ // Use dashboard API for spawn/release when available (preferred - works from any context)
144
+ dashboardPort,
145
+ // Wire up spawn/release callbacks as fallback (if no dashboardPort)
146
+ onSpawn: async (workerName, workerCli, task) => {
147
+ console.error(`[${agentName}] Spawning ${workerName} (${workerCli})...`);
148
+ const result = await spawner.spawn({
149
+ name: workerName,
150
+ cli: workerCli,
151
+ task,
152
+ // No team by default - agents are flat unless team is specified
153
+ });
154
+ if (result.success) {
155
+ console.error(`[${agentName}] ✓ Spawned ${workerName} [pid: ${result.pid}]`);
156
+ }
157
+ else {
158
+ console.error(`[${agentName}] ✗ Failed to spawn ${workerName}: ${result.error}`);
159
+ }
160
+ },
161
+ onRelease: async (workerName) => {
162
+ console.error(`[${agentName}] Releasing ${workerName}...`);
163
+ const released = await spawner.release(workerName);
164
+ if (released) {
165
+ console.error(`[${agentName}] ✓ Released ${workerName}`);
166
+ }
167
+ else {
168
+ console.error(`[${agentName}] ✗ Worker ${workerName} not found`);
169
+ }
170
+ },
171
+ });
172
+ process.on('SIGINT', async () => {
173
+ await spawner.releaseAll();
174
+ await wrapper.stop();
175
+ process.exit(0);
176
+ });
177
+ await wrapper.start();
178
+ let shadowName;
179
+ let shadowRole;
180
+ let speakOn;
181
+ let shadowCli;
182
+ let shadowPrompt;
183
+ const rolePresets = {
184
+ reviewer: ['CODE_WRITTEN', 'REVIEW_REQUEST', 'EXPLICIT_ASK'],
185
+ auditor: ['SESSION_END', 'EXPLICIT_ASK'],
186
+ active: ['ALL_MESSAGES'],
187
+ };
188
+ if (options.shadow) {
189
+ // CLI flags provided
190
+ shadowName = options.shadow;
191
+ const role = options.shadowRole || 'EXPLICIT_ASK';
192
+ shadowRole = role;
193
+ if (rolePresets[role.toLowerCase()]) {
194
+ speakOn = rolePresets[role.toLowerCase()];
195
+ }
196
+ else {
197
+ speakOn = role.split(',').map((s) => s.trim().toUpperCase());
198
+ }
199
+ }
200
+ else {
201
+ // Check config file for shadow configuration
202
+ const shadowConfig = getShadowForAgent(paths.projectRoot, agentName);
203
+ if (shadowConfig) {
204
+ shadowName = shadowConfig.shadowName;
205
+ shadowRole = shadowConfig.roleName;
206
+ speakOn = shadowConfig.speakOn;
207
+ shadowCli = shadowConfig.cli;
208
+ shadowPrompt = shadowConfig.prompt;
209
+ console.error(`Shadow config: ${shadowName} (from .agent-relay.json)`);
210
+ }
211
+ }
212
+ // Spawn shadow if configured
213
+ if (shadowName && speakOn) {
214
+ // Decide how to run the shadow (subagent for Claude/OpenCode primaries)
215
+ let shadowSelection = null;
216
+ try {
217
+ shadowSelection = await selectShadowCli(mainCommand, { preferredShadowCli: shadowCli });
218
+ console.error(`[shadow] Mode: ${shadowSelection.mode} via ${shadowSelection.command || shadowSelection.cli} (primary: ${mainCommand})`);
219
+ }
220
+ catch (err) {
221
+ console.error(`[shadow] Shadow CLI selection failed: ${err.message}`);
222
+ }
223
+ // Subagent mode: do not spawn a separate shadow process
224
+ if (shadowSelection?.mode === 'subagent') {
225
+ console.error(`[shadow] ${shadowName} will run as ${shadowSelection.cli} subagent inside ${agentName}; no separate process spawned`);
226
+ return;
227
+ }
228
+ console.error(`Shadow: ${shadowName} (shadowing ${agentName}, speakOn: ${speakOn.join(',')})`);
229
+ // Wait for primary to register before spawning shadow
230
+ await new Promise(r => setTimeout(r, 3000));
231
+ // Build shadow task prompt
232
+ const defaultPrompt = `You are a shadow agent monitoring "${agentName}". You receive copies of their messages. Your role: ${shadowRole || 'observer'}. Stay passive unless your triggers activate.`;
233
+ const shadowTask = shadowPrompt || defaultPrompt;
234
+ const result = await spawner.spawn({
235
+ name: shadowName,
236
+ cli: shadowSelection?.command || shadowCli || mainCommand,
237
+ task: shadowTask,
238
+ shadowOf: agentName,
239
+ shadowSpeakOn: speakOn,
240
+ });
241
+ if (result.success) {
242
+ console.error(`Shadow ${shadowName} started [pid: ${result.pid}]`);
243
+ }
244
+ else {
245
+ console.error(`Failed to spawn shadow ${shadowName}: ${result.error}`);
246
+ }
247
+ }
248
+ });
249
+ // up - Start daemon (dashboard disabled by default)
250
+ program
251
+ .command('up')
252
+ .description('Start daemon (use --dashboard to enable web dashboard)')
253
+ .option('--dashboard', 'Enable web dashboard (disabled by default)')
254
+ .option('--port <port>', 'Dashboard port (requires --dashboard)', DEFAULT_DASHBOARD_PORT)
255
+ .option('--spawn', 'Force spawn all agents from teams.json')
256
+ .option('--no-spawn', 'Do not auto-spawn agents (just start daemon)')
257
+ .option('--watch', 'Auto-restart daemon on crash (supervisor mode)')
258
+ .option('--max-restarts <n>', 'Max restarts in 60s before giving up (default: 5)', '5')
259
+ .action(async (options) => {
260
+ // If --watch is specified, run in supervisor mode
261
+ if (options.watch) {
262
+ const { spawn } = await import('node:child_process');
263
+ const maxRestarts = parseInt(options.maxRestarts, 10) || 5;
264
+ const restartWindow = 60_000; // 60 seconds
265
+ const restartTimes = [];
266
+ let child = null;
267
+ let shuttingDown = false;
268
+ const startDaemon = () => {
269
+ // Build args without --watch to prevent infinite recursion
270
+ const args = ['up'];
271
+ if (options.dashboard === true) {
272
+ args.push('--dashboard');
273
+ if (options.port)
274
+ args.push('--port', options.port);
275
+ }
276
+ if (options.spawn === true)
277
+ args.push('--spawn');
278
+ if (options.spawn === false)
279
+ args.push('--no-spawn');
280
+ console.log(`[supervisor] Starting daemon...`);
281
+ child = spawn(process.execPath, [process.argv[1], ...args], {
282
+ stdio: 'inherit',
283
+ env: { ...process.env, AGENT_RELAY_SUPERVISED: '1' },
284
+ });
285
+ child.on('exit', (code, signal) => {
286
+ if (shuttingDown) {
287
+ process.exit(0);
288
+ return;
289
+ }
290
+ const now = Date.now();
291
+ restartTimes.push(now);
292
+ // Remove restarts outside the window
293
+ while (restartTimes.length > 0 && restartTimes[0] < now - restartWindow) {
294
+ restartTimes.shift();
295
+ }
296
+ if (restartTimes.length >= maxRestarts) {
297
+ console.error(`[supervisor] Daemon crashed ${maxRestarts} times in ${restartWindow / 1000}s, giving up`);
298
+ process.exit(1);
299
+ }
300
+ const exitReason = signal ? `signal ${signal}` : `code ${code}`;
301
+ console.log(`[supervisor] Daemon exited (${exitReason}), restarting in 2s... (${restartTimes.length}/${maxRestarts} restarts)`);
302
+ setTimeout(startDaemon, 2000);
303
+ });
304
+ };
305
+ process.on('SIGINT', () => {
306
+ console.log('\n[supervisor] Stopping...');
307
+ shuttingDown = true;
308
+ if (child)
309
+ child.kill('SIGINT');
310
+ });
311
+ process.on('SIGTERM', () => {
312
+ shuttingDown = true;
313
+ if (child)
314
+ child.kill('SIGTERM');
315
+ });
316
+ startDaemon();
317
+ return;
318
+ }
319
+ const { ensureProjectDir } = await import('@agent-relay/config');
320
+ const { loadTeamsConfig } = await import('@agent-relay/config');
321
+ const { AgentSpawner } = await import('@agent-relay/bridge');
322
+ const paths = ensureProjectDir();
323
+ const socketPath = paths.socketPath;
324
+ const dbPath = paths.dbPath;
325
+ const pidFilePath = pidFilePathForSocket(socketPath);
326
+ console.log(`Project: ${paths.projectRoot}`);
327
+ console.log(`Socket: ${socketPath}`);
328
+ // Load teams.json if present
329
+ const teamsConfig = loadTeamsConfig(paths.projectRoot);
330
+ if (teamsConfig) {
331
+ console.log(`Team: ${teamsConfig.team} (${teamsConfig.agents.length} agents defined)`);
332
+ }
333
+ const daemon = new Daemon({
334
+ socketPath,
335
+ pidFilePath,
336
+ storagePath: dbPath,
337
+ teamDir: paths.teamDir,
338
+ // TODO: Add daemon-based spawning support when SDK extraction is complete
339
+ // See: docs/SDK-MIGRATION-PLAN.md
340
+ });
341
+ // Create spawner for auto-spawn (will be initialized after dashboard starts)
342
+ let spawner = null;
343
+ // Track if we're already shutting down to prevent double-cleanup
344
+ let isShuttingDown = false;
345
+ const gracefulShutdown = async (reason) => {
346
+ if (isShuttingDown)
347
+ return;
348
+ isShuttingDown = true;
349
+ console.log(`\n[daemon] ${reason}, shutting down...`);
350
+ try {
351
+ if (spawner)
352
+ await spawner.releaseAll();
353
+ await daemon.stop();
354
+ }
355
+ catch (err) {
356
+ console.error('[daemon] Error during shutdown:', err);
357
+ }
358
+ process.exit(1);
359
+ };
360
+ // Handle uncaught exceptions - log and exit (supervisor will restart)
361
+ process.on('uncaughtException', (err) => {
362
+ console.error('[daemon] Uncaught exception:', err);
363
+ gracefulShutdown('Uncaught exception');
364
+ });
365
+ // Handle unhandled promise rejections
366
+ process.on('unhandledRejection', (reason, promise) => {
367
+ console.error('[daemon] Unhandled rejection at:', promise, 'reason:', reason);
368
+ // Don't exit on unhandled rejections - just log them
369
+ // Most are recoverable (e.g., failed message delivery)
370
+ });
371
+ process.on('SIGINT', async () => {
372
+ console.log('\nStopping...');
373
+ if (spawner) {
374
+ await spawner.releaseAll();
375
+ }
376
+ await daemon.stop();
377
+ process.exit(0);
378
+ });
379
+ process.on('SIGTERM', async () => {
380
+ if (spawner) {
381
+ await spawner.releaseAll();
382
+ }
383
+ await daemon.stop();
384
+ process.exit(0);
385
+ });
386
+ try {
387
+ await daemon.start();
388
+ console.log('Daemon started.');
389
+ let dashboardPort;
390
+ // Dashboard is disabled by default (use --dashboard to enable)
391
+ // Dashboard is an optional separate package: @agent-relay/dashboard
392
+ if (options.dashboard === true) {
393
+ const port = parseInt(options.port, 10);
394
+ try {
395
+ const { startDashboard } = await import('@agent-relay/dashboard');
396
+ dashboardPort = await startDashboard({
397
+ port,
398
+ dataDir: paths.dataDir,
399
+ teamDir: paths.teamDir,
400
+ dbPath,
401
+ enableSpawner: true,
402
+ projectRoot: paths.projectRoot,
403
+ // Pass spawn tracking callbacks so messages can be queued before HELLO completes
404
+ onMarkSpawning: (name) => daemon.markSpawning(name),
405
+ onClearSpawning: (name) => daemon.clearSpawning(name),
406
+ });
407
+ console.log(`Dashboard: http://localhost:${dashboardPort}`);
408
+ // Hook daemon log output to dashboard WebSocket
409
+ daemon.onLogOutput = (agentName, data, _timestamp) => {
410
+ const broadcast = global.__broadcastLogOutput;
411
+ if (broadcast) {
412
+ broadcast(agentName, data);
413
+ }
414
+ };
415
+ }
416
+ catch (err) {
417
+ const error = err;
418
+ if (error.code === 'ERR_MODULE_NOT_FOUND' || error.code === 'MODULE_NOT_FOUND') {
419
+ console.error(`
420
+ Dashboard package not installed.
421
+
422
+ The dashboard has moved to a separate optional package.
423
+ To use the web dashboard, install it:
424
+
425
+ npm install -g @agent-relay/dashboard
426
+
427
+ Then try again:
428
+
429
+ agent-relay up --dashboard
430
+
431
+ Or run without dashboard:
432
+
433
+ agent-relay up
434
+ `);
435
+ process.exit(1);
436
+ }
437
+ throw err;
438
+ }
439
+ }
440
+ // Determine if we should auto-spawn agents
441
+ // --spawn: force spawn
442
+ // --no-spawn: never spawn
443
+ // Neither: check teamsConfig.autoSpawn
444
+ const shouldSpawn = options.spawn === true
445
+ ? true
446
+ : options.spawn === false
447
+ ? false
448
+ : teamsConfig?.autoSpawn ?? false;
449
+ if (shouldSpawn && teamsConfig && teamsConfig.agents.length > 0) {
450
+ console.log('');
451
+ console.log('Auto-spawning agents from teams.json...');
452
+ spawner = new AgentSpawner({
453
+ projectRoot: paths.projectRoot,
454
+ dashboardPort,
455
+ onMarkSpawning: (name) => daemon.markSpawning(name),
456
+ onClearSpawning: (name) => daemon.clearSpawning(name),
457
+ });
458
+ for (const agent of teamsConfig.agents) {
459
+ console.log(` Spawning ${agent.name} (${agent.cli})...`);
460
+ const result = await spawner.spawn({
461
+ name: agent.name,
462
+ cli: agent.cli,
463
+ task: agent.task ?? '',
464
+ team: teamsConfig.team,
465
+ });
466
+ if (result.success) {
467
+ console.log(` ✓ ${agent.name} started [pid: ${result.pid}]`);
468
+ }
469
+ else {
470
+ console.error(` ✗ ${agent.name} failed: ${result.error}`);
471
+ }
472
+ }
473
+ console.log('');
474
+ }
475
+ else if (options.spawn === true && !teamsConfig) {
476
+ console.warn('Warning: --spawn specified but no teams.json found');
477
+ }
478
+ console.log('Press Ctrl+C to stop.');
479
+ await new Promise(() => { });
480
+ }
481
+ catch (err) {
482
+ console.error('Failed:', err);
483
+ process.exit(1);
484
+ }
485
+ });
486
+ // down - Stop daemon
487
+ program
488
+ .command('down')
489
+ .description('Stop daemon')
490
+ .action(async () => {
491
+ const { getProjectPaths } = await import('@agent-relay/config');
492
+ const paths = getProjectPaths();
493
+ const pidPath = pidFilePathForSocket(paths.socketPath);
494
+ if (!fs.existsSync(pidPath)) {
495
+ console.log('Not running');
496
+ return;
497
+ }
498
+ const pid = Number(fs.readFileSync(pidPath, 'utf-8').trim());
499
+ try {
500
+ process.kill(pid, 'SIGTERM');
501
+ console.log('Stopped');
502
+ }
503
+ catch {
504
+ fs.unlinkSync(pidPath);
505
+ console.log('Cleaned up stale pid');
506
+ }
507
+ });
508
+ // System prompt for Dashboard agent - plain text to avoid shell escaping issues
509
+ const MEGA_SYSTEM_PROMPT = [
510
+ 'You are Dashboard, a lead coordinator in agent-relay.',
511
+ 'Your PRIMARY job is to delegate - you should almost NEVER do implementation work yourself.',
512
+ 'ALWAYS SPAWN AGENTS: For any non-trivial task, spawn specialized workers.',
513
+ ].join(' ');
514
+ // Helper function for starting Dashboard coordinator with a specific provider
515
+ async function startDashboardCoordinator(operator) {
516
+ const { spawn } = await import('node:child_process');
517
+ const { getProjectPaths } = await import('@agent-relay/config');
518
+ const paths = getProjectPaths();
519
+ console.log(`Starting Dashboard with ${operator}...`);
520
+ console.log(`Project: ${paths.projectRoot}`);
521
+ // Step 1: Check if daemon is already running, start if needed
522
+ console.log('\n[1/3] Checking daemon...');
523
+ // Check if socket exists (daemon running)
524
+ const socketExists = fs.existsSync(paths.socketPath);
525
+ // Ports to try for dashboard detection
526
+ const portsToTry = [
527
+ parseInt(DEFAULT_DASHBOARD_PORT, 10),
528
+ 3889, 3890, 3891,
529
+ ];
530
+ // Check if dashboard is responding for THIS project
531
+ let dashboardReady = false;
532
+ let detectedPort;
533
+ // Helper to check health at a port
534
+ const checkPort = async (port) => {
535
+ try {
536
+ const response = await fetch(`http://localhost:${port}/api/health`, {
537
+ signal: AbortSignal.timeout(500),
538
+ });
539
+ if (response.ok) {
540
+ const health = await response.json();
541
+ return health.status === 'healthy';
542
+ }
543
+ }
544
+ catch {
545
+ // Port not responding
546
+ }
547
+ return false;
548
+ };
549
+ if (socketExists) {
550
+ for (const port of portsToTry) {
551
+ if (await checkPort(port)) {
552
+ dashboardReady = true;
553
+ detectedPort = port;
554
+ break;
555
+ }
556
+ }
557
+ }
558
+ if (dashboardReady && detectedPort) {
559
+ console.log(`Daemon already running at port ${detectedPort}, reusing...`);
560
+ }
561
+ else {
562
+ console.log('Starting daemon...');
563
+ const daemonProc = spawn(process.execPath, [process.argv[1], 'up', '--dashboard'], {
564
+ stdio: 'ignore',
565
+ detached: true,
566
+ });
567
+ daemonProc.unref();
568
+ // Wait for dashboard to be ready (up to 10 seconds)
569
+ const maxWait = 10000;
570
+ const startTime = Date.now();
571
+ while (Date.now() - startTime < maxWait) {
572
+ await new Promise((resolve) => setTimeout(resolve, 500));
573
+ for (const port of portsToTry) {
574
+ if (await checkPort(port)) {
575
+ dashboardReady = true;
576
+ detectedPort = port;
577
+ break;
578
+ }
579
+ }
580
+ if (dashboardReady)
581
+ break;
582
+ }
583
+ if (!dashboardReady) {
584
+ console.error('Warning: Dashboard may not be fully ready. Spawn might not work.');
585
+ detectedPort = parseInt(DEFAULT_DASHBOARD_PORT, 10); // Fallback
586
+ }
587
+ }
588
+ const dashboardPort = detectedPort || parseInt(DEFAULT_DASHBOARD_PORT, 10);
589
+ // Step 2: Install prpm snippet via npx
590
+ console.log('[2/3] Installing agent-relay snippet...');
591
+ const prpmArgs = operator.toLowerCase() === 'claude'
592
+ ? ['prpm', 'install', '@agent-relay/agent-relay-snippet', '--location', 'CLAUDE.md']
593
+ : ['prpm', 'install', '@agent-relay/agent-relay-snippet'];
594
+ try {
595
+ await new Promise((resolve, reject) => {
596
+ const prpmProc = spawn('npx', prpmArgs, {
597
+ stdio: 'inherit',
598
+ });
599
+ prpmProc.on('close', (code) => {
600
+ if (code === 0)
601
+ resolve();
602
+ else
603
+ reject(new Error(`npx prpm exited with code ${code}`));
604
+ });
605
+ prpmProc.on('error', reject);
606
+ });
607
+ }
608
+ catch (err) {
609
+ console.warn(`Warning: prpm install failed: ${err.message}`);
610
+ console.warn('Continuing without snippet installation...');
611
+ }
612
+ // Step 3: Start Dashboard agent with system prompt
613
+ console.log(`[3/3] Starting Dashboard agent with ${operator}...`);
614
+ console.log('');
615
+ const op = operator.toLowerCase();
616
+ // Build CLI-specific arguments for system prompt
617
+ // These args go AFTER the operator command, passed through to the CLI
618
+ let cliArgs = [];
619
+ if (op === 'claude') {
620
+ // Claude: --append-system-prompt <content> (takes content directly, not file)
621
+ cliArgs = ['--append-system-prompt', MEGA_SYSTEM_PROMPT];
622
+ }
623
+ else if (op === 'codex') {
624
+ // Codex: --config developer_instructions="<content>"
625
+ cliArgs = ['--config', `developer_instructions=${MEGA_SYSTEM_PROMPT}`];
626
+ }
627
+ // Use '--' to separate agent-relay options from the command + its args
628
+ // Format: agent-relay create-agent -n Dashboard --skip-instructions --dashboard-port <port> -- claude --append-system-prompt "..."
629
+ const agentProc = spawn(process.execPath, [process.argv[1], 'create-agent', '-n', 'Dashboard', '--skip-instructions', '--dashboard-port', String(dashboardPort), '--', operator, ...cliArgs], { stdio: 'inherit' });
630
+ // Forward signals to agent process
631
+ process.on('SIGINT', () => {
632
+ agentProc.kill('SIGINT');
633
+ });
634
+ process.on('SIGTERM', () => {
635
+ agentProc.kill('SIGTERM');
636
+ });
637
+ agentProc.on('close', (code) => {
638
+ process.exit(code ?? 0);
639
+ });
640
+ }
641
+ // claude - Start daemon and spawn Dashboard coordinator with Claude
642
+ program
643
+ .command('claude')
644
+ .description('Start daemon and Dashboard coordinator with Claude')
645
+ .action(async () => {
646
+ await startDashboardCoordinator('claude');
647
+ });
648
+ // codex - Start daemon and spawn Dashboard coordinator with Codex
649
+ program
650
+ .command('codex')
651
+ .description('Start daemon and Dashboard coordinator with Codex')
652
+ .action(async () => {
653
+ await startDashboardCoordinator('codex');
654
+ });
655
+ // status - Check daemon status
656
+ program
657
+ .command('status')
658
+ .description('Check daemon status')
659
+ .action(async () => {
660
+ const { getProjectPaths } = await import('@agent-relay/config');
661
+ const paths = getProjectPaths();
662
+ const relaySessions = await discoverRelaySessions();
663
+ if (!fs.existsSync(paths.socketPath)) {
664
+ console.log('Status: STOPPED');
665
+ logRelaySessions(relaySessions);
666
+ return;
667
+ }
668
+ const client = new RelayClient({
669
+ agentName: '__status__',
670
+ socketPath: paths.socketPath,
671
+ reconnect: false,
672
+ });
673
+ try {
674
+ await client.connect();
675
+ console.log('Status: RUNNING');
676
+ console.log(`Socket: ${paths.socketPath}`);
677
+ logRelaySessions(relaySessions);
678
+ client.disconnect();
679
+ }
680
+ catch {
681
+ console.log('Status: STOPPED');
682
+ logRelaySessions(relaySessions);
683
+ }
684
+ });
685
+ // agents - List connected agents (from registry file) and spawned workers
686
+ program
687
+ .command('agents')
688
+ .description('List connected agents and spawned workers')
689
+ .option('--all', 'Include internal/CLI agents')
690
+ .option('--remote', 'Include agents from other linked machines (requires cloud link)')
691
+ .option('--json', 'Output as JSON')
692
+ .action(async (options) => {
693
+ const { getProjectPaths } = await import('@agent-relay/config');
694
+ const os = await import('node:os');
695
+ const paths = getProjectPaths();
696
+ const agentsPath = path.join(paths.teamDir, 'agents.json');
697
+ const connectedAgentsPath = path.join(paths.teamDir, 'connected-agents.json');
698
+ // Load registered agents
699
+ const allAgents = loadAgents(agentsPath);
700
+ const agents = options.all
701
+ ? allAgents
702
+ : allAgents.filter(isVisibleAgent);
703
+ // Load spawned workers
704
+ const workers = readWorkersMetadata(paths.projectRoot);
705
+ // Load currently connected agents (agents with active socket connections)
706
+ let connectedAgentNames = new Set();
707
+ let connectedUpdatedAt = 0;
708
+ try {
709
+ if (fs.existsSync(connectedAgentsPath)) {
710
+ const connectedData = JSON.parse(fs.readFileSync(connectedAgentsPath, 'utf-8'));
711
+ connectedAgentNames = new Set([
712
+ ...(connectedData.agents || []),
713
+ ...(connectedData.users || []),
714
+ ]);
715
+ connectedUpdatedAt = connectedData.updatedAt || 0;
716
+ }
717
+ }
718
+ catch {
719
+ // If file doesn't exist or is invalid, fall back to registry-based status
720
+ }
721
+ // Check if connected-agents.json is fresh (within 30 seconds)
722
+ const isConnectedAgentsStale = Date.now() - connectedUpdatedAt > 30_000;
723
+ const combined = [];
724
+ // Add registered agents
725
+ agents.forEach((agent) => {
726
+ const worker = workers.find(w => w.name === agent.name);
727
+ // Use connected-agents.json if fresh, otherwise fall back to registry lastSeen
728
+ let status;
729
+ if (!isConnectedAgentsStale && connectedAgentNames.size > 0) {
730
+ // We have fresh connected-agents data - use actual connection status
731
+ status = connectedAgentNames.has(agent.name ?? '') ? 'ONLINE' : 'OFFLINE';
732
+ }
733
+ else {
734
+ // Fall back to registry-based status
735
+ status = getAgentStatus(agent);
736
+ }
737
+ combined.push({
738
+ name: agent.name ?? 'unknown',
739
+ status,
740
+ cli: agent.cli ?? '-',
741
+ lastSeen: agent.lastSeen,
742
+ team: worker?.team,
743
+ pid: worker?.pid,
744
+ location: 'local',
745
+ });
746
+ });
747
+ // Add workers not in registry (orphaned or not yet registered)
748
+ workers.forEach((worker) => {
749
+ const existsInAgents = agents.some(a => a.name === worker.name);
750
+ if (!existsInAgents) {
751
+ combined.push({
752
+ name: worker.name || 'unknown',
753
+ status: 'ONLINE',
754
+ cli: worker.cli || '-',
755
+ team: worker.team,
756
+ pid: worker.pid,
757
+ location: 'local',
758
+ });
759
+ }
760
+ });
761
+ // Include remote agents if --remote flag is set
762
+ if (options.remote) {
763
+ const dataDir = process.env.AGENT_RELAY_DATA_DIR ||
764
+ path.join(os.homedir(), '.local', 'share', 'agent-relay');
765
+ const configPath = path.join(dataDir, 'cloud-config.json');
766
+ if (fs.existsSync(configPath)) {
767
+ try {
768
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
769
+ const response = await fetch(`${config.cloudUrl}/api/daemons/agents`, {
770
+ method: 'POST',
771
+ headers: {
772
+ 'Authorization': `Bearer ${config.apiKey}`,
773
+ 'Content-Type': 'application/json',
774
+ },
775
+ body: JSON.stringify({ agents: [] }),
776
+ });
777
+ if (response.ok) {
778
+ const data = await response.json();
779
+ // Add remote agents (exclude local ones by name)
780
+ const localNames = new Set(combined.map(a => a.name));
781
+ for (const agent of data.allAgents) {
782
+ if (!localNames.has(agent.name)) {
783
+ combined.push({
784
+ name: agent.name,
785
+ status: agent.status.toUpperCase(),
786
+ cli: '-',
787
+ location: agent.daemonName,
788
+ daemonId: agent.daemonId,
789
+ });
790
+ }
791
+ }
792
+ }
793
+ }
794
+ catch (err) {
795
+ console.error('[warn] Failed to fetch remote agents:', err.message);
796
+ }
797
+ }
798
+ else {
799
+ console.error('[warn] Cloud not linked. Run `agent-relay cloud link` to see remote agents.');
800
+ }
801
+ }
802
+ if (options.json) {
803
+ console.log(JSON.stringify(combined, null, 2));
804
+ return;
805
+ }
806
+ if (!combined.length) {
807
+ const hint = options.all ? '' : ' (use --all to include internal/cli agents)';
808
+ console.log(`No agents found. Ensure the daemon is running and agents are connected${hint}.`);
809
+ return;
810
+ }
811
+ const hasRemote = combined.some(a => a.location !== 'local');
812
+ if (hasRemote) {
813
+ console.log('NAME STATUS CLI LOCATION');
814
+ console.log('─'.repeat(55));
815
+ combined.forEach((agent) => {
816
+ const name = agent.name.padEnd(15);
817
+ const status = agent.status.padEnd(8);
818
+ const cli = agent.cli.padEnd(9);
819
+ const location = agent.location ?? 'local';
820
+ console.log(`${name} ${status} ${cli} ${location}`);
821
+ });
822
+ }
823
+ else {
824
+ console.log('NAME STATUS CLI TEAM');
825
+ console.log('─'.repeat(50));
826
+ combined.forEach((agent) => {
827
+ const name = agent.name.padEnd(15);
828
+ const status = agent.status.padEnd(8);
829
+ const cli = agent.cli.padEnd(9);
830
+ const team = agent.team ?? '-';
831
+ console.log(`${name} ${status} ${cli} ${team}`);
832
+ });
833
+ }
834
+ if (workers.length > 0) {
835
+ console.log('');
836
+ console.log('Commands:');
837
+ console.log(' agent-relay agents:logs <name> - View spawned agent output');
838
+ console.log(' agent-relay agents:kill <name> - Kill a spawned agent');
839
+ }
840
+ if (!options.remote) {
841
+ console.log('');
842
+ console.log('Tip: Use --remote to include agents from other linked machines.');
843
+ }
844
+ });
845
+ // who - Show currently active agents (online within last 30s)
846
+ program
847
+ .command('who')
848
+ .description('Show currently active agents (last seen within 30 seconds)')
849
+ .option('--all', 'Include internal/CLI agents')
850
+ .option('--json', 'Output as JSON')
851
+ .action(async (options) => {
852
+ const { getProjectPaths } = await import('@agent-relay/config');
853
+ const paths = getProjectPaths();
854
+ const agentsPath = path.join(paths.teamDir, 'agents.json');
855
+ const allAgents = loadAgents(agentsPath);
856
+ const visibleAgents = options.all
857
+ ? allAgents
858
+ : allAgents.filter(a => !isInternalAgent(a.name));
859
+ const onlineAgents = visibleAgents.filter(isAgentOnline);
860
+ if (options.json) {
861
+ console.log(JSON.stringify(onlineAgents.map(a => ({ ...a, status: getAgentStatus(a) })), null, 2));
862
+ return;
863
+ }
864
+ if (!onlineAgents.length) {
865
+ const hint = options.all ? '' : ' (use --all to include internal/cli agents)';
866
+ console.log(`No active agents found${hint}.`);
867
+ return;
868
+ }
869
+ console.log('NAME STATUS CLI LAST SEEN');
870
+ console.log('---------------------------------------------');
871
+ onlineAgents.forEach((agent) => {
872
+ const name = (agent.name ?? 'unknown').padEnd(15);
873
+ const status = getAgentStatus(agent).padEnd(8);
874
+ const cli = (agent.cli ?? '-').padEnd(8);
875
+ const lastSeen = formatRelativeTime(agent.lastSeen);
876
+ console.log(`${name} ${status} ${cli} ${lastSeen}`);
877
+ });
878
+ });
879
+ // read - Read full message by ID (for truncated messages)
880
+ program
881
+ .command('read')
882
+ .description('Read full message by ID (for truncated messages)')
883
+ .argument('<id>', 'Message ID')
884
+ .action(async (messageId) => {
885
+ const { getProjectPaths } = await import('@agent-relay/config');
886
+ const { createStorageAdapter } = await import('@agent-relay/storage/adapter');
887
+ const paths = getProjectPaths();
888
+ const adapter = await createStorageAdapter(paths.dbPath);
889
+ if (!adapter.getMessageById) {
890
+ console.error('Storage does not support message lookup');
891
+ process.exit(1);
892
+ }
893
+ const msg = await adapter.getMessageById(messageId);
894
+ if (!msg) {
895
+ console.error(`Message not found: ${messageId}`);
896
+ process.exit(1);
897
+ }
898
+ console.log(`From: ${msg.from}`);
899
+ console.log(`To: ${msg.to}`);
900
+ console.log(`Time: ${new Date(msg.ts).toISOString()}`);
901
+ console.log('---');
902
+ console.log(msg.body);
903
+ await adapter.close?.();
904
+ });
905
+ // ============================================
906
+ // Hidden commands (for agents, not in --help)
907
+ // ============================================
908
+ // history - Show recent messages (hidden from help, for agent use)
909
+ program
910
+ .command('history', { hidden: true })
911
+ .description('Show recent messages')
912
+ .option('-n, --limit <count>', 'Number of messages to show', '50')
913
+ .option('-f, --from <agent>', 'Filter by sender')
914
+ .option('-t, --to <agent>', 'Filter by recipient')
915
+ .option('--since <time>', 'Since time (e.g., "1h", "2024-01-01")')
916
+ .option('--json', 'Output as JSON')
917
+ .action(async (options) => {
918
+ const { getProjectPaths } = await import('@agent-relay/config');
919
+ const { createStorageAdapter } = await import('@agent-relay/storage/adapter');
920
+ const paths = getProjectPaths();
921
+ const adapter = await createStorageAdapter(paths.dbPath);
922
+ const limit = Number.parseInt(options.limit ?? '50', 10) || 50;
923
+ const sinceTs = parseSince(options.since);
924
+ try {
925
+ const messages = await adapter.getMessages({
926
+ limit,
927
+ from: options.from,
928
+ to: options.to,
929
+ sinceTs,
930
+ order: 'desc',
931
+ });
932
+ if (options.json) {
933
+ const payload = messages.map((m) => ({
934
+ id: m.id,
935
+ ts: m.ts,
936
+ timestamp: new Date(m.ts).toISOString(),
937
+ from: m.from,
938
+ to: m.to,
939
+ topic: m.topic,
940
+ thread: m.thread,
941
+ kind: m.kind,
942
+ body: m.body,
943
+ }));
944
+ console.log(JSON.stringify(payload, null, 2));
945
+ return;
946
+ }
947
+ if (!messages.length) {
948
+ console.log('No messages found.');
949
+ return;
950
+ }
951
+ messages.forEach((msg) => {
952
+ const ts = new Date(msg.ts).toISOString();
953
+ const body = msg.body.length > 120 ? `${msg.body.slice(0, 117)}...` : msg.body;
954
+ console.log(`${ts} ${msg.from} -> ${msg.to}:${body}`);
955
+ });
956
+ }
957
+ finally {
958
+ await adapter.close?.();
959
+ }
960
+ });
961
+ // version - Show version info
962
+ program
963
+ .command('version')
964
+ .description('Show version information')
965
+ .action(() => {
966
+ console.log(`agent-relay v${VERSION}`);
967
+ });
968
+ // update - Check for updates and optionally install
969
+ program
970
+ .command('update')
971
+ .description('Check for updates and install if available')
972
+ .option('--check', 'Only check for updates, do not install')
973
+ .action(async (options) => {
974
+ console.log(`Current version: ${VERSION}`);
975
+ console.log('Checking for updates...');
976
+ const info = await checkForUpdates(VERSION);
977
+ if (info.error) {
978
+ console.error(`Failed to check for updates: ${info.error}`);
979
+ process.exit(1);
980
+ }
981
+ if (!info.updateAvailable) {
982
+ console.log('You are running the latest version.');
983
+ return;
984
+ }
985
+ console.log(`New version available: ${info.latestVersion}`);
986
+ if (options.check) {
987
+ console.log('Run `agent-relay update` to install.');
988
+ return;
989
+ }
990
+ console.log('Installing update...');
991
+ try {
992
+ const { stdout, stderr } = await execAsync('npm install -g agent-relay@latest');
993
+ if (stdout)
994
+ console.log(stdout);
995
+ if (stderr)
996
+ console.error(stderr);
997
+ console.log(`Successfully updated to ${info.latestVersion}`);
998
+ }
999
+ catch (err) {
1000
+ console.error('Failed to install update:', err.message);
1001
+ console.log('Try running manually: npm install -g agent-relay@latest');
1002
+ process.exit(1);
1003
+ }
1004
+ });
1005
+ // check-tmux - Check tmux availability (hidden - for diagnostics)
1006
+ program
1007
+ .command('check-tmux', { hidden: true })
1008
+ .description('Check tmux availability and version')
1009
+ .action(async () => {
1010
+ const { resolveTmux, checkTmuxVersion } = await import('@agent-relay/wrapper');
1011
+ const info = resolveTmux();
1012
+ if (!info) {
1013
+ console.log('tmux: NOT FOUND');
1014
+ console.log('');
1015
+ console.log('Install tmux, then reinstall agent-relay:');
1016
+ console.log(' brew install tmux # macOS');
1017
+ console.log(' apt install tmux # Ubuntu/Debian');
1018
+ console.log(' npm install agent-relay # Reinstall to bundle tmux');
1019
+ process.exit(1);
1020
+ }
1021
+ console.log(`tmux: ${info.path}`);
1022
+ console.log(`Version: ${info.version}`);
1023
+ console.log(`Source: ${info.isBundled ? 'bundled' : 'system'}`);
1024
+ const versionCheck = checkTmuxVersion();
1025
+ if (!versionCheck.ok) {
1026
+ console.log(`Warning: tmux ${versionCheck.minimum}+ recommended`);
1027
+ }
1028
+ });
1029
+ // bridge - Multi-project orchestration
1030
+ program
1031
+ .command('bridge')
1032
+ .description('Bridge multiple projects as orchestrator')
1033
+ .argument('[projects...]', 'Project paths to bridge')
1034
+ .option('--cli <tool>', 'CLI tool override for all projects')
1035
+ .option('--architect [cli]', 'Spawn an architect agent to coordinate all projects (default: claude)')
1036
+ .action(async (projectPaths, options) => {
1037
+ const { resolveProjects, validateDaemons, getAgentOutboxTemplate } = await import('@agent-relay/config');
1038
+ const { MultiProjectClient } = await import('@agent-relay/bridge');
1039
+ const { getProjectPaths } = await import('@agent-relay/config');
1040
+ const fs = await import('node:fs');
1041
+ const pathModule = await import('node:path');
1042
+ // Resolve projects from args or config
1043
+ const projects = resolveProjects(projectPaths, options.cli);
1044
+ if (projects.length === 0) {
1045
+ console.error('No projects specified.');
1046
+ console.error('Usage: agent-relay bridge ~/project1 ~/project2');
1047
+ console.error(' or: Create ~/.agent-relay/bridge.json with project config');
1048
+ process.exit(1);
1049
+ }
1050
+ console.log('Bridge Mode - Multi-Project Orchestration');
1051
+ console.log('─'.repeat(40));
1052
+ // Check which daemons are running
1053
+ const { valid, missing } = validateDaemons(projects);
1054
+ if (missing.length > 0) {
1055
+ console.error('\nMissing daemons for:');
1056
+ for (const p of missing) {
1057
+ console.error(` - ${p.path}`);
1058
+ console.error(` Run: cd "${p.path}" && agent-relay up`);
1059
+ }
1060
+ console.error('');
1061
+ }
1062
+ if (valid.length === 0) {
1063
+ console.error('No projects have running daemons. Start them first.');
1064
+ process.exit(1);
1065
+ }
1066
+ console.log('\nConnecting to projects:');
1067
+ for (const p of valid) {
1068
+ console.log(` - ${p.id} (${p.path})`);
1069
+ console.log(` Lead: ${p.leadName}, CLI: ${p.cli}`);
1070
+ }
1071
+ console.log('');
1072
+ // Get data directories for ALL bridged projects (so each project's dashboard can show bridge state)
1073
+ const bridgeStatePaths = valid.map(p => {
1074
+ const projectPaths = getProjectPaths(p.path);
1075
+ // Ensure directory exists
1076
+ if (!fs.existsSync(projectPaths.dataDir)) {
1077
+ fs.mkdirSync(projectPaths.dataDir, { recursive: true });
1078
+ }
1079
+ return pathModule.join(projectPaths.dataDir, 'bridge-state.json');
1080
+ });
1081
+ const bridgeState = {
1082
+ projects: valid.map(p => ({
1083
+ id: p.id,
1084
+ name: pathModule.basename(p.path),
1085
+ path: p.path,
1086
+ connected: false,
1087
+ lead: { name: p.leadName, connected: false },
1088
+ agents: [],
1089
+ })),
1090
+ messages: [],
1091
+ connected: false,
1092
+ startedAt: new Date().toISOString(),
1093
+ };
1094
+ // Write bridge state to ALL project data directories
1095
+ const writeBridgeState = () => {
1096
+ const stateJson = JSON.stringify(bridgeState, null, 2);
1097
+ for (const statePath of bridgeStatePaths) {
1098
+ try {
1099
+ fs.writeFileSync(statePath, stateJson);
1100
+ }
1101
+ catch (err) {
1102
+ console.error(`[bridge] Failed to write state to ${statePath}:`, err);
1103
+ }
1104
+ }
1105
+ };
1106
+ // Initial state write
1107
+ writeBridgeState();
1108
+ console.log(`Bridge state written to ${bridgeStatePaths.length} project(s)`);
1109
+ // Connect to all project daemons
1110
+ const client = new MultiProjectClient(valid);
1111
+ // Track connection state changes (daemon connection, not agent registration)
1112
+ // Also track "reconnecting" state for UI feedback
1113
+ const wasConnected = new Map();
1114
+ client.onProjectStateChange = (projectId, connected) => {
1115
+ const project = bridgeState.projects.find(p => p.id === projectId);
1116
+ if (project) {
1117
+ const hadConnection = wasConnected.get(projectId) || false;
1118
+ project.connected = connected;
1119
+ // Set reconnecting if we lost connection (had it before, now disconnected)
1120
+ project.reconnecting = !connected && hadConnection;
1121
+ wasConnected.set(projectId, connected);
1122
+ // Note: lead.connected should only be true when an actual lead agent registers
1123
+ // The bridge connecting to daemon doesn't mean a lead agent is active
1124
+ }
1125
+ bridgeState.connected = bridgeState.projects.some(p => p.connected);
1126
+ writeBridgeState();
1127
+ };
1128
+ try {
1129
+ await client.connect();
1130
+ }
1131
+ catch (_err) {
1132
+ console.error('Failed to connect to all projects');
1133
+ writeBridgeState(); // Write final state before exit
1134
+ process.exit(1);
1135
+ }
1136
+ bridgeState.connected = true;
1137
+ writeBridgeState();
1138
+ console.log('Connected to all projects.');
1139
+ console.log('');
1140
+ console.log('Cross-project messaging:');
1141
+ console.log(' ->relay:projectId:agent Message');
1142
+ console.log(' ->relay:*:lead Broadcast to all leads');
1143
+ console.log('');
1144
+ // Spawn architect agent if --architect flag is set
1145
+ let architectWrapper = null;
1146
+ if (options.architect !== undefined) {
1147
+ // Determine CLI to use (default to claude)
1148
+ const architectCli = typeof options.architect === 'string' ? options.architect : 'claude';
1149
+ // Use first project as the base for the architect
1150
+ const baseProject = valid[0];
1151
+ const basePaths = getProjectPaths(baseProject.path);
1152
+ // Build project context for the architect
1153
+ const projectContext = valid.map(p => `- ${p.id}: ${p.path} (Lead: ${p.leadName})`).join('\n');
1154
+ // Get outbox path template for agent instructions (escaped for template literal)
1155
+ const outboxPath = getAgentOutboxTemplate().replace(/\$/g, '\\$');
1156
+ // Create architect system prompt
1157
+ const architectPrompt = `You are the Architect, a cross-project coordinator overseeing multiple codebases.
1158
+
1159
+ ## Connected Projects
1160
+ ${projectContext}
1161
+
1162
+ ## Your Role
1163
+ - Coordinate high-level work across all projects
1164
+ - Assign tasks to project leads
1165
+ - Ensure consistency and resolve cross-project dependencies
1166
+ - Review overall architecture decisions
1167
+
1168
+ ## Cross-Project Messaging
1169
+
1170
+ Write a file to your outbox, then output the trigger. Use project:AgentName syntax:
1171
+
1172
+ \`\`\`bash
1173
+ # Message specific project lead
1174
+ cat > ${outboxPath}/msg << 'EOF'
1175
+ TO: ${valid[0].id}:${valid[0].leadName}
1176
+
1177
+ Your message to this project's lead.
1178
+ EOF
1179
+ \`\`\`
1180
+ Then output: \`->relay-file:msg\`
1181
+
1182
+ \`\`\`bash
1183
+ # Broadcast to all agents in a project
1184
+ cat > ${outboxPath}/broadcast << 'EOF'
1185
+ TO: ${valid.length > 1 ? valid[1].id : valid[0].id}:*
1186
+
1187
+ Broadcast to all agents in a project.
1188
+ EOF
1189
+ \`\`\`
1190
+ Then output: \`->relay-file:broadcast\`
1191
+
1192
+ \`\`\`bash
1193
+ # Broadcast to ALL agents in ALL projects
1194
+ cat > ${outboxPath}/all << 'EOF'
1195
+ TO: *:*
1196
+
1197
+ Broadcast to ALL agents in ALL projects.
1198
+ EOF
1199
+ \`\`\`
1200
+ Then output: \`->relay-file:all\`
1201
+
1202
+ ## Getting Started
1203
+ 1. Check in with each project lead to understand current status
1204
+ 2. Identify cross-project dependencies
1205
+ 3. Coordinate work across teams
1206
+
1207
+ Start by greeting the project leads and asking for status updates.`;
1208
+ console.log('Spawning Architect agent...');
1209
+ console.log(` CLI: ${architectCli}`);
1210
+ console.log(` Base project: ${baseProject.path}`);
1211
+ console.log('');
1212
+ // Determine command and args based on CLI
1213
+ let command;
1214
+ let args = [];
1215
+ if (architectCli === 'claude' || architectCli.startsWith('claude:')) {
1216
+ command = 'claude';
1217
+ args = ['--dangerously-skip-permissions'];
1218
+ // Add model if specified (e.g., claude:opus)
1219
+ if (architectCli.includes(':')) {
1220
+ const model = architectCli.split(':')[1];
1221
+ args.push('--model', model);
1222
+ }
1223
+ }
1224
+ else if (architectCli === 'codex') {
1225
+ command = 'codex';
1226
+ args = ['--dangerously-skip-permissions'];
1227
+ }
1228
+ else {
1229
+ command = architectCli;
1230
+ }
1231
+ try {
1232
+ architectWrapper = new RelayPtyOrchestrator({
1233
+ name: 'Architect',
1234
+ command,
1235
+ args,
1236
+ socketPath: basePaths.socketPath,
1237
+ cwd: baseProject.path,
1238
+ streamLogs: true,
1239
+ });
1240
+ await architectWrapper.start();
1241
+ // Wait for agent to be ready, then inject the prompt
1242
+ setTimeout(async () => {
1243
+ try {
1244
+ await architectWrapper.injectMessage(architectPrompt);
1245
+ console.log('Architect agent started and initialized.');
1246
+ console.log('');
1247
+ }
1248
+ catch (err) {
1249
+ console.error('Failed to inject architect prompt:', err);
1250
+ }
1251
+ }, 3000);
1252
+ }
1253
+ catch (err) {
1254
+ console.error('Failed to spawn Architect agent:', err);
1255
+ }
1256
+ }
1257
+ // Handle messages from projects
1258
+ client.onMessage = (projectId, from, payload, messageId) => {
1259
+ console.log(`[${projectId}] ${from}: ${payload.body.substring(0, 80)}...`);
1260
+ // Track message in bridge state
1261
+ bridgeState.messages.push({
1262
+ id: messageId,
1263
+ from,
1264
+ to: '*', // Incoming messages are from agents
1265
+ body: payload.body,
1266
+ sourceProject: projectId,
1267
+ timestamp: new Date().toISOString(),
1268
+ });
1269
+ // Keep last 100 messages
1270
+ if (bridgeState.messages.length > 100) {
1271
+ bridgeState.messages = bridgeState.messages.slice(-100);
1272
+ }
1273
+ writeBridgeState();
1274
+ };
1275
+ // Clean up on exit
1276
+ const cleanup = () => {
1277
+ bridgeState.connected = false;
1278
+ bridgeState.projects.forEach(p => {
1279
+ p.connected = false;
1280
+ if (p.lead)
1281
+ p.lead.connected = false;
1282
+ });
1283
+ writeBridgeState();
1284
+ };
1285
+ // Keep running
1286
+ process.on('SIGINT', () => {
1287
+ console.log('\nDisconnecting...');
1288
+ cleanup();
1289
+ client.disconnect();
1290
+ process.exit(0);
1291
+ });
1292
+ // Start a simple REPL for sending messages
1293
+ const readline = await import('node:readline');
1294
+ const rl = readline.createInterface({
1295
+ input: process.stdin,
1296
+ output: process.stdout,
1297
+ });
1298
+ console.log('Enter messages as: projectId:agent message');
1299
+ console.log('Or: *:lead message (broadcast to all leads)');
1300
+ console.log('Type "quit" to exit.\n');
1301
+ const promptForInput = () => {
1302
+ rl.question('> ', (input) => {
1303
+ if (input.toLowerCase() === 'quit') {
1304
+ client.disconnect();
1305
+ rl.close();
1306
+ process.exit(0);
1307
+ }
1308
+ // Parse input: projectId:agent message
1309
+ const match = input.match(/^(\S+):(\S+)\s+(.+)$/);
1310
+ if (match) {
1311
+ const [, projectId, agent, message] = match;
1312
+ if (projectId === '*' && agent === 'lead') {
1313
+ client.broadcastToLeads(message);
1314
+ console.log('→ Broadcast to all leads');
1315
+ }
1316
+ else if (projectId === '*') {
1317
+ client.broadcastAll(message);
1318
+ console.log('→ Broadcast to all');
1319
+ }
1320
+ else {
1321
+ const sent = client.sendToProject(projectId, agent, message);
1322
+ if (sent) {
1323
+ console.log(`→ ${projectId}:${agent}`);
1324
+ }
1325
+ }
1326
+ }
1327
+ else {
1328
+ console.log('Format: projectId:agent message');
1329
+ }
1330
+ promptForInput();
1331
+ });
1332
+ };
1333
+ promptForInput();
1334
+ });
1335
+ // gc - Clean up orphaned tmux sessions (hidden - for agent use)
1336
+ program
1337
+ .command('gc', { hidden: true })
1338
+ .description('Clean up orphaned tmux sessions (sessions with no connected agent)')
1339
+ .option('--dry-run', 'Show what would be cleaned without actually doing it')
1340
+ .option('--force', 'Kill all relay sessions regardless of connection status')
1341
+ .action(async (options) => {
1342
+ const { getProjectPaths } = await import('@agent-relay/config');
1343
+ const paths = getProjectPaths();
1344
+ const agentsPath = path.join(paths.teamDir, 'agents.json');
1345
+ // Get all relay tmux sessions
1346
+ const sessions = await discoverRelaySessions();
1347
+ if (!sessions.length) {
1348
+ console.log('No relay tmux sessions found.');
1349
+ return;
1350
+ }
1351
+ // Get connected agents
1352
+ const connectedAgents = new Set();
1353
+ if (!options.force) {
1354
+ const agents = loadAgents(agentsPath);
1355
+ // Consider an agent "connected" if last seen within 30 seconds
1356
+ const staleThresholdMs = 30_000;
1357
+ const now = Date.now();
1358
+ agents.forEach(a => {
1359
+ if (a.name && a.lastSeen) {
1360
+ const lastSeenTs = Date.parse(a.lastSeen);
1361
+ if (!Number.isNaN(lastSeenTs) && now - lastSeenTs < staleThresholdMs) {
1362
+ connectedAgents.add(a.name);
1363
+ }
1364
+ }
1365
+ });
1366
+ }
1367
+ // Find orphaned sessions
1368
+ const orphaned = sessions.filter(s => options.force || (s.agentName && !connectedAgents.has(s.agentName)));
1369
+ if (!orphaned.length) {
1370
+ console.log(`All ${sessions.length} session(s) have active agents.`);
1371
+ return;
1372
+ }
1373
+ console.log(`Found ${orphaned.length} orphaned session(s):`);
1374
+ for (const session of orphaned) {
1375
+ console.log(` - ${session.sessionName} (agent: ${session.agentName ?? 'unknown'})`);
1376
+ }
1377
+ if (options.dryRun) {
1378
+ console.log('\nDry run - no sessions killed.');
1379
+ return;
1380
+ }
1381
+ // Kill orphaned sessions
1382
+ let killed = 0;
1383
+ const tmuxPath = getTmuxPath();
1384
+ for (const session of orphaned) {
1385
+ try {
1386
+ await execAsync(`"${tmuxPath}" kill-session -t ${session.sessionName}`);
1387
+ killed++;
1388
+ console.log(`Killed: ${session.sessionName}`);
1389
+ }
1390
+ catch (err) {
1391
+ console.error(`Failed to kill ${session.sessionName}: ${err.message}`);
1392
+ }
1393
+ }
1394
+ console.log(`\nCleaned up ${killed}/${orphaned.length} session(s).`);
1395
+ });
1396
+ async function discoverRelaySessions() {
1397
+ try {
1398
+ const tmuxPath = getTmuxPath();
1399
+ const { stdout } = await execAsync(`"${tmuxPath}" list-sessions -F "#{session_name}"`);
1400
+ const sessionNames = stdout
1401
+ .split('\n')
1402
+ .map(s => s.trim())
1403
+ .filter(Boolean);
1404
+ const relaySessions = sessionNames
1405
+ .map(name => {
1406
+ const match = name.match(/^relay-(.+)$/);
1407
+ if (!match)
1408
+ return undefined;
1409
+ return { sessionName: name, agentName: match[1] };
1410
+ })
1411
+ .filter((s) => Boolean(s));
1412
+ return await Promise.all(relaySessions.map(async (session) => {
1413
+ let cwd;
1414
+ try {
1415
+ const { stdout: cwdOut } = await execAsync(`"${tmuxPath}" display-message -t ${session.sessionName} -p '#{pane_current_path}'`);
1416
+ cwd = cwdOut.trim() || undefined;
1417
+ }
1418
+ catch {
1419
+ cwd = undefined;
1420
+ }
1421
+ return { ...session, cwd };
1422
+ }));
1423
+ }
1424
+ catch {
1425
+ return [];
1426
+ }
1427
+ }
1428
+ function logRelaySessions(sessions) {
1429
+ if (!sessions.length) {
1430
+ console.log('Relay tmux sessions: none detected');
1431
+ return;
1432
+ }
1433
+ console.log('Relay tmux sessions:');
1434
+ sessions.forEach((session) => {
1435
+ const parts = [
1436
+ `agent: ${session.agentName ?? 'unknown'}`,
1437
+ session.cwd ? `cwd: ${session.cwd}` : undefined,
1438
+ ].filter(Boolean);
1439
+ console.log(`- ${session.sessionName}${parts.length ? ` (${parts.join(', ')})` : ''}`);
1440
+ });
1441
+ }
1442
+ function loadAgents(agentsPath) {
1443
+ if (!fs.existsSync(agentsPath)) {
1444
+ return [];
1445
+ }
1446
+ try {
1447
+ const raw = JSON.parse(fs.readFileSync(agentsPath, 'utf-8'));
1448
+ const agentsArray = Array.isArray(raw?.agents)
1449
+ ? raw.agents
1450
+ : raw?.agents
1451
+ ? Object.values(raw.agents)
1452
+ : [];
1453
+ return agentsArray
1454
+ .filter((a) => a?.name)
1455
+ .map((a) => ({
1456
+ id: a.id,
1457
+ name: a.name,
1458
+ cli: a.cli,
1459
+ workingDirectory: a.workingDirectory,
1460
+ firstSeen: a.firstSeen,
1461
+ lastSeen: a.lastSeen,
1462
+ messagesSent: typeof a.messagesSent === 'number' ? a.messagesSent : 0,
1463
+ messagesReceived: typeof a.messagesReceived === 'number' ? a.messagesReceived : 0,
1464
+ }));
1465
+ }
1466
+ catch (err) {
1467
+ console.error('Failed to read agents.json:', err.message);
1468
+ return [];
1469
+ }
1470
+ }
1471
+ const STALE_THRESHOLD_MS = 30_000;
1472
+ // Internal agents that should be hidden from `agents` and `who` by default
1473
+ const INTERNAL_AGENTS = new Set(['cli', 'Dashboard']);
1474
+ function isInternalAgent(name) {
1475
+ if (!name)
1476
+ return true;
1477
+ if (name.startsWith('__'))
1478
+ return true;
1479
+ return INTERNAL_AGENTS.has(name);
1480
+ }
1481
+ function getAgentStatus(agent) {
1482
+ if (!agent.lastSeen)
1483
+ return 'UNKNOWN';
1484
+ const ts = Date.parse(agent.lastSeen);
1485
+ if (Number.isNaN(ts))
1486
+ return 'UNKNOWN';
1487
+ return Date.now() - ts < STALE_THRESHOLD_MS ? 'ONLINE' : 'STALE';
1488
+ }
1489
+ function isAgentOnline(agent) {
1490
+ return getAgentStatus(agent) === 'ONLINE';
1491
+ }
1492
+ // Visible agents: not internal and not stale (used by `agents` command)
1493
+ function isVisibleAgent(agent) {
1494
+ if (isInternalAgent(agent.name))
1495
+ return false;
1496
+ if (getAgentStatus(agent) === 'STALE')
1497
+ return false;
1498
+ return true;
1499
+ }
1500
+ function formatRelativeTime(iso) {
1501
+ if (!iso)
1502
+ return 'unknown';
1503
+ const ts = Date.parse(iso);
1504
+ if (Number.isNaN(ts))
1505
+ return 'unknown';
1506
+ const diffMs = Date.now() - ts;
1507
+ const diffSec = Math.floor(diffMs / 1000);
1508
+ if (diffSec < 60)
1509
+ return `${diffSec}s ago`;
1510
+ const diffMin = Math.floor(diffSec / 60);
1511
+ if (diffMin < 60)
1512
+ return `${diffMin}m ago`;
1513
+ const diffHours = Math.floor(diffMin / 60);
1514
+ if (diffHours < 48)
1515
+ return `${diffHours}h ago`;
1516
+ const diffDays = Math.floor(diffHours / 24);
1517
+ return `${diffDays}d ago`;
1518
+ }
1519
+ function parseSince(input) {
1520
+ if (!input)
1521
+ return undefined;
1522
+ const trimmed = String(input).trim();
1523
+ if (!trimmed)
1524
+ return undefined;
1525
+ const durationMatch = trimmed.match(/^(-?\d+)([smhd])$/i);
1526
+ if (durationMatch) {
1527
+ const value = Number(durationMatch[1]);
1528
+ const unit = durationMatch[2].toLowerCase();
1529
+ const multipliers = {
1530
+ s: 1000,
1531
+ m: 60_000,
1532
+ h: 3_600_000,
1533
+ d: 86_400_000,
1534
+ };
1535
+ return Date.now() - value * multipliers[unit];
1536
+ }
1537
+ const parsed = Date.parse(trimmed);
1538
+ if (Number.isNaN(parsed))
1539
+ return undefined;
1540
+ return parsed;
1541
+ }
1542
+ // ============================================
1543
+ // Spawned agent debugging commands
1544
+ // ============================================
1545
+ // agents:logs - Show log file output for a spawned agent
1546
+ program
1547
+ .command('agents:logs')
1548
+ .description('Show recent output from a spawned agent')
1549
+ .argument('<name>', 'Agent name')
1550
+ .option('-n, --lines <n>', 'Number of lines to show', '50')
1551
+ .option('-f, --follow', 'Follow output (like tail -f)')
1552
+ .action(async (name, options) => {
1553
+ const { getProjectPaths } = await import('@agent-relay/config');
1554
+ const paths = getProjectPaths();
1555
+ const logsDir = getWorkerLogsDir(paths.projectRoot);
1556
+ const logFile = path.join(logsDir, `${name}.log`);
1557
+ if (!fs.existsSync(logFile)) {
1558
+ console.error(`No logs found for agent "${name}"`);
1559
+ console.log(`Log file not found: ${logFile}`);
1560
+ console.log(`Run 'agent-relay agents' to see available agents`);
1561
+ process.exit(1);
1562
+ }
1563
+ if (options.follow) {
1564
+ console.log(`Following logs for ${name} (Ctrl+C to stop)...`);
1565
+ console.log('─'.repeat(50));
1566
+ // Use tail -f approach
1567
+ const { spawn } = await import('child_process');
1568
+ const child = spawn('tail', ['-f', logFile], {
1569
+ stdio: ['ignore', 'inherit', 'inherit'],
1570
+ });
1571
+ process.on('SIGINT', () => {
1572
+ child.kill();
1573
+ console.log('\nStopped following');
1574
+ process.exit(0);
1575
+ });
1576
+ child.on('exit', () => {
1577
+ process.exit(0);
1578
+ });
1579
+ }
1580
+ else {
1581
+ try {
1582
+ const lines = parseInt(options.lines || '50', 10);
1583
+ const { stdout } = await execAsync(`tail -n ${lines} "${logFile}"`);
1584
+ console.log(`Logs for ${name} (last ${lines} lines):`);
1585
+ console.log('─'.repeat(50));
1586
+ console.log(stdout || '(empty)');
1587
+ }
1588
+ catch (err) {
1589
+ console.error('Failed to read logs:', err.message);
1590
+ }
1591
+ }
1592
+ });
1593
+ // spawn - Spawn an agent via API (works from any context, no TTY required)
1594
+ // Use this for programmatic spawning from scripts, detached processes, or containers
1595
+ program
1596
+ .command('spawn')
1597
+ .description('Spawn an agent via dashboard API (recommended for programmatic use, no TTY required)')
1598
+ .argument('<name>', 'Agent name')
1599
+ .argument('<cli>', 'CLI to use (claude, codex, gemini, etc.)')
1600
+ .argument('[task]', 'Task description (can also be piped via stdin)')
1601
+ .option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
1602
+ .option('--team <team>', 'Team name for the agent')
1603
+ .option('--spawner <name>', 'Name of the agent requesting the spawn (for policy enforcement)')
1604
+ .option('--interactive', 'Disable auto-accept of permission prompts (for auth setup flows)')
1605
+ .option('--cwd <path>', 'Working directory for the agent')
1606
+ .option('--shadow-mode <mode>', 'Shadow execution mode: subagent or process')
1607
+ .option('--shadow-of <name>', 'Primary agent to shadow (if this agent is a shadow)')
1608
+ .option('--shadow-agent <profile>', 'Shadow agent profile to use')
1609
+ .option('--shadow-triggers <triggers>', 'When to trigger shadow (comma-separated: SESSION_END,CODE_WRITTEN,REVIEW_REQUEST,EXPLICIT_ASK,ALL_MESSAGES)')
1610
+ .option('--shadow-speak-on <triggers>', 'When shadow should speak (comma-separated, same values as --shadow-triggers)')
1611
+ .action(async (name, cli, task, options) => {
1612
+ const port = options.port || DEFAULT_DASHBOARD_PORT;
1613
+ // Read task from stdin if not provided as argument
1614
+ let finalTask = task;
1615
+ if (!finalTask && !process.stdin.isTTY) {
1616
+ const chunks = [];
1617
+ for await (const chunk of process.stdin) {
1618
+ chunks.push(chunk);
1619
+ }
1620
+ finalTask = Buffer.concat(chunks).toString('utf-8').trim();
1621
+ }
1622
+ if (!finalTask) {
1623
+ console.error('Error: Task description required (as argument or via stdin)');
1624
+ process.exit(1);
1625
+ }
1626
+ // Validate shadow mode if provided
1627
+ if (options.shadowMode && !['subagent', 'process'].includes(options.shadowMode)) {
1628
+ console.error('Error: --shadow-mode must be "subagent" or "process"');
1629
+ process.exit(1);
1630
+ }
1631
+ // Parse comma-separated trigger lists
1632
+ const parseTriggers = (value) => {
1633
+ if (!value)
1634
+ return undefined;
1635
+ const validTriggers = ['SESSION_END', 'CODE_WRITTEN', 'REVIEW_REQUEST', 'EXPLICIT_ASK', 'ALL_MESSAGES'];
1636
+ const triggers = value.split(',').map(t => t.trim().toUpperCase());
1637
+ const invalid = triggers.filter(t => !validTriggers.includes(t));
1638
+ if (invalid.length > 0) {
1639
+ console.error(`Error: Invalid triggers: ${invalid.join(', ')}`);
1640
+ console.error(`Valid triggers: ${validTriggers.join(', ')}`);
1641
+ process.exit(1);
1642
+ }
1643
+ return triggers;
1644
+ };
1645
+ // Build spawn request using the SpawnRequest type for consistency
1646
+ const spawnRequest = {
1647
+ name,
1648
+ cli,
1649
+ task: finalTask,
1650
+ team: options.team,
1651
+ spawnerName: options.spawner,
1652
+ interactive: options.interactive,
1653
+ cwd: options.cwd,
1654
+ shadowMode: options.shadowMode,
1655
+ shadowOf: options.shadowOf,
1656
+ shadowAgent: options.shadowAgent,
1657
+ shadowTriggers: parseTriggers(options.shadowTriggers),
1658
+ shadowSpeakOn: parseTriggers(options.shadowSpeakOn),
1659
+ };
1660
+ // Try daemon socket first (preferred path)
1661
+ try {
1662
+ const { getProjectPaths } = await import('@agent-relay/config');
1663
+ const paths = getProjectPaths();
1664
+ // TODO: Re-enable daemon-based spawning when client.spawn() is implemented
1665
+ // See: docs/SDK-MIGRATION-PLAN.md for planned implementation
1666
+ // For now, fall through to HTTP API
1667
+ throw new Error('Daemon-based spawn not yet implemented');
1668
+ }
1669
+ catch (daemonErr) {
1670
+ // Fall through to HTTP API
1671
+ // console.log('Daemon not available, trying HTTP API...');
1672
+ }
1673
+ // Fall back to HTTP API
1674
+ try {
1675
+ const response = await fetch(`http://localhost:${port}/api/spawn`, {
1676
+ method: 'POST',
1677
+ headers: { 'Content-Type': 'application/json' },
1678
+ body: JSON.stringify(spawnRequest),
1679
+ });
1680
+ const result = await response.json();
1681
+ if (result.success) {
1682
+ console.log(`Spawned agent: ${name} (pid: ${result.pid})`);
1683
+ process.exit(0);
1684
+ }
1685
+ else {
1686
+ if (result.policyDecision) {
1687
+ console.error(`Policy denied spawn: ${result.policyDecision.reason}`);
1688
+ console.error(`Policy source: ${result.policyDecision.policySource}`);
1689
+ }
1690
+ else {
1691
+ console.error(`Failed to spawn ${name}: ${result.error || 'Unknown error'}`);
1692
+ }
1693
+ process.exit(1);
1694
+ }
1695
+ }
1696
+ catch (err) {
1697
+ if (err.code === 'ECONNREFUSED') {
1698
+ console.error(`Cannot connect to dashboard at port ${port}. Is the daemon running?`);
1699
+ console.log(`Run 'agent-relay up' to start the daemon.`);
1700
+ }
1701
+ else {
1702
+ console.error(`Failed to spawn ${name}: ${err.message}`);
1703
+ }
1704
+ process.exit(1);
1705
+ }
1706
+ });
1707
+ // release - Release a spawned agent via API (works from any context, no terminal required)
1708
+ program
1709
+ .command('release')
1710
+ .description('Release a spawned agent via API (no terminal required)')
1711
+ .argument('<name>', 'Agent name to release')
1712
+ .option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
1713
+ .action(async (name, options) => {
1714
+ const port = options.port || DEFAULT_DASHBOARD_PORT;
1715
+ // Try daemon socket first (preferred path)
1716
+ try {
1717
+ const { getProjectPaths } = await import('@agent-relay/config');
1718
+ const paths = getProjectPaths();
1719
+ const client = new RelayClient({
1720
+ socketPath: paths.socketPath,
1721
+ agentName: '__cli_releaser__',
1722
+ quiet: true,
1723
+ reconnect: false,
1724
+ maxReconnectAttempts: 0,
1725
+ reconnectDelayMs: 0,
1726
+ reconnectMaxDelayMs: 0,
1727
+ });
1728
+ // TODO: Re-enable daemon-based release when client.release() is implemented
1729
+ // See: docs/SDK-MIGRATION-PLAN.md for planned implementation
1730
+ // For now, fall through to HTTP API
1731
+ throw new Error('Daemon-based release not yet implemented');
1732
+ }
1733
+ catch (daemonErr) {
1734
+ // Fall through to HTTP API
1735
+ // console.log('Daemon not available, trying HTTP API...');
1736
+ }
1737
+ // Fall back to HTTP API
1738
+ try {
1739
+ const response = await fetch(`http://localhost:${port}/api/spawned/${encodeURIComponent(name)}`, {
1740
+ method: 'DELETE',
1741
+ });
1742
+ const result = await response.json();
1743
+ if (result.success) {
1744
+ console.log(`Released agent: ${name}`);
1745
+ process.exit(0);
1746
+ }
1747
+ else {
1748
+ console.error(`Failed to release ${name}: ${result.error || 'Unknown error'}`);
1749
+ process.exit(1);
1750
+ }
1751
+ }
1752
+ catch (err) {
1753
+ // If API call fails, try to provide helpful error message
1754
+ if (err.code === 'ECONNREFUSED') {
1755
+ console.error(`Cannot connect to dashboard at port ${port}. Is the daemon running?`);
1756
+ console.log(`Run 'agent-relay up' to start the daemon.`);
1757
+ }
1758
+ else {
1759
+ console.error(`Failed to release ${name}: ${err.message}`);
1760
+ }
1761
+ process.exit(1);
1762
+ }
1763
+ });
1764
+ // agents:kill - Kill a spawned agent by PID
1765
+ program
1766
+ .command('agents:kill')
1767
+ .description('Kill a spawned agent')
1768
+ .argument('<name>', 'Agent name')
1769
+ .option('--force', 'Skip graceful shutdown, kill immediately')
1770
+ .action(async (name, options) => {
1771
+ const { getProjectPaths } = await import('@agent-relay/config');
1772
+ const paths = getProjectPaths();
1773
+ const workers = readWorkersMetadata(paths.projectRoot);
1774
+ const worker = workers.find(w => w.name === name);
1775
+ if (!worker) {
1776
+ console.error(`Spawned agent "${name}" not found`);
1777
+ console.log(`Run 'agent-relay agents' to see available agents`);
1778
+ process.exit(1);
1779
+ }
1780
+ if (!worker.pid) {
1781
+ console.error(`Agent "${name}" has no PID recorded`);
1782
+ process.exit(1);
1783
+ }
1784
+ try {
1785
+ if (!options.force) {
1786
+ // Try graceful shutdown first (SIGTERM)
1787
+ console.log(`Sending SIGTERM to ${name} (pid: ${worker.pid})...`);
1788
+ process.kill(worker.pid, 'SIGTERM');
1789
+ // Wait for graceful shutdown
1790
+ await new Promise(r => setTimeout(r, 2000));
1791
+ // Check if still running
1792
+ try {
1793
+ process.kill(worker.pid, 0); // Check if process exists
1794
+ console.log(`Agent still running, sending SIGKILL...`);
1795
+ process.kill(worker.pid, 'SIGKILL');
1796
+ }
1797
+ catch {
1798
+ // Process no longer exists, graceful shutdown worked
1799
+ }
1800
+ }
1801
+ else {
1802
+ // Force kill immediately
1803
+ console.log(`Force killing ${name} (pid: ${worker.pid})...`);
1804
+ process.kill(worker.pid, 'SIGKILL');
1805
+ }
1806
+ console.log(`Killed agent: ${name}`);
1807
+ }
1808
+ catch (err) {
1809
+ if (err.code === 'ESRCH') {
1810
+ console.log(`Agent ${name} is no longer running (pid: ${worker.pid})`);
1811
+ }
1812
+ else {
1813
+ console.error(`Failed to kill ${name}:`, err.message);
1814
+ process.exit(1);
1815
+ }
1816
+ }
1817
+ });
1818
+ // ============================================================================
1819
+ // Cloud commands
1820
+ // ============================================================================
1821
+ const cloudCommand = program
1822
+ .command('cloud')
1823
+ .description('Cloud account and sync commands');
1824
+ cloudCommand
1825
+ .command('link')
1826
+ .description('Link this machine to your Agent Relay Cloud account')
1827
+ .option('--name <name>', 'Name for this machine')
1828
+ .option('--cloud-url <url>', 'Cloud API URL', process.env.AGENT_RELAY_CLOUD_URL || 'https://agent-relay.com')
1829
+ .action(async (options) => {
1830
+ const os = await import('node:os');
1831
+ const crypto = await import('node:crypto');
1832
+ const readline = await import('node:readline');
1833
+ const cloudUrl = options.cloudUrl;
1834
+ const machineName = options.name || os.hostname();
1835
+ // Generate machine ID
1836
+ const dataDir = process.env.AGENT_RELAY_DATA_DIR ||
1837
+ path.join(os.homedir(), '.local', 'share', 'agent-relay');
1838
+ const machineIdPath = path.join(dataDir, 'machine-id');
1839
+ const configPath = path.join(dataDir, 'cloud-config.json');
1840
+ let machineId;
1841
+ if (fs.existsSync(machineIdPath)) {
1842
+ machineId = fs.readFileSync(machineIdPath, 'utf-8').trim();
1843
+ }
1844
+ else {
1845
+ machineId = `${os.hostname()}-${crypto.randomBytes(8).toString('hex')}`;
1846
+ fs.mkdirSync(dataDir, { recursive: true });
1847
+ fs.writeFileSync(machineIdPath, machineId);
1848
+ }
1849
+ console.log('');
1850
+ console.log('🔗 Agent Relay Cloud - Link Machine');
1851
+ console.log('');
1852
+ console.log(`Machine: ${machineName}`);
1853
+ console.log(`ID: ${machineId}`);
1854
+ console.log('');
1855
+ // Generate a temporary code for the browser auth flow
1856
+ const tempCode = crypto.randomBytes(16).toString('hex');
1857
+ // Store temp code for callback
1858
+ const tempCodePath = path.join(dataDir, '.link-code');
1859
+ fs.writeFileSync(tempCodePath, tempCode);
1860
+ const authUrl = `${cloudUrl.replace('/api', '')}/cloud/link?code=${tempCode}&machine=${encodeURIComponent(machineId)}&name=${encodeURIComponent(machineName)}`;
1861
+ console.log('Open this URL in your browser to authenticate:');
1862
+ console.log('');
1863
+ console.log(` ${authUrl}`);
1864
+ console.log('');
1865
+ // Try to open browser automatically
1866
+ try {
1867
+ const openCommand = process.platform === 'darwin' ? 'open' :
1868
+ process.platform === 'win32' ? 'start' : 'xdg-open';
1869
+ await execAsync(`${openCommand} "${authUrl}"`);
1870
+ console.log('(Browser opened automatically)');
1871
+ }
1872
+ catch {
1873
+ console.log('(Copy the URL above and paste it in your browser)');
1874
+ }
1875
+ console.log('');
1876
+ console.log('After authenticating, paste your API key here:');
1877
+ const rl = readline.createInterface({
1878
+ input: process.stdin,
1879
+ output: process.stdout,
1880
+ });
1881
+ const apiKey = await new Promise((resolve) => {
1882
+ rl.question('API Key: ', (answer) => {
1883
+ rl.close();
1884
+ resolve(answer.trim());
1885
+ });
1886
+ });
1887
+ if (!apiKey || !apiKey.startsWith('ar_live_')) {
1888
+ console.error('');
1889
+ console.error('Invalid API key format. Expected ar_live_...');
1890
+ process.exit(1);
1891
+ }
1892
+ // Verify the API key works
1893
+ console.log('');
1894
+ console.log('Verifying API key...');
1895
+ try {
1896
+ const response = await fetch(`${cloudUrl}/api/daemons/heartbeat`, {
1897
+ method: 'POST',
1898
+ headers: {
1899
+ 'Authorization': `Bearer ${apiKey}`,
1900
+ 'Content-Type': 'application/json',
1901
+ },
1902
+ body: JSON.stringify({
1903
+ agents: [],
1904
+ metrics: { linkedAt: new Date().toISOString() },
1905
+ }),
1906
+ });
1907
+ if (!response.ok) {
1908
+ const error = await response.text();
1909
+ console.error(`Failed to verify API key: ${error}`);
1910
+ process.exit(1);
1911
+ }
1912
+ // Save config
1913
+ const config = {
1914
+ apiKey,
1915
+ cloudUrl,
1916
+ machineId,
1917
+ machineName,
1918
+ linkedAt: new Date().toISOString(),
1919
+ };
1920
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
1921
+ fs.chmodSync(configPath, 0o600); // Secure the file
1922
+ // Clean up temp code
1923
+ if (fs.existsSync(tempCodePath)) {
1924
+ fs.unlinkSync(tempCodePath);
1925
+ }
1926
+ console.log('');
1927
+ console.log('✓ Machine linked successfully!');
1928
+ console.log('');
1929
+ console.log('Your daemon will now sync with Agent Relay Cloud.');
1930
+ console.log('Run `agent-relay up` to start with cloud sync enabled.');
1931
+ console.log('');
1932
+ }
1933
+ catch (err) {
1934
+ console.error(`Failed to connect to cloud: ${err.message}`);
1935
+ process.exit(1);
1936
+ }
1937
+ });
1938
+ cloudCommand
1939
+ .command('unlink')
1940
+ .description('Unlink this machine from Agent Relay Cloud')
1941
+ .action(async () => {
1942
+ const os = await import('node:os');
1943
+ const dataDir = process.env.AGENT_RELAY_DATA_DIR ||
1944
+ path.join(os.homedir(), '.local', 'share', 'agent-relay');
1945
+ const configPath = path.join(dataDir, 'cloud-config.json');
1946
+ if (!fs.existsSync(configPath)) {
1947
+ console.log('This machine is not linked to Agent Relay Cloud.');
1948
+ return;
1949
+ }
1950
+ // Read current config
1951
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
1952
+ // Delete config file
1953
+ fs.unlinkSync(configPath);
1954
+ console.log('');
1955
+ console.log('✓ Machine unlinked from Agent Relay Cloud');
1956
+ console.log('');
1957
+ console.log(`Machine ID: ${config.machineId}`);
1958
+ console.log(`Was linked since: ${config.linkedAt}`);
1959
+ console.log('');
1960
+ console.log('Note: The API key has been removed locally. To fully revoke access,');
1961
+ console.log('visit your Agent Relay Cloud dashboard and remove this machine.');
1962
+ console.log('');
1963
+ });
1964
+ cloudCommand
1965
+ .command('status')
1966
+ .description('Show cloud sync status')
1967
+ .action(async () => {
1968
+ const os = await import('node:os');
1969
+ const dataDir = process.env.AGENT_RELAY_DATA_DIR ||
1970
+ path.join(os.homedir(), '.local', 'share', 'agent-relay');
1971
+ const configPath = path.join(dataDir, 'cloud-config.json');
1972
+ if (!fs.existsSync(configPath)) {
1973
+ console.log('');
1974
+ console.log('Cloud sync: Not configured');
1975
+ console.log('');
1976
+ console.log('Run `agent-relay cloud link` to connect to Agent Relay Cloud.');
1977
+ console.log('');
1978
+ return;
1979
+ }
1980
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
1981
+ console.log('');
1982
+ console.log('Cloud sync: Enabled');
1983
+ console.log('');
1984
+ console.log(` Machine: ${config.machineName}`);
1985
+ console.log(` ID: ${config.machineId}`);
1986
+ console.log(` Cloud URL: ${config.cloudUrl}`);
1987
+ console.log(` Linked: ${new Date(config.linkedAt).toLocaleString()}`);
1988
+ console.log('');
1989
+ // Check if daemon is running and connected
1990
+ const { getProjectPaths } = await import('@agent-relay/config');
1991
+ const paths = getProjectPaths();
1992
+ if (fs.existsSync(paths.socketPath)) {
1993
+ console.log(' Daemon: Running');
1994
+ // Try to get cloud sync status from daemon
1995
+ try {
1996
+ const response = await fetch(`${config.cloudUrl}/api/daemons/heartbeat`, {
1997
+ method: 'POST',
1998
+ headers: {
1999
+ 'Authorization': `Bearer ${config.apiKey}`,
2000
+ 'Content-Type': 'application/json',
2001
+ },
2002
+ body: JSON.stringify({ agents: [], metrics: {} }),
2003
+ });
2004
+ if (response.ok) {
2005
+ console.log(' Cloud connection: Online');
2006
+ }
2007
+ else {
2008
+ console.log(' Cloud connection: Error (API key may be invalid)');
2009
+ }
2010
+ }
2011
+ catch (err) {
2012
+ console.log(` Cloud connection: Offline (${err.message})`);
2013
+ }
2014
+ }
2015
+ else {
2016
+ console.log(' Daemon: Not running');
2017
+ console.log(' Cloud connection: Offline (daemon not started)');
2018
+ }
2019
+ console.log('');
2020
+ });
2021
+ cloudCommand
2022
+ .command('sync')
2023
+ .description('Manually sync credentials from cloud')
2024
+ .action(async () => {
2025
+ const os = await import('node:os');
2026
+ const dataDir = process.env.AGENT_RELAY_DATA_DIR ||
2027
+ path.join(os.homedir(), '.local', 'share', 'agent-relay');
2028
+ const configPath = path.join(dataDir, 'cloud-config.json');
2029
+ if (!fs.existsSync(configPath)) {
2030
+ console.error('Not linked to cloud. Run `agent-relay cloud link` first.');
2031
+ process.exit(1);
2032
+ }
2033
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
2034
+ console.log('Syncing credentials from cloud...');
2035
+ try {
2036
+ const response = await fetch(`${config.cloudUrl}/api/daemons/credentials`, {
2037
+ headers: {
2038
+ 'Authorization': `Bearer ${config.apiKey}`,
2039
+ },
2040
+ });
2041
+ if (!response.ok) {
2042
+ const error = await response.text();
2043
+ console.error(`Failed to sync: ${error}`);
2044
+ process.exit(1);
2045
+ }
2046
+ const data = await response.json();
2047
+ console.log('');
2048
+ console.log(`Synced ${data.credentials.length} provider credentials:`);
2049
+ for (const cred of data.credentials) {
2050
+ console.log(` - ${cred.provider}`);
2051
+ }
2052
+ // Save credentials locally for daemon to use
2053
+ const credentialsPath = path.join(dataDir, 'cloud-credentials.json');
2054
+ fs.writeFileSync(credentialsPath, JSON.stringify(data.credentials, null, 2));
2055
+ fs.chmodSync(credentialsPath, 0o600);
2056
+ console.log('');
2057
+ console.log('✓ Credentials synced successfully');
2058
+ console.log('');
2059
+ }
2060
+ catch (err) {
2061
+ console.error(`Failed to sync: ${err.message}`);
2062
+ process.exit(1);
2063
+ }
2064
+ });
2065
+ // ============================================================================
2066
+ // TRAJECTORY COMMANDS (trail proxy)
2067
+ // ============================================================================
2068
+ // trail - Proxy to trail CLI for trajectory tracking
2069
+ program
2070
+ .command('trail')
2071
+ .description('Trajectory tracking commands (proxies to trail CLI)')
2072
+ .argument('[args...]', 'Arguments to pass to trail CLI')
2073
+ .allowUnknownOption()
2074
+ .action(async (args) => {
2075
+ const { spawn } = await import('node:child_process');
2076
+ const { getProjectPaths } = await import('@agent-relay/config');
2077
+ const { getPrimaryTrajectoriesDir, ensureTrajectoriesDir } = await import('@agent-relay/config/trajectory-config');
2078
+ const paths = getProjectPaths();
2079
+ // Check if trail is available
2080
+ const trailCheck = spawn('which', ['trail'], { stdio: 'pipe' });
2081
+ const trailExists = await new Promise((resolve) => {
2082
+ trailCheck.on('close', (code) => resolve(code === 0));
2083
+ trailCheck.on('error', () => resolve(false));
2084
+ });
2085
+ if (!trailExists) {
2086
+ console.error('trail CLI not found. Install with: npm install -g agent-trajectories');
2087
+ console.log('');
2088
+ console.log('The trail CLI provides trajectory tracking for agent work:');
2089
+ console.log(' trail start "<task>" Start tracking a new trajectory');
2090
+ console.log(' trail status Show current trajectory status');
2091
+ console.log(' trail phase <phase> Transition to PDERO phase');
2092
+ console.log(' trail decision "<choice>" Record a decision');
2093
+ console.log(' trail complete Complete the trajectory');
2094
+ console.log(' trail list List all trajectories');
2095
+ console.log('');
2096
+ console.log('PDERO phases: plan, design, execute, review, observe');
2097
+ process.exit(1);
2098
+ }
2099
+ // Get trajectory storage path based on config (respects opt-in/opt-out)
2100
+ // Uses TRAJECTORIES_DATA_DIR env var which trail CLI reads
2101
+ const trajectoriesDir = getPrimaryTrajectoriesDir(paths.projectRoot);
2102
+ ensureTrajectoriesDir(paths.projectRoot);
2103
+ // Spawn trail with the provided arguments
2104
+ const trailProc = spawn('trail', args, {
2105
+ cwd: paths.projectRoot,
2106
+ stdio: 'inherit',
2107
+ env: {
2108
+ ...process.env,
2109
+ // Trajectory env vars override parent shell settings
2110
+ // This ensures config-based TRAJECTORIES_DATA_DIR takes precedence
2111
+ TRAJECTORIES_PROJECT: paths.projectId,
2112
+ TRAJECTORIES_DATA_DIR: trajectoriesDir,
2113
+ },
2114
+ });
2115
+ trailProc.on('close', (code) => {
2116
+ process.exit(code ?? 0);
2117
+ });
2118
+ trailProc.on('error', (err) => {
2119
+ console.error(`Failed to run trail: ${err.message}`);
2120
+ process.exit(1);
2121
+ });
2122
+ });
2123
+ cloudCommand
2124
+ .command('agents')
2125
+ .description('List agents across all linked machines')
2126
+ .option('--json', 'Output as JSON')
2127
+ .action(async (options) => {
2128
+ const os = await import('node:os');
2129
+ const dataDir = process.env.AGENT_RELAY_DATA_DIR ||
2130
+ path.join(os.homedir(), '.local', 'share', 'agent-relay');
2131
+ const configPath = path.join(dataDir, 'cloud-config.json');
2132
+ if (!fs.existsSync(configPath)) {
2133
+ console.error('Not linked to cloud. Run `agent-relay cloud link` first.');
2134
+ process.exit(1);
2135
+ }
2136
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
2137
+ try {
2138
+ // Get agents from cloud
2139
+ const response = await fetch(`${config.cloudUrl}/api/daemons/agents`, {
2140
+ method: 'POST',
2141
+ headers: {
2142
+ 'Authorization': `Bearer ${config.apiKey}`,
2143
+ 'Content-Type': 'application/json',
2144
+ },
2145
+ body: JSON.stringify({ agents: [] }), // Report no agents, just fetch list
2146
+ });
2147
+ if (!response.ok) {
2148
+ const error = await response.text();
2149
+ console.error(`Failed to fetch agents: ${error}`);
2150
+ process.exit(1);
2151
+ }
2152
+ const data = await response.json();
2153
+ if (options.json) {
2154
+ console.log(JSON.stringify(data.allAgents, null, 2));
2155
+ return;
2156
+ }
2157
+ if (!data.allAgents.length) {
2158
+ console.log('No agents found across linked machines.');
2159
+ console.log('Make sure daemons are running on linked machines.');
2160
+ return;
2161
+ }
2162
+ console.log('');
2163
+ console.log('Agents across all linked machines:');
2164
+ console.log('');
2165
+ console.log('NAME STATUS DAEMON MACHINE');
2166
+ console.log('─'.repeat(65));
2167
+ // Group by daemon
2168
+ const byDaemon = new Map();
2169
+ for (const agent of data.allAgents) {
2170
+ const existing = byDaemon.get(agent.daemonName) || [];
2171
+ existing.push(agent);
2172
+ byDaemon.set(agent.daemonName, existing);
2173
+ }
2174
+ for (const [daemonName, agents] of byDaemon.entries()) {
2175
+ for (const agent of agents) {
2176
+ const name = agent.name.padEnd(15);
2177
+ const status = agent.status.padEnd(8);
2178
+ const daemon = daemonName.padEnd(18);
2179
+ const machine = agent.machineId.substring(0, 20);
2180
+ console.log(`${name} ${status} ${daemon} ${machine}`);
2181
+ }
2182
+ }
2183
+ console.log('');
2184
+ console.log(`Total: ${data.allAgents.length} agents on ${byDaemon.size} machines`);
2185
+ console.log('');
2186
+ }
2187
+ catch (err) {
2188
+ console.error(`Failed to fetch agents: ${err.message}`);
2189
+ process.exit(1);
2190
+ }
2191
+ });
2192
+ cloudCommand
2193
+ .command('send')
2194
+ .description('Send a message to an agent on any linked machine')
2195
+ .argument('<agent>', 'Target agent name')
2196
+ .argument('<message>', 'Message to send')
2197
+ .option('--from <name>', 'Sender name', 'cli')
2198
+ .action(async (agent, message, options) => {
2199
+ const os = await import('node:os');
2200
+ const dataDir = process.env.AGENT_RELAY_DATA_DIR ||
2201
+ path.join(os.homedir(), '.local', 'share', 'agent-relay');
2202
+ const configPath = path.join(dataDir, 'cloud-config.json');
2203
+ if (!fs.existsSync(configPath)) {
2204
+ console.error('Not linked to cloud. Run `agent-relay cloud link` first.');
2205
+ process.exit(1);
2206
+ }
2207
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
2208
+ console.log(`Sending message to ${agent}...`);
2209
+ try {
2210
+ // First, find which daemon the agent is on
2211
+ const agentsResponse = await fetch(`${config.cloudUrl}/api/daemons/agents`, {
2212
+ method: 'POST',
2213
+ headers: {
2214
+ 'Authorization': `Bearer ${config.apiKey}`,
2215
+ 'Content-Type': 'application/json',
2216
+ },
2217
+ body: JSON.stringify({ agents: [] }),
2218
+ });
2219
+ if (!agentsResponse.ok) {
2220
+ const error = await agentsResponse.text();
2221
+ console.error(`Failed to find agent: ${error}`);
2222
+ process.exit(1);
2223
+ }
2224
+ const agentsData = await agentsResponse.json();
2225
+ const targetAgent = agentsData.allAgents.find(a => a.name === agent);
2226
+ if (!targetAgent) {
2227
+ console.error(`Agent "${agent}" not found.`);
2228
+ console.log('Available agents:');
2229
+ for (const a of agentsData.allAgents) {
2230
+ console.log(` - ${a.name} (on ${a.daemonName})`);
2231
+ }
2232
+ process.exit(1);
2233
+ }
2234
+ // Send the message
2235
+ const sendResponse = await fetch(`${config.cloudUrl}/api/daemons/message`, {
2236
+ method: 'POST',
2237
+ headers: {
2238
+ 'Authorization': `Bearer ${config.apiKey}`,
2239
+ 'Content-Type': 'application/json',
2240
+ },
2241
+ body: JSON.stringify({
2242
+ targetDaemonId: targetAgent.daemonId,
2243
+ targetAgent: agent,
2244
+ message: {
2245
+ from: options.from,
2246
+ content: message,
2247
+ },
2248
+ }),
2249
+ });
2250
+ if (!sendResponse.ok) {
2251
+ const error = await sendResponse.text();
2252
+ console.error(`Failed to send message: ${error}`);
2253
+ process.exit(1);
2254
+ }
2255
+ console.log('');
2256
+ console.log(`✓ Message sent to ${agent} on ${targetAgent.daemonName}`);
2257
+ console.log('');
2258
+ }
2259
+ catch (err) {
2260
+ console.error(`Failed to send message: ${err.message}`);
2261
+ process.exit(1);
2262
+ }
2263
+ });
2264
+ cloudCommand
2265
+ .command('daemons')
2266
+ .description('List all linked daemon instances')
2267
+ .option('--json', 'Output as JSON')
2268
+ .action(async (_options) => {
2269
+ const os = await import('node:os');
2270
+ const dataDir = process.env.AGENT_RELAY_DATA_DIR ||
2271
+ path.join(os.homedir(), '.local', 'share', 'agent-relay');
2272
+ const configPath = path.join(dataDir, 'cloud-config.json');
2273
+ if (!fs.existsSync(configPath)) {
2274
+ console.error('Not linked to cloud. Run `agent-relay cloud link` first.');
2275
+ process.exit(1);
2276
+ }
2277
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
2278
+ try {
2279
+ // Get daemons list (requires browser auth, so we use a workaround)
2280
+ // For now, just show what we know about our own daemon
2281
+ console.log('');
2282
+ console.log('Linked Daemon:');
2283
+ console.log('');
2284
+ console.log(` Machine: ${config.machineName}`);
2285
+ console.log(` ID: ${config.machineId}`);
2286
+ console.log(` Cloud: ${config.cloudUrl}`);
2287
+ console.log(` Linked: ${new Date(config.linkedAt).toLocaleString()}`);
2288
+ console.log('');
2289
+ console.log('Note: To see all linked daemons, visit your cloud dashboard.');
2290
+ console.log('');
2291
+ }
2292
+ catch (err) {
2293
+ console.error(`Failed: ${err.message}`);
2294
+ process.exit(1);
2295
+ }
2296
+ });
2297
+ // ============================================================================
2298
+ // Monitoring commands (metrics, health, profiler)
2299
+ // ============================================================================
2300
+ // metrics - Show agent memory metrics
2301
+ program
2302
+ .command('metrics')
2303
+ .description('Show agent memory metrics and resource usage')
2304
+ .option('--agent <name>', 'Show metrics for specific agent')
2305
+ .option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
2306
+ .option('--json', 'Output as JSON')
2307
+ .option('--watch', 'Continuously update metrics')
2308
+ .option('--interval <ms>', 'Update interval for watch mode', '5000')
2309
+ .action(async (options) => {
2310
+ const port = options.port || DEFAULT_DASHBOARD_PORT;
2311
+ const fetchMetrics = async () => {
2312
+ try {
2313
+ const response = await fetch(`http://localhost:${port}/api/metrics/agents`);
2314
+ if (!response.ok) {
2315
+ throw new Error(`HTTP ${response.status}`);
2316
+ }
2317
+ return await response.json();
2318
+ }
2319
+ catch (err) {
2320
+ if (err.code === 'ECONNREFUSED') {
2321
+ console.error(`Cannot connect to dashboard at port ${port}. Is the daemon running?`);
2322
+ console.log(`Run 'agent-relay up' to start the daemon.`);
2323
+ }
2324
+ else {
2325
+ console.error(`Failed to fetch metrics: ${err.message}`);
2326
+ }
2327
+ process.exit(1);
2328
+ }
2329
+ };
2330
+ const formatBytes = (bytes) => {
2331
+ if (bytes === 0)
2332
+ return '0 B';
2333
+ const k = 1024;
2334
+ const sizes = ['B', 'KB', 'MB', 'GB'];
2335
+ const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k));
2336
+ return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
2337
+ };
2338
+ const formatUptime = (ms) => {
2339
+ if (ms < 60000)
2340
+ return `${Math.floor(ms / 1000)}s`;
2341
+ if (ms < 3600000)
2342
+ return `${Math.floor(ms / 60000)}m`;
2343
+ return `${Math.floor(ms / 3600000)}h ${Math.floor((ms % 3600000) / 60000)}m`;
2344
+ };
2345
+ const displayMetrics = (data) => {
2346
+ let agents = data.agents;
2347
+ if (options.agent) {
2348
+ agents = agents.filter(a => a.name === options.agent);
2349
+ if (agents.length === 0) {
2350
+ console.error(`Agent "${options.agent}" not found`);
2351
+ return;
2352
+ }
2353
+ }
2354
+ if (options.json) {
2355
+ console.log(JSON.stringify({ agents, system: data.system }, null, 2));
2356
+ return;
2357
+ }
2358
+ if (options.watch) {
2359
+ // Clear screen for watch mode
2360
+ console.clear();
2361
+ console.log(`Agent Metrics (updating every ${options.interval}ms) [Ctrl+C to stop]`);
2362
+ console.log(`System: ${formatBytes(data.system.heapUsed)} heap / ${formatBytes(data.system.freeMemory)} free`);
2363
+ console.log('');
2364
+ }
2365
+ if (agents.length === 0) {
2366
+ console.log('No agents with memory metrics.');
2367
+ console.log('Ensure agents are running and memory monitoring is enabled.');
2368
+ return;
2369
+ }
2370
+ console.log('AGENT PID MEMORY CPU TREND ALERT UPTIME');
2371
+ console.log('─'.repeat(75));
2372
+ for (const agent of agents) {
2373
+ const name = agent.name.padEnd(15);
2374
+ const pid = (agent.pid?.toString() || '-').padEnd(8);
2375
+ const memory = formatBytes(agent.rssBytes || 0).padEnd(11);
2376
+ const cpu = ((agent.cpuPercent?.toFixed(1) || '0') + '%').padEnd(6);
2377
+ const trend = (agent.trend || 'unknown').padEnd(11);
2378
+ const alertColors = {
2379
+ normal: 'normal',
2380
+ warning: '\x1b[33mwarning\x1b[0m',
2381
+ critical: '\x1b[31mcritical\x1b[0m',
2382
+ oom_imminent: '\x1b[31;1mOOM!\x1b[0m',
2383
+ };
2384
+ const alert = (alertColors[agent.alertLevel || 'normal'] || agent.alertLevel || '-').padEnd(9);
2385
+ const uptime = formatUptime(agent.uptimeMs || 0);
2386
+ console.log(`${name} ${pid} ${memory} ${cpu} ${trend} ${alert} ${uptime}`);
2387
+ }
2388
+ if (!options.watch) {
2389
+ console.log('');
2390
+ console.log(`Total: ${agents.length} agent(s)`);
2391
+ if (agents.some(a => a.alertLevel && a.alertLevel !== 'normal')) {
2392
+ console.log('');
2393
+ console.log('⚠️ Some agents have elevated memory usage. Run `agent-relay health` for details.');
2394
+ }
2395
+ }
2396
+ };
2397
+ if (options.watch) {
2398
+ const interval = parseInt(options.interval || '5000', 10);
2399
+ const update = async () => {
2400
+ try {
2401
+ const data = await fetchMetrics();
2402
+ displayMetrics(data);
2403
+ }
2404
+ catch {
2405
+ // Error already logged in fetchMetrics
2406
+ }
2407
+ };
2408
+ process.on('SIGINT', () => {
2409
+ console.log('\nStopped watching metrics.');
2410
+ process.exit(0);
2411
+ });
2412
+ await update();
2413
+ setInterval(update, interval);
2414
+ }
2415
+ else {
2416
+ const data = await fetchMetrics();
2417
+ displayMetrics(data);
2418
+ }
2419
+ });
2420
+ // health - Show crash insights and system health
2421
+ program
2422
+ .command('health')
2423
+ .description('Show system health, crash insights, and recommendations')
2424
+ .option('--port <port>', 'Dashboard port', DEFAULT_DASHBOARD_PORT)
2425
+ .option('--json', 'Output as JSON')
2426
+ .option('--crashes', 'Show recent crash history')
2427
+ .option('--alerts', 'Show unacknowledged alerts')
2428
+ .action(async (options) => {
2429
+ const port = options.port || DEFAULT_DASHBOARD_PORT;
2430
+ try {
2431
+ const response = await fetch(`http://localhost:${port}/api/metrics/health`);
2432
+ if (!response.ok) {
2433
+ throw new Error(`HTTP ${response.status}`);
2434
+ }
2435
+ const data = await response.json();
2436
+ if (options.json) {
2437
+ console.log(JSON.stringify(data, null, 2));
2438
+ return;
2439
+ }
2440
+ // Health score with color
2441
+ const scoreColor = data.healthScore >= 80 ? '\x1b[32m' : // Green
2442
+ data.healthScore >= 50 ? '\x1b[33m' : // Yellow
2443
+ '\x1b[31m'; // Red
2444
+ const resetColor = '\x1b[0m';
2445
+ console.log('');
2446
+ console.log('═══════════════════════════════════════════════════════════════');
2447
+ console.log(` SYSTEM HEALTH: ${scoreColor}${data.healthScore}/100${resetColor}`);
2448
+ console.log('═══════════════════════════════════════════════════════════════');
2449
+ console.log('');
2450
+ console.log(` ${data.summary}`);
2451
+ console.log('');
2452
+ // Show stats
2453
+ console.log(` Agents: ${data.stats.agentCount}`);
2454
+ console.log(` Crashes (24h): ${data.stats.totalCrashes24h}`);
2455
+ console.log(` Alerts (24h): ${data.stats.totalAlerts24h}`);
2456
+ console.log('');
2457
+ // Show issues
2458
+ if (data.issues.length > 0) {
2459
+ console.log(' ISSUES:');
2460
+ for (const issue of data.issues) {
2461
+ const icon = issue.severity === 'critical' ? '🔴' :
2462
+ issue.severity === 'high' ? '🟠' :
2463
+ issue.severity === 'medium' ? '🟡' : '🔵';
2464
+ console.log(` ${icon} ${issue.message}`);
2465
+ }
2466
+ console.log('');
2467
+ }
2468
+ // Show recommendations
2469
+ if (data.recommendations.length > 0) {
2470
+ console.log(' RECOMMENDATIONS:');
2471
+ for (const rec of data.recommendations) {
2472
+ console.log(` → ${rec}`);
2473
+ }
2474
+ console.log('');
2475
+ }
2476
+ // Show crashes if requested
2477
+ if (options.crashes && data.crashes.length > 0) {
2478
+ console.log(' RECENT CRASHES:');
2479
+ console.log(' ─────────────────────────────────────────────────────────────');
2480
+ for (const crash of data.crashes.slice(0, 10)) {
2481
+ const time = new Date(crash.crashedAt).toLocaleString();
2482
+ console.log(` ${crash.agentName} - ${time}`);
2483
+ console.log(` Cause: ${crash.likelyCause} | ${crash.summary.slice(0, 60)}...`);
2484
+ }
2485
+ console.log('');
2486
+ }
2487
+ // Show alerts if requested
2488
+ if (options.alerts && data.alerts.length > 0) {
2489
+ console.log(' UNACKNOWLEDGED ALERTS:');
2490
+ console.log(' ─────────────────────────────────────────────────────────────');
2491
+ for (const alert of data.alerts.slice(0, 10)) {
2492
+ const _time = new Date(alert.createdAt).toLocaleString();
2493
+ const icon = alert.alertType === 'oom_imminent' ? '🔴' :
2494
+ alert.alertType === 'critical' ? '🟠' : '🟡';
2495
+ console.log(` ${icon} ${alert.agentName} - ${alert.alertType}`);
2496
+ console.log(` ${alert.message}`);
2497
+ }
2498
+ console.log('');
2499
+ }
2500
+ console.log('═══════════════════════════════════════════════════════════════');
2501
+ console.log('');
2502
+ if (!options.crashes && data.stats.totalCrashes24h > 0) {
2503
+ console.log(' Tip: Run `agent-relay health --crashes` to see crash details');
2504
+ }
2505
+ if (!options.alerts && data.stats.totalAlerts24h > 0) {
2506
+ console.log(' Tip: Run `agent-relay health --alerts` to see alerts');
2507
+ }
2508
+ console.log('');
2509
+ }
2510
+ catch (err) {
2511
+ if (err.code === 'ECONNREFUSED') {
2512
+ console.error(`Cannot connect to dashboard at port ${port}. Is the daemon running?`);
2513
+ console.log(`Run 'agent-relay up' to start the daemon.`);
2514
+ }
2515
+ else {
2516
+ console.error(`Failed to fetch health data: ${err.message}`);
2517
+ }
2518
+ process.exit(1);
2519
+ }
2520
+ });
2521
+ // profile - Run agent with profiling enabled
2522
+ program
2523
+ .command('profile')
2524
+ .description('Run an agent with memory profiling enabled')
2525
+ .argument('<command...>', 'Command to profile')
2526
+ .option('-n, --name <name>', 'Agent name')
2527
+ .option('--heap-snapshot-interval <ms>', 'Take heap snapshots at interval (ms)', '60000')
2528
+ .option('--output-dir <dir>', 'Directory for profile output', './profiles')
2529
+ .option('--expose-gc', 'Expose garbage collector for manual GC')
2530
+ .action(async (commandParts, options) => {
2531
+ const { getProjectPaths } = await import('@agent-relay/config');
2532
+ if (!commandParts || commandParts.length === 0) {
2533
+ console.error('No command specified');
2534
+ process.exit(1);
2535
+ }
2536
+ const [cmd, ...args] = commandParts;
2537
+ const agentName = options.name ?? generateAgentName();
2538
+ const outputDir = options.outputDir || './profiles';
2539
+ const snapshotInterval = parseInt(options.heapSnapshotInterval || '60000', 10);
2540
+ // Create output directory
2541
+ if (!fs.existsSync(outputDir)) {
2542
+ fs.mkdirSync(outputDir, { recursive: true });
2543
+ }
2544
+ console.log('');
2545
+ console.log('🔬 Agent Relay Profiler');
2546
+ console.log('');
2547
+ console.log(` Agent: ${agentName}`);
2548
+ console.log(` Command: ${cmd} ${args.join(' ')}`);
2549
+ console.log(` Output: ${outputDir}`);
2550
+ console.log(` Heap snapshots: every ${snapshotInterval}ms`);
2551
+ console.log('');
2552
+ // Build Node.js flags for profiling
2553
+ const nodeFlags = [
2554
+ '--inspect', // Enable inspector
2555
+ '--inspect-brk=0', // Don't actually break, just enable
2556
+ ];
2557
+ if (options.exposeGc) {
2558
+ nodeFlags.push('--expose-gc');
2559
+ }
2560
+ // Set environment variables for profiling
2561
+ const profileEnv = {
2562
+ ...process.env,
2563
+ NODE_OPTIONS: `${process.env.NODE_OPTIONS || ''} ${nodeFlags.join(' ')}`.trim(),
2564
+ AGENT_RELAY_PROFILE_ENABLED: '1',
2565
+ AGENT_RELAY_PROFILE_OUTPUT: outputDir,
2566
+ AGENT_RELAY_PROFILE_INTERVAL: snapshotInterval.toString(),
2567
+ };
2568
+ console.log('Starting profiled agent...');
2569
+ console.log('');
2570
+ // Use the regular wrapper but with profiling environment
2571
+ const paths = getProjectPaths();
2572
+ const wrapper = new RelayPtyOrchestrator({
2573
+ name: agentName,
2574
+ command: cmd,
2575
+ args,
2576
+ socketPath: paths.socketPath,
2577
+ cwd: paths.projectRoot,
2578
+ env: profileEnv,
2579
+ streamLogs: true,
2580
+ });
2581
+ // Start memory sampling
2582
+ const sampleInterval = setInterval(() => {
2583
+ const memUsage = process.memoryUsage();
2584
+ const timestamp = new Date().toISOString();
2585
+ const sample = {
2586
+ timestamp,
2587
+ heapUsed: memUsage.heapUsed,
2588
+ heapTotal: memUsage.heapTotal,
2589
+ external: memUsage.external,
2590
+ rss: memUsage.rss,
2591
+ };
2592
+ // Append to samples file
2593
+ const samplesFile = path.join(outputDir, `${agentName}-memory.jsonl`);
2594
+ fs.appendFileSync(samplesFile, JSON.stringify(sample) + '\n');
2595
+ }, 5000);
2596
+ process.on('SIGINT', async () => {
2597
+ clearInterval(sampleInterval);
2598
+ console.log('\n');
2599
+ console.log('Profiling stopped.');
2600
+ console.log('');
2601
+ console.log(`Profile data saved to: ${outputDir}/`);
2602
+ console.log(` - ${agentName}-memory.jsonl (memory samples)`);
2603
+ console.log('');
2604
+ console.log('To analyze:');
2605
+ console.log(` 1. Open chrome://inspect in Chrome`);
2606
+ console.log(` 2. Load CPU/heap profiles from ${outputDir}/`);
2607
+ console.log('');
2608
+ wrapper.stop();
2609
+ process.exit(0);
2610
+ });
2611
+ await wrapper.start();
2612
+ console.log(`Profiling ${agentName}... Press Ctrl+C to stop.`);
2613
+ });
2614
+ // ============================================================================
2615
+ // codex-auth - SSH tunnel helper for Codex/OpenAI authentication
2616
+ // ============================================================================
2617
+ program
2618
+ .command('codex-auth')
2619
+ .description('Connect Codex via SSH tunnel to workspace (run this when connecting Codex in Agent Relay)')
2620
+ .option('--workspace <id>', 'Workspace ID to connect to')
2621
+ .option('--cloud-url <url>', 'Cloud API URL', process.env.AGENT_RELAY_CLOUD_URL || 'https://agent-relay.com')
2622
+ .option('--token <token>', 'CLI authentication token (from dashboard)')
2623
+ .option('--session-cookie <cookie>', 'Session cookie for authentication (deprecated, use --token)')
2624
+ .option('--timeout <seconds>', 'Timeout in seconds (default: 300)', '300')
2625
+ .action(async (options) => {
2626
+ const TIMEOUT_MS = parseInt(options.timeout, 10) * 1000;
2627
+ const CLOUD_URL = options.cloudUrl.replace(/\/$/, '');
2628
+ const TUNNEL_PORT = 1455;
2629
+ // Colors for terminal output
2630
+ const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
2631
+ const green = (s) => `\x1b[32m${s}\x1b[0m`;
2632
+ const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
2633
+ const red = (s) => `\x1b[31m${s}\x1b[0m`;
2634
+ const dim = (s) => `\x1b[2m${s}\x1b[0m`;
2635
+ console.log('');
2636
+ console.log(cyan('═══════════════════════════════════════════════════'));
2637
+ console.log(cyan(' Codex Authentication Helper'));
2638
+ console.log(cyan('═══════════════════════════════════════════════════'));
2639
+ console.log('');
2640
+ if (!options.workspace) {
2641
+ console.log(red('Missing --workspace parameter.'));
2642
+ console.log('');
2643
+ console.log('To connect Codex, follow these steps:');
2644
+ console.log('');
2645
+ console.log(' 1. Go to the Agent Relay dashboard');
2646
+ console.log(' 2. Click "Connect with Codex" (Settings → AI Providers)');
2647
+ console.log(' 3. Copy the command shown (it includes the workspace ID and token)');
2648
+ console.log(' 4. Run the command in your terminal');
2649
+ console.log('');
2650
+ console.log('The command will look like:');
2651
+ console.log(cyan(' npx agent-relay codex-auth --workspace=<ID> --token=<TOKEN>'));
2652
+ console.log('');
2653
+ process.exit(1);
2654
+ }
2655
+ const workspaceId = options.workspace;
2656
+ console.log(`Workspace: ${workspaceId.slice(0, 8)}...`);
2657
+ // Get tunnel info from cloud API
2658
+ console.log('Getting workspace connection info...');
2659
+ const headers = { 'Content-Type': 'application/json' };
2660
+ if (options.sessionCookie) {
2661
+ headers['Cookie'] = options.sessionCookie;
2662
+ }
2663
+ // Validate token is provided
2664
+ if (!options.token && !options.sessionCookie) {
2665
+ console.log(red('Missing --token parameter.'));
2666
+ console.log('');
2667
+ console.log('The token is provided by the dashboard when you click "Connect with Codex".');
2668
+ console.log('Copy the complete command from the dashboard and paste it here.');
2669
+ console.log('');
2670
+ process.exit(1);
2671
+ }
2672
+ let tunnelInfo;
2673
+ try {
2674
+ // Build URL with token query parameter
2675
+ const tunnelInfoUrl = new URL(`${CLOUD_URL}/api/auth/codex-helper/tunnel-info/${workspaceId}`);
2676
+ if (options.token) {
2677
+ tunnelInfoUrl.searchParams.set('token', options.token);
2678
+ }
2679
+ const response = await fetch(tunnelInfoUrl.toString(), {
2680
+ method: 'GET',
2681
+ headers,
2682
+ credentials: 'include',
2683
+ });
2684
+ if (!response.ok) {
2685
+ const errorData = await response.json();
2686
+ console.log(red(`Failed to get tunnel info: ${errorData.error || response.statusText}`));
2687
+ process.exit(1);
2688
+ }
2689
+ tunnelInfo = await response.json();
2690
+ }
2691
+ catch (err) {
2692
+ console.log(red(`Failed to connect to cloud API: ${err instanceof Error ? err.message : String(err)}`));
2693
+ process.exit(1);
2694
+ }
2695
+ console.log(`Workspace: ${cyan(tunnelInfo.workspaceName)}`);
2696
+ console.log('');
2697
+ // Establish SSH tunnel using ssh2 library (no external tools needed)
2698
+ console.log(yellow('Establishing SSH tunnel...'));
2699
+ console.log(dim(` SSH: ${tunnelInfo.host}:${tunnelInfo.port}`));
2700
+ console.log(dim(` Tunnel: localhost:${TUNNEL_PORT} → workspace:${tunnelInfo.tunnelPort}`));
2701
+ console.log('');
2702
+ const { Client } = await import('ssh2');
2703
+ const net = await import('node:net');
2704
+ const sshClient = new Client();
2705
+ // Use object to hold server reference (avoids TypeScript narrowing issues)
2706
+ const tunnel = { server: null };
2707
+ let tunnelReady = false;
2708
+ let tunnelError = null;
2709
+ // Create a promise that resolves when tunnel is ready or rejects on error
2710
+ const tunnelPromise = new Promise((resolve, reject) => {
2711
+ sshClient.on('ready', () => {
2712
+ // Create local server that forwards connections through SSH
2713
+ tunnel.server = net.createServer((localSocket) => {
2714
+ sshClient.forwardOut('127.0.0.1', TUNNEL_PORT, 'localhost', tunnelInfo.tunnelPort, (err, stream) => {
2715
+ if (err) {
2716
+ localSocket.end();
2717
+ return;
2718
+ }
2719
+ localSocket.pipe(stream).pipe(localSocket);
2720
+ });
2721
+ });
2722
+ tunnel.server.on('error', (err) => {
2723
+ if (err.code === 'EADDRINUSE') {
2724
+ tunnelError = `Port ${TUNNEL_PORT} is already in use. Close any other applications using this port.`;
2725
+ }
2726
+ else {
2727
+ tunnelError = err.message;
2728
+ }
2729
+ reject(new Error(tunnelError));
2730
+ });
2731
+ tunnel.server.listen(TUNNEL_PORT, '127.0.0.1', () => {
2732
+ tunnelReady = true;
2733
+ resolve();
2734
+ });
2735
+ });
2736
+ sshClient.on('error', (err) => {
2737
+ if (err.message.includes('Authentication')) {
2738
+ tunnelError = 'SSH authentication failed. Check the password.';
2739
+ }
2740
+ else if (err.message.includes('ECONNREFUSED')) {
2741
+ tunnelError = `Cannot connect to SSH server at ${tunnelInfo.host}:${tunnelInfo.port}. Is the workspace running and SSH enabled?`;
2742
+ }
2743
+ else if (err.message.includes('ENOTFOUND') || err.message.includes('getaddrinfo')) {
2744
+ tunnelError = `Cannot resolve hostname: ${tunnelInfo.host}. Check network connectivity.`;
2745
+ }
2746
+ else if (err.message.includes('ETIMEDOUT')) {
2747
+ tunnelError = `Connection timed out to ${tunnelInfo.host}:${tunnelInfo.port}. Is the workspace running?`;
2748
+ }
2749
+ else {
2750
+ tunnelError = `SSH error: ${err.message}`;
2751
+ }
2752
+ reject(new Error(tunnelError));
2753
+ });
2754
+ sshClient.on('close', () => {
2755
+ if (!tunnelReady) {
2756
+ // Only set error if not already set by error handler
2757
+ if (!tunnelError) {
2758
+ tunnelError = `SSH connection to ${tunnelInfo.host}:${tunnelInfo.port} closed unexpectedly. The workspace may not have SSH enabled or the port may be blocked.`;
2759
+ }
2760
+ reject(new Error(tunnelError));
2761
+ }
2762
+ });
2763
+ // Connect to SSH server
2764
+ sshClient.connect({
2765
+ host: tunnelInfo.host,
2766
+ port: tunnelInfo.port,
2767
+ username: tunnelInfo.user,
2768
+ password: tunnelInfo.password,
2769
+ readyTimeout: 10000,
2770
+ // Disable host key checking for simplicity (workspace containers)
2771
+ hostVerifier: () => true,
2772
+ });
2773
+ });
2774
+ // Wait for tunnel to establish
2775
+ try {
2776
+ await Promise.race([
2777
+ tunnelPromise,
2778
+ new Promise((_, reject) => setTimeout(() => reject(new Error('SSH connection timeout')), 15000)),
2779
+ ]);
2780
+ }
2781
+ catch (err) {
2782
+ console.log(red(`Failed to establish tunnel: ${err instanceof Error ? err.message : String(err)}`));
2783
+ sshClient.end();
2784
+ process.exit(1);
2785
+ }
2786
+ console.log(green('✓ SSH tunnel established!'));
2787
+ console.log('');
2788
+ // Handle Ctrl+C gracefully
2789
+ const cleanup = () => {
2790
+ console.log('');
2791
+ console.log(dim('Shutting down...'));
2792
+ if (tunnel.server) {
2793
+ tunnel.server.close();
2794
+ }
2795
+ sshClient.end();
2796
+ process.exit(0);
2797
+ };
2798
+ process.on('SIGINT', cleanup);
2799
+ process.on('SIGTERM', cleanup);
2800
+ // Display the OAuth URL
2801
+ if (tunnelInfo.authUrl) {
2802
+ console.log('');
2803
+ console.log(green('Ready! Open this URL in your browser to complete authentication:'));
2804
+ console.log('');
2805
+ console.log(cyan(tunnelInfo.authUrl));
2806
+ console.log('');
2807
+ console.log(dim('The browser will redirect to localhost:1455, which tunnels to the workspace.'));
2808
+ console.log(dim('The Codex CLI in the workspace will receive the callback and complete auth.'));
2809
+ console.log('');
2810
+ }
2811
+ else {
2812
+ console.log('');
2813
+ console.log(yellow('OAuth URL not available. Please start authentication from the dashboard.'));
2814
+ console.log('');
2815
+ }
2816
+ // Poll for authentication completion
2817
+ console.log(cyan(`Waiting for authentication... (timeout: ${options.timeout}s)`));
2818
+ const startTime = Date.now();
2819
+ let authenticated = false;
2820
+ while (!authenticated && (Date.now() - startTime) < TIMEOUT_MS) {
2821
+ await new Promise(resolve => setTimeout(resolve, 3000));
2822
+ try {
2823
+ // Build URL with token for authentication
2824
+ const authStatusUrl = new URL(`${CLOUD_URL}/api/auth/codex-helper/auth-status/${workspaceId}`);
2825
+ if (options.token) {
2826
+ authStatusUrl.searchParams.set('token', options.token);
2827
+ }
2828
+ const statusResponse = await fetch(authStatusUrl.toString(), { method: 'GET', headers, credentials: 'include' });
2829
+ if (statusResponse.ok) {
2830
+ const statusData = await statusResponse.json();
2831
+ if (statusData.authenticated) {
2832
+ authenticated = true;
2833
+ }
2834
+ }
2835
+ }
2836
+ catch {
2837
+ // Ignore polling errors
2838
+ }
2839
+ const elapsed = Math.floor((Date.now() - startTime) / 1000);
2840
+ if (!authenticated && elapsed > 0 && elapsed % 30 === 0) {
2841
+ console.log(` Still waiting... (${elapsed}s)`);
2842
+ }
2843
+ }
2844
+ // Cleanup SSH tunnel
2845
+ if (tunnel.server) {
2846
+ tunnel.server.close();
2847
+ }
2848
+ sshClient.end();
2849
+ if (authenticated) {
2850
+ console.log('');
2851
+ console.log(green('═══════════════════════════════════════════════════'));
2852
+ console.log(green(' Authentication Complete!'));
2853
+ console.log(green('═══════════════════════════════════════════════════'));
2854
+ console.log('');
2855
+ console.log('Your Codex account is now connected to the workspace.');
2856
+ console.log('You can close this terminal and return to the dashboard.');
2857
+ console.log('');
2858
+ }
2859
+ else {
2860
+ console.log('');
2861
+ console.log(red('Timeout waiting for authentication.'));
2862
+ console.log('');
2863
+ console.log('If you completed sign-in, the workspace may not have received');
2864
+ console.log('the callback. Check if the SSH tunnel was working correctly.');
2865
+ process.exit(1);
2866
+ }
2867
+ });
2868
+ // init - First-time setup wizard for Agent Relay
2869
+ async function runInit(options) {
2870
+ const readline = await import('node:readline');
2871
+ const { existsSync } = await import('node:fs');
2872
+ const { spawn } = await import('node:child_process');
2873
+ // Helper to prompt user
2874
+ const prompt = async (question, defaultYes = true) => {
2875
+ if (options.yes)
2876
+ return true;
2877
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
2878
+ const suffix = defaultYes ? '[Y/n]' : '[y/N]';
2879
+ const answer = await new Promise((resolve) => {
2880
+ rl.question(`${question} ${suffix} `, resolve);
2881
+ });
2882
+ rl.close();
2883
+ const normalized = answer.toLowerCase().trim();
2884
+ if (!normalized)
2885
+ return defaultYes;
2886
+ return normalized === 'y' || normalized === 'yes';
2887
+ };
2888
+ // Banner
2889
+ console.log('');
2890
+ console.log(' ╭─────────────────────────────────────╮');
2891
+ console.log(' │ │');
2892
+ console.log(' │ 🚀 Agent Relay - First Time Setup │');
2893
+ console.log(' │ │');
2894
+ console.log(' │ Real-time AI agent communication │');
2895
+ console.log(' │ │');
2896
+ console.log(' ╰─────────────────────────────────────╯');
2897
+ console.log('');
2898
+ // Step 1: Detect environment
2899
+ const isCloud = !!process.env.WORKSPACE_ID;
2900
+ if (isCloud) {
2901
+ console.log(' ℹ Detected: Cloud workspace');
2902
+ console.log(' MCP tools are pre-configured in cloud environments.');
2903
+ console.log('');
2904
+ return;
2905
+ }
2906
+ console.log(' ℹ Detected: Local environment');
2907
+ console.log('');
2908
+ // Step 2: Check daemon status
2909
+ let daemonRunning = false;
2910
+ const socketPath = process.env.RELAY_SOCKET;
2911
+ if (socketPath && existsSync(socketPath)) {
2912
+ daemonRunning = true;
2913
+ }
2914
+ else {
2915
+ // Check default locations
2916
+ const { homedir } = await import('node:os');
2917
+ const { join } = await import('node:path');
2918
+ const platform = process.platform;
2919
+ let dataDir;
2920
+ if (platform === 'darwin') {
2921
+ dataDir = join(homedir(), 'Library', 'Application Support', 'agent-relay');
2922
+ }
2923
+ else if (platform === 'win32') {
2924
+ dataDir = join(process.env.APPDATA || homedir(), 'agent-relay');
2925
+ }
2926
+ else {
2927
+ dataDir = join(process.env.XDG_DATA_HOME || join(homedir(), '.local', 'share'), 'agent-relay');
2928
+ }
2929
+ const defaultSocket = join(dataDir, 'projects', 'default', 'daemon.sock');
2930
+ if (existsSync(defaultSocket)) {
2931
+ daemonRunning = true;
2932
+ }
2933
+ }
2934
+ if (daemonRunning) {
2935
+ console.log(' ✓ Daemon is already running');
2936
+ }
2937
+ else {
2938
+ console.log(' ○ Daemon is not running');
2939
+ }
2940
+ console.log('');
2941
+ // Step 3: Install MCP for editors
2942
+ let mcpInstalled = false;
2943
+ if (!options.skipMcp) {
2944
+ console.log(' ┌─ Step 1: MCP Server for AI Editors ─────────────────────┐');
2945
+ console.log(' │ │');
2946
+ console.log(' │ MCP (Model Context Protocol) gives AI editors native │');
2947
+ console.log(' │ tools for agent communication: │');
2948
+ console.log(' │ │');
2949
+ console.log(' │ • relay_send - Send messages to agents/channels │');
2950
+ console.log(' │ • relay_spawn - Create worker agents │');
2951
+ console.log(' │ • relay_inbox - Check for messages │');
2952
+ console.log(' │ • relay_who - List online agents │');
2953
+ console.log(' │ │');
2954
+ console.log(' │ Supported editors: Claude Code, Cursor, VS Code │');
2955
+ console.log(' │ │');
2956
+ console.log(' └──────────────────────────────────────────────────────────┘');
2957
+ console.log('');
2958
+ const shouldInstallMcp = await prompt(' Install MCP server for your AI editors?');
2959
+ if (shouldInstallMcp) {
2960
+ console.log('');
2961
+ console.log(' Installing MCP server...');
2962
+ try {
2963
+ const { runInstall } = await import('@agent-relay/mcp/install');
2964
+ runInstall({ editor: undefined });
2965
+ mcpInstalled = true;
2966
+ }
2967
+ catch (err) {
2968
+ if (err.code === 'ERR_MODULE_NOT_FOUND') {
2969
+ console.log(' ⚠ MCP package not bundled. Install separately:');
2970
+ console.log(' npm install -g @agent-relay/mcp');
2971
+ }
2972
+ else {
2973
+ console.error(' ✗ Error:', err.message);
2974
+ }
2975
+ }
2976
+ console.log('');
2977
+ }
2978
+ }
2979
+ // Step 4: Start daemon
2980
+ if (!daemonRunning && !options.skipDaemon) {
2981
+ console.log(' ┌─ Step 2: Start the Relay Daemon ─────────────────────────┐');
2982
+ console.log(' │ │');
2983
+ console.log(' │ The daemon manages agent connections and message │');
2984
+ console.log(' │ routing. It runs in the background. │');
2985
+ console.log(' │ │');
2986
+ console.log(' └──────────────────────────────────────────────────────────┘');
2987
+ console.log('');
2988
+ const shouldStartDaemon = await prompt(' Start the relay daemon now?');
2989
+ if (shouldStartDaemon) {
2990
+ console.log('');
2991
+ console.log(' Starting daemon...');
2992
+ // Start daemon in background
2993
+ const daemonProcess = spawn(process.execPath, [process.argv[1], 'up', '--background'], {
2994
+ detached: true,
2995
+ stdio: 'ignore',
2996
+ });
2997
+ daemonProcess.unref();
2998
+ // Wait a moment for it to start
2999
+ await new Promise((resolve) => setTimeout(resolve, 2000));
3000
+ console.log(' ✓ Daemon started in background');
3001
+ console.log('');
3002
+ daemonRunning = true;
3003
+ }
3004
+ }
3005
+ // Summary
3006
+ console.log(' ╭─────────────────────────────────────────────────────────╮');
3007
+ console.log(' │ Setup Complete! │');
3008
+ console.log(' ╰─────────────────────────────────────────────────────────╯');
3009
+ console.log('');
3010
+ if (mcpInstalled || daemonRunning) {
3011
+ console.log(' Status:');
3012
+ if (mcpInstalled)
3013
+ console.log(' ✓ MCP server configured for editors');
3014
+ if (daemonRunning)
3015
+ console.log(' ✓ Daemon running');
3016
+ console.log('');
3017
+ }
3018
+ console.log(' Quick Start:');
3019
+ console.log('');
3020
+ console.log(' 1. Open Claude Code (or Cursor)');
3021
+ console.log('');
3022
+ console.log(' 2. The relay tools are ready! Try asking Claude:');
3023
+ console.log(' "Use relay_who to see online agents"');
3024
+ console.log('');
3025
+ console.log(' 3. Spawn a worker agent:');
3026
+ console.log(' "Spawn a worker named TestRunner to run the tests"');
3027
+ console.log('');
3028
+ console.log(' Commands:');
3029
+ console.log(' agent-relay up Start daemon with dashboard');
3030
+ console.log(' agent-relay status Check daemon status');
3031
+ console.log(' agent-relay who List online agents');
3032
+ console.log('');
3033
+ console.log(' Dashboard: http://localhost:3888 (when daemon is running)');
3034
+ console.log('');
3035
+ }
3036
+ program
3037
+ .command('init')
3038
+ .description('First-time setup wizard - install MCP and start daemon')
3039
+ .option('-y, --yes', 'Accept all defaults (non-interactive)')
3040
+ .option('--skip-daemon', 'Skip daemon startup prompt')
3041
+ .option('--skip-mcp', 'Skip MCP installation prompt')
3042
+ .action(runInit);
3043
+ // setup - Alias for init (backwards compatibility)
3044
+ program
3045
+ .command('setup')
3046
+ .description('Alias for "init" - first-time setup wizard')
3047
+ .option('-y, --yes', 'Accept all defaults')
3048
+ .option('--skip-daemon', 'Skip daemon startup')
3049
+ .option('--skip-mcp', 'Skip MCP installation')
3050
+ .action(runInit);
3051
+ // mcp - MCP server management
3052
+ program
3053
+ .command('mcp')
3054
+ .description('Manage MCP server for AI editors')
3055
+ .argument('<command>', 'Command to run (install, serve)')
3056
+ .option('-e, --editor <name>', 'Editor to configure')
3057
+ .option('-g, --global', 'Install globally')
3058
+ .action(async (cmd, options) => {
3059
+ try {
3060
+ if (cmd === 'install') {
3061
+ const { runInstall } = await import('@agent-relay/mcp/install');
3062
+ runInstall(options);
3063
+ }
3064
+ else if (cmd === 'serve') {
3065
+ const { createRelayClient, runMCPServer, discoverSocket } = await import('@agent-relay/mcp');
3066
+ const discovery = discoverSocket();
3067
+ const client = createRelayClient({
3068
+ agentName: process.env.RELAY_AGENT_NAME || `mcp-${process.pid}`,
3069
+ socketPath: discovery?.socketPath,
3070
+ project: discovery?.project,
3071
+ });
3072
+ await runMCPServer(client);
3073
+ }
3074
+ else {
3075
+ console.error(`Unknown mcp command: ${cmd}`);
3076
+ process.exit(1);
3077
+ }
3078
+ }
3079
+ catch (err) {
3080
+ if (err.code === 'ERR_MODULE_NOT_FOUND') {
3081
+ console.error('Error: @agent-relay/mcp package not found.');
3082
+ console.error('This command requires the MCP package to be installed.');
3083
+ }
3084
+ else {
3085
+ console.error('Error running MCP command:', err);
3086
+ }
3087
+ process.exit(1);
3088
+ }
3089
+ });
3090
+ program.parse();
3091
+ //# sourceMappingURL=index.js.map