loki-mode 7.43.0 → 7.45.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 CHANGED
@@ -13,7 +13,7 @@ _The free, source-available autonomous coding agent by [Autonomi](https://www.au
13
13
  [![Docker Pulls](https://img.shields.io/docker/pulls/asklokesh/loki-mode?style=for-the-badge&logo=docker&logoColor=white&color=2F71E3)](https://hub.docker.com/r/asklokesh/loki-mode)
14
14
  [![License](https://img.shields.io/badge/License-BUSL--1.1-36342E?style=for-the-badge)](LICENSE)
15
15
 
16
- [Website](https://www.autonomi.dev/) | [Documentation](wiki/Home.md) | [Installation](docs/INSTALLATION.md) | [Changelog](CHANGELOG.md) | [Purple Lab Web UI](#purple-lab)
16
+ [Website](https://www.autonomi.dev/) | [Documentation](wiki/Home.md) | [Installation](docs/INSTALLATION.md) | [Changelog](CHANGELOG.md) | [Purple Lab -- deprecated v7.44.0](#purple-lab)
17
17
 
18
18
  </div>
19
19
 
@@ -105,7 +105,8 @@ loki quick "build a landing page with a signup form"
105
105
  |--------|---------|-------|
106
106
  | **Bun (recommended)** | `bun install -g loki-mode` | Fastest startup for CLI commands. |
107
107
  | **Homebrew** | `brew tap asklokesh/tap && brew install loki-mode` | Auto-installs Bun as a dep |
108
- | **Docker** | `docker pull asklokesh/loki-mode:7.40.0 && docker run --rm asklokesh/loki-mode:7.40.0 start prd.md` | Bun pre-installed in image |
108
+ | **Docker (easiest)** | `loki docker start prd.md` | Host wrapper: runs loki in the published image with zero config. Bind-mounts the current folder so `.loki` state, resume, and continuity work exactly like local. Auto-detects auth (`ANTHROPIC_API_KEY`, else your host Claude Code login). Needs loki + Docker on the host. See DOCKER_README.md |
109
+ | **Docker (raw)** | `docker pull asklokesh/loki-mode:7.45.0 && docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" asklokesh/loki-mode:7.45.0 start prd.md` | Bun + Claude CLI pre-installed; needs an API key, or use docker compose with a .env file, see DOCKER_README.md |
109
110
  | **npm (compat)** | `npm install -g loki-mode` | Works without Bun (bash fallback). Migrate any time with `loki self-update --to bun`. |
110
111
 
111
112
  **Upgrading:**
@@ -165,7 +166,7 @@ The next major release sunsets the Bash runtime entirely. There is no firm calen
165
166
  | Method | Command |
166
167
  |--------|---------|
167
168
  | **Homebrew** | `brew tap asklokesh/tap && brew install loki-mode` |
168
- | **Docker** | `docker pull asklokesh/loki-mode:7.40.0` |
169
+ | **Docker** | `docker pull asklokesh/loki-mode:7.45.0` |
169
170
  | **Inside Claude Code** | `claude --dangerously-skip-permissions` then type "Loki Mode" |
170
171
  | **Git clone** | `git clone https://github.com/asklokesh/loki-mode.git` |
171
172
 
@@ -290,36 +291,9 @@ TLS, OIDC/SSO, RBAC, OTEL tracing, policy engine, audit trails. Activated via en
290
291
 
291
292
  ## Purple Lab
292
293
 
293
- The hosted development platform. A Replit-like web UI for visual PRD-to-code workflow, with the Loki agent for iterative development. The same software is free and source-available as the local Loki Mode dashboard; offered managed to teams and enterprises under the **Autonomi** brand (Autonomi Cloud).
294
+ **[DEPRECATED in v7.44.0]** Purple Lab (`loki web`, port 57375) is deprecated. The local build monitor and project dashboard are now the dashboard (auto-launched by `loki start`, http://localhost:57374). For the hosted/commercial platform, see [Autonomi Cloud](https://www.autonomi.dev/).
294
295
 
295
- ```bash
296
- loki web # launches at http://localhost:57375
297
- ```
298
-
299
- <table>
300
- <tr>
301
- <td width="50%" valign="top">
302
-
303
- **Platform Pages**
304
- - Home -- One-line prompt to start building instantly
305
- - Projects -- Browse, search, filter past builds
306
- - Templates -- 20+ starter PRDs by category
307
- - Showcase -- Gallery of example projects to build
308
- - Compare -- Feature comparison vs competitors
309
-
310
- </td>
311
- <td width="50%" valign="top">
312
-
313
- **IDE Workspace**
314
- - Monaco editor with tabs, Cmd+P quick open
315
- - AI chat panel for iterative development
316
- - Activity panel: build log, agents, quality gates
317
- - Live preview with URL bar navigation
318
- - Right-click context menu: Review, Test, Explain
319
-
320
- </td>
321
- </tr>
322
- </table>
296
+ The historical feature set (platform pages, Monaco IDE workspace, AI chat panel) lives on in the dashboard and in Autonomi Cloud. `loki web` still invokes the old binary for backward compatibility but will be removed in a future major version.
323
297
 
324
298
  ---
325
299
 
@@ -372,7 +346,7 @@ Status legend: "E2E-verified" means we run real spec-to-code builds on it oursel
372
346
  | `loki status` | Show current status |
373
347
  | `loki dashboard` | Open web dashboard |
374
348
  | `loki preview` | Print running app URL and open in browser (Live App Preview, v7.24.0; was: `loki open`) |
375
- | `loki web` | Launch Purple Lab web UI |
349
+ | `loki web` | Launch Purple Lab web UI [DEPRECATED in v7.44.0 -- use `loki start` which auto-opens the dashboard at http://localhost:57374; for the hosted platform see Autonomi Cloud] |
376
350
  | `loki doctor` | Check environment and dependencies |
377
351
  | `loki plan [PRD]` | Pre-execution analysis: complexity, cost, iterations |
378
352
  | `loki review [--staged\|--diff]` | AI-powered code review with severity filtering |
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 11 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v7.43.0
6
+ # Loki Mode v7.45.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -219,6 +219,15 @@ loki start --provider aider ./prd.md # Aider (18+ providers), degraded m
219
219
  loki start ./prd.md --parallel
220
220
  loki start 123 --ship # Issue -> PR -> auto-merge
221
221
 
222
+ # Run any loki command inside the published Docker image, zero config (v7.45.0).
223
+ # Bind-mounts the current folder to /workspace so .loki state, resume, and
224
+ # continuity behave exactly like the local CLI. Auth auto-detected: ANTHROPIC_API_KEY,
225
+ # else the host Claude Code login (Max/Pro), else an honest error. Requires loki + Docker on the host.
226
+ loki docker start prd.md # full local experience in Docker
227
+ loki docker status # any loki command works
228
+ loki docker --dry-run start prd.md # print the docker command, do not run
229
+ loki docker --image IMG start prd.md # override the image
230
+
222
231
  # Legacy: `loki run <issue>` still works but prints a deprecation notice.
223
232
  # It is an alias for `loki start <issue>` and will be removed in a future major.
224
233
  ```
@@ -398,4 +407,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
398
407
 
399
408
  ---
400
409
 
401
- **v7.43.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
410
+ **v7.45.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.43.0
1
+ 7.45.0
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env bash
2
+ #===============================================================================
3
+ # Loki Mode - zero-friction Docker host wrapper
4
+ #
5
+ # Lets a user run loki inside the published Docker image with the SAME
6
+ # experience as the local CLI: run it in a project folder and it just works.
7
+ # .loki/ state (memory, session, queue, checkpoints) persists via the workspace
8
+ # bind mount, so resume and continuity behave exactly like `loki start` on the
9
+ # host.
10
+ #
11
+ # Auth precedence (zero config for Claude Code subscribers):
12
+ # 1. ANTHROPIC_API_KEY set in the environment -> passed through (explicit).
13
+ # 2. else host Claude Code login auto-detected -> credentials extracted to a
14
+ # per-run temp copy and mounted read-write so the in-container claude can
15
+ # refresh the short-lived token for the duration of the run. Each run
16
+ # re-extracts a fresh token from the host store, so a long-idle token is
17
+ # never an issue. (The temp copy is wiped on exit; refreshes are not written
18
+ # back to the host store, which is why per-run re-extraction matters.)
19
+ # 3. else -> honest error with guidance.
20
+ #
21
+ # Token handling: the extracted credentials file is written to a private temp
22
+ # path (0600) and removed on exit. It is NEVER written into the project, never
23
+ # committed, never logged.
24
+ #
25
+ # Sourced by autonomy/loki (cmd_docker). Also runnable standalone for tests:
26
+ # loki_docker_detect_auth -> prints: apikey | oauth | none
27
+ # loki_docker_extract_creds <dest> -> writes host OAuth creds to <dest>
28
+ # loki_docker_build_argv ... -> prints the docker argv it would run
29
+ #===============================================================================
30
+
31
+ # Default image. Overridable for local builds / pinning.
32
+ : "${LOKI_DOCKER_IMAGE:=asklokesh/loki-mode:latest}"
33
+ # In-container path claude reads OAuth credentials from (Linux, no keychain).
34
+ _LOKI_DOCKER_CRED_DEST="/home/loki/.claude/.credentials.json"
35
+ # Dashboard port (kept identical to the local default).
36
+ : "${LOKI_DASHBOARD_PORT:=57374}"
37
+
38
+ # Detect which auth method is available on this host.
39
+ # Echoes one of: apikey | oauth | none
40
+ loki_docker_detect_auth() {
41
+ if [ -n "${ANTHROPIC_API_KEY:-}" ]; then
42
+ echo "apikey"
43
+ return 0
44
+ fi
45
+ # macOS: token in the login keychain under "Claude Code-credentials".
46
+ if command -v security >/dev/null 2>&1; then
47
+ if security find-generic-password -s "Claude Code-credentials" -w >/dev/null 2>&1; then
48
+ echo "oauth"
49
+ return 0
50
+ fi
51
+ fi
52
+ # Linux / non-keychain: claude stores creds as a plain file.
53
+ if [ -f "${HOME}/.claude/.credentials.json" ]; then
54
+ echo "oauth"
55
+ return 0
56
+ fi
57
+ echo "none"
58
+ return 0
59
+ }
60
+
61
+ # Extract host Claude Code OAuth credentials into $1 (0600). Returns non-zero
62
+ # if no host login is found. Writes ONLY the claudeAiOauth object claude needs;
63
+ # never the unrelated mcpOAuth blobs.
64
+ loki_docker_extract_creds() {
65
+ local dest="$1"
66
+ [ -n "$dest" ] || { echo "loki_docker_extract_creds: missing dest" >&2; return 2; }
67
+
68
+ # Create the file privately BEFORE writing the token (no race on perms).
69
+ ( umask 077; : > "$dest" ) || return 2
70
+ chmod 600 "$dest" 2>/dev/null || true
71
+
72
+ local raw=""
73
+ if command -v security >/dev/null 2>&1; then
74
+ raw="$(security find-generic-password -s "Claude Code-credentials" -w 2>/dev/null || true)"
75
+ fi
76
+ if [ -z "$raw" ] && [ -f "${HOME}/.claude/.credentials.json" ]; then
77
+ raw="$(cat "${HOME}/.claude/.credentials.json" 2>/dev/null || true)"
78
+ fi
79
+ if [ -z "$raw" ]; then
80
+ echo "loki_docker_extract_creds: no host Claude Code login found" >&2
81
+ return 1
82
+ fi
83
+
84
+ # Normalize to {"claudeAiOauth": {...}}. Prefer jq; fall back to raw if jq
85
+ # is absent and the payload already looks like the right shape.
86
+ if command -v jq >/dev/null 2>&1; then
87
+ if ! printf '%s' "$raw" | jq -e 'has("claudeAiOauth")' >/dev/null 2>&1; then
88
+ echo "loki_docker_extract_creds: host credentials missing claudeAiOauth" >&2
89
+ return 1
90
+ fi
91
+ printf '%s' "$raw" | jq '{claudeAiOauth}' > "$dest" 2>/dev/null || return 2
92
+ else
93
+ printf '%s' "$raw" > "$dest"
94
+ fi
95
+ chmod 600 "$dest" 2>/dev/null || true
96
+ return 0
97
+ }
98
+
99
+ # Assemble the docker argv (printed one arg per line so callers can read it
100
+ # into an array safely). $1 = auth method (apikey|oauth|none), $2 = creds path
101
+ # (for oauth), remaining args = the loki command to run in the container.
102
+ # $1 = auth, $2 = creds path, $3 = with_api (1 to publish the dashboard port +
103
+ # enable the dashboard, like local `loki start --api`; 0 for a plain one-shot
104
+ # build). Remaining args = the loki command.
105
+ loki_docker_build_argv() {
106
+ local auth="$1"; shift
107
+ local creds="$1"; shift
108
+ local with_api="${1:-0}"; shift
109
+ local workspace; workspace="$(pwd)"
110
+
111
+ local -a argv=(docker run --rm)
112
+ # Allocate a TTY / keep stdin open ONLY when we actually have a terminal.
113
+ # `-it` against a non-terminal (piped, CI, headless) fails with
114
+ # "cannot attach stdin to a TTY-enabled container because stdin is not a
115
+ # terminal". Probe stdin (-t 0) and stdout (-t 1) and add the flags
116
+ # conditionally so the wrapper works both interactively and headless.
117
+ # Override with LOKI_DOCKER_TTY=1 (force) or =0 (never).
118
+ local _tty="${LOKI_DOCKER_TTY:-auto}"
119
+ if [ "$_tty" = "1" ]; then
120
+ argv+=(-it)
121
+ elif [ "$_tty" = "auto" ] && [ -t 0 ] && [ -t 1 ]; then
122
+ argv+=(-it)
123
+ elif [ "$_tty" = "auto" ] && [ -t 0 ]; then
124
+ argv+=(-i)
125
+ fi
126
+ # Workspace mount: this is what makes .loki state (and thus resume +
127
+ # continuity) persist exactly like the local CLI.
128
+ argv+=(-v "${workspace}:/workspace:rw" -w /workspace)
129
+ # Dashboard: OFF by default, exactly like local `loki start` (which only
130
+ # starts the dashboard with --api). A plain one-shot build does not need it,
131
+ # and publishing a fixed host port made two `loki docker` runs collide on
132
+ # 57374 and left a lingering container holding the port. With --api we both
133
+ # enable the dashboard inside the container AND publish the port.
134
+ if [ "$with_api" = "1" ]; then
135
+ argv+=(-p "${LOKI_DASHBOARD_PORT}:${LOKI_DASHBOARD_PORT}")
136
+ argv+=(-e "LOKI_DASHBOARD=true" -e "LOKI_DASHBOARD_PORT=${LOKI_DASHBOARD_PORT}")
137
+ else
138
+ argv+=(-e "LOKI_DASHBOARD=false")
139
+ fi
140
+ # Forward git/gh identity so commits + PRs work like local. Mounting just
141
+ # ~/.gitconfig is NOT enough: it commonly uses `includeIf` to pull the real
142
+ # identity from per-host files (e.g. a github.com identity in a separate
143
+ # included file). Those includes are absent in the container, so git would
144
+ # silently fall back to the top-level [user] -- which can be a corporate
145
+ # default -- and mis-attribute commits. A `loki docker` build is destined for
146
+ # github.com, so the container must commit with the GitHub identity. We
147
+ # resolve it by preference:
148
+ # 1. the identity from the user's github includeIf target (the file pulled
149
+ # in for github.com remotes), if we can find it -- this is the correct
150
+ # identity for the autonomous greenfield flow (build -> add github
151
+ # remote -> commit -> push), which a workspace-context lookup would miss
152
+ # because a fresh repo has no github remote yet;
153
+ # 2. else the host's globally-configured identity.
154
+ # Forwarded as authoritative GIT_AUTHOR_*/GIT_COMMITTER_* env. NOTE: this
155
+ # freezes the identity for the run (env overrides per-commit includeIf
156
+ # re-evaluation), which is the intended behavior here -- the container has
157
+ # ONE purpose (github.com builds) so one frozen GitHub identity is correct.
158
+ [ -f "${HOME}/.gitconfig" ] && argv+=(-v "${HOME}/.gitconfig:/home/loki/.gitconfig:ro")
159
+ if command -v git >/dev/null 2>&1; then
160
+ local _gname="" _gemail="" _ghinc=""
161
+ # Find the path referenced by a github includeIf, if any.
162
+ if [ -f "${HOME}/.gitconfig" ]; then
163
+ _ghinc="$(git config --file "${HOME}/.gitconfig" --get-regexp 'includeif\..*github.*\.path' 2>/dev/null | awk '{print $2; exit}')"
164
+ # Expand a leading ~ to $HOME. (The "~/" here is a literal case
165
+ # pattern we are matching against, not a path to expand.)
166
+ # shellcheck disable=SC2088
167
+ case "$_ghinc" in "~/"*) _ghinc="${HOME}/${_ghinc#\~/}";; esac
168
+ fi
169
+ if [ -n "$_ghinc" ] && [ -f "$_ghinc" ]; then
170
+ _gname="$(git config --file "$_ghinc" --get user.name 2>/dev/null || true)"
171
+ _gemail="$(git config --file "$_ghinc" --get user.email 2>/dev/null || true)"
172
+ fi
173
+ # Fallback to the globally-resolved identity if the github include did
174
+ # not yield one.
175
+ [ -z "$_gname" ] && _gname="$(git config --get user.name 2>/dev/null || true)"
176
+ [ -z "$_gemail" ] && _gemail="$(git config --get user.email 2>/dev/null || true)"
177
+ if [ -n "$_gname" ]; then
178
+ argv+=(-e "GIT_AUTHOR_NAME=${_gname}" -e "GIT_COMMITTER_NAME=${_gname}")
179
+ fi
180
+ if [ -n "$_gemail" ]; then
181
+ argv+=(-e "GIT_AUTHOR_EMAIL=${_gemail}" -e "GIT_COMMITTER_EMAIL=${_gemail}")
182
+ fi
183
+ fi
184
+ [ -d "${HOME}/.config/gh" ] && argv+=(-v "${HOME}/.config/gh:/home/loki/.config/gh:ro")
185
+ # Forward GitHub tokens if set.
186
+ [ -n "${GITHUB_TOKEN:-}" ] && argv+=(-e "GITHUB_TOKEN=${GITHUB_TOKEN}")
187
+ [ -n "${GH_TOKEN:-}" ] && argv+=(-e "GH_TOKEN=${GH_TOKEN}")
188
+ # No desktop notifications inside a container.
189
+ argv+=(-e "LOKI_NOTIFICATIONS=false")
190
+ # Multi-repo unified dashboard (Option B): the in-container run.sh must NOT
191
+ # register into the project registry. A container only sees /workspace, not
192
+ # the real host path, and registry.register_project() hard-fails on a path
193
+ # that does not exist inside the container -- so in-container registration
194
+ # is both wrong (every repo would key as /workspace) and a silent no-op.
195
+ # Instead cmd_docker registers on the HOST with the real $(pwd), no pid, so
196
+ # the existing host dashboard aggregates every `loki docker` repo exactly
197
+ # like it aggregates host `loki start` repos (state read from the
198
+ # bind-mounted .loki/session.json). See loki_register_running_project.
199
+ argv+=(-e "LOKI_SKIP_PROJECT_REGISTRY=1")
200
+ # Deterministic per-host-path container name: two repos get two distinct
201
+ # concurrent containers (multi-repo parity with the host CLI) and a stable
202
+ # handle for `loki docker stop`. Hash the workspace path; fall back to a
203
+ # sanitized basename if no sha tool is present.
204
+ local _name_hash=""
205
+ if command -v shasum >/dev/null 2>&1; then
206
+ _name_hash="$(printf '%s' "$workspace" | shasum -a 256 2>/dev/null | cut -c1-12)"
207
+ elif command -v sha256sum >/dev/null 2>&1; then
208
+ _name_hash="$(printf '%s' "$workspace" | sha256sum 2>/dev/null | cut -c1-12)"
209
+ fi
210
+ if [ -n "$_name_hash" ]; then
211
+ argv+=(--name "loki-${_name_hash}")
212
+ else
213
+ argv+=(--name "loki-$(basename "$workspace" | tr -c 'A-Za-z0-9_.-' '_')")
214
+ fi
215
+ # The container IS the session boundary, so the runner must NOT setsid into
216
+ # a new, detached session: setsid detach inside a `--rm` container makes the
217
+ # `docker run` exit code report 0 even when the runner failed (a user with
218
+ # an expired token would get RC=0 and a silently empty folder). Running the
219
+ # runner in the foreground propagates the real exit code out through
220
+ # `docker run`, so the wrapper exits non-zero on a failed build -- exactly
221
+ # like the local CLI. Stop semantics still work: `docker stop` / Ctrl+C
222
+ # signals the container's main process.
223
+ argv+=(-e "LOKI_NO_NEW_SESSION=1")
224
+
225
+ case "$auth" in
226
+ apikey)
227
+ argv+=(-e "ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}")
228
+ ;;
229
+ oauth)
230
+ # rw so the in-container claude can refresh the short-lived token
231
+ # for the duration of THIS run. (The mounted file is a per-run temp
232
+ # copy that is wiped on exit, so the refresh does not persist to the
233
+ # host store -- but each run re-extracts a fresh, host-refreshed
234
+ # token, so an idle token is never a problem.)
235
+ argv+=(-v "${creds}:${_LOKI_DOCKER_CRED_DEST}:rw")
236
+ ;;
237
+ esac
238
+
239
+ argv+=("$LOKI_DOCKER_IMAGE")
240
+ argv+=("$@")
241
+
242
+ printf '%s\n' "${argv[@]}"
243
+ }
package/autonomy/loki CHANGED
@@ -891,7 +891,6 @@ show_landing() {
891
891
  echo "Get started:"
892
892
  echo -e " ${CYAN}loki start ./prd.md${NC} Build from a spec (PRD file, GitHub issue, or no arg)"
893
893
  echo -e " ${CYAN}loki demo${NC} Build a sample todo app end to end (real run)"
894
- echo -e " ${CYAN}loki web${NC} Open the visual builder to input a spec and watch agents build"
895
894
  echo -e " ${CYAN}loki dashboard start${NC} Start the live run monitor (then: loki dashboard open)"
896
895
  echo ""
897
896
  echo -e "Need help? ${CYAN}loki help${NC} lists every command."
@@ -1932,6 +1931,21 @@ cmd_start() {
1932
1931
  if [ -f "$_start_pid_file" ]; then
1933
1932
  local _existing_pid
1934
1933
  _existing_pid=$(cat "$_start_pid_file" 2>/dev/null | tr -dc '0-9')
1934
+ # Docker resume fix: in a `--rm` container every run starts a fresh PID
1935
+ # namespace where the orchestrator is PID 1. A prior run that wrote
1936
+ # loki.pid=1 leaves that marker in the bind-mounted .loki on the host;
1937
+ # the NEXT container is ALSO PID 1, so `kill -0 1` (a self-signal) always
1938
+ # succeeds and the lock falsely reads "another instance is running",
1939
+ # making resume in the same folder impossible. Two cases are NOT a live
1940
+ # sibling and must be cleared: (a) the recorded pid equals OUR OWN pid
1941
+ # ($$) -- this is the in-container resume case, where the new run is PID 1
1942
+ # and the stale marker is also 1; (b) the recorded pid is 1 but we are
1943
+ # NOT pid 1 (a host reading a container's leftover marker). Mirrors the
1944
+ # downstream guard in run.sh. A genuine concurrent sibling can never
1945
+ # share our $$, so this never masks a real running instance.
1946
+ if [ "$_existing_pid" = "$$" ] || { [ "$_existing_pid" = "1" ] && [ "$$" != "1" ]; }; then
1947
+ _existing_pid=""
1948
+ fi
1935
1949
  if [ -n "$_existing_pid" ] && kill -0 "$_existing_pid" 2>/dev/null; then
1936
1950
  echo -e "${RED}Error: another loki instance is running (pid $_existing_pid).${NC}" >&2
1937
1951
  echo -e "${YELLOW}Run 'loki stop' first, then retry 'loki start'.${NC}" >&2
@@ -3970,9 +3984,9 @@ cmd_dashboard_help() {
3970
3984
  echo "Usage: loki dashboard <command> [options]"
3971
3985
  echo ""
3972
3986
  echo "Note: 'loki dashboard' is the operations/observability UI (port ${DASHBOARD_DEFAULT_PORT})."
3973
- echo " It is NOT the same as 'loki web' (Purple Lab, port ${PURPLE_LAB_DEFAULT_PORT}, where you input PRDs)."
3974
- echo " Use 'loki dashboard' to monitor agents, tasks, costs, council, escalations."
3975
- echo " Use 'loki web' to submit a PRD and watch agents build."
3987
+ echo " It is NOT the same as 'loki web' (Purple Lab, port ${PURPLE_LAB_DEFAULT_PORT}, deprecated v7.44.0)."
3988
+ echo " Use 'loki dashboard' to monitor agents, tasks, costs, council, escalations,"
3989
+ echo " and to submit a PRD via the embedded 'Lab' tab (replaces the deprecated 'loki web')."
3976
3990
  echo ""
3977
3991
  echo "Commands:"
3978
3992
  echo " start Start the dashboard server"
@@ -4205,9 +4219,11 @@ cmd_dashboard_start() {
4205
4219
  echo " URL: $url"
4206
4220
  echo " Logs: $log_file"
4207
4221
  echo ""
4208
- # v7.28.x UX #5: distinguish the two browser UIs up front so a newcomer
4209
- # who wants to submit a spec does not land on the ops monitor by mistake.
4210
- echo -e "${DIM}Looking for the project web UI to submit a spec? loki web (:${PURPLE_LAB_DEFAULT_PORT:-57375})${NC}"
4222
+ # v7.28.x UX #5: point a newcomer who wants to submit a spec at the right
4223
+ # surface. v7.44.0: the standalone Purple Lab (loki web) is deprecated and
4224
+ # consolidated here, so steer to the embedded Lab tab instead of spawning
4225
+ # a second port.
4226
+ echo -e "${DIM}Want to submit a spec in the browser? Open the 'Lab' tab in this dashboard.${NC}"
4211
4227
  echo ""
4212
4228
  echo -e "Open in browser: ${CYAN}loki dashboard open${NC}"
4213
4229
  echo -e "Check status: ${CYAN}loki dashboard status${NC}"
@@ -4578,6 +4594,32 @@ PURPLE_LAB_PID_FILE="${PURPLE_LAB_STATE_DIR}/purple-lab.pid"
4578
4594
  cmd_web() {
4579
4595
  local subcommand="${1:-start}"
4580
4596
 
4597
+ # Deprecation (v7.44.0): Purple Lab (loki web, port 57375) is deprecated and
4598
+ # consolidated into the dashboard. The hosted/commercial role moved to the
4599
+ # separate Autonomi Cloud repo. This is a VALUE-PRESERVING deprecation: the
4600
+ # command STILL WORKS (it launches after the banner) so no one is stranded.
4601
+ # The banner + telemetry mirror the run->start deprecation contract: print
4602
+ # to stderr, suppress under machine-output flags (--json/-q/--quiet/json...),
4603
+ # and only emit telemetry when .loki already exists (events/emit.sh creates
4604
+ # "$LOKI_DIR/events/pending" as a side effect that would flip downstream
4605
+ # guards in a clean directory). The --help/-h/help path shows the deprecation
4606
+ # in its own help text instead, so we skip the banner there.
4607
+ case "$subcommand" in
4608
+ --help|-h|help) ;;
4609
+ *)
4610
+ if ! _deprecated_alias_should_suppress "$@"; then
4611
+ echo "loki web (Purple Lab) is deprecated as of v7.44.0. For local build + monitoring, use the dashboard (auto-launches at 'loki start', http://localhost:${DASHBOARD_DEFAULT_PORT}; or 'loki dashboard'). For the hosted platform, see Autonomi Cloud." >&2
4612
+ if [ -d "${LOKI_DIR:-.loki}" ]; then
4613
+ emit_event cli_command_deprecated cli web \
4614
+ "from=web" \
4615
+ "to=dashboard" \
4616
+ "version=7.44.0" \
4617
+ "argv=${*:-}"
4618
+ fi
4619
+ fi
4620
+ ;;
4621
+ esac
4622
+
4581
4623
  case "$subcommand" in
4582
4624
  start)
4583
4625
  shift || true
@@ -4617,13 +4659,19 @@ cmd_web() {
4617
4659
  }
4618
4660
 
4619
4661
  cmd_web_help() {
4620
- echo -e "${BOLD}Purple Lab -- Loki Mode Web UI${NC}"
4662
+ echo -e "${BOLD}Purple Lab -- Loki Mode Web UI (deprecated -- use the dashboard)${NC}"
4621
4663
  echo ""
4622
4664
  echo "Usage: loki web [command] [options]"
4623
4665
  echo ""
4666
+ echo "DEPRECATED as of v7.44.0: Purple Lab is consolidated into the dashboard."
4667
+ echo " For local build + monitoring, use the dashboard (auto-launches at"
4668
+ echo " 'loki start', http://localhost:${DASHBOARD_DEFAULT_PORT}; or 'loki dashboard')."
4669
+ echo " For the hosted platform, see Autonomi Cloud."
4670
+ echo " 'loki web' still works for now (it launches after a deprecation notice),"
4671
+ echo " but it will be removed in a future release. Migrate to the dashboard."
4672
+ echo ""
4624
4673
  echo "Note: 'loki web' is Purple Lab (the PRD-input/build-watch UI, port ${PURPLE_LAB_DEFAULT_PORT})."
4625
4674
  echo " It is NOT the same as 'loki dashboard' (operations UI, port ${DASHBOARD_DEFAULT_PORT})."
4626
- echo " Use 'loki web' to submit a PRD and watch agents build it."
4627
4675
  echo " Use 'loki dashboard' to monitor running agents, tasks, costs, council, escalations."
4628
4676
  echo ""
4629
4677
  echo "Commands:"
@@ -14337,6 +14385,9 @@ main() {
14337
14385
  web)
14338
14386
  cmd_web "$@"
14339
14387
  ;;
14388
+ docker)
14389
+ cmd_docker "$@"
14390
+ ;;
14340
14391
  preview)
14341
14392
  cmd_preview "$@"
14342
14393
  ;;
@@ -28327,4 +28378,178 @@ PYEOF
28327
28378
  esac
28328
28379
  }
28329
28380
 
28381
+ # ---------------------------------------------------------------------------
28382
+ # loki docker - zero-friction Docker host wrapper
28383
+ #
28384
+ # Runs loki inside the published image with the SAME experience as the local
28385
+ # CLI: invoke it in a project folder and it just works. The current directory
28386
+ # is bind-mounted to /workspace, so .loki/ state (memory, session, queue,
28387
+ # checkpoints) persists on the host and resume + continuity behave exactly like
28388
+ # `loki start` locally. Auth is auto-detected (ANTHROPIC_API_KEY if set, else
28389
+ # the host Claude Code login), so Claude Code subscribers get zero-config runs.
28390
+ #
28391
+ # Usage:
28392
+ # loki docker start prd.md # run a build in the container, .loki persists
28393
+ # loki docker <any-loki-command> # forwards any command into the container
28394
+ # loki docker --dry-run start # print the docker argv without running
28395
+ # loki docker --image IMG ... # override the image (default published tag)
28396
+ # ---------------------------------------------------------------------------
28397
+ cmd_docker() {
28398
+ local docker_mod="$_LOKI_SCRIPT_DIR/docker-run.sh"
28399
+ if [ ! -f "$docker_mod" ]; then
28400
+ echo -e "${RED}Error: docker wrapper module not found at $docker_mod${NC}" >&2
28401
+ return 3
28402
+ fi
28403
+
28404
+ local sub="${1:-}"
28405
+ case "$sub" in
28406
+ ""|--help|-h|help)
28407
+ echo -e "${BOLD}loki docker${NC} - run loki inside the Docker image, zero config"
28408
+ echo ""
28409
+ echo "Usage: loki docker [--image IMG] [--dry-run] <loki-command> [args]"
28410
+ echo ""
28411
+ echo "Runs the published Loki image with your current folder mounted at"
28412
+ echo "/workspace, so .loki state persists and resume works exactly like"
28413
+ echo "the local CLI. Auth is auto-detected:"
28414
+ echo " 1. ANTHROPIC_API_KEY (if set in your environment), else"
28415
+ echo " 2. your host Claude Code login (Max/Pro subscription), else"
28416
+ echo " 3. an error with setup guidance."
28417
+ echo ""
28418
+ echo "Examples:"
28419
+ echo " loki docker start prd.md"
28420
+ echo " loki docker status"
28421
+ echo " loki docker --dry-run start prd.md"
28422
+ echo ""
28423
+ echo "Options:"
28424
+ echo " --image IMG Override the image (default: ${LOKI_DOCKER_IMAGE:-asklokesh/loki-mode:latest})"
28425
+ echo " --dry-run Print the docker command that would run, then exit"
28426
+ [ "$sub" = "" ] && return 1
28427
+ return 0
28428
+ ;;
28429
+ esac
28430
+
28431
+ if ! command -v docker >/dev/null 2>&1; then
28432
+ echo -e "${RED}Error: docker is not installed or not on PATH.${NC}" >&2
28433
+ echo "Install Docker Desktop (https://www.docker.com/products/docker-desktop/) and retry." >&2
28434
+ return 4
28435
+ fi
28436
+
28437
+ # Parse wrapper-only flags (everything else is the loki command to forward).
28438
+ # --api is BOTH a forwarded loki flag (start --api) and a wrapper signal to
28439
+ # publish the dashboard port, so we detect it without consuming it.
28440
+ local dry_run=0
28441
+ local with_api=0
28442
+ local -a fwd=()
28443
+ while [ $# -gt 0 ]; do
28444
+ case "$1" in
28445
+ --image) shift; export LOKI_DOCKER_IMAGE="${1:-}"; shift ;;
28446
+ --dry-run) dry_run=1; shift ;;
28447
+ --api) with_api=1; fwd+=("$1"); shift ;;
28448
+ *) fwd+=("$1"); shift ;;
28449
+ esac
28450
+ done
28451
+
28452
+ # shellcheck source=/dev/null
28453
+ source "$docker_mod"
28454
+
28455
+ local auth; auth="$(loki_docker_detect_auth)"
28456
+ local creds=""
28457
+ local cleanup_creds=0
28458
+
28459
+ case "$auth" in
28460
+ apikey)
28461
+ echo -e "${DIM:-}Auth: ANTHROPIC_API_KEY (from environment)${NC}" >&2
28462
+ ;;
28463
+ oauth)
28464
+ creds="$(mktemp "${TMPDIR:-/tmp}/loki-docker-creds.XXXXXX")" || {
28465
+ echo -e "${RED}Error: could not create a temp file for credentials.${NC}" >&2
28466
+ return 2
28467
+ }
28468
+ cleanup_creds=1
28469
+ # Wipe the token file on any exit path.
28470
+ # shellcheck disable=SC2064
28471
+ trap "rm -f '$creds' 2>/dev/null || true" EXIT INT TERM
28472
+ if ! loki_docker_extract_creds "$creds"; then
28473
+ echo -e "${RED}Error: found a host Claude Code login but could not read its token.${NC}" >&2
28474
+ return 2
28475
+ fi
28476
+ echo -e "${DIM:-}Auth: host Claude Code login (auto-detected, mounted read-write)${NC}" >&2
28477
+ ;;
28478
+ none)
28479
+ echo -e "${RED}Error: no auth available for the Docker run.${NC}" >&2
28480
+ echo "Do ONE of the following, then retry:" >&2
28481
+ echo " - export ANTHROPIC_API_KEY=sk-ant-... (get one at https://console.anthropic.com/settings/keys)" >&2
28482
+ echo " - or log in to Claude Code on this host (claude) so loki can reuse it." >&2
28483
+ return 5
28484
+ ;;
28485
+ esac
28486
+
28487
+ # Default the command to 'help' if the user passed only wrapper flags.
28488
+ [ ${#fwd[@]} -eq 0 ] && fwd=(help)
28489
+
28490
+ local -a docker_argv=()
28491
+ while IFS= read -r line; do docker_argv+=("$line"); done < <(loki_docker_build_argv "$auth" "$creds" "$with_api" "${fwd[@]}")
28492
+
28493
+ if [ "$dry_run" = "1" ]; then
28494
+ echo "Would run:"
28495
+ # Mask the API key value: dry-run output is exactly what users paste into
28496
+ # bug reports / CI logs, so never print the secret.
28497
+ local _a
28498
+ for _a in "${docker_argv[@]}"; do
28499
+ case "$_a" in
28500
+ ANTHROPIC_API_KEY=*) printf ' %q' "ANTHROPIC_API_KEY=***" ;;
28501
+ *) printf ' %q' "$_a" ;;
28502
+ esac
28503
+ done
28504
+ echo ""
28505
+ [ "$cleanup_creds" = "1" ] && rm -f "$creds" 2>/dev/null || true
28506
+ return 0
28507
+ fi
28508
+
28509
+ # Multi-repo unified dashboard (Option B): register THIS project on the host
28510
+ # with the real cwd and NO pid, so the existing host dashboard
28511
+ # (`loki dashboard`) lists it alongside host `loki start` projects and reads
28512
+ # its live status from the bind-mounted .loki/session.json. Best-effort and
28513
+ # failure-swallowed: registry bookkeeping must never block a build.
28514
+ _loki_docker_register_host running
28515
+ "${docker_argv[@]}"
28516
+ local rc=$?
28517
+ _loki_docker_register_host stopped
28518
+ [ "$cleanup_creds" = "1" ] && rm -f "$creds" 2>/dev/null || true
28519
+ return $rc
28520
+ }
28521
+
28522
+ # Host-side project registry bookkeeping for `loki docker` (Option B). Registers
28523
+ # $(pwd) in ~/.loki/dashboard/projects.json with NO pid (pid=None) so the
28524
+ # dashboard's "running" decision falls back to .loki/session.json (which the
28525
+ # container writes into the bind-mounted workspace). $1 = running|stopped.
28526
+ _loki_docker_register_host() {
28527
+ local _state="${1:-running}"
28528
+ command -v python3 >/dev/null 2>&1 || return 0
28529
+ local _skill="${SKILL_DIR:-$_LOKI_SCRIPT_DIR/..}"
28530
+ LOKI_REG_TARGET="$(pwd)" LOKI_REG_SKILL="$_skill" LOKI_REG_STATE="$_state" \
28531
+ python3 - <<'PYDOCKERREG' >/dev/null 2>&1 || true
28532
+ import os, sys
28533
+ sys.path.insert(0, os.environ.get("LOKI_REG_SKILL", "."))
28534
+ try:
28535
+ from dashboard import registry
28536
+ target = os.path.abspath(os.environ["LOKI_REG_TARGET"])
28537
+ state = os.environ.get("LOKI_REG_STATE", "running")
28538
+ if state == "stopped":
28539
+ registry.mark_project_stopped(target)
28540
+ else:
28541
+ registry.register_project(target)
28542
+ reg = registry._load_registry()
28543
+ pid_key = registry._generate_project_id(target)
28544
+ if pid_key in reg.get("projects", {}):
28545
+ # No host pid for a containerized run: the dashboard uses the
28546
+ # bind-mounted .loki/session.json to decide "running".
28547
+ reg["projects"][pid_key]["pid"] = None
28548
+ reg["projects"][pid_key]["status"] = "running"
28549
+ registry._save_registry(reg)
28550
+ except Exception:
28551
+ pass
28552
+ PYDOCKERREG
28553
+ }
28554
+
28330
28555
  main "$@"