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 +7 -33
- package/SKILL.md +11 -2
- package/VERSION +1 -1
- package/autonomy/docker-run.sh +243 -0
- package/autonomy/loki +234 -9
- package/autonomy/run.sh +18 -1
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +244 -4
- package/dashboard/static/index.html +78 -21
- 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
|
@@ -13,7 +13,7 @@ _The free, source-available autonomous coding agent by [Autonomi](https://www.au
|
|
|
13
13
|
[](https://hub.docker.com/r/asklokesh/loki-mode)
|
|
14
14
|
[](LICENSE)
|
|
15
15
|
|
|
16
|
-
[Website](https://www.autonomi.dev/) | [Documentation](wiki/Home.md) | [Installation](docs/INSTALLATION.md) | [Changelog](CHANGELOG.md) | [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
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
@@ -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},
|
|
3974
|
-
echo " Use 'loki dashboard' to monitor agents, tasks, costs, council, escalations
|
|
3975
|
-
echo "
|
|
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:
|
|
4209
|
-
#
|
|
4210
|
-
|
|
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 "$@"
|