loki-mode 11.17.37 → 11.17.39

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.
Files changed (77) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/lib/chat.sh +3 -2
  4. package/autonomy/lib/config-map.sh +11 -4
  5. package/autonomy/lib/secret-scan.sh +1 -0
  6. package/autonomy/loki +172 -8
  7. package/autonomy/verify.sh +1 -0
  8. package/completions/_loki +5 -0
  9. package/completions/loki.bash +2 -2
  10. package/dashboard/__init__.py +1 -1
  11. package/docs/CLI-REFERENCE.md +1 -1
  12. package/docs/DEPLOYMENT.md +22 -0
  13. package/docs/INSTALLATION.md +1 -1
  14. package/docs/v10/BOARD.md +52 -19
  15. package/docs/v10/PROGRESS.md +6 -0
  16. package/loki-ts/dist/loki.js +1274 -1257
  17. package/mcp/__init__.py +1 -1
  18. package/package.json +2 -2
  19. package/packages/control-plane/dist/server.js +512 -318
  20. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  21. package/web-app/dist/assets/{AdminPage-8Ms4e7v3.js → AdminPage-sMRXVp8V.js} +1 -1
  22. package/web-app/dist/assets/{Avatar-D-_RMtAF.js → Avatar-sqshsKDj.js} +1 -1
  23. package/web-app/dist/assets/{Badge-BiTryACM.js → Badge-DVLT6AZl.js} +1 -1
  24. package/web-app/dist/assets/{Button-D5J90sJA.js → Button-CUGOx2F-.js} +1 -1
  25. package/web-app/dist/assets/{CockpitPage-CBsCLHsX.js → CockpitPage-_5XpWSyB.js} +1 -1
  26. package/web-app/dist/assets/{ComparePage-ZU4NYIxk.js → ComparePage-CHybAwF6.js} +1 -1
  27. package/web-app/dist/assets/{ErrorBoundary-kRsIDD2c.js → ErrorBoundary-yws0dvvJ.js} +1 -1
  28. package/web-app/dist/assets/{EvidenceReceiptPanel-DV6ZxN0r.js → EvidenceReceiptPanel-DQPXKaYq.js} +1 -1
  29. package/web-app/dist/assets/{GitHubIssuesPanel-B0uPt2Uy.js → GitHubIssuesPanel-CDnklfFn.js} +1 -1
  30. package/web-app/dist/assets/{GitHubPRsPanel-DnrIz7Qh.js → GitHubPRsPanel-F1H3q6Mg.js} +1 -1
  31. package/web-app/dist/assets/{HomePage-SjkWXTYO.js → HomePage-Donxu3UG.js} +2 -2
  32. package/web-app/dist/assets/{LoginPage-D1XU1UOK.js → LoginPage-vabZaQ4T.js} +1 -1
  33. package/web-app/dist/assets/{MagicPage-Cg6MzSOR.js → MagicPage-zJiSFKm6.js} +1 -1
  34. package/web-app/dist/assets/{MetricsPage-Bs4xxEL4.js → MetricsPage-tW1d9Pc4.js} +1 -1
  35. package/web-app/dist/assets/{NotFoundPage-xoIjTbo3.js → NotFoundPage-DfL2kcAp.js} +1 -1
  36. package/web-app/dist/assets/{ProjectPage-DtX7g5N3.js → ProjectPage-Bnx3Slii.js} +3 -3
  37. package/web-app/dist/assets/{ProjectsPage-BZiUAYq4.js → ProjectsPage-C74jNljb.js} +1 -1
  38. package/web-app/dist/assets/{SettingsPage-BGWiG3O9.js → SettingsPage-FA9_6XvR.js} +1 -1
  39. package/web-app/dist/assets/{ShowcasePage-DgrnwUsK.js → ShowcasePage-CUYKoPB0.js} +1 -1
  40. package/web-app/dist/assets/{SystemSettingsPage-cHvinh4M.js → SystemSettingsPage-ClEk_OL-.js} +1 -1
  41. package/web-app/dist/assets/{TeamsPage-BVCDcah9.js → TeamsPage-CZod1nxw.js} +1 -1
  42. package/web-app/dist/assets/{TemplatesPage-BVZWaHmc.js → TemplatesPage-ftxcEJ5S.js} +1 -1
  43. package/web-app/dist/assets/{TerminalOutput-DXipqPhV.js → TerminalOutput-C-ztoDne.js} +1 -1
  44. package/web-app/dist/assets/{activity-CshhOoCC.js → activity-ClWb6nuh.js} +1 -1
  45. package/web-app/dist/assets/{bell-C2aVKCgl.js → bell-DSdTj9Kf.js} +1 -1
  46. package/web-app/dist/assets/{bot-CS4hsHcl.js → bot-DX8ebhTj.js} +1 -1
  47. package/web-app/dist/assets/{check-C9BOGqR2.js → check-6OAuuO_W.js} +1 -1
  48. package/web-app/dist/assets/{chevron-left-BBTdOAuW.js → chevron-left-DTdjxSL_.js} +1 -1
  49. package/web-app/dist/assets/{circle-alert-D7SSuEIf.js → circle-alert-B40DtHik.js} +1 -1
  50. package/web-app/dist/assets/{clock-CHFYgPU3.js → clock-cVEP2obv.js} +1 -1
  51. package/web-app/dist/assets/{cloud-cEefzPuf.js → cloud-CmG80ODy.js} +1 -1
  52. package/web-app/dist/assets/{code-xml-B7St2q5i.js → code-xml-uDISZWZx.js} +1 -1
  53. package/web-app/dist/assets/{database-irsYoPwy.js → database-BBjrBzZH.js} +1 -1
  54. package/web-app/dist/assets/{dollar-sign-ChdZBjDE.js → dollar-sign-QuSiEBvi.js} +1 -1
  55. package/web-app/dist/assets/{file-code-corner-CdnsaqHY.js → file-code-corner-DaYWw_54.js} +1 -1
  56. package/web-app/dist/assets/{file-plus-Cm5WJLtA.js → file-plus-DBrjgfG0.js} +1 -1
  57. package/web-app/dist/assets/{globe-itCMNb3u.js → globe-yLg95rUG.js} +1 -1
  58. package/web-app/dist/assets/{hammer-DpZbHRqZ.js → hammer-Cm_tQ3f7.js} +1 -1
  59. package/web-app/dist/assets/{index-BY6H3gcR.js → index-WwgJyEgR.js} +3 -3
  60. package/web-app/dist/assets/{layers-Pja4FEia.js → layers-CVu0QAr_.js} +1 -1
  61. package/web-app/dist/assets/{loader-circle-CeRQm3kJ.js → loader-circle-C1ByA4HR.js} +1 -1
  62. package/web-app/dist/assets/{lock-DN-Y7N_K.js → lock-COFCj4kU.js} +1 -1
  63. package/web-app/dist/assets/{package-xmriszo-.js → package-DPdWCR4l.js} +1 -1
  64. package/web-app/dist/assets/{plus-_Uj6A6jB.js → plus-CP2Oh8yP.js} +1 -1
  65. package/web-app/dist/assets/{refresh-cw-BsssvIIM.js → refresh-cw-CR9Wf9cI.js} +1 -1
  66. package/web-app/dist/assets/{rotate-ccw-BuGEgNBP.js → rotate-ccw-jJugFDir.js} +1 -1
  67. package/web-app/dist/assets/{scroll-text-D3I5iPxb.js → scroll-text-BiJUO5k9.js} +1 -1
  68. package/web-app/dist/assets/{server-CmqFqyr6.js → server-US70f4RI.js} +1 -1
  69. package/web-app/dist/assets/{shield-alert-BDfXzBqa.js → shield-alert-C7ig1ziQ.js} +1 -1
  70. package/web-app/dist/assets/{thumbs-up-_tO386xs.js → thumbs-up-D3SDwx7o.js} +1 -1
  71. package/web-app/dist/assets/{trash-2-BY17xDYh.js → trash-2-CikJqAs_.js} +1 -1
  72. package/web-app/dist/assets/{trending-down-2q53wyOn.js → trending-down-Bcyok37Q.js} +1 -1
  73. package/web-app/dist/assets/{trending-up-k3BMoWKv.js → trending-up-CFJbw392.js} +1 -1
  74. package/web-app/dist/assets/{upload-BchR2Xs4.js → upload-BvhLwHkC.js} +1 -1
  75. package/web-app/dist/assets/{usePolling-DO8v6wKt.js → usePolling-Bck_ydy1.js} +1 -1
  76. package/web-app/dist/assets/{user-ThJCW69N.js → user-BpuwYFeB.js} +1 -1
  77. package/web-app/dist/index.html +1 -1
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: An autonomous software factory that knows what it is supposed to deliver, and proves it did. Use when the user says Loki Mode or asks to build, fix or verify software autonomously.
4
4
  ---
5
5
 
6
- # Loki Mode v11.17.37
6
+ # Loki Mode v11.17.39
7
7
 
8
8
  **Current entry points (Loki 10 engine):** `loki "<task>"`, `loki owner/repo#N` and `loki quick "<task>"` run the Loki 10 engine and end with a signed receipt (`loki verify`). `loki backlog owner/repo --all` runs many issues in parallel. Guide: `docs/v10/GUIDE.md`. The skill instructions below, and `loki start`, describe the previous bash loop. PRD-file starts (`loki start ./prd.md`) and the opencode provider still run on it until FC38-SWEEP ports them; the legacy-engine command and the LOKI_ENGINE switch remain removed. Prefer `loki owner/repo#N` for issues.
9
9
 
@@ -483,4 +483,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
483
483
 
484
484
  ---
485
485
 
486
- **v11.17.37 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
486
+ **v11.17.39 | [Autonomi](https://www.autonomi.dev/) flagship product | core skill**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 11.17.37
1
+ 11.17.39
@@ -3,7 +3,7 @@
3
3
  # Sourced by autonomy/loki; defines cmd_chat. Free text goes to the provider
4
4
  # in the current mode; slash commands delegate to existing loki commands.
5
5
 
6
- _LOKI_CHAT_SYSTEM_PROMPT="You are running inside Loki Mode chat. Be concise. Prefer small, verifiable changes. Loki commands available to the user: loki start (also --workspace and --deploy), loki quick, loki verify, loki receipt, loki undo. During a run, /steer sends a mid-run directive via loki steer (applied at the next plan or implement stage); /pause and /resume control the run, and /answer replies to a blocked run."
6
+ _LOKI_CHAT_SYSTEM_PROMPT="You are running inside Loki Mode chat. Be concise. Prefer small, verifiable changes. Loki commands available to the user: loki start (also --workspace and --deploy; deploy takes --destination, --canary, --env-file, --domain and --health-url), loki quick, loki verify (loki verify --url https://host checks a live deploy against its provenance), loki receipt, loki undo. During a run, /steer sends a mid-run directive via loki steer (applied at the next plan or implement stage); /pause and /resume control the run, and /answer replies to a blocked run."
7
7
 
8
8
  _chat_loki_bin() {
9
9
  local d="${_LOKI_SCRIPT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
@@ -50,7 +50,8 @@ Loki chat commands:
50
50
  /resume loki resume
51
51
  /answer <text> loki answer --text: answer a BLOCKED run with a follow-up run
52
52
  /workspace <name> <brief> loki start --workspace (multi-repo run, composite receipt)
53
- /deploy [--health-url U] [--auto-rollback] loki deploy --execute after you confirm (gated on a VERIFIED receipt)
53
+ /deploy [--destination D] [--canary] [--env-file F] [--domain H] [--health-url U] [--auto-rollback]
54
+ loki deploy --execute after you confirm (gated on a VERIFIED receipt)
54
55
  /attach attach to the current or most recent loki start run in this repo
55
56
  /quick <task> loki quick
56
57
  /edit <change> apply one change (build mode for this turn)
@@ -749,18 +749,25 @@ loki_maybe_apply_config_file() {
749
749
  path="$LOKI_CONFIG_FILE"
750
750
  fi
751
751
  # 2. scan args (explicit flag overrides the env var).
752
- local prev=""
753
- local a
752
+ # With --deploy on the line, --env-file is the deploy env file, not config.
753
+ local prev="" a ef_cfg=1
754
+ for a in "$@"; do [ "$a" = "--deploy" ] && ef_cfg=0; done
754
755
  for a in "$@"; do
755
756
  case "$prev" in
756
- --config|--vars|--env-file)
757
+ --config|--vars)
757
758
  path="$a"
758
759
  ;;
760
+ --env-file)
761
+ [ "$ef_cfg" = 1 ] && path="$a"
762
+ ;;
759
763
  esac
760
764
  case "$a" in
761
- --config=*|--vars=*|--env-file=*)
765
+ --config=*|--vars=*)
762
766
  path="${a#*=}"
763
767
  ;;
768
+ --env-file=*)
769
+ [ "$ef_cfg" = 1 ] && path="${a#*=}"
770
+ ;;
764
771
  esac
765
772
  prev="$a"
766
773
  done
@@ -43,6 +43,7 @@ _commit_scan_secret_file() {
43
43
  'github_pat_[A-Za-z0-9_]{60,}' # GitHub fine-grained PAT
44
44
  'xox[baprs]-[A-Za-z0-9-]{10,}' # Slack token (xoxb-/xoxp-/...)
45
45
  'sk-[A-Za-z0-9]{20,}' # OpenAI-style secret key
46
+ 'sk-ant-[A-Za-z0-9_-]{20,}' # Anthropic API key (hyphenated body)
46
47
  'AIza[0-9A-Za-z_-]{35}' # Google API key
47
48
  'glpat-[A-Za-z0-9_-]{20,}' # GitLab personal access token
48
49
  )
package/autonomy/loki CHANGED
@@ -1904,6 +1904,9 @@ show_help_all() {
1904
1904
  echo " --openspec PATH Use OpenSpec change directory as input"
1905
1905
  echo " --config PATH Load settings from a config file (.yaml/.json/.env). Alias: --env-file, --vars"
1906
1906
  echo " Precedence: CLI flag > env var > --config file > defaults. Secrets via \${VAR}."
1907
+ echo " --deploy After a start that exits 0, run the gated loki deploy --execute. Deploy flags: --destination preview|staging|production,"
1908
+ echo " --canary, --env-file FILE (deploy env, not config, with --deploy), --domain HOST, --github (publish after deploy),"
1909
+ echo " --health-url URL, --health-match STR, --auto-rollback"
1907
1910
  echo ""
1908
1911
  echo "Deprecated alias 'run': 'loki run <issue-ref>' still works as an alias"
1909
1912
  echo "for 'loki start'. See its options with 'loki run --help'."
@@ -2644,8 +2647,31 @@ cmd_start() {
2644
2647
  # that ended 0. cmd_deploy owns every gate: no VERIFIED HEAD-anchored
2645
2648
  # receipt means a refusal, never a deploy.
2646
2649
  local _ps_ws="" _ps_deploy=0 _ps_health="" _ps_hmatch="" _ps_rb=0 _ps_rest=() _ps_brief=() _ps_a
2650
+ local _ps_dest="" _ps_canary=0 _ps_envf="" _ps_domain="" _ps_gh=0 _ps_dep_in=0
2651
+ # --env-file and --github already mean config alias and issue sync for a plain
2652
+ # start; they are deploy flags only when --deploy is on the line.
2653
+ for _ps_a in "$@"; do [ "$_ps_a" = "--deploy" ] && _ps_dep_in=1; done
2647
2654
  while [ $# -gt 0 ]; do
2648
2655
  case "$1" in
2656
+ --destination)
2657
+ [ -n "${2:-}" ] && [ "${2#-}" = "$2" ] || { echo -e "${RED}--destination requires preview, staging or production${NC}" >&2; exit 2; }
2658
+ _ps_dest="$2"; shift 2 ;;
2659
+ --destination=*) _ps_dest="${1#*=}"; shift ;;
2660
+ --domain)
2661
+ [ -n "${2:-}" ] && [ "${2#-}" = "$2" ] || { echo -e "${RED}--domain requires a hostname${NC}" >&2; exit 2; }
2662
+ _ps_domain="$2"; shift 2 ;;
2663
+ --domain=*) _ps_domain="${1#*=}"; shift ;;
2664
+ --env-file)
2665
+ if [ "$_ps_dep_in" != 1 ]; then _ps_rest+=("$1"); shift; continue; fi
2666
+ [ -n "${2:-}" ] && [ "${2#-}" = "$2" ] || { echo -e "${RED}--env-file requires a file path${NC}" >&2; exit 2; }
2667
+ _ps_envf="$2"; shift 2 ;;
2668
+ --env-file=*)
2669
+ if [ "$_ps_dep_in" = 1 ]; then _ps_envf="${1#*=}"; else _ps_rest+=("$1"); fi
2670
+ shift ;;
2671
+ --canary) _ps_canary=1; shift ;;
2672
+ --github)
2673
+ if [ "$_ps_dep_in" = 1 ]; then _ps_gh=1; else _ps_rest+=("$1"); fi
2674
+ shift ;;
2649
2675
  --workspace)
2650
2676
  [ -n "${2:-}" ] && [ "${2#-}" = "$2" ] || { echo -e "${RED}--workspace requires a workspace name${NC}" >&2; exit 2; }
2651
2677
  _ps_ws="$2"; shift 2 ;;
@@ -2663,8 +2689,8 @@ cmd_start() {
2663
2689
  *) _ps_rest+=("$1"); shift ;;
2664
2690
  esac
2665
2691
  done
2666
- if { [ -n "$_ps_health" ] || [ -n "$_ps_hmatch" ] || [ "$_ps_rb" = 1 ]; } && [ "$_ps_deploy" != 1 ]; then
2667
- echo -e "${RED}--health-url, --health-match and --auto-rollback apply only with --deploy${NC}" >&2
2692
+ if { [ -n "$_ps_health" ] || [ -n "$_ps_hmatch" ] || [ "$_ps_rb" = 1 ] || [ -n "$_ps_dest" ] || [ "$_ps_canary" = 1 ] || [ -n "$_ps_domain" ]; } && [ "$_ps_deploy" != 1 ]; then
2693
+ echo -e "${RED}--health-url, --health-match, --auto-rollback, --destination, --canary and --domain apply only with --deploy${NC}" >&2
2668
2694
  exit 2
2669
2695
  fi
2670
2696
  if [ -n "$_ps_ws" ]; then
@@ -2708,8 +2734,17 @@ cmd_start() {
2708
2734
  [ -n "$_ps_health" ] && _ps_dargs+=(--health-url "$_ps_health")
2709
2735
  [ -n "$_ps_hmatch" ] && _ps_dargs+=(--health-match "$_ps_hmatch")
2710
2736
  [ "$_ps_rb" = 1 ] && _ps_dargs+=(--auto-rollback)
2711
- cmd_deploy "${_ps_dargs[@]}"
2712
- return $?
2737
+ [ -n "$_ps_dest" ] && _ps_dargs+=(--destination "$_ps_dest")
2738
+ [ "$_ps_canary" = 1 ] && _ps_dargs+=(--canary)
2739
+ [ -n "$_ps_envf" ] && _ps_dargs+=(--env-file "$_ps_envf")
2740
+ [ -n "$_ps_domain" ] && _ps_dargs+=(--domain "$_ps_domain")
2741
+ local _ps_drc=0
2742
+ cmd_deploy "${_ps_dargs[@]}" || _ps_drc=$?
2743
+ if [ "$_ps_drc" -ne 0 ]; then return "$_ps_drc"; fi
2744
+ if [ "$_ps_gh" = 1 ]; then
2745
+ ( cmd_github publish ) || _ps_drc=$?
2746
+ fi
2747
+ return "$_ps_drc"
2713
2748
  fi
2714
2749
  # P12-07b: a bare `loki start --resume [run-id]` goes to the same engine10
2715
2750
  # resume path as `loki resume` (util/run_outputs.ts resumeMain), never into a
@@ -2841,6 +2876,11 @@ cmd_start() {
2841
2876
  echo " --quiet Suppress [INFO]/[STEP]; warnings and errors still print"
2842
2877
  echo " --log-level LEVEL debug | info | warn | error (default: info)"
2843
2878
  echo " --max-duration DUR Wall-clock cap: seconds, or 90m / 2h (default: none)"
2879
+ echo " --deploy After a start that exits 0, run the gated loki deploy --execute. Deploy flags:"
2880
+ echo " --destination preview|staging|production, --canary, --domain HOST,"
2881
+ echo " --env-file FILE (the deploy env file, not config, when --deploy is set),"
2882
+ echo " --health-url URL, --health-match STR, --auto-rollback,"
2883
+ echo " and, with --deploy, the flag --github runs loki github publish after a good deploy"
2844
2884
  echo ""
2845
2885
  echo "Issue-mode options (used when input is an ISSUE-REF or a --brief):"
2846
2886
  echo " --dry-run Preview generated PRD without starting"
@@ -10009,7 +10049,7 @@ _deploy_latest_receipt() {
10009
10049
  return 0
10010
10050
  }
10011
10051
 
10012
- # _deploy_record <dir> <command> <exit_code> <run_id> <anchor_sha> [state] [health_status] [health_url] [rb_attempted] [rb_exit] [receipt_sha256] [started_at] [finished_at] [target] [canary] [post_rollback_health] [health_http_code] [health_match] [tree_sha] [destination] [deployed_url] [health_url_source] [env_keys_csv] [env_source]
10052
+ # _deploy_record <dir> <command> <exit_code> <run_id> <anchor_sha> [state] [health_status] [health_url] [rb_attempted] [rb_exit] [receipt_sha256] [started_at] [finished_at] [target] [canary] [post_rollback_health] [health_http_code] [health_match] [tree_sha] [destination] [deployed_url] [health_url_source] [env_keys_csv] [env_source] [domain] [domain_status] [tls_status] [tls_not_after] [domain_provenance]
10013
10053
  # Writes the executed deploy's own record, then signs it (loki.deploy/1, Ed25519 via the
10014
10054
  # receipt signer in loki-ts/src/util/receipt_signer.ts) so it is bound to the receipt that
10015
10055
  # authorized it; `loki verify <record>` re-checks it offline. An unsigned record is reported
@@ -10037,6 +10077,8 @@ _deploy_record() {
10037
10077
  # --env-file: key NAMES only (comma-joined, validated identifiers) and the file
10038
10078
  # path. A value is never passed here, so the signed record cannot carry one.
10039
10079
  local env_keys_csv="${23:-}" env_source="${24:-}"
10080
+ # --domain: tls_status is "unchecked" unless the openssl proof ran and said otherwise.
10081
+ local domain_rec="${25:-}" domain_status="${26:-}" tls_status="${27:-unchecked}" tls_not_after="${28:-}" domain_prov="${29:-}"
10040
10082
  local ddir="$dir/.loki/deploys"
10041
10083
  [ -d "${LOKI_DIR:-}" ] && ddir="${LOKI_DIR}/deploys"
10042
10084
  mkdir -p "$ddir" 2>/dev/null || return 0
@@ -10046,6 +10088,7 @@ _deploy_record() {
10046
10088
  DR_RBA="$rb_attempted" DR_RBX="$rb_exit" DR_RSHA="$receipt_sha" DR_T0="$started_at" \
10047
10089
  DR_T1="$finished_at" DR_TGT="$target" DR_CAN="$canary" DR_PRB="$post_rb" DR_HC="$hcode_rec" DR_HM="$hmatch" DR_TREE="$tree_sha" DR_DEST="$dest_rec" DR_DURL="$deployed_url" DR_HSRC="$hsrc" \
10048
10090
  DR_ENVK="$env_keys_csv" DR_ENVS="$env_source" \
10091
+ DR_DOM="$domain_rec" DR_DOMS="$domain_status" DR_TLS="$tls_status" DR_TLSA="$tls_not_after" DR_DOMP="$domain_prov" \
10049
10092
  python3 -c '
10050
10093
  import json, os, datetime
10051
10094
  state = os.environ.get("DR_STATE") or "anchored"
@@ -10079,6 +10122,11 @@ json.dump({
10079
10122
  "health_url_source": os.environ.get("DR_HSRC", ""),
10080
10123
  "env_keys": sorted(k for k in os.environ.get("DR_ENVK", "").split(",") if k),
10081
10124
  "env_source": os.environ.get("DR_ENVS", ""),
10125
+ "domain": os.environ.get("DR_DOM", ""),
10126
+ "domain_status": os.environ.get("DR_DOMS", ""),
10127
+ "tls_status": os.environ.get("DR_TLS") or "unchecked",
10128
+ "tls_not_after": os.environ.get("DR_TLSA", ""),
10129
+ "domain_provenance": os.environ.get("DR_DOMP", ""),
10082
10130
  "health_http_code": int(os.environ["DR_HC"]) if os.environ.get("DR_HC", "").isdigit() else None,
10083
10131
  }, open(os.environ["DR_OUT"], "w"), indent=2)
10084
10132
  ' 2>/dev/null || return 0
@@ -10300,6 +10348,80 @@ out("__LOKI_ENV_OK__")
10300
10348
  PYEOF
10301
10349
  }
10302
10350
 
10351
+ # _deploy_domain_step <dir> <command> <domain> <deployed_url> <head_sha>
10352
+ # --domain: attach <domain> with the provider's documented verb, then PROVE TLS with
10353
+ # openssl. Sets _DD_STATUS (attached|manual|failed), _DD_TLS (valid|expiring|invalid|
10354
+ # unchecked), _DD_NOTAFTER and _DD_PROV (match|mismatch|unchecked). Returns 6 when the
10355
+ # attach failed or the TLS handshake/cert is invalid, else 0. tls_status is "valid" ONLY
10356
+ # when the handshake gave a cert and 'openssl x509 -checkend 604800' accepted it.
10357
+ _deploy_domain_step() {
10358
+ local dir="$1" command="$2" domain="$3" durl="$4" head="$5"
10359
+ local arc=0 hint="" pem="" xout="" xrc=0 tmo="" na="" body="" psha=""
10360
+ _DD_STATUS="" _DD_TLS="unchecked" _DD_NOTAFTER="" _DD_PROV="unchecked"
10361
+ echo "Attaching domain: ${domain}"
10362
+ case "$command" in
10363
+ "vercel --prod")
10364
+ hint="Vercel needs DNS for ${domain}: A 76.76.21.21 (apex) or CNAME cname.vercel-dns.com"
10365
+ if [ -z "$durl" ]; then
10366
+ echo " The deploy printed no URL, so 'vercel alias set' has nothing to alias." >&2
10367
+ _DD_STATUS=failed
10368
+ else
10369
+ _DD_STATUS=attached
10370
+ ( cd "$dir" && vercel alias set "$durl" "$domain" ) || arc=$?
10371
+ [ "$arc" -eq 0 ] || _DD_STATUS=failed
10372
+ fi
10373
+ ;;
10374
+ "fly deploy")
10375
+ hint="Fly needs DNS for ${domain}: the A/AAAA or CNAME records 'fly certs show ${domain}' prints"
10376
+ _DD_STATUS=attached
10377
+ ( cd "$dir" && fly certs add "$domain" ) || arc=$?
10378
+ [ "$arc" -eq 0 ] || _DD_STATUS=failed
10379
+ ;;
10380
+ *)
10381
+ _DD_STATUS=manual
10382
+ hint="${command%% *} has no wired domain verb: add ${domain} in its dashboard, then point DNS at the record it shows"
10383
+ echo " Manual step: ${hint}."
10384
+ ;;
10385
+ esac
10386
+ [ "$_DD_STATUS" != failed ] || echo -e "${RED}Domain attach failed (exit ${arc}).${NC}" >&2
10387
+
10388
+ if ! command -v openssl >/dev/null 2>&1; then
10389
+ echo "TLS: openssl not found. tls_status=unchecked (never a claim of valid)."
10390
+ else
10391
+ command -v timeout >/dev/null 2>&1 && tmo="timeout 15"
10392
+ # shellcheck disable=SC2086
10393
+ pem="$($tmo openssl s_client -servername "$domain" -connect "${domain}:443" </dev/null 2>/dev/null)" || true
10394
+ case "$pem" in
10395
+ *"BEGIN CERTIFICATE"*)
10396
+ xout="$(printf '%s\n' "$pem" | openssl x509 -noout -enddate -checkend 604800 2>/dev/null)" || xrc=$?
10397
+ na="$(sed -n 's/^notAfter=//p' <<<"$xout" | head -n 1)"
10398
+ if [ -z "$na" ]; then _DD_TLS=invalid
10399
+ elif [ "$xrc" -eq 0 ]; then _DD_TLS=valid
10400
+ else _DD_TLS=expiring; fi
10401
+ [ -z "$na" ] || _DD_NOTAFTER="$(python3 -c 'import sys,datetime
10402
+ try: print(datetime.datetime.strptime(sys.argv[1].strip(), "%b %d %H:%M:%S %Y GMT").strftime("%Y-%m-%dT%H:%M:%SZ"))
10403
+ except Exception: print(sys.argv[1].strip())' "$na" 2>/dev/null || printf '%s' "$na")"
10404
+ ;;
10405
+ *) _DD_TLS=invalid ;;
10406
+ esac
10407
+ echo "TLS: ${domain} tls_status=${_DD_TLS}${_DD_NOTAFTER:+ (not after ${_DD_NOTAFTER})}"
10408
+ [ "$_DD_TLS" != expiring ] || echo -e "${YELLOW} The certificate expires within 7 days.${NC}" >&2
10409
+ fi
10410
+
10411
+ if [ "$_DD_TLS" = invalid ]; then
10412
+ echo -e "${RED}TLS invalid for ${domain}: no usable certificate was served. DNS may not point at the provider yet.${NC}" >&2
10413
+ [ -z "$hint" ] || echo " ${hint}." >&2
10414
+ elif command -v curl >/dev/null 2>&1; then
10415
+ body="$(curl -fsS --max-time 5 "https://${domain}/.well-known/loki-provenance.json" 2>/dev/null)" || body=""
10416
+ psha="$(python3 -c 'import json,sys;print(json.loads(sys.stdin.read()).get("head_sha") or "")' <<<"$body" 2>/dev/null || true)"
10417
+ if [ -n "$psha" ] && [ -n "$head" ] && [ "$psha" = "$head" ]; then _DD_PROV=match
10418
+ elif [ -n "$psha" ]; then _DD_PROV=mismatch; echo -e "${YELLOW}Provenance head_sha ${psha:0:12} differs from the deployed ${head:0:12}.${NC}" >&2; fi
10419
+ echo "Provenance: ${_DD_PROV}"
10420
+ fi
10421
+ if [ "$_DD_STATUS" = failed ] || [ "$_DD_TLS" = invalid ]; then return 6; fi
10422
+ return 0
10423
+ }
10424
+
10303
10425
  # _deploy_try_execute <dir> <type>
10304
10426
  # The ONLY place a deploy command is ever run. Evaluates the gate, and either
10305
10427
  # executes + records, or REFUSES naming every failed check. Returns the deploy
@@ -10592,12 +10714,21 @@ _deploy_try_execute() {
10592
10714
  fi
10593
10715
  fi
10594
10716
 
10717
+ # --domain: only after a deploy that exited 0 and did not fail its health probe.
10718
+ local domain="${_LOKI_DEPLOY_DOMAIN:-}" dom_rc=0
10719
+ _DD_STATUS="" _DD_TLS="unchecked" _DD_NOTAFTER="" _DD_PROV=""
10720
+ if [ -n "$domain" ] && [ "$rc" -eq 0 ] && [ "$health_status" != "FAILED_HEALTH" ]; then
10721
+ _deploy_domain_step "$dir" "$command" "$domain" "$deployed_url" "$anchor" || dom_rc=$?
10722
+ [ "$_DD_STATUS" != attached ] || exec_disp="${exec_disp}; domain ${domain}"
10723
+ fi
10724
+
10595
10725
  local rec rsha
10596
10726
  finished_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
10597
10727
  rsha="$(python3 -c 'import json,sys;print((json.load(open(sys.argv[1])).get("verification") or {}).get("hash") or "")' "$pj" 2>/dev/null || printf '')"
10598
10728
  rec="$(_deploy_record "$dir" "$exec_disp" "$rc" "${run_id:-unknown}" "${anchor:-unknown}" \
10599
10729
  "$anchor_state" "$health_status" "$health_url" "$rb_attempted" "$rb_exit" \
10600
- "$rsha" "$started_at" "$finished_at" "${command%% *}" "$canary" "$post_rb" "$hcode" "$health_match" "$(git -C "$dir" rev-parse 'HEAD^{tree}' 2>/dev/null || true)" "$dest" "$deployed_url" "$health_src" "$env_keys" "$env_file")"
10730
+ "$rsha" "$started_at" "$finished_at" "${command%% *}" "$canary" "$post_rb" "$hcode" "$health_match" "$(git -C "$dir" rev-parse 'HEAD^{tree}' 2>/dev/null || true)" "$dest" "$deployed_url" "$health_src" "$env_keys" "$env_file" \
10731
+ "$domain" "$_DD_STATUS" "$_DD_TLS" "$_DD_NOTAFTER" "$_DD_PROV")"
10601
10732
  echo ""
10602
10733
  if [ "$rc" -eq 0 ]; then
10603
10734
  echo -e "${GREEN}Deploy command exited 0.${NC}"
@@ -10628,6 +10759,8 @@ _deploy_try_execute() {
10628
10759
  [ "$post_rb" = "FAILED_HEALTH" ] && return 5
10629
10760
  return 4
10630
10761
  fi
10762
+ # 6: the domain could not be attached or its TLS is invalid.
10763
+ if [ "$rc" -eq 0 ] && [ "$dom_rc" -ne 0 ]; then return 6; fi
10631
10764
  return "$rc"
10632
10765
  }
10633
10766
 
@@ -10746,7 +10879,7 @@ cmd_deploy() {
10746
10879
  local LOKI_DEPLOY_ALLOW_UNANCHORED=false
10747
10880
  # Per-invocation like --execute; local so an ambient env var cannot set them.
10748
10881
  local _LOKI_DEPLOY_HEALTH_URL="" _LOKI_DEPLOY_HEALTH_MATCH="" _LOKI_DEPLOY_AUTO_ROLLBACK=false _LOKI_DEPLOY_CANARY=false _LOKI_DEPLOY_DESTINATION=""
10749
- local _LOKI_DEPLOY_ENV_FILE=""
10882
+ local _LOKI_DEPLOY_ENV_FILE="" _LOKI_DEPLOY_DOMAIN=""
10750
10883
 
10751
10884
  # Arg parse (mirror cmd_preview: lenient on unknown args -> no behavior drift).
10752
10885
  while [ $# -gt 0 ]; do
@@ -10754,7 +10887,7 @@ cmd_deploy() {
10754
10887
  --help|-h|help)
10755
10888
  echo -e "${BOLD}Loki Mode -- deploy command advisory (print-only by default)${NC}"
10756
10889
  echo ""
10757
- echo "Usage: loki deploy [--dir <path>] [--no-clip] [--execute [--canary] [--env-file FILE]] [--destination preview|staging|production] [--health-url URL [--health-match STR] [--auto-rollback]]"
10890
+ echo "Usage: loki deploy [--dir <path>] [--no-clip] [--execute [--canary] [--env-file FILE] [--domain HOST]] [--destination preview|staging|production] [--health-url URL [--health-match STR] [--auto-rollback]]"
10758
10891
  echo ""
10759
10892
  echo "Detects your project type and your installed cloud CLI (and any"
10760
10893
  echo "CI/CD pipeline), then PRINTS the exact deploy command for YOU to run."
@@ -10854,6 +10987,15 @@ cmd_deploy() {
10854
10987
  echo " step fails the deploy is not run. Values are masked as K=*** in"
10855
10988
  echo " output; the deploy record stores env_keys (names) and env_source,"
10856
10989
  echo " never values."
10990
+ echo " --domain HOST With --execute, after a deploy that exited 0 and is not FAILED_HEALTH:"
10991
+ echo " attach HOST (Vercel: 'vercel alias set <deployed_url> HOST'; Fly:"
10992
+ echo " 'fly certs add HOST'; Netlify and Cloudflare print the manual dashboard"
10993
+ echo " step, domain_status=manual). Then prove TLS with openssl s_client and"
10994
+ echo " 'openssl x509 -checkend 604800' and re-probe"
10995
+ echo " https://HOST/.well-known/loki-provenance.json. The record stores domain,"
10996
+ echo " domain_status, tls_status (valid|expiring|invalid|unchecked) and"
10997
+ echo " tls_not_after; no openssl means unchecked, never valid. An invalid TLS"
10998
+ echo " handshake or a failed attach exits 6 and prints the DNS record needed."
10857
10999
  echo " --allow-unanchored"
10858
11000
  echo " Proceed when the receipt records NO baseline (nothing"
10859
11001
  echo " about this tree is proven). Never bypasses a recorded"
@@ -10914,6 +11056,16 @@ cmd_deploy() {
10914
11056
  _LOKI_DEPLOY_ENV_FILE="${1#--env-file=}"
10915
11057
  [ -n "$_LOKI_DEPLOY_ENV_FILE" ] || { echo "loki deploy --env-file requires a file path" >&2; return 1; }
10916
11058
  ;;
11059
+ --domain)
11060
+ case "${2:-}" in
11061
+ -*|"") echo "loki deploy --domain requires a hostname" >&2; return 1 ;;
11062
+ *) _LOKI_DEPLOY_DOMAIN="$2"; shift ;;
11063
+ esac
11064
+ ;;
11065
+ --domain=*)
11066
+ _LOKI_DEPLOY_DOMAIN="${1#--domain=}"
11067
+ [ -n "$_LOKI_DEPLOY_DOMAIN" ] || { echo "loki deploy --domain requires a hostname" >&2; return 1; }
11068
+ ;;
10917
11069
  esac
10918
11070
  shift
10919
11071
  done
@@ -10931,6 +11083,18 @@ cmd_deploy() {
10931
11083
  _LOKI_DEPLOY_ENV_FILE="$(cd "$(dirname -- "$_LOKI_DEPLOY_ENV_FILE")" && pwd -P)/$(basename -- "$_LOKI_DEPLOY_ENV_FILE")"
10932
11084
  fi
10933
11085
 
11086
+ if [ -n "$_LOKI_DEPLOY_DOMAIN" ]; then
11087
+ if [ "$do_execute" != true ]; then
11088
+ echo "loki deploy --domain requires --execute (the advisory run attaches nothing)" >&2
11089
+ return 1
11090
+ fi
11091
+ _LOKI_DEPLOY_DOMAIN="$(printf '%s' "$_LOKI_DEPLOY_DOMAIN" | tr '[:upper:]' '[:lower:]')"
11092
+ if ! printf '%s' "$_LOKI_DEPLOY_DOMAIN" | grep -Eq '^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,}$'; then
11093
+ echo "loki deploy --domain: not a valid hostname: $_LOKI_DEPLOY_DOMAIN" >&2
11094
+ return 1
11095
+ fi
11096
+ fi
11097
+
10934
11098
  if [ "$_LOKI_DEPLOY_AUTO_ROLLBACK" = true ] && [ -z "$_LOKI_DEPLOY_HEALTH_URL" ]; then
10935
11099
  echo "loki deploy --auto-rollback requires --health-url (no health signal, no rollback)" >&2
10936
11100
  return 1
@@ -1175,6 +1175,7 @@ verify_secret_scan_file() {
1175
1175
  'github_pat_[A-Za-z0-9_]{60,}' # GitHub fine-grained PAT
1176
1176
  'xox[baprs]-[A-Za-z0-9-]{10,}' # Slack token (xoxb-/xoxp-/...)
1177
1177
  'sk-[A-Za-z0-9]{20,}' # OpenAI-style secret key
1178
+ 'sk-ant-[A-Za-z0-9_-]{20,}' # Anthropic API key (hyphenated body)
1178
1179
  'AIza[0-9A-Za-z_-]{35}' # Google API key
1179
1180
  'glpat-[A-Za-z0-9_-]{20,}' # GitLab personal access token
1180
1181
  )
package/completions/_loki CHANGED
@@ -179,6 +179,7 @@ function _loki {
179
179
  '--execute[Run the deploy command if the receipt gate passes]' \
180
180
  '--canary[With --execute: staged rollout (Vercel promote, Fly canary)]' \
181
181
  '--env-file[With --execute: push KEY=VALUE env via Vercel, Fly, Netlify or Cloudflare (names-only record)]:env file:_files' \
182
+ '--domain[With --execute: attach a custom domain and prove TLS with openssl]:hostname:' \
182
183
  '--allow-unanchored[Proceed when the receipt records no baseline]' \
183
184
  '--health-url[Health probe URL]:url:' \
184
185
  '--auto-rollback[Roll back on a failed health probe]' \
@@ -364,6 +365,10 @@ function _loki_start {
364
365
  '--deploy[Deploy after a VERIFIED run via loki deploy --execute]' \
365
366
  '--health-url[With --deploy: health probe URL]:url:' \
366
367
  '--auto-rollback[With --deploy: roll back on a failed health probe]' \
368
+ '--destination[With --deploy: preview, staging or production]:destination:(preview staging production)' \
369
+ '--canary[With --deploy: canary rollout]' \
370
+ '--domain[With --deploy: attach this domain and prove TLS]:hostname:' \
371
+ '--env-file[With --deploy: env file pushed to the provider]:file:_files' \
367
372
  '--background[Background mode]' \
368
373
  '--bg[Background mode]' \
369
374
  '--simple[Force simple complexity]' \
@@ -28,7 +28,7 @@ _loki_completion() {
28
28
  # If the word starts with a dash, show flags
29
29
  # Rule: list every flag bin/loki routes (Bun/engine10 ones such as --model, --max-cost, --spec, --no-pr too), not only cmd_start's own.
30
30
  if [[ "$cur" == -* ]]; then
31
- local flags="--provider --max-iterations --parallel --background --bg --simple --complex --github --no-dashboard --sandbox --skip-memory --cost --spec --spec-first --yes --budget --attempts --model --isolation --max-cost --no-pr --explain-route --brief --worktree --pr --ship --detach --workspace --deploy --health-url --auto-rollback --resume --help"
31
+ local flags="--provider --max-iterations --parallel --background --bg --simple --complex --github --no-dashboard --sandbox --skip-memory --cost --spec --spec-first --yes --budget --attempts --model --isolation --max-cost --no-pr --explain-route --brief --worktree --pr --ship --detach --workspace --deploy --health-url --auto-rollback --destination --canary --domain --env-file --resume --help"
32
32
  COMPREPLY=( $(compgen -W "${flags}" -- "$cur") )
33
33
  return 0
34
34
  fi
@@ -150,7 +150,7 @@ _loki_completion() {
150
150
 
151
151
  deploy)
152
152
  if [[ "$cur" == -* ]]; then
153
- COMPREPLY=( $(compgen -W "--execute --destination --canary --env-file --health-url --health-match --auto-rollback --dir --no-clip --allow-unanchored --help" -- "$cur") )
153
+ COMPREPLY=( $(compgen -W "--execute --destination --canary --env-file --domain --health-url --health-match --auto-rollback --dir --no-clip --allow-unanchored --help" -- "$cur") )
154
154
  return 0
155
155
  fi
156
156
  ;;
@@ -7,6 +7,6 @@ Modules:
7
7
  control: Session control helpers (status, atomic writes, origin check)
8
8
  """
9
9
 
10
- __version__ = "11.17.37"
10
+ __version__ = "11.17.39"
11
11
 
12
12
  __all__ = ["__version__"]
@@ -1,6 +1,6 @@
1
1
  # CLI reference
2
2
 
3
- Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.17.37. Do not edit by hand.
3
+ Generated from `loki-ts/src/cli/registry.ts` by `scripts/generate-stale-zero.sh` for v11.17.39. Do not edit by hand.
4
4
  Hidden and legacy-only commands are omitted; see `docs/v10/CLI-MODERN.md` for the full inventory.
5
5
 
6
6
  | Command | Description |
@@ -523,6 +523,28 @@ deliberately.
523
523
 
524
524
  ---
525
525
 
526
+ ## From brief to verified URL
527
+
528
+ One command builds from a brief, deploys the result and proves what is live.
529
+
530
+ ```bash
531
+ loki start "a todo app with login" --deploy \
532
+ --destination production --domain todo.example.com --env-file .env.production \
533
+ --health-url https://todo.example.com/health --auto-rollback --github
534
+ loki verify --url https://todo.example.com
535
+ ```
536
+
537
+ - `loki start` runs first. Only a start that exits 0 reaches the deploy; a non-zero start prints `not deploying` and returns its own code.
538
+ - `--deploy` runs `loki deploy --execute` with `--destination`, `--canary`, `--env-file`, `--domain`, `--health-url`, `--health-match` and `--auto-rollback` passed through. The deploy still refuses without a VERIFIED receipt for this exact HEAD.
539
+ - With `--deploy`, `--env-file` is the deploy env file (key names are recorded, values are not), not a config file.
540
+ - `--github` runs `loki github publish` after a deploy that exited 0. A failed deploy skips it.
541
+ - A flag value that starts with `-` exits 2. The deploy-only flags without `--deploy` exit 2.
542
+ - `loki verify --url` fetches `/.well-known/loki-provenance.json` from the live host and is VERIFIED only when its signature and cited receipt both check out.
543
+
544
+ In `loki chat`, `/deploy` takes the same deploy flags, prints the full argv and waits for a y before running.
545
+
546
+ ---
547
+
526
548
  ## Reference
527
549
 
528
550
  | Env var | Where | Meaning |
@@ -2,7 +2,7 @@
2
2
 
3
3
  Loki Mode, by [Autonomi](https://www.autonomi.dev/), turns a spec or an issue into a verified pull request with a receipt you can check. This page covers installing it, a first run, and the options around it.
4
4
 
5
- **Version:** v11.17.37
5
+ **Version:** v11.17.39
6
6
 
7
7
  **Engine note:** `loki start` is the primary command. `loki start`, `loki "<task>"`, `loki owner/repo#N` and `loki quick` all run the Loki 10 engine, whatever the flag order or input form (PRD file, issue ref, task or one-word brief). The previous loop runs only on `--engine legacy`, `LOKI_ENGINE=legacy`, or for one call when a flag Loki 10 cannot honor is present. See the [user guide](v10/GUIDE.md) and the [5-minute quickstart](QUICKSTART.md).
8
8