@frostbridge/imdl 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +166 -59
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1351,6 +1351,7 @@ async function initCommand(options) {
|
|
|
1351
1351
|
installShellWrapper(agent.name, agent.dir);
|
|
1352
1352
|
}
|
|
1353
1353
|
}
|
|
1354
|
+
installDaemon();
|
|
1354
1355
|
let apiConnected = false;
|
|
1355
1356
|
try {
|
|
1356
1357
|
const healthRes = await fetch(`${config.apiUrl}/health`, { signal: AbortSignal.timeout(3e3) });
|
|
@@ -1471,6 +1472,60 @@ function installShellWrapper(agentName, agentDir) {
|
|
|
1471
1472
|
console.log(pc2.dim(` \u2022 Shell wrapper: couldn't configure for ${agentName} (configure manually)`));
|
|
1472
1473
|
}
|
|
1473
1474
|
}
|
|
1475
|
+
function installDaemon() {
|
|
1476
|
+
if (process.platform !== "darwin") {
|
|
1477
|
+
console.log(pc2.dim(" \u2022 Background daemon: only auto-installed on macOS (run `imdl daemon` manually)"));
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
const plistName = "com.frostbridge.imdl-daemon.plist";
|
|
1481
|
+
const plistDir = join6(homedir4(), "Library", "LaunchAgents");
|
|
1482
|
+
const plistPath = join6(plistDir, plistName);
|
|
1483
|
+
if (existsSync7(plistPath)) {
|
|
1484
|
+
console.log(pc2.dim(" \u2022 Background daemon already installed"));
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
const { execSync: execSync2 } = __require("child_process");
|
|
1488
|
+
let imdlBin = "";
|
|
1489
|
+
try {
|
|
1490
|
+
imdlBin = execSync2("which imdl", { encoding: "utf-8" }).trim();
|
|
1491
|
+
} catch {
|
|
1492
|
+
imdlBin = "/usr/local/bin/imdl";
|
|
1493
|
+
}
|
|
1494
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1495
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1496
|
+
<plist version="1.0">
|
|
1497
|
+
<dict>
|
|
1498
|
+
<key>Label</key>
|
|
1499
|
+
<string>com.frostbridge.imdl-daemon</string>
|
|
1500
|
+
<key>ProgramArguments</key>
|
|
1501
|
+
<array>
|
|
1502
|
+
<string>${imdlBin}</string>
|
|
1503
|
+
<string>daemon</string>
|
|
1504
|
+
</array>
|
|
1505
|
+
<key>RunAtLoad</key>
|
|
1506
|
+
<true/>
|
|
1507
|
+
<key>KeepAlive</key>
|
|
1508
|
+
<true/>
|
|
1509
|
+
<key>StandardOutPath</key>
|
|
1510
|
+
<string>${join6(homedir4(), ".imdl", "daemon.log")}</string>
|
|
1511
|
+
<key>StandardErrorPath</key>
|
|
1512
|
+
<string>${join6(homedir4(), ".imdl", "daemon.log")}</string>
|
|
1513
|
+
<key>EnvironmentVariables</key>
|
|
1514
|
+
<dict>
|
|
1515
|
+
<key>PATH</key>
|
|
1516
|
+
<string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
|
|
1517
|
+
</dict>
|
|
1518
|
+
</dict>
|
|
1519
|
+
</plist>`;
|
|
1520
|
+
try {
|
|
1521
|
+
if (!existsSync7(plistDir)) mkdirSync2(plistDir, { recursive: true });
|
|
1522
|
+
writeFileSync4(plistPath, plist);
|
|
1523
|
+
execSync2(`launchctl load ${plistPath}`, { stdio: "ignore" });
|
|
1524
|
+
console.log(pc2.green(" \u2713 Background daemon installed (auto-starts on login)"));
|
|
1525
|
+
} catch {
|
|
1526
|
+
console.log(pc2.yellow(" \u26A0 Could not install daemon service (run `imdl daemon` manually)"));
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1474
1529
|
function installMCPProxy(mcpConfigPath, agentName) {
|
|
1475
1530
|
let existing = {};
|
|
1476
1531
|
const dir = join6(mcpConfigPath, "..");
|
|
@@ -1508,7 +1563,8 @@ import pc3 from "picocolors";
|
|
|
1508
1563
|
import { createServer } from "http";
|
|
1509
1564
|
import { URL as URL2 } from "url";
|
|
1510
1565
|
import { randomBytes } from "crypto";
|
|
1511
|
-
var
|
|
1566
|
+
var DEFAULT_DASHBOARD_URL = "https://imdl-dashboard-1028078600113.us-central1.run.app";
|
|
1567
|
+
var DASHBOARD_URL = process.env.IMDL_DASHBOARD_URL || DEFAULT_DASHBOARD_URL;
|
|
1512
1568
|
async function loginCommand() {
|
|
1513
1569
|
console.log("");
|
|
1514
1570
|
console.log(pc3.bold(pc3.cyan("IMDL Login")));
|
|
@@ -1542,6 +1598,7 @@ async function loginCommand() {
|
|
|
1542
1598
|
config.authToken = result.token;
|
|
1543
1599
|
config.teamId = result.teamId;
|
|
1544
1600
|
if (result.teamName) config.teamName = result.teamName;
|
|
1601
|
+
if (result.developerId) config.developerId = result.developerId;
|
|
1545
1602
|
saveConfig(config);
|
|
1546
1603
|
console.log(pc3.green(" \u2713 Authenticated successfully!"));
|
|
1547
1604
|
console.log("");
|
|
@@ -1573,6 +1630,7 @@ function waitForCallback(port, expectedState) {
|
|
|
1573
1630
|
const token = url.searchParams.get("token");
|
|
1574
1631
|
const teamId = url.searchParams.get("teamId");
|
|
1575
1632
|
const teamName = url.searchParams.get("teamName");
|
|
1633
|
+
const developerId = url.searchParams.get("developerId");
|
|
1576
1634
|
const returnedState = url.searchParams.get("state");
|
|
1577
1635
|
const error = url.searchParams.get("error");
|
|
1578
1636
|
res.setHeader("Access-Control-Allow-Origin", DASHBOARD_URL);
|
|
@@ -1598,7 +1656,7 @@ function waitForCallback(port, expectedState) {
|
|
|
1598
1656
|
res.end("<html><body><h2>Authenticated!</h2><p>You can close this tab and return to your terminal.</p></body></html>");
|
|
1599
1657
|
clearTimeout(timeout);
|
|
1600
1658
|
server.close();
|
|
1601
|
-
resolve4({ token, teamId, teamName: teamName || void 0 });
|
|
1659
|
+
resolve4({ token, teamId, teamName: teamName || void 0, developerId: developerId || void 0 });
|
|
1602
1660
|
});
|
|
1603
1661
|
server.listen(port);
|
|
1604
1662
|
});
|
|
@@ -3574,6 +3632,8 @@ async function collectCommand() {
|
|
|
3574
3632
|
|
|
3575
3633
|
// src/commands/daemon.ts
|
|
3576
3634
|
import pc7 from "picocolors";
|
|
3635
|
+
import { existsSync as existsSync19, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
|
|
3636
|
+
import { join as join18 } from "path";
|
|
3577
3637
|
var INTERVAL_MS = 3e4;
|
|
3578
3638
|
async function daemonCommand() {
|
|
3579
3639
|
console.log(pc7.cyan("imdl daemon started \u2014 collecting every 30s"));
|
|
@@ -3581,9 +3641,12 @@ async function daemonCommand() {
|
|
|
3581
3641
|
const tick = async () => {
|
|
3582
3642
|
try {
|
|
3583
3643
|
const count = await collectPrompts();
|
|
3584
|
-
|
|
3585
|
-
|
|
3644
|
+
const shellCount = await ingestShellEvents();
|
|
3645
|
+
const total = count + shellCount;
|
|
3646
|
+
if (total > 0) {
|
|
3647
|
+
console.log(pc7.green(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Collected ${total} event${total !== 1 ? "s" : ""} (prompts: ${count}, shell: ${shellCount})`));
|
|
3586
3648
|
}
|
|
3649
|
+
await tryFlush();
|
|
3587
3650
|
} catch (err) {
|
|
3588
3651
|
console.error(pc7.red(`[${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] Error: ${err.message}`));
|
|
3589
3652
|
}
|
|
@@ -3591,6 +3654,50 @@ async function daemonCommand() {
|
|
|
3591
3654
|
await tick();
|
|
3592
3655
|
setInterval(tick, INTERVAL_MS);
|
|
3593
3656
|
}
|
|
3657
|
+
async function ingestShellEvents() {
|
|
3658
|
+
const shellLog = join18(getBufferDir(), "shell-events.ndjson");
|
|
3659
|
+
if (!existsSync19(shellLog)) return 0;
|
|
3660
|
+
const content = readFileSync13(shellLog, "utf-8").trim();
|
|
3661
|
+
if (!content) return 0;
|
|
3662
|
+
const config = loadConfig();
|
|
3663
|
+
const lines = content.split("\n");
|
|
3664
|
+
const events = lines.map((line) => {
|
|
3665
|
+
try {
|
|
3666
|
+
return JSON.parse(line);
|
|
3667
|
+
} catch {
|
|
3668
|
+
return null;
|
|
3669
|
+
}
|
|
3670
|
+
}).filter(Boolean);
|
|
3671
|
+
if (events.length === 0) return 0;
|
|
3672
|
+
const headers = { "Content-Type": "application/json" };
|
|
3673
|
+
if (config.authToken) headers["X-IMDL-Key"] = config.authToken;
|
|
3674
|
+
const sessionId = `shell_${config.developerId}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
3675
|
+
const apiEvents = events.map((e) => ({
|
|
3676
|
+
type: "pre_tool_use",
|
|
3677
|
+
toolName: "shell_command",
|
|
3678
|
+
toolInput: { command: e.command, agent: e.agent, decision: e.decision, reason: e.reason },
|
|
3679
|
+
timestamp: e.timestamp,
|
|
3680
|
+
violation: e.decision === "block" ? { action: "block", reason: e.reason, policyName: e.policyName } : void 0
|
|
3681
|
+
}));
|
|
3682
|
+
let sent = 0;
|
|
3683
|
+
for (let i = 0; i < apiEvents.length; i += 50) {
|
|
3684
|
+
const batch = apiEvents.slice(i, i + 50);
|
|
3685
|
+
try {
|
|
3686
|
+
const res = await fetch(`${config.apiUrl}/api/sessions/${sessionId}/events`, {
|
|
3687
|
+
method: "POST",
|
|
3688
|
+
headers,
|
|
3689
|
+
body: JSON.stringify({ events: batch, developerId: config.developerId }),
|
|
3690
|
+
signal: AbortSignal.timeout(1e4)
|
|
3691
|
+
});
|
|
3692
|
+
if (res.ok) sent += batch.length;
|
|
3693
|
+
} catch {
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
if (sent === apiEvents.length) {
|
|
3697
|
+
writeFileSync7(shellLog, "", { mode: 384 });
|
|
3698
|
+
}
|
|
3699
|
+
return sent;
|
|
3700
|
+
}
|
|
3594
3701
|
|
|
3595
3702
|
// src/commands/request.ts
|
|
3596
3703
|
import pc8 from "picocolors";
|
|
@@ -3655,8 +3762,8 @@ async function requestCommand(options) {
|
|
|
3655
3762
|
// src/commands/lock.ts
|
|
3656
3763
|
import pc9 from "picocolors";
|
|
3657
3764
|
import { createHash as createHash2 } from "crypto";
|
|
3658
|
-
import { readFileSync as
|
|
3659
|
-
import { join as
|
|
3765
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync8, existsSync as existsSync20 } from "fs";
|
|
3766
|
+
import { join as join19, resolve as resolve3 } from "path";
|
|
3660
3767
|
var LOCKFILE_NAME = "mcp-lock.json";
|
|
3661
3768
|
function computeIntegrity(server, identity) {
|
|
3662
3769
|
const payload = JSON.stringify({
|
|
@@ -3671,7 +3778,7 @@ function computeIntegrity(server, identity) {
|
|
|
3671
3778
|
}
|
|
3672
3779
|
function getLockfilePath(customPath) {
|
|
3673
3780
|
if (customPath) return resolve3(customPath);
|
|
3674
|
-
return
|
|
3781
|
+
return join19(process.cwd(), LOCKFILE_NAME);
|
|
3675
3782
|
}
|
|
3676
3783
|
async function lockCommand(options) {
|
|
3677
3784
|
const detection = await detectMCPConfigs(options.path);
|
|
@@ -3718,7 +3825,7 @@ async function lockCommand(options) {
|
|
|
3718
3825
|
servers: entries
|
|
3719
3826
|
};
|
|
3720
3827
|
const lockPath = getLockfilePath(options.output);
|
|
3721
|
-
|
|
3828
|
+
writeFileSync8(lockPath, JSON.stringify(lockfile, null, 2), { mode: 420 });
|
|
3722
3829
|
if (options.json) {
|
|
3723
3830
|
console.log(JSON.stringify(lockfile, null, 2));
|
|
3724
3831
|
} else {
|
|
@@ -3741,7 +3848,7 @@ async function lockCommand(options) {
|
|
|
3741
3848
|
}
|
|
3742
3849
|
async function verifyCommand(options) {
|
|
3743
3850
|
const lockPath = getLockfilePath(options.path);
|
|
3744
|
-
if (!
|
|
3851
|
+
if (!existsSync20(lockPath)) {
|
|
3745
3852
|
if (options.json) {
|
|
3746
3853
|
console.log(JSON.stringify({ error: "No lockfile found. Run `imdl lock` first.", path: lockPath }));
|
|
3747
3854
|
} else {
|
|
@@ -3753,7 +3860,7 @@ async function verifyCommand(options) {
|
|
|
3753
3860
|
}
|
|
3754
3861
|
let lockfile;
|
|
3755
3862
|
try {
|
|
3756
|
-
lockfile = JSON.parse(
|
|
3863
|
+
lockfile = JSON.parse(readFileSync14(lockPath, "utf-8"));
|
|
3757
3864
|
} catch {
|
|
3758
3865
|
console.log(pc9.red("Failed to parse lockfile."));
|
|
3759
3866
|
process.exit(1);
|
|
@@ -4162,8 +4269,8 @@ async function checkCompliance(developerId) {
|
|
|
4162
4269
|
}
|
|
4163
4270
|
|
|
4164
4271
|
// src/permissions/fixer.ts
|
|
4165
|
-
import { existsSync as
|
|
4166
|
-
import { join as
|
|
4272
|
+
import { existsSync as existsSync21, readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "fs";
|
|
4273
|
+
import { join as join20 } from "path";
|
|
4167
4274
|
import { homedir as homedir14 } from "os";
|
|
4168
4275
|
function buildFixesFromChanges(changes) {
|
|
4169
4276
|
const fixes = [];
|
|
@@ -4177,10 +4284,10 @@ function buildFixesFromChanges(changes) {
|
|
|
4177
4284
|
function buildFixForAgent(change, home) {
|
|
4178
4285
|
const { agentType, category, action, targetGrant } = change;
|
|
4179
4286
|
if (agentType === "claude-code") {
|
|
4180
|
-
const configPath =
|
|
4181
|
-
if (!
|
|
4287
|
+
const configPath = join20(home, ".claude", "settings.json");
|
|
4288
|
+
if (!existsSync21(configPath)) return null;
|
|
4182
4289
|
try {
|
|
4183
|
-
const settings = JSON.parse(
|
|
4290
|
+
const settings = JSON.parse(readFileSync15(configPath, "utf-8"));
|
|
4184
4291
|
const tools = settings.allowedTools || [];
|
|
4185
4292
|
if (category === "shell") {
|
|
4186
4293
|
if (!tools.includes("Bash") && !tools.some((t) => t.startsWith("Bash("))) return null;
|
|
@@ -4208,10 +4315,10 @@ function buildFixForAgent(change, home) {
|
|
|
4208
4315
|
}
|
|
4209
4316
|
}
|
|
4210
4317
|
if (agentType === "cursor") {
|
|
4211
|
-
const configPath =
|
|
4212
|
-
if (!
|
|
4318
|
+
const configPath = join20(home, ".cursor", "settings.json");
|
|
4319
|
+
if (!existsSync21(configPath)) return null;
|
|
4213
4320
|
try {
|
|
4214
|
-
const settings = JSON.parse(
|
|
4321
|
+
const settings = JSON.parse(readFileSync15(configPath, "utf-8"));
|
|
4215
4322
|
if ((category === "agentic_mode" || category === "shell") && settings["cursor.composer.yoloMode"] === true) {
|
|
4216
4323
|
return { agentType, category: "agentic_mode", currentGrant: "unrestricted", newGrant: "confirmation", configPath, description: "Disable YOLO mode" };
|
|
4217
4324
|
}
|
|
@@ -4220,10 +4327,10 @@ function buildFixForAgent(change, home) {
|
|
|
4220
4327
|
}
|
|
4221
4328
|
}
|
|
4222
4329
|
if (agentType === "codex") {
|
|
4223
|
-
const configPath =
|
|
4224
|
-
if (!
|
|
4330
|
+
const configPath = join20(home, ".codex", "config.json");
|
|
4331
|
+
if (!existsSync21(configPath)) return null;
|
|
4225
4332
|
try {
|
|
4226
|
-
const config = JSON.parse(
|
|
4333
|
+
const config = JSON.parse(readFileSync15(configPath, "utf-8"));
|
|
4227
4334
|
if ((category === "agentic_mode" || category === "shell") && config.sandbox_mode === "full_auto") {
|
|
4228
4335
|
return { agentType, category: "agentic_mode", currentGrant: "unrestricted", newGrant: "confirmation", configPath, description: "Switch to supervised mode" };
|
|
4229
4336
|
}
|
|
@@ -4232,10 +4339,10 @@ function buildFixForAgent(change, home) {
|
|
|
4232
4339
|
}
|
|
4233
4340
|
}
|
|
4234
4341
|
if (agentType === "copilot") {
|
|
4235
|
-
const configPath =
|
|
4236
|
-
if (!
|
|
4342
|
+
const configPath = join20(home, "Library", "Application Support", "Code", "User", "settings.json");
|
|
4343
|
+
if (!existsSync21(configPath)) return null;
|
|
4237
4344
|
try {
|
|
4238
|
-
const settings = JSON.parse(
|
|
4345
|
+
const settings = JSON.parse(readFileSync15(configPath, "utf-8"));
|
|
4239
4346
|
if ((category === "agentic_mode" || category === "shell") && (settings["github.copilot.chat.agent.autoApprove"] || settings["chat.agent.autoApprove"])) {
|
|
4240
4347
|
return { agentType, category: "agentic_mode", currentGrant: "unrestricted", newGrant: "confirmation", configPath, description: "Disable autoApprove" };
|
|
4241
4348
|
}
|
|
@@ -4245,10 +4352,10 @@ function buildFixForAgent(change, home) {
|
|
|
4245
4352
|
}
|
|
4246
4353
|
if (agentType === "windsurf") {
|
|
4247
4354
|
if (category === "mcp_tools") {
|
|
4248
|
-
const configPath =
|
|
4249
|
-
if (!
|
|
4355
|
+
const configPath = join20(home, ".windsurf", "mcp.json");
|
|
4356
|
+
if (!existsSync21(configPath)) return null;
|
|
4250
4357
|
try {
|
|
4251
|
-
const config = JSON.parse(
|
|
4358
|
+
const config = JSON.parse(readFileSync15(configPath, "utf-8"));
|
|
4252
4359
|
const servers = Object.keys(config.mcpServers || {});
|
|
4253
4360
|
if (servers.length === 0) return null;
|
|
4254
4361
|
return { agentType, category, currentGrant: "unrestricted", newGrant: "denied", configPath, description: `Remove MCP servers (${servers.join(", ")})` };
|
|
@@ -4261,7 +4368,7 @@ function buildFixForAgent(change, home) {
|
|
|
4261
4368
|
}
|
|
4262
4369
|
function applyFix(fix) {
|
|
4263
4370
|
try {
|
|
4264
|
-
const content =
|
|
4371
|
+
const content = readFileSync15(fix.configPath, "utf-8");
|
|
4265
4372
|
const config = JSON.parse(content);
|
|
4266
4373
|
let modified = false;
|
|
4267
4374
|
if (fix.agentType === "claude-code") {
|
|
@@ -4315,7 +4422,7 @@ function applyFix(fix) {
|
|
|
4315
4422
|
}
|
|
4316
4423
|
}
|
|
4317
4424
|
if (modified) {
|
|
4318
|
-
|
|
4425
|
+
writeFileSync9(fix.configPath, JSON.stringify(config, null, 2) + "\n");
|
|
4319
4426
|
return { fix, applied: true };
|
|
4320
4427
|
}
|
|
4321
4428
|
return { fix, applied: false, error: "No changes needed" };
|
|
@@ -4552,13 +4659,13 @@ function grantLevel(grant) {
|
|
|
4552
4659
|
|
|
4553
4660
|
// src/commands/gateway.ts
|
|
4554
4661
|
import pc12 from "picocolors";
|
|
4555
|
-
import { existsSync as
|
|
4556
|
-
import { join as
|
|
4662
|
+
import { existsSync as existsSync22, readFileSync as readFileSync16, writeFileSync as writeFileSync10, mkdirSync as mkdirSync3 } from "fs";
|
|
4663
|
+
import { join as join21 } from "path";
|
|
4557
4664
|
import { homedir as homedir15 } from "os";
|
|
4558
4665
|
import { execSync, spawn } from "child_process";
|
|
4559
|
-
var IMDL_DIR3 =
|
|
4560
|
-
var GATEWAY_CONFIG =
|
|
4561
|
-
var GATEWAY_PID_FILE =
|
|
4666
|
+
var IMDL_DIR3 = join21(homedir15(), ".imdl");
|
|
4667
|
+
var GATEWAY_CONFIG = join21(IMDL_DIR3, "gateway.toml");
|
|
4668
|
+
var GATEWAY_PID_FILE = join21(IMDL_DIR3, "gateway.pid");
|
|
4562
4669
|
var DEFAULT_PORT = 9443;
|
|
4563
4670
|
async function gatewayCommand(opts) {
|
|
4564
4671
|
const status = await getGatewayStatus();
|
|
@@ -4633,8 +4740,8 @@ async function gatewayStartCommand(opts) {
|
|
|
4633
4740
|
});
|
|
4634
4741
|
child.unref();
|
|
4635
4742
|
if (child.pid) {
|
|
4636
|
-
if (!
|
|
4637
|
-
|
|
4743
|
+
if (!existsSync22(IMDL_DIR3)) mkdirSync3(IMDL_DIR3, { recursive: true });
|
|
4744
|
+
writeFileSync10(GATEWAY_PID_FILE, String(child.pid));
|
|
4638
4745
|
console.log(pc12.green(`Gateway started (PID ${child.pid}, port ${port})`));
|
|
4639
4746
|
console.log(pc12.dim(` Config: ${GATEWAY_CONFIG}`));
|
|
4640
4747
|
console.log(pc12.dim(` Set ANTHROPIC_BASE_URL=http://127.0.0.1:${port} to intercept traffic`));
|
|
@@ -4693,12 +4800,12 @@ async function gatewayDisableCommand(opts) {
|
|
|
4693
4800
|
console.log(pc12.dim("Restart gateway for changes to take effect."));
|
|
4694
4801
|
}
|
|
4695
4802
|
async function gatewayAlertsCommand(opts) {
|
|
4696
|
-
const logPath =
|
|
4697
|
-
if (!
|
|
4803
|
+
const logPath = join21(IMDL_DIR3, "gateway-alerts.jsonl");
|
|
4804
|
+
if (!existsSync22(logPath)) {
|
|
4698
4805
|
console.log(pc12.dim("No alerts logged yet."));
|
|
4699
4806
|
return;
|
|
4700
4807
|
}
|
|
4701
|
-
const lines =
|
|
4808
|
+
const lines = readFileSync16(logPath, "utf-8").split("\n").filter(Boolean);
|
|
4702
4809
|
const limit = opts.limit ? parseInt(opts.limit, 10) : 20;
|
|
4703
4810
|
const recent = lines.slice(-limit);
|
|
4704
4811
|
if (opts.json) {
|
|
@@ -4800,9 +4907,9 @@ async function getGatewayStatus() {
|
|
|
4800
4907
|
return { running: true, pid, port, stats, config };
|
|
4801
4908
|
}
|
|
4802
4909
|
function getRunningPid() {
|
|
4803
|
-
if (!
|
|
4910
|
+
if (!existsSync22(GATEWAY_PID_FILE)) return null;
|
|
4804
4911
|
try {
|
|
4805
|
-
const pid = parseInt(
|
|
4912
|
+
const pid = parseInt(readFileSync16(GATEWAY_PID_FILE, "utf-8").trim(), 10);
|
|
4806
4913
|
return isNaN(pid) ? null : pid;
|
|
4807
4914
|
} catch {
|
|
4808
4915
|
return null;
|
|
@@ -4817,9 +4924,9 @@ function isProcessAlive(pid) {
|
|
|
4817
4924
|
}
|
|
4818
4925
|
}
|
|
4819
4926
|
function getConfiguredPort() {
|
|
4820
|
-
if (!
|
|
4927
|
+
if (!existsSync22(GATEWAY_CONFIG)) return DEFAULT_PORT;
|
|
4821
4928
|
try {
|
|
4822
|
-
const content =
|
|
4929
|
+
const content = readFileSync16(GATEWAY_CONFIG, "utf-8");
|
|
4823
4930
|
const match = content.match(/listen_port\s*=\s*(\d+)/);
|
|
4824
4931
|
return match ? parseInt(match[1], 10) : DEFAULT_PORT;
|
|
4825
4932
|
} catch {
|
|
@@ -4828,12 +4935,12 @@ function getConfiguredPort() {
|
|
|
4828
4935
|
}
|
|
4829
4936
|
function findGatewayBinary() {
|
|
4830
4937
|
const candidates = [
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4938
|
+
join21(process.cwd(), "target", "release", "imdl-gateway"),
|
|
4939
|
+
join21(process.cwd(), "..", "ai-gateway", "target", "release", "imdl-gateway"),
|
|
4940
|
+
join21(homedir15(), ".imdl", "bin", "imdl-gateway")
|
|
4834
4941
|
];
|
|
4835
4942
|
for (const path of candidates) {
|
|
4836
|
-
if (
|
|
4943
|
+
if (existsSync22(path)) return path;
|
|
4837
4944
|
}
|
|
4838
4945
|
try {
|
|
4839
4946
|
const result = execSync("which imdl-gateway", { encoding: "utf-8" }).trim();
|
|
@@ -4843,8 +4950,8 @@ function findGatewayBinary() {
|
|
|
4843
4950
|
return null;
|
|
4844
4951
|
}
|
|
4845
4952
|
function ensureConfig(port) {
|
|
4846
|
-
if (
|
|
4847
|
-
if (!
|
|
4953
|
+
if (existsSync22(GATEWAY_CONFIG)) return;
|
|
4954
|
+
if (!existsSync22(IMDL_DIR3)) mkdirSync3(IMDL_DIR3, { recursive: true });
|
|
4848
4955
|
const defaultToml = `# IMDL AI Gateway configuration
|
|
4849
4956
|
listen_host = "127.0.0.1"
|
|
4850
4957
|
listen_port = ${port}
|
|
@@ -4871,7 +4978,7 @@ openai_url = "https://api.openai.com"
|
|
|
4871
4978
|
[audit]
|
|
4872
4979
|
enabled = true
|
|
4873
4980
|
`;
|
|
4874
|
-
|
|
4981
|
+
writeFileSync10(GATEWAY_CONFIG, defaultToml, { mode: 384 });
|
|
4875
4982
|
}
|
|
4876
4983
|
function normalizeModule(input) {
|
|
4877
4984
|
const map = {
|
|
@@ -4888,13 +4995,13 @@ function normalizeModule(input) {
|
|
|
4888
4995
|
return map[input.toLowerCase()] ?? null;
|
|
4889
4996
|
}
|
|
4890
4997
|
function loadGatewayToml() {
|
|
4891
|
-
if (!
|
|
4998
|
+
if (!existsSync22(GATEWAY_CONFIG)) {
|
|
4892
4999
|
ensureConfig(DEFAULT_PORT);
|
|
4893
5000
|
}
|
|
4894
|
-
return
|
|
5001
|
+
return readFileSync16(GATEWAY_CONFIG, "utf-8");
|
|
4895
5002
|
}
|
|
4896
5003
|
function saveGatewayToml(content) {
|
|
4897
|
-
|
|
5004
|
+
writeFileSync10(GATEWAY_CONFIG, content, { mode: 384 });
|
|
4898
5005
|
}
|
|
4899
5006
|
function setModuleEnabled(content, module, enabled) {
|
|
4900
5007
|
const sectionRegex = new RegExp(`(\\[${module}\\][^\\[]*?)enabled\\s*=\\s*(true|false)`, "s");
|
|
@@ -5224,18 +5331,18 @@ function ruleTypeToCategory(type) {
|
|
|
5224
5331
|
}
|
|
5225
5332
|
|
|
5226
5333
|
// src/transport/sync.ts
|
|
5227
|
-
import { readFileSync as
|
|
5228
|
-
import { join as
|
|
5334
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync11, existsSync as existsSync23 } from "fs";
|
|
5335
|
+
import { join as join22 } from "path";
|
|
5229
5336
|
var SYNC_INTERVAL = 6e4;
|
|
5230
5337
|
var SYNC_TIMEOUT = 3e3;
|
|
5231
5338
|
function getSyncStateFile() {
|
|
5232
|
-
return
|
|
5339
|
+
return join22(getImdlDir(), "last_sync.json");
|
|
5233
5340
|
}
|
|
5234
5341
|
function getLastSyncTime() {
|
|
5235
5342
|
try {
|
|
5236
5343
|
const file = getSyncStateFile();
|
|
5237
|
-
if (
|
|
5238
|
-
const data = JSON.parse(
|
|
5344
|
+
if (existsSync23(file)) {
|
|
5345
|
+
const data = JSON.parse(readFileSync17(file, "utf-8"));
|
|
5239
5346
|
return data.lastSync || 0;
|
|
5240
5347
|
}
|
|
5241
5348
|
} catch {
|
|
@@ -5244,7 +5351,7 @@ function getLastSyncTime() {
|
|
|
5244
5351
|
}
|
|
5245
5352
|
function setLastSyncTime() {
|
|
5246
5353
|
try {
|
|
5247
|
-
|
|
5354
|
+
writeFileSync11(getSyncStateFile(), JSON.stringify({ lastSync: Date.now() }), { mode: 384 });
|
|
5248
5355
|
} catch {
|
|
5249
5356
|
}
|
|
5250
5357
|
}
|