loki-mode 7.28.2 → 7.29.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 +3 -2
- package/SKILL.md +8 -2
- package/VERSION +1 -1
- package/autonomy/loki +222 -9
- package/autonomy/provider-offer.sh +249 -0
- package/autonomy/quickstart.sh +584 -0
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +10 -1
- package/loki-ts/dist/loki.js +208 -208
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,7 @@
|
|
|
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).
|
|
@@ -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.
|
|
105
|
+
| **Docker** | `docker pull asklokesh/loki-mode:7.29.0 && docker run --rm asklokesh/loki-mode:7.29.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.
|
|
165
|
+
| **Docker** | `docker pull asklokesh/loki-mode:7.29.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.
|
|
6
|
+
# Loki Mode v7.29.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.
|
|
401
|
+
**v7.29.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.29.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)"
|
|
@@ -1260,6 +1279,17 @@ cmd_start() {
|
|
|
1260
1279
|
esac
|
|
1261
1280
|
done
|
|
1262
1281
|
|
|
1282
|
+
# v7.29.0: provider pre-flight gate. If no provider CLI is installed, offer
|
|
1283
|
+
# to install one (TTY) or print honest manual instructions and exit 2
|
|
1284
|
+
# (non-TTY/CI), BEFORE any spend or runner entry. This moves what used to be
|
|
1285
|
+
# an opaque deep-runner failure to a friendly, actionable top-of-run gate.
|
|
1286
|
+
# --help already returned inside the arg loop, so we never gate help.
|
|
1287
|
+
if declare -f provider_offer_gate >/dev/null 2>&1; then
|
|
1288
|
+
if ! provider_offer_gate; then
|
|
1289
|
+
exit 2
|
|
1290
|
+
fi
|
|
1291
|
+
fi
|
|
1292
|
+
|
|
1263
1293
|
# Clear any stale raw-brief marker from a PRIOR run before we decide the
|
|
1264
1294
|
# mode of THIS run. Only the brief branch (below) re-writes it. Without this,
|
|
1265
1295
|
# a brief run leaves .loki/state/brief.txt behind and a later non-brief run
|
|
@@ -2286,10 +2316,18 @@ PYDASH
|
|
|
2286
2316
|
# v7.7.30: --all preserves the legacy machine-wide kill. It runs even when
|
|
2287
2317
|
# the current folder has no live session (the "clean everything" use case),
|
|
2288
2318
|
# reaping every folder's orphaned loki-run-* temp script (SIGTERM, SIGKILL).
|
|
2319
|
+
#
|
|
2320
|
+
# The kill pattern is "loki-run-" for real users and is NOT user-overridable
|
|
2321
|
+
# at the CLI -- that is the documented machine-wide behavior. LOKI_STOP_ALL_PATTERN
|
|
2322
|
+
# exists ONLY so the test suite can exercise this exact code path against its
|
|
2323
|
+
# own uniquely-marked fake runners without SIGKILLing an unrelated live
|
|
2324
|
+
# loki-run-* on the same machine (e.g. a long SWE-bench instance). It defaults
|
|
2325
|
+
# to "loki-run-" so user-facing semantics are unchanged when unset.
|
|
2289
2326
|
if [ "$stop_all" = true ]; then
|
|
2290
|
-
|
|
2327
|
+
local _stop_all_pat="${LOKI_STOP_ALL_PATTERN:-loki-run-}"
|
|
2328
|
+
pkill -f "$_stop_all_pat" 2>/dev/null || true
|
|
2291
2329
|
sleep 0.5
|
|
2292
|
-
pkill -9 -f "
|
|
2330
|
+
pkill -9 -f "$_stop_all_pat" 2>/dev/null || true
|
|
2293
2331
|
echo -e "${RED}--all: signalled all loki-run-* processes on this machine.${NC}"
|
|
2294
2332
|
fi
|
|
2295
2333
|
}
|
|
@@ -7728,15 +7766,19 @@ cmd_doctor() {
|
|
|
7728
7766
|
doctor_check "Cline CLI" cline optional || true
|
|
7729
7767
|
doctor_check "Aider CLI" aider optional || true
|
|
7730
7768
|
|
|
7731
|
-
# Check if at least one provider is installed
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
command -v "$_dp" &>/dev/null && _any_provider=true && break
|
|
7735
|
-
done
|
|
7736
|
-
if ! $_any_provider; then
|
|
7769
|
+
# Check if at least one provider is installed (detect_any_provider is the
|
|
7770
|
+
# shared helper from provider-offer.sh, extracted from this exact loop).
|
|
7771
|
+
if ! detect_any_provider; then
|
|
7737
7772
|
echo -e " ${RED}FAIL${NC} No AI provider CLI installed -- at least one is required"
|
|
7738
7773
|
echo -e " ${YELLOW}Install: npm install -g @anthropic-ai/claude-code${NC}"
|
|
7739
7774
|
fail_count=$((fail_count + 1))
|
|
7775
|
+
# v7.29.0: on a TTY (non-json), append the consent-gated install offer.
|
|
7776
|
+
# In report mode the helper is a no-op on non-TTY/CI, so the doctor
|
|
7777
|
+
# output stays byte-identical for the bun-parity matrix. --json never
|
|
7778
|
+
# reaches here (cmd_doctor dispatches to cmd_doctor_json earlier).
|
|
7779
|
+
if declare -f offer_provider_install >/dev/null 2>&1; then
|
|
7780
|
+
offer_provider_install report || true
|
|
7781
|
+
fi
|
|
7740
7782
|
fi
|
|
7741
7783
|
echo ""
|
|
7742
7784
|
|
|
@@ -9030,6 +9072,69 @@ cmd_sandbox() {
|
|
|
9030
9072
|
exec "$SANDBOX_SH" "$subcommand" "$@"
|
|
9031
9073
|
}
|
|
9032
9074
|
|
|
9075
|
+
# v7.29.0 (feature #4): print the SIMPLE-tier cost/time/iteration estimate for
|
|
9076
|
+
# the demo PRD, parsed from the single estimator source (show_prd_plan JSON).
|
|
9077
|
+
# Because `loki demo` always runs `loki start --simple` (which exports
|
|
9078
|
+
# LOKI_COMPLEXITY=simple), the estimate is computed with LOKI_COMPLEXITY=simple
|
|
9079
|
+
# so the quoted figures are the ones the demo will actually incur -- honest by
|
|
9080
|
+
# construction (the keystone fix makes show_prd_plan honor LOKI_COMPLEXITY).
|
|
9081
|
+
# Returns 0 when a real estimate was printed, 1 when the estimator failed (the
|
|
9082
|
+
# caller then falls back to a no-number confirm). Never fabricates a number.
|
|
9083
|
+
emit_demo_estimate() {
|
|
9084
|
+
local prd_path="$1"
|
|
9085
|
+
local plan_json=""
|
|
9086
|
+
plan_json=$(LOKI_COMPLEXITY=simple show_prd_plan "$prd_path" "true" "false" 2>/dev/null) || plan_json=""
|
|
9087
|
+
|
|
9088
|
+
if [ -z "$plan_json" ]; then
|
|
9089
|
+
echo -e "${YELLOW}Could not compute a cost estimate (the estimator did not return a result).${NC}"
|
|
9090
|
+
return 1
|
|
9091
|
+
fi
|
|
9092
|
+
|
|
9093
|
+
local parsed
|
|
9094
|
+
parsed=$(printf '%s' "$plan_json" | python3 -c "
|
|
9095
|
+
import json, sys
|
|
9096
|
+
try:
|
|
9097
|
+
d = json.load(sys.stdin)
|
|
9098
|
+
except Exception:
|
|
9099
|
+
sys.exit(1)
|
|
9100
|
+
cost = d.get('cost', {}).get('total_usd')
|
|
9101
|
+
time_est = d.get('time', {}).get('estimated')
|
|
9102
|
+
iters = d.get('iterations', {}).get('estimated')
|
|
9103
|
+
rng = d.get('iterations', {}).get('range', [])
|
|
9104
|
+
tier = d.get('complexity', {}).get('tier', 'simple')
|
|
9105
|
+
if cost is None or time_est is None or iters is None:
|
|
9106
|
+
sys.exit(1)
|
|
9107
|
+
rng_str = ''
|
|
9108
|
+
if isinstance(rng, list) and len(rng) == 2:
|
|
9109
|
+
rng_str = ' (range {}-{})'.format(rng[0], rng[1])
|
|
9110
|
+
print(tier.upper())
|
|
9111
|
+
print('{:.2f}'.format(float(cost)))
|
|
9112
|
+
print(time_est)
|
|
9113
|
+
print('{}{}'.format(iters, rng_str))
|
|
9114
|
+
" 2>/dev/null) || parsed=""
|
|
9115
|
+
|
|
9116
|
+
if [ -z "$parsed" ]; then
|
|
9117
|
+
echo -e "${YELLOW}Could not compute a cost estimate (the estimator did not return a result).${NC}"
|
|
9118
|
+
return 1
|
|
9119
|
+
fi
|
|
9120
|
+
|
|
9121
|
+
local tier_u cost_u time_u iter_u
|
|
9122
|
+
tier_u=$(printf '%s' "$parsed" | sed -n '1p')
|
|
9123
|
+
cost_u=$(printf '%s' "$parsed" | sed -n '2p')
|
|
9124
|
+
time_u=$(printf '%s' "$parsed" | sed -n '3p')
|
|
9125
|
+
iter_u=$(printf '%s' "$parsed" | sed -n '4p')
|
|
9126
|
+
|
|
9127
|
+
echo -e "${BOLD}Estimate (${tier_u} tier, the path this demo actually runs):${NC}"
|
|
9128
|
+
printf ' Cost: ~$%s\n' "$cost_u"
|
|
9129
|
+
echo " Time: ~$time_u"
|
|
9130
|
+
echo " Iterations: $iter_u"
|
|
9131
|
+
echo ""
|
|
9132
|
+
echo -e "${DIM}This is an estimate. Actual usage depends on PRD complexity,"
|
|
9133
|
+
echo -e "code review cycles, and test failures.${NC}"
|
|
9134
|
+
echo ""
|
|
9135
|
+
return 0
|
|
9136
|
+
}
|
|
9137
|
+
|
|
9033
9138
|
# Demo mode - build a real project from a bundled PRD template
|
|
9034
9139
|
cmd_demo() {
|
|
9035
9140
|
# Handle --help
|
|
@@ -9062,10 +9167,21 @@ cmd_demo() {
|
|
|
9062
9167
|
local provider=""
|
|
9063
9168
|
local dry_run=false
|
|
9064
9169
|
local start_args=()
|
|
9170
|
+
# v7.29.0 (feature #4): demo now shows the FORCED-simple cost estimate
|
|
9171
|
+
# before spending and asks for confirmation. --yes / LOKI_ASSUME_YES skip
|
|
9172
|
+
# the prompt but still print the estimate (the spend is never hidden).
|
|
9173
|
+
local assume_yes=false
|
|
9174
|
+
if [ "${LOKI_ASSUME_YES:-}" = "1" ] || [ "${LOKI_ASSUME_YES:-}" = "true" ]; then
|
|
9175
|
+
assume_yes=true
|
|
9176
|
+
fi
|
|
9065
9177
|
|
|
9066
9178
|
# Parse arguments
|
|
9067
9179
|
while [[ $# -gt 0 ]]; do
|
|
9068
9180
|
case "$1" in
|
|
9181
|
+
--yes|-y)
|
|
9182
|
+
assume_yes=true
|
|
9183
|
+
shift
|
|
9184
|
+
;;
|
|
9069
9185
|
--dir)
|
|
9070
9186
|
if [[ -z "${2:-}" ]]; then
|
|
9071
9187
|
echo -e "${RED}Error: --dir requires a path${NC}"
|
|
@@ -9106,6 +9222,17 @@ cmd_demo() {
|
|
|
9106
9222
|
esac
|
|
9107
9223
|
done
|
|
9108
9224
|
|
|
9225
|
+
# v7.29.0: provider pre-flight gate (skipped for --dry-run, which never
|
|
9226
|
+
# spends). On a TTY, offer to install; on non-TTY/CI, print honest manual
|
|
9227
|
+
# instructions and exit 2 before any spend. Detect-first, so the nested
|
|
9228
|
+
# cmd_start gate below no-ops once a provider is present. --help already
|
|
9229
|
+
# returned at the top of cmd_demo.
|
|
9230
|
+
if [ "$dry_run" != true ] && declare -f provider_offer_gate >/dev/null 2>&1; then
|
|
9231
|
+
if ! provider_offer_gate; then
|
|
9232
|
+
exit 2
|
|
9233
|
+
fi
|
|
9234
|
+
fi
|
|
9235
|
+
|
|
9109
9236
|
# Fall back to examples/ if templates/ doesn't exist
|
|
9110
9237
|
if [ ! -f "$demo_prd" ]; then
|
|
9111
9238
|
demo_prd="$SKILL_DIR/examples/simple-todo-app.md"
|
|
@@ -9146,6 +9273,9 @@ cmd_demo() {
|
|
|
9146
9273
|
fi
|
|
9147
9274
|
|
|
9148
9275
|
if [ "$dry_run" = true ]; then
|
|
9276
|
+
# v7.29.0: dry-run is now a genuine cost preview -- show the estimate,
|
|
9277
|
+
# no prompt, no spend.
|
|
9278
|
+
emit_demo_estimate "$demo_dir/prd.md" || true
|
|
9149
9279
|
echo -e "${YELLOW}[dry-run] Would run:${NC}"
|
|
9150
9280
|
echo " cd $demo_dir"
|
|
9151
9281
|
echo -n " loki start prd.md --simple --yes"
|
|
@@ -9156,6 +9286,52 @@ cmd_demo() {
|
|
|
9156
9286
|
return 0
|
|
9157
9287
|
fi
|
|
9158
9288
|
|
|
9289
|
+
# v7.29.0 (feature #4): cost confirm before any spend. The estimate ALWAYS
|
|
9290
|
+
# prints (even with --yes), so the spend is never hidden. The prompt is
|
|
9291
|
+
# shown only on a TTY without --yes; non-TTY without --yes refuses (exit 2)
|
|
9292
|
+
# rather than hanging on read -- the project's "never hang in CI" idiom.
|
|
9293
|
+
local estimate_ok=true
|
|
9294
|
+
emit_demo_estimate "$demo_dir/prd.md" || estimate_ok=false
|
|
9295
|
+
|
|
9296
|
+
# Interactive only when stdin is a real TTY and CI is not forcing
|
|
9297
|
+
# non-interactive (matches the project's first-run gate semantics).
|
|
9298
|
+
local demo_interactive=true
|
|
9299
|
+
if [ ! -t 0 ] || [ -n "${CI:-}" ]; then
|
|
9300
|
+
demo_interactive=false
|
|
9301
|
+
fi
|
|
9302
|
+
|
|
9303
|
+
if [ "$assume_yes" != true ]; then
|
|
9304
|
+
if [ "$demo_interactive" = true ]; then
|
|
9305
|
+
local demo_answer=""
|
|
9306
|
+
if [ "$estimate_ok" = true ]; then
|
|
9307
|
+
# Default YES: Enter proceeds.
|
|
9308
|
+
echo -n "Build the Todo app now? [Y/n] "
|
|
9309
|
+
read -r demo_answer </dev/tty 2>/dev/null || demo_answer=""
|
|
9310
|
+
if [[ -n "$demo_answer" && ! "$demo_answer" =~ ^[Yy] ]]; then
|
|
9311
|
+
echo ""
|
|
9312
|
+
echo -e "Cancelled. Nothing was spent. The demo PRD is at:"
|
|
9313
|
+
echo -e " ${CYAN}$demo_dir/prd.md${NC}"
|
|
9314
|
+
echo -e "Run 'loki plan ${demo_dir}/prd.md' for the full breakdown."
|
|
9315
|
+
return 0
|
|
9316
|
+
fi
|
|
9317
|
+
else
|
|
9318
|
+
# No honest number available: default NO (the safe direction).
|
|
9319
|
+
echo -n "Proceed with the demo build anyway? [y/N] "
|
|
9320
|
+
read -r demo_answer </dev/tty 2>/dev/null || demo_answer=""
|
|
9321
|
+
if [[ ! "$demo_answer" =~ ^[Yy] ]]; then
|
|
9322
|
+
echo ""
|
|
9323
|
+
echo -e "Cancelled. Nothing was spent. The demo PRD is at:"
|
|
9324
|
+
echo -e " ${CYAN}$demo_dir/prd.md${NC}"
|
|
9325
|
+
return 0
|
|
9326
|
+
fi
|
|
9327
|
+
fi
|
|
9328
|
+
else
|
|
9329
|
+
# Non-TTY without --yes: never hang; refuse with an honest message.
|
|
9330
|
+
echo "demo needs confirmation; re-run with --yes to proceed non-interactively" >&2
|
|
9331
|
+
return 2
|
|
9332
|
+
fi
|
|
9333
|
+
fi
|
|
9334
|
+
|
|
9159
9335
|
echo -e "${DIM}Starting autonomous build...${NC}"
|
|
9160
9336
|
echo -e "${DIM}(Press Ctrl+C to stop at any time)${NC}"
|
|
9161
9337
|
echo ""
|
|
@@ -9380,6 +9556,15 @@ cmd_quick() {
|
|
|
9380
9556
|
exit 2
|
|
9381
9557
|
fi
|
|
9382
9558
|
|
|
9559
|
+
# v7.29.0: provider pre-flight gate. Offer install on a TTY; print honest
|
|
9560
|
+
# manual instructions and exit 2 on non-TTY/CI, before any spend. --help and
|
|
9561
|
+
# the no-args usage path already returned/exited above.
|
|
9562
|
+
if declare -f provider_offer_gate >/dev/null 2>&1; then
|
|
9563
|
+
if ! provider_offer_gate; then
|
|
9564
|
+
exit 2
|
|
9565
|
+
fi
|
|
9566
|
+
fi
|
|
9567
|
+
|
|
9383
9568
|
local task_desc="$*"
|
|
9384
9569
|
local version=$(get_version)
|
|
9385
9570
|
local max_iter="${LOKI_MAX_ITERATIONS:-3}"
|
|
@@ -12833,6 +13018,20 @@ show_verbose = sys.argv[3] == 'true'
|
|
|
12833
13018
|
session_model_env = (os.environ.get('LOKI_SESSION_MODEL', 'sonnet') or 'sonnet').strip().lower()
|
|
12834
13019
|
legacy_tier_switching = (os.environ.get('LOKI_LEGACY_TIER_SWITCHING', 'false') or 'false').strip().lower() == 'true'
|
|
12835
13020
|
|
|
13021
|
+
# v7.29.0: Honor LOKI_COMPLEXITY -- the SAME env var the runner honors at
|
|
13022
|
+
# run.sh:920 -- so a forced-tier run (e.g. 'loki demo' / 'loki start --simple'
|
|
13023
|
+
# which export LOKI_COMPLEXITY=simple) quotes the tier it will actually run,
|
|
13024
|
+
# not the content-derived tier. Without this the estimate diverges from
|
|
13025
|
+
# execution (audit finding #9). Runner vocabulary is auto|simple|standard|complex;
|
|
13026
|
+
# the estimator's internal tiers are simple|moderate|complex|enterprise, so
|
|
13027
|
+
# 'standard' aliases to 'moderate' (the 6-12 iteration analog). 'auto', empty,
|
|
13028
|
+
# and any unrecognized value are ignored (content tier is used) -- honest by
|
|
13029
|
+
# construction. The override is applied AFTER the content score-to-tier map.
|
|
13030
|
+
_forced_complexity_raw = (os.environ.get('LOKI_COMPLEXITY', '') or '').strip().lower()
|
|
13031
|
+
forced_complexity = {'standard': 'moderate'}.get(_forced_complexity_raw, _forced_complexity_raw)
|
|
13032
|
+
if forced_complexity not in ('simple', 'moderate', 'complex', 'enterprise'):
|
|
13033
|
+
forced_complexity = ''
|
|
13034
|
+
|
|
12836
13035
|
# Map session model name to tier key used in tokens_per_tier below.
|
|
12837
13036
|
# Unknown models fall through to 'development' (Sonnet) as a safe default.
|
|
12838
13037
|
_session_tier_map = {
|
|
@@ -12985,6 +13184,14 @@ elif complexity_score <= 5:
|
|
|
12985
13184
|
else:
|
|
12986
13185
|
complexity = 'enterprise'
|
|
12987
13186
|
|
|
13187
|
+
# v7.29.0: apply the forced-tier override AFTER the content score-to-tier map,
|
|
13188
|
+
# so it is not clobbered by the assignment above. Everything downstream
|
|
13189
|
+
# (iterations, tokens, cost, time) derives from the complexity variable, so the
|
|
13190
|
+
# whole estimate becomes consistent with what the run will actually do.
|
|
13191
|
+
if forced_complexity:
|
|
13192
|
+
complexity = forced_complexity
|
|
13193
|
+
complexity_reasons.insert(0, 'Complexity forced via LOKI_COMPLEXITY=' + _forced_complexity_raw)
|
|
13194
|
+
|
|
12988
13195
|
# --- Estimate iterations ---
|
|
12989
13196
|
iteration_map = {
|
|
12990
13197
|
'simple': (3, 5),
|
|
@@ -13215,7 +13422,10 @@ if ui_count > 0:
|
|
|
13215
13422
|
color = {'simple': GREEN, 'moderate': YELLOW, 'complex': RED, 'enterprise': RED}
|
|
13216
13423
|
cx_color = color.get(complexity, NC)
|
|
13217
13424
|
print(f'\n{CYAN}Complexity{NC}')
|
|
13218
|
-
|
|
13425
|
+
if forced_complexity:
|
|
13426
|
+
print(f' Tier: {cx_color}{BOLD}{complexity.upper()}{NC} (forced via LOKI_COMPLEXITY={_forced_complexity_raw})')
|
|
13427
|
+
else:
|
|
13428
|
+
print(f' Tier: {cx_color}{BOLD}{complexity.upper()}{NC} (score: {complexity_score})')
|
|
13219
13429
|
for reason in complexity_reasons:
|
|
13220
13430
|
print(f' {DIM}- {reason}{NC}')
|
|
13221
13431
|
|
|
@@ -13446,6 +13656,9 @@ main() {
|
|
|
13446
13656
|
demo)
|
|
13447
13657
|
cmd_demo "$@"
|
|
13448
13658
|
;;
|
|
13659
|
+
quickstart)
|
|
13660
|
+
cmd_quickstart "$@"
|
|
13661
|
+
;;
|
|
13449
13662
|
welcome)
|
|
13450
13663
|
cmd_welcome "$@"
|
|
13451
13664
|
;;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# provider-offer.sh -- shared, self-contained provider install offer (v7.29.0).
|
|
3
|
+
#
|
|
4
|
+
# Single source of truth for "no AI provider CLI found" handling, used by BOTH
|
|
5
|
+
# the bash CLI (autonomy/loki, sourced) and the Bun-routed doctor
|
|
6
|
+
# (loki-ts/src/commands/doctor.ts, via child_process). Parity is by
|
|
7
|
+
# construction: there is exactly one prompt + npm install + login handoff
|
|
8
|
+
# implementation, and both routes call it.
|
|
9
|
+
#
|
|
10
|
+
# Self-containment contract (load-bearing for parity): this file depends ONLY
|
|
11
|
+
# on bash builtins + npm/claude on PATH. It defines its own colors and never
|
|
12
|
+
# reads $RED/$NC or any other variable owned by autonomy/loki, because when
|
|
13
|
+
# doctor.ts spawns it standalone those variables are unset. If this file ever
|
|
14
|
+
# starts depending on loki's environment, the bash-route and Bun-route bytes
|
|
15
|
+
# diverge and the bun-parity matrix breaks.
|
|
16
|
+
#
|
|
17
|
+
# Security posture (design 1.7): the ONLY command ever executed on the user's
|
|
18
|
+
# behalf is `npm install -g @anthropic-ai/claude-code`, only after explicit
|
|
19
|
+
# consent, with the exact command printed first. No sudo. No curl-pipe-bash.
|
|
20
|
+
# Non-interactive / CI contexts never run an install.
|
|
21
|
+
|
|
22
|
+
# Guard against double-source (loki may source this more than once via reloads).
|
|
23
|
+
if [ -n "${_LOKI_PROVIDER_OFFER_SOURCED:-}" ]; then
|
|
24
|
+
return 0 2>/dev/null || true
|
|
25
|
+
fi
|
|
26
|
+
_LOKI_PROVIDER_OFFER_SOURCED=1
|
|
27
|
+
|
|
28
|
+
# --- Self-contained colors (honor NO_COLOR; no dependency on loki) ----------
|
|
29
|
+
if [ -n "${NO_COLOR:-}" ] || [ ! -t 1 ]; then
|
|
30
|
+
_PO_RED=''; _PO_YELLOW=''; _PO_BOLD=''; _PO_DIM=''; _PO_NC=''
|
|
31
|
+
else
|
|
32
|
+
_PO_RED=$'\033[0;31m'
|
|
33
|
+
_PO_YELLOW=$'\033[1;33m'
|
|
34
|
+
_PO_BOLD=$'\033[1m'
|
|
35
|
+
_PO_NC=$'\033[0m'
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
# The one canonical install command. Quoted everywhere; never re-derived.
|
|
39
|
+
_PO_INSTALL_CMD="npm install -g @anthropic-ai/claude-code"
|
|
40
|
+
|
|
41
|
+
# detect_any_provider: true (0) if any supported provider CLI is on PATH.
|
|
42
|
+
# Extracted verbatim from the loki doctor detection loop (design 1.2).
|
|
43
|
+
detect_any_provider() {
|
|
44
|
+
local _dp
|
|
45
|
+
for _dp in claude codex cline aider; do
|
|
46
|
+
command -v "$_dp" >/dev/null 2>&1 && return 0
|
|
47
|
+
done
|
|
48
|
+
return 1
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
# _po_assume_yes: true when the user has opted into unattended confirmation.
|
|
52
|
+
# Honors --yes (LOKI_AUTO_CONFIRM, set by loki:1013) and LOKI_ASSUME_YES.
|
|
53
|
+
_po_assume_yes() {
|
|
54
|
+
[ "${LOKI_ASSUME_YES:-}" = "1" ] && return 0
|
|
55
|
+
[ "${LOKI_AUTO_CONFIRM:-}" = "true" ] && return 0
|
|
56
|
+
return 1
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# _po_non_interactive: true when we must NEVER prompt (non-TTY or CI).
|
|
60
|
+
# Mirrors cmd_welcome_maybe_firstrun (loki:4286) and maybe_show_auto_plan.
|
|
61
|
+
_po_non_interactive() {
|
|
62
|
+
[ ! -t 1 ] && return 0
|
|
63
|
+
[ ! -t 0 ] && return 0
|
|
64
|
+
[ -n "${CI:-}" ] && return 0
|
|
65
|
+
return 1
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
# _po_run_login: offer (or auto-accept) the claude auth login handoff after a
|
|
69
|
+
# successful install. Inherited stdio; Loki never handles credentials.
|
|
70
|
+
_po_run_login() {
|
|
71
|
+
# claude must actually be on PATH for login to make sense.
|
|
72
|
+
if ! command -v claude >/dev/null 2>&1; then
|
|
73
|
+
printf "%sInstalled, but 'claude' is not on your PATH yet. You may need to restart your shell or add npm's global bin to PATH (npm config get prefix). Run 'loki doctor' to recheck.%s\n" "$_PO_YELLOW" "$_PO_NC"
|
|
74
|
+
return 0
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
local do_login=""
|
|
78
|
+
if _po_assume_yes; then
|
|
79
|
+
do_login="y"
|
|
80
|
+
else
|
|
81
|
+
printf 'Claude Code installed.\n'
|
|
82
|
+
printf '\n'
|
|
83
|
+
printf 'You still need to authenticate. Run the login flow now? [Y/n] '
|
|
84
|
+
read -r do_login || do_login="n"
|
|
85
|
+
fi
|
|
86
|
+
case "$do_login" in
|
|
87
|
+
""|y|Y|yes|YES)
|
|
88
|
+
if claude auth login; then
|
|
89
|
+
# Do not trust the exit code alone: verify the session is
|
|
90
|
+
# actually authenticated before claiming readiness (council
|
|
91
|
+
# HIGH: the old path could falsely report success).
|
|
92
|
+
if claude auth status 2>/dev/null | grep -q '"loggedIn"[[:space:]]*:[[:space:]]*true'; then
|
|
93
|
+
printf "%sProvider ready. Run 'loki doctor' to confirm, or 'loki quickstart' to build.%s\n" "$_PO_BOLD" "$_PO_NC"
|
|
94
|
+
return 0
|
|
95
|
+
fi
|
|
96
|
+
printf "Login finished but authentication could not be confirmed. Run 'claude auth status' to check, then 'loki doctor'.\n"
|
|
97
|
+
return 0
|
|
98
|
+
fi
|
|
99
|
+
printf "Login not completed. Run 'claude auth login' when ready, then 'loki doctor'.\n"
|
|
100
|
+
return 0
|
|
101
|
+
;;
|
|
102
|
+
*)
|
|
103
|
+
printf "Login not completed. Run 'claude auth login' when ready, then 'loki doctor'.\n"
|
|
104
|
+
return 0
|
|
105
|
+
;;
|
|
106
|
+
esac
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# _po_do_install: run the one consented command, print it first, handle result.
|
|
110
|
+
# Returns 0 on success, non-zero on failure (caller decides exit behavior).
|
|
111
|
+
_po_do_install() {
|
|
112
|
+
printf 'Installing Claude Code (%s) ...\n' "$_PO_INSTALL_CMD"
|
|
113
|
+
# The exact, fixed argv. No interpolation, no extra flags. (design 1.7)
|
|
114
|
+
# Capture npm's exit code directly (not via `if`, whose statement status is
|
|
115
|
+
# 0 when the condition is false with no else, masking the real npm code).
|
|
116
|
+
local code=0
|
|
117
|
+
npm install -g @anthropic-ai/claude-code || code=$?
|
|
118
|
+
if [ "$code" -eq 0 ]; then
|
|
119
|
+
printf '\n'
|
|
120
|
+
_po_run_login
|
|
121
|
+
return 0
|
|
122
|
+
fi
|
|
123
|
+
printf '%sInstall failed (npm exited %s). You can retry manually:%s\n' "$_PO_RED" "$code" "$_PO_NC"
|
|
124
|
+
printf ' %s\n' "$_PO_INSTALL_CMD"
|
|
125
|
+
printf 'If this is a permissions error, see https://docs.npmjs.com/resolving-eacces-permissions-errors\n'
|
|
126
|
+
return "$code"
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
# offer_provider_install <mode>
|
|
130
|
+
# mode = "report" -> doctor: append the offer on a TTY; on non-TTY/CI do
|
|
131
|
+
# NOTHING (doctor already printed the FAIL + install line,
|
|
132
|
+
# and we must keep non-TTY/json bytes identical for parity).
|
|
133
|
+
# Never exits the process.
|
|
134
|
+
# mode = "gate" -> start/demo/quick pre-flight: on non-TTY/CI print the
|
|
135
|
+
# honest one-liner to stderr and return 2. On a TTY, prompt;
|
|
136
|
+
# on decline return 2. On accept install + login.
|
|
137
|
+
#
|
|
138
|
+
# Honors:
|
|
139
|
+
# LOKI_NO_INSTALL_OFFER=1 -> never prompt; print manual command (1.4)
|
|
140
|
+
# --yes / LOKI_ASSUME_YES -> auto-accept install + login (1.4)
|
|
141
|
+
offer_provider_install() {
|
|
142
|
+
local mode="${1:-gate}"
|
|
143
|
+
|
|
144
|
+
# Opt-out: never offer, just surface the manual command.
|
|
145
|
+
if [ "${LOKI_NO_INSTALL_OFFER:-}" = "1" ]; then
|
|
146
|
+
if [ "$mode" = "gate" ]; then
|
|
147
|
+
printf 'No AI provider CLI found. Install one when ready:\n' >&2
|
|
148
|
+
printf ' %s (then: claude auth login)\n' "$_PO_INSTALL_CMD" >&2
|
|
149
|
+
return 2
|
|
150
|
+
fi
|
|
151
|
+
printf '\n'
|
|
152
|
+
printf 'Install a provider when ready:\n'
|
|
153
|
+
printf ' %s (then: claude auth login)\n' "$_PO_INSTALL_CMD"
|
|
154
|
+
printf ' Other supported providers: codex, cline, aider.\n'
|
|
155
|
+
return 0
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
# Non-interactive / CI: NEVER prompt, NEVER install.
|
|
159
|
+
#
|
|
160
|
+
# gate (start/demo/quick): print the honest one-liner to stderr and return 2
|
|
161
|
+
# so the caller exits with an actionable message before any spend.
|
|
162
|
+
# report (doctor): stay SILENT. doctor has already printed the FAIL line and
|
|
163
|
+
# the install command on stdout, so no information is lost. Silence here is
|
|
164
|
+
# load-bearing for parity: doctor.ts gates its child_process bridge on
|
|
165
|
+
# process.stdout.isTTY, so on a non-TTY/CI run the Bun route emits nothing
|
|
166
|
+
# extra. If report-mode printed a stderr line, the bash route would diverge
|
|
167
|
+
# from Bun in exactly the no-provider/non-TTY case the bun-parity matrix
|
|
168
|
+
# captures (2>&1) on CI runners, which have no provider installed.
|
|
169
|
+
if _po_non_interactive; then
|
|
170
|
+
if [ "$mode" = "gate" ]; then
|
|
171
|
+
printf 'No AI provider CLI found; cannot prompt to install in a non-interactive shell. Run: %s\n' "$_PO_INSTALL_CMD" >&2
|
|
172
|
+
return 2
|
|
173
|
+
fi
|
|
174
|
+
return 0
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
# npm missing: degraded path, never attempt a non-npm install.
|
|
178
|
+
if ! command -v npm >/dev/null 2>&1; then
|
|
179
|
+
printf '\n'
|
|
180
|
+
printf '%sNo AI provider CLI was found, and npm is not installed either, so Loki%s\n' "$_PO_BOLD" "$_PO_NC"
|
|
181
|
+
printf 'cannot install one for you.\n'
|
|
182
|
+
printf '\n'
|
|
183
|
+
printf 'Install Node.js + npm first (https://nodejs.org), then run:\n'
|
|
184
|
+
printf ' %s\n' "$_PO_INSTALL_CMD"
|
|
185
|
+
printf ' claude auth login\n'
|
|
186
|
+
printf '\n'
|
|
187
|
+
printf "Already have a provider via another method? Make sure 'claude' (or codex,\n"
|
|
188
|
+
printf "cline, aider) is on your PATH, then run 'loki doctor'.\n"
|
|
189
|
+
[ "$mode" = "gate" ] && return 2
|
|
190
|
+
return 0
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# TTY, npm present: the interactive offer.
|
|
194
|
+
printf '\n'
|
|
195
|
+
printf 'No AI provider CLI was found. Loki needs one agent CLI to run a build.\n'
|
|
196
|
+
printf '\n'
|
|
197
|
+
printf 'Claude Code is the recommended provider (full feature support).\n'
|
|
198
|
+
printf ' Install: %s\n' "$_PO_INSTALL_CMD"
|
|
199
|
+
printf ' Then: claude auth login\n'
|
|
200
|
+
printf '\n'
|
|
201
|
+
|
|
202
|
+
local answer=""
|
|
203
|
+
if _po_assume_yes; then
|
|
204
|
+
answer="y"
|
|
205
|
+
else
|
|
206
|
+
printf 'Install Claude Code now? [Y/n] '
|
|
207
|
+
read -r answer || answer="n"
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
case "$answer" in
|
|
211
|
+
""|y|Y|yes|YES)
|
|
212
|
+
if _po_do_install; then
|
|
213
|
+
return 0
|
|
214
|
+
fi
|
|
215
|
+
# Install failed: honest failure already printed by _po_do_install.
|
|
216
|
+
[ "$mode" = "gate" ] && return 2
|
|
217
|
+
return 1
|
|
218
|
+
;;
|
|
219
|
+
*)
|
|
220
|
+
printf 'Skipped. Install a provider when ready:\n'
|
|
221
|
+
printf ' %s (then: claude auth login)\n' "$_PO_INSTALL_CMD"
|
|
222
|
+
printf 'Other supported providers: codex, cline, aider.\n'
|
|
223
|
+
[ "$mode" = "gate" ] && return 2
|
|
224
|
+
return 0
|
|
225
|
+
;;
|
|
226
|
+
esac
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
# provider_offer_gate: convenience wrapper for the start/demo/quick pre-flight.
|
|
230
|
+
# Returns 0 if a provider is present (or one was just installed); returns 2 to
|
|
231
|
+
# signal the caller should `exit 2` (no provider, declined or non-interactive).
|
|
232
|
+
provider_offer_gate() {
|
|
233
|
+
detect_any_provider && return 0
|
|
234
|
+
offer_provider_install gate || return 2
|
|
235
|
+
# After an accepted install, re-detect; if still absent, fail the gate.
|
|
236
|
+
detect_any_provider && return 0
|
|
237
|
+
return 2
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
# Executed directly (doctor.ts child_process bridge, or manual): run the offer.
|
|
241
|
+
# When sourced by autonomy/loki, this block does not run.
|
|
242
|
+
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
|
|
243
|
+
case "${1:-report}" in
|
|
244
|
+
offer|report) offer_provider_install report ;;
|
|
245
|
+
gate) offer_provider_install gate ;;
|
|
246
|
+
detect) detect_any_provider ;;
|
|
247
|
+
*) offer_provider_install report ;;
|
|
248
|
+
esac
|
|
249
|
+
fi
|