loki-mode 7.125.0 → 7.126.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 -0
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/cockpit-render.sh +191 -0
- package/autonomy/loki +370 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/static/index.html +143 -94
- package/docs/COCKPIT-SPEC.md +70 -0
- package/docs/COCKPIT-UX-MANDATE.md +53 -0
- package/docs/INSTALLATION.md +2 -2
- package/loki-ts/dist/cockpit.js +16 -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
package/README.md
CHANGED
|
@@ -483,6 +483,7 @@ Status legend: "E2E-verified" means we run real spec-to-code builds on it oursel
|
|
|
483
483
|
| `loki modernize heal <path>` | Legacy system healing (archaeology, stabilize, isolate, modernize, validate -- v6.67.0; was: `loki heal`) |
|
|
484
484
|
| `loki pause` / `resume` | Pause/resume after current session |
|
|
485
485
|
| `loki status` | Show current status |
|
|
486
|
+
| `loki cockpit` | Live multi-repo status as an inline terminal image (Kitty/iTerm2/WezTerm/Ghostty); text + dashboard fallback elsewhere (v7.126.0) |
|
|
486
487
|
| `loki dashboard` | Open web dashboard |
|
|
487
488
|
| `loki preview` | Print running app URL and open in browser (Live App Preview, v7.24.0; was: `loki open`) |
|
|
488
489
|
| `loki web` | Launch Purple Lab web UI [DEPRECATED in v7.44.0 -- use `loki start` which auto-opens the dashboard at http://localhost:57374; for the hosted platform see Autonomi Cloud] |
|
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.
|
|
6
|
+
# Loki Mode v7.126.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.
|
|
411
|
+
**v7.126.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.126.0
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# autonomy/lib/cockpit-render.sh -- state gathering + render dispatch for
|
|
3
|
+
# `loki cockpit`. Thin wrapper: it builds a CockpitState JSON from the machine
|
|
4
|
+
# registry + each project's .loki state, then pipes it to the pure Bun render
|
|
5
|
+
# pipeline (loki-ts/src/cockpit/cli.ts). All the SVG/PNG/terminal-encode logic
|
|
6
|
+
# lives there so it stays bun-testable; this file only does I/O and fallback.
|
|
7
|
+
#
|
|
8
|
+
# Contract with the TS entry:
|
|
9
|
+
# stdin : CockpitState JSON
|
|
10
|
+
# stdout : raw terminal image escape (image path), empty on fallback
|
|
11
|
+
# exit 0 : image emitted ; exit 3 : fallback (reason on stderr as FALLBACK\t..)
|
|
12
|
+
# other : hard error (bun missing etc.) -> caller falls back too
|
|
13
|
+
#
|
|
14
|
+
# Nothing here changes cmd_watch or any existing command.
|
|
15
|
+
|
|
16
|
+
# cockpit_gather_state <skill_dir> <focus_repo_or_empty>
|
|
17
|
+
# Emits a CockpitState JSON on stdout. Never fails hard: missing files degrade
|
|
18
|
+
# to empty/default fields so the render always has something to show.
|
|
19
|
+
cockpit_gather_state() {
|
|
20
|
+
local skill_dir="$1"
|
|
21
|
+
local focus_repo="${2:-}"
|
|
22
|
+
|
|
23
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
24
|
+
# No python: emit a minimal single-repo state from the cwd only.
|
|
25
|
+
printf '{"run":"%s","iteration":0,"phase":"unknown","tier":"","provider":"","verdict":"unknown","budgetUsd":0,"gates":[],"council":[],"fleet":[]}\n' \
|
|
26
|
+
"$(basename "$(pwd)")"
|
|
27
|
+
return 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
LOKI_CK_SKILL="$skill_dir" LOKI_CK_FOCUS="$focus_repo" LOKI_CK_CWD="$(pwd)" \
|
|
31
|
+
python3 - <<'PYCOCKPIT' 2>/dev/null || printf '{"run":"cockpit","iteration":0,"phase":"unknown","tier":"","provider":"","verdict":"unknown","budgetUsd":0,"gates":[],"council":[],"fleet":[]}\n'
|
|
32
|
+
import json, os, sys
|
|
33
|
+
|
|
34
|
+
skill = os.environ.get("LOKI_CK_SKILL", ".")
|
|
35
|
+
focus = (os.environ.get("LOKI_CK_FOCUS") or "").strip()
|
|
36
|
+
cwd = os.environ.get("LOKI_CK_CWD") or os.getcwd()
|
|
37
|
+
sys.path.insert(0, skill)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def read_json(path):
|
|
41
|
+
try:
|
|
42
|
+
with open(path) as f:
|
|
43
|
+
return json.load(f)
|
|
44
|
+
except Exception:
|
|
45
|
+
return {}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def project_state(path):
|
|
49
|
+
"""Best-effort per-project state from its .loki/ files."""
|
|
50
|
+
loki = os.path.join(path, ".loki")
|
|
51
|
+
st = read_json(os.path.join(loki, "autonomy-state.json"))
|
|
52
|
+
ev = read_json(os.path.join(loki, "verify", "evidence.json"))
|
|
53
|
+
|
|
54
|
+
iteration = st.get("iterationCount", st.get("iteration", 0)) or 0
|
|
55
|
+
phase = st.get("phase") or st.get("status") or ""
|
|
56
|
+
|
|
57
|
+
verdict = "unknown"
|
|
58
|
+
v = str(ev.get("verdict", "")).upper()
|
|
59
|
+
if v in ("PASS", "VERIFIED"):
|
|
60
|
+
verdict = "verified"
|
|
61
|
+
elif v in ("BLOCKED", "FAIL", "FAILED"):
|
|
62
|
+
verdict = "failed"
|
|
63
|
+
elif v in ("WORKING", "PARTIAL", "INCONCLUSIVE"):
|
|
64
|
+
verdict = "working"
|
|
65
|
+
elif v:
|
|
66
|
+
verdict = "pending"
|
|
67
|
+
|
|
68
|
+
gates = []
|
|
69
|
+
for g in ev.get("deterministic_gates", []) or []:
|
|
70
|
+
gs = str(g.get("status", "")).lower()
|
|
71
|
+
status = {"pass": "pass", "fail": "fail", "failed": "fail",
|
|
72
|
+
"skipped": "skip", "skip": "skip"}.get(gs, "pending")
|
|
73
|
+
gates.append({"name": g.get("gate", "gate"), "status": status})
|
|
74
|
+
|
|
75
|
+
# Council votes: .loki/council/*.json, each with a vote-ish field.
|
|
76
|
+
council = []
|
|
77
|
+
cdir = os.path.join(loki, "council")
|
|
78
|
+
try:
|
|
79
|
+
for fn in sorted(os.listdir(cdir)):
|
|
80
|
+
if not fn.endswith(".json"):
|
|
81
|
+
continue
|
|
82
|
+
cj = read_json(os.path.join(cdir, fn))
|
|
83
|
+
raw = str(cj.get("vote") or cj.get("verdict") or "").lower()
|
|
84
|
+
vote = "pending"
|
|
85
|
+
if "approve" in raw:
|
|
86
|
+
vote = "approve"
|
|
87
|
+
elif "reject" in raw:
|
|
88
|
+
vote = "reject"
|
|
89
|
+
elif "concern" in raw:
|
|
90
|
+
vote = "concern"
|
|
91
|
+
reviewer = cj.get("reviewer") or cj.get("name") or os.path.splitext(fn)[0]
|
|
92
|
+
council.append({"reviewer": str(reviewer)[:16], "vote": vote})
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
"iteration": int(iteration) if isinstance(iteration, (int, float)) else 0,
|
|
98
|
+
"phase": phase or "idle",
|
|
99
|
+
"verdict": verdict,
|
|
100
|
+
"gates": gates,
|
|
101
|
+
"council": council,
|
|
102
|
+
"budgetUsd": float(st.get("cost_usd", 0.0) or 0.0),
|
|
103
|
+
"tier": st.get("tier", "") or "",
|
|
104
|
+
"provider": st.get("provider", "") or "",
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# Fleet from the registry; graceful empty on any failure.
|
|
109
|
+
fleet = []
|
|
110
|
+
runs = []
|
|
111
|
+
try:
|
|
112
|
+
from dashboard import registry
|
|
113
|
+
runs = registry.get_fleet_runs(include_inactive=True)
|
|
114
|
+
except Exception:
|
|
115
|
+
runs = []
|
|
116
|
+
|
|
117
|
+
for r in runs:
|
|
118
|
+
fleet.append({
|
|
119
|
+
"name": r.get("name") or os.path.basename(r.get("path", "") or "project"),
|
|
120
|
+
"phase": r.get("phase", "") or "",
|
|
121
|
+
"iteration": r.get("iteration", 0) or 0,
|
|
122
|
+
"status": r.get("status", "") or "",
|
|
123
|
+
"running": bool(r.get("running")),
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
# Focused run: --repo wins; else first running fleet run; else cwd.
|
|
127
|
+
focus_path = focus or cwd
|
|
128
|
+
focus_entry = None
|
|
129
|
+
for r in runs:
|
|
130
|
+
rp = os.path.abspath(r.get("path", "") or "")
|
|
131
|
+
if rp and rp == os.path.abspath(focus_path):
|
|
132
|
+
focus_entry = r
|
|
133
|
+
break
|
|
134
|
+
if focus_entry is None and not focus:
|
|
135
|
+
for r in runs:
|
|
136
|
+
if r.get("running"):
|
|
137
|
+
focus_entry = r
|
|
138
|
+
focus_path = r.get("path", "") or cwd
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
ps = project_state(focus_path)
|
|
142
|
+
run_name = (focus_entry or {}).get("name") if focus_entry else os.path.basename(os.path.abspath(focus_path))
|
|
143
|
+
|
|
144
|
+
# Registry values override file reads where present (registry is fresher).
|
|
145
|
+
if focus_entry:
|
|
146
|
+
if focus_entry.get("iteration"):
|
|
147
|
+
ps["iteration"] = focus_entry["iteration"]
|
|
148
|
+
if focus_entry.get("phase"):
|
|
149
|
+
ps["phase"] = focus_entry["phase"]
|
|
150
|
+
if focus_entry.get("cost_usd"):
|
|
151
|
+
ps["budgetUsd"] = float(focus_entry["cost_usd"])
|
|
152
|
+
|
|
153
|
+
state = {
|
|
154
|
+
"run": run_name or "cockpit",
|
|
155
|
+
"iteration": ps["iteration"],
|
|
156
|
+
"phase": ps["phase"],
|
|
157
|
+
"tier": ps["tier"] or (os.environ.get("LOKI_SESSION_MODEL", "")),
|
|
158
|
+
"provider": ps["provider"] or (os.environ.get("LOKI_PROVIDER", "claude")),
|
|
159
|
+
"verdict": ps["verdict"],
|
|
160
|
+
"budgetUsd": ps["budgetUsd"],
|
|
161
|
+
"budgetLimitUsd": float(os.environ.get("LOKI_MAX_BUDGET_USD", "0") or 0) or None,
|
|
162
|
+
"freshness": "now",
|
|
163
|
+
"gates": ps["gates"],
|
|
164
|
+
"council": ps["council"],
|
|
165
|
+
"fleet": fleet,
|
|
166
|
+
}
|
|
167
|
+
print(json.dumps(state))
|
|
168
|
+
PYCOCKPIT
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# cockpit_render <skill_dir> <ts_dir> <protocol> <no_image> <focus_repo>
|
|
172
|
+
# Returns: 0 (image emitted to stdout), 3 (fallback), or 2 (bun unavailable).
|
|
173
|
+
# On fallback / bun-missing the caller is responsible for the text summary.
|
|
174
|
+
cockpit_render() {
|
|
175
|
+
local skill_dir="$1" ts_dir="$2" protocol="${3:-auto}" no_image="${4:-0}" focus_repo="${5:-}"
|
|
176
|
+
|
|
177
|
+
# Prefer the shipped bundle (dist/cockpit.js -- present in npm/Docker/brew
|
|
178
|
+
# installs, which ship loki-ts/dist/ but not src/); fall back to the source
|
|
179
|
+
# entry for in-repo development.
|
|
180
|
+
local entry="$ts_dir/dist/cockpit.js"
|
|
181
|
+
[ -f "$entry" ] || entry="$ts_dir/src/cockpit/cli.ts"
|
|
182
|
+
if ! command -v bun >/dev/null 2>&1 || [ ! -f "$entry" ]; then
|
|
183
|
+
return 2
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
local args=(--protocol "$protocol")
|
|
187
|
+
[ "$no_image" = "1" ] && args+=(--no-image)
|
|
188
|
+
|
|
189
|
+
# Gather -> render. Preserve the TS exit code (0 image / 3 fallback).
|
|
190
|
+
cockpit_gather_state "$skill_dir" "$focus_repo" | bun "$entry" "${args[@]}"
|
|
191
|
+
}
|
package/autonomy/loki
CHANGED
|
@@ -959,6 +959,7 @@ show_help() {
|
|
|
959
959
|
echo " loki config set maxTier sonnet # Cap model cost"
|
|
960
960
|
echo " loki dashboard start # Web dashboard at localhost:57374"
|
|
961
961
|
echo " loki watch # Auto-rerun on PRD changes"
|
|
962
|
+
echo " loki cockpit # Live multi-repo cockpit (terminal image)"
|
|
962
963
|
echo " loki remote # Remote session (phone/browser)"
|
|
963
964
|
echo ""
|
|
964
965
|
echo "Phase A-J features (v7.5.18 - v7.5.28) are default-on. See CHANGELOG."
|
|
@@ -1156,6 +1157,170 @@ detect_arg_type() {
|
|
|
1156
1157
|
echo "unknown"
|
|
1157
1158
|
}
|
|
1158
1159
|
|
|
1160
|
+
# --- loki start detach-default handoff (S1) --------------------------------
|
|
1161
|
+
# On an interactive TTY, `loki start` now detaches the build to the background
|
|
1162
|
+
# by default after a rich, reversible handoff card. A non-interactive shell,
|
|
1163
|
+
# --bg, --yes/-y, or CI takes the exact pre-existing foreground path (no card,
|
|
1164
|
+
# byte-identical behavior). See tests/test-start-handoff.sh.
|
|
1165
|
+
|
|
1166
|
+
# Decide whether the interactive handoff should fire. Returns 0 (fire) only when
|
|
1167
|
+
# stdout is a TTY, --bg was not requested, auto-confirm/-y is off, and we are not
|
|
1168
|
+
# in CI / a non-interactive shell. Any other case returns 1 (take today's path).
|
|
1169
|
+
# $1 = "1" if --bg is present in args, else "".
|
|
1170
|
+
_loki_start_should_handoff() {
|
|
1171
|
+
local bg_requested="${1:-}"
|
|
1172
|
+
[ "$bg_requested" = "1" ] && return 1
|
|
1173
|
+
[ -t 1 ] || return 1
|
|
1174
|
+
[ -t 0 ] || return 1
|
|
1175
|
+
[ "${LOKI_AUTO_CONFIRM:-}" = "true" ] && return 1
|
|
1176
|
+
[ "${CI:-}" = "true" ] && return 1
|
|
1177
|
+
[ "${LOKI_NO_HANDOFF:-}" = "1" ] && return 1
|
|
1178
|
+
return 0
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
# Stare-worthy brand banner (S6). Renders the real Autonomi logo (purple squircle
|
|
1182
|
+
# + white "A" + teal dot) as a crafted truecolor block, next to a "LOKI" wordmark,
|
|
1183
|
+
# straight to stderr. ZERO new tooling: pure ANSI escapes. Degrades by color depth
|
|
1184
|
+
# ($COLORTERM truecolor -> 24-bit; else a clean 256/mono badge). Never required,
|
|
1185
|
+
# always the default -- this is the moment the user stares at.
|
|
1186
|
+
_loki_brand_banner() {
|
|
1187
|
+
local proj="$1"
|
|
1188
|
+
local nc=$'\033[0m' b=$'\033[1m' dim=$'\033[2m'
|
|
1189
|
+
# Truecolor when the terminal advertises it (nearly all modern terminals).
|
|
1190
|
+
if [ "${COLORTERM:-}" = "truecolor" ] || [ "${COLORTERM:-}" = "24bit" ]; then
|
|
1191
|
+
local pB=$'\033[48;2;85;61;233m' # squircle purple bg #553DE9
|
|
1192
|
+
local wF=$'\033[38;2;255;254;251m' # white A
|
|
1193
|
+
local tF=$'\033[38;2;31;197;168m' # teal dot
|
|
1194
|
+
local pF=$'\033[38;2;123;107;240m' # light purple wordmark
|
|
1195
|
+
# 6-row squircle badge; each cell two chars. '#'=A stroke, 'o'=teal dot.
|
|
1196
|
+
local rows=(
|
|
1197
|
+
"......"
|
|
1198
|
+
"..##.."
|
|
1199
|
+
".#..#."
|
|
1200
|
+
".####."
|
|
1201
|
+
".#.o#."
|
|
1202
|
+
".#..#."
|
|
1203
|
+
)
|
|
1204
|
+
printf '\n' >&2
|
|
1205
|
+
local i=0 line r c ch
|
|
1206
|
+
for r in "${rows[@]}"; do
|
|
1207
|
+
line=""
|
|
1208
|
+
for ((c=0; c<${#r}; c++)); do
|
|
1209
|
+
ch="${r:$c:1}"
|
|
1210
|
+
case "$ch" in
|
|
1211
|
+
'#') line+="${pB}${wF}\xE2\x96\x88\xE2\x96\x88${nc}" ;;
|
|
1212
|
+
'o') line+="${pB}${tF}\xE2\x97\x8F ${nc}" ;;
|
|
1213
|
+
*) line+="${pB} ${nc}" ;;
|
|
1214
|
+
esac
|
|
1215
|
+
done
|
|
1216
|
+
# Wordmark + tagline riding beside the badge on two of the rows.
|
|
1217
|
+
case $i in
|
|
1218
|
+
2) printf " %b %b%bLOKI%b\n" "$line" "$b" "$pF" "$nc" >&2 ;;
|
|
1219
|
+
3) printf " %b %bAutonomi cockpit%b\n" "$line" "$dim" "$nc" >&2 ;;
|
|
1220
|
+
*) printf " %b\n" "$line" >&2 ;;
|
|
1221
|
+
esac
|
|
1222
|
+
i=$((i+1))
|
|
1223
|
+
done
|
|
1224
|
+
printf '\n' >&2
|
|
1225
|
+
else
|
|
1226
|
+
# 256-color / limited: a compact, still-branded badge (no truecolor blocks).
|
|
1227
|
+
local p=$'\033[38;5;99m' t=$'\033[38;5;43m'
|
|
1228
|
+
printf '\n %s%s\xE2\x97\x86%s %sLOKI%s %s\xE2\x97\x8F%s %sAutonomi cockpit%s\n\n' \
|
|
1229
|
+
"$b" "$p" "$nc" "$b" "$nc" "$t" "$nc" "$dim" "$nc" >&2
|
|
1230
|
+
fi
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
# Print the rich handoff card and resolve the user's choice. Echoes exactly one
|
|
1234
|
+
# decision word on stdout:
|
|
1235
|
+
# launch-bg detach only
|
|
1236
|
+
# launch-bg-dashboard detach + open the dashboard in a browser
|
|
1237
|
+
# launch-bg-watch detach + print the `loki cockpit` hint
|
|
1238
|
+
# cancel do not launch (user chose Stop)
|
|
1239
|
+
# All non-cancel decisions detach the build (arm --bg). The choice is remembered
|
|
1240
|
+
# per project under .loki/config/start-view.json; a remembered choice skips the
|
|
1241
|
+
# prompt and prints only a one-line summary.
|
|
1242
|
+
# Args: $1=spec label $2=tier $3=provider
|
|
1243
|
+
_loki_start_handoff() {
|
|
1244
|
+
local spec="$1" tier="$2" prov="$3"
|
|
1245
|
+
local port="${LOKI_DASHBOARD_PORT:-57374}"
|
|
1246
|
+
local dash_url="http://127.0.0.1:${port}"
|
|
1247
|
+
local budget="${LOKI_BUDGET_LIMIT:-none}"
|
|
1248
|
+
local cfg_dir="${LOKI_DIR:-.loki}/config"
|
|
1249
|
+
local cfg_file="$cfg_dir/start-view.json"
|
|
1250
|
+
local pwd_path; pwd_path="$(pwd)"
|
|
1251
|
+
local proj; proj="$(basename "$pwd_path")"
|
|
1252
|
+
|
|
1253
|
+
# Remembered choice: skip the prompt, print one-line summary, reuse decision.
|
|
1254
|
+
local saved=""
|
|
1255
|
+
if [ -f "$cfg_file" ]; then
|
|
1256
|
+
saved="$(sed -n 's/.*"view"[[:space:]]*:[[:space:]]*"\([a-z-]*\)".*/\1/p' "$cfg_file" 2>/dev/null | head -1)"
|
|
1257
|
+
fi
|
|
1258
|
+
if [ -n "$saved" ]; then
|
|
1259
|
+
case "$saved" in
|
|
1260
|
+
both|dashboard|watch|detach)
|
|
1261
|
+
echo -e "${DIM}Detaching to background (remembered view: ${saved}; change with 'loki cockpit' or delete ${cfg_file}).${NC}" >&2
|
|
1262
|
+
case "$saved" in
|
|
1263
|
+
dashboard) echo "launch-bg-dashboard" ;;
|
|
1264
|
+
watch) echo "launch-bg-watch" ;;
|
|
1265
|
+
*) echo "launch-bg" ;;
|
|
1266
|
+
esac
|
|
1267
|
+
return 0
|
|
1268
|
+
;;
|
|
1269
|
+
esac
|
|
1270
|
+
fi
|
|
1271
|
+
|
|
1272
|
+
# Stare-worthy brand banner (S6) then the rich handoff card. Both to stderr so
|
|
1273
|
+
# stdout carries only the decision word.
|
|
1274
|
+
_loki_brand_banner "$proj"
|
|
1275
|
+
{
|
|
1276
|
+
echo -e " ${BOLD}Building ${proj}${NC} \033[38;2;31;197;168m\xE2\x97\x8F${NC} ${DIM}live${NC}"
|
|
1277
|
+
echo ""
|
|
1278
|
+
echo -e " ${DIM}spec${NC} ${spec}"
|
|
1279
|
+
echo -e " ${DIM}path${NC} ${pwd_path}"
|
|
1280
|
+
echo -e " ${DIM}tier${NC} ${tier}"
|
|
1281
|
+
echo -e " ${DIM}provider${NC} ${prov}"
|
|
1282
|
+
echo -e " ${DIM}iteration${NC} starting"
|
|
1283
|
+
echo -e " ${DIM}budget${NC} \$0 / ${budget} (breaker: on)"
|
|
1284
|
+
echo -e " ${DIM}log${NC} ${LOKI_DIR:-.loki}/logs/background-*.log"
|
|
1285
|
+
echo -e " ${DIM}dashboard${NC} ${dash_url}"
|
|
1286
|
+
echo ""
|
|
1287
|
+
echo -e " ${BOLD}Both${NC}[default] ${BOLD}D${NC}ashboard ${BOLD}W${NC}atch De${BOLD}t${NC}ach ${BOLD}S${NC}top"
|
|
1288
|
+
echo -e " ${DIM}opening your cockpit + dashboard in 8s -- press a key to choose${NC}"
|
|
1289
|
+
} >&2
|
|
1290
|
+
|
|
1291
|
+
local reply=""
|
|
1292
|
+
read -t 8 -r reply 2>/dev/null || reply=""
|
|
1293
|
+
|
|
1294
|
+
local decision="launch-bg" view="both"
|
|
1295
|
+
case "$(printf '%s' "$reply" | tr '[:upper:]' '[:lower:]')" in
|
|
1296
|
+
""|b|both) decision="launch-bg"; view="both" ;;
|
|
1297
|
+
d|dash|dashboard) decision="launch-bg-dashboard"; view="dashboard" ;;
|
|
1298
|
+
w|watch) decision="launch-bg-watch"; view="watch" ;;
|
|
1299
|
+
t|detach) decision="launch-bg"; view="detach" ;;
|
|
1300
|
+
s|stop|cancel|q) decision="cancel"; view="" ;;
|
|
1301
|
+
*) decision="launch-bg"; view="both" ;;
|
|
1302
|
+
esac
|
|
1303
|
+
|
|
1304
|
+
if [ "$decision" = "cancel" ]; then
|
|
1305
|
+
echo -e "${YELLOW}run cancelled${NC}" >&2
|
|
1306
|
+
echo "cancel"
|
|
1307
|
+
return 0
|
|
1308
|
+
fi
|
|
1309
|
+
|
|
1310
|
+
# Remember the choice for next time (best-effort; never blocks the launch).
|
|
1311
|
+
mkdir -p "$cfg_dir" 2>/dev/null && \
|
|
1312
|
+
printf '{"view":"%s"}\n' "$view" > "$cfg_file" 2>/dev/null || true
|
|
1313
|
+
|
|
1314
|
+
if [ "$view" = "both" ]; then
|
|
1315
|
+
echo -e "${DIM}Open the dashboard at ${dash_url} or run 'loki cockpit' to watch.${NC}" >&2
|
|
1316
|
+
elif [ "$view" = "watch" ]; then
|
|
1317
|
+
echo -e "${DIM}Run 'loki cockpit' to watch the build.${NC}" >&2
|
|
1318
|
+
fi
|
|
1319
|
+
|
|
1320
|
+
echo "$decision"
|
|
1321
|
+
return 0
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1159
1324
|
# Start Loki Mode
|
|
1160
1325
|
cmd_start() {
|
|
1161
1326
|
# First-run welcome (once): open the branded welcome page on the very
|
|
@@ -2316,6 +2481,41 @@ cmd_start() {
|
|
|
2316
2481
|
loki_project_graph_discover "$_pg_target" || true
|
|
2317
2482
|
fi
|
|
2318
2483
|
|
|
2484
|
+
# S1: detach-to-background by default on an interactive TTY. Fires a rich,
|
|
2485
|
+
# reversible handoff card, then arms the existing --bg path so run.sh
|
|
2486
|
+
# backgrounds the runner (it writes the log + PID file and prints its own
|
|
2487
|
+
# "Running in Background" banner). The HARD gate below keeps every
|
|
2488
|
+
# non-interactive / --bg / --yes / CI caller on the exact pre-existing
|
|
2489
|
+
# foreground exec -- byte-identical to before this change.
|
|
2490
|
+
local _bg_already=""
|
|
2491
|
+
for _a in ${args[@]+"${args[@]}"}; do
|
|
2492
|
+
if [ "$_a" = "--bg" ]; then _bg_already=1; break; fi
|
|
2493
|
+
done
|
|
2494
|
+
if _loki_start_should_handoff "$_bg_already"; then
|
|
2495
|
+
local _spec_label="${prd_file:-codebase (no PRD)}"
|
|
2496
|
+
local _tier_label="${LOKI_COMPLEXITY:-auto}"
|
|
2497
|
+
local _decision
|
|
2498
|
+
_decision="$(_loki_start_handoff "$_spec_label" "$_tier_label" "$effective_provider")"
|
|
2499
|
+
case "$_decision" in
|
|
2500
|
+
cancel)
|
|
2501
|
+
exit 0
|
|
2502
|
+
;;
|
|
2503
|
+
launch-bg-dashboard|launch-bg)
|
|
2504
|
+
# Both (default) AND Dashboard open the browser -- Both = dashboard
|
|
2505
|
+
# + cockpit, honoring the card's "open dashboard" promise. The
|
|
2506
|
+
# cockpit hint is printed by _loki_start_handoff for the Both path.
|
|
2507
|
+
args+=("--bg")
|
|
2508
|
+
local _du="http://127.0.0.1:${LOKI_DASHBOARD_PORT:-57374}"
|
|
2509
|
+
if command -v open >/dev/null 2>&1; then open "$_du" >/dev/null 2>&1 &
|
|
2510
|
+
elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$_du" >/dev/null 2>&1 & fi
|
|
2511
|
+
;;
|
|
2512
|
+
launch-bg-watch)
|
|
2513
|
+
# Watch (cockpit only) detaches without opening the browser.
|
|
2514
|
+
args+=("--bg")
|
|
2515
|
+
;;
|
|
2516
|
+
esac
|
|
2517
|
+
fi
|
|
2518
|
+
|
|
2319
2519
|
# v7.7.13 fix (user-reported bug): `"${args[@]}"` triggers "unbound
|
|
2320
2520
|
# variable" under bash 3.2 (macOS default) + `set -u` when args is empty.
|
|
2321
2521
|
# User report: `loki start` with no PRD on /Users/lokesh/git/anonima
|
|
@@ -8545,6 +8745,173 @@ cmd_watch() {
|
|
|
8545
8745
|
esac
|
|
8546
8746
|
}
|
|
8547
8747
|
|
|
8748
|
+
#===============================================================================
|
|
8749
|
+
# loki cockpit - Multi-repo live status as a terminal inline image (v7.126.0)
|
|
8750
|
+
#
|
|
8751
|
+
# Renders the Autonomi cockpit frame directly in supported terminals (iTerm2 /
|
|
8752
|
+
# WezTerm / ghostty via the iTerm2 protocol; Kitty via its graphics protocol).
|
|
8753
|
+
# On any unsupported terminal, or when rasterization is unavailable, it falls
|
|
8754
|
+
# back HONESTLY to a compact text summary and points at the browser dashboard.
|
|
8755
|
+
# This never touches `loki watch` (the PRD-file auto-rerun watcher).
|
|
8756
|
+
#===============================================================================
|
|
8757
|
+
|
|
8758
|
+
# Compact honest text summary printed on the fallback path. Reads the same
|
|
8759
|
+
# CockpitState JSON the renderer would have used, so text and image agree.
|
|
8760
|
+
_cockpit_text_summary() {
|
|
8761
|
+
local skill_dir="$1" focus_repo="$2"
|
|
8762
|
+
local state_json
|
|
8763
|
+
state_json="$(cockpit_gather_state "$skill_dir" "$focus_repo" 2>/dev/null || true)"
|
|
8764
|
+
if [ -z "$state_json" ] || ! command -v python3 >/dev/null 2>&1; then
|
|
8765
|
+
echo " (no cockpit state available)"
|
|
8766
|
+
return 0
|
|
8767
|
+
fi
|
|
8768
|
+
LOKI_CK_STATE="$state_json" python3 - <<'PYTXT' 2>/dev/null || echo " (could not render text summary)"
|
|
8769
|
+
import json, os
|
|
8770
|
+
s = json.loads(os.environ.get("LOKI_CK_STATE") or "{}")
|
|
8771
|
+
print(f" Run {s.get('run','?')}")
|
|
8772
|
+
print(f" Iteration {s.get('iteration',0)} Phase {s.get('phase','?')} Verdict {str(s.get('verdict','?')).upper()}")
|
|
8773
|
+
tier = s.get('tier') or '-'
|
|
8774
|
+
prov = s.get('provider') or '-'
|
|
8775
|
+
print(f" Tier {tier} Provider {prov} Budget ${float(s.get('budgetUsd',0)):.2f}")
|
|
8776
|
+
gates = s.get('gates') or []
|
|
8777
|
+
if gates:
|
|
8778
|
+
line = " Gates " + " ".join(f"{g.get('name','?')}={g.get('status','?')}" for g in gates[:8])
|
|
8779
|
+
print(line)
|
|
8780
|
+
council = s.get('council') or []
|
|
8781
|
+
if council:
|
|
8782
|
+
print(" Council " + " ".join(f"{c.get('reviewer','?')}:{c.get('vote','?')}" for c in council[:6]))
|
|
8783
|
+
fleet = s.get('fleet') or []
|
|
8784
|
+
if fleet:
|
|
8785
|
+
print(f" Fleet ({len(fleet)}):")
|
|
8786
|
+
for r in fleet[:8]:
|
|
8787
|
+
mark = "*" if r.get("running") else " "
|
|
8788
|
+
print(f" {mark} {r.get('name','?'):<28} iter {r.get('iteration',0)} {r.get('phase') or r.get('status') or ''}")
|
|
8789
|
+
PYTXT
|
|
8790
|
+
}
|
|
8791
|
+
|
|
8792
|
+
cmd_cockpit() {
|
|
8793
|
+
local protocol="auto"
|
|
8794
|
+
local no_image="0"
|
|
8795
|
+
local run_once="0"
|
|
8796
|
+
local follow="1"
|
|
8797
|
+
local interval="2"
|
|
8798
|
+
local focus_repo=""
|
|
8799
|
+
|
|
8800
|
+
while [[ $# -gt 0 ]]; do
|
|
8801
|
+
case "$1" in
|
|
8802
|
+
--help|-h)
|
|
8803
|
+
echo -e "${BOLD}loki cockpit${NC} - Live multi-repo status as a terminal inline image (v7.126.0)"
|
|
8804
|
+
echo ""
|
|
8805
|
+
echo "Usage: loki cockpit [options]"
|
|
8806
|
+
echo ""
|
|
8807
|
+
echo "Renders the Autonomi cockpit (iteration, phase, tier, provider, gates,"
|
|
8808
|
+
echo "council votes, verdict, budget, and the multi-repo fleet) as an inline"
|
|
8809
|
+
echo "image in supported terminals. Falls back to a text summary + the browser"
|
|
8810
|
+
echo "dashboard on terminals without inline-image support."
|
|
8811
|
+
echo ""
|
|
8812
|
+
echo "Options:"
|
|
8813
|
+
echo " --once Render one frame then exit"
|
|
8814
|
+
echo " --follow Re-render on an interval (default)"
|
|
8815
|
+
echo " --interval N Seconds between frames in --follow (default: 2)"
|
|
8816
|
+
echo " --repo PATH Focus one repo instead of the running fleet head"
|
|
8817
|
+
echo " --protocol P iterm2 | kitty | auto (default: auto-detect)"
|
|
8818
|
+
echo " --no-image Force the text/browser fallback"
|
|
8819
|
+
echo " --help, -h Show this help"
|
|
8820
|
+
echo ""
|
|
8821
|
+
echo "Supported terminals: iTerm2, WezTerm, ghostty (iTerm2 protocol); Kitty."
|
|
8822
|
+
echo "Override detection with LOKI_COCKPIT_PROTOCOL=iterm2|kitty."
|
|
8823
|
+
echo ""
|
|
8824
|
+
echo "Note: this is distinct from 'loki watch', which auto-reruns on PRD edits."
|
|
8825
|
+
return 0
|
|
8826
|
+
;;
|
|
8827
|
+
--once) run_once="1"; follow="0"; shift ;;
|
|
8828
|
+
--follow) follow="1"; run_once="0"; shift ;;
|
|
8829
|
+
--interval) interval="${2:-2}"; [ $# -ge 2 ] && shift 2 || shift ;;
|
|
8830
|
+
--interval=*) interval="${1#*=}"; shift ;;
|
|
8831
|
+
--repo) focus_repo="${2:-}"; [ $# -ge 2 ] && shift 2 || shift ;;
|
|
8832
|
+
--repo=*) focus_repo="${1#*=}"; shift ;;
|
|
8833
|
+
--protocol) protocol="${2:-auto}"; [ $# -ge 2 ] && shift 2 || shift ;;
|
|
8834
|
+
--protocol=*) protocol="${1#*=}"; shift ;;
|
|
8835
|
+
--no-image) no_image="1"; shift ;;
|
|
8836
|
+
-*)
|
|
8837
|
+
echo -e "${RED}Unknown option: $1${NC}"
|
|
8838
|
+
echo "Run 'loki cockpit --help' for usage."
|
|
8839
|
+
return 1
|
|
8840
|
+
;;
|
|
8841
|
+
*)
|
|
8842
|
+
if [ -z "$focus_repo" ]; then
|
|
8843
|
+
focus_repo="$1"
|
|
8844
|
+
else
|
|
8845
|
+
echo -e "${RED}Unexpected argument: $1${NC}"
|
|
8846
|
+
return 1
|
|
8847
|
+
fi
|
|
8848
|
+
shift
|
|
8849
|
+
;;
|
|
8850
|
+
esac
|
|
8851
|
+
done
|
|
8852
|
+
|
|
8853
|
+
# Load the render/gather helpers (pure logic lives in loki-ts).
|
|
8854
|
+
local ts_dir="${_LOKI_SCRIPT_DIR}/../loki-ts"
|
|
8855
|
+
local render_lib="${_LOKI_SCRIPT_DIR}/lib/cockpit-render.sh"
|
|
8856
|
+
if [ -f "$render_lib" ]; then
|
|
8857
|
+
# shellcheck disable=SC1090
|
|
8858
|
+
source "$render_lib"
|
|
8859
|
+
else
|
|
8860
|
+
echo -e "${RED}cockpit render library not found${NC} ($render_lib)" >&2
|
|
8861
|
+
return 1
|
|
8862
|
+
fi
|
|
8863
|
+
|
|
8864
|
+
_cockpit_frame() {
|
|
8865
|
+
local rc=0
|
|
8866
|
+
# cockpit_render prints the terminal image to stdout on success (rc 0),
|
|
8867
|
+
# or exits 3 (fallback) / 2 (bun unavailable). Capture stderr for the
|
|
8868
|
+
# FALLBACK reason without swallowing the image bytes on stdout.
|
|
8869
|
+
cockpit_render "$SKILL_DIR" "$ts_dir" "$protocol" "$no_image" "$focus_repo" 2>/tmp/loki-cockpit-err.$$ || rc=$?
|
|
8870
|
+
if [ "$rc" = "0" ]; then
|
|
8871
|
+
rm -f "/tmp/loki-cockpit-err.$$" 2>/dev/null
|
|
8872
|
+
return 0
|
|
8873
|
+
fi
|
|
8874
|
+
# Honest fallback: text summary + browser dashboard pointer. NEVER claim
|
|
8875
|
+
# an image was rendered here.
|
|
8876
|
+
local reason=""
|
|
8877
|
+
if [ -f "/tmp/loki-cockpit-err.$$" ]; then
|
|
8878
|
+
reason="$(sed -n 's/^FALLBACK\t//p' "/tmp/loki-cockpit-err.$$" | head -1)"
|
|
8879
|
+
rm -f "/tmp/loki-cockpit-err.$$" 2>/dev/null
|
|
8880
|
+
fi
|
|
8881
|
+
[ -z "$reason" ] && reason="inline image unavailable in this terminal"
|
|
8882
|
+
echo ""
|
|
8883
|
+
echo -e "${BOLD}Loki Cockpit${NC} ${DIM}(text fallback: ${reason})${NC}"
|
|
8884
|
+
_cockpit_text_summary "$SKILL_DIR" "$focus_repo"
|
|
8885
|
+
echo ""
|
|
8886
|
+
echo -e " Open the full visual dashboard: ${CYAN}loki dashboard${NC}"
|
|
8887
|
+
return 3
|
|
8888
|
+
}
|
|
8889
|
+
|
|
8890
|
+
if [ "$run_once" = "1" ]; then
|
|
8891
|
+
_cockpit_frame
|
|
8892
|
+
return 0
|
|
8893
|
+
fi
|
|
8894
|
+
|
|
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.
|
|
8898
|
+
local frame_rc=0
|
|
8899
|
+
_cockpit_frame || frame_rc=$?
|
|
8900
|
+
if [ "$frame_rc" != "0" ]; then
|
|
8901
|
+
# Fallback path: no live image loop; the text summary + dashboard hint
|
|
8902
|
+
# were already printed once. Point the user at --follow-capable UIs.
|
|
8903
|
+
return 0
|
|
8904
|
+
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
|
|
8912
|
+
return 0
|
|
8913
|
+
}
|
|
8914
|
+
|
|
8548
8915
|
#===============================================================================
|
|
8549
8916
|
# loki export - Export session data (v6.0.0)
|
|
8550
8917
|
#===============================================================================
|
|
@@ -17043,6 +17410,9 @@ main() {
|
|
|
17043
17410
|
watch)
|
|
17044
17411
|
cmd_watch "$@"
|
|
17045
17412
|
;;
|
|
17413
|
+
cockpit)
|
|
17414
|
+
cmd_cockpit "$@"
|
|
17415
|
+
;;
|
|
17046
17416
|
export)
|
|
17047
17417
|
# CLI consolidation (Phase A): 'export' -> 'report export'.
|
|
17048
17418
|
_deprecated_alias export "report export" "$@"
|