lastlight 0.1.8 → 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 +423 -437
  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 +31 -12
  272. package/dist/setup.js +516 -174
  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 +19 -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/setup.js CHANGED
@@ -3,23 +3,44 @@
3
3
  * Last Light setup wizard — `npx lastlight setup`
4
4
  *
5
5
  * Guides a user from a bare server to a fully configured Last Light instance.
6
- * Uses only Node.js built-ins: readline/promises, crypto, fs, child_process.
7
- * Zero new dependencies.
6
+ * Uses @clack/prompts for the interactive UI and chalk for styling.
8
7
  */
9
- import { createInterface } from "node:readline/promises";
10
8
  import { randomBytes } from "node:crypto";
11
- import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, copyFileSync, } from "node:fs";
9
+ import { existsSync, readFileSync, readdirSync, writeFileSync, mkdirSync, chmodSync, copyFileSync, symlinkSync, lstatSync, unlinkSync, } from "node:fs";
12
10
  import { resolve, join } from "node:path";
13
11
  import { execSync } from "node:child_process";
12
+ import * as p from "@clack/prompts";
13
+ import chalk from "chalk";
14
+ // ── Brand colors ───────────────────────────────────────────────────────────
15
+ const gold = chalk.hex("#F0B429");
16
+ const teal = chalk.hex("#1A7A8A");
17
+ const orange = chalk.hex("#E8752A");
18
+ const dim = chalk.dim;
19
+ const bright = chalk.white.bold;
14
20
  // ── Validation helpers (exported for unit tests) ────────────────────────────
15
- /** Returns true if the string is a positive integer (no decimals, no leading spaces). */
16
21
  export function isPositiveInt(s) {
17
22
  return /^\d+$/.test(s) && parseInt(s, 10) > 0;
18
23
  }
19
24
  /**
20
- * Returns true if the file at `filePath` exists and starts with a PEM header.
21
- * Accepts both RSA PRIVATE KEY and PKCS8 PRIVATE KEY formats.
25
+ * Try to resolve a PEM path. Checks: as given, relative to cwd, relative
26
+ * to parent dir (for when the wizard chdir'd into a clone), and with ~ expanded.
27
+ * Returns the resolved absolute path or null if not found.
22
28
  */
29
+ function resolvePem(input) {
30
+ let expanded = input;
31
+ if (expanded.startsWith("~/")) {
32
+ expanded = join(process.env.HOME || "", expanded.slice(2));
33
+ }
34
+ const candidates = [
35
+ resolve(expanded),
36
+ resolve("..", expanded),
37
+ ];
38
+ for (const candidate of candidates) {
39
+ if (isPemFile(candidate))
40
+ return candidate;
41
+ }
42
+ return null;
43
+ }
23
44
  export function isPemFile(filePath) {
24
45
  try {
25
46
  const content = readFileSync(filePath, "utf8");
@@ -30,28 +51,39 @@ export function isPemFile(filePath) {
30
51
  return false;
31
52
  }
32
53
  }
33
- /** Returns true if the key has the `sk-ant-` prefix. */
34
54
  export function isAnthropicKey(s) {
35
55
  return s.startsWith("sk-ant-");
36
56
  }
37
- /** Returns true if the token has the `xoxb-` prefix. */
57
+ export function isOpenaiKey(s) {
58
+ return s.startsWith("sk-") && !s.startsWith("sk-ant-") && !s.startsWith("sk-or-");
59
+ }
60
+ export function isOpenrouterKey(s) {
61
+ return s.startsWith("sk-or-");
62
+ }
63
+ /**
64
+ * Default model — kept aligned with `config.ts`'s `DEFAULT_MODEL`.
65
+ * Update both in lockstep when the canonical default changes.
66
+ */
67
+ export const DEFAULT_LASTLIGHT_MODEL = "anthropic/claude-sonnet-4-6";
38
68
  export function isSlackBotToken(s) {
39
69
  return s.startsWith("xoxb-");
40
70
  }
41
- /** Returns true if the token has the `xapp-` prefix. */
42
71
  export function isSlackAppToken(s) {
43
72
  return s.startsWith("xapp-");
44
73
  }
45
74
  // ── .env serialization (exported for unit tests) ────────────────────────────
46
- /** Builds the full .env file content from the collected config. */
47
75
  export function buildEnvContent(config) {
48
76
  const lines = [
49
77
  "# ── Last Light — Environment Variables ─────────────────────",
50
78
  "# Generated by: npx lastlight setup",
51
79
  "",
80
+ "# ── Overlay (this deployment's private config + assets) ──",
81
+ "LASTLIGHT_OVERLAY_DIR=/app/instance",
82
+ "",
52
83
  "# ── GitHub App (required) ────────────────────────────────",
53
84
  `GITHUB_APP_ID=${config.GITHUB_APP_ID}`,
54
- `GITHUB_APP_PRIVATE_KEY_PATH=./secrets/app.pem`,
85
+ // PEM lives at instance/secrets/app.pem; the entrypoint symlinks it to /app/app.pem.
86
+ `GITHUB_APP_PRIVATE_KEY_PATH=./app.pem`,
55
87
  `GITHUB_APP_INSTALLATION_ID=${config.GITHUB_APP_INSTALLATION_ID}`,
56
88
  "",
57
89
  "# ── Webhook (required) ──────────────────────────────────",
@@ -60,12 +92,20 @@ export function buildEnvContent(config) {
60
92
  "# ── Domain (used by Caddy for TLS) ─────────────────────────────────────",
61
93
  `DOMAIN=${config.DOMAIN}`,
62
94
  "",
63
- "# ── Claude API ───────────────────────────────────────────",
64
- `ANTHROPIC_API_KEY=${config.ANTHROPIC_API_KEY}`,
65
- "",
66
- "# ── Admin Dashboard ────────────────────────────────────────",
67
- `ADMIN_SECRET=${config.ADMIN_SECRET}`,
95
+ "# ── Model + provider API key ────────────────────────────────",
96
+ `LASTLIGHT_MODEL=${config.LASTLIGHT_MODEL}`,
97
+ "# Set whichever matches your LASTLIGHT_MODEL (anthropic/…, openai/…, or openrouter/…).",
68
98
  ];
99
+ if (config.OPENAI_API_KEY) {
100
+ lines.push(`OPENAI_API_KEY=${config.OPENAI_API_KEY}`);
101
+ }
102
+ if (config.ANTHROPIC_API_KEY) {
103
+ lines.push(`ANTHROPIC_API_KEY=${config.ANTHROPIC_API_KEY}`);
104
+ }
105
+ if (config.OPENROUTER_API_KEY) {
106
+ lines.push(`OPENROUTER_API_KEY=${config.OPENROUTER_API_KEY}`);
107
+ }
108
+ lines.push("", "# ── Admin Dashboard ────────────────────────────────────────", `ADMIN_SECRET=${config.ADMIN_SECRET}`);
69
109
  if (config.ADMIN_PASSWORD) {
70
110
  lines.push(`ADMIN_PASSWORD=${config.ADMIN_PASSWORD}`);
71
111
  }
@@ -86,189 +126,500 @@ export function buildEnvContent(config) {
86
126
  lines.push("");
87
127
  return lines.join("\n");
88
128
  }
89
- async function prompt(rl, question, validator, errorMsg) {
90
- while (true) {
91
- const answer = (await rl.question(question)).trim();
92
- if (!validator || validator(answer))
93
- return answer;
94
- console.error(` ${errorMsg ?? "Invalid input. Please try again."}`);
129
+ /** Build the overlay config.yaml (instance/config.yaml) — merged over config/default.yaml. */
130
+ export function buildOverlayConfig(config) {
131
+ const lines = [
132
+ "# Last Light — private deployment overlay config",
133
+ "# Merged over the public config/default.yaml at startup; arrays replace,",
134
+ "# maps deep-merge, env vars override. Restart the agent to apply:",
135
+ "# docker compose restart agent",
136
+ "",
137
+ "managedRepos:",
138
+ ];
139
+ if (config.managedRepos.length === 0) {
140
+ lines.push(" [] # add owner/repo entries — the bot ignores repos not listed here");
141
+ }
142
+ else {
143
+ for (const repo of config.managedRepos)
144
+ lines.push(` - ${repo}`);
95
145
  }
146
+ lines.push("");
147
+ return lines.join("\n");
96
148
  }
97
- async function promptYesNo(rl, question, defaultYes = false) {
98
- const hint = defaultYes ? "[Y/n]" : "[y/N]";
99
- while (true) {
100
- const answer = (await rl.question(`${question} ${hint}: `)).trim().toLowerCase();
101
- if (answer === "")
102
- return defaultYes;
103
- if (answer === "y" || answer === "yes")
104
- return true;
105
- if (answer === "n" || answer === "no")
106
- return false;
107
- console.error(" Please enter y or n.");
149
+ /** .gitignore for the instance/ overlay so it can be its own private repo without leaking secrets. */
150
+ const OVERLAY_GITIGNORE = [
151
+ "# Host-only secrets — never commit. Everything under secrets/ is ignored",
152
+ "# except the template.",
153
+ "secrets/*",
154
+ "!secrets/.env.example",
155
+ ".env",
156
+ "*.pem",
157
+ "",
158
+ ].join("\n");
159
+ /** Compose override (overlay): disable Caddy when the operator opts out of its TLS. */
160
+ export const CADDY_DISABLED_OVERRIDE = [
161
+ "# Deployment compose override — version-controlled in the overlay (instance/).",
162
+ "# Symlinked into the project dir as ./docker-compose.override.yml so",
163
+ "# `docker compose` auto-loads it on top of the repo's docker-compose.yml.",
164
+ "# This deployment opted out of Caddy TLS at setup time.",
165
+ "services:",
166
+ " caddy:",
167
+ " profiles:",
168
+ " - disabled",
169
+ "",
170
+ ].join("\n");
171
+ const OVERRIDE_FILE = "docker-compose.override.yml";
172
+ /**
173
+ * Symlink the overlay's compose override into the project dir so plain
174
+ * `docker compose` auto-discovers it (Compose only auto-loads
175
+ * ./docker-compose.override.yml). No-op if the overlay has no override.
176
+ * Leaves a pre-existing regular file untouched.
177
+ */
178
+ export function ensureOverrideSymlink() {
179
+ const target = join("instance", OVERRIDE_FILE);
180
+ if (!existsSync(target))
181
+ return;
182
+ let existing;
183
+ try {
184
+ existing = lstatSync(OVERRIDE_FILE);
108
185
  }
186
+ catch {
187
+ /* link absent */
188
+ }
189
+ if (existing && !existing.isSymbolicLink()) {
190
+ p.log.warn(`${OVERRIDE_FILE} already exists as a regular file — leaving it; not symlinking the overlay override.`);
191
+ return;
192
+ }
193
+ if (existing)
194
+ unlinkSync(OVERRIDE_FILE);
195
+ symlinkSync(target, OVERRIDE_FILE);
196
+ p.log.success(dim(`${OVERRIDE_FILE}`) + " → " + dim(target));
109
197
  }
110
- // ── Setup steps ─────────────────────────────────────────────────────────────
198
+ // ── Clack helper — bail on cancel ───────────────────────────────────────────
199
+ function required(value) {
200
+ if (p.isCancel(value)) {
201
+ p.cancel("Setup cancelled.");
202
+ process.exit(0);
203
+ }
204
+ return value;
205
+ }
206
+ // ── Banner ──────────────────────────────────────────────────────────────────
207
+ function printBanner() {
208
+ // Inner width = 46 chars between the box-drawing borders
209
+ const W = 46;
210
+ const pad = (text, styled) => {
211
+ const left = Math.floor((W - text.length) / 2);
212
+ const right = W - text.length - left;
213
+ return gold(" │") + " ".repeat(left) + styled + " ".repeat(right) + gold("│");
214
+ };
215
+ const empty = gold(" │") + " ".repeat(W) + gold("│");
216
+ const rule = "─".repeat(W);
217
+ console.log();
218
+ console.log(gold(` ╭${rule}╮`));
219
+ console.log(empty);
220
+ console.log(pad("✦ L A S T L I G H T ✦", bright("✦ L A S T L I G H T ✦")));
221
+ console.log(empty);
222
+ console.log(pad("GitHub Repository Maintenance Agent", dim("GitHub Repository Maintenance Agent")));
223
+ console.log(empty);
224
+ console.log(gold(` ╰${rule}╯`));
225
+ console.log();
226
+ }
227
+ // ── Preflight ───────────────────────────────────────────────────────────────
111
228
  function preflight() {
112
- // Check for docker-compose.yml; clone if not present, then cd into it
113
229
  if (!existsSync("docker-compose.yml")) {
114
- // Maybe we're one level up and a `lastlight/` clone already exists
115
230
  if (existsSync("lastlight/docker-compose.yml")) {
116
- console.log("\nFound existing lastlight/ directory — continuing setup there.\n");
231
+ p.log.info("Found existing lastlight/ directory — continuing setup there.");
117
232
  process.chdir("lastlight");
118
233
  }
119
234
  else {
120
- console.log("\nNo docker-compose.yml found in current directory.");
121
- console.log("Cloning cliftonc/lastlight into ./lastlight ...\n");
235
+ const s = p.spinner();
236
+ s.start("Cloning cliftonc/lastlight ...");
122
237
  try {
123
238
  execSync("git clone https://github.com/cliftonc/lastlight.git lastlight", {
124
- stdio: "inherit",
239
+ stdio: "pipe",
125
240
  });
241
+ s.stop("Repository cloned.");
126
242
  }
127
243
  catch {
128
- console.error("Failed to clone repository. Please clone it manually:");
129
- console.error(" git clone https://github.com/cliftonc/lastlight.git");
244
+ s.stop("Clone failed.");
245
+ p.log.error("Failed to clone. Please clone manually:");
246
+ p.log.info(" git clone https://github.com/cliftonc/lastlight.git");
130
247
  process.exit(1);
131
248
  }
132
249
  process.chdir("lastlight");
133
- console.log(`\nContinuing setup in ${process.cwd()}\n`);
250
+ p.log.info(`Working directory: ${dim(process.cwd())}`);
134
251
  }
135
252
  }
136
- // Check Docker is available
137
253
  try {
138
254
  execSync("docker info", { stdio: "ignore" });
139
255
  }
140
256
  catch {
141
- console.error("\nDocker is not running or not installed.");
142
- console.error("Please start Docker and re-run: npx lastlight setup\n");
257
+ p.log.error("Docker is not running or not installed.");
258
+ p.log.info("Please start Docker and re-run: " + teal("npx lastlight setup"));
143
259
  process.exit(1);
144
260
  }
145
- // Check .env doesn't already exist
146
- if (existsSync(".env")) {
147
- console.error("\n.env already exists. To reconfigure, edit it directly.");
148
- console.error("If you want to start over, remove it first: rm .env\n");
261
+ if (existsSync(join("instance", "secrets", ".env"))) {
262
+ p.log.error("instance/secrets/.env already exists. To reconfigure, remove it first: " +
263
+ dim("rm instance/secrets/.env"));
149
264
  process.exit(1);
150
265
  }
151
266
  }
152
- async function collectGitHubApp(rl) {
153
- console.log("\n── Step 1: GitHub App ─────────────────────────────────────");
154
- console.log("Create a GitHub App at https://github.com/settings/apps/new if you haven't already.\n");
155
- const appId = await prompt(rl, "GitHub App ID (numeric): ", isPositiveInt, "Must be a positive integer (e.g. 123456).");
156
- const installationId = await prompt(rl, "GitHub App Installation ID (numeric): ", isPositiveInt, "Must be a positive integer (e.g. 789012).");
157
- const pemPath = await prompt(rl, "Path to GitHub App private key (.pem): ", (p) => isPemFile(resolve(p)), "File not found or does not look like a PEM private key.");
267
+ // ── Setup steps ─────────────────────────────────────────────────────────────
268
+ async function collectGitHubApp() {
269
+ p.log.step(gold("GitHub App"));
270
+ p.log.info(dim("Create one at ") +
271
+ teal("https://github.com/settings/apps/new") +
272
+ dim(" if you haven't already."));
273
+ const appId = required(await p.text({
274
+ message: "GitHub App ID",
275
+ placeholder: "123456",
276
+ validate: (v) => v && isPositiveInt(v) ? undefined : "Must be a positive integer.",
277
+ }));
278
+ const installationId = required(await p.text({
279
+ message: "Installation ID",
280
+ placeholder: "789012",
281
+ validate: (v) => v && isPositiveInt(v) ? undefined : "Must be a positive integer.",
282
+ }));
283
+ // Search cwd and parent for .pem files to offer as choices
284
+ const pemCandidates = [];
285
+ for (const dir of [process.cwd(), resolve("..")]) {
286
+ try {
287
+ for (const f of readdirSync(dir)) {
288
+ if (f.endsWith(".pem")) {
289
+ const full = join(dir, f);
290
+ if (isPemFile(full))
291
+ pemCandidates.push(full);
292
+ }
293
+ }
294
+ }
295
+ catch { /* unreadable dir */ }
296
+ }
297
+ let pemResolved;
298
+ if (pemCandidates.length > 0) {
299
+ const choice = required(await p.select({
300
+ message: "Private key (.pem)",
301
+ options: [
302
+ ...pemCandidates.map((f) => ({ value: f, label: f.replace(process.env.HOME || "", "~") })),
303
+ { value: "__other__", label: dim("Enter a different path...") },
304
+ ],
305
+ }));
306
+ if (choice === "__other__") {
307
+ const manual = required(await p.text({
308
+ message: "Path to private key (.pem)",
309
+ validate: (v) => {
310
+ if (!v)
311
+ return "Enter a file path.";
312
+ return resolvePem(v) ? undefined : "File not found or not a valid PEM.";
313
+ },
314
+ }));
315
+ pemResolved = resolvePem(manual);
316
+ }
317
+ else {
318
+ pemResolved = choice;
319
+ }
320
+ }
321
+ else {
322
+ const manual = required(await p.text({
323
+ message: "Path to private key (.pem)",
324
+ placeholder: "~/downloads/your-app.private-key.pem",
325
+ validate: (v) => {
326
+ if (!v)
327
+ return "Enter a file path.";
328
+ return resolvePem(v) ? undefined : "File not found or not a valid PEM.";
329
+ },
330
+ }));
331
+ pemResolved = resolvePem(manual);
332
+ }
158
333
  return {
159
- appId,
160
- installationId,
161
- pemSourcePath: resolve(pemPath),
334
+ appId: appId,
335
+ installationId: installationId,
336
+ pemSourcePath: pemResolved,
162
337
  };
163
338
  }
164
- function generateSecrets() {
165
- console.log("\n── Step 2: Secrets ────────────────────────────────────────");
166
- const webhookSecret = randomBytes(32).toString("hex");
167
- const adminSecret = randomBytes(32).toString("hex");
168
- console.log("\nAuto-generated WEBHOOK_SECRET (paste this into your GitHub App webhook settings):");
169
- console.log(` ${webhookSecret}\n`);
170
- return { webhookSecret, adminSecret };
339
+ async function collectDomain() {
340
+ p.log.step(gold("Domain & TLS"));
341
+ const domain = required(await p.text({
342
+ message: "Your domain",
343
+ placeholder: "lastlight.example.com",
344
+ validate: (v) => v && v.length > 0 && v.includes(".")
345
+ ? undefined
346
+ : "Enter a valid domain name.",
347
+ }));
348
+ const useCaddy = required(await p.confirm({
349
+ message: "Use Caddy for automatic TLS?",
350
+ initialValue: true,
351
+ }));
352
+ p.log.info(`Webhook URL: ${teal(`https://${domain}/webhook`)}`);
353
+ return { domain: domain, useCaddy: useCaddy };
171
354
  }
172
- async function collectDomain(rl) {
173
- console.log("── Step 3: Domain + TLS ───────────────────────────────────");
174
- const domain = await prompt(rl, "Your domain (e.g. lastlight.example.com): ", (d) => d.length > 0 && d.includes("."), "Please enter a valid domain name.");
175
- const useCaddy = await promptYesNo(rl, "Use Caddy for automatic TLS (recommended)?", true);
176
- console.log(`\nWebhook URL: https://${domain}/webhook`);
177
- return { domain, useCaddy };
355
+ /** Parse a free-text list of `owner/repo` entries (comma/space/newline separated). */
356
+ export function parseManagedRepos(input) {
357
+ const seen = new Set();
358
+ for (const tok of input.split(/[\s,]+/).map((t) => t.trim()).filter(Boolean)) {
359
+ if (/^[^/\s]+\/[^/\s]+$/.test(tok))
360
+ seen.add(tok);
361
+ }
362
+ return [...seen];
178
363
  }
179
- async function collectAnthropicKey(rl) {
180
- console.log("\n── Step 4: Anthropic API Key ──────────────────────────────");
181
- return prompt(rl, "ANTHROPIC_API_KEY (sk-ant-...): ", isAnthropicKey, "Must start with sk-ant-");
364
+ async function collectManagedRepos() {
365
+ p.log.step(gold("Managed repositories"));
366
+ p.log.info(dim("Repos the bot will operate on. Install the GitHub App on each. Add more later in instance/config.yaml."));
367
+ const raw = required(await p.text({
368
+ message: "Managed repos (owner/repo, space- or comma-separated)",
369
+ placeholder: "your-org/repo-one your-org/repo-two",
370
+ validate: (v) => {
371
+ if (!v || !v.trim())
372
+ return undefined; // allow empty — can fill in later
373
+ return parseManagedRepos(v).length > 0 ? undefined : "Use owner/repo form, e.g. acme/widgets.";
374
+ },
375
+ }));
376
+ const repos = parseManagedRepos(raw || "");
377
+ if (repos.length === 0) {
378
+ p.log.warn("No managed repos set — the bot won't act until you add some to instance/config.yaml.");
379
+ }
380
+ else {
381
+ p.log.success(`Managing ${repos.length} repo${repos.length === 1 ? "" : "s"}: ${dim(repos.join(", "))}`);
382
+ }
383
+ return repos;
182
384
  }
183
- async function collectAdminPassword(rl) {
184
- console.log("\n── Step 5: Admin Dashboard Password ──────────────────────");
185
- const wantPassword = await promptYesNo(rl, "Set an admin dashboard password?", false);
385
+ async function collectModelAndKey() {
386
+ p.log.step(gold("Model provider"));
387
+ p.log.info(dim("agentic-pi (pi-ai) is provider-agnostic. Pick the model you want the agent to use; ") +
388
+ dim("the wizard will ask for the matching API key."));
389
+ // Curated short list — extend as new defaults emerge. The custom escape
390
+ // hatch below lets the user enter any provider/model string pi-ai accepts.
391
+ const catalog = [
392
+ DEFAULT_LASTLIGHT_MODEL,
393
+ "anthropic/claude-sonnet-4-6-20251015",
394
+ "openai/gpt-5.5",
395
+ "openrouter/google/gemini-2.5-pro",
396
+ "openrouter/anthropic/claude-sonnet-4.5",
397
+ ];
398
+ // Deduplicate while preserving order (DEFAULT_LASTLIGHT_MODEL first).
399
+ const seen = new Set();
400
+ const ordered = [];
401
+ for (const m of catalog) {
402
+ if (!seen.has(m)) {
403
+ seen.add(m);
404
+ ordered.push(m);
405
+ }
406
+ }
407
+ const choice = required(await p.select({
408
+ message: `LASTLIGHT_MODEL ${dim("(short list — pick 'other' for a custom value)")}`,
409
+ initialValue: DEFAULT_LASTLIGHT_MODEL,
410
+ options: [
411
+ ...ordered.map((m) => ({
412
+ value: m,
413
+ label: m === DEFAULT_LASTLIGHT_MODEL ? `${m} ${dim("(default)")}` : m,
414
+ })),
415
+ { value: "__custom__", label: dim("Enter a different provider/model...") },
416
+ ],
417
+ }));
418
+ let model = choice;
419
+ if (choice === "__custom__") {
420
+ model = required(await p.text({
421
+ message: "provider/model",
422
+ placeholder: "openai/gpt-5.3-codex or anthropic/claude-…",
423
+ validate: (v) => v && /^[a-z][\w-]*\/[A-Za-z][\w.-]+$/.test(v)
424
+ ? undefined
425
+ : "Format must be provider/model (e.g. openai/gpt-5.3-codex).",
426
+ }));
427
+ }
428
+ const provider = model.split("/")[0].toLowerCase();
429
+ if (provider === "anthropic") {
430
+ const key = required(await p.text({
431
+ message: "ANTHROPIC_API_KEY",
432
+ placeholder: "sk-ant-...",
433
+ validate: (v) => v && isAnthropicKey(v) ? undefined : "Must start with sk-ant-",
434
+ }));
435
+ return { model, anthropicKey: key };
436
+ }
437
+ if (provider === "openai") {
438
+ const key = required(await p.text({
439
+ message: "OPENAI_API_KEY",
440
+ placeholder: "sk-...",
441
+ validate: (v) => v && isOpenaiKey(v) ? undefined : "Must start with sk- (and not sk-ant- or sk-or-)",
442
+ }));
443
+ return { model, openaiKey: key };
444
+ }
445
+ if (provider === "openrouter") {
446
+ const key = required(await p.text({
447
+ message: "OPENROUTER_API_KEY",
448
+ placeholder: "sk-or-v1-...",
449
+ validate: (v) => v && isOpenrouterKey(v) ? undefined : "Must start with sk-or-",
450
+ }));
451
+ return { model, openrouterKey: key };
452
+ }
453
+ // Unknown provider — prompt for any key and route by shape.
454
+ p.log.warn(`Provider "${provider}" isn't auto-detected. Enter the API key your model needs.`);
455
+ const key = required(await p.text({
456
+ message: "API key",
457
+ validate: (v) => (v && v.length > 0 ? undefined : "Enter a non-empty key."),
458
+ }));
459
+ if (isAnthropicKey(key))
460
+ return { model, anthropicKey: key };
461
+ if (isOpenrouterKey(key))
462
+ return { model, openrouterKey: key };
463
+ return { model, openaiKey: key };
464
+ }
465
+ async function collectAdminPassword() {
466
+ p.log.step(gold("Admin Dashboard"));
467
+ const wantPassword = required(await p.confirm({
468
+ message: "Set an admin dashboard password?",
469
+ initialValue: false,
470
+ }));
186
471
  if (!wantPassword)
187
472
  return undefined;
188
- return prompt(rl, "Admin password: ", (p) => p.length >= 8, "Password must be at least 8 characters.");
473
+ const password = required(await p.password({
474
+ message: "Admin password",
475
+ validate: (v) => v && v.length >= 8 ? undefined : "Must be at least 8 characters.",
476
+ }));
477
+ return password;
189
478
  }
190
- async function collectSlack(rl) {
191
- console.log("\n── Step 6: Slack (optional) ───────────────────────────────");
192
- const wantSlack = await promptYesNo(rl, "Enable Slack integration?", false);
479
+ async function collectSlack() {
480
+ p.log.step(gold("Slack") + dim(" (optional)"));
481
+ const wantSlack = required(await p.confirm({
482
+ message: "Enable Slack integration?",
483
+ initialValue: false,
484
+ }));
193
485
  if (!wantSlack)
194
486
  return {};
195
- const botToken = await prompt(rl, "SLACK_BOT_TOKEN (xoxb-...): ", isSlackBotToken, "Must start with xoxb-");
196
- const appToken = await prompt(rl, "SLACK_APP_TOKEN (xapp-...): ", isSlackAppToken, "Must start with xapp-");
197
- const deliveryChannelRaw = (await rl.question("SLACK_DELIVERY_CHANNEL (optional, press Enter to skip): ")).trim();
198
- const deliveryChannel = deliveryChannelRaw || undefined;
199
- const allowedUsersRaw = (await rl.question("SLACK_ALLOWED_USERS (optional comma-separated user IDs, press Enter to skip): ")).trim();
200
- const allowedUsers = allowedUsersRaw || undefined;
201
- return { botToken, appToken, deliveryChannel, allowedUsers };
487
+ const botToken = required(await p.text({
488
+ message: "SLACK_BOT_TOKEN",
489
+ placeholder: "xoxb-...",
490
+ validate: (v) => v && isSlackBotToken(v) ? undefined : "Must start with xoxb-",
491
+ }));
492
+ const appToken = required(await p.text({
493
+ message: "SLACK_APP_TOKEN",
494
+ placeholder: "xapp-...",
495
+ validate: (v) => v && isSlackAppToken(v) ? undefined : "Must start with xapp-",
496
+ }));
497
+ const deliveryChannel = required(await p.text({
498
+ message: "Delivery channel ID",
499
+ placeholder: "C0123456789 (press Enter to skip)",
500
+ defaultValue: "",
501
+ }));
502
+ const allowedUsers = required(await p.text({
503
+ message: "Allowed user IDs",
504
+ placeholder: "U0123,U0456 (press Enter to skip)",
505
+ defaultValue: "",
506
+ }));
507
+ return {
508
+ botToken,
509
+ appToken,
510
+ deliveryChannel: deliveryChannel || undefined,
511
+ allowedUsers: allowedUsers || undefined,
512
+ };
202
513
  }
203
514
  function writeConfig(config) {
204
- console.log("\n── Step 7: Writing config ─────────────────────────────────");
205
- // Create secrets/ directory
515
+ p.log.step(gold("Writing config"));
516
+ const secretsDir = join("instance", "secrets");
206
517
  try {
207
- mkdirSync("secrets", { recursive: true });
518
+ mkdirSync(secretsDir, { recursive: true });
208
519
  }
209
520
  catch (err) {
210
521
  const e = err;
211
522
  if (e.code === "EACCES") {
212
- console.error("Permission denied creating secrets/ directory.");
523
+ p.log.error("Permission denied creating instance/secrets/ directory.");
213
524
  process.exit(1);
214
525
  }
215
526
  throw err;
216
527
  }
217
- // Copy PEM to secrets/app.pem
218
- copyFileSync(config.pemSourcePath, join("secrets", "app.pem"));
219
- chmodSync(join("secrets", "app.pem"), 0o600);
220
- // Write .env atomically (build in memory then write once)
528
+ // Secrets instance/secrets/ (mounted at /app/instance, never committed/baked).
529
+ copyFileSync(config.pemSourcePath, join(secretsDir, "app.pem"));
530
+ chmodSync(join(secretsDir, "app.pem"), 0o600);
221
531
  const envContent = buildEnvContent(config);
222
- writeFileSync(".env", envContent, { encoding: "utf8" });
223
- chmodSync(".env", 0o600);
224
- console.log(" .env written (mode 600)");
225
- console.log(" secrets/app.pem copied (mode 600)");
532
+ writeFileSync(join(secretsDir, ".env"), envContent, { encoding: "utf8" });
533
+ chmodSync(join(secretsDir, ".env"), 0o600);
534
+ // Overlay config + .gitignore so instance/ can become a private repo.
535
+ writeFileSync(join("instance", "config.yaml"), buildOverlayConfig(config), { encoding: "utf8" });
536
+ if (!existsSync(join("instance", ".gitignore"))) {
537
+ writeFileSync(join("instance", ".gitignore"), OVERLAY_GITIGNORE, { encoding: "utf8" });
538
+ }
539
+ // Opted out of Caddy → disable it via an overlay compose override.
540
+ const overlayOverride = join("instance", OVERRIDE_FILE);
541
+ if (!config.useCaddy && !existsSync(overlayOverride)) {
542
+ writeFileSync(overlayOverride, CADDY_DISABLED_OVERRIDE, { encoding: "utf8" });
543
+ }
544
+ // Symlink the overlay override (generated above, or pre-existing from a
545
+ // cloned overlay) into the project dir so `docker compose` auto-loads it.
546
+ ensureOverrideSymlink();
547
+ p.log.success(dim("instance/secrets/app.pem") + " copied " + dim("(mode 600)") + "\n" +
548
+ " " + dim("instance/secrets/.env") + " written " + dim("(mode 600)") + "\n" +
549
+ " " + dim("instance/config.yaml") + " written " + dim(`(${config.managedRepos.length} managed repo${config.managedRepos.length === 1 ? "" : "s"})`));
550
+ }
551
+ /** Detect whether to use `docker compose` (v2) or `docker-compose` (v1/colima). */
552
+ function composeCmd() {
553
+ try {
554
+ execSync("docker compose version", { stdio: "ignore" });
555
+ return "docker compose";
556
+ }
557
+ catch {
558
+ try {
559
+ execSync("docker-compose version", { stdio: "ignore" });
560
+ return "docker-compose";
561
+ }
562
+ catch {
563
+ return "docker compose"; // fall through — will error with a clear message
564
+ }
565
+ }
226
566
  }
227
- async function dockerBuildAndLaunch(rl) {
228
- console.log("\n── Step 8: Docker ─────────────────────────────────────────");
229
- const wantLaunch = await promptYesNo(rl, "Build and launch Last Light with Docker now?", true);
567
+ async function dockerBuildAndLaunch() {
568
+ p.log.step(gold("Docker"));
569
+ const dc = composeCmd();
570
+ p.log.info(dim(`Using: ${dc}`));
571
+ const wantLaunch = required(await p.confirm({
572
+ message: "Build and launch Last Light now?",
573
+ initialValue: true,
574
+ }));
230
575
  if (!wantLaunch) {
231
- console.log("\nWhen ready, run:");
232
- console.log(" docker compose --profile build-only build sandbox");
233
- console.log(" docker compose up -d");
576
+ p.log.info("When ready, run:\n" +
577
+ dim(` ${dc} --profile build-only build sandbox\n`) +
578
+ dim(` ${dc} up -d`));
234
579
  return;
235
580
  }
236
- console.log("\nBuilding sandbox image...");
581
+ const s = p.spinner();
582
+ s.start("Building sandbox image...");
237
583
  try {
238
- execSync("docker compose --profile build-only build sandbox", {
239
- stdio: "inherit",
584
+ execSync(`${dc} --profile build-only build sandbox`, {
585
+ stdio: "pipe",
240
586
  });
587
+ s.stop("Sandbox image built.");
241
588
  }
242
589
  catch {
243
- console.error("Build failed. Check the output above and ensure Docker Compose v2 is installed.");
590
+ s.stop("Sandbox build failed.");
591
+ p.log.error("Check Docker Compose v2 is installed.");
244
592
  process.exit(1);
245
593
  }
246
- console.log("\nStarting services...");
594
+ s.start("Starting services...");
247
595
  try {
248
- execSync("docker compose up -d", { stdio: "inherit" });
596
+ execSync(`${dc} up -d`, { stdio: "pipe" });
597
+ s.stop("Services started.");
249
598
  }
250
599
  catch {
251
- console.error("docker compose up failed. Check the output above.");
600
+ s.stop("Failed to start services.");
601
+ p.log.error("Check docker compose logs for details.");
252
602
  process.exit(1);
253
603
  }
254
- // Check status
255
- try {
256
- execSync("docker compose ps", { stdio: "inherit" });
257
- }
258
- catch {
259
- // Non-fatal — status check is informational
260
- }
261
604
  }
262
605
  function printSummary(domain, webhookSecret) {
263
- console.log("\n── Step 9: Done! ──────────────────────────────────────────");
264
- console.log(`\nWebhook URL: https://${domain}/webhook`);
265
- console.log(`Dashboard URL: https://${domain}/admin`);
266
- console.log(`\nREMINDER: Paste this WEBHOOK_SECRET into your GitHub App settings:`);
267
- console.log(` ${webhookSecret}`);
268
- console.log(`\nNext steps:`);
269
- console.log(` - Read agent-context/rules.md to customise bot behaviour`);
270
- console.log(` - Verify webhook delivery in GitHub App settings`);
271
- console.log(` - Check logs: docker compose logs -f\n`);
606
+ console.log();
607
+ console.log(gold(" ┌──────────────────────────────────────────────┐"));
608
+ console.log(gold(" │") + bright(" Setup complete! ") + gold("│"));
609
+ console.log(gold(" └──────────────────────────────────────────────┘"));
610
+ console.log();
611
+ console.log(" " + dim("Webhook URL ") + teal(`https://${domain}/webhook`));
612
+ console.log(" " + dim("Dashboard ") + teal(`https://${domain}/admin`));
613
+ console.log();
614
+ console.log(" " + orange("Paste this WEBHOOK_SECRET into your GitHub App settings:"));
615
+ console.log(" " + bright(webhookSecret));
616
+ console.log();
617
+ console.log(" " + dim("Next steps:"));
618
+ console.log(" " + dim(" • Tune ") + teal("instance/config.yaml") + dim(" (managed repos, models, overrides), then ") + teal("docker compose restart agent"));
619
+ console.log(" " + dim(" • Optional: version your overlay — ") + teal("git -C instance init && …push to a private repo"));
620
+ console.log(" " + dim(" • Verify webhook delivery in GitHub App settings"));
621
+ console.log(" " + dim(" • Check logs: ") + teal("docker compose logs -f") + dim(" (or docker-compose)"));
622
+ console.log();
272
623
  }
273
624
  // ── Main entry point ─────────────────────────────────────────────────────────
274
625
  export async function runSetup() {
@@ -276,49 +627,40 @@ export async function runSetup() {
276
627
  console.error("Error: setup must be run interactively (stdin must be a TTY).");
277
628
  process.exit(1);
278
629
  }
279
- console.log("\nLast Light Setup Wizard");
280
- console.log("=======================");
281
- console.log("This wizard will configure Last Light on your server.\n");
282
- // Step 0 — Pre-flight
630
+ printBanner();
631
+ p.intro(dim("This wizard will configure Last Light on your server."));
283
632
  preflight();
284
- const rl = createInterface({ input: process.stdin, output: process.stdout });
285
- try {
286
- // Step 1 — GitHub App
287
- const { appId, installationId, pemSourcePath } = await collectGitHubApp(rl);
288
- // Step 2 Secrets
289
- const { webhookSecret, adminSecret } = generateSecrets();
290
- // Step 3 Domain + TLS
291
- const { domain, useCaddy } = await collectDomain(rl);
292
- // Step 4 Anthropic key
293
- const anthropicKey = await collectAnthropicKey(rl);
294
- // Step 5 — Admin password
295
- const adminPassword = await collectAdminPassword(rl);
296
- // Step 6 — Slack
297
- const { botToken, appToken, deliveryChannel, allowedUsers } = await collectSlack(rl);
298
- // Step 7 — Write config
299
- const config = {
300
- GITHUB_APP_ID: appId,
301
- GITHUB_APP_INSTALLATION_ID: installationId,
302
- WEBHOOK_SECRET: webhookSecret,
303
- ADMIN_SECRET: adminSecret,
304
- DOMAIN: domain,
305
- ANTHROPIC_API_KEY: anthropicKey,
306
- ADMIN_PASSWORD: adminPassword,
307
- SLACK_BOT_TOKEN: botToken,
308
- SLACK_APP_TOKEN: appToken,
309
- SLACK_DELIVERY_CHANNEL: deliveryChannel,
310
- SLACK_ALLOWED_USERS: allowedUsers,
311
- useCaddy,
312
- pemSourcePath,
313
- };
314
- writeConfig(config);
315
- // Step 8 — Docker
316
- await dockerBuildAndLaunch(rl);
317
- // Step 9 — Summary
318
- printSummary(domain, webhookSecret);
319
- }
320
- finally {
321
- rl.close();
322
- }
633
+ const { appId, installationId, pemSourcePath } = await collectGitHubApp();
634
+ const webhookSecret = randomBytes(32).toString("hex");
635
+ const adminSecret = randomBytes(32).toString("hex");
636
+ p.log.success("Secrets auto-generated " + dim("(WEBHOOK_SECRET + ADMIN_SECRET)"));
637
+ const { domain, useCaddy } = await collectDomain();
638
+ const managedRepos = await collectManagedRepos();
639
+ const { model, openaiKey, anthropicKey, openrouterKey } = await collectModelAndKey();
640
+ const adminPassword = await collectAdminPassword();
641
+ const { botToken, appToken, deliveryChannel, allowedUsers } = await collectSlack();
642
+ const config = {
643
+ GITHUB_APP_ID: appId,
644
+ GITHUB_APP_INSTALLATION_ID: installationId,
645
+ WEBHOOK_SECRET: webhookSecret,
646
+ ADMIN_SECRET: adminSecret,
647
+ DOMAIN: domain,
648
+ LASTLIGHT_MODEL: model,
649
+ OPENAI_API_KEY: openaiKey,
650
+ ANTHROPIC_API_KEY: anthropicKey,
651
+ OPENROUTER_API_KEY: openrouterKey,
652
+ ADMIN_PASSWORD: adminPassword,
653
+ SLACK_BOT_TOKEN: botToken,
654
+ SLACK_APP_TOKEN: appToken,
655
+ SLACK_DELIVERY_CHANNEL: deliveryChannel,
656
+ SLACK_ALLOWED_USERS: allowedUsers,
657
+ useCaddy,
658
+ pemSourcePath,
659
+ managedRepos,
660
+ };
661
+ writeConfig(config);
662
+ await dockerBuildAndLaunch();
663
+ p.outro(gold("Last Light is ready."));
664
+ printSummary(domain, webhookSecret);
323
665
  }
324
666
  //# sourceMappingURL=setup.js.map