loki-mode 11.3.1 → 11.3.2
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 +4 -4
- package/VERSION +1 -1
- package/autonomy/lib/efficiency_cost.py +8 -0
- package/autonomy/lib/engine10-push.sh +22 -8
- package/autonomy/loki +62 -30
- package/bin/loki +30 -1
- package/completions/_loki +6 -0
- package/completions/loki.bash +2 -2
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +22 -14
- package/docs/AGENT-CHANGE-RECEIPT.md +2 -2
- package/docs/CLI-REFERENCE.md +5 -1
- package/docs/INSTALLATION.md +1 -1
- package/docs/OTEL-COLLECTOR.md +24 -0
- package/docs/ROUTING-EVIDENCE.md +32 -0
- package/docs/environment-variables.md +1 -0
- package/docs/spec-first-intent.md +28 -0
- package/docs/v10/CLI-MODERN.md +6 -3
- package/docs/v10/DECISIONS.md +15 -0
- package/docs/v10/FAILURE-CLASSES.md +95 -1
- package/docs/v10/GUARDS.md +18 -0
- package/docs/v10/GUIDE.md +7 -5
- package/docs/v10/METRICS.md +12 -0
- package/docs/v10/OPERATING-MODEL.md +3 -3
- package/docs/v10/PROGRESS.md +6 -0
- package/docs/v11/INTOTO-AGENT-CHANGE-PREDICATE.md +77 -0
- package/docs/v11/MCP-2026-07-28.md +55 -0
- package/docs/v11/VERIFY-PR-SANDBOX.md +41 -0
- package/loki-ts/dist/loki.js +1130 -969
- package/mcp/__init__.py +1 -1
- package/mcp/v10_tools.py +29 -0
- package/package.json +9 -11
- package/packages/control-plane/dist/ask-tools-server.js +8 -2
- package/packages/control-plane/dist/server.js +15728 -13988
- package/packages/control-plane/drizzle/0006_token_sessions.sql +1 -0
- package/packages/control-plane/drizzle/meta/0006_snapshot.json +807 -0
- package/packages/control-plane/drizzle/meta/_journal.json +7 -0
- package/packages/control-plane/src/db/schema.ts +1 -0
- package/packages/control-plane/src/server/routes/cost.ts +4 -3
- package/packages/control-plane/src/server/runs.ts +3 -3
- package/packages/control-plane/ui/dist/assets/index-DbLzr87u.js +190 -0
- package/packages/control-plane/ui/dist/index.html +1 -1
- package/plugins/loki-mode/.claude-plugin/plugin.json +1 -1
- package/providers/opencode.sh +2 -3
- package/src/observability/genai-spans.js +104 -0
- package/src/observability/otel-bridge.js +49 -1
- package/src/observability/otel.js +2 -2
- package/src/protocols/mcp-server.js +180 -5
- package/src/protocols/transport/sse.js +23 -2
- package/web-app/dist/assets/{AdminPage-CMkHNJbF.js → AdminPage-DuRQZsF9.js} +1 -1
- package/web-app/dist/assets/{Avatar-BJyjV7Zi.js → Avatar-7GtOQvsO.js} +1 -1
- package/web-app/dist/assets/{Badge-D572l0uq.js → Badge-CYgTnIxj.js} +1 -1
- package/web-app/dist/assets/{Button-T4BnOZgl.js → Button-rYSyIu7X.js} +1 -1
- package/web-app/dist/assets/{CockpitPage-Cj_GFsNn.js → CockpitPage-9adANY4o.js} +1 -1
- package/web-app/dist/assets/{ComparePage-A8m4ZXak.js → ComparePage-DEG2oA-R.js} +1 -1
- package/web-app/dist/assets/{ErrorBoundary-hugCg5yg.js → ErrorBoundary-CwtvY4xs.js} +1 -1
- package/web-app/dist/assets/{EvidenceReceiptPanel-CuJKDC2G.js → EvidenceReceiptPanel-DTT73pPB.js} +1 -1
- package/web-app/dist/assets/{GitHubIssuesPanel-BYGKjarC.js → GitHubIssuesPanel-CpWZLz2x.js} +1 -1
- package/web-app/dist/assets/{GitHubPRsPanel-c7sFIJlf.js → GitHubPRsPanel-H293R7E2.js} +1 -1
- package/web-app/dist/assets/{HomePage-A1MIkG2P.js → HomePage-B11Imp9k.js} +2 -2
- package/web-app/dist/assets/{LoginPage-8KQMRhtR.js → LoginPage-BZBgx5Jr.js} +1 -1
- package/web-app/dist/assets/{MagicPage-C05QW2Lx.js → MagicPage-B27S4_X8.js} +1 -1
- package/web-app/dist/assets/{MetricsPage-Oukv5hme.js → MetricsPage-CfbJWNDG.js} +1 -1
- package/web-app/dist/assets/{NotFoundPage-DKIwsawz.js → NotFoundPage-Tp_4zMSm.js} +1 -1
- package/web-app/dist/assets/{ProjectPage-DwFRPLCG.js → ProjectPage-CM4fgMsV.js} +3 -3
- package/web-app/dist/assets/{ProjectsPage-CgY4YYDX.js → ProjectsPage-DP2Fvsrd.js} +1 -1
- package/web-app/dist/assets/{SettingsPage-BpfcSjUA.js → SettingsPage-C4G9-EAy.js} +1 -1
- package/web-app/dist/assets/{ShowcasePage-BqBakC4K.js → ShowcasePage-Dj-n4dwu.js} +1 -1
- package/web-app/dist/assets/{SystemSettingsPage-CZLu5VB0.js → SystemSettingsPage-BqMLpuGt.js} +1 -1
- package/web-app/dist/assets/{TeamsPage-D55S1jKz.js → TeamsPage-mIH8H96m.js} +1 -1
- package/web-app/dist/assets/{TemplatesPage-D3SydvxN.js → TemplatesPage-BagW-v4n.js} +1 -1
- package/web-app/dist/assets/{TerminalOutput-hNg8G1-e.js → TerminalOutput-CTQRHhnK.js} +1 -1
- package/web-app/dist/assets/{activity-D2C0VagO.js → activity-DNQiBDDL.js} +1 -1
- package/web-app/dist/assets/{bell-CMvYi36w.js → bell-Coiwdad1.js} +1 -1
- package/web-app/dist/assets/{bot-BvtQ6lmo.js → bot-Cou-rTgR.js} +1 -1
- package/web-app/dist/assets/{check-B13xkXed.js → check-CsF-XtQp.js} +1 -1
- package/web-app/dist/assets/{chevron-left-emu6r49E.js → chevron-left-BJiy55Ht.js} +1 -1
- package/web-app/dist/assets/{circle-alert-CExlwjuy.js → circle-alert-D6-pQQWq.js} +1 -1
- package/web-app/dist/assets/{clock-D7Uas3oL.js → clock-BOGqwgt9.js} +1 -1
- package/web-app/dist/assets/{cloud-CkB4S86C.js → cloud-DsxhTtf5.js} +1 -1
- package/web-app/dist/assets/{code-xml-SL9IIWIn.js → code-xml-CxJDHUlE.js} +1 -1
- package/web-app/dist/assets/{database-BQFkd7t1.js → database-BJePscl_.js} +1 -1
- package/web-app/dist/assets/{dollar-sign-qHmnNYsU.js → dollar-sign-6qWGAkZo.js} +1 -1
- package/web-app/dist/assets/{file-code-corner-C2YUDP0K.js → file-code-corner-DcOtYeDb.js} +1 -1
- package/web-app/dist/assets/{file-plus-C_ooIkDe.js → file-plus-WuRyLFo0.js} +1 -1
- package/web-app/dist/assets/{globe-CQ-GL485.js → globe-r-xCP4DE.js} +1 -1
- package/web-app/dist/assets/{hammer-U05L_D7V.js → hammer-DTt0SsCB.js} +1 -1
- package/web-app/dist/assets/{index-CooHd7I-.js → index-BkbXyH8y.js} +3 -3
- package/web-app/dist/assets/{layers-Buo17rjW.js → layers-BvZhvSMY.js} +1 -1
- package/web-app/dist/assets/{loader-circle-15vK3oV1.js → loader-circle-3gosTei9.js} +1 -1
- package/web-app/dist/assets/{lock-BeXLebuJ.js → lock-CZZMxP0U.js} +1 -1
- package/web-app/dist/assets/{package-MVh_6-6R.js → package-C5A8CkJr.js} +1 -1
- package/web-app/dist/assets/{plus-Dh_nd9vS.js → plus-Btv7tTIF.js} +1 -1
- package/web-app/dist/assets/{refresh-cw-C9cBJEAT.js → refresh-cw-DsWLXgs_.js} +1 -1
- package/web-app/dist/assets/{rotate-ccw-D3PAtzST.js → rotate-ccw-DOVDjway.js} +1 -1
- package/web-app/dist/assets/{scroll-text-DZCoXQjo.js → scroll-text-smySzlrX.js} +1 -1
- package/web-app/dist/assets/{server-OCOLI5J_.js → server-b5PHsqAU.js} +1 -1
- package/web-app/dist/assets/{shield-alert-oVhmf1_h.js → shield-alert-BD7FDlh-.js} +1 -1
- package/web-app/dist/assets/{thumbs-up-B-kbX4vN.js → thumbs-up-CAsFzbyE.js} +1 -1
- package/web-app/dist/assets/{trash-2-EL6moveW.js → trash-2-BJ35qHgl.js} +1 -1
- package/web-app/dist/assets/{trending-down-Cl3kjpsx.js → trending-down-Ozrd26cZ.js} +1 -1
- package/web-app/dist/assets/{trending-up-DST7I6M2.js → trending-up-BhnMcM32.js} +1 -1
- package/web-app/dist/assets/{upload-CUOT780G.js → upload-JVHfdwoA.js} +1 -1
- package/web-app/dist/assets/{usePolling-Djmt90eb.js → usePolling-CTzbDV-e.js} +1 -1
- package/web-app/dist/assets/{user-BRBohA6J.js → user-Bks3-GYv.js} +1 -1
- package/web-app/dist/index.html +1 -1
- 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-
|
|
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.
|
|
5
|
+
"version": "11.3.2",
|
|
6
6
|
"description": "Autonomous spec-to-product build system with a built-in trust layer (RARV-C closure loop, 8 quality gates, completion council). Ships Loki's spec-hardening, drift-detection, and deterministic PR verification commands plus the Loki MCP server.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Autonomi",
|
package/providers/opencode.sh
CHANGED
|
@@ -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
|
|
25
|
-
# MiniMax M2.5
|
|
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 &&
|
|
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
|
-
|
|
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-
|
|
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-BkbXyH8y.js";import{B as C}from"./Button-rYSyIu7X.js";import{T as $e}from"./trending-up-BhnMcM32.js";import{C as z}from"./clock-BOGqwgt9.js";import{A as Be,U as ue,M as De}from"./Avatar-7GtOQvsO.js";import{A as J}from"./activity-DNQiBDDL.js";import{R as pe}from"./refresh-cw-DsWLXgs_.js";import{G as ze}from"./globe-r-xCP4DE.js";import{C as Le}from"./chevron-left-BJiy55Ht.js";import{C as je}from"./check-CsF-XtQp.js";import{D as Y}from"./dollar-sign-6qWGAkZo.js";import{U as Te}from"./user-Bks3-GYv.js";import{D as Me}from"./database-BJePscl_.js";import{L as Ue}from"./lock-CZZMxP0U.js";import{S as Re}from"./scroll-text-smySzlrX.js";import{S as Ie}from"./shield-alert-BD7FDlh-.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-BkbXyH8y.js";import{C as r}from"./clock-BOGqwgt9.js";import{C as a}from"./circle-alert-D6-pQQWq.js";import{C as l}from"./check-CsF-XtQp.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-BkbXyH8y.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-
|
|
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-BkbXyH8y.js";import{P as K,S as He,E as Be}from"./ErrorBoundary-CwtvY4xs.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-CAsFzbyE.js";import{C as oe}from"./check-CsF-XtQp.js";import{C as Ye}from"./clock-BOGqwgt9.js";import{D as Xe}from"./dollar-sign-6qWGAkZo.js";import{U as Ze}from"./upload-JVHfdwoA.js";import{L as V}from"./loader-circle-3gosTei9.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-BkbXyH8y.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};
|