@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.
Files changed (81) hide show
  1. package/README.md +76 -17
  2. package/bin/cirvix.mjs +539 -85
  3. package/bin/escape-benchmark.mjs +67 -0
  4. package/package.json +36 -16
  5. package/src/adapters/base.mjs +150 -0
  6. package/src/adapters/claude-code.mjs +161 -0
  7. package/src/adapters/cline.mjs +107 -0
  8. package/src/adapters/codex.mjs +104 -0
  9. package/src/adapters/cursor.mjs +104 -0
  10. package/src/adapters/frameworks.mjs +110 -0
  11. package/src/adapters/gemini-cli.mjs +104 -0
  12. package/src/adapters/generic-mcp.mjs +101 -0
  13. package/src/adapters/index.mjs +209 -0
  14. package/src/adapters/roo-code.mjs +106 -0
  15. package/src/adapters/vscode.mjs +104 -0
  16. package/src/adapters/windsurf.mjs +107 -0
  17. package/src/commands/console.mjs +58 -0
  18. package/src/commands/demo.mjs +55 -124
  19. package/src/commands/doctor.mjs +235 -0
  20. package/src/commands/init.mjs +292 -30
  21. package/src/commands/interactive.mjs +690 -0
  22. package/src/commands/kill.mjs +74 -0
  23. package/src/commands/login.mjs +227 -0
  24. package/src/commands/onboard.mjs +52 -0
  25. package/src/commands/passport.mjs +149 -0
  26. package/src/commands/policy.mjs +10 -6
  27. package/src/commands/protect.mjs +293 -0
  28. package/src/commands/prove.mjs +209 -0
  29. package/src/commands/redteam.mjs +51 -0
  30. package/src/commands/scan.mjs +11 -9
  31. package/src/commands/shadow.mjs +62 -0
  32. package/src/commands/simulate.mjs +96 -0
  33. package/src/commands/status.mjs +122 -41
  34. package/src/commands/upgrade.mjs +11 -11
  35. package/src/commands/welcome.mjs +105 -0
  36. package/src/core/authority.mjs +909 -0
  37. package/src/core/baseline.mjs +97 -0
  38. package/src/core/config-store.mjs +280 -0
  39. package/src/core/cost.mjs +0 -0
  40. package/src/core/detect.mjs +4 -33
  41. package/src/core/entitlements.mjs +7 -24
  42. package/src/core/escape-benchmark.mjs +597 -0
  43. package/src/core/events.mjs +234 -0
  44. package/src/core/evidence.mjs +212 -0
  45. package/src/core/format.mjs +44 -18
  46. package/src/core/gateway.mjs +15 -211
  47. package/src/core/graph.mjs +270 -0
  48. package/src/core/guard.mjs +118 -4
  49. package/src/core/intent.mjs +166 -0
  50. package/src/core/journal.mjs +131 -40
  51. package/src/core/kill-switch.mjs +122 -0
  52. package/src/core/notices.mjs +22 -2
  53. package/src/core/packs.mjs +193 -0
  54. package/src/core/passport.mjs +555 -0
  55. package/src/core/pipeline.mjs +148 -6
  56. package/src/core/prompts.mjs +51 -0
  57. package/src/core/proof.mjs +440 -0
  58. package/src/core/redteam/index.mjs +185 -0
  59. package/src/core/referral.mjs +187 -0
  60. package/src/core/sandbox.mjs +139 -0
  61. package/src/core/session.mjs +172 -0
  62. package/src/core/shadow.mjs +95 -0
  63. package/src/core/theme.mjs +240 -0
  64. package/src/core/trifecta.mjs +321 -0
  65. package/src/core/ui/controller.mjs +192 -0
  66. package/src/core/ui/decisions.mjs +55 -0
  67. package/src/core/ui/index.mjs +49 -0
  68. package/src/core/ui/intercept.mjs +103 -0
  69. package/src/core/ui/live.mjs +51 -0
  70. package/src/core/ui/primitives.mjs +123 -0
  71. package/src/core/ui/theme.mjs +92 -0
  72. package/src/core/verified.mjs +108 -0
  73. package/src/core/windows.mjs +270 -0
  74. package/src/index.mjs +67 -0
  75. package/src/tui/activity.mjs +71 -0
  76. package/src/tui/app.mjs +292 -0
  77. package/src/tui/cards.mjs +235 -0
  78. package/src/tui/composer.mjs +88 -0
  79. package/src/tui/palette.mjs +48 -0
  80. package/src/tui/status.mjs +42 -0
  81. package/src/core/cinematic.mjs +0 -545
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Cursor Adapter for CIRVIX AgentControl.
3
+ *
4
+ * Supports:
5
+ * - ~/.cursor/mcp.json and project .cursor/mcp.json
6
+ * - .cursorrules and .cursor/rules/
7
+ * - Executable detection (cursor / cursor.cmd)
8
+ */
9
+
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { BaseAgentAdapter } from "./base.mjs";
13
+ import { resolveExecutable } from "../core/windows.mjs";
14
+
15
+ export class CursorAdapter extends BaseAgentAdapter {
16
+ constructor() {
17
+ super({
18
+ id: "cursor",
19
+ label: "Cursor",
20
+ type: "editor",
21
+ mcpKey: "mcpServers",
22
+ });
23
+ }
24
+
25
+ async detect(cwd) {
26
+ const home = homedir();
27
+ const candidatePaths = [
28
+ join(cwd, ".cursor", "mcp.json"),
29
+ join(home, ".cursor", "mcp.json"),
30
+ ];
31
+
32
+ const existingPaths = [];
33
+ const servers = {};
34
+ let configFound = null;
35
+
36
+ for (const p of candidatePaths) {
37
+ if (await this.fileExists(p)) {
38
+ existingPaths.push(p);
39
+ const data = await this.readJson(p);
40
+ if (data) {
41
+ if (!configFound) configFound = data;
42
+ Object.assign(servers, data[this.mcpKey] ?? {});
43
+ }
44
+ }
45
+ }
46
+
47
+ const execPath = resolveExecutable("cursor", { cwd });
48
+ const hasRules = (await this.fileExists(join(cwd, ".cursorrules"))) || (await this.fileExists(join(cwd, ".cursor", "rules")));
49
+ const detected = existingPaths.length > 0 || Boolean(execPath) || hasRules;
50
+
51
+ const isIntegrated = Object.entries(servers).some(([name, def]) => this.isCirvixServer(name, def));
52
+
53
+ return {
54
+ detected,
55
+ paths: existingPaths,
56
+ targetConfigPath: existingPaths[0] ?? join(home, ".cursor", "mcp.json"),
57
+ config: configFound,
58
+ servers,
59
+ serverCount: Object.keys(servers).length,
60
+ isIntegrated,
61
+ hasConfig: existingPaths.length > 0,
62
+ executable: execPath,
63
+ metadata: {
64
+ hasRules,
65
+ },
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: "Routes Cursor MCP tool calls through the CIRVIX runtime governance gateway.",
102
+ };
103
+ }
104
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Autonomous Agent Frameworks Adapter for CIRVIX AgentControl.
3
+ *
4
+ * Inspects declared dependencies in package.json, requirements.txt, and pyproject.toml
5
+ * for agent frameworks:
6
+ * - LangChain / LangGraph
7
+ * - CrewAI
8
+ * - OpenAI Agents SDK
9
+ * - Vercel AI SDK
10
+ * - Anthropic SDK
11
+ * - AutoGen
12
+ * - Model Context Protocol SDK
13
+ */
14
+
15
+ import { readFile } from "node:fs/promises";
16
+ import { join } from "node:path";
17
+ import { BaseAgentAdapter } from "./base.mjs";
18
+
19
+ const FRAMEWORK_SPECS = [
20
+ { id: "langchain", label: "LangChain / LangGraph", deps: ["langchain", "@langchain/core", "langgraph", "langchain-core"] },
21
+ { id: "crewai", label: "CrewAI", deps: ["crewai"] },
22
+ { id: "autogen", label: "AutoGen / AG2", deps: ["pyautogen", "autogen-agentchat", "autogen-core"] },
23
+ { id: "openai-agents", label: "OpenAI Agents SDK", deps: ["@openai/agents", "openai-agents"] },
24
+ { id: "vercel-ai", label: "Vercel AI SDK", deps: ["ai", "@ai-sdk/openai", "@ai-sdk/anthropic"] },
25
+ { id: "anthropic", label: "Anthropic SDK", deps: ["@anthropic-ai/sdk", "anthropic"] },
26
+ { id: "mcp-sdk", label: "MCP SDK", deps: ["@modelcontextprotocol/sdk", "mcp"] },
27
+ ];
28
+
29
+ export class FrameworksAdapter extends BaseAgentAdapter {
30
+ constructor() {
31
+ super({
32
+ id: "frameworks",
33
+ label: "Autonomous Agent Frameworks",
34
+ type: "framework",
35
+ mcpKey: "servers",
36
+ });
37
+ }
38
+
39
+ async detect(cwd) {
40
+ const hits = [];
41
+
42
+ // Check package.json
43
+ const pkg = await this.readJson(join(cwd, "package.json"));
44
+ if (pkg) {
45
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
46
+ for (const spec of FRAMEWORK_SPECS) {
47
+ const found = spec.deps.find((d) => d in deps);
48
+ if (found) {
49
+ hits.push({
50
+ id: spec.id,
51
+ label: spec.label,
52
+ via: `package.json -> ${found}`,
53
+ });
54
+ }
55
+ }
56
+ }
57
+
58
+ // Check Python files
59
+ for (const file of ["requirements.txt", "pyproject.toml"]) {
60
+ const path = join(cwd, file);
61
+ if (!(await this.fileExists(path))) continue;
62
+ let text = "";
63
+ try {
64
+ text = await readFile(path, "utf8");
65
+ } catch {
66
+ continue;
67
+ }
68
+
69
+ for (const spec of FRAMEWORK_SPECS) {
70
+ if (hits.some((h) => h.id === spec.id)) continue;
71
+ const found = spec.deps.find((d) => new RegExp(`(^|[\\s"'=])${d.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "m").test(text));
72
+ if (found) {
73
+ hits.push({
74
+ id: spec.id,
75
+ label: spec.label,
76
+ via: `${file} -> ${found}`,
77
+ });
78
+ }
79
+ }
80
+ }
81
+
82
+ return {
83
+ detected: hits.length > 0,
84
+ frameworks: hits,
85
+ paths: [],
86
+ targetConfigPath: null,
87
+ config: null,
88
+ servers: {},
89
+ serverCount: 0,
90
+ isIntegrated: false,
91
+ hasConfig: false,
92
+ executable: null,
93
+ metadata: { hits },
94
+ };
95
+ }
96
+
97
+ async generateIntegrationPlan(cwd, options = {}) {
98
+ return {
99
+ adapterId: this.id,
100
+ label: this.label,
101
+ targetFile: null,
102
+ canIntegrate: false,
103
+ currentServers: {},
104
+ upstreams: {},
105
+ plan: null,
106
+ snippet: `import { guard } from "@cirvix_ai/agent-control/guard";\nconst governedTools = guard.wrap(myTools, { agent: "worker" });`,
107
+ reason: "Agent frameworks in application code are instrumented via guard.wrap(tools, { agent, rules }).",
108
+ };
109
+ }
110
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Gemini CLI Adapter for CIRVIX AgentControl.
3
+ *
4
+ * Supports:
5
+ * - ~/.gemini/settings.json, gemini.json, .gemini/mcp.json
6
+ * - Executable detection (gemini / gemini.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 GeminiCliAdapter extends BaseAgentAdapter {
15
+ constructor() {
16
+ super({
17
+ id: "gemini-cli",
18
+ label: "Gemini CLI",
19
+ type: "cli",
20
+ mcpKey: "mcpServers",
21
+ });
22
+ }
23
+
24
+ async detect(cwd) {
25
+ const home = homedir();
26
+ const candidatePaths = [
27
+ join(cwd, "gemini.json"),
28
+ join(cwd, ".gemini", "settings.json"),
29
+ join(cwd, ".gemini", "mcp.json"),
30
+ join(home, ".gemini", "settings.json"),
31
+ join(home, ".gemini", "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.servers ?? {};
45
+ Object.assign(servers, map);
46
+ }
47
+ }
48
+ }
49
+
50
+ const execPath = resolveExecutable("gemini", { 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, ".gemini", "settings.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: "Brokers Gemini CLI tool execution through CIRVIX runtime policy.",
102
+ };
103
+ }
104
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Generic MCP Adapter for CIRVIX AgentControl.
3
+ *
4
+ * Supports:
5
+ * - Arbitrary MCP configuration files (mcp.json, .mcp.json, servers.json)
6
+ * - Environment variables specifying MCP server definitions
7
+ * - Local and remote MCP servers over stdio or Streamable HTTP
8
+ */
9
+
10
+ import { join } from "node:path";
11
+ import { BaseAgentAdapter } from "./base.mjs";
12
+
13
+ export class GenericMcpAdapter extends BaseAgentAdapter {
14
+ constructor() {
15
+ super({
16
+ id: "generic-mcp",
17
+ label: "Generic MCP Client",
18
+ type: "generic",
19
+ mcpKey: "mcpServers",
20
+ });
21
+ }
22
+
23
+ async detect(cwd) {
24
+ const candidatePaths = [
25
+ join(cwd, "mcp.json"),
26
+ join(cwd, ".mcp.json"),
27
+ join(cwd, "servers.json"),
28
+ join(cwd, "cirvix.servers.json"),
29
+ ];
30
+
31
+ const existingPaths = [];
32
+ const servers = {};
33
+ let configFound = null;
34
+
35
+ for (const p of candidatePaths) {
36
+ if (await this.fileExists(p)) {
37
+ existingPaths.push(p);
38
+ const data = await this.readJson(p);
39
+ if (data) {
40
+ if (!configFound) configFound = data;
41
+ const map = data.mcpServers ?? data.servers ?? data;
42
+ if (typeof map === "object" && !Array.isArray(map)) {
43
+ Object.assign(servers, map);
44
+ }
45
+ }
46
+ }
47
+ }
48
+
49
+ const detected = existingPaths.length > 0;
50
+ const isIntegrated = Object.entries(servers).some(([name, def]) => this.isCirvixServer(name, def));
51
+
52
+ return {
53
+ detected,
54
+ paths: existingPaths,
55
+ targetConfigPath: existingPaths[0] ?? join(cwd, "mcp.json"),
56
+ config: configFound,
57
+ servers,
58
+ serverCount: Object.keys(servers).length,
59
+ isIntegrated,
60
+ hasConfig: existingPaths.length > 0,
61
+ executable: null,
62
+ metadata: {},
63
+ };
64
+ }
65
+
66
+ async generateIntegrationPlan(cwd, options = {}) {
67
+ const info = await this.detect(cwd);
68
+ const targetFile = info.targetConfigPath;
69
+ const currentServers = { ...info.servers };
70
+
71
+ const upstreams = {};
72
+ for (const [k, v] of Object.entries(currentServers)) {
73
+ if (!this.isCirvixServer(k, v)) upstreams[k] = v;
74
+ }
75
+
76
+ const cirvixServerDef = {
77
+ command: "cirvix",
78
+ args: ["gateway", "--servers", targetFile.replace(/\\/g, "/")],
79
+ };
80
+
81
+ const newConfig = {
82
+ ...(info.config || {}),
83
+ mcpServers: {
84
+ ...currentServers,
85
+ cirvix: cirvixServerDef,
86
+ },
87
+ };
88
+
89
+ return {
90
+ adapterId: this.id,
91
+ label: this.label,
92
+ targetFile,
93
+ canIntegrate: true,
94
+ currentServers,
95
+ upstreams,
96
+ plan: newConfig,
97
+ snippet: JSON.stringify({ mcpServers: { cirvix: cirvixServerDef } }, null, 2),
98
+ reason: "Universal gateway configuration for generic MCP clients.",
99
+ };
100
+ }
101
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Universal Agent Adapter Registry & Fleet Coordinator for CIRVIX AgentControl.
3
+ *
4
+ * Coordinates detection, compatibility level verification, configuration
5
+ * generation, and request routing across all supported AI agent environments.
6
+ */
7
+
8
+ import { join } from "node:path";
9
+ import { BaseAgentAdapter, COMPATIBILITY_LEVEL } from "./base.mjs";
10
+ import { ClaudeCodeAdapter } from "./claude-code.mjs";
11
+ import { CursorAdapter } from "./cursor.mjs";
12
+ import { WindsurfAdapter } from "./windsurf.mjs";
13
+ import { ClineAdapter } from "./cline.mjs";
14
+ import { RooCodeAdapter } from "./roo-code.mjs";
15
+ import { CodexAdapter } from "./codex.mjs";
16
+ import { GeminiCliAdapter } from "./gemini-cli.mjs";
17
+ import { VSCodeAdapter } from "./vscode.mjs";
18
+ import { GenericMcpAdapter } from "./generic-mcp.mjs";
19
+ import { FrameworksAdapter } from "./frameworks.mjs";
20
+ import { read as readJournal } from "../core/journal.mjs";
21
+
22
+ export {
23
+ BaseAgentAdapter,
24
+ COMPATIBILITY_LEVEL,
25
+ ClaudeCodeAdapter,
26
+ CursorAdapter,
27
+ WindsurfAdapter,
28
+ ClineAdapter,
29
+ RooCodeAdapter,
30
+ CodexAdapter,
31
+ GeminiCliAdapter,
32
+ VSCodeAdapter,
33
+ GenericMcpAdapter,
34
+ FrameworksAdapter,
35
+ };
36
+
37
+ /**
38
+ * Returns instantiated adapters for all known agent environments.
39
+ */
40
+ export function getAllAdapters() {
41
+ return [
42
+ new ClaudeCodeAdapter(),
43
+ new CursorAdapter(),
44
+ new WindsurfAdapter(),
45
+ new ClineAdapter(),
46
+ new RooCodeAdapter(),
47
+ new CodexAdapter(),
48
+ new GeminiCliAdapter(),
49
+ new VSCodeAdapter(),
50
+ new GenericMcpAdapter(),
51
+ new FrameworksAdapter(),
52
+ ];
53
+ }
54
+
55
+ /**
56
+ * Finds the adapter that handles a specific agent/runtime ID.
57
+ */
58
+ export function getAdapter(id) {
59
+ const adapters = getAllAdapters();
60
+ return adapters.find((a) => a.id === id) ?? null;
61
+ }
62
+
63
+ /**
64
+ * Scans the machine and workspace for all agent runtimes, assessing their
65
+ * actual, measured compatibility and enforcement level.
66
+ *
67
+ * @param {string} cwd
68
+ * @param {object} [options]
69
+ * @param {string} [options.stateDir]
70
+ * @returns {Promise<{ runtimes: object[], frameworks: object[], mcpServers: object[], summary: object }>}
71
+ */
72
+ export async function detectFleet(cwd = process.cwd(), { stateDir = join(cwd, ".cirvix") } = {}) {
73
+ const adapters = getAllAdapters();
74
+ const detectionPromises = adapters.map(async (adapter) => {
75
+ try {
76
+ const info = await adapter.detect(cwd);
77
+ return { adapter, info };
78
+ } catch {
79
+ return { adapter, info: { detected: false } };
80
+ }
81
+ });
82
+
83
+ const results = await Promise.all(detectionPromises);
84
+
85
+ // Read audit log to check for verified executions
86
+ let auditRecords = [];
87
+ try {
88
+ auditRecords = await readJournal(join(stateDir, "audit.jsonl"));
89
+ } catch {
90
+ auditRecords = [];
91
+ }
92
+
93
+ const agentsWithAudits = new Set(auditRecords.map((r) => r.agent).filter(Boolean));
94
+ const agentsWithVerifiedPermits = new Set(
95
+ auditRecords
96
+ .filter((r) => (r.verdict === "permit" || r.decision === "allow") && r.tool)
97
+ .map((r) => r.agent)
98
+ .filter(Boolean),
99
+ );
100
+
101
+ const detectedRuntimes = [];
102
+ let detectedFrameworks = [];
103
+ const serverMap = new Map();
104
+
105
+ for (const { adapter, info } of results) {
106
+ if (!info || !info.detected) continue;
107
+
108
+ if (adapter.id === "frameworks") {
109
+ detectedFrameworks = info.frameworks ?? [];
110
+ continue;
111
+ }
112
+
113
+ const hasAuditLogs = agentsWithAudits.has(adapter.id);
114
+ const hasVerifiedCall = agentsWithVerifiedPermits.has(adapter.id);
115
+
116
+ const level = await adapter.getCompatibilityLevel(
117
+ {
118
+ ...info,
119
+ hasAuditLogs,
120
+ hasVerifiedCall,
121
+ },
122
+ stateDir,
123
+ );
124
+
125
+ detectedRuntimes.push({
126
+ id: adapter.id,
127
+ label: adapter.label,
128
+ type: adapter.type,
129
+ path: info.paths?.[0] ?? info.targetConfigPath,
130
+ paths: info.paths ?? [],
131
+ targetConfigPath: info.targetConfigPath,
132
+ governed: info.isIntegrated,
133
+ isIntegrated: info.isIntegrated,
134
+ compatibilityLevel: level,
135
+ serverCount: info.serverCount ?? 0,
136
+ servers: info.servers ?? {},
137
+ executable: info.executable ?? null,
138
+ metadata: info.metadata ?? {},
139
+ });
140
+
141
+ // Aggregate MCP servers across runtimes
142
+ for (const [name, def] of Object.entries(info.servers ?? {})) {
143
+ if (adapter.isCirvixServer(name, def)) continue;
144
+ const transport = def?.url ? "http" : "stdio";
145
+ const command = def?.command ?? def?.url ?? "";
146
+ const key = `${name}::${command}`;
147
+ const existing = serverMap.get(key);
148
+ if (existing) {
149
+ if (!existing.runtimes.includes(adapter.label)) {
150
+ existing.runtimes.push(adapter.label);
151
+ }
152
+ } else {
153
+ serverMap.set(key, {
154
+ name,
155
+ transport,
156
+ command,
157
+ args: def?.args ?? [],
158
+ runtimes: [adapter.label],
159
+ envKeys: Object.keys(def?.env ?? {}),
160
+ scope: inferScope(def),
161
+ });
162
+ }
163
+ }
164
+ }
165
+
166
+ const flattenedServers = [...serverMap.values()];
167
+
168
+ const summary = {
169
+ totalDetected: detectedRuntimes.length,
170
+ integrated: detectedRuntimes.filter((r) => r.isIntegrated).length,
171
+ enforced: detectedRuntimes.filter((r) => r.compatibilityLevel === COMPATIBILITY_LEVEL.ENFORCED || r.compatibilityLevel === COMPATIBILITY_LEVEL.VERIFIED).length,
172
+ verified: detectedRuntimes.filter((r) => r.compatibilityLevel === COMPATIBILITY_LEVEL.VERIFIED).length,
173
+ mcpServerCount: flattenedServers.length,
174
+ };
175
+
176
+ return {
177
+ runtimes: detectedRuntimes,
178
+ frameworks: detectedFrameworks,
179
+ mcpServers: flattenedServers,
180
+ summary,
181
+ };
182
+ }
183
+
184
+ function inferScope(def) {
185
+ const args = def?.args ?? [];
186
+ const paths = args.filter((a) => typeof a === "string" && (a.startsWith("/") || /^[A-Za-z]:[\\/]/.test(a)));
187
+ if (paths.length === 0) return null;
188
+ const widest = paths.find((p) => p === "/" || /^[A-Za-z]:[\\/]?$/.test(p));
189
+ return { paths, broad: Boolean(widest), widest: widest ?? null };
190
+ }
191
+
192
+ /**
193
+ * Generates safe integration plans across all detected un-integrated agents.
194
+ */
195
+ export async function generateFleetPlan(cwd = process.cwd(), options = {}) {
196
+ const { runtimes } = await detectFleet(cwd, options);
197
+ const plans = [];
198
+
199
+ for (const rt of runtimes) {
200
+ const adapter = getAdapter(rt.id);
201
+ if (!adapter) continue;
202
+ try {
203
+ const plan = await adapter.generateIntegrationPlan(cwd, options);
204
+ plans.push(plan);
205
+ } catch {}
206
+ }
207
+
208
+ return plans;
209
+ }