lastlight 0.1.15 → 0.2.0

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 (478) hide show
  1. package/README.md +265 -118
  2. package/agent-context/rules.md +62 -9
  3. package/agent-context/security.md +69 -0
  4. package/config/default.yaml +89 -0
  5. package/deploy/.env.production.example +20 -2
  6. package/deploy/entrypoint.sh +16 -17
  7. package/deploy/native/README.md +160 -0
  8. package/deploy/native/install.sh +110 -0
  9. package/deploy/native/lastlight.env.example +95 -0
  10. package/deploy/native/lastlight.service +50 -0
  11. package/deploy/sandbox-entrypoint.sh +59 -42
  12. package/dist/admin/auth.d.ts +1 -1
  13. package/dist/admin/auth.js +3 -1
  14. package/dist/admin/auth.js.map +1 -1
  15. package/dist/admin/chat-session-reader.d.ts +11 -14
  16. package/dist/admin/chat-session-reader.js +27 -51
  17. package/dist/admin/chat-session-reader.js.map +1 -1
  18. package/dist/admin/docker.d.ts +37 -0
  19. package/dist/admin/docker.js +86 -1
  20. package/dist/admin/docker.js.map +1 -1
  21. package/dist/admin/index.js +38 -3
  22. package/dist/admin/index.js.map +1 -1
  23. package/dist/admin/log-search.test.d.ts +1 -0
  24. package/dist/admin/log-search.test.js +78 -0
  25. package/dist/admin/log-search.test.js.map +1 -0
  26. package/dist/admin/routes.d.ts +52 -3
  27. package/dist/admin/routes.js +780 -63
  28. package/dist/admin/routes.js.map +1 -1
  29. package/dist/admin/routes.test.js +680 -14
  30. package/dist/admin/routes.test.js.map +1 -1
  31. package/dist/admin/server-logs.test.d.ts +1 -0
  32. package/dist/admin/server-logs.test.js +58 -0
  33. package/dist/admin/server-logs.test.js.map +1 -0
  34. package/dist/admin/sessions.d.ts +26 -39
  35. package/dist/admin/sessions.js +86 -310
  36. package/dist/admin/sessions.js.map +1 -1
  37. package/dist/admin/sessions.test.d.ts +1 -0
  38. package/dist/admin/sessions.test.js +78 -0
  39. package/dist/admin/sessions.test.js.map +1 -0
  40. package/dist/admin/version.d.ts +15 -0
  41. package/dist/admin/version.js +73 -0
  42. package/dist/admin/version.js.map +1 -0
  43. package/dist/admin/version.test.d.ts +1 -0
  44. package/dist/admin/version.test.js +71 -0
  45. package/dist/admin/version.test.js.map +1 -0
  46. package/dist/cli-config.d.ts +55 -0
  47. package/dist/cli-config.js +119 -0
  48. package/dist/cli-config.js.map +1 -0
  49. package/dist/cli-config.test.d.ts +1 -0
  50. package/dist/cli-config.test.js +92 -0
  51. package/dist/cli-config.test.js.map +1 -0
  52. package/dist/cli-format.d.ts +19 -0
  53. package/dist/cli-format.js +107 -0
  54. package/dist/cli-format.js.map +1 -0
  55. package/dist/cli-server.d.ts +73 -0
  56. package/dist/cli-server.js +347 -0
  57. package/dist/cli-server.js.map +1 -0
  58. package/dist/cli-server.test.d.ts +1 -0
  59. package/dist/cli-server.test.js +38 -0
  60. package/dist/cli-server.test.js.map +1 -0
  61. package/dist/cli-timeline.d.ts +35 -0
  62. package/dist/cli-timeline.js +373 -0
  63. package/dist/cli-timeline.js.map +1 -0
  64. package/dist/cli-timeline.test.d.ts +1 -0
  65. package/dist/cli-timeline.test.js +104 -0
  66. package/dist/cli-timeline.test.js.map +1 -0
  67. package/dist/cli.d.ts +0 -19
  68. package/dist/cli.js +910 -194
  69. package/dist/cli.js.map +1 -1
  70. package/dist/config-overlay.test.d.ts +1 -0
  71. package/dist/config-overlay.test.js +112 -0
  72. package/dist/config-overlay.test.js.map +1 -0
  73. package/dist/config-resolve.d.ts +28 -0
  74. package/dist/config-resolve.js +47 -0
  75. package/dist/config-resolve.js.map +1 -0
  76. package/dist/config-resolve.test.d.ts +1 -0
  77. package/dist/config-resolve.test.js +68 -0
  78. package/dist/config-resolve.test.js.map +1 -0
  79. package/dist/config.d.ts +62 -49
  80. package/dist/config.js +452 -76
  81. package/dist/config.js.map +1 -1
  82. package/dist/config.test.js +201 -32
  83. package/dist/config.test.js.map +1 -1
  84. package/dist/connectors/github-webhook.js +83 -5
  85. package/dist/connectors/github-webhook.js.map +1 -1
  86. package/dist/connectors/github-webhook.test.d.ts +1 -0
  87. package/dist/connectors/github-webhook.test.js +156 -0
  88. package/dist/connectors/github-webhook.test.js.map +1 -0
  89. package/dist/connectors/messaging/session-manager.d.ts +18 -0
  90. package/dist/connectors/messaging/session-manager.js +83 -2
  91. package/dist/connectors/messaging/session-manager.js.map +1 -1
  92. package/dist/connectors/messaging/session-manager.test.d.ts +1 -0
  93. package/dist/connectors/messaging/session-manager.test.js +144 -0
  94. package/dist/connectors/messaging/session-manager.test.js.map +1 -0
  95. package/dist/connectors/slack/connector.d.ts +9 -0
  96. package/dist/connectors/slack/connector.js +15 -0
  97. package/dist/connectors/slack/connector.js.map +1 -1
  98. package/dist/connectors/slack/mrkdwn.js +81 -0
  99. package/dist/connectors/slack/mrkdwn.js.map +1 -1
  100. package/dist/connectors/slack/mrkdwn.test.js +49 -0
  101. package/dist/connectors/slack/mrkdwn.test.js.map +1 -1
  102. package/dist/connectors/types.d.ts +1 -1
  103. package/dist/cron/fanout.d.ts +33 -0
  104. package/dist/cron/fanout.js +55 -0
  105. package/dist/cron/fanout.js.map +1 -0
  106. package/dist/cron/fanout.test.d.ts +1 -0
  107. package/dist/cron/fanout.test.js +73 -0
  108. package/dist/cron/fanout.test.js.map +1 -0
  109. package/dist/cron/jobs.d.ts +5 -0
  110. package/dist/cron/jobs.js +10 -3
  111. package/dist/cron/jobs.js.map +1 -1
  112. package/dist/cron/scheduler.d.ts +12 -0
  113. package/dist/cron/scheduler.js +27 -1
  114. package/dist/cron/scheduler.js.map +1 -1
  115. package/dist/engine/agent-executor.d.ts +158 -0
  116. package/dist/engine/agent-executor.js +1140 -0
  117. package/dist/engine/agent-executor.js.map +1 -0
  118. package/dist/engine/agent-executor.test.d.ts +1 -0
  119. package/dist/engine/agent-executor.test.js +395 -0
  120. package/dist/engine/agent-executor.test.js.map +1 -0
  121. package/dist/engine/chat-runner.d.ts +123 -0
  122. package/dist/engine/chat-runner.js +379 -0
  123. package/dist/engine/chat-runner.js.map +1 -0
  124. package/dist/engine/chat-runner.test.d.ts +1 -0
  125. package/dist/engine/chat-runner.test.js +104 -0
  126. package/dist/engine/chat-runner.test.js.map +1 -0
  127. package/dist/engine/chat-skills.d.ts +45 -0
  128. package/dist/engine/chat-skills.js +184 -0
  129. package/dist/engine/chat-skills.js.map +1 -0
  130. package/dist/engine/chat-skills.test.d.ts +1 -0
  131. package/dist/engine/chat-skills.test.js +20 -0
  132. package/dist/engine/chat-skills.test.js.map +1 -0
  133. package/dist/engine/chat.d.ts +20 -23
  134. package/dist/engine/chat.js +251 -155
  135. package/dist/engine/chat.js.map +1 -1
  136. package/dist/engine/chat.test.d.ts +1 -0
  137. package/dist/engine/chat.test.js +42 -0
  138. package/dist/engine/chat.test.js.map +1 -0
  139. package/dist/engine/classifier.d.ts +35 -2
  140. package/dist/engine/classifier.js +170 -31
  141. package/dist/engine/classifier.js.map +1 -1
  142. package/dist/engine/classifier.test.d.ts +1 -0
  143. package/dist/engine/classifier.test.js +115 -0
  144. package/dist/engine/classifier.test.js.map +1 -0
  145. package/dist/engine/dispatcher.d.ts +60 -0
  146. package/dist/engine/dispatcher.js +601 -0
  147. package/dist/engine/dispatcher.js.map +1 -0
  148. package/dist/engine/dispatcher.test.d.ts +1 -0
  149. package/dist/engine/dispatcher.test.js +511 -0
  150. package/dist/engine/dispatcher.test.js.map +1 -0
  151. package/dist/engine/event-shim.d.ts +117 -0
  152. package/dist/engine/event-shim.js +435 -0
  153. package/dist/engine/event-shim.js.map +1 -0
  154. package/dist/engine/event-shim.test.d.ts +1 -0
  155. package/dist/engine/event-shim.test.js +194 -0
  156. package/dist/engine/event-shim.test.js.map +1 -0
  157. package/dist/engine/git-auth.d.ts +13 -17
  158. package/dist/engine/git-auth.js +99 -42
  159. package/dist/engine/git-auth.js.map +1 -1
  160. package/dist/engine/git-auth.test.d.ts +1 -0
  161. package/dist/engine/git-auth.test.js +117 -0
  162. package/dist/engine/git-auth.test.js.map +1 -0
  163. package/dist/engine/github-app-client.d.ts +7 -0
  164. package/dist/engine/github-app-client.js +16 -0
  165. package/dist/engine/github-app-client.js.map +1 -0
  166. package/dist/engine/github-app-client.test.d.ts +1 -0
  167. package/dist/engine/github-app-client.test.js +44 -0
  168. package/dist/engine/github-app-client.test.js.map +1 -0
  169. package/dist/engine/github-tools.d.ts +12 -0
  170. package/dist/engine/github-tools.js +261 -0
  171. package/dist/engine/github-tools.js.map +1 -0
  172. package/dist/engine/github.d.ts +1027 -43
  173. package/dist/engine/github.js +153 -14
  174. package/dist/engine/github.js.map +1 -1
  175. package/dist/engine/llm.d.ts +47 -0
  176. package/dist/engine/llm.js +221 -0
  177. package/dist/engine/llm.js.map +1 -0
  178. package/dist/engine/llm.test.d.ts +1 -0
  179. package/dist/engine/llm.test.js +189 -0
  180. package/dist/engine/llm.test.js.map +1 -0
  181. package/dist/engine/profiles.d.ts +249 -0
  182. package/dist/engine/profiles.js +42 -0
  183. package/dist/engine/profiles.js.map +1 -0
  184. package/dist/engine/router.d.ts +12 -6
  185. package/dist/engine/router.js +339 -81
  186. package/dist/engine/router.js.map +1 -1
  187. package/dist/engine/router.test.js +428 -78
  188. package/dist/engine/router.test.js.map +1 -1
  189. package/dist/engine/screen.d.ts +41 -0
  190. package/dist/engine/screen.js +93 -0
  191. package/dist/engine/screen.js.map +1 -0
  192. package/dist/engine/screen.test.d.ts +1 -0
  193. package/dist/engine/screen.test.js +82 -0
  194. package/dist/engine/screen.test.js.map +1 -0
  195. package/dist/index.js +377 -432
  196. package/dist/index.js.map +1 -1
  197. package/dist/managed-repos.d.ts +7 -9
  198. package/dist/managed-repos.js +12 -15
  199. package/dist/managed-repos.js.map +1 -1
  200. package/dist/managed-repos.test.js +24 -19
  201. package/dist/managed-repos.test.js.map +1 -1
  202. package/dist/notify/index.d.ts +15 -0
  203. package/dist/notify/index.js +6 -0
  204. package/dist/notify/index.js.map +1 -0
  205. package/dist/notify/model.d.ts +57 -0
  206. package/dist/notify/model.js +89 -0
  207. package/dist/notify/model.js.map +1 -0
  208. package/dist/notify/model.test.d.ts +1 -0
  209. package/dist/notify/model.test.js +109 -0
  210. package/dist/notify/model.test.js.map +1 -0
  211. package/dist/notify/notifier.d.ts +17 -0
  212. package/dist/notify/notifier.js +87 -0
  213. package/dist/notify/notifier.js.map +1 -0
  214. package/dist/notify/notifier.test.d.ts +1 -0
  215. package/dist/notify/notifier.test.js +100 -0
  216. package/dist/notify/notifier.test.js.map +1 -0
  217. package/dist/notify/render.d.ts +21 -0
  218. package/dist/notify/render.js +59 -0
  219. package/dist/notify/render.js.map +1 -0
  220. package/dist/notify/render.test.d.ts +1 -0
  221. package/dist/notify/render.test.js +65 -0
  222. package/dist/notify/render.test.js.map +1 -0
  223. package/dist/notify/transports/github.d.ts +26 -0
  224. package/dist/notify/transports/github.js +26 -0
  225. package/dist/notify/transports/github.js.map +1 -0
  226. package/dist/notify/transports/slack.d.ts +26 -0
  227. package/dist/notify/transports/slack.js +28 -0
  228. package/dist/notify/transports/slack.js.map +1 -0
  229. package/dist/notify/transports.test.d.ts +1 -0
  230. package/dist/notify/transports.test.js +71 -0
  231. package/dist/notify/transports.test.js.map +1 -0
  232. package/dist/notify/types.d.ts +95 -0
  233. package/dist/notify/types.js +16 -0
  234. package/dist/notify/types.js.map +1 -0
  235. package/dist/sandbox/docker-compose.test.d.ts +1 -0
  236. package/dist/sandbox/docker-compose.test.js +128 -0
  237. package/dist/sandbox/docker-compose.test.js.map +1 -0
  238. package/dist/sandbox/docker.d.ts +97 -9
  239. package/dist/sandbox/docker.js +192 -41
  240. package/dist/sandbox/docker.js.map +1 -1
  241. package/dist/sandbox/docker.test.d.ts +1 -0
  242. package/dist/sandbox/docker.test.js +137 -0
  243. package/dist/sandbox/docker.test.js.map +1 -0
  244. package/dist/sandbox/egress-allowlist.d.ts +69 -0
  245. package/dist/sandbox/egress-allowlist.js +164 -0
  246. package/dist/sandbox/egress-allowlist.js.map +1 -0
  247. package/dist/sandbox/egress-allowlist.test.d.ts +1 -0
  248. package/dist/sandbox/egress-allowlist.test.js +65 -0
  249. package/dist/sandbox/egress-allowlist.test.js.map +1 -0
  250. package/dist/sandbox/egress-firewall-config.d.ts +127 -0
  251. package/dist/sandbox/egress-firewall-config.js +434 -0
  252. package/dist/sandbox/egress-firewall-config.js.map +1 -0
  253. package/dist/sandbox/egress-firewall-config.test.d.ts +1 -0
  254. package/dist/sandbox/egress-firewall-config.test.js +244 -0
  255. package/dist/sandbox/egress-firewall-config.test.js.map +1 -0
  256. package/dist/sandbox/images.d.ts +23 -0
  257. package/dist/sandbox/images.js +55 -0
  258. package/dist/sandbox/images.js.map +1 -0
  259. package/dist/sandbox/index.d.ts +82 -3
  260. package/dist/sandbox/index.js +223 -93
  261. package/dist/sandbox/index.js.map +1 -1
  262. package/dist/sandbox/index.test.d.ts +1 -0
  263. package/dist/sandbox/index.test.js +157 -0
  264. package/dist/sandbox/index.test.js.map +1 -0
  265. package/dist/session-log.d.ts +63 -0
  266. package/dist/session-log.js +290 -0
  267. package/dist/session-log.js.map +1 -0
  268. package/dist/session-log.test.d.ts +1 -0
  269. package/dist/session-log.test.js +85 -0
  270. package/dist/session-log.test.js.map +1 -0
  271. package/dist/setup.d.ts +30 -1
  272. package/dist/setup.js +241 -25
  273. package/dist/setup.js.map +1 -1
  274. package/dist/setup.test.js +138 -6
  275. package/dist/setup.test.js.map +1 -1
  276. package/dist/state/approval-store.d.ts +86 -0
  277. package/dist/state/approval-store.js +140 -0
  278. package/dist/state/approval-store.js.map +1 -0
  279. package/dist/state/build-assets.d.ts +50 -0
  280. package/dist/state/build-assets.js +154 -0
  281. package/dist/state/build-assets.js.map +1 -0
  282. package/dist/state/build-assets.test.d.ts +1 -0
  283. package/dist/state/build-assets.test.js +106 -0
  284. package/dist/state/build-assets.test.js.map +1 -0
  285. package/dist/state/db.d.ts +65 -333
  286. package/dist/state/db.js +112 -835
  287. package/dist/state/db.js.map +1 -1
  288. package/dist/state/db.test.js +127 -91
  289. package/dist/state/db.test.js.map +1 -1
  290. package/dist/state/execution-store.d.ts +257 -0
  291. package/dist/state/execution-store.js +527 -0
  292. package/dist/state/execution-store.js.map +1 -0
  293. package/dist/state/migrate.d.ts +15 -0
  294. package/dist/state/migrate.js +175 -0
  295. package/dist/state/migrate.js.map +1 -0
  296. package/dist/state/workflow-run-store.d.ts +195 -0
  297. package/dist/state/workflow-run-store.js +363 -0
  298. package/dist/state/workflow-run-store.js.map +1 -0
  299. package/dist/state/workflow-run-store.test.d.ts +1 -0
  300. package/dist/state/workflow-run-store.test.js +264 -0
  301. package/dist/state/workflow-run-store.test.js.map +1 -0
  302. package/dist/telemetry/index.d.ts +38 -0
  303. package/dist/telemetry/index.js +253 -0
  304. package/dist/telemetry/index.js.map +1 -0
  305. package/dist/telemetry/index.test.d.ts +1 -0
  306. package/dist/telemetry/index.test.js +73 -0
  307. package/dist/telemetry/index.test.js.map +1 -0
  308. package/dist/telemetry/pi-events.d.ts +12 -0
  309. package/dist/telemetry/pi-events.js +134 -0
  310. package/dist/telemetry/pi-events.js.map +1 -0
  311. package/dist/telemetry/pi-events.test.d.ts +1 -0
  312. package/dist/telemetry/pi-events.test.js +69 -0
  313. package/dist/telemetry/pi-events.test.js.map +1 -0
  314. package/dist/workflows/dag.d.ts +13 -1
  315. package/dist/workflows/dag.js +7 -3
  316. package/dist/workflows/dag.js.map +1 -1
  317. package/dist/workflows/dag.test.js +22 -0
  318. package/dist/workflows/dag.test.js.map +1 -1
  319. package/dist/workflows/golden-build.test.d.ts +1 -0
  320. package/dist/workflows/golden-build.test.js +45 -0
  321. package/dist/workflows/golden-build.test.js.map +1 -0
  322. package/dist/workflows/loader-overlay.test.d.ts +1 -0
  323. package/dist/workflows/loader-overlay.test.js +101 -0
  324. package/dist/workflows/loader-overlay.test.js.map +1 -0
  325. package/dist/workflows/loader.d.ts +36 -11
  326. package/dist/workflows/loader.js +311 -87
  327. package/dist/workflows/loader.js.map +1 -1
  328. package/dist/workflows/loader.test.js +114 -10
  329. package/dist/workflows/loader.test.js.map +1 -1
  330. package/dist/workflows/loop-eval.js +2 -0
  331. package/dist/workflows/loop-eval.js.map +1 -1
  332. package/dist/workflows/loop-eval.test.js +14 -0
  333. package/dist/workflows/loop-eval.test.js.map +1 -1
  334. package/dist/workflows/phase-executor.d.ts +145 -0
  335. package/dist/workflows/phase-executor.js +691 -0
  336. package/dist/workflows/phase-executor.js.map +1 -0
  337. package/dist/workflows/phase-executor.test.d.ts +1 -0
  338. package/dist/workflows/phase-executor.test.js +434 -0
  339. package/dist/workflows/phase-executor.test.js.map +1 -0
  340. package/dist/workflows/phase-ref.d.ts +44 -0
  341. package/dist/workflows/phase-ref.js +78 -0
  342. package/dist/workflows/phase-ref.js.map +1 -0
  343. package/dist/workflows/phase-ref.test.d.ts +1 -0
  344. package/dist/workflows/phase-ref.test.js +24 -0
  345. package/dist/workflows/phase-ref.test.js.map +1 -0
  346. package/dist/workflows/resume.d.ts +5 -11
  347. package/dist/workflows/resume.js +88 -7
  348. package/dist/workflows/resume.js.map +1 -1
  349. package/dist/workflows/runner.d.ts +34 -27
  350. package/dist/workflows/runner.js +252 -930
  351. package/dist/workflows/runner.js.map +1 -1
  352. package/dist/workflows/runner.test.js +346 -35
  353. package/dist/workflows/runner.test.js.map +1 -1
  354. package/dist/workflows/schema.d.ts +52 -10
  355. package/dist/workflows/schema.js +147 -7
  356. package/dist/workflows/schema.js.map +1 -1
  357. package/dist/workflows/simple.d.ts +67 -3
  358. package/dist/workflows/simple.js +248 -87
  359. package/dist/workflows/simple.js.map +1 -1
  360. package/dist/workflows/simple.test.d.ts +1 -0
  361. package/dist/workflows/simple.test.js +107 -0
  362. package/dist/workflows/simple.test.js.map +1 -0
  363. package/dist/workflows/templates.d.ts +29 -0
  364. package/dist/workflows/templates.js +42 -4
  365. package/dist/workflows/templates.js.map +1 -1
  366. package/dist/workflows/templates.test.js +103 -0
  367. package/dist/workflows/templates.test.js.map +1 -1
  368. package/dist/workflows/triggers.d.ts +21 -0
  369. package/dist/workflows/triggers.js +44 -0
  370. package/dist/workflows/triggers.js.map +1 -0
  371. package/dist/workflows/verdict.d.ts +19 -0
  372. package/dist/workflows/verdict.js +30 -0
  373. package/dist/workflows/verdict.js.map +1 -0
  374. package/dist/workflows/verdict.test.d.ts +1 -0
  375. package/dist/workflows/verdict.test.js +44 -0
  376. package/dist/workflows/verdict.test.js.map +1 -0
  377. package/dist/worktree/manager.d.ts +1 -1
  378. package/dist/worktree/manager.js +14 -14
  379. package/dist/worktree/manager.js.map +1 -1
  380. package/dist/worktree/manager.test.d.ts +1 -0
  381. package/dist/worktree/manager.test.js +87 -0
  382. package/dist/worktree/manager.test.js.map +1 -0
  383. package/docker-compose.yml +206 -5
  384. package/package.json +17 -7
  385. package/sandbox.Dockerfile +97 -18
  386. package/skills/README.md +40 -0
  387. package/skills/browser-qa/SKILL.md +193 -0
  388. package/skills/browser-qa/scripts/agent-browser.mjs +380 -0
  389. package/skills/building/SKILL.md +95 -0
  390. package/skills/chat/SKILL.md +30 -11
  391. package/skills/code-review/SKILL.md +59 -0
  392. package/skills/debug-production/SKILL.md +108 -0
  393. package/skills/demo/SKILL.md +194 -0
  394. package/skills/demo/scripts/compose-demo.sh +205 -0
  395. package/skills/issue-answer/SKILL.md +90 -0
  396. package/skills/issue-comment/SKILL.md +38 -24
  397. package/skills/issue-triage/SKILL.md +103 -37
  398. package/skills/issue-triage/references/AGENT-BRIEF.md +61 -0
  399. package/skills/pr-comment/SKILL.md +64 -0
  400. package/skills/pr-review/SKILL.md +65 -49
  401. package/skills/qa-test/SKILL.md +97 -0
  402. package/skills/repo-health/SKILL.md +48 -45
  403. package/skills/security-feedback/SKILL.md +173 -0
  404. package/skills/security-feedback/references/templates.md +77 -0
  405. package/skills/security-review/SKILL.md +213 -0
  406. package/skills/security-review/references/issue-format.md +302 -0
  407. package/skills/verify/SKILL.md +118 -0
  408. package/workflows/answer.yaml +37 -0
  409. package/workflows/build.yaml +32 -22
  410. package/workflows/cron-security.yaml +6 -0
  411. package/workflows/demo.yaml +55 -0
  412. package/workflows/explore.yaml +9 -0
  413. package/workflows/pr-comment.yaml +16 -0
  414. package/workflows/pr-fix.yaml +3 -0
  415. package/workflows/pr-review.yaml +4 -1
  416. package/workflows/prompts/answer.md +67 -0
  417. package/workflows/prompts/architect.md +24 -7
  418. package/workflows/prompts/demo.md +100 -0
  419. package/workflows/prompts/executor.md +29 -20
  420. package/workflows/prompts/explore-ask.md +51 -22
  421. package/workflows/prompts/explore-publish.md +2 -2
  422. package/workflows/prompts/explore-read.md +24 -10
  423. package/workflows/prompts/explore-synthesize.md +15 -8
  424. package/workflows/prompts/fix.md +9 -10
  425. package/workflows/prompts/guardrails.md +26 -8
  426. package/workflows/prompts/pr-fix.md +6 -7
  427. package/workflows/prompts/pr.md +11 -10
  428. package/workflows/prompts/qa-browser.md +95 -0
  429. package/workflows/prompts/qa-synth.md +44 -0
  430. package/workflows/prompts/qa-test.md +60 -0
  431. package/workflows/prompts/re-reviewer.md +8 -9
  432. package/workflows/prompts/reviewer.md +10 -9
  433. package/workflows/prompts/verify-browser.md +93 -0
  434. package/workflows/prompts/verify-synth.md +44 -0
  435. package/workflows/prompts/verify.md +63 -0
  436. package/workflows/qa-test.yaml +87 -0
  437. package/workflows/security-feedback.yaml +25 -0
  438. package/workflows/security-review.yaml +29 -0
  439. package/workflows/verify.yaml +85 -0
  440. package/deploy/mcp-config.tmpl.json +0 -14
  441. package/dist/cron/rate-limits.d.ts +0 -26
  442. package/dist/cron/rate-limits.js +0 -193
  443. package/dist/cron/rate-limits.js.map +0 -1
  444. package/dist/engine/executor.d.ts +0 -75
  445. package/dist/engine/executor.js +0 -292
  446. package/dist/engine/executor.js.map +0 -1
  447. package/mcp-github-app/package.json +0 -18
  448. package/mcp-github-app/src/auth.js +0 -66
  449. package/mcp-github-app/src/github.js +0 -371
  450. package/mcp-github-app/src/index.js +0 -534
  451. package/skills/devops/webhook-subscriptions/SKILL.md +0 -180
  452. package/skills/github/DESCRIPTION.md +0 -3
  453. package/skills/github/codebase-inspection/SKILL.md +0 -115
  454. package/skills/github/github-auth/SKILL.md +0 -54
  455. package/skills/github/github-auth/scripts/gh-env.sh +0 -66
  456. package/skills/github/github-code-review/SKILL.md +0 -480
  457. package/skills/github/github-code-review/references/review-output-template.md +0 -74
  458. package/skills/github/github-issues/SKILL.md +0 -369
  459. package/skills/github/github-issues/templates/bug-report.md +0 -35
  460. package/skills/github/github-issues/templates/feature-request.md +0 -31
  461. package/skills/github/github-pr-workflow/SKILL.md +0 -395
  462. package/skills/github/github-pr-workflow/references/ci-troubleshooting.md +0 -183
  463. package/skills/github/github-pr-workflow/references/conventional-commits.md +0 -71
  464. package/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +0 -35
  465. package/skills/github/github-pr-workflow/templates/pr-body-feature.md +0 -33
  466. package/skills/github/github-repo-management/SKILL.md +0 -515
  467. package/skills/github/github-repo-management/references/github-api-cheatsheet.md +0 -161
  468. package/skills/github-orchestrator/SKILL.md +0 -103
  469. package/skills/mcp/DESCRIPTION.md +0 -3
  470. package/skills/mcp/mcporter/SKILL.md +0 -122
  471. package/skills/mcp/native-mcp/SKILL.md +0 -356
  472. package/skills/software-development/architect/SKILL.md +0 -154
  473. package/skills/software-development/assure-guardrails/SKILL.md +0 -239
  474. package/skills/software-development/plan/SKILL.md +0 -64
  475. package/skills/software-development/requesting-code-review/SKILL.md +0 -291
  476. package/skills/software-development/subagent-driven-development/SKILL.md +0 -433
  477. package/skills/software-development/systematic-debugging/SKILL.md +0 -366
  478. package/skills/software-development/test-driven-development/SKILL.md +0 -342
package/dist/cli.js CHANGED
@@ -1,245 +1,961 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Last Light CLI — thin client that triggers the server.
3
+ * Last Light CLI — `lastlight`.
4
4
  *
5
- * Usage:
6
- * npx tsx src/cli.ts <github-url> Triage that issue (default cheap)
7
- * npx tsx src/cli.ts <owner/repo#number> Same, shorthand
8
- * npx tsx src/cli.ts triage <owner/repo> Scan repo for issues to triage
9
- * npx tsx src/cli.ts review <owner/repo> Scan repo for PRs to review
10
- * npx tsx src/cli.ts health <owner/repo> Generate weekly health report
11
- * npx tsx src/cli.ts build <github-url> Run FULL build cycle (architect/executor/reviewer/PR)
12
- * npx tsx src/cli.ts build <owner/repo#N> Same, shorthand
5
+ * A thin client for a running Last Light instance. It does NOT run agents
6
+ * itself; it POSTs triggers and reads the instance's admin API over HTTP.
13
7
  *
14
- * The default action for a single-issue reference is now TRIAGE, not build.
15
- * Build cycles are expensive (multiple agent phases, may create PRs) so they
16
- * require an explicit `build` subcommand to opt in.
8
+ * lastlight login [url] Authenticate (browser) + save the token
9
+ * lastlight <github-url|ref> Triage that issue (default cheap)
10
+ * lastlight build <ref> Run the FULL build cycle
11
+ * lastlight workflow list Inspect recent workflow runs
12
+ * lastlight session log <id> -f Tail a sandbox session live
13
+ * lastlight logs search "<text>" Search execution errors / transcripts
17
14
  *
18
- * The CLI does NOT run agents directly — it POSTs to the running server.
19
- * Start the server first: npm run dev
15
+ * Auth + target resolution (`src/cli-config.ts`): `--url`/`--token`
16
+ * `LASTLIGHT_URL`/`LASTLIGHT_TOKEN` env `~/.lastlight/config.json` (written
17
+ * by `login`) → `http://localhost:8644`.
20
18
  */
21
- const SERVER_URL = process.env.LASTLIGHT_URL || "http://localhost:8644";
22
- let AUTH_TOKEN = process.env.LASTLIGHT_TOKEN || "";
23
- const args = process.argv.slice(2);
24
- if (args.length === 0) {
25
- console.log(`
26
- Last Light CLI
27
-
28
- Usage:
29
- tsx src/cli.ts setup Interactive setup wizard (run before first launch)
30
- tsx src/cli.ts <github-url> Triage that one issue (default — cheap)
31
- tsx src/cli.ts <owner/repo#number> Same, shorthand
32
- tsx src/cli.ts triage <owner/repo> Scan repo for issues to triage
33
- tsx src/cli.ts review <owner/repo> Scan repo for PRs to review
34
- tsx src/cli.ts health <owner/repo> Generate weekly health report
35
- tsx src/cli.ts build <github-url> Run FULL build cycle (architect/executor/reviewer/PR)
36
- tsx src/cli.ts build <owner/repo#N> Same, shorthand
37
-
38
- The default for a single issue reference is now TRIAGE, not build.
39
- Build cycles are expensive — opt in explicitly with the \`build\` subcommand.
40
-
41
- The server must be running (npm run dev). Set LASTLIGHT_URL to override.
42
- `);
43
- process.exit(0);
44
- }
45
- function authHeaders() {
46
- const headers = { "Content-Type": "application/json" };
47
- if (AUTH_TOKEN)
48
- headers.Authorization = `Bearer ${AUTH_TOKEN}`;
49
- return headers;
50
- }
51
- async function authenticate() {
52
- // If we already have a token, verify it works
53
- if (AUTH_TOKEN)
54
- return;
55
- // Try to login with ADMIN_PASSWORD
56
- const password = process.env.ADMIN_PASSWORD;
57
- if (!password)
58
- return; // No auth configured
59
- try {
60
- const res = await fetch(`${SERVER_URL}/admin/api/login`, {
61
- method: "POST",
62
- headers: { "Content-Type": "application/json" },
63
- body: JSON.stringify({ password }),
64
- });
65
- if (res.ok) {
66
- const { token } = await res.json();
67
- AUTH_TOKEN = token;
19
+ import http from "node:http";
20
+ import crypto from "node:crypto";
21
+ import readline from "node:readline";
22
+ import { spawn } from "node:child_process";
23
+ import * as p from "@clack/prompts";
24
+ import chalk from "chalk";
25
+ import { resolveTarget, saveConfig, clearConfig, loadConfig, DEFAULT_URL, } from "./cli-config.js";
26
+ import { table, age, colorStatus, checkmark, followSSE } from "./cli-format.js";
27
+ import { renderTimeline, renderMessage, renderRaw } from "./cli-timeline.js";
28
+ // ── arg parsing ────────────────────────────────────────────────────────────
29
+ const BOOLEAN_FLAGS = new Set([
30
+ "json", "follow", "f", "no-browser", "password", "help", "h", "full",
31
+ // `server` lifecycle flags
32
+ "no-core", "no-overlay", "no-build", "yes",
33
+ // `setup` mode selectors (skip the interactive client/server prompt)
34
+ "client", "server",
35
+ ]);
36
+ function parseArgs(argv) {
37
+ const positionals = [];
38
+ const flags = {};
39
+ for (let i = 0; i < argv.length; i++) {
40
+ const a = argv[i];
41
+ if (a.startsWith("--")) {
42
+ const body = a.slice(2);
43
+ const eq = body.indexOf("=");
44
+ if (eq !== -1) {
45
+ flags[body.slice(0, eq)] = body.slice(eq + 1);
46
+ }
47
+ else if (BOOLEAN_FLAGS.has(body)) {
48
+ flags[body] = true;
49
+ }
50
+ else {
51
+ // value flag: consume next arg if present and not another flag
52
+ const next = argv[i + 1];
53
+ if (next !== undefined && !next.startsWith("-")) {
54
+ flags[body] = next;
55
+ i++;
56
+ }
57
+ else {
58
+ flags[body] = true;
59
+ }
60
+ }
61
+ }
62
+ else if (a.startsWith("-") && a.length > 1 && a !== "-") {
63
+ const short = a.slice(1);
64
+ flags[short] = true; // -f
65
+ }
66
+ else {
67
+ positionals.push(a);
68
68
  }
69
69
  }
70
+ if (flags.f)
71
+ flags.follow = true;
72
+ if (flags.h)
73
+ flags.help = true;
74
+ return { positionals, flags };
75
+ }
76
+ const { positionals, flags } = parseArgs(process.argv.slice(2));
77
+ const JSON_OUT = flags.json === true;
78
+ function out(human, data) {
79
+ if (JSON_OUT) {
80
+ console.log(JSON.stringify(data ?? {}, null, 2));
81
+ }
82
+ else {
83
+ console.log(human);
84
+ }
85
+ }
86
+ function die(msg) {
87
+ console.error(chalk.red(msg));
88
+ process.exit(1);
89
+ }
90
+ // ── HTTP helpers ─────────────────────────────────────────────────────────────
91
+ function target() {
92
+ return resolveTarget({
93
+ url: typeof flags.url === "string" ? flags.url : undefined,
94
+ token: typeof flags.token === "string" ? flags.token : undefined,
95
+ });
96
+ }
97
+ function authHeaders(token) {
98
+ const h = { "Content-Type": "application/json" };
99
+ if (token)
100
+ h.Authorization = `Bearer ${token}`;
101
+ return h;
102
+ }
103
+ async function handle(res, path) {
104
+ if (res.status === 401) {
105
+ die("Not logged in or token expired — run: lastlight login");
106
+ }
107
+ const text = await res.text();
108
+ let data;
109
+ try {
110
+ data = text ? JSON.parse(text) : {};
111
+ }
70
112
  catch {
71
- // Auth may not be required — continue without token
113
+ data = { raw: text };
114
+ }
115
+ if (!res.ok) {
116
+ die(`Request failed (${res.status}) on ${path}: ${JSON.stringify(data)}`);
72
117
  }
118
+ return data;
73
119
  }
74
- async function main() {
75
- // Setup wizard — runs before server health check (no server needed)
76
- if (args[0] === "setup") {
77
- const { runSetup } = await import("./setup.js");
78
- await runSetup();
79
- process.exit(0);
120
+ async function apiGet(path) {
121
+ const t = target();
122
+ let res;
123
+ try {
124
+ res = await fetch(`${t.url}${path}`, { headers: authHeaders(t.token) });
125
+ }
126
+ catch (e) {
127
+ return die(`Cannot reach ${t.url} — is the server running? (${e.message})`);
128
+ }
129
+ return handle(res, path);
130
+ }
131
+ async function apiPost(path, body) {
132
+ const t = target();
133
+ let res;
134
+ try {
135
+ res = await fetch(`${t.url}${path}`, {
136
+ method: "POST",
137
+ headers: authHeaders(t.token),
138
+ body: JSON.stringify(body),
139
+ });
80
140
  }
81
- // Check server is running
141
+ catch (e) {
142
+ return die(`Cannot reach ${t.url} — is the server running? (${e.message})`);
143
+ }
144
+ return handle(res, path);
145
+ }
146
+ function num(flag, fallback) {
147
+ const n = typeof flag === "string" ? parseInt(flag, 10) : NaN;
148
+ return Number.isFinite(n) ? n : fallback;
149
+ }
150
+ // ── help ───────────────────────────────────────────────────────────────────
151
+ const HELP = `
152
+ ${chalk.bold("Last Light CLI")}
153
+
154
+ ${chalk.bold("Auth")}
155
+ lastlight login [url] Authenticate via browser, save the token
156
+ lastlight logout Forget the saved instance + token
157
+ lastlight status Show instance, token validity, server health
158
+
159
+ ${chalk.bold("Chat")}
160
+ lastlight chat [message] Chat with the bot (REPL if no message)
161
+
162
+ ${chalk.bold("Trigger")}
163
+ lastlight <github-url|owner/repo#N> Triage that issue (default — cheap)
164
+ lastlight build <ref> Run the FULL build cycle (architect→PR)
165
+ lastlight triage <owner/repo[#N]> Triage a repo (scan) or one issue
166
+ lastlight review <owner/repo[#N]> Review a repo's PRs (scan) or one PR
167
+ lastlight verify <owner/repo#N> [-- "<claim>"] Test a claim → CONFIRMED/REFUTED
168
+ lastlight qa-test <owner/repo#N> [-- "<steps>"] Drive a flow → step pass/fail
169
+ lastlight health <owner/repo> Weekly health report
170
+ lastlight security <owner/repo> Security review
171
+
172
+ ${chalk.bold("Debug")} (read the running instance instead of SSH)
173
+ lastlight workflow list [--status s] [--workflow name] [--limit n]
174
+ lastlight workflow log <id> [--follow]
175
+ lastlight session list [--limit n]
176
+ lastlight session log <id> [--follow] [--since n] [--full] (--full = raw, unformatted dump)
177
+ lastlight logs search "<text>" [--scope errors|messages|all] [--limit n]
178
+ lastlight server list (the lastlight-* containers)
179
+ lastlight server logs [service|container] [--tail n] [--since 10m] [--follow]
180
+ lastlight approvals list
181
+ lastlight approvals approve <id> [--reason "..."]
182
+ lastlight approvals reject <id> [--reason "..."]
183
+ lastlight stats [--daily n | --hourly n]
184
+
185
+ ${chalk.bold("Server")} (host-local — run on the server; manages the docker stack)
186
+ lastlight server setup Scaffold/adopt the working dir (checkout + overlay)
187
+ lastlight server start [service] docker compose up -d
188
+ lastlight server stop [service] Stop one service, or the whole stack (down)
189
+ lastlight server restart [service] Restart a service (default: agent)
190
+ lastlight server update Pull core + overlay, build, recreate, restart sidecars
191
+ [--no-core] [--no-overlay] [--no-build] [--yes]
192
+ lastlight server status Compose state + core/overlay version drift
193
+ ${chalk.dim("Working dir resolves from --home, then LASTLIGHT_HOME, then ~/.lastlight, then ~/lastlight.")}
194
+
195
+ ${chalk.bold("Other")}
196
+ lastlight setup First-run wizard — asks client (login) or server (stack)
197
+ [--client | --server to skip the prompt]
198
+
199
+ ${chalk.dim("Global flags: --json (machine output), --url <u>, --token <t>.")}
200
+ ${chalk.dim("Target resolves from --url/--token, then LASTLIGHT_URL/LASTLIGHT_TOKEN, then ~/.lastlight, then " + DEFAULT_URL + ".")}
201
+ `;
202
+ // ── browser-handoff login ────────────────────────────────────────────────────
203
+ function openBrowser(url) {
204
+ const platform = process.platform;
205
+ const cmd = platform === "darwin" ? "open" : platform === "win32" ? "start" : "xdg-open";
206
+ const args = platform === "win32" ? ["", url] : [url];
82
207
  try {
83
- const healthRes = await fetch(`${SERVER_URL}/health`);
84
- if (!healthRes.ok)
85
- throw new Error();
208
+ const child = spawn(cmd, args, { stdio: "ignore", detached: true, shell: platform === "win32" });
209
+ child.unref();
86
210
  }
87
211
  catch {
88
- console.error(`Server not running at ${SERVER_URL}`);
89
- console.error(`Start it first: npm run dev`);
90
- process.exit(1);
212
+ /* best-effort we also print the URL */
91
213
  }
92
- // Authenticate if needed
93
- await authenticate();
94
- const firstArg = args[0];
95
- // ── Explicit `build` subcommand: trigger the full build cycle ──────────
96
- // This is the only path that hits /api/build and runs architect → executor →
97
- // reviewer PR. Build cycles are expensive and may create PRs, so opt-in.
98
- if (firstArg === "build") {
99
- const target = args[1];
100
- if (!target) {
101
- console.error(`Usage: tsx src/cli.ts build <github-url> | <owner/repo#N>`);
214
+ }
215
+ async function cmdLogin() {
216
+ const saved = loadConfig();
217
+ let url = positionals[1]; // positionals[0] is the "login" command itself
218
+ if (!url) {
219
+ const answer = await p.text({
220
+ message: "Last Light instance URL",
221
+ placeholder: saved?.url ?? "https://lastlight.example.com",
222
+ initialValue: saved?.url ?? "",
223
+ validate: (v) => (v && /^https?:\/\//.test(v) ? undefined : "Enter a URL starting with http(s)://"),
224
+ });
225
+ if (p.isCancel(answer)) {
226
+ p.cancel("Login cancelled.");
102
227
  process.exit(1);
103
228
  }
104
- const parsed = parseGitHubRef(target);
105
- if (!parsed) {
106
- console.error(`Could not parse GitHub reference: ${target}`);
107
- console.error(`Expected: https://github.com/owner/repo/issues/N or owner/repo#N`);
229
+ url = answer;
230
+ }
231
+ url = url.replace(/\/+$/, "");
232
+ // Password fallback: headless / no browser.
233
+ if (flags.password === true || flags["no-browser"] === true) {
234
+ const pw = await p.password({ message: `Admin password for ${url}` });
235
+ if (p.isCancel(pw)) {
236
+ p.cancel("Login cancelled.");
108
237
  process.exit(1);
109
238
  }
110
- const { owner, repo, number } = parsed;
111
- console.log(`Triggering BUILD cycle for ${owner}/${repo}#${number}...`);
112
- const res = await fetch(`${SERVER_URL}/api/build`, {
239
+ const res = await fetch(`${url}/admin/api/login`, {
113
240
  method: "POST",
114
- headers: authHeaders(),
115
- body: JSON.stringify({ owner, repo, issueNumber: number }),
241
+ headers: { "Content-Type": "application/json" },
242
+ body: JSON.stringify({ password: pw }),
243
+ });
244
+ if (!res.ok)
245
+ die(`Login failed (${res.status}).`);
246
+ const { token } = (await res.json());
247
+ saveConfig({ url, token });
248
+ console.log(chalk.green(`✓ Logged in to ${url}`) + chalk.dim(" (token valid ~7 days)"));
249
+ return;
250
+ }
251
+ // Browser handoff: spin up a loopback listener, open the dashboard with a
252
+ // cli_callback pointing back here, and wait for the dashboard to redirect the
253
+ // token to /callback once the user authenticates (any method).
254
+ const state = crypto.randomBytes(16).toString("hex");
255
+ const token = await new Promise((resolve, reject) => {
256
+ const server = http.createServer((req, res) => {
257
+ const reqUrl = new URL(req.url ?? "/", "http://127.0.0.1");
258
+ if (reqUrl.pathname !== "/callback") {
259
+ res.writeHead(404).end();
260
+ return;
261
+ }
262
+ const gotState = reqUrl.searchParams.get("state");
263
+ const gotToken = reqUrl.searchParams.get("token");
264
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
265
+ if (gotState !== state || !gotToken) {
266
+ res.end("<html><body><h2>Login failed</h2><p>Invalid state — you can close this tab.</p></body></html>");
267
+ server.close();
268
+ reject(new Error("state mismatch or missing token"));
269
+ return;
270
+ }
271
+ res.end("<html><body><h2>✓ Logged in</h2><p>You can close this tab and return to the terminal.</p></body></html>");
272
+ server.close();
273
+ resolve(gotToken);
274
+ });
275
+ server.on("error", reject);
276
+ server.listen(0, "127.0.0.1", () => {
277
+ const addr = server.address();
278
+ const port = typeof addr === "object" && addr ? addr.port : 0;
279
+ const callback = `http://127.0.0.1:${port}/callback`;
280
+ const loginUrl = `${url}/admin/?cli_callback=${encodeURIComponent(callback)}&cli_state=${state}`;
281
+ console.log(`Opening browser to authenticate…`);
282
+ console.log(chalk.dim(` If it doesn't open, visit:\n ${loginUrl}`));
283
+ openBrowser(loginUrl);
116
284
  });
117
- const data = await res.json();
118
- if (res.ok) {
119
- console.log(`Accepted: ${JSON.stringify(data)}`);
120
- console.log(`Check server logs for progress.`);
285
+ setTimeout(() => {
286
+ server.close();
287
+ reject(new Error("timed out waiting for browser login (2 min)"));
288
+ }, 120_000).unref();
289
+ });
290
+ saveConfig({ url, token });
291
+ console.log(chalk.green(`✓ Logged in to ${url}`) + chalk.dim(" (token valid ~7 days)"));
292
+ }
293
+ // ── status ───────────────────────────────────────────────────────────────────
294
+ async function cmdStatus() {
295
+ const t = target();
296
+ const saved = loadConfig();
297
+ let health = null;
298
+ let healthOk = false;
299
+ try {
300
+ const res = await fetch(`${t.url}/health`);
301
+ healthOk = res.ok;
302
+ health = res.ok ? await res.json() : null;
303
+ }
304
+ catch {
305
+ healthOk = false;
306
+ }
307
+ let authMethods = null;
308
+ try {
309
+ const res = await fetch(`${t.url}/admin/api/auth-required`);
310
+ if (res.ok)
311
+ authMethods = await res.json();
312
+ }
313
+ catch { /* ignore */ }
314
+ // Probe token validity against an authed endpoint.
315
+ let tokenValid = null;
316
+ if (t.token) {
317
+ try {
318
+ const res = await fetch(`${t.url}/admin/api/stats`, { headers: authHeaders(t.token) });
319
+ tokenValid = res.status !== 401;
121
320
  }
122
- else {
123
- console.error(`Failed: ${JSON.stringify(data)}`);
124
- process.exit(1);
321
+ catch {
322
+ tokenValid = null;
125
323
  }
324
+ }
325
+ if (JSON_OUT) {
326
+ out("", { url: t.url, tokenPresent: Boolean(t.token), tokenValid, healthOk, health, authMethods, savedAt: saved?.savedAt });
126
327
  return;
127
328
  }
128
- // ── Skill commands: triage, review, health ─────────────────────────────
129
- // `triage` and `review` accept either:
130
- // - <owner/repo> → repo-wide scan (existing behavior)
131
- // - <owner/repo#N> single-issue / single-PR action
132
- // `health` only takes a repo (it's always a repo-level report).
133
- if (["triage", "review", "health"].includes(firstArg)) {
134
- const target = args[1];
135
- if (!target) {
136
- console.error(`Usage: tsx src/cli.ts ${firstArg} <owner/repo>${firstArg !== "health" ? " | <owner/repo#N>" : ""}`);
137
- process.exit(1);
329
+ console.log(`${chalk.bold("Instance")} ${t.url}`);
330
+ console.log(`${chalk.bold("Server")} ${healthOk ? chalk.green("healthy") : chalk.red("unreachable")}`);
331
+ console.log(`${chalk.bold("Token")} ${!t.token ? chalk.yellow("none run: lastlight login")
332
+ : tokenValid === false ? chalk.red("expired/invalid run: lastlight login")
333
+ : tokenValid === true ? chalk.green("valid")
334
+ : chalk.dim("present (unverified)")}`);
335
+ if (saved?.savedAt)
336
+ console.log(`${chalk.bold("Saved")} ${age(saved.savedAt)}`);
337
+ if (authMethods && typeof authMethods === "object") {
338
+ const m = authMethods;
339
+ const methods = [m.required ? "password" : null, m.slackOAuth ? "slack" : null, m.githubOAuth ? "github" : null].filter(Boolean);
340
+ console.log(`${chalk.bold("Auth")} ${m.required ? methods.join(", ") : chalk.dim("disabled")}`);
341
+ }
342
+ }
343
+ // ── debug: workflows ──────────────────────────────────────────────────────────
344
+ /** Interactive picker over the recent workflow runs (used when `log` has no id). */
345
+ async function pickWorkflowRun() {
346
+ if (!process.stdout.isTTY || JSON_OUT)
347
+ die("Usage: lastlight workflow log <id> [--follow]");
348
+ const data = await apiGet(`/admin/api/workflow-runs?limit=${num(flags.limit, 20)}`);
349
+ const runs = data.workflowRuns;
350
+ if (runs.length === 0)
351
+ die("No workflow runs found.");
352
+ const choice = await p.select({
353
+ message: "Select a workflow run",
354
+ options: runs.map((r) => ({
355
+ value: r.id,
356
+ label: `${r.workflowName} ${r.status}`,
357
+ hint: `${r.repo ?? ""} · ${age(r.startedAt)} · ${String(r.id).slice(0, 8)}`,
358
+ })),
359
+ });
360
+ if (p.isCancel(choice)) {
361
+ p.cancel("Cancelled.");
362
+ process.exit(1);
363
+ }
364
+ return choice;
365
+ }
366
+ async function cmdWorkflow() {
367
+ const sub = positionals[1] ?? "list";
368
+ if (sub === "list") {
369
+ const params = new URLSearchParams();
370
+ params.set("limit", String(num(flags.limit, 20)));
371
+ if (typeof flags.status === "string")
372
+ params.set("status", flags.status);
373
+ if (typeof flags.workflow === "string")
374
+ params.set("workflow", flags.workflow);
375
+ const data = await apiGet(`/admin/api/workflow-runs?${params}`);
376
+ if (JSON_OUT)
377
+ return out("", data);
378
+ const rows = data.workflowRuns.map((r) => ({
379
+ id: r.id,
380
+ workflow: r.workflowName,
381
+ status: colorStatus(r.status),
382
+ phase: r.currentPhase ?? "",
383
+ repo: r.repo ?? "",
384
+ started: age(r.startedAt),
385
+ }));
386
+ console.log(table(rows, [
387
+ { key: "id", header: "ID" },
388
+ { key: "workflow", header: "WORKFLOW" },
389
+ { key: "status", header: "STATUS" },
390
+ { key: "phase", header: "PHASE" },
391
+ { key: "repo", header: "REPO" },
392
+ { key: "started", header: "STARTED" },
393
+ ]));
394
+ console.log(chalk.dim(`\n${data.total} total. Detail: lastlight workflow log <id>`));
395
+ return;
396
+ }
397
+ if (sub === "log") {
398
+ const id = positionals[2] ?? (await pickWorkflowRun());
399
+ const [runData, execData] = await Promise.all([
400
+ apiGet(`/admin/api/workflow-runs/${id}`),
401
+ apiGet(`/admin/api/workflow-runs/${id}/executions`),
402
+ ]);
403
+ const run = runData.workflowRun;
404
+ const execs = execData.executions;
405
+ if (JSON_OUT)
406
+ return out("", { workflowRun: run, executions: execs });
407
+ console.log(`${chalk.bold(run.workflowName)} ${chalk.dim(run.id)}`);
408
+ console.log(`status ${colorStatus(run.status)} phase ${run.currentPhase} repo ${run.repo ?? "-"} started ${age(run.startedAt)}`);
409
+ console.log("");
410
+ const rows = execs.map((e) => ({
411
+ ok: checkmark(e.success),
412
+ phase: (e.skill ?? "").replace(`${run.workflowName}:`, ""),
413
+ dur: e.durationMs ? `${Math.round(e.durationMs / 1000)}s` : "",
414
+ session: e.sessionId ?? "",
415
+ error: e.error ? chalk.red(String(e.error).slice(0, 60)) : "",
416
+ }));
417
+ console.log(table(rows, [
418
+ { key: "ok", header: "" },
419
+ { key: "phase", header: "PHASE" },
420
+ { key: "dur", header: "DUR" },
421
+ { key: "session", header: "SESSION" },
422
+ { key: "error", header: "ERROR" },
423
+ ]));
424
+ if (flags.follow) {
425
+ const last = [...execs].reverse().find((e) => e.sessionId);
426
+ if (!last) {
427
+ console.log(chalk.dim("\n(no session to follow yet)"));
428
+ return;
429
+ }
430
+ console.log(chalk.dim(`\nFollowing session ${last.sessionId} … (Ctrl-C to stop)\n`));
431
+ await followSession(last.sessionId);
138
432
  }
139
- const skillMap = {
140
- triage: "issue-triage",
141
- review: "pr-review",
142
- health: "repo-health",
143
- };
144
- const skill = skillMap[firstArg];
145
- // Detect single-issue/PR form (allowed for triage and review only)
146
- const parsed = firstArg !== "health" ? parseGitHubRef(target) : null;
147
- let context;
148
- if (parsed) {
149
- const { owner, repo, number } = parsed;
150
- context = {
151
- repo: `${owner}/${repo}`,
152
- issueNumber: number,
153
- sender: "cli",
154
- };
155
- console.log(`Triggering ${firstArg} on ${owner}/${repo}#${number}...`);
433
+ return;
434
+ }
435
+ die("Usage: lastlight workflow list|log");
436
+ }
437
+ // ── debug: sessions ───────────────────────────────────────────────────────────
438
+ async function followSession(id) {
439
+ const t = target();
440
+ await followSSE(`${t.url}/admin/api/sessions/${id}/stream`, t.token, (data) => {
441
+ try {
442
+ const lines = renderMessage(JSON.parse(data));
443
+ if (lines.length)
444
+ console.log(lines.join("\n"));
156
445
  }
157
- else {
158
- context = { repos: [target], mode: "scan" };
159
- console.log(`Triggering ${firstArg} scan on ${target}...`);
446
+ catch {
447
+ console.log(data);
160
448
  }
161
- const res = await fetch(`${SERVER_URL}/api/run`, {
162
- method: "POST",
163
- headers: authHeaders(),
164
- body: JSON.stringify({ skill, context }),
165
- });
166
- const data = await res.json();
167
- if (res.ok) {
168
- console.log(`Accepted: ${JSON.stringify(data)}`);
169
- console.log(`Check server logs for progress.`);
449
+ });
450
+ }
451
+ /** Interactive picker over the recent sessions (used when `log` has no id). */
452
+ async function pickSession() {
453
+ if (!process.stdout.isTTY || JSON_OUT)
454
+ die("Usage: lastlight session log <id> [--follow] [--since n]");
455
+ const data = await apiGet(`/admin/api/sessions?limit=${num(flags.limit, 30)}`);
456
+ const sessions = data.sessions;
457
+ if (sessions.length === 0)
458
+ die("No sessions found.");
459
+ const choice = await p.select({
460
+ message: "Select a session",
461
+ options: sessions.map((s) => ({
462
+ value: s.id,
463
+ label: `${s.sessionType ?? "agent"}${s.live ? " ●" : ""}`,
464
+ hint: `${s.message_count ?? 0} msgs · ${age(s.last_message_at ?? s.started_at)} · ${s.id}`,
465
+ })),
466
+ });
467
+ if (p.isCancel(choice)) {
468
+ p.cancel("Cancelled.");
469
+ process.exit(1);
470
+ }
471
+ return choice;
472
+ }
473
+ async function cmdSession() {
474
+ const sub = positionals[1] ?? "list";
475
+ if (sub === "list") {
476
+ const data = await apiGet(`/admin/api/sessions?limit=${num(flags.limit, 30)}`);
477
+ if (JSON_OUT)
478
+ return out("", data);
479
+ const rows = data.sessions.map((s) => ({
480
+ id: s.id,
481
+ type: s.sessionType ?? "",
482
+ model: s.model ?? "",
483
+ msgs: String(s.message_count ?? 0),
484
+ live: s.live ? chalk.green("●") : "",
485
+ last: age(s.last_message_at ?? s.started_at),
486
+ }));
487
+ console.log(table(rows, [
488
+ { key: "id", header: "ID" },
489
+ { key: "type", header: "TYPE" },
490
+ { key: "model", header: "MODEL" },
491
+ { key: "msgs", header: "MSGS" },
492
+ { key: "live", header: "LIVE" },
493
+ { key: "last", header: "LAST" },
494
+ ]));
495
+ console.log(chalk.dim(`\n${data.liveCount ?? 0} live. Detail: lastlight session log <id>`));
496
+ return;
497
+ }
498
+ if (sub === "log") {
499
+ const id = positionals[2] ?? (await pickSession());
500
+ const since = num(flags.since, -1);
501
+ const data = await apiGet(`/admin/api/sessions/${id}/messages?since=${since}`);
502
+ if (JSON_OUT && !flags.follow)
503
+ return out("", data);
504
+ if (!JSON_OUT) {
505
+ const lines = flags.full === true
506
+ ? renderRaw(data.messages)
507
+ : renderTimeline(data.messages);
508
+ if (lines.length)
509
+ console.log(lines.join("\n"));
170
510
  }
171
- else {
172
- console.error(`Failed: ${JSON.stringify(data)}`);
173
- process.exit(1);
511
+ if (flags.follow) {
512
+ console.log(chalk.dim(`\nFollowing ${id} … (Ctrl-C to stop)\n`));
513
+ await followSession(id);
174
514
  }
175
515
  return;
176
516
  }
177
- // ── Default: shorthand <github-url> or <owner/repo#N> → triage that issue
178
- //
179
- // This is intentionally cheap. To run a full build cycle, use `build <ref>`.
180
- const parsed = parseGitHubRef(firstArg);
181
- if (!parsed) {
182
- console.error(`Could not parse GitHub reference: ${firstArg}`);
183
- console.error(`Expected: https://github.com/owner/repo/issues/N or owner/repo#N`);
184
- console.error(``);
185
- console.error(`To trigger a full build cycle, use: tsx src/cli.ts build ${firstArg}`);
186
- process.exit(1);
517
+ die("Usage: lastlight session list|log");
518
+ }
519
+ // ── debug: logs search ────────────────────────────────────────────────────────
520
+ async function cmdLogsSearch(query) {
521
+ if (!query)
522
+ die('Usage: lastlight logs search "<text>" [--scope errors|messages|all]');
523
+ const params = new URLSearchParams();
524
+ params.set("q", query);
525
+ params.set("scope", typeof flags.scope === "string" ? flags.scope : "errors");
526
+ params.set("limit", String(num(flags.limit, 50)));
527
+ const data = await apiGet(`/admin/api/log-search?${params}`);
528
+ if (JSON_OUT)
529
+ return out("", data);
530
+ const results = data.results;
531
+ if (results.length === 0) {
532
+ console.log(chalk.dim("(no matches)"));
533
+ return;
187
534
  }
188
- const { owner, repo, number, type } = parsed;
189
- const isPr = type === "pr";
190
- const skill = isPr ? "pr-review" : "issue-triage";
191
- const action = isPr ? "PR review" : "issue triage";
192
- console.log(`Triggering ${action} for ${owner}/${repo}#${number}...`);
193
- console.log(`(For a full build cycle: tsx src/cli.ts build ${owner}/${repo}#${number})`);
194
- const res = await fetch(`${SERVER_URL}/api/run`, {
195
- method: "POST",
196
- headers: authHeaders(),
197
- body: JSON.stringify({
198
- skill,
199
- context: {
200
- repo: `${owner}/${repo}`,
201
- ...(isPr ? { prNumber: number } : { issueNumber: number }),
202
- sender: "cli",
203
- },
204
- }),
535
+ for (const r of results) {
536
+ if (r.source === "error") {
537
+ console.log(`${checkmark(r.success)} ${chalk.dim(age(r.startedAt))} ${chalk.bold(r.skill)} ${r.repo ?? ""} ${chalk.dim(r.sessionId ?? "")}`);
538
+ console.log(` ${chalk.red(String(r.snippet).slice(0, 200))}`);
539
+ }
540
+ else {
541
+ console.log(`${chalk.magenta("msg")} ${chalk.dim(r.sessionId)}#${r.messageIndex} ${chalk.dim(r.role ?? "")}`);
542
+ console.log(` ${String(r.snippet).slice(0, 200)}`);
543
+ }
544
+ }
545
+ }
546
+ // ── debug: approvals + stats ────────────────────────────────────────────────
547
+ /** Interactive picker over pending approvals (used when approve/reject has no id). */
548
+ async function pickApproval() {
549
+ if (!process.stdout.isTTY || JSON_OUT)
550
+ die("Usage: lastlight approvals approve|reject <id> [--reason \"...\"]");
551
+ const data = await apiGet(`/admin/api/approvals`);
552
+ const approvals = data.approvals;
553
+ if (approvals.length === 0)
554
+ die("No pending approvals.");
555
+ const choice = await p.select({
556
+ message: "Select an approval",
557
+ options: approvals.map((a) => ({
558
+ value: a.id,
559
+ label: `${a.gate} ${String(a.summary ?? "").slice(0, 50)}`,
560
+ hint: `${a.workflowRunId} · ${age(a.createdAt)}`,
561
+ })),
205
562
  });
206
- const data = await res.json();
207
- if (res.ok) {
208
- console.log(`Accepted: ${JSON.stringify(data)}`);
209
- console.log(`Check server logs for progress.`);
563
+ if (p.isCancel(choice)) {
564
+ p.cancel("Cancelled.");
565
+ process.exit(1);
210
566
  }
211
- else {
212
- console.error(`Failed: ${JSON.stringify(data)}`);
567
+ return choice;
568
+ }
569
+ async function cmdApprovals() {
570
+ const sub = positionals[1] ?? "list";
571
+ if (sub === "list") {
572
+ const data = await apiGet(`/admin/api/approvals`);
573
+ if (JSON_OUT)
574
+ return out("", data);
575
+ const rows = data.approvals.map((a) => ({
576
+ id: a.id,
577
+ gate: a.gate,
578
+ kind: a.kind,
579
+ run: a.workflowRunId,
580
+ summary: String(a.summary ?? "").slice(0, 50),
581
+ age: age(a.createdAt),
582
+ }));
583
+ console.log(table(rows, [
584
+ { key: "id", header: "ID" },
585
+ { key: "gate", header: "GATE" },
586
+ { key: "kind", header: "KIND" },
587
+ { key: "run", header: "RUN" },
588
+ { key: "summary", header: "SUMMARY" },
589
+ { key: "age", header: "AGE" },
590
+ ]));
591
+ return;
592
+ }
593
+ if (sub === "approve" || sub === "reject") {
594
+ const id = positionals[2] ?? (await pickApproval());
595
+ const decision = sub === "approve" ? "approved" : "rejected";
596
+ const data = await apiPost(`/admin/api/approvals/${id}/respond`, {
597
+ decision,
598
+ reason: typeof flags.reason === "string" ? flags.reason : undefined,
599
+ });
600
+ out(chalk.green(`✓ ${decision} ${id}`), data);
601
+ return;
602
+ }
603
+ die("Usage: lastlight approvals list|approve|reject");
604
+ }
605
+ async function cmdStats() {
606
+ if (flags.daily !== undefined) {
607
+ const data = await apiGet(`/admin/api/stats/daily?days=${num(flags.daily, 30)}`);
608
+ if (JSON_OUT)
609
+ return out("", data);
610
+ const rows = data.daily.map((d) => ({
611
+ date: d.date,
612
+ execs: String(d.executions),
613
+ ok: String(d.successes),
614
+ fail: String(d.failures),
615
+ tokens: String(d.totalTokens ?? 0),
616
+ cost: `$${(d.costUsd ?? 0).toFixed(2)}`,
617
+ }));
618
+ console.log(table(rows, [
619
+ { key: "date", header: "DATE" }, { key: "execs", header: "EXECS" },
620
+ { key: "ok", header: "OK" }, { key: "fail", header: "FAIL" },
621
+ { key: "tokens", header: "TOKENS" }, { key: "cost", header: "COST" },
622
+ ]));
623
+ return;
624
+ }
625
+ if (flags.hourly !== undefined) {
626
+ const data = await apiGet(`/admin/api/stats/hourly?hours=${num(flags.hourly, 24)}`);
627
+ return out("", data);
628
+ }
629
+ const data = await apiGet(`/admin/api/stats`);
630
+ if (JSON_OUT)
631
+ return out("", data);
632
+ console.log(`${chalk.bold("Total executions")} ${data.total_executions}`);
633
+ console.log(`${chalk.bold("Today")} ${data.today_count}`);
634
+ console.log(`${chalk.bold("Running")} ${data.running}`);
635
+ const bySkill = data.by_skill;
636
+ const rows = Object.entries(bySkill).map(([skill, v]) => ({
637
+ skill, count: String(v.count), ok: chalk.green(String(v.success)), fail: v.fail ? chalk.red(String(v.fail)) : "0",
638
+ }));
639
+ console.log("");
640
+ console.log(table(rows, [
641
+ { key: "skill", header: "SKILL" }, { key: "count", header: "RUNS" },
642
+ { key: "ok", header: "OK" }, { key: "fail", header: "FAIL" },
643
+ ]));
644
+ }
645
+ // ── debug: server logs ────────────────────────────────────────────────────────
646
+ /** Interactive picker over the lastlight-* containers (used when `logs` has no
647
+ * container). Returns undefined when non-interactive so the server defaults to
648
+ * the agent. */
649
+ async function pickServerContainer() {
650
+ if (!process.stdout.isTTY || JSON_OUT)
651
+ return undefined;
652
+ const data = await apiGet(`/admin/api/server/containers`);
653
+ const containers = data.containers;
654
+ if (containers.length === 0)
655
+ return undefined;
656
+ const choice = await p.select({
657
+ message: "Select a container",
658
+ options: containers.map((c) => ({
659
+ value: c.name,
660
+ label: c.service,
661
+ hint: `${c.status} · ${c.name}`,
662
+ })),
663
+ });
664
+ if (p.isCancel(choice)) {
665
+ p.cancel("Cancelled.");
213
666
  process.exit(1);
214
667
  }
668
+ return choice;
215
669
  }
216
- // ── GitHub reference parser ─────────────────────────────────────────
670
+ async function cmdServer() {
671
+ const sub = positionals[1];
672
+ if (!sub || sub === "list" || sub === "containers") {
673
+ const data = await apiGet(`/admin/api/server/containers`);
674
+ if (JSON_OUT)
675
+ return out("", data);
676
+ const rows = data.containers.map((c) => ({
677
+ service: c.service,
678
+ name: c.name,
679
+ status: c.status,
680
+ image: c.image,
681
+ }));
682
+ console.log(table(rows, [
683
+ { key: "service", header: "SERVICE" },
684
+ { key: "name", header: "CONTAINER" },
685
+ { key: "status", header: "STATUS" },
686
+ { key: "image", header: "IMAGE" },
687
+ ]));
688
+ console.log(chalk.dim(`\nLogs: lastlight server logs [service|container] [--tail n] [--since 10m] [--follow]`));
689
+ return;
690
+ }
691
+ if (sub === "logs") {
692
+ // optional; if omitted, prompt (or default to the agent server-side)
693
+ const container = positionals[2] ?? (await pickServerContainer());
694
+ const tail = num(flags.tail, 200);
695
+ if (flags.follow) {
696
+ const t = target();
697
+ const u = new URL(`${t.url}/admin/api/server/logs/stream`);
698
+ if (container)
699
+ u.searchParams.set("container", container);
700
+ u.searchParams.set("tail", String(tail));
701
+ console.log(chalk.dim(`Following ${container ?? "agent"} logs … (Ctrl-C to stop)\n`));
702
+ await followSSE(u.toString(), t.token, (line) => {
703
+ try {
704
+ const obj = JSON.parse(line);
705
+ if (obj && typeof obj === "object" && "error" in obj) {
706
+ die(String(obj.error));
707
+ }
708
+ }
709
+ catch { /* normal log line */ }
710
+ console.log(line);
711
+ });
712
+ return;
713
+ }
714
+ const params = new URLSearchParams();
715
+ if (container)
716
+ params.set("container", container);
717
+ params.set("tail", String(tail));
718
+ if (typeof flags.since === "string")
719
+ params.set("since", flags.since);
720
+ const data = await apiGet(`/admin/api/server/logs?${params}`);
721
+ if (JSON_OUT)
722
+ return out("", data);
723
+ for (const line of data.lines)
724
+ console.log(line);
725
+ return;
726
+ }
727
+ // ── host-local lifecycle (run on the server, not over HTTP) ──────────────
728
+ // setup | start | stop | restart | update | status operate on the working
729
+ // directory (checkout + overlay) via git + docker compose. See cli-server.ts.
730
+ if (sub === "setup" || sub === "start" || sub === "stop" || sub === "restart" || sub === "update" || sub === "status") {
731
+ const home = typeof flags.home === "string" ? flags.home : undefined;
732
+ const yes = flags.yes === true;
733
+ const service = positionals[2];
734
+ const srv = await import("./cli-server.js");
735
+ switch (sub) {
736
+ case "setup": return srv.serverSetup({ home, yes });
737
+ case "start": return srv.serverStart(service, { home });
738
+ case "stop": return srv.serverStop(service, { home });
739
+ case "restart": return srv.serverRestart(service, { home });
740
+ case "update": return srv.serverUpdate({
741
+ home, yes,
742
+ core: !flags["no-core"],
743
+ overlay: !flags["no-overlay"],
744
+ build: !flags["no-build"],
745
+ });
746
+ case "status": {
747
+ const res = await srv.serverStatus({ home });
748
+ if (JSON_OUT)
749
+ out("", res);
750
+ return;
751
+ }
752
+ }
753
+ }
754
+ die("Usage:\n" +
755
+ " lastlight server list|logs [service|container] [--tail n] [--since dur] [--follow]\n" +
756
+ " lastlight server setup|start|stop|restart|update|status [service] [--home dir]\n" +
757
+ " update flags: --no-core --no-overlay --no-build --yes");
758
+ }
759
+ // ── chat ──────────────────────────────────────────────────────────────────────
760
+ async function sendChat(message, thread, user) {
761
+ const data = await apiPost(`/api/chat`, { message, thread, user });
762
+ if (JSON_OUT)
763
+ return out("", data);
764
+ console.log(`${chalk.cyan("assistant")} ${data.text ?? ""}`);
765
+ if (data.turns || data.costUsd) {
766
+ const cost = data.costUsd ? `, $${Number(data.costUsd).toFixed(4)}` : "";
767
+ console.log(chalk.dim(` (${data.turns ?? "?"} turns${cost})`));
768
+ }
769
+ }
770
+ async function cmdChat() {
771
+ const user = typeof flags.user === "string" ? flags.user : "cli";
772
+ const thread = crypto.randomUUID();
773
+ const oneShot = positionals.slice(1).join(" ").trim();
774
+ if (oneShot) {
775
+ await sendChat(oneShot, thread, user);
776
+ return;
777
+ }
778
+ // Interactive REPL — one stable thread for the whole session.
779
+ const t = target();
780
+ console.log(chalk.dim(`Chatting with ${t.url} · thread ${thread.slice(0, 8)} · type 'exit' to quit`));
781
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
782
+ const ask = (q) => new Promise((res) => rl.question(q, res));
783
+ try {
784
+ for (;;) {
785
+ const line = (await ask(chalk.green("› "))).trim();
786
+ if (!line)
787
+ continue;
788
+ if (line === "exit" || line === "quit")
789
+ break;
790
+ try {
791
+ await sendChat(line, thread, user);
792
+ }
793
+ catch (e) {
794
+ console.error(chalk.red(e.message));
795
+ }
796
+ }
797
+ }
798
+ finally {
799
+ rl.close();
800
+ }
801
+ }
802
+ // ── trigger commands (unchanged contract) ─────────────────────────────────────
217
803
  function parseGitHubRef(input) {
218
- // Full URL: https://github.com/owner/repo/issues/42
219
804
  const urlMatch = input.match(/github\.com\/([^/]+)\/([^/]+)\/(issues|pull)\/(\d+)/);
220
805
  if (urlMatch) {
221
- return {
222
- owner: urlMatch[1],
223
- repo: urlMatch[2],
224
- number: parseInt(urlMatch[4], 10),
225
- type: urlMatch[3] === "pull" ? "pr" : "issue",
226
- };
227
- }
228
- // Shorthand: owner/repo#42
806
+ return { owner: urlMatch[1], repo: urlMatch[2], number: parseInt(urlMatch[4], 10), type: urlMatch[3] === "pull" ? "pr" : "issue" };
807
+ }
229
808
  const shortMatch = input.match(/^([^/]+)\/([^#]+)#(\d+)$/);
230
809
  if (shortMatch) {
231
- return {
232
- owner: shortMatch[1],
233
- repo: shortMatch[2],
234
- number: parseInt(shortMatch[3], 10),
235
- type: "issue",
236
- };
810
+ return { owner: shortMatch[1], repo: shortMatch[2], number: parseInt(shortMatch[3], 10), type: "issue" };
237
811
  }
238
812
  return null;
239
813
  }
814
+ async function cmdBuild() {
815
+ const ref = positionals[1];
816
+ if (!ref)
817
+ die("Usage: lastlight build <github-url> | <owner/repo#N>");
818
+ const parsed = parseGitHubRef(ref);
819
+ if (!parsed)
820
+ die(`Could not parse GitHub reference: ${ref}`);
821
+ const { owner, repo, number } = parsed;
822
+ if (!JSON_OUT)
823
+ console.log(`Triggering BUILD cycle for ${owner}/${repo}#${number}…`);
824
+ const data = await apiPost(`/api/build`, { owner, repo, issueNumber: number });
825
+ out(`Accepted: ${JSON.stringify(data)}`, data);
826
+ }
827
+ async function cmdSkill(name) {
828
+ const target = positionals[1];
829
+ const skillMap = {
830
+ triage: "issue-triage", review: "pr-review", health: "repo-health", security: "security-review",
831
+ verify: "verify", "qa-test": "qa-test", demo: "demo",
832
+ };
833
+ const skill = skillMap[name];
834
+ const repoLevelOnly = name === "health" || name === "security";
835
+ // verify / qa-test / demo take a free-text argument after the target (claim,
836
+ // steps, or demo notes) — accept it either as trailing positionals or as a
837
+ // quoted `-- "<text>"` (the arg parser folds the latter into flags[""]).
838
+ const takesClaim = name === "verify" || name === "qa-test" || name === "demo";
839
+ const claim = takesClaim
840
+ ? (positionals.slice(2).join(" ") || (typeof flags[""] === "string" ? flags[""] : "")).trim()
841
+ : "";
842
+ if (!target) {
843
+ die(`Usage: lastlight ${name} <owner/repo${repoLevelOnly ? "" : "#N"}>${takesClaim ? ` [-- "<claim or steps>"]` : ""}`);
844
+ }
845
+ const parsed = repoLevelOnly ? null : parseGitHubRef(target);
846
+ let context;
847
+ if (parsed) {
848
+ context = { repo: `${parsed.owner}/${parsed.repo}`, issueNumber: parsed.number, sender: "cli" };
849
+ if (claim)
850
+ context.commentBody = claim;
851
+ if (!JSON_OUT)
852
+ console.log(`Triggering ${name} on ${parsed.owner}/${parsed.repo}#${parsed.number}…`);
853
+ }
854
+ else {
855
+ context = { repos: [target], mode: "scan" };
856
+ if (!JSON_OUT)
857
+ console.log(`Triggering ${name} scan on ${target}…`);
858
+ }
859
+ const data = await apiPost(`/api/run`, { skill, context });
860
+ out(`Accepted: ${JSON.stringify(data)}`, data);
861
+ }
862
+ async function cmdDefaultRef(ref) {
863
+ const parsed = parseGitHubRef(ref);
864
+ if (!parsed) {
865
+ die(`Unknown command or reference: ${ref}\nRun \`lastlight --help\` for usage, or build with: lastlight build ${ref}`);
866
+ }
867
+ const { owner, repo, number, type } = parsed;
868
+ const isPr = type === "pr";
869
+ const skill = isPr ? "pr-review" : "issue-triage";
870
+ if (!JSON_OUT) {
871
+ console.log(`Triggering ${isPr ? "PR review" : "issue triage"} for ${owner}/${repo}#${number}…`);
872
+ console.log(chalk.dim(`(For a full build cycle: lastlight build ${owner}/${repo}#${number})`));
873
+ }
874
+ const data = await apiPost(`/api/run`, {
875
+ skill,
876
+ context: { repo: `${owner}/${repo}`, ...(isPr ? { prNumber: number } : { issueNumber: number }), sender: "cli" },
877
+ });
878
+ out(`Accepted: ${JSON.stringify(data)}`, data);
879
+ }
880
+ // ── setup (client vs server) ───────────────────────────────────────────────
881
+ /**
882
+ * `lastlight setup` — onboarding. First choice: is this machine a **client**
883
+ * (the CLI just talks to a remote instance → login) or a **server** (it runs
884
+ * the agent + docker stack → the full config wizard)? `--client` / `--server`
885
+ * skip the prompt for non-interactive use.
886
+ */
887
+ async function cmdSetup() {
888
+ let mode = flags.client === true ? "client" : flags.server === true ? "server" : undefined;
889
+ if (!mode) {
890
+ if (!process.stdin.isTTY) {
891
+ die("setup must run interactively, or pass --client / --server.");
892
+ }
893
+ const choice = await p.select({
894
+ message: "What are you setting up on this machine?",
895
+ options: [
896
+ { value: "client", label: "Client", hint: "this CLI talks to a remote Last Light instance" },
897
+ { value: "server", label: "Server", hint: "this machine runs the agent + docker stack" },
898
+ ],
899
+ });
900
+ if (p.isCancel(choice)) {
901
+ p.cancel("Cancelled.");
902
+ process.exit(1);
903
+ }
904
+ mode = choice;
905
+ }
906
+ if (mode === "client")
907
+ return cmdLogin();
908
+ // Server: the full first-run config wizard (secrets, keys, managed repos).
909
+ // For a fresh host with no checkout yet, `lastlight server setup` clones the
910
+ // working dir first; `server start|update|status` then manage the stack.
911
+ const { runSetup } = await import("./setup.js");
912
+ await runSetup();
913
+ }
914
+ // ── dispatch ─────────────────────────────────────────────────────────────────
915
+ async function main() {
916
+ const cmd = positionals[0];
917
+ if (!cmd || flags.help) {
918
+ console.log(HELP);
919
+ process.exit(cmd ? 0 : 0);
920
+ }
921
+ switch (cmd) {
922
+ case "setup": return cmdSetup();
923
+ case "login": return cmdLogin();
924
+ case "logout":
925
+ clearConfig();
926
+ console.log(chalk.green("✓ Logged out (cleared ~/.lastlight/config.json)"));
927
+ return;
928
+ case "status":
929
+ case "whoami": return cmdStatus();
930
+ case "workflow":
931
+ case "workflows": return cmdWorkflow();
932
+ case "session":
933
+ case "sessions": return cmdSession();
934
+ case "logs":
935
+ case "log": {
936
+ // `logs search <q>` or legacy `log search <q>`
937
+ if (positionals[1] === "search")
938
+ return cmdLogsSearch(positionals[2]);
939
+ die('Usage: lastlight logs search "<text>"');
940
+ return;
941
+ }
942
+ case "approvals": return cmdApprovals();
943
+ case "server": return cmdServer();
944
+ case "stats": return cmdStats();
945
+ case "chat": return cmdChat();
946
+ case "build": return cmdBuild();
947
+ case "triage":
948
+ case "review":
949
+ case "health":
950
+ case "security":
951
+ case "verify":
952
+ case "qa-test":
953
+ case "demo": return cmdSkill(cmd);
954
+ default: return cmdDefaultRef(cmd);
955
+ }
956
+ }
240
957
  main().catch((err) => {
241
- console.error("Fatal:", err.message || err);
958
+ console.error(chalk.red("Fatal:"), err?.message || err);
242
959
  process.exit(1);
243
960
  });
244
- export {};
245
961
  //# sourceMappingURL=cli.js.map