@cirvix_ai/agent-control 0.1.3 → 0.2.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 +76 -17
- package/bin/cirvix.mjs +539 -85
- package/bin/escape-benchmark.mjs +67 -0
- package/package.json +36 -16
- package/src/adapters/base.mjs +150 -0
- package/src/adapters/claude-code.mjs +161 -0
- package/src/adapters/cline.mjs +107 -0
- package/src/adapters/codex.mjs +104 -0
- package/src/adapters/cursor.mjs +104 -0
- package/src/adapters/frameworks.mjs +110 -0
- package/src/adapters/gemini-cli.mjs +104 -0
- package/src/adapters/generic-mcp.mjs +101 -0
- package/src/adapters/index.mjs +209 -0
- package/src/adapters/roo-code.mjs +106 -0
- package/src/adapters/vscode.mjs +104 -0
- package/src/adapters/windsurf.mjs +107 -0
- package/src/commands/console.mjs +58 -0
- package/src/commands/demo.mjs +55 -124
- package/src/commands/doctor.mjs +235 -0
- package/src/commands/init.mjs +292 -30
- package/src/commands/interactive.mjs +690 -0
- package/src/commands/kill.mjs +74 -0
- package/src/commands/login.mjs +227 -0
- package/src/commands/onboard.mjs +52 -0
- package/src/commands/passport.mjs +149 -0
- package/src/commands/policy.mjs +10 -6
- package/src/commands/protect.mjs +293 -0
- package/src/commands/prove.mjs +209 -0
- package/src/commands/redteam.mjs +51 -0
- package/src/commands/scan.mjs +11 -9
- package/src/commands/shadow.mjs +62 -0
- package/src/commands/simulate.mjs +96 -0
- package/src/commands/status.mjs +122 -41
- package/src/commands/upgrade.mjs +11 -11
- package/src/commands/welcome.mjs +105 -0
- package/src/core/authority.mjs +909 -0
- package/src/core/baseline.mjs +97 -0
- package/src/core/config-store.mjs +280 -0
- package/src/core/cost.mjs +0 -0
- package/src/core/detect.mjs +4 -33
- package/src/core/entitlements.mjs +7 -24
- package/src/core/escape-benchmark.mjs +597 -0
- package/src/core/events.mjs +234 -0
- package/src/core/evidence.mjs +212 -0
- package/src/core/format.mjs +44 -18
- package/src/core/gateway.mjs +15 -211
- package/src/core/graph.mjs +270 -0
- package/src/core/guard.mjs +118 -4
- package/src/core/intent.mjs +166 -0
- package/src/core/journal.mjs +131 -40
- package/src/core/kill-switch.mjs +122 -0
- package/src/core/notices.mjs +22 -2
- package/src/core/packs.mjs +193 -0
- package/src/core/passport.mjs +555 -0
- package/src/core/pipeline.mjs +148 -6
- package/src/core/prompts.mjs +51 -0
- package/src/core/proof.mjs +440 -0
- package/src/core/redteam/index.mjs +185 -0
- package/src/core/referral.mjs +187 -0
- package/src/core/sandbox.mjs +139 -0
- package/src/core/session.mjs +172 -0
- package/src/core/shadow.mjs +95 -0
- package/src/core/theme.mjs +240 -0
- package/src/core/trifecta.mjs +321 -0
- package/src/core/ui/controller.mjs +192 -0
- package/src/core/ui/decisions.mjs +55 -0
- package/src/core/ui/index.mjs +49 -0
- package/src/core/ui/intercept.mjs +103 -0
- package/src/core/ui/live.mjs +51 -0
- package/src/core/ui/primitives.mjs +123 -0
- package/src/core/ui/theme.mjs +92 -0
- package/src/core/verified.mjs +108 -0
- package/src/core/windows.mjs +270 -0
- package/src/index.mjs +67 -0
- package/src/tui/activity.mjs +71 -0
- package/src/tui/app.mjs +292 -0
- package/src/tui/cards.mjs +235 -0
- package/src/tui/composer.mjs +88 -0
- package/src/tui/palette.mjs +48 -0
- package/src/tui/status.mjs +42 -0
- package/src/core/cinematic.mjs +0 -545
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cirvix Verified & MCP Trust Layer.
|
|
3
|
+
*
|
|
4
|
+
* Implements security verification for MCP servers and tools:
|
|
5
|
+
* - Provenance and publisher tracking
|
|
6
|
+
* - Tool poisoning / description tampering detection
|
|
7
|
+
* - Unpinned network egress analysis
|
|
8
|
+
* - Trust score computation (0-100)
|
|
9
|
+
* - Verification status: VERIFIED | REVIEW_REQUIRED | SUSPICIOUS | BLOCKED
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export const VERIFICATION_STATUS = {
|
|
13
|
+
VERIFIED: "VERIFIED",
|
|
14
|
+
REVIEW_REQUIRED: "REVIEW_REQUIRED",
|
|
15
|
+
SUSPICIOUS: "SUSPICIOUS",
|
|
16
|
+
BLOCKED: "BLOCKED",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Scans an MCP server declaration and its tools for security risks and assigns trust score.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} server
|
|
23
|
+
* @param {string} server.name
|
|
24
|
+
* @param {string} [server.publisher]
|
|
25
|
+
* @param {string} [server.version]
|
|
26
|
+
* @param {Array} [server.tools]
|
|
27
|
+
* @param {Array} [server.destinations]
|
|
28
|
+
* @returns {Object} Inspection report
|
|
29
|
+
*/
|
|
30
|
+
export function inspectMcpServer({
|
|
31
|
+
name,
|
|
32
|
+
publisher = null,
|
|
33
|
+
version = null,
|
|
34
|
+
tools = [],
|
|
35
|
+
destinations = [],
|
|
36
|
+
} = {}) {
|
|
37
|
+
let trustScore = 70; // Starting baseline for unverified server
|
|
38
|
+
const findings = [];
|
|
39
|
+
|
|
40
|
+
// Publisher trust
|
|
41
|
+
const knownTrustedPublishers = ["anthropic", "google", "github", "stripe", "aws", "cirvix"];
|
|
42
|
+
if (publisher && knownTrustedPublishers.includes(publisher.toLowerCase())) {
|
|
43
|
+
trustScore += 25;
|
|
44
|
+
} else if (!publisher) {
|
|
45
|
+
trustScore -= 15;
|
|
46
|
+
findings.push("Missing publisher provenance metadata");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Network destinations check
|
|
50
|
+
for (const dest of destinations) {
|
|
51
|
+
const dLower = String(dest).toLowerCase();
|
|
52
|
+
if (dLower.includes("169.254.") || dLower.includes("metadata.google")) {
|
|
53
|
+
trustScore = 0;
|
|
54
|
+
findings.push(`Dangerous cloud metadata destination declared: ${dest}`);
|
|
55
|
+
return {
|
|
56
|
+
name,
|
|
57
|
+
trustScore: 0,
|
|
58
|
+
status: VERIFICATION_STATUS.BLOCKED,
|
|
59
|
+
findings,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Tools inspection
|
|
65
|
+
for (const tool of tools) {
|
|
66
|
+
const desc = (tool.description ?? "").toLowerCase();
|
|
67
|
+
const toolName = (tool.name ?? "").toLowerCase();
|
|
68
|
+
|
|
69
|
+
// Check for tool poisoning / prompt injection markers in description
|
|
70
|
+
if (
|
|
71
|
+
desc.includes("ignore previous instructions") ||
|
|
72
|
+
desc.includes("system prompt") ||
|
|
73
|
+
desc.includes("do not tell the user") ||
|
|
74
|
+
desc.includes("send secrets to")
|
|
75
|
+
) {
|
|
76
|
+
trustScore = 0;
|
|
77
|
+
findings.push(`Tool poisoning detected in tool '${tool.name}': suspicious prompt injection phrases in description`);
|
|
78
|
+
return {
|
|
79
|
+
name,
|
|
80
|
+
trustScore: 0,
|
|
81
|
+
status: VERIFICATION_STATUS.BLOCKED,
|
|
82
|
+
findings,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check for excessive wildcard permissions or raw shell execution
|
|
87
|
+
if (toolName === "bash" || toolName === "exec" || toolName === "eval") {
|
|
88
|
+
trustScore -= 20;
|
|
89
|
+
findings.push(`Tool '${tool.name}' exposes raw shell/process execution capability`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
trustScore = Math.max(0, Math.min(100, trustScore));
|
|
94
|
+
|
|
95
|
+
let status = VERIFICATION_STATUS.REVIEW_REQUIRED;
|
|
96
|
+
if (trustScore >= 85) status = VERIFICATION_STATUS.VERIFIED;
|
|
97
|
+
else if (trustScore < 50) status = VERIFICATION_STATUS.SUSPICIOUS;
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
name,
|
|
101
|
+
publisher,
|
|
102
|
+
version,
|
|
103
|
+
trustScore,
|
|
104
|
+
status,
|
|
105
|
+
findings,
|
|
106
|
+
scannedAt: new Date().toISOString(),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows first-class platform support for CIRVIX AgentControl.
|
|
3
|
+
*
|
|
4
|
+
* Provides cross-platform utilities designed to eliminate POSIX-only
|
|
5
|
+
* assumptions without bringing in external dependencies:
|
|
6
|
+
* - PATH and PATHEXT binary resolution (.exe, .cmd, .bat, etc.)
|
|
7
|
+
* - Global and user-level npm/node binary location discovery
|
|
8
|
+
* - Argument quoting and escaping for Windows shells
|
|
9
|
+
* - Process tree termination (preventing orphaned background processes)
|
|
10
|
+
* - Path canonicalization and case-folding
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
14
|
+
import { accessSync, constants, statSync } from "node:fs";
|
|
15
|
+
import { homedir } from "node:os";
|
|
16
|
+
import { delimiter, dirname, isAbsolute, join, normalize, resolve } from "node:path";
|
|
17
|
+
|
|
18
|
+
export const IS_WINDOWS = process.platform === "win32";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Standard executable extensions on Windows.
|
|
22
|
+
*/
|
|
23
|
+
export const DEFAULT_PATHEXT = IS_WINDOWS
|
|
24
|
+
? (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC")
|
|
25
|
+
.split(";")
|
|
26
|
+
.map((ext) => ext.toLowerCase())
|
|
27
|
+
: [""];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Normalizes a filesystem path across platforms.
|
|
31
|
+
* Replaces backslashes with forward slashes for internal consistency
|
|
32
|
+
* and normalizes drive letters to uppercase.
|
|
33
|
+
*/
|
|
34
|
+
export function normalizeFsPath(inputPath) {
|
|
35
|
+
if (typeof inputPath !== "string" || !inputPath) return "";
|
|
36
|
+
let p = inputPath.replace(/\\/g, "/");
|
|
37
|
+
// Normalize drive letter: 'c:/...' -> 'C:/...'
|
|
38
|
+
if (/^[a-zA-Z]:\//.test(p)) {
|
|
39
|
+
p = p.charAt(0).toUpperCase() + p.slice(1);
|
|
40
|
+
}
|
|
41
|
+
return p;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Checks if two filesystem paths refer to the same target (case-insensitive on Windows).
|
|
46
|
+
*/
|
|
47
|
+
export function arePathsEqual(pathA, pathB) {
|
|
48
|
+
const a = normalizeFsPath(resolve(pathA));
|
|
49
|
+
const b = normalizeFsPath(resolve(pathB));
|
|
50
|
+
return IS_WINDOWS ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Checks if a file exists and is executable.
|
|
55
|
+
*/
|
|
56
|
+
function isFileExecutable(filePath) {
|
|
57
|
+
try {
|
|
58
|
+
const st = statSync(filePath);
|
|
59
|
+
if (!st.isFile()) return false;
|
|
60
|
+
accessSync(filePath, constants.X_OK | constants.R_OK);
|
|
61
|
+
return true;
|
|
62
|
+
} catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Discovers common npm and global tool binary directories.
|
|
69
|
+
*/
|
|
70
|
+
export function getGlobalBinaryDirectories() {
|
|
71
|
+
const dirs = [];
|
|
72
|
+
const home = homedir();
|
|
73
|
+
|
|
74
|
+
if (IS_WINDOWS) {
|
|
75
|
+
if (process.env.APPDATA) {
|
|
76
|
+
dirs.push(join(process.env.APPDATA, "npm"));
|
|
77
|
+
}
|
|
78
|
+
if (process.env.LOCALAPPDATA) {
|
|
79
|
+
dirs.push(join(process.env.LOCALAPPDATA, "Programs"));
|
|
80
|
+
dirs.push(join(process.env.LOCALAPPDATA, "pnpm"));
|
|
81
|
+
}
|
|
82
|
+
if (process.env.ProgramFiles) {
|
|
83
|
+
dirs.push(join(process.env.ProgramFiles, "nodejs"));
|
|
84
|
+
}
|
|
85
|
+
if (process.env["ProgramFiles(x86)"]) {
|
|
86
|
+
dirs.push(join(process.env["ProgramFiles(x86)"], "nodejs"));
|
|
87
|
+
}
|
|
88
|
+
dirs.push(join(home, "AppData", "Roaming", "npm"));
|
|
89
|
+
dirs.push(join(home, ".cargo", "bin"));
|
|
90
|
+
} else {
|
|
91
|
+
dirs.push("/usr/local/bin");
|
|
92
|
+
dirs.push("/usr/bin");
|
|
93
|
+
dirs.push(join(home, ".nvm", "versions", "node", process.version, "bin"));
|
|
94
|
+
dirs.push(join(home, ".local", "bin"));
|
|
95
|
+
dirs.push(join(home, ".cargo", "bin"));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return dirs.filter((d) => {
|
|
99
|
+
try {
|
|
100
|
+
return statSync(d).isDirectory();
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Resolves an executable command name to an absolute file path.
|
|
109
|
+
* On Windows, handles PATHEXT extensions (.exe, .cmd, .bat) and npm globals.
|
|
110
|
+
*
|
|
111
|
+
* @param {string} command - e.g. "npx", "node", "cirvix", "git"
|
|
112
|
+
* @param {object} [options]
|
|
113
|
+
* @param {string} [options.cwd]
|
|
114
|
+
* @param {string[]} [options.searchPaths]
|
|
115
|
+
* @returns {string|null} Absolute path or null if not found
|
|
116
|
+
*/
|
|
117
|
+
export function resolveExecutable(command, { cwd = process.cwd(), searchPaths } = {}) {
|
|
118
|
+
if (typeof command !== "string" || !command) return null;
|
|
119
|
+
|
|
120
|
+
const raw = command.trim();
|
|
121
|
+
const pathext = DEFAULT_PATHEXT;
|
|
122
|
+
|
|
123
|
+
// If path is already absolute or explicitly relative (./ or ../)
|
|
124
|
+
if (isAbsolute(raw) || raw.startsWith("./") || raw.startsWith("../") || (IS_WINDOWS && /^[a-zA-Z]:[\\/]/.test(raw))) {
|
|
125
|
+
const candidate = resolve(cwd, raw);
|
|
126
|
+
if (isFileExecutable(candidate)) return candidate;
|
|
127
|
+
if (IS_WINDOWS) {
|
|
128
|
+
for (const ext of pathext) {
|
|
129
|
+
const withExt = candidate + ext;
|
|
130
|
+
if (isFileExecutable(withExt)) return withExt;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Search in PATH and standard directories
|
|
137
|
+
const pathEnv = (process.env.PATH || "").split(delimiter).filter(Boolean);
|
|
138
|
+
const allSearchPaths = [
|
|
139
|
+
cwd,
|
|
140
|
+
...(searchPaths ?? []),
|
|
141
|
+
...pathEnv,
|
|
142
|
+
...getGlobalBinaryDirectories(),
|
|
143
|
+
];
|
|
144
|
+
|
|
145
|
+
for (const dir of allSearchPaths) {
|
|
146
|
+
const candidate = join(dir, raw);
|
|
147
|
+
if (isFileExecutable(candidate)) return candidate;
|
|
148
|
+
|
|
149
|
+
if (IS_WINDOWS) {
|
|
150
|
+
// Check if command already has an extension
|
|
151
|
+
const hasExt = pathext.some((ext) => raw.toLowerCase().endsWith(ext));
|
|
152
|
+
if (!hasExt) {
|
|
153
|
+
for (const ext of pathext) {
|
|
154
|
+
const withExt = candidate + ext;
|
|
155
|
+
if (isFileExecutable(withExt)) return withExt;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Prepares process spawn options for Windows to ensure batch scripts (.cmd, .bat)
|
|
166
|
+
* and executables with spaces run cleanly.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} command
|
|
169
|
+
* @param {string[]} args
|
|
170
|
+
* @param {import("node:child_process").SpawnOptions} options
|
|
171
|
+
* @returns {{ command: string, args: string[], options: import("node:child_process").SpawnOptions }}
|
|
172
|
+
*/
|
|
173
|
+
export function prepareSpawn(command, args = [], options = {}) {
|
|
174
|
+
const resolved = resolveExecutable(command, { cwd: options.cwd ? String(options.cwd) : process.cwd() });
|
|
175
|
+
const targetCommand = resolved || command;
|
|
176
|
+
|
|
177
|
+
if (IS_WINDOWS) {
|
|
178
|
+
const lower = targetCommand.toLowerCase();
|
|
179
|
+
const isBatch = lower.endsWith(".cmd") || lower.endsWith(".bat");
|
|
180
|
+
|
|
181
|
+
if (isBatch) {
|
|
182
|
+
const comspec = process.env.ComSpec || "cmd.exe";
|
|
183
|
+
return {
|
|
184
|
+
command: comspec,
|
|
185
|
+
args: ["/d", "/s", "/c", `"${quoteArg(targetCommand)}"`, ...args.map(quoteArg)],
|
|
186
|
+
options: {
|
|
187
|
+
...options,
|
|
188
|
+
windowsVerbatimArguments: true,
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
command: targetCommand,
|
|
196
|
+
args,
|
|
197
|
+
options,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Escapes and quotes an argument for Windows cmd.exe / PowerShell.
|
|
203
|
+
*/
|
|
204
|
+
export function quoteArg(arg) {
|
|
205
|
+
const s = String(arg ?? "");
|
|
206
|
+
if (!s) return '""';
|
|
207
|
+
if (!/[\s"&|<>()^%!=]/.test(s)) return s;
|
|
208
|
+
return `"${s.replace(/"/g, '\\"')}"`;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Terminate a process and all of its spawned child processes cleanly.
|
|
213
|
+
* Uses taskkill /T /F on Windows; process group kill or fallback on POSIX.
|
|
214
|
+
*
|
|
215
|
+
* @param {import("node:child_process").ChildProcess|number} procOrPid
|
|
216
|
+
* @param {string} [signal="SIGTERM"]
|
|
217
|
+
*/
|
|
218
|
+
export function killProcessTree(procOrPid, signal = "SIGTERM") {
|
|
219
|
+
const pid = typeof procOrPid === "number" ? procOrPid : procOrPid?.pid;
|
|
220
|
+
if (!pid) return;
|
|
221
|
+
|
|
222
|
+
if (IS_WINDOWS) {
|
|
223
|
+
try {
|
|
224
|
+
if (typeof procOrPid === "object" && typeof procOrPid.kill === "function") {
|
|
225
|
+
procOrPid.kill();
|
|
226
|
+
}
|
|
227
|
+
execFileSync("taskkill.exe", ["/PID", String(pid), "/T", "/F"], {
|
|
228
|
+
stdio: "ignore",
|
|
229
|
+
windowsHide: true,
|
|
230
|
+
timeout: 2000,
|
|
231
|
+
});
|
|
232
|
+
} catch {
|
|
233
|
+
// Process may already have terminated
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
try {
|
|
237
|
+
process.kill(-pid, signal);
|
|
238
|
+
} catch {
|
|
239
|
+
try {
|
|
240
|
+
if (typeof procOrPid === "object" && typeof procOrPid.kill === "function") {
|
|
241
|
+
procOrPid.kill(signal);
|
|
242
|
+
} else {
|
|
243
|
+
process.kill(pid, signal);
|
|
244
|
+
}
|
|
245
|
+
} catch {}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export const isWindows = IS_WINDOWS;
|
|
251
|
+
|
|
252
|
+
export function getPathext() {
|
|
253
|
+
return [...DEFAULT_PATHEXT];
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function quoteCmdArg(arg) {
|
|
257
|
+
return quoteArg(arg);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function quotePowerShellArg(arg) {
|
|
261
|
+
const s = String(arg ?? "");
|
|
262
|
+
if (!s) return "''";
|
|
263
|
+
if (!/[\s"&|<>()^%!=']/.test(s)) return s;
|
|
264
|
+
return `'${s.replace(/'/g, "''")}'`;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export function getNamedPipePath(identifier) {
|
|
268
|
+
const slug = String(identifier).replace(/[^A-Za-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(-64);
|
|
269
|
+
return `\\\\.\\pipe\\cirvix-${slug || "default"}`;
|
|
270
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -140,4 +140,71 @@ export { scan } from "./commands/scan.mjs";
|
|
|
140
140
|
export { init, STARTER_POLICY } from "./commands/init.mjs";
|
|
141
141
|
export { status } from "./commands/status.mjs";
|
|
142
142
|
export { demo } from "./commands/demo.mjs";
|
|
143
|
+
export { consoleCmd as console } from "./commands/console.mjs";
|
|
144
|
+
export { onboard } from "./commands/onboard.mjs";
|
|
145
|
+
|
|
146
|
+
/* Product UI (engine stays presentation-free; these render events) -------- */
|
|
147
|
+
export {
|
|
148
|
+
THEME_NAMES,
|
|
149
|
+
THEME_NAMES as THEMES,
|
|
150
|
+
ROLES,
|
|
151
|
+
badgeForDecision,
|
|
152
|
+
colors,
|
|
153
|
+
roleForDecision,
|
|
154
|
+
roleForRisk,
|
|
155
|
+
setTheme,
|
|
156
|
+
style,
|
|
157
|
+
themeName,
|
|
158
|
+
} from "./core/theme.mjs";
|
|
159
|
+
export {
|
|
160
|
+
EVENT,
|
|
161
|
+
EventBus,
|
|
162
|
+
attachPipeline,
|
|
163
|
+
createEvent,
|
|
164
|
+
initialState,
|
|
165
|
+
latencyStats,
|
|
166
|
+
reduce,
|
|
167
|
+
} from "./core/events.mjs";
|
|
168
|
+
export {
|
|
169
|
+
blockedCard,
|
|
170
|
+
cirvixRow,
|
|
171
|
+
explainDecision,
|
|
172
|
+
frame,
|
|
173
|
+
header,
|
|
174
|
+
heldCard,
|
|
175
|
+
policyCard,
|
|
176
|
+
rule,
|
|
177
|
+
spinnerFrame,
|
|
178
|
+
toolCard,
|
|
179
|
+
userRow,
|
|
180
|
+
} from "./tui/cards.mjs";
|
|
181
|
+
export { statusBar } from "./tui/status.mjs";
|
|
182
|
+
export { activitySummary, collapsedFeed, activityRow } from "./tui/activity.mjs";
|
|
183
|
+
export { COMMANDS, filterCommands, paletteBox } from "./tui/palette.mjs";
|
|
184
|
+
export { ConsoleApp, parseRequest } from "./tui/app.mjs";
|
|
143
185
|
export { check as policyCheck, explain as policyExplain, list as policyList, loadPolicyFile, test as policyTest } from "./commands/policy.mjs";
|
|
186
|
+
|
|
187
|
+
/* Adapters & Platform ---------------------------------------------------- */
|
|
188
|
+
export * as adapters from "./adapters/index.mjs";
|
|
189
|
+
export * as windows from "./core/windows.mjs";
|
|
190
|
+
export { ConfigBackupManager, SafeConfigPatcher, parseConfigJson, stripJsonComments } from "./core/config-store.mjs";
|
|
191
|
+
|
|
192
|
+
/* Platform Transformation Primitives ------------------------------------- */
|
|
193
|
+
export { evaluateIntent, classifyIntent, INTENT_CATEGORIES } from "./core/intent.mjs";
|
|
194
|
+
export { SessionTracker, CHAIN_TYPES } from "./core/session.mjs";
|
|
195
|
+
export { BehavioralBaseline } from "./core/baseline.mjs";
|
|
196
|
+
export { KillSwitchEngine, globalKillSwitch, KILL_SCOPES } from "./core/kill-switch.mjs";
|
|
197
|
+
export { AgentSandbox, SANDBOX_ADAPTERS } from "./core/sandbox.mjs";
|
|
198
|
+
export { ShadowEngine } from "./core/shadow.mjs";
|
|
199
|
+
export { runRedTeamSuite, BUILTIN_ATTACK_PLUGINS, ATTACK_VECTORS } from "./core/redteam/index.mjs";
|
|
200
|
+
export { inspectMcpServer, VERIFICATION_STATUS } from "./core/verified.mjs";
|
|
201
|
+
export {
|
|
202
|
+
generateAgentKeypair,
|
|
203
|
+
issueCryptographicPassport,
|
|
204
|
+
verifyPassportSignature,
|
|
205
|
+
rotatePassportKeys,
|
|
206
|
+
} from "./core/passport.mjs";
|
|
207
|
+
export {
|
|
208
|
+
issueActionReceipt,
|
|
209
|
+
verifyActionReceipt,
|
|
210
|
+
} from "./core/proof.mjs";
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collapsible activity feed — the fix for the "wall of logs".
|
|
3
|
+
*
|
|
4
|
+
* 100 tool calls never dump as 100 lines. They collapse to:
|
|
5
|
+
*
|
|
6
|
+
* ✓ Deployment completed
|
|
7
|
+
* 14 actions ├─ 8 allowed ├─ 4 sanitized └─ 2 blocked
|
|
8
|
+
* [Enter] inspect
|
|
9
|
+
*
|
|
10
|
+
* Expanding (Enter / click / `inspect`) shows the per-call rows with the
|
|
11
|
+
* semantic badge (icon + color + WORD — never icon alone).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { style, bold, dim, badgeForDecision, roleForDecision } from "../core/theme.mjs";
|
|
15
|
+
|
|
16
|
+
export function activitySummary(activity) {
|
|
17
|
+
const counts = { allow: 0, sanitize: 0, deny: 0, require_approval: 0, audit_only: 0 };
|
|
18
|
+
for (const e of activity) {
|
|
19
|
+
const d = e.decision ?? e.raw?.decision ?? "allow";
|
|
20
|
+
if (d in counts) counts[d]++;
|
|
21
|
+
}
|
|
22
|
+
return { total: activity.length, ...counts };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function collapsedFeed(activity, { expanded = false, maxRows = 8 } = {}) {
|
|
26
|
+
const sum = activitySummary(activity);
|
|
27
|
+
if (sum.total === 0) return dim("No activity yet. Ask Cirvix to evaluate something.");
|
|
28
|
+
|
|
29
|
+
const head = `${style("✓", "allow")} ${bold(summaryTitle(sum))}\n\n` +
|
|
30
|
+
` ${dim(`${sum.total} actions`)}\n` +
|
|
31
|
+
` ${dim("├─")} ${sum.allow} allowed\n` +
|
|
32
|
+
` ${dim("├─")} ${sum.sanitize} sanitized\n` +
|
|
33
|
+
` ${dim("└─")} ${sum.deny + sum.require_approval} blocked/held\n\n` +
|
|
34
|
+
` ${dim("[Enter] inspect")}`;
|
|
35
|
+
|
|
36
|
+
if (!expanded) return head;
|
|
37
|
+
|
|
38
|
+
const rows = activity.slice(-maxRows).map((e) => activityRow(e)).join("\n");
|
|
39
|
+
const more = sum.total > maxRows ? dim(` … ${sum.total - maxRows} earlier (collapsed)`) + "\n" : "";
|
|
40
|
+
return `▼ ${bold("Activity")}\n\n${more}${rows}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function summaryTitle(sum) {
|
|
44
|
+
if (sum.deny > 0) return `Session activity — ${sum.deny} blocked`;
|
|
45
|
+
if (sum.require_approval > 0) return `Session activity — ${sum.require_approval} held`;
|
|
46
|
+
return `Session activity — all clear`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function activityRow(event) {
|
|
50
|
+
const d = event.decision ?? event.raw?.decision ?? "allow";
|
|
51
|
+
const badge = badgeForDecision(d);
|
|
52
|
+
const role = roleForDecision(d);
|
|
53
|
+
const when = clock(event.ts);
|
|
54
|
+
const tool = event.tool ?? event.raw?.tool ?? "—";
|
|
55
|
+
const target = truncate(event.resource ?? event.raw?.resource ?? "", 36);
|
|
56
|
+
const suffix = d === "deny" ? style(" → blocked", "block")
|
|
57
|
+
: d === "sanitize" ? style(" → sanitized", "sanitize")
|
|
58
|
+
: d === "require_approval" ? style(" → held", "hold")
|
|
59
|
+
: "";
|
|
60
|
+
return ` ${dim(when)} ${style(`${badge.icon} ${tool}`, role)}${target ? dim(` ${target}`) : ""}${suffix}`;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function clock(ts) {
|
|
64
|
+
const m = String(ts ?? "").match(/T(\d{2}:\d{2}:\d{2})/);
|
|
65
|
+
return m ? m[1] : "--:--:--";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function truncate(s, n) {
|
|
69
|
+
const v = String(s ?? "");
|
|
70
|
+
return v.length <= n ? v : `…${v.slice(-(n - 1))}`;
|
|
71
|
+
}
|