loki-mode 11.3.1 → 11.3.4

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 (107) hide show
  1. package/SKILL.md +4 -4
  2. package/VERSION +1 -1
  3. package/autonomy/lib/efficiency_cost.py +8 -0
  4. package/autonomy/lib/engine10-push.sh +22 -8
  5. package/autonomy/loki +69 -30
  6. package/bin/loki +30 -1
  7. package/completions/_loki +22 -0
  8. package/completions/loki.bash +17 -3
  9. package/dashboard/__init__.py +1 -1
  10. package/dashboard/server.py +22 -14
  11. package/docs/AGENT-CHANGE-RECEIPT.md +2 -2
  12. package/docs/CLI-REFERENCE.md +5 -1
  13. package/docs/INSTALLATION.md +1 -1
  14. package/docs/OTEL-COLLECTOR.md +24 -0
  15. package/docs/ROUTING-EVIDENCE.md +32 -0
  16. package/docs/environment-variables.md +1 -0
  17. package/docs/spec-first-intent.md +28 -0
  18. package/docs/v10/CLI-MODERN.md +6 -3
  19. package/docs/v10/DECISIONS.md +20 -0
  20. package/docs/v10/FAILURE-CLASSES.md +263 -1
  21. package/docs/v10/GUARDS.md +18 -0
  22. package/docs/v10/GUIDE.md +7 -5
  23. package/docs/v10/METRICS.md +12 -0
  24. package/docs/v10/OPERATING-MODEL.md +3 -3
  25. package/docs/v10/PROGRESS.md +12 -0
  26. package/docs/v11/INTOTO-AGENT-CHANGE-PREDICATE.md +77 -0
  27. package/docs/v11/MCP-2026-07-28.md +55 -0
  28. package/docs/v11/VERIFY-PR-SANDBOX.md +41 -0
  29. package/loki-ts/dist/loki.js +1130 -969
  30. package/mcp/__init__.py +1 -1
  31. package/mcp/v10_tools.py +29 -0
  32. package/package.json +9 -11
  33. package/packages/control-plane/dist/ask-tools-server.js +8 -2
  34. package/packages/control-plane/dist/server.js +15752 -13978
  35. package/packages/control-plane/drizzle/0006_token_sessions.sql +1 -0
  36. package/packages/control-plane/drizzle/meta/0006_snapshot.json +807 -0
  37. package/packages/control-plane/drizzle/meta/_journal.json +7 -0
  38. package/packages/control-plane/src/db/schema.ts +1 -0
  39. package/packages/control-plane/src/server/routes/cost.ts +4 -3
  40. package/packages/control-plane/src/server/runs.ts +3 -3
  41. package/packages/control-plane/ui/dist/assets/index-DbLzr87u.js +190 -0
  42. package/packages/control-plane/ui/dist/index.html +1 -1
  43. package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
  44. package/providers/opencode.sh +2 -3
  45. package/src/observability/genai-spans.js +104 -0
  46. package/src/observability/otel-bridge.js +49 -1
  47. package/src/observability/otel.js +2 -2
  48. package/src/protocols/mcp-server.js +180 -5
  49. package/src/protocols/transport/sse.js +23 -2
  50. package/web-app/dist/assets/{AdminPage-CMkHNJbF.js → AdminPage-CKj0pDt0.js} +1 -1
  51. package/web-app/dist/assets/{Avatar-BJyjV7Zi.js → Avatar-BOV__pMM.js} +1 -1
  52. package/web-app/dist/assets/{Badge-D572l0uq.js → Badge-4a6mAYjU.js} +1 -1
  53. package/web-app/dist/assets/{Button-T4BnOZgl.js → Button-B3GM7oxm.js} +1 -1
  54. package/web-app/dist/assets/{CockpitPage-Cj_GFsNn.js → CockpitPage-Cwge3_kH.js} +1 -1
  55. package/web-app/dist/assets/{ComparePage-A8m4ZXak.js → ComparePage-CBQUGKsm.js} +1 -1
  56. package/web-app/dist/assets/{ErrorBoundary-hugCg5yg.js → ErrorBoundary-TM7lDjUJ.js} +1 -1
  57. package/web-app/dist/assets/{EvidenceReceiptPanel-CuJKDC2G.js → EvidenceReceiptPanel-DUOseTYA.js} +1 -1
  58. package/web-app/dist/assets/{GitHubIssuesPanel-BYGKjarC.js → GitHubIssuesPanel-hK_uML6T.js} +1 -1
  59. package/web-app/dist/assets/{GitHubPRsPanel-c7sFIJlf.js → GitHubPRsPanel-Bt0mug-u.js} +1 -1
  60. package/web-app/dist/assets/{HomePage-A1MIkG2P.js → HomePage-DcOogAai.js} +2 -2
  61. package/web-app/dist/assets/{LoginPage-8KQMRhtR.js → LoginPage-D41pjjhF.js} +1 -1
  62. package/web-app/dist/assets/{MagicPage-C05QW2Lx.js → MagicPage-iDcdAcL2.js} +1 -1
  63. package/web-app/dist/assets/{MetricsPage-Oukv5hme.js → MetricsPage-Tt4sfFD1.js} +1 -1
  64. package/web-app/dist/assets/{NotFoundPage-DKIwsawz.js → NotFoundPage-DPQMausn.js} +1 -1
  65. package/web-app/dist/assets/{ProjectPage-DwFRPLCG.js → ProjectPage-DdMz4frY.js} +3 -3
  66. package/web-app/dist/assets/{ProjectsPage-CgY4YYDX.js → ProjectsPage-Be0v4ace.js} +1 -1
  67. package/web-app/dist/assets/{SettingsPage-BpfcSjUA.js → SettingsPage-90yIh-mE.js} +1 -1
  68. package/web-app/dist/assets/{ShowcasePage-BqBakC4K.js → ShowcasePage-D5x5eczl.js} +1 -1
  69. package/web-app/dist/assets/{SystemSettingsPage-CZLu5VB0.js → SystemSettingsPage-UWeWT8KX.js} +1 -1
  70. package/web-app/dist/assets/{TeamsPage-D55S1jKz.js → TeamsPage-BtofFlme.js} +1 -1
  71. package/web-app/dist/assets/{TemplatesPage-D3SydvxN.js → TemplatesPage-DwB8Va2Z.js} +1 -1
  72. package/web-app/dist/assets/{TerminalOutput-hNg8G1-e.js → TerminalOutput-CGIgTmby.js} +1 -1
  73. package/web-app/dist/assets/{activity-D2C0VagO.js → activity-BZ77CO72.js} +1 -1
  74. package/web-app/dist/assets/{bell-CMvYi36w.js → bell-Bzpqv6un.js} +1 -1
  75. package/web-app/dist/assets/{bot-BvtQ6lmo.js → bot-yXo1B_-f.js} +1 -1
  76. package/web-app/dist/assets/{check-B13xkXed.js → check-BSxGjsta.js} +1 -1
  77. package/web-app/dist/assets/{chevron-left-emu6r49E.js → chevron-left-CqTRVUB5.js} +1 -1
  78. package/web-app/dist/assets/{circle-alert-CExlwjuy.js → circle-alert-88gdjY71.js} +1 -1
  79. package/web-app/dist/assets/{clock-D7Uas3oL.js → clock-XdFXoaJi.js} +1 -1
  80. package/web-app/dist/assets/{cloud-CkB4S86C.js → cloud-Tx1sJrXz.js} +1 -1
  81. package/web-app/dist/assets/{code-xml-SL9IIWIn.js → code-xml-DaH9CKWy.js} +1 -1
  82. package/web-app/dist/assets/{database-BQFkd7t1.js → database-BpvOmzij.js} +1 -1
  83. package/web-app/dist/assets/{dollar-sign-qHmnNYsU.js → dollar-sign-VC2UX2UE.js} +1 -1
  84. package/web-app/dist/assets/{file-code-corner-C2YUDP0K.js → file-code-corner--TF3VpoT.js} +1 -1
  85. package/web-app/dist/assets/{file-plus-C_ooIkDe.js → file-plus-DZpwU9pY.js} +1 -1
  86. package/web-app/dist/assets/{globe-CQ-GL485.js → globe-tbpeHRPV.js} +1 -1
  87. package/web-app/dist/assets/{hammer-U05L_D7V.js → hammer-CKLXn4vL.js} +1 -1
  88. package/web-app/dist/assets/{index-CooHd7I-.js → index-DnCsWpCh.js} +3 -3
  89. package/web-app/dist/assets/{layers-Buo17rjW.js → layers-B0fkGsk8.js} +1 -1
  90. package/web-app/dist/assets/{loader-circle-15vK3oV1.js → loader-circle-czscMzmQ.js} +1 -1
  91. package/web-app/dist/assets/{lock-BeXLebuJ.js → lock-CTtO6vc8.js} +1 -1
  92. package/web-app/dist/assets/{package-MVh_6-6R.js → package-DZeSY0-g.js} +1 -1
  93. package/web-app/dist/assets/{plus-Dh_nd9vS.js → plus-mE12CJVN.js} +1 -1
  94. package/web-app/dist/assets/{refresh-cw-C9cBJEAT.js → refresh-cw-9DSznasU.js} +1 -1
  95. package/web-app/dist/assets/{rotate-ccw-D3PAtzST.js → rotate-ccw-DcHI0M43.js} +1 -1
  96. package/web-app/dist/assets/{scroll-text-DZCoXQjo.js → scroll-text-BWLthSw-.js} +1 -1
  97. package/web-app/dist/assets/{server-OCOLI5J_.js → server-_I247LrS.js} +1 -1
  98. package/web-app/dist/assets/{shield-alert-oVhmf1_h.js → shield-alert-XRJYD_6c.js} +1 -1
  99. package/web-app/dist/assets/{thumbs-up-B-kbX4vN.js → thumbs-up-CxuTD3eO.js} +1 -1
  100. package/web-app/dist/assets/{trash-2-EL6moveW.js → trash-2-DuGxuVt8.js} +1 -1
  101. package/web-app/dist/assets/{trending-down-Cl3kjpsx.js → trending-down-Cg_zGt__.js} +1 -1
  102. package/web-app/dist/assets/{trending-up-DST7I6M2.js → trending-up-DpUJzIrV.js} +1 -1
  103. package/web-app/dist/assets/{upload-CUOT780G.js → upload-Db7YZ4zR.js} +1 -1
  104. package/web-app/dist/assets/{usePolling-Djmt90eb.js → usePolling-BGRYm-vq.js} +1 -1
  105. package/web-app/dist/assets/{user-BRBohA6J.js → user-CxHrowEP.js} +1 -1
  106. package/web-app/dist/index.html +1 -1
  107. package/packages/control-plane/ui/dist/assets/index-DcBJXjCJ.js +0 -190
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Loki Mode</title>
7
- <script type="module" crossorigin src="./assets/index-DcBJXjCJ.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-DbLzr87u.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="./assets/index-CxZWgwie.css">
9
9
  </head>
10
10
  <body>
@@ -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": "11.3.1",
5
+ "version": "11.3.4",
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",
@@ -21,9 +21,8 @@
21
21
  # v0.86.0 from 2025-08-09, and Roo Code is archived, Continue read-only --
22
22
  # so opencode, not aider, is the durable cheap-model route.)
23
23
  #
24
- # Cost context (SWE-bench uniform scaffold, mini-swe-agent v2.0.0, 2026-02-17):
25
- # MiniMax M2.5 resolved 75.8 at $36.64 total vs Claude Opus 4.6 at 75.6 for
26
- # $275.76 -- an open model matching frontier accuracy at ~7.5x lower cost.
24
+ # Cost context: see docs/ROUTING-EVIDENCE.md (measurement source, date and
25
+ # caveats for the MiniMax M2.5 vs Claude Opus 4.6 cost-parity figures).
27
26
  #
28
27
  # CLI SURFACE (verified against the installed binary, v1.17.9, not assumed):
29
28
  # opencode run [message..] non-interactive run
@@ -0,0 +1,104 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Pure mapper: engine10 events.jsonl envelopes -> gen_ai span descriptors.
5
+ * No I/O, no network, no clock. Attributes are built from a fixed whitelist of
6
+ * event fields, so prompt or completion content and secrets can never reach a span.
7
+ */
8
+
9
+ // OpenTelemetry GenAI semantic conventions version these attribute names follow.
10
+ const GENAI_SEMCONV_VERSION = '1.37.0';
11
+
12
+ const AGENT_NAME = 'loki';
13
+
14
+ function toNs(ts) {
15
+ const ms = Date.parse(ts);
16
+ return Number.isFinite(ms) ? BigInt(ms) * 1000000n : null;
17
+ }
18
+
19
+ function isNum(x) {
20
+ return typeof x === 'number' && Number.isFinite(x);
21
+ }
22
+
23
+ // Unknown usage is absent, never 0 (mirrors engine10/cost.ts): an unmetered or
24
+ // unmeasured cost event, or an all-zero usage record, carries no token attributes.
25
+ function usageAttrs(d) {
26
+ if (d.source === 'cli-invoker-unmetered' || d.source === 'not measured') return {};
27
+ if (!isNum(d.input_tokens) || !isNum(d.output_tokens)) return {};
28
+ if (d.input_tokens === 0 && d.output_tokens === 0) return {};
29
+ return { 'gen_ai.usage.input_tokens': d.input_tokens, 'gen_ai.usage.output_tokens': d.output_tokens };
30
+ }
31
+
32
+ /**
33
+ * @param {Array<{seq:number,ts:string,run:string,type:string,stage:string|null,data:object}>} events
34
+ * @returns {Array<{key:string,parentKey:string|null,name:string,startNs:bigint,endNs:bigint,status:'ok'|'error'|'unset',attributes:object}>}
35
+ */
36
+ function mapRunEvents(events) {
37
+ const list = (Array.isArray(events) ? events : []).filter((e) => e && typeof e.type === 'string' && toNs(e.ts) !== null);
38
+ if (list.length === 0) return [];
39
+ const started = list.find((e) => e.type === 'run.started');
40
+ const root = started || list[0];
41
+ const last = list[list.length - 1];
42
+ const done = list.find((e) => e.type === 'run.completed');
43
+ const provider = started && typeof started.data.provider === 'string' ? started.data.provider : null;
44
+ const runModel = started && typeof started.data.model === 'string' ? started.data.model : null;
45
+
46
+ const base = { 'gen_ai.agent.name': AGENT_NAME };
47
+ if (provider) base['gen_ai.provider.name'] = provider;
48
+ const rootKey = 'run:' + root.run;
49
+ const spans = [{
50
+ key: rootKey, parentKey: null, name: 'invoke_agent ' + AGENT_NAME,
51
+ startNs: toNs(root.ts), endNs: toNs((done || last).ts),
52
+ status: done ? (done.data.verdict === 'FAILED' ? 'error' : 'ok') : 'unset',
53
+ attributes: Object.assign({ 'gen_ai.operation.name': 'invoke_agent' }, base, runModel ? { 'gen_ai.request.model': runModel } : {}),
54
+ }];
55
+
56
+ const open = new Map();
57
+ const costs = [];
58
+ for (const e of list) {
59
+ const d = e.data || {};
60
+ if (e.type === 'stage.started' && e.stage) {
61
+ open.set(e.stage, e);
62
+ } else if ((e.type === 'stage.completed' || e.type === 'stage.failed' || e.type === 'stage.skipped') && e.stage) {
63
+ const s = open.get(e.stage);
64
+ open.delete(e.stage);
65
+ spans.push({
66
+ key: 'stage:' + e.stage, parentKey: rootKey, name: 'stage ' + e.stage,
67
+ startNs: toNs((s || e).ts), endNs: toNs(e.ts), status: e.type === 'stage.failed' ? 'error' : 'ok',
68
+ attributes: { 'gen_ai.operation.name': 'invoke_agent', 'loki.stage': e.stage },
69
+ });
70
+ } else if (e.type === 'cost') {
71
+ costs.push(e);
72
+ } else if (e.type.indexOf('tool.') === 0 && typeof d.name === 'string' && d.name) {
73
+ const t = toNs(e.ts);
74
+ spans.push({
75
+ key: 'tool:' + e.seq, parentKey: e.stage ? 'stage:' + e.stage : rootKey, name: 'execute_tool ' + d.name,
76
+ startNs: t, endNs: t, status: e.type === 'tool.failed' ? 'error' : 'ok',
77
+ attributes: { 'gen_ai.operation.name': 'execute_tool', 'gen_ai.tool.name': d.name },
78
+ });
79
+ }
80
+ }
81
+ // Stages still open at the end of the log end at the last event.
82
+ for (const [stage, s] of open) {
83
+ spans.push({
84
+ key: 'stage:' + stage, parentKey: rootKey, name: 'stage ' + stage,
85
+ startNs: toNs(s.ts), endNs: toNs(last.ts), status: 'unset',
86
+ attributes: { 'gen_ai.operation.name': 'invoke_agent', 'loki.stage': stage },
87
+ });
88
+ }
89
+ for (const e of costs) {
90
+ const attrs = usageAttrs(e.data || {});
91
+ const stageSpan = spans.find((s) => s.key === 'stage:' + e.stage);
92
+ const targets = stageSpan ? [stageSpan, spans[0]] : [spans[0]];
93
+ for (const t of targets) {
94
+ if (t === spans[0] && typeof e.data.model === 'string' && e.data.model) t.attributes['gen_ai.request.model'] = e.data.model;
95
+ for (const [k, v] of Object.entries(attrs)) t.attributes[k] = (t.attributes[k] || 0) + v;
96
+ }
97
+ if (stageSpan && typeof e.data.model === 'string' && e.data.model) stageSpan.attributes['gen_ai.request.model'] = e.data.model;
98
+ }
99
+ // Parents before children so a replaying exporter can link them.
100
+ const rank = (x) => (x.parentKey === null ? 0 : x.key.indexOf('stage:') === 0 ? 1 : 2);
101
+ return spans.map((x, i) => ({ x, i })).sort((p, q) => rank(p.x) - rank(q.x) || p.i - q.i).map((o) => o.x);
102
+ }
103
+
104
+ module.exports = { GENAI_SEMCONV_VERSION, mapRunEvents };
@@ -195,6 +195,52 @@ function processEventFile(filepath, tracer, traceId, otelRef) {
195
195
  }
196
196
  }
197
197
 
198
+ // ---------------------------------------------------------------------------
199
+ // Engine10 gen_ai spans (LOKI_OTEL_GENAI=1): completed runs under .loki/runs/<id>/events.jsonl
200
+ // ---------------------------------------------------------------------------
201
+
202
+ const exportedRuns = new Set();
203
+
204
+ function genaiEnabled() {
205
+ return /^(1|true|yes|on)$/i.test(String(process.env.LOKI_OTEL_GENAI || '').trim());
206
+ }
207
+
208
+ /** Emit the span tree for one run's envelopes. Returns the number of spans ended. */
209
+ function exportEngine10Run(events, tracer, traceId, otelRef, rootParentSpanId) {
210
+ const spans = require('./genai-spans').mapRunEvents(events);
211
+ const live = new Map();
212
+ for (const d of spans) {
213
+ const parent = d.parentKey ? live.get(d.parentKey) : null;
214
+ const span = tracer.startSpan(d.name, { traceId: traceId, parentSpanId: parent ? parent.spanId : (d.parentKey ? undefined : rootParentSpanId), attributes: Object.assign({}, d.attributes) });
215
+ span.startTimeUnixNano = String(d.startNs);
216
+ live.set(d.key, span);
217
+ if (d.status === 'ok') span.setStatus(otelRef.SpanStatusCode.OK);
218
+ else if (d.status === 'error') span.setStatus(otelRef.SpanStatusCode.ERROR);
219
+ span.end(d.endNs);
220
+ }
221
+ return spans.length;
222
+ }
223
+
224
+ function scanEngine10Runs(tracer, traceId, otelRef) {
225
+ if (!genaiEnabled()) return;
226
+ const runsDir = path.join(process.cwd(), lokiDir, 'runs');
227
+ try {
228
+ for (const id of fs.readdirSync(runsDir)) {
229
+ if (exportedRuns.has(id)) continue;
230
+ const f = path.join(runsDir, id, 'events.jsonl');
231
+ if (!fs.existsSync(f)) continue;
232
+ const events = [];
233
+ for (const line of fs.readFileSync(f, 'utf8').split('\n')) {
234
+ try { if (line.trim()) events.push(JSON.parse(line)); } catch (e) { /* torn line */ }
235
+ }
236
+ if (!events.some((e) => e && e.type === 'run.completed')) continue; // export complete trees only
237
+ exportedRuns.add(id);
238
+ const envParent = /^[0-9a-f]{16}$/.test(process.env.LOKI_PARENT_SPAN_ID || '') ? process.env.LOKI_PARENT_SPAN_ID : undefined; // MCP-D: parent from an MCP task's traceparent
239
+ exportEngine10Run(events, tracer, traceId, otelRef, envParent);
240
+ }
241
+ } catch (e) { /* no runs dir */ }
242
+ }
243
+
198
244
  // ---------------------------------------------------------------------------
199
245
  // Polling loop
200
246
  // ---------------------------------------------------------------------------
@@ -229,7 +275,7 @@ function start() {
229
275
 
230
276
  // Bound versions that close over initialized dependencies
231
277
  const boundProcessEventFile = (filepath) => processEventFile(filepath, tracer, traceId, otelMod);
232
- const boundScanPendingEvents = () => scanPendingEvents(tracer, traceId, otelMod);
278
+ const boundScanPendingEvents = () => { scanPendingEvents(tracer, traceId, otelMod); scanEngine10Runs(tracer, traceId, otelMod); };
233
279
 
234
280
  pollInterval = setInterval(boundScanPendingEvents, POLL_INTERVAL_MS);
235
281
 
@@ -275,6 +321,8 @@ function start() {
275
321
  module.exports = {
276
322
  start,
277
323
  processEventFile,
324
+ exportEngine10Run,
325
+ scanEngine10Runs,
278
326
  activeSpans,
279
327
  _getLastProcessedFile: function() { return lastProcessedFile; },
280
328
  _resetState: function() { lastProcessedFile = ''; },
@@ -95,10 +95,10 @@ class Span {
95
95
  return this;
96
96
  }
97
97
 
98
- end() {
98
+ end(endNanos) {
99
99
  if (this._ended) return;
100
100
  this._ended = true;
101
- this.endTimeUnixNano = nowNanos();
101
+ this.endTimeUnixNano = endNanos !== undefined ? String(endNanos) : nowNanos(); // endNanos: replayed spans (gen_ai bridge) keep event time
102
102
  // Register with the exporter
103
103
  if (_activeExporter) {
104
104
  _activeExporter.addSpan(this);
@@ -20,6 +20,8 @@
20
20
 
21
21
  const path = require('path');
22
22
  const fs = require('fs');
23
+ const crypto = require('crypto');
24
+ const { spawn } = require('child_process');
23
25
 
24
26
  // ---------------------------------------------------------------------------
25
27
  // Lazy tool/resource loading
@@ -100,12 +102,151 @@ function getServerInfo() {
100
102
  // Oldest to newest; the last entry is the fallback offered to unknown clients.
101
103
  const SUPPORTED_PROTOCOL_VERSIONS = ['2024-11-05', '2025-03-26', '2025-06-18', '2025-11-25'];
102
104
 
105
+ // MCP-C: 2026-07-28 (stateless profile) is advertised only with LOKI_MCP_2026_07=1. It is never the initialize fallback,
106
+ // because that profile removes the handshake and a legacy client must keep getting a stateful version.
107
+ const STATELESS_VERSION = '2026-07-28';
108
+ const META_PROTOCOL_VERSION = 'io.modelcontextprotocol/protocolVersion';
109
+
110
+ function advertisedProtocolVersions() {
111
+ return process.env.LOKI_MCP_2026_07 === '1' ? SUPPORTED_PROTOCOL_VERSIONS.concat(STATELESS_VERSION) : SUPPORTED_PROTOCOL_VERSIONS.slice();
112
+ }
113
+
103
114
  function negotiateProtocolVersion(params) {
104
115
  const requested = params && typeof params.protocolVersion === 'string' ? params.protocolVersion : null;
105
- if (requested && SUPPORTED_PROTOCOL_VERSIONS.includes(requested)) return requested;
116
+ if (requested && advertisedProtocolVersions().includes(requested)) return requested;
106
117
  return SUPPORTED_PROTOCOL_VERSIONS[SUPPORTED_PROTOCOL_VERSIONS.length - 1];
107
118
  }
108
119
 
120
+ // ---------------------------------------------------------------------------
121
+ // MCP-D: Tasks extension (io.modelcontextprotocol/tasks), LOKI_MCP_TASKS=1
122
+ // ---------------------------------------------------------------------------
123
+ // A task wraps exactly two existing tools, loki_v10_verify and loki_v10_run, by running the read-only adapter in
124
+ // mcp/v10_tools.py (argument list, no shell). It never computes or edits a verdict: the task result is the adapter's
125
+ // stdout, byte for byte. Cancel kills only the child this server spawned, by its recorded pid. State is in memory.
126
+
127
+ const TASK_TOOLS = {
128
+ loki_v10_verify: { cmd: 'verify', keys: ['receipt_path', 'repo_path'], required: [['receipt_path', 'repo_path']] },
129
+ loki_v10_run: { cmd: 'run', keys: ['ref', 'repo_path'], required: [['ref'], ['repo_path']] }
130
+ };
131
+ const TASK_MAX = 64;
132
+ const TASK_TIMEOUT_MS = 300000;
133
+ const TRACEPARENT_RE = /^00-([0-9a-f]{32})-([0-9a-f]{16})-[0-9a-f]{2}$/;
134
+ // Same list the adapter's _env() drops for the direct tool (mcp/v10_tools.py _DROPPED_ENV): one mechanism, so a task
135
+ // sees the same environment as the direct call. MCP_AUTH_TOKEN is the server's own bearer and is never forwarded.
136
+ const TASK_DROPPED_ENV = ['LOKI_CONTROL_TOKEN', 'SLACK_BOT_TOKEN', 'SLACK_SIGNING_SECRET', 'SLACK_WEBHOOK_URL', 'MCP_AUTH_TOKEN'];
137
+
138
+ const tasks = new Map();
139
+ let _taskSpawner = null;
140
+
141
+ function tasksEnabled() {
142
+ return process.env.LOKI_MCP_TASKS === '1';
143
+ }
144
+
145
+ function _setTaskSpawnerForTests(fn) {
146
+ _taskSpawner = fn;
147
+ }
148
+
149
+ function _taskPidForTests(taskId) {
150
+ const t = tasks.get(taskId);
151
+ return t ? t.pid : undefined;
152
+ }
153
+
154
+ /** W3C traceparent -> { traceId, parentSpanId }, or null for anything malformed or all-zero. */
155
+ function parseTraceparent(value) {
156
+ if (typeof value !== 'string') return null;
157
+ const m = TRACEPARENT_RE.exec(value);
158
+ if (!m || /^0+$/.test(m[1]) || /^0+$/.test(m[2])) return null;
159
+ return { traceId: m[1], parentSpanId: m[2] };
160
+ }
161
+
162
+ function taskChildEnv(trace) {
163
+ const env = {};
164
+ for (const [k, v] of Object.entries(process.env)) {
165
+ if (typeof v === 'string' && !TASK_DROPPED_ENV.includes(k)) env[k] = v;
166
+ }
167
+ delete env.LOKI_PARENT_SPAN_ID;
168
+ env.LOKI_NO_BROWSER = '1';
169
+ if (trace) {
170
+ env.LOKI_TRACE_ID = trace.traceId;
171
+ env.LOKI_PARENT_SPAN_ID = trace.parentSpanId;
172
+ }
173
+ return env;
174
+ }
175
+
176
+ function taskArgs(tool, args) {
177
+ const spec = TASK_TOOLS[Object.prototype.hasOwnProperty.call(TASK_TOOLS, tool) ? tool : ''];
178
+ if (!spec) return { error: 'tool cannot run as a task: ' + String(tool) };
179
+ if (!args || typeof args !== 'object' || Array.isArray(args)) return { error: 'arguments must be an object' };
180
+ for (const k of Object.keys(args)) {
181
+ if (!spec.keys.includes(k)) return { error: 'unknown argument: ' + k };
182
+ }
183
+ for (const k of spec.keys) {
184
+ const v = args[k];
185
+ if (v === undefined) continue;
186
+ if (typeof v !== 'string' || v.length > 4096 || v.includes('\0') || v.startsWith('-')) return { error: 'invalid ' + k };
187
+ }
188
+ for (const anyOf of spec.required) {
189
+ if (!anyOf.some((k) => typeof args[k] === 'string' && args[k] !== '')) return { error: anyOf.join(' or ') + ' is required' };
190
+ }
191
+ return { spec, argv: spec.keys.map((k) => (typeof args[k] === 'string' ? args[k] : '')) };
192
+ }
193
+
194
+ function taskView(t) {
195
+ return { taskId: t.id, status: t.status, tool: t.tool };
196
+ }
197
+
198
+ function taskCreate(params, id) {
199
+ const p = params || {};
200
+ const a = taskArgs(p.tool, p.arguments);
201
+ if (a.error) return makeError(-32602, a.error, id);
202
+ const trace = parseTraceparent(p._meta && p._meta.traceparent);
203
+ while (tasks.size >= TASK_MAX) {
204
+ const old = Array.from(tasks.values()).find((t) => t.status !== 'working');
205
+ if (!old) return makeError(-32603, 'too many running tasks', id);
206
+ tasks.delete(old.id);
207
+ }
208
+ const adapter = path.resolve(__dirname, '..', '..', 'mcp', 'v10_tools.py');
209
+ const cmd = process.env.LOKI_PYTHON || 'python3';
210
+ const args = ['-I', adapter, a.spec.cmd].concat(a.argv);
211
+ const spawner = _taskSpawner || ((c, av, o) => spawn(c, av, { env: o.env, stdio: ['ignore', 'pipe', 'pipe'] }));
212
+ let child;
213
+ try {
214
+ child = spawner(cmd, args, { env: taskChildEnv(trace) });
215
+ } catch (err) {
216
+ return makeError(-32603, 'could not start task', id);
217
+ }
218
+ const t = { id: crypto.randomUUID(), tool: p.tool, status: 'working', pid: child.pid, stdout: '', text: null, child: child };
219
+ tasks.set(t.id, t);
220
+ if (child.stdout) child.stdout.on('data', (d) => { if (t.stdout.length < 1048576) t.stdout += d; });
221
+ const timer = setTimeout(() => { if (t.status === 'working') { t.status = 'failed'; t.text = 'task timed out'; try { child.kill('SIGTERM'); } catch (e) { /* gone */ } } }, TASK_TIMEOUT_MS);
222
+ timer.unref();
223
+ child.on('error', () => { clearTimeout(timer); if (t.status === 'working') { t.status = 'failed'; t.text = 'adapter could not run'; } });
224
+ child.on('close', (code) => {
225
+ clearTimeout(timer);
226
+ if (t.status !== 'working') return;
227
+ if (code === 0 && t.stdout.trim()) { t.status = 'completed'; t.text = t.stdout.replace(/\n$/, ''); } else { t.status = 'failed'; t.text = 'adapter exited ' + String(code); }
228
+ });
229
+ return makeResult({ task: taskView(t) }, id);
230
+ }
231
+
232
+ function taskDispatch(method, params, id) {
233
+ if (method === 'tasks/create') return taskCreate(params, id);
234
+ const t = params && typeof params.taskId === 'string' ? tasks.get(params.taskId) : undefined;
235
+ if (!t) return makeError(-32602, 'Unknown taskId', id);
236
+ if (method === 'tasks/get') return makeResult({ task: taskView(t) }, id);
237
+ if (method === 'tasks/result') {
238
+ if (t.status === 'working') return makeError(-32602, 'Task not finished', id);
239
+ if (t.status === 'cancelled') return makeError(-32602, 'Task was cancelled', id);
240
+ return makeResult({ content: [{ type: 'text', text: t.text }], isError: t.status === 'failed' }, id);
241
+ }
242
+ // tasks/cancel: signal only the child this server recorded
243
+ if (t.status === 'working') {
244
+ t.status = 'cancelled';
245
+ try { t.child.kill('SIGTERM'); } catch (e) { /* already gone */ }
246
+ }
247
+ return makeResult({ task: taskView(t) }, id);
248
+ }
249
+
109
250
  function getCapabilities() {
110
251
  return {
111
252
  tools: {},
@@ -117,7 +258,7 @@ function getCapabilities() {
117
258
  // JSON-RPC 2.0 request handler
118
259
  // ---------------------------------------------------------------------------
119
260
 
120
- function handleRequest(request) {
261
+ function handleRequest(request, ctx) {
121
262
  // Validate basic JSON-RPC structure
122
263
  if (!request || typeof request !== 'object') {
123
264
  return makeError(-32600, 'Invalid request', null);
@@ -137,10 +278,17 @@ function handleRequest(request) {
137
278
  const isNotification = id === undefined || id === null;
138
279
 
139
280
  // Auth check for tool/resource calls
140
- if (method === 'tools/call' || method === 'resources/read') {
281
+ if (method === 'tools/call' || method === 'resources/read' || (tasksEnabled() && method.startsWith('tasks/'))) {
141
282
  const auth = getAuth();
283
+ const overHttp = !!(ctx && ctx.transport === 'http') && method.startsWith('tasks/');
284
+ // Tasks spawn processes: over HTTP a token is mandatory even when general auth is off (fail closed).
285
+ if (overHttp && !auth.enabled) {
286
+ if (isNotification) return null;
287
+ return makeError(-32001, 'Tasks over HTTP require an auth token (set MCP_AUTH_TOKEN)', id);
288
+ }
142
289
  if (auth.enabled) {
143
- const validation = auth.validate(request);
290
+ let validation = auth.validate(request);
291
+ if (overHttp && !validation.valid && ctx.authorization) validation = auth.validateHeader(ctx.authorization);
144
292
  if (!validation.valid) {
145
293
  if (isNotification) return null;
146
294
  return makeError(-32001, validation.error || 'Unauthorized', id);
@@ -148,6 +296,15 @@ function handleRequest(request) {
148
296
  }
149
297
  }
150
298
 
299
+ // Stateless profile: a request may carry its protocol version in _meta. No session is created or stored.
300
+ const metaVersion = params && params._meta && typeof params._meta === 'object' ? params._meta[META_PROTOCOL_VERSION] : undefined;
301
+ if (metaVersion !== undefined && !advertisedProtocolVersions().includes(metaVersion)) {
302
+ if (isNotification) return null;
303
+ const err = makeError(-32022, 'Unsupported protocol version: ' + String(metaVersion), id);
304
+ err.error.data = { supported: advertisedProtocolVersions() };
305
+ return err;
306
+ }
307
+
151
308
  let result;
152
309
 
153
310
  switch (method) {
@@ -159,6 +316,24 @@ function handleRequest(request) {
159
316
  };
160
317
  break;
161
318
 
319
+ case 'tasks/create':
320
+ case 'tasks/get':
321
+ case 'tasks/result':
322
+ case 'tasks/cancel':
323
+ if (!tasksEnabled()) {
324
+ if (isNotification) return null;
325
+ return makeError(-32601, 'Method not found: ' + method, id);
326
+ }
327
+ return taskDispatch(method, params, id);
328
+
329
+ case 'server/discover':
330
+ result = {
331
+ supportedVersions: advertisedProtocolVersions(),
332
+ serverInfo: getServerInfo(),
333
+ capabilities: getCapabilities()
334
+ };
335
+ break;
336
+
162
337
  case 'initialized':
163
338
  case 'notifications/initialized':
164
339
  // Client acknowledgment -- no response needed
@@ -369,7 +544,7 @@ function main() {
369
544
  }
370
545
 
371
546
  // Export for testing
372
- module.exports = { SUPPORTED_PROTOCOL_VERSIONS, handleRequest, getTools, getResources, getAuth, getServerInfo, main };
547
+ module.exports = { parseTraceparent, _setTaskSpawnerForTests, _taskPidForTests, SUPPORTED_PROTOCOL_VERSIONS, handleRequest, getTools, getResources, getAuth, getServerInfo, main };
373
548
 
374
549
  // Run if executed directly
375
550
  if (require.main === module) {
@@ -14,6 +14,12 @@ const http = require('http');
14
14
  // Maximum allowed POST body size (10 MB)
15
15
  const MAX_BODY_BYTES = 10 * 1024 * 1024;
16
16
 
17
+ const LOOPBACK_ORIGIN_RE = /^https?:\/\/(localhost|127\.0\.0\.1|\[::1\])(:\d+)?$/i;
18
+
19
+ function _isTaskMethod(r) {
20
+ return !!r && typeof r === 'object' && typeof r.method === 'string' && r.method.startsWith('tasks/');
21
+ }
22
+
17
23
  class SSETransport {
18
24
  constructor(handler, options) {
19
25
  this._handler = handler;
@@ -151,9 +157,24 @@ class SSETransport {
151
157
  return;
152
158
  }
153
159
 
160
+ // Task methods spawn processes. A text/plain cross-origin POST is a CORS simple request (no preflight), so
161
+ // require a JSON Content-Type and a loopback (or absent) Origin before the handler ever sees them.
162
+ const ctx = { transport: 'http', authorization: req.headers.authorization };
163
+ const touchesTasks = Array.isArray(request) ? request.some(_isTaskMethod) : _isTaskMethod(request);
164
+ if (touchesTasks) {
165
+ const ctype = String(req.headers['content-type'] || '').split(';')[0].trim().toLowerCase();
166
+ const origin = req.headers.origin;
167
+ const reject = (status, message) => {
168
+ res.writeHead(status, { 'Content-Type': 'application/json' });
169
+ res.end(JSON.stringify({ jsonrpc: '2.0', error: { code: -32600, message: message }, id: null }));
170
+ };
171
+ if (ctype !== 'application/json') { reject(415, 'Task requests require Content-Type: application/json'); return; }
172
+ if (origin !== undefined && !LOOPBACK_ORIGIN_RE.test(String(origin))) { reject(403, 'Task requests require a loopback Origin'); return; }
173
+ }
174
+
154
175
  // Handle batch
155
176
  if (Array.isArray(request)) {
156
- const promises = request.map((r) => Promise.resolve().then(() => this._handler(r)));
177
+ const promises = request.map((r) => Promise.resolve().then(() => this._handler(r, ctx)));
157
178
  Promise.all(promises).then((results) => {
158
179
  const responses = results.filter((r) => r !== null);
159
180
  res.writeHead(200, { 'Content-Type': 'application/json' });
@@ -171,7 +192,7 @@ class SSETransport {
171
192
  return;
172
193
  }
173
194
 
174
- Promise.resolve().then(() => this._handler(request)).then((response) => {
195
+ Promise.resolve().then(() => this._handler(request, ctx)).then((response) => {
175
196
  res.writeHead(200, { 'Content-Type': 'application/json' });
176
197
  res.end(JSON.stringify(response));
177
198
  }).catch(() => {
@@ -1,4 +1,4 @@
1
- import{c as L,r as x,j as e,w as re,Z as ne,m as le,h as ie,p as P,F as ye,U as I,X as ce,L as de,x as oe,M as Ae,T as Z,S as we,l as xe,k as me,y as Ce,E as Fe,a0 as Se,a as U,a2 as he}from"./index-CooHd7I-.js";import{B as C}from"./Button-T4BnOZgl.js";import{T as $e}from"./trending-up-DST7I6M2.js";import{C as z}from"./clock-D7Uas3oL.js";import{A as Be,U as ue,M as De}from"./Avatar-BJyjV7Zi.js";import{A as J}from"./activity-D2C0VagO.js";import{R as pe}from"./refresh-cw-C9cBJEAT.js";import{G as ze}from"./globe-CQ-GL485.js";import{C as Le}from"./chevron-left-emu6r49E.js";import{C as je}from"./check-B13xkXed.js";import{D as Y}from"./dollar-sign-qHmnNYsU.js";import{U as Te}from"./user-BRBohA6J.js";import{D as Me}from"./database-BQFkd7t1.js";import{L as Ue}from"./lock-BeXLebuJ.js";import{S as Re}from"./scroll-text-DZCoXQjo.js";import{S as Ie}from"./shield-alert-oVhmf1_h.js";/**
1
+ import{c as L,r as x,j as e,w as re,Z as ne,m as le,h as ie,p as P,F as ye,U as I,X as ce,L as de,x as oe,M as Ae,T as Z,S as we,l as xe,k as me,y as Ce,E as Fe,a0 as Se,a as U,a2 as he}from"./index-DnCsWpCh.js";import{B as C}from"./Button-B3GM7oxm.js";import{T as $e}from"./trending-up-DpUJzIrV.js";import{C as z}from"./clock-XdFXoaJi.js";import{A as Be,U as ue,M as De}from"./Avatar-BOV__pMM.js";import{A as J}from"./activity-BZ77CO72.js";import{R as pe}from"./refresh-cw-9DSznasU.js";import{G as ze}from"./globe-tbpeHRPV.js";import{C as Le}from"./chevron-left-CqTRVUB5.js";import{C as je}from"./check-BSxGjsta.js";import{D as Y}from"./dollar-sign-VC2UX2UE.js";import{U as Te}from"./user-CxHrowEP.js";import{D as Me}from"./database-BpvOmzij.js";import{L as Ue}from"./lock-CTtO6vc8.js";import{S as Re}from"./scroll-text-BWLthSw-.js";import{S as Ie}from"./shield-alert-XRJYD_6c.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 l,j as s}from"./index-CooHd7I-.js";/**
1
+ import{c as l,j as s}from"./index-DnCsWpCh.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-CooHd7I-.js";import{C as r}from"./clock-D7Uas3oL.js";import{C as a}from"./circle-alert-CExlwjuy.js";import{C as l}from"./check-B13xkXed.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-DnCsWpCh.js";import{C as r}from"./clock-XdFXoaJi.js";import{C as a}from"./circle-alert-88gdjY71.js";import{C as l}from"./check-BSxGjsta.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-CooHd7I-.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-DnCsWpCh.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,4 +1,4 @@
1
- import{c as $,r as l,j as e,G as we,Q as Se,F as Ce,h as Re,T as U,Z as Ee,a as S,V as Me,S as $e,Y as Te,X as Pe,P as ie,i as Ae,g as ze,C as Ie,l as Le,N as I,J as _e,K as De,O as Fe,u as Oe}from"./index-CooHd7I-.js";import{P as K,S as He,E as Be}from"./ErrorBoundary-hugCg5yg.js";import{C as qe,F as Ge,M as Ue,H as Ke,a as We,G as Ve,T as Qe}from"./thumbs-up-B-kbX4vN.js";import{C as oe}from"./check-B13xkXed.js";import{C as Ye}from"./clock-D7Uas3oL.js";import{D as Xe}from"./dollar-sign-qHmnNYsU.js";import{U as Ze}from"./upload-CUOT780G.js";import{L as V}from"./loader-circle-15vK3oV1.js";/**
1
+ import{c as $,r as l,j as e,G as we,Q as Se,F as Ce,h as Re,T as U,Z as Ee,a as S,V as Me,S as $e,Y as Te,X as Pe,P as ie,i as Ae,g as ze,C as Ie,l as Le,N as I,J as _e,K as De,O as Fe,u as Oe}from"./index-DnCsWpCh.js";import{P as K,S as He,E as Be}from"./ErrorBoundary-TM7lDjUJ.js";import{C as qe,F as Ge,M as Ue,H as Ke,a as We,G as Ve,T as Qe}from"./thumbs-up-CxuTD3eO.js";import{C as oe}from"./check-BSxGjsta.js";import{C as Ye}from"./clock-XdFXoaJi.js";import{D as Xe}from"./dollar-sign-VC2UX2UE.js";import{U as Ze}from"./upload-Db7YZ4zR.js";import{L as V}from"./loader-circle-czscMzmQ.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{r as o,j as e}from"./index-CooHd7I-.js";function f({value:r,prefix:t="",suffix:s=""}){const[u,n]=o.useState(0),c=o.useRef(0);return o.useEffect(()=>{const a=c.current,h=r-a;if(h===0)return;const i=600,d=performance.now();function l(b){const v=b-d,m=Math.min(v/i,1),N=1-Math.pow(1-m,3),p=Math.round(a+h*N);n(p),c.current=p,m<1&&requestAnimationFrame(l)}requestAnimationFrame(l)},[r]),e.jsxs("span",{className:"tabular-nums",children:[t,u.toLocaleString(),s]})}function g(){const[r,t]=o.useState(5),[s,u]=o.useState(30),[n,c]=o.useState(75),a=.4,i=Math.round(r*s*a*4.33),d=Math.round(i*n);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=>u(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:["$",n,"/hr"]})]}),e.jsx("input",{type:"range",min:25,max:250,step:5,value:n,onChange:l=>c(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-2 gap-4 text-center",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-2xl font-bold text-[#553DE9]",children:e.jsx(f,{value:i,suffix:"h"})}),e.jsx("div",{className:"text-xs text-[#6B6960] mt-1",children:"Est. hours saved / month"})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-2xl font-bold text-[#1FC5A8]",children:e.jsx(f,{value:d,prefix:"$"})}),e.jsx("div",{className:"text-xs text-[#6B6960] mt-1",children:"Est. cost saved / month"})]})]}),e.jsxs("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:["If Loki Mode saved ",Math.round(a*100),"% of coding time, your team could save"," ",e.jsxs("span",{className:"font-bold text-[#553DE9]",children:[i.toLocaleString()," hours"]})," ","and"," ",e.jsxs("span",{className:"font-bold text-[#1FC5A8]",children:["$",d.toLocaleString()]})," ","per month."]}),e.jsxs("p",{className:"mt-2 text-xs text-[#6B6960] text-center",children:["The ",Math.round(a*100),"% is an assumption, not a measurement. Model and API spend is not included."]})]})]})]})}const j=[{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:"5-provider auto-failover",category:"AI Models",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"8 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 y(){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 w(){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 x({value:r}){return typeof r=="string"?e.jsx("span",{className:"text-xs text-[#6B6960] font-medium",children:r}):r?e.jsx(y,{}):e.jsx(w,{})}const k=[{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:"8 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 C(){const r=[...new Set(j.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}`),j.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(x,{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(x,{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(x,{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(x,{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:k.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.jsx("div",{className:"mt-12 max-w-xl mx-auto",children:e.jsx(g,{})})]})})}export{C as default};
1
+ import{r as o,j as e}from"./index-DnCsWpCh.js";function f({value:r,prefix:t="",suffix:s=""}){const[u,n]=o.useState(0),c=o.useRef(0);return o.useEffect(()=>{const a=c.current,h=r-a;if(h===0)return;const i=600,d=performance.now();function l(b){const v=b-d,m=Math.min(v/i,1),N=1-Math.pow(1-m,3),p=Math.round(a+h*N);n(p),c.current=p,m<1&&requestAnimationFrame(l)}requestAnimationFrame(l)},[r]),e.jsxs("span",{className:"tabular-nums",children:[t,u.toLocaleString(),s]})}function g(){const[r,t]=o.useState(5),[s,u]=o.useState(30),[n,c]=o.useState(75),a=.4,i=Math.round(r*s*a*4.33),d=Math.round(i*n);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=>u(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:["$",n,"/hr"]})]}),e.jsx("input",{type:"range",min:25,max:250,step:5,value:n,onChange:l=>c(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-2 gap-4 text-center",children:[e.jsxs("div",{children:[e.jsx("div",{className:"text-2xl font-bold text-[#553DE9]",children:e.jsx(f,{value:i,suffix:"h"})}),e.jsx("div",{className:"text-xs text-[#6B6960] mt-1",children:"Est. hours saved / month"})]}),e.jsxs("div",{children:[e.jsx("div",{className:"text-2xl font-bold text-[#1FC5A8]",children:e.jsx(f,{value:d,prefix:"$"})}),e.jsx("div",{className:"text-xs text-[#6B6960] mt-1",children:"Est. cost saved / month"})]})]}),e.jsxs("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:["If Loki Mode saved ",Math.round(a*100),"% of coding time, your team could save"," ",e.jsxs("span",{className:"font-bold text-[#553DE9]",children:[i.toLocaleString()," hours"]})," ","and"," ",e.jsxs("span",{className:"font-bold text-[#1FC5A8]",children:["$",d.toLocaleString()]})," ","per month."]}),e.jsxs("p",{className:"mt-2 text-xs text-[#6B6960] text-center",children:["The ",Math.round(a*100),"% is an assumption, not a measurement. Model and API spend is not included."]})]})]})]})}const j=[{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:"5-provider auto-failover",category:"AI Models",lokiMode:!0,boltNew:!1,replit:!1,lovable:!1},{feature:"8 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 y(){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 w(){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 x({value:r}){return typeof r=="string"?e.jsx("span",{className:"text-xs text-[#6B6960] font-medium",children:r}):r?e.jsx(y,{}):e.jsx(w,{})}const k=[{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:"8 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 C(){const r=[...new Set(j.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}`),j.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(x,{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(x,{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(x,{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(x,{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:k.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.jsx("div",{className:"mt-12 max-w-xl mx-auto",children:e.jsx(g,{})})]})})}export{C as default};
@@ -1,4 +1,4 @@
1
- import{c as o,r as a,j as e,T as n,h as i,p as c}from"./index-CooHd7I-.js";/**
1
+ import{c as o,r as a,j as e,T as n,h as i,p as c}from"./index-DnCsWpCh.js";/**
2
2
  * @license lucide-react v0.577.0 - ISC
3
3
  *
4
4
  * This source code is licensed under the ISC license.