agdi 2.2.0 → 2.2.1

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 +1571 -96
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { Command } from "commander";
5
- import chalk9 from "chalk";
5
+ import chalk12 from "chalk";
6
6
  import ora4 from "ora";
7
7
 
8
8
  // src/core/llm/index.ts
@@ -1020,7 +1020,7 @@ async function runProject(targetDir) {
1020
1020
  if (!fs3.existsSync(nodeModulesPath)) {
1021
1021
  console.log(chalk6.yellow("\u{1F4E6} Installing dependencies...\n"));
1022
1022
  const installSpinner = ora2("Running npm install...").start();
1023
- await new Promise((resolve, reject) => {
1023
+ await new Promise((resolve5, reject) => {
1024
1024
  const install = spawn("npm", ["install"], {
1025
1025
  cwd: absoluteDir,
1026
1026
  stdio: "inherit",
@@ -1029,7 +1029,7 @@ async function runProject(targetDir) {
1029
1029
  install.on("close", (code) => {
1030
1030
  if (code === 0) {
1031
1031
  installSpinner.succeed("Dependencies installed!");
1032
- resolve();
1032
+ resolve5();
1033
1033
  } else {
1034
1034
  installSpinner.fail("npm install failed");
1035
1035
  reject(new Error(`npm install exited with code ${code}`));
@@ -1272,16 +1272,1475 @@ async function selectModel() {
1272
1272
 
1273
1273
  // src/commands/codex.ts
1274
1274
  import { input as input4 } from "@inquirer/prompts";
1275
- import chalk8 from "chalk";
1275
+ import chalk11 from "chalk";
1276
1276
  import ora3 from "ora";
1277
- var SYSTEM_PROMPT3 = `You are Agdi dev, an elite AI coding assistant. You help developers write code, debug issues, and build applications.
1277
+
1278
+ // src/actions/plan-executor.ts
1279
+ import { select as select4, confirm } from "@inquirer/prompts";
1280
+ import chalk10 from "chalk";
1281
+ import { spawn as spawn2 } from "child_process";
1282
+ import { resolve as resolve4 } from "path";
1283
+
1284
+ // src/actions/types.ts
1285
+ function summarizePlan(plan) {
1286
+ let filesCreated = 0;
1287
+ let filesDeleted = 0;
1288
+ let dirsCreated = 0;
1289
+ let commandsToRun = 0;
1290
+ const domains = [];
1291
+ const ports = [];
1292
+ let maxRiskTier = 0;
1293
+ for (const action of plan.actions) {
1294
+ switch (action.type) {
1295
+ case "mkdir":
1296
+ dirsCreated++;
1297
+ maxRiskTier = Math.max(maxRiskTier, 1);
1298
+ break;
1299
+ case "writeFile":
1300
+ filesCreated++;
1301
+ maxRiskTier = Math.max(maxRiskTier, 1);
1302
+ break;
1303
+ case "deleteFile":
1304
+ filesDeleted++;
1305
+ maxRiskTier = Math.max(maxRiskTier, 1);
1306
+ break;
1307
+ case "exec":
1308
+ commandsToRun++;
1309
+ if (["npm", "yarn", "pnpm", "pip"].includes(action.argv[0])) {
1310
+ maxRiskTier = Math.max(maxRiskTier, 2);
1311
+ if (["npm", "yarn", "pnpm"].includes(action.argv[0])) {
1312
+ domains.push("registry.npmjs.org");
1313
+ }
1314
+ if (action.argv[0] === "pip") {
1315
+ domains.push("pypi.org");
1316
+ }
1317
+ }
1318
+ if (action.argv.includes("dev") || action.argv.includes("start")) {
1319
+ ports.push(3e3);
1320
+ }
1321
+ break;
1322
+ }
1323
+ }
1324
+ return {
1325
+ filesCreated,
1326
+ filesDeleted,
1327
+ dirsCreated,
1328
+ commandsToRun,
1329
+ domains: [...new Set(domains)],
1330
+ ports: [...new Set(ports)],
1331
+ riskTier: maxRiskTier
1332
+ };
1333
+ }
1334
+ function parseActionPlan(response) {
1335
+ try {
1336
+ const jsonMatch = response.match(/\{[\s\S]*"actions"[\s\S]*\}/);
1337
+ if (!jsonMatch) {
1338
+ return null;
1339
+ }
1340
+ const parsed = JSON.parse(jsonMatch[0]);
1341
+ if (!parsed.actions || !Array.isArray(parsed.actions)) {
1342
+ return null;
1343
+ }
1344
+ return {
1345
+ projectName: parsed.projectName || "generated-app",
1346
+ actions: parsed.actions,
1347
+ nextSteps: parsed.nextSteps
1348
+ };
1349
+ } catch {
1350
+ return null;
1351
+ }
1352
+ }
1353
+
1354
+ // src/actions/fs-tools.ts
1355
+ import { mkdir, writeFile, rm, readFile } from "fs/promises";
1356
+ import { existsSync as existsSync3 } from "fs";
1357
+ import { resolve, dirname, relative, isAbsolute } from "path";
1358
+
1359
+ // src/security/execution-env.ts
1360
+ import chalk8 from "chalk";
1361
+ import { platform, cwd as processCwd } from "os";
1362
+ import { existsSync, readFileSync } from "fs";
1363
+ function detectWSL() {
1364
+ if (platform() !== "linux") return false;
1365
+ try {
1366
+ if (process.env.WSL_DISTRO_NAME || process.env.WSLENV) {
1367
+ return true;
1368
+ }
1369
+ if (existsSync("/proc/version")) {
1370
+ const version = readFileSync("/proc/version", "utf-8");
1371
+ return version.toLowerCase().includes("microsoft");
1372
+ }
1373
+ } catch {
1374
+ }
1375
+ return false;
1376
+ }
1377
+ function detectContainer() {
1378
+ try {
1379
+ if (existsSync("/.dockerenv")) {
1380
+ return true;
1381
+ }
1382
+ if (existsSync("/proc/1/cgroup")) {
1383
+ const cgroup = readFileSync("/proc/1/cgroup", "utf-8");
1384
+ if (cgroup.includes("docker") || cgroup.includes("lxc") || cgroup.includes("kubepods")) {
1385
+ return true;
1386
+ }
1387
+ }
1388
+ if (process.env.KUBERNETES_SERVICE_HOST) {
1389
+ return true;
1390
+ }
1391
+ } catch {
1392
+ }
1393
+ return false;
1394
+ }
1395
+ function detectBackend() {
1396
+ const os2 = platform();
1397
+ if (detectContainer()) {
1398
+ return "container";
1399
+ }
1400
+ if (detectWSL()) {
1401
+ return "wsl";
1402
+ }
1403
+ switch (os2) {
1404
+ case "win32":
1405
+ return "windows-host";
1406
+ case "linux":
1407
+ return "linux-host";
1408
+ case "darwin":
1409
+ return "macos-host";
1410
+ default:
1411
+ return "linux-host";
1412
+ }
1413
+ }
1414
+ function getWorkspaceRoot() {
1415
+ return processCwd();
1416
+ }
1417
+ function detectEnvironment(overrides) {
1418
+ const os2 = platform() === "win32" ? "windows" : platform() === "darwin" ? "darwin" : "linux";
1419
+ const backend = detectBackend();
1420
+ const workspaceRoot = getWorkspaceRoot();
1421
+ return {
1422
+ os: os2,
1423
+ backend,
1424
+ workspaceRoot,
1425
+ cwd: workspaceRoot,
1426
+ // Start cwd at workspace root
1427
+ networkPolicy: "off",
1428
+ // Default: network off
1429
+ allowedDomains: [
1430
+ "registry.npmjs.org",
1431
+ "pypi.org",
1432
+ "github.com",
1433
+ "api.github.com"
1434
+ ],
1435
+ trustLevel: "untrusted",
1436
+ // Default: untrusted
1437
+ isContainer: backend === "container",
1438
+ isWSL: backend === "wsl",
1439
+ ...overrides
1440
+ };
1441
+ }
1442
+ function formatBackend(backend) {
1443
+ switch (backend) {
1444
+ case "windows-host":
1445
+ return "Windows host";
1446
+ case "linux-host":
1447
+ return "Linux host";
1448
+ case "macos-host":
1449
+ return "macOS host";
1450
+ case "wsl":
1451
+ return "WSL (Windows Subsystem for Linux)";
1452
+ case "container":
1453
+ return "Container sandbox";
1454
+ }
1455
+ }
1456
+ function formatNetwork(policy, domains) {
1457
+ switch (policy) {
1458
+ case "off":
1459
+ return "off";
1460
+ case "allowlist":
1461
+ return `allowlist (${domains.slice(0, 2).join(", ")}${domains.length > 2 ? "..." : ""})`;
1462
+ case "on":
1463
+ return "unrestricted";
1464
+ }
1465
+ }
1466
+ function formatTrust(trust) {
1467
+ switch (trust) {
1468
+ case "untrusted":
1469
+ return chalk8.red("untrusted (read-only mode)");
1470
+ case "session":
1471
+ return chalk8.yellow("session trusted");
1472
+ case "persistent":
1473
+ return chalk8.green("trusted");
1474
+ }
1475
+ }
1476
+ function displaySessionHeader(env) {
1477
+ const boxWidth = 54;
1478
+ const topBorder = "\u256D\u2500 Agdi Terminal Session " + "\u2500".repeat(boxWidth - 25) + "\u256E";
1479
+ const bottomBorder = "\u2570" + "\u2500".repeat(boxWidth) + "\u256F";
1480
+ const lines = [
1481
+ `Exec env: ${formatBackend(env.backend)}`,
1482
+ `Workspace: ${truncatePath(env.workspaceRoot, 40)}`,
1483
+ `cwd: ${truncatePath(env.cwd, 40)}`,
1484
+ `Network: ${formatNetwork(env.networkPolicy, env.allowedDomains)}`,
1485
+ `Trust: ${formatTrust(env.trustLevel)}`
1486
+ ];
1487
+ console.log(chalk8.cyan(topBorder));
1488
+ for (const line of lines) {
1489
+ const padding = " ".repeat(Math.max(0, boxWidth - stripAnsi(line).length - 2));
1490
+ console.log(chalk8.cyan("\u2502 ") + line + padding + chalk8.cyan(" \u2502"));
1491
+ }
1492
+ console.log(chalk8.cyan(bottomBorder));
1493
+ console.log("");
1494
+ }
1495
+ function truncatePath(path4, maxLen) {
1496
+ if (path4.length <= maxLen) return path4;
1497
+ return "..." + path4.slice(-(maxLen - 3));
1498
+ }
1499
+ function stripAnsi(str) {
1500
+ return str.replace(/\u001b\[[0-9;]*m/g, "");
1501
+ }
1502
+ var currentEnvironment = null;
1503
+ function initSession(overrides) {
1504
+ currentEnvironment = detectEnvironment(overrides);
1505
+ return currentEnvironment;
1506
+ }
1507
+ function getEnvironment() {
1508
+ if (!currentEnvironment) {
1509
+ return initSession();
1510
+ }
1511
+ return currentEnvironment;
1512
+ }
1513
+ function updateEnvironment(updates) {
1514
+ currentEnvironment = { ...getEnvironment(), ...updates };
1515
+ return currentEnvironment;
1516
+ }
1517
+
1518
+ // src/security/audit-logger.ts
1519
+ import { existsSync as existsSync2, appendFileSync, readFileSync as readFileSync2, mkdirSync, writeFileSync } from "fs";
1520
+ import { join } from "path";
1521
+ import { homedir as homedir2 } from "os";
1522
+ var LOG_DIR = join(homedir2(), ".agdi");
1523
+ var LOG_FILE = join(LOG_DIR, "audit.jsonl");
1524
+ var MAX_OUTPUT_LENGTH = 1e3;
1525
+ var currentSessionId = null;
1526
+ function generateSessionId() {
1527
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
1528
+ }
1529
+ function getSessionId() {
1530
+ if (!currentSessionId) {
1531
+ currentSessionId = generateSessionId();
1532
+ }
1533
+ return currentSessionId;
1534
+ }
1535
+ function ensureLogDir() {
1536
+ if (!existsSync2(LOG_DIR)) {
1537
+ mkdirSync(LOG_DIR, { recursive: true });
1538
+ }
1539
+ }
1540
+ function truncateOutput(output) {
1541
+ if (!output) return output;
1542
+ if (output.length <= MAX_OUTPUT_LENGTH) return output;
1543
+ return output.substring(0, MAX_OUTPUT_LENGTH) + `... (truncated, ${output.length} total chars)`;
1544
+ }
1545
+ function logEvent(event) {
1546
+ ensureLogDir();
1547
+ const fullEvent = {
1548
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1549
+ sessionId: getSessionId(),
1550
+ ...event
1551
+ };
1552
+ if (fullEvent.result?.output) {
1553
+ fullEvent.result.output = truncateOutput(fullEvent.result.output);
1554
+ }
1555
+ try {
1556
+ appendFileSync(LOG_FILE, JSON.stringify(fullEvent) + "\n");
1557
+ } catch (error) {
1558
+ console.error("Failed to write audit log:", error);
1559
+ }
1560
+ }
1561
+ function logSessionStart(workspacePath, trustLevel) {
1562
+ currentSessionId = generateSessionId();
1563
+ logEvent({
1564
+ eventType: "session_start",
1565
+ metadata: {
1566
+ workspacePath,
1567
+ trustLevel,
1568
+ platform: process.platform,
1569
+ nodeVersion: process.version
1570
+ }
1571
+ });
1572
+ }
1573
+ function logSessionEnd() {
1574
+ logEvent({
1575
+ eventType: "session_end"
1576
+ });
1577
+ }
1578
+
1579
+ // src/actions/fs-tools.ts
1580
+ function isWithinWorkspace(p, workspaceRoot) {
1581
+ const resolved = resolve(workspaceRoot, p);
1582
+ const root = resolve(workspaceRoot);
1583
+ const rel = relative(root, resolved);
1584
+ return rel === "" || !rel.startsWith("..") && !isAbsolute(rel);
1585
+ }
1586
+ function validatePath(path4) {
1587
+ const env = getEnvironment();
1588
+ const resolved = resolve(env.workspaceRoot, path4);
1589
+ if (!isWithinWorkspace(path4, env.workspaceRoot)) {
1590
+ return {
1591
+ valid: false,
1592
+ resolved,
1593
+ error: `Path outside workspace: ${path4}`
1594
+ };
1595
+ }
1596
+ return { valid: true, resolved };
1597
+ }
1598
+ async function mkdirTool(path4) {
1599
+ const validation = validatePath(path4);
1600
+ if (!validation.valid) {
1601
+ return { success: false, error: validation.error };
1602
+ }
1603
+ try {
1604
+ await mkdir(validation.resolved, { recursive: true });
1605
+ logEvent({
1606
+ eventType: "command_result",
1607
+ command: `mkdir ${path4}`,
1608
+ result: { exitCode: 0 },
1609
+ metadata: { tool: "mkdirTool", path: validation.resolved }
1610
+ });
1611
+ return { success: true };
1612
+ } catch (error) {
1613
+ const msg = error instanceof Error ? error.message : String(error);
1614
+ return { success: false, error: msg };
1615
+ }
1616
+ }
1617
+ async function writeFileTool(path4, content) {
1618
+ const validation = validatePath(path4);
1619
+ if (!validation.valid) {
1620
+ return { success: false, error: validation.error };
1621
+ }
1622
+ try {
1623
+ const dir = dirname(validation.resolved);
1624
+ await mkdir(dir, { recursive: true });
1625
+ await writeFile(validation.resolved, content, "utf-8");
1626
+ logEvent({
1627
+ eventType: "command_result",
1628
+ command: `writeFile ${path4}`,
1629
+ result: { exitCode: 0 },
1630
+ metadata: {
1631
+ tool: "writeFileTool",
1632
+ path: validation.resolved,
1633
+ contentLength: content.length
1634
+ }
1635
+ });
1636
+ return { success: true };
1637
+ } catch (error) {
1638
+ const msg = error instanceof Error ? error.message : String(error);
1639
+ return { success: false, error: msg };
1640
+ }
1641
+ }
1642
+ async function deleteFileTool(path4) {
1643
+ const validation = validatePath(path4);
1644
+ if (!validation.valid) {
1645
+ return { success: false, error: validation.error };
1646
+ }
1647
+ try {
1648
+ if (!existsSync3(validation.resolved)) {
1649
+ return { success: true };
1650
+ }
1651
+ await rm(validation.resolved);
1652
+ logEvent({
1653
+ eventType: "command_result",
1654
+ command: `deleteFile ${path4}`,
1655
+ result: { exitCode: 0 },
1656
+ metadata: { tool: "deleteFileTool", path: validation.resolved }
1657
+ });
1658
+ return { success: true };
1659
+ } catch (error) {
1660
+ const msg = error instanceof Error ? error.message : String(error);
1661
+ return { success: false, error: msg };
1662
+ }
1663
+ }
1664
+
1665
+ // src/security/permission-gate.ts
1666
+ import { resolve as resolve2, relative as relative2, isAbsolute as isAbsolute2 } from "path";
1667
+
1668
+ // src/security/argv-parser.ts
1669
+ var WINDOWS_FLAG_PATTERNS = [
1670
+ // Single letter flags: /i, /s, /r, /q, etc.
1671
+ /^\/[aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ]$/,
1672
+ // Numbered flags: /1, /2, etc.
1673
+ /^\/[0-9]+$/,
1674
+ // Common multi-char flags
1675
+ /^\/(?:all|help|version|quiet|verbose|force|recursive)$/i,
1676
+ // findstr flags
1677
+ /^\/(?:b|e|l|r|s|i|x|v|n|m|o|p|offline|c|g|f|d|a)$/i,
1678
+ // xcopy/robocopy flags
1679
+ /^\/(?:e|s|h|k|y|q|f|l|c|v|w|d|u|m|a|t|n|o|x|exclude|copy|move|purge)$/i,
1680
+ // dir flags
1681
+ /^\/(?:a|b|c|d|l|n|o|p|q|r|s|t|w|x|4)$/i
1682
+ ];
1683
+ var TOOL_ARG_SCHEMAS = {
1684
+ findstr: { flags: [], notPaths: [] },
1685
+ // All /x args are flags
1686
+ xcopy: { flags: [], notPaths: [] },
1687
+ robocopy: { flags: [], notPaths: [] },
1688
+ dir: { flags: [], notPaths: [] },
1689
+ find: { flags: [], notPaths: [0] },
1690
+ // First arg is search string
1691
+ grep: { flags: [], notPaths: [0] },
1692
+ // First arg is pattern
1693
+ sed: { flags: [], notPaths: [0] },
1694
+ // First arg is expression
1695
+ awk: { flags: [], notPaths: [0] }
1696
+ // First arg is program
1697
+ };
1698
+ function parseArgv(command) {
1699
+ const argv = [];
1700
+ let current = "";
1701
+ let inSingleQuote = false;
1702
+ let inDoubleQuote = false;
1703
+ let escaped = false;
1704
+ for (let i = 0; i < command.length; i++) {
1705
+ const char = command[i];
1706
+ if (escaped) {
1707
+ current += char;
1708
+ escaped = false;
1709
+ continue;
1710
+ }
1711
+ if (char === "\\" && !inSingleQuote) {
1712
+ escaped = true;
1713
+ continue;
1714
+ }
1715
+ if (char === "'" && !inDoubleQuote) {
1716
+ inSingleQuote = !inSingleQuote;
1717
+ continue;
1718
+ }
1719
+ if (char === '"' && !inSingleQuote) {
1720
+ inDoubleQuote = !inDoubleQuote;
1721
+ continue;
1722
+ }
1723
+ if ((char === " " || char === " ") && !inSingleQuote && !inDoubleQuote) {
1724
+ if (current) {
1725
+ argv.push(current);
1726
+ current = "";
1727
+ }
1728
+ continue;
1729
+ }
1730
+ current += char;
1731
+ }
1732
+ if (current) {
1733
+ argv.push(current);
1734
+ }
1735
+ return {
1736
+ argv,
1737
+ command: argv[0] || "",
1738
+ args: argv.slice(1),
1739
+ rawCommand: command
1740
+ };
1741
+ }
1742
+ function isWindowsFlag(token, commandName) {
1743
+ for (const pattern of WINDOWS_FLAG_PATTERNS) {
1744
+ if (pattern.test(token)) {
1745
+ return true;
1746
+ }
1747
+ }
1748
+ if (commandName && TOOL_ARG_SCHEMAS[commandName.toLowerCase()]) {
1749
+ if (/^\/[a-zA-Z]/.test(token)) {
1750
+ return true;
1751
+ }
1752
+ }
1753
+ return false;
1754
+ }
1755
+ function isLikelyPath(token, commandName) {
1756
+ if (isWindowsFlag(token, commandName)) {
1757
+ return false;
1758
+ }
1759
+ if (token.startsWith("/") && token.length > 1 && token.includes("/")) {
1760
+ return true;
1761
+ }
1762
+ if (/^[A-Za-z]:[\\/]/.test(token)) {
1763
+ return true;
1764
+ }
1765
+ if (token.includes("/") || token.includes("\\")) {
1766
+ return true;
1767
+ }
1768
+ if (/\.[a-zA-Z0-9]{1,6}$/.test(token) && !token.startsWith("-") && !token.startsWith("/")) {
1769
+ return true;
1770
+ }
1771
+ if (token === "." || token === ".." || token.startsWith("./") || token.startsWith("../")) {
1772
+ return true;
1773
+ }
1774
+ return false;
1775
+ }
1776
+ var WRITE_COMMANDS = {
1777
+ // Position of write path(s) in args (0-indexed)
1778
+ cp: [1],
1779
+ mv: [1],
1780
+ touch: [0, 1, 2, 3, 4],
1781
+ // All args
1782
+ mkdir: [0, 1, 2, 3, 4],
1783
+ rm: [0, 1, 2, 3, 4],
1784
+ echo: [],
1785
+ // Only writes with >
1786
+ tee: [0],
1787
+ sed: [1],
1788
+ // With -i
1789
+ tar: [1],
1790
+ // -cf archive
1791
+ zip: [0],
1792
+ unzip: [],
1793
+ // Uses -d flag
1794
+ git: [],
1795
+ // Complex
1796
+ npm: [],
1797
+ // Writes to node_modules
1798
+ yarn: [],
1799
+ pnpm: []
1800
+ };
1801
+ var READ_COMMANDS = {
1802
+ cat: [0, 1, 2, 3, 4],
1803
+ head: [0],
1804
+ tail: [0],
1805
+ less: [0],
1806
+ more: [0],
1807
+ grep: [1, 2, 3, 4],
1808
+ find: [0],
1809
+ ls: [0],
1810
+ dir: [0],
1811
+ type: [0]
1812
+ };
1813
+ function extractPaths(parsed) {
1814
+ const paths = [];
1815
+ const cmd = parsed.command.toLowerCase();
1816
+ const rawCmd = parsed.rawCommand;
1817
+ const redirectMatch = rawCmd.match(/>\s*(\S+)/);
1818
+ if (redirectMatch) {
1819
+ paths.push({
1820
+ path: redirectMatch[1],
1821
+ operation: "write",
1822
+ position: -1
1823
+ });
1824
+ }
1825
+ for (let i = 0; i < parsed.args.length; i++) {
1826
+ const arg = parsed.args[i];
1827
+ if (!isLikelyPath(arg, cmd)) {
1828
+ continue;
1829
+ }
1830
+ let operation = "unknown";
1831
+ if (WRITE_COMMANDS[cmd]?.includes(i)) {
1832
+ operation = "write";
1833
+ } else if (READ_COMMANDS[cmd]?.includes(i)) {
1834
+ operation = "read";
1835
+ }
1836
+ paths.push({
1837
+ path: arg,
1838
+ operation,
1839
+ position: i
1840
+ });
1841
+ }
1842
+ return paths;
1843
+ }
1844
+ function extractDomains(command) {
1845
+ const domains = [];
1846
+ const urlPattern = /https?:\/\/([a-zA-Z0-9.-]+)/gi;
1847
+ let match;
1848
+ while ((match = urlPattern.exec(command)) !== null) {
1849
+ domains.push(match[1]);
1850
+ }
1851
+ const networkCommands = ["curl", "wget", "fetch", "npm", "yarn", "pnpm", "pip", "git"];
1852
+ const parsed = parseArgv(command);
1853
+ if (networkCommands.includes(parsed.command.toLowerCase())) {
1854
+ if (["npm", "yarn", "pnpm"].includes(parsed.command.toLowerCase())) {
1855
+ domains.push("registry.npmjs.org");
1856
+ }
1857
+ if (parsed.command.toLowerCase() === "pip") {
1858
+ domains.push("pypi.org");
1859
+ }
1860
+ }
1861
+ return [...new Set(domains)];
1862
+ }
1863
+ function extractPorts(command) {
1864
+ const ports = [];
1865
+ const patterns = [
1866
+ /-p\s*(\d+)/gi,
1867
+ // -p 3000
1868
+ /--port[=\s]+(\d+)/gi,
1869
+ // --port 3000 or --port=3000
1870
+ /:(\d{2,5})(?:\s|$|\/)/g,
1871
+ // :3000 or localhost:3000
1872
+ /PORT[=:]\s*(\d+)/gi
1873
+ // PORT=3000
1874
+ ];
1875
+ for (const pattern of patterns) {
1876
+ let match;
1877
+ while ((match = pattern.exec(command)) !== null) {
1878
+ const port = parseInt(match[1], 10);
1879
+ if (port > 0 && port <= 65535) {
1880
+ ports.push(port);
1881
+ }
1882
+ }
1883
+ }
1884
+ return [...new Set(ports)];
1885
+ }
1886
+
1887
+ // src/security/rules-engine.ts
1888
+ import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
1889
+ import { join as join2 } from "path";
1890
+ import { homedir as homedir3 } from "os";
1891
+ var DEFAULT_RULES = [
1892
+ // Tier 0: Read-only (allow)
1893
+ { id: "default-ls", pattern: ["ls"], action: "allow", source: "default", createdAt: "", description: "List directory" },
1894
+ { id: "default-dir", pattern: ["dir"], action: "allow", source: "default", createdAt: "", description: "List directory (Windows)" },
1895
+ { id: "default-cat", pattern: ["cat"], action: "allow", source: "default", createdAt: "", description: "View file" },
1896
+ { id: "default-type", pattern: ["type"], action: "allow", source: "default", createdAt: "", description: "View file (Windows)" },
1897
+ { id: "default-pwd", pattern: ["pwd"], action: "allow", source: "default", createdAt: "", description: "Print working directory" },
1898
+ { id: "default-echo", pattern: ["echo"], action: "allow", source: "default", createdAt: "", description: "Print text" },
1899
+ { id: "default-head", pattern: ["head"], action: "allow", source: "default", createdAt: "", description: "View file head" },
1900
+ { id: "default-tail", pattern: ["tail"], action: "allow", source: "default", createdAt: "", description: "View file tail" },
1901
+ { id: "default-find", pattern: ["find"], action: "allow", source: "default", createdAt: "", description: "Find files" },
1902
+ { id: "default-grep", pattern: ["grep"], action: "allow", source: "default", createdAt: "", description: "Search in files" },
1903
+ { id: "default-findstr", pattern: ["findstr"], action: "allow", source: "default", createdAt: "", description: "Search in files (Windows)" },
1904
+ // Git read commands (allow)
1905
+ { id: "default-git-status", pattern: ["git", "status"], action: "allow", source: "default", createdAt: "", description: "Git status" },
1906
+ { id: "default-git-diff", pattern: ["git", "diff"], action: "allow", source: "default", createdAt: "", description: "Git diff" },
1907
+ { id: "default-git-log", pattern: ["git", "log"], action: "allow", source: "default", createdAt: "", description: "Git log" },
1908
+ { id: "default-git-branch", pattern: ["git", "branch"], action: "allow", source: "default", createdAt: "", description: "Git branch" },
1909
+ { id: "default-git-show", pattern: ["git", "show"], action: "allow", source: "default", createdAt: "", description: "Git show" },
1910
+ // Tier 1: Workspace writes (prompt)
1911
+ { id: "default-touch", pattern: ["touch"], action: "prompt", source: "default", createdAt: "", description: "Create file" },
1912
+ { id: "default-mkdir", pattern: ["mkdir"], action: "prompt", source: "default", createdAt: "", description: "Create directory" },
1913
+ { id: "default-git-add", pattern: ["git", "add"], action: "prompt", source: "default", createdAt: "", description: "Git add" },
1914
+ { id: "default-git-commit", pattern: ["git", "commit"], action: "prompt", source: "default", createdAt: "", description: "Git commit" },
1915
+ // Tier 2: Package managers (prompt)
1916
+ { id: "default-npm-install", pattern: ["npm", "install"], action: "prompt", source: "default", createdAt: "", description: "npm install" },
1917
+ { id: "default-npm-i", pattern: ["npm", "i"], action: "prompt", source: "default", createdAt: "", description: "npm install (short)" },
1918
+ { id: "default-yarn-add", pattern: ["yarn", "add"], action: "prompt", source: "default", createdAt: "", description: "yarn add" },
1919
+ { id: "default-yarn-install", pattern: ["yarn", "install"], action: "prompt", source: "default", createdAt: "", description: "yarn install" },
1920
+ { id: "default-pnpm-install", pattern: ["pnpm", "install"], action: "prompt", source: "default", createdAt: "", description: "pnpm install" },
1921
+ { id: "default-pnpm-add", pattern: ["pnpm", "add"], action: "prompt", source: "default", createdAt: "", description: "pnpm add" },
1922
+ { id: "default-pip-install", pattern: ["pip", "install"], action: "prompt", source: "default", createdAt: "", description: "pip install" },
1923
+ // Tier 3: Dangerous (forbid)
1924
+ { id: "default-sudo", pattern: ["sudo"], action: "forbid", source: "default", createdAt: "", description: "Elevated privileges" },
1925
+ { id: "default-rm-rf", pattern: ["rm", "-rf"], action: "forbid", source: "default", createdAt: "", description: "Recursive force delete" },
1926
+ { id: "default-rm-fr", pattern: ["rm", "-fr"], action: "forbid", source: "default", createdAt: "", description: "Recursive force delete" },
1927
+ { id: "default-chmod-777", pattern: ["chmod", "777"], action: "forbid", source: "default", createdAt: "", description: "World-writable permissions" },
1928
+ { id: "default-curl-bash", pattern: ["curl"], action: "prompt", source: "default", createdAt: "", description: "HTTP request" },
1929
+ { id: "default-wget", pattern: ["wget"], action: "prompt", source: "default", createdAt: "", description: "HTTP download" },
1930
+ { id: "default-git-push-force", pattern: ["git", "push", "--force"], action: "forbid", source: "default", createdAt: "", description: "Force push" },
1931
+ { id: "default-git-push-f", pattern: ["git", "push", "-f"], action: "forbid", source: "default", createdAt: "", description: "Force push" }
1932
+ ];
1933
+ function matchesPosition(argvItem, patternItem) {
1934
+ const argLower = argvItem.toLowerCase();
1935
+ if (Array.isArray(patternItem)) {
1936
+ return patternItem.some((p) => p.toLowerCase() === argLower);
1937
+ }
1938
+ return patternItem.toLowerCase() === argLower;
1939
+ }
1940
+ function matchesPattern(argv, pattern) {
1941
+ if (argv.length < pattern.length) {
1942
+ return false;
1943
+ }
1944
+ for (let i = 0; i < pattern.length; i++) {
1945
+ if (!matchesPosition(argv[i], pattern[i])) {
1946
+ return false;
1947
+ }
1948
+ }
1949
+ return true;
1950
+ }
1951
+ function getRestrictionLevel(action) {
1952
+ switch (action) {
1953
+ case "forbid":
1954
+ return 2;
1955
+ case "prompt":
1956
+ return 1;
1957
+ case "allow":
1958
+ return 0;
1959
+ }
1960
+ }
1961
+ function evaluateRules(argv, rules) {
1962
+ const matchedRules = [];
1963
+ for (const rule of rules) {
1964
+ if (matchesPattern(argv, rule.pattern)) {
1965
+ matchedRules.push(rule);
1966
+ }
1967
+ }
1968
+ if (matchedRules.length === 0) {
1969
+ return {
1970
+ decision: "prompt",
1971
+ // Default: prompt for unknown
1972
+ matchedRules: [],
1973
+ mostRestrictive: null
1974
+ };
1975
+ }
1976
+ let mostRestrictive = matchedRules[0];
1977
+ for (const rule of matchedRules) {
1978
+ if (getRestrictionLevel(rule.action) > getRestrictionLevel(mostRestrictive.action)) {
1979
+ mostRestrictive = rule;
1980
+ }
1981
+ }
1982
+ return {
1983
+ decision: mostRestrictive.action,
1984
+ matchedRules,
1985
+ mostRestrictive
1986
+ };
1987
+ }
1988
+ var RULES_DIR = join2(homedir3(), ".agdi");
1989
+ var RULES_FILE = join2(RULES_DIR, "rules.json");
1990
+ function loadUserRules() {
1991
+ try {
1992
+ if (existsSync4(RULES_FILE)) {
1993
+ const data = readFileSync3(RULES_FILE, "utf-8");
1994
+ const parsed = JSON.parse(data);
1995
+ return parsed.rules || [];
1996
+ }
1997
+ } catch {
1998
+ }
1999
+ return [];
2000
+ }
2001
+ var sessionRules = [];
2002
+ function getAllRules() {
2003
+ return [...DEFAULT_RULES, ...loadUserRules(), ...sessionRules];
2004
+ }
2005
+
2006
+ // src/security/shell-wrapper-detector.ts
2007
+ var SHELL_WRAPPERS = {
2008
+ bash: {
2009
+ patterns: [/^bash$/i],
2010
+ extractScript: (argv) => {
2011
+ const cIdx = argv.findIndex((a) => a === "-c" || a === "-lc");
2012
+ return cIdx !== -1 && argv[cIdx + 1] ? argv[cIdx + 1] : void 0;
2013
+ }
2014
+ },
2015
+ sh: {
2016
+ patterns: [/^sh$/i],
2017
+ extractScript: (argv) => {
2018
+ const cIdx = argv.findIndex((a) => a === "-c");
2019
+ return cIdx !== -1 && argv[cIdx + 1] ? argv[cIdx + 1] : void 0;
2020
+ }
2021
+ },
2022
+ zsh: {
2023
+ patterns: [/^zsh$/i],
2024
+ extractScript: (argv) => {
2025
+ const cIdx = argv.findIndex((a) => a === "-c");
2026
+ return cIdx !== -1 && argv[cIdx + 1] ? argv[cIdx + 1] : void 0;
2027
+ }
2028
+ },
2029
+ cmd: {
2030
+ patterns: [/^cmd$/i, /^cmd\.exe$/i],
2031
+ extractScript: (argv) => {
2032
+ const cIdx = argv.findIndex((a) => a.toLowerCase() === "/c" || a.toLowerCase() === "/k");
2033
+ return cIdx !== -1 ? argv.slice(cIdx + 1).join(" ") : void 0;
2034
+ }
2035
+ },
2036
+ powershell: {
2037
+ patterns: [/^powershell$/i, /^powershell\.exe$/i],
2038
+ extractScript: (argv) => {
2039
+ const cIdx = argv.findIndex(
2040
+ (a) => a.toLowerCase() === "-command" || a.toLowerCase() === "-c" || a.toLowerCase() === "-encodedcommand" || a.toLowerCase() === "-e"
2041
+ );
2042
+ return cIdx !== -1 && argv[cIdx + 1] ? argv[cIdx + 1] : void 0;
2043
+ }
2044
+ },
2045
+ pwsh: {
2046
+ patterns: [/^pwsh$/i, /^pwsh\.exe$/i],
2047
+ extractScript: (argv) => {
2048
+ const cIdx = argv.findIndex(
2049
+ (a) => a.toLowerCase() === "-command" || a.toLowerCase() === "-c"
2050
+ );
2051
+ return cIdx !== -1 && argv[cIdx + 1] ? argv[cIdx + 1] : void 0;
2052
+ }
2053
+ }
2054
+ };
2055
+ var COMPLEX_SCRIPT_PATTERNS = [
2056
+ /\bif\b.*\bthen\b/i,
2057
+ // if-then
2058
+ /\bfor\b.*\bdo\b/i,
2059
+ // for loops
2060
+ /\bwhile\b.*\bdo\b/i,
2061
+ // while loops
2062
+ /\bfunction\b/i,
2063
+ // function definitions
2064
+ /\$\(/,
2065
+ // command substitution
2066
+ /`[^`]+`/,
2067
+ // backtick substitution
2068
+ /\|\|/,
2069
+ // OR chain
2070
+ /\&\&.*\&\&/,
2071
+ // multiple AND chains
2072
+ /\beval\b/i,
2073
+ // eval
2074
+ /\bexec\b/i,
2075
+ // exec
2076
+ /\bsource\b/i,
2077
+ // source
2078
+ /\.\s+\//,
2079
+ // dot source
2080
+ /<<[<-]?\s*[A-Z]+/
2081
+ // heredocs
2082
+ ];
2083
+ function isComplexScript(script) {
2084
+ return COMPLEX_SCRIPT_PATTERNS.some((pattern) => pattern.test(script));
2085
+ }
2086
+ function splitSimpleScript(script) {
2087
+ if (isComplexScript(script)) {
2088
+ return null;
2089
+ }
2090
+ const commands = [];
2091
+ const semiParts = script.split(/\s*;\s*/);
2092
+ if (semiParts.length > 1) {
2093
+ for (const part of semiParts) {
2094
+ const trimmed = part.trim();
2095
+ if (trimmed) {
2096
+ commands.push(trimmed);
2097
+ }
2098
+ }
2099
+ return commands;
2100
+ }
2101
+ const andParts = script.split(/\s*&&\s*/);
2102
+ if (andParts.length > 1 && andParts.length <= 3) {
2103
+ for (const part of andParts) {
2104
+ const trimmed = part.trim();
2105
+ if (trimmed) {
2106
+ commands.push(trimmed);
2107
+ }
2108
+ }
2109
+ return commands;
2110
+ }
2111
+ return [script.trim()];
2112
+ }
2113
+ function detectShellWrapper(argv) {
2114
+ if (argv.length === 0) {
2115
+ return { isWrapper: false, isComplex: false };
2116
+ }
2117
+ const cmd = argv[0];
2118
+ for (const [wrapperType, config] of Object.entries(SHELL_WRAPPERS)) {
2119
+ for (const pattern of config.patterns) {
2120
+ if (pattern.test(cmd)) {
2121
+ const script = config.extractScript(argv);
2122
+ if (!script) {
2123
+ return {
2124
+ isWrapper: true,
2125
+ wrapperType,
2126
+ isComplex: true
2127
+ // Treat interactive shells as complex
2128
+ };
2129
+ }
2130
+ const isComplex = isComplexScript(script);
2131
+ const subCommands = splitSimpleScript(script);
2132
+ return {
2133
+ isWrapper: true,
2134
+ wrapperType,
2135
+ embeddedScript: script,
2136
+ subCommands: subCommands || void 0,
2137
+ isComplex
2138
+ };
2139
+ }
2140
+ }
2141
+ }
2142
+ return { isWrapper: false, isComplex: false };
2143
+ }
2144
+ function getMostRestrictiveTier(tiers) {
2145
+ return Math.max(...tiers, 0);
2146
+ }
2147
+ function isHighRiskWrapper(result) {
2148
+ if (result.isComplex) {
2149
+ return true;
2150
+ }
2151
+ if (result.embeddedScript?.includes("encodedcommand")) {
2152
+ return true;
2153
+ }
2154
+ return false;
2155
+ }
2156
+
2157
+ // src/security/permission-gate.ts
2158
+ var HARD_DENY_PATTERNS = [
2159
+ { pattern: /\.ssh\/.*(?:id_rsa|id_ed25519|authorized_keys)/i, msg: "SSH key access" },
2160
+ { pattern: /\/etc\/shadow/i, msg: "Shadow file access" },
2161
+ { pattern: /\/etc\/passwd/i, msg: "Passwd file access" },
2162
+ { pattern: /\brm\s+(-[rf]+\s+)*[\/\\]$/i, msg: "Root filesystem deletion" },
2163
+ { pattern: /\brm\s+(-[rf]+\s+)*~$/i, msg: "Home directory deletion" },
2164
+ { pattern: /\bcurl\s+[^\|]*\|\s*(bash|sh|zsh)/i, msg: "Piped script execution" },
2165
+ { pattern: /\bwget\s+[^\|]*\|\s*(bash|sh|zsh)/i, msg: "Piped script execution" },
2166
+ { pattern: /\bchmod\s+777\s+\//i, msg: "World-writable root" },
2167
+ { pattern: /\b(sudo|su|runas|doas)\b/i, msg: "Privilege escalation" },
2168
+ { pattern: /\bformat\s+[a-z]:/i, msg: "Drive format" },
2169
+ { pattern: /\bmkfs\b/i, msg: "Filesystem creation" },
2170
+ { pattern: /\bdd\s+.*of=\/dev/i, msg: "Raw device write" }
2171
+ ];
2172
+ var TIER_0_COMMANDS = /* @__PURE__ */ new Set([
2173
+ "ls",
2174
+ "dir",
2175
+ "cat",
2176
+ "type",
2177
+ "head",
2178
+ "tail",
2179
+ "less",
2180
+ "more",
2181
+ "pwd",
2182
+ "echo",
2183
+ "find",
2184
+ "grep",
2185
+ "findstr",
2186
+ "which",
2187
+ "where",
2188
+ "wc",
2189
+ "sort",
2190
+ "uniq",
2191
+ "diff",
2192
+ "cmp",
2193
+ "file",
2194
+ "stat"
2195
+ ]);
2196
+ var TIER_0_GIT = /* @__PURE__ */ new Set([
2197
+ "status",
2198
+ "diff",
2199
+ "log",
2200
+ "show",
2201
+ "branch",
2202
+ "remote",
2203
+ "tag",
2204
+ "stash",
2205
+ "describe",
2206
+ "rev-parse",
2207
+ "config"
2208
+ ]);
2209
+ var TIER_1_COMMANDS = /* @__PURE__ */ new Set([
2210
+ "touch",
2211
+ "mkdir",
2212
+ "cp",
2213
+ "mv",
2214
+ "rm",
2215
+ "rmdir",
2216
+ "ln"
2217
+ ]);
2218
+ var TIER_1_GIT = /* @__PURE__ */ new Set([
2219
+ "add",
2220
+ "commit",
2221
+ "checkout",
2222
+ "switch",
2223
+ "merge",
2224
+ "rebase",
2225
+ "reset",
2226
+ "stash",
2227
+ "clean",
2228
+ "restore"
2229
+ ]);
2230
+ var TIER_2_COMMANDS = /* @__PURE__ */ new Set([
2231
+ "npm",
2232
+ "yarn",
2233
+ "pnpm",
2234
+ "pip",
2235
+ "pip3",
2236
+ "poetry",
2237
+ "cargo",
2238
+ "node",
2239
+ "python",
2240
+ "python3",
2241
+ "ruby",
2242
+ "go",
2243
+ "java",
2244
+ "make",
2245
+ "cmake",
2246
+ "gcc",
2247
+ "g++",
2248
+ "clang",
2249
+ "docker",
2250
+ "docker-compose",
2251
+ "chmod",
2252
+ "chown"
2253
+ ]);
2254
+ function classifyRisk(argv, rawCommand) {
2255
+ if (argv.length === 0) return 3;
2256
+ const cmd = argv[0].toLowerCase();
2257
+ const wrapperResult = detectShellWrapper(argv);
2258
+ if (wrapperResult.isWrapper) {
2259
+ if (isHighRiskWrapper(wrapperResult)) {
2260
+ return 3;
2261
+ }
2262
+ if (wrapperResult.subCommands && wrapperResult.subCommands.length > 0) {
2263
+ const subTiers = wrapperResult.subCommands.map((sc) => {
2264
+ const parsed = parseArgv(sc);
2265
+ return classifyRisk(parsed.argv, sc);
2266
+ });
2267
+ return getMostRestrictiveTier(subTiers);
2268
+ }
2269
+ }
2270
+ for (const { pattern } of HARD_DENY_PATTERNS) {
2271
+ if (pattern.test(rawCommand)) {
2272
+ return 3;
2273
+ }
2274
+ }
2275
+ if (cmd === "git" && argv.length > 1) {
2276
+ const subCmd = argv[1].toLowerCase();
2277
+ if (TIER_0_GIT.has(subCmd)) return 0;
2278
+ if (TIER_1_GIT.has(subCmd)) return 1;
2279
+ if (subCmd === "push" || subCmd === "pull" || subCmd === "fetch" || subCmd === "clone") {
2280
+ return 2;
2281
+ }
2282
+ return 2;
2283
+ }
2284
+ if (TIER_0_COMMANDS.has(cmd)) return 0;
2285
+ if (TIER_1_COMMANDS.has(cmd)) return 1;
2286
+ if (TIER_2_COMMANDS.has(cmd)) return 2;
2287
+ return 2;
2288
+ }
2289
+ function isWithinWorkspace2(p, workspaceRoot, cwd) {
2290
+ const resolved = resolve2(cwd, p);
2291
+ const root = resolve2(workspaceRoot);
2292
+ const rel = relative2(root, resolved);
2293
+ return rel === "" || !rel.startsWith("..") && !isAbsolute2(rel);
2294
+ }
2295
+ function validatePaths(paths, workspaceRoot, cwd) {
2296
+ const violations = [];
2297
+ for (const p of paths) {
2298
+ if (p.operation === "write" && !isWithinWorkspace2(p.path, workspaceRoot, cwd)) {
2299
+ violations.push({
2300
+ message: `Write to path outside workspace: ${p.path}`,
2301
+ severity: "promptable"
2302
+ // User might intentionally allow
2303
+ });
2304
+ }
2305
+ }
2306
+ return violations;
2307
+ }
2308
+ function validateNetwork(domains, policy, allowedDomains) {
2309
+ if (policy === "on" || domains.length === 0) {
2310
+ return [];
2311
+ }
2312
+ const violations = [];
2313
+ if (policy === "off" && domains.length > 0) {
2314
+ violations.push({
2315
+ message: `Network access requested: ${domains.join(", ")}`,
2316
+ severity: "promptable"
2317
+ // User can approve network
2318
+ });
2319
+ return violations;
2320
+ }
2321
+ for (const domain of domains) {
2322
+ const isAllowed = allowedDomains.some(
2323
+ (allowed) => domain === allowed || domain.endsWith("." + allowed)
2324
+ );
2325
+ if (!isAllowed) {
2326
+ violations.push({
2327
+ message: `Domain not in allowlist: ${domain}`,
2328
+ severity: "promptable"
2329
+ });
2330
+ }
2331
+ }
2332
+ return violations;
2333
+ }
2334
+ function checkHardViolations(command) {
2335
+ const violations = [];
2336
+ for (const { pattern, msg } of HARD_DENY_PATTERNS) {
2337
+ if (pattern.test(command)) {
2338
+ violations.push({
2339
+ message: msg,
2340
+ severity: "hard"
2341
+ });
2342
+ }
2343
+ }
2344
+ return violations;
2345
+ }
2346
+ function evaluateCommand(command, cwd) {
2347
+ const env = getEnvironment();
2348
+ const effectiveCwd = cwd || env.cwd;
2349
+ const parsed = parseArgv(command);
2350
+ const paths = extractPaths(parsed);
2351
+ const domains = extractDomains(command);
2352
+ const ports = extractPorts(command);
2353
+ const wrapperResult = detectShellWrapper(parsed.argv);
2354
+ const riskTier = classifyRisk(parsed.argv, command);
2355
+ const violations = [];
2356
+ violations.push(...checkHardViolations(command));
2357
+ if (env.trustLevel === "untrusted" && riskTier > 0) {
2358
+ return {
2359
+ decision: "prompt",
2360
+ riskTier,
2361
+ command,
2362
+ cwd: effectiveCwd,
2363
+ parsedArgv: parsed.argv,
2364
+ paths,
2365
+ ports,
2366
+ domains,
2367
+ reason: "Workspace is untrusted. Trust this folder to allow file writes and command execution.",
2368
+ violations: [{ message: "Workspace not trusted", severity: "promptable" }],
2369
+ isShellWrapper: wrapperResult.isWrapper,
2370
+ subCommands: wrapperResult.subCommands
2371
+ };
2372
+ }
2373
+ violations.push(...validatePaths(paths, env.workspaceRoot, effectiveCwd));
2374
+ violations.push(...validateNetwork(domains, env.networkPolicy, env.allowedDomains));
2375
+ const rules = getAllRules();
2376
+ const ruleResult = evaluateRules(parsed.argv, rules);
2377
+ const hasHardViolation = violations.some((v) => v.severity === "hard");
2378
+ const hasPromptableViolation = violations.some((v) => v.severity === "promptable");
2379
+ let decision;
2380
+ let reason;
2381
+ if (hasHardViolation) {
2382
+ decision = "deny";
2383
+ reason = violations.find((v) => v.severity === "hard")?.message || "Security violation";
2384
+ } else if (ruleResult.decision === "forbid") {
2385
+ decision = "deny";
2386
+ reason = ruleResult.mostRestrictive?.description || "Forbidden by rule";
2387
+ } else if (hasPromptableViolation) {
2388
+ decision = "prompt";
2389
+ reason = violations.find((v) => v.severity === "promptable")?.message || "Approval required";
2390
+ } else if (ruleResult.decision === "allow" && riskTier <= 1) {
2391
+ decision = "allow";
2392
+ reason = ruleResult.mostRestrictive?.description || "Allowed by rule";
2393
+ } else {
2394
+ decision = "prompt";
2395
+ reason = getRiskDescription(riskTier, parsed.argv);
2396
+ }
2397
+ return {
2398
+ decision,
2399
+ riskTier,
2400
+ command,
2401
+ cwd: effectiveCwd,
2402
+ parsedArgv: parsed.argv,
2403
+ paths,
2404
+ ports,
2405
+ domains,
2406
+ reason,
2407
+ violations,
2408
+ matchedRuleId: ruleResult.mostRestrictive?.id,
2409
+ isShellWrapper: wrapperResult.isWrapper,
2410
+ subCommands: wrapperResult.subCommands
2411
+ };
2412
+ }
2413
+ function getRiskDescription(tier, argv) {
2414
+ const cmd = argv[0] || "unknown";
2415
+ switch (tier) {
2416
+ case 0:
2417
+ return `Read-only: ${cmd}`;
2418
+ case 1:
2419
+ return `Workspace modification: ${cmd}`;
2420
+ case 2:
2421
+ return `System/package operation: ${cmd}`;
2422
+ case 3:
2423
+ return `Potentially dangerous: ${cmd}`;
2424
+ }
2425
+ }
2426
+
2427
+ // src/security/workspace-trust.ts
2428
+ import { select as select3 } from "@inquirer/prompts";
2429
+ import chalk9 from "chalk";
2430
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
2431
+ import { join as join3, resolve as resolve3 } from "path";
2432
+ import { homedir as homedir4 } from "os";
2433
+ var CONFIG_DIR2 = join3(homedir4(), ".agdi");
2434
+ var TRUST_FILE = join3(CONFIG_DIR2, "trusted-workspaces.json");
2435
+ function loadTrustConfig() {
2436
+ try {
2437
+ if (existsSync5(TRUST_FILE)) {
2438
+ const data = readFileSync4(TRUST_FILE, "utf-8");
2439
+ return JSON.parse(data);
2440
+ }
2441
+ } catch {
2442
+ }
2443
+ return { trustedWorkspaces: [] };
2444
+ }
2445
+ function saveTrustConfig(config) {
2446
+ try {
2447
+ if (!existsSync5(CONFIG_DIR2)) {
2448
+ mkdirSync3(CONFIG_DIR2, { recursive: true });
2449
+ }
2450
+ writeFileSync3(TRUST_FILE, JSON.stringify(config, null, 2));
2451
+ } catch (error) {
2452
+ console.error("Failed to save trust config:", error);
2453
+ }
2454
+ }
2455
+ function normalizePath(path4) {
2456
+ return resolve3(path4).toLowerCase().replace(/\\/g, "/");
2457
+ }
2458
+ function trustWorkspace(workspacePath) {
2459
+ const config = loadTrustConfig();
2460
+ const normalized = normalizePath(workspacePath);
2461
+ if (config.trustedWorkspaces.some((w) => w.normalizedPath === normalized)) {
2462
+ return;
2463
+ }
2464
+ config.trustedWorkspaces.push({
2465
+ path: workspacePath,
2466
+ normalizedPath: normalized,
2467
+ trustedAt: (/* @__PURE__ */ new Date()).toISOString()
2468
+ });
2469
+ saveTrustConfig(config);
2470
+ }
2471
+
2472
+ // src/actions/plan-executor.ts
2473
+ function displayPlanSummary(plan) {
2474
+ const summary = summarizePlan(plan);
2475
+ const boxWidth = 56;
2476
+ console.log("");
2477
+ console.log(chalk10.cyan("\u256D\u2500 Action Plan \u2500" + "\u2500".repeat(boxWidth - 15) + "\u256E"));
2478
+ console.log(chalk10.cyan("\u2502 ") + chalk10.white(`Project: ${plan.projectName}`.padEnd(boxWidth - 2)) + chalk10.cyan(" \u2502"));
2479
+ console.log(chalk10.cyan("\u2502 ") + "\u2500".repeat(boxWidth - 2) + chalk10.cyan(" \u2502"));
2480
+ const lines = [];
2481
+ if (summary.dirsCreated > 0) {
2482
+ lines.push(`\u{1F4C1} Create ${summary.dirsCreated} directories`);
2483
+ }
2484
+ if (summary.filesCreated > 0) {
2485
+ lines.push(`\u{1F4C4} Create ${summary.filesCreated} files`);
2486
+ }
2487
+ if (summary.filesDeleted > 0) {
2488
+ lines.push(`\u{1F5D1}\uFE0F Delete ${summary.filesDeleted} files`);
2489
+ }
2490
+ if (summary.commandsToRun > 0) {
2491
+ lines.push(`\u26A1 Run ${summary.commandsToRun} commands`);
2492
+ }
2493
+ if (summary.domains.length > 0) {
2494
+ lines.push(`\u{1F310} Network: ${summary.domains.join(", ")}`);
2495
+ }
2496
+ if (summary.ports.length > 0) {
2497
+ lines.push(`\u{1F50C} Ports: ${summary.ports.join(", ")}`);
2498
+ }
2499
+ for (const line of lines) {
2500
+ console.log(chalk10.cyan("\u2502 ") + chalk10.gray(line.padEnd(boxWidth - 2)) + chalk10.cyan(" \u2502"));
2501
+ }
2502
+ console.log(chalk10.cyan("\u2570" + "\u2500".repeat(boxWidth) + "\u256F"));
2503
+ console.log("");
2504
+ }
2505
+ function displayActionProgress(action, index, total) {
2506
+ const num = `[${index + 1}/${total}]`;
2507
+ switch (action.type) {
2508
+ case "mkdir":
2509
+ console.log(chalk10.gray(`${num} Creating directory: ${action.path}`));
2510
+ break;
2511
+ case "writeFile":
2512
+ console.log(chalk10.gray(`${num} Writing file: ${action.path}`));
2513
+ break;
2514
+ case "deleteFile":
2515
+ console.log(chalk10.gray(`${num} Deleting file: ${action.path}`));
2516
+ break;
2517
+ case "exec":
2518
+ console.log(chalk10.blue(`${num} Running: ${action.argv.join(" ")}`));
2519
+ break;
2520
+ }
2521
+ }
2522
+ async function dryRunActions(plan) {
2523
+ const gateResults = [];
2524
+ let requiresTrust = false;
2525
+ let hasHardDeny = false;
2526
+ for (const action of plan.actions) {
2527
+ if (action.type === "exec") {
2528
+ const command = action.argv.join(" ");
2529
+ const result = evaluateCommand(command, action.cwd);
2530
+ gateResults.push(result);
2531
+ if (result.decision === "deny") {
2532
+ const isHard = result.violations.some((v) => v.severity === "hard");
2533
+ if (isHard) {
2534
+ hasHardDeny = true;
2535
+ }
2536
+ }
2537
+ if (result.violations.some((v) => v.message === "Workspace not trusted")) {
2538
+ requiresTrust = true;
2539
+ }
2540
+ } else {
2541
+ const env = getEnvironment();
2542
+ if (env.trustLevel === "untrusted") {
2543
+ requiresTrust = true;
2544
+ }
2545
+ }
2546
+ }
2547
+ return {
2548
+ canProceed: !hasHardDeny,
2549
+ requiresTrust,
2550
+ gateResults
2551
+ };
2552
+ }
2553
+ async function executeAction(action) {
2554
+ const env = getEnvironment();
2555
+ switch (action.type) {
2556
+ case "mkdir": {
2557
+ const result = await mkdirTool(action.path);
2558
+ return {
2559
+ action,
2560
+ success: result.success,
2561
+ error: result.error
2562
+ };
2563
+ }
2564
+ case "writeFile": {
2565
+ const result = await writeFileTool(action.path, action.content);
2566
+ return {
2567
+ action,
2568
+ success: result.success,
2569
+ error: result.error
2570
+ };
2571
+ }
2572
+ case "deleteFile": {
2573
+ const result = await deleteFileTool(action.path);
2574
+ return {
2575
+ action,
2576
+ success: result.success,
2577
+ error: result.error
2578
+ };
2579
+ }
2580
+ case "exec": {
2581
+ const cwd = action.cwd ? resolve4(env.workspaceRoot, action.cwd) : env.workspaceRoot;
2582
+ const command = action.argv.join(" ");
2583
+ return new Promise((resolvePromise) => {
2584
+ let output = "";
2585
+ let error = "";
2586
+ const child = spawn2(action.argv[0], action.argv.slice(1), {
2587
+ cwd,
2588
+ shell: true,
2589
+ stdio: "pipe"
2590
+ });
2591
+ child.stdout?.on("data", (data) => {
2592
+ const text = data.toString();
2593
+ output += text;
2594
+ process.stdout.write(text);
2595
+ });
2596
+ child.stderr?.on("data", (data) => {
2597
+ const text = data.toString();
2598
+ error += text;
2599
+ process.stderr.write(text);
2600
+ });
2601
+ child.on("close", (code) => {
2602
+ resolvePromise({
2603
+ action,
2604
+ success: code === 0,
2605
+ error: code !== 0 ? error || `Exit code: ${code}` : void 0,
2606
+ output
2607
+ });
2608
+ });
2609
+ child.on("error", (err) => {
2610
+ resolvePromise({
2611
+ action,
2612
+ success: false,
2613
+ error: err.message
2614
+ });
2615
+ });
2616
+ });
2617
+ }
2618
+ }
2619
+ }
2620
+ async function executePlan(plan) {
2621
+ const env = getEnvironment();
2622
+ const results = [];
2623
+ const filesCreated = [];
2624
+ const commandsRun = [];
2625
+ const errors = [];
2626
+ displayPlanSummary(plan);
2627
+ const dryRun = await dryRunActions(plan);
2628
+ if (!dryRun.canProceed) {
2629
+ console.log(chalk10.red("\n\u26D4 Plan contains actions that cannot be approved:"));
2630
+ for (const result of dryRun.gateResults) {
2631
+ if (result.decision === "deny") {
2632
+ console.log(chalk10.red(` - ${result.command}: ${result.reason}`));
2633
+ errors.push(result.reason);
2634
+ }
2635
+ }
2636
+ return { success: false, results, filesCreated, commandsRun, errors };
2637
+ }
2638
+ if (dryRun.requiresTrust) {
2639
+ console.log(chalk10.yellow("\u26A0\uFE0F This workspace is not trusted."));
2640
+ console.log(chalk10.gray(" The plan requires file writes and command execution.\n"));
2641
+ const trustChoice = await select4({
2642
+ message: "Trust this workspace?",
2643
+ choices: [
2644
+ { name: "Trust for this session", value: "session" },
2645
+ { name: "Trust and remember", value: "persistent" },
2646
+ { name: "Cancel", value: "cancel" }
2647
+ ]
2648
+ });
2649
+ if (trustChoice === "cancel") {
2650
+ console.log(chalk10.yellow("\n\u{1F44B} Plan cancelled.\n"));
2651
+ return { success: false, results, filesCreated, commandsRun, errors: ["User cancelled"] };
2652
+ }
2653
+ if (trustChoice === "persistent") {
2654
+ trustWorkspace(env.workspaceRoot);
2655
+ updateEnvironment({ trustLevel: "persistent" });
2656
+ console.log(chalk10.green("\u2713 Workspace trusted and remembered\n"));
2657
+ } else {
2658
+ updateEnvironment({ trustLevel: "session" });
2659
+ console.log(chalk10.green("\u2713 Trusted for this session\n"));
2660
+ }
2661
+ }
2662
+ const approved = await confirm({
2663
+ message: `Execute ${plan.actions.length} actions?`,
2664
+ default: true
2665
+ });
2666
+ if (!approved) {
2667
+ console.log(chalk10.yellow("\n\u{1F44B} Plan cancelled.\n"));
2668
+ return { success: false, results, filesCreated, commandsRun, errors: ["User cancelled"] };
2669
+ }
2670
+ logEvent({
2671
+ eventType: "command_start",
2672
+ command: `executePlan: ${plan.projectName}`,
2673
+ metadata: {
2674
+ actionsCount: plan.actions.length,
2675
+ summary: summarizePlan(plan)
2676
+ }
2677
+ });
2678
+ console.log(chalk10.cyan("\n\u25B6 Executing plan...\n"));
2679
+ for (let i = 0; i < plan.actions.length; i++) {
2680
+ const action = plan.actions[i];
2681
+ displayActionProgress(action, i, plan.actions.length);
2682
+ const result = await executeAction(action);
2683
+ results.push(result);
2684
+ if (result.success) {
2685
+ if (action.type === "writeFile" || action.type === "mkdir") {
2686
+ filesCreated.push(action.path);
2687
+ }
2688
+ if (action.type === "exec") {
2689
+ commandsRun.push(action.argv.join(" "));
2690
+ }
2691
+ } else {
2692
+ errors.push(result.error || "Unknown error");
2693
+ console.log(chalk10.red(` \u2717 Failed: ${result.error}`));
2694
+ }
2695
+ }
2696
+ const success = errors.length === 0;
2697
+ if (success) {
2698
+ console.log(chalk10.green(`
2699
+ \u2713 Plan executed successfully!`));
2700
+ console.log(chalk10.gray(` Created ${filesCreated.length} files`));
2701
+ console.log(chalk10.gray(` Ran ${commandsRun.length} commands
2702
+ `));
2703
+ } else {
2704
+ console.log(chalk10.red(`
2705
+ \u2717 Plan completed with ${errors.length} errors
2706
+ `));
2707
+ }
2708
+ if (plan.nextSteps) {
2709
+ console.log(chalk10.cyan("Next steps:"));
2710
+ console.log(chalk10.gray(` ${plan.nextSteps}
2711
+ `));
2712
+ }
2713
+ logEvent({
2714
+ eventType: "command_result",
2715
+ command: `executePlan: ${plan.projectName}`,
2716
+ result: {
2717
+ exitCode: success ? 0 : 1
2718
+ },
2719
+ metadata: {
2720
+ filesCreated,
2721
+ commandsRun,
2722
+ errors
2723
+ }
2724
+ });
2725
+ return { success, results, filesCreated, commandsRun, errors };
2726
+ }
2727
+ async function parseAndExecutePlan(response) {
2728
+ const plan = parseActionPlan(response);
2729
+ if (!plan) {
2730
+ console.log(chalk10.yellow("\n\u26A0\uFE0F Could not parse action plan from response.\n"));
2731
+ return null;
2732
+ }
2733
+ return executePlan(plan);
2734
+ }
2735
+
2736
+ // src/commands/codex.ts
2737
+ var CHAT_SYSTEM_PROMPT = `You are Agdi dev, an elite AI coding assistant. You help developers write code, debug issues, and build applications.
1278
2738
 
1279
2739
  ## Your Capabilities
1280
2740
  - Write complete, production-ready code
1281
2741
  - Debug and fix code issues
1282
2742
  - Explain code and architecture
1283
- - Generate full applications from descriptions
1284
- - Run shell commands (when requested)
2743
+ - Answer coding questions
1285
2744
 
1286
2745
  ## Response Style
1287
2746
  - Be concise and direct
@@ -1293,31 +2752,66 @@ var SYSTEM_PROMPT3 = `You are Agdi dev, an elite AI coding assistant. You help d
1293
2752
  - Use TypeScript by default
1294
2753
  - Follow modern best practices
1295
2754
  - Include proper error handling
1296
- - Write self-documenting code
2755
+ - Write self-documenting code`;
2756
+ var BUILD_SYSTEM_PROMPT = `You are Agdi dev, an AI coding assistant that generates applications.
2757
+
2758
+ ## CRITICAL: Output Format
2759
+ When the user asks to build an app, do NOT print file contents in markdown blocks.
2760
+
2761
+ Instead, output a single JSON object with this exact structure:
2762
+ {
2763
+ "projectName": "my-app",
2764
+ "actions": [
2765
+ { "type": "mkdir", "path": "src" },
2766
+ { "type": "writeFile", "path": "package.json", "content": "{...}" },
2767
+ { "type": "writeFile", "path": "src/index.ts", "content": "..." },
2768
+ { "type": "exec", "argv": ["pnpm", "install"], "cwd": "." },
2769
+ { "type": "exec", "argv": ["pnpm", "dev"], "cwd": "." }
2770
+ ],
2771
+ "nextSteps": "Open http://localhost:3000 to see your app"
2772
+ }
1297
2773
 
1298
- When generating applications, create complete file structures with all necessary code.`;
2774
+ ## Action Types
2775
+ - mkdir: { type: "mkdir", path: "relative/path" }
2776
+ - writeFile: { type: "writeFile", path: "relative/path/file.ts", content: "file contents" }
2777
+ - deleteFile: { type: "deleteFile", path: "relative/path/file.ts" }
2778
+ - exec: { type: "exec", argv: ["command", "arg1", "arg2"], cwd: "." }
2779
+
2780
+ ## Rules
2781
+ 1. All paths MUST be relative to workspace root (no leading /)
2782
+ 2. Use pnpm as package manager
2783
+ 3. Include exec steps for install/dev only if user requests running the app
2784
+ 4. Keep actions minimal (no redundant writes)
2785
+ 5. Create complete, production-ready code
2786
+ 6. Use TypeScript by default
2787
+ 7. Include proper error handling
2788
+ 8. Output ONLY the JSON object, no extra text`;
1299
2789
  async function startCodingMode() {
1300
2790
  const activeConfig = getActiveProvider();
1301
2791
  if (!activeConfig) {
1302
- console.log(chalk8.red("\u274C No API key configured. Run: agdi"));
2792
+ console.log(chalk11.red("\u274C No API key configured. Run: agdi"));
1303
2793
  return;
1304
2794
  }
1305
2795
  const { provider, apiKey, model } = activeConfig;
1306
2796
  const config = loadConfig();
1307
- console.log(chalk8.cyan.bold("\n\u26A1 Agdi dev\n"));
1308
- console.log(chalk8.gray(`Model: ${chalk8.cyan(model)}`));
1309
- console.log(chalk8.gray("Commands: /model, /chat, /build, /help, /exit\n"));
1310
- console.log(chalk8.gray("\u2500".repeat(50) + "\n"));
2797
+ const env = initSession();
2798
+ displaySessionHeader(env);
2799
+ logSessionStart(env.workspaceRoot, env.trustLevel);
2800
+ console.log(chalk11.cyan.bold("\u26A1 Agdi dev\n"));
2801
+ console.log(chalk11.gray(`Model: ${chalk11.cyan(model)}`));
2802
+ console.log(chalk11.gray("Commands: /model, /chat, /build, /help, /exit\n"));
2803
+ console.log(chalk11.gray("\u2500".repeat(50) + "\n"));
1311
2804
  const pm = new ProjectManager();
1312
2805
  let llm = createLLMProvider(provider, { apiKey, model });
1313
2806
  while (true) {
1314
2807
  try {
1315
2808
  const userInput = await input4({
1316
- message: chalk8.green("\u2192")
2809
+ message: chalk11.green("\u2192")
1317
2810
  });
1318
2811
  const trimmed = userInput.trim().toLowerCase();
1319
2812
  if (trimmed === "/exit" || trimmed === "exit" || trimmed === "quit") {
1320
- console.log(chalk8.gray("\n\u{1F44B} Goodbye!\n"));
2813
+ logSessionEnd();
2814
+ console.log(chalk11.gray("\n\u{1F44B} Goodbye!\n"));
1321
2815
  break;
1322
2816
  }
1323
2817
  if (trimmed === "/help") {
@@ -1332,22 +2826,22 @@ async function startCodingMode() {
1332
2826
  apiKey: newConfig.apiKey,
1333
2827
  model: newConfig.model
1334
2828
  });
1335
- console.log(chalk8.gray(`Now using: ${chalk8.cyan(newConfig.model)}
2829
+ console.log(chalk11.gray(`Now using: ${chalk11.cyan(newConfig.model)}
1336
2830
  `));
1337
2831
  }
1338
2832
  continue;
1339
2833
  }
1340
2834
  if (trimmed === "/chat") {
1341
- console.log(chalk8.gray("\nSwitching to chat mode. Type /code to return.\n"));
2835
+ console.log(chalk11.gray("\nSwitching to chat mode. Type /code to return.\n"));
1342
2836
  await chatMode(llm);
1343
2837
  continue;
1344
2838
  }
1345
2839
  if (trimmed.startsWith("/build ") || trimmed.startsWith("build ")) {
1346
2840
  const prompt = userInput.replace(/^\/?build\s+/i, "").trim();
1347
2841
  if (prompt) {
1348
- await buildApp(prompt, llm, pm);
2842
+ await buildAppWithPlan(prompt, llm);
1349
2843
  } else {
1350
- console.log(chalk8.yellow("\nUsage: /build <description>\n"));
2844
+ console.log(chalk11.yellow("\nUsage: /build <description>\n"));
1351
2845
  }
1352
2846
  continue;
1353
2847
  }
@@ -1356,12 +2850,12 @@ async function startCodingMode() {
1356
2850
  }
1357
2851
  const isGenerationRequest = /^(create|build|make|generate|design|implement)\s+(me\s+)?(a\s+|an\s+)?/i.test(userInput.trim());
1358
2852
  if (isGenerationRequest) {
1359
- await buildApp(userInput, llm, pm);
2853
+ await buildAppWithPlan(userInput, llm);
1360
2854
  continue;
1361
2855
  }
1362
2856
  const spinner = ora3("Thinking...").start();
1363
2857
  try {
1364
- const response = await llm.generate(userInput, SYSTEM_PROMPT3);
2858
+ const response = await llm.generate(userInput, CHAT_SYSTEM_PROMPT);
1365
2859
  spinner.stop();
1366
2860
  console.log("\n" + formatResponse(response.text) + "\n");
1367
2861
  } catch (error) {
@@ -1370,43 +2864,23 @@ async function startCodingMode() {
1370
2864
  }
1371
2865
  } catch (error) {
1372
2866
  if (error.name === "ExitPromptError") {
1373
- console.log(chalk8.gray("\n\n\u{1F44B} Goodbye!\n"));
2867
+ logSessionEnd();
2868
+ console.log(chalk11.gray("\n\n\u{1F44B} Goodbye!\n"));
1374
2869
  process.exit(0);
1375
2870
  }
1376
2871
  throw error;
1377
2872
  }
1378
2873
  }
1379
2874
  }
1380
- async function buildApp(prompt, llm, pm) {
1381
- const spinner = ora3("Generating application...").start();
2875
+ async function buildAppWithPlan(prompt, llm) {
2876
+ const spinner = ora3("Generating action plan...").start();
1382
2877
  try {
1383
- pm.create("generated-app", prompt);
1384
- const { plan, files } = await generateApp(prompt, llm, (step, file) => {
1385
- spinner.text = file ? `${step} ${chalk8.gray(file)}` : step;
1386
- });
1387
- pm.updateFiles(files);
1388
- spinner.succeed(chalk8.green("Application generated!"));
1389
- console.log(chalk8.gray("\n\u{1F4C1} Files:"));
1390
- for (const file of files.slice(0, 10)) {
1391
- console.log(chalk8.gray(` ${file.path}`));
1392
- }
1393
- if (files.length > 10) {
1394
- console.log(chalk8.gray(` ... and ${files.length - 10} more`));
1395
- }
1396
- const shouldWrite = await input4({
1397
- message: "Write to disk? (y/n):",
1398
- default: "y"
1399
- });
1400
- if (shouldWrite.toLowerCase() === "y") {
1401
- const dir = await input4({
1402
- message: "Output directory:",
1403
- default: "./generated-app"
1404
- });
1405
- await writeProject(pm.get(), dir);
1406
- console.log(chalk8.green(`
1407
- \u2705 Written to ${dir}
1408
- `));
1409
- console.log(chalk8.gray("Next: cd " + dir + " && npm install && npm run dev\n"));
2878
+ const response = await llm.generate(prompt, BUILD_SYSTEM_PROMPT);
2879
+ spinner.stop();
2880
+ const result = await parseAndExecutePlan(response.text);
2881
+ if (!result) {
2882
+ console.log(chalk11.yellow("\n\u26A0\uFE0F Model did not return an action plan. Showing response:\n"));
2883
+ console.log(formatResponse(response.text) + "\n");
1410
2884
  }
1411
2885
  } catch (error) {
1412
2886
  spinner.fail("Generation failed");
@@ -1417,10 +2891,10 @@ async function chatMode(llm) {
1417
2891
  while (true) {
1418
2892
  try {
1419
2893
  const userInput = await input4({
1420
- message: chalk8.blue("\u{1F4AC}")
2894
+ message: chalk11.blue("\u{1F4AC}")
1421
2895
  });
1422
2896
  if (userInput.toLowerCase() === "/code" || userInput.toLowerCase() === "/exit") {
1423
- console.log(chalk8.gray("\nBack to Agdi dev mode.\n"));
2897
+ console.log(chalk11.gray("\nBack to Agdi dev mode.\n"));
1424
2898
  return;
1425
2899
  }
1426
2900
  if (!userInput.trim()) continue;
@@ -1438,48 +2912,49 @@ async function chatMode(llm) {
1438
2912
  }
1439
2913
  function formatResponse(text) {
1440
2914
  return text.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => {
1441
- const header = lang ? chalk8.gray(`\u2500\u2500 ${lang} \u2500\u2500`) : chalk8.gray("\u2500\u2500 code \u2500\u2500");
2915
+ const header = lang ? chalk11.gray(`\u2500\u2500 ${lang} \u2500\u2500`) : chalk11.gray("\u2500\u2500 code \u2500\u2500");
1442
2916
  return `
1443
2917
  ${header}
1444
- ${chalk8.white(code.trim())}
1445
- ${chalk8.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
2918
+ ${chalk11.white(code.trim())}
2919
+ ${chalk11.gray("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}
1446
2920
  `;
1447
2921
  });
1448
2922
  }
1449
2923
  function showHelp() {
1450
- console.log(chalk8.cyan.bold("\n\u{1F4D6} Commands\n"));
1451
- console.log(chalk8.gray(" /model ") + "Change AI model");
1452
- console.log(chalk8.gray(" /build ") + "Generate a full application");
1453
- console.log(chalk8.gray(" /chat ") + "Switch to chat mode");
1454
- console.log(chalk8.gray(" /help ") + "Show this help");
1455
- console.log(chalk8.gray(" /exit ") + "Exit Agdi");
1456
- console.log(chalk8.gray("\n Or just type your coding question!\n"));
2924
+ console.log(chalk11.cyan.bold("\n\u{1F4D6} Commands\n"));
2925
+ console.log(chalk11.gray(" /build ") + "Generate and execute an application");
2926
+ console.log(chalk11.gray(" /model ") + "Change AI model");
2927
+ console.log(chalk11.gray(" /chat ") + "Switch to chat mode");
2928
+ console.log(chalk11.gray(" /help ") + "Show this help");
2929
+ console.log(chalk11.gray(" /exit ") + "Exit Agdi");
2930
+ console.log(chalk11.gray("\n Or just type your coding question!\n"));
2931
+ console.log(chalk11.gray('Tip: "Create a todo app" will generate & write files.\n'));
1457
2932
  }
1458
2933
  function handleError(error) {
1459
2934
  const msg = error instanceof Error ? error.message : String(error);
1460
2935
  if (msg.includes("429") || msg.includes("quota")) {
1461
- console.log(chalk8.yellow("\n\u26A0\uFE0F Quota exceeded. Run /model to switch.\n"));
2936
+ console.log(chalk11.yellow("\n\u26A0\uFE0F Quota exceeded. Run /model to switch.\n"));
1462
2937
  } else if (msg.includes("401") || msg.includes("403")) {
1463
- console.log(chalk8.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
2938
+ console.log(chalk11.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
1464
2939
  } else {
1465
- console.log(chalk8.red("\n" + msg + "\n"));
2940
+ console.log(chalk11.red("\n" + msg + "\n"));
1466
2941
  }
1467
2942
  }
1468
2943
 
1469
2944
  // src/index.ts
1470
2945
  var BANNER = `
1471
- ${chalk9.cyan(` ___ __ _ `)}
1472
- ${chalk9.cyan(` / | ____ _____/ /(_) `)}
1473
- ${chalk9.cyan(` / /| | / __ \`/ __ // / `)}
1474
- ${chalk9.cyan(` / ___ |/ /_/ / /_/ // / `)}
1475
- ${chalk9.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
1476
- ${chalk9.cyan(` /____/ `)}
2946
+ ${chalk12.cyan(` ___ __ _ `)}
2947
+ ${chalk12.cyan(` / | ____ _____/ /(_) `)}
2948
+ ${chalk12.cyan(` / /| | / __ \`/ __ // / `)}
2949
+ ${chalk12.cyan(` / ___ |/ /_/ / /_/ // / `)}
2950
+ ${chalk12.cyan(`/_/ |_|\\_, /\\__,_//_/ `)}
2951
+ ${chalk12.cyan(` /____/ `)}
1477
2952
  `;
1478
2953
  var program = new Command();
1479
- program.name("agdi").description(chalk9.cyan("\u{1F680} AI-powered coding assistant")).version("2.1.0").configureHelp({
2954
+ program.name("agdi").description(chalk12.cyan("\u{1F680} AI-powered coding assistant")).version("2.1.0").configureHelp({
1480
2955
  // Show banner only when help is requested
1481
2956
  formatHelp: (cmd, helper) => {
1482
- return BANNER + "\n" + chalk9.gray(" The Open Source AI Architect") + "\n" + chalk9.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n") + "\n" + helper.formatHelp(cmd, helper);
2957
+ return BANNER + "\n" + chalk12.gray(" The Open Source AI Architect") + "\n" + chalk12.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n") + "\n" + helper.formatHelp(cmd, helper);
1483
2958
  }
1484
2959
  });
1485
2960
  program.action(async () => {
@@ -1490,7 +2965,7 @@ program.action(async () => {
1490
2965
  await startCodingMode();
1491
2966
  } catch (error) {
1492
2967
  if (error.name === "ExitPromptError") {
1493
- console.log(chalk9.gray("\n\n\u{1F44B} Goodbye!\n"));
2968
+ console.log(chalk12.gray("\n\n\u{1F44B} Goodbye!\n"));
1494
2969
  process.exit(0);
1495
2970
  }
1496
2971
  throw error;
@@ -1505,7 +2980,7 @@ program.command("auth").description("Configure API keys").option("--status", "Sh
1505
2980
  }
1506
2981
  } catch (error) {
1507
2982
  if (error.name === "ExitPromptError") {
1508
- console.log(chalk9.gray("\n\n\u{1F44B} Cancelled.\n"));
2983
+ console.log(chalk12.gray("\n\n\u{1F44B} Cancelled.\n"));
1509
2984
  process.exit(0);
1510
2985
  }
1511
2986
  throw error;
@@ -1516,7 +2991,7 @@ program.command("model").alias("models").description("Change AI model").action(a
1516
2991
  await selectModel();
1517
2992
  } catch (error) {
1518
2993
  if (error.name === "ExitPromptError") {
1519
- console.log(chalk9.gray("\n\n\u{1F44B} Cancelled.\n"));
2994
+ console.log(chalk12.gray("\n\n\u{1F44B} Cancelled.\n"));
1520
2995
  process.exit(0);
1521
2996
  }
1522
2997
  throw error;
@@ -1530,7 +3005,7 @@ program.command("chat").description("Start a chat session").action(async () => {
1530
3005
  await startChat();
1531
3006
  } catch (error) {
1532
3007
  if (error.name === "ExitPromptError") {
1533
- console.log(chalk9.gray("\n\n\u{1F44B} Goodbye!\n"));
3008
+ console.log(chalk12.gray("\n\n\u{1F44B} Goodbye!\n"));
1534
3009
  process.exit(0);
1535
3010
  }
1536
3011
  throw error;
@@ -1541,7 +3016,7 @@ program.command("run [directory]").description("Run a generated project").action
1541
3016
  await runProject(directory);
1542
3017
  } catch (error) {
1543
3018
  if (error.name === "ExitPromptError") {
1544
- console.log(chalk9.gray("\n\n\u{1F44B} Cancelled.\n"));
3019
+ console.log(chalk12.gray("\n\n\u{1F44B} Cancelled.\n"));
1545
3020
  process.exit(0);
1546
3021
  }
1547
3022
  throw error;
@@ -1554,7 +3029,7 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
1554
3029
  }
1555
3030
  const activeConfig = getActiveProvider();
1556
3031
  if (!activeConfig) {
1557
- console.log(chalk9.red("\u274C No API key configured. Run: agdi auth"));
3032
+ console.log(chalk12.red("\u274C No API key configured. Run: agdi auth"));
1558
3033
  return;
1559
3034
  }
1560
3035
  const spinner = ora4("Generating application...").start();
@@ -1566,30 +3041,30 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
1566
3041
  const pm = new ProjectManager();
1567
3042
  pm.create(options.output.replace("./", ""), prompt);
1568
3043
  const { plan, files } = await generateApp(prompt, llm, (step, file) => {
1569
- spinner.text = file ? `${step} ${chalk9.gray(file)}` : step;
3044
+ spinner.text = file ? `${step} ${chalk12.gray(file)}` : step;
1570
3045
  });
1571
3046
  pm.updateFiles(files);
1572
3047
  pm.updateDependencies(plan.dependencies);
1573
3048
  await writeProject(pm.get(), options.output);
1574
- spinner.succeed(chalk9.green("App generated!"));
1575
- console.log(chalk9.gray(`
1576
- \u{1F4C1} Created ${files.length} files in ${chalk9.cyan(options.output)}`));
1577
- console.log(chalk9.gray("\nNext: cd " + options.output + " && npm install && npm run dev\n"));
3049
+ spinner.succeed(chalk12.green("App generated!"));
3050
+ console.log(chalk12.gray(`
3051
+ \u{1F4C1} Created ${files.length} files in ${chalk12.cyan(options.output)}`));
3052
+ console.log(chalk12.gray("\nNext: cd " + options.output + " && npm install && npm run dev\n"));
1578
3053
  } catch (error) {
1579
3054
  spinner.fail("Generation failed");
1580
3055
  const msg = error instanceof Error ? error.message : String(error);
1581
3056
  if (msg.includes("429") || msg.includes("quota")) {
1582
- console.log(chalk9.yellow("\n\u26A0\uFE0F Quota exceeded. Run: agdi model\n"));
3057
+ console.log(chalk12.yellow("\n\u26A0\uFE0F Quota exceeded. Run: agdi model\n"));
1583
3058
  } else if (msg.includes("401") || msg.includes("403")) {
1584
- console.log(chalk9.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
3059
+ console.log(chalk12.red("\n\u{1F511} Invalid API key. Run: agdi auth\n"));
1585
3060
  } else {
1586
- console.error(chalk9.red("\n" + msg + "\n"));
3061
+ console.error(chalk12.red("\n" + msg + "\n"));
1587
3062
  }
1588
3063
  process.exit(1);
1589
3064
  }
1590
3065
  } catch (error) {
1591
3066
  if (error.name === "ExitPromptError") {
1592
- console.log(chalk9.gray("\n\n\u{1F44B} Cancelled.\n"));
3067
+ console.log(chalk12.gray("\n\n\u{1F44B} Cancelled.\n"));
1593
3068
  process.exit(0);
1594
3069
  }
1595
3070
  throw error;
@@ -1598,11 +3073,11 @@ program.command("build <prompt>").alias("b").description("Generate an app from a
1598
3073
  program.command("config").description("Show configuration").action(async () => {
1599
3074
  const config = loadConfig();
1600
3075
  const active = getActiveProvider();
1601
- console.log(chalk9.cyan.bold("\n\u2699\uFE0F Configuration\n"));
1602
- console.log(chalk9.gray(" Provider: ") + chalk9.cyan(config.defaultProvider || "not set"));
1603
- console.log(chalk9.gray(" Model: ") + chalk9.cyan(config.defaultModel || "not set"));
1604
- console.log(chalk9.gray(" Config: ") + chalk9.gray("~/.agdi/config.json"));
1605
- console.log(chalk9.cyan.bold("\n\u{1F510} API Keys\n"));
3076
+ console.log(chalk12.cyan.bold("\n\u2699\uFE0F Configuration\n"));
3077
+ console.log(chalk12.gray(" Provider: ") + chalk12.cyan(config.defaultProvider || "not set"));
3078
+ console.log(chalk12.gray(" Model: ") + chalk12.cyan(config.defaultModel || "not set"));
3079
+ console.log(chalk12.gray(" Config: ") + chalk12.gray("~/.agdi/config.json"));
3080
+ console.log(chalk12.cyan.bold("\n\u{1F510} API Keys\n"));
1606
3081
  const keys = [
1607
3082
  ["Gemini", config.geminiApiKey],
1608
3083
  ["OpenRouter", config.openrouterApiKey],
@@ -1611,7 +3086,7 @@ program.command("config").description("Show configuration").action(async () => {
1611
3086
  ["DeepSeek", config.deepseekApiKey]
1612
3087
  ];
1613
3088
  for (const [name, key] of keys) {
1614
- const status = key ? chalk9.green("\u2713") : chalk9.gray("\u2717");
3089
+ const status = key ? chalk12.green("\u2713") : chalk12.gray("\u2717");
1615
3090
  console.log(` ${status} ${name}`);
1616
3091
  }
1617
3092
  console.log("");