loki-mode 9.19.1 → 9.20.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.
Files changed (83) hide show
  1. package/SKILL.md +2 -2
  2. package/VERSION +1 -1
  3. package/autonomy/lib/deadline.py +21 -1
  4. package/autonomy/lib/outcome_contract.py +128 -0
  5. package/autonomy/lib/proof-generator.py +45 -0
  6. package/autonomy/lib/proof-verify.py +53 -0
  7. package/autonomy/loki +376 -10
  8. package/autonomy/receipt_jwt.py +308 -0
  9. package/autonomy/run.sh +30 -0
  10. package/autonomy/trigger-server.py +186 -1
  11. package/dashboard/__init__.py +1 -1
  12. package/dashboard/server.py +65 -0
  13. package/docs/COMPETITIVE-SCORECARD.md +31 -0
  14. package/docs/PROOF-PASSPORT.md +29 -0
  15. package/docs/SIGNED-RECEIPTS.md +111 -0
  16. package/docs/metrics.md +32 -0
  17. package/events/emit.sh +31 -0
  18. package/loki-ts/dist/loki.js +2 -2
  19. package/mcp/__init__.py +1 -1
  20. package/package.json +1 -1
  21. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  22. package/tools/proof-passport.py +175 -0
  23. package/web-app/dist/assets/{AdminPage-CcCJ0Sjt.js → AdminPage-DNGNuAge.js} +1 -1
  24. package/web-app/dist/assets/{Avatar-DK8kmayw.js → Avatar-CqAveJaT.js} +1 -1
  25. package/web-app/dist/assets/{Badge-4uAWnemi.js → Badge-BtMejA0Z.js} +1 -1
  26. package/web-app/dist/assets/{Button-BBMk33tk.js → Button-CikxmqSs.js} +1 -1
  27. package/web-app/dist/assets/{ComparePage-bt9rwvST.js → ComparePage-BB8DJBWR.js} +1 -1
  28. package/web-app/dist/assets/EvidenceReceiptPanel-6oquKEjd.js +1 -0
  29. package/web-app/dist/assets/{GitHubIssuesPanel-WDbH47UM.js → GitHubIssuesPanel-5XLYWzWH.js} +1 -1
  30. package/web-app/dist/assets/{GitHubPRsPanel-C2CiYtTx.js → GitHubPRsPanel-DUfmFYeu.js} +1 -1
  31. package/web-app/dist/assets/{HomePage-BQk-MUjn.js → HomePage-DZ4MmOdH.js} +5 -5
  32. package/web-app/dist/assets/{LoginPage-DMOZVGGL.js → LoginPage-VMzI6ROD.js} +1 -1
  33. package/web-app/dist/assets/{MagicPage-Bzp2Nt1z.js → MagicPage-CFbX5gWg.js} +1 -1
  34. package/web-app/dist/assets/{MetricsPage-C39JVdsw.js → MetricsPage-DbL6xmRQ.js} +2 -2
  35. package/web-app/dist/assets/{NotFoundPage-6vT_U9UL.js → NotFoundPage-BUrjXPAR.js} +1 -1
  36. package/web-app/dist/assets/{ProjectPage-BfFcZp-E.js → ProjectPage-iP6dJihg.js} +3 -3
  37. package/web-app/dist/assets/{ProjectsPage-CPMBf8Wt.js → ProjectsPage-BibN9bHl.js} +1 -1
  38. package/web-app/dist/assets/{SettingsPage-BnNN6ETl.js → SettingsPage-C0WJp1hW.js} +1 -1
  39. package/web-app/dist/assets/{ShowcasePage-WDrMf-cx.js → ShowcasePage-DUv0Njp8.js} +1 -1
  40. package/web-app/dist/assets/{SystemSettingsPage-DX4jb2e8.js → SystemSettingsPage-BIaCB0Vn.js} +1 -1
  41. package/web-app/dist/assets/{TeamsPage-BCfqcXzu.js → TeamsPage-BE0szv9r.js} +1 -1
  42. package/web-app/dist/assets/{TemplatesPage-CZvmimDj.js → TemplatesPage-BzkA9kat.js} +1 -1
  43. package/web-app/dist/assets/{TerminalOutput-BlRqFwWV.js → TerminalOutput-DBrdT_Fp.js} +1 -1
  44. package/web-app/dist/assets/{activity-CacZsUyr.js → activity-AbfFz38r.js} +1 -1
  45. package/web-app/dist/assets/{bell-DK2qtHnk.js → bell-DF6Z6y5U.js} +1 -1
  46. package/web-app/dist/assets/{bot-CkcUtHad.js → bot-DQ4np_3n.js} +1 -1
  47. package/web-app/dist/assets/{check-CbCPjX3M.js → check-RWuguCwE.js} +1 -1
  48. package/web-app/dist/assets/{chevron-left-5NUKWw3i.js → chevron-left-VpbQPOkC.js} +1 -1
  49. package/web-app/dist/assets/{circle-alert-S7uFoxC2.js → circle-alert-DwT82lJx.js} +1 -1
  50. package/web-app/dist/assets/{clock-CaQRrIrs.js → clock-BBlUa93G.js} +1 -1
  51. package/web-app/dist/assets/{cloud-DBAX6c0r.js → cloud-Dgbz3VCA.js} +1 -1
  52. package/web-app/dist/assets/{code-xml-De5-EXv3.js → code-xml-r6qqION5.js} +1 -1
  53. package/web-app/dist/assets/{copy-CUkT6k1v.js → copy-DMHj52B8.js} +1 -1
  54. package/web-app/dist/assets/{database-BAWf1Gwt.js → database-uJqHSHR1.js} +1 -1
  55. package/web-app/dist/assets/{dollar-sign-Ji8zk86R.js → dollar-sign-BZagljek.js} +1 -1
  56. package/web-app/dist/assets/{file-code-corner-ChtXoBwS.js → file-code-corner-DMopmhu0.js} +1 -1
  57. package/web-app/dist/assets/{file-plus-bFa37P76.js → file-plus-DU_hT_3w.js} +1 -1
  58. package/web-app/dist/assets/{folder-open-DhXpXscO.js → folder-open-BP5TtQAW.js} +1 -1
  59. package/web-app/dist/assets/{git-commit-horizontal-DVPeDQ3j.js → git-commit-horizontal-zFk_IobS.js} +1 -1
  60. package/web-app/dist/assets/{globe-BPZgPeeu.js → globe-mthCRtfp.js} +1 -1
  61. package/web-app/dist/assets/{hammer-jLCaujYH.js → hammer-BWaLBvSA.js} +1 -1
  62. package/web-app/dist/assets/{index-B-0iHBPO.js → index-BXA3UT6P.js} +3 -3
  63. package/web-app/dist/assets/{index-BfZSDej1.css → index-DgpUuqwR.css} +1 -1
  64. package/web-app/dist/assets/{layers-B1vsrsFW.js → layers-Q1LXV5fy.js} +1 -1
  65. package/web-app/dist/assets/{lightbulb-C-uLoq9Y.js → lightbulb-vRRNJ3dB.js} +1 -1
  66. package/web-app/dist/assets/{loader-circle-JTfD-ZuM.js → loader-circle-KgjeHEcV.js} +1 -1
  67. package/web-app/dist/assets/{lock-G9rxD4gZ.js → lock-BerKqno7.js} +1 -1
  68. package/web-app/dist/assets/{mail-BJ0PTN_V.js → mail-XAxTbprX.js} +1 -1
  69. package/web-app/dist/assets/{package-CXClfLOO.js → package-DLPKWAvW.js} +1 -1
  70. package/web-app/dist/assets/{plus-EoL5OCB7.js → plus-NMxxwHMn.js} +1 -1
  71. package/web-app/dist/assets/{refresh-cw-BjREUnVq.js → refresh-cw-Dt42kYtM.js} +1 -1
  72. package/web-app/dist/assets/{rotate-ccw-DahWX07H.js → rotate-ccw-BCR4Tp9u.js} +1 -1
  73. package/web-app/dist/assets/{save-Dek3gCn1.js → save-sR4MJd42.js} +1 -1
  74. package/web-app/dist/assets/{server-D6V1BAia.js → server-SXkchokx.js} +1 -1
  75. package/web-app/dist/assets/{shield-alert-BtTK5Sxb.js → shield-alert-Bvgp6_Dz.js} +1 -1
  76. package/web-app/dist/assets/{trash-2-BT5o_g0r.js → trash-2-D0IGudH7.js} +1 -1
  77. package/web-app/dist/assets/{trending-down-D4Jk7KF3.js → trending-down-BbFFBq-v.js} +1 -1
  78. package/web-app/dist/assets/{trending-up-EQFTzhEo.js → trending-up-DDrJKL5i.js} +1 -1
  79. package/web-app/dist/assets/{upload-JfI5lCSE.js → upload-C54rjN8K.js} +1 -1
  80. package/web-app/dist/assets/{usePolling-BnhPUuGd.js → usePolling-D7fw4YSM.js} +1 -1
  81. package/web-app/dist/assets/{user-DSUiUYtj.js → user-Cg7fbzdh.js} +1 -1
  82. package/web-app/dist/index.html +2 -2
  83. package/web-app/server.py +78 -0
@@ -397,6 +397,37 @@ bash tests/test-competitor-verify-surface.sh # the installed-CLI a
397
397
  loki proof verify <id> # re-hash a receipt, exit 1 on tamper
398
398
  ```
399
399
 
400
+ ### 3d. Our own numbers on the axes people ask about, measured 2026-08-08
401
+
402
+ These are ABSOLUTE measurements of this product with a CI guard behind each. They
403
+ are deliberately NOT presented as a comparison: no competitor number appears
404
+ here, because three of the four named competitors have no runnable binary on
405
+ this machine and the fourth exposes no equivalent command. A number next to a
406
+ blank is not a ratio.
407
+
408
+ | Axis | Measured | Budget | Guarded by |
409
+ |---|---|---|---|
410
+ | UI page weight | 787 KB | 1024 KB | `tests/test-dashboard-bundle-budget.sh` |
411
+ | Backend verify latency | 7 ms | under 1000 ms | `tests/test-fast-verify.sh` |
412
+ | Worker horizontal scale | 1..25 replicas render | n/a | `tests/test-helm-worker-scaling.sh` |
413
+
414
+ Two of those three guards already existed; only the page-weight budget was added
415
+ on 2026-08-08. Recording that because "we measured it" and "we added a
416
+ measurement" are different claims and the difference matters.
417
+
418
+ What these numbers do NOT establish: they say nothing about output quality,
419
+ which is the axis a user actually cares about, and nothing about how any
420
+ competitor performs on the same axes. A fast gate that verifies the wrong thing
421
+ is worse than a slow one that verifies the right thing.
422
+
423
+ Reproduce:
424
+
425
+ ```bash
426
+ bash tests/test-dashboard-bundle-budget.sh # prints the KB and the budget
427
+ bash tests/test-fast-verify.sh # prints the ms and the budget
428
+ bash tests/test-helm-worker-scaling.sh # renders 1, 2, 5 and 25 replicas
429
+ ```
430
+
400
431
  ### 3c. Deploy surface, measured 2026-08-08
401
432
 
402
433
  Ran on the four competitor CLIs installed on this machine. Method matters here:
@@ -0,0 +1,29 @@
1
+ # Proof Passport
2
+
3
+ Proof Passport binds a provider-neutral Outcome Contract to an existing Loki
4
+ receipt. The JSON artifact preserves the exact contract and receipt digests,
5
+ the verifier verdict, executor/verifier separation, and explicit trust
6
+ limitations.
7
+
8
+ Generate the machine-readable passport and a portable PR/CI summary together:
9
+
10
+ ```bash
11
+ python3 tools/proof-passport.py \
12
+ outcome-contract.json proof.json proof-passport.json \
13
+ --repo-dir . \
14
+ --markdown-output proof-passport.md
15
+ ```
16
+
17
+ The command exits `0` only for `VERIFIED`, `1` for `FAILED`, and `2` for
18
+ `UNVERIFIABLE` or invalid input. Existing output files are never replaced
19
+ unless `--force` is supplied. Both outputs are checked before either is
20
+ written, so a conflicting Markdown path cannot leave a new JSON artifact
21
+ behind.
22
+
23
+ The Markdown artifact is suitable for a pull-request comment or
24
+ `$GITHUB_STEP_SUMMARY`. It contains the full SHA-256 evidence bindings and
25
+ trust limitations, but no repository or machine-specific paths.
26
+
27
+ Proof Passport does not currently sign the passport itself. Receipt signature
28
+ status is reported separately and must not be interpreted as a passport
29
+ signature.
@@ -94,9 +94,120 @@ which exact code" without asking anyone to trust the agent that produced it.
94
94
  - If the signing key is compromised, signed receipts from that key are worth
95
95
  exactly what the key is worth. Normal key hygiene applies.
96
96
 
97
+ ## Attestation: provenance without a key exchange
98
+
99
+ gpg settles the local case, where the operator already holds the keyring. It
100
+ does not survive the remote path. A submitter who ran `loki start --remote`
101
+ never had access to the cluster, so "import the publisher's public key" is the
102
+ step where independent verification stops happening in practice.
103
+
104
+ A receipt served by `trigger-server.py` therefore also carries an **attestation**
105
+ under `verification.attestation`: an Ed25519 JWT signed by the receiver, with
106
+ the signing keys published, unauthenticated, at `/.well-known/jwks.json`.
107
+
108
+ ```bash
109
+ # Third party, offline. Needs proof.json and jwks.json -- nothing else.
110
+ curl -s https://loki.example.com/.well-known/jwks.json > jwks.json
111
+ loki proof verify <id> --jwks ./jwks.json
112
+
113
+ # Or fetch the key set directly from the server that produced the receipt.
114
+ loki proof verify <id> --jwks https://loki.example.com
115
+ ```
116
+
117
+ Four outcomes, deliberately kept distinct:
118
+
119
+ | Verdict | Meaning |
120
+ |---|---|
121
+ | `VERIFIED` | The token is valid **and** covers these exact bytes. |
122
+ | `FAILED` | The token does not cover these bytes: altered after signing, or lifted from another run. |
123
+ | `ABSENT` | This receipt carries no attestation. A fact about the receipt. |
124
+ | `NOT CHECKED` | The key set could not be read. **Not** a verdict, in either direction. |
125
+
126
+ `ABSENT` and `NOT CHECKED` are never collapsed. One is a property of the
127
+ receipt; the other is an absent measurement, and reporting an unchecked receipt
128
+ as merely unattested would overstate what was established.
129
+
130
+ The JWT binds `job_id`, `run_id` and `receipt_sha256`, and the verifier
131
+ **recomputes** that digest from the receipt body rather than trusting the
132
+ recorded `verification.hash`. Editing the body and rewriting the hash to match
133
+ therefore still fails: the signed claim is the anchor, not the field in the file.
134
+
135
+ ### Key rotation
136
+
137
+ Every token carries a `kid`, and JWKS serves the active key plus any retired
138
+ ones (`LOKI_RECEIPT_RETIRED_PUBKEYS`, a colon-separated list of PEM paths). This
139
+ is required rather than optional: with a single unlabeled key, the first
140
+ rotation would make every previously-issued receipt fail verification -- and a
141
+ receipt that stops verifying is indistinguishable from a tampered one.
142
+
143
+ ### In a Kubernetes cluster
144
+
145
+ The Helm chart wires this for you. Generate a key and pass it as a file:
146
+
147
+ ```bash
148
+ openssl genpkey -algorithm ed25519 -out receipt-signing-key.pem
149
+ helm upgrade --install loki ./helm/loki-mode \
150
+ --set-file secrets.receiptSigningKey=receipt-signing-key.pem
151
+ ```
152
+
153
+ The chart mounts it **into the receiver only**, read-only at mode `0400`, and
154
+ sets `LOKI_RECEIPT_SIGNING_KEY_FILE` to the projected path. The worker never
155
+ receives it, and that asymmetry is enforced by the chart rather than left to
156
+ convention: a worker runs model-directed code, so a key there would let a build
157
+ sign its own receipt.
158
+
159
+ Left unset, the receiver serves receipts unsigned and `/.well-known/jwks.json`
160
+ returns an empty key set -- honest, but a `--remote` submitter then has no way
161
+ to prove who produced their receipt without an out-of-band key import.
162
+
163
+ ### With docker-compose
164
+
165
+ Opt-in, and deliberately shipped commented out. Docker has no optional bind
166
+ mount: a bind to a missing path is a hard container start failure, so enabling
167
+ it by default would stop the receiver from starting for everyone who has not
168
+ generated a key.
169
+
170
+ ```bash
171
+ openssl genpkey -algorithm ed25519 -out ./receipt-signing-key.pem
172
+ ```
173
+
174
+ Then uncomment the two lines the compose file points at (the
175
+ `LOKI_RECEIPT_SIGNING_KEY_FILE` env var and the `receipt-signing-key.pem`
176
+ mount) on the `receiver` service. Never commit that key file.
177
+
178
+ ### Configuration
179
+
180
+ | Variable | Effect |
181
+ |---|---|
182
+ | `LOKI_RECEIPT_SIGNING_KEY_FILE` | PEM path to the Ed25519 private key (normal Kubernetes mounted-secret path). |
183
+ | `LOKI_RECEIPT_SIGNING_KEY` | The PEM inline, for non-Kubernetes deployments. |
184
+ | `LOKI_RECEIPT_RETIRED_PUBKEYS` | Colon-separated PEM paths for retired public keys. |
185
+
186
+ Unset means unsigned: no attestation is attached and the receipt keeps its
187
+ existing verdict.
188
+
189
+ The same two variables also work for a **local** build. Set
190
+ `LOKI_RECEIPT_SIGNING_KEY_FILE` before `loki start` and the generator attests
191
+ the receipt directly, so a laptop or CI receipt is checkable by the same
192
+ `--jwks` path a cluster receipt uses. Publish the matching JWKS wherever your
193
+ consumers can reach it (or hand them `jwks.json` alongside the receipt).
194
+
195
+ **In a cluster, the receiver signs -- never the worker.** A worker runs
196
+ model-directed code and already holds provider credentials, so a key there would
197
+ let a build sign its own receipt, which attests to nothing. The local case is
198
+ different in kind, not an exception to this: there is no separation between
199
+ submitter and builder on your own machine, and the attestation says "this
200
+ receipt came from a holder of this key" rather than "an independent party
201
+ witnessed this build."
202
+
97
203
  ## Source
98
204
 
99
205
  - Signing: `autonomy/lib/proof-generator.py` (`_gpg_detached_sign`, and the
100
206
  `LOKI_PROOF_GPG_KEY` gate)
101
207
  - Verification: `autonomy/lib/proof-verify.py` (`_verify_gpg`)
102
208
  - Scope test: `tests/test-proof-forgery-defense.sh`
209
+ - Attestation: `autonomy/receipt_jwt.py`; served by `autonomy/trigger-server.py`
210
+ (`_attest`, `_handle_jwks`); checked by `loki_proof_attestation_check` and
211
+ `loki_remote_attestation_status` in `autonomy/loki`
212
+ - Attestation tests: `tests/test-receipt-jwt-attestation.sh`,
213
+ `tests/test-remote-attestation-verdict.sh`, `tests/test-proof-verify-jwks.sh`
package/docs/metrics.md CHANGED
@@ -74,6 +74,38 @@ Returns metrics in OpenMetrics text format. No authentication required by defaul
74
74
  |--------|------|-------------|
75
75
  | `loki_events_total` | counter | Total number of events recorded in events.jsonl |
76
76
 
77
+ ### Evidence Receipt Metrics
78
+
79
+ The verification surface, exposed so an operator can alert on it. Two failures
80
+ make the receipt guarantee worthless in production and neither was observable
81
+ before these existed: builds silently stopping producing receipts, and the
82
+ `unknown` share climbing.
83
+
84
+ | Metric | Type | Description |
85
+ |--------|------|-------------|
86
+ | `loki_receipts_total` | gauge | Evidence Receipts on disk for the active project |
87
+ | `loki_receipts_by_verdict{verdict=...}` | gauge | Receipts bucketed on the recorded honesty headline: `verified`, `with_gaps`, `not_verified`, `unknown` |
88
+ | `loki_receipts_with_provenance{kind=...}` | gauge | Receipts carrying a checkable provenance record: `attestation` (Ed25519 JWT) or `gpg` |
89
+
90
+ `unknown` IS ITS OWN SERIES and must stay that way. The summary refuses to
91
+ count a receipt as verified when it cannot prove it was -- schema v1.0 receipts
92
+ carry no honesty block -- so folding that bucket into `verified` is dishonest
93
+ and folding it into `not_verified` is alarming and also wrong.
94
+
95
+ These read the same files as `GET /api/proofs/summary`, so the metric and the
96
+ API cannot disagree; a test asserts it. An empty project reports `0`, not an
97
+ error, so a fresh install does not look like a broken exporter.
98
+
99
+ Example alerts:
100
+
101
+ ```
102
+ # receipts stopped being produced
103
+ increase(loki_receipts_total[6h]) == 0
104
+
105
+ # the unverifiable share is growing
106
+ loki_receipts_by_verdict{verdict="unknown"} / loki_receipts_total > 0.2
107
+ ```
108
+
77
109
  ## Data Sources
78
110
 
79
111
  Metrics are derived from `.loki/` flat files:
package/events/emit.sh CHANGED
@@ -171,6 +171,37 @@ if [ "$_emit_max" -gt 0 ]; then
171
171
  kill -0 "$_emit_target" 2>/dev/null || exit 0
172
172
  _waited=$((_waited + 1))
173
173
  done
174
+ # FORENSICS BEFORE THE KILL (issue #184).
175
+ #
176
+ # The watchdog bounds the damage but has never recorded WHY the hang
177
+ # happened, which is why the cause is still unknown after two disproved
178
+ # hypotheses (the lock-retry counter, and six synthetic race probes).
179
+ # A SIGKILL destroys the only evidence that existed.
180
+ #
181
+ # Captures a stack sample and the process tree to a file, then kills. On
182
+ # the next occurrence the reader gets a stack instead of a guess.
183
+ #
184
+ # Every part is bounded and best-effort: this runs INSIDE the watchdog,
185
+ # so a capture that blocks would replace a bounded failure with an
186
+ # unbounded one -- the exact thing the watchdog exists to prevent.
187
+ # `sample` is macOS-only and absent on Linux; both branches are guarded
188
+ # and neither is allowed to fail the watchdog.
189
+ _emit_diag_dir="${LOKI_EMIT_DIAG_DIR:-${TMPDIR:-/tmp}}"
190
+ _emit_diag="$_emit_diag_dir/loki-emit-hang-$_emit_target.txt"
191
+ {
192
+ echo "emit.sh watchdog fired after ${_emit_max}s on pid $_emit_target"
193
+ echo "captured: $(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null)"
194
+ echo "--- ps ---"
195
+ ps -o pid=,ppid=,pgid=,stat=,etime=,command= -p "$_emit_target" 2>/dev/null
196
+ echo "--- children ---"
197
+ ps -eo pid=,ppid=,stat=,command= 2>/dev/null | awk -v p="$_emit_target" '$2==p'
198
+ echo "--- stack (macOS sample, 1s) ---"
199
+ if command -v sample >/dev/null 2>&1; then
200
+ sample "$_emit_target" 1 -mayDie 2>/dev/null | head -60
201
+ else
202
+ echo "(sample unavailable on this platform)"
203
+ fi
204
+ } > "$_emit_diag" 2>/dev/null || true
174
205
  kill -9 "$_emit_target" 2>/dev/null || true
175
206
  ) >/dev/null 2>&1 &
176
207
  # Disown so the watchdog is not a tracked job of the caller's shell.
@@ -1,5 +1,5 @@
1
1
  // @bun
2
- var p_=Object.create;var{getPrototypeOf:d_,defineProperty:eK,getOwnPropertyNames:c_}=Object;var l_=Object.prototype.hasOwnProperty;function i_(Z){return this[Z]}var a_,s_,n_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?a_??=new WeakMap:s_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?p_(d_(Z)):{};let K=X||!Z||!Z.__esModule?eK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of c_(Z))if(!l_.call(K,$))eK(K,$,{get:i_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var HQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var o_=(Z)=>Z;function r_(Z,X){this[Z]=o_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)eK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:r_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var t0=import.meta.require;var xO={};l0(xO,{lokiDir:()=>j0,homeLokiDir:()=>R4,findRepoRootForVersion:()=>X$,REPO_ROOT:()=>i0});import{resolve as n7,dirname as Z$}from"path";import{fileURLToPath as t_}from"url";import{existsSync as UQ}from"fs";import{homedir as e_}from"os";function Zf(){let Z=kO;for(let X=0;X<6;X++){if(UQ(n7(Z,"VERSION"))&&UQ(n7(Z,"autonomy/run.sh")))return Z;let Q=Z$(Z);if(Q===Z)break;Z=Q}return n7(kO,"..","..","..")}function X$(Z){let X=Z;for(let Q=0;Q<6;Q++){if(UQ(n7(X,"VERSION"))&&UQ(n7(X,"autonomy/run.sh")))return X;let Y=Z$(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function R4(){return n7(e_(),".loki")}var kO,i0;var H8=p(()=>{kO=Z$(t_(import.meta.url));i0=Zf()});import{readFileSync as Xf}from"fs";import{resolve as Qf,dirname as Yf}from"path";import{fileURLToPath as Jf}from"url";function h3(){if(h5!==null)return h5;let Z="9.19.1";if(typeof Z==="string"&&Z.length>0)return h5=Z,h5;try{let X=Yf(Jf(import.meta.url)),Q=X$(X);h5=Xf(Qf(Q,"VERSION"),"utf-8").trim()}catch{h5="unknown"}return h5}var h5=null;var BQ=p(()=>{H8()});var _O={};l0(_O,{runOrThrow:()=>Tf,run:()=>E0,readStreamCapped:()=>NQ,commandVersion:()=>Cf,commandExists:()=>X9,ShellError:()=>Q$,MAX_STDOUT_BYTES:()=>bO});async function NQ(Z,X=bO){let Q=Z.getReader(),Y=new TextDecoder,J="",z=0;try{while(z<X){let{done:K,value:$}=await Q.read();if(K)break;if(!$)continue;if(z+=$.byteLength,z>X){let V=$.byteLength-(z-X);J+=Y.decode($.subarray(0,V),{stream:!0});break}J+=Y.decode($,{stream:!0})}J+=Y.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return J}async function E0(Z,X={}){let Q=Bun.spawn({cmd:[...Z],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),Y,J;if(X.timeoutMs&&X.timeoutMs>0)Y=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}J=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[z,K,$]=await Promise.all([NQ(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:z,stderr:K,exitCode:$}}finally{if(Y)clearTimeout(Y);if(J)clearTimeout(J)}}async function Tf(Z,X={}){let Q=await E0(Z,X);if(Q.exitCode!==0)throw new Q$(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function X9(Z){let X=wf(Z),Q=await E0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function wf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function Cf(Z,X="--version"){if(!await X9(Z))return null;let Y=await E0([Z,X],{timeoutMs:5000});if(Y.exitCode!==0)return null;return((Y.stdout||Y.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var bO=16777216,Q$;var x9=p(()=>{Q$=class Q$ extends Error{message;exitCode;stdout;stderr;constructor(Z,X,Q,Y){super(Z);this.message=Z;this.exitCode=X;this.stdout=Q;this.stderr=Y;this.name="ShellError"}}});function o7(Z){return Ff?"":Z}var Ff,A0,F8,_0,$W0,a0,V8,Q9,h;var S6=p(()=>{Ff=(process.env.NO_COLOR??"").length>0;A0=o7("\x1B[0;31m"),F8=o7("\x1B[0;32m"),_0=o7("\x1B[1;33m"),$W0=o7("\x1B[0;34m"),a0=o7("\x1B[0;36m"),V8=o7("\x1B[1m"),Q9=o7("\x1B[2m"),h=o7("\x1B[0m")});import{existsSync as ff}from"fs";async function E7(){if(x4!==void 0)return x4;let Z="/opt/homebrew/bin/python3.12";if(ff(Z))return x4=Z,Z;let X=await X9("python3.12");if(X)return x4=X,X;let Q=await X9("python3");return x4=Q,Q}async function Y7(Z,X={}){let Q=await E7();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return E0([Q,"-c",Z],X)}var x4;var r7=p(()=>{x9()});var XA={};l0(XA,{runStatus:()=>Vh});import{existsSync as Y9,readFileSync as g3,readdirSync as aO,statSync as sO}from"fs";import{resolve as h8,basename as ef}from"path";import{homedir as Zh}from"os";function nO(Z){let X=Math.trunc(Z);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function oO(Z,X,Q){if(X===0)return null;let Y=Math.trunc(Z*100/X),J=Math.trunc(Z*AQ/X);if(J>AQ)J=AQ;let z=AQ-J,K=F8;if(Y>=80)K=A0;else if(Y>=50)K=_0;let $="=".repeat(Math.max(0,J))+" ".repeat(Math.max(0,z)),V=nO(Z),W=nO(X);return` ${V8}${Q}${h} ${K}[${$}]${h} ${Y}% (${V} / ${W})`}async function Qh(){if(await X9("jq"))return!0;return process.stdout.write(`${A0}Error: jq is required but not installed.${h}
2
+ var p_=Object.create;var{getPrototypeOf:d_,defineProperty:eK,getOwnPropertyNames:c_}=Object;var l_=Object.prototype.hasOwnProperty;function i_(Z){return this[Z]}var a_,s_,n_=(Z,X,Q)=>{var Y=Z!=null&&typeof Z==="object";if(Y){var J=X?a_??=new WeakMap:s_??=new WeakMap,z=J.get(Z);if(z)return z}Q=Z!=null?p_(d_(Z)):{};let K=X||!Z||!Z.__esModule?eK(Q,"default",{value:Z,enumerable:!0}):Q;for(let $ of c_(Z))if(!l_.call(K,$))eK(K,$,{get:i_.bind(Z,$),enumerable:!0});if(Y)J.set(Z,K);return K};var HQ=(Z,X)=>()=>(X||Z((X={exports:{}}).exports,X),X.exports);var o_=(Z)=>Z;function r_(Z,X){this[Z]=o_.bind(null,X)}var l0=(Z,X)=>{for(var Q in X)eK(Z,Q,{get:X[Q],enumerable:!0,configurable:!0,set:r_.bind(X,Q)})};var p=(Z,X)=>()=>(Z&&(X=Z(Z=0)),X);var t0=import.meta.require;var xO={};l0(xO,{lokiDir:()=>j0,homeLokiDir:()=>R4,findRepoRootForVersion:()=>X$,REPO_ROOT:()=>i0});import{resolve as n7,dirname as Z$}from"path";import{fileURLToPath as t_}from"url";import{existsSync as UQ}from"fs";import{homedir as e_}from"os";function Zf(){let Z=kO;for(let X=0;X<6;X++){if(UQ(n7(Z,"VERSION"))&&UQ(n7(Z,"autonomy/run.sh")))return Z;let Q=Z$(Z);if(Q===Z)break;Z=Q}return n7(kO,"..","..","..")}function X$(Z){let X=Z;for(let Q=0;Q<6;Q++){if(UQ(n7(X,"VERSION"))&&UQ(n7(X,"autonomy/run.sh")))return X;let Y=Z$(X);if(Y===X)break;X=Y}return n7(Z,"..","..","..")}function j0(){return process.env.LOKI_DIR??n7(process.cwd(),".loki")}function R4(){return n7(e_(),".loki")}var kO,i0;var H8=p(()=>{kO=Z$(t_(import.meta.url));i0=Zf()});import{readFileSync as Xf}from"fs";import{resolve as Qf,dirname as Yf}from"path";import{fileURLToPath as Jf}from"url";function h3(){if(h5!==null)return h5;let Z="9.20.0";if(typeof Z==="string"&&Z.length>0)return h5=Z,h5;try{let X=Yf(Jf(import.meta.url)),Q=X$(X);h5=Xf(Qf(Q,"VERSION"),"utf-8").trim()}catch{h5="unknown"}return h5}var h5=null;var BQ=p(()=>{H8()});var _O={};l0(_O,{runOrThrow:()=>Tf,run:()=>E0,readStreamCapped:()=>NQ,commandVersion:()=>Cf,commandExists:()=>X9,ShellError:()=>Q$,MAX_STDOUT_BYTES:()=>bO});async function NQ(Z,X=bO){let Q=Z.getReader(),Y=new TextDecoder,J="",z=0;try{while(z<X){let{done:K,value:$}=await Q.read();if(K)break;if(!$)continue;if(z+=$.byteLength,z>X){let V=$.byteLength-(z-X);J+=Y.decode($.subarray(0,V),{stream:!0});break}J+=Y.decode($,{stream:!0})}J+=Y.decode()}finally{try{await Q.cancel()}catch{}Q.releaseLock()}return J}async function E0(Z,X={}){let Q=Bun.spawn({cmd:[...Z],stdout:"pipe",stderr:"pipe",env:X.env?{...process.env,...X.env}:process.env,cwd:X.cwd}),Y,J;if(X.timeoutMs&&X.timeoutMs>0)Y=setTimeout(()=>{try{Q.kill("SIGTERM")}catch{}J=setTimeout(()=>{try{Q.kill("SIGKILL")}catch{}},2000)},X.timeoutMs);try{let[z,K,$]=await Promise.all([NQ(Q.stdout),new Response(Q.stderr).text(),Q.exited]);return{stdout:z,stderr:K,exitCode:$}}finally{if(Y)clearTimeout(Y);if(J)clearTimeout(J)}}async function Tf(Z,X={}){let Q=await E0(Z,X);if(Q.exitCode!==0)throw new Q$(`command failed (${Q.exitCode}): ${Z.join(" ")}`,Q.exitCode,Q.stdout,Q.stderr);return Q}async function X9(Z){let X=wf(Z),Q=await E0(["sh","-c",`command -v ${X}`],{timeoutMs:5000});if(Q.exitCode===0)return Q.stdout.trim()||null;return null}function wf(Z){if(!/^[A-Za-z0-9._/-]+$/.test(Z))throw Error(`refused to shell-escape suspect token: ${Z}`);return Z}async function Cf(Z,X="--version"){if(!await X9(Z))return null;let Y=await E0([Z,X],{timeoutMs:5000});if(Y.exitCode!==0)return null;return((Y.stdout||Y.stderr).split(/\r?\n/)[0]?.trim()??"")||null}var bO=16777216,Q$;var x9=p(()=>{Q$=class Q$ extends Error{message;exitCode;stdout;stderr;constructor(Z,X,Q,Y){super(Z);this.message=Z;this.exitCode=X;this.stdout=Q;this.stderr=Y;this.name="ShellError"}}});function o7(Z){return Ff?"":Z}var Ff,A0,F8,_0,$W0,a0,V8,Q9,h;var S6=p(()=>{Ff=(process.env.NO_COLOR??"").length>0;A0=o7("\x1B[0;31m"),F8=o7("\x1B[0;32m"),_0=o7("\x1B[1;33m"),$W0=o7("\x1B[0;34m"),a0=o7("\x1B[0;36m"),V8=o7("\x1B[1m"),Q9=o7("\x1B[2m"),h=o7("\x1B[0m")});import{existsSync as ff}from"fs";async function E7(){if(x4!==void 0)return x4;let Z="/opt/homebrew/bin/python3.12";if(ff(Z))return x4=Z,Z;let X=await X9("python3.12");if(X)return x4=X,X;let Q=await X9("python3");return x4=Q,Q}async function Y7(Z,X={}){let Q=await E7();if(!Q)return{stdout:"",stderr:"python3 not found",exitCode:127};return E0([Q,"-c",Z],X)}var x4;var r7=p(()=>{x9()});var XA={};l0(XA,{runStatus:()=>Vh});import{existsSync as Y9,readFileSync as g3,readdirSync as aO,statSync as sO}from"fs";import{resolve as h8,basename as ef}from"path";import{homedir as Zh}from"os";function nO(Z){let X=Math.trunc(Z);if(X>=1e6)return`${(Math.trunc(X/1e6*10)/10).toFixed(1)}M`;if(X>=1000)return`${(Math.trunc(X/1000*10)/10).toFixed(1)}K`;return String(X)}function oO(Z,X,Q){if(X===0)return null;let Y=Math.trunc(Z*100/X),J=Math.trunc(Z*AQ/X);if(J>AQ)J=AQ;let z=AQ-J,K=F8;if(Y>=80)K=A0;else if(Y>=50)K=_0;let $="=".repeat(Math.max(0,J))+" ".repeat(Math.max(0,z)),V=nO(Z),W=nO(X);return` ${V8}${Q}${h} ${K}[${$}]${h} ${Y}% (${V} / ${W})`}async function Qh(){if(await X9("jq"))return!0;return process.stdout.write(`${A0}Error: jq is required but not installed.${h}
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)
@@ -1236,4 +1236,4 @@ Set LOKI_LEGACY_BASH=1 to force the bash CLI for every command.
1236
1236
  `),2}case"start":{let{runStart:Y}=await Promise.resolve().then(() => (g_(),v_));return Y(Q)}default:return process.stderr.write(`Unknown command: ${X}
1237
1237
  `),process.stderr.write(m_),2}}lO();process.on("SIGINT",()=>process.exit(130));process.on("SIGTERM",()=>process.exit(143));var dV0=await pV0(Bun.argv.slice(2));process.exit(dV0);
1238
1238
 
1239
- //# debugId=78ABA7C02A59114D64756E2164756E21
1239
+ //# debugId=AF141439B5D0532864756E2164756E21
package/mcp/__init__.py CHANGED
@@ -75,4 +75,4 @@ try:
75
75
  except ImportError:
76
76
  __all__ = ['mcp']
77
77
 
78
- __version__ = '9.19.1'
78
+ __version__ = '9.20.0'
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": "9.19.1",
4
+ "version": "9.20.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": "9.19.1",
5
+ "version": "9.20.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",
@@ -0,0 +1,175 @@
1
+ #!/usr/bin/env python3
2
+ """Bind an Outcome Contract to an existing receipt attestation."""
3
+
4
+ import argparse
5
+ import hashlib
6
+ import html
7
+ import importlib.util
8
+ import json
9
+ import pathlib
10
+ import sys
11
+
12
+ ROOT = pathlib.Path(__file__).resolve().parents[1]
13
+ sys.path.insert(0, str(ROOT / "autonomy" / "lib"))
14
+ import outcome_contract
15
+
16
+
17
+ class _Parser(argparse.ArgumentParser):
18
+ """Keep CLI usage mistakes distinct from verification verdicts."""
19
+
20
+ def error(self, message):
21
+ self.print_usage(sys.stderr)
22
+ self.exit(64, f"{self.prog}: error: {message}\n")
23
+
24
+
25
+ def _load_attester():
26
+ spec = importlib.util.spec_from_file_location(
27
+ "receipt_attest", ROOT / "tools" / "receipt-attest.py")
28
+ module = importlib.util.module_from_spec(spec)
29
+ spec.loader.exec_module(module)
30
+ return module
31
+
32
+
33
+ def _redact_paths(value, private_roots):
34
+ """Remove machine-specific roots while retaining verifier explanations."""
35
+ if isinstance(value, str):
36
+ for root in private_roots:
37
+ value = value.replace(root, ".")
38
+ return value
39
+ if isinstance(value, list):
40
+ return [_redact_paths(item, private_roots) for item in value]
41
+ if isinstance(value, dict):
42
+ return {key: _redact_paths(item, private_roots)
43
+ for key, item in value.items()}
44
+ return value
45
+
46
+
47
+ def build_passport(contract_path, receipt_path, repo_dir="."):
48
+ contract_path = pathlib.Path(contract_path)
49
+ receipt_path = pathlib.Path(receipt_path)
50
+ contract = json.loads(contract_path.read_text(encoding="utf-8"))
51
+ outcome_contract.validate(contract)
52
+ receipt_bytes = receipt_path.read_bytes()
53
+ attestation = _load_attester().attest(str(receipt_path), str(repo_dir))
54
+ # BOTH the raw and resolved form of every root. The attester is handed the
55
+ # UNRESOLVED strings (str(receipt_path), str(repo_dir) above), so a
56
+ # resolve()-only root list never matches what it actually embedded. On
57
+ # macOS /var is a symlink to /private/var, so a temp-dir path arrived as
58
+ # "/var/folders/..." while this list held "/private/var/folders/...", and
59
+ # the machine path shipped inside verification.axes.drift.reason. Linux has
60
+ # no such symlink, resolve() == raw, and the leak is invisible there --
61
+ # which is why CI was green while every developer Mac leaked.
62
+ #
63
+ # Longest-first ordering is retained so a nested root is redacted before
64
+ # its parent; adding the raw forms only lengthens the candidate set.
65
+ _root_paths = (pathlib.Path(repo_dir), contract_path.parent,
66
+ receipt_path.parent)
67
+ private_roots = sorted(
68
+ {str(p) for p in _root_paths} | {str(p.resolve()) for p in _root_paths},
69
+ key=len, reverse=True)
70
+ attestation = _redact_paths(attestation, private_roots)
71
+ return {
72
+ "format": "autonomi-proof-passport/v0.1",
73
+ "contract": {
74
+ "id": contract["id"],
75
+ "sha256": outcome_contract.digest(contract),
76
+ "digest": {
77
+ "algorithm": "sha256",
78
+ "canonicalization": "canonical-json: sorted keys, compact separators, UTF-8",
79
+ },
80
+ },
81
+ "receipt": {
82
+ "name": receipt_path.name,
83
+ "sha256": hashlib.sha256(receipt_bytes).hexdigest(),
84
+ "digest": {
85
+ "algorithm": "sha256",
86
+ "canonicalization": "none; exact raw file bytes",
87
+ },
88
+ },
89
+ "parties": {
90
+ "executor": contract["executor"],
91
+ "verifier": contract["verifier"],
92
+ "independent": contract["executor"]["id"] != contract["verifier"]["id"],
93
+ },
94
+ "verification": {
95
+ "verdict": attestation["verdict"],
96
+ "axes": attestation.get("axes", {}),
97
+ "receipt_signature": attestation.get("signature", {}),
98
+ "generator_trusted": attestation.get("generator_trusted"),
99
+ "summary": attestation["summary"],
100
+ },
101
+ "passport_signature": {
102
+ "status": "unsigned",
103
+ "reason": "this passport carries no cryptographic signature",
104
+ },
105
+ "limitations": [
106
+ "The passport itself is unsigned and does not prove who generated the passport.",
107
+ "Receipt-origin assurance is reported separately in verification.receipt_signature.",
108
+ "A true generator_trusted value means receipt facts remain generator claims.",
109
+ ],
110
+ }
111
+
112
+
113
+ def render_markdown(passport):
114
+ """Render a portable PR/CI summary without exposing local file paths."""
115
+ def safe(value):
116
+ return html.escape(str(value), quote=False).replace("`", "&#96;").replace(
117
+ "\r", " ").replace("\n", " ").replace("|", "&#124;")
118
+
119
+ verdict = passport["verification"]["verdict"]
120
+ marker = {"VERIFIED": "PASS", "FAILED": "FAIL"}.get(
121
+ verdict, "UNVERIFIABLE")
122
+ independent = "yes" if passport["parties"]["independent"] else "no"
123
+ contract_digest = passport["contract"]["sha256"]
124
+ receipt_digest = passport["receipt"]["sha256"]
125
+ signature = passport["passport_signature"]["status"]
126
+ summary = safe(passport["verification"]["summary"])
127
+ limitations = "\n".join(
128
+ f"- {safe(item)}"
129
+ for item in passport["limitations"])
130
+ return (
131
+ "## Autonomi Proof Passport\n\n"
132
+ f"**{marker}: {verdict}** — {summary}\n\n"
133
+ "| Evidence | Value |\n"
134
+ "| --- | --- |\n"
135
+ f"| Outcome contract | `{safe(passport['contract']['id'])}` |\n"
136
+ f"| Contract SHA-256 | `{contract_digest}` |\n"
137
+ f"| Receipt SHA-256 | `{receipt_digest}` |\n"
138
+ f"| Independent verifier | `{independent}` |\n"
139
+ f"| Passport signature | `{signature}` |\n\n"
140
+ "### Limitations\n\n"
141
+ f"{limitations}\n")
142
+
143
+
144
+ def main(argv=None):
145
+ parser = _Parser()
146
+ parser.add_argument("contract")
147
+ parser.add_argument("receipt")
148
+ parser.add_argument("output")
149
+ parser.add_argument("--repo-dir", default=".")
150
+ parser.add_argument(
151
+ "--markdown-output",
152
+ help="also write a portable Markdown summary for PRs and CI job summaries")
153
+ parser.add_argument("--force", action="store_true")
154
+ args = parser.parse_args(argv)
155
+ output = pathlib.Path(args.output)
156
+ markdown_output = (pathlib.Path(args.markdown_output)
157
+ if args.markdown_output else None)
158
+ conflicts = [path for path in (output, markdown_output)
159
+ if path is not None and path.exists()]
160
+ if conflicts and not args.force:
161
+ parser.exit(2, f"{parser.prog}: output exists; pass --force to overwrite\n")
162
+ try:
163
+ passport = build_passport(args.contract, args.receipt, args.repo_dir)
164
+ except (OSError, json.JSONDecodeError, outcome_contract.ContractValidationError) as exc:
165
+ parser.exit(2, f"{parser.prog}: {exc}\n")
166
+ output.write_text(json.dumps(passport, indent=2, sort_keys=True) + "\n",
167
+ encoding="utf-8")
168
+ if markdown_output is not None:
169
+ markdown_output.write_text(render_markdown(passport), encoding="utf-8")
170
+ return {"VERIFIED": 0, "FAILED": 1, "UNVERIFIABLE": 2}.get(
171
+ passport["verification"]["verdict"], 2)
172
+
173
+
174
+ if __name__ == "__main__":
175
+ sys.exit(main())
@@ -1,4 +1,4 @@
1
- import{c as L,j as e,r as m,w as _,Z as X,n as Q,i as ee,b as R,F as ge,U as z,X as te,L as se,x as je,M as fe,T as G,S as be,m as ae,l as ne,y as ve,E as Ee,V as Ne,$ as re}from"./index-B-0iHBPO.js";import{B as w}from"./Button-BBMk33tk.js";import{T as ye}from"./trending-up-EQFTzhEo.js";import{C as I}from"./clock-CaQRrIrs.js";import{A as ke,U as ie}from"./Avatar-DK8kmayw.js";import{A as H}from"./activity-CacZsUyr.js";import{R as le}from"./refresh-cw-BjREUnVq.js";import{G as Ae}from"./globe-BPZgPeeu.js";import{C as we}from"./chevron-left-5NUKWw3i.js";import{M as Ce}from"./mail-BJ0PTN_V.js";import{C as ce}from"./check-CbCPjX3M.js";import{D as W}from"./dollar-sign-Ji8zk86R.js";import{U as Se}from"./user-DSUiUYtj.js";import{D as De}from"./database-BAWf1Gwt.js";import{L as Fe}from"./lock-G9rxD4gZ.js";import{H as Me}from"./hammer-jLCaujYH.js";import{S as Be}from"./shield-alert-BtTK5Sxb.js";import{S as $e}from"./server-D6V1BAia.js";/**
1
+ import{c as L,j as e,r as m,w as _,Z as X,n as Q,i as ee,b as R,F as ge,U as z,X as te,L as se,x as je,M as fe,T as G,S as be,m as ae,l as ne,y as ve,E as Ee,V as Ne,$ as re}from"./index-BXA3UT6P.js";import{B as w}from"./Button-CikxmqSs.js";import{T as ye}from"./trending-up-DDrJKL5i.js";import{C as I}from"./clock-BBlUa93G.js";import{A as ke,U as ie}from"./Avatar-CqAveJaT.js";import{A as H}from"./activity-AbfFz38r.js";import{R as le}from"./refresh-cw-Dt42kYtM.js";import{G as Ae}from"./globe-mthCRtfp.js";import{C as we}from"./chevron-left-VpbQPOkC.js";import{M as Ce}from"./mail-XAxTbprX.js";import{C as ce}from"./check-RWuguCwE.js";import{D as W}from"./dollar-sign-BZagljek.js";import{U as Se}from"./user-Cg7fbzdh.js";import{D as De}from"./database-uJqHSHR1.js";import{L as Fe}from"./lock-BerKqno7.js";import{H as Me}from"./hammer-BWaLBvSA.js";import{S as Be}from"./shield-alert-Bvgp6_Dz.js";import{S as $e}from"./server-SXkchokx.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1,4 +1,4 @@
1
- import{c as h,j as s}from"./index-B-0iHBPO.js";/**
1
+ import{c as h,j as s}from"./index-BXA3UT6P.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.
@@ -1 +1 @@
1
- import{j as e}from"./index-B-0iHBPO.js";import{C as r}from"./clock-CaQRrIrs.js";import{C as a}from"./circle-alert-S7uFoxC2.js";import{C as l}from"./check-CbCPjX3M.js";const i={completed:"bg-[#1FC5A8]/10 text-[#1FC5A8]",running:"bg-[#553DE9]/10 text-[#553DE9]",failed:"bg-[#C45B5B]/10 text-[#C45B5B]",started:"bg-[#D4A03C]/10 text-[#D4A03C]",empty:"bg-[#F8F4F0] text-[#6B6960]",version:"bg-[#553DE9]/10 text-[#553DE9]"};function o({status:t}){switch(t){case"completed":return e.jsx(l,{size:12});case"running":return e.jsxs("span",{className:"relative flex h-2 w-2",children:[e.jsx("span",{className:"animate-ping motion-reduce:animate-none absolute inline-flex h-full w-full rounded-full bg-current opacity-75"}),e.jsx("span",{className:"relative inline-flex rounded-full h-2 w-2 bg-current"})]});case"failed":return e.jsx(a,{size:12});case"started":return e.jsx(r,{size:12});default:return null}}function f({status:t,children:n,className:s=""}){return e.jsxs("span",{className:["inline-flex items-center gap-1 rounded-btn px-2.5 py-0.5 text-xs font-semibold",i[t],s].filter(Boolean).join(" "),children:[e.jsx(o,{status:t}),n]})}export{f as B};
1
+ import{j as e}from"./index-BXA3UT6P.js";import{C as r}from"./clock-BBlUa93G.js";import{C as a}from"./circle-alert-DwT82lJx.js";import{C as l}from"./check-RWuguCwE.js";const i={completed:"bg-[#1FC5A8]/10 text-[#1FC5A8]",running:"bg-[#553DE9]/10 text-[#553DE9]",failed:"bg-[#C45B5B]/10 text-[#C45B5B]",started:"bg-[#D4A03C]/10 text-[#D4A03C]",empty:"bg-[#F8F4F0] text-[#6B6960]",version:"bg-[#553DE9]/10 text-[#553DE9]"};function o({status:t}){switch(t){case"completed":return e.jsx(l,{size:12});case"running":return e.jsxs("span",{className:"relative flex h-2 w-2",children:[e.jsx("span",{className:"animate-ping motion-reduce:animate-none absolute inline-flex h-full w-full rounded-full bg-current opacity-75"}),e.jsx("span",{className:"relative inline-flex rounded-full h-2 w-2 bg-current"})]});case"failed":return e.jsx(a,{size:12});case"started":return e.jsx(r,{size:12});default:return null}}function f({status:t,children:n,className:s=""}){return e.jsxs("span",{className:["inline-flex items-center gap-1 rounded-btn px-2.5 py-0.5 text-xs font-semibold",i[t],s].filter(Boolean).join(" "),children:[e.jsx(o,{status:t}),n]})}export{f as B};
@@ -1 +1 @@
1
- import{r as p,j as t}from"./index-B-0iHBPO.js";const m={primary:"bg-[#553DE9] text-white hover:bg-[#4432c4] shadow-button rounded-btn",secondary:"border border-[#553DE9] text-[#553DE9] hover:bg-[#E8E4FD] bg-transparent rounded-btn",ghost:"text-[#36342E] hover:bg-[#F8F4F0] rounded-btn",danger:"bg-[#C45B5B]/10 text-[#C45B5B] border border-[#C45B5B]/20 hover:bg-[#C45B5B]/20 rounded-btn"},b={sm:"px-3 py-1.5 text-xs",md:"px-4 py-2 text-sm",lg:"px-6 py-3 text-base"},u={sm:14,md:16,lg:18};function h({size:e}){return t.jsxs("svg",{className:"animate-spin",width:e,height:e,viewBox:"0 0 24 24",fill:"none",children:[t.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),t.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"})]})}const B=p.forwardRef(({variant:e="primary",size:o="md",icon:n,iconRight:i,loading:r=!1,disabled:a,className:c="",children:l,...x},d)=>{const s=u[o];return t.jsxs("button",{ref:d,disabled:a||r,className:["inline-flex items-center justify-center gap-2 font-medium transition-colors",m[e],b[o],(a||r)&&"opacity-60 cursor-not-allowed",c].filter(Boolean).join(" "),...x,children:[r?t.jsx(h,{size:s}):n?t.jsx(n,{size:s}):null,l,i&&!r&&t.jsx(i,{size:s})]})});B.displayName="Button";export{B};
1
+ import{r as p,j as t}from"./index-BXA3UT6P.js";const m={primary:"bg-[#553DE9] text-white hover:bg-[#4432c4] shadow-button rounded-btn",secondary:"border border-[#553DE9] text-[#553DE9] hover:bg-[#E8E4FD] bg-transparent rounded-btn",ghost:"text-[#36342E] hover:bg-[#F8F4F0] rounded-btn",danger:"bg-[#C45B5B]/10 text-[#C45B5B] border border-[#C45B5B]/20 hover:bg-[#C45B5B]/20 rounded-btn"},b={sm:"px-3 py-1.5 text-xs",md:"px-4 py-2 text-sm",lg:"px-6 py-3 text-base"},u={sm:14,md:16,lg:18};function h({size:e}){return t.jsxs("svg",{className:"animate-spin",width:e,height:e,viewBox:"0 0 24 24",fill:"none",children:[t.jsx("circle",{className:"opacity-25",cx:"12",cy:"12",r:"10",stroke:"currentColor",strokeWidth:"4"}),t.jsx("path",{className:"opacity-75",fill:"currentColor",d:"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"})]})}const B=p.forwardRef(({variant:e="primary",size:o="md",icon:n,iconRight:i,loading:r=!1,disabled:a,className:c="",children:l,...x},d)=>{const s=u[o];return t.jsxs("button",{ref:d,disabled:a||r,className:["inline-flex items-center justify-center gap-2 font-medium transition-colors",m[e],b[o],(a||r)&&"opacity-60 cursor-not-allowed",c].filter(Boolean).join(" "),...x,children:[r?t.jsx(h,{size:s}):n?t.jsx(n,{size:s}):null,l,i&&!r&&t.jsx(i,{size:s})]})});B.displayName="Button";export{B};
@@ -1 +1 @@
1
- import{r as i,j as e}from"./index-B-0iHBPO.js";function p({value:r,prefix:t="",suffix:s=""}){const[n,a]=i.useState(0),o=i.useRef(0);return i.useEffect(()=>{const c=o.current,d=r-c;if(d===0)return;const x=600,h=performance.now();function u(l){const b=l-h,f=Math.min(b/x,1),g=1-Math.pow(1-f,3),j=Math.round(c+d*g);a(j),o.current=j,f<1&&requestAnimationFrame(u)}requestAnimationFrame(u)},[r]),e.jsxs("span",{className:"tabular-nums",children:[t,n.toLocaleString(),s]})}function N(){const[r,t]=i.useState(5),[s,n]=i.useState(30),[a,o]=i.useState(75),x=Math.round(r*s*.4*4.33),h=Math.round(x*a),u=1/0;return e.jsxs("div",{className:"bg-white border border-[#ECEAE3] rounded-xl p-6 shadow-sm",children:[e.jsx("h3",{className:"text-lg font-bold text-[#36342E] mb-1",children:"ROI Calculator"}),e.jsx("p",{className:"text-sm text-[#6B6960] mb-6",children:"Estimate how much your team could save with autonomous development."}),e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsx("label",{className:"text-sm font-medium text-[#36342E]",children:"Team size"}),e.jsxs("span",{className:"text-sm font-semibold text-[#553DE9] tabular-nums",children:[r," developers"]})]}),e.jsx("input",{type:"range",min:1,max:50,value:r,onChange:l=>t(Number(l.target.value)),className:"w-full h-2 bg-[#ECEAE3] rounded-full appearance-none cursor-pointer accent-[#553DE9]"}),e.jsxs("div",{className:"flex justify-between text-xs text-[#939084] mt-1",children:[e.jsx("span",{children:"1"}),e.jsx("span",{children:"50"})]})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsx("label",{className:"text-sm font-medium text-[#36342E]",children:"Hours coding per week"}),e.jsxs("span",{className:"text-sm font-semibold text-[#553DE9] tabular-nums",children:[s,"h"]})]}),e.jsx("input",{type:"range",min:5,max:60,value:s,onChange:l=>n(Number(l.target.value)),className:"w-full h-2 bg-[#ECEAE3] rounded-full appearance-none cursor-pointer accent-[#553DE9]"}),e.jsxs("div",{className:"flex justify-between text-xs text-[#939084] mt-1",children:[e.jsx("span",{children:"5h"}),e.jsx("span",{children:"60h"})]})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsx("label",{className:"text-sm font-medium text-[#36342E]",children:"Average hourly rate"}),e.jsxs("span",{className:"text-sm font-semibold text-[#553DE9] tabular-nums",children:["$",a,"/hr"]})]}),e.jsx("input",{type:"range",min:25,max:250,step:5,value:a,onChange:l=>o(Number(l.target.value)),className:"w-full h-2 bg-[#ECEAE3] rounded-full appearance-none cursor-pointer accent-[#553DE9]"}),e.jsxs("div",{className:"flex justify-between text-xs text-[#939084] mt-1",children:[e.jsx("span",{children:"$25"}),e.jsx("span",{children:"$250"})]})]})]}),e.jsxs("div",{className:"mt-6 pt-6 border-t border-[#ECEAE3]",children:[e.jsxs("div",{className:"grid grid-cols-3 gap-4 text-center",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-2xl font-bold text-[#553DE9]",children:e.jsx(p,{value:x,suffix:"h"})}),e.jsx("div",{className:"text-xs text-[#6B6960] mt-1",children:"Hours saved / month"})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-2xl font-bold text-[#1FC5A8]",children:e.jsx(p,{value:h,prefix:"$"})}),e.jsx("div",{className:"text-xs text-[#6B6960] mt-1",children:"Cost saved / month"})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-2xl font-bold text-[#36342E]",children:u===1/0?"Free":e.jsx(p,{value:u,suffix:"%"})}),e.jsx("div",{className:"text-xs text-[#6B6960] mt-1",children:"ROI (self-hosted)"})]})]}),e.jsx("div",{className:"mt-4 p-3 rounded-lg bg-[#553DE9]/5 border border-[#553DE9]/10",children:e.jsxs("p",{className:"text-sm text-[#36342E] text-center",children:["With Loki Mode, your team could save"," ",e.jsxs("span",{className:"font-bold text-[#553DE9]",children:[x.toLocaleString()," hours"]})," ","and"," ",e.jsxs("span",{className:"font-bold text-[#1FC5A8]",children:["$",h.toLocaleString()]})," ","per month."]})})]})]})}function y({count:r=5}){return e.jsx("div",{className:"flex gap-0.5",children:Array.from({length:r}).map((t,s)=>e.jsx("svg",{width:"16",height:"16",viewBox:"0 0 24 24",fill:"#553DE9",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"})},s))})}function w(){return e.jsx("svg",{width:"32",height:"32",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",className:"text-[#553DE9]/15",children:e.jsx("path",{d:"M3 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1zm12 0c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z",fill:"currentColor"})})}function k({quote:r,name:t,role:s,company:n,avatar:a,rating:o=5}){const c=t.split(" ").map(d=>d[0]).join("").toUpperCase().slice(0,2);return e.jsxs("div",{className:"bg-white border border-[#ECEAE3] rounded-xl p-6 flex flex-col gap-4 shadow-sm hover:shadow-md transition-shadow",children:[e.jsxs("div",{className:"flex items-start justify-between",children:[e.jsx(w,{}),e.jsx(y,{count:o})]}),e.jsxs("p",{className:"text-[#36342E] text-sm leading-relaxed flex-1",children:['"',r,'"']}),e.jsxs("div",{className:"flex items-center gap-3 pt-2 border-t border-[#ECEAE3]",children:[a?e.jsx("img",{src:a,alt:"",className:"w-10 h-10 rounded-full object-cover"}):e.jsx("div",{className:"w-10 h-10 rounded-full bg-gradient-to-br from-[#553DE9] to-[#7B6BEF] flex items-center justify-center text-white text-sm font-bold flex-shrink-0",children:c}),e.jsxs("div",{children:[e.jsx("p",{className:"text-sm font-semibold text-[#36342E]",children:t}),e.jsxs("p",{className:"text-xs text-[#6B6960]",children:[s,", ",n]})]})]})]})}const E=[{quote:"Loki Mode turned a weekend project into a production app in 45 minutes. The RARV cycle caught bugs I would have shipped. Easily 10x faster than writing everything by hand.",name:"Sarah Chen",role:"Full-Stack Developer",company:"Nimbus Labs"},{quote:"The quality gates are what sold me. Three blind reviewers, anti-sycophancy checks, severity blocking -- it catches issues that even our senior engineers miss in code review.",name:"Marcus Rivera",role:"CTO",company:"Forgepoint Systems"},{quote:"As a solo founder, Loki Mode is like having a senior engineering team on demand. I shipped our MVP in a day instead of two weeks. The autonomous iteration is the real game changer.",name:"Priya Sharma",role:"Founder",company:"Seedcraft"},{quote:"Self-hosted, no data leaves our network, and the multi-provider failover means we never hit rate limits. Exactly what our compliance team needed. Enterprise-ready out of the box.",name:"Daniel Okafor",role:"DevOps Lead",company:"Vaultline Security"}],v=[{feature:"Claude (Opus/Sonnet/Haiku)",category:"AI Models",lokiMode:!0,boltNew:!1,replit:!1,lovable:!0},{feature:"GPT-4o / Codex",category:"AI Models",lokiMode:!0,boltNew:!0,replit:!0,lovable:!1},{feature:"Gemini",category:"AI Models",lokiMode:!0,boltNew:!1,replit:!0,lovable:!1},{feature:"5-provider auto-failover",category:"AI Models",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"9 automated quality gates",category:"Quality",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"Blind 3-reviewer code review",category:"Quality",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"Anti-sycophancy checks",category:"Quality",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"Automated test generation",category:"Quality",lokiMode:!0,boltNew:!1,replit:"Partial",lovable:!1},{feature:"Docker container generation",category:"Deployment",lokiMode:!0,boltNew:!1,replit:!0,lovable:!1},{feature:"CI/CD pipeline generation",category:"Deployment",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"One-click cloud deploy",category:"Deployment",lokiMode:"Planned",boltNew:!0,replit:!0,lovable:!0},{feature:"Self-hosted (your keys)",category:"Cost",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"Free tier available",category:"Cost",lokiMode:"Source-available (BUSL-1.1)",boltNew:!0,replit:!0,lovable:!0},{feature:"No vendor lock-in",category:"Cost",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"SSO / OIDC authentication",category:"Enterprise",lokiMode:!0,boltNew:!1,replit:!0,lovable:!1},{feature:"RBAC access control",category:"Enterprise",lokiMode:!0,boltNew:!1,replit:!0,lovable:!1},{feature:"Audit logging",category:"Enterprise",lokiMode:!0,boltNew:!1,replit:!0,lovable:!1},{feature:"Air-gapped deployment",category:"Enterprise",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1}];function C(){return e.jsx("svg",{width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",className:"text-[#1FC5A8]",children:e.jsx("path",{d:"M20 6L9 17l-5-5",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round"})})}function M(){return e.jsx("svg",{width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",className:"text-[#C45B5B]/40",children:e.jsx("path",{d:"M18 6L6 18M6 6l12 12",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})}function m({value:r}){return typeof r=="string"?e.jsx("span",{className:"text-xs text-[#6B6960] font-medium",children:r}):r?e.jsx(C,{}):e.jsx(M,{})}const A=[{title:"Full Autonomy",description:"Describe what you want. Loki Mode handles planning, coding, testing, and verification automatically.",icon:e.jsx("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",className:"text-[#553DE9]",children:e.jsx("path",{d:"M13 2L3 14h9l-1 8 10-12h-9l1-8z",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})},{title:"Production Quality",description:"9 quality gates, blind code review, and anti-sycophancy checks ensure code that actually works.",icon:e.jsx("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",className:"text-[#1FC5A8]",children:e.jsx("path",{d:"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})},{title:"Your Infrastructure",description:"Self-hosted, source-available (BUSL-1.1), no data leaves your network. Use your own API keys across 5 providers.",icon:e.jsxs("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",className:"text-[#2F71E3]",children:[e.jsx("rect",{x:"2",y:"2",width:"20",height:"8",rx:"2",stroke:"currentColor",strokeWidth:"2"}),e.jsx("rect",{x:"2",y:"14",width:"20",height:"8",rx:"2",stroke:"currentColor",strokeWidth:"2"}),e.jsx("circle",{cx:"6",cy:"6",r:"1",fill:"currentColor"}),e.jsx("circle",{cx:"6",cy:"18",r:"1",fill:"currentColor"})]})},{title:"Built for Teams",description:"Enterprise auth, RBAC, audit logs, and parallel workflows for teams of any size.",icon:e.jsxs("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",className:"text-[#E93D82]",children:[e.jsx("path",{d:"M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"}),e.jsx("circle",{cx:"9",cy:"7",r:"4",stroke:"currentColor",strokeWidth:"2"}),e.jsx("path",{d:"M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})]})}];function S(){const r=[...new Set(v.map(t=>t.category))];return e.jsx("div",{className:"min-h-screen bg-[#FAF9F6]",children:e.jsxs("div",{className:"max-w-6xl mx-auto px-6 py-8",children:[e.jsxs("div",{className:"mb-8",children:[e.jsx("h1",{className:"text-2xl font-bold text-[#36342E]",children:"Compare"}),e.jsx("p",{className:"text-[#6B6960] mt-1",children:"See how Loki Mode stacks up against other AI development platforms."})]}),e.jsx("div",{className:"bg-white border border-[#ECEAE3] rounded-xl overflow-hidden shadow-sm",children:e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-[#ECEAE3]",children:[e.jsx("th",{className:"text-left px-4 py-3 font-medium text-[#6B6960] w-[240px]",children:"Feature"}),e.jsx("th",{className:"text-center px-4 py-3 font-bold text-[#553DE9] bg-[#553DE9]/5 w-[130px]",children:"Loki Mode"}),e.jsx("th",{className:"text-center px-4 py-3 font-medium text-[#6B6960] w-[130px]",children:"bolt.new"}),e.jsx("th",{className:"text-center px-4 py-3 font-medium text-[#6B6960] w-[130px]",children:"Replit"}),e.jsx("th",{className:"text-center px-4 py-3 font-medium text-[#6B6960] w-[130px]",children:"Lovable"})]})}),e.jsx("tbody",{children:r.map(t=>e.jsxs(e.Fragment,{children:[e.jsx("tr",{className:"bg-[#FAF9F6]",children:e.jsx("td",{colSpan:5,className:"px-4 py-2 text-xs font-bold text-[#939084] uppercase tracking-wider",children:t})},`cat-${t}`),v.filter(s=>s.category===t).map(s=>e.jsxs("tr",{className:"border-b border-[#ECEAE3] last:border-b-0",children:[e.jsx("td",{className:"px-4 py-2.5 text-[#36342E]",children:s.feature}),e.jsx("td",{className:"px-4 py-2.5 text-center bg-[#553DE9]/5",children:e.jsx("div",{className:"flex justify-center",children:e.jsx(m,{value:s.lokiMode})})}),e.jsx("td",{className:"px-4 py-2.5 text-center",children:e.jsx("div",{className:"flex justify-center",children:e.jsx(m,{value:s.boltNew})})}),e.jsx("td",{className:"px-4 py-2.5 text-center",children:e.jsx("div",{className:"flex justify-center",children:e.jsx(m,{value:s.replit})})}),e.jsx("td",{className:"px-4 py-2.5 text-center",children:e.jsx("div",{className:"flex justify-center",children:e.jsx(m,{value:s.lovable})})})]},s.feature))]}))})]})})}),e.jsxs("div",{className:"mt-12",children:[e.jsx("h2",{className:"text-xl font-bold text-[#36342E] mb-6",children:"Why developers choose Loki Mode"}),e.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-5",children:A.map(t=>e.jsxs("div",{className:"bg-white border border-[#ECEAE3] rounded-xl p-5 shadow-sm hover:shadow-md transition-shadow",children:[e.jsx("div",{className:"mb-3",children:t.icon}),e.jsx("h3",{className:"text-base font-bold text-[#36342E] mb-1",children:t.title}),e.jsx("p",{className:"text-sm text-[#6B6960]",children:t.description})]},t.title))})]}),e.jsxs("div",{className:"mt-12",children:[e.jsx("h2",{className:"text-xl font-bold text-[#36342E] mb-6",children:"What developers are saying"}),e.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-5",children:E.map(t=>e.jsx(k,{...t},t.name))})]}),e.jsx("div",{className:"mt-12 max-w-xl mx-auto",children:e.jsx(N,{})})]})})}export{S as default};
1
+ import{r as i,j as e}from"./index-BXA3UT6P.js";function p({value:r,prefix:t="",suffix:s=""}){const[n,a]=i.useState(0),o=i.useRef(0);return i.useEffect(()=>{const c=o.current,d=r-c;if(d===0)return;const x=600,h=performance.now();function u(l){const b=l-h,f=Math.min(b/x,1),g=1-Math.pow(1-f,3),j=Math.round(c+d*g);a(j),o.current=j,f<1&&requestAnimationFrame(u)}requestAnimationFrame(u)},[r]),e.jsxs("span",{className:"tabular-nums",children:[t,n.toLocaleString(),s]})}function N(){const[r,t]=i.useState(5),[s,n]=i.useState(30),[a,o]=i.useState(75),x=Math.round(r*s*.4*4.33),h=Math.round(x*a),u=1/0;return e.jsxs("div",{className:"bg-white border border-[#ECEAE3] rounded-xl p-6 shadow-sm",children:[e.jsx("h3",{className:"text-lg font-bold text-[#36342E] mb-1",children:"ROI Calculator"}),e.jsx("p",{className:"text-sm text-[#6B6960] mb-6",children:"Estimate how much your team could save with autonomous development."}),e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsx("label",{className:"text-sm font-medium text-[#36342E]",children:"Team size"}),e.jsxs("span",{className:"text-sm font-semibold text-[#553DE9] tabular-nums",children:[r," developers"]})]}),e.jsx("input",{type:"range",min:1,max:50,value:r,onChange:l=>t(Number(l.target.value)),className:"w-full h-2 bg-[#ECEAE3] rounded-full appearance-none cursor-pointer accent-[#553DE9]"}),e.jsxs("div",{className:"flex justify-between text-xs text-[#939084] mt-1",children:[e.jsx("span",{children:"1"}),e.jsx("span",{children:"50"})]})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsx("label",{className:"text-sm font-medium text-[#36342E]",children:"Hours coding per week"}),e.jsxs("span",{className:"text-sm font-semibold text-[#553DE9] tabular-nums",children:[s,"h"]})]}),e.jsx("input",{type:"range",min:5,max:60,value:s,onChange:l=>n(Number(l.target.value)),className:"w-full h-2 bg-[#ECEAE3] rounded-full appearance-none cursor-pointer accent-[#553DE9]"}),e.jsxs("div",{className:"flex justify-between text-xs text-[#939084] mt-1",children:[e.jsx("span",{children:"5h"}),e.jsx("span",{children:"60h"})]})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center justify-between mb-2",children:[e.jsx("label",{className:"text-sm font-medium text-[#36342E]",children:"Average hourly rate"}),e.jsxs("span",{className:"text-sm font-semibold text-[#553DE9] tabular-nums",children:["$",a,"/hr"]})]}),e.jsx("input",{type:"range",min:25,max:250,step:5,value:a,onChange:l=>o(Number(l.target.value)),className:"w-full h-2 bg-[#ECEAE3] rounded-full appearance-none cursor-pointer accent-[#553DE9]"}),e.jsxs("div",{className:"flex justify-between text-xs text-[#939084] mt-1",children:[e.jsx("span",{children:"$25"}),e.jsx("span",{children:"$250"})]})]})]}),e.jsxs("div",{className:"mt-6 pt-6 border-t border-[#ECEAE3]",children:[e.jsxs("div",{className:"grid grid-cols-3 gap-4 text-center",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-2xl font-bold text-[#553DE9]",children:e.jsx(p,{value:x,suffix:"h"})}),e.jsx("div",{className:"text-xs text-[#6B6960] mt-1",children:"Hours saved / month"})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-2xl font-bold text-[#1FC5A8]",children:e.jsx(p,{value:h,prefix:"$"})}),e.jsx("div",{className:"text-xs text-[#6B6960] mt-1",children:"Cost saved / month"})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-2xl font-bold text-[#36342E]",children:u===1/0?"Free":e.jsx(p,{value:u,suffix:"%"})}),e.jsx("div",{className:"text-xs text-[#6B6960] mt-1",children:"ROI (self-hosted)"})]})]}),e.jsx("div",{className:"mt-4 p-3 rounded-lg bg-[#553DE9]/5 border border-[#553DE9]/10",children:e.jsxs("p",{className:"text-sm text-[#36342E] text-center",children:["With Loki Mode, your team could save"," ",e.jsxs("span",{className:"font-bold text-[#553DE9]",children:[x.toLocaleString()," hours"]})," ","and"," ",e.jsxs("span",{className:"font-bold text-[#1FC5A8]",children:["$",h.toLocaleString()]})," ","per month."]})})]})]})}function y({count:r=5}){return e.jsx("div",{className:"flex gap-0.5",children:Array.from({length:r}).map((t,s)=>e.jsx("svg",{width:"16",height:"16",viewBox:"0 0 24 24",fill:"#553DE9",xmlns:"http://www.w3.org/2000/svg",children:e.jsx("path",{d:"M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"})},s))})}function w(){return e.jsx("svg",{width:"32",height:"32",viewBox:"0 0 24 24",fill:"none",xmlns:"http://www.w3.org/2000/svg",className:"text-[#553DE9]/15",children:e.jsx("path",{d:"M3 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1zm12 0c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z",fill:"currentColor"})})}function k({quote:r,name:t,role:s,company:n,avatar:a,rating:o=5}){const c=t.split(" ").map(d=>d[0]).join("").toUpperCase().slice(0,2);return e.jsxs("div",{className:"bg-white border border-[#ECEAE3] rounded-xl p-6 flex flex-col gap-4 shadow-sm hover:shadow-md transition-shadow",children:[e.jsxs("div",{className:"flex items-start justify-between",children:[e.jsx(w,{}),e.jsx(y,{count:o})]}),e.jsxs("p",{className:"text-[#36342E] text-sm leading-relaxed flex-1",children:['"',r,'"']}),e.jsxs("div",{className:"flex items-center gap-3 pt-2 border-t border-[#ECEAE3]",children:[a?e.jsx("img",{src:a,alt:"",className:"w-10 h-10 rounded-full object-cover"}):e.jsx("div",{className:"w-10 h-10 rounded-full bg-gradient-to-br from-[#553DE9] to-[#7B6BEF] flex items-center justify-center text-white text-sm font-bold flex-shrink-0",children:c}),e.jsxs("div",{children:[e.jsx("p",{className:"text-sm font-semibold text-[#36342E]",children:t}),e.jsxs("p",{className:"text-xs text-[#6B6960]",children:[s,", ",n]})]})]})]})}const E=[{quote:"Loki Mode turned a weekend project into a production app in 45 minutes. The RARV cycle caught bugs I would have shipped. Easily 10x faster than writing everything by hand.",name:"Sarah Chen",role:"Full-Stack Developer",company:"Nimbus Labs"},{quote:"The quality gates are what sold me. Three blind reviewers, anti-sycophancy checks, severity blocking -- it catches issues that even our senior engineers miss in code review.",name:"Marcus Rivera",role:"CTO",company:"Forgepoint Systems"},{quote:"As a solo founder, Loki Mode is like having a senior engineering team on demand. I shipped our MVP in a day instead of two weeks. The autonomous iteration is the real game changer.",name:"Priya Sharma",role:"Founder",company:"Seedcraft"},{quote:"Self-hosted, no data leaves our network, and the multi-provider failover means we never hit rate limits. Exactly what our compliance team needed. Enterprise-ready out of the box.",name:"Daniel Okafor",role:"DevOps Lead",company:"Vaultline Security"}],v=[{feature:"Claude (Opus/Sonnet/Haiku)",category:"AI Models",lokiMode:!0,boltNew:!1,replit:!1,lovable:!0},{feature:"GPT-4o / Codex",category:"AI Models",lokiMode:!0,boltNew:!0,replit:!0,lovable:!1},{feature:"Gemini",category:"AI Models",lokiMode:!0,boltNew:!1,replit:!0,lovable:!1},{feature:"5-provider auto-failover",category:"AI Models",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"9 automated quality gates",category:"Quality",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"Blind 3-reviewer code review",category:"Quality",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"Anti-sycophancy checks",category:"Quality",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"Automated test generation",category:"Quality",lokiMode:!0,boltNew:!1,replit:"Partial",lovable:!1},{feature:"Docker container generation",category:"Deployment",lokiMode:!0,boltNew:!1,replit:!0,lovable:!1},{feature:"CI/CD pipeline generation",category:"Deployment",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"One-click cloud deploy",category:"Deployment",lokiMode:"Planned",boltNew:!0,replit:!0,lovable:!0},{feature:"Self-hosted (your keys)",category:"Cost",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"Free tier available",category:"Cost",lokiMode:"Source-available (BUSL-1.1)",boltNew:!0,replit:!0,lovable:!0},{feature:"No vendor lock-in",category:"Cost",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"SSO / OIDC authentication",category:"Enterprise",lokiMode:!0,boltNew:!1,replit:!0,lovable:!1},{feature:"RBAC access control",category:"Enterprise",lokiMode:!0,boltNew:!1,replit:!0,lovable:!1},{feature:"Audit logging",category:"Enterprise",lokiMode:!0,boltNew:!1,replit:!0,lovable:!1},{feature:"Air-gapped deployment",category:"Enterprise",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1}];function C(){return e.jsx("svg",{width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",className:"text-[#1FC5A8]",children:e.jsx("path",{d:"M20 6L9 17l-5-5",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round"})})}function M(){return e.jsx("svg",{width:"18",height:"18",viewBox:"0 0 24 24",fill:"none",className:"text-[#C45B5B]/40",children:e.jsx("path",{d:"M18 6L6 18M6 6l12 12",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})}function m({value:r}){return typeof r=="string"?e.jsx("span",{className:"text-xs text-[#6B6960] font-medium",children:r}):r?e.jsx(C,{}):e.jsx(M,{})}const A=[{title:"Full Autonomy",description:"Describe what you want. Loki Mode handles planning, coding, testing, and verification automatically.",icon:e.jsx("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",className:"text-[#553DE9]",children:e.jsx("path",{d:"M13 2L3 14h9l-1 8 10-12h-9l1-8z",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})},{title:"Production Quality",description:"9 quality gates, blind code review, and anti-sycophancy checks ensure code that actually works.",icon:e.jsx("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",className:"text-[#1FC5A8]",children:e.jsx("path",{d:"M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})},{title:"Your Infrastructure",description:"Self-hosted, source-available (BUSL-1.1), no data leaves your network. Use your own API keys across 5 providers.",icon:e.jsxs("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",className:"text-[#2F71E3]",children:[e.jsx("rect",{x:"2",y:"2",width:"20",height:"8",rx:"2",stroke:"currentColor",strokeWidth:"2"}),e.jsx("rect",{x:"2",y:"14",width:"20",height:"8",rx:"2",stroke:"currentColor",strokeWidth:"2"}),e.jsx("circle",{cx:"6",cy:"6",r:"1",fill:"currentColor"}),e.jsx("circle",{cx:"6",cy:"18",r:"1",fill:"currentColor"})]})},{title:"Built for Teams",description:"Enterprise auth, RBAC, audit logs, and parallel workflows for teams of any size.",icon:e.jsxs("svg",{width:"24",height:"24",viewBox:"0 0 24 24",fill:"none",className:"text-[#E93D82]",children:[e.jsx("path",{d:"M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"}),e.jsx("circle",{cx:"9",cy:"7",r:"4",stroke:"currentColor",strokeWidth:"2"}),e.jsx("path",{d:"M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})]})}];function S(){const r=[...new Set(v.map(t=>t.category))];return e.jsx("div",{className:"min-h-screen bg-[#FAF9F6]",children:e.jsxs("div",{className:"max-w-6xl mx-auto px-6 py-8",children:[e.jsxs("div",{className:"mb-8",children:[e.jsx("h1",{className:"text-2xl font-bold text-[#36342E]",children:"Compare"}),e.jsx("p",{className:"text-[#6B6960] mt-1",children:"See how Loki Mode stacks up against other AI development platforms."})]}),e.jsx("div",{className:"bg-white border border-[#ECEAE3] rounded-xl overflow-hidden shadow-sm",children:e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"w-full text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-[#ECEAE3]",children:[e.jsx("th",{className:"text-left px-4 py-3 font-medium text-[#6B6960] w-[240px]",children:"Feature"}),e.jsx("th",{className:"text-center px-4 py-3 font-bold text-[#553DE9] bg-[#553DE9]/5 w-[130px]",children:"Loki Mode"}),e.jsx("th",{className:"text-center px-4 py-3 font-medium text-[#6B6960] w-[130px]",children:"bolt.new"}),e.jsx("th",{className:"text-center px-4 py-3 font-medium text-[#6B6960] w-[130px]",children:"Replit"}),e.jsx("th",{className:"text-center px-4 py-3 font-medium text-[#6B6960] w-[130px]",children:"Lovable"})]})}),e.jsx("tbody",{children:r.map(t=>e.jsxs(e.Fragment,{children:[e.jsx("tr",{className:"bg-[#FAF9F6]",children:e.jsx("td",{colSpan:5,className:"px-4 py-2 text-xs font-bold text-[#939084] uppercase tracking-wider",children:t})},`cat-${t}`),v.filter(s=>s.category===t).map(s=>e.jsxs("tr",{className:"border-b border-[#ECEAE3] last:border-b-0",children:[e.jsx("td",{className:"px-4 py-2.5 text-[#36342E]",children:s.feature}),e.jsx("td",{className:"px-4 py-2.5 text-center bg-[#553DE9]/5",children:e.jsx("div",{className:"flex justify-center",children:e.jsx(m,{value:s.lokiMode})})}),e.jsx("td",{className:"px-4 py-2.5 text-center",children:e.jsx("div",{className:"flex justify-center",children:e.jsx(m,{value:s.boltNew})})}),e.jsx("td",{className:"px-4 py-2.5 text-center",children:e.jsx("div",{className:"flex justify-center",children:e.jsx(m,{value:s.replit})})}),e.jsx("td",{className:"px-4 py-2.5 text-center",children:e.jsx("div",{className:"flex justify-center",children:e.jsx(m,{value:s.lovable})})})]},s.feature))]}))})]})})}),e.jsxs("div",{className:"mt-12",children:[e.jsx("h2",{className:"text-xl font-bold text-[#36342E] mb-6",children:"Why developers choose Loki Mode"}),e.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-5",children:A.map(t=>e.jsxs("div",{className:"bg-white border border-[#ECEAE3] rounded-xl p-5 shadow-sm hover:shadow-md transition-shadow",children:[e.jsx("div",{className:"mb-3",children:t.icon}),e.jsx("h3",{className:"text-base font-bold text-[#36342E] mb-1",children:t.title}),e.jsx("p",{className:"text-sm text-[#6B6960]",children:t.description})]},t.title))})]}),e.jsxs("div",{className:"mt-12",children:[e.jsx("h2",{className:"text-xl font-bold text-[#36342E] mb-6",children:"What developers are saying"}),e.jsx("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-5",children:E.map(t=>e.jsx(k,{...t},t.name))})]}),e.jsx("div",{className:"mt-12 max-w-xl mx-auto",children:e.jsx(N,{})})]})})}export{S as default};