loki-mode 9.19.1 → 9.21.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/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/lib/deadline.py +21 -1
- package/autonomy/lib/outcome_contract.py +128 -0
- package/autonomy/lib/proof-generator.py +45 -0
- package/autonomy/lib/proof-verify.py +53 -0
- package/autonomy/loki +437 -10
- package/autonomy/receipt_jwt.py +308 -0
- package/autonomy/run.sh +37 -2
- package/autonomy/trigger-server.py +186 -1
- package/bin/loki +1 -0
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +65 -0
- package/docs/COMPETITIVE-SCORECARD.md +31 -0
- package/docs/OUTCOME-CANARY.md +61 -0
- package/docs/OUTCOME-ROUTER.md +7 -0
- package/docs/PROOF-PASSPORT.md +29 -0
- package/docs/SIGNED-RECEIPTS.md +111 -0
- package/docs/metrics.md +32 -0
- package/events/emit.sh +31 -0
- 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/tools/outcome-canary.py +194 -0
- package/tools/outcome-router.py +80 -0
- package/tools/proof-passport.py +175 -0
- package/web-app/dist/assets/{AdminPage-CcCJ0Sjt.js → AdminPage-DNGNuAge.js} +1 -1
- package/web-app/dist/assets/{Avatar-DK8kmayw.js → Avatar-CqAveJaT.js} +1 -1
- package/web-app/dist/assets/{Badge-4uAWnemi.js → Badge-BtMejA0Z.js} +1 -1
- package/web-app/dist/assets/{Button-BBMk33tk.js → Button-CikxmqSs.js} +1 -1
- package/web-app/dist/assets/{ComparePage-bt9rwvST.js → ComparePage-BB8DJBWR.js} +1 -1
- package/web-app/dist/assets/EvidenceReceiptPanel-6oquKEjd.js +1 -0
- package/web-app/dist/assets/{GitHubIssuesPanel-WDbH47UM.js → GitHubIssuesPanel-5XLYWzWH.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-C2CiYtTx.js → GitHubPRsPanel-DUfmFYeu.js} +1 -1
- package/web-app/dist/assets/{HomePage-BQk-MUjn.js → HomePage-DZ4MmOdH.js} +5 -5
- package/web-app/dist/assets/{LoginPage-DMOZVGGL.js → LoginPage-VMzI6ROD.js} +1 -1
- package/web-app/dist/assets/{MagicPage-Bzp2Nt1z.js → MagicPage-CFbX5gWg.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-C39JVdsw.js → MetricsPage-DbL6xmRQ.js} +2 -2
- package/web-app/dist/assets/{NotFoundPage-6vT_U9UL.js → NotFoundPage-BUrjXPAR.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-BfFcZp-E.js → ProjectPage-iP6dJihg.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-CPMBf8Wt.js → ProjectsPage-BibN9bHl.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-BnNN6ETl.js → SettingsPage-C0WJp1hW.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-WDrMf-cx.js → ShowcasePage-DUv0Njp8.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-DX4jb2e8.js → SystemSettingsPage-BIaCB0Vn.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-BCfqcXzu.js → TeamsPage-BE0szv9r.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-CZvmimDj.js → TemplatesPage-BzkA9kat.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-BlRqFwWV.js → TerminalOutput-DBrdT_Fp.js} +1 -1
- package/web-app/dist/assets/{activity-CacZsUyr.js → activity-AbfFz38r.js} +1 -1
- package/web-app/dist/assets/{bell-DK2qtHnk.js → bell-DF6Z6y5U.js} +1 -1
- package/web-app/dist/assets/{bot-CkcUtHad.js → bot-DQ4np_3n.js} +1 -1
- package/web-app/dist/assets/{check-CbCPjX3M.js → check-RWuguCwE.js} +1 -1
- package/web-app/dist/assets/{chevron-left-5NUKWw3i.js → chevron-left-VpbQPOkC.js} +1 -1
- package/web-app/dist/assets/{circle-alert-S7uFoxC2.js → circle-alert-DwT82lJx.js} +1 -1
- package/web-app/dist/assets/{clock-CaQRrIrs.js → clock-BBlUa93G.js} +1 -1
- package/web-app/dist/assets/{cloud-DBAX6c0r.js → cloud-Dgbz3VCA.js} +1 -1
- package/web-app/dist/assets/{code-xml-De5-EXv3.js → code-xml-r6qqION5.js} +1 -1
- package/web-app/dist/assets/{copy-CUkT6k1v.js → copy-DMHj52B8.js} +1 -1
- package/web-app/dist/assets/{database-BAWf1Gwt.js → database-uJqHSHR1.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-Ji8zk86R.js → dollar-sign-BZagljek.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-ChtXoBwS.js → file-code-corner-DMopmhu0.js} +1 -1
- package/web-app/dist/assets/{file-plus-bFa37P76.js → file-plus-DU_hT_3w.js} +1 -1
- package/web-app/dist/assets/{folder-open-DhXpXscO.js → folder-open-BP5TtQAW.js} +1 -1
- package/web-app/dist/assets/{git-commit-horizontal-DVPeDQ3j.js → git-commit-horizontal-zFk_IobS.js} +1 -1
- package/web-app/dist/assets/{globe-BPZgPeeu.js → globe-mthCRtfp.js} +1 -1
- package/web-app/dist/assets/{hammer-jLCaujYH.js → hammer-BWaLBvSA.js} +1 -1
- package/web-app/dist/assets/{index-B-0iHBPO.js → index-BXA3UT6P.js} +3 -3
- package/web-app/dist/assets/{index-BfZSDej1.css → index-DgpUuqwR.css} +1 -1
- package/web-app/dist/assets/{layers-B1vsrsFW.js → layers-Q1LXV5fy.js} +1 -1
- package/web-app/dist/assets/{lightbulb-C-uLoq9Y.js → lightbulb-vRRNJ3dB.js} +1 -1
- package/web-app/dist/assets/{loader-circle-JTfD-ZuM.js → loader-circle-KgjeHEcV.js} +1 -1
- package/web-app/dist/assets/{lock-G9rxD4gZ.js → lock-BerKqno7.js} +1 -1
- package/web-app/dist/assets/{mail-BJ0PTN_V.js → mail-XAxTbprX.js} +1 -1
- package/web-app/dist/assets/{package-CXClfLOO.js → package-DLPKWAvW.js} +1 -1
- package/web-app/dist/assets/{plus-EoL5OCB7.js → plus-NMxxwHMn.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-BjREUnVq.js → refresh-cw-Dt42kYtM.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-DahWX07H.js → rotate-ccw-BCR4Tp9u.js} +1 -1
- package/web-app/dist/assets/{save-Dek3gCn1.js → save-sR4MJd42.js} +1 -1
- package/web-app/dist/assets/{server-D6V1BAia.js → server-SXkchokx.js} +1 -1
- package/web-app/dist/assets/{shield-alert-BtTK5Sxb.js → shield-alert-Bvgp6_Dz.js} +1 -1
- package/web-app/dist/assets/{trash-2-BT5o_g0r.js → trash-2-D0IGudH7.js} +1 -1
- package/web-app/dist/assets/{trending-down-D4Jk7KF3.js → trending-down-BbFFBq-v.js} +1 -1
- package/web-app/dist/assets/{trending-up-EQFTzhEo.js → trending-up-DDrJKL5i.js} +1 -1
- package/web-app/dist/assets/{upload-JfI5lCSE.js → upload-C54rjN8K.js} +1 -1
- package/web-app/dist/assets/{usePolling-BnhPUuGd.js → usePolling-D7fw4YSM.js} +1 -1
- package/web-app/dist/assets/{user-DSUiUYtj.js → user-Cg7fbzdh.js} +1 -1
- package/web-app/dist/index.html +2 -2
- package/web-app/server.py +78 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Plan a reversible canary split between two measured routes, or refuse."""
|
|
3
|
+
import argparse, hashlib, json, math, os, sys
|
|
4
|
+
|
|
5
|
+
OK, REFUSED, USAGE, NO_INPUT = 0, 3, 64, 66
|
|
6
|
+
REPORT = "loki-outcome-router/v1"
|
|
7
|
+
DOMAIN = "loki-outcome-canary/v1"
|
|
8
|
+
|
|
9
|
+
class Parser(argparse.ArgumentParser):
|
|
10
|
+
def error(self, message):
|
|
11
|
+
self.print_usage(sys.stderr)
|
|
12
|
+
self.exit(USAGE, f"{self.prog}: error: {message}\n")
|
|
13
|
+
|
|
14
|
+
def _number(value, low, high=None):
|
|
15
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
|
|
16
|
+
return None
|
|
17
|
+
if value < low or (high is not None and value > high):
|
|
18
|
+
return None
|
|
19
|
+
return float(value)
|
|
20
|
+
|
|
21
|
+
def _count(value):
|
|
22
|
+
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
|
|
23
|
+
return None
|
|
24
|
+
return value
|
|
25
|
+
|
|
26
|
+
def sha256_file(path):
|
|
27
|
+
digest = hashlib.sha256()
|
|
28
|
+
with open(path, "rb") as handle:
|
|
29
|
+
for chunk in iter(lambda: handle.read(65536), b""):
|
|
30
|
+
digest.update(chunk)
|
|
31
|
+
return digest.hexdigest()
|
|
32
|
+
|
|
33
|
+
def evidence_digest(subject, report_sha256, source_sha256, primary, control, percent):
|
|
34
|
+
# NUL-joined so a route name containing a separator cannot collide with another split.
|
|
35
|
+
parts = [DOMAIN, subject, report_sha256, source_sha256, primary, control, f"{percent:.6f}"]
|
|
36
|
+
return hashlib.sha256("\x00".join(parts).encode("utf-8")).hexdigest()
|
|
37
|
+
|
|
38
|
+
def assign(digest, percent):
|
|
39
|
+
return "canary" if int(digest, 16) % 10000 < round(percent * 100) else "control"
|
|
40
|
+
|
|
41
|
+
def load_report(path):
|
|
42
|
+
"""Return (report, report_sha256, refusal_reasons) from one exact byte read."""
|
|
43
|
+
with open(path, "rb") as handle:
|
|
44
|
+
report_bytes = handle.read()
|
|
45
|
+
report_sha256 = hashlib.sha256(report_bytes).hexdigest()
|
|
46
|
+
report = json.loads(report_bytes.decode("utf-8"))
|
|
47
|
+
reasons = []
|
|
48
|
+
if not isinstance(report, dict):
|
|
49
|
+
return None, report_sha256, ["report is not a JSON object"]
|
|
50
|
+
if report.get("report") != REPORT:
|
|
51
|
+
reasons.append(f"report version is not {REPORT}")
|
|
52
|
+
source = report.get("source")
|
|
53
|
+
if not isinstance(source, str) or not source.strip():
|
|
54
|
+
reasons.append("report has no source path")
|
|
55
|
+
source_sha256 = report.get("source_sha256")
|
|
56
|
+
if not isinstance(source_sha256, str) or len(source_sha256) != 64 or any(c not in "0123456789abcdef" for c in source_sha256):
|
|
57
|
+
reasons.append("report has no valid source_sha256")
|
|
58
|
+
if not isinstance(report.get("candidates"), list):
|
|
59
|
+
reasons.append("report has no candidates")
|
|
60
|
+
return report, report_sha256, reasons
|
|
61
|
+
|
|
62
|
+
def candidate(report, name):
|
|
63
|
+
for row in report.get("candidates") or []:
|
|
64
|
+
if isinstance(row, dict) and row.get("route") == name:
|
|
65
|
+
return row
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
def plan(report_path, subject, control_route, canary_percent=10.0, max_risk=.25, enable_canary=False):
|
|
69
|
+
"""Build a canary plan or an explained refusal. Reads only; writes nothing."""
|
|
70
|
+
reasons = []
|
|
71
|
+
if not isinstance(subject, str) or not subject.strip():
|
|
72
|
+
reasons.append("subject must be non-empty")
|
|
73
|
+
if not isinstance(control_route, str) or not control_route.strip():
|
|
74
|
+
reasons.append("control route must be non-empty")
|
|
75
|
+
if not enable_canary:
|
|
76
|
+
reasons.append("canary is opt-in; pass --enable-canary")
|
|
77
|
+
try:
|
|
78
|
+
report, report_sha256, schema_reasons = load_report(report_path)
|
|
79
|
+
except Exception as exc:
|
|
80
|
+
return {"plan": DOMAIN, "report": os.path.abspath(report_path), "report_sha256": None,
|
|
81
|
+
"assignment": None,
|
|
82
|
+
"refusal_reasons": reasons + [f"report is malformed: {exc}"]}
|
|
83
|
+
reasons += schema_reasons
|
|
84
|
+
if report is None:
|
|
85
|
+
return {"plan": DOMAIN, "report": os.path.abspath(report_path),
|
|
86
|
+
"report_sha256": report_sha256, "assignment": None,
|
|
87
|
+
"refusal_reasons": reasons}
|
|
88
|
+
|
|
89
|
+
source = report.get("source") if isinstance(report.get("source"), str) else None
|
|
90
|
+
source_sha256 = report.get("source_sha256")
|
|
91
|
+
source_missing = False
|
|
92
|
+
if source and not schema_reasons:
|
|
93
|
+
if not os.path.isfile(source):
|
|
94
|
+
source_missing = True
|
|
95
|
+
reasons.append(f"evidence source is absent: {source}")
|
|
96
|
+
elif sha256_file(source) != source_sha256:
|
|
97
|
+
reasons.append("evidence source has drifted from the report digest")
|
|
98
|
+
|
|
99
|
+
primary = report.get("selected_route")
|
|
100
|
+
if not isinstance(primary, str) or not primary.strip():
|
|
101
|
+
reasons.append("report selected no primary route")
|
|
102
|
+
primary = None
|
|
103
|
+
rows = {"primary": candidate(report, primary) if primary else None,
|
|
104
|
+
"control": candidate(report, control_route)}
|
|
105
|
+
for role, row in rows.items():
|
|
106
|
+
name = primary if role == "primary" else control_route
|
|
107
|
+
if row is None:
|
|
108
|
+
reasons.append(f"{role} route {name!r} is absent from the report")
|
|
109
|
+
elif row.get("eligible") is not True:
|
|
110
|
+
reasons.append(f"{role} route {name!r} is not eligible on measured evidence")
|
|
111
|
+
if primary is not None and primary == control_route:
|
|
112
|
+
reasons.append("primary and control must differ")
|
|
113
|
+
|
|
114
|
+
percent = _number(canary_percent, 0, 100)
|
|
115
|
+
if percent is None:
|
|
116
|
+
reasons.append("canary percent must be a finite number between 0 and 100")
|
|
117
|
+
ceiling = _number(max_risk, 0, 1)
|
|
118
|
+
if ceiling is None:
|
|
119
|
+
reasons.append("max risk must be a finite number between 0 and 1")
|
|
120
|
+
|
|
121
|
+
trials = {}
|
|
122
|
+
for role, row in rows.items():
|
|
123
|
+
if row is None:
|
|
124
|
+
continue
|
|
125
|
+
name = row.get("route")
|
|
126
|
+
risk = _number(row.get("mean_risk"), 0, 1)
|
|
127
|
+
count = _count(row.get("trials"))
|
|
128
|
+
if risk is None or count is None:
|
|
129
|
+
reasons.append(f"{role} route {name!r} has invalid measured values")
|
|
130
|
+
continue
|
|
131
|
+
trials[role] = count
|
|
132
|
+
if ceiling is not None and risk > ceiling:
|
|
133
|
+
reasons.append(f"{role} route {name!r} mean risk {risk:.6f} exceeds {ceiling:.6f}")
|
|
134
|
+
if len(trials) == 2 and trials["primary"] != trials["control"]:
|
|
135
|
+
reasons.append(f"evidence is unmatched: {trials['primary']} primary trials vs {trials['control']} control trials")
|
|
136
|
+
|
|
137
|
+
out = {"plan": DOMAIN, "report": os.path.abspath(report_path),
|
|
138
|
+
"report_sha256": report_sha256, "report_version": report.get("report"),
|
|
139
|
+
"source": source, "source_sha256": source_sha256, "source_missing": source_missing,
|
|
140
|
+
"primary_route": primary, "control_route": control_route,
|
|
141
|
+
"canary_percent": percent, "max_risk": ceiling, "subject": subject,
|
|
142
|
+
"assignment": None, "evidence_digest": None, "reversible": True,
|
|
143
|
+
"rollback": None, "refusal_reasons": reasons}
|
|
144
|
+
if reasons:
|
|
145
|
+
return out
|
|
146
|
+
digest = evidence_digest(subject, report_sha256, source_sha256, primary, control_route, percent)
|
|
147
|
+
out["evidence_digest"] = digest
|
|
148
|
+
out["assignment"] = assign(digest, percent)
|
|
149
|
+
out["route"] = primary if out["assignment"] == "canary" else control_route
|
|
150
|
+
out["rollback"] = {
|
|
151
|
+
"assignment": "control", "route": control_route, "reversible": True,
|
|
152
|
+
"effect": "every subject is assigned the control route",
|
|
153
|
+
"command": f"python3 tools/outcome-canary.py {report_path} --enable-canary "
|
|
154
|
+
f"--subject {subject} --control-route {control_route} --canary-percent 0"}
|
|
155
|
+
return out
|
|
156
|
+
|
|
157
|
+
def main(argv=None):
|
|
158
|
+
p = Parser(prog="outcome-canary")
|
|
159
|
+
p.add_argument("report")
|
|
160
|
+
p.add_argument("--enable-canary", action="store_true")
|
|
161
|
+
p.add_argument("--subject", required=True)
|
|
162
|
+
p.add_argument("--control-route", required=True)
|
|
163
|
+
p.add_argument("--canary-percent", type=float, default=10.0)
|
|
164
|
+
p.add_argument("--max-risk", type=float, default=.25)
|
|
165
|
+
p.add_argument("--json", action="store_true")
|
|
166
|
+
a = p.parse_args(argv)
|
|
167
|
+
if not a.subject.strip() or not a.control_route.strip():
|
|
168
|
+
p.error("subject and control route must be non-empty")
|
|
169
|
+
if _number(a.canary_percent, 0, 100) is None or _number(a.max_risk, 0, 1) is None:
|
|
170
|
+
p.error("invalid policy threshold")
|
|
171
|
+
if not os.path.isfile(a.report):
|
|
172
|
+
print(f"outcome-canary: no such report: {a.report}", file=sys.stderr)
|
|
173
|
+
return NO_INPUT
|
|
174
|
+
result = plan(a.report, a.subject, a.control_route, a.canary_percent, a.max_risk, a.enable_canary)
|
|
175
|
+
if result.get("source_missing"):
|
|
176
|
+
if a.json: print(json.dumps(result, sort_keys=True))
|
|
177
|
+
else: print(f"outcome-canary: evidence source is absent: {result['source']}", file=sys.stderr)
|
|
178
|
+
return NO_INPUT
|
|
179
|
+
if a.json:
|
|
180
|
+
print(json.dumps(result, sort_keys=True))
|
|
181
|
+
elif result["refusal_reasons"]:
|
|
182
|
+
print("Canary plan: REFUSED")
|
|
183
|
+
for reason in result["refusal_reasons"]:
|
|
184
|
+
print(f" {reason}")
|
|
185
|
+
else:
|
|
186
|
+
print(f"Canary plan: {result['assignment']} -> {result['route']}")
|
|
187
|
+
print(f" primary={result['primary_route']} control={result['control_route']} percent={result['canary_percent']:.6f}")
|
|
188
|
+
print(f" report sha256: {result['report_sha256']}")
|
|
189
|
+
print(f" evidence digest: {result['evidence_digest']}")
|
|
190
|
+
print(f" reversible: {result['reversible']}; rollback assigns {result['rollback']['assignment']}")
|
|
191
|
+
print(f" rollback: {result['rollback']['command']}")
|
|
192
|
+
return REFUSED if result["refusal_reasons"] else OK
|
|
193
|
+
|
|
194
|
+
if __name__ == "__main__": raise SystemExit(main())
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Recommend a route from measured accepted-outcome efficiency, or refuse."""
|
|
3
|
+
import argparse, hashlib, json, math, os, sys
|
|
4
|
+
|
|
5
|
+
OK, NO_ELIGIBLE, USAGE, NO_INPUT = 0, 3, 64, 66
|
|
6
|
+
|
|
7
|
+
class Parser(argparse.ArgumentParser):
|
|
8
|
+
def error(self, message):
|
|
9
|
+
self.print_usage(sys.stderr)
|
|
10
|
+
self.exit(USAGE, f"{self.prog}: error: {message}\n")
|
|
11
|
+
|
|
12
|
+
def _number(value, low, high=None, strict=False):
|
|
13
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
|
|
14
|
+
return None
|
|
15
|
+
if value < low or (strict and value == low) or (high is not None and value > high):
|
|
16
|
+
return None
|
|
17
|
+
return float(value)
|
|
18
|
+
|
|
19
|
+
def route(path, min_trials=3, max_risk=.25, risk_weight=1.0):
|
|
20
|
+
groups, invalid = {}, []
|
|
21
|
+
with open(path, encoding="utf-8") as handle:
|
|
22
|
+
for line_no, line in enumerate(handle, 1):
|
|
23
|
+
try:
|
|
24
|
+
row = json.loads(line)
|
|
25
|
+
name = row.get("route") if isinstance(row, dict) else None
|
|
26
|
+
cost = _number(row.get("cost_usd"), 0) if isinstance(row, dict) else None
|
|
27
|
+
mins = _number(row.get("duration_minutes"), 0, strict=True) if isinstance(row, dict) else None
|
|
28
|
+
risk = _number(row.get("risk"), 0, 1) if isinstance(row, dict) else None
|
|
29
|
+
accepted = row.get("accepted") if isinstance(row, dict) else None
|
|
30
|
+
verifier = row.get("verifier", "") if isinstance(row, dict) else ""
|
|
31
|
+
if not isinstance(name, str) or not name.strip() or type(accepted) is not bool or cost is None or mins is None or risk is None or not isinstance(verifier, str):
|
|
32
|
+
raise ValueError("invalid route observation schema")
|
|
33
|
+
groups.setdefault(name, []).append((accepted, cost, mins, risk))
|
|
34
|
+
except Exception as exc:
|
|
35
|
+
invalid.append({"line": line_no, "reason": str(exc)})
|
|
36
|
+
candidates = []
|
|
37
|
+
for name in sorted(groups):
|
|
38
|
+
rows = groups[name]; n = len(rows); accepted = sum(x[0] for x in rows)
|
|
39
|
+
cost = sum(x[1] for x in rows); mins = sum(x[2] for x in rows)
|
|
40
|
+
mean_risk = sum(x[3] for x in rows) / n
|
|
41
|
+
reasons = []
|
|
42
|
+
if invalid: reasons.append("input contains invalid observations")
|
|
43
|
+
if n < min_trials: reasons.append(f"needs {min_trials} trials; has {n}")
|
|
44
|
+
if accepted == 0: reasons.append("has no accepted outcome")
|
|
45
|
+
if mean_risk > max_risk: reasons.append(f"mean risk {mean_risk:.6f} exceeds {max_risk:.6f}")
|
|
46
|
+
per_dollar = accepted / max(cost, 1e-9)
|
|
47
|
+
per_minute = accepted / mins
|
|
48
|
+
harmonic = 2 * per_dollar * per_minute / (per_dollar + per_minute) if accepted else 0.0
|
|
49
|
+
score = harmonic * max(0.0, 1.0 - risk_weight * mean_risk)
|
|
50
|
+
candidates.append({"route": name, "trials": n, "accepted": accepted,
|
|
51
|
+
"cost_usd": cost, "duration_minutes": mins, "mean_risk": mean_risk,
|
|
52
|
+
"accepted_per_dollar": per_dollar, "accepted_per_minute": per_minute,
|
|
53
|
+
"score": score, "eligible": not reasons, "refusal_reasons": reasons})
|
|
54
|
+
eligible = sorted((c for c in candidates if c["eligible"]), key=lambda c: (-c["score"], c["route"]))
|
|
55
|
+
with open(path, "rb") as source_handle:
|
|
56
|
+
source_sha256 = hashlib.sha256(source_handle.read()).hexdigest()
|
|
57
|
+
return {"report": "loki-outcome-router/v1", "source": os.path.abspath(path),
|
|
58
|
+
"source_sha256": source_sha256,
|
|
59
|
+
"policy": {"min_trials": min_trials, "max_risk": max_risk,
|
|
60
|
+
"risk_weight": risk_weight},
|
|
61
|
+
"selected_route": eligible[0]["route"] if eligible else None,
|
|
62
|
+
"invalid_observations": invalid, "candidates": candidates,
|
|
63
|
+
"formula": "harmonic(accepted/USD, accepted/minute) * max(0, 1-risk_weight*mean_risk)"}
|
|
64
|
+
|
|
65
|
+
def main(argv=None):
|
|
66
|
+
p = Parser(); p.add_argument("path"); p.add_argument("--json", action="store_true")
|
|
67
|
+
p.add_argument("--min-trials", type=int, default=3); p.add_argument("--max-risk", type=float, default=.25)
|
|
68
|
+
p.add_argument("--risk-weight", type=float, default=1.0); a = p.parse_args(argv)
|
|
69
|
+
if a.min_trials < 1 or not math.isfinite(a.max_risk) or not 0 <= a.max_risk <= 1 or not math.isfinite(a.risk_weight) or a.risk_weight < 0: p.error("invalid threshold")
|
|
70
|
+
if not os.path.isfile(a.path): print(f"outcome-router: no such file: {a.path}", file=sys.stderr); return NO_INPUT
|
|
71
|
+
report = route(a.path, a.min_trials, a.max_risk, a.risk_weight)
|
|
72
|
+
if a.json: print(json.dumps(report, sort_keys=True))
|
|
73
|
+
else:
|
|
74
|
+
print("Outcome route: " + (report["selected_route"] or "REFUSED"))
|
|
75
|
+
for c in report["candidates"]:
|
|
76
|
+
print(f" {c['route']}: score={c['score']:.6f} trials={c['trials']} accepted={c['accepted']} " + ("eligible" if c['eligible'] else "; ".join(c['refusal_reasons'])))
|
|
77
|
+
if report["invalid_observations"]: print(f" invalid observations: {len(report['invalid_observations'])}")
|
|
78
|
+
return OK if report["selected_route"] else NO_ELIGIBLE
|
|
79
|
+
|
|
80
|
+
if __name__ == "__main__": raise SystemExit(main())
|
|
@@ -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("`", "`").replace(
|
|
117
|
+
"\r", " ").replace("\n", " ").replace("|", "|")
|
|
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-
|
|
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 +1 @@
|
|
|
1
|
-
import{j as e}from"./index-
|
|
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-
|
|
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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{r as d,a as x,j as e}from"./index-BXA3UT6P.js";const j={verified:{badge:"bg-success/10 text-success border-success/20",dot:"bg-success",label:"Verified",proven:"These receipt bytes are unaltered, and they were signed by the holder of the signing key.",notProven:"That the code is correct. A receipt records which checks ran, not that the result is right."},unsigned:{badge:"bg-warning/10 text-warning border-warning/20",dot:"bg-warning",label:"Unsigned",proven:"The receipt is internally consistent with its own integrity hash.",notProven:"Who produced it. An unsigned receipt is forgeable: whoever controls the builder can rewrite the facts and recompute the hash."},unchecked:{badge:"bg-warning/10 text-warning border-warning/20",dot:"bg-warning",label:"Not checked",proven:"The integrity hash matches, so nothing is wrong with the contents.",notProven:"Provenance. A signature is present but was not evaluated here, which is not the same as a bad signature."},tampered:{badge:"bg-danger/10 text-danger border-danger/20",dot:"bg-danger",label:"Tampered",proven:"Nothing. The recorded hash does not match the contents.",notProven:"Anything at all. This receipt was edited after it was written. Do not trust this build."}};function w(t){const n=t==null?void 0:t.verification;return!n||!n.hash?"unsigned":n.attestation||n.gpg_signature?"unchecked":"unsigned"}const b=[{key:"verified",label:"Verified",bar:"bg-success",text:"text-success"},{key:"with_gaps",label:"With gaps",bar:"bg-warning",text:"text-warning"},{key:"not_verified",label:"Not verified",bar:"bg-danger",text:"text-danger"},{key:"unknown",label:"Unknown",bar:"bg-muted/40",text:"text-muted"}];function N({summary:t}){const n=t.total_receipts;return n?e.jsxs("div",{className:"space-y-2",children:[e.jsx("div",{className:"flex h-1.5 rounded-full overflow-hidden bg-muted/10",children:b.map(({key:a,bar:s})=>{const i=t[a];return i?e.jsx("div",{className:s,style:{width:`${i/n*100}%`},title:`${i} ${a}`},a):null})}),e.jsx("div",{className:"flex flex-wrap gap-x-4 gap-y-1 text-xs",children:b.map(({key:a,label:s,text:i})=>{const l=t[a];return l?e.jsxs("span",{className:i,children:[e.jsx("span",{className:"font-semibold",children:l})," ",s.toLowerCase()]},a):null})}),e.jsxs("p",{className:"text-muted text-xs",children:["Recorded verdicts across ",n," receipt",n===1?"":"s",", not re-verified here."]})]}):null}function o({label:t,value:n}){return e.jsxs("div",{className:"flex items-baseline gap-2 text-xs",children:[e.jsx("span",{className:"text-muted w-28 flex-shrink-0",children:t}),e.jsx("span",{className:"font-mono truncate",title:n,children:n})]})}function y({proof:t}){var m,p,g,f;const[n,a]=d.useState(!1),[s,i]=d.useState(null),[l,c]=d.useState(null);d.useEffect(()=>{if(!n||s||l)return;let h=!1;return x.getProof(t.run_id).then(u=>{h||i(u)}).catch(u=>{h||c((u==null?void 0:u.message)||"could not load this receipt")}),()=>{h=!0}},[n,t.run_id,s,l]);const r=j[w(s)],v=t.generated_at?new Date(t.generated_at).toLocaleString():"unknown";return e.jsxs("div",{className:`border rounded-card overflow-hidden ${s?r.badge:"border-muted/20"}`,children:[e.jsxs("button",{type:"button",className:"w-full flex items-center gap-3 px-3 py-2.5 text-left",onClick:()=>a(!n),"aria-expanded":n,children:[e.jsx("span",{className:`w-2 h-2 rounded-full flex-shrink-0 ${s?r.dot:"bg-muted/40"}`}),e.jsx("span",{className:"text-sm font-medium flex-1 truncate",children:t.headline||t.run_id}),t.final_verdict&&e.jsx("span",{className:"text-xs font-mono uppercase tracking-wider flex-shrink-0 text-muted",children:t.final_verdict}),e.jsx("span",{className:"text-muted text-xs flex-shrink-0",children:n?"−":"+"})]}),n&&e.jsxs("div",{className:"px-3 pb-3 space-y-3 border-t border-current/10 pt-3",children:[l&&e.jsxs("p",{className:"text-xs text-danger",children:["Could not load this receipt: ",l]}),!l&&!s&&e.jsx("p",{className:"text-xs text-muted",children:"Loading receipt…"}),s&&e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx(o,{label:"Run",value:s.run_id}),e.jsx(o,{label:"Generated",value:v}),e.jsx(o,{label:"Loki version",value:s.loki_version||"unknown"}),e.jsx(o,{label:"Cost",value:typeof s.cost_usd=="number"?`$${s.cost_usd.toFixed(2)}`:"unknown"}),e.jsx(o,{label:"Files changed",value:typeof s.files_changed=="number"?String(s.files_changed):"unknown"}),((m=s.verification)==null?void 0:m.hash)&&e.jsx(o,{label:"Integrity hash",value:s.verification.hash})]}),e.jsxs("div",{className:`rounded-card border px-3 py-2 ${r.badge}`,children:[e.jsx("p",{className:"text-xs font-semibold uppercase tracking-wider mb-1",children:r.label}),e.jsxs("p",{className:"text-xs mb-1",children:[e.jsx("span",{className:"font-semibold",children:"Proven: "}),r.proven]}),e.jsxs("p",{className:"text-xs opacity-80",children:[e.jsx("span",{className:"font-semibold",children:"Not proven: "}),r.notProven]})]}),(((p=s.verification)==null?void 0:p.attestation)||((g=s.verification)==null?void 0:g.gpg_signature))&&e.jsxs("p",{className:"text-xs text-muted",children:["Verify it yourself:"," ",e.jsxs("code",{className:"font-mono",children:["loki proof verify ",s.run_id,(f=s.verification)!=null&&f.attestation?" --jwks <url|file>":""]})]}),t.pr_url&&e.jsx("a",{href:t.pr_url,target:"_blank",rel:"noreferrer noopener",className:"text-xs text-primary underline",children:"View the pull request"})]})]})]})}function _(){const[t,n]=d.useState(null),[a,s]=d.useState(null),[i,l]=d.useState(null);return d.useEffect(()=>{let c=!1;return x.listProofs().then(r=>{c||n(r.proofs||[])}).catch(r=>{c||l((r==null?void 0:r.message)||"could not load receipts")}),x.proofsSummary().then(r=>{c||s(r)}).catch(()=>{}),()=>{c=!0}},[]),e.jsxs("section",{className:"space-y-3",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-sm font-semibold",children:"Evidence Receipts"}),e.jsx("p",{className:"text-muted text-xs mt-1",children:"What each build actually checked, recorded so you can verify it yourself."})]}),i&&e.jsxs("p",{className:"text-xs text-danger",children:["Could not load receipts: ",i]}),!i&&t===null&&e.jsx("p",{className:"text-xs text-muted",children:"Loading…"}),a&&e.jsx(N,{summary:a}),t!==null&&t.length===0&&e.jsxs("p",{className:"text-xs text-muted",children:["No receipts yet. One is written at the end of each run (set ",e.jsx("code",{className:"font-mono",children:"LOKI_PROOF=0"})," to opt out)."]}),e.jsx("div",{className:"space-y-2",children:t==null?void 0:t.map(c=>e.jsx(y,{proof:c},c.run_id))})]})}export{_ as E};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as M,r as o,a as C,j as e,n as P,J as w,N as f,a2 as B,M as R,m as A,E as H}from"./index-
|
|
1
|
+
import{c as M,r as o,a as C,j as e,n as P,J as w,N as f,a2 as B,M as R,m as A,E as H}from"./index-BXA3UT6P.js";import{B as v}from"./Button-CikxmqSs.js";import{C as k,G as q}from"./ProjectPage-iP6dJihg.js";import{R as F}from"./refresh-cw-Dt42kYtM.js";import{C as S}from"./circle-alert-DwT82lJx.js";import{U as D}from"./user-Cg7fbzdh.js";import{R as T}from"./rotate-ccw-BCR4Tp9u.js";import{L as U}from"./loader-circle-KgjeHEcV.js";import"./TerminalOutput-DBrdT_Fp.js";import"./lock-BerKqno7.js";import"./package-DLPKWAvW.js";import"./file-code-corner-DMopmhu0.js";import"./file-plus-DU_hT_3w.js";import"./clock-BBlUa93G.js";import"./activity-AbfFz38r.js";import"./bot-DQ4np_3n.js";import"./check-RWuguCwE.js";import"./hammer-BWaLBvSA.js";import"./dollar-sign-BZagljek.js";import"./globe-mthCRtfp.js";import"./copy-DMHj52B8.js";import"./git-commit-horizontal-zFk_IobS.js";import"./upload-C54rjN8K.js";import"./plus-NMxxwHMn.js";import"./trash-2-D0IGudH7.js";import"./trending-down-BbFFBq-v.js";import"./save-sR4MJd42.js";import"./code-xml-r6qqION5.js";import"./trending-up-DDrJKL5i.js";import"./layers-Q1LXV5fy.js";import"./folder-open-BP5TtQAW.js";import"./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 I,r as c,a as b,j as e,G as xe,A as me,S as Z,l as ee,n as ue,i as pe,b as he,X as te,M as fe}from"./index-
|
|
1
|
+
import{c as I,r as c,a as b,j as e,G as xe,A as me,S as Z,l as ee,n as ue,i as pe,b as he,X as te,M as fe}from"./index-BXA3UT6P.js";import{B as y}from"./Button-CikxmqSs.js";import{C as be}from"./chevron-left-VpbQPOkC.js";import{G as P,L as V,S as ge}from"./ProjectPage-iP6dJihg.js";import{R as L}from"./refresh-cw-Dt42kYtM.js";import{C as Ne}from"./circle-alert-DwT82lJx.js";import{S as se}from"./shield-alert-Bvgp6_Dz.js";import{F as je}from"./file-code-corner-DMopmhu0.js";import{C as H}from"./clock-BBlUa93G.js";import{C as re}from"./check-RWuguCwE.js";import"./TerminalOutput-DBrdT_Fp.js";import"./lock-BerKqno7.js";import"./package-DLPKWAvW.js";import"./file-plus-DU_hT_3w.js";import"./activity-AbfFz38r.js";import"./bot-DQ4np_3n.js";import"./loader-circle-KgjeHEcV.js";import"./hammer-BWaLBvSA.js";import"./dollar-sign-BZagljek.js";import"./globe-mthCRtfp.js";import"./copy-DMHj52B8.js";import"./git-commit-horizontal-zFk_IobS.js";import"./upload-C54rjN8K.js";import"./plus-NMxxwHMn.js";import"./trash-2-D0IGudH7.js";import"./rotate-ccw-BCR4Tp9u.js";import"./trending-down-BbFFBq-v.js";import"./save-sR4MJd42.js";import"./code-xml-r6qqION5.js";import"./trending-up-DDrJKL5i.js";import"./layers-Q1LXV5fy.js";import"./folder-open-BP5TtQAW.js";import"./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.
|