loki-mode 7.30.0 → 7.31.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/autonomy/loki CHANGED
@@ -132,8 +132,12 @@ find_skill_dir() {
132
132
  return 1
133
133
  }
134
134
 
135
- # Use || true to prevent set -e from exiting before error message
136
- SKILL_DIR=$(find_skill_dir) || true
135
+ # Use || true to prevent set -e from exiting before error message.
136
+ # Honor a pre-set SKILL_DIR (test harnesses point it at a fixture); fall back to
137
+ # auto-detection when unset. No production code exports SKILL_DIR into a child
138
+ # loki invocation (children receive LOKI_SKILL_DIR instead), so this only changes
139
+ # behavior when a caller deliberately exports SKILL_DIR.
140
+ SKILL_DIR="${SKILL_DIR:-$(find_skill_dir)}" || true
137
141
  if [ -z "$SKILL_DIR" ]; then
138
142
  # Check if installation exists but is incomplete (missing run.sh)
139
143
  _check_dir1="$HOME/.claude/skills/loki-mode"
@@ -324,6 +328,70 @@ emit_event() {
324
328
  fi
325
329
  }
326
330
 
331
+ # --- CLI consolidation (Phase A): deprecated-alias back-compat contract -------
332
+ # An old command token continues to dispatch and forward 1:1 to its canonical
333
+ # command. It prints exactly ONE pointer line to STDERR and never touches
334
+ # machine-readable stdout. The deprecation line is suppressed whenever an
335
+ # explicit machine-output flag (--json, -q, --quiet) is present in the args, so
336
+ # callers that capture combined 2>&1 (and JSON consumers) see a clean stream.
337
+ # It is ALSO suppressed for the positional machine-output formats of forwarded
338
+ # commands (json|csv|timeline as the first arg, e.g. `loki export json`), since
339
+ # those formats emit a pure machine-readable stream that a combined 2>&1 capture
340
+ # would otherwise dirty with the note line. The human-readable `markdown` format
341
+ # is intentionally NOT suppressed.
342
+ # Non-TTY stdout is intentionally NOT a suppression trigger (some callers pipe
343
+ # stdout but read stderr).
344
+ #
345
+ # Usage:
346
+ # _deprecated_alias <old> <new> "$@" # emit pointer + telemetry (suppressed
347
+ # # under --json/-q/--quiet); then the
348
+ # # caller forwards to the canonical cmd.
349
+ # Reference implementation: the run->start deprecation (cmd_run / v6.84.0).
350
+ _deprecated_alias_should_suppress() {
351
+ # Returns 0 (suppress) when a machine-output flag is present anywhere in
352
+ # "$@", OR when the FIRST arg is a positional machine-output format
353
+ # (json|csv|timeline) used by forwarded commands like `loki export json`.
354
+ # The positional check is first-arg-only so we never suppress on an
355
+ # incidental later token; markdown (human-readable) is deliberately excluded.
356
+ case "${1:-}" in
357
+ json|csv|timeline) return 0 ;;
358
+ esac
359
+ local a
360
+ for a in "$@"; do
361
+ case "$a" in
362
+ --json|-q|--quiet) return 0 ;;
363
+ esac
364
+ done
365
+ return 1
366
+ }
367
+
368
+ _deprecated_alias() {
369
+ local old="$1"; shift
370
+ local new="$1"; shift
371
+ # "$@" is now the user-supplied argv for the alias invocation.
372
+ if _deprecated_alias_should_suppress "$@"; then
373
+ return 0
374
+ fi
375
+ echo "note: 'loki ${old}' is now 'loki ${new}'. The old form still works." >&2
376
+ # Reuse the existing cli_command_deprecated telemetry event (from=/to=).
377
+ # IMPORTANT: a forwarding alias must add no side effect the canonical command
378
+ # lacks. events/emit.sh creates "$LOKI_DIR/events/pending" as a side effect,
379
+ # which would make a no-session directory suddenly contain .loki and flip the
380
+ # exit code of downstream guards (e.g. cmd_share's "No .loki/ directory"
381
+ # check). It also removes a bash-vs-bun race for read-only reporters
382
+ # (cmd_stats sees .loki appear vs Bun seeing it absent). So we only emit
383
+ # telemetry when .loki already exists; the stderr pointer line is always
384
+ # printed (modulo the machine-output suppression above). The matching Bun
385
+ # helper (loki-ts/src/util/deprecated_alias.ts) emits no telemetry, so the
386
+ # two routes are side-effect consistent.
387
+ if [ -d "${LOKI_DIR:-.loki}" ]; then
388
+ emit_event cli_command_deprecated cli alias \
389
+ "from=${old}" \
390
+ "to=${new}" \
391
+ "argv=${*:-}"
392
+ fi
393
+ }
394
+
327
395
  # Emit learning signal (non-blocking) - SYN-018
328
396
  # Usage: emit_learning_signal <signal_type> [options]
329
397
  # Signal types: user_preference, error_pattern, success_pattern, tool_efficiency, workflow_pattern
@@ -523,6 +591,12 @@ PYEOF
523
591
  # Show help
524
592
  show_help() {
525
593
  local version=$(get_version)
594
+ # Strip color when stdout is not a TTY (piped/redirected) so captured help
595
+ # output is clean, matching the bare-loki welcome path. Local overrides only.
596
+ local BOLD="$BOLD" NC="$NC"
597
+ if [ ! -t 1 ]; then
598
+ BOLD=''; NC=''
599
+ fi
526
600
  echo -e "${BOLD}Loki Mode v$version${NC}"
527
601
  echo ""
528
602
  echo "Usage: loki <command> [options]"
@@ -534,82 +608,62 @@ show_help() {
534
608
  echo " loki start ./prd.md Build from a spec (PRD file, GitHub issue, or no arg)"
535
609
  echo " Docs: https://github.com/asklokesh/loki-mode | Report a problem: loki crash"
536
610
  echo ""
611
+ # CLI consolidation (Phase A): the front page presents ~17 canonical
612
+ # workflow-oriented entries grouped by section. Deprecated alias names
613
+ # (stats, metrics, cost, trust-metrics, serve, open, otel, cp, wt, rc, ...)
614
+ # do NOT appear here; they live in the collapsed footer below and in
615
+ # `loki help aliases`. The full long-tail surface is still dispatchable and
616
+ # documented per-command via `loki <command> --help`.
537
617
  echo "Commands:"
538
- echo " start [PRD|ISSUE] Start Loki Mode (PRD file, issue ref, or no arg)"
539
- echo " run <issue> (deprecated) Alias for 'loki start <issue-ref>'"
540
- echo " quick \"task\" Quick single-task mode (lightweight, 3 iterations max)"
541
- echo " monitor [path] Monitor Docker Compose services with auto-fix (v6.67.0)"
542
- echo " demo Build a sample todo app end to end (real run)"
618
+ echo ""
619
+ echo "Build:"
620
+ echo " start [SPEC] Start a build (PRD file, GitHub issue, or no arg)"
621
+ echo " plan <PRD-file> Dry-run analysis: complexity, cost, execution plan"
622
+ echo " grill [SPEC] Devil's-advocate spec interrogation before you build"
623
+ echo " spec [cmd] Living spec: lock|status|sync (drift detection)"
543
624
  echo " quickstart [idea] Guided first build: setup, idea, template, plan, go"
544
- echo " init [name] Project scaffolding with 21 PRD templates"
545
- echo " issue <url|num> (deprecated) Use 'loki start <issue-ref>' instead"
546
- echo " watch [prd] Auto-rerun on PRD file changes (v6.33.0)"
547
- echo " export <format> Export session data: json|markdown|csv|timeline (v6.0.0)"
625
+ echo ""
626
+ echo "Session:"
627
+ echo " status [--json] Show current status (--json for machine-readable)"
548
628
  echo " stop Stop execution immediately"
549
- echo " cleanup Kill orphaned processes from crashed sessions"
550
629
  echo " pause Pause after current session"
551
630
  echo " resume Resume paused execution"
552
- echo " status [--json] Show current status (--json for machine-readable)"
553
- echo " stats [flags] Session statistics (--json, --efficiency)"
554
- echo " kpis [--json] Accuracy + efficiency KPIs (v7.5.28+) [Phase K]"
555
- echo " logs Show recent log output"
556
- echo " dashboard [cmd] Dashboard server (start|stop|status|url|open)"
557
- echo " web [cmd] Web app UI (start|stop|status) -- serves web-app/dist/"
558
- echo " preview Open the running app Loki built (alias: open)"
559
- echo " provider [cmd] Manage AI provider (show|set|list|info)"
560
- echo " serve Start dashboard/API server (alias for api start)"
561
- echo " api [cmd] Dashboard/API server (start|stop|status)"
562
- echo " sandbox [cmd] Docker sandbox (start|stop|status|logs|shell|build)"
563
- echo " notify [cmd] Send notifications (test|slack|discord|webhook|status)"
564
- echo " telemetry [cmd] OpenTelemetry management (status|enable|disable|stop|start)"
565
- echo " import Import GitHub issues as tasks"
566
- echo " github [cmd] GitHub integration (sync|export|pr|status)"
567
- echo " config [cmd] Manage configuration (show|init|edit|path|set|get)"
568
- echo " completions [bash|zsh] Output shell completion scripts"
631
+ echo " cleanup Kill orphaned processes from crashed sessions"
632
+ echo ""
633
+ echo "Verify / trust:"
634
+ echo " verify [base] Deterministic PR verification (CI-gate exit codes)"
635
+ echo " review [opts] Standalone code review with quality gates"
636
+ echo " trust [cmd] Visible trust trajectory; 'trust detail' for metrics"
637
+ echo ""
638
+ echo "Observe:"
639
+ echo " dashboard [cmd] Operations UI server (start|stop|status|url|open)"
640
+ echo " mcp Launch the MCP server (Model Context Protocol, stdio)"
641
+ echo ""
642
+ echo "Report:"
643
+ echo " report [cmd] Reporting: session|metrics|cost|export|share|dogfood"
644
+ echo ""
645
+ echo "Knowledge:"
569
646
  echo " memory [cmd] Cross-project learnings (list|show|search|stats)"
570
- echo " compound [cmd] Knowledge compounding (list|show|search|run|stats)"
571
- echo " council [cmd] Completion council (status|verdicts|convergence|force-review|report)"
572
- echo " checkpoint|cp Save/restore session checkpoints"
573
- echo " projects Multi-project registry management"
574
- echo " audit [cmd] Agent audit log and quality scanning (log|scan)"
647
+ echo ""
648
+ echo "Modernize:"
575
649
  echo " heal <path> Legacy system healing (archaeology, stabilize, modernize)"
576
- echo " verify [base] Deterministic PR verification (Autonomi Verify MVP; CI-gate exit codes)"
577
- echo " spec [cmd] Living spec: keep the spec true (lock|status|sync; drift detection, CI-gate exit codes)"
578
- echo " grill [spec] Interrogate a spec with the hardest questions before you build it (Devil's Advocate)"
579
- echo " review [opts] Standalone code review with quality gates (diff, staged, PR, files)"
580
- echo " optimize Optimize prompts based on session history"
581
- echo " enterprise Enterprise feature management (tokens, OIDC)"
582
- echo " metrics [opts] Session productivity report (--json, --last N, --save, --share)"
583
- echo " cost [opts] Transparent cost view: per-run/project spend + budget (--json, --last N)"
584
- echo " trust [--json] Visible trust trajectory: council/gate pass-rate + interventions over runs [R4]"
585
- echo " trust-metrics Trust-layer metrics: evidence-block rate, gate distribution, council split, cost/verified (--json)"
586
- echo " dogfood Show self-development statistics"
587
- echo " secrets [cmd] API key status and validation (status|validate)"
588
- echo " reset [target] Reset session state (all|retries|failed)"
650
+ echo ""
651
+ echo "Config:"
652
+ echo " config [cmd] Manage configuration + provider (show|set|get|provider)"
589
653
  echo " doctor [--json] Check system prerequisites and skill symlinks"
590
- echo " setup-skill Create skill symlinks for all providers"
591
- echo " self-update Upgrade loki via current manager (use --to bun|npm|brew to switch)"
592
- echo " watchdog [cmd] Process health monitoring (status|help)"
593
- echo " worktree [cmd] Parallel worktree management (list|merge|clean|status)"
594
- echo " agent [cmd] Agent type dispatch (list|info|run|start|review)"
595
- echo " remote [PRD] Start remote session (connect from phone/browser, Claude Pro/Max)"
596
- echo " trigger Event-driven autonomous execution (schedules, webhooks)"
597
- echo " failover [cmd] Cross-provider auto-failover (status|--enable|--test|--chain)"
598
- echo " onboard [path] Analyze a repo and generate CLAUDE.md (structure, conventions, commands)"
599
- echo ' explain [path] Analyze any codebase and explain its architecture in plain English'
600
- echo " docs [cmd] Generate, update, check project documentation"
601
- echo " magic [cmd] Spec-driven component generation (MagicModules + MoMoA)"
602
- echo " plan <PRD> Dry-run PRD analysis: complexity, cost, and execution plan"
603
- echo " ci [opts] CI/CD quality gate integration (--pr, --report, --github-comment)"
604
- echo " test [opts] AI-powered test generation (--file, --dir, --changed, --dry-run)"
605
- echo " context [cmd] Context window management (show|files|tools|add|clear)"
606
- echo " code [cmd] Codebase intelligence (overview|symbols|deps|hotspots|diff|search)"
607
- echo " report [opts] Session report generator (--format text|markdown|html, --output)"
608
- echo " share [opts] Share session report as GitHub Gist (--private, --format)"
609
- echo " proof [cmd] Inspect/share proof-of-run artifacts (list|show|open|share)"
610
- echo " bench [cmd] Head-to-head benchmark harness (run|vs|list|verify|report)"
654
+ echo ""
611
655
  echo " version Show version"
612
- echo " help Show this help"
656
+ echo " help Show this help ('loki help aliases' for old names)"
657
+ echo ""
658
+ echo "More commands (init, watch, demo, web, api, logs, github,"
659
+ echo "import, council, proof, audit, agent, template, magic, onboard, explain,"
660
+ echo "code, context, docs, wiki, ci, test, bench, secrets, telemetry, crash,"
661
+ echo "worktree, failover, monitor, remote, kpis, ...) are dispatchable and"
662
+ echo "documented via 'loki <command> --help'."
663
+ echo ""
664
+ echo "Aliases (deprecated): older command names still work; they print a"
665
+ echo "one-line pointer to stderr and never alter --json output. See the full"
666
+ echo "list with 'loki help aliases'."
613
667
  echo ""
614
668
  echo "Options for 'start':"
615
669
  echo " --provider NAME AI provider: claude (default), codex, cline, aider"
@@ -655,11 +709,11 @@ show_help() {
655
709
  echo ""
656
710
  echo " # Session ops + observability"
657
711
  echo " loki status [--json] # Current status"
658
- echo " loki stats --efficiency # Token + cost stats"
712
+ echo " loki report session --efficiency # Token + cost stats"
659
713
  echo " loki kpis [--json] # Accuracy + efficiency KPI snapshot"
660
714
  echo " loki doctor [--json] # System prereq + skill symlinks"
661
715
  echo " loki logs # Tail recent log output"
662
- echo " loki export json|markdown|csv|timeline # Export session"
716
+ echo " loki report export json|markdown|csv|timeline # Export session"
663
717
  echo " loki assets export team.tgz # Export shareable team assets (redacted)"
664
718
  echo " loki cleanup # Kill orphaned processes"
665
719
  echo ""
@@ -694,6 +748,47 @@ show_help() {
694
748
  echo " See: $RUN_SH (header comments) for full list."
695
749
  }
696
750
 
751
+ # CLI consolidation (Phase A): `loki help aliases` subview. Lists every
752
+ # deprecated alias and its canonical command for users with scripts. The old
753
+ # forms continue to work (they print a one-line stderr pointer and never alter
754
+ # --json output); they are supported through at least two MAJOR versions and
755
+ # considered for removal no earlier than vN+2.0.0.
756
+ show_help_aliases() {
757
+ # Strip color when stdout is not a TTY (piped/redirected), matching the
758
+ # bare-loki welcome path and show_help. Local overrides only.
759
+ local BOLD="$BOLD" NC="$NC"
760
+ if [ ! -t 1 ]; then
761
+ BOLD=''; NC=''
762
+ fi
763
+ echo -e "${BOLD}Loki Mode -- deprecated command aliases${NC}"
764
+ echo ""
765
+ echo "These older command names still work. Each forwards 1:1 to its"
766
+ echo "canonical command, prints exactly one pointer line to stderr, and"
767
+ echo "never alters --json output (the pointer is suppressed under --json,"
768
+ echo "-q, and --quiet). They are supported through at least two MAJOR"
769
+ echo "versions; removal no earlier than vN+2.0.0."
770
+ echo ""
771
+ echo " Old command Canonical command"
772
+ echo " ----------- -----------------"
773
+ echo " run <issue> start <issue>"
774
+ echo " serve api start"
775
+ echo " open preview"
776
+ echo " otel telemetry"
777
+ echo " cp checkpoint"
778
+ echo " wt worktree"
779
+ echo " rc remote"
780
+ echo " stats report session"
781
+ echo " metrics report metrics"
782
+ echo " cost report cost"
783
+ echo " export report export"
784
+ echo " share report share"
785
+ echo " dogfood report dogfood"
786
+ echo " trust-metrics trust detail"
787
+ echo ""
788
+ echo "More groupings (ui, analyze, modernize, new, admin) land in Phase B."
789
+ echo "See internal/CLI-CONSOLIDATION-DESIGN.md for the full migration plan."
790
+ }
791
+
697
792
  # Tight newcomer landing for the bare `loki` invocation (no args).
698
793
  # v7.28.x UX: the full 70-command table lives in `loki help`; a no-args run
699
794
  # now shows only a calm ~12-line orientation so the "what do I do first"
@@ -5349,17 +5444,28 @@ cmd_run() {
5349
5444
  break
5350
5445
  fi
5351
5446
  done
5352
- if [ "$_show_warn" = "true" ]; then
5353
- echo -e "${YELLOW}Notice: 'loki run' is deprecated. Use 'loki start <issue-ref>' instead.${NC}" >&2
5354
- echo -e "${DIM} All features (--worktree, --pr, --ship, --dry-run) work on 'loki start'.${NC}" >&2
5355
- echo "" >&2
5447
+ # CLI consolidation (Phase A): align the run->start notice with the
5448
+ # standardized one-line alias contract (stderr-only, suppressed under
5449
+ # --json/-q/--quiet, contains "is now 'loki start'"). The word
5450
+ # "deprecated" is kept so existing run/start unification tests still
5451
+ # match. Telemetry reuses the cli_command_deprecated event.
5452
+ if [ "$_show_warn" = "true" ] && ! _deprecated_alias_should_suppress "$@"; then
5453
+ echo "note: 'loki run' is now 'loki start <issue-ref>' (run is deprecated). The old form still works." >&2
5356
5454
  # Emit telemetry event so we can measure adoption of the unified command.
5357
5455
  # Signature: emit_event <type> <source> <action> [key=value ...]
5358
- emit_event cli_command_deprecated cli run_to_start \
5359
- "old_command=run" \
5360
- "new_command=start" \
5361
- "version=6.84.0" \
5362
- "argv=${*:-}"
5456
+ # No-side-effect alias contract (matches _deprecated_alias at :371):
5457
+ # events/emit.sh creates "$LOKI_DIR/events/pending" as a side effect,
5458
+ # which would make a clean directory suddenly contain .loki and flip
5459
+ # downstream guards (cmd_share's "No .loki/" check, bash-vs-bun read
5460
+ # divergence). Only emit when .loki already exists; the stderr pointer
5461
+ # is always printed (modulo machine-output suppression above).
5462
+ if [ -d "${LOKI_DIR:-.loki}" ]; then
5463
+ emit_event cli_command_deprecated cli run_to_start \
5464
+ "from=run" \
5465
+ "to=start" \
5466
+ "version=6.84.0" \
5467
+ "argv=${*:-}"
5468
+ fi
5363
5469
  fi
5364
5470
  fi
5365
5471
 
@@ -10476,17 +10582,29 @@ cmd_dogfood() {
10476
10582
  return 0
10477
10583
  fi
10478
10584
 
10479
- local stats_script="$SKILL_DIR/scripts/dogfood-stats.sh"
10480
- if [ ! -f "$stats_script" ]; then
10481
- echo -e "${RED}Error: dogfood-stats.sh not found${NC}"
10482
- exit 1
10483
- fi
10484
-
10485
10585
  local json_flag=""
10486
10586
  if [[ "${1:-}" == "--json" ]]; then
10487
10587
  json_flag="--json"
10488
10588
  fi
10489
10589
 
10590
+ # Honest degradation: scripts/dogfood-stats.sh is a development-only helper
10591
+ # that is NOT shipped in the npm tarball (package.json `files` omits
10592
+ # scripts/). On a packaged install it is absent, so report that plainly
10593
+ # rather than a bare "not found" that reads like a bug. Message goes to
10594
+ # STDERR so a --json caller's stdout stays clean; --json gets a structured
10595
+ # degraded payload on stdout so consumers do not choke on prose.
10596
+ local stats_script="$SKILL_DIR/scripts/dogfood-stats.sh"
10597
+ if [ ! -f "$stats_script" ]; then
10598
+ if [ -n "$json_flag" ]; then
10599
+ printf '{"available": false, "reason": "dogfood-stats.sh is a dev-only helper not shipped in the published package"}\n'
10600
+ else
10601
+ echo "loki report dogfood: self-development stats are unavailable in this install." >&2
10602
+ echo "The dogfood-stats.sh helper is a development-only script and is not" >&2
10603
+ echo "shipped in the published package. Run it from a loki-mode source checkout." >&2
10604
+ fi
10605
+ return 0
10606
+ fi
10607
+
10490
10608
  bash "$stats_script" $json_flag
10491
10609
  }
10492
10610
 
@@ -13061,6 +13179,95 @@ show_verbose = sys.argv[3] == 'true'
13061
13179
  session_model_env = (os.environ.get('LOKI_SESSION_MODEL', 'sonnet') or 'sonnet').strip().lower()
13062
13180
  legacy_tier_switching = (os.environ.get('LOKI_LEGACY_TIER_SWITCHING', 'false') or 'false').strip().lower() == 'true'
13063
13181
 
13182
+ # Model-honesty contract: the estimator must quote the model the runner will
13183
+ # actually invoke, and name the lever that selected it. The runner honors only
13184
+ # two session-level levers (LOKI_MODEL was estimator-only and is removed):
13185
+ # 1. A pending mid-flight override file (.loki/state/model-override) -- the
13186
+ # most specific, just-requested intent; it wins.
13187
+ # 2. LOKI_SESSION_MODEL (the session pin the runner reads at run.sh:12309).
13188
+ # Canonical normalization is shared with run.sh + the dashboard: trim +
13189
+ # lowercase + EXACT allowlist match. A value with interior whitespace (fab le)
13190
+ # is rejected everywhere, so the three readers agree.
13191
+ def _loki_norm_alias(raw):
13192
+ raw = (raw or '').strip().lower()
13193
+ return raw if raw in ('haiku', 'sonnet', 'opus', 'fable') else ''
13194
+
13195
+ # session_model_env starts from LOKI_SESSION_MODEL; track the lever that set it
13196
+ # so the provenance line names the real source (never a false attribution).
13197
+ _session_model_source = 'LOKI_SESSION_MODEL'
13198
+ session_model_env = _loki_norm_alias(os.environ.get('LOKI_SESSION_MODEL', '')) or 'sonnet'
13199
+
13200
+ _fable_override = ''
13201
+ try:
13202
+ _ov_path = os.path.join('.loki', 'state', 'model-override')
13203
+ if os.path.isfile(_ov_path):
13204
+ with open(_ov_path) as _ovf:
13205
+ _fable_override = _loki_norm_alias(_ovf.read())
13206
+ except Exception:
13207
+ _fable_override = ''
13208
+ if _fable_override:
13209
+ session_model_env = _fable_override
13210
+ _session_model_source = '.loki/state/model-override'
13211
+
13212
+ # Architect opt-in (LOKI_FABLE_ARCHITECT=1): the runner routes ONLY the first
13213
+ # (architecture) iteration to fable. Disclose that here so the quote is not
13214
+ # blind to the lever (it would otherwise under-quote by one fable iteration).
13215
+ # Suppressed when an explicit planning-model override or a fable session/override
13216
+ # already applies. The maxTier clamp below still caps the disclosed cost.
13217
+ _fable_architect = (
13218
+ (os.environ.get('LOKI_FABLE_ARCHITECT', '0') or '0').strip() == '1'
13219
+ and not os.environ.get('LOKI_CLAUDE_MODEL_PLANNING')
13220
+ and not os.environ.get('LOKI_MODEL_PLANNING')
13221
+ and session_model_env != 'fable'
13222
+ and not (os.environ.get('LOKI_LEGACY_TIER_SWITCHING', 'false') or 'false').strip().lower() == 'true'
13223
+ )
13224
+
13225
+ # Apply the SAME LOKI_MAX_TIER ceiling the runner applies, so the quoted model
13226
+ # (and the architect disclosure) never exceed the operator cost cap.
13227
+ #
13228
+ # SYNC: This resolves the clamp result through the SAME provider config the runner
13229
+ # uses, a byte-faithful port of providers/claude.sh loki_apply_max_tier_clamp plus
13230
+ # the PROVIDER_MODEL_FAST / PROVIDER_MODEL_DEVELOPMENT resolution chains
13231
+ # (claude.sh:55-67, :318). The same port also lives in the dashboard
13232
+ # (dashboard/server.py _provider_model_fast / _provider_model_development /
13233
+ # _clamp_to_max_tier). All three readers MUST agree; the agreement is locked by
13234
+ # the parity test in tests/test-model-override.sh (resolver parity matrix) and the
13235
+ # cross-route tests in test-plan-command.sh. If you change resolution here, change
13236
+ # it in claude.sh AND dashboard/server.py, and re-run those tests. The 'or' chains
13237
+ # mirror bash ':-' empty-string-fallthrough; allow_haiku uses an exact 'true'
13238
+ # match to mirror bash [ \"\$x\" = \"true\" ]. PROVIDER_MODEL_DEVELOPMENT is opus
13239
+ # by default; PROVIDER_MODEL_FAST is sonnet by default (haiku when ALLOW_HAIKU).
13240
+ _allow_haiku = (os.environ.get('LOKI_ALLOW_HAIKU', 'false') or 'false') == 'true'
13241
+ def _provider_model_fast():
13242
+ return (os.environ.get('LOKI_CLAUDE_MODEL_FAST')
13243
+ or os.environ.get('LOKI_MODEL_FAST')
13244
+ or ('haiku' if _allow_haiku else 'sonnet'))
13245
+ def _provider_model_development():
13246
+ return (os.environ.get('LOKI_CLAUDE_MODEL_DEVELOPMENT')
13247
+ or os.environ.get('LOKI_MODEL_DEVELOPMENT')
13248
+ or ('sonnet' if _allow_haiku else 'opus'))
13249
+ _max_tier = (os.environ.get('LOKI_MAX_TIER', '') or '').strip().lower()
13250
+ def _loki_clamp_alias(alias):
13251
+ if not _max_tier:
13252
+ return alias
13253
+ if _max_tier == 'haiku':
13254
+ return _provider_model_fast()
13255
+ if _max_tier == 'sonnet':
13256
+ # alias passed as both model and tier (override-path convention): the
13257
+ # runner's sonnet arm reduces to 'downgrade iff alias==fable'.
13258
+ return _provider_model_development() if alias == 'fable' else alias
13259
+ if _max_tier == 'opus':
13260
+ return 'opus' if alias == 'fable' else alias
13261
+ return alias
13262
+ if _max_tier:
13263
+ _clamped = _loki_clamp_alias(session_model_env)
13264
+ if _clamped != session_model_env:
13265
+ session_model_env = _clamped
13266
+ _session_model_source = 'LOKI_MAX_TIER (clamped)'
13267
+ if _fable_architect and _loki_clamp_alias('fable') != 'fable':
13268
+ # The architect iteration would clamp down too; do not disclose fable.
13269
+ _fable_architect = False
13270
+
13064
13271
  # v7.29.0: Honor LOKI_COMPLEXITY -- the SAME env var the runner honors at
13065
13272
  # run.sh:920 -- so a forced-tier run (e.g. 'loki demo' / 'loki start --simple'
13066
13273
  # which export LOKI_COMPLEXITY=simple) quotes the tier it will actually run,
@@ -13078,6 +13285,7 @@ if forced_complexity not in ('simple', 'moderate', 'complex', 'enterprise'):
13078
13285
  # Map session model name to tier key used in tokens_per_tier below.
13079
13286
  # Unknown models fall through to 'development' (Sonnet) as a safe default.
13080
13287
  _session_tier_map = {
13288
+ 'fable': 'advisor',
13081
13289
  'opus': 'planning',
13082
13290
  'sonnet': 'development',
13083
13291
  'haiku': 'fast',
@@ -13256,16 +13464,25 @@ estimated_iterations = (min_iter + max_iter) // 2
13256
13464
  # RARV cycle: 4 iterations per cycle (plan, develop, develop, test)
13257
13465
  # Tokens estimated per iteration step
13258
13466
  tokens_per_tier = {
13467
+ 'advisor': {'input': 50000, 'output': 8000, 'model': 'Fable'},
13259
13468
  'planning': {'input': 50000, 'output': 8000, 'model': 'Opus'},
13260
13469
  'development': {'input': 80000, 'output': 15000, 'model': 'Sonnet'},
13261
13470
  'fast': {'input': 30000, 'output': 5000, 'model': 'Haiku'},
13262
13471
  }
13263
13472
 
13264
- # Pricing per 1M tokens
13473
+ # Pricing per 1M tokens. Fable 5 is the top-tier advisory model at exactly 2x
13474
+ # Opus per token (published \$10/\$50 in/out vs Opus \$5/\$25). The Opus row was
13475
+ # corrected from a stale \$15/\$75 to the real Opus 4.8 \$5/\$25, and the Haiku
13476
+ # row from a stale \$0.25/\$1.25 (Haiku 3.5) to the published Claude Haiku 4.5
13477
+ # price of \$1/\$5 (anthropic.com/news/claude-haiku-4-5), so all four rows now
13478
+ # match the three canonical model-keyed tables (run.sh pricing.json, run.sh
13479
+ # check_budget_limit, dashboard _DEFAULT_PRICING) and a fable-forced quote
13480
+ # honestly shows MORE than Opus, not less.
13265
13481
  pricing = {
13266
- 'Opus': {'input': 15.00, 'output': 75.00},
13482
+ 'Fable': {'input': 10.00, 'output': 50.00},
13483
+ 'Opus': {'input': 5.00, 'output': 25.00},
13267
13484
  'Sonnet': {'input': 3.00, 'output': 15.00},
13268
- 'Haiku': {'input': 0.25, 'output': 1.25},
13485
+ 'Haiku': {'input': 1.00, 'output': 5.00},
13269
13486
  }
13270
13487
 
13271
13488
  # Build per-iteration plan
@@ -13273,8 +13490,8 @@ iteration_plan = []
13273
13490
  total_input_tokens = 0
13274
13491
  total_output_tokens = 0
13275
13492
  total_cost = 0.0
13276
- tier_totals = {'Opus': 0.0, 'Sonnet': 0.0, 'Haiku': 0.0}
13277
- tier_iterations = {'Opus': 0, 'Sonnet': 0, 'Haiku': 0}
13493
+ tier_totals = {'Fable': 0.0, 'Opus': 0.0, 'Sonnet': 0.0, 'Haiku': 0.0}
13494
+ tier_iterations = {'Fable': 0, 'Opus': 0, 'Sonnet': 0, 'Haiku': 0}
13278
13495
 
13279
13496
  for i in range(estimated_iterations):
13280
13497
  rarv_step = i % 4
@@ -13300,6 +13517,11 @@ for i in range(estimated_iterations):
13300
13517
  # MUST use the same tier for all iterations to avoid quoting a
13301
13518
  # cost higher than the user will actually pay.
13302
13519
  tier = session_tier
13520
+ # Architect opt-in: the runner routes ONLY the first iteration to fable
13521
+ # (the architecture pass). Mirror that here so the quote is not blind to
13522
+ # the lever: iteration 0 -> advisor (fable), the rest -> session tier.
13523
+ if _fable_architect and i == 0:
13524
+ tier = 'advisor'
13303
13525
 
13304
13526
  info = tokens_per_tier[tier]
13305
13527
  model = info['model']
@@ -13476,6 +13698,7 @@ for reason in complexity_reasons:
13476
13698
  print(f'\n{CYAN}Estimated Iterations{NC}')
13477
13699
  print(f' Count: {BOLD}{estimated_iterations}{NC} (range: {min_iter}-{max_iter})')
13478
13700
  print(f' RARV cycles: {cycle_count} (4 iterations per cycle)')
13701
+ fable_n = tier_iterations.get('Fable', 0)
13479
13702
  opus_n = tier_iterations.get('Opus', 0)
13480
13703
  sonnet_n = tier_iterations.get('Sonnet', 0)
13481
13704
  haiku_n = tier_iterations.get('Haiku', 0)
@@ -13485,6 +13708,8 @@ if legacy_tier_switching:
13485
13708
  else:
13486
13709
  # Session-pinned: exactly one tier has all iterations.
13487
13710
  pinned_parts = []
13711
+ if fable_n > 0:
13712
+ pinned_parts.append(f'Fable x{fable_n}')
13488
13713
  if opus_n > 0:
13489
13714
  pinned_parts.append(f'Opus x{opus_n}')
13490
13715
  if sonnet_n > 0:
@@ -13492,7 +13717,14 @@ else:
13492
13717
  if haiku_n > 0:
13493
13718
  pinned_parts.append(f'Haiku x{haiku_n}')
13494
13719
  print(' Model distribution: ' + (' | '.join(pinned_parts) if pinned_parts else '(none)'))
13495
- print(f' {DIM}(pinned via LOKI_SESSION_MODEL={session_model_env}; set LOKI_LEGACY_TIER_SWITCHING=true to rotate){NC}')
13720
+ # Provenance: name the lever that actually selected the pinned model, never a
13721
+ # false attribution. _session_model_source is the real source (env, override
13722
+ # file, or maxTier clamp) tracked at selection time.
13723
+ print(f' {DIM}(pinned via {_session_model_source}={session_model_env}; set LOKI_LEGACY_TIER_SWITCHING=true to rotate){NC}')
13724
+ if _fable_architect:
13725
+ print(f' {DIM}(+ 1 architecture iteration on Fable via LOKI_FABLE_ARCHITECT=1){NC}')
13726
+ if fable_n > 0:
13727
+ print(f' {YELLOW}Fable 5 is the top-tier advisory model priced at 2x Opus ({chr(36)}10/{chr(36)}50 per MTok).{NC}')
13496
13728
 
13497
13729
  # Tokens
13498
13730
  total_tok = total_input_tokens + total_output_tokens
@@ -13505,7 +13737,7 @@ print(f' Total: {total_tok:>12,} tokens')
13505
13737
  ds = chr(36) # dollar sign
13506
13738
  print(f'\n{CYAN}Cost Estimate{NC}')
13507
13739
  print(f' {BOLD}Total: {ds}{total_cost:.2f}{NC}')
13508
- for model in ['Opus', 'Sonnet', 'Haiku']:
13740
+ for model in ['Fable', 'Opus', 'Sonnet', 'Haiku']:
13509
13741
  # v6.81.1: suppress zero-cost tiers (e.g. session-pinned runs). Keeps
13510
13742
  # the breakdown focused on models actually used. Legacy mode still
13511
13743
  # shows all three because each tier has non-zero iterations.
@@ -13590,7 +13822,7 @@ cmd_plan() {
13590
13822
  echo ""
13591
13823
  echo "Environment Variables:"
13592
13824
  echo " LOKI_SESSION_MODEL Pin cost estimate to a single tier"
13593
- echo " (opus|sonnet|haiku, default: sonnet)"
13825
+ echo " (opus|sonnet|haiku|fable, default: sonnet)"
13594
13826
  echo " LOKI_LEGACY_TIER_SWITCHING Set to 'true' to restore the legacy"
13595
13827
  echo " Opus/Sonnet/Haiku per-iteration rotation"
13596
13828
  echo ""
@@ -13724,7 +13956,12 @@ main() {
13724
13956
  cmd_status "$@"
13725
13957
  ;;
13726
13958
  stats)
13727
- cmd_stats "$@"
13959
+ # CLI consolidation (Phase A): 'stats' is a deprecated alias of
13960
+ # 'report session'. On the Bun route this arm is never reached
13961
+ # (stats is Bun-native); the matching deprecation lives in
13962
+ # loki-ts/src/commands/stats.ts so both routes behave identically.
13963
+ _deprecated_alias stats "report session" "$@"
13964
+ cmd_report session "$@"
13728
13965
  ;;
13729
13966
  dashboard)
13730
13967
  cmd_dashboard "$@"
@@ -13732,13 +13969,24 @@ main() {
13732
13969
  web)
13733
13970
  cmd_web "$@"
13734
13971
  ;;
13735
- preview|open)
13972
+ preview)
13973
+ cmd_preview "$@"
13974
+ ;;
13975
+ open)
13976
+ # CLI consolidation (Phase A): 'open' is a deprecated alias of 'preview'.
13977
+ _deprecated_alias open preview "$@"
13736
13978
  cmd_preview "$@"
13737
13979
  ;;
13738
13980
  logs)
13739
13981
  cmd_logs "$@"
13740
13982
  ;;
13741
13983
  serve)
13984
+ # CLI consolidation (Phase A): 'serve' is a deprecated alias of
13985
+ # 'api start'. Emit the pointer BEFORE the --help short-circuit so it
13986
+ # fires on every form (including 'serve --help'), consistent with the
13987
+ # other short aliases and testable without binding a TCP port.
13988
+ # Suppressed under --json/-q/--quiet by the helper.
13989
+ _deprecated_alias serve "api start" "$@"
13742
13990
  # v7.6.2 B-12 fix: 'serve --help' previously routed to 'cmd_api start --help'
13743
13991
  # which started the dashboard as a side effect instead of printing help.
13744
13992
  case "${1:-}" in
@@ -13784,7 +14032,9 @@ main() {
13784
14032
  cmd_watch "$@"
13785
14033
  ;;
13786
14034
  export)
13787
- cmd_export "$@"
14035
+ # CLI consolidation (Phase A): 'export' -> 'report export'.
14036
+ _deprecated_alias export "report export" "$@"
14037
+ cmd_report export "$@"
13788
14038
  ;;
13789
14039
  assets)
13790
14040
  cmd_assets "$@"
@@ -13805,6 +14055,8 @@ main() {
13805
14055
  cmd_compound "$@"
13806
14056
  ;;
13807
14057
  checkpoint|cp)
14058
+ # CLI consolidation (Phase A): 'cp' is a deprecated alias of 'checkpoint'.
14059
+ [ "$command" = "cp" ] && _deprecated_alias cp checkpoint "$@"
13808
14060
  cmd_checkpoint "$@"
13809
14061
  ;;
13810
14062
  rollback)
@@ -13814,7 +14066,9 @@ main() {
13814
14066
  cmd_council "$@"
13815
14067
  ;;
13816
14068
  dogfood)
13817
- cmd_dogfood "$@"
14069
+ # CLI consolidation (Phase A): 'dogfood' -> 'report dogfood'.
14070
+ _deprecated_alias dogfood "report dogfood" "$@"
14071
+ cmd_report dogfood "$@"
13818
14072
  ;;
13819
14073
  projects)
13820
14074
  cmd_projects "$@"
@@ -13875,6 +14129,8 @@ main() {
13875
14129
  cmd_cluster "$@"
13876
14130
  ;;
13877
14131
  worktree|wt)
14132
+ # CLI consolidation (Phase A): 'wt' is a deprecated alias of 'worktree'.
14133
+ [ "$command" = "wt" ] && _deprecated_alias wt worktree "$@"
13878
14134
  cmd_worktree "$@"
13879
14135
  ;;
13880
14136
  agent)
@@ -13887,24 +14143,34 @@ main() {
13887
14143
  cmd_state "$@"
13888
14144
  ;;
13889
14145
  metrics)
13890
- cmd_metrics "$@"
14146
+ # CLI consolidation (Phase A): 'metrics' -> 'report metrics'.
14147
+ _deprecated_alias metrics "report metrics" "$@"
14148
+ cmd_report metrics "$@"
13891
14149
  ;;
13892
14150
  cost)
13893
- cmd_cost "$@"
14151
+ # CLI consolidation (Phase A): 'cost' -> 'report cost'.
14152
+ _deprecated_alias cost "report cost" "$@"
14153
+ cmd_report cost "$@"
13894
14154
  ;;
13895
14155
  trust)
13896
14156
  cmd_trust "$@"
13897
14157
  ;;
13898
14158
  trust-metrics)
13899
- cmd_trust_metrics "$@"
14159
+ # CLI consolidation (Phase A): 'trust-metrics' -> 'trust detail'.
14160
+ _deprecated_alias trust-metrics "trust detail" "$@"
14161
+ cmd_trust detail "$@"
13900
14162
  ;;
13901
14163
  syslog)
13902
14164
  cmd_syslog "$@"
13903
14165
  ;;
13904
14166
  telemetry|otel)
14167
+ # CLI consolidation (Phase A): 'otel' is a deprecated alias of 'telemetry'.
14168
+ [ "$command" = "otel" ] && _deprecated_alias otel telemetry "$@"
13905
14169
  cmd_telemetry "$@"
13906
14170
  ;;
13907
14171
  remote|rc)
14172
+ # CLI consolidation (Phase A): 'rc' is a deprecated alias of 'remote'.
14173
+ [ "$command" = "rc" ] && _deprecated_alias rc remote "$@"
13908
14174
  cmd_remote "$@"
13909
14175
  ;;
13910
14176
  trigger)
@@ -13944,7 +14210,9 @@ main() {
13944
14210
  cmd_crash "$@"
13945
14211
  ;;
13946
14212
  share)
13947
- cmd_share "$@"
14213
+ # CLI consolidation (Phase A): 'share' -> 'report share'.
14214
+ _deprecated_alias share "report share" "$@"
14215
+ cmd_report share "$@"
13948
14216
  ;;
13949
14217
  proof)
13950
14218
  cmd_proof "$@"
@@ -13964,8 +14232,30 @@ main() {
13964
14232
  completions)
13965
14233
  cmd_completions "$@"
13966
14234
  ;;
14235
+ kpis)
14236
+ # kpis is a Bun-only command (Phase K, v7.5.28+): it ships in the
14237
+ # Bun runtime (loki-ts) and bin/loki routes it there. On the bash
14238
+ # route (LOKI_LEGACY_BASH=1, or a machine without Bun installed)
14239
+ # there is no bash implementation. Rather than the generic
14240
+ # "Unknown command" -- which contradicts `loki help` listing kpis --
14241
+ # state the requirement honestly. Respect --json with a structured
14242
+ # payload so a machine consumer is not handed prose.
14243
+ if [ "${1:-}" = "--json" ]; then
14244
+ printf '{"available": false, "reason": "loki kpis requires the Bun runtime; it is not implemented on the bash (LOKI_LEGACY_BASH) route"}\n'
14245
+ else
14246
+ echo "loki kpis requires the Bun runtime and is not available on the bash route." >&2
14247
+ echo "Remove LOKI_LEGACY_BASH=1 (or install Bun: https://bun.sh) to use 'loki kpis'." >&2
14248
+ fi
14249
+ exit 1
14250
+ ;;
13967
14251
  help|--help|-h)
13968
- show_help
14252
+ # CLI consolidation (Phase A): `loki help aliases` prints the full
14253
+ # deprecated-alias table; bare help prints the grouped front page.
14254
+ if [ "${1:-}" = "aliases" ]; then
14255
+ show_help_aliases
14256
+ else
14257
+ show_help
14258
+ fi
13969
14259
  ;;
13970
14260
  *)
13971
14261
  echo -e "${RED}Unknown command: $command${NC}"
@@ -19100,6 +19390,31 @@ cmd_syslog() {
19100
19390
  # dashboard endpoint, and the tests all agree. Honest: with <2 runs it prints
19101
19391
  # "not enough history yet", never a fabricated trend.
19102
19392
  cmd_trust() {
19393
+ # CLI consolidation (Phase A): `trust detail` is the grouped form of the
19394
+ # trust-metrics breakdown. It forwards 1:1 to cmd_trust_metrics (no handler
19395
+ # logic moves). The bare `loki trust` remains the trajectory view. The
19396
+ # deprecation pointer for the old `trust-metrics` name is emitted at the
19397
+ # alias dispatch arm (main()), never here, so `trust detail` stays clean.
19398
+ #
19399
+ # Flag-anywhere: `detail` is accepted in any position (`trust detail`,
19400
+ # `trust --json detail`, `trust detail --json`) so the bash route matches the
19401
+ # bin/loki routing (which forwards on `detail` anywhere) and both routes
19402
+ # behave byte-identically for the same input. The `detail` token is stripped
19403
+ # and the remaining args (e.g. --json) forward to cmd_trust_metrics.
19404
+ local _trust_detail=0
19405
+ local _trust_rest=()
19406
+ local _ta
19407
+ for _ta in "$@"; do
19408
+ if [ "$_ta" = "detail" ]; then
19409
+ _trust_detail=1
19410
+ else
19411
+ _trust_rest+=("$_ta")
19412
+ fi
19413
+ done
19414
+ if [ "$_trust_detail" -eq 1 ]; then
19415
+ cmd_trust_metrics ${_trust_rest[@]+"${_trust_rest[@]}"}
19416
+ return $?
19417
+ fi
19103
19418
  local pass_args=()
19104
19419
  while [[ $# -gt 0 ]]; do
19105
19420
  case "$1" in
@@ -19123,7 +19438,9 @@ cmd_trust() {
19123
19438
  exit 0
19124
19439
  ;;
19125
19440
  --json) pass_args+=("--json"); shift ;;
19126
- *) echo -e "${RED}Unknown option: $1${NC}"; echo "Run 'loki trust --help' for usage."; exit 1 ;;
19441
+ # Errors go to STDERR (not stdout) so a --json consumer's stdout
19442
+ # stays clean, matching the Bun route (trust.ts writes to stderr).
19443
+ *) echo -e "${RED}Unknown option: $1${NC}" >&2; echo "Run 'loki trust --help' for usage." >&2; exit 1 ;;
19127
19444
  esac
19128
19445
  done
19129
19446
 
@@ -25917,7 +26234,35 @@ _test_gen_bats() {
25917
26234
  }
25918
26235
 
25919
26236
  # Session report generator (v6.27.0)
26237
+ # CLI consolidation (Phase A): `report` noun dispatcher.
26238
+ # Groups the read-only reporting cluster under one noun:
26239
+ # report session -> cmd_stats (session statistics)
26240
+ # report metrics -> cmd_metrics (productivity report)
26241
+ # report cost -> cmd_cost (cost + budget view)
26242
+ # report export -> cmd_export (session data export)
26243
+ # report share -> cmd_share (share report as Gist)
26244
+ # report dogfood -> cmd_dogfood (self-development stats)
26245
+ # Any other first token (including a flag like --format, or no token) falls
26246
+ # through to the legacy session-report generator (cmd_report_legacy) so that the
26247
+ # pre-existing `loki report [--format ...]` surface is byte-for-byte unchanged.
26248
+ # No handler logic moves in Phase A: each subcommand calls the existing bare
26249
+ # cmd_* function so the canonical `report <sub>` output is identical to the
26250
+ # legacy top-level command. The deprecation pointer for the old top-level names
26251
+ # is emitted at the alias dispatch arm (main()), never here, so `report <sub>`
26252
+ # stays clean.
25920
26253
  cmd_report() {
26254
+ case "${1:-}" in
26255
+ session) shift; cmd_stats "$@"; return $? ;;
26256
+ metrics) shift; cmd_metrics "$@"; return $? ;;
26257
+ cost) shift; cmd_cost "$@"; return $? ;;
26258
+ export) shift; cmd_export "$@"; return $? ;;
26259
+ share) shift; cmd_share "$@"; return $? ;;
26260
+ dogfood) shift; cmd_dogfood "$@"; return $? ;;
26261
+ esac
26262
+ cmd_report_legacy "$@"
26263
+ }
26264
+
26265
+ cmd_report_legacy() {
25921
26266
  local session_id=""
25922
26267
  local format="text"
25923
26268
  local output_file=""
@@ -25930,10 +26275,22 @@ cmd_report() {
25930
26275
  --help|-h)
25931
26276
  echo -e "${BOLD}loki report${NC} - Session report generator (v6.27.0)"
25932
26277
  echo ""
25933
- echo "Usage: loki report [options]"
26278
+ echo "Usage: loki report [subcommand] [options]"
25934
26279
  echo ""
25935
26280
  echo "Generates a readable session report from .loki/ data."
25936
26281
  echo ""
26282
+ echo "Subcommands (grouped reporting surface, v7.31):"
26283
+ echo " session Session statistics (was: loki stats)"
26284
+ echo " metrics Efficiency/reward metrics (was: loki metrics)"
26285
+ echo " cost Token cost breakdown (was: loki cost)"
26286
+ echo " export Export session data (was: loki export) [json|markdown|csv|timeline]"
26287
+ echo " share Share session assets (was: loki share)"
26288
+ echo " dogfood Self-development stats (was: loki dogfood)"
26289
+ echo " (Run 'loki report <subcommand> --help' for each. The old"
26290
+ echo " top-level names still work; see 'loki help aliases'.)"
26291
+ echo ""
26292
+ echo "With no subcommand, generates the legacy session report below."
26293
+ echo ""
25937
26294
  echo "Options:"
25938
26295
  echo " --session <id> Report for specific session (default: latest)"
25939
26296
  echo " --format <fmt> Output format: text, markdown, html (default: text)"