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.
- package/README.md +265 -118
- package/agent-context/rules.md +62 -9
- package/agent-context/security.md +69 -0
- package/config/default.yaml +89 -0
- package/deploy/.env.production.example +20 -2
- package/deploy/entrypoint.sh +16 -17
- package/deploy/native/README.md +160 -0
- package/deploy/native/install.sh +110 -0
- package/deploy/native/lastlight.env.example +95 -0
- package/deploy/native/lastlight.service +50 -0
- package/deploy/sandbox-entrypoint.sh +59 -42
- package/dist/admin/auth.d.ts +1 -1
- package/dist/admin/auth.js +3 -1
- package/dist/admin/auth.js.map +1 -1
- package/dist/admin/chat-session-reader.d.ts +11 -14
- package/dist/admin/chat-session-reader.js +27 -51
- package/dist/admin/chat-session-reader.js.map +1 -1
- package/dist/admin/docker.d.ts +37 -0
- package/dist/admin/docker.js +86 -1
- package/dist/admin/docker.js.map +1 -1
- package/dist/admin/index.js +38 -3
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/log-search.test.d.ts +1 -0
- package/dist/admin/log-search.test.js +78 -0
- package/dist/admin/log-search.test.js.map +1 -0
- package/dist/admin/routes.d.ts +52 -3
- package/dist/admin/routes.js +780 -63
- package/dist/admin/routes.js.map +1 -1
- package/dist/admin/routes.test.js +680 -14
- package/dist/admin/routes.test.js.map +1 -1
- package/dist/admin/server-logs.test.d.ts +1 -0
- package/dist/admin/server-logs.test.js +58 -0
- package/dist/admin/server-logs.test.js.map +1 -0
- package/dist/admin/sessions.d.ts +26 -39
- package/dist/admin/sessions.js +86 -310
- package/dist/admin/sessions.js.map +1 -1
- package/dist/admin/sessions.test.d.ts +1 -0
- package/dist/admin/sessions.test.js +78 -0
- package/dist/admin/sessions.test.js.map +1 -0
- package/dist/admin/version.d.ts +15 -0
- package/dist/admin/version.js +73 -0
- package/dist/admin/version.js.map +1 -0
- package/dist/admin/version.test.d.ts +1 -0
- package/dist/admin/version.test.js +71 -0
- package/dist/admin/version.test.js.map +1 -0
- package/dist/cli-config.d.ts +55 -0
- package/dist/cli-config.js +119 -0
- package/dist/cli-config.js.map +1 -0
- package/dist/cli-config.test.d.ts +1 -0
- package/dist/cli-config.test.js +92 -0
- package/dist/cli-config.test.js.map +1 -0
- package/dist/cli-format.d.ts +19 -0
- package/dist/cli-format.js +107 -0
- package/dist/cli-format.js.map +1 -0
- package/dist/cli-server.d.ts +73 -0
- package/dist/cli-server.js +347 -0
- package/dist/cli-server.js.map +1 -0
- package/dist/cli-server.test.d.ts +1 -0
- package/dist/cli-server.test.js +38 -0
- package/dist/cli-server.test.js.map +1 -0
- package/dist/cli-timeline.d.ts +35 -0
- package/dist/cli-timeline.js +373 -0
- package/dist/cli-timeline.js.map +1 -0
- package/dist/cli-timeline.test.d.ts +1 -0
- package/dist/cli-timeline.test.js +104 -0
- package/dist/cli-timeline.test.js.map +1 -0
- package/dist/cli.d.ts +0 -19
- package/dist/cli.js +910 -194
- package/dist/cli.js.map +1 -1
- package/dist/config-overlay.test.d.ts +1 -0
- package/dist/config-overlay.test.js +112 -0
- package/dist/config-overlay.test.js.map +1 -0
- package/dist/config-resolve.d.ts +28 -0
- package/dist/config-resolve.js +47 -0
- package/dist/config-resolve.js.map +1 -0
- package/dist/config-resolve.test.d.ts +1 -0
- package/dist/config-resolve.test.js +68 -0
- package/dist/config-resolve.test.js.map +1 -0
- package/dist/config.d.ts +62 -49
- package/dist/config.js +452 -76
- package/dist/config.js.map +1 -1
- package/dist/config.test.js +201 -32
- package/dist/config.test.js.map +1 -1
- package/dist/connectors/github-webhook.js +83 -5
- package/dist/connectors/github-webhook.js.map +1 -1
- package/dist/connectors/github-webhook.test.d.ts +1 -0
- package/dist/connectors/github-webhook.test.js +156 -0
- package/dist/connectors/github-webhook.test.js.map +1 -0
- package/dist/connectors/messaging/session-manager.d.ts +18 -0
- package/dist/connectors/messaging/session-manager.js +83 -2
- package/dist/connectors/messaging/session-manager.js.map +1 -1
- package/dist/connectors/messaging/session-manager.test.d.ts +1 -0
- package/dist/connectors/messaging/session-manager.test.js +144 -0
- package/dist/connectors/messaging/session-manager.test.js.map +1 -0
- package/dist/connectors/slack/connector.d.ts +9 -0
- package/dist/connectors/slack/connector.js +15 -0
- package/dist/connectors/slack/connector.js.map +1 -1
- package/dist/connectors/slack/mrkdwn.js +81 -0
- package/dist/connectors/slack/mrkdwn.js.map +1 -1
- package/dist/connectors/slack/mrkdwn.test.js +49 -0
- package/dist/connectors/slack/mrkdwn.test.js.map +1 -1
- package/dist/connectors/types.d.ts +1 -1
- package/dist/cron/fanout.d.ts +33 -0
- package/dist/cron/fanout.js +55 -0
- package/dist/cron/fanout.js.map +1 -0
- package/dist/cron/fanout.test.d.ts +1 -0
- package/dist/cron/fanout.test.js +73 -0
- package/dist/cron/fanout.test.js.map +1 -0
- package/dist/cron/jobs.d.ts +5 -0
- package/dist/cron/jobs.js +10 -3
- package/dist/cron/jobs.js.map +1 -1
- package/dist/cron/scheduler.d.ts +12 -0
- package/dist/cron/scheduler.js +27 -1
- package/dist/cron/scheduler.js.map +1 -1
- package/dist/engine/agent-executor.d.ts +158 -0
- package/dist/engine/agent-executor.js +1140 -0
- package/dist/engine/agent-executor.js.map +1 -0
- package/dist/engine/agent-executor.test.d.ts +1 -0
- package/dist/engine/agent-executor.test.js +395 -0
- package/dist/engine/agent-executor.test.js.map +1 -0
- package/dist/engine/chat-runner.d.ts +123 -0
- package/dist/engine/chat-runner.js +379 -0
- package/dist/engine/chat-runner.js.map +1 -0
- package/dist/engine/chat-runner.test.d.ts +1 -0
- package/dist/engine/chat-runner.test.js +104 -0
- package/dist/engine/chat-runner.test.js.map +1 -0
- package/dist/engine/chat-skills.d.ts +45 -0
- package/dist/engine/chat-skills.js +184 -0
- package/dist/engine/chat-skills.js.map +1 -0
- package/dist/engine/chat-skills.test.d.ts +1 -0
- package/dist/engine/chat-skills.test.js +20 -0
- package/dist/engine/chat-skills.test.js.map +1 -0
- package/dist/engine/chat.d.ts +20 -23
- package/dist/engine/chat.js +251 -155
- package/dist/engine/chat.js.map +1 -1
- package/dist/engine/chat.test.d.ts +1 -0
- package/dist/engine/chat.test.js +42 -0
- package/dist/engine/chat.test.js.map +1 -0
- package/dist/engine/classifier.d.ts +35 -2
- package/dist/engine/classifier.js +170 -31
- package/dist/engine/classifier.js.map +1 -1
- package/dist/engine/classifier.test.d.ts +1 -0
- package/dist/engine/classifier.test.js +115 -0
- package/dist/engine/classifier.test.js.map +1 -0
- package/dist/engine/dispatcher.d.ts +60 -0
- package/dist/engine/dispatcher.js +601 -0
- package/dist/engine/dispatcher.js.map +1 -0
- package/dist/engine/dispatcher.test.d.ts +1 -0
- package/dist/engine/dispatcher.test.js +511 -0
- package/dist/engine/dispatcher.test.js.map +1 -0
- package/dist/engine/event-shim.d.ts +117 -0
- package/dist/engine/event-shim.js +435 -0
- package/dist/engine/event-shim.js.map +1 -0
- package/dist/engine/event-shim.test.d.ts +1 -0
- package/dist/engine/event-shim.test.js +194 -0
- package/dist/engine/event-shim.test.js.map +1 -0
- package/dist/engine/git-auth.d.ts +13 -17
- package/dist/engine/git-auth.js +99 -42
- package/dist/engine/git-auth.js.map +1 -1
- package/dist/engine/git-auth.test.d.ts +1 -0
- package/dist/engine/git-auth.test.js +117 -0
- package/dist/engine/git-auth.test.js.map +1 -0
- package/dist/engine/github-app-client.d.ts +7 -0
- package/dist/engine/github-app-client.js +16 -0
- package/dist/engine/github-app-client.js.map +1 -0
- package/dist/engine/github-app-client.test.d.ts +1 -0
- package/dist/engine/github-app-client.test.js +44 -0
- package/dist/engine/github-app-client.test.js.map +1 -0
- package/dist/engine/github-tools.d.ts +12 -0
- package/dist/engine/github-tools.js +261 -0
- package/dist/engine/github-tools.js.map +1 -0
- package/dist/engine/github.d.ts +1027 -43
- package/dist/engine/github.js +153 -14
- package/dist/engine/github.js.map +1 -1
- package/dist/engine/llm.d.ts +47 -0
- package/dist/engine/llm.js +221 -0
- package/dist/engine/llm.js.map +1 -0
- package/dist/engine/llm.test.d.ts +1 -0
- package/dist/engine/llm.test.js +189 -0
- package/dist/engine/llm.test.js.map +1 -0
- package/dist/engine/profiles.d.ts +249 -0
- package/dist/engine/profiles.js +42 -0
- package/dist/engine/profiles.js.map +1 -0
- package/dist/engine/router.d.ts +12 -6
- package/dist/engine/router.js +339 -81
- package/dist/engine/router.js.map +1 -1
- package/dist/engine/router.test.js +428 -78
- package/dist/engine/router.test.js.map +1 -1
- package/dist/engine/screen.d.ts +41 -0
- package/dist/engine/screen.js +93 -0
- package/dist/engine/screen.js.map +1 -0
- package/dist/engine/screen.test.d.ts +1 -0
- package/dist/engine/screen.test.js +82 -0
- package/dist/engine/screen.test.js.map +1 -0
- package/dist/index.js +423 -437
- package/dist/index.js.map +1 -1
- package/dist/managed-repos.d.ts +7 -9
- package/dist/managed-repos.js +12 -15
- package/dist/managed-repos.js.map +1 -1
- package/dist/managed-repos.test.js +24 -19
- package/dist/managed-repos.test.js.map +1 -1
- package/dist/notify/index.d.ts +15 -0
- package/dist/notify/index.js +6 -0
- package/dist/notify/index.js.map +1 -0
- package/dist/notify/model.d.ts +57 -0
- package/dist/notify/model.js +89 -0
- package/dist/notify/model.js.map +1 -0
- package/dist/notify/model.test.d.ts +1 -0
- package/dist/notify/model.test.js +109 -0
- package/dist/notify/model.test.js.map +1 -0
- package/dist/notify/notifier.d.ts +17 -0
- package/dist/notify/notifier.js +87 -0
- package/dist/notify/notifier.js.map +1 -0
- package/dist/notify/notifier.test.d.ts +1 -0
- package/dist/notify/notifier.test.js +100 -0
- package/dist/notify/notifier.test.js.map +1 -0
- package/dist/notify/render.d.ts +21 -0
- package/dist/notify/render.js +59 -0
- package/dist/notify/render.js.map +1 -0
- package/dist/notify/render.test.d.ts +1 -0
- package/dist/notify/render.test.js +65 -0
- package/dist/notify/render.test.js.map +1 -0
- package/dist/notify/transports/github.d.ts +26 -0
- package/dist/notify/transports/github.js +26 -0
- package/dist/notify/transports/github.js.map +1 -0
- package/dist/notify/transports/slack.d.ts +26 -0
- package/dist/notify/transports/slack.js +28 -0
- package/dist/notify/transports/slack.js.map +1 -0
- package/dist/notify/transports.test.d.ts +1 -0
- package/dist/notify/transports.test.js +71 -0
- package/dist/notify/transports.test.js.map +1 -0
- package/dist/notify/types.d.ts +95 -0
- package/dist/notify/types.js +16 -0
- package/dist/notify/types.js.map +1 -0
- package/dist/sandbox/docker-compose.test.d.ts +1 -0
- package/dist/sandbox/docker-compose.test.js +128 -0
- package/dist/sandbox/docker-compose.test.js.map +1 -0
- package/dist/sandbox/docker.d.ts +97 -9
- package/dist/sandbox/docker.js +192 -41
- package/dist/sandbox/docker.js.map +1 -1
- package/dist/sandbox/docker.test.d.ts +1 -0
- package/dist/sandbox/docker.test.js +137 -0
- package/dist/sandbox/docker.test.js.map +1 -0
- package/dist/sandbox/egress-allowlist.d.ts +69 -0
- package/dist/sandbox/egress-allowlist.js +164 -0
- package/dist/sandbox/egress-allowlist.js.map +1 -0
- package/dist/sandbox/egress-allowlist.test.d.ts +1 -0
- package/dist/sandbox/egress-allowlist.test.js +65 -0
- package/dist/sandbox/egress-allowlist.test.js.map +1 -0
- package/dist/sandbox/egress-firewall-config.d.ts +127 -0
- package/dist/sandbox/egress-firewall-config.js +434 -0
- package/dist/sandbox/egress-firewall-config.js.map +1 -0
- package/dist/sandbox/egress-firewall-config.test.d.ts +1 -0
- package/dist/sandbox/egress-firewall-config.test.js +244 -0
- package/dist/sandbox/egress-firewall-config.test.js.map +1 -0
- package/dist/sandbox/images.d.ts +23 -0
- package/dist/sandbox/images.js +55 -0
- package/dist/sandbox/images.js.map +1 -0
- package/dist/sandbox/index.d.ts +82 -3
- package/dist/sandbox/index.js +223 -93
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/index.test.d.ts +1 -0
- package/dist/sandbox/index.test.js +157 -0
- package/dist/sandbox/index.test.js.map +1 -0
- package/dist/session-log.d.ts +63 -0
- package/dist/session-log.js +290 -0
- package/dist/session-log.js.map +1 -0
- package/dist/session-log.test.d.ts +1 -0
- package/dist/session-log.test.js +85 -0
- package/dist/session-log.test.js.map +1 -0
- package/dist/setup.d.ts +31 -12
- package/dist/setup.js +516 -174
- package/dist/setup.js.map +1 -1
- package/dist/setup.test.js +138 -6
- package/dist/setup.test.js.map +1 -1
- package/dist/state/approval-store.d.ts +86 -0
- package/dist/state/approval-store.js +140 -0
- package/dist/state/approval-store.js.map +1 -0
- package/dist/state/build-assets.d.ts +50 -0
- package/dist/state/build-assets.js +154 -0
- package/dist/state/build-assets.js.map +1 -0
- package/dist/state/build-assets.test.d.ts +1 -0
- package/dist/state/build-assets.test.js +106 -0
- package/dist/state/build-assets.test.js.map +1 -0
- package/dist/state/db.d.ts +65 -333
- package/dist/state/db.js +112 -835
- package/dist/state/db.js.map +1 -1
- package/dist/state/db.test.js +127 -91
- package/dist/state/db.test.js.map +1 -1
- package/dist/state/execution-store.d.ts +257 -0
- package/dist/state/execution-store.js +527 -0
- package/dist/state/execution-store.js.map +1 -0
- package/dist/state/migrate.d.ts +15 -0
- package/dist/state/migrate.js +175 -0
- package/dist/state/migrate.js.map +1 -0
- package/dist/state/workflow-run-store.d.ts +195 -0
- package/dist/state/workflow-run-store.js +363 -0
- package/dist/state/workflow-run-store.js.map +1 -0
- package/dist/state/workflow-run-store.test.d.ts +1 -0
- package/dist/state/workflow-run-store.test.js +264 -0
- package/dist/state/workflow-run-store.test.js.map +1 -0
- package/dist/telemetry/index.d.ts +38 -0
- package/dist/telemetry/index.js +253 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/index.test.d.ts +1 -0
- package/dist/telemetry/index.test.js +73 -0
- package/dist/telemetry/index.test.js.map +1 -0
- package/dist/telemetry/pi-events.d.ts +12 -0
- package/dist/telemetry/pi-events.js +134 -0
- package/dist/telemetry/pi-events.js.map +1 -0
- package/dist/telemetry/pi-events.test.d.ts +1 -0
- package/dist/telemetry/pi-events.test.js +69 -0
- package/dist/telemetry/pi-events.test.js.map +1 -0
- package/dist/workflows/dag.d.ts +13 -1
- package/dist/workflows/dag.js +7 -3
- package/dist/workflows/dag.js.map +1 -1
- package/dist/workflows/dag.test.js +22 -0
- package/dist/workflows/dag.test.js.map +1 -1
- package/dist/workflows/golden-build.test.d.ts +1 -0
- package/dist/workflows/golden-build.test.js +45 -0
- package/dist/workflows/golden-build.test.js.map +1 -0
- package/dist/workflows/loader-overlay.test.d.ts +1 -0
- package/dist/workflows/loader-overlay.test.js +101 -0
- package/dist/workflows/loader-overlay.test.js.map +1 -0
- package/dist/workflows/loader.d.ts +36 -11
- package/dist/workflows/loader.js +311 -87
- package/dist/workflows/loader.js.map +1 -1
- package/dist/workflows/loader.test.js +114 -10
- package/dist/workflows/loader.test.js.map +1 -1
- package/dist/workflows/loop-eval.js +2 -0
- package/dist/workflows/loop-eval.js.map +1 -1
- package/dist/workflows/loop-eval.test.js +14 -0
- package/dist/workflows/loop-eval.test.js.map +1 -1
- package/dist/workflows/phase-executor.d.ts +145 -0
- package/dist/workflows/phase-executor.js +691 -0
- package/dist/workflows/phase-executor.js.map +1 -0
- package/dist/workflows/phase-executor.test.d.ts +1 -0
- package/dist/workflows/phase-executor.test.js +434 -0
- package/dist/workflows/phase-executor.test.js.map +1 -0
- package/dist/workflows/phase-ref.d.ts +44 -0
- package/dist/workflows/phase-ref.js +78 -0
- package/dist/workflows/phase-ref.js.map +1 -0
- package/dist/workflows/phase-ref.test.d.ts +1 -0
- package/dist/workflows/phase-ref.test.js +24 -0
- package/dist/workflows/phase-ref.test.js.map +1 -0
- package/dist/workflows/resume.d.ts +5 -11
- package/dist/workflows/resume.js +88 -7
- package/dist/workflows/resume.js.map +1 -1
- package/dist/workflows/runner.d.ts +34 -27
- package/dist/workflows/runner.js +252 -930
- package/dist/workflows/runner.js.map +1 -1
- package/dist/workflows/runner.test.js +346 -35
- package/dist/workflows/runner.test.js.map +1 -1
- package/dist/workflows/schema.d.ts +52 -10
- package/dist/workflows/schema.js +147 -7
- package/dist/workflows/schema.js.map +1 -1
- package/dist/workflows/simple.d.ts +67 -3
- package/dist/workflows/simple.js +248 -87
- package/dist/workflows/simple.js.map +1 -1
- package/dist/workflows/simple.test.d.ts +1 -0
- package/dist/workflows/simple.test.js +107 -0
- package/dist/workflows/simple.test.js.map +1 -0
- package/dist/workflows/templates.d.ts +29 -0
- package/dist/workflows/templates.js +42 -4
- package/dist/workflows/templates.js.map +1 -1
- package/dist/workflows/templates.test.js +103 -0
- package/dist/workflows/templates.test.js.map +1 -1
- package/dist/workflows/triggers.d.ts +21 -0
- package/dist/workflows/triggers.js +44 -0
- package/dist/workflows/triggers.js.map +1 -0
- package/dist/workflows/verdict.d.ts +19 -0
- package/dist/workflows/verdict.js +30 -0
- package/dist/workflows/verdict.js.map +1 -0
- package/dist/workflows/verdict.test.d.ts +1 -0
- package/dist/workflows/verdict.test.js +44 -0
- package/dist/workflows/verdict.test.js.map +1 -0
- package/dist/worktree/manager.d.ts +1 -1
- package/dist/worktree/manager.js +14 -14
- package/dist/worktree/manager.js.map +1 -1
- package/dist/worktree/manager.test.d.ts +1 -0
- package/dist/worktree/manager.test.js +87 -0
- package/dist/worktree/manager.test.js.map +1 -0
- package/docker-compose.yml +206 -5
- package/package.json +19 -7
- package/sandbox.Dockerfile +97 -18
- package/skills/README.md +40 -0
- package/skills/browser-qa/SKILL.md +193 -0
- package/skills/browser-qa/scripts/agent-browser.mjs +380 -0
- package/skills/building/SKILL.md +95 -0
- package/skills/chat/SKILL.md +30 -11
- package/skills/code-review/SKILL.md +59 -0
- package/skills/debug-production/SKILL.md +108 -0
- package/skills/demo/SKILL.md +194 -0
- package/skills/demo/scripts/compose-demo.sh +205 -0
- package/skills/issue-answer/SKILL.md +90 -0
- package/skills/issue-comment/SKILL.md +38 -24
- package/skills/issue-triage/SKILL.md +103 -37
- package/skills/issue-triage/references/AGENT-BRIEF.md +61 -0
- package/skills/pr-comment/SKILL.md +64 -0
- package/skills/pr-review/SKILL.md +65 -49
- package/skills/qa-test/SKILL.md +97 -0
- package/skills/repo-health/SKILL.md +48 -45
- package/skills/security-feedback/SKILL.md +173 -0
- package/skills/security-feedback/references/templates.md +77 -0
- package/skills/security-review/SKILL.md +213 -0
- package/skills/security-review/references/issue-format.md +302 -0
- package/skills/verify/SKILL.md +118 -0
- package/workflows/answer.yaml +37 -0
- package/workflows/build.yaml +32 -22
- package/workflows/cron-security.yaml +6 -0
- package/workflows/demo.yaml +55 -0
- package/workflows/explore.yaml +9 -0
- package/workflows/pr-comment.yaml +16 -0
- package/workflows/pr-fix.yaml +3 -0
- package/workflows/pr-review.yaml +4 -1
- package/workflows/prompts/answer.md +67 -0
- package/workflows/prompts/architect.md +24 -7
- package/workflows/prompts/demo.md +100 -0
- package/workflows/prompts/executor.md +29 -20
- package/workflows/prompts/explore-ask.md +51 -22
- package/workflows/prompts/explore-publish.md +2 -2
- package/workflows/prompts/explore-read.md +24 -10
- package/workflows/prompts/explore-synthesize.md +15 -8
- package/workflows/prompts/fix.md +9 -10
- package/workflows/prompts/guardrails.md +26 -8
- package/workflows/prompts/pr-fix.md +6 -7
- package/workflows/prompts/pr.md +11 -10
- package/workflows/prompts/qa-browser.md +95 -0
- package/workflows/prompts/qa-synth.md +44 -0
- package/workflows/prompts/qa-test.md +60 -0
- package/workflows/prompts/re-reviewer.md +8 -9
- package/workflows/prompts/reviewer.md +10 -9
- package/workflows/prompts/verify-browser.md +93 -0
- package/workflows/prompts/verify-synth.md +44 -0
- package/workflows/prompts/verify.md +63 -0
- package/workflows/qa-test.yaml +87 -0
- package/workflows/security-feedback.yaml +25 -0
- package/workflows/security-review.yaml +29 -0
- package/workflows/verify.yaml +85 -0
- package/deploy/mcp-config.tmpl.json +0 -14
- package/dist/cron/rate-limits.d.ts +0 -26
- package/dist/cron/rate-limits.js +0 -193
- package/dist/cron/rate-limits.js.map +0 -1
- package/dist/engine/executor.d.ts +0 -75
- package/dist/engine/executor.js +0 -292
- package/dist/engine/executor.js.map +0 -1
- package/mcp-github-app/package.json +0 -18
- package/mcp-github-app/src/auth.js +0 -66
- package/mcp-github-app/src/github.js +0 -371
- package/mcp-github-app/src/index.js +0 -534
- package/skills/devops/webhook-subscriptions/SKILL.md +0 -180
- package/skills/github/DESCRIPTION.md +0 -3
- package/skills/github/codebase-inspection/SKILL.md +0 -115
- package/skills/github/github-auth/SKILL.md +0 -54
- package/skills/github/github-auth/scripts/gh-env.sh +0 -66
- package/skills/github/github-code-review/SKILL.md +0 -480
- package/skills/github/github-code-review/references/review-output-template.md +0 -74
- package/skills/github/github-issues/SKILL.md +0 -369
- package/skills/github/github-issues/templates/bug-report.md +0 -35
- package/skills/github/github-issues/templates/feature-request.md +0 -31
- package/skills/github/github-pr-workflow/SKILL.md +0 -395
- package/skills/github/github-pr-workflow/references/ci-troubleshooting.md +0 -183
- package/skills/github/github-pr-workflow/references/conventional-commits.md +0 -71
- package/skills/github/github-pr-workflow/templates/pr-body-bugfix.md +0 -35
- package/skills/github/github-pr-workflow/templates/pr-body-feature.md +0 -33
- package/skills/github/github-repo-management/SKILL.md +0 -515
- package/skills/github/github-repo-management/references/github-api-cheatsheet.md +0 -161
- package/skills/github-orchestrator/SKILL.md +0 -103
- package/skills/mcp/DESCRIPTION.md +0 -3
- package/skills/mcp/mcporter/SKILL.md +0 -122
- package/skills/mcp/native-mcp/SKILL.md +0 -356
- package/skills/software-development/architect/SKILL.md +0 -154
- package/skills/software-development/assure-guardrails/SKILL.md +0 -239
- package/skills/software-development/plan/SKILL.md +0 -64
- package/skills/software-development/requesting-code-review/SKILL.md +0 -291
- package/skills/software-development/subagent-driven-development/SKILL.md +0 -433
- package/skills/software-development/systematic-debugging/SKILL.md +0 -366
- 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
|
|
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
|
-
*
|
|
21
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
"# ──
|
|
64
|
-
`
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
// ──
|
|
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
|
-
|
|
231
|
+
p.log.info("Found existing lastlight/ directory — continuing setup there.");
|
|
117
232
|
process.chdir("lastlight");
|
|
118
233
|
}
|
|
119
234
|
else {
|
|
120
|
-
|
|
121
|
-
|
|
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: "
|
|
239
|
+
stdio: "pipe",
|
|
125
240
|
});
|
|
241
|
+
s.stop("Repository cloned.");
|
|
126
242
|
}
|
|
127
243
|
catch {
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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:
|
|
334
|
+
appId: appId,
|
|
335
|
+
installationId: installationId,
|
|
336
|
+
pemSourcePath: pemResolved,
|
|
162
337
|
};
|
|
163
338
|
}
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
const
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
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
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
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(
|
|
191
|
-
|
|
192
|
-
const wantSlack = await
|
|
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
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
const
|
|
201
|
-
|
|
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
|
-
|
|
205
|
-
|
|
515
|
+
p.log.step(gold("Writing config"));
|
|
516
|
+
const secretsDir = join("instance", "secrets");
|
|
206
517
|
try {
|
|
207
|
-
mkdirSync(
|
|
518
|
+
mkdirSync(secretsDir, { recursive: true });
|
|
208
519
|
}
|
|
209
520
|
catch (err) {
|
|
210
521
|
const e = err;
|
|
211
522
|
if (e.code === "EACCES") {
|
|
212
|
-
|
|
523
|
+
p.log.error("Permission denied creating instance/secrets/ directory.");
|
|
213
524
|
process.exit(1);
|
|
214
525
|
}
|
|
215
526
|
throw err;
|
|
216
527
|
}
|
|
217
|
-
//
|
|
218
|
-
copyFileSync(config.pemSourcePath, join(
|
|
219
|
-
chmodSync(join(
|
|
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
|
-
|
|
225
|
-
|
|
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(
|
|
228
|
-
|
|
229
|
-
const
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
|
|
581
|
+
const s = p.spinner();
|
|
582
|
+
s.start("Building sandbox image...");
|
|
237
583
|
try {
|
|
238
|
-
execSync(
|
|
239
|
-
stdio: "
|
|
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
|
-
|
|
590
|
+
s.stop("Sandbox build failed.");
|
|
591
|
+
p.log.error("Check Docker Compose v2 is installed.");
|
|
244
592
|
process.exit(1);
|
|
245
593
|
}
|
|
246
|
-
|
|
594
|
+
s.start("Starting services...");
|
|
247
595
|
try {
|
|
248
|
-
execSync(
|
|
596
|
+
execSync(`${dc} up -d`, { stdio: "pipe" });
|
|
597
|
+
s.stop("Services started.");
|
|
249
598
|
}
|
|
250
599
|
catch {
|
|
251
|
-
|
|
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(
|
|
264
|
-
console.log(
|
|
265
|
-
console.log(
|
|
266
|
-
console.log(
|
|
267
|
-
console.log(
|
|
268
|
-
console.log(
|
|
269
|
-
console.log(
|
|
270
|
-
console.log(
|
|
271
|
-
console.log(
|
|
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
|
-
|
|
280
|
-
|
|
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
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|