loki-mode 7.28.1 → 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 +30 -19
- package/SKILL.md +8 -2
- package/VERSION +1 -1
- package/autonomy/loki +273 -30
- 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/docs/alternative-installations.md +1 -1
- package/loki-ts/dist/loki.js +208 -208
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/templates/simple-todo-app.md +13 -144
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"
|
|
@@ -510,7 +528,7 @@ show_help() {
|
|
|
510
528
|
echo "Usage: loki <command> [options]"
|
|
511
529
|
echo ""
|
|
512
530
|
echo "New here? Try one of these first:"
|
|
513
|
-
echo " loki doctor Check your setup is ready (
|
|
531
|
+
echo " loki doctor Check your setup is ready (a few seconds)"
|
|
514
532
|
echo " loki quick \"add a health endpoint\" One small task, start to finish"
|
|
515
533
|
echo " loki demo Build a sample todo app end to end (real run)"
|
|
516
534
|
echo " loki start ./prd.md Build from a spec (PRD file, GitHub issue, or no arg)"
|
|
@@ -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)"
|
|
@@ -675,6 +694,27 @@ show_help() {
|
|
|
675
694
|
echo " See: $RUN_SH (header comments) for full list."
|
|
676
695
|
}
|
|
677
696
|
|
|
697
|
+
# Tight newcomer landing for the bare `loki` invocation (no args).
|
|
698
|
+
# v7.28.x UX: the full 70-command table lives in `loki help`; a no-args run
|
|
699
|
+
# now shows only a calm ~12-line orientation so the "what do I do first"
|
|
700
|
+
# block is never buried under the command wall. `loki help`/`--help` are
|
|
701
|
+
# unchanged (still call show_help).
|
|
702
|
+
show_landing() {
|
|
703
|
+
local version
|
|
704
|
+
version=$(get_version)
|
|
705
|
+
echo -e "${BOLD}Loki Mode v$version${NC} - the spec-driven builder that verifies its own work."
|
|
706
|
+
echo ""
|
|
707
|
+
echo -e "First time here? ${CYAN}loki doctor${NC} checks your setup (an AI provider CLI is required)."
|
|
708
|
+
echo ""
|
|
709
|
+
echo "Get started:"
|
|
710
|
+
echo -e " ${CYAN}loki start ./prd.md${NC} Build from a spec (PRD file, GitHub issue, or no arg)"
|
|
711
|
+
echo -e " ${CYAN}loki demo${NC} Build a sample todo app end to end (real run)"
|
|
712
|
+
echo -e " ${CYAN}loki dashboard start${NC} Start the live run monitor (then: loki dashboard open)"
|
|
713
|
+
echo ""
|
|
714
|
+
echo -e "Need help? ${CYAN}loki help${NC} lists every command."
|
|
715
|
+
echo "Tip: preview cost and scope before building: loki plan <your-prd.md>"
|
|
716
|
+
}
|
|
717
|
+
|
|
678
718
|
# Detect argument type for unified `loki start` (v6.84.0)
|
|
679
719
|
# Returns one of: prd | issue | empty | unknown
|
|
680
720
|
# Logic:
|
|
@@ -1239,6 +1279,17 @@ cmd_start() {
|
|
|
1239
1279
|
esac
|
|
1240
1280
|
done
|
|
1241
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
|
+
|
|
1242
1293
|
# Clear any stale raw-brief marker from a PRIOR run before we decide the
|
|
1243
1294
|
# mode of THIS run. Only the brief branch (below) re-writes it. Without this,
|
|
1244
1295
|
# a brief run leaves .loki/state/brief.txt behind and a later non-brief run
|
|
@@ -2265,10 +2316,18 @@ PYDASH
|
|
|
2265
2316
|
# v7.7.30: --all preserves the legacy machine-wide kill. It runs even when
|
|
2266
2317
|
# the current folder has no live session (the "clean everything" use case),
|
|
2267
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.
|
|
2268
2326
|
if [ "$stop_all" = true ]; then
|
|
2269
|
-
|
|
2327
|
+
local _stop_all_pat="${LOKI_STOP_ALL_PATTERN:-loki-run-}"
|
|
2328
|
+
pkill -f "$_stop_all_pat" 2>/dev/null || true
|
|
2270
2329
|
sleep 0.5
|
|
2271
|
-
pkill -9 -f "
|
|
2330
|
+
pkill -9 -f "$_stop_all_pat" 2>/dev/null || true
|
|
2272
2331
|
echo -e "${RED}--all: signalled all loki-run-* processes on this machine.${NC}"
|
|
2273
2332
|
fi
|
|
2274
2333
|
}
|
|
@@ -3369,7 +3428,7 @@ cmd_provider_set() {
|
|
|
3369
3428
|
if [ -z "$new_provider" ]; then
|
|
3370
3429
|
echo -e "${RED}Error: Provider name required${NC}"
|
|
3371
3430
|
echo "Usage: loki provider set <claude|codex|cline|aider>"
|
|
3372
|
-
exit
|
|
3431
|
+
exit 2
|
|
3373
3432
|
fi
|
|
3374
3433
|
|
|
3375
3434
|
# Validate provider
|
|
@@ -3923,12 +3982,16 @@ cmd_dashboard_start() {
|
|
|
3923
3982
|
|
|
3924
3983
|
if kill -0 "$new_pid" 2>/dev/null; then
|
|
3925
3984
|
local url="${url_scheme}://${host}:${port}"
|
|
3926
|
-
echo -e "${GREEN}Dashboard
|
|
3985
|
+
echo -e "${GREEN}Dashboard (live run monitor) started${NC}"
|
|
3927
3986
|
echo ""
|
|
3928
3987
|
echo " PID: $new_pid"
|
|
3929
3988
|
echo " URL: $url"
|
|
3930
3989
|
echo " Logs: $log_file"
|
|
3931
3990
|
echo ""
|
|
3991
|
+
# v7.28.x UX #5: distinguish the two browser UIs up front so a newcomer
|
|
3992
|
+
# who wants to submit a spec does not land on the ops monitor by mistake.
|
|
3993
|
+
echo -e "${DIM}Looking for the project web UI to submit a spec? loki web (:${PURPLE_LAB_DEFAULT_PORT:-57375})${NC}"
|
|
3994
|
+
echo ""
|
|
3932
3995
|
echo -e "Open in browser: ${CYAN}loki dashboard open${NC}"
|
|
3933
3996
|
echo -e "Check status: ${CYAN}loki dashboard status${NC}"
|
|
3934
3997
|
echo -e "Stop server: ${CYAN}loki dashboard stop${NC}"
|
|
@@ -4541,12 +4604,15 @@ cmd_web_start() {
|
|
|
4541
4604
|
local url="http://${PURPLE_LAB_DEFAULT_HOST}:${port}/lab/"
|
|
4542
4605
|
|
|
4543
4606
|
if [ "$server_ready" = true ]; then
|
|
4544
|
-
echo -e "${GREEN}Purple Lab running at: $url${NC} (PID: $pid)"
|
|
4607
|
+
echo -e "${GREEN}Purple Lab (project web UI) running at: $url${NC} (PID: $pid)"
|
|
4545
4608
|
else
|
|
4546
|
-
echo -e "${YELLOW}Purple Lab starting at: $url${NC} (PID: $pid)"
|
|
4609
|
+
echo -e "${YELLOW}Purple Lab (project web UI) starting at: $url${NC} (PID: $pid)"
|
|
4547
4610
|
echo "Server may still be loading. Refresh the browser if it does not load immediately."
|
|
4548
4611
|
fi
|
|
4549
4612
|
echo -e "Logs: $log_file"
|
|
4613
|
+
# v7.28.x UX #5: distinguish the two browser UIs so the ops monitor and the
|
|
4614
|
+
# spec-input web UI are never confused for each other.
|
|
4615
|
+
echo -e "${DIM}Looking for the live run monitor? loki dashboard (:${DASHBOARD_DEFAULT_PORT:-57374})${NC}"
|
|
4550
4616
|
echo -e "Stop with: ${CYAN}loki web stop${NC}"
|
|
4551
4617
|
|
|
4552
4618
|
# Open browser only after server is confirmed ready
|
|
@@ -5133,7 +5199,7 @@ cmd_issue_parse() {
|
|
|
5133
5199
|
if [[ -z "$issue_ref" ]]; then
|
|
5134
5200
|
echo -e "${RED}Error: Issue reference required${NC}"
|
|
5135
5201
|
echo "Usage: loki issue parse <issue-ref>"
|
|
5136
|
-
exit
|
|
5202
|
+
exit 2
|
|
5137
5203
|
fi
|
|
5138
5204
|
|
|
5139
5205
|
# Find and run issue-parser.sh
|
|
@@ -5177,7 +5243,7 @@ cmd_issue_view() {
|
|
|
5177
5243
|
if [[ -z "$issue_ref" ]]; then
|
|
5178
5244
|
echo -e "${RED}Error: Issue reference required${NC}"
|
|
5179
5245
|
echo "Usage: loki issue view <issue-ref>"
|
|
5180
|
-
exit
|
|
5246
|
+
exit 2
|
|
5181
5247
|
fi
|
|
5182
5248
|
|
|
5183
5249
|
# Find and run issue-parser.sh
|
|
@@ -5515,7 +5581,7 @@ cmd_run() {
|
|
|
5515
5581
|
echo " loki run owner/repo#789 # GitHub with specific repo"
|
|
5516
5582
|
echo ""
|
|
5517
5583
|
echo "Run 'loki run --help' for full usage."
|
|
5518
|
-
exit
|
|
5584
|
+
exit 2
|
|
5519
5585
|
fi
|
|
5520
5586
|
|
|
5521
5587
|
# Source issue provider abstraction
|
|
@@ -5994,7 +6060,7 @@ cmd_issue() {
|
|
|
5994
6060
|
echo -e "${RED}Error: No issue number specified${NC}"
|
|
5995
6061
|
echo "Usage: loki issue <number> or loki issue --number <number>"
|
|
5996
6062
|
echo "Run 'loki issue --help' for full usage."
|
|
5997
|
-
exit
|
|
6063
|
+
exit 2
|
|
5998
6064
|
fi
|
|
5999
6065
|
|
|
6000
6066
|
# Auto-detect repo if not specified
|
|
@@ -6912,7 +6978,7 @@ cmd_config_set() {
|
|
|
6912
6978
|
if [[ -z "$key" || -z "$value" ]]; then
|
|
6913
6979
|
echo -e "${RED}Usage: loki config set [--global] <key> <value>${NC}"
|
|
6914
6980
|
echo "Run 'loki config' for list of settable keys."
|
|
6915
|
-
return
|
|
6981
|
+
return 2
|
|
6916
6982
|
fi
|
|
6917
6983
|
|
|
6918
6984
|
# Determine config directory: --global writes to ~/.config/loki-mode/
|
|
@@ -7059,7 +7125,7 @@ cmd_config_get() {
|
|
|
7059
7125
|
|
|
7060
7126
|
if [[ -z "$key" ]]; then
|
|
7061
7127
|
echo -e "${RED}Usage: loki config get <key>${NC}"
|
|
7062
|
-
return
|
|
7128
|
+
return 2
|
|
7063
7129
|
fi
|
|
7064
7130
|
|
|
7065
7131
|
local config_store="$LOKI_DIR/config/settings.json"
|
|
@@ -7700,15 +7766,19 @@ cmd_doctor() {
|
|
|
7700
7766
|
doctor_check "Cline CLI" cline optional || true
|
|
7701
7767
|
doctor_check "Aider CLI" aider optional || true
|
|
7702
7768
|
|
|
7703
|
-
# Check if at least one provider is installed
|
|
7704
|
-
|
|
7705
|
-
|
|
7706
|
-
command -v "$_dp" &>/dev/null && _any_provider=true && break
|
|
7707
|
-
done
|
|
7708
|
-
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
|
|
7709
7772
|
echo -e " ${RED}FAIL${NC} No AI provider CLI installed -- at least one is required"
|
|
7710
7773
|
echo -e " ${YELLOW}Install: npm install -g @anthropic-ai/claude-code${NC}"
|
|
7711
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
|
|
7712
7782
|
fi
|
|
7713
7783
|
echo ""
|
|
7714
7784
|
|
|
@@ -8781,7 +8851,7 @@ cmd_notify_send() {
|
|
|
8781
8851
|
if [ -z "$message" ]; then
|
|
8782
8852
|
echo -e "${RED}Error: Message required${NC}"
|
|
8783
8853
|
echo "Usage: loki notify $channel <message>"
|
|
8784
|
-
exit
|
|
8854
|
+
exit 2
|
|
8785
8855
|
fi
|
|
8786
8856
|
|
|
8787
8857
|
case "$channel" in
|
|
@@ -9002,6 +9072,69 @@ cmd_sandbox() {
|
|
|
9002
9072
|
exec "$SANDBOX_SH" "$subcommand" "$@"
|
|
9003
9073
|
}
|
|
9004
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
|
+
|
|
9005
9138
|
# Demo mode - build a real project from a bundled PRD template
|
|
9006
9139
|
cmd_demo() {
|
|
9007
9140
|
# Handle --help
|
|
@@ -9034,10 +9167,21 @@ cmd_demo() {
|
|
|
9034
9167
|
local provider=""
|
|
9035
9168
|
local dry_run=false
|
|
9036
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
|
|
9037
9177
|
|
|
9038
9178
|
# Parse arguments
|
|
9039
9179
|
while [[ $# -gt 0 ]]; do
|
|
9040
9180
|
case "$1" in
|
|
9181
|
+
--yes|-y)
|
|
9182
|
+
assume_yes=true
|
|
9183
|
+
shift
|
|
9184
|
+
;;
|
|
9041
9185
|
--dir)
|
|
9042
9186
|
if [[ -z "${2:-}" ]]; then
|
|
9043
9187
|
echo -e "${RED}Error: --dir requires a path${NC}"
|
|
@@ -9078,6 +9222,17 @@ cmd_demo() {
|
|
|
9078
9222
|
esac
|
|
9079
9223
|
done
|
|
9080
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
|
+
|
|
9081
9236
|
# Fall back to examples/ if templates/ doesn't exist
|
|
9082
9237
|
if [ ! -f "$demo_prd" ]; then
|
|
9083
9238
|
demo_prd="$SKILL_DIR/examples/simple-todo-app.md"
|
|
@@ -9118,6 +9273,9 @@ cmd_demo() {
|
|
|
9118
9273
|
fi
|
|
9119
9274
|
|
|
9120
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
|
|
9121
9279
|
echo -e "${YELLOW}[dry-run] Would run:${NC}"
|
|
9122
9280
|
echo " cd $demo_dir"
|
|
9123
9281
|
echo -n " loki start prd.md --simple --yes"
|
|
@@ -9128,6 +9286,52 @@ cmd_demo() {
|
|
|
9128
9286
|
return 0
|
|
9129
9287
|
fi
|
|
9130
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
|
+
|
|
9131
9335
|
echo -e "${DIM}Starting autonomous build...${NC}"
|
|
9132
9336
|
echo -e "${DIM}(Press Ctrl+C to stop at any time)${NC}"
|
|
9133
9337
|
echo ""
|
|
@@ -9349,7 +9553,16 @@ cmd_quick() {
|
|
|
9349
9553
|
echo " loki quick \"fix the login bug in auth.js\""
|
|
9350
9554
|
echo " loki quick \"add input validation to the signup form\""
|
|
9351
9555
|
echo " loki quick \"write unit tests for the API endpoints\""
|
|
9352
|
-
exit
|
|
9556
|
+
exit 2
|
|
9557
|
+
fi
|
|
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
|
|
9353
9566
|
fi
|
|
9354
9567
|
|
|
9355
9568
|
local task_desc="$*"
|
|
@@ -10692,7 +10905,7 @@ cmd_migrate_start() {
|
|
|
10692
10905
|
echo "Specify the migration target (e.g. --target typescript, --target react, --target microservices)"
|
|
10693
10906
|
echo ""
|
|
10694
10907
|
echo "Run 'loki migrate --help' for usage."
|
|
10695
|
-
return
|
|
10908
|
+
return 2
|
|
10696
10909
|
fi
|
|
10697
10910
|
|
|
10698
10911
|
# Validate compliance preset
|
|
@@ -11662,7 +11875,7 @@ for f in data.get('frictions', []):
|
|
|
11662
11875
|
if [ -z "$codebase_path" ]; then
|
|
11663
11876
|
echo -e "${RED}Error: Codebase path is required${NC}"
|
|
11664
11877
|
echo "Usage: loki heal <path-to-codebase> [options]"
|
|
11665
|
-
return
|
|
11878
|
+
return 2
|
|
11666
11879
|
fi
|
|
11667
11880
|
|
|
11668
11881
|
if [[ ! -d "$codebase_path" ]]; then
|
|
@@ -12030,7 +12243,7 @@ cmd_migrate() {
|
|
|
12030
12243
|
echo "Usage: loki migrate <path-to-codebase> --target <target>"
|
|
12031
12244
|
echo ""
|
|
12032
12245
|
echo "Run 'loki migrate --help' for usage."
|
|
12033
|
-
return
|
|
12246
|
+
return 2
|
|
12034
12247
|
fi
|
|
12035
12248
|
|
|
12036
12249
|
cmd_migrate_start "$codebase_path" "$target" "$plan_only" "$phase" "$parallel" "$compliance" "$dry_run" "$do_resume" "$multi_repo" "$export_report" "$no_dashboard" "$no_docs"
|
|
@@ -12805,6 +13018,20 @@ show_verbose = sys.argv[3] == 'true'
|
|
|
12805
13018
|
session_model_env = (os.environ.get('LOKI_SESSION_MODEL', 'sonnet') or 'sonnet').strip().lower()
|
|
12806
13019
|
legacy_tier_switching = (os.environ.get('LOKI_LEGACY_TIER_SWITCHING', 'false') or 'false').strip().lower() == 'true'
|
|
12807
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
|
+
|
|
12808
13035
|
# Map session model name to tier key used in tokens_per_tier below.
|
|
12809
13036
|
# Unknown models fall through to 'development' (Sonnet) as a safe default.
|
|
12810
13037
|
_session_tier_map = {
|
|
@@ -12957,6 +13184,14 @@ elif complexity_score <= 5:
|
|
|
12957
13184
|
else:
|
|
12958
13185
|
complexity = 'enterprise'
|
|
12959
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
|
+
|
|
12960
13195
|
# --- Estimate iterations ---
|
|
12961
13196
|
iteration_map = {
|
|
12962
13197
|
'simple': (3, 5),
|
|
@@ -13187,7 +13422,10 @@ if ui_count > 0:
|
|
|
13187
13422
|
color = {'simple': GREEN, 'moderate': YELLOW, 'complex': RED, 'enterprise': RED}
|
|
13188
13423
|
cx_color = color.get(complexity, NC)
|
|
13189
13424
|
print(f'\n{CYAN}Complexity{NC}')
|
|
13190
|
-
|
|
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})')
|
|
13191
13429
|
for reason in complexity_reasons:
|
|
13192
13430
|
print(f' {DIM}- {reason}{NC}')
|
|
13193
13431
|
|
|
@@ -13333,7 +13571,7 @@ cmd_plan() {
|
|
|
13333
13571
|
if [ -z "$prd_file" ]; then
|
|
13334
13572
|
echo -e "${RED}Usage: loki plan <PRD file>${NC}"
|
|
13335
13573
|
echo "Run 'loki plan --help' for usage."
|
|
13336
|
-
return
|
|
13574
|
+
return 2
|
|
13337
13575
|
fi
|
|
13338
13576
|
|
|
13339
13577
|
if [ ! -f "$prd_file" ]; then
|
|
@@ -13388,7 +13626,9 @@ main() {
|
|
|
13388
13626
|
fi
|
|
13389
13627
|
|
|
13390
13628
|
if [ $# -eq 0 ]; then
|
|
13391
|
-
|
|
13629
|
+
# v7.28.x UX: bare `loki` shows a tight newcomer landing, not the full
|
|
13630
|
+
# command wall. `loki help`/`--help` still print show_help unchanged.
|
|
13631
|
+
show_landing
|
|
13392
13632
|
exit 0
|
|
13393
13633
|
fi
|
|
13394
13634
|
|
|
@@ -13416,6 +13656,9 @@ main() {
|
|
|
13416
13656
|
demo)
|
|
13417
13657
|
cmd_demo "$@"
|
|
13418
13658
|
;;
|
|
13659
|
+
quickstart)
|
|
13660
|
+
cmd_quickstart "$@"
|
|
13661
|
+
;;
|
|
13419
13662
|
welcome)
|
|
13420
13663
|
cmd_welcome "$@"
|
|
13421
13664
|
;;
|
|
@@ -17722,7 +17965,7 @@ cmd_enterprise() {
|
|
|
17722
17965
|
if [ -z "$name" ]; then
|
|
17723
17966
|
echo -e "${RED}Error: Token name required${NC}"
|
|
17724
17967
|
echo "Usage: loki enterprise token generate <name> [--scopes '*'] [--expires 30]"
|
|
17725
|
-
exit
|
|
17968
|
+
exit 2
|
|
17726
17969
|
fi
|
|
17727
17970
|
|
|
17728
17971
|
# Validate name is not a flag
|
|
@@ -17890,7 +18133,7 @@ else:
|
|
|
17890
18133
|
|
|
17891
18134
|
if [ -z "$identifier" ]; then
|
|
17892
18135
|
echo -e "${RED}Error: Token ID or name required${NC}"
|
|
17893
|
-
exit
|
|
18136
|
+
exit 2
|
|
17894
18137
|
fi
|
|
17895
18138
|
|
|
17896
18139
|
python3 -c "
|
|
@@ -17929,7 +18172,7 @@ else:
|
|
|
17929
18172
|
|
|
17930
18173
|
if [ -z "$identifier" ]; then
|
|
17931
18174
|
echo -e "${RED}Error: Token ID or name required${NC}"
|
|
17932
|
-
exit
|
|
18175
|
+
exit 2
|
|
17933
18176
|
fi
|
|
17934
18177
|
|
|
17935
18178
|
python3 -c "
|