@frostbridge/imdl 0.1.13 → 0.1.14
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/dist/index.js +1496 -145
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1376,6 +1376,7 @@ async function initCommand(options) {
|
|
|
1376
1376
|
}
|
|
1377
1377
|
}
|
|
1378
1378
|
installDaemon();
|
|
1379
|
+
setupGateway(detectedAgents);
|
|
1379
1380
|
let apiConnected = false;
|
|
1380
1381
|
try {
|
|
1381
1382
|
const healthRes = await fetch(`${config.apiUrl}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -1401,9 +1402,10 @@ async function initCommand(options) {
|
|
|
1401
1402
|
console.log("");
|
|
1402
1403
|
console.log(pc2.bold(" Useful commands:"));
|
|
1403
1404
|
console.log(pc2.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
1404
|
-
console.log(` ${pc2.cyan("imdl status")}
|
|
1405
|
-
console.log(` ${pc2.cyan("imdl
|
|
1406
|
-
console.log(` ${pc2.cyan("imdl
|
|
1405
|
+
console.log(` ${pc2.cyan("imdl status")} Show connection, agents, and policy summary`);
|
|
1406
|
+
console.log(` ${pc2.cyan("imdl sync-history")} Import past sessions and detect historical violations`);
|
|
1407
|
+
console.log(` ${pc2.cyan("imdl scan")} Scan MCP servers for supply chain risks`);
|
|
1408
|
+
console.log(` ${pc2.cyan("imdl login")} Authenticate to access your team dashboard`);
|
|
1407
1409
|
console.log("");
|
|
1408
1410
|
if (!options.teamToken && config.teamId === "default") {
|
|
1409
1411
|
console.log(pc2.dim(" Tip: Got an invite token? Run: imdl init --token <token>"));
|
|
@@ -1594,6 +1596,126 @@ function installMCPProxy(mcpConfigPath, agentName) {
|
|
|
1594
1596
|
writeFileSync4(mcpConfigPath, JSON.stringify(existing, null, 2));
|
|
1595
1597
|
console.log(pc2.green(` \u2713 MCP proxy registered for ${agentName}`));
|
|
1596
1598
|
}
|
|
1599
|
+
function setupGateway(detectedAgents) {
|
|
1600
|
+
console.log("");
|
|
1601
|
+
console.log(pc2.bold(" [8/9] AI Gateway (token optimization + cost tracking)"));
|
|
1602
|
+
const IMDL_DIR4 = join6(homedir4(), ".imdl");
|
|
1603
|
+
const gatewayConfig = join6(IMDL_DIR4, "gateway.toml");
|
|
1604
|
+
if (!existsSync7(gatewayConfig)) {
|
|
1605
|
+
if (!existsSync7(IMDL_DIR4)) mkdirSync2(IMDL_DIR4, { recursive: true });
|
|
1606
|
+
writeFileSync4(gatewayConfig, `listen_host = "127.0.0.1"
|
|
1607
|
+
listen_port = 9443
|
|
1608
|
+
|
|
1609
|
+
[secret_scanning]
|
|
1610
|
+
enabled = true
|
|
1611
|
+
mode = "audit"
|
|
1612
|
+
|
|
1613
|
+
[dlp]
|
|
1614
|
+
enabled = true
|
|
1615
|
+
mode = "audit"
|
|
1616
|
+
|
|
1617
|
+
[injection_detection]
|
|
1618
|
+
enabled = true
|
|
1619
|
+
mode = "block"
|
|
1620
|
+
|
|
1621
|
+
[routing]
|
|
1622
|
+
enabled = true
|
|
1623
|
+
|
|
1624
|
+
[budget]
|
|
1625
|
+
enabled = false
|
|
1626
|
+
|
|
1627
|
+
[shadow_ai]
|
|
1628
|
+
enabled = true
|
|
1629
|
+
|
|
1630
|
+
[upstream]
|
|
1631
|
+
anthropic_url = "https://api.anthropic.com"
|
|
1632
|
+
openai_url = "https://api.openai.com"
|
|
1633
|
+
|
|
1634
|
+
[context_optimization]
|
|
1635
|
+
enabled = true
|
|
1636
|
+
|
|
1637
|
+
[optimization]
|
|
1638
|
+
enabled = true
|
|
1639
|
+
effort_modulation = true
|
|
1640
|
+
prompt_caching = true
|
|
1641
|
+
|
|
1642
|
+
[audit]
|
|
1643
|
+
enabled = true
|
|
1644
|
+
|
|
1645
|
+
[reporter]
|
|
1646
|
+
enabled = true
|
|
1647
|
+
api_url = "https://imdl-api-1028078600113.us-central1.run.app"
|
|
1648
|
+
flush_interval_secs = 30
|
|
1649
|
+
`, { mode: 384 });
|
|
1650
|
+
console.log(pc2.green(" \u2713 Gateway config created"));
|
|
1651
|
+
} else {
|
|
1652
|
+
console.log(pc2.dim(" \u2713 Gateway config exists"));
|
|
1653
|
+
}
|
|
1654
|
+
const claudeSettings = join6(homedir4(), ".claude", "settings.json");
|
|
1655
|
+
const hasClaudeCode = detectedAgents.some((a) => a.name === "claude-code");
|
|
1656
|
+
const hasCodex = detectedAgents.some((a) => a.name === "codex");
|
|
1657
|
+
if (hasClaudeCode && existsSync7(claudeSettings)) {
|
|
1658
|
+
try {
|
|
1659
|
+
const settings = JSON.parse(readFileSync4(claudeSettings, "utf-8"));
|
|
1660
|
+
if (!settings.env) settings.env = {};
|
|
1661
|
+
if (!settings.env.ANTHROPIC_BEDROCK_BASE_URL) {
|
|
1662
|
+
settings.env.ANTHROPIC_BEDROCK_BASE_URL = "http://127.0.0.1:9443";
|
|
1663
|
+
writeFileSync4(claudeSettings, JSON.stringify(settings, null, 2) + "\n");
|
|
1664
|
+
console.log(pc2.green(" \u2713 Claude Code \u2192 gateway (ANTHROPIC_BEDROCK_BASE_URL)"));
|
|
1665
|
+
} else {
|
|
1666
|
+
console.log(pc2.dim(" \u2713 Claude Code already routed through gateway"));
|
|
1667
|
+
}
|
|
1668
|
+
} catch {
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
const vsCodeSettings = join6(homedir4(), "Library", "Application Support", "Code", "User", "settings.json");
|
|
1672
|
+
if (existsSync7(vsCodeSettings)) {
|
|
1673
|
+
try {
|
|
1674
|
+
let content = readFileSync4(vsCodeSettings, "utf-8");
|
|
1675
|
+
if (!content.includes('"http.proxy"')) {
|
|
1676
|
+
const insertion = ` "http.proxy": "http://127.0.0.1:9443",
|
|
1677
|
+
"http.proxyStrictSSL": false,
|
|
1678
|
+
`;
|
|
1679
|
+
const lastBrace = content.lastIndexOf("}");
|
|
1680
|
+
if (lastBrace > 0) {
|
|
1681
|
+
content = content.slice(0, lastBrace) + insertion + content.slice(lastBrace);
|
|
1682
|
+
writeFileSync4(vsCodeSettings, content);
|
|
1683
|
+
console.log(pc2.green(" \u2713 VS Code http.proxy \u2192 gateway (Copilot optimization)"));
|
|
1684
|
+
}
|
|
1685
|
+
} else {
|
|
1686
|
+
console.log(pc2.dim(" \u2713 VS Code proxy already configured"));
|
|
1687
|
+
}
|
|
1688
|
+
} catch {
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
const gatewayCandidates = [
|
|
1692
|
+
join6(IMDL_DIR4, "bin", "imdl-gateway"),
|
|
1693
|
+
join6(process.cwd(), "packages", "ai-gateway", "target", "release", "imdl-gateway")
|
|
1694
|
+
];
|
|
1695
|
+
const gatewayBin = gatewayCandidates.find((p) => existsSync7(p));
|
|
1696
|
+
if (gatewayBin) {
|
|
1697
|
+
try {
|
|
1698
|
+
const net = __require("net");
|
|
1699
|
+
const sock = new net.Socket();
|
|
1700
|
+
sock.setTimeout(1e3);
|
|
1701
|
+
sock.on("connect", () => {
|
|
1702
|
+
sock.destroy();
|
|
1703
|
+
});
|
|
1704
|
+
sock.on("error", () => {
|
|
1705
|
+
const { spawn: spawn2 } = __require("child_process");
|
|
1706
|
+
const child = spawn2(gatewayBin, [], { detached: true, stdio: "ignore", env: { ...process.env, RUST_LOG: "imdl_ai_gateway=info" } });
|
|
1707
|
+
child.unref();
|
|
1708
|
+
console.log(pc2.green(` \u2713 Gateway started (PID ${child.pid})`));
|
|
1709
|
+
});
|
|
1710
|
+
sock.connect(9443, "127.0.0.1");
|
|
1711
|
+
} catch {
|
|
1712
|
+
console.log(pc2.dim(" \u26A0 Gateway binary found but could not start"));
|
|
1713
|
+
}
|
|
1714
|
+
} else {
|
|
1715
|
+
console.log(pc2.dim(" \u26A0 Gateway binary not found \u2014 run separately: imdl gateway start"));
|
|
1716
|
+
}
|
|
1717
|
+
console.log(pc2.dim(" Dashboard: http://127.0.0.1:9443/dashboard"));
|
|
1718
|
+
}
|
|
1597
1719
|
|
|
1598
1720
|
// src/commands/login.ts
|
|
1599
1721
|
import pc3 from "picocolors";
|
|
@@ -2587,12 +2709,12 @@ function detectAgents() {
|
|
|
2587
2709
|
const home = homedir11();
|
|
2588
2710
|
const claudeDir = join13(home, ".claude");
|
|
2589
2711
|
if (existsSync14(claudeDir)) {
|
|
2590
|
-
const hooksPath = join13(claudeDir, "hooks.json");
|
|
2591
2712
|
let hooked = false;
|
|
2592
|
-
|
|
2713
|
+
const settingsPath = join13(claudeDir, "settings.json");
|
|
2714
|
+
if (existsSync14(settingsPath)) {
|
|
2593
2715
|
try {
|
|
2594
|
-
const
|
|
2595
|
-
hooked = JSON.stringify(
|
|
2716
|
+
const settings = JSON.parse(readFileSync10(settingsPath, "utf-8"));
|
|
2717
|
+
hooked = JSON.stringify(settings).includes("imdl hook");
|
|
2596
2718
|
} catch {
|
|
2597
2719
|
}
|
|
2598
2720
|
}
|
|
@@ -2708,8 +2830,8 @@ function resumeCommand() {
|
|
|
2708
2830
|
import pc6 from "picocolors";
|
|
2709
2831
|
|
|
2710
2832
|
// src/adapters/index.ts
|
|
2711
|
-
import { existsSync as existsSync19, readFileSync as readFileSync14, writeFileSync as
|
|
2712
|
-
import { join as
|
|
2833
|
+
import { existsSync as existsSync19, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
2834
|
+
import { join as join19 } from "path";
|
|
2713
2835
|
|
|
2714
2836
|
// src/transport/buffer.ts
|
|
2715
2837
|
import { appendFileSync, readFileSync as readFileSync11, writeFileSync as writeFileSync5, existsSync as existsSync15, statSync as statSync7, unlinkSync as unlinkSync2 } from "fs";
|
|
@@ -2787,29 +2909,75 @@ function releaseLock() {
|
|
|
2787
2909
|
}
|
|
2788
2910
|
|
|
2789
2911
|
// src/transport/sender.ts
|
|
2912
|
+
import { writeFileSync as writeFileSync6 } from "fs";
|
|
2913
|
+
import { join as join15 } from "path";
|
|
2914
|
+
import { homedir as homedir12 } from "os";
|
|
2790
2915
|
var FLUSH_TIMEOUT = 1e4;
|
|
2791
|
-
var BATCH_SIZE =
|
|
2916
|
+
var BATCH_SIZE = 25;
|
|
2917
|
+
var DEBUG_LOG = join15(homedir12(), ".imdl", "flush-debug.log");
|
|
2918
|
+
function debugLog(msg) {
|
|
2919
|
+
try {
|
|
2920
|
+
writeFileSync6(DEBUG_LOG, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
|
|
2921
|
+
`, { flag: "a" });
|
|
2922
|
+
} catch {
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2792
2925
|
async function tryFlush() {
|
|
2793
2926
|
if (!acquireLock()) return;
|
|
2794
2927
|
try {
|
|
2795
2928
|
const events = readBuffer();
|
|
2796
2929
|
if (events.length === 0) return;
|
|
2797
2930
|
const config = loadConfig();
|
|
2931
|
+
if (!config.apiUrl) {
|
|
2932
|
+
debugLog("No apiUrl configured, skipping flush");
|
|
2933
|
+
return;
|
|
2934
|
+
}
|
|
2798
2935
|
const grouped = groupBySession(events);
|
|
2799
|
-
|
|
2936
|
+
const failedEvents = [];
|
|
2937
|
+
debugLog(`Flushing ${events.length} events across ${grouped.size} sessions`);
|
|
2800
2938
|
for (const [sessionId, sessionEvents] of grouped) {
|
|
2801
|
-
|
|
2802
|
-
|
|
2939
|
+
if (!sessionId || sessionId === "unknown" || sessionId === "?") {
|
|
2940
|
+
debugLog(`Skipping invalid session: ${sessionId}`);
|
|
2941
|
+
continue;
|
|
2942
|
+
}
|
|
2943
|
+
const validTypes = /* @__PURE__ */ new Set(["user_prompt", "pre_tool_use", "post_tool_use", "response", "notification", "tool_call", "tool_result", "prompt", "violation", "error", "system"]);
|
|
2944
|
+
const apiEvents = [];
|
|
2945
|
+
for (const e of sessionEvents) {
|
|
2946
|
+
if (e.type === "usage") continue;
|
|
2947
|
+
const copy = { ...e };
|
|
2948
|
+
if (copy.type === "assistant_response") copy.type = "response";
|
|
2949
|
+
if (copy.toolOutput && typeof copy.toolOutput !== "string") {
|
|
2950
|
+
copy.toolOutput = JSON.stringify(copy.toolOutput);
|
|
2951
|
+
}
|
|
2952
|
+
if (copy.toolOutput && copy.toolOutput.length > 5e4) {
|
|
2953
|
+
copy.toolOutput = copy.toolOutput.slice(0, 5e4) + "\u2026[truncated]";
|
|
2954
|
+
}
|
|
2955
|
+
if (copy.prompt && copy.prompt.length > 5e4) {
|
|
2956
|
+
copy.prompt = copy.prompt.slice(0, 5e4) + "\u2026[truncated]";
|
|
2957
|
+
}
|
|
2958
|
+
if (!validTypes.has(copy.type)) continue;
|
|
2959
|
+
apiEvents.push(copy);
|
|
2960
|
+
}
|
|
2961
|
+
let sessionFailed = false;
|
|
2962
|
+
for (let i = 0; i < apiEvents.length; i += BATCH_SIZE) {
|
|
2963
|
+
const batch = apiEvents.slice(i, i + BATCH_SIZE);
|
|
2803
2964
|
const success = await sendBatch(config.apiUrl, sessionId, batch, config.developerId);
|
|
2804
2965
|
if (!success) {
|
|
2805
|
-
|
|
2966
|
+
failedEvents.push(...sessionEvents.slice(i));
|
|
2967
|
+
sessionFailed = true;
|
|
2806
2968
|
break;
|
|
2807
2969
|
}
|
|
2808
2970
|
}
|
|
2809
|
-
if (
|
|
2971
|
+
if (sessionFailed) continue;
|
|
2810
2972
|
}
|
|
2811
|
-
|
|
2812
|
-
|
|
2973
|
+
clearBuffer();
|
|
2974
|
+
if (failedEvents.length > 0) {
|
|
2975
|
+
debugLog(`Re-queuing ${failedEvents.length} failed events`);
|
|
2976
|
+
for (const event of failedEvents) {
|
|
2977
|
+
appendEvent(event);
|
|
2978
|
+
}
|
|
2979
|
+
} else {
|
|
2980
|
+
debugLog("All events flushed successfully");
|
|
2813
2981
|
}
|
|
2814
2982
|
} finally {
|
|
2815
2983
|
releaseLock();
|
|
@@ -2829,8 +2997,13 @@ async function sendBatch(apiUrl, sessionId, events, developerId) {
|
|
|
2829
2997
|
body: JSON.stringify({ events, developerId, ...agentType && { agentType } }),
|
|
2830
2998
|
signal: AbortSignal.timeout(FLUSH_TIMEOUT)
|
|
2831
2999
|
});
|
|
3000
|
+
if (!res.ok) {
|
|
3001
|
+
const body = await res.text().catch(() => "");
|
|
3002
|
+
debugLog(`sendBatch FAILED for ${sessionId}: ${res.status} ${body.slice(0, 200)}`);
|
|
3003
|
+
}
|
|
2832
3004
|
return res.ok;
|
|
2833
|
-
} catch {
|
|
3005
|
+
} catch (e) {
|
|
3006
|
+
debugLog(`sendBatch ERROR for ${sessionId}: ${e.message}`);
|
|
2834
3007
|
return false;
|
|
2835
3008
|
}
|
|
2836
3009
|
}
|
|
@@ -2962,20 +3135,85 @@ function redactObject(obj) {
|
|
|
2962
3135
|
|
|
2963
3136
|
// src/hooks/prompt-scanner.ts
|
|
2964
3137
|
var KNOWN_PREFIX_PATTERNS = [
|
|
3138
|
+
// ─── Cloud Providers ───
|
|
2965
3139
|
{ pattern: /AKIA[0-9A-Z]{16}/g, label: "AWS Access Key", severity: "critical" },
|
|
3140
|
+
{ pattern: /(?:ASIA|ABIA|ACCA)[0-9A-Z]{16}/g, label: "AWS Temporary Key", severity: "critical" },
|
|
3141
|
+
{ pattern: /aws_secret_access_key\s*[=:]\s*["']?([A-Za-z0-9/+=]{40})["']?/gi, label: "AWS Secret Key", severity: "critical" },
|
|
3142
|
+
{ pattern: /AIzaSy[A-Za-z0-9_\-]{30,36}/g, label: "Google API Key", severity: "critical" },
|
|
3143
|
+
{ pattern: /[0-9]+-[a-z0-9]{32}\.apps\.googleusercontent\.com/g, label: "Google OAuth Client ID", severity: "high" },
|
|
3144
|
+
{ pattern: /ya29\.[A-Za-z0-9_\-]{50,}/g, label: "Google OAuth Token", severity: "critical" },
|
|
3145
|
+
{ pattern: /GOCSPX-[A-Za-z0-9_\-]{28}/g, label: "Google OAuth Secret", severity: "critical" },
|
|
3146
|
+
// ─── AI/LLM Providers ───
|
|
3147
|
+
{ pattern: /sk-ant-[A-Za-z0-9\-_]{20,}/g, label: "Anthropic Key", severity: "critical" },
|
|
3148
|
+
{ pattern: /sk-(?:proj-)?[A-Za-z0-9\-_]{20,}/g, label: "OpenAI Key", severity: "critical" },
|
|
3149
|
+
{ pattern: /sess-[A-Za-z0-9]{40,}/g, label: "OpenAI Session Token", severity: "critical" },
|
|
3150
|
+
{ pattern: /r8_[A-Za-z0-9]{20,}/g, label: "Replicate Token", severity: "critical" },
|
|
3151
|
+
{ pattern: /hf_[A-Za-z0-9]{20,}/g, label: "Hugging Face Token", severity: "critical" },
|
|
3152
|
+
{ pattern: /key-[A-Za-z0-9]{32,}/g, label: "Cohere API Key", severity: "critical" },
|
|
3153
|
+
// ─── Code Platforms ───
|
|
2966
3154
|
{ pattern: /gh[pousr]_[A-Za-z0-9_]{36,255}/g, label: "GitHub Token", severity: "critical" },
|
|
2967
3155
|
{ pattern: /github_pat_[A-Za-z0-9_]{22,255}/g, label: "GitHub PAT", severity: "critical" },
|
|
3156
|
+
{ pattern: /ghu_[A-Za-z0-9]{36,}/g, label: "GitHub User Token", severity: "critical" },
|
|
3157
|
+
{ pattern: /glpat-[A-Za-z0-9\-_]{20,}/g, label: "GitLab PAT", severity: "critical" },
|
|
3158
|
+
{ pattern: /glrt-[A-Za-z0-9\-_]{20,}/g, label: "GitLab Runner Token", severity: "critical" },
|
|
3159
|
+
{ pattern: /ATATT[A-Za-z0-9\-_+/=]{30,}/g, label: "Atlassian/Bitbucket Token", severity: "critical" },
|
|
2968
3160
|
{ pattern: /npm_[A-Za-z0-9]{36,}/g, label: "NPM Token", severity: "critical" },
|
|
3161
|
+
{ pattern: /pypi-[A-Za-z0-9\-_]{100,}/g, label: "PyPI API Token", severity: "critical" },
|
|
3162
|
+
{ pattern: /nuget\.org\/v3\/[A-Za-z0-9\-]{30,}/g, label: "NuGet API Key", severity: "critical" },
|
|
3163
|
+
{ pattern: /rubygems_[A-Za-z0-9]{48}/g, label: "RubyGems Token", severity: "critical" },
|
|
3164
|
+
// ─── Communication ───
|
|
2969
3165
|
{ pattern: /xox[baprs]-[A-Za-z0-9\-]{10,}/g, label: "Slack Token", severity: "critical" },
|
|
3166
|
+
{ pattern: /https:\/\/hooks\.slack\.com\/services\/T[A-Z0-9]+\/B[A-Z0-9]+\/[A-Za-z0-9]+/g, label: "Slack Webhook", severity: "high" },
|
|
3167
|
+
{ pattern: /https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[A-Za-z0-9_\-]+/g, label: "Discord Webhook", severity: "high" },
|
|
3168
|
+
{ pattern: /[0-9]{8,}:[A-Za-z0-9_\-]{35}/g, label: "Telegram Bot Token", severity: "critical" },
|
|
3169
|
+
{ pattern: /EAA[A-Za-z0-9]{100,}/g, label: "Facebook Access Token", severity: "critical" },
|
|
3170
|
+
// ─── Payment/Financial ───
|
|
2970
3171
|
{ pattern: /[spr]k_(?:live|test)_[A-Za-z0-9]{24,}/g, label: "Stripe Key", severity: "critical" },
|
|
3172
|
+
{ pattern: /whsec_[A-Za-z0-9]{32,}/g, label: "Stripe Webhook Secret", severity: "critical" },
|
|
3173
|
+
{ pattern: /sq0[a-z]{3}-[A-Za-z0-9\-_]{22,}/g, label: "Square Token", severity: "critical" },
|
|
3174
|
+
{ pattern: /access_token\$(?:sandbox|production)\$[a-z0-9]{16}\$[a-f0-9]{32}/g, label: "PayPal Token", severity: "critical" },
|
|
3175
|
+
// ─── Messaging/Email ───
|
|
2971
3176
|
{ pattern: /SG\.[A-Za-z0-9_\-]{22,}\.[A-Za-z0-9_\-]{22,}/g, label: "SendGrid Key", severity: "critical" },
|
|
2972
|
-
{ pattern: /
|
|
2973
|
-
{ pattern: /
|
|
2974
|
-
{ pattern: /
|
|
3177
|
+
{ pattern: /key-[a-f0-9]{32}/g, label: "Mailgun Key", severity: "critical" },
|
|
3178
|
+
{ pattern: /re_[A-Za-z0-9]{20,}/g, label: "Resend API Key", severity: "critical" },
|
|
3179
|
+
{ pattern: /[a-f0-9]{32}-us[0-9]{1,2}/g, label: "Mailchimp Key", severity: "high" },
|
|
3180
|
+
// ─── Infrastructure ───
|
|
3181
|
+
{ pattern: /SK[0-9a-fA-F]{32}/g, label: "Twilio Key", severity: "critical" },
|
|
3182
|
+
{ pattern: /AC[a-f0-9]{32}/g, label: "Twilio Account SID", severity: "high" },
|
|
3183
|
+
{ pattern: /dop_v1_[a-f0-9]{64}/g, label: "DigitalOcean Token", severity: "critical" },
|
|
3184
|
+
{ pattern: /do[po]_v1_[a-f0-9]{64}/g, label: "DigitalOcean PAT", severity: "critical" },
|
|
3185
|
+
{ pattern: /FLWSECK_TEST-[a-f0-9]{32}/g, label: "Flutterwave Secret", severity: "critical" },
|
|
3186
|
+
{ pattern: /vault:[a-z0-9_\-\/]+#[A-Za-z0-9_\-]{8,}/g, label: "Vault Path", severity: "high" },
|
|
3187
|
+
{ pattern: /AgEAAr[A-Za-z0-9+/=]{40,}/g, label: "Doppler Token", severity: "critical" },
|
|
3188
|
+
// ─── CI/CD ───
|
|
3189
|
+
{ pattern: /v1\.[a-f0-9]{40}/g, label: "CircleCI Token", severity: "critical" },
|
|
3190
|
+
{ pattern: /tfp_[A-Za-z0-9]{40,}/g, label: "Terraform Cloud Token", severity: "critical" },
|
|
3191
|
+
{ pattern: /AstraCS:[A-Za-z0-9+/=]{40,}/g, label: "DataStax Token", severity: "critical" },
|
|
3192
|
+
{ pattern: /snyk[_-][a-f0-9\-]{36,}/gi, label: "Snyk Token", severity: "critical" },
|
|
3193
|
+
// ─── Observability ───
|
|
3194
|
+
{ pattern: /https?:\/\/[a-f0-9]{32}@[^\s"']+\.sentry\.io/g, label: "Sentry DSN", severity: "high" },
|
|
3195
|
+
{ pattern: /dd[apt]_[A-Za-z0-9]{32,40}/g, label: "Datadog API Key", severity: "critical" },
|
|
3196
|
+
{ pattern: /[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}:x-]?[a-f0-9]{8}/g, label: "New Relic Key", severity: "high" },
|
|
3197
|
+
// ─── Database ───
|
|
3198
|
+
{ pattern: /(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp|mssql|cockroachdb):\/\/[^:]+:[^@\s]+@/gi, label: "Database Connection String", severity: "critical" },
|
|
3199
|
+
{ pattern: /redis:\/\/default:[^\s@]+@/gi, label: "Redis Password URL", severity: "critical" },
|
|
3200
|
+
// ─── Crypto/Keys ───
|
|
2975
3201
|
{ pattern: /-----BEGIN (?:RSA |EC |DSA |ED25519 |OPENSSH )?PRIVATE KEY-----/g, label: "Private Key", severity: "critical" },
|
|
3202
|
+
{ pattern: /-----BEGIN PGP PRIVATE KEY BLOCK-----/g, label: "PGP Private Key", severity: "critical" },
|
|
3203
|
+
{ pattern: /age1[a-z0-9]{58}/g, label: "Age Encryption Key", severity: "critical" },
|
|
3204
|
+
// ─── Auth/Session ───
|
|
2976
3205
|
{ pattern: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_\-+/=]{10,}/g, label: "JWT Token", severity: "high" },
|
|
2977
|
-
{ pattern: /
|
|
2978
|
-
{ pattern: /
|
|
3206
|
+
{ pattern: /Bearer\s+[A-Za-z0-9_\-./+=]{20,}/g, label: "Bearer Token", severity: "high" },
|
|
3207
|
+
{ pattern: /Basic\s+[A-Za-z0-9+/=]{20,}/g, label: "Basic Auth Credential", severity: "high" },
|
|
3208
|
+
{ pattern: /SAML[A-Za-z0-9+/=]{50,}/g, label: "SAML Token", severity: "high" },
|
|
3209
|
+
// ─── Cloud-specific secrets ───
|
|
3210
|
+
{ pattern: /AccountKey=[A-Za-z0-9+/=]{86}==/g, label: "Azure Storage Key", severity: "critical" },
|
|
3211
|
+
{ pattern: /SharedAccessSignature=sv=[^&\s]{50,}/g, label: "Azure SAS Token", severity: "critical" },
|
|
3212
|
+
{ pattern: /DefaultEndpointsProtocol=https;AccountName=[^;]+;AccountKey=[^;]+/g, label: "Azure Connection String", severity: "critical" },
|
|
3213
|
+
{ pattern: /AKCp[A-Za-z0-9]{50,}/g, label: "Artifactory Token", severity: "critical" },
|
|
3214
|
+
// ─── Clerk (our own auth!) ───
|
|
3215
|
+
{ pattern: /sk_(?:live|test)_[A-Za-z0-9]{20,}/g, label: "Clerk Secret Key", severity: "critical" },
|
|
3216
|
+
{ pattern: /pk_(?:live|test)_[A-Za-z0-9]{20,}/g, label: "Clerk Publishable Key", severity: "medium" }
|
|
2979
3217
|
];
|
|
2980
3218
|
function shannonEntropy(str) {
|
|
2981
3219
|
if (str.length === 0) return 0;
|
|
@@ -3103,12 +3341,75 @@ function createPromptViolation(findings) {
|
|
|
3103
3341
|
reason
|
|
3104
3342
|
};
|
|
3105
3343
|
}
|
|
3344
|
+
var LLM_SCAN_PROMPT = `You are a security scanner. Determine if the text below contains a secret, credential, API key, or sensitive token that should NOT be shared.
|
|
3345
|
+
|
|
3346
|
+
Respond ONLY with JSON: {"isSecret": true/false, "confidence": 0.0-1.0, "label": "type if found"}
|
|
3347
|
+
|
|
3348
|
+
Text to analyze:
|
|
3349
|
+
`;
|
|
3350
|
+
async function llmScanAmbiguous(text, config) {
|
|
3351
|
+
if (!config.enabled || !config.apiUrl) {
|
|
3352
|
+
return { isSecret: false, confidence: 0 };
|
|
3353
|
+
}
|
|
3354
|
+
const maxChars = config.maxInputChars || 500;
|
|
3355
|
+
const truncated = text.length > maxChars ? text.slice(0, maxChars) : text;
|
|
3356
|
+
try {
|
|
3357
|
+
const res = await fetch(config.apiUrl, {
|
|
3358
|
+
method: "POST",
|
|
3359
|
+
headers: { "Content-Type": "application/json" },
|
|
3360
|
+
body: JSON.stringify({
|
|
3361
|
+
model: config.model || "claude-haiku-4-5-20251001",
|
|
3362
|
+
max_tokens: 80,
|
|
3363
|
+
messages: [{ role: "user", content: LLM_SCAN_PROMPT + truncated }]
|
|
3364
|
+
}),
|
|
3365
|
+
signal: AbortSignal.timeout(3e3)
|
|
3366
|
+
});
|
|
3367
|
+
if (!res.ok) return { isSecret: false, confidence: 0 };
|
|
3368
|
+
const data = await res.json();
|
|
3369
|
+
const content = data.content?.[0]?.text || data.choices?.[0]?.message?.content || "";
|
|
3370
|
+
const jsonMatch = content.match(/\{[^}]+\}/);
|
|
3371
|
+
if (!jsonMatch) return { isSecret: false, confidence: 0 };
|
|
3372
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
3373
|
+
return {
|
|
3374
|
+
isSecret: parsed.isSecret === true,
|
|
3375
|
+
confidence: typeof parsed.confidence === "number" ? parsed.confidence : 0,
|
|
3376
|
+
label: parsed.label,
|
|
3377
|
+
reasoning: parsed.reasoning
|
|
3378
|
+
};
|
|
3379
|
+
} catch {
|
|
3380
|
+
return { isSecret: false, confidence: 0 };
|
|
3381
|
+
}
|
|
3382
|
+
}
|
|
3383
|
+
async function scanWithLlmFallback(prompt, llmConfig) {
|
|
3384
|
+
const regexResult = scanPromptForSecrets(prompt);
|
|
3385
|
+
if (regexResult.hasSecrets || !llmConfig.enabled) {
|
|
3386
|
+
return regexResult;
|
|
3387
|
+
}
|
|
3388
|
+
const hasAssignments = ENV_ASSIGNMENT.test(prompt);
|
|
3389
|
+
ENV_ASSIGNMENT.lastIndex = 0;
|
|
3390
|
+
const hasLongStrings = /[A-Za-z0-9_\-]{30,}/.test(prompt);
|
|
3391
|
+
if (!hasAssignments && !hasLongStrings) {
|
|
3392
|
+
return regexResult;
|
|
3393
|
+
}
|
|
3394
|
+
const llmResult = await llmScanAmbiguous(prompt, llmConfig);
|
|
3395
|
+
if (llmResult.isSecret && llmResult.confidence >= 0.7) {
|
|
3396
|
+
return {
|
|
3397
|
+
hasSecrets: true,
|
|
3398
|
+
findings: [{
|
|
3399
|
+
label: llmResult.label || "LLM-detected secret",
|
|
3400
|
+
severity: llmResult.confidence >= 0.9 ? "critical" : "high",
|
|
3401
|
+
snippet: prompt.slice(0, 20) + "..."
|
|
3402
|
+
}]
|
|
3403
|
+
};
|
|
3404
|
+
}
|
|
3405
|
+
return regexResult;
|
|
3406
|
+
}
|
|
3106
3407
|
|
|
3107
3408
|
// src/adapters/claude-code.ts
|
|
3108
3409
|
import { createReadStream, existsSync as existsSync16, statSync as statSync8, readdirSync as readdirSync3 } from "fs";
|
|
3109
3410
|
import { createInterface } from "readline";
|
|
3110
|
-
import { join as
|
|
3111
|
-
import { homedir as
|
|
3411
|
+
import { join as join16 } from "path";
|
|
3412
|
+
import { homedir as homedir13 } from "os";
|
|
3112
3413
|
var ClaudeCodeAdapter = class {
|
|
3113
3414
|
agentType = "claude-code";
|
|
3114
3415
|
async extractEvents(source) {
|
|
@@ -3196,7 +3497,7 @@ var ClaudeCodeAdapter = class {
|
|
|
3196
3497
|
const events = [];
|
|
3197
3498
|
const files = readdirSync3(dirPath).filter((f) => f.endsWith(".jsonl"));
|
|
3198
3499
|
for (const file of files) {
|
|
3199
|
-
const filePath =
|
|
3500
|
+
const filePath = join16(dirPath, file);
|
|
3200
3501
|
if (since) {
|
|
3201
3502
|
const stat = statSync8(filePath);
|
|
3202
3503
|
if (stat.mtimeMs < since.getTime()) continue;
|
|
@@ -3221,20 +3522,20 @@ var ClaudeCodeAdapter = class {
|
|
|
3221
3522
|
}
|
|
3222
3523
|
static getProjectLogDir(projectPath) {
|
|
3223
3524
|
const encoded = projectPath.replace(/\//g, "-");
|
|
3224
|
-
const logDir =
|
|
3525
|
+
const logDir = join16(homedir13(), ".claude", "projects", encoded);
|
|
3225
3526
|
return existsSync16(logDir) ? logDir : null;
|
|
3226
3527
|
}
|
|
3227
3528
|
static getAllProjectDirs() {
|
|
3228
|
-
const base =
|
|
3529
|
+
const base = join16(homedir13(), ".claude", "projects");
|
|
3229
3530
|
if (!existsSync16(base)) return [];
|
|
3230
|
-
return readdirSync3(base).map((d) =>
|
|
3531
|
+
return readdirSync3(base).map((d) => join16(base, d)).filter((p) => statSync8(p).isDirectory());
|
|
3231
3532
|
}
|
|
3232
3533
|
};
|
|
3233
3534
|
|
|
3234
3535
|
// src/adapters/cursor.ts
|
|
3235
3536
|
import { existsSync as existsSync17, readdirSync as readdirSync4, readFileSync as readFileSync12, statSync as statSync9 } from "fs";
|
|
3236
|
-
import { join as
|
|
3237
|
-
import { homedir as
|
|
3537
|
+
import { join as join17 } from "path";
|
|
3538
|
+
import { homedir as homedir14 } from "os";
|
|
3238
3539
|
import { createRequire } from "module";
|
|
3239
3540
|
var esmRequire = createRequire(import.meta.url);
|
|
3240
3541
|
function loadSqlite() {
|
|
@@ -3287,7 +3588,7 @@ var CursorAdapter = class _CursorAdapter {
|
|
|
3287
3588
|
*/
|
|
3288
3589
|
findVscdbFiles(baseDir) {
|
|
3289
3590
|
const files = [];
|
|
3290
|
-
const directDb =
|
|
3591
|
+
const directDb = join17(baseDir, "state.vscdb");
|
|
3291
3592
|
if (existsSync17(directDb)) {
|
|
3292
3593
|
files.push(directDb);
|
|
3293
3594
|
return files;
|
|
@@ -3295,11 +3596,11 @@ var CursorAdapter = class _CursorAdapter {
|
|
|
3295
3596
|
try {
|
|
3296
3597
|
const entries = readdirSync4(baseDir);
|
|
3297
3598
|
for (const entry of entries) {
|
|
3298
|
-
const entryPath =
|
|
3599
|
+
const entryPath = join17(baseDir, entry);
|
|
3299
3600
|
try {
|
|
3300
3601
|
const stat = statSync9(entryPath);
|
|
3301
3602
|
if (stat.isDirectory()) {
|
|
3302
|
-
const dbPath =
|
|
3603
|
+
const dbPath = join17(entryPath, "state.vscdb");
|
|
3303
3604
|
if (existsSync17(dbPath)) {
|
|
3304
3605
|
files.push(dbPath);
|
|
3305
3606
|
}
|
|
@@ -3620,14 +3921,14 @@ var CursorAdapter = class _CursorAdapter {
|
|
|
3620
3921
|
* Uses offsets to only read new lines since last scan.
|
|
3621
3922
|
*/
|
|
3622
3923
|
extractFromTranscripts(since, offsets) {
|
|
3623
|
-
const projectsDir =
|
|
3924
|
+
const projectsDir = join17(homedir14(), ".cursor", "projects");
|
|
3624
3925
|
if (!existsSync17(projectsDir)) return { events: [], newOffsets: offsets };
|
|
3625
3926
|
const events = [];
|
|
3626
3927
|
const newOffsets = { ...offsets };
|
|
3627
3928
|
try {
|
|
3628
3929
|
const projects = readdirSync4(projectsDir);
|
|
3629
3930
|
for (const proj of projects) {
|
|
3630
|
-
const transcriptsDir =
|
|
3931
|
+
const transcriptsDir = join17(projectsDir, proj, "agent-transcripts");
|
|
3631
3932
|
if (!existsSync17(transcriptsDir)) continue;
|
|
3632
3933
|
let sessionDirs;
|
|
3633
3934
|
try {
|
|
@@ -3636,7 +3937,7 @@ var CursorAdapter = class _CursorAdapter {
|
|
|
3636
3937
|
continue;
|
|
3637
3938
|
}
|
|
3638
3939
|
for (const sessionDir of sessionDirs) {
|
|
3639
|
-
const jsonlPath =
|
|
3940
|
+
const jsonlPath = join17(transcriptsDir, sessionDir, `${sessionDir}.jsonl`);
|
|
3640
3941
|
if (!existsSync17(jsonlPath)) continue;
|
|
3641
3942
|
if (since) {
|
|
3642
3943
|
try {
|
|
@@ -3727,8 +4028,8 @@ var CursorAdapter = class _CursorAdapter {
|
|
|
3727
4028
|
*/
|
|
3728
4029
|
static getLogPath() {
|
|
3729
4030
|
const paths = [
|
|
3730
|
-
|
|
3731
|
-
|
|
4031
|
+
join17(homedir14(), ".cursor", "User", "workspaceStorage"),
|
|
4032
|
+
join17(homedir14(), "Library", "Application Support", "Cursor", "User", "workspaceStorage")
|
|
3732
4033
|
];
|
|
3733
4034
|
for (const p of paths) {
|
|
3734
4035
|
if (existsSync17(p)) return p;
|
|
@@ -3742,7 +4043,7 @@ var CursorAdapter = class _CursorAdapter {
|
|
|
3742
4043
|
const base = _CursorAdapter.getLogPath();
|
|
3743
4044
|
if (!base) return [];
|
|
3744
4045
|
try {
|
|
3745
|
-
return readdirSync4(base).map((d) =>
|
|
4046
|
+
return readdirSync4(base).map((d) => join17(base, d)).filter((p) => {
|
|
3746
4047
|
try {
|
|
3747
4048
|
return statSync9(p).isDirectory();
|
|
3748
4049
|
} catch {
|
|
@@ -3757,16 +4058,17 @@ var CursorAdapter = class _CursorAdapter {
|
|
|
3757
4058
|
* Returns the Cursor projects directory for transcript scanning.
|
|
3758
4059
|
*/
|
|
3759
4060
|
static getProjectsDir() {
|
|
3760
|
-
const dir =
|
|
4061
|
+
const dir = join17(homedir14(), ".cursor", "projects");
|
|
3761
4062
|
return existsSync17(dir) ? dir : null;
|
|
3762
4063
|
}
|
|
3763
4064
|
};
|
|
3764
4065
|
|
|
3765
4066
|
// src/adapters/codex.ts
|
|
3766
4067
|
import { existsSync as existsSync18, readdirSync as readdirSync5, readFileSync as readFileSync13, statSync as statSync10 } from "fs";
|
|
3767
|
-
import { join as
|
|
3768
|
-
import { homedir as
|
|
3769
|
-
var
|
|
4068
|
+
import { join as join18 } from "path";
|
|
4069
|
+
import { homedir as homedir15 } from "os";
|
|
4070
|
+
var MAX_FILE_SIZE = 50 * 1024 * 1024;
|
|
4071
|
+
var CodexAdapter = class _CodexAdapter {
|
|
3770
4072
|
agentType = "codex";
|
|
3771
4073
|
async extractEvents(source) {
|
|
3772
4074
|
if (source.kind === "directory") {
|
|
@@ -3774,6 +4076,22 @@ var CodexAdapter = class {
|
|
|
3774
4076
|
}
|
|
3775
4077
|
return [];
|
|
3776
4078
|
}
|
|
4079
|
+
extractWithOffsets(since, offsets = {}) {
|
|
4080
|
+
const basePath = _CodexAdapter.getSessionsDir();
|
|
4081
|
+
if (!basePath) return { events: [], newOffsets: offsets };
|
|
4082
|
+
const events = [];
|
|
4083
|
+
const newOffsets = { ...offsets };
|
|
4084
|
+
const files = this.findSessionFiles(basePath);
|
|
4085
|
+
for (const filePath of files) {
|
|
4086
|
+
const prevOffset = offsets[filePath] || 0;
|
|
4087
|
+
const { events: fileEvents, newOffset } = this.readSessionFileWithOffset(filePath, prevOffset, since);
|
|
4088
|
+
events.push(...fileEvents);
|
|
4089
|
+
if (newOffset > prevOffset) {
|
|
4090
|
+
newOffsets[filePath] = newOffset;
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
return { events, newOffsets };
|
|
4094
|
+
}
|
|
3777
4095
|
readSessionsDir(basePath, since) {
|
|
3778
4096
|
if (!existsSync18(basePath)) return [];
|
|
3779
4097
|
const events = [];
|
|
@@ -3784,20 +4102,19 @@ var CodexAdapter = class {
|
|
|
3784
4102
|
}
|
|
3785
4103
|
return events;
|
|
3786
4104
|
}
|
|
3787
|
-
findSessionFiles(basePath,
|
|
4105
|
+
findSessionFiles(basePath, _since) {
|
|
3788
4106
|
const files = [];
|
|
3789
4107
|
const walk = (dir) => {
|
|
3790
4108
|
try {
|
|
3791
4109
|
for (const entry of readdirSync5(dir)) {
|
|
3792
|
-
const full =
|
|
4110
|
+
const full = join18(dir, entry);
|
|
3793
4111
|
try {
|
|
3794
4112
|
const stat = statSync10(full);
|
|
3795
4113
|
if (stat.isDirectory()) {
|
|
3796
4114
|
walk(full);
|
|
3797
4115
|
} else if (entry.endsWith(".jsonl")) {
|
|
3798
|
-
if (
|
|
3799
|
-
|
|
3800
|
-
}
|
|
4116
|
+
if (stat.size > MAX_FILE_SIZE) continue;
|
|
4117
|
+
files.push(full);
|
|
3801
4118
|
}
|
|
3802
4119
|
} catch {
|
|
3803
4120
|
}
|
|
@@ -3808,6 +4125,120 @@ var CodexAdapter = class {
|
|
|
3808
4125
|
walk(basePath);
|
|
3809
4126
|
return files;
|
|
3810
4127
|
}
|
|
4128
|
+
readSessionFileWithOffset(filePath, lineOffset, since) {
|
|
4129
|
+
const events = [];
|
|
4130
|
+
let content;
|
|
4131
|
+
try {
|
|
4132
|
+
content = readFileSync13(filePath, "utf-8");
|
|
4133
|
+
} catch {
|
|
4134
|
+
return { events, newOffset: lineOffset };
|
|
4135
|
+
}
|
|
4136
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
4137
|
+
const sessionId = this.sessionIdFromPath(filePath);
|
|
4138
|
+
let cwd;
|
|
4139
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4140
|
+
let entry;
|
|
4141
|
+
try {
|
|
4142
|
+
entry = JSON.parse(lines[i]);
|
|
4143
|
+
} catch {
|
|
4144
|
+
continue;
|
|
4145
|
+
}
|
|
4146
|
+
if (entry.type === "turn_context" && entry.payload?.cwd) {
|
|
4147
|
+
cwd = entry.payload.cwd;
|
|
4148
|
+
}
|
|
4149
|
+
if (i < lineOffset) continue;
|
|
4150
|
+
if (since && entry.timestamp) {
|
|
4151
|
+
const entryTime = new Date(entry.timestamp).getTime();
|
|
4152
|
+
if (entryTime <= since.getTime()) continue;
|
|
4153
|
+
}
|
|
4154
|
+
const ptype = entry.payload?.type;
|
|
4155
|
+
if (ptype === "user_message" && entry.payload.message) {
|
|
4156
|
+
const msg = entry.payload.message.trim();
|
|
4157
|
+
if (msg.length > 0) {
|
|
4158
|
+
events.push({
|
|
4159
|
+
sessionId,
|
|
4160
|
+
type: "user_prompt",
|
|
4161
|
+
prompt: msg.slice(0, 1e4),
|
|
4162
|
+
timestamp: entry.timestamp,
|
|
4163
|
+
cwd,
|
|
4164
|
+
agentType: "codex"
|
|
4165
|
+
});
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
if (ptype === "function_call" && entry.payload.name) {
|
|
4169
|
+
let toolInput;
|
|
4170
|
+
if (entry.payload.arguments) {
|
|
4171
|
+
try {
|
|
4172
|
+
toolInput = JSON.parse(entry.payload.arguments);
|
|
4173
|
+
} catch {
|
|
4174
|
+
toolInput = { raw: entry.payload.arguments };
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
events.push({
|
|
4178
|
+
sessionId,
|
|
4179
|
+
type: "pre_tool_use",
|
|
4180
|
+
toolName: entry.payload.name,
|
|
4181
|
+
toolInput,
|
|
4182
|
+
timestamp: entry.timestamp,
|
|
4183
|
+
cwd,
|
|
4184
|
+
agentType: "codex"
|
|
4185
|
+
});
|
|
4186
|
+
}
|
|
4187
|
+
if (ptype === "function_call_output" && entry.payload.output) {
|
|
4188
|
+
events.push({
|
|
4189
|
+
sessionId,
|
|
4190
|
+
type: "post_tool_use",
|
|
4191
|
+
toolName: entry.payload.call_id || "unknown",
|
|
4192
|
+
toolOutput: entry.payload.output.slice(0, 1e4),
|
|
4193
|
+
timestamp: entry.timestamp,
|
|
4194
|
+
cwd,
|
|
4195
|
+
agentType: "codex"
|
|
4196
|
+
});
|
|
4197
|
+
}
|
|
4198
|
+
if (ptype === "web_search_end" || ptype === "web_search_call") {
|
|
4199
|
+
const action = entry.payload.action;
|
|
4200
|
+
events.push({
|
|
4201
|
+
sessionId,
|
|
4202
|
+
type: "pre_tool_use",
|
|
4203
|
+
toolName: "web_search",
|
|
4204
|
+
toolInput: action ? { query: action.query, type: action.type } : void 0,
|
|
4205
|
+
timestamp: entry.timestamp,
|
|
4206
|
+
cwd,
|
|
4207
|
+
agentType: "codex"
|
|
4208
|
+
});
|
|
4209
|
+
}
|
|
4210
|
+
if (ptype === "agent_message" && entry.payload.message) {
|
|
4211
|
+
events.push({
|
|
4212
|
+
sessionId,
|
|
4213
|
+
type: "response",
|
|
4214
|
+
toolOutput: entry.payload.message.slice(0, 1e4),
|
|
4215
|
+
timestamp: entry.timestamp,
|
|
4216
|
+
cwd,
|
|
4217
|
+
agentType: "codex"
|
|
4218
|
+
});
|
|
4219
|
+
}
|
|
4220
|
+
if (ptype === "token_count") {
|
|
4221
|
+
const p = entry.payload;
|
|
4222
|
+
if (p.input_tokens || p.output_tokens) {
|
|
4223
|
+
events.push({
|
|
4224
|
+
sessionId,
|
|
4225
|
+
type: "usage",
|
|
4226
|
+
timestamp: entry.timestamp,
|
|
4227
|
+
cwd,
|
|
4228
|
+
agentType: "codex",
|
|
4229
|
+
usage: {
|
|
4230
|
+
model: p.model || "unknown",
|
|
4231
|
+
inputTokens: p.input_tokens || 0,
|
|
4232
|
+
outputTokens: p.output_tokens || 0,
|
|
4233
|
+
cacheReadTokens: p.cache_read_tokens || 0,
|
|
4234
|
+
cacheWriteTokens: p.cache_write_tokens || 0
|
|
4235
|
+
}
|
|
4236
|
+
});
|
|
4237
|
+
}
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
return { events, newOffset: lines.length };
|
|
4241
|
+
}
|
|
3811
4242
|
readSessionFile(filePath, since) {
|
|
3812
4243
|
const events = [];
|
|
3813
4244
|
let content;
|
|
@@ -3831,7 +4262,7 @@ var CodexAdapter = class {
|
|
|
3831
4262
|
if (entryTime <= since.getTime()) continue;
|
|
3832
4263
|
}
|
|
3833
4264
|
const ptype = entry.payload?.type;
|
|
3834
|
-
if (
|
|
4265
|
+
if (entry.type === "turn_context" && entry.payload?.cwd) {
|
|
3835
4266
|
cwd = entry.payload.cwd;
|
|
3836
4267
|
}
|
|
3837
4268
|
if (ptype === "user_message" && entry.payload.message) {
|
|
@@ -3877,6 +4308,47 @@ var CodexAdapter = class {
|
|
|
3877
4308
|
agentType: "codex"
|
|
3878
4309
|
});
|
|
3879
4310
|
}
|
|
4311
|
+
if (ptype === "web_search_end" || ptype === "web_search_call") {
|
|
4312
|
+
const action = entry.payload.action;
|
|
4313
|
+
events.push({
|
|
4314
|
+
sessionId,
|
|
4315
|
+
type: "pre_tool_use",
|
|
4316
|
+
toolName: "web_search",
|
|
4317
|
+
toolInput: action ? { query: action.query, type: action.type } : void 0,
|
|
4318
|
+
timestamp: entry.timestamp,
|
|
4319
|
+
cwd,
|
|
4320
|
+
agentType: "codex"
|
|
4321
|
+
});
|
|
4322
|
+
}
|
|
4323
|
+
if (ptype === "agent_message" && entry.payload.message) {
|
|
4324
|
+
events.push({
|
|
4325
|
+
sessionId,
|
|
4326
|
+
type: "response",
|
|
4327
|
+
toolOutput: entry.payload.message.slice(0, 1e4),
|
|
4328
|
+
timestamp: entry.timestamp,
|
|
4329
|
+
cwd,
|
|
4330
|
+
agentType: "codex"
|
|
4331
|
+
});
|
|
4332
|
+
}
|
|
4333
|
+
if (ptype === "token_count") {
|
|
4334
|
+
const p = entry.payload;
|
|
4335
|
+
if (p.input_tokens || p.output_tokens) {
|
|
4336
|
+
events.push({
|
|
4337
|
+
sessionId,
|
|
4338
|
+
type: "usage",
|
|
4339
|
+
timestamp: entry.timestamp,
|
|
4340
|
+
cwd,
|
|
4341
|
+
agentType: "codex",
|
|
4342
|
+
usage: {
|
|
4343
|
+
model: p.model || "unknown",
|
|
4344
|
+
inputTokens: p.input_tokens || 0,
|
|
4345
|
+
outputTokens: p.output_tokens || 0,
|
|
4346
|
+
cacheReadTokens: p.cache_read_tokens || 0,
|
|
4347
|
+
cacheWriteTokens: p.cache_write_tokens || 0
|
|
4348
|
+
}
|
|
4349
|
+
});
|
|
4350
|
+
}
|
|
4351
|
+
}
|
|
3880
4352
|
}
|
|
3881
4353
|
return events;
|
|
3882
4354
|
}
|
|
@@ -3886,13 +4358,13 @@ var CodexAdapter = class {
|
|
|
3886
4358
|
return match ? `codex-${match[1].slice(0, 8)}` : `codex-${filename.replace(".jsonl", "").slice(-8)}`;
|
|
3887
4359
|
}
|
|
3888
4360
|
static getSessionsDir() {
|
|
3889
|
-
const dir =
|
|
4361
|
+
const dir = join18(homedir15(), ".codex", "sessions");
|
|
3890
4362
|
return existsSync18(dir) ? dir : null;
|
|
3891
4363
|
}
|
|
3892
4364
|
};
|
|
3893
4365
|
|
|
3894
4366
|
// src/adapters/index.ts
|
|
3895
|
-
var STATE_FILE =
|
|
4367
|
+
var STATE_FILE = join19(getImdlDir(), "adapter-state.json");
|
|
3896
4368
|
function loadState() {
|
|
3897
4369
|
try {
|
|
3898
4370
|
if (existsSync19(STATE_FILE)) {
|
|
@@ -3904,7 +4376,7 @@ function loadState() {
|
|
|
3904
4376
|
}
|
|
3905
4377
|
function saveState(state) {
|
|
3906
4378
|
ensureImdlDir();
|
|
3907
|
-
|
|
4379
|
+
writeFileSync7(STATE_FILE, JSON.stringify(state, null, 2), { mode: 384 });
|
|
3908
4380
|
}
|
|
3909
4381
|
var adapters = [
|
|
3910
4382
|
new ClaudeCodeAdapter(),
|
|
@@ -3934,16 +4406,18 @@ async function collectPrompts() {
|
|
|
3934
4406
|
path: dir,
|
|
3935
4407
|
since: new Date(state.lastRun)
|
|
3936
4408
|
});
|
|
3937
|
-
const
|
|
4409
|
+
const usageBySessionDay = /* @__PURE__ */ new Map();
|
|
3938
4410
|
for (const event of events) {
|
|
3939
4411
|
if (event.usage) {
|
|
3940
|
-
const
|
|
4412
|
+
const date = event.timestamp ? event.timestamp.slice(0, 10) : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4413
|
+
const key = `${event.sessionId}|${date}`;
|
|
4414
|
+
const existing = usageBySessionDay.get(key) || { model: event.usage.model, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, date };
|
|
3941
4415
|
existing.model = event.usage.model;
|
|
3942
4416
|
existing.input += event.usage.inputTokens;
|
|
3943
4417
|
existing.output += event.usage.outputTokens;
|
|
3944
4418
|
existing.cacheRead += event.usage.cacheReadTokens;
|
|
3945
4419
|
existing.cacheWrite += event.usage.cacheWriteTokens;
|
|
3946
|
-
|
|
4420
|
+
usageBySessionDay.set(key, existing);
|
|
3947
4421
|
continue;
|
|
3948
4422
|
}
|
|
3949
4423
|
let violation;
|
|
@@ -3966,7 +4440,8 @@ async function collectPrompts() {
|
|
|
3966
4440
|
appendEvent(buffered);
|
|
3967
4441
|
collected++;
|
|
3968
4442
|
}
|
|
3969
|
-
for (const [
|
|
4443
|
+
for (const [key, usage] of usageBySessionDay) {
|
|
4444
|
+
const sessionId = key.split("|")[0];
|
|
3970
4445
|
try {
|
|
3971
4446
|
await fetch(`${config.apiUrl}/api/sessions/${sessionId}/usage`, {
|
|
3972
4447
|
method: "POST",
|
|
@@ -3979,20 +4454,22 @@ async function collectPrompts() {
|
|
|
3979
4454
|
}
|
|
3980
4455
|
}
|
|
3981
4456
|
const cursorAdapter = new CursorAdapter();
|
|
3982
|
-
const
|
|
4457
|
+
const cursorUsageBySessionDay = /* @__PURE__ */ new Map();
|
|
3983
4458
|
if (CursorAdapter.getProjectsDir()) {
|
|
3984
4459
|
try {
|
|
3985
4460
|
const { events: transcriptEvents, newOffsets } = cursorAdapter.extractFromTranscripts(new Date(state.lastRun), state.offsets);
|
|
3986
4461
|
state.offsets = newOffsets;
|
|
3987
4462
|
for (const event of transcriptEvents) {
|
|
3988
4463
|
if (event.usage) {
|
|
3989
|
-
const
|
|
4464
|
+
const date = event.timestamp ? event.timestamp.slice(0, 10) : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
4465
|
+
const key = `${event.sessionId}|${date}`;
|
|
4466
|
+
const existing = cursorUsageBySessionDay.get(key) || { model: event.usage.model, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, date };
|
|
3990
4467
|
existing.model = event.usage.model;
|
|
3991
4468
|
existing.input += event.usage.inputTokens;
|
|
3992
4469
|
existing.output += event.usage.outputTokens;
|
|
3993
4470
|
existing.cacheRead += event.usage.cacheReadTokens;
|
|
3994
4471
|
existing.cacheWrite += event.usage.cacheWriteTokens;
|
|
3995
|
-
|
|
4472
|
+
cursorUsageBySessionDay.set(key, existing);
|
|
3996
4473
|
continue;
|
|
3997
4474
|
}
|
|
3998
4475
|
let violation;
|
|
@@ -4054,7 +4531,8 @@ async function collectPrompts() {
|
|
|
4054
4531
|
}
|
|
4055
4532
|
}
|
|
4056
4533
|
}
|
|
4057
|
-
for (const [
|
|
4534
|
+
for (const [key, usage] of cursorUsageBySessionDay) {
|
|
4535
|
+
const sessionId = key.split("|")[0];
|
|
4058
4536
|
try {
|
|
4059
4537
|
await fetch(`${config.apiUrl}/api/sessions/${sessionId}/usage`, {
|
|
4060
4538
|
method: "POST",
|
|
@@ -4069,11 +4547,13 @@ async function collectPrompts() {
|
|
|
4069
4547
|
if (codexSessionsDir) {
|
|
4070
4548
|
try {
|
|
4071
4549
|
const codexAdapter = new CodexAdapter();
|
|
4072
|
-
const codexEvents =
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4550
|
+
const { events: codexEvents, newOffsets: codexOffsets } = codexAdapter.extractWithOffsets(
|
|
4551
|
+
new Date(state.lastRun),
|
|
4552
|
+
state.offsets
|
|
4553
|
+
);
|
|
4554
|
+
for (const [k, v] of Object.entries(codexOffsets)) {
|
|
4555
|
+
state.offsets[k] = v;
|
|
4556
|
+
}
|
|
4077
4557
|
for (const event of codexEvents) {
|
|
4078
4558
|
let violation;
|
|
4079
4559
|
if (event.prompt) {
|
|
@@ -4087,7 +4567,7 @@ async function collectPrompts() {
|
|
|
4087
4567
|
type: event.type,
|
|
4088
4568
|
toolName: event.toolName,
|
|
4089
4569
|
toolInput: event.toolInput ? redactObject(event.toolInput) : void 0,
|
|
4090
|
-
toolOutput: event.toolOutput ? redactObject(event.toolOutput) : void 0,
|
|
4570
|
+
toolOutput: event.toolOutput ? String(typeof event.toolOutput === "string" ? redactObject(event.toolOutput) : JSON.stringify(redactObject(event.toolOutput))) : void 0,
|
|
4091
4571
|
prompt: event.prompt ? redactObject(event.prompt) : void 0,
|
|
4092
4572
|
timestamp: event.timestamp,
|
|
4093
4573
|
cwd: event.cwd,
|
|
@@ -4124,8 +4604,8 @@ async function collectCommand() {
|
|
|
4124
4604
|
|
|
4125
4605
|
// src/commands/daemon.ts
|
|
4126
4606
|
import pc7 from "picocolors";
|
|
4127
|
-
import { existsSync as existsSync20, readFileSync as readFileSync15, writeFileSync as
|
|
4128
|
-
import { join as
|
|
4607
|
+
import { existsSync as existsSync20, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
|
|
4608
|
+
import { join as join20 } from "path";
|
|
4129
4609
|
var INTERVAL_MS = 3e4;
|
|
4130
4610
|
async function daemonCommand() {
|
|
4131
4611
|
console.log(pc7.cyan("imdl daemon started \u2014 collecting every 30s"));
|
|
@@ -4148,7 +4628,7 @@ async function daemonCommand() {
|
|
|
4148
4628
|
setInterval(tick, INTERVAL_MS);
|
|
4149
4629
|
}
|
|
4150
4630
|
async function ingestAgentEvents() {
|
|
4151
|
-
const agentLog =
|
|
4631
|
+
const agentLog = join20(getBufferDir(), "agent-events.ndjson");
|
|
4152
4632
|
if (!existsSync20(agentLog)) return 0;
|
|
4153
4633
|
const content = readFileSync15(agentLog, "utf-8").trim();
|
|
4154
4634
|
if (!content) return 0;
|
|
@@ -4198,12 +4678,12 @@ async function ingestAgentEvents() {
|
|
|
4198
4678
|
}
|
|
4199
4679
|
}
|
|
4200
4680
|
if (sent === apiEvents.length) {
|
|
4201
|
-
|
|
4681
|
+
writeFileSync8(agentLog, "", { mode: 384 });
|
|
4202
4682
|
}
|
|
4203
4683
|
return sent;
|
|
4204
4684
|
}
|
|
4205
4685
|
async function ingestShellEvents() {
|
|
4206
|
-
const shellLog =
|
|
4686
|
+
const shellLog = join20(getBufferDir(), "shell-events.ndjson");
|
|
4207
4687
|
if (!existsSync20(shellLog)) return 0;
|
|
4208
4688
|
const content = readFileSync15(shellLog, "utf-8").trim();
|
|
4209
4689
|
if (!content) return 0;
|
|
@@ -4242,7 +4722,7 @@ async function ingestShellEvents() {
|
|
|
4242
4722
|
}
|
|
4243
4723
|
}
|
|
4244
4724
|
if (sent === apiEvents.length) {
|
|
4245
|
-
|
|
4725
|
+
writeFileSync8(shellLog, "", { mode: 384 });
|
|
4246
4726
|
}
|
|
4247
4727
|
return sent;
|
|
4248
4728
|
}
|
|
@@ -4310,8 +4790,8 @@ async function requestCommand(options) {
|
|
|
4310
4790
|
// src/commands/lock.ts
|
|
4311
4791
|
import pc9 from "picocolors";
|
|
4312
4792
|
import { createHash as createHash2 } from "crypto";
|
|
4313
|
-
import { readFileSync as readFileSync16, writeFileSync as
|
|
4314
|
-
import { join as
|
|
4793
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync9, existsSync as existsSync21 } from "fs";
|
|
4794
|
+
import { join as join21, resolve as resolve3 } from "path";
|
|
4315
4795
|
var LOCKFILE_NAME = "mcp-lock.json";
|
|
4316
4796
|
function computeIntegrity(server, identity) {
|
|
4317
4797
|
const payload = JSON.stringify({
|
|
@@ -4326,7 +4806,7 @@ function computeIntegrity(server, identity) {
|
|
|
4326
4806
|
}
|
|
4327
4807
|
function getLockfilePath(customPath) {
|
|
4328
4808
|
if (customPath) return resolve3(customPath);
|
|
4329
|
-
return
|
|
4809
|
+
return join21(process.cwd(), LOCKFILE_NAME);
|
|
4330
4810
|
}
|
|
4331
4811
|
async function lockCommand(options) {
|
|
4332
4812
|
const detection = await detectMCPConfigs(options.path);
|
|
@@ -4373,7 +4853,7 @@ async function lockCommand(options) {
|
|
|
4373
4853
|
servers: entries
|
|
4374
4854
|
};
|
|
4375
4855
|
const lockPath = getLockfilePath(options.output);
|
|
4376
|
-
|
|
4856
|
+
writeFileSync9(lockPath, JSON.stringify(lockfile, null, 2), { mode: 420 });
|
|
4377
4857
|
if (options.json) {
|
|
4378
4858
|
console.log(JSON.stringify(lockfile, null, 2));
|
|
4379
4859
|
} else {
|
|
@@ -4817,12 +5297,12 @@ async function checkCompliance(developerId) {
|
|
|
4817
5297
|
}
|
|
4818
5298
|
|
|
4819
5299
|
// src/permissions/fixer.ts
|
|
4820
|
-
import { existsSync as existsSync22, readFileSync as readFileSync17, writeFileSync as
|
|
4821
|
-
import { join as
|
|
4822
|
-
import { homedir as
|
|
5300
|
+
import { existsSync as existsSync22, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
|
|
5301
|
+
import { join as join22 } from "path";
|
|
5302
|
+
import { homedir as homedir16 } from "os";
|
|
4823
5303
|
function buildFixesFromChanges(changes) {
|
|
4824
5304
|
const fixes = [];
|
|
4825
|
-
const home =
|
|
5305
|
+
const home = homedir16();
|
|
4826
5306
|
for (const change of changes) {
|
|
4827
5307
|
const fix = buildFixForAgent(change, home);
|
|
4828
5308
|
if (fix) fixes.push(fix);
|
|
@@ -4832,7 +5312,7 @@ function buildFixesFromChanges(changes) {
|
|
|
4832
5312
|
function buildFixForAgent(change, home) {
|
|
4833
5313
|
const { agentType, category, action, targetGrant } = change;
|
|
4834
5314
|
if (agentType === "claude-code") {
|
|
4835
|
-
const configPath =
|
|
5315
|
+
const configPath = join22(home, ".claude", "settings.json");
|
|
4836
5316
|
if (!existsSync22(configPath)) return null;
|
|
4837
5317
|
try {
|
|
4838
5318
|
const settings = JSON.parse(readFileSync17(configPath, "utf-8"));
|
|
@@ -4863,7 +5343,7 @@ function buildFixForAgent(change, home) {
|
|
|
4863
5343
|
}
|
|
4864
5344
|
}
|
|
4865
5345
|
if (agentType === "cursor") {
|
|
4866
|
-
const configPath =
|
|
5346
|
+
const configPath = join22(home, ".cursor", "settings.json");
|
|
4867
5347
|
if (!existsSync22(configPath)) return null;
|
|
4868
5348
|
try {
|
|
4869
5349
|
const settings = JSON.parse(readFileSync17(configPath, "utf-8"));
|
|
@@ -4875,7 +5355,7 @@ function buildFixForAgent(change, home) {
|
|
|
4875
5355
|
}
|
|
4876
5356
|
}
|
|
4877
5357
|
if (agentType === "codex") {
|
|
4878
|
-
const configPath =
|
|
5358
|
+
const configPath = join22(home, ".codex", "config.json");
|
|
4879
5359
|
if (!existsSync22(configPath)) return null;
|
|
4880
5360
|
try {
|
|
4881
5361
|
const config = JSON.parse(readFileSync17(configPath, "utf-8"));
|
|
@@ -4887,7 +5367,7 @@ function buildFixForAgent(change, home) {
|
|
|
4887
5367
|
}
|
|
4888
5368
|
}
|
|
4889
5369
|
if (agentType === "copilot") {
|
|
4890
|
-
const configPath =
|
|
5370
|
+
const configPath = join22(home, "Library", "Application Support", "Code", "User", "settings.json");
|
|
4891
5371
|
if (!existsSync22(configPath)) return null;
|
|
4892
5372
|
try {
|
|
4893
5373
|
const settings = JSON.parse(readFileSync17(configPath, "utf-8"));
|
|
@@ -4900,7 +5380,7 @@ function buildFixForAgent(change, home) {
|
|
|
4900
5380
|
}
|
|
4901
5381
|
if (agentType === "windsurf") {
|
|
4902
5382
|
if (category === "mcp_tools") {
|
|
4903
|
-
const configPath =
|
|
5383
|
+
const configPath = join22(home, ".windsurf", "mcp.json");
|
|
4904
5384
|
if (!existsSync22(configPath)) return null;
|
|
4905
5385
|
try {
|
|
4906
5386
|
const config = JSON.parse(readFileSync17(configPath, "utf-8"));
|
|
@@ -4970,7 +5450,7 @@ function applyFix(fix) {
|
|
|
4970
5450
|
}
|
|
4971
5451
|
}
|
|
4972
5452
|
if (modified) {
|
|
4973
|
-
|
|
5453
|
+
writeFileSync10(fix.configPath, JSON.stringify(config, null, 2) + "\n");
|
|
4974
5454
|
return { fix, applied: true };
|
|
4975
5455
|
}
|
|
4976
5456
|
return { fix, applied: false, error: "No changes needed" };
|
|
@@ -5207,13 +5687,13 @@ function grantLevel(grant) {
|
|
|
5207
5687
|
|
|
5208
5688
|
// src/commands/gateway.ts
|
|
5209
5689
|
import pc12 from "picocolors";
|
|
5210
|
-
import { existsSync as existsSync23, readFileSync as readFileSync18, writeFileSync as
|
|
5211
|
-
import { join as
|
|
5212
|
-
import { homedir as
|
|
5690
|
+
import { existsSync as existsSync23, readFileSync as readFileSync18, writeFileSync as writeFileSync11, mkdirSync as mkdirSync3 } from "fs";
|
|
5691
|
+
import { join as join23 } from "path";
|
|
5692
|
+
import { homedir as homedir17 } from "os";
|
|
5213
5693
|
import { execSync as execSync2, spawn } from "child_process";
|
|
5214
|
-
var IMDL_DIR3 =
|
|
5215
|
-
var GATEWAY_CONFIG =
|
|
5216
|
-
var GATEWAY_PID_FILE =
|
|
5694
|
+
var IMDL_DIR3 = join23(homedir17(), ".imdl");
|
|
5695
|
+
var GATEWAY_CONFIG = join23(IMDL_DIR3, "gateway.toml");
|
|
5696
|
+
var GATEWAY_PID_FILE = join23(IMDL_DIR3, "gateway.pid");
|
|
5217
5697
|
var DEFAULT_PORT = 9443;
|
|
5218
5698
|
async function gatewayCommand(opts) {
|
|
5219
5699
|
const status = await getGatewayStatus();
|
|
@@ -5289,7 +5769,7 @@ async function gatewayStartCommand(opts) {
|
|
|
5289
5769
|
child.unref();
|
|
5290
5770
|
if (child.pid) {
|
|
5291
5771
|
if (!existsSync23(IMDL_DIR3)) mkdirSync3(IMDL_DIR3, { recursive: true });
|
|
5292
|
-
|
|
5772
|
+
writeFileSync11(GATEWAY_PID_FILE, String(child.pid));
|
|
5293
5773
|
console.log(pc12.green(`Gateway started (PID ${child.pid}, port ${port})`));
|
|
5294
5774
|
console.log(pc12.dim(` Config: ${GATEWAY_CONFIG}`));
|
|
5295
5775
|
console.log(pc12.dim(` Set ANTHROPIC_BASE_URL=http://127.0.0.1:${port} to intercept traffic`));
|
|
@@ -5348,7 +5828,7 @@ async function gatewayDisableCommand(opts) {
|
|
|
5348
5828
|
console.log(pc12.dim("Restart gateway for changes to take effect."));
|
|
5349
5829
|
}
|
|
5350
5830
|
async function gatewayAlertsCommand(opts) {
|
|
5351
|
-
const logPath =
|
|
5831
|
+
const logPath = join23(IMDL_DIR3, "gateway-alerts.jsonl");
|
|
5352
5832
|
if (!existsSync23(logPath)) {
|
|
5353
5833
|
console.log(pc12.dim("No alerts logged yet."));
|
|
5354
5834
|
return;
|
|
@@ -5387,9 +5867,26 @@ async function gatewayAlertsCommand(opts) {
|
|
|
5387
5867
|
}
|
|
5388
5868
|
}
|
|
5389
5869
|
}
|
|
5390
|
-
async function
|
|
5870
|
+
async function gatewayDashboardCommand() {
|
|
5391
5871
|
const port = getConfiguredPort();
|
|
5392
|
-
|
|
5872
|
+
const url = `http://127.0.0.1:${port}/dashboard`;
|
|
5873
|
+
try {
|
|
5874
|
+
const controller = new AbortController();
|
|
5875
|
+
const timeout = setTimeout(() => controller.abort(), 2e3);
|
|
5876
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`, { signal: controller.signal });
|
|
5877
|
+
clearTimeout(timeout);
|
|
5878
|
+
if (!res.ok) throw new Error();
|
|
5879
|
+
} catch {
|
|
5880
|
+
console.log(pc12.red("Gateway is not running. Start it first: `imdl gateway start`"));
|
|
5881
|
+
return;
|
|
5882
|
+
}
|
|
5883
|
+
console.log(pc12.green(`Opening dashboard: ${url}`));
|
|
5884
|
+
const { exec } = await import("child_process");
|
|
5885
|
+
exec(`open "${url}"`);
|
|
5886
|
+
}
|
|
5887
|
+
async function gatewayCostCommand(opts) {
|
|
5888
|
+
const port = getConfiguredPort();
|
|
5889
|
+
let data;
|
|
5393
5890
|
try {
|
|
5394
5891
|
const controller = new AbortController();
|
|
5395
5892
|
const timeout = setTimeout(() => controller.abort(), 2e3);
|
|
@@ -5483,9 +5980,9 @@ function getConfiguredPort() {
|
|
|
5483
5980
|
}
|
|
5484
5981
|
function findGatewayBinary() {
|
|
5485
5982
|
const candidates = [
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5983
|
+
join23(process.cwd(), "target", "release", "imdl-gateway"),
|
|
5984
|
+
join23(process.cwd(), "..", "ai-gateway", "target", "release", "imdl-gateway"),
|
|
5985
|
+
join23(homedir17(), ".imdl", "bin", "imdl-gateway")
|
|
5489
5986
|
];
|
|
5490
5987
|
for (const path of candidates) {
|
|
5491
5988
|
if (existsSync23(path)) return path;
|
|
@@ -5526,7 +6023,7 @@ openai_url = "https://api.openai.com"
|
|
|
5526
6023
|
[audit]
|
|
5527
6024
|
enabled = true
|
|
5528
6025
|
`;
|
|
5529
|
-
|
|
6026
|
+
writeFileSync11(GATEWAY_CONFIG, defaultToml, { mode: 384 });
|
|
5530
6027
|
}
|
|
5531
6028
|
function normalizeModule(input) {
|
|
5532
6029
|
const map = {
|
|
@@ -5549,7 +6046,7 @@ function loadGatewayToml() {
|
|
|
5549
6046
|
return readFileSync18(GATEWAY_CONFIG, "utf-8");
|
|
5550
6047
|
}
|
|
5551
6048
|
function saveGatewayToml(content) {
|
|
5552
|
-
|
|
6049
|
+
writeFileSync11(GATEWAY_CONFIG, content, { mode: 384 });
|
|
5553
6050
|
}
|
|
5554
6051
|
function setModuleEnabled(content, module, enabled) {
|
|
5555
6052
|
const sectionRegex = new RegExp(`(\\[${module}\\][^\\[]*?)enabled\\s*=\\s*(true|false)`, "s");
|
|
@@ -5563,6 +6060,153 @@ function printModule(name, enabled, mode) {
|
|
|
5563
6060
|
const modeStr = mode ? pc12.dim(` (${mode})`) : "";
|
|
5564
6061
|
console.log(` ${icon} ${name}${modeStr}`);
|
|
5565
6062
|
}
|
|
6063
|
+
async function gatewayActivateCommand(opts = {}) {
|
|
6064
|
+
const port = getConfiguredPort();
|
|
6065
|
+
try {
|
|
6066
|
+
const controller = new AbortController();
|
|
6067
|
+
const timeout = setTimeout(() => controller.abort(), 3e3);
|
|
6068
|
+
const res = await fetch(`http://127.0.0.1:${port}/health`, { signal: controller.signal });
|
|
6069
|
+
clearTimeout(timeout);
|
|
6070
|
+
if (!res.ok) {
|
|
6071
|
+
console.log(pc12.red("Gateway is not responding. Start it first: `imdl gateway start`"));
|
|
6072
|
+
return;
|
|
6073
|
+
}
|
|
6074
|
+
} catch {
|
|
6075
|
+
console.log(pc12.red(`Cannot reach gateway on port ${port}. Start it first: \`imdl gateway start\``));
|
|
6076
|
+
return;
|
|
6077
|
+
}
|
|
6078
|
+
if (opts.copilot) {
|
|
6079
|
+
activateCopilot(port);
|
|
6080
|
+
return;
|
|
6081
|
+
}
|
|
6082
|
+
const claudeSettings = getClaudeSettingsPath();
|
|
6083
|
+
if (!claudeSettings) {
|
|
6084
|
+
console.log(pc12.red("Claude Code settings not found at ~/.claude/settings.json"));
|
|
6085
|
+
return;
|
|
6086
|
+
}
|
|
6087
|
+
let settings;
|
|
6088
|
+
try {
|
|
6089
|
+
settings = JSON.parse(readFileSync18(claudeSettings, "utf-8"));
|
|
6090
|
+
} catch {
|
|
6091
|
+
console.log(pc12.red("Could not parse Claude Code settings."));
|
|
6092
|
+
return;
|
|
6093
|
+
}
|
|
6094
|
+
if (!settings.env) settings.env = {};
|
|
6095
|
+
settings.env.ANTHROPIC_BEDROCK_BASE_URL = `http://127.0.0.1:${port}`;
|
|
6096
|
+
if (opts.codex) {
|
|
6097
|
+
settings.env.OPENAI_BASE_URL = `http://127.0.0.1:${port}`;
|
|
6098
|
+
updateCodexConfig(port, true);
|
|
6099
|
+
}
|
|
6100
|
+
writeFileSync11(claudeSettings, JSON.stringify(settings, null, 2) + "\n");
|
|
6101
|
+
console.log(pc12.green("Gateway activated for Claude Code"));
|
|
6102
|
+
console.log(pc12.dim(` ANTHROPIC_BEDROCK_BASE_URL \u2192 http://127.0.0.1:${port}`));
|
|
6103
|
+
if (opts.codex) {
|
|
6104
|
+
console.log(pc12.dim(` OPENAI_BASE_URL \u2192 http://127.0.0.1:${port}`));
|
|
6105
|
+
console.log(pc12.dim(" Codex WebSocket traffic will also route through the gateway."));
|
|
6106
|
+
}
|
|
6107
|
+
console.log(pc12.dim(" New Claude Code sessions will route through the gateway."));
|
|
6108
|
+
console.log(pc12.dim(" Run `imdl gateway deactivate` to revert immediately."));
|
|
6109
|
+
console.log("");
|
|
6110
|
+
console.log(pc12.yellow(" \u26A0 Current session is unaffected. Only NEW sessions use the gateway."));
|
|
6111
|
+
console.log(pc12.yellow(" \u26A0 If gateway goes down, run `imdl gateway deactivate` to restore direct access."));
|
|
6112
|
+
}
|
|
6113
|
+
function activateCopilot(port) {
|
|
6114
|
+
const caCertPath = join23(homedir17(), ".imdl", "gateway-ca.pem");
|
|
6115
|
+
if (!existsSync23(caCertPath)) {
|
|
6116
|
+
console.log(pc12.red("CA certificate not found at " + caCertPath));
|
|
6117
|
+
console.log(pc12.dim("The gateway generates this automatically on startup."));
|
|
6118
|
+
console.log(pc12.dim("Start the gateway first: `imdl gateway start`"));
|
|
6119
|
+
return;
|
|
6120
|
+
}
|
|
6121
|
+
console.log("");
|
|
6122
|
+
console.log(pc12.bold(pc12.cyan("Copilot Gateway Activation")));
|
|
6123
|
+
console.log(pc12.dim("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
6124
|
+
console.log("");
|
|
6125
|
+
console.log(pc12.green("CA certificate: ") + caCertPath);
|
|
6126
|
+
console.log("");
|
|
6127
|
+
console.log(pc12.bold(" VS Code Settings (settings.json):"));
|
|
6128
|
+
console.log("");
|
|
6129
|
+
console.log(pc12.cyan(" {"));
|
|
6130
|
+
console.log(pc12.cyan(` "http.proxy": "http://127.0.0.1:${port}",`));
|
|
6131
|
+
console.log(pc12.cyan(' "http.proxyStrictSSL": false'));
|
|
6132
|
+
console.log(pc12.cyan(" }"));
|
|
6133
|
+
console.log("");
|
|
6134
|
+
console.log(pc12.bold(" OR use NODE_EXTRA_CA_CERTS (strict TLS):"));
|
|
6135
|
+
console.log("");
|
|
6136
|
+
console.log(pc12.cyan(` export NODE_EXTRA_CA_CERTS="${caCertPath}"`));
|
|
6137
|
+
console.log("");
|
|
6138
|
+
console.log(pc12.dim(" Then set the proxy in VS Code settings:"));
|
|
6139
|
+
console.log(pc12.cyan(` "http.proxy": "http://127.0.0.1:${port}"`));
|
|
6140
|
+
console.log("");
|
|
6141
|
+
console.log(pc12.bold(" What happens:"));
|
|
6142
|
+
console.log(pc12.dim(" - Copilot traffic (api.githubcopilot.com) is intercepted"));
|
|
6143
|
+
console.log(pc12.dim(" - Context optimization reduces token usage"));
|
|
6144
|
+
console.log(pc12.dim(" - Cost tracking reports Copilot spend"));
|
|
6145
|
+
console.log(pc12.dim(" - All other HTTPS traffic tunnels through transparently"));
|
|
6146
|
+
console.log("");
|
|
6147
|
+
}
|
|
6148
|
+
async function gatewayDeactivateCommand() {
|
|
6149
|
+
const claudeSettings = getClaudeSettingsPath();
|
|
6150
|
+
if (!claudeSettings) {
|
|
6151
|
+
console.log(pc12.red("Claude Code settings not found at ~/.claude/settings.json"));
|
|
6152
|
+
return;
|
|
6153
|
+
}
|
|
6154
|
+
let settings;
|
|
6155
|
+
try {
|
|
6156
|
+
settings = JSON.parse(readFileSync18(claudeSettings, "utf-8"));
|
|
6157
|
+
} catch {
|
|
6158
|
+
console.log(pc12.red("Could not parse Claude Code settings."));
|
|
6159
|
+
return;
|
|
6160
|
+
}
|
|
6161
|
+
if (settings.env) {
|
|
6162
|
+
delete settings.env.ANTHROPIC_BEDROCK_BASE_URL;
|
|
6163
|
+
delete settings.env.ANTHROPIC_BASE_URL;
|
|
6164
|
+
delete settings.env.OPENAI_BASE_URL;
|
|
6165
|
+
}
|
|
6166
|
+
writeFileSync11(claudeSettings, JSON.stringify(settings, null, 2) + "\n");
|
|
6167
|
+
updateCodexConfig(0, false);
|
|
6168
|
+
console.log(pc12.green("Gateway deactivated"));
|
|
6169
|
+
console.log(pc12.dim(" Claude Code will connect directly to Bedrock/Anthropic (no gateway)."));
|
|
6170
|
+
console.log(pc12.dim(" Codex will connect directly to OpenAI (no gateway)."));
|
|
6171
|
+
console.log(pc12.dim(" Existing sessions are unaffected \u2014 only new sessions pick up this change."));
|
|
6172
|
+
}
|
|
6173
|
+
function getClaudeSettingsPath() {
|
|
6174
|
+
const candidates = [
|
|
6175
|
+
join23(homedir17(), ".claude", "settings.json")
|
|
6176
|
+
];
|
|
6177
|
+
for (const p of candidates) {
|
|
6178
|
+
if (existsSync23(p)) return p;
|
|
6179
|
+
}
|
|
6180
|
+
return null;
|
|
6181
|
+
}
|
|
6182
|
+
function updateCodexConfig(port, enable) {
|
|
6183
|
+
const codexConfig = join23(homedir17(), ".codex", "config.toml");
|
|
6184
|
+
if (!existsSync23(codexConfig)) return;
|
|
6185
|
+
try {
|
|
6186
|
+
let content = readFileSync18(codexConfig, "utf-8");
|
|
6187
|
+
if (enable) {
|
|
6188
|
+
if (content.includes("# openai_base_url")) {
|
|
6189
|
+
content = content.replace(/# *openai_base_url *= *"[^"]*"/, `openai_base_url = "http://127.0.0.1:${port}"`);
|
|
6190
|
+
} else if (content.includes("openai_base_url")) {
|
|
6191
|
+
content = content.replace(/openai_base_url *= *"[^"]*"/, `openai_base_url = "http://127.0.0.1:${port}"`);
|
|
6192
|
+
} else {
|
|
6193
|
+
if (content.includes("[model_providers]")) {
|
|
6194
|
+
content = content.replace("[model_providers]", `openai_base_url = "http://127.0.0.1:${port}"
|
|
6195
|
+
|
|
6196
|
+
[model_providers]`);
|
|
6197
|
+
} else {
|
|
6198
|
+
content += `
|
|
6199
|
+
openai_base_url = "http://127.0.0.1:${port}"
|
|
6200
|
+
`;
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
} else {
|
|
6204
|
+
content = content.replace(/^(openai_base_url *= *"[^"]*")/m, "# $1");
|
|
6205
|
+
}
|
|
6206
|
+
writeFileSync11(codexConfig, content);
|
|
6207
|
+
} catch {
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
5566
6210
|
function formatUptime(seconds) {
|
|
5567
6211
|
if (seconds < 60) return `${seconds}s`;
|
|
5568
6212
|
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
|
|
@@ -5571,24 +6215,712 @@ function formatUptime(seconds) {
|
|
|
5571
6215
|
return `${h}h ${m}m`;
|
|
5572
6216
|
}
|
|
5573
6217
|
|
|
6218
|
+
// src/commands/sync-history.ts
|
|
6219
|
+
import { createReadStream as createReadStream2, existsSync as existsSync24, readdirSync as readdirSync6, statSync as statSync11 } from "fs";
|
|
6220
|
+
import { createInterface as createInterface2 } from "readline";
|
|
6221
|
+
import { join as join24 } from "path";
|
|
6222
|
+
import { homedir as homedir18 } from "os";
|
|
6223
|
+
import pc13 from "picocolors";
|
|
6224
|
+
var BATCH_SIZE2 = 50;
|
|
6225
|
+
var SEND_TIMEOUT = 15e3;
|
|
6226
|
+
async function syncHistoryCommand(opts) {
|
|
6227
|
+
const config = loadConfig();
|
|
6228
|
+
if (!config.apiUrl || !config.developerId) {
|
|
6229
|
+
console.log(pc13.red("Not configured. Run `imdl init` first."));
|
|
6230
|
+
return;
|
|
6231
|
+
}
|
|
6232
|
+
const days = parseInt(opts.days || "30", 10);
|
|
6233
|
+
const since = new Date(Date.now() - days * 864e5);
|
|
6234
|
+
const agentFilter = opts.agent?.toLowerCase();
|
|
6235
|
+
console.log(pc13.bold("Syncing historical session data"));
|
|
6236
|
+
console.log(pc13.dim(` Period: last ${days} days (since ${since.toISOString().split("T")[0]})`));
|
|
6237
|
+
console.log(pc13.dim(` Agent filter: ${agentFilter || "all"}`));
|
|
6238
|
+
if (opts.dryRun) console.log(pc13.yellow(" DRY RUN \u2014 no data will be sent"));
|
|
6239
|
+
console.log("");
|
|
6240
|
+
const stats = {
|
|
6241
|
+
filesScanned: 0,
|
|
6242
|
+
filesSkipped: 0,
|
|
6243
|
+
eventsProcessed: 0,
|
|
6244
|
+
eventsSent: 0,
|
|
6245
|
+
violationsFound: 0,
|
|
6246
|
+
secretsFound: 0,
|
|
6247
|
+
errors: 0,
|
|
6248
|
+
bytesProcessed: 0
|
|
6249
|
+
};
|
|
6250
|
+
const sessionFiles = [];
|
|
6251
|
+
if (!agentFilter || agentFilter === "claude-code" || agentFilter === "claude") {
|
|
6252
|
+
const claudeDirs = ClaudeCodeAdapter.getAllProjectDirs();
|
|
6253
|
+
for (const dir of claudeDirs) {
|
|
6254
|
+
const sessionsDir = join24(dir, "sessions");
|
|
6255
|
+
if (!existsSync24(sessionsDir)) continue;
|
|
6256
|
+
for (const file of safeReaddir(sessionsDir)) {
|
|
6257
|
+
if (!file.endsWith(".jsonl")) continue;
|
|
6258
|
+
const full = join24(sessionsDir, file);
|
|
6259
|
+
const stat = safeStat(full);
|
|
6260
|
+
if (!stat) continue;
|
|
6261
|
+
if (stat.mtimeMs < since.getTime()) continue;
|
|
6262
|
+
sessionFiles.push({ path: full, agent: "claude-code", size: stat.size });
|
|
6263
|
+
}
|
|
6264
|
+
}
|
|
6265
|
+
}
|
|
6266
|
+
if (!agentFilter || agentFilter === "codex") {
|
|
6267
|
+
const codexDir = join24(homedir18(), ".codex", "sessions");
|
|
6268
|
+
if (existsSync24(codexDir)) {
|
|
6269
|
+
const files = findJsonlFiles(codexDir, since);
|
|
6270
|
+
for (const f of files) {
|
|
6271
|
+
const stat = safeStat(f);
|
|
6272
|
+
if (stat) sessionFiles.push({ path: f, agent: "codex", size: stat.size });
|
|
6273
|
+
}
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
6276
|
+
if (!agentFilter || agentFilter === "cursor") {
|
|
6277
|
+
const cursorProjects = join24(homedir18(), ".cursor", "projects");
|
|
6278
|
+
if (existsSync24(cursorProjects)) {
|
|
6279
|
+
const files = findJsonlFiles(cursorProjects, since);
|
|
6280
|
+
for (const f of files) {
|
|
6281
|
+
const stat = safeStat(f);
|
|
6282
|
+
if (stat) sessionFiles.push({ path: f, agent: "cursor", size: stat.size });
|
|
6283
|
+
}
|
|
6284
|
+
}
|
|
6285
|
+
}
|
|
6286
|
+
const totalSize = sessionFiles.reduce((sum, f) => sum + f.size, 0);
|
|
6287
|
+
console.log(pc13.dim(`Found ${sessionFiles.length} session files (${formatBytes2(totalSize)})`));
|
|
6288
|
+
console.log("");
|
|
6289
|
+
if (sessionFiles.length === 0) {
|
|
6290
|
+
console.log(pc13.dim("No session files found for the specified period."));
|
|
6291
|
+
return;
|
|
6292
|
+
}
|
|
6293
|
+
for (let i = 0; i < sessionFiles.length; i++) {
|
|
6294
|
+
const { path: filePath, agent, size } = sessionFiles[i];
|
|
6295
|
+
const shortPath = filePath.replace(homedir18(), "~");
|
|
6296
|
+
const progress = `[${i + 1}/${sessionFiles.length}]`;
|
|
6297
|
+
process.stdout.write(`${pc13.dim(progress)} ${shortPath} (${formatBytes2(size)})...`);
|
|
6298
|
+
try {
|
|
6299
|
+
const result = await processFile(filePath, agent, since, config, opts.dryRun || false);
|
|
6300
|
+
stats.filesScanned++;
|
|
6301
|
+
stats.eventsProcessed += result.events;
|
|
6302
|
+
stats.eventsSent += result.sent;
|
|
6303
|
+
stats.violationsFound += result.violations;
|
|
6304
|
+
stats.secretsFound += result.secrets;
|
|
6305
|
+
stats.bytesProcessed += size;
|
|
6306
|
+
const parts = [];
|
|
6307
|
+
if (result.events > 0) parts.push(`${result.events} events`);
|
|
6308
|
+
if (result.violations > 0) parts.push(pc13.yellow(`${result.violations} violations`));
|
|
6309
|
+
if (result.secrets > 0) parts.push(pc13.red(`${result.secrets} secrets`));
|
|
6310
|
+
if (parts.length > 0) {
|
|
6311
|
+
console.log(` ${parts.join(", ")}`);
|
|
6312
|
+
} else {
|
|
6313
|
+
console.log(pc13.dim(" (no new events)"));
|
|
6314
|
+
}
|
|
6315
|
+
} catch {
|
|
6316
|
+
stats.errors++;
|
|
6317
|
+
stats.filesSkipped++;
|
|
6318
|
+
console.log(pc13.red(" error"));
|
|
6319
|
+
}
|
|
6320
|
+
}
|
|
6321
|
+
console.log("");
|
|
6322
|
+
console.log(pc13.bold("\u2500\u2500\u2500 Sync Complete \u2500\u2500\u2500"));
|
|
6323
|
+
console.log(` Files processed: ${stats.filesScanned} (${formatBytes2(stats.bytesProcessed)})`);
|
|
6324
|
+
if (stats.filesSkipped > 0) console.log(` Files skipped: ${pc13.yellow(String(stats.filesSkipped))}`);
|
|
6325
|
+
console.log(` Events found: ${stats.eventsProcessed}`);
|
|
6326
|
+
if (!opts.dryRun) console.log(` Events synced: ${pc13.green(String(stats.eventsSent))}`);
|
|
6327
|
+
if (stats.violationsFound > 0) {
|
|
6328
|
+
console.log(` ${pc13.yellow("\u26A0")} Historical violations: ${pc13.yellow(String(stats.violationsFound))}`);
|
|
6329
|
+
console.log(pc13.dim(" These violations occurred before IMDL was monitoring."));
|
|
6330
|
+
console.log(pc13.dim(" They are tagged as historical in the dashboard."));
|
|
6331
|
+
}
|
|
6332
|
+
if (stats.secretsFound > 0) {
|
|
6333
|
+
console.log(` ${pc13.red("\u26A0")} Secrets detected: ${pc13.red(String(stats.secretsFound))}`);
|
|
6334
|
+
console.log(pc13.dim(" Secrets were found in historical prompts/tool calls."));
|
|
6335
|
+
console.log(pc13.dim(" Review in Dashboard \u2192 Violations (filter: historical)."));
|
|
6336
|
+
}
|
|
6337
|
+
if (stats.errors > 0) console.log(` Errors: ${pc13.red(String(stats.errors))}`);
|
|
6338
|
+
}
|
|
6339
|
+
async function processFile(filePath, agent, since, config, dryRun) {
|
|
6340
|
+
const result = { events: 0, sent: 0, violations: 0, secrets: 0 };
|
|
6341
|
+
const batch = [];
|
|
6342
|
+
const sessionId = deriveSessionId(filePath, agent);
|
|
6343
|
+
let cwd;
|
|
6344
|
+
const rl = createInterface2({
|
|
6345
|
+
input: createReadStream2(filePath, { encoding: "utf-8" }),
|
|
6346
|
+
crlfDelay: Infinity
|
|
6347
|
+
});
|
|
6348
|
+
for await (const line of rl) {
|
|
6349
|
+
if (!line.trim()) continue;
|
|
6350
|
+
let entry;
|
|
6351
|
+
try {
|
|
6352
|
+
entry = JSON.parse(line);
|
|
6353
|
+
} catch {
|
|
6354
|
+
continue;
|
|
6355
|
+
}
|
|
6356
|
+
const ts = entry.timestamp || entry.ts;
|
|
6357
|
+
if (ts) {
|
|
6358
|
+
const entryTime = new Date(ts).getTime();
|
|
6359
|
+
if (entryTime < since.getTime()) continue;
|
|
6360
|
+
}
|
|
6361
|
+
const event = parseEntry(entry, agent, sessionId, cwd);
|
|
6362
|
+
if (!event && entry.type === "turn_context" && entry.payload?.cwd) {
|
|
6363
|
+
cwd = entry.payload.cwd;
|
|
6364
|
+
continue;
|
|
6365
|
+
}
|
|
6366
|
+
if (!event) continue;
|
|
6367
|
+
if (event.cwd) cwd = event.cwd;
|
|
6368
|
+
let violation;
|
|
6369
|
+
if (event.prompt) {
|
|
6370
|
+
const scan = scanPromptForSecrets(event.prompt);
|
|
6371
|
+
if (scan.hasSecrets) {
|
|
6372
|
+
violation = createPromptViolation(scan.findings);
|
|
6373
|
+
if (violation) violation.action = "logged";
|
|
6374
|
+
result.violations++;
|
|
6375
|
+
result.secrets += scan.findings.length;
|
|
6376
|
+
}
|
|
6377
|
+
}
|
|
6378
|
+
if (event.toolInput) {
|
|
6379
|
+
const inputStr = JSON.stringify(event.toolInput);
|
|
6380
|
+
const scan = scanPromptForSecrets(inputStr);
|
|
6381
|
+
if (scan.hasSecrets && !violation) {
|
|
6382
|
+
violation = {
|
|
6383
|
+
policyId: "builtin:prompt-secrets",
|
|
6384
|
+
ruleId: "tool-input-secret-scan",
|
|
6385
|
+
action: "logged",
|
|
6386
|
+
reason: `Secret in tool input (historical): ${scan.findings.map((f) => f.label).join(", ")}`
|
|
6387
|
+
};
|
|
6388
|
+
result.violations++;
|
|
6389
|
+
result.secrets += scan.findings.length;
|
|
6390
|
+
}
|
|
6391
|
+
}
|
|
6392
|
+
const buffered = {
|
|
6393
|
+
sessionId: event.sessionId,
|
|
6394
|
+
type: event.type,
|
|
6395
|
+
toolName: event.toolName,
|
|
6396
|
+
toolInput: event.toolInput ? redactObject(event.toolInput) : void 0,
|
|
6397
|
+
toolOutput: event.toolOutput ? redactObject(event.toolOutput) : void 0,
|
|
6398
|
+
prompt: event.prompt ? redactObject(event.prompt) : void 0,
|
|
6399
|
+
timestamp: event.timestamp,
|
|
6400
|
+
cwd: event.cwd || cwd,
|
|
6401
|
+
agentType: agent,
|
|
6402
|
+
violation
|
|
6403
|
+
};
|
|
6404
|
+
batch.push(buffered);
|
|
6405
|
+
result.events++;
|
|
6406
|
+
if (batch.length >= BATCH_SIZE2) {
|
|
6407
|
+
if (!dryRun) {
|
|
6408
|
+
const sent = await sendBatch2(config, sessionId, batch.splice(0, BATCH_SIZE2), agent);
|
|
6409
|
+
if (sent) result.sent += BATCH_SIZE2;
|
|
6410
|
+
} else {
|
|
6411
|
+
batch.splice(0, BATCH_SIZE2);
|
|
6412
|
+
}
|
|
6413
|
+
}
|
|
6414
|
+
}
|
|
6415
|
+
if (batch.length > 0 && !dryRun) {
|
|
6416
|
+
const sent = await sendBatch2(config, sessionId, batch, agent);
|
|
6417
|
+
if (sent) result.sent += batch.length;
|
|
6418
|
+
} else if (batch.length > 0) {
|
|
6419
|
+
}
|
|
6420
|
+
return result;
|
|
6421
|
+
}
|
|
6422
|
+
function parseEntry(entry, agent, sessionId, cwd) {
|
|
6423
|
+
const ts = entry.timestamp || entry.ts || (/* @__PURE__ */ new Date()).toISOString();
|
|
6424
|
+
if (agent === "codex") {
|
|
6425
|
+
const ptype = entry.payload?.type;
|
|
6426
|
+
if (ptype === "user_message" && entry.payload.message?.trim()) {
|
|
6427
|
+
return { sessionId, type: "user_prompt", prompt: entry.payload.message.trim().slice(0, 1e4), timestamp: ts, cwd };
|
|
6428
|
+
}
|
|
6429
|
+
if (ptype === "function_call" && entry.payload.name) {
|
|
6430
|
+
let toolInput;
|
|
6431
|
+
if (entry.payload.arguments) {
|
|
6432
|
+
try {
|
|
6433
|
+
toolInput = JSON.parse(entry.payload.arguments);
|
|
6434
|
+
} catch {
|
|
6435
|
+
toolInput = { raw: entry.payload.arguments };
|
|
6436
|
+
}
|
|
6437
|
+
}
|
|
6438
|
+
return { sessionId, type: "pre_tool_use", toolName: entry.payload.name, toolInput, timestamp: ts, cwd };
|
|
6439
|
+
}
|
|
6440
|
+
if (ptype === "function_call_output" && entry.payload.output) {
|
|
6441
|
+
return { sessionId, type: "post_tool_use", toolName: entry.payload.call_id || "unknown", toolOutput: entry.payload.output.slice(0, 1e4), timestamp: ts, cwd };
|
|
6442
|
+
}
|
|
6443
|
+
return null;
|
|
6444
|
+
}
|
|
6445
|
+
if (agent === "claude-code") {
|
|
6446
|
+
if (entry.type === "human" && entry.message?.content) {
|
|
6447
|
+
const text = Array.isArray(entry.message.content) ? entry.message.content.filter((b) => b.type === "text").map((b) => b.text).join("\n") : typeof entry.message.content === "string" ? entry.message.content : "";
|
|
6448
|
+
if (text.trim()) {
|
|
6449
|
+
return { sessionId, type: "user_prompt", prompt: text.trim().slice(0, 1e4), timestamp: ts, cwd: entry.cwd || cwd };
|
|
6450
|
+
}
|
|
6451
|
+
}
|
|
6452
|
+
if (entry.type === "assistant" && entry.message?.content) {
|
|
6453
|
+
const blocks = Array.isArray(entry.message.content) ? entry.message.content : [];
|
|
6454
|
+
for (const block of blocks) {
|
|
6455
|
+
if (block.type === "tool_use") {
|
|
6456
|
+
return { sessionId, type: "pre_tool_use", toolName: block.name, toolInput: block.input, timestamp: ts, cwd: entry.cwd || cwd };
|
|
6457
|
+
}
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6460
|
+
if (entry.type === "tool_result" || entry.type === "result") {
|
|
6461
|
+
const output = typeof entry.content === "string" ? entry.content : JSON.stringify(entry.content || "").slice(0, 1e4);
|
|
6462
|
+
return { sessionId, type: "post_tool_use", toolName: entry.tool_use_id || "unknown", toolOutput: output, timestamp: ts, cwd: entry.cwd || cwd };
|
|
6463
|
+
}
|
|
6464
|
+
return null;
|
|
6465
|
+
}
|
|
6466
|
+
if (agent === "cursor") {
|
|
6467
|
+
if (entry.role === "user" && entry.content) {
|
|
6468
|
+
const text = typeof entry.content === "string" ? entry.content : "";
|
|
6469
|
+
if (text.trim()) {
|
|
6470
|
+
return { sessionId, type: "user_prompt", prompt: text.trim().slice(0, 1e4), timestamp: ts, cwd };
|
|
6471
|
+
}
|
|
6472
|
+
}
|
|
6473
|
+
if (entry.role === "assistant" && entry.tool_calls) {
|
|
6474
|
+
for (const tc of entry.tool_calls) {
|
|
6475
|
+
if (tc.function?.name) {
|
|
6476
|
+
let toolInput;
|
|
6477
|
+
if (tc.function.arguments) {
|
|
6478
|
+
try {
|
|
6479
|
+
toolInput = JSON.parse(tc.function.arguments);
|
|
6480
|
+
} catch {
|
|
6481
|
+
toolInput = { raw: tc.function.arguments };
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
return { sessionId, type: "pre_tool_use", toolName: tc.function.name, toolInput, timestamp: ts, cwd };
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6487
|
+
}
|
|
6488
|
+
return null;
|
|
6489
|
+
}
|
|
6490
|
+
return null;
|
|
6491
|
+
}
|
|
6492
|
+
async function sendBatch2(config, sessionId, events, agentType) {
|
|
6493
|
+
const headers = { "Content-Type": "application/json" };
|
|
6494
|
+
if (config.authToken) headers["X-IMDL-Key"] = config.authToken;
|
|
6495
|
+
try {
|
|
6496
|
+
const res = await fetch(`${config.apiUrl}/api/sessions/${sessionId}/events`, {
|
|
6497
|
+
method: "POST",
|
|
6498
|
+
headers,
|
|
6499
|
+
body: JSON.stringify({
|
|
6500
|
+
events,
|
|
6501
|
+
developerId: config.developerId,
|
|
6502
|
+
agentType,
|
|
6503
|
+
historical: true
|
|
6504
|
+
}),
|
|
6505
|
+
signal: AbortSignal.timeout(SEND_TIMEOUT)
|
|
6506
|
+
});
|
|
6507
|
+
return res.ok;
|
|
6508
|
+
} catch {
|
|
6509
|
+
return false;
|
|
6510
|
+
}
|
|
6511
|
+
}
|
|
6512
|
+
function deriveSessionId(filePath, agent) {
|
|
6513
|
+
const filename = filePath.split("/").pop() || "";
|
|
6514
|
+
if (agent === "codex") {
|
|
6515
|
+
const match = filename.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/);
|
|
6516
|
+
return match ? `codex-${match[1].slice(0, 8)}` : `codex-${filename.replace(".jsonl", "").slice(-8)}`;
|
|
6517
|
+
}
|
|
6518
|
+
if (agent === "claude-code") {
|
|
6519
|
+
const parts = filePath.split("/");
|
|
6520
|
+
const sessionsIdx = parts.indexOf("sessions");
|
|
6521
|
+
if (sessionsIdx >= 0 && parts[sessionsIdx + 1]) {
|
|
6522
|
+
return parts[sessionsIdx + 1];
|
|
6523
|
+
}
|
|
6524
|
+
return `cc-${filename.replace(".jsonl", "").slice(-8)}`;
|
|
6525
|
+
}
|
|
6526
|
+
if (agent === "cursor") {
|
|
6527
|
+
const match = filename.match(/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/);
|
|
6528
|
+
return match ? `cursor-${match[1].slice(0, 8)}` : `cursor-${filename.replace(".jsonl", "").slice(-8)}`;
|
|
6529
|
+
}
|
|
6530
|
+
return `unknown-${filename.slice(-8)}`;
|
|
6531
|
+
}
|
|
6532
|
+
function findJsonlFiles(basePath, since) {
|
|
6533
|
+
const files = [];
|
|
6534
|
+
const walk = (dir) => {
|
|
6535
|
+
try {
|
|
6536
|
+
for (const entry of readdirSync6(dir)) {
|
|
6537
|
+
const full = join24(dir, entry);
|
|
6538
|
+
try {
|
|
6539
|
+
const stat = statSync11(full);
|
|
6540
|
+
if (stat.isDirectory()) walk(full);
|
|
6541
|
+
else if (entry.endsWith(".jsonl") && stat.mtimeMs >= since.getTime()) {
|
|
6542
|
+
files.push(full);
|
|
6543
|
+
}
|
|
6544
|
+
} catch {
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
} catch {
|
|
6548
|
+
}
|
|
6549
|
+
};
|
|
6550
|
+
walk(basePath);
|
|
6551
|
+
return files;
|
|
6552
|
+
}
|
|
6553
|
+
function safeReaddir(dir) {
|
|
6554
|
+
try {
|
|
6555
|
+
return readdirSync6(dir);
|
|
6556
|
+
} catch {
|
|
6557
|
+
return [];
|
|
6558
|
+
}
|
|
6559
|
+
}
|
|
6560
|
+
function safeStat(path) {
|
|
6561
|
+
try {
|
|
6562
|
+
return statSync11(path);
|
|
6563
|
+
} catch {
|
|
6564
|
+
return null;
|
|
6565
|
+
}
|
|
6566
|
+
}
|
|
6567
|
+
function formatBytes2(bytes) {
|
|
6568
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
6569
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
|
|
6570
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
6571
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
|
|
6572
|
+
}
|
|
6573
|
+
|
|
6574
|
+
// src/commands/optimize.ts
|
|
6575
|
+
import pc14 from "picocolors";
|
|
6576
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19 } from "fs";
|
|
6577
|
+
import { join as join25 } from "path";
|
|
6578
|
+
import { homedir as homedir19 } from "os";
|
|
6579
|
+
var FEATURES = [
|
|
6580
|
+
{ id: "prompt_cache", name: "Prompt Semantic Cache", category: "optimization" },
|
|
6581
|
+
{ id: "tool_call_cache", name: "Tool Call Cache", category: "optimization" },
|
|
6582
|
+
{ id: "model_routing", name: "Smart Model Routing", category: "optimization" },
|
|
6583
|
+
{ id: "idle_alerts", name: "Idle Session Alerts", category: "optimization" },
|
|
6584
|
+
{ id: "session_resume", name: "Session Resume Hints", category: "optimization" },
|
|
6585
|
+
{ id: "context_optimization", name: "Context Window Optimizer", category: "optimization" },
|
|
6586
|
+
{ id: "budget_enforcement", name: "Budget Enforcement", category: "governance" },
|
|
6587
|
+
{ id: "model_governance", name: "Model Governance", category: "governance" }
|
|
6588
|
+
];
|
|
6589
|
+
async function optimizeCommand(opts) {
|
|
6590
|
+
const config = loadConfig();
|
|
6591
|
+
if (opts.enable) {
|
|
6592
|
+
await toggleFeature(config, opts.enable, true);
|
|
6593
|
+
return;
|
|
6594
|
+
}
|
|
6595
|
+
if (opts.disable) {
|
|
6596
|
+
await toggleFeature(config, opts.disable, false);
|
|
6597
|
+
return;
|
|
6598
|
+
}
|
|
6599
|
+
if (opts.check) {
|
|
6600
|
+
await checkCompliance2(config, opts.json);
|
|
6601
|
+
return;
|
|
6602
|
+
}
|
|
6603
|
+
if (opts.report) {
|
|
6604
|
+
await sendReport(config);
|
|
6605
|
+
return;
|
|
6606
|
+
}
|
|
6607
|
+
await showSummary(config, opts);
|
|
6608
|
+
}
|
|
6609
|
+
async function showSummary(config, opts) {
|
|
6610
|
+
console.log("");
|
|
6611
|
+
console.log(pc14.bold("Token Usage & Optimization"));
|
|
6612
|
+
console.log(pc14.dim("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
|
|
6613
|
+
console.log("");
|
|
6614
|
+
const claudeUsage = readClaudeCodeUsage();
|
|
6615
|
+
const codexUsage = await readCodexUsage();
|
|
6616
|
+
if (claudeUsage) {
|
|
6617
|
+
const models = Object.entries(claudeUsage.modelUsage || {});
|
|
6618
|
+
console.log(pc14.bold(" Claude Code") + pc14.dim(` (${claudeUsage.configuredModel || "default"})`));
|
|
6619
|
+
let totalCost = 0;
|
|
6620
|
+
for (const [model, usage] of models) {
|
|
6621
|
+
const u = usage;
|
|
6622
|
+
const cost = u.costUSD || estimateCost(model, u.inputTokens || 0, u.outputTokens || 0);
|
|
6623
|
+
totalCost += cost;
|
|
6624
|
+
console.log(` ${pc14.dim("\u25CF")} ${shortenModel(model)}`);
|
|
6625
|
+
console.log(` Input: ${formatTokens(u.inputTokens || 0)} ${pc14.dim(`($${((u.inputTokens || 0) * inputRate(model) / 1e6).toFixed(2)})`)}`);
|
|
6626
|
+
console.log(` Output: ${formatTokens(u.outputTokens || 0)} ${pc14.dim(`($${((u.outputTokens || 0) * outputRate(model) / 1e6).toFixed(2)})`)}`);
|
|
6627
|
+
if (u.cacheReadInputTokens) {
|
|
6628
|
+
console.log(` Cache: ${formatTokens(u.cacheReadInputTokens)} reads`);
|
|
6629
|
+
}
|
|
6630
|
+
}
|
|
6631
|
+
console.log(` ${pc14.bold("Total")}: $${totalCost.toFixed(2)}`);
|
|
6632
|
+
if (claudeUsage.totalSessions) console.log(` Sessions: ${claudeUsage.totalSessions} | Messages: ${claudeUsage.totalMessages || 0}`);
|
|
6633
|
+
console.log("");
|
|
6634
|
+
}
|
|
6635
|
+
if (codexUsage) {
|
|
6636
|
+
console.log(pc14.bold(" Codex") + pc14.dim(` (${codexUsage.model || "default"})`));
|
|
6637
|
+
console.log(` Tokens: ${formatTokens(codexUsage.totalTokens)}`);
|
|
6638
|
+
console.log(` Sessions: ${codexUsage.sessionCount}`);
|
|
6639
|
+
if (codexUsage.estimatedCost > 0) {
|
|
6640
|
+
console.log(` Est. cost: $${codexUsage.estimatedCost.toFixed(2)}`);
|
|
6641
|
+
}
|
|
6642
|
+
console.log("");
|
|
6643
|
+
}
|
|
6644
|
+
if (!claudeUsage && !codexUsage) {
|
|
6645
|
+
console.log(pc14.dim(" No local usage data found."));
|
|
6646
|
+
console.log(pc14.dim(" Usage is tracked per-session and visible in the dashboard."));
|
|
6647
|
+
console.log("");
|
|
6648
|
+
}
|
|
6649
|
+
console.log(pc14.bold(" Optimization Features"));
|
|
6650
|
+
console.log(pc14.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6651
|
+
let features = null;
|
|
6652
|
+
if (config.apiUrl && config.authToken) {
|
|
6653
|
+
try {
|
|
6654
|
+
const headers = { "Content-Type": "application/json" };
|
|
6655
|
+
if (config.authToken) headers["X-IMDL-Key"] = config.authToken;
|
|
6656
|
+
const res = await fetch(`${config.apiUrl}/api/optimize/my-features`, {
|
|
6657
|
+
headers,
|
|
6658
|
+
signal: AbortSignal.timeout(5e3)
|
|
6659
|
+
});
|
|
6660
|
+
if (res.ok) {
|
|
6661
|
+
const data = await res.json();
|
|
6662
|
+
features = data.features;
|
|
6663
|
+
}
|
|
6664
|
+
} catch {
|
|
6665
|
+
}
|
|
6666
|
+
}
|
|
6667
|
+
if (features) {
|
|
6668
|
+
for (const f of features) {
|
|
6669
|
+
const icon = f.enabled ? pc14.green("\u25CF") : pc14.dim("\u25CB");
|
|
6670
|
+
const locked = f.lockedByAdmin ? pc14.yellow(" (admin)") : "";
|
|
6671
|
+
console.log(` ${icon} ${f.name}${locked}`);
|
|
6672
|
+
}
|
|
6673
|
+
} else {
|
|
6674
|
+
for (const f of FEATURES) {
|
|
6675
|
+
console.log(` ${pc14.dim("\u25CB")} ${f.name} ${pc14.dim("(connect to API for status)")}`);
|
|
6676
|
+
}
|
|
6677
|
+
}
|
|
6678
|
+
console.log("");
|
|
6679
|
+
console.log(pc14.dim(" Toggle: imdl optimize --enable <feature> | --disable <feature>"));
|
|
6680
|
+
console.log(pc14.dim(" Features: prompt_cache, tool_call_cache, model_routing,"));
|
|
6681
|
+
console.log(pc14.dim(" idle_alerts, session_resume, context_optimization"));
|
|
6682
|
+
console.log("");
|
|
6683
|
+
if (opts.json) {
|
|
6684
|
+
const output = {
|
|
6685
|
+
claude_code: claudeUsage,
|
|
6686
|
+
codex: codexUsage,
|
|
6687
|
+
features: features || FEATURES.map((f) => ({ ...f, enabled: false }))
|
|
6688
|
+
};
|
|
6689
|
+
console.log(JSON.stringify(output, null, 2));
|
|
6690
|
+
}
|
|
6691
|
+
}
|
|
6692
|
+
async function toggleFeature(config, feature, enabled) {
|
|
6693
|
+
const valid = FEATURES.find((f) => f.id === feature);
|
|
6694
|
+
if (!valid) {
|
|
6695
|
+
console.log(pc14.red(`Unknown feature: ${feature}`));
|
|
6696
|
+
console.log(pc14.dim(`Available: ${FEATURES.map((f) => f.id).join(", ")}`));
|
|
6697
|
+
return;
|
|
6698
|
+
}
|
|
6699
|
+
if (!config.apiUrl || !config.authToken) {
|
|
6700
|
+
console.log(pc14.red("Not connected to API. Run `imdl init` first."));
|
|
6701
|
+
return;
|
|
6702
|
+
}
|
|
6703
|
+
const headers = { "Content-Type": "application/json" };
|
|
6704
|
+
if (config.authToken) headers["X-IMDL-Key"] = config.authToken;
|
|
6705
|
+
try {
|
|
6706
|
+
const res = await fetch(`${config.apiUrl}/api/optimize/my-features`, {
|
|
6707
|
+
method: "PUT",
|
|
6708
|
+
headers,
|
|
6709
|
+
body: JSON.stringify({ feature, enabled }),
|
|
6710
|
+
signal: AbortSignal.timeout(5e3)
|
|
6711
|
+
});
|
|
6712
|
+
if (res.ok) {
|
|
6713
|
+
const icon = enabled ? pc14.green("\u25CF") : pc14.dim("\u25CB");
|
|
6714
|
+
console.log(`${icon} ${valid.name}: ${enabled ? pc14.green("enabled") : pc14.dim("disabled")}`);
|
|
6715
|
+
} else {
|
|
6716
|
+
const data = await res.json().catch(() => ({}));
|
|
6717
|
+
if (data.error?.includes("locked")) {
|
|
6718
|
+
console.log(pc14.yellow(`Cannot toggle "${valid.name}" \u2014 locked by your admin.`));
|
|
6719
|
+
} else {
|
|
6720
|
+
console.log(pc14.red(`Failed: ${data.error || res.statusText}`));
|
|
6721
|
+
}
|
|
6722
|
+
}
|
|
6723
|
+
} catch {
|
|
6724
|
+
console.log(pc14.red("Failed to reach API."));
|
|
6725
|
+
}
|
|
6726
|
+
}
|
|
6727
|
+
async function checkCompliance2(config, json) {
|
|
6728
|
+
if (!config.apiUrl || !config.authToken) {
|
|
6729
|
+
console.log(pc14.red("Not connected to API. Run `imdl init` first."));
|
|
6730
|
+
return;
|
|
6731
|
+
}
|
|
6732
|
+
const headers = { "Content-Type": "application/json" };
|
|
6733
|
+
if (config.authToken) headers["X-IMDL-Key"] = config.authToken;
|
|
6734
|
+
try {
|
|
6735
|
+
const res = await fetch(`${config.apiUrl}/api/optimize/check`, {
|
|
6736
|
+
headers,
|
|
6737
|
+
signal: AbortSignal.timeout(5e3)
|
|
6738
|
+
});
|
|
6739
|
+
if (!res.ok) {
|
|
6740
|
+
console.log(pc14.dim("No cost policies configured for your team."));
|
|
6741
|
+
return;
|
|
6742
|
+
}
|
|
6743
|
+
const data = await res.json();
|
|
6744
|
+
if (json) {
|
|
6745
|
+
console.log(JSON.stringify(data, null, 2));
|
|
6746
|
+
return;
|
|
6747
|
+
}
|
|
6748
|
+
if (!data.violations || data.violations.length === 0) {
|
|
6749
|
+
console.log(pc14.green("\u2713 All cost policies satisfied."));
|
|
6750
|
+
return;
|
|
6751
|
+
}
|
|
6752
|
+
console.log(pc14.yellow(`\u26A0 ${data.violations.length} cost policy violation(s):`));
|
|
6753
|
+
for (const v of data.violations) {
|
|
6754
|
+
console.log(` ${pc14.yellow("\u25CF")} ${v.detail}`);
|
|
6755
|
+
if (v.costImpact) console.log(pc14.dim(` Excess cost: $${v.costImpact.toFixed(2)}`));
|
|
6756
|
+
}
|
|
6757
|
+
} catch {
|
|
6758
|
+
console.log(pc14.red("Failed to reach API."));
|
|
6759
|
+
}
|
|
6760
|
+
}
|
|
6761
|
+
async function sendReport(config) {
|
|
6762
|
+
if (!config.apiUrl || !config.authToken) {
|
|
6763
|
+
console.log(pc14.red("Not connected to API. Run `imdl init` first."));
|
|
6764
|
+
return;
|
|
6765
|
+
}
|
|
6766
|
+
const claudeUsage = readClaudeCodeUsage();
|
|
6767
|
+
const codexUsage = await readCodexUsage();
|
|
6768
|
+
const report = {
|
|
6769
|
+
developerId: config.developerId,
|
|
6770
|
+
reportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6771
|
+
agents: []
|
|
6772
|
+
};
|
|
6773
|
+
if (claudeUsage) {
|
|
6774
|
+
report.agents.push({
|
|
6775
|
+
agentType: "claude-code",
|
|
6776
|
+
configuredModel: claudeUsage.configuredModel,
|
|
6777
|
+
aggregate: Object.entries(claudeUsage.modelUsage || {}).map(([model, u]) => ({
|
|
6778
|
+
model,
|
|
6779
|
+
provider: "anthropic",
|
|
6780
|
+
inputTokens: u.inputTokens || 0,
|
|
6781
|
+
outputTokens: u.outputTokens || 0,
|
|
6782
|
+
cacheReadTokens: u.cacheReadInputTokens || 0,
|
|
6783
|
+
cacheWriteTokens: u.cacheCreationInputTokens || 0,
|
|
6784
|
+
estimatedCostUSD: u.costUSD || estimateCost(model, u.inputTokens || 0, u.outputTokens || 0)
|
|
6785
|
+
}))
|
|
6786
|
+
});
|
|
6787
|
+
}
|
|
6788
|
+
if (codexUsage) {
|
|
6789
|
+
report.agents.push({
|
|
6790
|
+
agentType: "codex",
|
|
6791
|
+
configuredModel: codexUsage.model,
|
|
6792
|
+
aggregate: [{
|
|
6793
|
+
model: codexUsage.model || "gpt-5",
|
|
6794
|
+
provider: "openai",
|
|
6795
|
+
inputTokens: codexUsage.totalTokens,
|
|
6796
|
+
outputTokens: 0,
|
|
6797
|
+
estimatedCostUSD: codexUsage.estimatedCost
|
|
6798
|
+
}]
|
|
6799
|
+
});
|
|
6800
|
+
}
|
|
6801
|
+
const headers = { "Content-Type": "application/json" };
|
|
6802
|
+
if (config.authToken) headers["X-IMDL-Key"] = config.authToken;
|
|
6803
|
+
try {
|
|
6804
|
+
const res = await fetch(`${config.apiUrl}/api/optimize/report`, {
|
|
6805
|
+
method: "POST",
|
|
6806
|
+
headers,
|
|
6807
|
+
body: JSON.stringify(report),
|
|
6808
|
+
signal: AbortSignal.timeout(1e4)
|
|
6809
|
+
});
|
|
6810
|
+
if (res.ok) {
|
|
6811
|
+
console.log(pc14.green("\u2713 Usage report sent to dashboard."));
|
|
6812
|
+
} else {
|
|
6813
|
+
console.log(pc14.red("Failed to send report."));
|
|
6814
|
+
}
|
|
6815
|
+
} catch {
|
|
6816
|
+
console.log(pc14.red("Failed to reach API."));
|
|
6817
|
+
}
|
|
6818
|
+
}
|
|
6819
|
+
function readClaudeCodeUsage() {
|
|
6820
|
+
const baseDir = join25(homedir19(), ".claude");
|
|
6821
|
+
const statsPath = join25(baseDir, "stats-cache.json");
|
|
6822
|
+
const settingsPath = join25(baseDir, "settings.json");
|
|
6823
|
+
if (!existsSync25(statsPath)) return null;
|
|
6824
|
+
try {
|
|
6825
|
+
const stats = JSON.parse(readFileSync19(statsPath, "utf-8"));
|
|
6826
|
+
let configuredModel = "unknown";
|
|
6827
|
+
if (existsSync25(settingsPath)) {
|
|
6828
|
+
try {
|
|
6829
|
+
const settings = JSON.parse(readFileSync19(settingsPath, "utf-8"));
|
|
6830
|
+
configuredModel = settings.model || "default";
|
|
6831
|
+
} catch {
|
|
6832
|
+
}
|
|
6833
|
+
}
|
|
6834
|
+
return {
|
|
6835
|
+
configuredModel,
|
|
6836
|
+
modelUsage: stats.modelUsage || {},
|
|
6837
|
+
totalSessions: stats.totalSessions || 0,
|
|
6838
|
+
totalMessages: stats.totalMessages || 0
|
|
6839
|
+
};
|
|
6840
|
+
} catch {
|
|
6841
|
+
return null;
|
|
6842
|
+
}
|
|
6843
|
+
}
|
|
6844
|
+
async function readCodexUsage() {
|
|
6845
|
+
const baseDir = join25(homedir19(), ".codex");
|
|
6846
|
+
const configPath = join25(baseDir, "config.toml");
|
|
6847
|
+
const dbPath = join25(baseDir, "state_5.sqlite");
|
|
6848
|
+
if (!existsSync25(dbPath)) return null;
|
|
6849
|
+
let model = "unknown";
|
|
6850
|
+
if (existsSync25(configPath)) {
|
|
6851
|
+
try {
|
|
6852
|
+
const content = readFileSync19(configPath, "utf-8");
|
|
6853
|
+
const modelMatch = content.match(/^model\s*=\s*"([^"]+)"/m);
|
|
6854
|
+
if (modelMatch) model = modelMatch[1];
|
|
6855
|
+
} catch {
|
|
6856
|
+
}
|
|
6857
|
+
}
|
|
6858
|
+
try {
|
|
6859
|
+
const Database = (await import("better-sqlite3")).default;
|
|
6860
|
+
const db = new Database(dbPath, { readonly: true });
|
|
6861
|
+
const row = db.prepare("SELECT SUM(tokens_used) as total, COUNT(*) as cnt FROM threads WHERE tokens_used > 0").get();
|
|
6862
|
+
db.close();
|
|
6863
|
+
const totalTokens = row?.total || 0;
|
|
6864
|
+
const sessionCount = row?.cnt || 0;
|
|
6865
|
+
const estimatedCost = estimateCost(model, totalTokens * 0.6, totalTokens * 0.4);
|
|
6866
|
+
return { model, totalTokens, sessionCount, estimatedCost };
|
|
6867
|
+
} catch {
|
|
6868
|
+
return null;
|
|
6869
|
+
}
|
|
6870
|
+
}
|
|
6871
|
+
function estimateCost(model, inputTokens, outputTokens) {
|
|
6872
|
+
return inputTokens / 1e6 * inputRate(model) + outputTokens / 1e6 * outputRate(model);
|
|
6873
|
+
}
|
|
6874
|
+
function inputRate(model) {
|
|
6875
|
+
if (model.includes("opus")) return 15;
|
|
6876
|
+
if (model.includes("sonnet")) return 3;
|
|
6877
|
+
if (model.includes("haiku")) return 0.8;
|
|
6878
|
+
if (model.includes("gpt-5")) return 10;
|
|
6879
|
+
if (model.includes("gpt-4o") || model.includes("gpt-4.1")) return 2;
|
|
6880
|
+
return 3;
|
|
6881
|
+
}
|
|
6882
|
+
function outputRate(model) {
|
|
6883
|
+
if (model.includes("opus")) return 75;
|
|
6884
|
+
if (model.includes("sonnet")) return 15;
|
|
6885
|
+
if (model.includes("haiku")) return 4;
|
|
6886
|
+
if (model.includes("gpt-5")) return 30;
|
|
6887
|
+
if (model.includes("gpt-4o") || model.includes("gpt-4.1")) return 8;
|
|
6888
|
+
return 15;
|
|
6889
|
+
}
|
|
6890
|
+
function formatTokens(n) {
|
|
6891
|
+
if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
|
|
6892
|
+
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
6893
|
+
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
6894
|
+
return String(n);
|
|
6895
|
+
}
|
|
6896
|
+
function shortenModel(model) {
|
|
6897
|
+
if (model.includes("opus")) return "Claude Opus";
|
|
6898
|
+
if (model.includes("sonnet")) return "Claude Sonnet";
|
|
6899
|
+
if (model.includes("haiku")) return "Claude Haiku";
|
|
6900
|
+
if (model.includes("gpt-5")) return "GPT-5";
|
|
6901
|
+
if (model.includes("gpt-4")) return "GPT-4";
|
|
6902
|
+
if (model.length > 25) return model.slice(0, 22) + "...";
|
|
6903
|
+
return model;
|
|
6904
|
+
}
|
|
6905
|
+
|
|
5574
6906
|
// src/commands/uninstall.ts
|
|
5575
6907
|
import { Command } from "commander";
|
|
5576
|
-
import { existsSync as
|
|
5577
|
-
import { homedir as
|
|
5578
|
-
import { join as
|
|
5579
|
-
import
|
|
6908
|
+
import { existsSync as existsSync26, readFileSync as readFileSync20, rmSync, unlinkSync as unlinkSync3 } from "fs";
|
|
6909
|
+
import { homedir as homedir20 } from "os";
|
|
6910
|
+
import { join as join26 } from "path";
|
|
6911
|
+
import pc15 from "picocolors";
|
|
5580
6912
|
var uninstall = new Command("uninstall").description("Remove all IMDL components, hooks, and configuration").option("--keep-config", "Keep ~/.imdl config directory").option("--force", "Skip confirmation prompt").action(async (opts) => {
|
|
5581
|
-
const home =
|
|
5582
|
-
const imdlDir =
|
|
6913
|
+
const home = homedir20();
|
|
6914
|
+
const imdlDir = join26(home, ".imdl");
|
|
5583
6915
|
console.log("");
|
|
5584
|
-
console.log(
|
|
6916
|
+
console.log(pc15.bold(" Frostbridge IMDL \u2014 Uninstaller"));
|
|
5585
6917
|
console.log("");
|
|
5586
6918
|
const removed = [];
|
|
5587
6919
|
const skipped = [];
|
|
5588
|
-
const claudeSettings =
|
|
5589
|
-
if (
|
|
6920
|
+
const claudeSettings = join26(home, ".claude", "settings.json");
|
|
6921
|
+
if (existsSync26(claudeSettings)) {
|
|
5590
6922
|
try {
|
|
5591
|
-
const raw =
|
|
6923
|
+
const raw = readFileSync20(claudeSettings, "utf8");
|
|
5592
6924
|
const settings = JSON.parse(raw);
|
|
5593
6925
|
let modified = false;
|
|
5594
6926
|
for (const hookType of ["PreToolUse", "PostToolUse", "UserPromptSubmit"]) {
|
|
@@ -5603,18 +6935,18 @@ var uninstall = new Command("uninstall").description("Remove all IMDL components
|
|
|
5603
6935
|
}
|
|
5604
6936
|
if (settings.hooks && Object.keys(settings.hooks).length === 0) delete settings.hooks;
|
|
5605
6937
|
if (modified) {
|
|
5606
|
-
const { writeFileSync:
|
|
5607
|
-
|
|
6938
|
+
const { writeFileSync: writeFileSync13 } = await import("fs");
|
|
6939
|
+
writeFileSync13(claudeSettings, JSON.stringify(settings, null, 2));
|
|
5608
6940
|
removed.push("Claude Code hooks");
|
|
5609
6941
|
}
|
|
5610
6942
|
} catch {
|
|
5611
6943
|
skipped.push("Claude Code hooks (could not parse settings.json)");
|
|
5612
6944
|
}
|
|
5613
6945
|
}
|
|
5614
|
-
const cursorSettings =
|
|
5615
|
-
if (
|
|
6946
|
+
const cursorSettings = join26(home, ".cursor", "settings.json");
|
|
6947
|
+
if (existsSync26(cursorSettings)) {
|
|
5616
6948
|
try {
|
|
5617
|
-
const raw =
|
|
6949
|
+
const raw = readFileSync20(cursorSettings, "utf8");
|
|
5618
6950
|
const settings = JSON.parse(raw);
|
|
5619
6951
|
let modified = false;
|
|
5620
6952
|
if (settings.hooks) {
|
|
@@ -5629,8 +6961,8 @@ var uninstall = new Command("uninstall").description("Remove all IMDL components
|
|
|
5629
6961
|
}
|
|
5630
6962
|
}
|
|
5631
6963
|
if (modified) {
|
|
5632
|
-
const { writeFileSync:
|
|
5633
|
-
|
|
6964
|
+
const { writeFileSync: writeFileSync13 } = await import("fs");
|
|
6965
|
+
writeFileSync13(cursorSettings, JSON.stringify(settings, null, 2));
|
|
5634
6966
|
removed.push("Cursor hooks");
|
|
5635
6967
|
}
|
|
5636
6968
|
} catch {
|
|
@@ -5638,18 +6970,18 @@ var uninstall = new Command("uninstall").description("Remove all IMDL components
|
|
|
5638
6970
|
}
|
|
5639
6971
|
}
|
|
5640
6972
|
for (const rc of [".bashrc", ".zshrc", ".profile"]) {
|
|
5641
|
-
const rcPath =
|
|
5642
|
-
if (
|
|
6973
|
+
const rcPath = join26(home, rc);
|
|
6974
|
+
if (existsSync26(rcPath)) {
|
|
5643
6975
|
try {
|
|
5644
|
-
const content =
|
|
6976
|
+
const content = readFileSync20(rcPath, "utf8");
|
|
5645
6977
|
if (content.includes("imdl") || content.includes("frostbridge")) {
|
|
5646
6978
|
const lines = content.split("\n");
|
|
5647
6979
|
const filtered = lines.filter(
|
|
5648
6980
|
(l) => !l.includes("imdl") && !l.includes("frostbridge") && !l.includes("# Added by Frostbridge")
|
|
5649
6981
|
);
|
|
5650
6982
|
if (filtered.length < lines.length) {
|
|
5651
|
-
const { writeFileSync:
|
|
5652
|
-
|
|
6983
|
+
const { writeFileSync: writeFileSync13 } = await import("fs");
|
|
6984
|
+
writeFileSync13(rcPath, filtered.join("\n"));
|
|
5653
6985
|
removed.push(`Shell rc entries (${rc})`);
|
|
5654
6986
|
}
|
|
5655
6987
|
}
|
|
@@ -5658,9 +6990,9 @@ var uninstall = new Command("uninstall").description("Remove all IMDL components
|
|
|
5658
6990
|
}
|
|
5659
6991
|
}
|
|
5660
6992
|
try {
|
|
5661
|
-
const pidFile =
|
|
5662
|
-
if (
|
|
5663
|
-
const pid = parseInt(
|
|
6993
|
+
const pidFile = join26(imdlDir, "daemon.pid");
|
|
6994
|
+
if (existsSync26(pidFile)) {
|
|
6995
|
+
const pid = parseInt(readFileSync20(pidFile, "utf8").trim());
|
|
5664
6996
|
if (pid > 0) {
|
|
5665
6997
|
try {
|
|
5666
6998
|
process.kill(pid, "SIGTERM");
|
|
@@ -5672,18 +7004,18 @@ var uninstall = new Command("uninstall").description("Remove all IMDL components
|
|
|
5672
7004
|
}
|
|
5673
7005
|
} catch {
|
|
5674
7006
|
}
|
|
5675
|
-
if (!opts.keepConfig &&
|
|
7007
|
+
if (!opts.keepConfig && existsSync26(imdlDir)) {
|
|
5676
7008
|
rmSync(imdlDir, { recursive: true, force: true });
|
|
5677
7009
|
removed.push("~/.imdl config directory");
|
|
5678
7010
|
} else if (opts.keepConfig) {
|
|
5679
7011
|
skipped.push("~/.imdl (--keep-config)");
|
|
5680
7012
|
}
|
|
5681
|
-
const bufferDir =
|
|
5682
|
-
if (
|
|
7013
|
+
const bufferDir = join26(home, ".imdl", "buffer");
|
|
7014
|
+
if (existsSync26(bufferDir)) {
|
|
5683
7015
|
rmSync(bufferDir, { recursive: true, force: true });
|
|
5684
7016
|
removed.push("Event buffer");
|
|
5685
7017
|
}
|
|
5686
|
-
console.log(
|
|
7018
|
+
console.log(pc15.dim(" Uninstalling npm packages..."));
|
|
5687
7019
|
const { execSync: execSync3 } = await import("child_process");
|
|
5688
7020
|
const packages = ["@frostbridge/imdl", "@frostbridge/imdl-shell-wrapper", "@frostbridge/imdl-mcp-proxy"];
|
|
5689
7021
|
for (const pkg of packages) {
|
|
@@ -5695,19 +7027,19 @@ var uninstall = new Command("uninstall").description("Remove all IMDL components
|
|
|
5695
7027
|
}
|
|
5696
7028
|
console.log("");
|
|
5697
7029
|
if (removed.length > 0) {
|
|
5698
|
-
console.log(
|
|
7030
|
+
console.log(pc15.green(pc15.bold(" Removed:")));
|
|
5699
7031
|
for (const item of removed) {
|
|
5700
|
-
console.log(
|
|
7032
|
+
console.log(pc15.green(` \u2713 ${item}`));
|
|
5701
7033
|
}
|
|
5702
7034
|
}
|
|
5703
7035
|
if (skipped.length > 0) {
|
|
5704
|
-
console.log(
|
|
7036
|
+
console.log(pc15.yellow(pc15.bold(" Skipped:")));
|
|
5705
7037
|
for (const item of skipped) {
|
|
5706
|
-
console.log(
|
|
7038
|
+
console.log(pc15.yellow(` \u25CB ${item}`));
|
|
5707
7039
|
}
|
|
5708
7040
|
}
|
|
5709
7041
|
console.log("");
|
|
5710
|
-
console.log(
|
|
7042
|
+
console.log(pc15.dim(" IMDL has been completely removed from this machine."));
|
|
5711
7043
|
console.log("");
|
|
5712
7044
|
});
|
|
5713
7045
|
|
|
@@ -6019,18 +7351,18 @@ function ruleTypeToCategory(type) {
|
|
|
6019
7351
|
}
|
|
6020
7352
|
|
|
6021
7353
|
// src/transport/sync.ts
|
|
6022
|
-
import { readFileSync as
|
|
6023
|
-
import { join as
|
|
7354
|
+
import { readFileSync as readFileSync21, writeFileSync as writeFileSync12, existsSync as existsSync27 } from "fs";
|
|
7355
|
+
import { join as join27 } from "path";
|
|
6024
7356
|
var SYNC_INTERVAL = 6e4;
|
|
6025
7357
|
var SYNC_TIMEOUT = 3e3;
|
|
6026
7358
|
function getSyncStateFile() {
|
|
6027
|
-
return
|
|
7359
|
+
return join27(getImdlDir(), "last_sync.json");
|
|
6028
7360
|
}
|
|
6029
7361
|
function getLastSyncTime() {
|
|
6030
7362
|
try {
|
|
6031
7363
|
const file = getSyncStateFile();
|
|
6032
|
-
if (
|
|
6033
|
-
const data = JSON.parse(
|
|
7364
|
+
if (existsSync27(file)) {
|
|
7365
|
+
const data = JSON.parse(readFileSync21(file, "utf-8"));
|
|
6034
7366
|
return data.lastSync || 0;
|
|
6035
7367
|
}
|
|
6036
7368
|
} catch {
|
|
@@ -6039,7 +7371,7 @@ function getLastSyncTime() {
|
|
|
6039
7371
|
}
|
|
6040
7372
|
function setLastSyncTime() {
|
|
6041
7373
|
try {
|
|
6042
|
-
|
|
7374
|
+
writeFileSync12(getSyncStateFile(), JSON.stringify({ lastSync: Date.now() }), { mode: 384 });
|
|
6043
7375
|
} catch {
|
|
6044
7376
|
}
|
|
6045
7377
|
}
|
|
@@ -6351,7 +7683,19 @@ async function handleHook(type) {
|
|
|
6351
7683
|
};
|
|
6352
7684
|
}
|
|
6353
7685
|
} else if (hookType === "user_prompt" && event.prompt) {
|
|
6354
|
-
const
|
|
7686
|
+
const config = loadConfig();
|
|
7687
|
+
const llmConfig = {
|
|
7688
|
+
enabled: config.features?.prompt_llm_scan === true,
|
|
7689
|
+
apiUrl: config.llmScanUrl || (config.apiUrl ? `${config.apiUrl}/api/gateway/scan` : void 0),
|
|
7690
|
+
model: config.llmScanModel || "claude-haiku-4-5-20251001",
|
|
7691
|
+
maxInputChars: 500
|
|
7692
|
+
};
|
|
7693
|
+
let scan;
|
|
7694
|
+
if (llmConfig.enabled) {
|
|
7695
|
+
scan = await scanWithLlmFallback(event.prompt, llmConfig);
|
|
7696
|
+
} else {
|
|
7697
|
+
scan = scanPromptForSecrets(event.prompt);
|
|
7698
|
+
}
|
|
6355
7699
|
if (scan.hasSecrets) {
|
|
6356
7700
|
violationData = createPromptViolation(scan.findings);
|
|
6357
7701
|
}
|
|
@@ -6378,13 +7722,14 @@ async function handleHook(type) {
|
|
|
6378
7722
|
behaviorRiskScore = computeRiskScore(signals);
|
|
6379
7723
|
}
|
|
6380
7724
|
}
|
|
7725
|
+
const toolOutput = event.tool_output || event.tool_response;
|
|
6381
7726
|
const sessionId = event.session_id || getLastSessionId();
|
|
6382
7727
|
const buffered = {
|
|
6383
7728
|
sessionId,
|
|
6384
7729
|
type: hookType,
|
|
6385
7730
|
toolName: event.tool_name,
|
|
6386
7731
|
toolInput: redactObject(event.tool_input),
|
|
6387
|
-
toolOutput:
|
|
7732
|
+
toolOutput: toolOutput ? String(typeof toolOutput === "string" ? redactObject(toolOutput) : JSON.stringify(redactObject(toolOutput))) : void 0,
|
|
6388
7733
|
prompt: event.prompt ? redactObject(event.prompt) : void 0,
|
|
6389
7734
|
timestamp: event.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
6390
7735
|
violation: violationData,
|
|
@@ -6442,7 +7787,7 @@ function getSafeAlternative(toolName, toolInput) {
|
|
|
6442
7787
|
|
|
6443
7788
|
// src/index.ts
|
|
6444
7789
|
var program = new Command2();
|
|
6445
|
-
program.name("imdl").description("IMDL \u2014 Intelligent Mediation & Detection Layer. AI agent security.").version("0.1.
|
|
7790
|
+
program.name("imdl").description("IMDL \u2014 Intelligent Mediation & Detection Layer. AI agent security.").version("0.1.13");
|
|
6446
7791
|
program.command("scan").description("Scan MCP server configs for security risks").option("-p, --path <path>", "Path to MCP config file").option("-u, --url <url>", "GitHub URL or org/repo to scan").option("--json", "Output as JSON").option("--no-color", "Disable colored output").option("-q, --quiet", "Only show warnings and errors").option("-d, --deep", "Deep scan: static code analysis + GitHub issue scanning").action(scanCommand);
|
|
6447
7792
|
program.command("init").description("Detect AI agents and install monitoring hooks").option("-t, --token <token>", "Invite token to join a team").option("--team-token <token>", "Alias for --token").option("--api <url>", "API URL to connect to").option("--non-interactive", "Skip all prompts and use defaults").action((opts) => initCommand({ teamToken: opts.token || opts.teamToken, apiUrl: opts.api, nonInteractive: opts.nonInteractive }));
|
|
6448
7793
|
program.command("login").description("Authenticate to your IMDL team").action(loginCommand);
|
|
@@ -6468,6 +7813,12 @@ gwCmd.command("enable <module>").description("Enable a gateway module (secrets,
|
|
|
6468
7813
|
gwCmd.command("disable <module>").description("Disable a gateway module").action((module) => gatewayDisableCommand({ module }));
|
|
6469
7814
|
gwCmd.command("alerts").description("Show recent gateway security alerts").option("-n, --limit <count>", "Number of alerts to show", "20").option("--json", "Output as JSON").action(gatewayAlertsCommand);
|
|
6470
7815
|
gwCmd.command("cost").description("Show AI spend and usage breakdown").option("--json", "Output as JSON").action(gatewayCostCommand);
|
|
7816
|
+
gwCmd.command("dashboard").description("Open the local gateway dashboard in your browser").action(gatewayDashboardCommand);
|
|
7817
|
+
gwCmd.command("activate").description("Route Claude Code through the gateway (adds env to settings.json)").option("--codex", "Also route Codex (OpenAI) WebSocket traffic through the gateway").option("--copilot", "Show setup instructions for routing GitHub Copilot through the gateway").action(gatewayActivateCommand);
|
|
7818
|
+
gwCmd.command("deactivate").description("Stop routing Claude Code through gateway (direct to Bedrock/Anthropic)").action(gatewayDeactivateCommand);
|
|
7819
|
+
program.command("sync-history").description("Sync historical session data and retroactively detect violations").option("-d, --days <days>", "Number of days to look back (default: 30)", "30").option("-a, --agent <agent>", "Only sync a specific agent (claude-code, codex, cursor)").option("--dry-run", "Scan and report without sending data to the API").action(syncHistoryCommand);
|
|
7820
|
+
program.command("optimize").description("Token usage, cost policies, and optimization features").option("--enable <feature>", "Enable an optimization feature").option("--disable <feature>", "Disable an optimization feature").option("--report", "Send local usage data to your team dashboard").option("--check", "Check compliance against org cost policies").option("--json", "Output as JSON").action(optimizeCommand);
|
|
6471
7821
|
program.addCommand(uninstall);
|
|
7822
|
+
program.command("dashboard").description("Open the IMDL control panel in your browser").action(gatewayDashboardCommand);
|
|
6472
7823
|
program.command("hook <type>").description("Handle hook events from AI agents (internal)").action(handleHook);
|
|
6473
7824
|
program.parse();
|