loki-mode 7.71.0 → 7.73.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 +1 -1
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/git-pr-advisory.sh +112 -0
- package/autonomy/loki +810 -37
- package/autonomy/run.sh +416 -28
- package/autonomy/verify.sh +7 -1
- package/dashboard/__init__.py +1 -1
- package/docs/BRANCH-LIFECYCLE-PLAN.md +354 -0
- package/docs/DEPLOY-PLAN.md +302 -0
- package/docs/INSTALLATION.md +2 -2
- package/docs/PREVIEW-LINK-PLAN.md +77 -0
- 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
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
# DEPLOY-PLAN.md -- `loki deploy` (ADVISORY / PRINT-ONLY)
|
|
2
|
+
|
|
3
|
+
**Feature:** `loki deploy` -- detect project type + the user's installed cloud CLI,
|
|
4
|
+
PRINT (and best-effort copy) the canonical deploy command(s), and **never run them**.
|
|
5
|
+
|
|
6
|
+
**Status:** Architecture plan. No implementation code here. Pattern family: the just-shipped
|
|
7
|
+
`loki preview --public` (`_preview_public` at `autonomy/loki:5306`, helpers from `_read_app_state`
|
|
8
|
+
at `:5216`, arg parsing in `cmd_preview` at `:5546`-`:5609`, dispatch arm `preview)` at `:14966`).
|
|
9
|
+
|
|
10
|
+
**Why this exists / why it is safe.** README states (line 447) *"Does not deploy -- human runs
|
|
11
|
+
deploy commands"* and (line 452) *"It does NOT access your cloud accounts ... Human oversight is
|
|
12
|
+
expected for deployment."* This feature keeps that literally true: it is an **advisory printer**.
|
|
13
|
+
It runs `command -v <cli>` ONLY (never the CLI itself, not even `--version`), prints the exact
|
|
14
|
+
command, and the **human runs it**. Fully reversible; touches no cloud account.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Product Owner scope locks (RECOMMENDED -- integrator to confirm)
|
|
19
|
+
|
|
20
|
+
These are the decisions the Product Owner / integrator should lock before build. Each has a
|
|
21
|
+
clear recommended default.
|
|
22
|
+
|
|
23
|
+
### LOCK 1 -- Command surface: NEW top-level command `loki deploy` (not a flag)
|
|
24
|
+
- Recommendation: **new top-level `deploy)` dispatch arm + `cmd_deploy()`**, NOT `loki preview --deploy`.
|
|
25
|
+
- Justification: `preview` is "show me the local app I already built/started" and is gated on a
|
|
26
|
+
**running app** (`state.json` status=running, live port). `deploy` is conceptually distinct:
|
|
27
|
+
it is a **static, filesystem-only advisory about project type** that must work with nothing
|
|
28
|
+
running and no build started. Folding it into `preview` would force the running-app
|
|
29
|
+
preconditions onto a feature that must not have them (see LOCK 6). Per the CLI-consolidation
|
|
30
|
+
mandate (`autonomy/loki:675`, lean ~17-entry front page), `deploy` does **not** go on the
|
|
31
|
+
front-page command list; it lives in the "More commands" footer (`:728`) and is fully
|
|
32
|
+
documented via `loki deploy --help`. It earns a command (verb users already expect) but not
|
|
33
|
+
front-page real estate.
|
|
34
|
+
|
|
35
|
+
### LOCK 2 -- Disambiguation: print ALL viable options, most-idiomatic first
|
|
36
|
+
- Recommendation: when several `(project-type x installed-CLI)` pairs are viable, **print every
|
|
37
|
+
viable option, clearly labeled, idiomatic one first.** Never silently pick one.
|
|
38
|
+
- Precedence (the "idiomatic first" order), recommended default:
|
|
39
|
+
| Detected project type | Idiomatic 1st | Then (if that CLI also installed) |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| Next.js | Vercel | Netlify, Fly (if Dockerfile present) |
|
|
42
|
+
| Static / SPA (dist/ or build/ + index.html; Vite/CRA) | Netlify | Cloudflare Pages, Vercel |
|
|
43
|
+
| Dockerfile / containerized server | Fly | Cloudflare (wrangler), (Render: doc link only) |
|
|
44
|
+
| Generic Node server (package.json, no static build) | Fly | Vercel |
|
|
45
|
+
| Python (requirements.txt / pyproject.toml) | Fly | (doc links: Render/Railway) |
|
|
46
|
+
- Within each row, only print options whose **CLI is actually installed** (`command -v`).
|
|
47
|
+
- "Idiomatic first" = ordering only; it never suppresses a viable installed alternative.
|
|
48
|
+
|
|
49
|
+
### LOCK 3 -- Per-provider canonical commands (DOCUMENTED, not invented)
|
|
50
|
+
Confidence column: "task" = exactly the form specified in the approved task brief (authoritative
|
|
51
|
+
for this build); placeholders are `<...>` the user fills. No flag is invented.
|
|
52
|
+
|
|
53
|
+
| Provider | CLI binary (`command -v`) | Command printed | Notes / placeholders | Confidence |
|
|
54
|
+
|---|---|---|---|---|
|
|
55
|
+
| Vercel | `vercel` | `vercel --prod` | none | task / high |
|
|
56
|
+
| Netlify | `netlify` | `netlify deploy --prod` | for **static**, append `--dir=<build-output>` (e.g. `dist` or `build`); print the placeholder, do not guess | task / high |
|
|
57
|
+
| Fly.io | `flyctl` | `fly deploy` | **Detect `flyctl`, print `fly deploy`** -- the binary is `flyctl` but the canonical subcommand verb is `fly deploy`; this is intentional, not a typo. If no `fly.toml`, append a one-line note: "run `fly launch` once first to create fly.toml" (note text only, never executed) | task / high |
|
|
58
|
+
| Cloudflare Pages (static) | `wrangler` | `wrangler pages deploy <build-output>` | placeholder dir | task / high |
|
|
59
|
+
| Cloudflare Workers/containers | `wrangler` | `wrangler deploy` | none | task / high |
|
|
60
|
+
|
|
61
|
+
- Where a flag depends on the project (build dir), print a `<placeholder>` and reference official
|
|
62
|
+
docs rather than inventing. Each printed block ends with the provider's official docs URL.
|
|
63
|
+
- **A wrong flag is worse than no feature** -- conservatism over completeness.
|
|
64
|
+
|
|
65
|
+
### LOCK 4 -- Clipboard: best-effort, cross-platform, never fatal, copy the idiomatic one
|
|
66
|
+
- Recommendation: **always copy the idiomatic (first) command**, and print a note:
|
|
67
|
+
`copied to clipboard: <cmd> (other options shown above)`. Cleaner than "copy only when exactly one".
|
|
68
|
+
- TTY gate: skip clipboard entirely when `[ ! -t 1 ]` (meaningless over SSH / pipes / non-TTY).
|
|
69
|
+
- Tools, in order, guarded by `command -v`: `pbcopy` (macOS); `wl-copy`, `xclip -selection clipboard`,
|
|
70
|
+
`xsel --clipboard --input` (Linux); `clip.exe`/`clip` (Windows/WSL).
|
|
71
|
+
- **Never fatal:** `printf '%s' "$cmd" | <tool> 2>/dev/null || true`. If no tool exists, print is the
|
|
72
|
+
primary output and the command still exits 0. Clipboard failure never changes the exit code.
|
|
73
|
+
- These clipboard tools ARE allowed to run. Only the FOUR cloud CLIs are forbidden (LOCK 5).
|
|
74
|
+
|
|
75
|
+
### LOCK 5 -- NON-EXECUTION is a tested invariant
|
|
76
|
+
- The command must **NEVER** invoke vercel / netlify / flyctl / wrangler -- not even `--version`.
|
|
77
|
+
- Detection is **`command -v <cli>` ONLY**. No `$cli ...` call anywhere in the code path.
|
|
78
|
+
- Proven by SDET test (see Section 8, headline test): PATH stubs for all four that write a sentinel
|
|
79
|
+
IF invoked; after `loki deploy` runs, assert all four sentinels ABSENT **and** the expected
|
|
80
|
+
command string was printed (both halves -- sentinel-absent alone passes vacuously).
|
|
81
|
+
|
|
82
|
+
### LOCK 6 -- Filesystem-only; NOT gated on a running app
|
|
83
|
+
- `loki deploy` is a static advisory. It must work with **nothing running, no build started.**
|
|
84
|
+
- It does **NOT** read `app-runner/state.json` and has **no** status=running / live-port
|
|
85
|
+
precondition (this is the key divergence from `_preview_public`). Detection is from project
|
|
86
|
+
files in the target dir, using the same `${TARGET_DIR:-.}` resolution app-runner uses.
|
|
87
|
+
- Only two real preconditions: (a) a project type is detected, else honest non-zero exit;
|
|
88
|
+
(b) at least one matching CLI is installed, else honest install hint + non-zero exit.
|
|
89
|
+
|
|
90
|
+
### LOCK 7 -- Bun parity: bash-only is acceptable
|
|
91
|
+
- bash-only, no Bun runner change. Precedent: HUD (`FEAT-HUD`) and preview (`FEAT-PREVIEW-LINK`)
|
|
92
|
+
are bash-only; the Bun runner is dormant for the live/advisory path. State explicitly in CHANGELOG.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 1. Project-type detection (filesystem signals)
|
|
97
|
+
|
|
98
|
+
Reuse the signals already proven in `autonomy/app-runner.sh`. All checks are read-only file/dir
|
|
99
|
+
existence + `grep` on `package.json`. Resolve the directory as `local dir="${TARGET_DIR:-.}"`
|
|
100
|
+
(same idiom as `app_runner_init` at `app-runner.sh:739` and `_detect_nextjs_standalone` at `:594`).
|
|
101
|
+
|
|
102
|
+
Detection cascade (first match wins for the *primary* type label; multiple provider options can
|
|
103
|
+
still be offered per LOCK 2):
|
|
104
|
+
|
|
105
|
+
| # | Type | Signal (exact checks) | Source in repo |
|
|
106
|
+
|---|---|---|---|
|
|
107
|
+
| 1 | Next.js | `grep -q '"next"' "$dir/package.json"` OR `[ -f "$dir/next.config.js" ]` OR `next.config.mjs`/`next.config.ts` present | mirrors next detection near `app-runner.sh:800-813` |
|
|
108
|
+
| 2 | Static / SPA | (`[ -d "$dir/dist" ]` OR `[ -d "$dir/build" ]`) AND a built `index.html` in that dir; OR `grep -q '"vite"' package.json` / CRA (`react-scripts`) | vite signal at `app-runner.sh:697` |
|
|
109
|
+
| 3 | Dockerfile / container | `[ -f "$dir/Dockerfile" ]` (and/or `docker-compose.yml`/`compose.yml`) | `app-runner.sh:759`, `:774` |
|
|
110
|
+
| 4 | Generic Node server | `[ -f "$dir/package.json" ]` with a `"start"`/`"dev"` script and no static build dir | `app-runner.sh:798`, `:814`, `:821` |
|
|
111
|
+
| 5 | Python | `[ -f "$dir/requirements.txt" ]` OR `[ -f "$dir/pyproject.toml" ]` | `app-runner.sh:953` |
|
|
112
|
+
|
|
113
|
+
- Do **not** call `_detect_nextjs_standalone`'s artifact-only path as the sole Next signal; for an
|
|
114
|
+
advisory we want the *source* signal (`"next"` in package.json / `next.config.*`) since the build
|
|
115
|
+
may not have run yet. (The artifact path keys on `.next/standalone/server.js` -- too narrow here.)
|
|
116
|
+
- Type -> candidate providers per the LOCK 2 precedence table.
|
|
117
|
+
- If **no** type matches: honest "no deployable project detected here" message naming the signals
|
|
118
|
+
it looked for (package.json / Dockerfile / dist|build / requirements.txt|pyproject.toml), non-zero exit.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 2. Command surface + dispatch wiring
|
|
123
|
+
|
|
124
|
+
### 2.1 Dispatch arm (new, contiguous block placement)
|
|
125
|
+
Add a `deploy)` arm to the dispatch `case` next to `preview)` (`autonomy/loki:14966`):
|
|
126
|
+
```
|
|
127
|
+
deploy)
|
|
128
|
+
cmd_deploy "$@"
|
|
129
|
+
;;
|
|
130
|
+
```
|
|
131
|
+
No deprecated alias (brand-new verb; nothing to alias).
|
|
132
|
+
|
|
133
|
+
### 2.2 Function placement -- CONTIGUOUS, for test extractability
|
|
134
|
+
Place ALL deploy code as **one contiguous block** so the SDET test can extract it by name anchor
|
|
135
|
+
the same way `test-preview-public.sh` extracts the preview block (awk from the first helper def to
|
|
136
|
+
the closing `}` of `cmd_deploy`). Recommended location: immediately AFTER `cmd_preview()` ends
|
|
137
|
+
(after `autonomy/loki:5649`-area close), so the two advisory features sit together. The block:
|
|
138
|
+
- `_deploy_detect_type()` -- echoes the primary type label (Section 1); always returns 0.
|
|
139
|
+
- `_deploy_options_for_type()`-- given type, echoes ordered `provider|cli|command` rows (pure, testable).
|
|
140
|
+
- `_deploy_copy_clipboard()` -- best-effort copy (LOCK 4); always returns 0; TTY/`command -v` guarded.
|
|
141
|
+
- `cmd_deploy()` -- arg parse + orchestration + printing.
|
|
142
|
+
|
|
143
|
+
Keep helpers pure where possible (take dir / type as args, echo to stdout, `return 0`) so unit
|
|
144
|
+
tests can call them directly without a process -- mirrors the pure extractors `_extract_tunnel_url_*`.
|
|
145
|
+
|
|
146
|
+
### 2.3 `cmd_deploy()` flow
|
|
147
|
+
1. Parse args in a `while [ $# -gt 0 ]` loop (mirror `cmd_preview` `:5555`):
|
|
148
|
+
- `--help|-h|help` -> print help block (Section 2.4), `return 0`.
|
|
149
|
+
- `--dir <path>` -> override the project dir to scan (default `${TARGET_DIR:-.}` then `.`).
|
|
150
|
+
- `--no-clip` -> disable clipboard copy.
|
|
151
|
+
- `--json` -> machine-readable output (optional; see 2.5). Lenient on unknown args (no hard
|
|
152
|
+
error -> no behavior drift), matching `cmd_preview`. Guard value-consuming shifts exactly as
|
|
153
|
+
`--provider` does at `:5595`-`:5599` (avoid set -e underflow when a flag is the last arg).
|
|
154
|
+
2. `type=$(_deploy_detect_type "$dir")`.
|
|
155
|
+
3. If empty -> no-project path: honest message + `return 1`.
|
|
156
|
+
4. `options=$(_deploy_options_for_type "$type")`; filter to rows whose CLI is installed
|
|
157
|
+
(`command -v "$cli" >/dev/null 2>&1`).
|
|
158
|
+
5. If zero installed CLIs -> no-CLI path: honest install hints for each candidate provider
|
|
159
|
+
(brew + official URL), `return 1` (Section 3).
|
|
160
|
+
6. Else: print a header (detected type), then each installed option block (label, command,
|
|
161
|
+
docs URL), idiomatic first. Best-effort copy the first command (LOCK 4). `return 0`.
|
|
162
|
+
7. **Never** call any cloud CLI. Print only.
|
|
163
|
+
|
|
164
|
+
### 2.4 `--help`
|
|
165
|
+
Mirror the `cmd_preview` help block (`:5557`-`:5585`). Must state: advisory/print-only; it does NOT
|
|
166
|
+
deploy and does NOT run any cloud CLI; it detects project type + your installed CLI and prints the
|
|
167
|
+
command for YOU to run; clipboard is best-effort. List options `--dir`, `--no-clip`, `--json`, `--help`.
|
|
168
|
+
|
|
169
|
+
### 2.5 `--json` (optional, recommended)
|
|
170
|
+
Emit `{"type": "...", "options": [{"provider","cli","command","docs"}], "copied": "<cmd|>"}`.
|
|
171
|
+
Honest: empty `options` when no CLI installed. Suppress clipboard note under `--json`.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## 3. No-CLI-installed path (honest install hints)
|
|
176
|
+
|
|
177
|
+
Mirror the gh-missing / tunnel-missing block at `autonomy/loki:5413`-`:5433`. For each candidate
|
|
178
|
+
provider for the detected type, print to **stderr**, then `return 1`:
|
|
179
|
+
```
|
|
180
|
+
No deploy CLI found for this <type> project.
|
|
181
|
+
Loki never accesses your cloud account or runs deploy for you -- you run the printed command.
|
|
182
|
+
Install one of the following, then re-run 'loki deploy':
|
|
183
|
+
|
|
184
|
+
Vercel: brew install vercel | https://vercel.com/docs/cli
|
|
185
|
+
Netlify: brew install netlify-cli | https://docs.netlify.com/cli/get-started/
|
|
186
|
+
Fly.io: brew install flyctl | https://fly.io/docs/flyctl/install/
|
|
187
|
+
Cloudflare: npm i -g wrangler | https://developers.cloudflare.com/workers/wrangler/install-and-update/
|
|
188
|
+
```
|
|
189
|
+
- NEVER fabricate success; NEVER download a binary. Only print candidates relevant to the type.
|
|
190
|
+
- Non-zero exit so scripts/CI see the failure.
|
|
191
|
+
|
|
192
|
+
## 3b. No-detected-project path
|
|
193
|
+
Honest message to stderr naming the signals checked (Section 1), `return 1`.
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 4. set -e / shellcheck safety (file runs under `set -euo pipefail`)
|
|
198
|
+
|
|
199
|
+
- Increment counters as `i=$((i + 1))` (never `((i++))`, which returns 1 at zero and aborts).
|
|
200
|
+
- All `grep`/`command -v` that may "fail" guarded: `grep -q ... || true` or `if command -v ...`.
|
|
201
|
+
- Quote every path: `"$dir"`, `"$dir/package.json"`. Pass paths as argv to any python helper
|
|
202
|
+
(never interpolate into a heredoc) -- mirror `_read_app_state` (`:5219`-`:5227`).
|
|
203
|
+
- If a python heredoc is used (e.g. for `--json`), escape `$` that bash must not expand
|
|
204
|
+
(`\$`); follow `tests/check-heredoc-dollar-digit.sh` (the repo gate for `$1`/`$2` in heredocs).
|
|
205
|
+
- Clipboard pipeline always `|| true`; never let a missing tool or non-TTY abort.
|
|
206
|
+
- Value-consuming flag shifts guarded (`[ $# -ge 2 ] && shift`) -- see `:5595`.
|
|
207
|
+
- Capture sub-function exit into a local then `return $rc` (don't bare-return a non-zero call line
|
|
208
|
+
under set -e) -- mirror `:5614`-`:5617`.
|
|
209
|
+
- Must pass `tests/run-shellcheck.sh` clean.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 5. SDET test plan -- `tests/test-deploy.sh` (NEW)
|
|
214
|
+
|
|
215
|
+
Structure mirrors `tests/test-preview-public.sh`: extract the contiguous deploy block by name anchor
|
|
216
|
+
into a temp lib, source it, drive `cmd_deploy` and the pure helpers. `pass/fail` counters, `mktemp -d`
|
|
217
|
+
WORKROOT, `trap cleanup EXIT INT TERM`. Color globals exported empty. Every un-runnable case emits a
|
|
218
|
+
visible FAIL/SKIP -- never a silent pass. Fixtures live under temp dirs (fake package.json,
|
|
219
|
+
Dockerfile, dist/index.html, requirements.txt).
|
|
220
|
+
|
|
221
|
+
Extraction sanity (non-vacuity gate, like preview lines 113-126): assert all 4 deploy function
|
|
222
|
+
defs are present in the extracted lib, else fail loudly and abort.
|
|
223
|
+
|
|
224
|
+
### Test 1 (HEADLINE) -- NON-EXECUTION invariant
|
|
225
|
+
- Build a `fake-bin` with stubs `vercel`, `netlify`, `flyctl`, `wrangler`, each:
|
|
226
|
+
`#!/usr/bin/env bash` -> `echo ran > "$SENTINEL_<name>"` -> `exit 0`.
|
|
227
|
+
- Prepend `fake-bin` to PATH so `command -v` RESOLVES all four (CLIs DETECTED + printed).
|
|
228
|
+
- Fixture: a Next.js project dir (package.json with `"next"`).
|
|
229
|
+
- Run `cmd_deploy --dir <fixture>`; capture stdout.
|
|
230
|
+
- Assert BOTH halves:
|
|
231
|
+
1. **NONE** of the four sentinels exists (no cloud CLI was invoked) -- the core invariant.
|
|
232
|
+
2. The expected command string (`vercel --prod`) **was printed** (proves non-vacuity -- a
|
|
233
|
+
deploy that prints nothing would pass half 1 falsely).
|
|
234
|
+
- This is the highest-stakes test (echoes the real tunnel-CLI-launched-during-dev incident).
|
|
235
|
+
|
|
236
|
+
### Test 2 -- Project-type detection (pure helper, per type)
|
|
237
|
+
For each fixture (Next.js / static dist+index.html / Dockerfile / generic Node / Python), assert
|
|
238
|
+
`_deploy_detect_type "$dir"` returns the expected label. Include a "none" fixture (empty dir) ->
|
|
239
|
+
empty label.
|
|
240
|
+
|
|
241
|
+
### Test 3 -- Option ordering / idiomatic-first (pure helper)
|
|
242
|
+
With all four stubs on PATH, assert `_deploy_options_for_type next` lists Vercel before Netlify
|
|
243
|
+
before Fly; static lists Netlify/Cloudflare before Vercel; Dockerfile lists Fly first. Assert the
|
|
244
|
+
printed command strings are EXACTLY the LOCK 3 canonical forms (mutation-proof: a wrong flag fails).
|
|
245
|
+
|
|
246
|
+
### Test 4 -- No-CLI-installed path
|
|
247
|
+
Curated clean PATH with NONE of the four CLIs (build_clean_bin idiom, preview lines 191-201;
|
|
248
|
+
symlink only coreutils). Next.js fixture. Assert: honest install hint printed (contains
|
|
249
|
+
"Install one of the following" and at least the Vercel brew + URL) AND non-zero exit AND no sentinel.
|
|
250
|
+
|
|
251
|
+
### Test 5 -- No-detected-project path
|
|
252
|
+
Empty dir fixture. Assert honest "no deployable project" message naming signals + non-zero exit.
|
|
253
|
+
|
|
254
|
+
### Test 6 -- Clipboard best-effort, never fatal
|
|
255
|
+
- 6a: PATH with NO clipboard tool (clean bin) + Next.js fixture + on a non-TTY (`</dev/null` /
|
|
256
|
+
piped stdout). Assert exit 0 and the command still PRINTED (clipboard absence is non-fatal).
|
|
257
|
+
- 6b (if a clipboard tool exists on host or via a fake `pbcopy` stub that writes a file): assert the
|
|
258
|
+
idiomatic command was copied AND the "copied to clipboard" note printed. Fake pbcopy is allowed to
|
|
259
|
+
run (it is not a cloud CLI). SKIP-safe if no TTY can be simulated.
|
|
260
|
+
|
|
261
|
+
### Test 7 -- `--json` honesty (if implemented)
|
|
262
|
+
Assert valid JSON; `options` empty when no CLI installed; non-empty with correct commands when stubs
|
|
263
|
+
present. Suppresses the clipboard note.
|
|
264
|
+
|
|
265
|
+
### Test 8 -- set -e safety / no-abort
|
|
266
|
+
Run a representative `cmd_deploy` invocation inside a `set -e -o pipefail` subshell and assert it
|
|
267
|
+
reaches a sentinel "ALIVE" echo after the call (proves no spurious abort), mirroring preview 1b
|
|
268
|
+
(lines 304-314).
|
|
269
|
+
|
|
270
|
+
### Wiring into `tests/run-all-tests.sh`
|
|
271
|
+
Add next to the preview registration (`tests/run-all-tests.sh:204`):
|
|
272
|
+
```
|
|
273
|
+
run_test "Deploy Advisory (print-only, non-execution invariant)" "$SCRIPT_DIR/test-deploy.sh"
|
|
274
|
+
```
|
|
275
|
+
Also covered transitively by `tests/run-shellcheck.sh` and the mutation/mock detectors (gates #8/#9).
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## 6. Docs to update on release
|
|
280
|
+
|
|
281
|
+
- `README.md:447` -- change the Deploy "What Works" cell to note advisory-print: e.g.
|
|
282
|
+
"Generates configs/Dockerfiles/CI-CD; `loki deploy` prints the exact deploy command (advisory)".
|
|
283
|
+
Keep "What Doesn't (Yet)": "Does not run deploy -- human runs the printed command." Line 452's
|
|
284
|
+
"Human oversight is expected for deployment" stays TRUE and unchanged.
|
|
285
|
+
- `CHANGELOG.md` -- new feature entry (FEAT-DEPLOY): advisory print-only `loki deploy`; non-execution
|
|
286
|
+
invariant; filesystem-only detection; best-effort clipboard; bash-only (Bun parity note).
|
|
287
|
+
- `docs/INSTALLATION.md` -- optional "Deploying your build" note pointing at `loki deploy`.
|
|
288
|
+
- `wiki/CLI-Reference.md` -- add the `deploy` command entry + options.
|
|
289
|
+
- `skills/production.md` (and `skills/00-index.md` if it indexes commands) -- document the advisory
|
|
290
|
+
deploy step in the production workflow.
|
|
291
|
+
- Create this plan's sibling precedent set is `docs/PREVIEW-LINK-PLAN.md` / `docs/BUILD-HUD-PLAN.md`
|
|
292
|
+
-- this file (`docs/DEPLOY-PLAN.md`) is the matching plan artifact.
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## 7. Critical files for implementation
|
|
297
|
+
|
|
298
|
+
- /Users/lokesh/git/loki-mode/autonomy/loki (cmd_deploy + helpers as a contiguous block after cmd_preview ~:5649; dispatch arm next to preview ~:14966; "More commands" footer ~:728)
|
|
299
|
+
- /Users/lokesh/git/loki-mode/autonomy/app-runner.sh (detection signal source: next ~:800-813, vite ~:697, Dockerfile ~:759/:774, python ~:953, ${TARGET_DIR:-.} idiom ~:739)
|
|
300
|
+
- /Users/lokesh/git/loki-mode/tests/test-deploy.sh (NEW -- mirror tests/test-preview-public.sh)
|
|
301
|
+
- /Users/lokesh/git/loki-mode/tests/run-all-tests.sh (register the new test near :204)
|
|
302
|
+
- /Users/lokesh/git/loki-mode/README.md (line 447 Deploy row -> advisory-print)
|
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.73.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -395,7 +395,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
|
|
|
395
395
|
# Run Loki Mode in Docker (Claude provider, API-key auth)
|
|
396
396
|
docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
|
|
397
397
|
-v $(pwd):/workspace -w /workspace \
|
|
398
|
-
asklokesh/loki-mode:7.
|
|
398
|
+
asklokesh/loki-mode:7.73.0 start ./my-spec.md
|
|
399
399
|
```
|
|
400
400
|
|
|
401
401
|
##### docker compose + .env (no host install)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# PREVIEW-LINK-PLAN.md -- Public Preview Link (BYO-tunnel)
|
|
2
|
+
|
|
3
|
+
## Product Owner scope locks (decided 2026-06-18)
|
|
4
|
+
1. Command surface: `loki preview --public` (a flag on the existing command, NOT a new top-level command and NOT `loki share preview` which is a deprecated alias to report-gist). Respects the CLI-consolidation mandate.
|
|
5
|
+
2. Lifecycle: FOREGROUND-blocking with a trap teardown + "Press Ctrl+C to stop sharing." (Safer than background+pidfile: no orphaned public tunnel a user forgets about.)
|
|
6
|
+
3. Default provider / detection order: cloudflared first (quick tunnels need NO account), ngrok second (needs authtoken). `--provider cloudflared|ngrok` override.
|
|
7
|
+
4. Host-header rewrite: default-ON (cloudflared `--http-host-header localhost`, ngrok `--host-header=rewrite`) with a `--no-host-rewrite` escape. Fixes the #1 dev-server "Invalid Host header" failure.
|
|
8
|
+
5. Bun parity: bash-only is acceptable for v7.72.0 (HUD precedent; the Bun runner is dormant for the live path). No loki-ts mirror required now.
|
|
9
|
+
6. Consent: explicit, default-NO. Interactive `[y/N]` on a TTY (only ^[Yy] proceeds); `--yes` skips the prompt but still prints the warning; non-TTY without `--yes` REFUSES.
|
|
10
|
+
|
|
11
|
+
## 1. Goal
|
|
12
|
+
Loki builds + runs the app locally and `loki preview` (cmd_preview, autonomy/loki:5212) opens it at http://localhost:PORT. There is no way to share the running app. Add a consent-gated `--public` path that creates a PUBLIC URL for the already-running local app by wrapping the USER'S OWN tunnel CLI (cloudflared or ngrok). The app + the user's creds stay on their machine; Loki never proxies traffic and never bundles/downloads a binary. Delivers the "share what was built" wow (Replit/Lovable/Bolt have it) without breaking "your keys, nothing leaves your network."
|
|
13
|
+
|
|
14
|
+
## 2. Command surface + dispatch
|
|
15
|
+
`loki preview --public` (+ `--provider`, `--yes`, `--no-host-rewrite`). The `preview)` dispatch arm (autonomy/loki:14596 -> cmd_preview "$@") already forwards args; no dispatch-table change. Add the flags to cmd_preview's arg parser (~:5214); `--public` branches into a new `_preview_public` helper BEFORE the existing browser-open logic. Update cmd_preview --help (~:5216-5229).
|
|
16
|
+
|
|
17
|
+
## 3. Precondition checks (REUSE cmd_preview state.json read)
|
|
18
|
+
Refactor the inline parse at autonomy/loki:5246-5263 into a shared `_read_app_state <state_file>` echoing url/status/port/primary_service; both the existing browser-open path and `--public` call it (no drift). Then, in order, each with honest degrade + non-zero exit:
|
|
19
|
+
1. state.json exists (${LOKI_DIR}/app-runner/state.json) -> else "No app running. loki start / loki status".
|
|
20
|
+
2. status == running (mirror :5265) -> else "App is not running (status: X)".
|
|
21
|
+
3. URL/port resolved (fallback http://localhost:${port:-3000} per :5271-5273).
|
|
22
|
+
4. PORT reachability (NEW): poll with the curl-readiness pattern at autonomy/loki:4979 (curl -s http://localhost:PORT >/dev/null, few retries, sleep 0.5, retries=$((retries+1))). Dead port -> "not exposing a dead port", non-zero. Never tunnel a dead port.
|
|
23
|
+
|
|
24
|
+
## 4. Consent (load-bearing, default-OFF)
|
|
25
|
+
- Interactive TTY ([ -t 0 ]): print the full warning (SS9), prompt `Expose this app publicly? [y/N] ` via `read -r` (idiom at :1897-1902) but DEFAULT-N (only ^[Yy] proceeds; deliberately NOT the default-Y at :1894 -- public exposure is unsafe).
|
|
26
|
+
- --yes: skips the prompt; warning still PRINTED.
|
|
27
|
+
- Non-TTY without --yes: REFUSE ("Refusing to expose a public tunnel non-interactively without --yes"), non-zero. Never silently expose.
|
|
28
|
+
|
|
29
|
+
## 5. BYO-CLI detection + install hint (command -v based, so a PATH stub works in CI)
|
|
30
|
+
Order (override with --provider): cloudflared, then ngrok, else honest install hint + non-zero. NEVER pretend success, NEVER download a binary. Hint mirrors the gh-missing block at :28304-28311; names brew + official URLs for both; states Loki wraps YOUR OWN client.
|
|
31
|
+
|
|
32
|
+
## 6. URL extraction (pure, testable: read from file/string, not a live process)
|
|
33
|
+
- cloudflared (`cloudflared tunnel --url http://localhost:PORT [--http-host-header localhost]`): quick-tunnel URL prints to stderr/log. Redirect stdout+stderr to ${LOKI_DIR}/preview/cloudflared.log; poll (bounded ~20 x sleep 0.5, tries=$((tries+1))) `grep -oE 'https://[a-z0-9-]+\.trycloudflare\.com'` first match. Timeout no-match -> teardown + non-zero.
|
|
34
|
+
- ngrok (`ngrok http PORT [--host-header=rewrite]`): scrape the local API `curl -s http://127.0.0.1:4040/api/tunnels` -> .tunnels[].public_url (prefer https); python3 json parse, grep fallback; bounded poll. No authtoken -> 4040 never comes up -> honest "ngrok config add-authtoken" hint, non-zero.
|
|
35
|
+
Factor `_extract_tunnel_url_cloudflared <logfile>` and `_extract_tunnel_url_ngrok <json-file-or-string>` as PURE functions for the stub test.
|
|
36
|
+
|
|
37
|
+
## 7. Lifecycle (foreground + trap, per lock #2)
|
|
38
|
+
- `tunnel_cmd ... > "$log" 2>&1 & tunnel_pid=$!`
|
|
39
|
+
- `trap '_preview_public_teardown "$tunnel_pid"' EXIT INT TERM` immediately. Teardown: `kill -TERM "$tunnel_pid" 2>/dev/null || true; sleep 1; kill -KILL "$tunnel_pid" 2>/dev/null || true` + remove log/pidfile (all || true, set -e safe).
|
|
40
|
+
- After URL capture: print public URL + live warning + "Press Ctrl+C to stop sharing." Then `wait "$tunnel_pid"`. Ctrl+C -> trap -> clean teardown.
|
|
41
|
+
- State dir ${LOKI_DIR}/preview/ (mkdir -p), parallel to app-runner/.
|
|
42
|
+
|
|
43
|
+
## 8. Host-header (default-ON, lock #4; escape --no-host-rewrite)
|
|
44
|
+
Dev servers (Vite/Next dev/webpack/Django ALLOWED_HOSTS) reject a tunneled Host: <random>.trycloudflare.com with "Invalid Host header". cloudflared `--http-host-header localhost`; ngrok `--host-header=rewrite`. Verify the exact flag against the installed CLI version at runtime; do not hardcode blindly. Document that production-style servers may still need the tunnel host added to their allowlist.
|
|
45
|
+
|
|
46
|
+
## 9. Help + warning copy (honest, no fabricated safety claims)
|
|
47
|
+
Help appended to cmd_preview --help: --public, --provider, --yes, --no-host-rewrite (per SS2). Warning printed before the prompt every time:
|
|
48
|
+
```
|
|
49
|
+
WARNING: This makes the app running on THIS machine reachable by ANYONE who has
|
|
50
|
+
the URL, over the public internet, using YOUR tunnel account.
|
|
51
|
+
- The app may have NO authentication. Anyone with the link can use it.
|
|
52
|
+
- Traffic flows through your own cloudflared/ngrok account, not through Loki.
|
|
53
|
+
- This stays up until you stop it. Stop it when you are done.
|
|
54
|
+
```
|
|
55
|
+
No "secure"/"encrypted" claims beyond what the tunnel CLI itself provides.
|
|
56
|
+
|
|
57
|
+
## 10. Degrade / error table (all set -e safe)
|
|
58
|
+
state.json absent / status!=running / dead port / no CLI / non-TTY-no-yes / URL-capture-timeout / ngrok-no-authtoken -> honest message + non-zero. Consent declined -> "Aborted. App was not exposed." exit 0. Ctrl+C -> trap teardown + "Tunnel stopped." exit 0.
|
|
59
|
+
|
|
60
|
+
## 11. Test plan (no real tunnel in CI; FAKE binary on PATH + pure extractors)
|
|
61
|
+
1. Consent: pipe `n` -> Aborted, no spawn; `y` (fake bin) -> proceeds; --yes skips prompt but prints warning; non-TTY no --yes -> refuse + non-zero.
|
|
62
|
+
2. CLI-absent: PATH without cloudflared/ngrok -> install hint + non-zero, no download.
|
|
63
|
+
3. URL extraction: cloudflared stub script prints a fixed trycloudflare URL to its log -> assert extractor returns it (+ a real-format log fixture); ngrok extractor against a fixture 4040 JSON -> assert public_url; empty log -> timeout path tears down + non-zero.
|
|
64
|
+
4. Preconditions: missing state.json; status=building; unreachable port -> each right message + non-zero.
|
|
65
|
+
5. Teardown: SIGINT to the fake-bin run -> child pid gone, log/pidfile cleaned, no orphan.
|
|
66
|
+
6. set -e / lint: bash parity + shellcheck/local-ci over the new code (x=$((x+1)), escaped $ in python heredocs, path as argv).
|
|
67
|
+
Mirror the existing CLI test harness that covers cmd_preview/gist-share.
|
|
68
|
+
|
|
69
|
+
## 12. Task list
|
|
70
|
+
Agent A (surface, consent, preconditions): extract _read_app_state from :5246-5263 + repoint the existing browser path (regression-test plain `loki preview`); add flags to arg parse; branch --public into _preview_public; preconditions incl port poll (:4979 pattern); consent (warning + default-N + non-TTY/--yes); update --help.
|
|
71
|
+
Agent B (detection, extraction, lifecycle): command -v detection + order + hint; pure _extract_tunnel_url_cloudflared / _extract_tunnel_url_ngrok; foreground launch + trap teardown; host-header flags + --no-host-rewrite.
|
|
72
|
+
Both: tests per SS11; local-ci/parity gate; v7.72.0 bump + changelog (integrator); no commit/push unless asked.
|
|
73
|
+
|
|
74
|
+
## Critical files
|
|
75
|
+
- autonomy/loki (cmd_preview :5212; arg parse :5214; help :5216-5229; state read to extract :5246-5263; dispatch :14596; consent prompt :1897; nohup/pidfile :4964-4968; curl poll :4979; pgid teardown :2221)
|
|
76
|
+
- autonomy/app-runner.sh (state.json writer :104-135 -- url/status/port/primary_service source of truth)
|
|
77
|
+
- The bash test harness covering cmd_preview (mirror its PATH-stub + fixture style)
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var t6=Object.defineProperty;var i6=($)=>$;function e6($,Q){this[$]=i6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)t6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:e6.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var q$=import.meta.require;var D1={};h(D1,{lokiDir:()=>j,homeLokiDir:()=>r$,findRepoRootForVersion:()=>s$,REPO_ROOT:()=>g});import{resolve as a,dirname as a$}from"path";import{fileURLToPath as $Q}from"url";import{existsSync as F$}from"fs";import{homedir as QQ}from"os";function ZQ(){let $=S1;for(let Q=0;Q<6;Q++){if(F$(a($,"VERSION"))&&F$(a($,"autonomy/run.sh")))return $;let Z=a$($);if(Z===$)break;$=Z}return a(S1,"..","..","..")}function s$($){let Q=$;for(let Z=0;Z<6;Z++){if(F$(a(Q,"VERSION"))&&F$(a(Q,"autonomy/run.sh")))return Q;let z=a$(Q);if(z===Q)break;Q=z}return a($,"..","..","..")}function j(){return process.env.LOKI_DIR??a(process.cwd(),".loki")}function r$(){return a(QQ(),".loki")}var S1,g;var b=P(()=>{S1=a$($Q(import.meta.url));g=ZQ()});import{readFileSync as zQ}from"fs";import{resolve as XQ,dirname as KQ}from"path";import{fileURLToPath as qQ}from"url";function R$(){if(Q$!==null)return Q$;let $="7.
|
|
2
|
+
var t6=Object.defineProperty;var i6=($)=>$;function e6($,Q){this[$]=i6.bind(null,Q)}var h=($,Q)=>{for(var Z in Q)t6($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:e6.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var q$=import.meta.require;var D1={};h(D1,{lokiDir:()=>j,homeLokiDir:()=>r$,findRepoRootForVersion:()=>s$,REPO_ROOT:()=>g});import{resolve as a,dirname as a$}from"path";import{fileURLToPath as $Q}from"url";import{existsSync as F$}from"fs";import{homedir as QQ}from"os";function ZQ(){let $=S1;for(let Q=0;Q<6;Q++){if(F$(a($,"VERSION"))&&F$(a($,"autonomy/run.sh")))return $;let Z=a$($);if(Z===$)break;$=Z}return a(S1,"..","..","..")}function s$($){let Q=$;for(let Z=0;Z<6;Z++){if(F$(a(Q,"VERSION"))&&F$(a(Q,"autonomy/run.sh")))return Q;let z=a$(Q);if(z===Q)break;Q=z}return a($,"..","..","..")}function j(){return process.env.LOKI_DIR??a(process.cwd(),".loki")}function r$(){return a(QQ(),".loki")}var S1,g;var b=P(()=>{S1=a$($Q(import.meta.url));g=ZQ()});import{readFileSync as zQ}from"fs";import{resolve as XQ,dirname as KQ}from"path";import{fileURLToPath as qQ}from"url";function R$(){if(Q$!==null)return Q$;let $="7.73.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=KQ(qQ(import.meta.url)),Z=s$(Q);Q$=zQ(XQ(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var t$=P(()=>{b()});var b1={};h(b1,{runOrThrow:()=>VQ,run:()=>k,commandVersion:()=>WQ,commandExists:()=>f,ShellError:()=>i$});async function k($,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[q,K,W]=await Promise.all([new Response(Z.stdout).text(),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function VQ($,Q={}){let Z=await k($,Q);if(Z.exitCode!==0)throw new i$(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=JQ($),Z=await k(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function JQ($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function WQ($,Q="--version"){if(!await f($))return null;let z=await k([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var i$;var d=P(()=>{i$=class i$ 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 s($){return UQ?"":$}var UQ,T,S,_,_Z,I,x,y,V;var c=P(()=>{UQ=(process.env.NO_COLOR??"").length>0;T=s("\x1B[0;31m"),S=s("\x1B[0;32m"),_=s("\x1B[1;33m"),_Z=s("\x1B[0;34m"),I=s("\x1B[0;36m"),x=s("\x1B[1m"),y=s("\x1B[2m"),V=s("\x1B[0m")});import{existsSync as _Q}from"fs";async function Z$(){if(Y$!==void 0)return Y$;let $="/opt/homebrew/bin/python3.12";if(_Q($))return Y$=$,$;let Q=await f("python3.12");if(Q)return Y$=Q,Q;let Z=await f("python3");return Y$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return k([Z,"-c",$],Q)}var Y$;var V$=P(()=>{d()});var e1={};h(e1,{runStatus:()=>cQ});import{existsSync as v,readFileSync as W$,readdirSync as d1,statSync as o1}from"fs";import{resolve as D,basename as CQ}from"path";import{homedir as bQ}from"os";function n1($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function a1($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*x$/Q);if(X>x$)X=x$;let q=x$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),J=n1($),U=n1(Q);return` ${x}${Z}${V} ${K}[${W}]${V} ${z}% (${J} / ${U})`}async function yQ(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${V}
|
|
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)
|
|
@@ -793,4 +793,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
793
793
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
794
794
|
`),process.stderr.write(r6),2}}l1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var qZ=await KZ(Bun.argv.slice(2));process.exit(qZ);
|
|
795
795
|
|
|
796
|
-
//# debugId=
|
|
796
|
+
//# debugId=8A68E6EB2762797464756E2164756E21
|
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.73.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 8 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.73.0",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 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",
|