@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.
Files changed (2) hide show
  1. package/dist/index.js +1496 -145
  2. 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")} Show connection, agents, and policy summary`);
1405
- console.log(` ${pc2.cyan("imdl scan")} Scan MCP servers for supply chain risks`);
1406
- console.log(` ${pc2.cyan("imdl login")} Authenticate to access your team dashboard`);
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
- if (existsSync14(hooksPath)) {
2713
+ const settingsPath = join13(claudeDir, "settings.json");
2714
+ if (existsSync14(settingsPath)) {
2593
2715
  try {
2594
- const hooks = JSON.parse(readFileSync10(hooksPath, "utf-8"));
2595
- hooked = JSON.stringify(hooks).includes("imdl hook");
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 writeFileSync6 } from "fs";
2712
- import { join as join18 } from "path";
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 = 50;
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
- let allSuccess = true;
2936
+ const failedEvents = [];
2937
+ debugLog(`Flushing ${events.length} events across ${grouped.size} sessions`);
2800
2938
  for (const [sessionId, sessionEvents] of grouped) {
2801
- for (let i = 0; i < sessionEvents.length; i += BATCH_SIZE) {
2802
- const batch = sessionEvents.slice(i, i + BATCH_SIZE);
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
- allSuccess = false;
2966
+ failedEvents.push(...sessionEvents.slice(i));
2967
+ sessionFailed = true;
2806
2968
  break;
2807
2969
  }
2808
2970
  }
2809
- if (!allSuccess) break;
2971
+ if (sessionFailed) continue;
2810
2972
  }
2811
- if (allSuccess) {
2812
- clearBuffer();
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: /sk-ant-[A-Za-z0-9\-_]{20,}/g, label: "Anthropic Key", severity: "critical" },
2973
- { pattern: /sk-(?:proj-)?[A-Za-z0-9\-_]{20,}/g, label: "OpenAI Key", severity: "critical" },
2974
- { pattern: /AIzaSy[A-Za-z0-9_\-]{30,36}/g, label: "Google API Key", severity: "critical" },
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: /(?:postgres(?:ql)?|mysql|mongodb(?:\+srv)?|redis|amqp):\/\/[^:]+:[^@\s]+@/gi, label: "Database Connection String", severity: "critical" },
2978
- { pattern: /Bearer\s+[A-Za-z0-9_\-./+=]{20,}/g, label: "Bearer Token", severity: "high" }
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 join15 } from "path";
3111
- import { homedir as homedir12 } from "os";
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 = join15(dirPath, file);
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 = join15(homedir12(), ".claude", "projects", encoded);
3525
+ const logDir = join16(homedir13(), ".claude", "projects", encoded);
3225
3526
  return existsSync16(logDir) ? logDir : null;
3226
3527
  }
3227
3528
  static getAllProjectDirs() {
3228
- const base = join15(homedir12(), ".claude", "projects");
3529
+ const base = join16(homedir13(), ".claude", "projects");
3229
3530
  if (!existsSync16(base)) return [];
3230
- return readdirSync3(base).map((d) => join15(base, d)).filter((p) => statSync8(p).isDirectory());
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 join16 } from "path";
3237
- import { homedir as homedir13 } from "os";
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 = join16(baseDir, "state.vscdb");
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 = join16(baseDir, entry);
3599
+ const entryPath = join17(baseDir, entry);
3299
3600
  try {
3300
3601
  const stat = statSync9(entryPath);
3301
3602
  if (stat.isDirectory()) {
3302
- const dbPath = join16(entryPath, "state.vscdb");
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 = join16(homedir13(), ".cursor", "projects");
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 = join16(projectsDir, proj, "agent-transcripts");
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 = join16(transcriptsDir, sessionDir, `${sessionDir}.jsonl`);
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
- join16(homedir13(), ".cursor", "User", "workspaceStorage"),
3731
- join16(homedir13(), "Library", "Application Support", "Cursor", "User", "workspaceStorage")
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) => join16(base, d)).filter((p) => {
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 = join16(homedir13(), ".cursor", "projects");
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 join17 } from "path";
3768
- import { homedir as homedir14 } from "os";
3769
- var CodexAdapter = class {
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, since) {
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 = join17(dir, entry);
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 (!since || stat.mtimeMs >= since.getTime()) {
3799
- files.push(full);
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 (ptype === "turn_context" && entry.payload.cwd) {
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 = join17(homedir14(), ".codex", "sessions");
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 = join18(getImdlDir(), "adapter-state.json");
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
- writeFileSync6(STATE_FILE, JSON.stringify(state, null, 2), { mode: 384 });
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 usageBySession = /* @__PURE__ */ new Map();
4409
+ const usageBySessionDay = /* @__PURE__ */ new Map();
3938
4410
  for (const event of events) {
3939
4411
  if (event.usage) {
3940
- const existing = usageBySession.get(event.sessionId) || { model: event.usage.model, input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
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
- usageBySession.set(event.sessionId, existing);
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 [sessionId, usage] of usageBySession) {
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 cursorUsageBySession = /* @__PURE__ */ new Map();
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 existing = cursorUsageBySession.get(event.sessionId) || { model: event.usage.model, input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
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
- cursorUsageBySession.set(event.sessionId, existing);
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 [sessionId, usage] of cursorUsageBySession) {
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 = await codexAdapter.extractEvents({
4073
- kind: "directory",
4074
- path: codexSessionsDir,
4075
- since: new Date(state.lastRun)
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 writeFileSync7 } from "fs";
4128
- import { join as join19 } from "path";
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 = join19(getBufferDir(), "agent-events.ndjson");
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
- writeFileSync7(agentLog, "", { mode: 384 });
4681
+ writeFileSync8(agentLog, "", { mode: 384 });
4202
4682
  }
4203
4683
  return sent;
4204
4684
  }
4205
4685
  async function ingestShellEvents() {
4206
- const shellLog = join19(getBufferDir(), "shell-events.ndjson");
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
- writeFileSync7(shellLog, "", { mode: 384 });
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 writeFileSync8, existsSync as existsSync21 } from "fs";
4314
- import { join as join20, resolve as resolve3 } from "path";
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 join20(process.cwd(), LOCKFILE_NAME);
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
- writeFileSync8(lockPath, JSON.stringify(lockfile, null, 2), { mode: 420 });
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 writeFileSync9 } from "fs";
4821
- import { join as join21 } from "path";
4822
- import { homedir as homedir15 } from "os";
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 = homedir15();
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 = join21(home, ".claude", "settings.json");
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 = join21(home, ".cursor", "settings.json");
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 = join21(home, ".codex", "config.json");
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 = join21(home, "Library", "Application Support", "Code", "User", "settings.json");
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 = join21(home, ".windsurf", "mcp.json");
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
- writeFileSync9(fix.configPath, JSON.stringify(config, null, 2) + "\n");
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 writeFileSync10, mkdirSync as mkdirSync3 } from "fs";
5211
- import { join as join22 } from "path";
5212
- import { homedir as homedir16 } from "os";
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 = join22(homedir16(), ".imdl");
5215
- var GATEWAY_CONFIG = join22(IMDL_DIR3, "gateway.toml");
5216
- var GATEWAY_PID_FILE = join22(IMDL_DIR3, "gateway.pid");
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
- writeFileSync10(GATEWAY_PID_FILE, String(child.pid));
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 = join22(IMDL_DIR3, "gateway-alerts.jsonl");
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 gatewayCostCommand(opts) {
5870
+ async function gatewayDashboardCommand() {
5391
5871
  const port = getConfiguredPort();
5392
- let data;
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
- join22(process.cwd(), "target", "release", "imdl-gateway"),
5487
- join22(process.cwd(), "..", "ai-gateway", "target", "release", "imdl-gateway"),
5488
- join22(homedir16(), ".imdl", "bin", "imdl-gateway")
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
- writeFileSync10(GATEWAY_CONFIG, defaultToml, { mode: 384 });
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
- writeFileSync10(GATEWAY_CONFIG, content, { mode: 384 });
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 existsSync24, readFileSync as readFileSync19, rmSync, unlinkSync as unlinkSync3 } from "fs";
5577
- import { homedir as homedir17 } from "os";
5578
- import { join as join23 } from "path";
5579
- import pc13 from "picocolors";
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 = homedir17();
5582
- const imdlDir = join23(home, ".imdl");
6913
+ const home = homedir20();
6914
+ const imdlDir = join26(home, ".imdl");
5583
6915
  console.log("");
5584
- console.log(pc13.bold(" Frostbridge IMDL \u2014 Uninstaller"));
6916
+ console.log(pc15.bold(" Frostbridge IMDL \u2014 Uninstaller"));
5585
6917
  console.log("");
5586
6918
  const removed = [];
5587
6919
  const skipped = [];
5588
- const claudeSettings = join23(home, ".claude", "settings.json");
5589
- if (existsSync24(claudeSettings)) {
6920
+ const claudeSettings = join26(home, ".claude", "settings.json");
6921
+ if (existsSync26(claudeSettings)) {
5590
6922
  try {
5591
- const raw = readFileSync19(claudeSettings, "utf8");
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: writeFileSync12 } = await import("fs");
5607
- writeFileSync12(claudeSettings, JSON.stringify(settings, null, 2));
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 = join23(home, ".cursor", "settings.json");
5615
- if (existsSync24(cursorSettings)) {
6946
+ const cursorSettings = join26(home, ".cursor", "settings.json");
6947
+ if (existsSync26(cursorSettings)) {
5616
6948
  try {
5617
- const raw = readFileSync19(cursorSettings, "utf8");
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: writeFileSync12 } = await import("fs");
5633
- writeFileSync12(cursorSettings, JSON.stringify(settings, null, 2));
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 = join23(home, rc);
5642
- if (existsSync24(rcPath)) {
6973
+ const rcPath = join26(home, rc);
6974
+ if (existsSync26(rcPath)) {
5643
6975
  try {
5644
- const content = readFileSync19(rcPath, "utf8");
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: writeFileSync12 } = await import("fs");
5652
- writeFileSync12(rcPath, filtered.join("\n"));
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 = join23(imdlDir, "daemon.pid");
5662
- if (existsSync24(pidFile)) {
5663
- const pid = parseInt(readFileSync19(pidFile, "utf8").trim());
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 && existsSync24(imdlDir)) {
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 = join23(home, ".imdl", "buffer");
5682
- if (existsSync24(bufferDir)) {
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(pc13.dim(" Uninstalling npm packages..."));
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(pc13.green(pc13.bold(" Removed:")));
7030
+ console.log(pc15.green(pc15.bold(" Removed:")));
5699
7031
  for (const item of removed) {
5700
- console.log(pc13.green(` \u2713 ${item}`));
7032
+ console.log(pc15.green(` \u2713 ${item}`));
5701
7033
  }
5702
7034
  }
5703
7035
  if (skipped.length > 0) {
5704
- console.log(pc13.yellow(pc13.bold(" Skipped:")));
7036
+ console.log(pc15.yellow(pc15.bold(" Skipped:")));
5705
7037
  for (const item of skipped) {
5706
- console.log(pc13.yellow(` \u25CB ${item}`));
7038
+ console.log(pc15.yellow(` \u25CB ${item}`));
5707
7039
  }
5708
7040
  }
5709
7041
  console.log("");
5710
- console.log(pc13.dim(" IMDL has been completely removed from this machine."));
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 readFileSync20, writeFileSync as writeFileSync11, existsSync as existsSync25 } from "fs";
6023
- import { join as join24 } from "path";
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 join24(getImdlDir(), "last_sync.json");
7359
+ return join27(getImdlDir(), "last_sync.json");
6028
7360
  }
6029
7361
  function getLastSyncTime() {
6030
7362
  try {
6031
7363
  const file = getSyncStateFile();
6032
- if (existsSync25(file)) {
6033
- const data = JSON.parse(readFileSync20(file, "utf-8"));
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
- writeFileSync11(getSyncStateFile(), JSON.stringify({ lastSync: Date.now() }), { mode: 384 });
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 scan = scanPromptForSecrets(event.prompt);
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: event.tool_output ? redactObject(event.tool_output) : void 0,
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.0");
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();