blun-king-cli 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/bin/blun.js +671 -9
  2. package/package.json +1 -1
package/bin/blun.js CHANGED
@@ -108,7 +108,7 @@ function printHeader() {
108
108
  var line = BOX.h.repeat(w - 4);
109
109
  console.log("");
110
110
  console.log(C.brightBlue + " " + BOX.tl + line + BOX.tr + C.reset);
111
- console.log(C.brightBlue + " " + BOX.v + C.reset + C.bold + C.brightWhite + " " + BOX.bot + " BLUN KING CLI" + C.reset + C.dim + " v1.1" + C.reset + " ".repeat(Math.max(0, w - 24)) + C.brightBlue + BOX.v + C.reset);
111
+ console.log(C.brightBlue + " " + BOX.v + C.reset + C.bold + C.brightWhite + " " + BOX.bot + " BLUN KING CLI" + C.reset + C.dim + " v1.4" + C.reset + " ".repeat(Math.max(0, w - 24)) + C.brightBlue + BOX.v + C.reset);
112
112
  console.log(C.brightBlue + " " + BOX.v + C.reset + C.gray + " Premium KI — Local First — Autonom" + C.reset + " ".repeat(Math.max(0, w - 41)) + C.brightBlue + BOX.v + C.reset);
113
113
  console.log(C.brightBlue + " " + BOX.v + line + BOX.v + C.reset);
114
114
  console.log(C.brightBlue + " " + BOX.v + C.reset + C.gray + " API " + C.brightCyan + config.api.base_url + C.reset + " ".repeat(Math.max(0, w - 14 - config.api.base_url.length)) + C.brightBlue + BOX.v + C.reset);
@@ -124,8 +124,10 @@ function printHeader() {
124
124
  // ── Update Checker ──
125
125
  function checkForUpdates() {
126
126
  try {
127
- var currentVersion = "1.3.0";
128
- var latest = execSync("npm view blun-king-cli version 2>/dev/null", { encoding: "utf8", timeout: 5000 }).trim();
127
+ var currentVersion = "1.4.0";
128
+ try { currentVersion = require(path.join(__dirname, "..", "package.json")).version; } catch(e) {}
129
+ var nullDev = process.platform === "win32" ? "2>NUL" : "2>/dev/null";
130
+ var latest = execSync("npm view blun-king-cli version " + nullDev, { encoding: "utf8", timeout: 8000, stdio: ["pipe", "pipe", "pipe"] }).trim();
129
131
  if (latest && latest !== currentVersion) {
130
132
  console.log(C.yellow + C.bold + " " + BOX.arrow + " Update available: " + currentVersion + " → " + latest + C.reset);
131
133
  console.log(C.yellow + " npm update -g blun-king-cli" + C.reset);
@@ -241,18 +243,45 @@ async function handleCommand(input) {
241
243
  console.log(" /write <file> Write/create a file");
242
244
  console.log(" /init Init project (AGENT.md, .gitignore, git init)");
243
245
  console.log("");
246
+ console.log(C.yellow + " CONFIG" + C.reset);
247
+ console.log(" /config list List all settings (all scopes)");
248
+ console.log(" /config get <key> Get a setting value");
249
+ console.log(" /config set <k> <v> Set a value [--global|--project|--local]");
250
+ console.log(" /config add <k> <v> Add to array setting");
251
+ console.log(" /config remove <k> Remove setting");
252
+ console.log("");
253
+ console.log(C.yellow + " HOOKS" + C.reset);
254
+ console.log(" /hooks list List all hooks");
255
+ console.log(" /hooks add <trigger> <cmd> Add hook (pre:chat, post:deploy...)");
256
+ console.log(" /hooks remove <trigger> Remove hook");
257
+ console.log("");
258
+ console.log(C.yellow + " AGENTS" + C.reset);
259
+ console.log(" /agents list List subagents");
260
+ console.log(" /agents create <n> Create new agent");
261
+ console.log(" /agents run <n> <task> Run agent with task");
262
+ console.log(" /agents info <n> Show agent details");
263
+ console.log("");
244
264
  console.log(C.yellow + " SYSTEM" + C.reset);
245
265
  console.log(" /status Runtime status");
246
266
  console.log(" /versions Prompt versions");
247
267
  console.log(" /health Quick health check");
268
+ console.log(" /doctor Full system diagnostics");
269
+ console.log(" /model [name] Show/switch model");
270
+ console.log(" /cost Session cost estimate");
271
+ console.log(" /compact Clear old context");
272
+ console.log(" /review Review current git diff");
273
+ console.log(" /login key <k> Login with API key");
274
+ console.log(" /logout Logout");
248
275
  console.log(" /watchdog Watchdog status");
249
- console.log(" /watchdog on/off Enable/disable watchdog");
250
276
  console.log(" /memory Show local memory");
251
- console.log(" /memory save <k> Save to memory");
252
- console.log(" /memory del <k> Delete from memory");
253
277
  console.log(" /files List workdir files");
254
278
  console.log(" /exit Exit CLI");
255
279
  console.log("");
280
+ console.log(C.yellow + " SHORTCUTS" + C.reset);
281
+ console.log(" !<cmd> Run bash command directly");
282
+ console.log(" #<key> Read memory entry");
283
+ console.log(" #save <k> <v> Save to memory");
284
+ console.log("");
256
285
  break;
257
286
 
258
287
  case "/clear":
@@ -357,6 +386,7 @@ async function handleCommand(input) {
357
386
  break;
358
387
 
359
388
  case "/plugin":
389
+ case "/mcp":
360
390
  cmdPlugin(args);
361
391
  break;
362
392
 
@@ -364,6 +394,46 @@ async function handleCommand(input) {
364
394
  cmdPermissions(args);
365
395
  break;
366
396
 
397
+ case "/config":
398
+ cmdConfig(args);
399
+ break;
400
+
401
+ case "/hooks":
402
+ cmdHooks(args);
403
+ break;
404
+
405
+ case "/agents":
406
+ cmdAgents(args);
407
+ break;
408
+
409
+ case "/doctor":
410
+ await cmdDoctor();
411
+ break;
412
+
413
+ case "/model":
414
+ cmdModel(args);
415
+ break;
416
+
417
+ case "/login":
418
+ cmdLogin(args);
419
+ break;
420
+
421
+ case "/logout":
422
+ cmdLogout();
423
+ break;
424
+
425
+ case "/compact":
426
+ cmdCompact();
427
+ break;
428
+
429
+ case "/review":
430
+ await cmdReview();
431
+ break;
432
+
433
+ case "/cost":
434
+ cmdCost();
435
+ break;
436
+
367
437
  case "/agent":
368
438
  await cmdAgent(args);
369
439
  break;
@@ -1341,6 +1411,551 @@ async function cmdRender(args) {
1341
1411
  } catch(e) { printError(e.message); }
1342
1412
  }
1343
1413
 
1414
+ // ══════════════════════════════════════════════════
1415
+ // ── 1. SETTINGS REGISTRY (Multi-Scope) ──
1416
+ // ══════════════════════════════════════════════════
1417
+ // Scopes: Managed > CLI Args > Local > Project > User
1418
+ const SETTINGS_SCOPES = ["managed", "cli", "local", "project", "user"];
1419
+ const USER_SETTINGS_FILE = path.join(CONFIG_DIR, "settings.json");
1420
+
1421
+ function findProjectRoot() {
1422
+ var dir = config.workdir;
1423
+ for (var i = 0; i < 10; i++) {
1424
+ if (fs.existsSync(path.join(dir, ".blun"))) return dir;
1425
+ if (fs.existsSync(path.join(dir, ".git"))) return dir;
1426
+ var parent = path.dirname(dir);
1427
+ if (parent === dir) break;
1428
+ dir = parent;
1429
+ }
1430
+ return config.workdir;
1431
+ }
1432
+
1433
+ function loadScopedSettings(scope) {
1434
+ var file;
1435
+ if (scope === "user") file = USER_SETTINGS_FILE;
1436
+ else if (scope === "project") file = path.join(findProjectRoot(), ".blun", "settings.json");
1437
+ else if (scope === "local") file = path.join(findProjectRoot(), ".blun", "settings.local.json");
1438
+ else if (scope === "managed") file = path.join(CONFIG_DIR, "managed-settings.json");
1439
+ else return {};
1440
+ if (file && fs.existsSync(file)) {
1441
+ try { return JSON.parse(fs.readFileSync(file, "utf8")); } catch(e) {}
1442
+ }
1443
+ return {};
1444
+ }
1445
+
1446
+ function saveScopedSettings(scope, data) {
1447
+ var file;
1448
+ if (scope === "user") file = USER_SETTINGS_FILE;
1449
+ else if (scope === "project") {
1450
+ var projDir = path.join(findProjectRoot(), ".blun");
1451
+ if (!fs.existsSync(projDir)) fs.mkdirSync(projDir, { recursive: true });
1452
+ file = path.join(projDir, "settings.json");
1453
+ } else if (scope === "local") {
1454
+ var projDir2 = path.join(findProjectRoot(), ".blun");
1455
+ if (!fs.existsSync(projDir2)) fs.mkdirSync(projDir2, { recursive: true });
1456
+ file = path.join(projDir2, "settings.local.json");
1457
+ } else if (scope === "managed") file = path.join(CONFIG_DIR, "managed-settings.json");
1458
+ else return;
1459
+ fs.writeFileSync(file, JSON.stringify(data, null, 2));
1460
+ }
1461
+
1462
+ // Resolve setting: highest priority scope wins
1463
+ function getSetting(key) {
1464
+ for (var i = 0; i < SETTINGS_SCOPES.length; i++) {
1465
+ var s = loadScopedSettings(SETTINGS_SCOPES[i]);
1466
+ if (s[key] !== undefined) return { value: s[key], scope: SETTINGS_SCOPES[i] };
1467
+ }
1468
+ return { value: undefined, scope: null };
1469
+ }
1470
+
1471
+ // All settings keys with defaults
1472
+ var SETTINGS_DEFAULTS = {
1473
+ model: "blun-king-v100", language: "de", theme: "dark", outputStyle: "markdown",
1474
+ effortLevel: "normal", fastMode: false, alwaysThinkingEnabled: false,
1475
+ showThinkingSummaries: false, verbose: false, autoMode: false,
1476
+ respectGitignore: true, includeCoAuthoredBy: true, cleanupPeriodDays: 30,
1477
+ prefersReducedMotion: false, channelsEnabled: true,
1478
+ "sandbox.enabled": false, "sandbox.autoAllowBashIfSandboxed": true,
1479
+ "permissions.defaultMode": "ask",
1480
+ "attribution.commit": true, "attribution.pr": true
1481
+ };
1482
+
1483
+ function cmdConfig(args) {
1484
+ var parts = (args || "").split(/\s+/);
1485
+ var action = parts[0] || "list";
1486
+ var scope = "user";
1487
+
1488
+ // Check for --global flag
1489
+ var globalIdx = parts.indexOf("--global");
1490
+ if (globalIdx !== -1) { scope = "user"; parts.splice(globalIdx, 1); }
1491
+ var projectIdx = parts.indexOf("--project");
1492
+ if (projectIdx !== -1) { scope = "project"; parts.splice(projectIdx, 1); }
1493
+ var localIdx = parts.indexOf("--local");
1494
+ if (localIdx !== -1) { scope = "local"; parts.splice(localIdx, 1); }
1495
+
1496
+ action = parts[0] || "list";
1497
+ var key = parts[1];
1498
+ var value = parts.slice(2).join(" ");
1499
+
1500
+ if (action === "list") {
1501
+ console.log("");
1502
+ console.log(C.bold + " Settings Registry:" + C.reset);
1503
+ console.log(C.brightBlue + " " + BOX.h.repeat(50) + C.reset);
1504
+ var allKeys = Object.keys(SETTINGS_DEFAULTS);
1505
+ allKeys.forEach(function(k) {
1506
+ var resolved = getSetting(k);
1507
+ var val = resolved.value !== undefined ? resolved.value : SETTINGS_DEFAULTS[k];
1508
+ var src = resolved.scope || "default";
1509
+ console.log(" " + C.gray + k + C.reset + " = " + C.brightCyan + JSON.stringify(val) + C.reset + C.dim + " (" + src + ")" + C.reset);
1510
+ });
1511
+ // Also show custom keys from all scopes
1512
+ SETTINGS_SCOPES.forEach(function(sc) {
1513
+ var s = loadScopedSettings(sc);
1514
+ Object.keys(s).forEach(function(k) {
1515
+ if (!SETTINGS_DEFAULTS.hasOwnProperty(k)) {
1516
+ console.log(" " + C.yellow + k + C.reset + " = " + C.brightCyan + JSON.stringify(s[k]) + C.reset + C.dim + " (" + sc + ")" + C.reset);
1517
+ }
1518
+ });
1519
+ });
1520
+ console.log("");
1521
+
1522
+ } else if (action === "get" && key) {
1523
+ var resolved = getSetting(key);
1524
+ if (resolved.value !== undefined) {
1525
+ console.log(C.brightCyan + JSON.stringify(resolved.value) + C.reset + C.dim + " (from: " + resolved.scope + ")" + C.reset);
1526
+ } else if (SETTINGS_DEFAULTS[key] !== undefined) {
1527
+ console.log(C.brightCyan + JSON.stringify(SETTINGS_DEFAULTS[key]) + C.reset + C.dim + " (default)" + C.reset);
1528
+ } else {
1529
+ printInfo("Not set: " + key);
1530
+ }
1531
+
1532
+ } else if (action === "set" && key) {
1533
+ var s = loadScopedSettings(scope);
1534
+ // Auto-parse value
1535
+ var parsed = value;
1536
+ if (value === "true") parsed = true;
1537
+ else if (value === "false") parsed = false;
1538
+ else if (/^\d+$/.test(value)) parsed = parseInt(value);
1539
+ s[key] = parsed;
1540
+ saveScopedSettings(scope, s);
1541
+ printSuccess(key + " = " + JSON.stringify(parsed) + " (scope: " + scope + ")");
1542
+
1543
+ } else if (action === "add" && key && value) {
1544
+ var s2 = loadScopedSettings(scope);
1545
+ if (!Array.isArray(s2[key])) s2[key] = [];
1546
+ s2[key].push(value);
1547
+ saveScopedSettings(scope, s2);
1548
+ printSuccess("Added '" + value + "' to " + key);
1549
+
1550
+ } else if (action === "remove" && key && value) {
1551
+ var s3 = loadScopedSettings(scope);
1552
+ if (Array.isArray(s3[key])) {
1553
+ s3[key] = s3[key].filter(function(v) { return v !== value; });
1554
+ saveScopedSettings(scope, s3);
1555
+ printSuccess("Removed '" + value + "' from " + key);
1556
+ } else {
1557
+ delete s3[key];
1558
+ saveScopedSettings(scope, s3);
1559
+ printSuccess("Removed " + key);
1560
+ }
1561
+ } else {
1562
+ printError("Usage: /config list|get|set|add|remove <key> [value] [--global|--project|--local]");
1563
+ }
1564
+ }
1565
+
1566
+ // ══════════════════════════════════════════════════
1567
+ // ── 2. SANDBOX ENGINE ──
1568
+ // ══════════════════════════════════════════════════
1569
+ function checkSandbox(action, target) {
1570
+ var sandbox = getSetting("sandbox.enabled").value;
1571
+ if (!sandbox) return { allowed: true };
1572
+
1573
+ var perms = loadPermissions();
1574
+
1575
+ if (action === "write") {
1576
+ var denyWrite = getSetting("sandbox.filesystem.denyWrite").value || [];
1577
+ var allowWrite = getSetting("sandbox.filesystem.allowWrite").value || [];
1578
+ for (var i = 0; i < denyWrite.length; i++) {
1579
+ if (target.includes(denyWrite[i])) return { allowed: false, reason: "denyWrite: " + denyWrite[i] };
1580
+ }
1581
+ if (allowWrite.length > 0) {
1582
+ var ok = false;
1583
+ for (var j = 0; j < allowWrite.length; j++) {
1584
+ if (target.startsWith(allowWrite[j])) { ok = true; break; }
1585
+ }
1586
+ if (!ok) return { allowed: false, reason: "Not in allowWrite list" };
1587
+ }
1588
+ }
1589
+
1590
+ if (action === "read") {
1591
+ var denyRead = getSetting("sandbox.filesystem.denyRead").value || [];
1592
+ for (var k = 0; k < denyRead.length; k++) {
1593
+ if (target.includes(denyRead[k])) return { allowed: false, reason: "denyRead: " + denyRead[k] };
1594
+ }
1595
+ }
1596
+
1597
+ if (action === "network") {
1598
+ var allowedDomains = getSetting("sandbox.network.allowedDomains").value || [];
1599
+ if (allowedDomains.length > 0) {
1600
+ var domainOk = false;
1601
+ for (var l = 0; l < allowedDomains.length; l++) {
1602
+ if (target.includes(allowedDomains[l])) { domainOk = true; break; }
1603
+ }
1604
+ if (!domainOk) return { allowed: false, reason: "Domain not in allowedDomains" };
1605
+ }
1606
+ }
1607
+
1608
+ return { allowed: true };
1609
+ }
1610
+
1611
+ // ══════════════════════════════════════════════════
1612
+ // ── 3. HOOKS ENGINE ──
1613
+ // ══════════════════════════════════════════════════
1614
+ const HOOKS_FILE = path.join(CONFIG_DIR, "hooks.json");
1615
+
1616
+ function loadHooks() {
1617
+ if (fs.existsSync(HOOKS_FILE)) {
1618
+ try { return JSON.parse(fs.readFileSync(HOOKS_FILE, "utf8")); } catch(e) {}
1619
+ }
1620
+ return { pre: {}, post: {} };
1621
+ }
1622
+
1623
+ function saveHooks(h) { fs.writeFileSync(HOOKS_FILE, JSON.stringify(h, null, 2)); }
1624
+
1625
+ function runHook(phase, command) {
1626
+ if (getSetting("disableAllHooks").value) return;
1627
+ var hooks = loadHooks();
1628
+ var list = (hooks[phase] || {})[command] || [];
1629
+ for (var i = 0; i < list.length; i++) {
1630
+ try {
1631
+ if (list[i].type === "command") {
1632
+ execSync(list[i].run, { cwd: config.workdir, encoding: "utf8", timeout: 10000, stdio: "pipe" });
1633
+ } else if (list[i].type === "http") {
1634
+ var allowed = getSetting("allowedHttpHookUrls").value || [];
1635
+ if (allowed.length > 0 && !allowed.some(function(u) { return list[i].url.startsWith(u); })) continue;
1636
+ execSync("curl -sX POST " + JSON.stringify(list[i].url) + " -d " + JSON.stringify(JSON.stringify({ event: phase + ":" + command })), { timeout: 5000, stdio: "pipe" });
1637
+ }
1638
+ } catch(e) { if (config.verbose) log("Hook error: " + e.message); }
1639
+ }
1640
+ }
1641
+
1642
+ function cmdHooks(args) {
1643
+ var hooks = loadHooks();
1644
+ var parts = (args || "").split(/\s+/);
1645
+ var action = parts[0] || "list";
1646
+
1647
+ if (action === "list") {
1648
+ console.log("");
1649
+ console.log(C.bold + " Hooks:" + C.reset);
1650
+ ["pre", "post"].forEach(function(phase) {
1651
+ var cmds = Object.keys(hooks[phase] || {});
1652
+ if (cmds.length === 0) return;
1653
+ console.log(C.yellow + " " + phase + ":" + C.reset);
1654
+ cmds.forEach(function(cmd) {
1655
+ hooks[phase][cmd].forEach(function(h) {
1656
+ console.log(" " + cmd + " → " + C.brightCyan + (h.run || h.url) + C.reset + C.dim + " (" + h.type + ")" + C.reset);
1657
+ });
1658
+ });
1659
+ });
1660
+ if (Object.keys(hooks.pre).length === 0 && Object.keys(hooks.post).length === 0) {
1661
+ console.log(C.gray + " (none)" + C.reset);
1662
+ }
1663
+ console.log("");
1664
+
1665
+ } else if (action === "add" && parts[1] && parts[2] && parts[3]) {
1666
+ // /hooks add pre:chat "echo hello"
1667
+ var trigger = parts[1].split(":");
1668
+ var phase = trigger[0]; // pre or post
1669
+ var cmd = trigger[1];
1670
+ var run = parts.slice(2).join(" ");
1671
+ if (!hooks[phase]) hooks[phase] = {};
1672
+ if (!hooks[phase][cmd]) hooks[phase][cmd] = [];
1673
+ hooks[phase][cmd].push({ type: "command", run: run });
1674
+ saveHooks(hooks);
1675
+ printSuccess("Hook added: " + phase + ":" + cmd + " → " + run);
1676
+
1677
+ } else if (action === "remove" && parts[1]) {
1678
+ var trigger2 = parts[1].split(":");
1679
+ if (hooks[trigger2[0]] && hooks[trigger2[0]][trigger2[1]]) {
1680
+ delete hooks[trigger2[0]][trigger2[1]];
1681
+ saveHooks(hooks);
1682
+ printSuccess("Hook removed: " + parts[1]);
1683
+ } else {
1684
+ printError("Hook not found: " + parts[1]);
1685
+ }
1686
+ } else {
1687
+ printError("Usage: /hooks list|add|remove — /hooks add pre:chat \"echo hello\"");
1688
+ }
1689
+ }
1690
+
1691
+ // ══════════════════════════════════════════════════
1692
+ // ── 4. SUBAGENTS ──
1693
+ // ══════════════════════════════════════════════════
1694
+ const AGENTS_DIR_USER = path.join(CONFIG_DIR, "agents");
1695
+ if (!fs.existsSync(AGENTS_DIR_USER)) fs.mkdirSync(AGENTS_DIR_USER, { recursive: true });
1696
+
1697
+ function loadSubagents() {
1698
+ var agents = [];
1699
+ // User-level agents
1700
+ [AGENTS_DIR_USER, path.join(findProjectRoot(), ".blun", "agents")].forEach(function(dir) {
1701
+ if (!fs.existsSync(dir)) return;
1702
+ fs.readdirSync(dir).forEach(function(f) {
1703
+ if (!f.endsWith(".md")) return;
1704
+ var content = fs.readFileSync(path.join(dir, f), "utf8");
1705
+ var meta = {};
1706
+ // Parse YAML frontmatter
1707
+ var fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
1708
+ if (fmMatch) {
1709
+ fmMatch[1].split("\n").forEach(function(line) {
1710
+ var kv = line.match(/^(\w+):\s*(.+)/);
1711
+ if (kv) meta[kv[1]] = kv[2].trim();
1712
+ });
1713
+ }
1714
+ agents.push({
1715
+ name: meta.name || f.replace(".md", ""),
1716
+ description: meta.description || "",
1717
+ role: meta.role || "general",
1718
+ tools: (meta.tools || "").split(",").map(function(t) { return t.trim(); }).filter(Boolean),
1719
+ file: path.join(dir, f),
1720
+ prompt: content.replace(/^---[\s\S]*?---\n*/, "").trim(),
1721
+ scope: dir.includes(CONFIG_DIR) ? "user" : "project"
1722
+ });
1723
+ });
1724
+ });
1725
+ return agents;
1726
+ }
1727
+
1728
+ function cmdAgents(args) {
1729
+ var parts = (args || "").split(/\s+/);
1730
+ var action = parts[0] || "list";
1731
+
1732
+ if (action === "list") {
1733
+ var agents = loadSubagents();
1734
+ console.log("");
1735
+ console.log(C.bold + " " + BOX.bot + " Subagents:" + C.reset);
1736
+ console.log(C.brightBlue + " " + BOX.h.repeat(40) + C.reset);
1737
+ if (agents.length === 0) {
1738
+ console.log(C.gray + " (none) — Create .md files in ~/.blun/agents/ or .blun/agents/" + C.reset);
1739
+ }
1740
+ agents.forEach(function(a) {
1741
+ console.log(" " + C.brightCyan + a.name + C.reset + " — " + a.description + C.dim + " (" + a.scope + ")" + C.reset);
1742
+ });
1743
+ console.log("");
1744
+
1745
+ } else if (action === "create" && parts[1]) {
1746
+ var name = parts[1];
1747
+ var desc = parts.slice(2).join(" ") || "Custom agent";
1748
+ var agentFile = path.join(AGENTS_DIR_USER, name + ".md");
1749
+ var content = "---\nname: " + name + "\ndescription: " + desc + "\nrole: general\ntools: chat,read,write\n---\n\nDu bist " + name + ", ein spezialisierter Agent bei BLUN King.\n\nDeine Aufgabe: " + desc + "\n";
1750
+ fs.writeFileSync(agentFile, content);
1751
+ printSuccess("Agent '" + name + "' created at " + agentFile);
1752
+
1753
+ } else if (action === "run" && parts[1]) {
1754
+ var agents2 = loadSubagents();
1755
+ var agent = agents2.find(function(a) { return a.name === parts[1]; });
1756
+ if (!agent) { printError("Agent not found: " + parts[1]); return; }
1757
+ var task = parts.slice(2).join(" ");
1758
+ if (!task) { printError("Usage: /agents run <name> <task>"); return; }
1759
+ // Run agent via API
1760
+ printInfo("Running agent '" + agent.name + "'...");
1761
+ apiCall("POST", "/agent", { goal: agent.prompt + "\n\nAKTUELLE AUFGABE: " + task }).then(function(resp) {
1762
+ if (resp.status === 200) {
1763
+ printAnswer(resp.data.answer, agent.name + " " + BOX.dot + " " + resp.data.steps_executed + " steps");
1764
+ } else {
1765
+ printError(resp.data.error || "Error");
1766
+ }
1767
+ }).catch(function(e) { printError(e.message); });
1768
+
1769
+ } else if (action === "info" && parts[1]) {
1770
+ var agents3 = loadSubagents();
1771
+ var a = agents3.find(function(a) { return a.name === parts[1]; });
1772
+ if (!a) { printError("Agent not found: " + parts[1]); return; }
1773
+ console.log("");
1774
+ console.log(C.bold + " Agent: " + a.name + C.reset);
1775
+ console.log(" Description: " + a.description);
1776
+ console.log(" Role: " + a.role);
1777
+ console.log(" Tools: " + a.tools.join(", "));
1778
+ console.log(" Scope: " + a.scope);
1779
+ console.log(" File: " + a.file);
1780
+ console.log(C.dim + " " + BOX.h.repeat(30) + C.reset);
1781
+ console.log(C.gray + a.prompt.slice(0, 300) + C.reset);
1782
+ console.log("");
1783
+
1784
+ } else {
1785
+ printError("Usage: /agents list|create|run|info <name>");
1786
+ }
1787
+ }
1788
+
1789
+ // ══════════════════════════════════════════════════
1790
+ // ── 5. SESSION HISTORY (per workdir) ──
1791
+ // ══════════════════════════════════════════════════
1792
+ const SESSIONS_DIR = path.join(CONFIG_DIR, "sessions");
1793
+ if (!fs.existsSync(SESSIONS_DIR)) fs.mkdirSync(SESSIONS_DIR, { recursive: true });
1794
+
1795
+ function getSessionFile() {
1796
+ var hash = require("crypto").createHash("md5").update(config.workdir).digest("hex").slice(0, 8);
1797
+ return path.join(SESSIONS_DIR, hash + ".json");
1798
+ }
1799
+
1800
+ function loadSessionHistory() {
1801
+ var f = getSessionFile();
1802
+ if (fs.existsSync(f)) {
1803
+ try { return JSON.parse(fs.readFileSync(f, "utf8")); } catch(e) {}
1804
+ }
1805
+ return { workdir: config.workdir, history: [], inputHistory: [] };
1806
+ }
1807
+
1808
+ function saveSessionHistory(session) {
1809
+ fs.writeFileSync(getSessionFile(), JSON.stringify(session, null, 2));
1810
+ }
1811
+
1812
+ // ══════════════════════════════════════════════════
1813
+ // ── 6. COST TRACKING ──
1814
+ // ══════════════════════════════════════════════════
1815
+ var sessionCost = { requests: 0, inputTokensEst: 0, outputTokensEst: 0 };
1816
+
1817
+ function cmdCost() {
1818
+ console.log("");
1819
+ console.log(C.bold + " Session Cost Estimate:" + C.reset);
1820
+ console.log(" Requests: " + C.brightCyan + sessionCost.requests + C.reset);
1821
+ console.log(" Est. Input Tokens: " + C.brightCyan + sessionCost.inputTokensEst + C.reset);
1822
+ console.log(" Est. Output Tokens: " + C.brightCyan + sessionCost.outputTokensEst + C.reset);
1823
+ console.log("");
1824
+ }
1825
+
1826
+ // ══════════════════════════════════════════════════
1827
+ // ── 7. /doctor — Diagnostics ──
1828
+ // ══════════════════════════════════════════════════
1829
+ async function cmdDoctor() {
1830
+ console.log("");
1831
+ console.log(C.bold + " " + BOX.bot + " BLUN Doctor — System Check" + C.reset);
1832
+ console.log(C.brightBlue + " " + BOX.h.repeat(40) + C.reset);
1833
+
1834
+ // Config
1835
+ var hasKey = config.auth.api_key && config.auth.api_key.length > 10;
1836
+ console.log(" Config: " + (fs.existsSync(CONFIG_FILE) ? C.green + "OK" : C.red + "MISSING") + C.reset);
1837
+ console.log(" API Key: " + (hasKey ? C.green + "OK" : C.red + "NOT SET") + C.reset);
1838
+
1839
+ // API
1840
+ try {
1841
+ var h = await apiCall("GET", "/health");
1842
+ console.log(" API: " + (h.status === 200 ? C.green + "OK (" + h.data.model + ")" : C.red + "ERROR " + h.status) + C.reset);
1843
+ } catch(e) {
1844
+ console.log(" API: " + C.red + "UNREACHABLE" + C.reset);
1845
+ }
1846
+
1847
+ // Git
1848
+ try {
1849
+ execSync("git --version", { stdio: "pipe" });
1850
+ console.log(" Git: " + C.green + "OK" + C.reset);
1851
+ } catch(e) {
1852
+ console.log(" Git: " + C.red + "NOT FOUND" + C.reset);
1853
+ }
1854
+
1855
+ // SSH
1856
+ try {
1857
+ execSync("ssh -V 2>&1", { stdio: "pipe" });
1858
+ console.log(" SSH: " + C.green + "OK" + C.reset);
1859
+ } catch(e) {
1860
+ console.log(" SSH: " + C.red + "NOT FOUND" + C.reset);
1861
+ }
1862
+
1863
+ // Node
1864
+ console.log(" Node: " + C.green + process.version + C.reset);
1865
+ console.log(" Platform: " + C.green + process.platform + C.reset);
1866
+
1867
+ // Plugins
1868
+ var plugins = loadPlugins();
1869
+ console.log(" Plugins: " + C.brightCyan + Object.keys(plugins).length + " installed" + C.reset);
1870
+
1871
+ // Agents
1872
+ var agents = loadSubagents();
1873
+ console.log(" Agents: " + C.brightCyan + agents.length + " loaded" + C.reset);
1874
+
1875
+ // Hooks
1876
+ var hooks = loadHooks();
1877
+ var hookCount = Object.keys(hooks.pre || {}).length + Object.keys(hooks.post || {}).length;
1878
+ console.log(" Hooks: " + C.brightCyan + hookCount + " registered" + C.reset);
1879
+
1880
+ // Memory
1881
+ var memFiles = fs.existsSync(MEMORY_DIR) ? fs.readdirSync(MEMORY_DIR).length : 0;
1882
+ console.log(" Memory: " + C.brightCyan + memFiles + " entries" + C.reset);
1883
+
1884
+ // Disk
1885
+ console.log(" Workdir: " + C.brightCyan + config.workdir + C.reset);
1886
+ console.log("");
1887
+ }
1888
+
1889
+ // ══════════════════════════════════════════════════
1890
+ // ── 8. /model — Model switcher ──
1891
+ // ══════════════════════════════════════════════════
1892
+ function cmdModel(args) {
1893
+ if (!args) {
1894
+ console.log("");
1895
+ console.log(C.bold + " Current Model: " + C.brightCyan + config.model + C.reset);
1896
+ console.log(C.gray + " /model <name> — Switch model" + C.reset);
1897
+ console.log("");
1898
+ return;
1899
+ }
1900
+ config.model = args.trim();
1901
+ saveConfig(config);
1902
+ printSuccess("Model switched to: " + config.model);
1903
+ }
1904
+
1905
+ // ══════════════════════════════════════════════════
1906
+ // ── 9. /login /logout ──
1907
+ // ══════════════════════════════════════════════════
1908
+ function cmdLogin(args) {
1909
+ if (args && args.startsWith("key ")) {
1910
+ config.auth.api_key = args.slice(4).trim();
1911
+ config.auth.type = "api_key";
1912
+ saveConfig(config);
1913
+ printSuccess("Logged in with API key.");
1914
+ } else if (args === "oauth") {
1915
+ printInfo("OAuth login not yet implemented. Use API key for now:");
1916
+ printInfo(" /login key <your-api-key>");
1917
+ } else {
1918
+ console.log("");
1919
+ console.log(C.bold + " Login:" + C.reset);
1920
+ console.log(" /login key <api-key> — Login with API key");
1921
+ console.log(" /login oauth — Login with OAuth (coming soon)");
1922
+ console.log("");
1923
+ }
1924
+ }
1925
+
1926
+ function cmdLogout() {
1927
+ config.auth.api_key = "";
1928
+ config.auth.oauth_token = "";
1929
+ config.auth.oauth_expires = null;
1930
+ saveConfig(config);
1931
+ printSuccess("Logged out.");
1932
+ }
1933
+
1934
+ // ══════════════════════════════════════════════════
1935
+ // ── 10. /compact — Clear context ──
1936
+ // ══════════════════════════════════════════════════
1937
+ function cmdCompact() {
1938
+ var before = chatHistory.length;
1939
+ chatHistory = chatHistory.slice(-4);
1940
+ printSuccess("Context compacted: " + before + " → " + chatHistory.length + " messages");
1941
+ }
1942
+
1943
+ // ══════════════════════════════════════════════════
1944
+ // ── 11. /review — Review current diff ──
1945
+ // ══════════════════════════════════════════════════
1946
+ async function cmdReview() {
1947
+ try {
1948
+ var diff = execSync("git diff", { cwd: config.workdir, encoding: "utf8", timeout: 10000 });
1949
+ if (!diff.trim()) { printInfo("No changes to review."); return; }
1950
+ printInfo("Sending diff for review...");
1951
+ var resp = await apiCall("POST", "/chat", {
1952
+ message: "Review diesen Git-Diff. Finde Bugs, Sicherheitsprobleme, Verbesserungen:\n\n```diff\n" + diff.slice(0, 8000) + "\n```"
1953
+ });
1954
+ if (resp.status === 200) printAnswer(resp.data.answer, "code-review");
1955
+ else printError(resp.data.error || "Error");
1956
+ } catch(e) { printError(e.message); }
1957
+ }
1958
+
1344
1959
  // ── Main Loop ──
1345
1960
  async function main() {
1346
1961
  // Handle CLI args
@@ -1379,9 +1994,23 @@ async function main() {
1379
1994
  }
1380
1995
 
1381
1996
  // Interactive mode
1997
+ // Workdir selection (like Claude Code)
1998
+ if (!process.argv.includes("--no-workdir-prompt")) {
1999
+ var cwdHasBlun = fs.existsSync(path.join(process.cwd(), ".blun"));
2000
+ var cwdHasGit = fs.existsSync(path.join(process.cwd(), ".git"));
2001
+ if (cwdHasBlun || cwdHasGit) {
2002
+ config.workdir = process.cwd();
2003
+ }
2004
+ // Show workdir in header
2005
+ }
2006
+
1382
2007
  printHeader();
1383
2008
  checkForUpdates();
1384
2009
 
2010
+ // Load session history for this workdir
2011
+ var session = loadSessionHistory();
2012
+ chatHistory = session.history.slice(-20);
2013
+
1385
2014
  // Health check
1386
2015
  try {
1387
2016
  var h = await apiCall("GET", "/health");
@@ -1397,7 +2026,9 @@ async function main() {
1397
2026
  "/generate", "/analyze", "/score", "/eval", "/settings", "/set",
1398
2027
  "/status", "/versions", "/health", "/watchdog", "/memory", "/files",
1399
2028
  "/sh", "/git", "/ssh", "/deploy", "/read", "/write", "/init",
1400
- "/screenshot", "/render", "/agent", "/plugin", "/permissions", "/exit"
2029
+ "/screenshot", "/render", "/agent", "/plugin", "/mcp", "/permissions",
2030
+ "/config", "/hooks", "/agents", "/doctor", "/model", "/login", "/logout",
2031
+ "/compact", "/review", "/cost", "/exit"
1401
2032
  ];
1402
2033
 
1403
2034
  var rl = readline.createInterface({
@@ -1419,15 +2050,46 @@ async function main() {
1419
2050
  var input = line.trim();
1420
2051
  if (!input) { rl.prompt(); return; }
1421
2052
 
1422
- if (input.startsWith("/")) {
2053
+ // ! prefix = direct bash
2054
+ if (input.startsWith("!")) {
2055
+ cmdShell(input.slice(1).trim());
2056
+ }
2057
+ // # prefix = memory shortcut
2058
+ else if (input.startsWith("#save ")) {
2059
+ var memParts = input.slice(6).split(/\s+/);
2060
+ var memKey = memParts[0];
2061
+ var memVal = memParts.slice(1).join(" ");
2062
+ if (memKey && memVal) {
2063
+ fs.writeFileSync(path.join(MEMORY_DIR, memKey + ".txt"), memVal);
2064
+ printSuccess("Memory saved: " + memKey);
2065
+ }
2066
+ }
2067
+ else if (input.startsWith("#") && !input.startsWith("#save")) {
2068
+ var memKey2 = input.slice(1).trim();
2069
+ var memFile = path.join(MEMORY_DIR, memKey2 + ".txt");
2070
+ if (fs.existsSync(memFile)) {
2071
+ console.log(C.brightCyan + fs.readFileSync(memFile, "utf8") + C.reset);
2072
+ } else {
2073
+ printError("Memory not found: " + memKey2);
2074
+ }
2075
+ }
2076
+ // / prefix = command
2077
+ else if (input.startsWith("/")) {
2078
+ runHook("pre", input.split(/\s+/)[0].slice(1));
1423
2079
  await handleCommand(input);
1424
- } else {
2080
+ runHook("post", input.split(/\s+/)[0].slice(1));
2081
+ }
2082
+ // plain text = chat
2083
+ else {
1425
2084
  await sendChat(input);
1426
2085
  }
1427
2086
  rl.prompt();
1428
2087
  });
1429
2088
 
1430
2089
  rl.on("close", function() {
2090
+ // Save session history
2091
+ session.history = chatHistory.slice(-50);
2092
+ saveSessionHistory(session);
1431
2093
  console.log(C.dim + "\nBye." + C.reset);
1432
2094
  process.exit(0);
1433
2095
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "blun-king-cli",
3
- "version": "1.3.0",
3
+ "version": "1.5.0",
4
4
  "description": "BLUN King CLI — Premium KI Console",
5
5
  "bin": {
6
6
  "blun": "./bin/blun.js"