loki-mode 7.44.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 +3 -2
- package/SKILL.md +11 -2
- package/VERSION +1 -1
- package/autonomy/docker-run.sh +243 -0
- package/autonomy/loki +192 -0
- package/autonomy/run.sh +18 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +18 -3
- package/docs/INSTALLATION.md +75 -9
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
package/README.md
CHANGED
|
@@ -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
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
410
|
+
**v7.45.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
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
|
@@ -1931,6 +1931,21 @@ cmd_start() {
|
|
|
1931
1931
|
if [ -f "$_start_pid_file" ]; then
|
|
1932
1932
|
local _existing_pid
|
|
1933
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
|
|
1934
1949
|
if [ -n "$_existing_pid" ] && kill -0 "$_existing_pid" 2>/dev/null; then
|
|
1935
1950
|
echo -e "${RED}Error: another loki instance is running (pid $_existing_pid).${NC}" >&2
|
|
1936
1951
|
echo -e "${YELLOW}Run 'loki stop' first, then retry 'loki start'.${NC}" >&2
|
|
@@ -14370,6 +14385,9 @@ main() {
|
|
|
14370
14385
|
web)
|
|
14371
14386
|
cmd_web "$@"
|
|
14372
14387
|
;;
|
|
14388
|
+
docker)
|
|
14389
|
+
cmd_docker "$@"
|
|
14390
|
+
;;
|
|
14373
14391
|
preview)
|
|
14374
14392
|
cmd_preview "$@"
|
|
14375
14393
|
;;
|
|
@@ -28360,4 +28378,178 @@ PYEOF
|
|
|
28360
28378
|
esac
|
|
28361
28379
|
}
|
|
28362
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
|
+
|
|
28363
28555
|
main "$@"
|
package/autonomy/run.sh
CHANGED
|
@@ -1550,7 +1550,24 @@ validate_api_keys() {
|
|
|
1550
1550
|
|
|
1551
1551
|
local key_var=""
|
|
1552
1552
|
case "$provider" in
|
|
1553
|
-
claude)
|
|
1553
|
+
claude)
|
|
1554
|
+
# Inside Docker, the Claude Code CLI can authenticate either via
|
|
1555
|
+
# ANTHROPIC_API_KEY OR via a mounted OAuth credentials file (the
|
|
1556
|
+
# zero-friction `loki docker` wrapper mounts the host login at
|
|
1557
|
+
# ~/.claude/.credentials.json). If that file is present, the CLI
|
|
1558
|
+
# has a valid login and we must NOT block on the env var -- doing
|
|
1559
|
+
# so was the bug that made OAuth-based Docker runs exit at
|
|
1560
|
+
# pre-flight with "Required API key ... is not set". Mirror the
|
|
1561
|
+
# OAuth-aware doctor check (run.sh:9268).
|
|
1562
|
+
if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
|
1563
|
+
local _claude_creds="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.credentials.json"
|
|
1564
|
+
if [[ -s "$_claude_creds" ]]; then
|
|
1565
|
+
log_info "Claude Code OAuth credentials detected ($_claude_creds); using CLI login."
|
|
1566
|
+
return 0
|
|
1567
|
+
fi
|
|
1568
|
+
fi
|
|
1569
|
+
key_var="ANTHROPIC_API_KEY"
|
|
1570
|
+
;;
|
|
1554
1571
|
codex) key_var="OPENAI_API_KEY" ;;
|
|
1555
1572
|
cline) # Cline manages its own keys via `cline auth`
|
|
1556
1573
|
if ! command -v cline &>/dev/null; then
|
package/dashboard/__init__.py
CHANGED
package/dashboard/server.py
CHANGED
|
@@ -2911,13 +2911,28 @@ async def stop_running_project(request: Request, body: RunningProjectStopRequest
|
|
|
2911
2911
|
|
|
2912
2912
|
pid = project.get("pid")
|
|
2913
2913
|
if not isinstance(pid, int) or pid <= 0:
|
|
2914
|
-
#
|
|
2914
|
+
# No host pid. Two sub-cases:
|
|
2915
|
+
# - A `loki docker` project registers with pid=None but may still be
|
|
2916
|
+
# actively building inside a container. Its runner polls .loki/STOP
|
|
2917
|
+
# (bind-mounted to the host path), so writing STOP here actually
|
|
2918
|
+
# stops the containerized build -- this is the unified-dashboard Stop
|
|
2919
|
+
# parity for Docker projects (the container pid is meaningless on the
|
|
2920
|
+
# host, so os.kill is not an option).
|
|
2921
|
+
# - A genuinely stopped project: the STOP write is harmless.
|
|
2922
|
+
# Write STOP when we resolved a real .loki dir, then reconcile.
|
|
2923
|
+
stop_signaled = False
|
|
2924
|
+
if loki_dir is not None:
|
|
2925
|
+
try:
|
|
2926
|
+
(loki_dir / "STOP").write_text(datetime.now(timezone.utc).isoformat())
|
|
2927
|
+
stop_signaled = True
|
|
2928
|
+
except OSError:
|
|
2929
|
+
pass
|
|
2915
2930
|
registry.mark_project_stopped(project_id)
|
|
2916
2931
|
return {
|
|
2917
2932
|
"success": True,
|
|
2918
2933
|
"project_id": project_id,
|
|
2919
|
-
"stopped":
|
|
2920
|
-
"already_stopped":
|
|
2934
|
+
"stopped": stop_signaled,
|
|
2935
|
+
"already_stopped": not stop_signaled,
|
|
2921
2936
|
}
|
|
2922
2937
|
|
|
2923
2938
|
# Write the STOP file so the runner's own cleanup STOP-branch fires for a
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v7.
|
|
5
|
+
**Version:** v7.45.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -341,20 +341,86 @@ loki start ./my-spec.md --provider cline
|
|
|
341
341
|
|
|
342
342
|
#### Docker
|
|
343
343
|
|
|
344
|
-
|
|
344
|
+
Docker is where sandboxes and runs happen. The easiest way to run Loki in a
|
|
345
|
+
container is the `loki docker` host wrapper, which gives you the full local
|
|
346
|
+
experience (including `.loki/` state, resume, and continuity) in one command.
|
|
347
|
+
The raw `docker run` and `docker compose` methods below remain as alternatives.
|
|
348
|
+
|
|
349
|
+
##### loki docker (easiest)
|
|
350
|
+
|
|
351
|
+
If you have loki installed on the host (npm/brew/bun) plus Docker, `loki docker`
|
|
352
|
+
runs any loki command inside the published `asklokesh/loki-mode` image with zero
|
|
353
|
+
config:
|
|
345
354
|
|
|
346
355
|
```bash
|
|
347
|
-
#
|
|
348
|
-
docker
|
|
349
|
-
|
|
350
|
-
|
|
356
|
+
loki docker start prd.md # full local experience in Docker
|
|
357
|
+
loki docker status # any loki command works
|
|
358
|
+
loki docker --dry-run start prd.md # print the docker command, do not run
|
|
359
|
+
```
|
|
351
360
|
|
|
352
|
-
|
|
353
|
-
|
|
361
|
+
It bind-mounts the current folder to `/workspace`, so `.loki/` state (memory,
|
|
362
|
+
session, queue, checkpoints) persists on the host and resume and continuity
|
|
363
|
+
behave exactly like the local `loki` CLI. Auth is auto-detected:
|
|
364
|
+
`ANTHROPIC_API_KEY` if set, else your host's existing Claude Code login
|
|
365
|
+
(Max/Pro subscribers need no API key), else an honest error with guidance. It
|
|
366
|
+
also forwards `~/.gitconfig` and `~/.config/gh` (read-only) plus
|
|
367
|
+
`GITHUB_TOKEN`/`GH_TOKEN` if set, so commits and PRs work like local, and
|
|
368
|
+
exposes the dashboard on port 57374. Use `--image IMG` to override the image.
|
|
369
|
+
|
|
370
|
+
`loki docker` is a thin host wrapper around the image; it requires loki and
|
|
371
|
+
Docker installed on the host.
|
|
372
|
+
|
|
373
|
+
Multi-repo + unified dashboard: you can run `loki docker start` in several
|
|
374
|
+
different repos at once, exactly like the host CLI. Each repo gets its own
|
|
375
|
+
container (deterministic name `loki-<hash-of-path>`) and its own bind-mounted
|
|
376
|
+
`.loki/` state. Every `loki docker` project registers with the host dashboard,
|
|
377
|
+
so running `loki dashboard` on the host shows ALL your projects in one unified
|
|
378
|
+
view, whether they run via local `loki start` or via `loki docker start`. Builds
|
|
379
|
+
run with the dashboard off by default (so concurrent runs do not collide on port
|
|
380
|
+
57374); use the host `loki dashboard`, or `loki docker start --api` for a single
|
|
381
|
+
containerized run's UI.
|
|
382
|
+
|
|
383
|
+
##### docker run
|
|
384
|
+
|
|
385
|
+
As of v7.45.0 the image ships the Claude Code CLI, so the default `claude`
|
|
386
|
+
provider works inside the container. Provide auth with your Anthropic API key:
|
|
387
|
+
|
|
388
|
+
```bash
|
|
389
|
+
# Run Loki Mode in Docker (Claude provider, API-key auth)
|
|
390
|
+
docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
|
|
354
391
|
-v $(pwd):/workspace -w /workspace \
|
|
355
|
-
asklokesh/loki-mode:
|
|
392
|
+
asklokesh/loki-mode:7.45.0 start ./my-spec.md
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
##### docker compose + .env (no host install)
|
|
396
|
+
|
|
397
|
+
If you prefer not to install loki on the host, the repo ships a
|
|
398
|
+
`docker-compose.yml` with `env_file: .env`. Copy the example,
|
|
399
|
+
set your key once, then run without retyping long `-e` flags:
|
|
400
|
+
|
|
401
|
+
```bash
|
|
402
|
+
cp .env.example .env # then edit .env and set ANTHROPIC_API_KEY
|
|
403
|
+
docker compose run loki start prd.md
|
|
356
404
|
```
|
|
357
405
|
|
|
406
|
+
`.env` is gitignored. Edit it and re-run any time; compose reloads it
|
|
407
|
+
automatically.
|
|
408
|
+
|
|
409
|
+
##### Host OAuth (Claude Code Max/Pro subscribers, no API key)
|
|
410
|
+
|
|
411
|
+
If you have a Claude Code subscription and no API key, you can reuse your host
|
|
412
|
+
login by mounting your credentials file at
|
|
413
|
+
`/home/loki/.claude/.credentials.json` instead of setting `ANTHROPIC_API_KEY`.
|
|
414
|
+
See [DOCKER_README.md](../DOCKER_README.md) "Option 2: host OAuth" for the
|
|
415
|
+
one-line export command and the compose volume to uncomment.
|
|
416
|
+
|
|
417
|
+
##### Other providers in Docker
|
|
418
|
+
|
|
419
|
+
The image ships only the Claude Code CLI. Codex, Cline, and Aider are
|
|
420
|
+
bring-your-own-CLI: install the provider CLI in a derived image (or mount it),
|
|
421
|
+
then select it with `-e LOKI_PROVIDER=<name>`. See
|
|
422
|
+
[DOCKER_README.md](../DOCKER_README.md) for details.
|
|
423
|
+
|
|
358
424
|
### Degraded Mode
|
|
359
425
|
|
|
360
426
|
When using `codex`, `cline`, or `aider` providers, Loki Mode operates in **degraded mode**:
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.
|
|
2
|
+
var n6=Object.defineProperty;var a6=($)=>$;function s6($,Q){this[$]=a6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)n6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:s6.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var K$=import.meta.require;var S1={};h(S1,{lokiDir:()=>P,homeLokiDir:()=>o$,findRepoRootForVersion:()=>d$,REPO_ROOT:()=>m});import{resolve as n,dirname as l$}from"path";import{fileURLToPath as t6}from"url";import{existsSync as P$}from"fs";import{homedir as r6}from"os";function i6(){let $=N1;for(let Q=0;Q<6;Q++){if(P$(n($,"VERSION"))&&P$(n($,"autonomy/run.sh")))return $;let Z=l$($);if(Z===$)break;$=Z}return n(N1,"..","..","..")}function d$($){let Q=$;for(let Z=0;Z<6;Z++){if(P$(n(Q,"VERSION"))&&P$(n(Q,"autonomy/run.sh")))return Q;let z=l$(Q);if(z===Q)break;Q=z}return n($,"..","..","..")}function P(){return process.env.LOKI_DIR??n(process.cwd(),".loki")}function o$(){return n(r6(),".loki")}var N1,m;var C=L(()=>{N1=l$(t6(import.meta.url));m=i6()});import{readFileSync as e6}from"fs";import{resolve as $Q,dirname as QQ}from"path";import{fileURLToPath as ZQ}from"url";function F$(){if($$!==null)return $$;let $="7.45.0";if(typeof $==="string"&&$.length>0)return $$=$,$$;try{let Q=QQ(ZQ(import.meta.url)),Z=d$(Q);$$=e6($Q(Z,"VERSION"),"utf-8").trim()}catch{$$="unknown"}return $$}var $$=null;var n$=L(()=>{C()});var C1={};h(C1,{runOrThrow:()=>zQ,run:()=>j,commandVersion:()=>KQ,commandExists:()=>f,ShellError:()=>a$});async function j($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[W,K,U]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:W,stderr:K,exitCode:U}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function zQ($,Q={}){let Z=await j($,Q);if(Z.exitCode!==0)throw new a$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=XQ($),Z=await j(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function XQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function KQ($,Q="--version"){if(!await f($))return null;let z=await j([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var a$;var d=L(()=>{a$=class a$ extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function a($){return WQ?"":$}var WQ,T,S,I,TZ,w,R,y,q;var c=L(()=>{WQ=(process.env.NO_COLOR??"").length>0;T=a("\x1B[0;31m"),S=a("\x1B[0;32m"),I=a("\x1B[1;33m"),TZ=a("\x1B[0;34m"),w=a("\x1B[0;36m"),R=a("\x1B[1m"),y=a("\x1B[2m"),q=a("\x1B[0m")});import{existsSync as TQ}from"fs";async function Q$(){if(B$!==void 0)return B$;let $="/opt/homebrew/bin/python3.12";if(TQ($))return B$=$,$;let Q=await f("python3.12");if(Q)return B$=Q,Q;let Z=await f("python3");return B$=Z,Z}async function Z$($,Q={}){let Z=await Q$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return j([Z,"-c",$],Q)}var B$;var W$=L(()=>{d()});var t1={};h(t1,{runStatus:()=>gQ});import{existsSync as v,readFileSync as U$,readdirSync as l1,statSync as d1}from"fs";import{resolve as D,basename as xQ}from"path";import{homedir as NQ}from"os";async function DQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${q}
|
|
3
3
|
`),process.stdout.write(`Install with:
|
|
4
4
|
`),process.stdout.write(` brew install jq (macOS)
|
|
5
5
|
`),process.stdout.write(` apt install jq (Debian/Ubuntu)
|
|
@@ -789,4 +789,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
789
789
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
790
790
|
`),process.stderr.write(o6),2}}p1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var ZZ=await QZ(Bun.argv.slice(2));process.exit(ZZ);
|
|
791
791
|
|
|
792
|
-
//# debugId=
|
|
792
|
+
//# debugId=7FCBCE9F1C748AE964756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.45.0",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 11 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "7.
|
|
5
|
+
"version": "7.45.0",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 11 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|