loki-mode 7.100.0 → 7.101.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 +8 -0
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/proof-generator.py +6 -1
- package/autonomy/loki +3 -0
- package/autonomy/run.sh +66 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/static/index.html +215 -1
- package/docs/INSTALLATION.md +1 -1
- package/loki-ts/dist/loki.js +2 -2
- package/mcp/__init__.py +1 -1
- package/package.json +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/bin/postinstall.js +0 -244
package/README.md
CHANGED
|
@@ -138,6 +138,14 @@ status check in your repository's branch-protection settings.
|
|
|
138
138
|
|
|
139
139
|
## Get Started in 30 Seconds
|
|
140
140
|
|
|
141
|
+
Zero install, zero key, zero spend. See a real Evidence Receipt right now:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npx loki-mode tour # replay a real sample receipt (no install, no key, no spend)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Ready to build? Install and run the guided first build:
|
|
148
|
+
|
|
141
149
|
```bash
|
|
142
150
|
bun install -g loki-mode # install (npm/brew/Docker also work, see below)
|
|
143
151
|
loki quickstart # one guided command: your first real build
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: loki-mode
|
|
|
3
3
|
description: Autonomous spec-driven build system with a built-in trust layer. It does not call work done until it is verified (RARV-C closure loop, 8 quality gates, completion council, verified-completion evidence gate). Triggers on "Loki Mode". Takes a spec (PRD, GitHub issue, OpenAPI doc, etc.) to deployed product with minimal human intervention. Provider-agnostic. Requires --dangerously-skip-permissions flag.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Loki Mode v7.
|
|
6
|
+
# Loki Mode v7.101.0
|
|
7
7
|
|
|
8
8
|
**You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
|
|
9
9
|
|
|
@@ -408,4 +408,4 @@ See `CHANGELOG.md` entries [7.5.7], [7.5.8], [7.5.13] for the per-fix list and r
|
|
|
408
408
|
|
|
409
409
|
---
|
|
410
410
|
|
|
411
|
-
**v7.
|
|
411
|
+
**v7.101.0 | [Autonomi](https://www.autonomi.dev/) flagship product | ~260 lines core**
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
7.
|
|
1
|
+
7.101.0
|
|
@@ -843,12 +843,17 @@ def _compute_headline(facts, degraded):
|
|
|
843
843
|
if tests_verified and not degraded and diff_nonempty:
|
|
844
844
|
return "VERIFIED"
|
|
845
845
|
# Any fact verified at all (tests/build verified, or a passed gate)?
|
|
846
|
+
# A non-empty diff is a PREREQUISITE for VERIFIED (checked above), NOT a
|
|
847
|
+
# positive fact of passage: code was written, but nothing was shown to pass.
|
|
848
|
+
# Including diff_nonempty here let a build that ran ZERO tests/gates but
|
|
849
|
+
# produced code emit "VERIFIED WITH GAPS" - a fake-green at the receipt. Only
|
|
850
|
+
# a fact that actually ran and passed (tests/build verified, or a passed gate)
|
|
851
|
+
# may qualify; otherwise the honest headline is NOT VERIFIED.
|
|
846
852
|
any_verified = (
|
|
847
853
|
tests.get("status") == "verified"
|
|
848
854
|
or build.get("status") == "verified"
|
|
849
855
|
or any(g.get("status") == "passed"
|
|
850
856
|
for g in (facts.get("quality_gates") or []))
|
|
851
|
-
or diff_nonempty
|
|
852
857
|
)
|
|
853
858
|
if any_verified and degraded:
|
|
854
859
|
return "VERIFIED WITH GAPS"
|
package/autonomy/loki
CHANGED
|
@@ -922,6 +922,7 @@ show_help() {
|
|
|
922
922
|
echo " loki start --bg ./prd.md # Start in background"
|
|
923
923
|
echo " loki start --parallel ./prd.md # Parallel mode (git worktrees)"
|
|
924
924
|
echo " loki quick \"add dark mode\" # Single-task mode (3 iters max)"
|
|
925
|
+
echo " loki tour # See a real sample result (no provider, no key, no spend)"
|
|
925
926
|
echo " loki demo # Build a sample todo app end to end"
|
|
926
927
|
echo " loki init -t saas-starter # Scaffold from template"
|
|
927
928
|
echo " loki template install <src> # Install a community PRD template"
|
|
@@ -1037,6 +1038,7 @@ show_landing() {
|
|
|
1037
1038
|
echo -e "First time here? ${CYAN}loki doctor${NC} checks your setup (an AI provider CLI is required)."
|
|
1038
1039
|
echo ""
|
|
1039
1040
|
echo "Get started (in order):"
|
|
1041
|
+
echo -e " ${CYAN}loki tour${NC} See a real result now (no provider, no key, no spend)"
|
|
1040
1042
|
echo -e " ${CYAN}loki doctor${NC} Check your setup is ready (a few seconds)"
|
|
1041
1043
|
echo -e " ${CYAN}loki quickstart${NC} Guided first build from your idea (no PRD needed)"
|
|
1042
1044
|
echo -e " ${CYAN}loki start ./prd.md${NC} Build from a spec (PRD file, GitHub issue, or no arg)"
|
|
@@ -5446,6 +5448,7 @@ cmd_welcome_terminal() {
|
|
|
5446
5448
|
echo -e " ${CYAN}Cross-project memory${NC} Lessons compound, so agents stop repeating mistakes."
|
|
5447
5449
|
echo -e " ${CYAN}Provider-agnostic${NC} Claude, Codex, Cline, or Aider. Your keys, your infra."
|
|
5448
5450
|
echo ""
|
|
5451
|
+
echo -e " Try it now: ${BOLD}loki tour${NC} (a real sample result, no provider, no key, no spend)"
|
|
5449
5452
|
echo -e " Quick start: ${BOLD}loki quickstart${NC} (from your idea) or ${BOLD}loki start ./prd.md${NC} (from a spec)"
|
|
5450
5453
|
echo -e " Docs: ${BOLD}https://www.autonomi.dev/docs${NC}"
|
|
5451
5454
|
echo ""
|
package/autonomy/run.sh
CHANGED
|
@@ -2023,6 +2023,35 @@ _loki_check_git_present() {
|
|
|
2023
2023
|
return 0
|
|
2024
2024
|
}
|
|
2025
2025
|
|
|
2026
|
+
# _loki_check_workspace_writable: the build writes state, proofs, and source into
|
|
2027
|
+
# the working directory and its .loki/ subtree on every iteration. A read-only
|
|
2028
|
+
# volume, a permission-denied directory, or a full disk would otherwise fail
|
|
2029
|
+
# silently deep inside the loop (every state write swallows errors with
|
|
2030
|
+
# `2>/dev/null || true`), leaving the user with a confusing half-built run. Catch
|
|
2031
|
+
# it once, up front, with one actionable message instead. HARD requirement.
|
|
2032
|
+
_loki_check_workspace_writable() {
|
|
2033
|
+
local dir="${TARGET_DIR:-$(pwd)}"
|
|
2034
|
+
local probe="${dir}/.loki/.write-probe.$$"
|
|
2035
|
+
# Ensure the .loki subtree can be created and written to.
|
|
2036
|
+
if ! mkdir -p "${dir}/.loki" 2>/dev/null; then
|
|
2037
|
+
if [ ! -w "$dir" ]; then
|
|
2038
|
+
log_error "Cannot create ${dir}/.loki -- the working directory is not writable. Fix permissions (e.g. chmod u+w '${dir}') or run from a directory you own, then retry."
|
|
2039
|
+
else
|
|
2040
|
+
log_error "Cannot create ${dir}/.loki. If the disk is full, free space (df -h '${dir}'); if it is a read-only mount, run from a writable location, then retry."
|
|
2041
|
+
fi
|
|
2042
|
+
return 1
|
|
2043
|
+
fi
|
|
2044
|
+
# Confirm we can actually write a byte (catches full disk / read-only mount
|
|
2045
|
+
# where the directory exists but writes fail).
|
|
2046
|
+
if ! (: > "$probe") 2>/dev/null; then
|
|
2047
|
+
rm -f "$probe" 2>/dev/null || true
|
|
2048
|
+
log_error "Cannot write to ${dir}/.loki -- check free disk space (df -h '${dir}') and that the volume is not read-only, then retry."
|
|
2049
|
+
return 1
|
|
2050
|
+
fi
|
|
2051
|
+
rm -f "$probe" 2>/dev/null || true
|
|
2052
|
+
return 0
|
|
2053
|
+
}
|
|
2054
|
+
|
|
2026
2055
|
# _loki_check_network_reachable: ADVISORY only. A fast (3s) reachability probe to
|
|
2027
2056
|
# the active provider endpoint that WARNS and continues if it cannot reach it -- a
|
|
2028
2057
|
# curl failure does not prove the provider CLI cannot connect (3s timeout under
|
|
@@ -2070,6 +2099,9 @@ validate_api_keys() {
|
|
|
2070
2099
|
if ! _loki_check_git_present; then
|
|
2071
2100
|
return 1
|
|
2072
2101
|
fi
|
|
2102
|
+
if ! _loki_check_workspace_writable; then
|
|
2103
|
+
return 1
|
|
2104
|
+
fi
|
|
2073
2105
|
if ! _loki_check_network_reachable "$provider"; then
|
|
2074
2106
|
return 1
|
|
2075
2107
|
fi
|
|
@@ -3229,6 +3261,22 @@ except Exception:
|
|
|
3229
3261
|
else
|
|
3230
3262
|
echo "Diff stat: (no changes detected vs run start, or git unavailable)"
|
|
3231
3263
|
fi
|
|
3264
|
+
# Empty-diff remedy (mirrors emit_completion_summary's on-screen line) so
|
|
3265
|
+
# a --bg or dashboard user reading COMPLETION.txt gets the same honest,
|
|
3266
|
+
# actionable guidance as a foreground user. Never implies success.
|
|
3267
|
+
case "$files_changed" in
|
|
3268
|
+
''|0)
|
|
3269
|
+
case "$outcome" in
|
|
3270
|
+
complete|max_iterations)
|
|
3271
|
+
echo ""
|
|
3272
|
+
echo "No file changes were produced this run. Likely the spec was too vague"
|
|
3273
|
+
echo "or already satisfied. Next: re-run with a more concrete spec, e.g."
|
|
3274
|
+
echo " loki start \"<one concrete, testable change you want>\""
|
|
3275
|
+
echo "or inspect what happened: loki why"
|
|
3276
|
+
;;
|
|
3277
|
+
esac
|
|
3278
|
+
;;
|
|
3279
|
+
esac
|
|
3232
3280
|
} > "$loki_dir/COMPLETION.txt" 2>/dev/null || true
|
|
3233
3281
|
|
|
3234
3282
|
# ---- Durable machine-readable file: .loki/state/completion.json -----------
|
|
@@ -3515,6 +3563,24 @@ except Exception:
|
|
|
3515
3563
|
fi
|
|
3516
3564
|
echo -e "${GREEN}|${NC} Branch: ${BOLD}${_branch}${NC}"
|
|
3517
3565
|
echo -e "${GREEN}|${NC} Files: ${BOLD}${_files}${NC} changed ${GREEN}+${_ins}${NC} / ${RED}-${_del}${NC}"
|
|
3566
|
+
# Empty-diff guard (honest, not fake-green): a run that ended without
|
|
3567
|
+
# producing ANY file change is the classic confusing new-user outcome
|
|
3568
|
+
# ("it said done, but nothing happened"). Never imply success on a 0-file
|
|
3569
|
+
# run -- surface it plainly with one actionable next step. Only for the
|
|
3570
|
+
# non-failure outcomes (a "failed"/"stopped" box already explains itself).
|
|
3571
|
+
case "$_files" in
|
|
3572
|
+
''|0)
|
|
3573
|
+
case "$_outcome" in
|
|
3574
|
+
complete|max_iterations)
|
|
3575
|
+
echo -e "${GREEN}|${NC}"
|
|
3576
|
+
echo -e "${GREEN}|${NC} ${YELLOW}No file changes were produced this run.${NC} Likely the spec was too vague"
|
|
3577
|
+
echo -e "${GREEN}|${NC} or already satisfied. Next: re-run with a more concrete spec, e.g."
|
|
3578
|
+
echo -e "${GREEN}|${NC} ${BOLD}loki start \"<one concrete, testable change you want>\"${NC}"
|
|
3579
|
+
echo -e "${GREEN}|${NC} or inspect what happened: ${BOLD}loki why${NC}"
|
|
3580
|
+
;;
|
|
3581
|
+
esac
|
|
3582
|
+
;;
|
|
3583
|
+
esac
|
|
3518
3584
|
case "$_atotal" in ''|0) : ;; *)
|
|
3519
3585
|
echo -e "${GREEN}|${NC} Spec assumptions recorded: ${BOLD}${_atotal}${NC} (${_ahigh} high) ${DIM}see .loki/assumptions/ledger.md${NC}"
|
|
3520
3586
|
;;
|
package/dashboard/__init__.py
CHANGED
|
@@ -851,6 +851,116 @@
|
|
|
851
851
|
|
|
852
852
|
/* Overview handled by <loki-overview> shadow DOM */
|
|
853
853
|
|
|
854
|
+
/* First-run hero: shown only to a brand-new user (no completed runs,
|
|
855
|
+
no active session, no current spec). Gated in JS on /api/spec/history
|
|
856
|
+
being empty so a returning-but-idle user never sees it. Hidden by
|
|
857
|
+
default; JS adds .first-run-hero--visible after the signal confirms. */
|
|
858
|
+
.first-run-hero {
|
|
859
|
+
display: none;
|
|
860
|
+
}
|
|
861
|
+
.first-run-hero--visible {
|
|
862
|
+
display: block;
|
|
863
|
+
margin-bottom: 28px;
|
|
864
|
+
background: var(--loki-bg-card);
|
|
865
|
+
border: 1px solid var(--loki-border);
|
|
866
|
+
border-radius: 8px;
|
|
867
|
+
padding: 28px 28px 24px;
|
|
868
|
+
}
|
|
869
|
+
.first-run-hero__top {
|
|
870
|
+
display: flex;
|
|
871
|
+
align-items: center;
|
|
872
|
+
gap: 12px;
|
|
873
|
+
margin-bottom: 6px;
|
|
874
|
+
}
|
|
875
|
+
.first-run-hero__icon {
|
|
876
|
+
width: 40px;
|
|
877
|
+
height: 40px;
|
|
878
|
+
flex-shrink: 0;
|
|
879
|
+
display: flex;
|
|
880
|
+
align-items: center;
|
|
881
|
+
justify-content: center;
|
|
882
|
+
border-radius: 9999px;
|
|
883
|
+
background: var(--loki-accent-glow);
|
|
884
|
+
color: var(--loki-accent);
|
|
885
|
+
}
|
|
886
|
+
.first-run-hero__icon svg { width: 22px; height: 22px; }
|
|
887
|
+
.first-run-hero__title {
|
|
888
|
+
font-family: 'DM Serif Display', Georgia, serif;
|
|
889
|
+
font-size: 1.5rem;
|
|
890
|
+
font-weight: 400;
|
|
891
|
+
color: var(--loki-text-primary);
|
|
892
|
+
letter-spacing: -0.01em;
|
|
893
|
+
}
|
|
894
|
+
.first-run-hero__desc {
|
|
895
|
+
font-size: 14px;
|
|
896
|
+
line-height: 1.6;
|
|
897
|
+
color: var(--loki-text-secondary);
|
|
898
|
+
max-width: 620px;
|
|
899
|
+
margin: 0 0 18px;
|
|
900
|
+
}
|
|
901
|
+
.first-run-hero__cmd-label {
|
|
902
|
+
font-size: 11px;
|
|
903
|
+
font-weight: 600;
|
|
904
|
+
text-transform: uppercase;
|
|
905
|
+
letter-spacing: 0.05em;
|
|
906
|
+
color: var(--loki-text-muted);
|
|
907
|
+
margin-bottom: 6px;
|
|
908
|
+
}
|
|
909
|
+
.first-run-hero__cmd {
|
|
910
|
+
display: flex;
|
|
911
|
+
align-items: center;
|
|
912
|
+
gap: 10px;
|
|
913
|
+
background: var(--loki-bg-tertiary);
|
|
914
|
+
border: 1px solid var(--loki-border);
|
|
915
|
+
border-radius: 6px;
|
|
916
|
+
padding: 10px 12px;
|
|
917
|
+
max-width: 420px;
|
|
918
|
+
margin-bottom: 14px;
|
|
919
|
+
}
|
|
920
|
+
.first-run-hero__cmd code {
|
|
921
|
+
flex: 1;
|
|
922
|
+
font-family: var(--loki-font-mono, 'JetBrains Mono', monospace);
|
|
923
|
+
font-size: 13px;
|
|
924
|
+
color: var(--loki-text-primary);
|
|
925
|
+
white-space: nowrap;
|
|
926
|
+
overflow-x: auto;
|
|
927
|
+
}
|
|
928
|
+
.first-run-hero__copy {
|
|
929
|
+
flex-shrink: 0;
|
|
930
|
+
display: inline-flex;
|
|
931
|
+
align-items: center;
|
|
932
|
+
gap: 5px;
|
|
933
|
+
background: var(--loki-accent);
|
|
934
|
+
color: #ffffff;
|
|
935
|
+
border: none;
|
|
936
|
+
border-radius: 5px;
|
|
937
|
+
padding: 6px 12px;
|
|
938
|
+
font-size: 12px;
|
|
939
|
+
font-weight: 600;
|
|
940
|
+
font-family: inherit;
|
|
941
|
+
cursor: pointer;
|
|
942
|
+
transition: background 0.15s ease;
|
|
943
|
+
}
|
|
944
|
+
.first-run-hero__copy:hover { background: var(--loki-accent-hover); }
|
|
945
|
+
.first-run-hero__copy:focus-visible {
|
|
946
|
+
outline: 2px solid var(--loki-accent);
|
|
947
|
+
outline-offset: 2px;
|
|
948
|
+
}
|
|
949
|
+
.first-run-hero__copy svg { width: 13px; height: 13px; }
|
|
950
|
+
.first-run-hero__secondary {
|
|
951
|
+
font-size: 13px;
|
|
952
|
+
color: var(--loki-text-muted);
|
|
953
|
+
line-height: 1.6;
|
|
954
|
+
}
|
|
955
|
+
.first-run-hero__secondary code {
|
|
956
|
+
font-family: var(--loki-font-mono, 'JetBrains Mono', monospace);
|
|
957
|
+
font-size: 12px;
|
|
958
|
+
background: var(--loki-bg-tertiary);
|
|
959
|
+
color: var(--loki-text-secondary);
|
|
960
|
+
padding: 1px 6px;
|
|
961
|
+
border-radius: 3px;
|
|
962
|
+
}
|
|
963
|
+
|
|
854
964
|
/* Offline Banner */
|
|
855
965
|
.offline-banner {
|
|
856
966
|
position: fixed;
|
|
@@ -1135,6 +1245,35 @@
|
|
|
1135
1245
|
<main class="main-content" id="main-content">
|
|
1136
1246
|
<!-- Overview + Tasks (combined) -->
|
|
1137
1247
|
<div class="section-page active" id="page-overview">
|
|
1248
|
+
<!-- First-run hero: a brand-new user (zero completed runs) lands here
|
|
1249
|
+
with nothing to show. Instead of three stacked passive empty
|
|
1250
|
+
states, give them one branded next action. Hidden by default;
|
|
1251
|
+
initFirstRunHero() reveals it only when /api/spec/history is
|
|
1252
|
+
empty AND no session is active AND no spec is loaded. A returning
|
|
1253
|
+
user who has shipped a run never sees it. -->
|
|
1254
|
+
<section class="first-run-hero" id="first-run-hero" role="region" aria-label="Get started with Loki" hidden>
|
|
1255
|
+
<div class="first-run-hero__top">
|
|
1256
|
+
<span class="first-run-hero__icon" aria-hidden="true">
|
|
1257
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>
|
|
1258
|
+
</span>
|
|
1259
|
+
<h2 class="first-run-hero__title">Start your first build</h2>
|
|
1260
|
+
</div>
|
|
1261
|
+
<p class="first-run-hero__desc">
|
|
1262
|
+
Loki turns a spec (a PRD, a GitHub issue, or a one-line idea) into a verified, working product.
|
|
1263
|
+
No build has run in this project yet. Run the guided quickstart in your terminal to kick off your first one, then watch it here live.
|
|
1264
|
+
</p>
|
|
1265
|
+
<div class="first-run-hero__cmd-label">Run in your terminal</div>
|
|
1266
|
+
<div class="first-run-hero__cmd">
|
|
1267
|
+
<code id="first-run-cmd">loki quickstart</code>
|
|
1268
|
+
<button type="button" class="first-run-hero__copy" id="first-run-copy" aria-label="Copy command: loki quickstart" data-copy="loki quickstart">
|
|
1269
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
|
1270
|
+
<span id="first-run-copy-label">Copy</span>
|
|
1271
|
+
</button>
|
|
1272
|
+
</div>
|
|
1273
|
+
<p class="first-run-hero__secondary">
|
|
1274
|
+
Just looking? Try a zero-key, zero-spend sample receipt: <code>loki tour</code>
|
|
1275
|
+
</p>
|
|
1276
|
+
</section>
|
|
1138
1277
|
<!-- Active spec: what Loki is building from + history of past specs -->
|
|
1139
1278
|
<div style="margin-bottom: 28px;">
|
|
1140
1279
|
<div class="section-page-header">
|
|
@@ -15819,7 +15958,82 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
15819
15958
|
|
|
15820
15959
|
// Overview cards are now handled by the <loki-overview> component
|
|
15821
15960
|
// which polls /api/status reactively via the unified API client.
|
|
15961
|
+
|
|
15962
|
+
// First-run hero: reveal only for a genuinely brand-new user. The honest
|
|
15963
|
+
// "has this project ever produced a build?" signal is /api/spec/history,
|
|
15964
|
+
// which is derived from real Evidence Receipts (never faked). We additionally
|
|
15965
|
+
// require no active session and no current spec so the hero never overlaps a
|
|
15966
|
+
// live or just-finished run. A returning-but-idle user (history > 0) never
|
|
15967
|
+
// sees it. Any fetch error -> leave the hero hidden (fail safe, no false
|
|
15968
|
+
// "you've never built" claim).
|
|
15969
|
+
(function initFirstRunHero() {
|
|
15970
|
+
var hero = document.getElementById('first-run-hero');
|
|
15971
|
+
if (!hero) return;
|
|
15972
|
+
|
|
15973
|
+
var copyBtn = document.getElementById('first-run-copy');
|
|
15974
|
+
if (copyBtn) {
|
|
15975
|
+
copyBtn.addEventListener('click', function() {
|
|
15976
|
+
var cmd = copyBtn.getAttribute('data-copy') || 'loki quickstart';
|
|
15977
|
+
var label = document.getElementById('first-run-copy-label');
|
|
15978
|
+
var done = function() {
|
|
15979
|
+
if (!label) return;
|
|
15980
|
+
var prev = label.textContent;
|
|
15981
|
+
label.textContent = 'Copied';
|
|
15982
|
+
setTimeout(function() { label.textContent = prev; }, 1600);
|
|
15983
|
+
};
|
|
15984
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
15985
|
+
navigator.clipboard.writeText(cmd).then(done).catch(function() {});
|
|
15986
|
+
} else {
|
|
15987
|
+
// Older/insecure-context fallback: select + execCommand.
|
|
15988
|
+
var ta = document.createElement('textarea');
|
|
15989
|
+
ta.value = cmd;
|
|
15990
|
+
ta.setAttribute('readonly', '');
|
|
15991
|
+
ta.style.position = 'absolute';
|
|
15992
|
+
ta.style.left = '-9999px';
|
|
15993
|
+
document.body.appendChild(ta);
|
|
15994
|
+
ta.select();
|
|
15995
|
+
try { document.execCommand('copy'); done(); } catch (e) { /* ignore */ }
|
|
15996
|
+
document.body.removeChild(ta);
|
|
15997
|
+
}
|
|
15998
|
+
});
|
|
15999
|
+
}
|
|
16000
|
+
|
|
16001
|
+
function showHero(show) {
|
|
16002
|
+
if (show) {
|
|
16003
|
+
hero.hidden = false;
|
|
16004
|
+
hero.classList.add('first-run-hero--visible');
|
|
16005
|
+
} else {
|
|
16006
|
+
hero.classList.remove('first-run-hero--visible');
|
|
16007
|
+
hero.hidden = true;
|
|
16008
|
+
}
|
|
16009
|
+
}
|
|
16010
|
+
|
|
16011
|
+
function evaluate() {
|
|
16012
|
+
Promise.all([
|
|
16013
|
+
fetch(detectedUrl + '/api/spec/history').then(function(r) { return r.ok ? r.json() : null; }).catch(function() { return null; }),
|
|
16014
|
+
fetch(detectedUrl + '/api/status').then(function(r) { return r.ok ? r.json() : null; }).catch(function() { return null; }),
|
|
16015
|
+
fetch(detectedUrl + '/api/spec').then(function(r) { return r.ok ? r.json() : null; }).catch(function() { return null; })
|
|
16016
|
+
]).then(function(res) {
|
|
16017
|
+
var history = res[0];
|
|
16018
|
+
var status = res[1];
|
|
16019
|
+
var spec = res[2];
|
|
16020
|
+
// Fail safe: if we cannot read the history signal, do not claim
|
|
16021
|
+
// "you've never built" -- keep the hero hidden.
|
|
16022
|
+
if (!history || !Array.isArray(history.history)) { showHero(false); return; }
|
|
16023
|
+
var hasEverBuilt = history.history.length > 0;
|
|
16024
|
+
var st = status && status.status ? String(status.status) : 'offline';
|
|
16025
|
+
var sessionActive = (st === 'running' || st === 'autonomous' || st === 'paused');
|
|
16026
|
+
var hasSpec = !!(spec && spec.type && spec.type !== 'none');
|
|
16027
|
+
showHero(!hasEverBuilt && !sessionActive && !hasSpec);
|
|
16028
|
+
}).catch(function() { showHero(false); });
|
|
16029
|
+
}
|
|
16030
|
+
|
|
16031
|
+
evaluate();
|
|
16032
|
+
// Re-check after a session starts/stops so the hero disappears once the
|
|
16033
|
+
// first build begins and never reappears once a run has shipped.
|
|
16034
|
+
setInterval(evaluate, 15000);
|
|
16035
|
+
})();
|
|
15822
16036
|
});
|
|
15823
|
-
|
|
16037
|
+
</script>
|
|
15824
16038
|
</body>
|
|
15825
16039
|
</html>
|
package/docs/INSTALLATION.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The flagship product of [Autonomi](https://www.autonomi.dev/). Loki Mode is a spec-driven autonomous builder with a built-in trust layer that takes any spec to a deployed product and verifies completion with evidence (quality gates plus a completion council), not just a "done" claim. Complete installation instructions for all platforms and use cases.
|
|
4
4
|
|
|
5
|
-
**Version:** v7.
|
|
5
|
+
**Version:** v7.101.0
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
package/loki-ts/dist/loki.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var z8=Object.defineProperty;var X8=($)=>$;function K8($,Q){this[$]=X8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)z8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:K8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var y1={};b(y1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as t,dirname as i$}from"path";import{fileURLToPath as q8}from"url";import{existsSync as E$}from"fs";import{homedir as J8}from"os";function V8(){let $=h1;for(let Q=0;Q<6;Q++){if(E$(t($,"VERSION"))&&E$(t($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return t(h1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(E$(t(Q,"VERSION"))&&E$(t(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function P(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(J8(),".loki")}var h1,h;var C=L(()=>{h1=i$(q8(import.meta.url));h=V8()});import{readFileSync as W8}from"fs";import{resolve as U8,dirname as H8}from"path";import{fileURLToPath as G8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.
|
|
2
|
+
var z8=Object.defineProperty;var X8=($)=>$;function K8($,Q){this[$]=X8.bind(null,Q)}var b=($,Q)=>{for(var Z in Q)z8($,Z,{get:Q[Z],enumerable:!0,configurable:!0,set:K8.bind(Q,Z)})};var L=($,Q)=>()=>($&&(Q=$($=0)),Q);var J$=import.meta.require;var y1={};b(y1,{lokiDir:()=>P,homeLokiDir:()=>T$,findRepoRootForVersion:()=>e$,REPO_ROOT:()=>h});import{resolve as t,dirname as i$}from"path";import{fileURLToPath as q8}from"url";import{existsSync as E$}from"fs";import{homedir as J8}from"os";function V8(){let $=h1;for(let Q=0;Q<6;Q++){if(E$(t($,"VERSION"))&&E$(t($,"autonomy/run.sh")))return $;let Z=i$($);if(Z===$)break;$=Z}return t(h1,"..","..","..")}function e$($){let Q=$;for(let Z=0;Z<6;Z++){if(E$(t(Q,"VERSION"))&&E$(t(Q,"autonomy/run.sh")))return Q;let z=i$(Q);if(z===Q)break;Q=z}return t($,"..","..","..")}function P(){return process.env.LOKI_DIR??t(process.cwd(),".loki")}function T$(){return t(J8(),".loki")}var h1,h;var C=L(()=>{h1=i$(q8(import.meta.url));h=V8()});import{readFileSync as W8}from"fs";import{resolve as U8,dirname as H8}from"path";import{fileURLToPath as G8}from"url";function N$(){if(Q$!==null)return Q$;let $="7.101.0";if(typeof $==="string"&&$.length>0)return Q$=$,Q$;try{let Q=H8(G8(import.meta.url)),Z=e$(Q);Q$=W8(U8(Z,"VERSION"),"utf-8").trim()}catch{Q$="unknown"}return Q$}var Q$=null;var $1=L(()=>{C()});var u1={};b(u1,{runOrThrow:()=>x8,run:()=>F,readStreamCapped:()=>f1,commandVersion:()=>N8,commandExists:()=>f,ShellError:()=>Q1,MAX_STDOUT_BYTES:()=>m1});async function f1($,Q=m1){let Z=$.getReader(),z=new TextDecoder,X="",q=0;try{while(q<Q){let{done:K,value:W}=await Z.read();if(K)break;if(!W)continue;if(q+=W.byteLength,q>Q){let V=W.byteLength-(q-Q);X+=z.decode(W.subarray(0,V),{stream:!0});break}X+=z.decode(W,{stream:!0})}X+=z.decode()}finally{try{await Z.cancel()}catch{}Z.releaseLock()}return X}async function F($,Q={}){let Z=Bun.spawn({cmd:[...$],stdout:"pipe",stderr:"pipe",env:Q.env?{...process.env,...Q.env}:process.env,cwd:Q.cwd}),z,X;if(Q.timeoutMs&&Q.timeoutMs>0)z=setTimeout(()=>{try{Z.kill("SIGTERM")}catch{}X=setTimeout(()=>{try{Z.kill("SIGKILL")}catch{}},2000)},Q.timeoutMs);try{let[q,K,W]=await Promise.all([f1(Z.stdout),new Response(Z.stderr).text(),Z.exited]);return{stdout:q,stderr:K,exitCode:W}}finally{if(z)clearTimeout(z);if(X)clearTimeout(X)}}async function x8($,Q={}){let Z=await F($,Q);if(Z.exitCode!==0)throw new Q1(`command failed (${Z.exitCode}): ${$.join(" ")}`,Z.exitCode,Z.stdout,Z.stderr);return Z}async function f($){let Q=E8($),Z=await F(["sh","-c",`command -v ${Q}`],{timeoutMs:5000});if(Z.exitCode===0)return Z.stdout.trim()||null;return null}function E8($){if(!/^[A-Za-z0-9._/-]+$/.test($))throw Error(`refused to shell-escape suspect token: ${$}`);return $}async function N8($,Q="--version"){if(!await f($))return null;let z=await F([$,Q],{timeoutMs:5000});if(z.exitCode!==0)return null;return((z.stdout||z.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var m1=16777216,Q1;var d=L(()=>{Q1=class Q1 extends Error{message;exitCode;stdout;stderr;constructor($,Q,Z,z){super($);this.message=$;this.exitCode=Q;this.stdout=Z;this.stderr=z;this.name="ShellError"}}});function r($){return S8?"":$}var S8,T,S,_,lZ,I,k,y,J;var c=L(()=>{S8=(process.env.NO_COLOR??"").length>0;T=r("\x1B[0;31m"),S=r("\x1B[0;32m"),_=r("\x1B[1;33m"),lZ=r("\x1B[0;34m"),I=r("\x1B[0;36m"),k=r("\x1B[1m"),y=r("\x1B[2m"),J=r("\x1B[0m")});import{existsSync as u8}from"fs";async function Z$(){if(A$!==void 0)return A$;let $="/opt/homebrew/bin/python3.12";if(u8($))return A$=$,$;let Q=await f("python3.12");if(Q)return A$=Q,Q;let Z=await f("python3");return A$=Z,Z}async function z$($,Q={}){let Z=await Z$();if(!Z)return{stdout:"",stderr:"python3 not found",exitCode:127};return F([Z,"-c",$],Q)}var A$;var V$=L(()=>{d()});var q0={};b(q0,{runStatus:()=>W3});import{existsSync as v,readFileSync as U$,readdirSync as i1,statSync as e1}from"fs";import{resolve as D,basename as $3}from"path";import{homedir as Q3}from"os";function $0($){let Q=Math.trunc($);if(Q>=1e6)return`${(Math.trunc(Q/1e6*10)/10).toFixed(1)}M`;if(Q>=1000)return`${(Math.trunc(Q/1000*10)/10).toFixed(1)}K`;return String(Q)}function Q0($,Q,Z){if(Q===0)return null;let z=Math.trunc($*100/Q),X=Math.trunc($*D$/Q);if(X>D$)X=D$;let q=D$-X,K=S;if(z>=80)K=T;else if(z>=50)K=_;let W="=".repeat(Math.max(0,X))+" ".repeat(Math.max(0,q)),V=$0($),U=$0(Q);return` ${k}${Z}${J} ${K}[${W}]${J} ${z}% (${V} / ${U})`}async function z3(){if(await f("jq"))return!0;return process.stdout.write(`${T}Error: jq is required but not installed.${J}
|
|
3
3
|
`),process.stdout.write(`Install with:
|
|
4
4
|
`),process.stdout.write(` brew install jq (macOS)
|
|
5
5
|
`),process.stdout.write(` apt install jq (Debian/Ubuntu)
|
|
@@ -802,4 +802,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
|
|
|
802
802
|
`),2}default:return process.stderr.write(`Unknown command: ${Q}
|
|
803
803
|
`),process.stderr.write(Z8),2}}r1();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var FZ=await jZ(Bun.argv.slice(2));process.exit(FZ);
|
|
804
804
|
|
|
805
|
-
//# debugId=
|
|
805
|
+
//# debugId=01C60773ABB86B6464756E2164756E21
|
package/mcp/__init__.py
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loki-mode",
|
|
3
3
|
"mcpName": "io.github.asklokesh/loki-mode",
|
|
4
|
-
"version": "7.
|
|
4
|
+
"version": "7.101.0",
|
|
5
5
|
"description": "Loki Mode by Autonomi. Autonomous spec-to-product system: takes a PRD, GitHub issue, OpenAPI/JSON/YAML, or one-line brief to a deployed app via the RARV-C closure loop with 8 quality gates. Provider-agnostic (Claude Code, OpenAI Codex, Cline, Aider).",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"agent",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
3
|
"name": "loki-mode",
|
|
4
4
|
"displayName": "Loki Mode",
|
|
5
|
-
"version": "7.
|
|
5
|
+
"version": "7.101.0",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|
package/bin/postinstall.js
DELETED
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Loki Mode postinstall script
|
|
4
|
-
* Sets up the skill symlink for Claude Code, Codex CLI, and Gemini CLI
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
const fs = require('fs');
|
|
8
|
-
const path = require('path');
|
|
9
|
-
const os = require('os');
|
|
10
|
-
|
|
11
|
-
const homeDir = os.homedir();
|
|
12
|
-
const packageDir = path.join(__dirname, '..');
|
|
13
|
-
|
|
14
|
-
const version = (() => {
|
|
15
|
-
try { return fs.readFileSync(path.join(packageDir, 'VERSION'), 'utf8').trim(); }
|
|
16
|
-
catch { return require(path.join(packageDir, 'package.json')).version; }
|
|
17
|
-
})();
|
|
18
|
-
|
|
19
|
-
console.log('');
|
|
20
|
-
console.log(`Loki Mode v${version} installed!`);
|
|
21
|
-
console.log('');
|
|
22
|
-
|
|
23
|
-
// Multi-provider skill targets
|
|
24
|
-
const skillTargets = [
|
|
25
|
-
{ dir: path.join(homeDir, '.claude', 'skills', 'loki-mode'), name: 'Claude Code' },
|
|
26
|
-
{ dir: path.join(homeDir, '.codex', 'skills', 'loki-mode'), name: 'Codex CLI' },
|
|
27
|
-
{ dir: path.join(homeDir, '.gemini', 'skills', 'loki-mode'), name: 'Gemini CLI' },
|
|
28
|
-
];
|
|
29
|
-
|
|
30
|
-
const results = [];
|
|
31
|
-
|
|
32
|
-
for (const target of skillTargets) {
|
|
33
|
-
try {
|
|
34
|
-
const skillParent = path.dirname(target.dir);
|
|
35
|
-
|
|
36
|
-
if (!fs.existsSync(skillParent)) {
|
|
37
|
-
fs.mkdirSync(skillParent, { recursive: true });
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Remove existing symlink/directory
|
|
41
|
-
if (fs.existsSync(target.dir)) {
|
|
42
|
-
const stats = fs.lstatSync(target.dir);
|
|
43
|
-
if (stats.isSymbolicLink()) {
|
|
44
|
-
fs.unlinkSync(target.dir);
|
|
45
|
-
} else {
|
|
46
|
-
// Existing real directory (not a symlink) - back it up and replace
|
|
47
|
-
const backupDir = target.dir + '.backup.' + Date.now();
|
|
48
|
-
console.log(`[WARNING] Existing non-symlink installation found at ${target.dir}`);
|
|
49
|
-
console.log(` Backing up to: ${backupDir}`);
|
|
50
|
-
try {
|
|
51
|
-
fs.renameSync(target.dir, backupDir);
|
|
52
|
-
} catch (backupErr) {
|
|
53
|
-
console.log(` Could not back up: ${backupErr.message}`);
|
|
54
|
-
results.push({ name: target.name, path: target.dir, ok: false });
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Create symlink
|
|
61
|
-
if (!fs.existsSync(target.dir)) {
|
|
62
|
-
fs.symlinkSync(packageDir, target.dir);
|
|
63
|
-
}
|
|
64
|
-
results.push({ name: target.name, path: target.dir, ok: true });
|
|
65
|
-
} catch (err) {
|
|
66
|
-
results.push({ name: target.name, path: target.dir, ok: false, error: err.message });
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Print summary
|
|
71
|
-
console.log('Skills installed:');
|
|
72
|
-
for (const r of results) {
|
|
73
|
-
const icon = r.ok ? 'OK' : 'SKIP';
|
|
74
|
-
const shortPath = r.path.replace(homeDir, '~');
|
|
75
|
-
if (r.ok) {
|
|
76
|
-
console.log(` [${icon}] ${r.name.padEnd(12)} (${shortPath})`);
|
|
77
|
-
} else {
|
|
78
|
-
console.log(` [${icon}] ${r.name.padEnd(12)} (${shortPath}) - ${r.error || 'backup failed'}`);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (results.some(r => !r.ok)) {
|
|
83
|
-
console.log('');
|
|
84
|
-
console.log('To fix missing symlinks:');
|
|
85
|
-
console.log(` loki setup-skill`);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// PATH check: warn if npm global bin is not in PATH, and auto-fix on macOS Homebrew
|
|
89
|
-
try {
|
|
90
|
-
const { execSync } = require('child_process');
|
|
91
|
-
const npmBin = execSync('npm bin -g 2>/dev/null || npm prefix -g', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
|
92
|
-
const npmBinDir = npmBin.endsWith('/bin') ? npmBin : npmBin + '/bin';
|
|
93
|
-
const pathDirs = (process.env.PATH || '').split(':');
|
|
94
|
-
const lokiBinInPath = pathDirs.some(d => d === npmBinDir || d === npmBin);
|
|
95
|
-
|
|
96
|
-
// On macOS with Homebrew Node, npm global bin path changes on every Node version upgrade
|
|
97
|
-
// (e.g., /opt/homebrew/Cellar/node/22.x/bin -> /opt/homebrew/Cellar/node/23.x/bin).
|
|
98
|
-
// Use the npm prefix bin directory which is stable across upgrades.
|
|
99
|
-
if (os.platform() === 'darwin') {
|
|
100
|
-
let stableDir;
|
|
101
|
-
try {
|
|
102
|
-
stableDir = execSync('npm prefix -g', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim() + '/bin';
|
|
103
|
-
} catch {
|
|
104
|
-
stableDir = '/opt/homebrew/bin';
|
|
105
|
-
}
|
|
106
|
-
if (fs.existsSync(stableDir) && stableDir !== npmBinDir) {
|
|
107
|
-
for (const bin of ['loki', 'loki-mode']) {
|
|
108
|
-
const src = path.join(npmBinDir, bin);
|
|
109
|
-
const dest = path.join(stableDir, bin);
|
|
110
|
-
try {
|
|
111
|
-
if (fs.existsSync(src)) {
|
|
112
|
-
// Remove stale symlink or existing file
|
|
113
|
-
try { fs.unlinkSync(dest); } catch {}
|
|
114
|
-
fs.symlinkSync(src, dest);
|
|
115
|
-
}
|
|
116
|
-
} catch {
|
|
117
|
-
// Silently skip if no permission (non-root)
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (!lokiBinInPath) {
|
|
124
|
-
console.log('');
|
|
125
|
-
console.log('[IMPORTANT] The `loki` command may not be in your PATH.');
|
|
126
|
-
console.log('');
|
|
127
|
-
console.log('Add the npm global bin directory to your PATH:');
|
|
128
|
-
console.log(` export PATH="${npmBinDir}:$PATH"`);
|
|
129
|
-
console.log('');
|
|
130
|
-
console.log('To make this permanent, add it to your shell config (~/.zshrc or ~/.bashrc):');
|
|
131
|
-
console.log(` echo 'export PATH="${npmBinDir}:$PATH"' >> ~/.zshrc && source ~/.zshrc`);
|
|
132
|
-
console.log('');
|
|
133
|
-
console.log('Or use the Homebrew tap (sets PATH automatically):');
|
|
134
|
-
console.log(' brew tap asklokesh/tap && brew install loki-mode');
|
|
135
|
-
}
|
|
136
|
-
} catch {
|
|
137
|
-
// If npm bin check fails, skip PATH warning silently
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Install Python dependencies for Purple Lab (pexpect, watchdog, httpx)
|
|
141
|
-
try {
|
|
142
|
-
const { execSync } = require('child_process');
|
|
143
|
-
const pyDeps = ['pexpect', 'watchdog', 'httpx'];
|
|
144
|
-
// Check if deps are already installed
|
|
145
|
-
const missing = pyDeps.filter(dep => {
|
|
146
|
-
try {
|
|
147
|
-
execSync(`python3 -c "import ${dep}"`, { stdio: 'pipe' });
|
|
148
|
-
return false;
|
|
149
|
-
} catch { return true; }
|
|
150
|
-
});
|
|
151
|
-
if (missing.length > 0) {
|
|
152
|
-
console.log(`Installing Python dependencies: ${missing.join(', ')}...`);
|
|
153
|
-
try {
|
|
154
|
-
execSync(`python3 -m pip install --break-system-packages ${missing.join(' ')}`, { stdio: 'pipe', timeout: 60000 });
|
|
155
|
-
console.log(' [OK] Python dependencies installed');
|
|
156
|
-
} catch {
|
|
157
|
-
try {
|
|
158
|
-
execSync(`python3 -m pip install ${missing.join(' ')}`, { stdio: 'pipe', timeout: 60000 });
|
|
159
|
-
console.log(' [OK] Python dependencies installed');
|
|
160
|
-
} catch {
|
|
161
|
-
console.log(` [WARN] Could not install Python deps. Run: pip install ${missing.join(' ')}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
} catch {
|
|
166
|
-
// Python not available, skip silently
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
console.log('');
|
|
170
|
-
console.log('CLI commands:');
|
|
171
|
-
console.log(' loki start ./prd.md Start with Claude (default)');
|
|
172
|
-
console.log(' loki start --provider codex Start with OpenAI Codex');
|
|
173
|
-
console.log(' loki start --provider gemini Start with Google Gemini');
|
|
174
|
-
console.log(' loki status Check status');
|
|
175
|
-
console.log(' loki doctor Verify installation');
|
|
176
|
-
console.log(' loki welcome Quick tour, docs, and setup');
|
|
177
|
-
console.log(' loki --help Show all commands');
|
|
178
|
-
console.log('');
|
|
179
|
-
console.log('New here? Run `loki welcome` for a 30-second tour.');
|
|
180
|
-
console.log('');
|
|
181
|
-
|
|
182
|
-
// Anonymous install telemetry (fire-and-forget, silent).
|
|
183
|
-
// Collection is OPT-IN and OFF by default: a default `npm install` (including
|
|
184
|
-
// air-gapped, GDPR, and FedRAMP environments) sends NOTHING. This precedence
|
|
185
|
-
// mirrors loki_collection_enabled in autonomy/crash.sh, _is_enabled in
|
|
186
|
-
// dashboard/telemetry.py, and _loki_telemetry_enabled in autonomy/telemetry.sh.
|
|
187
|
-
// 1. Any opt-out flag present -> false (hard kill, always wins)
|
|
188
|
-
// 2. Else any opt-in flag present -> true
|
|
189
|
-
// 3. Else (default) -> false (no egress)
|
|
190
|
-
function _lokiCollectionEnabled() {
|
|
191
|
-
const telem = (process.env.LOKI_TELEMETRY || '').toLowerCase();
|
|
192
|
-
// 1. Opt-out always wins.
|
|
193
|
-
if (telem === 'off') return false;
|
|
194
|
-
if (process.env.LOKI_TELEMETRY_DISABLED === 'true') return false;
|
|
195
|
-
if (process.env.DO_NOT_TRACK === '1') return false;
|
|
196
|
-
let configEnabled = false;
|
|
197
|
-
try {
|
|
198
|
-
const cfg = path.join(homeDir, '.loki', 'config');
|
|
199
|
-
const lines = fs.readFileSync(cfg, 'utf8').split('\n');
|
|
200
|
-
if (lines.some((l) => l.startsWith('TELEMETRY_DISABLED=true'))) return false;
|
|
201
|
-
if (lines.some((l) => l.startsWith('TELEMETRY_ENABLED=true'))) configEnabled = true;
|
|
202
|
-
} catch {}
|
|
203
|
-
// 2. Opt-in required.
|
|
204
|
-
if (telem === 'on') return true;
|
|
205
|
-
if (configEnabled) return true;
|
|
206
|
-
// 3. Default: OFF.
|
|
207
|
-
return false;
|
|
208
|
-
}
|
|
209
|
-
try {
|
|
210
|
-
if (_lokiCollectionEnabled()) {
|
|
211
|
-
const https = require('https');
|
|
212
|
-
const crypto = require('crypto');
|
|
213
|
-
const idFile = path.join(homeDir, '.loki-telemetry-id');
|
|
214
|
-
let distinctId;
|
|
215
|
-
try {
|
|
216
|
-
distinctId = fs.readFileSync(idFile, 'utf8').trim();
|
|
217
|
-
} catch {
|
|
218
|
-
distinctId = crypto.randomUUID();
|
|
219
|
-
try { fs.writeFileSync(idFile, distinctId + '\n'); } catch {}
|
|
220
|
-
}
|
|
221
|
-
const payload = JSON.stringify({
|
|
222
|
-
api_key: 'phc_ya0vGBru41AJWtGNfZZ8H9W4yjoZy4KON0nnayS7s87',
|
|
223
|
-
event: 'install',
|
|
224
|
-
distinct_id: distinctId,
|
|
225
|
-
properties: {
|
|
226
|
-
os: os.platform(),
|
|
227
|
-
arch: os.arch(),
|
|
228
|
-
version: version,
|
|
229
|
-
channel: 'npm',
|
|
230
|
-
node_version: process.version,
|
|
231
|
-
providers_installed: results.filter(r => r.ok).map(r => r.name).join(','),
|
|
232
|
-
},
|
|
233
|
-
});
|
|
234
|
-
const req = https.request({
|
|
235
|
-
hostname: 'us.i.posthog.com',
|
|
236
|
-
path: '/capture/',
|
|
237
|
-
method: 'POST',
|
|
238
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': payload.length },
|
|
239
|
-
timeout: 3000,
|
|
240
|
-
});
|
|
241
|
-
req.on('error', () => {});
|
|
242
|
-
req.end(payload);
|
|
243
|
-
}
|
|
244
|
-
} catch {}
|