loki-mode 7.28.2 → 7.30.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 CHANGED
@@ -28,13 +28,14 @@
28
28
  - **Production quality built in** -- 11 quality gates (`skills/quality-gates.md`), blind 3-reviewer code review (`run.sh:run_code_review()`), anti-sycophancy checks
29
29
  - **Standalone verification: `loki verify`** -- Run Loki's deterministic gates (build, tests, static analysis, secret scan, dependency audit) against any branch or PR diff, including code written by other agents or humans. CI-ready exit codes (0 VERIFIED, 1 CONCERNS, 2 BLOCKED), machine-readable evidence at `.loki/verify/evidence.json`. Inconclusive evidence is never reported as VERIFIED (v7.27.0).
30
30
  - **Living spec and pre-build interrogation** -- `loki spec` locks a spec and detects drift deterministically (`spec.lock`, `drift-report.json`, and a `SPEC_DRIFT` finding in `loki verify` with CI exit codes), so you can tell when the build diverges from what was agreed. `loki grill` runs a Devil's-Advocate interrogation of the spec before you build, surfacing gaps and contradictions early (v7.28.0).
31
+ - **Guided first build: `loki quickstart`** -- four quick questions (setup check, one-line idea, template pick, plan review) and your build starts; pressing Enter through every step builds the sample Todo app. The plan step quotes the real cost/time estimate before anything is spent, and `loki demo` now confirms its estimate the same way. If no AI provider CLI is installed, Loki offers to install Claude Code (consent-gated, interactive terminals only) (v7.29.0).
31
32
  - **Live App Preview** -- The dashboard embeds the locally-running app in an iframe so you can interact with it immediately during a build. Use `loki preview` (alias `loki open`) to print the URL and open it in your browser. Local-first: no hosted service, no vendor lock (v7.24.0).
32
33
  - **Compose-first fullstack** -- When a spec needs more than one service (web + database + cache) Loki generates a 12-factor `docker-compose.yml` with healthchecks, `depends_on` wiring, env-var config, and a `.env.example`. The Live App Preview surfaces the web service URL (not a database port), and health reflects the web service's Docker healthcheck so a crashed app shows as crashed even when the database stays up. Single-service apps stay on a plain run command. All local-first, no hosted service (v7.26.0).
33
34
  - **Intelligent `loki start`** -- For interactive foreground runs the dashboard auto-opens in the browser (cross-platform; skipped in CI, SSH-without-TTY, and piped runs; opt out with `LOKI_NO_AUTO_OPEN=1`). The completion summary shows "Your app is live at <url>" so you know exactly where to try what Loki just built. The autonomous loop passes Claude Code's `--effort`, `--max-budget-usd`, and `--fallback-model` on every iteration (each gated on CLI support and individual opt-out env vars) for better long-run unattended execution (v7.25.0).
34
35
  - **Cross-project memory** -- Episodic/semantic/procedural memory with vector search; knowledge learned on one project surfaces on the next (v5.15.0+, see `memory/engine.py`)
35
36
  - **Self-hosted and private** -- Your keys, your infrastructure, no data leaves your network
36
37
  - **Legacy system healing** -- `loki heal` archaeology/stabilize/isolate/modernize/validate phases (v6.67.0, see `skills/healing.md`)
37
- - **MCP server** -- 34 tools (including ChromaDB code search) plus 3 resources and 2 prompts (`mcp/server.py`, with managed-memory and magic tools registered from `mcp/managed_tools.py` and `mcp/magic_tools.py`)
38
+ - **MCP server** -- 34 tools (including ChromaDB code search) plus 3 resources and 2 prompts (`mcp/server.py`, with magic tools registered from `mcp/magic_tools.py` and the managed-memory tool from `mcp/managed_tools.py`). Of the 34, 33 are always available; `loki_memory_redact` is registered but only succeeds when `LOKI_MANAGED_AGENTS=true` and `LOKI_MANAGED_MEMORY=true`. Launch with `loki mcp` (bootstraps the Python MCP SDK on first run).
38
39
  - **Full-stack output** -- Source code, tests, Docker Compose stacks (multi-service with healthchecks), CI/CD pipelines, audit logs
39
40
  - **Provider-agnostic** -- runs on Claude, Codex, Cline, or Aider with automatic failover (`loki-ts/src/runner/providers.ts`); no vendor lock-in. Gemini CLI deprecated v7.5.18; Antigravity CLI coming soon.
40
41
  - **Open source** -- Free for personal, internal, and academic use.
@@ -101,7 +102,7 @@ loki quick "build a landing page with a signup form"
101
102
  |--------|---------|-------|
102
103
  | **Bun (recommended)** | `bun install -g loki-mode` | Fastest startup for CLI commands. |
103
104
  | **Homebrew** | `brew tap asklokesh/tap && brew install loki-mode` | Auto-installs Bun as a dep |
104
- | **Docker** | `docker pull asklokesh/loki-mode:7.28.2 && docker run --rm asklokesh/loki-mode:7.28.2 start prd.md` | Bun pre-installed in image |
105
+ | **Docker** | `docker pull asklokesh/loki-mode:7.30.0 && docker run --rm asklokesh/loki-mode:7.30.0 start prd.md` | Bun pre-installed in image |
105
106
  | **npm (compat)** | `npm install -g loki-mode` | Works without Bun (bash fallback). Migrate any time with `loki self-update --to bun`. |
106
107
 
107
108
  **Upgrading:**
@@ -161,7 +162,7 @@ The next major release sunsets the Bash runtime entirely. There is no firm calen
161
162
  | Method | Command |
162
163
  |--------|---------|
163
164
  | **Homebrew** | `brew tap asklokesh/tap && brew install loki-mode` |
164
- | **Docker** | `docker pull asklokesh/loki-mode:7.28.2` |
165
+ | **Docker** | `docker pull asklokesh/loki-mode:7.30.0` |
165
166
  | **Inside Claude Code** | `claude --dangerously-skip-permissions` then type "Loki Mode" |
166
167
  | **Git clone** | `git clone https://github.com/asklokesh/loki-mode.git` |
167
168
 
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 11 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v7.28.2
6
+ # Loki Mode v7.30.0
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -342,6 +342,12 @@ Two completion-trust features extend the verification gates. Full details in `sk
342
342
  - **Held-out spec evals:** ~25% of checklist items (deterministic `sha256(id)` order, `N >= 4`) are reserved into `.loki/checklist/held-out.json` and excluded from the build prompt feed; the completion council blocks if a held-out item fails. Opt out with `LOKI_HELDOUT_GATE=0`. Honest limit: this guards the prompt feed, not a sandbox; the reservation file is on disk and an agent with filesystem access can read it.
343
343
  - **Inconclusive-baseline disclosure:** when the evidence gate cannot establish a diff baseline (`no_git_repo` / `no_run_start_sha`) it writes `.loki/state/evidence-inconclusive.json` and `COMPLETION.txt` carries an honest "not independently verified" line. It never blocks non-git projects; red tests still block.
344
344
 
345
+ ## First-run UX (v7.29.0)
346
+
347
+ - **`loki quickstart`:** guided 4-step first build (setup check, one-line idea, offline template match, plan review with real estimator figures); Enter-through-everything builds the sample Todo app; non-TTY/CI exits 2 with an automation hint.
348
+ - **Provider install offer:** when no provider CLI is found, doctor and the start/demo/quick/quickstart pre-flight offer to install Claude Code. Consent-gated on an interactive TTY only; the single command executed is printed first; auth handoff via `claude auth login` with readiness confirmed by `claude auth status`. Opt out: `LOKI_NO_INSTALL_OFFER=1`.
349
+ - **`loki demo` cost confirm:** the estimate always prints before spending; `--yes` skips the prompt, never the estimate. `LOKI_COMPLEXITY` is honored by `loki plan` with an honest forced-tier note.
350
+
345
351
  ---
346
352
 
347
353
  ## Concurrency and Security Hardening (v7.5.7 - v7.5.13)
@@ -392,4 +398,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
392
398
 
393
399
  ---
394
400
 
395
- **v7.28.2 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
401
+ **v7.30.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 7.28.2
1
+ 7.30.0
package/autonomy/loki CHANGED
@@ -68,6 +68,24 @@ if [ -f "$_LOKI_SCRIPT_DIR/crash.sh" ]; then
68
68
  source "$_LOKI_SCRIPT_DIR/crash.sh"
69
69
  fi
70
70
 
71
+ # Provider install offer (v7.29.0): shared, self-contained helper providing
72
+ # detect_any_provider, offer_provider_install, and provider_offer_gate. Sourced
73
+ # here for the bash route; doctor.ts invokes the same file via child_process so
74
+ # the offer copy never drifts between routes. Self-guarded against double-source.
75
+ if [ -f "$_LOKI_SCRIPT_DIR/provider-offer.sh" ]; then
76
+ # shellcheck source=provider-offer.sh
77
+ source "$_LOKI_SCRIPT_DIR/provider-offer.sh"
78
+ fi
79
+
80
+ # Quickstart guided interview (v7.29.0): provides cmd_quickstart and its
81
+ # deterministic offline template matcher. Functions-only file (no top-level
82
+ # command), sourced here so the dispatch case can call cmd_quickstart. Composes
83
+ # the provider offer (above), show_prd_plan, and cmd_start. Self-guarded.
84
+ if [ -f "$_LOKI_SCRIPT_DIR/quickstart.sh" ]; then
85
+ # shellcheck source=quickstart.sh
86
+ source "$_LOKI_SCRIPT_DIR/quickstart.sh"
87
+ fi
88
+
71
89
  # Resolve the script's real path (handles symlinks)
72
90
  resolve_script_path() {
73
91
  local script="$1"
@@ -522,6 +540,7 @@ show_help() {
522
540
  echo " quick \"task\" Quick single-task mode (lightweight, 3 iterations max)"
523
541
  echo " monitor [path] Monitor Docker Compose services with auto-fix (v6.67.0)"
524
542
  echo " demo Build a sample todo app end to end (real run)"
543
+ echo " quickstart [idea] Guided first build: setup, idea, template, plan, go"
525
544
  echo " init [name] Project scaffolding with 21 PRD templates"
526
545
  echo " issue <url|num> (deprecated) Use 'loki start <issue-ref>' instead"
527
546
  echo " watch [prd] Auto-rerun on PRD file changes (v6.33.0)"
@@ -683,6 +702,13 @@ show_help() {
683
702
  show_landing() {
684
703
  local version
685
704
  version=$(get_version)
705
+ # v7.30.0 (item 5): strip color when stdout is not a TTY (piped/redirected)
706
+ # in addition to the global NO_COLOR honoring above, so captured landing
707
+ # output is clean. Local overrides only; the rest of the CLI is untouched.
708
+ local BOLD="$BOLD" CYAN="$CYAN" NC="$NC"
709
+ if [ ! -t 1 ]; then
710
+ BOLD=''; CYAN=''; NC=''
711
+ fi
686
712
  echo -e "${BOLD}Loki Mode v$version${NC} - the spec-driven builder that verifies its own work."
687
713
  echo ""
688
714
  echo -e "First time here? ${CYAN}loki doctor${NC} checks your setup (an AI provider CLI is required)."
@@ -690,6 +716,7 @@ show_landing() {
690
716
  echo "Get started:"
691
717
  echo -e " ${CYAN}loki start ./prd.md${NC} Build from a spec (PRD file, GitHub issue, or no arg)"
692
718
  echo -e " ${CYAN}loki demo${NC} Build a sample todo app end to end (real run)"
719
+ echo -e " ${CYAN}loki web${NC} Open the visual builder to input a spec and watch agents build"
693
720
  echo -e " ${CYAN}loki dashboard start${NC} Start the live run monitor (then: loki dashboard open)"
694
721
  echo ""
695
722
  echo -e "Need help? ${CYAN}loki help${NC} lists every command."
@@ -1260,6 +1287,17 @@ cmd_start() {
1260
1287
  esac
1261
1288
  done
1262
1289
 
1290
+ # v7.29.0: provider pre-flight gate. If no provider CLI is installed, offer
1291
+ # to install one (TTY) or print honest manual instructions and exit 2
1292
+ # (non-TTY/CI), BEFORE any spend or runner entry. This moves what used to be
1293
+ # an opaque deep-runner failure to a friendly, actionable top-of-run gate.
1294
+ # --help already returned inside the arg loop, so we never gate help.
1295
+ if declare -f provider_offer_gate >/dev/null 2>&1; then
1296
+ if ! provider_offer_gate; then
1297
+ exit 2
1298
+ fi
1299
+ fi
1300
+
1263
1301
  # Clear any stale raw-brief marker from a PRIOR run before we decide the
1264
1302
  # mode of THIS run. Only the brief branch (below) re-writes it. Without this,
1265
1303
  # a brief run leaves .loki/state/brief.txt behind and a later non-brief run
@@ -2286,10 +2324,18 @@ PYDASH
2286
2324
  # v7.7.30: --all preserves the legacy machine-wide kill. It runs even when
2287
2325
  # the current folder has no live session (the "clean everything" use case),
2288
2326
  # reaping every folder's orphaned loki-run-* temp script (SIGTERM, SIGKILL).
2327
+ #
2328
+ # The kill pattern is "loki-run-" for real users and is NOT user-overridable
2329
+ # at the CLI -- that is the documented machine-wide behavior. LOKI_STOP_ALL_PATTERN
2330
+ # exists ONLY so the test suite can exercise this exact code path against its
2331
+ # own uniquely-marked fake runners without SIGKILLing an unrelated live
2332
+ # loki-run-* on the same machine (e.g. a long SWE-bench instance). It defaults
2333
+ # to "loki-run-" so user-facing semantics are unchanged when unset.
2289
2334
  if [ "$stop_all" = true ]; then
2290
- pkill -f "loki-run-" 2>/dev/null || true
2335
+ local _stop_all_pat="${LOKI_STOP_ALL_PATTERN:-loki-run-}"
2336
+ pkill -f "$_stop_all_pat" 2>/dev/null || true
2291
2337
  sleep 0.5
2292
- pkill -9 -f "loki-run-" 2>/dev/null || true
2338
+ pkill -9 -f "$_stop_all_pat" 2>/dev/null || true
2293
2339
  echo -e "${RED}--all: signalled all loki-run-* processes on this machine.${NC}"
2294
2340
  fi
2295
2341
  }
@@ -4698,8 +4744,10 @@ cmd_web_stop() {
4698
4744
  rm -f "$pids_file" 2>/dev/null || true
4699
4745
  fi
4700
4746
 
4701
- # Global cleanup: kill ALL loki-related processes regardless of CWD
4702
- # This ensures "loki web stop" from anywhere cleans up everything
4747
+ # Companion cleanup: stop the dashboard server that the web UI runs
4748
+ # alongside. Scoped to the dashboard, NOT to every loki process: the
4749
+ # documented intent of "loki web stop" is to stop the Purple Lab web UI
4750
+ # session, not to reap unrelated builds.
4703
4751
 
4704
4752
  # Kill any dashboard server (by process name and by port)
4705
4753
  local dash_port="${LOKI_DASHBOARD_PORT:-57374}"
@@ -4724,21 +4772,14 @@ cmd_web_stop() {
4724
4772
  kill -0 "$dash_port_pid" 2>/dev/null && kill -9 "$dash_port_pid" 2>/dev/null || true
4725
4773
  fi
4726
4774
 
4727
- # Kill any loki run.sh orchestrator processes (status monitors, resource monitors)
4728
- local run_pids
4729
- run_pids=$(pgrep -f "loki-run-\|status-monitor\|resource-monitor" 2>/dev/null || true)
4730
- if [ -n "$run_pids" ]; then
4731
- for rpid in $run_pids; do
4732
- kill "$rpid" 2>/dev/null || true
4733
- done
4734
- sleep 1
4735
- for rpid in $run_pids; do
4736
- kill -0 "$rpid" 2>/dev/null && kill -9 "$rpid" 2>/dev/null || true
4737
- done
4738
- if [ "$stopped" = true ]; then
4739
- echo "Background processes cleaned up"
4740
- fi
4741
- fi
4775
+ # FIX-563 (v7.30.0): do NOT blanket-kill loki-run-* orchestrators here.
4776
+ # The prior unscoped `pgrep -f "loki-run-..."` reaped EVERY orchestrator on
4777
+ # the machine, including foreign `loki start` sessions launched from other
4778
+ # terminals/CWDs that the web UI never owned. Purple Lab's own build
4779
+ # processes are already reaped authoritatively above via child-pids.json
4780
+ # (the only PIDs this session actually started). Foreign builds survive,
4781
+ # mirroring the cwd-scoped dashboard-stop pattern (FIX rationale: a user
4782
+ # invoking "loki web stop" expects the web UI gone, not their other builds).
4742
4783
 
4743
4784
  # Clean up all PID files globally
4744
4785
  rm -f "${LOKI_DIR}/dashboard/dashboard.pid" 2>/dev/null || true
@@ -7728,15 +7769,19 @@ cmd_doctor() {
7728
7769
  doctor_check "Cline CLI" cline optional || true
7729
7770
  doctor_check "Aider CLI" aider optional || true
7730
7771
 
7731
- # Check if at least one provider is installed
7732
- local _any_provider=false
7733
- for _dp in claude codex cline aider; do
7734
- command -v "$_dp" &>/dev/null && _any_provider=true && break
7735
- done
7736
- if ! $_any_provider; then
7772
+ # Check if at least one provider is installed (detect_any_provider is the
7773
+ # shared helper from provider-offer.sh, extracted from this exact loop).
7774
+ if ! detect_any_provider; then
7737
7775
  echo -e " ${RED}FAIL${NC} No AI provider CLI installed -- at least one is required"
7738
7776
  echo -e " ${YELLOW}Install: npm install -g @anthropic-ai/claude-code${NC}"
7739
7777
  fail_count=$((fail_count + 1))
7778
+ # v7.29.0: on a TTY (non-json), append the consent-gated install offer.
7779
+ # In report mode the helper is a no-op on non-TTY/CI, so the doctor
7780
+ # output stays byte-identical for the bun-parity matrix. --json never
7781
+ # reaches here (cmd_doctor dispatches to cmd_doctor_json earlier).
7782
+ if declare -f offer_provider_install >/dev/null 2>&1; then
7783
+ offer_provider_install report || true
7784
+ fi
7740
7785
  fi
7741
7786
  echo ""
7742
7787
 
@@ -9030,6 +9075,77 @@ cmd_sandbox() {
9030
9075
  exec "$SANDBOX_SH" "$subcommand" "$@"
9031
9076
  }
9032
9077
 
9078
+ # v7.29.0 (feature #4): print the SIMPLE-tier cost/time/iteration estimate for
9079
+ # the demo PRD, parsed from the single estimator source (show_prd_plan JSON).
9080
+ # Because `loki demo` always runs `loki start --simple` (which exports
9081
+ # LOKI_COMPLEXITY=simple), the estimate is computed with LOKI_COMPLEXITY=simple
9082
+ # so the quoted figures are the ones the demo will actually incur -- honest by
9083
+ # construction (the keystone fix makes show_prd_plan honor LOKI_COMPLEXITY).
9084
+ # Returns 0 when a real estimate was printed, 1 when the estimator failed (the
9085
+ # caller then falls back to a no-number confirm). Never fabricates a number.
9086
+ emit_demo_estimate() {
9087
+ local prd_path="$1"
9088
+ # v7.30.0 (item 5): the demo estimate always prints before spending, incl.
9089
+ # non-TTY (--dry-run / --yes piped), so gate color on a TTY here too (the
9090
+ # global vars are only blanked on NO_COLOR). Mirrors provider-offer.sh and
9091
+ # quickstart.sh. Local overrides only.
9092
+ local BOLD="$BOLD" YELLOW="$YELLOW" DIM="$DIM" NC="$NC"
9093
+ if [ ! -t 1 ]; then
9094
+ BOLD=''; YELLOW=''; DIM=''; NC=''
9095
+ fi
9096
+ local plan_json=""
9097
+ plan_json=$(LOKI_COMPLEXITY=simple show_prd_plan "$prd_path" "true" "false" 2>/dev/null) || plan_json=""
9098
+
9099
+ if [ -z "$plan_json" ]; then
9100
+ echo -e "${YELLOW}Could not compute a cost estimate (the estimator did not return a result).${NC}"
9101
+ return 1
9102
+ fi
9103
+
9104
+ local parsed
9105
+ parsed=$(printf '%s' "$plan_json" | python3 -c "
9106
+ import json, sys
9107
+ try:
9108
+ d = json.load(sys.stdin)
9109
+ except Exception:
9110
+ sys.exit(1)
9111
+ cost = d.get('cost', {}).get('total_usd')
9112
+ time_est = d.get('time', {}).get('estimated')
9113
+ iters = d.get('iterations', {}).get('estimated')
9114
+ rng = d.get('iterations', {}).get('range', [])
9115
+ tier = d.get('complexity', {}).get('tier', 'simple')
9116
+ if cost is None or time_est is None or iters is None:
9117
+ sys.exit(1)
9118
+ rng_str = ''
9119
+ if isinstance(rng, list) and len(rng) == 2:
9120
+ rng_str = ' (range {}-{})'.format(rng[0], rng[1])
9121
+ print(tier.upper())
9122
+ print('{:.2f}'.format(float(cost)))
9123
+ print(time_est)
9124
+ print('{}{}'.format(iters, rng_str))
9125
+ " 2>/dev/null) || parsed=""
9126
+
9127
+ if [ -z "$parsed" ]; then
9128
+ echo -e "${YELLOW}Could not compute a cost estimate (the estimator did not return a result).${NC}"
9129
+ return 1
9130
+ fi
9131
+
9132
+ local tier_u cost_u time_u iter_u
9133
+ tier_u=$(printf '%s' "$parsed" | sed -n '1p')
9134
+ cost_u=$(printf '%s' "$parsed" | sed -n '2p')
9135
+ time_u=$(printf '%s' "$parsed" | sed -n '3p')
9136
+ iter_u=$(printf '%s' "$parsed" | sed -n '4p')
9137
+
9138
+ echo -e "${BOLD}Estimate (${tier_u} tier, the path this demo actually runs):${NC}"
9139
+ printf ' Cost: ~$%s\n' "$cost_u"
9140
+ echo " Time: ~$time_u"
9141
+ echo " Iterations: $iter_u"
9142
+ echo ""
9143
+ echo -e "${DIM}This is an estimate. Actual usage depends on PRD complexity,"
9144
+ echo -e "code review cycles, and test failures.${NC}"
9145
+ echo ""
9146
+ return 0
9147
+ }
9148
+
9033
9149
  # Demo mode - build a real project from a bundled PRD template
9034
9150
  cmd_demo() {
9035
9151
  # Handle --help
@@ -9055,6 +9171,16 @@ cmd_demo() {
9055
9171
  return 0
9056
9172
  fi
9057
9173
 
9174
+ # v7.30.0 (item 5): demo emits user-facing color (header, dry-run block,
9175
+ # cancel/refuse messages) on paths that always run even when piped/non-TTY,
9176
+ # so gate color on a TTY here (the global vars are only blanked on NO_COLOR).
9177
+ # Placed AFTER the --help early return so help output is untouched. Local
9178
+ # overrides only; mirrors provider-offer.sh / quickstart.sh.
9179
+ local BOLD="$BOLD" CYAN="$CYAN" YELLOW="$YELLOW" GREEN="$GREEN" DIM="$DIM" RED="$RED" NC="$NC"
9180
+ if [ ! -t 1 ]; then
9181
+ BOLD=''; CYAN=''; YELLOW=''; GREEN=''; DIM=''; RED=''; NC=''
9182
+ fi
9183
+
9058
9184
  local version
9059
9185
  version=$(get_version)
9060
9186
  local demo_prd="$SKILL_DIR/templates/simple-todo-app.md"
@@ -9062,10 +9188,21 @@ cmd_demo() {
9062
9188
  local provider=""
9063
9189
  local dry_run=false
9064
9190
  local start_args=()
9191
+ # v7.29.0 (feature #4): demo now shows the FORCED-simple cost estimate
9192
+ # before spending and asks for confirmation. --yes / LOKI_ASSUME_YES skip
9193
+ # the prompt but still print the estimate (the spend is never hidden).
9194
+ local assume_yes=false
9195
+ if [ "${LOKI_ASSUME_YES:-}" = "1" ] || [ "${LOKI_ASSUME_YES:-}" = "true" ]; then
9196
+ assume_yes=true
9197
+ fi
9065
9198
 
9066
9199
  # Parse arguments
9067
9200
  while [[ $# -gt 0 ]]; do
9068
9201
  case "$1" in
9202
+ --yes|-y)
9203
+ assume_yes=true
9204
+ shift
9205
+ ;;
9069
9206
  --dir)
9070
9207
  if [[ -z "${2:-}" ]]; then
9071
9208
  echo -e "${RED}Error: --dir requires a path${NC}"
@@ -9106,6 +9243,17 @@ cmd_demo() {
9106
9243
  esac
9107
9244
  done
9108
9245
 
9246
+ # v7.29.0: provider pre-flight gate (skipped for --dry-run, which never
9247
+ # spends). On a TTY, offer to install; on non-TTY/CI, print honest manual
9248
+ # instructions and exit 2 before any spend. Detect-first, so the nested
9249
+ # cmd_start gate below no-ops once a provider is present. --help already
9250
+ # returned at the top of cmd_demo.
9251
+ if [ "$dry_run" != true ] && declare -f provider_offer_gate >/dev/null 2>&1; then
9252
+ if ! provider_offer_gate; then
9253
+ exit 2
9254
+ fi
9255
+ fi
9256
+
9109
9257
  # Fall back to examples/ if templates/ doesn't exist
9110
9258
  if [ ! -f "$demo_prd" ]; then
9111
9259
  demo_prd="$SKILL_DIR/examples/simple-todo-app.md"
@@ -9146,6 +9294,9 @@ cmd_demo() {
9146
9294
  fi
9147
9295
 
9148
9296
  if [ "$dry_run" = true ]; then
9297
+ # v7.29.0: dry-run is now a genuine cost preview -- show the estimate,
9298
+ # no prompt, no spend.
9299
+ emit_demo_estimate "$demo_dir/prd.md" || true
9149
9300
  echo -e "${YELLOW}[dry-run] Would run:${NC}"
9150
9301
  echo " cd $demo_dir"
9151
9302
  echo -n " loki start prd.md --simple --yes"
@@ -9156,6 +9307,52 @@ cmd_demo() {
9156
9307
  return 0
9157
9308
  fi
9158
9309
 
9310
+ # v7.29.0 (feature #4): cost confirm before any spend. The estimate ALWAYS
9311
+ # prints (even with --yes), so the spend is never hidden. The prompt is
9312
+ # shown only on a TTY without --yes; non-TTY without --yes refuses (exit 2)
9313
+ # rather than hanging on read -- the project's "never hang in CI" idiom.
9314
+ local estimate_ok=true
9315
+ emit_demo_estimate "$demo_dir/prd.md" || estimate_ok=false
9316
+
9317
+ # Interactive only when stdin is a real TTY and CI is not forcing
9318
+ # non-interactive (matches the project's first-run gate semantics).
9319
+ local demo_interactive=true
9320
+ if [ ! -t 0 ] || [ -n "${CI:-}" ]; then
9321
+ demo_interactive=false
9322
+ fi
9323
+
9324
+ if [ "$assume_yes" != true ]; then
9325
+ if [ "$demo_interactive" = true ]; then
9326
+ local demo_answer=""
9327
+ if [ "$estimate_ok" = true ]; then
9328
+ # Default YES: Enter proceeds.
9329
+ echo -n "Build the Todo app now? [Y/n] "
9330
+ read -r demo_answer </dev/tty 2>/dev/null || demo_answer=""
9331
+ if [[ -n "$demo_answer" && ! "$demo_answer" =~ ^[Yy] ]]; then
9332
+ echo ""
9333
+ echo -e "Cancelled. Nothing was spent. The demo PRD is at:"
9334
+ echo -e " ${CYAN}$demo_dir/prd.md${NC}"
9335
+ echo -e "Run 'loki plan ${demo_dir}/prd.md' for the full breakdown."
9336
+ return 0
9337
+ fi
9338
+ else
9339
+ # No honest number available: default NO (the safe direction).
9340
+ echo -n "Proceed with the demo build anyway? [y/N] "
9341
+ read -r demo_answer </dev/tty 2>/dev/null || demo_answer=""
9342
+ if [[ ! "$demo_answer" =~ ^[Yy] ]]; then
9343
+ echo ""
9344
+ echo -e "Cancelled. Nothing was spent. The demo PRD is at:"
9345
+ echo -e " ${CYAN}$demo_dir/prd.md${NC}"
9346
+ return 0
9347
+ fi
9348
+ fi
9349
+ else
9350
+ # Non-TTY without --yes: never hang; refuse with an honest message.
9351
+ echo "demo needs confirmation; re-run with --yes to proceed non-interactively" >&2
9352
+ return 2
9353
+ fi
9354
+ fi
9355
+
9159
9356
  echo -e "${DIM}Starting autonomous build...${NC}"
9160
9357
  echo -e "${DIM}(Press Ctrl+C to stop at any time)${NC}"
9161
9358
  echo ""
@@ -9380,6 +9577,15 @@ cmd_quick() {
9380
9577
  exit 2
9381
9578
  fi
9382
9579
 
9580
+ # v7.29.0: provider pre-flight gate. Offer install on a TTY; print honest
9581
+ # manual instructions and exit 2 on non-TTY/CI, before any spend. --help and
9582
+ # the no-args usage path already returned/exited above.
9583
+ if declare -f provider_offer_gate >/dev/null 2>&1; then
9584
+ if ! provider_offer_gate; then
9585
+ exit 2
9586
+ fi
9587
+ fi
9588
+
9383
9589
  local task_desc="$*"
9384
9590
  local version=$(get_version)
9385
9591
  local max_iter="${LOKI_MAX_ITERATIONS:-3}"
@@ -11451,6 +11657,28 @@ cmd_grill() {
11451
11657
  return $?
11452
11658
  }
11453
11659
 
11660
+ # ---------------------------------------------------------------------------
11661
+ # loki mcp - launch the MCP (Model Context Protocol) server
11662
+ #
11663
+ # Thin dispatcher that sources autonomy/mcp-launch.sh and delegates to
11664
+ # mcp_launch_main(). The launcher checks for python3 + the MCP SDK and, when
11665
+ # the SDK is missing, offers a consent-gated bootstrap into a project-local
11666
+ # virtualenv (.loki/mcp-venv) before exec'ing the server over stdio. This
11667
+ # closes the fresh-npm-consumer gap where the only bin was `loki` and the
11668
+ # Python MCP dependencies were never installed (task 562).
11669
+ # ---------------------------------------------------------------------------
11670
+ cmd_mcp() {
11671
+ local mcp_mod="$_LOKI_SCRIPT_DIR/mcp-launch.sh"
11672
+ if [ ! -f "$mcp_mod" ]; then
11673
+ echo -e "${RED}Error: mcp launcher not found at $mcp_mod${NC}" >&2
11674
+ return 3
11675
+ fi
11676
+ # shellcheck source=/dev/null
11677
+ source "$mcp_mod"
11678
+ mcp_launch_main "$@"
11679
+ return $?
11680
+ }
11681
+
11454
11682
  cmd_heal_help() {
11455
11683
  echo -e "${BOLD}loki heal${NC} - Legacy system healing (v6.67.0)"
11456
11684
  echo ""
@@ -12833,6 +13061,20 @@ show_verbose = sys.argv[3] == 'true'
12833
13061
  session_model_env = (os.environ.get('LOKI_SESSION_MODEL', 'sonnet') or 'sonnet').strip().lower()
12834
13062
  legacy_tier_switching = (os.environ.get('LOKI_LEGACY_TIER_SWITCHING', 'false') or 'false').strip().lower() == 'true'
12835
13063
 
13064
+ # v7.29.0: Honor LOKI_COMPLEXITY -- the SAME env var the runner honors at
13065
+ # run.sh:920 -- so a forced-tier run (e.g. 'loki demo' / 'loki start --simple'
13066
+ # which export LOKI_COMPLEXITY=simple) quotes the tier it will actually run,
13067
+ # not the content-derived tier. Without this the estimate diverges from
13068
+ # execution (audit finding #9). Runner vocabulary is auto|simple|standard|complex;
13069
+ # the estimator's internal tiers are simple|moderate|complex|enterprise, so
13070
+ # 'standard' aliases to 'moderate' (the 6-12 iteration analog). 'auto', empty,
13071
+ # and any unrecognized value are ignored (content tier is used) -- honest by
13072
+ # construction. The override is applied AFTER the content score-to-tier map.
13073
+ _forced_complexity_raw = (os.environ.get('LOKI_COMPLEXITY', '') or '').strip().lower()
13074
+ forced_complexity = {'standard': 'moderate'}.get(_forced_complexity_raw, _forced_complexity_raw)
13075
+ if forced_complexity not in ('simple', 'moderate', 'complex', 'enterprise'):
13076
+ forced_complexity = ''
13077
+
12836
13078
  # Map session model name to tier key used in tokens_per_tier below.
12837
13079
  # Unknown models fall through to 'development' (Sonnet) as a safe default.
12838
13080
  _session_tier_map = {
@@ -12985,6 +13227,14 @@ elif complexity_score <= 5:
12985
13227
  else:
12986
13228
  complexity = 'enterprise'
12987
13229
 
13230
+ # v7.29.0: apply the forced-tier override AFTER the content score-to-tier map,
13231
+ # so it is not clobbered by the assignment above. Everything downstream
13232
+ # (iterations, tokens, cost, time) derives from the complexity variable, so the
13233
+ # whole estimate becomes consistent with what the run will actually do.
13234
+ if forced_complexity:
13235
+ complexity = forced_complexity
13236
+ complexity_reasons.insert(0, 'Complexity forced via LOKI_COMPLEXITY=' + _forced_complexity_raw)
13237
+
12988
13238
  # --- Estimate iterations ---
12989
13239
  iteration_map = {
12990
13240
  'simple': (3, 5),
@@ -13215,7 +13465,10 @@ if ui_count > 0:
13215
13465
  color = {'simple': GREEN, 'moderate': YELLOW, 'complex': RED, 'enterprise': RED}
13216
13466
  cx_color = color.get(complexity, NC)
13217
13467
  print(f'\n{CYAN}Complexity{NC}')
13218
- print(f' Tier: {cx_color}{BOLD}{complexity.upper()}{NC} (score: {complexity_score})')
13468
+ if forced_complexity:
13469
+ print(f' Tier: {cx_color}{BOLD}{complexity.upper()}{NC} (forced via LOKI_COMPLEXITY={_forced_complexity_raw})')
13470
+ else:
13471
+ print(f' Tier: {cx_color}{BOLD}{complexity.upper()}{NC} (score: {complexity_score})')
13219
13472
  for reason in complexity_reasons:
13220
13473
  print(f' {DIM}- {reason}{NC}')
13221
13474
 
@@ -13446,6 +13699,9 @@ main() {
13446
13699
  demo)
13447
13700
  cmd_demo "$@"
13448
13701
  ;;
13702
+ quickstart)
13703
+ cmd_quickstart "$@"
13704
+ ;;
13449
13705
  welcome)
13450
13706
  cmd_welcome "$@"
13451
13707
  ;;
@@ -13609,6 +13865,9 @@ main() {
13609
13865
  grill)
13610
13866
  cmd_grill "$@"
13611
13867
  ;;
13868
+ mcp)
13869
+ cmd_mcp "$@"
13870
+ ;;
13612
13871
  migrate)
13613
13872
  cmd_migrate "$@"
13614
13873
  ;;