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