@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,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Runs the Agent Escape Benchmark and prints the result.
|
|
4
|
+
*
|
|
5
|
+
* Exits non-zero when anything escapes, so it can gate a release. A benchmark
|
|
6
|
+
* that always exits 0 is a slideshow.
|
|
7
|
+
*
|
|
8
|
+
* node bin/escape-benchmark.mjs # human-readable
|
|
9
|
+
* node bin/escape-benchmark.mjs --json # machine-readable
|
|
10
|
+
* node bin/escape-benchmark.mjs --out FILE # write JSON evidence
|
|
11
|
+
*/
|
|
12
|
+
import { writeFileSync } from "node:fs";
|
|
13
|
+
import { runBenchmark } from "../src/core/escape-benchmark.mjs";
|
|
14
|
+
|
|
15
|
+
const argv = process.argv.slice(2);
|
|
16
|
+
const asJson = argv.includes("--json");
|
|
17
|
+
const outAt = argv.indexOf("--out");
|
|
18
|
+
const outFile = outAt === -1 ? null : argv[outAt + 1];
|
|
19
|
+
|
|
20
|
+
const r = await runBenchmark();
|
|
21
|
+
|
|
22
|
+
if (outFile) {
|
|
23
|
+
/* The full results, including every step — the summary alone is a claim,
|
|
24
|
+
and the steps are what let someone else check it. */
|
|
25
|
+
writeFileSync(outFile, JSON.stringify(r, null, 2));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (asJson) {
|
|
29
|
+
console.log(JSON.stringify(r, null, 2));
|
|
30
|
+
} else {
|
|
31
|
+
const bar = "─".repeat(58);
|
|
32
|
+
console.log(bar);
|
|
33
|
+
console.log(" CIRVIX AGENT ESCAPE BENCHMARK");
|
|
34
|
+
console.log(bar);
|
|
35
|
+
console.log(` Scenarios ${String(r.scenarios).padStart(5)}`);
|
|
36
|
+
console.log(` Escape attempts ${String(r.attempts).padStart(5)}`);
|
|
37
|
+
console.log(` Blocked ${String(r.blocked).padStart(5)}`);
|
|
38
|
+
console.log(` Successful escapes ${String(r.escaped).padStart(5)}`);
|
|
39
|
+
console.log(` Authorization integrity ${String(r.integrity + "%").padStart(5)}`);
|
|
40
|
+
console.log(` Controls held ${String(`${r.controls.held}/${r.controls.total}`).padStart(5)}`);
|
|
41
|
+
console.log(bar);
|
|
42
|
+
for (const [k, v] of Object.entries(r.byCategory)) {
|
|
43
|
+
console.log(` ${k.replace(/_/g, " ").toUpperCase().padEnd(26)} ${String(v.attempts).padStart(4)}`);
|
|
44
|
+
console.log(` blocked ${String(v.blocked).padStart(4)}${v.escaped ? ` ESCAPED ${v.escaped}` : ""}`);
|
|
45
|
+
}
|
|
46
|
+
console.log(bar);
|
|
47
|
+
if (r.escapes.length) {
|
|
48
|
+
console.log("\n ESCAPES — investigate before shipping:");
|
|
49
|
+
for (const e of r.escapes) {
|
|
50
|
+
console.log(` ${e.id} ${e.name} [${e.category}, level ${e.level}]`);
|
|
51
|
+
for (const s of e.steps) console.log(` ${s.tool} → ${s.verdict} (${s.rule})`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
if (r.brokenControls.length) {
|
|
55
|
+
console.log("\n BROKEN CONTROLS — the mission cannot do its own job:");
|
|
56
|
+
for (const b of r.brokenControls) {
|
|
57
|
+
console.log(` ${b.id} ${b.name}`);
|
|
58
|
+
for (const s of b.steps) console.log(` ${s.tool} → ${s.verdict} (${s.rule})`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (!r.escapes.length && !r.brokenControls.length) {
|
|
62
|
+
console.log("\n Every attempt contained; every mission still able to work.");
|
|
63
|
+
}
|
|
64
|
+
console.log(`\n ${r.generatedAt}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
process.exit(r.escaped > 0 || r.brokenControls.length > 0 ? 1 : 0);
|
package/package.json
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cirvix_ai/agent-control",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Cirvix AgentControl
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Cirvix AgentControl \u2014 runtime governance for AI agents. Scan what is ungoverned, evaluate policy, and broker tool calls.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
+
"homepage": "https://cirvix.com/",
|
|
6
7
|
"type": "module",
|
|
7
|
-
"homepage": "https://www.cirvix.com",
|
|
8
|
-
"repository": {
|
|
9
|
-
"type": "git",
|
|
10
|
-
"url": "git+https://github.com/CIRVIX/agent-control.git",
|
|
11
|
-
"directory": "packages/agent-control"
|
|
12
|
-
},
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://github.com/CIRVIX/agent-control/issues"
|
|
15
|
-
},
|
|
16
|
-
"publishConfig": {
|
|
17
|
-
"access": "public"
|
|
18
|
-
},
|
|
19
8
|
"bin": {
|
|
20
9
|
"cirvix": "./bin/cirvix.mjs"
|
|
21
10
|
},
|
|
22
11
|
"engines": {
|
|
23
12
|
"node": ">=20"
|
|
24
13
|
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/CIRVIX/agent-control"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public",
|
|
20
|
+
"provenance": true
|
|
21
|
+
},
|
|
25
22
|
"files": [
|
|
26
23
|
"bin",
|
|
27
24
|
"src",
|
|
@@ -36,7 +33,8 @@
|
|
|
36
33
|
"bench": "node ../../benchmarks/decision.mjs",
|
|
37
34
|
"scan": "node bin/cirvix.mjs scan",
|
|
38
35
|
"demo": "node bin/cirvix.mjs demo",
|
|
39
|
-
"verify:adversarial": "node test/adversarial/verify.mjs"
|
|
36
|
+
"verify:adversarial": "node test/adversarial/verify.mjs",
|
|
37
|
+
"benchmark:escape": "node bin/escape-benchmark.mjs"
|
|
40
38
|
},
|
|
41
39
|
"keywords": [
|
|
42
40
|
"ai",
|
|
@@ -55,6 +53,28 @@
|
|
|
55
53
|
"./audit": "./src/core/audit.mjs",
|
|
56
54
|
"./secrets": "./src/core/secrets.mjs",
|
|
57
55
|
"./guard": "./src/core/guard.mjs",
|
|
58
|
-
"./testing": "./src/testing.mjs"
|
|
56
|
+
"./testing": "./src/testing.mjs",
|
|
57
|
+
"./passport": "./src/core/passport.mjs",
|
|
58
|
+
"./cost": "./src/core/cost.mjs",
|
|
59
|
+
"./proof": "./src/core/proof.mjs",
|
|
60
|
+
"./trifecta": "./src/core/trifecta.mjs",
|
|
61
|
+
"./packs": "./src/core/packs.mjs",
|
|
62
|
+
"./graph": "./src/core/graph.mjs",
|
|
63
|
+
"./evidence": "./src/core/evidence.mjs",
|
|
64
|
+
"./referral": "./src/core/referral.mjs",
|
|
65
|
+
"./authority": "./src/core/authority.mjs",
|
|
66
|
+
"./delegation": "./src/core/delegation.mjs",
|
|
67
|
+
"./escape-benchmark": "./src/core/escape-benchmark.mjs",
|
|
68
|
+
"./adapters": "./src/adapters/index.mjs",
|
|
69
|
+
"./windows": "./src/core/windows.mjs",
|
|
70
|
+
"./config-store": "./src/core/config-store.mjs",
|
|
71
|
+
"./intent": "./src/core/intent.mjs",
|
|
72
|
+
"./session": "./src/core/session.mjs",
|
|
73
|
+
"./baseline": "./src/core/baseline.mjs",
|
|
74
|
+
"./kill-switch": "./src/core/kill-switch.mjs",
|
|
75
|
+
"./sandbox": "./src/core/sandbox.mjs",
|
|
76
|
+
"./shadow": "./src/core/shadow.mjs",
|
|
77
|
+
"./redteam": "./src/core/redteam/index.mjs",
|
|
78
|
+
"./verified": "./src/core/verified.mjs"
|
|
59
79
|
}
|
|
60
80
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Agent Adapter interface and definitions for CIRVIX AgentControl.
|
|
3
|
+
*
|
|
4
|
+
* Provides the unified foundation for runtime/editor/framework-agnostic
|
|
5
|
+
* governance. Each adapter implements detection, configuration lifecycle,
|
|
6
|
+
* and request/response normalization for a specific agent environment.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { access, readFile, writeFile } from "node:fs/promises";
|
|
10
|
+
import { resolve } from "node:path";
|
|
11
|
+
import { parseConfigJson } from "../core/config-store.mjs";
|
|
12
|
+
import { normalizeFsPath, arePathsEqual } from "../core/windows.mjs";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Strict Compatibility Levels.
|
|
16
|
+
* Never claim "supported" or "enforced" without empirical verification.
|
|
17
|
+
*/
|
|
18
|
+
export const COMPATIBILITY_LEVEL = {
|
|
19
|
+
/** Config file or executable exists on disk. */
|
|
20
|
+
DISCOVERED: "DISCOVERED",
|
|
21
|
+
/** Valid configuration found; CIRVIX adapter can generate an integration plan. */
|
|
22
|
+
CONFIGURABLE: "CONFIGURABLE",
|
|
23
|
+
/** Configuration routes agent tool traffic to CIRVIX. */
|
|
24
|
+
INTEGRATED: "INTEGRATED",
|
|
25
|
+
/** Gateway or runtime has received protocol handshake/initialize from this agent. */
|
|
26
|
+
ROUTED: "ROUTED",
|
|
27
|
+
/** Policy engine has evaluated at least one tool call from this agent. */
|
|
28
|
+
ENFORCED: "ENFORCED",
|
|
29
|
+
/** Full end-to-end tool invocation passed through CIRVIX with verified audit entry. */
|
|
30
|
+
VERIFIED: "VERIFIED",
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export class BaseAgentAdapter {
|
|
34
|
+
/**
|
|
35
|
+
* @param {object} spec
|
|
36
|
+
* @param {string} spec.id - Unique ID, e.g. "claude-code", "cursor", "codex"
|
|
37
|
+
* @param {string} spec.label - Human readable name, e.g. "Claude Code"
|
|
38
|
+
* @param {string} spec.type - "cli" | "editor" | "extension" | "framework" | "generic"
|
|
39
|
+
* @param {string} [spec.mcpKey="mcpServers"] - Property holding MCP server definitions
|
|
40
|
+
*/
|
|
41
|
+
constructor({ id, label, type, mcpKey = "mcpServers" }) {
|
|
42
|
+
this.id = id;
|
|
43
|
+
this.label = label;
|
|
44
|
+
this.type = type;
|
|
45
|
+
this.mcpKey = mcpKey;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Discovers presence of this runtime on the host and in the workspace.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} cwd
|
|
52
|
+
* @returns {Promise<{ detected: boolean, paths: string[], config: object|null, executable: string|null, version: string|null, metadata: object }>}
|
|
53
|
+
*/
|
|
54
|
+
async detect(cwd) {
|
|
55
|
+
throw new Error(`detect() not implemented on ${this.id}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Determines the strict compatibility level of this agent.
|
|
60
|
+
*
|
|
61
|
+
* @param {object} detectedInfo
|
|
62
|
+
* @param {string} [stateDir]
|
|
63
|
+
* @returns {Promise<string>} One of COMPATIBILITY_LEVEL values
|
|
64
|
+
*/
|
|
65
|
+
async getCompatibilityLevel(detectedInfo, stateDir) {
|
|
66
|
+
if (!detectedInfo || !detectedInfo.detected) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { isIntegrated, hasConfig, hasAuditLogs, hasVerifiedCall } = detectedInfo;
|
|
71
|
+
|
|
72
|
+
if (hasVerifiedCall) return COMPATIBILITY_LEVEL.VERIFIED;
|
|
73
|
+
if (hasAuditLogs) return COMPATIBILITY_LEVEL.ENFORCED;
|
|
74
|
+
if (detectedInfo.isRouted) return COMPATIBILITY_LEVEL.ROUTED;
|
|
75
|
+
if (isIntegrated) return COMPATIBILITY_LEVEL.INTEGRATED;
|
|
76
|
+
if (hasConfig) return COMPATIBILITY_LEVEL.CONFIGURABLE;
|
|
77
|
+
return COMPATIBILITY_LEVEL.DISCOVERED;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Helper to check if a file path exists and is readable.
|
|
82
|
+
*/
|
|
83
|
+
async fileExists(filePath) {
|
|
84
|
+
try {
|
|
85
|
+
await access(filePath);
|
|
86
|
+
return true;
|
|
87
|
+
} catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Reads and parses a JSON/JSONC configuration file.
|
|
94
|
+
*/
|
|
95
|
+
async readJson(filePath) {
|
|
96
|
+
try {
|
|
97
|
+
const raw = await readFile(filePath, "utf8");
|
|
98
|
+
return parseConfigJson(raw);
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Checks if an MCP server definition belongs to CIRVIX.
|
|
106
|
+
*/
|
|
107
|
+
isCirvixServer(name, def) {
|
|
108
|
+
if (name === "cirvix") return true;
|
|
109
|
+
if (typeof def?.command === "string" && def.command.includes("cirvix")) return true;
|
|
110
|
+
if (Array.isArray(def?.args) && def.args.some((a) => typeof a === "string" && a.includes("cirvix"))) return true;
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generates a non-destructive integration plan.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} cwd
|
|
118
|
+
* @param {object} options
|
|
119
|
+
* @returns {Promise<{ adapterId: string, label: string, targetFile: string, canIntegrate: boolean, currentServers: Record<string, any>, plan: object, reason: string }>}
|
|
120
|
+
*/
|
|
121
|
+
async generateIntegrationPlan(cwd, options = {}) {
|
|
122
|
+
throw new Error(`generateIntegrationPlan() not implemented on ${this.id}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Normalizes a tool call into the universal CIRVIX format.
|
|
127
|
+
*/
|
|
128
|
+
normalizeRequest(rawRequest, ctx = {}) {
|
|
129
|
+
const rawArgs = rawRequest.args ?? rawRequest.arguments ?? rawRequest.tool_input ?? rawRequest.toolInput ?? {};
|
|
130
|
+
return {
|
|
131
|
+
agent: this.id,
|
|
132
|
+
runtime: this.label,
|
|
133
|
+
...rawRequest,
|
|
134
|
+
args: rawArgs,
|
|
135
|
+
arguments: rawArgs,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Formats a CIRVIX decision into the response expected by this runtime.
|
|
141
|
+
*/
|
|
142
|
+
formatResponse(decision, rawResult) {
|
|
143
|
+
return {
|
|
144
|
+
decision: decision.decision,
|
|
145
|
+
verdict: decision.verdict,
|
|
146
|
+
reason: decision.reason,
|
|
147
|
+
rawResult,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Adapter for CIRVIX AgentControl.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - ~/.claude/settings.json, ~/.claude.json, workspace .claude/settings.json
|
|
6
|
+
* - PreToolUse hook bridging for non-MCP built-in tools (Bash, Read, Write, Edit, WebFetch)
|
|
7
|
+
* - CLAUDE.md instruction integration
|
|
8
|
+
* - Executable detection (claude / claude.cmd)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { BaseAgentAdapter, COMPATIBILITY_LEVEL } from "./base.mjs";
|
|
14
|
+
import { resolveExecutable } from "../core/windows.mjs";
|
|
15
|
+
|
|
16
|
+
export class ClaudeCodeAdapter extends BaseAgentAdapter {
|
|
17
|
+
constructor() {
|
|
18
|
+
super({
|
|
19
|
+
id: "claude-code",
|
|
20
|
+
label: "Claude Code",
|
|
21
|
+
type: "cli",
|
|
22
|
+
mcpKey: "mcpServers",
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async detect(cwd) {
|
|
27
|
+
const home = homedir();
|
|
28
|
+
const candidatePaths = [
|
|
29
|
+
join(cwd, ".claude", "settings.json"),
|
|
30
|
+
join(cwd, ".claude.json"),
|
|
31
|
+
join(home, ".claude", "settings.json"),
|
|
32
|
+
join(home, ".claude.json"),
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const existingPaths = [];
|
|
36
|
+
const servers = {};
|
|
37
|
+
let configFound = null;
|
|
38
|
+
|
|
39
|
+
for (const p of candidatePaths) {
|
|
40
|
+
if (await this.fileExists(p)) {
|
|
41
|
+
existingPaths.push(p);
|
|
42
|
+
const data = await this.readJson(p);
|
|
43
|
+
if (data) {
|
|
44
|
+
if (!configFound) configFound = data;
|
|
45
|
+
Object.assign(servers, data[this.mcpKey] ?? {});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const execPath = resolveExecutable("claude", { cwd });
|
|
51
|
+
const hasInstructions = await this.fileExists(join(cwd, "CLAUDE.md"));
|
|
52
|
+
const detected = existingPaths.length > 0 || Boolean(execPath) || hasInstructions;
|
|
53
|
+
|
|
54
|
+
const isIntegrated = Object.entries(servers).some(([name, def]) => this.isCirvixServer(name, def));
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
detected,
|
|
58
|
+
paths: existingPaths,
|
|
59
|
+
targetConfigPath: existingPaths[0] ?? join(home, ".claude", "settings.json"),
|
|
60
|
+
config: configFound,
|
|
61
|
+
servers,
|
|
62
|
+
serverCount: Object.keys(servers).length,
|
|
63
|
+
isIntegrated,
|
|
64
|
+
hasConfig: existingPaths.length > 0,
|
|
65
|
+
executable: execPath,
|
|
66
|
+
metadata: {
|
|
67
|
+
hasInstructions,
|
|
68
|
+
hookSupported: true,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async generateIntegrationPlan(cwd, options = {}) {
|
|
74
|
+
const info = await this.detect(cwd);
|
|
75
|
+
const targetFile = info.targetConfigPath;
|
|
76
|
+
const currentServers = { ...info.servers };
|
|
77
|
+
|
|
78
|
+
// Upstream servers without cirvix itself
|
|
79
|
+
const upstreams = {};
|
|
80
|
+
for (const [k, v] of Object.entries(currentServers)) {
|
|
81
|
+
if (!this.isCirvixServer(k, v)) upstreams[k] = v;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const cirvixServerDef = {
|
|
85
|
+
command: "cirvix",
|
|
86
|
+
args: ["gateway", "--servers", targetFile.replace(/\\/g, "/")],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const newConfig = {
|
|
90
|
+
...(info.config || {}),
|
|
91
|
+
[this.mcpKey]: {
|
|
92
|
+
...currentServers,
|
|
93
|
+
cirvix: cirvixServerDef,
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
adapterId: this.id,
|
|
99
|
+
label: this.label,
|
|
100
|
+
targetFile,
|
|
101
|
+
canIntegrate: true,
|
|
102
|
+
currentServers,
|
|
103
|
+
upstreams,
|
|
104
|
+
plan: newConfig,
|
|
105
|
+
snippet: JSON.stringify({ [this.mcpKey]: { cirvix: cirvixServerDef } }, null, 2),
|
|
106
|
+
reason: "Routes Claude Code MCP tool invocations through the CIRVIX gateway.",
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
normalizeRequest(rawRequest, ctx = {}) {
|
|
111
|
+
// Built-in tools mapping if called from PreToolUse hook
|
|
112
|
+
const name = rawRequest.tool_name ?? rawRequest.toolName ?? rawRequest.tool ?? "";
|
|
113
|
+
const args = rawRequest.tool_input ?? rawRequest.toolInput ?? rawRequest.arguments ?? {};
|
|
114
|
+
|
|
115
|
+
let tool = name;
|
|
116
|
+
let normalizedArgs = { ...args };
|
|
117
|
+
|
|
118
|
+
switch (name) {
|
|
119
|
+
case "Bash":
|
|
120
|
+
tool = "shell.exec";
|
|
121
|
+
normalizedArgs = { command: args.command ?? "" };
|
|
122
|
+
break;
|
|
123
|
+
case "Read":
|
|
124
|
+
tool = "filesystem.read";
|
|
125
|
+
normalizedArgs = { path: args.file_path ?? args.path ?? "" };
|
|
126
|
+
break;
|
|
127
|
+
case "Write":
|
|
128
|
+
tool = "filesystem.write";
|
|
129
|
+
normalizedArgs = { path: args.file_path ?? args.path ?? "" };
|
|
130
|
+
break;
|
|
131
|
+
case "Edit":
|
|
132
|
+
case "NotebookEdit":
|
|
133
|
+
tool = "filesystem.write";
|
|
134
|
+
normalizedArgs = { path: args.file_path ?? args.notebook_path ?? "" };
|
|
135
|
+
break;
|
|
136
|
+
case "Glob":
|
|
137
|
+
tool = "filesystem.list";
|
|
138
|
+
normalizedArgs = { path: args.path ?? args.pattern ?? "" };
|
|
139
|
+
break;
|
|
140
|
+
case "Grep":
|
|
141
|
+
tool = "filesystem.search";
|
|
142
|
+
normalizedArgs = { path: args.path ?? "" };
|
|
143
|
+
break;
|
|
144
|
+
case "WebFetch":
|
|
145
|
+
tool = "network.request";
|
|
146
|
+
normalizedArgs = { url: args.url ?? "" };
|
|
147
|
+
break;
|
|
148
|
+
default:
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
agent: this.id,
|
|
154
|
+
runtime: this.label,
|
|
155
|
+
tool,
|
|
156
|
+
args: normalizedArgs,
|
|
157
|
+
arguments: normalizedArgs,
|
|
158
|
+
source: rawRequest.source ?? "claude-code",
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cline Adapter for CIRVIX AgentControl.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - VS Code globalStorage cline_mcp_settings.json across Windows, macOS, Linux
|
|
6
|
+
* - Workspace .cline/mcp_settings.json
|
|
7
|
+
* - .clinerules
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { homedir } from "node:os";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { BaseAgentAdapter } from "./base.mjs";
|
|
13
|
+
|
|
14
|
+
export class ClineAdapter extends BaseAgentAdapter {
|
|
15
|
+
constructor() {
|
|
16
|
+
super({
|
|
17
|
+
id: "cline",
|
|
18
|
+
label: "Cline",
|
|
19
|
+
type: "extension",
|
|
20
|
+
mcpKey: "mcpServers",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async detect(cwd) {
|
|
25
|
+
const home = homedir();
|
|
26
|
+
const candidatePaths = [
|
|
27
|
+
join(cwd, ".cline", "mcp_settings.json"),
|
|
28
|
+
// Windows
|
|
29
|
+
join(home, "AppData", "Roaming", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
30
|
+
// macOS
|
|
31
|
+
join(home, "Library", "Application Support", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
32
|
+
// Linux
|
|
33
|
+
join(home, ".config", "Code", "User", "globalStorage", "saoudrizwan.claude-dev", "settings", "cline_mcp_settings.json"),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const existingPaths = [];
|
|
37
|
+
const servers = {};
|
|
38
|
+
let configFound = null;
|
|
39
|
+
|
|
40
|
+
for (const p of candidatePaths) {
|
|
41
|
+
if (await this.fileExists(p)) {
|
|
42
|
+
existingPaths.push(p);
|
|
43
|
+
const data = await this.readJson(p);
|
|
44
|
+
if (data) {
|
|
45
|
+
if (!configFound) configFound = data;
|
|
46
|
+
Object.assign(servers, data[this.mcpKey] ?? {});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const hasRules = await this.fileExists(join(cwd, ".clinerules"));
|
|
52
|
+
const detected = existingPaths.length > 0 || hasRules;
|
|
53
|
+
|
|
54
|
+
const isIntegrated = Object.entries(servers).some(([name, def]) => this.isCirvixServer(name, def));
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
detected,
|
|
58
|
+
paths: existingPaths,
|
|
59
|
+
targetConfigPath: existingPaths[0] ?? candidatePaths[1],
|
|
60
|
+
config: configFound,
|
|
61
|
+
servers,
|
|
62
|
+
serverCount: Object.keys(servers).length,
|
|
63
|
+
isIntegrated,
|
|
64
|
+
hasConfig: existingPaths.length > 0,
|
|
65
|
+
executable: null,
|
|
66
|
+
metadata: {
|
|
67
|
+
hasRules,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async generateIntegrationPlan(cwd, options = {}) {
|
|
73
|
+
const info = await this.detect(cwd);
|
|
74
|
+
const targetFile = info.targetConfigPath;
|
|
75
|
+
const currentServers = { ...info.servers };
|
|
76
|
+
|
|
77
|
+
const upstreams = {};
|
|
78
|
+
for (const [k, v] of Object.entries(currentServers)) {
|
|
79
|
+
if (!this.isCirvixServer(k, v)) upstreams[k] = v;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const cirvixServerDef = {
|
|
83
|
+
command: "cirvix",
|
|
84
|
+
args: ["gateway", "--servers", targetFile.replace(/\\/g, "/")],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const newConfig = {
|
|
88
|
+
...(info.config || {}),
|
|
89
|
+
[this.mcpKey]: {
|
|
90
|
+
...currentServers,
|
|
91
|
+
cirvix: cirvixServerDef,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
adapterId: this.id,
|
|
97
|
+
label: this.label,
|
|
98
|
+
targetFile,
|
|
99
|
+
canIntegrate: true,
|
|
100
|
+
currentServers,
|
|
101
|
+
upstreams,
|
|
102
|
+
plan: newConfig,
|
|
103
|
+
snippet: JSON.stringify({ [this.mcpKey]: { cirvix: cirvixServerDef } }, null, 2),
|
|
104
|
+
reason: "Binds Cline MCP server actions to CIRVIX policy evaluation.",
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAI Codex CLI Adapter for CIRVIX AgentControl.
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - ~/.codex/config.json, codex.json, .codex/mcp.json
|
|
6
|
+
* - Executable detection (codex / codex.cmd)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { BaseAgentAdapter } from "./base.mjs";
|
|
12
|
+
import { resolveExecutable } from "../core/windows.mjs";
|
|
13
|
+
|
|
14
|
+
export class CodexAdapter extends BaseAgentAdapter {
|
|
15
|
+
constructor() {
|
|
16
|
+
super({
|
|
17
|
+
id: "codex",
|
|
18
|
+
label: "OpenAI Codex CLI",
|
|
19
|
+
type: "cli",
|
|
20
|
+
mcpKey: "mcpServers",
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async detect(cwd) {
|
|
25
|
+
const home = homedir();
|
|
26
|
+
const candidatePaths = [
|
|
27
|
+
join(cwd, "codex.json"),
|
|
28
|
+
join(cwd, ".codex", "config.json"),
|
|
29
|
+
join(cwd, ".codex", "mcp.json"),
|
|
30
|
+
join(home, ".codex", "config.json"),
|
|
31
|
+
join(home, ".codex", "mcp.json"),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const existingPaths = [];
|
|
35
|
+
const servers = {};
|
|
36
|
+
let configFound = null;
|
|
37
|
+
|
|
38
|
+
for (const p of candidatePaths) {
|
|
39
|
+
if (await this.fileExists(p)) {
|
|
40
|
+
existingPaths.push(p);
|
|
41
|
+
const data = await this.readJson(p);
|
|
42
|
+
if (data) {
|
|
43
|
+
if (!configFound) configFound = data;
|
|
44
|
+
const map = data.mcpServers ?? data.mcp_servers ?? data.servers ?? {};
|
|
45
|
+
Object.assign(servers, map);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const execPath = resolveExecutable("codex", { cwd });
|
|
51
|
+
const detected = existingPaths.length > 0 || Boolean(execPath);
|
|
52
|
+
|
|
53
|
+
const isIntegrated = Object.entries(servers).some(([name, def]) => this.isCirvixServer(name, def));
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
detected,
|
|
57
|
+
paths: existingPaths,
|
|
58
|
+
targetConfigPath: existingPaths[0] ?? join(home, ".codex", "config.json"),
|
|
59
|
+
config: configFound,
|
|
60
|
+
servers,
|
|
61
|
+
serverCount: Object.keys(servers).length,
|
|
62
|
+
isIntegrated,
|
|
63
|
+
hasConfig: existingPaths.length > 0,
|
|
64
|
+
executable: execPath,
|
|
65
|
+
metadata: {},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async generateIntegrationPlan(cwd, options = {}) {
|
|
70
|
+
const info = await this.detect(cwd);
|
|
71
|
+
const targetFile = info.targetConfigPath;
|
|
72
|
+
const currentServers = { ...info.servers };
|
|
73
|
+
|
|
74
|
+
const upstreams = {};
|
|
75
|
+
for (const [k, v] of Object.entries(currentServers)) {
|
|
76
|
+
if (!this.isCirvixServer(k, v)) upstreams[k] = v;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const cirvixServerDef = {
|
|
80
|
+
command: "cirvix",
|
|
81
|
+
args: ["gateway", "--servers", targetFile.replace(/\\/g, "/")],
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const newConfig = {
|
|
85
|
+
...(info.config || {}),
|
|
86
|
+
[this.mcpKey]: {
|
|
87
|
+
...currentServers,
|
|
88
|
+
cirvix: cirvixServerDef,
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
adapterId: this.id,
|
|
94
|
+
label: this.label,
|
|
95
|
+
targetFile,
|
|
96
|
+
canIntegrate: true,
|
|
97
|
+
currentServers,
|
|
98
|
+
upstreams,
|
|
99
|
+
plan: newConfig,
|
|
100
|
+
snippet: JSON.stringify({ [this.mcpKey]: { cirvix: cirvixServerDef } }, null, 2),
|
|
101
|
+
reason: "Places CIRVIX policy enforcement between Codex CLI and local execution tools.",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|