loki-mode 7.126.0 → 7.128.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/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, 8 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.126.0
6
+ # Loki Mode v7.128.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
408
408
 
409
409
  ---
410
410
 
411
- **v7.126.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
411
+ **v7.128.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.126.0
1
+ 7.128.0
@@ -70,14 +70,33 @@ def project_state(path):
70
70
  gs = str(g.get("status", "")).lower()
71
71
  status = {"pass": "pass", "fail": "fail", "failed": "fail",
72
72
  "skipped": "skip", "skip": "skip"}.get(gs, "pending")
73
- gates.append({"name": g.get("gate", "gate"), "status": status})
73
+ # runner/scanner names the tool; summary is the one-line evidence.
74
+ runner = g.get("runner") or g.get("scanner") or ""
75
+ gates.append({
76
+ "name": g.get("gate", "gate"),
77
+ "status": status,
78
+ "runner": str(runner)[:24],
79
+ "evidence": str(g.get("summary", ""))[:80],
80
+ })
74
81
 
75
- # Council votes: .loki/council/*.json, each with a vote-ish field.
82
+ # Default model tier per reviewer (matches the standing council roster).
83
+ REVIEWER_TIER = {
84
+ "architecture-strategist": "opus",
85
+ "maintainer-mergeability": "opus",
86
+ "security-sentinel": "opus",
87
+ "test-coverage-auditor": "sonnet",
88
+ "performance-oracle": "opus",
89
+ "dependency-analyst": "sonnet",
90
+ }
91
+
92
+ # Council votes: .loki/council/*.json, each with a vote-ish field. Skips the
93
+ # aggregate state.json (no per-reviewer vote) so only real reviewer entries
94
+ # populate the strip.
76
95
  council = []
77
96
  cdir = os.path.join(loki, "council")
78
97
  try:
79
98
  for fn in sorted(os.listdir(cdir)):
80
- if not fn.endswith(".json"):
99
+ if not fn.endswith(".json") or fn == "state.json":
81
100
  continue
82
101
  cj = read_json(os.path.join(cdir, fn))
83
102
  raw = str(cj.get("vote") or cj.get("verdict") or "").lower()
@@ -89,7 +108,9 @@ def project_state(path):
89
108
  elif "concern" in raw:
90
109
  vote = "concern"
91
110
  reviewer = cj.get("reviewer") or cj.get("name") or os.path.splitext(fn)[0]
92
- council.append({"reviewer": str(reviewer)[:16], "vote": vote})
111
+ reviewer = str(reviewer)
112
+ tier = cj.get("tier") or cj.get("model_tier") or REVIEWER_TIER.get(reviewer, "")
113
+ council.append({"reviewer": reviewer[:24], "vote": vote, "tier": str(tier)[:8]})
93
114
  except Exception:
94
115
  pass
95
116
 
@@ -117,6 +138,7 @@ except Exception:
117
138
  for r in runs:
118
139
  fleet.append({
119
140
  "name": r.get("name") or os.path.basename(r.get("path", "") or "project"),
141
+ "path": r.get("path", "") or "",
120
142
  "phase": r.get("phase", "") or "",
121
143
  "iteration": r.get("iteration", 0) or 0,
122
144
  "status": r.get("status", "") or "",
@@ -189,3 +211,148 @@ cockpit_render() {
189
211
  # Gather -> render. Preserve the TS exit code (0 image / 3 fallback).
190
212
  cockpit_gather_state "$skill_dir" "$focus_repo" | bun "$entry" "${args[@]}"
191
213
  }
214
+
215
+ #===============================================================================
216
+ # E3: keyboard interactivity for `loki cockpit --follow`.
217
+ # These live here (sourced by cmd_cockpit) so they can be extracted + unit
218
+ # tested without pulling in the whole autonomy/loki CLI.
219
+ #===============================================================================
220
+
221
+ # Pure key -> action mapping. Takes a normalized key token, echoes one action
222
+ # word. Side-effect-free so it is trivially unit-testable.
223
+ # TAB / RIGHT / DOWN -> next (cycle focus forward across the fleet)
224
+ # LEFT / UP -> prev (cycle focus backward)
225
+ # e -> explain (print verify evidence for the focused run)
226
+ # s -> steer (print how to steer the focused run)
227
+ # q / ESC / Ctrl-C -> quit
228
+ # anything else -> noop
229
+ cockpit_handle_key() {
230
+ case "$1" in
231
+ TAB|RIGHT|DOWN) echo "next" ;;
232
+ LEFT|UP) echo "prev" ;;
233
+ e|E) echo "explain" ;;
234
+ s|S) echo "steer" ;;
235
+ q|Q|ESC|$'\x03') echo "quit" ;;
236
+ *) echo "noop" ;;
237
+ esac
238
+ }
239
+
240
+ # Read one keypress (non-blocking, no redraw stall) and normalize it to a token
241
+ # cockpit_handle_key understands. Echoes the token, or nothing if no key was
242
+ # pressed within the timeout. Arrow keys arrive as ESC [ A/B/C/D; a lone ESC (no
243
+ # following bytes) normalizes to ESC (quit). Only usable on a TTY.
244
+ # $1 = timeout seconds (fractional ok; feeds `read -t`)
245
+ cockpit_read_key() {
246
+ local timeout="${1:-2}"
247
+ local k rest
248
+ IFS= read -rsn1 -t "$timeout" k 2>/dev/null || return 1
249
+ case "$k" in
250
+ $'\t') echo "TAB"; return 0 ;;
251
+ $'\x03') echo "ESC"; return 0 ;; # Ctrl-C byte if trapped as data
252
+ $'\x1b')
253
+ # Possible escape sequence: grab up to 2 more bytes without blocking.
254
+ IFS= read -rsn2 -t 0.01 rest 2>/dev/null || rest=""
255
+ case "$rest" in
256
+ '[A') echo "UP" ;;
257
+ '[B') echo "DOWN" ;;
258
+ '[C') echo "RIGHT" ;;
259
+ '[D') echo "LEFT" ;;
260
+ *) echo "ESC" ;;
261
+ esac
262
+ return 0 ;;
263
+ '') return 1 ;; # timed out, no key
264
+ *) echo "$k"; return 0 ;;
265
+ esac
266
+ }
267
+
268
+ # Echo the fleet repo paths (one per line) so the follow loop can cycle focus.
269
+ # Empty output = no fleet (cycling is a no-op). Best-effort, never errors.
270
+ cockpit_fleet_paths() {
271
+ local skill_dir="$1"
272
+ local state_json
273
+ state_json="$(cockpit_gather_state "$skill_dir" "" 2>/dev/null || true)"
274
+ [ -z "$state_json" ] && return 0
275
+ command -v python3 >/dev/null 2>&1 || return 0
276
+ LOKI_CK_STATE="$state_json" python3 - <<'PYPATHS' 2>/dev/null || true
277
+ import json, os
278
+ s = json.loads(os.environ.get("LOKI_CK_STATE") or "{}")
279
+ for r in s.get("fleet") or []:
280
+ p = r.get("path") or ""
281
+ if p:
282
+ print(p)
283
+ PYPATHS
284
+ }
285
+
286
+ # Given the current focus path, a direction (next|prev), and a newline list of
287
+ # fleet paths, echo the path to focus after cycling. Wraps around. If focus is
288
+ # not in the list, next -> first entry, prev -> last entry. Empty list echoes the
289
+ # current focus unchanged. Pure, unit-testable.
290
+ # $1 = current focus path (may be empty) $2 = next|prev $3 = newline paths
291
+ cockpit_cycle_focus() {
292
+ local cur="$1" dir="$2" paths="$3"
293
+ [ -z "$paths" ] && { echo "$cur"; return 0; }
294
+ local -a arr=()
295
+ local line
296
+ while IFS= read -r line; do
297
+ [ -n "$line" ] && arr+=("$line")
298
+ done <<< "$paths"
299
+ local n="${#arr[@]}"
300
+ [ "$n" -eq 0 ] && { echo "$cur"; return 0; }
301
+ # Find current index.
302
+ local idx=-1 i
303
+ for i in "${!arr[@]}"; do
304
+ if [ "${arr[$i]}" = "$cur" ]; then idx="$i"; break; fi
305
+ done
306
+ local target
307
+ if [ "$idx" -lt 0 ]; then
308
+ # Not in list: next -> first, prev -> last.
309
+ if [ "$dir" = "prev" ]; then target=$((n - 1)); else target=0; fi
310
+ elif [ "$dir" = "prev" ]; then
311
+ target=$(( (idx - 1 + n) % n ))
312
+ else
313
+ target=$(( (idx + 1) % n ))
314
+ fi
315
+ echo "${arr[$target]}"
316
+ }
317
+
318
+ # Print the verify evidence ("explain") for the focused run. Reads the run's
319
+ # .loki/verify/evidence.json directly. Never fabricates: says so if absent.
320
+ cockpit_explain_focus() {
321
+ local focus_repo="$1"
322
+ local repo="${focus_repo:-$(pwd)}"
323
+ local ev="$repo/.loki/verify/evidence.json"
324
+ printf '\n'
325
+ printf 'verify --explain (%s)\n' "$repo"
326
+ if [ ! -f "$ev" ] || ! command -v python3 >/dev/null 2>&1; then
327
+ echo " (no evidence.json for this run yet)"
328
+ return 0
329
+ fi
330
+ LOKI_CK_EV="$ev" python3 - <<'PYEV' 2>/dev/null || echo " (could not read evidence)"
331
+ import json, os
332
+ try:
333
+ ev = json.load(open(os.environ["LOKI_CK_EV"]))
334
+ except Exception:
335
+ print(" (could not read evidence)"); raise SystemExit(0)
336
+ print(f" Verdict {str(ev.get('verdict','?')).upper()}")
337
+ checks = ev.get("deterministic_gates") or ev.get("checks") or ev.get("gates") or []
338
+ for c in checks[:12]:
339
+ name = c.get("gate") or c.get("name") or c.get("id") or "?"
340
+ status = c.get("status") or c.get("result") or "?"
341
+ print(f" {name:<28} {status}")
342
+ reason = ev.get("reason") or ev.get("summary")
343
+ if reason:
344
+ print(f" Reason {reason}")
345
+ PYEV
346
+ }
347
+
348
+ # Print a steering hint for the focused run. Steering is opt-in prompt injection;
349
+ # this only tells the operator how, it does not inject anything.
350
+ cockpit_steer_hint() {
351
+ local focus_repo="$1"
352
+ local repo="${focus_repo:-$(pwd)}"
353
+ printf '\n'
354
+ echo "Steer this run"
355
+ echo " Drop a note the next iteration will read:"
356
+ echo " echo 'your guidance' > $repo/.loki/steering.md"
357
+ echo " Or open the dashboard chat: loki dashboard"
358
+ }
package/autonomy/loki CHANGED
@@ -1230,6 +1230,31 @@ _loki_brand_banner() {
1230
1230
  fi
1231
1231
  }
1232
1232
 
1233
+ # E4: a 3-line "what Loki will do" preview from REAL signals only. Line 1 ties
1234
+ # the detected tier to its phase count (simple=3, standard=6, complex=8 -- the
1235
+ # same mapping run.sh uses; auto/unknown stays honest as "auto-detecting"). Line
1236
+ # 2 names the spec, or admits "exploring the codebase" when there is none. Line 3
1237
+ # is the fixed RARV-C closure promise. Never fabricates. Prints 3 lines to stdout.
1238
+ # Args: $1=spec label $2=tier
1239
+ _loki_plan_preview() {
1240
+ local spec="$1" tier="$2"
1241
+ local phases shape
1242
+ case "$(printf '%s' "$tier" | tr '[:upper:]' '[:lower:]')" in
1243
+ simple) phases="3 phases"; shape="a focused build" ;;
1244
+ standard) phases="6 phases"; shape="a full SDLC pass" ;;
1245
+ complex) phases="8 phases"; shape="a deep multi-phase build" ;;
1246
+ *) phases="phase count auto-detecting"; shape="scope from your spec" ;;
1247
+ esac
1248
+ printf ' plan %s -- %s\n' "$phases" "$shape"
1249
+ case "$spec" in
1250
+ ""|*"no PRD"*|*"codebase"*)
1251
+ printf ' from exploring the codebase (no spec file)\n' ;;
1252
+ *)
1253
+ printf ' from %s\n' "$spec" ;;
1254
+ esac
1255
+ printf ' loop Reason - Act - Reflect - Verify - Close, until verified\n'
1256
+ }
1257
+
1233
1258
  # Print the rich handoff card and resolve the user's choice. Echoes exactly one
1234
1259
  # decision word on stdout:
1235
1260
  # launch-bg detach only
@@ -1284,8 +1309,16 @@ _loki_start_handoff() {
1284
1309
  echo -e " ${DIM}log${NC} ${LOKI_DIR:-.loki}/logs/background-*.log"
1285
1310
  echo -e " ${DIM}dashboard${NC} ${dash_url}"
1286
1311
  echo ""
1312
+ # E4 plan preview: 3 lines of real intent before backgrounding.
1313
+ _loki_plan_preview "$spec" "$tier"
1314
+ echo ""
1287
1315
  echo -e " ${BOLD}Both${NC}[default] ${BOLD}D${NC}ashboard ${BOLD}W${NC}atch De${BOLD}t${NC}ach ${BOLD}S${NC}top"
1288
1316
  echo -e " ${DIM}opening your cockpit + dashboard in 8s -- press a key to choose${NC}"
1317
+ # First-run delight: only when this project has never chosen a view, teach
1318
+ # the cockpit once. Subsequent runs (config file exists) stay lean.
1319
+ if [ ! -f "$cfg_file" ]; then
1320
+ echo -e " ${DIM}first build here -- 'loki cockpit' reopens this live view any time${NC}"
1321
+ fi
1289
1322
  } >&2
1290
1323
 
1291
1324
  local reply=""
@@ -8746,7 +8779,7 @@ cmd_watch() {
8746
8779
  }
8747
8780
 
8748
8781
  #===============================================================================
8749
- # loki cockpit - Multi-repo live status as a terminal inline image (v7.126.0)
8782
+ # loki cockpit - Multi-repo live status as a terminal inline image (v7.128.0)
8750
8783
  #
8751
8784
  # Renders the Autonomi cockpit frame directly in supported terminals (iTerm2 /
8752
8785
  # WezTerm / ghostty via the iTerm2 protocol; Kitty via its graphics protocol).
@@ -8782,10 +8815,13 @@ if council:
8782
8815
  print(" Council " + " ".join(f"{c.get('reviewer','?')}:{c.get('vote','?')}" for c in council[:6]))
8783
8816
  fleet = s.get('fleet') or []
8784
8817
  if fleet:
8818
+ cap = 8 # cap so a huge registry (e.g. 171 runs) never floods the summary
8785
8819
  print(f" Fleet ({len(fleet)}):")
8786
- for r in fleet[:8]:
8820
+ for r in fleet[:cap]:
8787
8821
  mark = "*" if r.get("running") else " "
8788
8822
  print(f" {mark} {r.get('name','?'):<28} iter {r.get('iteration',0)} {r.get('phase') or r.get('status') or ''}")
8823
+ if len(fleet) > cap:
8824
+ print(f" + {len(fleet) - cap} more")
8789
8825
  PYTXT
8790
8826
  }
8791
8827
 
@@ -8800,7 +8836,7 @@ cmd_cockpit() {
8800
8836
  while [[ $# -gt 0 ]]; do
8801
8837
  case "$1" in
8802
8838
  --help|-h)
8803
- echo -e "${BOLD}loki cockpit${NC} - Live multi-repo status as a terminal inline image (v7.126.0)"
8839
+ echo -e "${BOLD}loki cockpit${NC} - Live multi-repo status as a terminal inline image (v7.128.0)"
8804
8840
  echo ""
8805
8841
  echo "Usage: loki cockpit [options]"
8806
8842
  echo ""
@@ -8887,14 +8923,44 @@ cmd_cockpit() {
8887
8923
  return 3
8888
8924
  }
8889
8925
 
8926
+ # E3 footer hints. _cockpit_hint is the persistent key legend under a frame;
8927
+ # _cockpit_hint_key is shown after an explain/steer panel to say how to return.
8928
+ _cockpit_hint() {
8929
+ printf '\n'
8930
+ echo -e " ${DIM}Tab/arrows focus e explain s steer q quit${NC}"
8931
+ }
8932
+ _cockpit_hint_key() {
8933
+ printf '\n'
8934
+ echo -e " ${DIM}press any key to return to the live view${NC}"
8935
+ }
8936
+
8937
+ # Max mtime (whole seconds) across the focus run's state files -- the honest
8938
+ # "something changed" signal for event-driven redraw. Prints empty when stat
8939
+ # is unavailable so the caller falls back to a blind interval. Both BSD stat
8940
+ # (-f %m, macOS) and GNU stat (-c %Y, Linux) are probed; nothing else is
8941
+ # installed. ponytail: whole-second granularity is enough for a status frame.
8942
+ _cockpit_mtime() {
8943
+ local repo="$1" loki base m best=""
8944
+ [ -n "$repo" ] || repo="$(pwd)"
8945
+ loki="$repo/.loki"
8946
+ for base in autonomy-state.json events.jsonl verify/evidence.json council; do
8947
+ local p="$loki/$base"
8948
+ [ -e "$p" ] || continue
8949
+ m="$(stat -f '%m' "$p" 2>/dev/null || stat -c '%Y' "$p" 2>/dev/null || true)"
8950
+ [ -n "$m" ] || continue
8951
+ if [ -z "$best" ] || { [ "$m" -gt "$best" ] 2>/dev/null; }; then best="$m"; fi
8952
+ done
8953
+ printf '%s' "$best"
8954
+ }
8955
+
8890
8956
  if [ "$run_once" = "1" ]; then
8891
8957
  _cockpit_frame
8892
8958
  return 0
8893
8959
  fi
8894
8960
 
8895
- # --follow (default): re-render until interrupted. On a fallback terminal we
8896
- # print the summary once (looping text is noise) and stop; on an image
8897
- # terminal we clear and redraw each interval.
8961
+ # --follow (default): re-render on state change until interrupted. Render the
8962
+ # FIRST frame BEFORE the alt-screen so a fallback terminal keeps its text
8963
+ # summary in the scrollback (looping text is noise -> we stop there).
8898
8964
  local frame_rc=0
8899
8965
  _cockpit_frame || frame_rc=$?
8900
8966
  if [ "$frame_rc" != "0" ]; then
@@ -8902,13 +8968,107 @@ cmd_cockpit() {
8902
8968
  # were already printed once. Point the user at --follow-capable UIs.
8903
8969
  return 0
8904
8970
  fi
8905
- # Image path: loop until Ctrl-C.
8906
- trap 'printf "\n"; return 0' INT
8907
- while :; do
8908
- sleep "$interval" 2>/dev/null || break
8909
- printf '\033[2J\033[H' # clear + home before redraw
8910
- _cockpit_frame || break
8911
- done
8971
+
8972
+ # Image path: enter the alternate screen buffer so redraws never scroll the
8973
+ # user's scrollback, and restore the terminal cleanly on any exit. The traps
8974
+ # cover Ctrl-C (INT), kill (TERM) and normal return (EXIT). The loop runs in a
8975
+ # subshell so the EXIT trap is scoped to the follow loop and cannot leak into
8976
+ # the caller's shell.
8977
+ #
8978
+ # E3 keyboard interactivity: on a TTY we put the terminal in raw single-key
8979
+ # mode so Tab/arrows/e/s/q are read without Enter, and the wait between frames
8980
+ # becomes a key poll (cockpit_read_key) instead of a blind sleep -- so a key
8981
+ # acts instantly instead of after the cadence. Restore stty + cursor on any
8982
+ # exit. If stdin is not a TTY, interactivity is skipped and the loop still
8983
+ # renders + refreshes exactly as before.
8984
+ local interactive=0
8985
+ if [ -t 0 ] && [ -t 1 ] && command -v stty >/dev/null 2>&1; then
8986
+ interactive=1
8987
+ fi
8988
+ (
8989
+ local _saved_stty=""
8990
+ [ "$interactive" = "1" ] && _saved_stty="$(stty -g 2>/dev/null || true)"
8991
+ _cockpit_restore() {
8992
+ [ -n "$_saved_stty" ] && stty "$_saved_stty" 2>/dev/null || true
8993
+ printf '\033[?25h\033[?1049l' # show cursor, leave alt-screen
8994
+ }
8995
+ trap '_cockpit_restore; exit 0' INT TERM
8996
+ trap '_cockpit_restore' EXIT
8997
+ [ "$interactive" = "1" ] && stty -echo -icanon min 0 time 0 2>/dev/null || true
8998
+ printf '\033[?1049h\033[?25l' # enter alt-screen, hide cursor
8999
+ printf '\033[H\033[2J' # home + clear the alt buffer once
9000
+ _cockpit_frame
9001
+ [ "$interactive" = "1" ] && _cockpit_hint
9002
+
9003
+ # Cadence bounds (seconds): poll every min_cadence for a state change
9004
+ # (anti-thrash upper bound on redraw rate); force a refresh at least every
9005
+ # max_idle so a stalled run still shows a live freshness clock. --interval
9006
+ # sets max_idle (and the blind fallback period when stat is missing).
9007
+ local min_cadence=1
9008
+ local max_idle="$interval"; [ "$max_idle" -ge 2 ] 2>/dev/null || max_idle=2
9009
+ local last_mtime; last_mtime="$(_cockpit_mtime "$focus_repo")"
9010
+ local idle=0
9011
+ while :; do
9012
+ # Wait one cadence step -- via a key poll when interactive (returns
9013
+ # early on a keypress), otherwise a plain sleep. On any wait failure
9014
+ # (interrupted) break out and let the trap restore the terminal.
9015
+ local key=""
9016
+ if [ "$interactive" = "1" ]; then
9017
+ key="$(cockpit_read_key "$min_cadence" 2>/dev/null || true)"
9018
+ else
9019
+ sleep "$min_cadence" 2>/dev/null || break
9020
+ fi
9021
+ idle=$((idle + min_cadence))
9022
+
9023
+ # Dispatch a keypress (no-op if none / not interactive).
9024
+ if [ -n "$key" ]; then
9025
+ local action; action="$(cockpit_handle_key "$key")"
9026
+ case "$action" in
9027
+ quit) break ;;
9028
+ next|prev)
9029
+ local paths; paths="$(cockpit_fleet_paths "$SKILL_DIR")"
9030
+ focus_repo="$(cockpit_cycle_focus "$focus_repo" "$action" "$paths")"
9031
+ last_mtime="$(_cockpit_mtime "$focus_repo")"
9032
+ idle=0
9033
+ printf '\033[H\033[2J'
9034
+ _cockpit_frame || break
9035
+ _cockpit_hint
9036
+ continue ;;
9037
+ explain)
9038
+ printf '\033[H\033[2J'
9039
+ _cockpit_frame
9040
+ cockpit_explain_focus "$focus_repo"
9041
+ _cockpit_hint_key
9042
+ continue ;;
9043
+ steer)
9044
+ printf '\033[H\033[2J'
9045
+ _cockpit_frame
9046
+ cockpit_steer_hint "$focus_repo"
9047
+ _cockpit_hint_key
9048
+ continue ;;
9049
+ esac
9050
+ fi
9051
+
9052
+ local cur; cur="$(_cockpit_mtime "$focus_repo")"
9053
+ local changed=0
9054
+ if [ -n "$cur" ]; then
9055
+ # Event-driven: redraw when a watched file advanced, or after
9056
+ # max_idle seconds with no change (keeps the freshness clock live).
9057
+ if [ "$cur" != "$last_mtime" ] || [ "$idle" -ge "$max_idle" ]; then
9058
+ changed=1; last_mtime="$cur"
9059
+ fi
9060
+ else
9061
+ # stat unavailable -> blind interval fallback.
9062
+ [ "$idle" -ge "$max_idle" ] && changed=1
9063
+ fi
9064
+ if [ "$changed" = "1" ]; then
9065
+ idle=0
9066
+ printf '\033[H\033[2J' # home + clear alt buffer, then redraw
9067
+ _cockpit_frame || break
9068
+ [ "$interactive" = "1" ] && _cockpit_hint
9069
+ fi
9070
+ done
9071
+ )
8912
9072
  return 0
8913
9073
  }
8914
9074
 
@@ -10175,7 +10335,7 @@ cmd_doctor() {
10175
10335
  echo " --json Output machine-readable JSON"
10176
10336
  echo ""
10177
10337
  echo "Checks: node, python3, jq, git, curl, bash version,"
10178
- echo " claude/codex CLIs, and disk space."
10338
+ echo " claude/codex CLIs, disk space, and cockpit render capability."
10179
10339
  return 0
10180
10340
  ;;
10181
10341
  *)
@@ -10627,6 +10787,54 @@ except Exception:
10627
10787
  fi
10628
10788
  echo ""
10629
10789
 
10790
+ # Cockpit capability (E5): report what `loki cockpit` will actually do in
10791
+ # this terminal. Informational only (does NOT touch pass/warn/fail counts,
10792
+ # like Runtime route above), so the bun-parity matrix stays reconcilable.
10793
+ # The protocol + resvg + path come from a bun probe that reuses the real
10794
+ # detection code (loki-ts) so this report can never drift from behavior;
10795
+ # with no bun we honestly report the text-only path.
10796
+ echo -e "${CYAN}Cockpit:${NC}"
10797
+ # Truecolor: purely from COLORTERM, same signal the renderer relies on.
10798
+ case "${COLORTERM:-}" in
10799
+ truecolor|24bit)
10800
+ echo -e " ${GREEN}PASS${NC} Truecolor: yes (COLORTERM=${COLORTERM})" ;;
10801
+ *)
10802
+ echo -e " ${DIM} -- ${NC} Truecolor: not signalled (COLORTERM unset) -- colors may be approximate" ;;
10803
+ esac
10804
+ local _ck_ts_dir="${_LOKI_SCRIPT_DIR}/../loki-ts"
10805
+ local _ck_entry="$_ck_ts_dir/dist/cockpit.js"
10806
+ [ -f "$_ck_entry" ] || _ck_entry="$_ck_ts_dir/src/cockpit/cli.ts"
10807
+ if command -v bun >/dev/null 2>&1 && [ -f "$_ck_entry" ]; then
10808
+ echo -e " ${GREEN}PASS${NC} Bun present -- cockpit renderer available"
10809
+ local _ck_probe _ck_proto _ck_resvg _ck_path
10810
+ _ck_probe="$(bun "$_ck_entry" --probe 2>/dev/null || true)"
10811
+ _ck_proto="$(printf '%s\n' "$_ck_probe" | sed -n 's/^protocol=//p' | head -1)"
10812
+ _ck_resvg="$(printf '%s\n' "$_ck_probe" | sed -n 's/^resvg=//p' | head -1)"
10813
+ _ck_path="$(printf '%s\n' "$_ck_probe" | sed -n 's/^path=//p' | head -1)"
10814
+ [ -n "$_ck_proto" ] || _ck_proto="none"
10815
+ [ -n "$_ck_resvg" ] || _ck_resvg="no"
10816
+ [ -n "$_ck_path" ] || _ck_path="text+dashboard"
10817
+ if [ "$_ck_proto" = "none" ]; then
10818
+ echo -e " ${DIM} -- ${NC} Inline-image protocol: none (this terminal has no iTerm2/Kitty graphics)"
10819
+ else
10820
+ echo -e " ${GREEN}PASS${NC} Inline-image protocol: ${_ck_proto}"
10821
+ fi
10822
+ if [ "$_ck_resvg" = "yes" ]; then
10823
+ echo -e " ${GREEN}PASS${NC} SVG rasterizer: ready (bundled wasm; renders the frame to an image)"
10824
+ else
10825
+ echo -e " ${DIM} -- ${NC} SVG rasterizer: unavailable (cockpit uses text+dashboard)"
10826
+ fi
10827
+ if [ "$_ck_path" = "image" ]; then
10828
+ echo -e " ${GREEN}PASS${NC} Render path: inline image ('loki cockpit' draws the frame in-terminal)"
10829
+ else
10830
+ echo -e " ${DIM} -- ${NC} Render path: text + browser dashboard ('loki cockpit' prints a summary; open 'loki dashboard')"
10831
+ fi
10832
+ else
10833
+ echo -e " ${YELLOW}WARN${NC} Bun not found -- 'loki cockpit' uses the text summary + browser dashboard (install bun for the inline-image frame)"
10834
+ echo -e " ${DIM} -- ${NC} Render path: text + browser dashboard"
10835
+ fi
10836
+ echo ""
10837
+
10630
10838
  # Summary
10631
10839
  echo -e "${BOLD}Summary:${NC} ${GREEN}$pass_count passed${NC}, ${RED}$fail_count failed${NC}, ${YELLOW}$warn_count warnings${NC}"
10632
10840
  echo ""
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "7.126.0"
10
+ __version__ = "7.128.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -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.126.0
5
+ **Version:** v7.128.0
6
6
 
7
7
  ---
8
8
 
@@ -396,7 +396,7 @@ provider works inside the container. Provide auth with your Anthropic API key:
396
396
  # Run Loki Mode in Docker (Claude provider, API-key auth)
397
397
  docker run --rm -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
398
398
  -v $(pwd):/workspace -w /workspace \
399
- asklokesh/loki-mode:7.126.0 start ./my-spec.md
399
+ asklokesh/loki-mode:7.128.0 start ./my-spec.md
400
400
  ```
401
401
 
402
402
  ##### docker compose + .env (no host install)
Binary file
@@ -1,16 +1,19 @@
1
1
  // @bun
2
- var _=import.meta.require;function Z(D){return String(D??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function h(D,L,Q){let N=Q/512;return`<g transform="translate(${D},${L}) scale(${N})">
2
+ var YJ=Object.defineProperty;var zJ=(J)=>J;function EJ(J,Q){this[J]=zJ.bind(null,Q)}var TJ=(J,Q)=>{for(var X in Q)YJ(J,X,{get:Q[X],enumerable:!0,configurable:!0,set:EJ.bind(Q,X)})};var WJ=(J,Q)=>()=>(J&&(Q=J(J=0)),Q);var T=import.meta.require;var DJ={};TJ(DJ,{initWasm:()=>wJ,Resvg:()=>lJ});function C(J){if(_===z.length)z.push(z.length+1);let Q=_;return _=z[Q],z[Q]=J,Q}function U(J){return z[J]}function kJ(J){if(J<132)return;z[J]=_,_=J}function E(J){let Q=U(J);return kJ(J),Q}function y(){if(j===null||j.byteLength===0)j=new Uint8Array(K.memory.buffer);return j}function c(J,Q,X){if(X===void 0){let q=b.encode(J),P=Q(q.length,1)>>>0;return y().subarray(P,P+q.length).set(q),h=q.length,P}let Z=J.length,G=Q(Z,1)>>>0,$=y(),D=0;for(;D<Z;D++){let q=J.charCodeAt(D);if(q>127)break;$[G+D]=q}if(D!==Z){if(D!==0)J=J.slice(D);G=X(G,Z,Z=D+J.length*3,1)>>>0;let q=y().subarray(G+D,G+Z),P=MJ(J,q);D+=P.written,G=X(G,Z,D,1)>>>0}return h=D,G}function g(J){return J===void 0||J===null}function A(){if(x===null||x.byteLength===0)x=new Int32Array(K.memory.buffer);return x}function w(J,Q){return J=J>>>0,GJ.decode(y().subarray(J,J+Q))}function OJ(J,Q){if(!(J instanceof Q))throw Error(`expected instance of ${Q.name}`);return J.ptr}function jJ(J,Q){try{return J.apply(this,Q)}catch(X){K.__wbindgen_exn_store(C(X))}}async function fJ(J,Q){if(typeof Response==="function"&&J instanceof Response){if(typeof WebAssembly.instantiateStreaming==="function")try{return await WebAssembly.instantiateStreaming(J,Q)}catch(Z){if(J.headers.get("Content-Type")!="application/wasm")console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n",Z);else throw Z}let X=await J.arrayBuffer();return await WebAssembly.instantiate(X,Q)}else{let X=await WebAssembly.instantiate(J,Q);if(X instanceof WebAssembly.Instance)return{instance:X,module:J};else return X}}function uJ(){let J={};return J.wbg={},J.wbg.__wbg_new_28c511d9baebfa89=function(Q,X){let Z=Error(w(Q,X));return C(Z)},J.wbg.__wbindgen_memory=function(){let Q=K.memory;return C(Q)},J.wbg.__wbg_buffer_12d079cc21e14bdb=function(Q){let X=U(Q).buffer;return C(X)},J.wbg.__wbg_newwithbyteoffsetandlength_aa4a17c33a06e5cb=function(Q,X,Z){let G=new Uint8Array(U(Q),X>>>0,Z>>>0);return C(G)},J.wbg.__wbindgen_object_drop_ref=function(Q){E(Q)},J.wbg.__wbg_new_63b92bc8671ed464=function(Q){let X=new Uint8Array(U(Q));return C(X)},J.wbg.__wbg_values_839f3396d5aac002=function(Q){let X=U(Q).values();return C(X)},J.wbg.__wbg_next_196c84450b364254=function(){return jJ(function(Q){let X=U(Q).next();return C(X)},arguments)},J.wbg.__wbg_done_298b57d23c0fc80c=function(Q){return U(Q).done},J.wbg.__wbg_value_d93c65011f51a456=function(Q){let X=U(Q).value;return C(X)},J.wbg.__wbg_instanceof_Uint8Array_2b3bbecd033d19f6=function(Q){let X;try{X=U(Q)instanceof Uint8Array}catch(G){X=!1}return X},J.wbg.__wbindgen_string_get=function(Q,X){let Z=U(X),G=typeof Z==="string"?Z:void 0;var $=g(G)?0:c(G,K.__wbindgen_malloc,K.__wbindgen_realloc),D=h;A()[Q/4+1]=D,A()[Q/4+0]=$},J.wbg.__wbg_new_16b304a2cfa7ff4a=function(){return C([])},J.wbg.__wbindgen_string_new=function(Q,X){let Z=w(Q,X);return C(Z)},J.wbg.__wbg_push_a5b05aedc7234f9f=function(Q,X){return U(Q).push(U(X))},J.wbg.__wbg_length_c20a40f15020d68a=function(Q){return U(Q).length},J.wbg.__wbg_set_a47bac70306a19a7=function(Q,X,Z){U(Q).set(U(X),Z>>>0)},J.wbg.__wbindgen_throw=function(Q,X){throw Error(w(Q,X))},J}function vJ(J,Q){}function yJ(J,Q){return K=J.exports,KJ.__wbindgen_wasm_module=Q,x=null,j=null,K}async function KJ(J){if(K!==void 0)return K;if(typeof J>"u")J=new URL("index_bg.wasm",void 0);let Q=uJ();if(typeof J==="string"||typeof Request==="function"&&J instanceof Request||typeof URL==="function"&&J instanceof URL)J=fetch(J);vJ(Q);let{instance:X,module:Z}=await fJ(await J,Q);return yJ(X,Z)}function mJ(J){return Object.prototype.hasOwnProperty.call(J,"fontBuffers")}var K,z,_,h=0,j=null,b,MJ,x=null,GJ,ZJ,p=class J{static __wrap(Q){Q=Q>>>0;let X=Object.create(J.prototype);return X.__wbg_ptr=Q,ZJ.register(X,X.__wbg_ptr,X),X}__destroy_into_raw(){let Q=this.__wbg_ptr;return this.__wbg_ptr=0,ZJ.unregister(this),Q}free(){let Q=this.__destroy_into_raw();K.__wbg_bbox_free(Q)}get x(){return K.__wbg_get_bbox_x(this.__wbg_ptr)}set x(Q){K.__wbg_set_bbox_x(this.__wbg_ptr,Q)}get y(){return K.__wbg_get_bbox_y(this.__wbg_ptr)}set y(Q){K.__wbg_set_bbox_y(this.__wbg_ptr,Q)}get width(){return K.__wbg_get_bbox_width(this.__wbg_ptr)}set width(Q){K.__wbg_set_bbox_width(this.__wbg_ptr,Q)}get height(){return K.__wbg_get_bbox_height(this.__wbg_ptr)}set height(Q){K.__wbg_set_bbox_height(this.__wbg_ptr,Q)}},$J,xJ=class J{static __wrap(Q){Q=Q>>>0;let X=Object.create(J.prototype);return X.__wbg_ptr=Q,$J.register(X,X.__wbg_ptr,X),X}__destroy_into_raw(){let Q=this.__wbg_ptr;return this.__wbg_ptr=0,$J.unregister(this),Q}free(){let Q=this.__destroy_into_raw();K.__wbg_renderedimage_free(Q)}get width(){return K.renderedimage_width(this.__wbg_ptr)>>>0}get height(){return K.renderedimage_height(this.__wbg_ptr)>>>0}asPng(){try{let G=K.__wbindgen_add_to_stack_pointer(-16);K.renderedimage_asPng(G,this.__wbg_ptr);var Q=A()[G/4+0],X=A()[G/4+1],Z=A()[G/4+2];if(Z)throw E(X);return E(Q)}finally{K.__wbindgen_add_to_stack_pointer(16)}}get pixels(){let Q=K.renderedimage_pixels(this.__wbg_ptr);return E(Q)}},_J,hJ=class{__destroy_into_raw(){let J=this.__wbg_ptr;return this.__wbg_ptr=0,_J.unregister(this),J}free(){let J=this.__destroy_into_raw();K.__wbg_resvg_free(J)}constructor(J,Q,X){try{let P=K.__wbindgen_add_to_stack_pointer(-16);var Z=g(Q)?0:c(Q,K.__wbindgen_malloc,K.__wbindgen_realloc),G=h;K.resvg_new(P,C(J),Z,G,g(X)?0:C(X));var $=A()[P/4+0],D=A()[P/4+1],q=A()[P/4+2];if(q)throw E(D);return this.__wbg_ptr=$>>>0,this}finally{K.__wbindgen_add_to_stack_pointer(16)}}get width(){return K.resvg_width(this.__wbg_ptr)}get height(){return K.resvg_height(this.__wbg_ptr)}render(){try{let Z=K.__wbindgen_add_to_stack_pointer(-16);K.resvg_render(Z,this.__wbg_ptr);var J=A()[Z/4+0],Q=A()[Z/4+1],X=A()[Z/4+2];if(X)throw E(Q);return xJ.__wrap(J)}finally{K.__wbindgen_add_to_stack_pointer(16)}}toString(){let J,Q;try{let G=K.__wbindgen_add_to_stack_pointer(-16);K.resvg_toString(G,this.__wbg_ptr);var X=A()[G/4+0],Z=A()[G/4+1];return J=X,Q=Z,w(X,Z)}finally{K.__wbindgen_add_to_stack_pointer(16),K.__wbindgen_free(J,Q,1)}}innerBBox(){let J=K.resvg_innerBBox(this.__wbg_ptr);return J===0?void 0:p.__wrap(J)}getBBox(){let J=K.resvg_getBBox(this.__wbg_ptr);return J===0?void 0:p.__wrap(J)}cropByBBox(J){OJ(J,p),K.resvg_cropByBBox(this.__wbg_ptr,J.__wbg_ptr)}imagesToResolve(){try{let Z=K.__wbindgen_add_to_stack_pointer(-16);K.resvg_imagesToResolve(Z,this.__wbg_ptr);var J=A()[Z/4+0],Q=A()[Z/4+1],X=A()[Z/4+2];if(X)throw E(Q);return E(J)}finally{K.__wbindgen_add_to_stack_pointer(16)}}resolveImage(J,Q){try{let G=K.__wbindgen_add_to_stack_pointer(-16),$=c(J,K.__wbindgen_malloc,K.__wbindgen_realloc),D=h;K.resvg_resolveImage(G,this.__wbg_ptr,$,D,C(Q));var X=A()[G/4+0],Z=A()[G/4+1];if(Z)throw E(X)}finally{K.__wbindgen_add_to_stack_pointer(16)}}},bJ,i=!1,wJ=async(J)=>{if(i)throw Error("Already initialized. The `initWasm()` function can be used only once.");await bJ(await J),i=!0},lJ;var qJ=WJ(()=>{z=Array(128).fill(void 0);z.push(void 0,null,!0,!1);_=z.length;b=typeof TextEncoder<"u"?new TextEncoder("utf-8"):{encode:()=>{throw Error("TextEncoder not available")}},MJ=typeof b.encodeInto==="function"?function(J,Q){return b.encodeInto(J,Q)}:function(J,Q){let X=b.encode(J);return Q.set(X),{read:J.length,written:X.length}};GJ=typeof TextDecoder<"u"?new TextDecoder("utf-8",{ignoreBOM:!0,fatal:!0}):{decode:()=>{throw Error("TextDecoder not available")}};if(typeof TextDecoder<"u")GJ.decode();ZJ=typeof FinalizationRegistry>"u"?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry((J)=>K.__wbg_bbox_free(J>>>0)),$J=typeof FinalizationRegistry>"u"?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry((J)=>K.__wbg_renderedimage_free(J>>>0)),_J=typeof FinalizationRegistry>"u"?{register:()=>{},unregister:()=>{}}:new FinalizationRegistry((J)=>K.__wbg_resvg_free(J>>>0));bJ=KJ,lJ=class extends hJ{constructor(J,Q){if(!i)throw Error("Wasm has not been initialized. Call `initWasm()` function.");let X=Q?.font;if(!!X&&mJ(X)){let Z={...Q,font:{...X,fontBuffers:void 0}};super(J,JSON.stringify(Z),X.fontBuffers)}else super(J,JSON.stringify(Q))}}});function N(J){return String(J??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}function FJ(J,Q,X){let Z=X/512;return`<g transform="translate(${J},${Q}) scale(${Z})">
3
3
  <rect x="0" y="0" width="512" height="512" rx="118" fill="#553de9"/>
4
4
  <path d="M152 405 L242 120" stroke="#FFFEFB" stroke-width="28" stroke-linecap="round" fill="none"/>
5
5
  <path d="M360 405 L270 120" stroke="#FFFEFB" stroke-width="28" stroke-linecap="round" fill="none"/>
6
6
  <path d="M242 120 Q256 86 270 120" stroke="#FFFEFB" stroke-width="28" stroke-linecap="round" fill="none"/>
7
7
  <circle cx="256" cy="295" r="20" fill="#1FC5A8"/>
8
- </g>`}function b(D){switch(D){case"verified":return"#1f8a52";case"failed":return"#b23a3a";case"working":case"pending":return"#9a6a12";default:return"#6b6675"}}function u(D){switch(D){case"pass":return"#1f8a52";case"fail":return"#b23a3a";case"pending":return"#9a6a12";default:return"#6b6675"}}function p(D){switch(D){case"approve":return"#1f8a52";case"reject":return"#b23a3a";case"concern":return"#9a6a12";default:return"#6b6675"}}function F(D,L,Q,N,U="#201515"){return`<text x="${D}" y="${L}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="12" fill="#6b6675" letter-spacing="0.5">${Z(Q.toUpperCase())}</text>
9
- <text x="${D}" y="${L+24}" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="20" font-weight="600" fill="${U}">${Z(N)}</text>`}function S(D){let U=D.budgetLimitUsd&&D.budgetLimitUsd>0?`$${D.budgetUsd.toFixed(2)} / $${D.budgetLimitUsd.toFixed(2)}`:`$${D.budgetUsd.toFixed(2)}`,J="";J+=h(32,32,44),J+=`<text x="92" y="54" font-family="'Fraunces','Georgia',serif" font-size="24" font-weight="600" fill="#201515">Autonomi</text>`,J+=`<text x="92" y="74" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="13" fill="#6b6675">Loki Cockpit</text>`;let X=D.freshness?`updated ${D.freshness}`:"live";J+=`<text x="868" y="54" text-anchor="end" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="12" fill="#6b6675">${Z(X)}</text>`,J+=`<text x="868" y="74" text-anchor="end" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="13" fill="${b(D.verdict)}" font-weight="600">${Z(D.verdict.toUpperCase())}</text>`;let $=96,M=168;J+=`<rect x="32" y="${$}" width="836" height="${M}" rx="16" fill="#ffffff" stroke="#e2e0e8"/>`,J+=`<text x="56" y="${$+34}" font-family="'Fraunces','Georgia',serif" font-size="20" font-weight="600" fill="#201515">${Z(D.run)}</text>`,J+=`<text x="56" y="${$+54}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="13" fill="#6b6675">phase ${Z(D.phase)}</text>`;let K=$+96,T=(V)=>56+V*168;J+=F(T(0),K,"iteration",String(D.iteration)),J+=F(T(1),K,"tier",D.tier),J+=F(T(2),K,"provider",D.provider),J+=F(T(3),K,"budget",U),J+=F(T(4),K,"phase",D.phase,"#553de9");let C=$+M+40;J+=`<text x="32" y="${C}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" fill="#6b6675" letter-spacing="0.5">QUALITY GATES</text>`;let W=32,P=C+16,m=D.gates.length?D.gates:[{name:"no gates yet",status:"pending"}];for(let V of m){let j=Z(V.name),E=Math.max(74,14+j.length*7),A=u(V.status);if(J+=`<rect x="${W}" y="${P}" width="${E}" height="30" rx="8" fill="#ffffff" stroke="${A}"/>`,J+=`<circle cx="${W+14}" cy="${P+15}" r="4" fill="${A}"/>`,J+=`<text x="${W+24}" y="${P+19}" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="12" fill="#201515">${j}</text>`,W+=E+10,W>778)break}let z=P+62;J+=`<text x="32" y="${z}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" fill="#6b6675" letter-spacing="0.5">COUNCIL</text>`;let q=32,I=z+16,x=D.council.length?D.council:[{reviewer:"pending",vote:"pending"}];for(let V of x){let j=`${Z(V.reviewer)}: ${Z(V.vote)}`,E=Math.max(90,14+j.length*7),A=p(V.vote);if(J+=`<rect x="${q}" y="${I}" width="${E}" height="30" rx="8" fill="${A}" fill-opacity="0.12" stroke="${A}"/>`,J+=`<text x="${q+12}" y="${I+19}" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="12" fill="#201515">${j}</text>`,q+=E+10,q>768)break}let B=I+62;J+=`<text x="32" y="${B}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" fill="#6b6675" letter-spacing="0.5">FLEET (${D.fleet.length})</text>`;let G=B+20,O=D.fleet.slice(0,4);for(let V of O){let j=V.running?"#1f8a52":"#6b6675";J+=`<circle cx="38" cy="${G-4}" r="5" fill="${j}"/>`,J+=`<text x="52" y="${G}" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="13" fill="#201515">${Z(V.name)}</text>`;let E=`iter ${V.iteration??0} ${Z(V.phase||V.status||"")}`;J+=`<text x="868" y="${G}" text-anchor="end" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="12" fill="#6b6675">${E}</text>`,G+=26}if(D.fleet.length>O.length)J+=`<text x="52" y="${G}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="12" fill="#6b6675">+ ${D.fleet.length-O.length} more</text>`;return`<svg xmlns="http://www.w3.org/2000/svg" width="900" height="560" viewBox="0 0 900 560">
10
- <rect x="0" y="0" width="900" height="560" fill="#f1f2f6"/>
11
- ${J}
12
- </svg>`}var H;function y(){if(H!==void 0)return H;try{let D=globalThis.require??_;D.resolve("@resvg/resvg-js"),H=D("@resvg/resvg-js")}catch{H=null}return H}function R(D,L=900){let Q=y();if(!Q||typeof Q.Resvg!=="function")return{png:null,available:!1,reason:"raster unavailable (@resvg/resvg-js not installed)"};try{let U=new Q.Resvg(D,{fitTo:{mode:"width",value:L}}).render().asPng();return{png:Uint8Array.from(U),available:!0}}catch(N){return{png:null,available:!1,reason:`raster failed: ${N.message}`}}}function Y(D){return Buffer.from(D).toString("base64")}function v(D){let L=Y(D);return`\x1B]1337;File=${`inline=1;size=${D.length};width=auto;height=auto;preserveAspectRatio=1`}:${L}\x07`}function i(D,L=4096){let Q=Y(D);if(Q.length===0)return"\x1B_Ga=T,f=100,m=0;\x1B\\";let N=[];for(let J=0;J<Q.length;J+=L)N.push(Q.slice(J,J+L));let U="";for(let J=0;J<N.length;J++){let $=J===N.length-1?0:1,M=J===0?`a=T,f=100,m=${$}`:`m=${$}`;U+=`\x1B_G${M};${N[J]}\x1B\\`}return U}function k(D,L){return D==="kitty"?i(L):v(L)}var g=new Set(["iterm.app","wezterm","ghostty"]);function w(D=process.env){let L=(D.LOKI_COCKPIT_PROTOCOL||"").trim().toLowerCase();if(L==="iterm2"||L==="kitty"||L==="none")return L;let Q=(D.KITTY_WINDOW_ID||"").trim(),N=(D.TERM||"").trim().toLowerCase();if(Q.length>0||N==="xterm-kitty")return"kitty";let U=(D.TERM_PROGRAM||"").trim().toLowerCase();if(g.has(U))return"iterm2";return"none"}function f(D,L={}){let Q=S(D);if(L.forceText)return{kind:"fallback",protocol:"none",reason:"--no-image (text/browser fallback)",svg:Q};let N;if(L.protocol&&L.protocol!=="auto")N=L.protocol;else N=w(L.env);if(N==="none")return{kind:"fallback",protocol:N,reason:"no inline-image terminal detected",svg:Q};let U=R(Q);if(!U.available||!U.png)return{kind:"fallback",protocol:N,reason:U.reason||"rasterization unavailable",svg:Q};let J=k(N,U.png);return{kind:"image",protocol:N,data:J,svg:Q}}async function l(){let D=[];for await(let L of process.stdin)D.push(L);return Buffer.concat(D).toString("utf-8")}function n(D){let L="auto",Q=!1,N;for(let U=0;U<D.length;U++){let J=D[U];if(J==="--protocol"){let X=(D[++U]||"auto").toLowerCase();L=X==="iterm2"||X==="kitty"||X==="none"?X:"auto"}else if(J==="--no-image")Q=!0;else if(J==="--svg-out")N=D[++U]}return{protocol:L,noImage:Q,svgOut:N}}async function d(D=process.argv.slice(2)){let{protocol:L,noImage:Q,svgOut:N}=n(D),U;try{let X=await l();U=JSON.parse(X)}catch(X){return process.stderr.write(`FALLBACK could not parse cockpit state: ${X.message}
13
- `),3}let J=f(U,{protocol:L,forceText:Q});if(N)try{let{writeFileSync:X}=await import("fs");X(N,J.svg)}catch{}if(J.kind==="image"&&J.data)return process.stdout.write(J.data),0;return process.stderr.write(`FALLBACK ${J.reason||"image unavailable"}
14
- `),3}if(import.meta.main)d().then((D)=>process.exit(D));export{d as main};
8
+ </g>`}function BJ(J){switch(J){case"verified":return"#1f8a52";case"failed":return"#b23a3a";case"working":case"pending":return"#9a6a12";default:return"#6b6675"}}function HJ(J){switch(J){case"pass":return"#1f8a52";case"fail":return"#b23a3a";case"pending":return"#9a6a12";default:return"#6b6675"}}function SJ(J){switch(J){case"approve":return"#1f8a52";case"reject":return"#b23a3a";case"concern":return"#9a6a12";default:return"#6b6675"}}function v(J,Q,X,Z,G="#201515"){return`<text x="${J}" y="${Q}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="12" fill="#6b6675" letter-spacing="0.5">${N(X.toUpperCase())}</text>
9
+ <text x="${J}" y="${Q+24}" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="20" font-weight="600" fill="${G}">${N(Z)}</text>`}var RJ=["Reason","Act","Reflect","Verify"];function IJ(J){let Q=(J||"").toLowerCase();if(/verif|complet|done|ship|deploy/.test(Q))return 3;if(/reflect|review|council|critique|assess/.test(Q))return 2;if(/reason|plan|design|architect|spec|analy/.test(Q))return 0;return 1}function XJ(J){let Z={run:J?.run||"cockpit",iteration:typeof J?.iteration==="number"?J.iteration:0,phase:J?.phase||"idle",tier:J?.tier||"",provider:J?.provider||"",verdict:J?.verdict||"unknown",budgetUsd:typeof J?.budgetUsd==="number"?J.budgetUsd:0,budgetLimitUsd:J?.budgetLimitUsd,freshness:J?.freshness,gates:Array.isArray(J?.gates)?J.gates:[],council:Array.isArray(J?.council)?J.council:[],fleet:Array.isArray(J?.fleet)?J.fleet:[]},G=Z.budgetLimitUsd&&Z.budgetLimitUsd>0?`$${Z.budgetUsd.toFixed(2)} / $${Z.budgetLimitUsd.toFixed(2)}`:`$${Z.budgetUsd.toFixed(2)}`,$="";$+=FJ(32,32,44),$+=`<text x="92" y="54" font-family="'Fraunces','Georgia',serif" font-size="24" font-weight="600" fill="#201515">Autonomi</text>`,$+=`<text x="92" y="74" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="13" fill="#6b6675">Loki Cockpit</text>`;let D=Z.freshness?`updated ${Z.freshness}`:"live";$+=`<text x="868" y="54" text-anchor="end" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="12" fill="#6b6675">${N(D)}</text>`,$+=`<text x="868" y="74" text-anchor="end" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="13" fill="${BJ(Z.verdict)}" font-weight="600">${N(Z.verdict.toUpperCase())}</text>`;let q=96,P=168;$+=`<rect x="32" y="${q}" width="836" height="${P}" rx="16" fill="#ffffff" stroke="#e2e0e8"/>`,$+=`<text x="56" y="${q+34}" font-family="'Fraunces','Georgia',serif" font-size="20" font-weight="600" fill="#201515">${N(Z.run)}</text>`,$+=`<text x="56" y="${q+54}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="13" fill="#6b6675">phase ${N(Z.phase)}</text>`;let S=q+96,W=(V)=>56+V*168;$+=v(W(0),S,"iteration",String(Z.iteration)),$+=v(W(1),S,"tier",Z.tier),$+=v(W(2),S,"provider",Z.provider),$+=v(W(3),S,"budget",G);let n=q+P+40;$+=`<text x="32" y="${n}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" fill="#6b6675" letter-spacing="0.5">RARV LOOP</text>`;let s=IJ(Z.phase),R=n+14,o=200;RJ.forEach((V,Y)=>{let L=32+Y*(o+12),O=Y<s,B=Y===s;$+=`<rect x="${L}" y="${R}" width="${o}" height="40" rx="10" fill="${B?"#553de9":"#ffffff"}" fill-opacity="${B?"0.10":"1"}" stroke="${B?"#553de9":O?"#1f8a52":"#6b6675"}" stroke-width="${B?2:1}"/>`;let QJ=O?"check":B?"dot":"wait";if(QJ==="check")$+=`<path d="M${L+16} ${R+20} l6 6 l10 -12" stroke="#1f8a52" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round"/>`;else if(QJ==="dot")$+=`<circle cx="${L+22}" cy="${R+20}" r="5" fill="#553de9"/>`;else $+=`<circle cx="${L+22}" cy="${R+20}" r="5" fill="none" stroke="#6b6675" stroke-width="1.5"/>`;$+=`<text x="${L+40}" y="${R+25}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="14" font-weight="${B?600:500}" fill="${B?"#553de9":O?"#201515":"#6b6675"}">${V}</text>`});let a=R+40+40;$+=`<text x="32" y="${a}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" fill="#6b6675" letter-spacing="0.5">QUALITY GATES</text>`;let r=Z.gates.length?Z.gates:[{name:"no gates yet",status:"pending"}],I=30,k=a+12;$+=`<rect x="32" y="${k}" width="836" height="${r.slice(0,8).length*I+8}" rx="12" fill="#ffffff" stroke="#e2e0e8"/>`,k+=8;for(let V of r.slice(0,8)){let Y=HJ(V.status),L=k+I/2;if($+=`<circle cx="50" cy="${L}" r="4" fill="${Y}"/>`,$+=`<text x="66" y="${L+4}" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="12" fill="#201515">${N(V.name)}</text>`,V.runner)$+=`<text x="242" y="${L+4}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="11" fill="#6b6675">${N(V.runner)}</text>`;if(V.evidence){let O=V.evidence.length>52?`${V.evidence.slice(0,49)}...`:V.evidence;$+=`<text x="332" y="${L+4}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="11" fill="#6b6675">${N(O)}</text>`}$+=`<text x="852" y="${L+4}" text-anchor="end" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="11" font-weight="700" fill="${Y}">${N(V.status.toUpperCase())}</text>`,k+=I}let t=k+40;$+=`<text x="32" y="${t}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" fill="#6b6675" letter-spacing="0.5">COMPLETION COUNCIL</text>`;let e=Z.council.length?Z.council:[{reviewer:"pending",vote:"pending"}],M=t+12;$+=`<rect x="32" y="${M}" width="836" height="${e.slice(0,6).length*I+8}" rx="12" fill="#ffffff" stroke="#e2e0e8"/>`,M+=8;for(let V of e.slice(0,6)){let Y=SJ(V.vote),L=M+I/2;if($+=`<circle cx="50" cy="${L}" r="4" fill="${Y}"/>`,$+=`<text x="66" y="${L+4}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="12.5" font-weight="500" fill="#201515">${N(V.reviewer)}</text>`,V.tier)$+=`<text x="292" y="${L+4}" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="11" fill="#6b6675">${N(V.tier)}</text>`;$+=`<text x="852" y="${L+4}" text-anchor="end" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="11" font-weight="700" fill="${Y}">${N(V.vote.toUpperCase())}</text>`,M+=I}let JJ=M+46;$+=`<text x="32" y="${JJ}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="13" font-weight="600" fill="#6b6675" letter-spacing="0.5">FLEET (${Z.fleet.length})</text>`;let F=JJ+20,m=Z.fleet.slice(0,4);for(let V of m){let Y=V.running?"#1f8a52":"#6b6675";$+=`<circle cx="38" cy="${F-4}" r="5" fill="${Y}"/>`,$+=`<text x="52" y="${F}" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="13" fill="#201515">${N(V.name)}</text>`;let L=`iter ${V.iteration??0} ${N(V.phase||V.status||"")}`;$+=`<text x="868" y="${F}" text-anchor="end" font-family="'JetBrains Mono','SF Mono',ui-monospace,monospace" font-size="12" fill="#6b6675">${L}</text>`,F+=26}if(Z.fleet.length>m.length)$+=`<text x="52" y="${F}" font-family="'Inter','Helvetica Neue',Arial,sans-serif" font-size="12" fill="#6b6675">+ ${Z.fleet.length-m.length} more</text>`,F+=26;let d=Math.round(F+32);return`<svg xmlns="http://www.w3.org/2000/svg" width="900" height="${d}" viewBox="0 0 900 ${d}">
10
+ <rect x="0" y="0" width="900" height="${d}" fill="#f1f2f6"/>
11
+ ${$}
12
+ </svg>`}var f,u;function VJ(){if(f!==void 0)return f;try{let J=globalThis.require??T;J.resolve("@resvg/resvg-js"),f=J("@resvg/resvg-js")}catch{f=null}return f}async function PJ(){if(u!==void 0)return u;try{let J=await Promise.resolve().then(() => (qJ(),DJ)),Q=globalThis.require??T,{readFileSync:X,existsSync:Z}=await import("fs"),{resolve:G,dirname:$}=await import("path"),{fileURLToPath:D}=await import("url"),q=null;try{let P=$(D(import.meta.url));for(let S of["../data/resvg.wasm","../../data/resvg.wasm"]){let W=G(P,S);if(Z(W)){q=X(W);break}}}catch{}if(!q){let P=Q.resolve("@resvg/resvg-wasm/index_bg.wasm");q=X(P)}await J.initWasm(q),u={Resvg:J.Resvg}}catch{u=null}return u}async function LJ(){if(VJ()?.Resvg)return!0;return(await PJ())?.Resvg!=null}var H;async function dJ(){if(H!==void 0)return H;try{let{readFileSync:J,existsSync:Q}=await import("fs"),X=["/System/Library/Fonts/Supplemental/Arial.ttf","/System/Library/Fonts/Helvetica.ttc","/Library/Fonts/Arial.ttf","/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf","/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf","/usr/share/fonts/dejavu/DejaVuSans.ttf","/usr/share/fonts/TTF/DejaVuSans.ttf","C:\\Windows\\Fonts\\arial.ttf","C:\\Windows\\Fonts\\segoeui.ttf"];for(let Z of X)if(Q(Z))return H=new Uint8Array(J(Z)),H;H=null}catch{H=null}return H}async function AJ(J,Q=900){let X=VJ();if(X?.Resvg)try{let G=new X.Resvg(J,{fitTo:{mode:"width",value:Q}});return{png:Uint8Array.from(G.render().asPng()),available:!0}}catch{}let Z=await PJ();if(Z?.Resvg)try{let G=await dJ(),$={fitTo:{mode:"width",value:Q}};if(G)$.font={fontBuffers:[G],defaultFontFamily:"Arial",loadSystemFonts:!1};else $.font={loadSystemFonts:!0};let D=new Z.Resvg(J,$);return{png:Uint8Array.from(D.render().asPng()),available:!0}}catch(G){return{png:null,available:!1,reason:`raster failed: ${G.message}`}}return{png:null,available:!1,reason:"rasterizer unavailable (no resvg native or wasm)"}}function NJ(J){return Buffer.from(J).toString("base64")}function pJ(J){let Q=NJ(J);return`\x1B]1337;File=${`inline=1;size=${J.length};width=auto;height=auto;preserveAspectRatio=1`}:${Q}\x07`}function cJ(J,Q=4096){let X=NJ(J);if(X.length===0)return"\x1B_Ga=T,f=100,m=0;\x1B\\";let Z=[];for(let $=0;$<X.length;$+=Q)Z.push(X.slice($,$+Q));let G="";for(let $=0;$<Z.length;$++){let q=$===Z.length-1?0:1,P=$===0?`a=T,f=100,m=${q}`:`m=${q}`;G+=`\x1B_G${P};${Z[$]}\x1B\\`}return G}function UJ(J,Q){return J==="kitty"?cJ(Q):pJ(Q)}var gJ=new Set(["iterm.app","wezterm","ghostty"]);function l(J=process.env){let Q=(J.LOKI_COCKPIT_PROTOCOL||"").trim().toLowerCase();if(Q==="iterm2"||Q==="kitty"||Q==="none")return Q;let X=(J.KITTY_WINDOW_ID||"").trim(),Z=(J.TERM||"").trim().toLowerCase();if(X.length>0||Z==="xterm-kitty")return"kitty";let G=(J.TERM_PROGRAM||"").trim().toLowerCase();if(gJ.has(G))return"iterm2";return"none"}async function CJ(J,Q={}){let X=XJ(J);if(Q.forceText)return{kind:"fallback",protocol:"none",reason:"--no-image (text/browser fallback)",svg:X};let Z;if(Q.protocol&&Q.protocol!=="auto")Z=Q.protocol;else Z=l(Q.env);if(Z==="none")return{kind:"fallback",protocol:Z,reason:"no inline-image terminal detected",svg:X};let G=await AJ(X);if(!G.available||!G.png)return{kind:"fallback",protocol:Z,reason:G.reason||"rasterization unavailable",svg:X};let $=UJ(Z,G.png);return{kind:"image",protocol:Z,data:$,svg:X}}async function iJ(){let J=[];for await(let Q of process.stdin)J.push(Q);return Buffer.concat(J).toString("utf-8")}function nJ(J){let Q="auto",X=!1,Z;for(let G=0;G<J.length;G++){let $=J[G];if($==="--protocol"){let D=(J[++G]||"auto").toLowerCase();Q=D==="iterm2"||D==="kitty"||D==="none"?D:"auto"}else if($==="--no-image")X=!0;else if($==="--svg-out")Z=J[++G]}return{protocol:Q,noImage:X,svgOut:Z}}async function sJ(){let J=l(),Q=await LJ(),X=J!=="none"&&Q?"image":"text+dashboard";return process.stdout.write(`protocol=${J}
13
+ `),process.stdout.write(`resvg=${Q?"yes":"no"}
14
+ `),process.stdout.write(`path=${X}
15
+ `),0}async function oJ(J=process.argv.slice(2)){if(J.includes("--probe"))return await sJ();let{protocol:Q,noImage:X,svgOut:Z}=nJ(J),G;try{let D=await iJ();G=JSON.parse(D)}catch(D){return process.stderr.write(`FALLBACK could not parse cockpit state: ${D.message}
16
+ `),3}let $=await CJ(G,{protocol:Q,forceText:X});if(Z)try{let{writeFileSync:D}=await import("fs");D(Z,$.svg)}catch{}if($.kind==="image"&&$.data)return process.stdout.write($.data),0;return process.stderr.write(`FALLBACK ${$.reason||"image unavailable"}
17
+ `),3}if(import.meta.main)oJ().then((J)=>process.exit(J));export{oJ as main};
15
18
 
16
- //# debugId=3D037F5476B14F7264756E2164756E21
19
+ //# debugId=6198ED4B6B100AF964756E2164756E21
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var q8=Object.defineProperty;var J8=($)=>$;function V8($,Q){this[$]=J8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)q8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:V8.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var m1={};b(m1,{lokiDir:()=>j,homeLokiDir:()=>T$,findRepoRootForVersion:()=>$1,REPO_ROOT:()=>h});import{resolve as t,dirname as e$}from"path";import{fileURLToPath as W8}from"url";import{existsSync as N$}from"fs";import{homedir as H8}from"os";function G8(){let $=v1;for(let Q=0;Q<6;Q++){if(N$(t($,"VERSION"))&&N$(t($,"autonomy/run.sh")))return $;let Z=e$($);if(Z===$)break;$=Z}return t(v1,"..","..","..")}function $1($){let Q=$;for(let Z=0;Z<6;Z++){if(N$(t(Q,"VERSION"))&&N$(t(Q,"autonomy/run.sh")))return Q;let z=e$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function j(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(H8(),".loki")}var v1,h;var C=P(()=>{v1=e$(W8(import.meta.url));h=G8()});import{readFileSync as U8}from"fs";import{resolve as Y8,dirname as B8}from"path";import{fileURLToPath as M8}from"url";function S$(){if(Q$!==null)return Q$;let $="7.126.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=B8(M8(import.meta.url)),Z=$1(Q);Q$=U8(Y8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var Q1=P(()=>{C()});var p1={};b(p1,{runOrThrow:()=>S8,run:()=>R,readStreamCapped:()=>c1,commandVersion:()=>C8,commandExists:()=>u,ShellError:()=>Z1,MAX_STDOUT_BYTES:()=>u1});async function c1($,Q=u1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function R($,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([c1(Z.stdout),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 S8($,Q={}){let Z=await R($,Q);if(Z.exitCode!==0)throw new Z1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function u($){let Q=D8($),Z=await R(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function D8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function C8($,Q="--version"){if(!await u($))return null;let z=await R([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var u1=16777216,Z1;var o=P(()=>{Z1=class Z1 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 r($){return b8?"":$}var b8,M,S,_,tZ,L,k,y,J;var p=P(()=>{b8=(process.env.NO_COLOR??"").length>0;M=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),tZ=r("\x1B[0;34m"),L=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as l8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(l8($))return A$=$,$;let Q=await u("python3.12");if(Q)return A$=Q,Q;let Z=await u("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return R([Z,"-c",$],Q)}var A$;var V$=P(()=>{o()});var V0={};b(V0,{runStatus:()=>U3});import{existsSync as v,readFileSync as H$,readdirSync as $0,statSync as Q0}from"fs";import{resolve as D,basename as z3}from"path";import{homedir as X3}from"os";function Z0($){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 z0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*C$/Q);if(X>C$)X=C$;let q=C$-X,K=S;if(z>=80)K=M;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=Z0($),H=Z0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${H})`}async function q3(){if(await u("jq"))return!0;return process.stdout.write(`${M}Error: jq is required but not installed.${J}
2
+ var q8=Object.defineProperty;var J8=($)=>$;function V8($,Q){this[$]=J8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)q8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:V8.bind(Q,Z)})};var P=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var m1={};b(m1,{lokiDir:()=>j,homeLokiDir:()=>T$,findRepoRootForVersion:()=>$1,REPO_ROOT:()=>h});import{resolve as t,dirname as e$}from"path";import{fileURLToPath as W8}from"url";import{existsSync as N$}from"fs";import{homedir as H8}from"os";function G8(){let $=v1;for(let Q=0;Q<6;Q++){if(N$(t($,"VERSION"))&&N$(t($,"autonomy/run.sh")))return $;let Z=e$($);if(Z===$)break;$=Z}return t(v1,"..","..","..")}function $1($){let Q=$;for(let Z=0;Z<6;Z++){if(N$(t(Q,"VERSION"))&&N$(t(Q,"autonomy/run.sh")))return Q;let z=e$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function j(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(H8(),".loki")}var v1,h;var C=P(()=>{v1=e$(W8(import.meta.url));h=G8()});import{readFileSync as U8}from"fs";import{resolve as Y8,dirname as B8}from"path";import{fileURLToPath as M8}from"url";function S$(){if(Q$!==null)return Q$;let $="7.128.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=B8(M8(import.meta.url)),Z=$1(Q);Q$=U8(Y8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var Q1=P(()=>{C()});var p1={};b(p1,{runOrThrow:()=>S8,run:()=>R,readStreamCapped:()=>c1,commandVersion:()=>C8,commandExists:()=>u,ShellError:()=>Z1,MAX_STDOUT_BYTES:()=>u1});async function c1($,Q=u1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function R($,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([c1(Z.stdout),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 S8($,Q={}){let Z=await R($,Q);if(Z.exitCode!==0)throw new Z1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function u($){let Q=D8($),Z=await R(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function D8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function C8($,Q="--version"){if(!await u($))return null;let z=await R([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var u1=16777216,Z1;var o=P(()=>{Z1=class Z1 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 r($){return b8?"":$}var b8,M,S,_,tZ,L,k,y,J;var p=P(()=>{b8=(process.env.NO_COLOR??"").length>0;M=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),tZ=r("\x1B[0;34m"),L=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as l8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(l8($))return A$=$,$;let Q=await u("python3.12");if(Q)return A$=Q,Q;let Z=await u("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return R([Z,"-c",$],Q)}var A$;var V$=P(()=>{o()});var V0={};b(V0,{runStatus:()=>U3});import{existsSync as v,readFileSync as H$,readdirSync as $0,statSync as Q0}from"fs";import{resolve as D,basename as z3}from"path";import{homedir as X3}from"os";function Z0($){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 z0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*C$/Q);if(X>C$)X=C$;let q=C$-X,K=S;if(z>=80)K=M;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=Z0($),H=Z0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${H})`}async function q3(){if(await u("jq"))return!0;return process.stdout.write(`${M}Error: jq is required but not installed.${J}
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)
@@ -814,4 +814,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
814
814
  `),2}default:return process.stderr.write(`Unknown command: ${Q}
815
815
  `),process.stderr.write(K8),2}}e1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var SZ=await NZ(Bun.argv.slice(2));process.exit(SZ);
816
816
 
817
- //# debugId=36875272847D5AB164756E2164756E21
817
+ //# debugId=01C7E7999085D66D64756E2164756E21
@@ -19,5 +19,11 @@
19
19
  "devDependencies": {
20
20
  "@types/bun": "latest",
21
21
  "typescript": "^5.6.0"
22
+ },
23
+ "dependencies": {
24
+ "@resvg/resvg-wasm": "^2.6.2"
25
+ },
26
+ "optionalDependencies": {
27
+ "@resvg/resvg-js": "^2.6.2"
22
28
  }
23
29
  }
package/mcp/__init__.py CHANGED
@@ -57,4 +57,4 @@ try:
57
57
  except ImportError:
58
58
  __all__ = ['mcp']
59
59
 
60
- __version__ = '7.126.0'
60
+ __version__ = '7.128.0'
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.126.0",
4
+ "version": "7.128.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",
@@ -139,5 +139,8 @@
139
139
  "jest": "^29.7.0",
140
140
  "jsdom": "^24.0.0",
141
141
  "typescript": "^5.9.3"
142
+ },
143
+ "dependencies": {
144
+ "@resvg/resvg-wasm": "^2.6.2"
142
145
  }
143
- }
146
+ }
@@ -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.126.0",
5
+ "version": "7.128.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",