sentinelayer-cli 0.1.1 → 0.3.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 +62 -54
- package/src/agents/jules/config/definition.js +209 -209
- package/src/agents/jules/config/system-prompt.js +175 -175
- package/src/agents/jules/error-intake.js +51 -51
- package/src/agents/jules/fix-cycle.js +377 -377
- package/src/agents/jules/loop.js +367 -367
- package/src/agents/jules/pulse.js +319 -319
- package/src/agents/jules/stream.js +186 -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 +308 -308
- package/src/agents/jules/tools/auth-audit.js +226 -222
- package/src/agents/jules/tools/dispatch.js +327 -327
- package/src/agents/jules/tools/file-edit.js +180 -180
- package/src/agents/jules/tools/file-read.js +100 -100
- package/src/agents/jules/tools/frontend-analyze.js +570 -570
- package/src/agents/jules/tools/glob.js +168 -168
- package/src/agents/jules/tools/grep.js +228 -228
- package/src/agents/jules/tools/index.js +29 -29
- package/src/agents/jules/tools/path-guards.js +161 -161
- package/src/agents/jules/tools/runtime-audit.js +493 -493
- package/src/agents/jules/tools/shell.js +383 -383
- package/src/ai/aidenid.js +972 -945
- package/src/ai/client.js +508 -508
- package/src/ai/domain-target-store.js +268 -268
- package/src/ai/identity-store.js +270 -270
- 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/http.js +113 -113
- package/src/auth/service.js +891 -848
- package/src/auth/session-store.js +359 -345
- package/src/cli.js +252 -252
- package/src/commands/ai/identity-lifecycle.js +1338 -1337
- package/src/commands/ai/provision-governance.js +1272 -1246
- 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 +1166 -1147
- package/src/commands/auth.js +375 -366
- 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 -404
- package/src/commands/omargate.js +15 -15
- 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 +866 -788
- package/src/commands/spec.js +716 -716
- package/src/commands/swarm.js +651 -651
- package/src/commands/telemetry.js +202 -202
- package/src/commands/watch.js +510 -510
- 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 +770 -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 +626 -626
- package/src/daemon/hybrid-mapper.js +929 -929
- package/src/daemon/ingest-refresh.js +195 -0
- package/src/daemon/jira-lifecycle.js +632 -632
- package/src/daemon/operator-control.js +657 -657
- package/src/daemon/reliability-lane.js +471 -471
- package/src/daemon/watchdog.js +971 -971
- package/src/guide/generator.js +316 -316
- package/src/ingest/engine.js +918 -918
- package/src/interactive/action-menu.js +132 -0
- package/src/interactive/auto-ingest.js +111 -0
- package/src/interactive/index.js +95 -0
- package/src/interactive/workspace.js +92 -0
- package/src/legacy-cli.js +2548 -2435
- 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 +118 -106
- package/src/review/ai-review.js +669 -669
- package/src/review/local-review.js +1284 -1284
- package/src/review/replay.js +235 -235
- package/src/review/report.js +664 -664
- package/src/review/spec-binding.js +487 -487
- package/src/scaffold/generator.js +67 -0
- package/src/scaffold/templates.js +150 -0
- package/src/scan/generator.js +418 -351
- package/src/scan/gh-secrets.js +107 -0
- package/src/spec/generator.js +519 -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 +118 -0
- package/src/telemetry/sync.js +190 -0
- package/src/ui/markdown.js +220 -220
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import pc from "picocolors";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Session Tracker — tracks tokens, tool calls, cost, and time per CLI run.
|
|
5
|
+
*
|
|
6
|
+
* Inspired by src/bootstrap/state.ts and src/cost-tracker.ts patterns:
|
|
7
|
+
* - Single global session state initialized once
|
|
8
|
+
* - Accumulator functions for each metric
|
|
9
|
+
* - Summary getter for final report
|
|
10
|
+
* - Print summary on completion
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
let SESSION = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Initialize a new tracking session.
|
|
17
|
+
* Call this at the start of any auditable command.
|
|
18
|
+
*/
|
|
19
|
+
export function startSession(command) {
|
|
20
|
+
SESSION = {
|
|
21
|
+
command: command || "unknown",
|
|
22
|
+
startedAt: Date.now(),
|
|
23
|
+
inputTokens: 0,
|
|
24
|
+
outputTokens: 0,
|
|
25
|
+
costUsd: 0,
|
|
26
|
+
toolCalls: 0,
|
|
27
|
+
llmCalls: 0,
|
|
28
|
+
findings: { P0: 0, P1: 0, P2: 0, P3: 0 },
|
|
29
|
+
};
|
|
30
|
+
return SESSION;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Record token usage from an LLM call.
|
|
35
|
+
*/
|
|
36
|
+
export function recordLlmUsage({ inputTokens = 0, outputTokens = 0, costUsd = 0 } = {}) {
|
|
37
|
+
if (!SESSION) return;
|
|
38
|
+
SESSION.inputTokens += inputTokens;
|
|
39
|
+
SESSION.outputTokens += outputTokens;
|
|
40
|
+
SESSION.costUsd += costUsd;
|
|
41
|
+
SESSION.llmCalls += 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Record a tool call.
|
|
46
|
+
*/
|
|
47
|
+
export function recordToolCall() {
|
|
48
|
+
if (!SESSION) return;
|
|
49
|
+
SESSION.toolCalls += 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Record findings.
|
|
54
|
+
*/
|
|
55
|
+
export function recordFindings(summary) {
|
|
56
|
+
if (!SESSION) return;
|
|
57
|
+
if (summary?.P0) SESSION.findings.P0 += summary.P0;
|
|
58
|
+
if (summary?.P1) SESSION.findings.P1 += summary.P1;
|
|
59
|
+
if (summary?.P2) SESSION.findings.P2 += summary.P2;
|
|
60
|
+
if (summary?.P3) SESSION.findings.P3 += summary.P3;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the current session summary.
|
|
65
|
+
*/
|
|
66
|
+
export function getSessionSummary() {
|
|
67
|
+
if (!SESSION) return null;
|
|
68
|
+
const durationMs = Date.now() - SESSION.startedAt;
|
|
69
|
+
return {
|
|
70
|
+
command: SESSION.command,
|
|
71
|
+
durationMs,
|
|
72
|
+
inputTokens: SESSION.inputTokens,
|
|
73
|
+
outputTokens: SESSION.outputTokens,
|
|
74
|
+
totalTokens: SESSION.inputTokens + SESSION.outputTokens,
|
|
75
|
+
costUsd: SESSION.costUsd,
|
|
76
|
+
toolCalls: SESSION.toolCalls,
|
|
77
|
+
llmCalls: SESSION.llmCalls,
|
|
78
|
+
findings: { ...SESSION.findings },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Print the session summary to stderr.
|
|
84
|
+
* Called at the end of any auditable command.
|
|
85
|
+
*/
|
|
86
|
+
export function printSessionSummary() {
|
|
87
|
+
const summary = getSessionSummary();
|
|
88
|
+
if (!summary) return;
|
|
89
|
+
|
|
90
|
+
const duration = summary.durationMs < 60000
|
|
91
|
+
? (summary.durationMs / 1000).toFixed(1) + "s"
|
|
92
|
+
: (summary.durationMs / 60000).toFixed(1) + "m";
|
|
93
|
+
|
|
94
|
+
const tokens = summary.totalTokens > 1000
|
|
95
|
+
? (summary.totalTokens / 1000).toFixed(1) + "K"
|
|
96
|
+
: String(summary.totalTokens);
|
|
97
|
+
|
|
98
|
+
const parts = [];
|
|
99
|
+
parts.push(pc.gray("Run complete:"));
|
|
100
|
+
parts.push(pc.white(tokens + " tokens"));
|
|
101
|
+
parts.push(pc.white(summary.toolCalls + " tools"));
|
|
102
|
+
if (summary.costUsd > 0) parts.push(pc.white("$" + summary.costUsd.toFixed(2)));
|
|
103
|
+
parts.push(pc.white(duration));
|
|
104
|
+
|
|
105
|
+
const findingParts = [];
|
|
106
|
+
if (summary.findings.P0 > 0) findingParts.push(pc.red("P0=" + summary.findings.P0));
|
|
107
|
+
if (summary.findings.P1 > 0) findingParts.push(pc.yellow("P1=" + summary.findings.P1));
|
|
108
|
+
if (summary.findings.P2 > 0) findingParts.push(pc.cyan("P2=" + summary.findings.P2));
|
|
109
|
+
if (summary.findings.P3 > 0) findingParts.push(pc.gray("P3=" + summary.findings.P3));
|
|
110
|
+
|
|
111
|
+
process.stderr.write("\n" + parts.join(" · "));
|
|
112
|
+
if (findingParts.length > 0) {
|
|
113
|
+
process.stderr.write(" | " + findingParts.join(" "));
|
|
114
|
+
}
|
|
115
|
+
process.stderr.write("\n");
|
|
116
|
+
|
|
117
|
+
return summary;
|
|
118
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
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 { readStoredSession } from "../auth/session-store.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 readStoredSession();
|
|
66
|
+
} catch {
|
|
67
|
+
return { synced: false, reason: "no_session" };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!session || !session.token) {
|
|
71
|
+
return { synced: false, reason: "not_authenticated" };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const apiUrl = session.apiUrl || "https://api.sentinelayer.com";
|
|
75
|
+
const runId = randomUUID();
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const payload = {
|
|
79
|
+
schema_version: "1.0",
|
|
80
|
+
tier: 2,
|
|
81
|
+
run: {
|
|
82
|
+
run_id: runId,
|
|
83
|
+
timestamp_utc: new Date().toISOString(),
|
|
84
|
+
duration_ms: runData.usage?.durationMs || 0,
|
|
85
|
+
state: runData.stopReason || "passed",
|
|
86
|
+
},
|
|
87
|
+
repo: {
|
|
88
|
+
owner: detectRepoOwner(),
|
|
89
|
+
name: detectRepoName(),
|
|
90
|
+
branch: detectGitRef(),
|
|
91
|
+
},
|
|
92
|
+
scan: {
|
|
93
|
+
mode: mapCommandToMode(runData.command),
|
|
94
|
+
tokens_in: runData.usage?.inputTokens || runData.usage?.tokens || 0,
|
|
95
|
+
tokens_out: runData.usage?.outputTokens || 0,
|
|
96
|
+
cost_estimate_usd: runData.usage?.costUsd || 0,
|
|
97
|
+
model_used: runData.persona || "deterministic",
|
|
98
|
+
},
|
|
99
|
+
findings: {
|
|
100
|
+
counts: {
|
|
101
|
+
P0: runData.summary?.P0 || 0,
|
|
102
|
+
P1: runData.summary?.P1 || 0,
|
|
103
|
+
P2: runData.summary?.P2 || 0,
|
|
104
|
+
P3: runData.summary?.P3 || 0,
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
gate: {
|
|
108
|
+
result: runData.summary?.blocking ? "blocked" : "passed",
|
|
109
|
+
severity_threshold: "P1",
|
|
110
|
+
},
|
|
111
|
+
meta: {
|
|
112
|
+
source: "cli",
|
|
113
|
+
command: runData.command,
|
|
114
|
+
persona: runData.persona || null,
|
|
115
|
+
framework: runData.framework || null,
|
|
116
|
+
cli_version: CLI_VERSION,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const response = await fetch(apiUrl + "/api/v1/telemetry", {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: {
|
|
123
|
+
"Content-Type": "application/json",
|
|
124
|
+
Authorization: "Bearer " + session.token,
|
|
125
|
+
},
|
|
126
|
+
signal: AbortSignal.timeout(SYNC_TIMEOUT_MS),
|
|
127
|
+
body: JSON.stringify(payload),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
consecutiveFailures++;
|
|
132
|
+
return { synced: false, reason: "api_" + response.status };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
consecutiveFailures = 0; // reset on success
|
|
136
|
+
return { synced: true, runId };
|
|
137
|
+
} catch (err) {
|
|
138
|
+
consecutiveFailures++;
|
|
139
|
+
return { synced: false, reason: err.message };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function mapCommandToMode(command) {
|
|
144
|
+
if (!command) return "quick_scan";
|
|
145
|
+
const lower = String(command).toLowerCase();
|
|
146
|
+
if (lower.includes("omargate")) return "deep_scan";
|
|
147
|
+
if (lower.includes("review")) return "quick_scan";
|
|
148
|
+
if (lower.includes("audit") && lower.includes("frontend")) return "audit_frontend";
|
|
149
|
+
if (lower.includes("audit")) return "audit_full";
|
|
150
|
+
if (lower.includes("fix")) return "edit_gated";
|
|
151
|
+
return "quick_scan";
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function detectRepoOwner() {
|
|
155
|
+
try {
|
|
156
|
+
const remote = execFileSync("git", ["remote", "get-url", "origin"], {
|
|
157
|
+
encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
158
|
+
}).trim();
|
|
159
|
+
// Match owner from patterns like:
|
|
160
|
+
// https://github.com/owner/repo.git
|
|
161
|
+
// git@github.com:owner/repo.git
|
|
162
|
+
const httpsMatch = remote.match(/(?:github\.com|gitlab\.com)[/:]([^/]+)\//);
|
|
163
|
+
if (httpsMatch) return httpsMatch[1];
|
|
164
|
+
return "unknown";
|
|
165
|
+
} catch {
|
|
166
|
+
return "unknown";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function detectRepoName() {
|
|
171
|
+
try {
|
|
172
|
+
const remote = execFileSync("git", ["remote", "get-url", "origin"], {
|
|
173
|
+
encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
174
|
+
}).trim();
|
|
175
|
+
const match = remote.match(/\/([^/]+?)(?:\.git)?$/);
|
|
176
|
+
return match ? match[1] : "unknown";
|
|
177
|
+
} catch {
|
|
178
|
+
return "local";
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function detectGitRef() {
|
|
183
|
+
try {
|
|
184
|
+
return execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
185
|
+
encoding: "utf-8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
186
|
+
}).trim() || "main";
|
|
187
|
+
} catch {
|
|
188
|
+
return "main";
|
|
189
|
+
}
|
|
190
|
+
}
|