sentinelayer-cli 0.6.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +996 -996
- package/bin/create-sentinelayer.js +5 -5
- package/bin/sentinelayer-cli.js +4 -4
- package/bin/sl.js +5 -5
- package/package.json +64 -63
- package/src/agents/jules/config/definition.js +160 -160
- package/src/agents/jules/config/system-prompt.js +182 -182
- package/src/agents/jules/error-intake.js +51 -51
- package/src/agents/jules/fix-cycle.js +17 -17
- package/src/agents/jules/loop.js +457 -450
- package/src/agents/jules/pulse.js +10 -10
- package/src/agents/jules/stream.js +187 -186
- package/src/agents/jules/swarm/file-scanner.js +74 -74
- package/src/agents/jules/swarm/index.js +11 -11
- package/src/agents/jules/swarm/orchestrator.js +362 -362
- package/src/agents/jules/swarm/pattern-hunter.js +123 -123
- package/src/agents/jules/swarm/sub-agent.js +311 -309
- package/src/agents/jules/tools/aidenid-email.js +189 -189
- package/src/agents/jules/tools/auth-audit.js +1699 -1691
- package/src/agents/jules/tools/dispatch.js +340 -335
- package/src/agents/jules/tools/file-edit.js +2 -2
- package/src/agents/jules/tools/file-read.js +2 -2
- package/src/agents/jules/tools/frontend-analyze.js +570 -570
- package/src/agents/jules/tools/glob.js +2 -2
- package/src/agents/jules/tools/grep.js +2 -2
- package/src/agents/jules/tools/index.js +29 -29
- package/src/agents/jules/tools/path-guards.js +2 -2
- package/src/agents/jules/tools/runtime-audit.js +507 -507
- package/src/agents/jules/tools/shell.js +2 -2
- package/src/agents/jules/tools/url-policy.js +100 -100
- package/src/agents/persona-visuals.js +64 -61
- package/src/agents/shared-tools/dispatch-core.js +320 -315
- package/src/agents/shared-tools/file-edit.js +180 -180
- package/src/agents/shared-tools/file-read.js +100 -100
- package/src/agents/shared-tools/glob.js +168 -168
- package/src/agents/shared-tools/grep.js +228 -228
- package/src/agents/shared-tools/index.js +46 -46
- package/src/agents/shared-tools/path-guards.js +161 -161
- package/src/agents/shared-tools/shell.js +383 -383
- package/src/ai/aidenid.js +1021 -1009
- package/src/ai/client.js +553 -553
- package/src/ai/domain-target-store.js +268 -268
- package/src/ai/identity-store.js +270 -270
- package/src/ai/proxy.js +137 -137
- package/src/ai/site-store.js +145 -145
- package/src/audit/agents/architecture.js +180 -180
- package/src/audit/agents/compliance.js +179 -179
- package/src/audit/agents/documentation.js +165 -165
- package/src/audit/agents/performance.js +145 -145
- package/src/audit/agents/security.js +215 -215
- package/src/audit/agents/testing.js +172 -172
- package/src/audit/orchestrator.js +557 -557
- package/src/audit/package.js +204 -204
- package/src/audit/registry.js +284 -284
- package/src/audit/replay.js +103 -103
- package/src/auth/gate.js +400 -371
- package/src/auth/http.js +681 -611
- package/src/auth/service.js +1106 -1106
- package/src/auth/session-store.js +813 -813
- package/src/cli.js +257 -252
- package/src/commands/ai/identity-lifecycle.js +1338 -1338
- package/src/commands/ai/provision-governance.js +1272 -1272
- package/src/commands/ai/shared.js +147 -147
- package/src/commands/ai.js +11 -11
- package/src/commands/apply.js +12 -12
- package/src/commands/audit.js +1171 -1166
- package/src/commands/auth.js +419 -419
- package/src/commands/chat.js +191 -191
- package/src/commands/config.js +184 -184
- package/src/commands/cost.js +311 -311
- package/src/commands/daemon/core.js +850 -850
- package/src/commands/daemon/extended.js +1048 -1048
- package/src/commands/daemon/shared.js +213 -213
- package/src/commands/daemon.js +11 -11
- package/src/commands/guide.js +174 -174
- package/src/commands/ingest.js +58 -58
- package/src/commands/init.js +55 -55
- package/src/commands/legacy-args.js +10 -10
- package/src/commands/mcp.js +461 -461
- package/src/commands/omargate.js +29 -29
- package/src/commands/persona.js +20 -20
- package/src/commands/plugin.js +260 -260
- package/src/commands/policy.js +132 -132
- package/src/commands/prompt.js +238 -238
- package/src/commands/review.js +704 -704
- package/src/commands/scan.js +872 -872
- package/src/commands/session.js +590 -0
- package/src/commands/spec.js +778 -716
- package/src/commands/swarm.js +651 -651
- package/src/commands/telemetry.js +202 -202
- package/src/commands/watch.js +511 -511
- package/src/config/agent-dictionary.js +182 -182
- package/src/config/io.js +56 -56
- package/src/config/paths.js +18 -18
- package/src/config/schema.js +55 -55
- package/src/config/service.js +184 -184
- package/src/cost/budget.js +235 -235
- package/src/cost/history.js +188 -188
- package/src/cost/tracker.js +171 -171
- package/src/daemon/artifact-lineage.js +534 -534
- package/src/daemon/assignment-ledger.js +966 -770
- package/src/daemon/ast-parser-layer.js +258 -258
- package/src/daemon/budget-governor.js +633 -633
- package/src/daemon/callgraph-overlay.js +646 -646
- package/src/daemon/error-worker.js +1209 -626
- package/src/daemon/fix-cycle.js +384 -377
- package/src/daemon/hybrid-mapper.js +929 -929
- package/src/daemon/ingest-refresh.js +10 -9
- package/src/daemon/jira-lifecycle.js +767 -632
- package/src/daemon/operator-control.js +657 -657
- package/src/daemon/pulse.js +327 -327
- package/src/daemon/reliability-lane.js +471 -471
- package/src/daemon/scope-engine.js +1068 -0
- package/src/daemon/watchdog.js +971 -971
- package/src/events/schema.js +190 -0
- package/src/guide/generator.js +316 -316
- package/src/ingest/engine.js +918 -918
- package/src/interactive/index.js +97 -97
- package/src/legacy-cli.js +3161 -2994
- package/src/mcp/registry.js +695 -695
- package/src/memory/blackboard.js +301 -301
- package/src/memory/retrieval.js +581 -581
- package/src/plugin/manifest.js +553 -553
- package/src/policy/packs.js +144 -144
- package/src/prompt/generator.js +136 -118
- package/src/review/ai-review.js +679 -679
- package/src/review/local-review.js +1351 -1305
- package/src/review/omargate-interactive.js +68 -68
- package/src/review/omargate-orchestrator.js +404 -300
- package/src/review/persona-prompts.js +296 -296
- package/src/review/replay.js +235 -235
- package/src/review/report.js +664 -664
- package/src/review/scan-modes.js +48 -42
- package/src/review/spec-binding.js +487 -487
- package/src/scaffold/generator.js +67 -67
- package/src/scaffold/templates.js +150 -150
- package/src/scan/generator.js +418 -418
- package/src/scan/gh-secrets.js +107 -107
- package/src/session/agent-registry.js +352 -0
- package/src/session/daemon.js +801 -0
- package/src/session/paths.js +33 -0
- package/src/session/runtime-bridge.js +739 -0
- package/src/session/store.js +388 -0
- package/src/session/stream.js +325 -0
- package/src/spec/generator.js +619 -519
- package/src/spec/regenerate.js +237 -237
- package/src/spec/templates.js +91 -91
- package/src/swarm/dashboard.js +247 -247
- package/src/swarm/factory.js +363 -363
- package/src/swarm/pentest.js +934 -934
- package/src/swarm/registry.js +419 -419
- package/src/swarm/report.js +158 -158
- package/src/swarm/runtime.js +576 -576
- package/src/swarm/scenario-dsl.js +272 -272
- package/src/telemetry/ledger.js +302 -302
- package/src/telemetry/session-tracker.js +234 -234
- package/src/telemetry/sync.js +203 -203
- package/src/ui/command-hints.js +13 -13
- package/src/ui/markdown.js +220 -220
package/src/telemetry/sync.js
CHANGED
|
@@ -1,203 +1,203 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { randomUUID } from "node:crypto";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
4
|
-
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { dirname, resolve } from "node:path";
|
|
6
|
-
import { resolveActiveAuthSession } from "../auth/service.js";
|
|
7
|
-
|
|
8
|
-
// Read CLI version from package.json at module load
|
|
9
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
-
const __dirname = dirname(__filename);
|
|
11
|
-
const pkgPath = resolve(__dirname, "../../package.json");
|
|
12
|
-
let CLI_VERSION = "0.0.0";
|
|
13
|
-
try {
|
|
14
|
-
CLI_VERSION = JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
15
|
-
} catch { /* fallback */ }
|
|
16
|
-
|
|
17
|
-
// Simple circuit breaker: skip sync after 3 consecutive failures
|
|
18
|
-
let consecutiveFailures = 0;
|
|
19
|
-
const MAX_CONSECUTIVE_FAILURES = 3;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Telemetry Sync — uploads CLI run results to sentinelayer-api.
|
|
23
|
-
*
|
|
24
|
-
* Called after audit, review, omargate commands complete.
|
|
25
|
-
* Fire-and-forget: never blocks the CLI, never crashes on failure.
|
|
26
|
-
* The API stores this data and the web dashboard renders it.
|
|
27
|
-
*
|
|
28
|
-
* What syncs:
|
|
29
|
-
* - Run metadata (command, persona, mode, framework detected)
|
|
30
|
-
* - Findings summary (P0/P1/P2/P3 counts, blocking status)
|
|
31
|
-
* - Usage telemetry (tokens, cost, duration, tool calls)
|
|
32
|
-
* - Stop reason (budget exhausted, max turns, completed, etc.)
|
|
33
|
-
*
|
|
34
|
-
* What stays local:
|
|
35
|
-
* - Full finding details (file:line evidence) — too large for sync
|
|
36
|
-
* - Ingest artifacts — regenerated locally
|
|
37
|
-
* - Spec/prompt/guide files — project artifacts
|
|
38
|
-
*/
|
|
39
|
-
|
|
40
|
-
const SYNC_TIMEOUT_MS = 10000;
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Sync a CLI run to the user's sentinelayer dashboard.
|
|
44
|
-
*
|
|
45
|
-
* @param {object} runData
|
|
46
|
-
* @param {string} runData.command - e.g., "audit frontend", "omargate deep", "review scan"
|
|
47
|
-
* @param {string} [runData.persona] - e.g., "Jules Tanaka"
|
|
48
|
-
* @param {string} [runData.mode] - e.g., "primary", "deep", "diff"
|
|
49
|
-
* @param {object} [runData.summary] - { total, P0, P1, P2, P3, blocking }
|
|
50
|
-
* @param {object} [runData.usage] - { tokens, inputTokens, outputTokens, costUsd, durationMs, toolCalls }
|
|
51
|
-
* @param {string} [runData.stopReason] - e.g., "completed", "budget_exhausted"
|
|
52
|
-
* @param {string} [runData.framework] - e.g., "next.js"
|
|
53
|
-
* @param {object} [runData.reconciliation] - baseline reconciliation summary
|
|
54
|
-
* @param {object} [runData.runtime] - runtime audit summary (lighthouse scores, headers)
|
|
55
|
-
* @returns {Promise<{ synced: boolean, reason?: string, runId?: string }>}
|
|
56
|
-
*/
|
|
57
|
-
export async function syncRunToDashboard(runData) {
|
|
58
|
-
// Circuit breaker: skip if too many consecutive failures
|
|
59
|
-
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
60
|
-
return { synced: false, reason: "circuit_breaker_open" };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
let session;
|
|
64
|
-
try {
|
|
65
|
-
session = await resolveActiveAuthSession({
|
|
66
|
-
cwd: process.cwd(),
|
|
67
|
-
env: process.env,
|
|
68
|
-
autoRotate: false,
|
|
69
|
-
});
|
|
70
|
-
} catch {
|
|
71
|
-
return { synced: false, reason: "no_session" };
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
if (!session || !session.token) {
|
|
75
|
-
return { synced: false, reason: "not_authenticated" };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const apiUrl = session.apiUrl || "https://api.sentinelayer.com";
|
|
79
|
-
const runId = randomUUID();
|
|
80
|
-
|
|
81
|
-
try {
|
|
82
|
-
const payload = {
|
|
83
|
-
schema_version: "1.0",
|
|
84
|
-
tier: 2,
|
|
85
|
-
run: {
|
|
86
|
-
run_id: runId,
|
|
87
|
-
timestamp_utc: new Date().toISOString(),
|
|
88
|
-
duration_ms: runData.usage?.durationMs || 0,
|
|
89
|
-
state: runData.stopReason || "passed",
|
|
90
|
-
},
|
|
91
|
-
repo: {
|
|
92
|
-
owner: detectRepoOwner(),
|
|
93
|
-
name: detectRepoName(),
|
|
94
|
-
branch: detectGitRef(),
|
|
95
|
-
},
|
|
96
|
-
scan: {
|
|
97
|
-
mode: mapCommandToMode(runData.command),
|
|
98
|
-
tokens_in: runData.usage?.inputTokens || runData.usage?.tokens || 0,
|
|
99
|
-
tokens_out: runData.usage?.outputTokens || 0,
|
|
100
|
-
cost_estimate_usd: runData.usage?.costUsd || 0,
|
|
101
|
-
model_used: runData.persona || "deterministic",
|
|
102
|
-
},
|
|
103
|
-
findings: {
|
|
104
|
-
counts: {
|
|
105
|
-
P0: runData.summary?.P0 || 0,
|
|
106
|
-
P1: runData.summary?.P1 || 0,
|
|
107
|
-
P2: runData.summary?.P2 || 0,
|
|
108
|
-
P3: runData.summary?.P3 || 0,
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
gate: {
|
|
112
|
-
result: runData.summary?.blocking ? "blocked" : "passed",
|
|
113
|
-
severity_threshold: "P1",
|
|
114
|
-
},
|
|
115
|
-
meta: {
|
|
116
|
-
source: "cli",
|
|
117
|
-
command: runData.command,
|
|
118
|
-
persona: runData.persona || null,
|
|
119
|
-
framework: runData.framework || null,
|
|
120
|
-
cli_version: CLI_VERSION,
|
|
121
|
-
},
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const response = await fetchWithTimeout(apiUrl + "/api/v1/telemetry", {
|
|
125
|
-
method: "POST",
|
|
126
|
-
headers: {
|
|
127
|
-
"Content-Type": "application/json",
|
|
128
|
-
Authorization: "Bearer " + session.token,
|
|
129
|
-
},
|
|
130
|
-
body: JSON.stringify(payload),
|
|
131
|
-
}, SYNC_TIMEOUT_MS);
|
|
132
|
-
|
|
133
|
-
if (!response.ok) {
|
|
134
|
-
consecutiveFailures++;
|
|
135
|
-
return { synced: false, reason: "api_" + response.status };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
consecutiveFailures = 0; // reset on success
|
|
139
|
-
return { synced: true, runId };
|
|
140
|
-
} catch (err) {
|
|
141
|
-
consecutiveFailures++;
|
|
142
|
-
return { synced: false, reason: err.message };
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function mapCommandToMode(command) {
|
|
147
|
-
if (!command) return "quick_scan";
|
|
148
|
-
const lower = String(command).toLowerCase();
|
|
149
|
-
if (lower.includes("omargate")) return "deep_scan";
|
|
150
|
-
if (lower.includes("review")) return "quick_scan";
|
|
151
|
-
if (lower.includes("audit") && lower.includes("frontend")) return "audit_frontend";
|
|
152
|
-
if (lower.includes("audit")) return "audit_full";
|
|
153
|
-
if (lower.includes("fix")) return "edit_gated";
|
|
154
|
-
return "quick_scan";
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function detectRepoOwner() {
|
|
158
|
-
try {
|
|
159
|
-
const remote = execFileSync("git", ["remote", "get-url", "origin"], {
|
|
160
|
-
encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
161
|
-
}).trim();
|
|
162
|
-
// Match owner from patterns like:
|
|
163
|
-
// https://github.com/owner/repo.git
|
|
164
|
-
// git@github.com:owner/repo.git
|
|
165
|
-
const httpsMatch = remote.match(/(?:github\.com|gitlab\.com)[/:]([^/]+)\//);
|
|
166
|
-
if (httpsMatch) return httpsMatch[1];
|
|
167
|
-
return "unknown";
|
|
168
|
-
} catch {
|
|
169
|
-
return "unknown";
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function detectRepoName() {
|
|
174
|
-
try {
|
|
175
|
-
const remote = execFileSync("git", ["remote", "get-url", "origin"], {
|
|
176
|
-
encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
177
|
-
}).trim();
|
|
178
|
-
const match = remote.match(/\/([^/]+?)(?:\.git)?$/);
|
|
179
|
-
return match ? match[1] : "unknown";
|
|
180
|
-
} catch {
|
|
181
|
-
return "local";
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function detectGitRef() {
|
|
186
|
-
try {
|
|
187
|
-
return execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
188
|
-
encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
189
|
-
}).trim() || "main";
|
|
190
|
-
} catch {
|
|
191
|
-
return "main";
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function fetchWithTimeout(url, options, timeoutMs) {
|
|
196
|
-
const controller = new AbortController();
|
|
197
|
-
const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
|
|
198
|
-
try {
|
|
199
|
-
return await fetch(url, { ...options, signal: controller.signal });
|
|
200
|
-
} finally {
|
|
201
|
-
clearTimeout(timeoutHandle);
|
|
202
|
-
}
|
|
203
|
-
}
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { dirname, resolve } from "node:path";
|
|
6
|
+
import { resolveActiveAuthSession } from "../auth/service.js";
|
|
7
|
+
|
|
8
|
+
// Read CLI version from package.json at module load
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
const pkgPath = resolve(__dirname, "../../package.json");
|
|
12
|
+
let CLI_VERSION = "0.0.0";
|
|
13
|
+
try {
|
|
14
|
+
CLI_VERSION = JSON.parse(readFileSync(pkgPath, "utf-8")).version;
|
|
15
|
+
} catch { /* fallback */ }
|
|
16
|
+
|
|
17
|
+
// Simple circuit breaker: skip sync after 3 consecutive failures
|
|
18
|
+
let consecutiveFailures = 0;
|
|
19
|
+
const MAX_CONSECUTIVE_FAILURES = 3;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Telemetry Sync — uploads CLI run results to sentinelayer-api.
|
|
23
|
+
*
|
|
24
|
+
* Called after audit, review, omargate commands complete.
|
|
25
|
+
* Fire-and-forget: never blocks the CLI, never crashes on failure.
|
|
26
|
+
* The API stores this data and the web dashboard renders it.
|
|
27
|
+
*
|
|
28
|
+
* What syncs:
|
|
29
|
+
* - Run metadata (command, persona, mode, framework detected)
|
|
30
|
+
* - Findings summary (P0/P1/P2/P3 counts, blocking status)
|
|
31
|
+
* - Usage telemetry (tokens, cost, duration, tool calls)
|
|
32
|
+
* - Stop reason (budget exhausted, max turns, completed, etc.)
|
|
33
|
+
*
|
|
34
|
+
* What stays local:
|
|
35
|
+
* - Full finding details (file:line evidence) — too large for sync
|
|
36
|
+
* - Ingest artifacts — regenerated locally
|
|
37
|
+
* - Spec/prompt/guide files — project artifacts
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
const SYNC_TIMEOUT_MS = 10000;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Sync a CLI run to the user's sentinelayer dashboard.
|
|
44
|
+
*
|
|
45
|
+
* @param {object} runData
|
|
46
|
+
* @param {string} runData.command - e.g., "audit frontend", "omargate deep", "review scan"
|
|
47
|
+
* @param {string} [runData.persona] - e.g., "Jules Tanaka"
|
|
48
|
+
* @param {string} [runData.mode] - e.g., "primary", "deep", "diff"
|
|
49
|
+
* @param {object} [runData.summary] - { total, P0, P1, P2, P3, blocking }
|
|
50
|
+
* @param {object} [runData.usage] - { tokens, inputTokens, outputTokens, costUsd, durationMs, toolCalls }
|
|
51
|
+
* @param {string} [runData.stopReason] - e.g., "completed", "budget_exhausted"
|
|
52
|
+
* @param {string} [runData.framework] - e.g., "next.js"
|
|
53
|
+
* @param {object} [runData.reconciliation] - baseline reconciliation summary
|
|
54
|
+
* @param {object} [runData.runtime] - runtime audit summary (lighthouse scores, headers)
|
|
55
|
+
* @returns {Promise<{ synced: boolean, reason?: string, runId?: string }>}
|
|
56
|
+
*/
|
|
57
|
+
export async function syncRunToDashboard(runData) {
|
|
58
|
+
// Circuit breaker: skip if too many consecutive failures
|
|
59
|
+
if (consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
60
|
+
return { synced: false, reason: "circuit_breaker_open" };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let session;
|
|
64
|
+
try {
|
|
65
|
+
session = await resolveActiveAuthSession({
|
|
66
|
+
cwd: process.cwd(),
|
|
67
|
+
env: process.env,
|
|
68
|
+
autoRotate: false,
|
|
69
|
+
});
|
|
70
|
+
} catch {
|
|
71
|
+
return { synced: false, reason: "no_session" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!session || !session.token) {
|
|
75
|
+
return { synced: false, reason: "not_authenticated" };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const apiUrl = session.apiUrl || "https://api.sentinelayer.com";
|
|
79
|
+
const runId = randomUUID();
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
const payload = {
|
|
83
|
+
schema_version: "1.0",
|
|
84
|
+
tier: 2,
|
|
85
|
+
run: {
|
|
86
|
+
run_id: runId,
|
|
87
|
+
timestamp_utc: new Date().toISOString(),
|
|
88
|
+
duration_ms: runData.usage?.durationMs || 0,
|
|
89
|
+
state: runData.stopReason || "passed",
|
|
90
|
+
},
|
|
91
|
+
repo: {
|
|
92
|
+
owner: detectRepoOwner(),
|
|
93
|
+
name: detectRepoName(),
|
|
94
|
+
branch: detectGitRef(),
|
|
95
|
+
},
|
|
96
|
+
scan: {
|
|
97
|
+
mode: mapCommandToMode(runData.command),
|
|
98
|
+
tokens_in: runData.usage?.inputTokens || runData.usage?.tokens || 0,
|
|
99
|
+
tokens_out: runData.usage?.outputTokens || 0,
|
|
100
|
+
cost_estimate_usd: runData.usage?.costUsd || 0,
|
|
101
|
+
model_used: runData.persona || "deterministic",
|
|
102
|
+
},
|
|
103
|
+
findings: {
|
|
104
|
+
counts: {
|
|
105
|
+
P0: runData.summary?.P0 || 0,
|
|
106
|
+
P1: runData.summary?.P1 || 0,
|
|
107
|
+
P2: runData.summary?.P2 || 0,
|
|
108
|
+
P3: runData.summary?.P3 || 0,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
gate: {
|
|
112
|
+
result: runData.summary?.blocking ? "blocked" : "passed",
|
|
113
|
+
severity_threshold: "P1",
|
|
114
|
+
},
|
|
115
|
+
meta: {
|
|
116
|
+
source: "cli",
|
|
117
|
+
command: runData.command,
|
|
118
|
+
persona: runData.persona || null,
|
|
119
|
+
framework: runData.framework || null,
|
|
120
|
+
cli_version: CLI_VERSION,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const response = await fetchWithTimeout(apiUrl + "/api/v1/telemetry", {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: {
|
|
127
|
+
"Content-Type": "application/json",
|
|
128
|
+
Authorization: "Bearer " + session.token,
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify(payload),
|
|
131
|
+
}, SYNC_TIMEOUT_MS);
|
|
132
|
+
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
consecutiveFailures++;
|
|
135
|
+
return { synced: false, reason: "api_" + response.status };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
consecutiveFailures = 0; // reset on success
|
|
139
|
+
return { synced: true, runId };
|
|
140
|
+
} catch (err) {
|
|
141
|
+
consecutiveFailures++;
|
|
142
|
+
return { synced: false, reason: err.message };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function mapCommandToMode(command) {
|
|
147
|
+
if (!command) return "quick_scan";
|
|
148
|
+
const lower = String(command).toLowerCase();
|
|
149
|
+
if (lower.includes("omargate")) return "deep_scan";
|
|
150
|
+
if (lower.includes("review")) return "quick_scan";
|
|
151
|
+
if (lower.includes("audit") && lower.includes("frontend")) return "audit_frontend";
|
|
152
|
+
if (lower.includes("audit")) return "audit_full";
|
|
153
|
+
if (lower.includes("fix")) return "edit_gated";
|
|
154
|
+
return "quick_scan";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function detectRepoOwner() {
|
|
158
|
+
try {
|
|
159
|
+
const remote = execFileSync("git", ["remote", "get-url", "origin"], {
|
|
160
|
+
encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
161
|
+
}).trim();
|
|
162
|
+
// Match owner from patterns like:
|
|
163
|
+
// https://github.com/owner/repo.git
|
|
164
|
+
// git@github.com:owner/repo.git
|
|
165
|
+
const httpsMatch = remote.match(/(?:github\.com|gitlab\.com)[/:]([^/]+)\//);
|
|
166
|
+
if (httpsMatch) return httpsMatch[1];
|
|
167
|
+
return "unknown";
|
|
168
|
+
} catch {
|
|
169
|
+
return "unknown";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function detectRepoName() {
|
|
174
|
+
try {
|
|
175
|
+
const remote = execFileSync("git", ["remote", "get-url", "origin"], {
|
|
176
|
+
encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
177
|
+
}).trim();
|
|
178
|
+
const match = remote.match(/\/([^/]+?)(?:\.git)?$/);
|
|
179
|
+
return match ? match[1] : "unknown";
|
|
180
|
+
} catch {
|
|
181
|
+
return "local";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function detectGitRef() {
|
|
186
|
+
try {
|
|
187
|
+
return execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
188
|
+
encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
189
|
+
}).trim() || "main";
|
|
190
|
+
} catch {
|
|
191
|
+
return "main";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function fetchWithTimeout(url, options, timeoutMs) {
|
|
196
|
+
const controller = new AbortController();
|
|
197
|
+
const timeoutHandle = setTimeout(() => controller.abort(), timeoutMs);
|
|
198
|
+
try {
|
|
199
|
+
return await fetch(url, { ...options, signal: controller.signal });
|
|
200
|
+
} finally {
|
|
201
|
+
clearTimeout(timeoutHandle);
|
|
202
|
+
}
|
|
203
|
+
}
|
package/src/ui/command-hints.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
|
-
|
|
3
|
-
export function preferredCliCommand({ platform = process.platform, env = process.env } = {}) {
|
|
4
|
-
const override = String(env?.SENTINELAYER_CLI_COMMAND || "").trim();
|
|
5
|
-
if (override) {
|
|
6
|
-
return override;
|
|
7
|
-
}
|
|
8
|
-
return platform === "win32" ? "sentinelayer-cli" : "sl";
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function authLoginHint({ platform = process.platform, env = process.env } = {}) {
|
|
12
|
-
return `${preferredCliCommand({ platform, env })} auth login`;
|
|
13
|
-
}
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
|
|
3
|
+
export function preferredCliCommand({ platform = process.platform, env = process.env } = {}) {
|
|
4
|
+
const override = String(env?.SENTINELAYER_CLI_COMMAND || "").trim();
|
|
5
|
+
if (override) {
|
|
6
|
+
return override;
|
|
7
|
+
}
|
|
8
|
+
return platform === "win32" ? "sentinelayer-cli" : "sl";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function authLoginHint({ platform = process.platform, env = process.env } = {}) {
|
|
12
|
+
return `${preferredCliCommand({ platform, env })} auth login`;
|
|
13
|
+
}
|