@loompad/cli 0.1.0 → 1.1.1

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 (687) hide show
  1. package/ARCHITECTURE.md +96 -11
  2. package/CHANGELOG.md +1042 -0
  3. package/README.md +1473 -44
  4. package/THIRD_PARTY_NOTICES.md +275 -0
  5. package/dist/adapters/antigravity-cli.d.ts +62 -0
  6. package/dist/adapters/antigravity-cli.js +258 -0
  7. package/dist/adapters/antigravity-cli.js.map +1 -0
  8. package/dist/adapters/base.d.ts +61 -9
  9. package/dist/adapters/base.js +266 -15
  10. package/dist/adapters/base.js.map +1 -1
  11. package/dist/adapters/bridges/antigravity.d.ts +9 -12
  12. package/dist/adapters/bridges/antigravity.js +9 -12
  13. package/dist/adapters/bridges/antigravity.js.map +1 -1
  14. package/dist/adapters/echo.d.ts +1 -0
  15. package/dist/adapters/echo.js +59 -3
  16. package/dist/adapters/echo.js.map +1 -1
  17. package/dist/adapters/grok.d.ts +7 -0
  18. package/dist/adapters/grok.js +20 -10
  19. package/dist/adapters/grok.js.map +1 -1
  20. package/dist/adapters/index.d.ts +5 -1
  21. package/dist/adapters/index.js +53 -7
  22. package/dist/adapters/index.js.map +1 -1
  23. package/dist/adapters/model.d.ts +137 -0
  24. package/dist/adapters/model.js +527 -0
  25. package/dist/adapters/model.js.map +1 -0
  26. package/dist/adapters/opencode.d.ts +101 -1
  27. package/dist/adapters/opencode.js +874 -98
  28. package/dist/adapters/opencode.js.map +1 -1
  29. package/dist/cli/doctor.d.ts +32 -0
  30. package/dist/cli/doctor.js +89 -4
  31. package/dist/cli/doctor.js.map +1 -1
  32. package/dist/cli/index.js +2651 -87
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/tui-model.js +3 -1
  35. package/dist/cli/tui-model.js.map +1 -1
  36. package/dist/cli/tui.js +44 -1
  37. package/dist/cli/tui.js.map +1 -1
  38. package/dist/cli/ui.d.ts +2 -0
  39. package/dist/cli/ui.js +13 -0
  40. package/dist/cli/ui.js.map +1 -1
  41. package/dist/core/ades.d.ts +32 -16
  42. package/dist/core/ades.js +57 -20
  43. package/dist/core/ades.js.map +1 -1
  44. package/dist/core/agent-state.d.ts +14 -0
  45. package/dist/core/agent-state.js +32 -0
  46. package/dist/core/agent-state.js.map +1 -0
  47. package/dist/core/approvals.d.ts +33 -0
  48. package/dist/core/approvals.js +29 -0
  49. package/dist/core/approvals.js.map +1 -0
  50. package/dist/core/backup.d.ts +19 -0
  51. package/dist/core/backup.js +86 -0
  52. package/dist/core/backup.js.map +1 -0
  53. package/dist/core/baton.d.ts +2 -2
  54. package/dist/core/baton.js.map +1 -1
  55. package/dist/core/brain-extract.d.ts +17 -0
  56. package/dist/core/brain-extract.js +28 -2
  57. package/dist/core/brain-extract.js.map +1 -1
  58. package/dist/core/brain-index.d.ts +71 -0
  59. package/dist/core/brain-index.js +204 -5
  60. package/dist/core/brain-index.js.map +1 -1
  61. package/dist/core/brain.d.ts +58 -2
  62. package/dist/core/brain.js +79 -1
  63. package/dist/core/brain.js.map +1 -1
  64. package/dist/core/chat-import.d.ts +71 -0
  65. package/dist/core/chat-import.js +419 -0
  66. package/dist/core/chat-import.js.map +1 -0
  67. package/dist/core/checkpoint.d.ts +107 -0
  68. package/dist/core/checkpoint.js +259 -0
  69. package/dist/core/checkpoint.js.map +1 -0
  70. package/dist/core/claude-cli.js +1 -1
  71. package/dist/core/claude-cli.js.map +1 -1
  72. package/dist/core/continuity/artifacts.d.ts +14 -0
  73. package/dist/core/continuity/artifacts.js +118 -0
  74. package/dist/core/continuity/artifacts.js.map +1 -0
  75. package/dist/core/continuity/capabilities.d.ts +65 -0
  76. package/dist/core/continuity/capabilities.js +129 -0
  77. package/dist/core/continuity/capabilities.js.map +1 -0
  78. package/dist/core/continuity/contracts.d.ts +352 -0
  79. package/dist/core/continuity/contracts.js +117 -0
  80. package/dist/core/continuity/contracts.js.map +1 -0
  81. package/dist/core/continuity/engine.d.ts +55 -0
  82. package/dist/core/continuity/engine.js +504 -0
  83. package/dist/core/continuity/engine.js.map +1 -0
  84. package/dist/core/continuity/store.d.ts +73 -0
  85. package/dist/core/continuity/store.js +438 -0
  86. package/dist/core/continuity/store.js.map +1 -0
  87. package/dist/core/conversations.d.ts +19 -0
  88. package/dist/core/conversations.js +87 -0
  89. package/dist/core/conversations.js.map +1 -0
  90. package/dist/core/crew-coordination.d.ts +6 -0
  91. package/dist/core/crew-coordination.js +24 -0
  92. package/dist/core/crew-coordination.js.map +1 -0
  93. package/dist/core/crew-policy.d.ts +18 -0
  94. package/dist/core/crew-policy.js +40 -0
  95. package/dist/core/crew-policy.js.map +1 -0
  96. package/dist/core/crew-protocol.d.ts +64 -0
  97. package/dist/core/crew-protocol.js +146 -0
  98. package/dist/core/crew-protocol.js.map +1 -0
  99. package/dist/core/crew-requests.d.ts +14 -0
  100. package/dist/core/crew-requests.js +22 -0
  101. package/dist/core/crew-requests.js.map +1 -0
  102. package/dist/core/crew.d.ts +395 -0
  103. package/dist/core/crew.js +1857 -0
  104. package/dist/core/crew.js.map +1 -0
  105. package/dist/core/digest.d.ts +40 -0
  106. package/dist/core/digest.js +94 -0
  107. package/dist/core/digest.js.map +1 -0
  108. package/dist/core/distill.js +3 -5
  109. package/dist/core/distill.js.map +1 -1
  110. package/dist/core/eventlog.d.ts +27 -25
  111. package/dist/core/eventlog.js +97 -150
  112. package/dist/core/eventlog.js.map +1 -1
  113. package/dist/core/events/contracts.d.ts +51 -0
  114. package/dist/core/events/contracts.js +2 -0
  115. package/dist/core/events/contracts.js.map +1 -0
  116. package/dist/core/events/jsonl-store.d.ts +23 -0
  117. package/dist/core/events/jsonl-store.js +90 -0
  118. package/dist/core/events/jsonl-store.js.map +1 -0
  119. package/dist/core/events/sqlite-store.d.ts +37 -0
  120. package/dist/core/events/sqlite-store.js +215 -0
  121. package/dist/core/events/sqlite-store.js.map +1 -0
  122. package/dist/core/forecast.d.ts +44 -0
  123. package/dist/core/forecast.js +46 -0
  124. package/dist/core/forecast.js.map +1 -0
  125. package/dist/core/free-pool.d.ts +65 -0
  126. package/dist/core/free-pool.js +171 -0
  127. package/dist/core/free-pool.js.map +1 -0
  128. package/dist/core/git.d.ts +30 -0
  129. package/dist/core/git.js +50 -2
  130. package/dist/core/git.js.map +1 -1
  131. package/dist/core/github-events.d.ts +53 -0
  132. package/dist/core/github-events.js +227 -0
  133. package/dist/core/github-events.js.map +1 -0
  134. package/dist/core/goal-lanes.d.ts +53 -0
  135. package/dist/core/goal-lanes.js +99 -0
  136. package/dist/core/goal-lanes.js.map +1 -0
  137. package/dist/core/hosted.d.ts +27 -0
  138. package/dist/core/hosted.js +50 -0
  139. package/dist/core/hosted.js.map +1 -0
  140. package/dist/core/http-server.d.ts +5 -0
  141. package/dist/core/http-server.js +8 -0
  142. package/dist/core/http-server.js.map +1 -0
  143. package/dist/core/invite-link.d.ts +31 -0
  144. package/dist/core/invite-link.js +45 -0
  145. package/dist/core/invite-link.js.map +1 -0
  146. package/dist/core/loops.d.ts +37 -0
  147. package/dist/core/loops.js +81 -0
  148. package/dist/core/loops.js.map +1 -0
  149. package/dist/core/mcp-catalog.d.ts +182 -0
  150. package/dist/core/mcp-catalog.js +431 -0
  151. package/dist/core/mcp-catalog.js.map +1 -0
  152. package/dist/core/mcp.d.ts +87 -0
  153. package/dist/core/mcp.js +181 -0
  154. package/dist/core/mcp.js.map +1 -0
  155. package/dist/core/memory.d.ts +1 -0
  156. package/dist/core/memory.js +5 -2
  157. package/dist/core/memory.js.map +1 -1
  158. package/dist/core/model-tools.d.ts +97 -0
  159. package/dist/core/model-tools.js +388 -0
  160. package/dist/core/model-tools.js.map +1 -0
  161. package/dist/core/notify.js +19 -1
  162. package/dist/core/notify.js.map +1 -1
  163. package/dist/core/orchestra.d.ts +635 -0
  164. package/dist/core/orchestra.js +2120 -0
  165. package/dist/core/orchestra.js.map +1 -0
  166. package/dist/core/permissions.d.ts +56 -0
  167. package/dist/core/permissions.js +122 -0
  168. package/dist/core/permissions.js.map +1 -0
  169. package/dist/core/preview-proxy.d.ts +70 -0
  170. package/dist/core/preview-proxy.js +563 -0
  171. package/dist/core/preview-proxy.js.map +1 -0
  172. package/dist/core/preview-shot.d.ts +46 -0
  173. package/dist/core/preview-shot.js +109 -0
  174. package/dist/core/preview-shot.js.map +1 -0
  175. package/dist/core/process.d.ts +36 -0
  176. package/dist/core/process.js +168 -0
  177. package/dist/core/process.js.map +1 -0
  178. package/dist/core/prompt-queue.d.ts +136 -0
  179. package/dist/core/prompt-queue.js +269 -0
  180. package/dist/core/prompt-queue.js.map +1 -0
  181. package/dist/core/prompts.d.ts +53 -0
  182. package/dist/core/prompts.js +127 -0
  183. package/dist/core/prompts.js.map +1 -0
  184. package/dist/core/proof.d.ts +82 -0
  185. package/dist/core/proof.js +216 -0
  186. package/dist/core/proof.js.map +1 -0
  187. package/dist/core/provider-text.d.ts +23 -0
  188. package/dist/core/provider-text.js +66 -0
  189. package/dist/core/provider-text.js.map +1 -0
  190. package/dist/core/providers.d.ts +152 -0
  191. package/dist/core/providers.js +343 -0
  192. package/dist/core/providers.js.map +1 -0
  193. package/dist/core/quota.d.ts +33 -0
  194. package/dist/core/quota.js +74 -0
  195. package/dist/core/quota.js.map +1 -0
  196. package/dist/core/recipes.d.ts +50 -0
  197. package/dist/core/recipes.js +99 -0
  198. package/dist/core/recipes.js.map +1 -0
  199. package/dist/core/registry.d.ts +54 -0
  200. package/dist/core/registry.js +62 -5
  201. package/dist/core/registry.js.map +1 -1
  202. package/dist/core/relay-client.d.ts +67 -0
  203. package/dist/core/relay-client.js +184 -0
  204. package/dist/core/relay-client.js.map +1 -0
  205. package/dist/core/relay-protocol.d.ts +140 -0
  206. package/dist/core/relay-protocol.js +189 -0
  207. package/dist/core/relay-protocol.js.map +1 -0
  208. package/dist/core/replay.d.ts +18 -0
  209. package/dist/core/replay.js +191 -0
  210. package/dist/core/replay.js.map +1 -0
  211. package/dist/core/review-triage.d.ts +41 -0
  212. package/dist/core/review-triage.js +124 -0
  213. package/dist/core/review-triage.js.map +1 -0
  214. package/dist/core/router.js +2 -2
  215. package/dist/core/router.js.map +1 -1
  216. package/dist/core/routes.d.ts +39 -3
  217. package/dist/core/routes.js +137 -4
  218. package/dist/core/routes.js.map +1 -1
  219. package/dist/core/run-command.d.ts +14 -0
  220. package/dist/core/run-command.js +45 -0
  221. package/dist/core/run-command.js.map +1 -0
  222. package/dist/core/runner-setup.d.ts +36 -0
  223. package/dist/core/runner-setup.js +114 -0
  224. package/dist/core/runner-setup.js.map +1 -0
  225. package/dist/core/search.js +0 -0
  226. package/dist/core/search.js.map +1 -1
  227. package/dist/core/semantic.d.ts +92 -0
  228. package/dist/core/semantic.js +227 -0
  229. package/dist/core/semantic.js.map +1 -0
  230. package/dist/core/servers.d.ts +115 -0
  231. package/dist/core/servers.js +305 -0
  232. package/dist/core/servers.js.map +1 -0
  233. package/dist/core/setup.js +64 -6
  234. package/dist/core/setup.js.map +1 -1
  235. package/dist/core/shield.d.ts +17 -0
  236. package/dist/core/shield.js +81 -0
  237. package/dist/core/shield.js.map +1 -0
  238. package/dist/core/shift.d.ts +69 -0
  239. package/dist/core/shift.js +120 -0
  240. package/dist/core/shift.js.map +1 -0
  241. package/dist/core/skill-install.d.ts +125 -0
  242. package/dist/core/skill-install.js +328 -0
  243. package/dist/core/skill-install.js.map +1 -0
  244. package/dist/core/skills.d.ts +116 -0
  245. package/dist/core/skills.js +223 -0
  246. package/dist/core/skills.js.map +1 -0
  247. package/dist/core/step-conditions.d.ts +65 -0
  248. package/dist/core/step-conditions.js +102 -0
  249. package/dist/core/step-conditions.js.map +1 -0
  250. package/dist/core/subagents.d.ts +45 -0
  251. package/dist/core/subagents.js +90 -0
  252. package/dist/core/subagents.js.map +1 -0
  253. package/dist/core/team-canon.d.ts +42 -0
  254. package/dist/core/team-canon.js +121 -0
  255. package/dist/core/team-canon.js.map +1 -0
  256. package/dist/core/team-crypto.d.ts +97 -0
  257. package/dist/core/team-crypto.js +175 -0
  258. package/dist/core/team-crypto.js.map +1 -0
  259. package/dist/core/team-hub.d.ts +514 -0
  260. package/dist/core/team-hub.js +964 -0
  261. package/dist/core/team-hub.js.map +1 -0
  262. package/dist/core/team-landing.d.ts +239 -0
  263. package/dist/core/team-landing.js +504 -0
  264. package/dist/core/team-landing.js.map +1 -0
  265. package/dist/core/team-leases.d.ts +44 -0
  266. package/dist/core/team-leases.js +145 -0
  267. package/dist/core/team-leases.js.map +1 -0
  268. package/dist/core/team-memory.d.ts +42 -0
  269. package/dist/core/team-memory.js +84 -0
  270. package/dist/core/team-memory.js.map +1 -0
  271. package/dist/core/team-policy.d.ts +85 -0
  272. package/dist/core/team-policy.js +191 -0
  273. package/dist/core/team-policy.js.map +1 -0
  274. package/dist/core/thread-md.d.ts +6 -0
  275. package/dist/core/thread-md.js +42 -0
  276. package/dist/core/thread-md.js.map +1 -0
  277. package/dist/core/turn-stats.d.ts +61 -0
  278. package/dist/core/turn-stats.js +124 -0
  279. package/dist/core/turn-stats.js.map +1 -0
  280. package/dist/core/updater.d.ts +73 -0
  281. package/dist/core/updater.js +153 -0
  282. package/dist/core/updater.js.map +1 -0
  283. package/dist/core/usage.d.ts +75 -0
  284. package/dist/core/usage.js +163 -0
  285. package/dist/core/usage.js.map +1 -0
  286. package/dist/core/windows-jobs.d.ts +15 -0
  287. package/dist/core/windows-jobs.js +170 -0
  288. package/dist/core/windows-jobs.js.map +1 -0
  289. package/dist/core/worktree-merge.d.ts +57 -0
  290. package/dist/core/worktree-merge.js +123 -0
  291. package/dist/core/worktree-merge.js.map +1 -0
  292. package/dist/core/worktree.d.ts +5 -0
  293. package/dist/core/worktree.js +45 -5
  294. package/dist/core/worktree.js.map +1 -1
  295. package/dist/core/worktrees.d.ts +6 -0
  296. package/dist/core/worktrees.js +21 -0
  297. package/dist/core/worktrees.js.map +1 -0
  298. package/dist/daemon/app-page.d.ts +0 -17
  299. package/dist/daemon/app-page.js +10 -6346
  300. package/dist/daemon/app-page.js.map +1 -1
  301. package/dist/daemon/auth.d.ts +24 -3
  302. package/dist/daemon/auth.js +66 -7
  303. package/dist/daemon/auth.js.map +1 -1
  304. package/dist/daemon/board.d.ts +7 -0
  305. package/dist/daemon/board.js +12 -1
  306. package/dist/daemon/board.js.map +1 -1
  307. package/dist/daemon/brand-icons.d.ts +6 -1
  308. package/dist/daemon/brand-icons.js +11 -3
  309. package/dist/daemon/brand-icons.js.map +1 -1
  310. package/dist/daemon/client.d.ts +665 -1
  311. package/dist/daemon/client.js +381 -3
  312. package/dist/daemon/client.js.map +1 -1
  313. package/dist/daemon/delivery.d.ts +34 -0
  314. package/dist/daemon/delivery.js +63 -0
  315. package/dist/daemon/delivery.js.map +1 -0
  316. package/dist/daemon/deploys.d.ts +62 -0
  317. package/dist/daemon/deploys.js +159 -0
  318. package/dist/daemon/deploys.js.map +1 -0
  319. package/dist/daemon/landing.d.ts +192 -0
  320. package/dist/daemon/landing.js +992 -0
  321. package/dist/daemon/landing.js.map +1 -0
  322. package/dist/daemon/onboard.d.ts +150 -0
  323. package/dist/daemon/onboard.js +390 -0
  324. package/dist/daemon/onboard.js.map +1 -0
  325. package/dist/daemon/proof.d.ts +70 -0
  326. package/dist/daemon/proof.js +172 -0
  327. package/dist/daemon/proof.js.map +1 -0
  328. package/dist/daemon/push.d.ts +19 -1
  329. package/dist/daemon/push.js +90 -3
  330. package/dist/daemon/push.js.map +1 -1
  331. package/dist/daemon/relay.d.ts +82 -0
  332. package/dist/daemon/relay.js +351 -0
  333. package/dist/daemon/relay.js.map +1 -0
  334. package/dist/daemon/routes/agent-discovery.d.ts +4 -0
  335. package/dist/daemon/routes/agent-discovery.js +29 -0
  336. package/dist/daemon/routes/agent-discovery.js.map +1 -0
  337. package/dist/daemon/routes/agent-settings.d.ts +4 -0
  338. package/dist/daemon/routes/agent-settings.js +94 -0
  339. package/dist/daemon/routes/agent-settings.js.map +1 -0
  340. package/dist/daemon/routes/agents.d.ts +4 -0
  341. package/dist/daemon/routes/agents.js +105 -0
  342. package/dist/daemon/routes/agents.js.map +1 -0
  343. package/dist/daemon/routes/alerts.d.ts +4 -0
  344. package/dist/daemon/routes/alerts.js +137 -0
  345. package/dist/daemon/routes/alerts.js.map +1 -0
  346. package/dist/daemon/routes/approvals.d.ts +4 -0
  347. package/dist/daemon/routes/approvals.js +40 -0
  348. package/dist/daemon/routes/approvals.js.map +1 -0
  349. package/dist/daemon/routes/assets.d.ts +3 -0
  350. package/dist/daemon/routes/assets.js +64 -0
  351. package/dist/daemon/routes/assets.js.map +1 -0
  352. package/dist/daemon/routes/board.d.ts +4 -0
  353. package/dist/daemon/routes/board.js +147 -0
  354. package/dist/daemon/routes/board.js.map +1 -0
  355. package/dist/daemon/routes/brain.d.ts +4 -0
  356. package/dist/daemon/routes/brain.js +270 -0
  357. package/dist/daemon/routes/brain.js.map +1 -0
  358. package/dist/daemon/routes/chats.d.ts +4 -0
  359. package/dist/daemon/routes/chats.js +249 -0
  360. package/dist/daemon/routes/chats.js.map +1 -0
  361. package/dist/daemon/routes/clients.d.ts +4 -0
  362. package/dist/daemon/routes/clients.js +49 -0
  363. package/dist/daemon/routes/clients.js.map +1 -0
  364. package/dist/daemon/routes/cloud.d.ts +4 -0
  365. package/dist/daemon/routes/cloud.js +46 -0
  366. package/dist/daemon/routes/cloud.js.map +1 -0
  367. package/dist/daemon/routes/config.d.ts +4 -0
  368. package/dist/daemon/routes/config.js +35 -0
  369. package/dist/daemon/routes/config.js.map +1 -0
  370. package/dist/daemon/routes/context.d.ts +51 -0
  371. package/dist/daemon/routes/context.js +2 -0
  372. package/dist/daemon/routes/context.js.map +1 -0
  373. package/dist/daemon/routes/crews.d.ts +4 -0
  374. package/dist/daemon/routes/crews.js +149 -0
  375. package/dist/daemon/routes/crews.js.map +1 -0
  376. package/dist/daemon/routes/files.d.ts +3 -0
  377. package/dist/daemon/routes/files.js +253 -0
  378. package/dist/daemon/routes/files.js.map +1 -0
  379. package/dist/daemon/routes/git.d.ts +4 -0
  380. package/dist/daemon/routes/git.js +96 -0
  381. package/dist/daemon/routes/git.js.map +1 -0
  382. package/dist/daemon/routes/history.d.ts +4 -0
  383. package/dist/daemon/routes/history.js +101 -0
  384. package/dist/daemon/routes/history.js.map +1 -0
  385. package/dist/daemon/routes/integrations-project.d.ts +3 -0
  386. package/dist/daemon/routes/integrations-project.js +103 -0
  387. package/dist/daemon/routes/integrations-project.js.map +1 -0
  388. package/dist/daemon/routes/integrations.d.ts +3 -0
  389. package/dist/daemon/routes/integrations.js +111 -0
  390. package/dist/daemon/routes/integrations.js.map +1 -0
  391. package/dist/daemon/routes/logs.d.ts +3 -0
  392. package/dist/daemon/routes/logs.js +40 -0
  393. package/dist/daemon/routes/logs.js.map +1 -0
  394. package/dist/daemon/routes/messages.d.ts +4 -0
  395. package/dist/daemon/routes/messages.js +60 -0
  396. package/dist/daemon/routes/messages.js.map +1 -0
  397. package/dist/daemon/routes/observability.d.ts +4 -0
  398. package/dist/daemon/routes/observability.js +247 -0
  399. package/dist/daemon/routes/observability.js.map +1 -0
  400. package/dist/daemon/routes/onboard.d.ts +12 -0
  401. package/dist/daemon/routes/onboard.js +67 -0
  402. package/dist/daemon/routes/onboard.js.map +1 -0
  403. package/dist/daemon/routes/orchestra.d.ts +4 -0
  404. package/dist/daemon/routes/orchestra.js +110 -0
  405. package/dist/daemon/routes/orchestra.js.map +1 -0
  406. package/dist/daemon/routes/pairing.d.ts +4 -0
  407. package/dist/daemon/routes/pairing.js +155 -0
  408. package/dist/daemon/routes/pairing.js.map +1 -0
  409. package/dist/daemon/routes/preview.d.ts +24 -0
  410. package/dist/daemon/routes/preview.js +122 -0
  411. package/dist/daemon/routes/preview.js.map +1 -0
  412. package/dist/daemon/routes/project-actions.d.ts +4 -0
  413. package/dist/daemon/routes/project-actions.js +53 -0
  414. package/dist/daemon/routes/project-actions.js.map +1 -0
  415. package/dist/daemon/routes/project-team.d.ts +4 -0
  416. package/dist/daemon/routes/project-team.js +201 -0
  417. package/dist/daemon/routes/project-team.js.map +1 -0
  418. package/dist/daemon/routes/projects.d.ts +14 -0
  419. package/dist/daemon/routes/projects.js +138 -0
  420. package/dist/daemon/routes/projects.js.map +1 -0
  421. package/dist/daemon/routes/prompts.d.ts +4 -0
  422. package/dist/daemon/routes/prompts.js +44 -0
  423. package/dist/daemon/routes/prompts.js.map +1 -0
  424. package/dist/daemon/routes/proof.d.ts +4 -0
  425. package/dist/daemon/routes/proof.js +22 -0
  426. package/dist/daemon/routes/proof.js.map +1 -0
  427. package/dist/daemon/routes/providers.d.ts +3 -0
  428. package/dist/daemon/routes/providers.js +60 -0
  429. package/dist/daemon/routes/providers.js.map +1 -0
  430. package/dist/daemon/routes/public.d.ts +4 -0
  431. package/dist/daemon/routes/public.js +64 -0
  432. package/dist/daemon/routes/public.js.map +1 -0
  433. package/dist/daemon/routes/queue.d.ts +4 -0
  434. package/dist/daemon/routes/queue.js +140 -0
  435. package/dist/daemon/routes/queue.js.map +1 -0
  436. package/dist/daemon/routes/routing.d.ts +4 -0
  437. package/dist/daemon/routes/routing.js +61 -0
  438. package/dist/daemon/routes/routing.js.map +1 -0
  439. package/dist/daemon/routes/runtime-handler.d.ts +2 -0
  440. package/dist/daemon/routes/runtime-handler.js +63 -0
  441. package/dist/daemon/routes/runtime-handler.js.map +1 -0
  442. package/dist/daemon/routes/search.d.ts +4 -0
  443. package/dist/daemon/routes/search.js +17 -0
  444. package/dist/daemon/routes/search.js.map +1 -0
  445. package/dist/daemon/routes/servers.d.ts +4 -0
  446. package/dist/daemon/routes/servers.js +133 -0
  447. package/dist/daemon/routes/servers.js.map +1 -0
  448. package/dist/daemon/routes/setup.d.ts +3 -0
  449. package/dist/daemon/routes/setup.js +50 -0
  450. package/dist/daemon/routes/setup.js.map +1 -0
  451. package/dist/daemon/routes/shift.d.ts +4 -0
  452. package/dist/daemon/routes/shift.js +30 -0
  453. package/dist/daemon/routes/shift.js.map +1 -0
  454. package/dist/daemon/routes/specs.d.ts +4 -0
  455. package/dist/daemon/routes/specs.js +57 -0
  456. package/dist/daemon/routes/specs.js.map +1 -0
  457. package/dist/daemon/routes/task-delivery.d.ts +4 -0
  458. package/dist/daemon/routes/task-delivery.js +38 -0
  459. package/dist/daemon/routes/task-delivery.js.map +1 -0
  460. package/dist/daemon/routes/team.d.ts +4 -0
  461. package/dist/daemon/routes/team.js +193 -0
  462. package/dist/daemon/routes/team.js.map +1 -0
  463. package/dist/daemon/routes/terminals.d.ts +4 -0
  464. package/dist/daemon/routes/terminals.js +87 -0
  465. package/dist/daemon/routes/terminals.js.map +1 -0
  466. package/dist/daemon/routes/tools.d.ts +4 -0
  467. package/dist/daemon/routes/tools.js +180 -0
  468. package/dist/daemon/routes/tools.js.map +1 -0
  469. package/dist/daemon/routes/updates.d.ts +4 -0
  470. package/dist/daemon/routes/updates.js +89 -0
  471. package/dist/daemon/routes/updates.js.map +1 -0
  472. package/dist/daemon/routes/voice.d.ts +4 -0
  473. package/dist/daemon/routes/voice.js +53 -0
  474. package/dist/daemon/routes/voice.js.map +1 -0
  475. package/dist/daemon/runner.d.ts +186 -0
  476. package/dist/daemon/runner.js +612 -0
  477. package/dist/daemon/runner.js.map +1 -0
  478. package/dist/daemon/runtime/accounting.d.ts +130 -0
  479. package/dist/daemon/runtime/accounting.js +303 -0
  480. package/dist/daemon/runtime/accounting.js.map +1 -0
  481. package/dist/daemon/runtime/agents.d.ts +16 -0
  482. package/dist/daemon/runtime/agents.js +71 -0
  483. package/dist/daemon/runtime/agents.js.map +1 -0
  484. package/dist/daemon/runtime/briefings.d.ts +134 -0
  485. package/dist/daemon/runtime/briefings.js +329 -0
  486. package/dist/daemon/runtime/briefings.js.map +1 -0
  487. package/dist/daemon/runtime/native-usage.d.ts +21 -0
  488. package/dist/daemon/runtime/native-usage.js +71 -0
  489. package/dist/daemon/runtime/native-usage.js.map +1 -0
  490. package/dist/daemon/runtime/queue.d.ts +94 -0
  491. package/dist/daemon/runtime/queue.js +222 -0
  492. package/dist/daemon/runtime/queue.js.map +1 -0
  493. package/dist/daemon/runtime/relay.d.ts +93 -0
  494. package/dist/daemon/runtime/relay.js +240 -0
  495. package/dist/daemon/runtime/relay.js.map +1 -0
  496. package/dist/daemon/runtime/turns.d.ts +202 -0
  497. package/dist/daemon/runtime/turns.js +663 -0
  498. package/dist/daemon/runtime/turns.js.map +1 -0
  499. package/dist/daemon/runtime-support.d.ts +76 -0
  500. package/dist/daemon/runtime-support.js +134 -0
  501. package/dist/daemon/runtime-support.js.map +1 -0
  502. package/dist/daemon/runtime.d.ts +739 -70
  503. package/dist/daemon/runtime.js +2751 -470
  504. package/dist/daemon/runtime.js.map +1 -1
  505. package/dist/daemon/server.d.ts +82 -55
  506. package/dist/daemon/server.js +674 -1435
  507. package/dist/daemon/server.js.map +1 -1
  508. package/dist/daemon/shift.d.ts +70 -0
  509. package/dist/daemon/shift.js +170 -0
  510. package/dist/daemon/shift.js.map +1 -0
  511. package/dist/daemon/specs.d.ts +92 -0
  512. package/dist/daemon/specs.js +285 -0
  513. package/dist/daemon/specs.js.map +1 -0
  514. package/dist/daemon/system.d.ts +208 -0
  515. package/dist/daemon/system.js +557 -0
  516. package/dist/daemon/system.js.map +1 -0
  517. package/dist/daemon/tasks.js +1 -1
  518. package/dist/daemon/team-brain.d.ts +136 -0
  519. package/dist/daemon/team-brain.js +659 -0
  520. package/dist/daemon/team-brain.js.map +1 -0
  521. package/dist/daemon/team-coordinator.d.ts +78 -0
  522. package/dist/daemon/team-coordinator.js +408 -0
  523. package/dist/daemon/team-coordinator.js.map +1 -0
  524. package/dist/daemon/team.d.ts +339 -0
  525. package/dist/daemon/team.js +1433 -0
  526. package/dist/daemon/team.js.map +1 -0
  527. package/dist/daemon/terminals.d.ts +22 -3
  528. package/dist/daemon/terminals.js +236 -16
  529. package/dist/daemon/terminals.js.map +1 -1
  530. package/dist/hub/client.d.ts +120 -0
  531. package/dist/hub/client.js +228 -0
  532. package/dist/hub/client.js.map +1 -0
  533. package/dist/hub/server.d.ts +32 -0
  534. package/dist/hub/server.js +180 -0
  535. package/dist/hub/server.js.map +1 -0
  536. package/dist/hub/supabase-client.d.ts +220 -0
  537. package/dist/hub/supabase-client.js +843 -0
  538. package/dist/hub/supabase-client.js.map +1 -0
  539. package/dist/observability/ask.d.ts +91 -0
  540. package/dist/observability/ask.js +179 -0
  541. package/dist/observability/ask.js.map +1 -0
  542. package/dist/observability/clickhouse.d.ts +33 -0
  543. package/dist/observability/clickhouse.js +48 -0
  544. package/dist/observability/clickhouse.js.map +1 -0
  545. package/dist/observability/decisions.d.ts +176 -0
  546. package/dist/observability/decisions.js +377 -0
  547. package/dist/observability/decisions.js.map +1 -0
  548. package/dist/observability/index.d.ts +89 -0
  549. package/dist/observability/index.js +348 -0
  550. package/dist/observability/index.js.map +1 -0
  551. package/dist/observability/insights.d.ts +151 -0
  552. package/dist/observability/insights.js +319 -0
  553. package/dist/observability/insights.js.map +1 -0
  554. package/dist/observability/logs-query.d.ts +82 -0
  555. package/dist/observability/logs-query.js +102 -0
  556. package/dist/observability/logs-query.js.map +1 -0
  557. package/dist/observability/logs.d.ts +72 -0
  558. package/dist/observability/logs.js +295 -0
  559. package/dist/observability/logs.js.map +1 -0
  560. package/dist/observability/metrics.d.ts +86 -0
  561. package/dist/observability/metrics.js +303 -0
  562. package/dist/observability/metrics.js.map +1 -0
  563. package/dist/observability/otlp.d.ts +127 -0
  564. package/dist/observability/otlp.js +242 -0
  565. package/dist/observability/otlp.js.map +1 -0
  566. package/dist/observability/snapshots.d.ts +43 -0
  567. package/dist/observability/snapshots.js +129 -0
  568. package/dist/observability/snapshots.js.map +1 -0
  569. package/dist/observability/triage.d.ts +49 -0
  570. package/dist/observability/triage.js +284 -0
  571. package/dist/observability/triage.js.map +1 -0
  572. package/dist/providers/adapter.d.ts +50 -0
  573. package/dist/providers/adapter.js +38 -0
  574. package/dist/providers/adapter.js.map +1 -0
  575. package/dist/providers/agent.d.ts +110 -0
  576. package/dist/providers/agent.js +487 -0
  577. package/dist/providers/agent.js.map +1 -0
  578. package/dist/providers/approvals.d.ts +24 -0
  579. package/dist/providers/approvals.js +78 -0
  580. package/dist/providers/approvals.js.map +1 -0
  581. package/dist/providers/attachments.d.ts +20 -0
  582. package/dist/providers/attachments.js +51 -0
  583. package/dist/providers/attachments.js.map +1 -0
  584. package/dist/providers/claude/adapter.d.ts +99 -0
  585. package/dist/providers/claude/adapter.js +790 -0
  586. package/dist/providers/claude/adapter.js.map +1 -0
  587. package/dist/providers/codex/adapter.d.ts +113 -0
  588. package/dist/providers/codex/adapter.js +881 -0
  589. package/dist/providers/codex/adapter.js.map +1 -0
  590. package/dist/providers/codex/instructions.d.ts +11 -0
  591. package/dist/providers/codex/instructions.js +154 -0
  592. package/dist/providers/codex/instructions.js.map +1 -0
  593. package/dist/providers/codex/rpc.d.ts +36 -0
  594. package/dist/providers/codex/rpc.js +104 -0
  595. package/dist/providers/codex/rpc.js.map +1 -0
  596. package/dist/providers/contracts.d.ts +339 -0
  597. package/dist/providers/contracts.js +19 -0
  598. package/dist/providers/contracts.js.map +1 -0
  599. package/dist/providers/directory.d.ts +70 -0
  600. package/dist/providers/directory.js +95 -0
  601. package/dist/providers/directory.js.map +1 -0
  602. package/dist/providers/errors.d.ts +47 -0
  603. package/dist/providers/errors.js +24 -0
  604. package/dist/providers/errors.js.map +1 -0
  605. package/dist/providers/index.d.ts +18 -0
  606. package/dist/providers/index.js +19 -0
  607. package/dist/providers/index.js.map +1 -0
  608. package/dist/providers/ingestion.d.ts +80 -0
  609. package/dist/providers/ingestion.js +330 -0
  610. package/dist/providers/ingestion.js.map +1 -0
  611. package/dist/providers/live.d.ts +22 -0
  612. package/dist/providers/live.js +56 -0
  613. package/dist/providers/live.js.map +1 -0
  614. package/dist/providers/process.d.ts +29 -0
  615. package/dist/providers/process.js +71 -0
  616. package/dist/providers/process.js.map +1 -0
  617. package/dist/providers/reaper.d.ts +33 -0
  618. package/dist/providers/reaper.js +65 -0
  619. package/dist/providers/reaper.js.map +1 -0
  620. package/dist/providers/service.d.ts +87 -0
  621. package/dist/providers/service.js +256 -0
  622. package/dist/providers/service.js.map +1 -0
  623. package/dist/sdk.d.ts +1 -1
  624. package/dist/sdk.js +1 -1
  625. package/dist/sdk.js.map +1 -1
  626. package/dist/types.d.ts +385 -6
  627. package/dist/types.js +1 -0
  628. package/dist/types.js.map +1 -1
  629. package/dist/version.d.ts +7 -0
  630. package/dist/version.js +16 -0
  631. package/dist/version.js.map +1 -0
  632. package/dist/web/app.css +4643 -0
  633. package/dist/web/app.js +22640 -0
  634. package/dist/web/app.js.map +7 -0
  635. package/dist/web/shell.html +39 -0
  636. package/docs/README.md +21 -0
  637. package/docs/SETUP.md +10 -0
  638. package/docs/adapters.md +21 -8
  639. package/docs/brain-continuity.md +186 -0
  640. package/docs/cloud.md +108 -0
  641. package/docs/crews.md +62 -0
  642. package/docs/design-system.md +31 -2
  643. package/docs/integration-notes.md +128 -20
  644. package/docs/loompad.md +105 -0
  645. package/docs/observability.md +220 -0
  646. package/docs/play-store.md +123 -0
  647. package/docs/proposals/agent-teams.md +249 -0
  648. package/docs/proposals/brain-continuity-implementation.md +468 -0
  649. package/docs/proposals/brain-session-continuity.md +623 -0
  650. package/docs/proposals/t3code-port.md +81 -0
  651. package/docs/proposals/t3code-reference.md +95 -0
  652. package/docs/refactoring/AGENT-TEAMS-PROGRESS.md +88 -0
  653. package/docs/refactoring/BOUNDARIES.md +120 -0
  654. package/docs/refactoring/BRAIN-AUDIT.md +176 -0
  655. package/docs/refactoring/BRAIN-NOTES.md +390 -0
  656. package/docs/refactoring/BRAIN-TODO.md +214 -0
  657. package/docs/refactoring/NOTES.md +129 -0
  658. package/docs/refactoring/T3-PORT-NOTES.md +165 -0
  659. package/docs/refactoring/T3-PORT-TODO.md +123 -0
  660. package/docs/refactoring/TODO.md +46 -0
  661. package/docs/refactoring/brain-benchmark-2026-09-28.json +48 -0
  662. package/docs/refactoring/brain-benchmark-audit-2026-09-28.json +48 -0
  663. package/docs/release-notes/v0.2.0.md +35 -0
  664. package/docs/release-notes/v0.2.1.md +59 -0
  665. package/docs/release-notes/v0.2.3.md +77 -0
  666. package/docs/release-notes/v0.2.4.md +128 -0
  667. package/docs/release-notes/v0.2.5.md +153 -0
  668. package/docs/release-notes/v0.2.6.md +62 -0
  669. package/docs/release-notes/v1.0.0.md +120 -0
  670. package/docs/release-notes/v1.0.1.md +77 -0
  671. package/docs/release-notes/v1.1.0.md +71 -0
  672. package/docs/release-notes/v1.1.1.md +38 -0
  673. package/docs/teams-architecture.md +697 -0
  674. package/docs/teams.md +171 -0
  675. package/docs/windows.md +85 -0
  676. package/package.json +22 -9
  677. package/dist/adapters/claude-code.d.ts +0 -26
  678. package/dist/adapters/claude-code.js +0 -209
  679. package/dist/adapters/claude-code.js.map +0 -1
  680. package/dist/adapters/codex.d.ts +0 -51
  681. package/dist/adapters/codex.js +0 -291
  682. package/dist/adapters/codex.js.map +0 -1
  683. package/docs/img/board-linear.png +0 -0
  684. package/docs/img/board-projects.png +0 -0
  685. package/docs/img/brain.png +0 -0
  686. package/docs/img/settings.png +0 -0
  687. package/docs/img/workspace.png +0 -0
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Night Shift: line up work, go to sleep, wake up to a report you can trust.
3
+ *
4
+ * Each task runs in its own chat, one after another, through the queue. While
5
+ * the shift runs, Proof checks every "done" against what ran and a clean
6
+ * re-run, the loop watch nudges an agent going in circles, and Quota Relay
7
+ * hands work on when an agent runs out. This file is the part that reads a
8
+ * task's chat afterwards and says, plainly, how it went. Pure.
9
+ */
10
+ import type { LoomEvent } from "../types.js";
11
+ export type ShiftTaskStatus = "verified" | "done" | "didnt_hold" | "failed" | "needs_you" | "not_started" | "running";
12
+ export interface ShiftTask {
13
+ text: string;
14
+ chat: string;
15
+ /** The queue item it went out as, until it's dispatched. */
16
+ queueId?: string;
17
+ /** Set once the shift knows it won't run (stopped by the clock or the budget). */
18
+ skipped?: boolean;
19
+ }
20
+ export interface ShiftConfig {
21
+ id: string;
22
+ startedAt: number;
23
+ /** Who takes the tasks; absent → whoever the queue would pick (auto). */
24
+ to?: string;
25
+ /** Stop starting new tasks after this moment (epoch ms). */
26
+ until?: number;
27
+ /** Stop starting new tasks once the shift has spent this much. */
28
+ maxUsd?: number;
29
+ tasks: ShiftTask[];
30
+ /** Set when it stops early, with why. */
31
+ stopped?: string;
32
+ /** Set once the report has gone out. */
33
+ reportedAt?: number;
34
+ }
35
+ export interface TaskOutcome {
36
+ text: string;
37
+ chat: string;
38
+ status: ShiftTaskStatus;
39
+ agent?: string;
40
+ /** Proof's one-line verdict, when it checked. */
41
+ headline?: string;
42
+ /** Times the loop watch caught it going in circles. */
43
+ loops: number;
44
+ files: string[];
45
+ costUsd: number;
46
+ ms: number;
47
+ error?: string;
48
+ question?: string;
49
+ }
50
+ export interface ShiftReport {
51
+ shiftId: string;
52
+ startedAt: number;
53
+ endedAt: number;
54
+ tasks: TaskOutcome[];
55
+ counts: Record<ShiftTaskStatus, number>;
56
+ costUsd: number;
57
+ stopped?: string;
58
+ /** "4 tasks: 3 verified, 1 needs you" */
59
+ summary: string;
60
+ }
61
+ /** How one task went, from its chat's events. */
62
+ export declare function outcomeOf(task: ShiftTask, events: LoomEvent[], opts?: {
63
+ queued?: boolean;
64
+ }): TaskOutcome;
65
+ export declare function report(shift: ShiftConfig, outcomes: TaskOutcome[], endedAt?: number): ShiftReport;
66
+ /** "7:00", "07:30", "7am", "6:45 pm", or an ISO time → the next such moment after `now`. */
67
+ export declare function parseUntil(raw: string | number | undefined, now?: number): number | undefined;
68
+ /** One task per line; blank lines and "- " / "1. " list markers dropped. */
69
+ export declare function parseTasks(raw: string | string[]): string[];
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Night Shift: line up work, go to sleep, wake up to a report you can trust.
3
+ *
4
+ * Each task runs in its own chat, one after another, through the queue. While
5
+ * the shift runs, Proof checks every "done" against what ran and a clean
6
+ * re-run, the loop watch nudges an agent going in circles, and Quota Relay
7
+ * hands work on when an agent runs out. This file is the part that reads a
8
+ * task's chat afterwards and says, plainly, how it went. Pure.
9
+ */
10
+ /** How one task went, from its chat's events. */
11
+ export function outcomeOf(task, events, opts = {}) {
12
+ const base = { text: task.text, chat: task.chat, status: "not_started", loops: 0, files: [], costUsd: 0, ms: 0 };
13
+ if (opts.queued || task.skipped)
14
+ return base;
15
+ const files = new Set();
16
+ let lastDone, lastProof, lastAsk, lastSay, lastAnswer, lastError;
17
+ for (const e of events) {
18
+ const p = e.payload ?? {};
19
+ if (e.kind === "run_complete" && e.agentId) {
20
+ lastDone = e;
21
+ base.ms += Number(p.durationMs ?? 0) || 0;
22
+ base.agent = e.agentId;
23
+ }
24
+ else if (e.kind === "proof")
25
+ lastProof = e;
26
+ else if (e.kind === "needs_input")
27
+ lastAsk = e;
28
+ else if (e.kind === "message" && !e.agentId)
29
+ lastSay = e;
30
+ else if (e.kind === "error" && e.agentId)
31
+ lastError = e;
32
+ else if (e.kind === "turn_diff")
33
+ for (const f of p.files ?? [])
34
+ files.add(f.path);
35
+ else if (e.kind === "status" && p.state === "question_answered")
36
+ lastAnswer = e;
37
+ else if (e.kind === "status" && p.state === "loop_detected")
38
+ base.loops++;
39
+ else if (e.kind === "status" && p.state === "turn_cost")
40
+ base.costUsd += Number(p.costUsd ?? 0) || 0;
41
+ }
42
+ base.files = [...files];
43
+ base.costUsd = Math.round(base.costUsd * 10_000) / 10_000;
44
+ if (lastDone?.payload.error)
45
+ return { ...base, status: "failed", error: String(lastDone.payload.error).slice(0, 300) };
46
+ // a turn that died with an error (no clean end after it, nothing said since)
47
+ if (lastError && (!lastDone || lastError.id > lastDone.id) && (!lastSay || lastError.id > lastSay.id)) {
48
+ return { ...base, status: "failed", agent: lastError.agentId ?? base.agent, error: String(lastError.payload.message ?? "it stopped with an error").slice(0, 300) };
49
+ }
50
+ // an open question: asked after the last thing you (or the queue) said, and not answered — whether the
51
+ // agent's turn is parked on it or ended on it, it's waiting for you
52
+ if (lastAsk && (!lastSay || lastAsk.id > lastSay.id) && (!lastAnswer || lastAsk.id > lastAnswer.id)) {
53
+ return { ...base, status: "needs_you", agent: lastAsk.agentId ?? base.agent, question: String(lastAsk.payload.question ?? "") };
54
+ }
55
+ if (!lastDone)
56
+ return { ...base, status: lastSay ? "running" : "not_started" };
57
+ // Proof only counts when it checked the last turn (a nudge can come after it)
58
+ if (lastProof && lastProof.id > lastDone.id) {
59
+ const v = String(lastProof.payload.verdict ?? "");
60
+ const headline = String(lastProof.payload.headline ?? "");
61
+ if (v === "contradicted")
62
+ return { ...base, status: "didnt_hold", headline };
63
+ if (v === "verified")
64
+ return { ...base, status: "verified", headline };
65
+ return { ...base, status: "done", headline };
66
+ }
67
+ return { ...base, status: "done" };
68
+ }
69
+ const WORDS = {
70
+ verified: "verified", done: "done (not checked)", didnt_hold: "didn't hold", failed: "failed",
71
+ needs_you: "need you", not_started: "not started", running: "still running",
72
+ };
73
+ const ORDER = ["verified", "done", "didnt_hold", "failed", "needs_you", "running", "not_started"];
74
+ export function report(shift, outcomes, endedAt = Date.now()) {
75
+ const counts = Object.fromEntries(ORDER.map((s) => [s, 0]));
76
+ for (const o of outcomes)
77
+ counts[o.status]++;
78
+ const parts = ORDER.filter((s) => counts[s]).map((s) => `${counts[s]} ${s === "needs_you" && counts[s] === 1 ? "needs you" : WORDS[s]}`);
79
+ const n = outcomes.length;
80
+ return {
81
+ shiftId: shift.id, startedAt: shift.startedAt, endedAt, tasks: outcomes, counts,
82
+ costUsd: Math.round(outcomes.reduce((x, o) => x + o.costUsd, 0) * 10_000) / 10_000,
83
+ ...(shift.stopped ? { stopped: shift.stopped } : {}),
84
+ summary: `${n} task${n === 1 ? "" : "s"}: ${parts.join(", ")}`,
85
+ };
86
+ }
87
+ /** "7:00", "07:30", "7am", "6:45 pm", or an ISO time → the next such moment after `now`. */
88
+ export function parseUntil(raw, now = Date.now()) {
89
+ if (raw === undefined || raw === "")
90
+ return undefined;
91
+ if (typeof raw === "number")
92
+ return Number.isFinite(raw) ? raw : undefined;
93
+ const m = /^\s*(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*$/i.exec(raw);
94
+ if (m) {
95
+ let h = Number(m[1]);
96
+ const min = Number(m[2] ?? 0);
97
+ const ap = m[3]?.toLowerCase();
98
+ if (ap === "pm" && h < 12)
99
+ h += 12;
100
+ if (ap === "am" && h === 12)
101
+ h = 0;
102
+ if (h > 23 || min > 59)
103
+ throw new Error(`"${raw}" isn't a time of day`);
104
+ const d = new Date(now);
105
+ d.setHours(h, min, 0, 0);
106
+ if (d.getTime() <= now)
107
+ d.setDate(d.getDate() + 1);
108
+ return d.getTime();
109
+ }
110
+ const t = Date.parse(raw);
111
+ if (!Number.isFinite(t))
112
+ throw new Error(`"${raw}" isn't a time I can read — try 7:00, 6:30am or an ISO time`);
113
+ return t;
114
+ }
115
+ /** One task per line; blank lines and "- " / "1. " list markers dropped. */
116
+ export function parseTasks(raw) {
117
+ const lines = Array.isArray(raw) ? raw : raw.split("\n");
118
+ return lines.map((l) => String(l).replace(/^\s*(?:[-*•]|\d+[.)])\s+/, "").trim()).filter(Boolean).slice(0, 50);
119
+ }
120
+ //# sourceMappingURL=shift.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shift.js","sourceRoot":"","sources":["../../src/core/shift.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Installing a skill — copying somebody else's SKILL.md into this project.
3
+ *
4
+ * Discovery (core/skills.ts) answers "what do I already have". This answers the
5
+ * other half: a skill you found in a gist, a repo, or a folder on disk becomes
6
+ * `<projectDir>/skills/<id>/`, which is the first root the loader scans and the
7
+ * only one Loom is allowed to write to.
8
+ *
9
+ * ## Why so much of this file is refusal
10
+ *
11
+ * Both entry points take a string from an HTTP body and turn it into filesystem
12
+ * writes, so the interesting cases are all the ones that must not happen:
13
+ *
14
+ * - **The id is a path.** `id` is joined onto `<projectDir>/skills/`, so an id
15
+ * of `../../.ssh` writes outside the project. It is validated against a
16
+ * character class rather than sanitised, because silently rewriting a
17
+ * malicious id into a benign one installs a skill under a name the caller
18
+ * didn't ask for, and then everything downstream is about the wrong skill.
19
+ *
20
+ * - **The URL is a command.** `installSkillFromGit` runs git. Every argument
21
+ * goes through `execFile` with an array — never a shell string — so a URL
22
+ * containing `;` or `$(…)` is a URL git will fail to clone, not a command.
23
+ * The URL is also checked to be http(s) or ssh git first: git happily
24
+ * accepts `ext::sh -c …` and `--upload-pack=…` as "remotes", and those are
25
+ * code execution with extra steps.
26
+ *
27
+ * - **An overwrite is silent.** Installing over an existing skill destroys
28
+ * whatever the user wrote there. It needs `force`, and the error says which
29
+ * id collided so the UI can offer the choice instead of guessing.
30
+ *
31
+ * A clone is bounded (90s) and runs with `GIT_TERMINAL_PROMPT=0`: a private repo
32
+ * must fail as a private repo, not hang a request forever on a credential prompt
33
+ * that nobody is at a terminal to answer.
34
+ */
35
+ /** A refusal with a reason the UI can show verbatim. Distinct so routes can 400 it. */
36
+ export declare class SkillInstallError extends Error {
37
+ constructor(message: string);
38
+ }
39
+ /** Where an installed skill came from — recorded so the caller can say so. */
40
+ export type SkillInstallOrigin = "dir" | "git";
41
+ export interface SkillInstallResult {
42
+ /** The directory name under `<projectDir>/skills/`, and the skill's id. */
43
+ id: string;
44
+ /** The `name:` from the frontmatter. */
45
+ name: string;
46
+ description: string;
47
+ /** Absolute path to the installed directory. */
48
+ dir: string;
49
+ /** Absolute path to the installed SKILL.md. */
50
+ path: string;
51
+ /** How it arrived: a local directory, or a clone. */
52
+ from: SkillInstallOrigin;
53
+ /** The exact source string the caller gave — the src dir, or the git URL. */
54
+ source: string;
55
+ /** True when an existing skill of the same id was replaced (needs `force`). */
56
+ replaced: boolean;
57
+ /** With #all: the other skills installed from the same repo. */
58
+ also?: string[];
59
+ }
60
+ export interface InstallOptions {
61
+ /** Replace an existing skill of the same id instead of refusing. */
62
+ force?: boolean;
63
+ /**
64
+ * Install under this id instead of the source directory's name. Validated
65
+ * exactly like a derived id — the caller doesn't get a shortcut past the
66
+ * traversal check by supplying the id itself.
67
+ */
68
+ id?: string;
69
+ }
70
+ /**
71
+ * Copy a skill directory into the project.
72
+ *
73
+ * The whole directory comes across, not just SKILL.md: skills reference scripts
74
+ * and `references/` files next to them, and a skill installed without its
75
+ * attachments is a skill whose instructions point at nothing.
76
+ */
77
+ /**
78
+ * Author a skill in place: scaffold `skills/<id>/SKILL.md`, validated.
79
+ *
80
+ * Skills could be browsed and installed but never written — authoring one
81
+ * meant knowing the frontmatter convention by heart and hand-building the
82
+ * directory. The validation is the same reader the roster uses (parseSkillMd),
83
+ * so a skill this accepts is a skill the loader will actually offer: refusing
84
+ * to write anything the roster would silently drop is the whole point.
85
+ */
86
+ export declare function authorSkill(projectDir: string, input: {
87
+ id: string;
88
+ name: string;
89
+ description: string;
90
+ body?: string;
91
+ }): {
92
+ dir: string;
93
+ file: string;
94
+ };
95
+ export declare function installSkillFromDir(srcDir: string, projectDir: string, opts?: InstallOptions): SkillInstallResult;
96
+ /**
97
+ * Clone a repository and install the skill inside it.
98
+ *
99
+ * `--depth 1` because history is irrelevant to a directory of markdown, and a
100
+ * shallow clone of a large skills repo is the difference between a second and a
101
+ * minute. The SKILL.md is looked for at the repo root first and then one level
102
+ * down — the two layouts people actually publish ("this repo IS a skill" and
103
+ * "this repo is a folder of skills"). Deeper than that is ambiguous: a repo with
104
+ * `a/b/SKILL.md` and `c/d/SKILL.md` has no obvious answer, and picking one is
105
+ * how you install something the user didn't choose.
106
+ *
107
+ * The temp clone is removed on every path, including the failures.
108
+ */
109
+ export declare function installSkillFromGit(url: string, projectDir: string, opts?: InstallOptions & {
110
+ timeoutMs?: number;
111
+ }): Promise<SkillInstallResult>;
112
+ /**
113
+ * The repo root if it is itself a skill, else the first child directory that is.
114
+ *
115
+ * Exported for the tests: the two layouts this handles are the whole of the
116
+ * git installer's judgement, and a clone is the one part of that path a test
117
+ * can't reach offline.
118
+ */
119
+ export declare function findSkillDir(root: string): string | null;
120
+ /**
121
+ * Every skill in a checkout: the root if it is one; else each child directory
122
+ * that is; else each one under skills/ (and .claude/skills/) — the layouts
123
+ * people publish.
124
+ */
125
+ export declare function findSkillDirs(root: string): string[];
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Installing a skill — copying somebody else's SKILL.md into this project.
3
+ *
4
+ * Discovery (core/skills.ts) answers "what do I already have". This answers the
5
+ * other half: a skill you found in a gist, a repo, or a folder on disk becomes
6
+ * `<projectDir>/skills/<id>/`, which is the first root the loader scans and the
7
+ * only one Loom is allowed to write to.
8
+ *
9
+ * ## Why so much of this file is refusal
10
+ *
11
+ * Both entry points take a string from an HTTP body and turn it into filesystem
12
+ * writes, so the interesting cases are all the ones that must not happen:
13
+ *
14
+ * - **The id is a path.** `id` is joined onto `<projectDir>/skills/`, so an id
15
+ * of `../../.ssh` writes outside the project. It is validated against a
16
+ * character class rather than sanitised, because silently rewriting a
17
+ * malicious id into a benign one installs a skill under a name the caller
18
+ * didn't ask for, and then everything downstream is about the wrong skill.
19
+ *
20
+ * - **The URL is a command.** `installSkillFromGit` runs git. Every argument
21
+ * goes through `execFile` with an array — never a shell string — so a URL
22
+ * containing `;` or `$(…)` is a URL git will fail to clone, not a command.
23
+ * The URL is also checked to be http(s) or ssh git first: git happily
24
+ * accepts `ext::sh -c …` and `--upload-pack=…` as "remotes", and those are
25
+ * code execution with extra steps.
26
+ *
27
+ * - **An overwrite is silent.** Installing over an existing skill destroys
28
+ * whatever the user wrote there. It needs `force`, and the error says which
29
+ * id collided so the UI can offer the choice instead of guessing.
30
+ *
31
+ * A clone is bounded (90s) and runs with `GIT_TERMINAL_PROMPT=0`: a private repo
32
+ * must fail as a private repo, not hang a request forever on a credential prompt
33
+ * that nobody is at a terminal to answer.
34
+ */
35
+ import { execFile } from "./process.js";
36
+ import fs from "node:fs";
37
+ import os from "node:os";
38
+ import path from "node:path";
39
+ import { parseSkillMd } from "./skills.js";
40
+ /** A refusal with a reason the UI can show verbatim. Distinct so routes can 400 it. */
41
+ export class SkillInstallError extends Error {
42
+ constructor(message) {
43
+ super(message);
44
+ this.name = "SkillInstallError";
45
+ }
46
+ }
47
+ /**
48
+ * A skill id is a single directory name and nothing else.
49
+ *
50
+ * Letters, digits, dot, dash, underscore. No separators (so it cannot escape),
51
+ * no leading dot (so it cannot be `.` , `..`, or a hidden directory that the
52
+ * loader would then never list), and a length cap so a 4KB id can't be used to
53
+ * probe path limits.
54
+ */
55
+ const ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
56
+ function assertSafeId(id) {
57
+ const clean = String(id ?? "").trim();
58
+ if (!clean)
59
+ throw new SkillInstallError("skill id is empty — cannot tell where to install it");
60
+ if (!ID_RE.test(clean) || clean.includes("..")) {
61
+ throw new SkillInstallError(`"${clean}" is not a usable skill id — it must be a single directory name of letters, digits, dot, dash or underscore`);
62
+ }
63
+ return clean;
64
+ }
65
+ /**
66
+ * Read and validate the SKILL.md that makes a directory a skill.
67
+ *
68
+ * A directory with no SKILL.md is not a skill, and one whose frontmatter has no
69
+ * `name:` is a skill nothing can refer to — the loader would fall back to the
70
+ * directory name, which means installing it produces a manifest whose name was
71
+ * invented here. Both are refused with the path, because "it didn't work" is
72
+ * useless when you cloned a repo with four candidate folders in it.
73
+ */
74
+ function readManifest(dir) {
75
+ const file = path.join(dir, "SKILL.md");
76
+ let raw;
77
+ try {
78
+ raw = fs.readFileSync(file, "utf8");
79
+ }
80
+ catch {
81
+ throw new SkillInstallError(`no readable SKILL.md in ${dir} — a skill is a directory containing SKILL.md`);
82
+ }
83
+ const { name, description } = parseSkillMd(raw);
84
+ if (!name.trim()) {
85
+ throw new SkillInstallError(`${file} has no \`name:\` in its frontmatter — a skill needs one to be referred to`);
86
+ }
87
+ return { name: name.trim(), description, file };
88
+ }
89
+ /**
90
+ * Copy a skill directory into the project.
91
+ *
92
+ * The whole directory comes across, not just SKILL.md: skills reference scripts
93
+ * and `references/` files next to them, and a skill installed without its
94
+ * attachments is a skill whose instructions point at nothing.
95
+ */
96
+ /**
97
+ * Author a skill in place: scaffold `skills/<id>/SKILL.md`, validated.
98
+ *
99
+ * Skills could be browsed and installed but never written — authoring one
100
+ * meant knowing the frontmatter convention by heart and hand-building the
101
+ * directory. The validation is the same reader the roster uses (parseSkillMd),
102
+ * so a skill this accepts is a skill the loader will actually offer: refusing
103
+ * to write anything the roster would silently drop is the whole point.
104
+ */
105
+ export function authorSkill(projectDir, input) {
106
+ const id = input.id.trim().toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-+|-+$/g, "");
107
+ if (!id)
108
+ throw new SkillInstallError("a skill needs an id (letters, digits, dashes)");
109
+ const name = input.name.trim();
110
+ if (!name)
111
+ throw new SkillInstallError("a skill needs a name");
112
+ const description = input.description.trim();
113
+ if (!description) {
114
+ throw new SkillInstallError("a skill needs a description — it's how agents decide when to use it");
115
+ }
116
+ const skillsDir = path.join(projectDir, "skills");
117
+ const dest = path.join(skillsDir, id);
118
+ if (path.dirname(dest) !== skillsDir) {
119
+ throw new SkillInstallError(`refusing "${id}" — it resolves outside ${skillsDir}`);
120
+ }
121
+ const file = path.join(dest, "SKILL.md");
122
+ if (fs.existsSync(file)) {
123
+ throw new SkillInstallError(`"${id}" already exists — edit ${file} instead`);
124
+ }
125
+ const md = `---\n` +
126
+ `name: ${name}\n` +
127
+ `description: ${description}\n` +
128
+ `---\n\n` +
129
+ (input.body?.trim()
130
+ ? `${input.body.trim()}\n`
131
+ : `# ${name}\n\nWrite the instructions an agent should follow here.\n\n` +
132
+ `## When to use\n\n${description}\n\n## Steps\n\n1. …\n`);
133
+ // Round-trip through the real parser before anything touches disk.
134
+ const parsed = parseSkillMd(md);
135
+ if (parsed.name !== name || parsed.description !== description) {
136
+ throw new SkillInstallError("the generated frontmatter didn't survive the parser — check for YAML-breaking characters");
137
+ }
138
+ fs.mkdirSync(dest, { recursive: true });
139
+ fs.writeFileSync(file, md);
140
+ return { dir: dest, file };
141
+ }
142
+ export function installSkillFromDir(srcDir, projectDir, opts = {}) {
143
+ const src = path.resolve(String(srcDir ?? "").trim());
144
+ if (!src || src === path.parse(src).root) {
145
+ throw new SkillInstallError("no source directory given");
146
+ }
147
+ let stat;
148
+ try {
149
+ stat = fs.statSync(src);
150
+ }
151
+ catch {
152
+ throw new SkillInstallError(`${src} does not exist`);
153
+ }
154
+ if (!stat.isDirectory())
155
+ throw new SkillInstallError(`${src} is not a directory`);
156
+ const id = assertSafeId(opts.id ?? path.basename(src));
157
+ const { name, description } = readManifest(src);
158
+ const skillsDir = path.join(projectDir, "skills");
159
+ const dest = path.join(skillsDir, id);
160
+ // Belt and braces: assertSafeId already forbids separators, so this can only
161
+ // fire if that regex is ever loosened. It is cheap and it is the check that
162
+ // actually protects the filesystem, so it stays.
163
+ if (path.dirname(dest) !== skillsDir) {
164
+ throw new SkillInstallError(`refusing to install "${id}" — it resolves outside ${skillsDir}`);
165
+ }
166
+ if (path.resolve(dest) === src) {
167
+ throw new SkillInstallError(`${src} is already this project's "${id}" skill`);
168
+ }
169
+ const existed = fs.existsSync(dest);
170
+ if (existed && !opts.force) {
171
+ throw new SkillInstallError(`a skill named "${id}" is already installed — pass force to replace it`);
172
+ }
173
+ fs.mkdirSync(skillsDir, { recursive: true });
174
+ if (existed)
175
+ fs.rmSync(dest, { recursive: true, force: true });
176
+ fs.cpSync(src, dest, { recursive: true });
177
+ return {
178
+ id,
179
+ name,
180
+ description,
181
+ dir: dest,
182
+ path: path.join(dest, "SKILL.md"),
183
+ from: "dir",
184
+ source: src,
185
+ replaced: existed,
186
+ };
187
+ }
188
+ // ---------------------------------------------------------------------------
189
+ // git
190
+ // ---------------------------------------------------------------------------
191
+ /**
192
+ * The remote forms git can be handed safely.
193
+ *
194
+ * https/http and ssh (both `ssh://host/path` and the scp-ish `git@host:path`).
195
+ * Deliberately NOT `file://`, `ext::`, `--upload-pack=`, or a bare local path:
196
+ * the transport helpers behind those run programs of the remote's choosing, and
197
+ * a URL field in a web form is not where that decision should be made.
198
+ */
199
+ const GIT_URL_RE = /^(?:https?:\/\/[^\s]+|ssh:\/\/[^\s]+|[A-Za-z0-9_.-]+@[A-Za-z0-9_.-]+:[^\s]+)$/;
200
+ function assertGitUrl(url) {
201
+ const clean = String(url ?? "").trim();
202
+ if (!clean)
203
+ throw new SkillInstallError("no git URL given");
204
+ if (clean.startsWith("-")) {
205
+ // `git clone -- <url>` already stops this, but a URL that looks like a flag
206
+ // is never a real repository and the error is clearer here.
207
+ throw new SkillInstallError(`"${clean}" is not a git URL`);
208
+ }
209
+ if (!GIT_URL_RE.test(clean)) {
210
+ throw new SkillInstallError(`"${clean}" is not a git URL — expected https://…, ssh://…, or git@host:owner/repo`);
211
+ }
212
+ return clean;
213
+ }
214
+ /** The repository's own name, used as the skill id when SKILL.md sits at the root. */
215
+ function repoName(url) {
216
+ const tail = url.replace(/\/+$/, "").split(/[/:]/).pop() ?? "";
217
+ return tail.replace(/\.git$/i, "");
218
+ }
219
+ function runGit(args, cwd, timeoutMs) {
220
+ return new Promise((resolve, reject) => {
221
+ execFile("git", args, {
222
+ cwd,
223
+ timeout: timeoutMs,
224
+ maxBuffer: 4 * 1024 * 1024,
225
+ // No credential prompt, no interactive host-key question: this runs
226
+ // inside an HTTP handler where there is nobody to answer either.
227
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "echo", GCM_INTERACTIVE: "never" },
228
+ }, (err, _stdout, stderr) => {
229
+ if (err) {
230
+ const detail = String(stderr || err.message).split("\n").find((l) => l.trim())?.trim() ?? "";
231
+ reject(new SkillInstallError(detail || `git ${args[0]} failed`));
232
+ return;
233
+ }
234
+ resolve();
235
+ });
236
+ });
237
+ }
238
+ /**
239
+ * Clone a repository and install the skill inside it.
240
+ *
241
+ * `--depth 1` because history is irrelevant to a directory of markdown, and a
242
+ * shallow clone of a large skills repo is the difference between a second and a
243
+ * minute. The SKILL.md is looked for at the repo root first and then one level
244
+ * down — the two layouts people actually publish ("this repo IS a skill" and
245
+ * "this repo is a folder of skills"). Deeper than that is ambiguous: a repo with
246
+ * `a/b/SKILL.md` and `c/d/SKILL.md` has no obvious answer, and picking one is
247
+ * how you install something the user didn't choose.
248
+ *
249
+ * The temp clone is removed on every path, including the failures.
250
+ */
251
+ export async function installSkillFromGit(url, projectDir, opts = {}) {
252
+ // "#name" picks one skill out of a repo that holds several; "#all" takes every one.
253
+ const hash = url.indexOf("#");
254
+ const pick = hash >= 0 ? url.slice(hash + 1).trim() : "";
255
+ const remote = assertGitUrl(hash >= 0 ? url.slice(0, hash) : url);
256
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "loom-skill-"));
257
+ const checkout = path.join(tmp, "repo");
258
+ try {
259
+ await runGit(["clone", "--depth", "1", "--quiet", "--", remote, checkout], tmp, opts.timeoutMs ?? 90_000);
260
+ const dirs = findSkillDirs(checkout);
261
+ if (!dirs.length) {
262
+ throw new SkillInstallError(`no SKILL.md found in ${remote} — looked in the repository root, one level below it, and under skills/`);
263
+ }
264
+ const idOf = (d) => (d === checkout ? repoName(remote) : path.basename(d));
265
+ let chosen;
266
+ if (pick === "all")
267
+ chosen = dirs;
268
+ else if (pick) {
269
+ const hit = dirs.find((d) => path.basename(d) === pick);
270
+ if (!hit)
271
+ throw new SkillInstallError(`no skill "${pick}" in ${remote} — it has: ${dirs.map(idOf).join(", ")}`);
272
+ chosen = [hit];
273
+ }
274
+ else if (dirs.length === 1)
275
+ chosen = dirs;
276
+ else {
277
+ // Several skills and no choice: say so, rather than install whichever sorts first.
278
+ throw new SkillInstallError(`${remote} holds ${dirs.length} skills: ${dirs.map(idOf).slice(0, 12).join(", ")}${dirs.length > 12 ? ", …" : ""} — add #<name> to the URL to pick one, or #all for every one`);
279
+ }
280
+ const results = chosen.map((d) => installSkillFromDir(d, projectDir, { ...opts, id: chosen.length === 1 ? (opts.id ?? idOf(d)) : idOf(d) }));
281
+ return { ...results[0], from: "git", source: remote, ...(results.length > 1 ? { also: results.slice(1).map((r) => r.id) } : {}) };
282
+ }
283
+ finally {
284
+ try {
285
+ fs.rmSync(tmp, { recursive: true, force: true });
286
+ }
287
+ catch {
288
+ // A leftover clone in the OS temp dir is not worth failing an install over.
289
+ }
290
+ }
291
+ }
292
+ /**
293
+ * The repo root if it is itself a skill, else the first child directory that is.
294
+ *
295
+ * Exported for the tests: the two layouts this handles are the whole of the
296
+ * git installer's judgement, and a clone is the one part of that path a test
297
+ * can't reach offline.
298
+ */
299
+ export function findSkillDir(root) {
300
+ return findSkillDirs(root)[0] ?? null;
301
+ }
302
+ /**
303
+ * Every skill in a checkout: the root if it is one; else each child directory
304
+ * that is; else each one under skills/ (and .claude/skills/) — the layouts
305
+ * people publish.
306
+ */
307
+ export function findSkillDirs(root) {
308
+ if (fs.existsSync(path.join(root, "SKILL.md")))
309
+ return [root];
310
+ const kids = (dir) => {
311
+ try {
312
+ return fs
313
+ .readdirSync(dir, { withFileTypes: true })
314
+ .filter((d) => d.isDirectory() && !d.name.startsWith("."))
315
+ .map((d) => path.join(dir, d.name))
316
+ .filter((d) => fs.existsSync(path.join(d, "SKILL.md")))
317
+ .sort();
318
+ }
319
+ catch {
320
+ return [];
321
+ }
322
+ };
323
+ const top = kids(root);
324
+ if (top.length)
325
+ return top;
326
+ return [...kids(path.join(root, "skills")), ...kids(path.join(root, ".claude", "skills"))];
327
+ }
328
+ //# sourceMappingURL=skill-install.js.map