opencodekit 0.12.6 → 0.13.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 (65) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +2756 -523
  3. package/dist/template/.opencode/AGENTS.md +35 -128
  4. package/dist/template/.opencode/README.md +4 -3
  5. package/dist/template/.opencode/agent/build.md +32 -21
  6. package/dist/template/.opencode/agent/explore.md +27 -16
  7. package/dist/template/.opencode/agent/planner.md +103 -63
  8. package/dist/template/.opencode/agent/review.md +31 -23
  9. package/dist/template/.opencode/agent/rush.md +27 -19
  10. package/dist/template/.opencode/agent/scout.md +27 -19
  11. package/dist/template/.opencode/agent/vision.md +29 -19
  12. package/dist/template/.opencode/command/accessibility-check.md +1 -0
  13. package/dist/template/.opencode/command/analyze-mockup.md +1 -0
  14. package/dist/template/.opencode/command/analyze-project.md +2 -1
  15. package/dist/template/.opencode/command/brainstorm.md +2 -1
  16. package/dist/template/.opencode/command/design-audit.md +1 -0
  17. package/dist/template/.opencode/command/design.md +1 -0
  18. package/dist/template/.opencode/command/finish.md +39 -4
  19. package/dist/template/.opencode/command/fix.md +28 -1
  20. package/dist/template/.opencode/command/implement.md +26 -6
  21. package/dist/template/.opencode/command/init.md +1 -0
  22. package/dist/template/.opencode/command/pr.md +28 -1
  23. package/dist/template/.opencode/command/research-ui.md +1 -0
  24. package/dist/template/.opencode/command/research.md +1 -4
  25. package/dist/template/.opencode/command/review-codebase.md +1 -0
  26. package/dist/template/.opencode/command/start.md +106 -0
  27. package/dist/template/.opencode/command/status.md +3 -2
  28. package/dist/template/.opencode/command/summarize.md +2 -1
  29. package/dist/template/.opencode/command/triage.md +66 -12
  30. package/dist/template/.opencode/command/ui-review.md +1 -0
  31. package/dist/template/.opencode/memory/project/architecture.md +59 -6
  32. package/dist/template/.opencode/memory/project/beads-workflow.md +278 -0
  33. package/dist/template/.opencode/memory/project/commands.md +20 -164
  34. package/dist/template/.opencode/memory/session-context.md +40 -0
  35. package/dist/template/.opencode/memory/user.md +24 -7
  36. package/dist/template/.opencode/opencode.json +77 -16
  37. package/dist/template/.opencode/package.json +1 -1
  38. package/dist/template/.opencode/plugin/compaction.ts +62 -18
  39. package/dist/template/.opencode/plugin/lib/notify.ts +2 -3
  40. package/dist/template/.opencode/plugin/sessions.ts +1 -1
  41. package/dist/template/.opencode/plugin/skill-mcp.ts +11 -12
  42. package/dist/template/.opencode/skill/beads/SKILL.md +44 -0
  43. package/dist/template/.opencode/skill/condition-based-waiting/example.ts +71 -65
  44. package/dist/template/.opencode/tool/ast-grep.ts +3 -3
  45. package/dist/template/.opencode/tool/bd-inbox.ts +7 -6
  46. package/dist/template/.opencode/tool/bd-msg.ts +3 -3
  47. package/dist/template/.opencode/tool/bd-release.ts +2 -2
  48. package/dist/template/.opencode/tool/bd-reserve.ts +5 -4
  49. package/dist/template/.opencode/tool/memory-read.ts +58 -58
  50. package/dist/template/.opencode/tool/memory-search.ts +2 -2
  51. package/dist/template/.opencode/tool/memory-update.ts +53 -54
  52. package/dist/template/.opencode/tool/observation.ts +6 -6
  53. package/dist/template/.opencode/tsconfig.json +19 -19
  54. package/package.json +8 -17
  55. package/dist/template/.opencode/command.backup/analyze-project.md +0 -465
  56. package/dist/template/.opencode/command.backup/finish.md +0 -167
  57. package/dist/template/.opencode/command.backup/implement.md +0 -143
  58. package/dist/template/.opencode/command.backup/pr.md +0 -252
  59. package/dist/template/.opencode/command.backup/status.md +0 -376
  60. package/dist/template/.opencode/lib/lsp/client.ts +0 -614
  61. package/dist/template/.opencode/lib/lsp/config.ts +0 -199
  62. package/dist/template/.opencode/lib/lsp/constants.ts +0 -339
  63. package/dist/template/.opencode/lib/lsp/types.ts +0 -138
  64. package/dist/template/.opencode/lib/lsp/utils.ts +0 -190
  65. package/dist/template/.opencode/memory/project/SHELL_OUTPUT_MIGRATION_PLAN.md +0 -551
package/dist/index.js CHANGED
@@ -750,7 +750,7 @@ var cac = (name = "") => new CAC(name);
750
750
  // package.json
751
751
  var package_default = {
752
752
  name: "opencodekit",
753
- version: "0.12.6",
753
+ version: "0.13.0",
754
754
  description: "CLI tool for bootstrapping and managing OpenCodeKit projects",
755
755
  type: "module",
756
756
  repository: {
@@ -764,13 +764,10 @@ var package_default = {
764
764
  bin: {
765
765
  ock: "dist/index.js"
766
766
  },
767
- files: [
768
- "dist",
769
- "README.md"
770
- ],
767
+ files: ["dist", "README.md"],
771
768
  scripts: {
772
769
  dev: "bun run src/index.ts",
773
- build: "bun build src/index.ts --outdir dist --target node && mkdir -p dist/template && rsync -av --exclude=node_modules --exclude=dist --exclude=.git --exclude=coverage --exclude=.next --exclude=.turbo --exclude=logs --exclude=package-lock.json .opencode/ dist/template/.opencode/",
770
+ build: "bun run build.ts && mkdir -p dist/template && rsync -av --exclude=node_modules --exclude=dist --exclude=.git --exclude=coverage --exclude=.next --exclude=.turbo --exclude=logs --exclude=package-lock.json .opencode/ dist/template/.opencode/",
774
771
  compile: "bun build src/index.ts --compile --outfile ock",
775
772
  "compile:binary": "bun build src/index.ts --compile --outfile bin/ock",
776
773
  typecheck: "tsc --noEmit",
@@ -779,14 +776,7 @@ var package_default = {
779
776
  lint: "biome check .",
780
777
  "lint:fix": "biome check --fix ."
781
778
  },
782
- keywords: [
783
- "cli",
784
- "opencodekit",
785
- "template",
786
- "agents",
787
- "mcp",
788
- "opencode"
789
- ],
779
+ keywords: ["cli", "opencodekit", "template", "agents", "mcp", "opencode"],
790
780
  author: "OpenCodeKit",
791
781
  license: "MIT",
792
782
  engines: {
@@ -795,11 +785,14 @@ var package_default = {
795
785
  dependencies: {
796
786
  "@clack/prompts": "^0.7.0",
797
787
  "@opencode-ai/plugin": "^1.1.2",
788
+ "@opentui/core": "^0.1.69",
789
+ "@opentui/solid": "^0.1.69",
798
790
  "beads-village": "^1.3.3",
799
791
  cac: "^6.7.14",
800
792
  "cli-table3": "^0.6.5",
801
793
  ora: "^9.0.0",
802
794
  picocolors: "^1.1.1",
795
+ "solid-js": "^1.9.10",
803
796
  zod: "^3.23.8"
804
797
  },
805
798
  devDependencies: {
@@ -809,9 +802,7 @@ var package_default = {
809
802
  "@types/node": "^22.10.1",
810
803
  typescript: "^5.7.2"
811
804
  },
812
- trustedDependencies: [
813
- "@beads/bd"
814
- ]
805
+ trustedDependencies: ["@beads/bd"]
815
806
  };
816
807
 
817
808
  // src/commands/agent.ts
@@ -821,6 +812,7 @@ import {
821
812
  mkdirSync,
822
813
  readFileSync,
823
814
  readdirSync,
815
+ unlinkSync,
824
816
  writeFileSync
825
817
  } from "node:fs";
826
818
  import { join } from "node:path";
@@ -1156,6 +1148,41 @@ class BD extends x {
1156
1148
  });
1157
1149
  }
1158
1150
  }
1151
+ var fD = Object.defineProperty;
1152
+ var gD = (e, u, F) => (u in e) ? fD(e, u, { enumerable: true, configurable: true, writable: true, value: F }) : e[u] = F;
1153
+ var K = (e, u, F) => (gD(e, typeof u != "symbol" ? u + "" : u, F), F);
1154
+ var vD = class extends x {
1155
+ constructor(u) {
1156
+ super(u, false), K(this, "options"), K(this, "cursor", 0), this.options = u.options, this.value = [...u.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: F }) => F === u.cursorAt), 0), this.on("key", (F) => {
1157
+ F === "a" && this.toggleAll();
1158
+ }), this.on("cursor", (F) => {
1159
+ switch (F) {
1160
+ case "left":
1161
+ case "up":
1162
+ this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
1163
+ break;
1164
+ case "down":
1165
+ case "right":
1166
+ this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
1167
+ break;
1168
+ case "space":
1169
+ this.toggleValue();
1170
+ break;
1171
+ }
1172
+ });
1173
+ }
1174
+ get _value() {
1175
+ return this.options[this.cursor].value;
1176
+ }
1177
+ toggleAll() {
1178
+ const u = this.value.length === this.options.length;
1179
+ this.value = u ? [] : this.options.map((F) => F.value);
1180
+ }
1181
+ toggleValue() {
1182
+ const u = this.value.includes(this._value);
1183
+ this.value = u ? this.value.filter((F) => F !== this._value) : [...this.value, this._value];
1184
+ }
1185
+ };
1159
1186
  var wD = Object.defineProperty;
1160
1187
  var yD = (e, u, F) => (u in e) ? wD(e, u, { enumerable: true, configurable: true, writable: true, value: F }) : e[u] = F;
1161
1188
  var Z = (e, u, F) => (yD(e, typeof u != "symbol" ? u + "" : u, F), F);
@@ -1239,7 +1266,7 @@ var H = o("◆", "*");
1239
1266
  var I2 = o("■", "x");
1240
1267
  var x2 = o("▲", "x");
1241
1268
  var S2 = o("◇", "o");
1242
- var K = o("┌", "T");
1269
+ var K2 = o("┌", "T");
1243
1270
  var a2 = o("│", "|");
1244
1271
  var d2 = o("└", "—");
1245
1272
  var b2 = o("●", ">");
@@ -1337,6 +1364,50 @@ ${import_picocolors2.default.cyan(d2)}
1337
1364
  }
1338
1365
  } }).prompt();
1339
1366
  };
1367
+ var ae = (r2) => {
1368
+ const n = (i, t) => {
1369
+ const s = i.label ?? String(i.value);
1370
+ return t === "active" ? `${import_picocolors2.default.cyan(C)} ${s} ${i.hint ? import_picocolors2.default.dim(`(${i.hint})`) : ""}` : t === "selected" ? `${import_picocolors2.default.green(w2)} ${import_picocolors2.default.dim(s)}` : t === "cancelled" ? `${import_picocolors2.default.strikethrough(import_picocolors2.default.dim(s))}` : t === "active-selected" ? `${import_picocolors2.default.green(w2)} ${s} ${i.hint ? import_picocolors2.default.dim(`(${i.hint})`) : ""}` : t === "submitted" ? `${import_picocolors2.default.dim(s)}` : `${import_picocolors2.default.dim(M2)} ${import_picocolors2.default.dim(s)}`;
1371
+ };
1372
+ return new vD({ options: r2.options, initialValues: r2.initialValues, required: r2.required ?? true, cursorAt: r2.cursorAt, validate(i) {
1373
+ if (this.required && i.length === 0)
1374
+ return `Please select at least one option.
1375
+ ${import_picocolors2.default.reset(import_picocolors2.default.dim(`Press ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" space ")))} to select, ${import_picocolors2.default.gray(import_picocolors2.default.bgWhite(import_picocolors2.default.inverse(" enter ")))} to submit`))}`;
1376
+ }, render() {
1377
+ let i = `${import_picocolors2.default.gray(a2)}
1378
+ ${y2(this.state)} ${r2.message}
1379
+ `;
1380
+ switch (this.state) {
1381
+ case "submit":
1382
+ return `${i}${import_picocolors2.default.gray(a2)} ${this.options.filter(({ value: t }) => this.value.includes(t)).map((t) => n(t, "submitted")).join(import_picocolors2.default.dim(", ")) || import_picocolors2.default.dim("none")}`;
1383
+ case "cancel": {
1384
+ const t = this.options.filter(({ value: s }) => this.value.includes(s)).map((s) => n(s, "cancelled")).join(import_picocolors2.default.dim(", "));
1385
+ return `${i}${import_picocolors2.default.gray(a2)} ${t.trim() ? `${t}
1386
+ ${import_picocolors2.default.gray(a2)}` : ""}`;
1387
+ }
1388
+ case "error": {
1389
+ const t = this.error.split(`
1390
+ `).map((s, c2) => c2 === 0 ? `${import_picocolors2.default.yellow(d2)} ${import_picocolors2.default.yellow(s)}` : ` ${s}`).join(`
1391
+ `);
1392
+ return i + import_picocolors2.default.yellow(a2) + " " + this.options.map((s, c2) => {
1393
+ const l2 = this.value.includes(s.value), u = c2 === this.cursor;
1394
+ return u && l2 ? n(s, "active-selected") : l2 ? n(s, "selected") : n(s, u ? "active" : "inactive");
1395
+ }).join(`
1396
+ ${import_picocolors2.default.yellow(a2)} `) + `
1397
+ ` + t + `
1398
+ `;
1399
+ }
1400
+ default:
1401
+ return `${i}${import_picocolors2.default.cyan(a2)} ${this.options.map((t, s) => {
1402
+ const c2 = this.value.includes(t.value), l2 = s === this.cursor;
1403
+ return l2 && c2 ? n(t, "active-selected") : c2 ? n(t, "selected") : n(t, l2 ? "active" : "inactive");
1404
+ }).join(`
1405
+ ${import_picocolors2.default.cyan(a2)} `)}
1406
+ ${import_picocolors2.default.cyan(d2)}
1407
+ `;
1408
+ }
1409
+ } }).prompt();
1410
+ };
1340
1411
  var R2 = (r2) => r2.replace(me(), "");
1341
1412
  var le = (r2 = "", n = "") => {
1342
1413
  const i = `
@@ -1356,7 +1427,7 @@ var ue = (r2 = "") => {
1356
1427
  `);
1357
1428
  };
1358
1429
  var oe = (r2 = "") => {
1359
- process.stdout.write(`${import_picocolors2.default.gray(K)} ${r2}
1430
+ process.stdout.write(`${import_picocolors2.default.gray(K2)} ${r2}
1360
1431
  `);
1361
1432
  };
1362
1433
  var $e = (r2 = "") => {
@@ -1424,9 +1495,9 @@ var import_picocolors4 = __toESM(require_picocolors(), 1);
1424
1495
  var import_picocolors3 = __toESM(require_picocolors(), 1);
1425
1496
  function showError(message, fix) {
1426
1497
  console.log();
1427
- f2.error(import_picocolors3.default.red("✗") + " " + message);
1498
+ f2.error(`${import_picocolors3.default.red("✗")} ${message}`);
1428
1499
  if (fix) {
1429
- f2.info(" " + import_picocolors3.default.dim("→ " + fix));
1500
+ f2.info(` ${import_picocolors3.default.dim(`→ ${fix}`)}`);
1430
1501
  }
1431
1502
  console.log();
1432
1503
  }
@@ -1437,16 +1508,13 @@ function notFound(resource, name) {
1437
1508
  const msg = name ? `${resource} "${name}" not found` : `${resource} not found`;
1438
1509
  showError(msg);
1439
1510
  }
1440
- function alreadyExists(resource, name) {
1441
- showError(`${resource} "${name}" already exists`);
1442
- }
1443
1511
  function unknownAction(action, available) {
1444
1512
  showError(`Unknown action: ${action}`, `Available: ${available.map((a3) => import_picocolors3.default.cyan(a3)).join(", ")}`);
1445
1513
  }
1446
1514
  function showWarning(message, suggestion) {
1447
- f2.warn(import_picocolors3.default.yellow("!") + " " + message);
1515
+ f2.warn(`${import_picocolors3.default.yellow("!")} ${message}`);
1448
1516
  if (suggestion) {
1449
- f2.info(" " + import_picocolors3.default.dim("→ " + suggestion));
1517
+ f2.info(` ${import_picocolors3.default.dim(`→ ${suggestion}`)}`);
1450
1518
  }
1451
1519
  }
1452
1520
  function showEmpty(resource, createCmd) {
@@ -1457,18 +1525,6 @@ function showEmpty(resource, createCmd) {
1457
1525
  }
1458
1526
 
1459
1527
  // src/commands/agent.ts
1460
- var AGENT_TEMPLATE = `# {{NAME}} Agent
1461
-
1462
- ## Role
1463
- Describe what this agent does.
1464
-
1465
- ## Capabilities
1466
- - Capability 1
1467
- - Capability 2
1468
-
1469
- ## Instructions
1470
- Provide specific instructions for this agent.
1471
- `;
1472
1528
  async function agentCommand(action) {
1473
1529
  const opencodePath = join(process.cwd(), ".opencode");
1474
1530
  if (!existsSync(opencodePath)) {
@@ -1480,23 +1536,54 @@ async function agentCommand(action) {
1480
1536
  case "list":
1481
1537
  await listAgents(agentPath);
1482
1538
  break;
1539
+ case "create":
1540
+ await createAgent(agentPath);
1541
+ break;
1483
1542
  case "add":
1484
- await addAgent(agentPath);
1543
+ await createAgent(agentPath);
1485
1544
  break;
1486
1545
  case "view": {
1487
1546
  const agentName = process.argv[4];
1488
1547
  await viewAgent(agentPath, agentName);
1489
1548
  break;
1490
1549
  }
1550
+ case "remove": {
1551
+ const agentName = process.argv[4];
1552
+ await removeAgent(agentPath, agentName);
1553
+ break;
1554
+ }
1491
1555
  default:
1492
- unknownAction(action, ["list", "add", "view"]);
1556
+ unknownAction(action, ["list", "create", "view", "remove"]);
1493
1557
  }
1494
1558
  }
1495
- async function listAgents(agentPath) {
1496
- if (!existsSync(agentPath)) {
1497
- showEmpty("agents", "ock agent add");
1498
- return;
1559
+ function parseFrontmatter(content) {
1560
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
1561
+ if (!match)
1562
+ return {};
1563
+ const frontmatter = {};
1564
+ const lines = match[1].split(`
1565
+ `);
1566
+ for (const line of lines) {
1567
+ const colonIndex = line.indexOf(":");
1568
+ if (colonIndex > 0) {
1569
+ const key = line.slice(0, colonIndex).trim();
1570
+ let value = line.slice(colonIndex + 1).trim();
1571
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1572
+ value = value.slice(1, -1);
1573
+ }
1574
+ if (value.startsWith("[") && value.endsWith("]")) {
1575
+ const items = value.slice(1, -1).split(",").map((s) => s.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
1576
+ frontmatter[key] = items;
1577
+ } else {
1578
+ frontmatter[key] = value;
1579
+ }
1580
+ }
1499
1581
  }
1582
+ return frontmatter;
1583
+ }
1584
+ function collectAgents(agentPath) {
1585
+ if (!existsSync(agentPath))
1586
+ return [];
1500
1587
  const opencodePath = join(process.cwd(), ".opencode");
1501
1588
  const configPath = join(opencodePath, "opencode.json");
1502
1589
  let agentConfigs = {};
@@ -1508,38 +1595,48 @@ async function listAgents(agentPath) {
1508
1595
  } catch {}
1509
1596
  }
1510
1597
  const entries = readdirSync(agentPath);
1511
- const agents = entries.filter((entry) => {
1598
+ const agents = [];
1599
+ for (const entry of entries) {
1512
1600
  const entryPath = join(agentPath, entry);
1513
- return lstatSync(entryPath).isFile() && entry.endsWith(".md");
1514
- }).map((file) => file.replace(".md", ""));
1601
+ if (!lstatSync(entryPath).isFile() || !entry.endsWith(".md"))
1602
+ continue;
1603
+ const name = entry.replace(".md", "");
1604
+ const content = readFileSync(entryPath, "utf-8");
1605
+ const frontmatter = parseFrontmatter(content);
1606
+ agents.push({
1607
+ name,
1608
+ path: entryPath,
1609
+ frontmatter,
1610
+ disabled: agentConfigs[name]?.disable,
1611
+ configModel: agentConfigs[name]?.model
1612
+ });
1613
+ }
1614
+ return agents.sort((a3, b3) => a3.name.localeCompare(b3.name));
1615
+ }
1616
+ async function listAgents(agentPath) {
1617
+ const agents = collectAgents(agentPath);
1515
1618
  if (agents.length === 0) {
1516
- showEmpty("agents", "ock agent add");
1619
+ showEmpty("agents", "ock agent create");
1517
1620
  return;
1518
1621
  }
1519
1622
  f2.info(import_picocolors4.default.bold("Agents"));
1520
1623
  for (const agent of agents) {
1521
- let model = agentConfigs[agent]?.model;
1522
- const disabled = agentConfigs[agent]?.disable;
1523
- if (!model) {
1524
- const agentFile = join(agentPath, `${agent}.md`);
1525
- try {
1526
- const content = readFileSync(agentFile, "utf-8");
1527
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
1528
- if (frontmatterMatch) {
1529
- const modelMatch = frontmatterMatch[1].match(/^model:\s*(.+)$/m);
1530
- if (modelMatch) {
1531
- model = modelMatch[1].trim();
1532
- }
1533
- }
1534
- } catch {}
1535
- }
1624
+ const model = agent.configModel || agent.frontmatter.model;
1625
+ const mode = agent.frontmatter.mode;
1626
+ const modeDisplay = mode ? import_picocolors4.default.yellow(`[${mode}]`) : "";
1536
1627
  const modelDisplay = model ? import_picocolors4.default.dim(` → ${model}`) : import_picocolors4.default.dim(" → (default)");
1537
- const disabledDisplay = disabled ? import_picocolors4.default.red(" [disabled]") : "";
1538
- console.log(` ${import_picocolors4.default.cyan("•")} ${agent}${modelDisplay}${disabledDisplay}`);
1628
+ const disabledDisplay = agent.disabled ? import_picocolors4.default.red(" [disabled]") : "";
1629
+ const descDisplay = agent.frontmatter.description ? import_picocolors4.default.dim(` - ${agent.frontmatter.description.slice(0, 40)}${agent.frontmatter.description.length > 40 ? "..." : ""}`) : "";
1630
+ console.log(` ${import_picocolors4.default.cyan("•")} ${agent.name} ${modeDisplay}${modelDisplay}${disabledDisplay}${descDisplay}`);
1539
1631
  }
1632
+ console.log();
1633
+ f2.info(import_picocolors4.default.dim(`Found ${agents.length} agent${agents.length === 1 ? "" : "s"}`));
1540
1634
  }
1541
- async function addAgent(agentPath) {
1542
- oe(import_picocolors4.default.bgMagenta(import_picocolors4.default.black(" Add Agent ")));
1635
+ async function createAgent(agentPath) {
1636
+ oe(import_picocolors4.default.bgMagenta(import_picocolors4.default.black(" Create Agent ")));
1637
+ if (!existsSync(agentPath)) {
1638
+ mkdirSync(agentPath, { recursive: true });
1639
+ }
1543
1640
  const name = await te({
1544
1641
  message: "Agent name",
1545
1642
  placeholder: "e.g. reviewer, planner, researcher",
@@ -1549,162 +1646,566 @@ async function addAgent(agentPath) {
1549
1646
  if (!/^[a-z][a-z0-9-]*$/.test(value)) {
1550
1647
  return "Use lowercase letters, numbers, and hyphens only";
1551
1648
  }
1649
+ if (existsSync(join(agentPath, `${value}.md`))) {
1650
+ return "Agent already exists";
1651
+ }
1652
+ return;
1552
1653
  }
1553
1654
  });
1554
1655
  if (lD(name)) {
1555
1656
  ue("Cancelled");
1556
1657
  return;
1557
1658
  }
1558
- const agentType = await ie({
1559
- message: "Agent type",
1659
+ const description = await te({
1660
+ message: "Description (what does this agent do?)",
1661
+ placeholder: "Reviews code for security issues and best practices",
1662
+ validate: (value) => {
1663
+ if (!value)
1664
+ return "Description is required";
1665
+ return;
1666
+ }
1667
+ });
1668
+ if (lD(description)) {
1669
+ ue("Cancelled");
1670
+ return;
1671
+ }
1672
+ const mode = await ie({
1673
+ message: "Agent mode",
1560
1674
  options: [
1561
- { value: "primary", label: "Primary agent" },
1562
- { value: "subagent", label: "Subagent" }
1675
+ {
1676
+ value: "subagent",
1677
+ label: "subagent",
1678
+ hint: "Invoked by primary agents via Task tool"
1679
+ },
1680
+ {
1681
+ value: "primary",
1682
+ label: "primary",
1683
+ hint: "Main agent that orchestrates work"
1684
+ },
1685
+ {
1686
+ value: "all",
1687
+ label: "all",
1688
+ hint: "Can be used in both modes"
1689
+ }
1563
1690
  ]
1564
1691
  });
1565
- if (lD(agentType)) {
1692
+ if (lD(mode)) {
1566
1693
  ue("Cancelled");
1567
1694
  return;
1568
1695
  }
1569
- if (!existsSync(agentPath)) {
1570
- mkdirSync(agentPath, { recursive: true });
1571
- }
1572
- const agentFile = join(agentPath, `${name}.md`);
1573
- if (existsSync(agentFile)) {
1574
- alreadyExists("Agent", String(name));
1575
- $e(import_picocolors4.default.red("Failed"));
1696
+ const toolsInput = await te({
1697
+ message: "Tools this agent can use (comma-separated, or empty for all)",
1698
+ placeholder: "read, glob, grep, bash"
1699
+ });
1700
+ if (lD(toolsInput)) {
1701
+ ue("Cancelled");
1576
1702
  return;
1577
1703
  }
1578
- const content = AGENT_TEMPLATE.replace("{{NAME}}", String(name).charAt(0).toUpperCase() + String(name).slice(1));
1579
- writeFileSync(agentFile, content);
1704
+ const tools = toolsInput ? String(toolsInput).split(",").map((t) => t.trim()).filter(Boolean) : [];
1705
+ const frontmatterLines = [`mode: ${mode}`, `description: "${description}"`];
1706
+ if (tools.length > 0) {
1707
+ frontmatterLines.push(`tools: [${tools.map((t) => `"${t}"`).join(", ")}]`);
1708
+ }
1709
+ const displayName = String(name).split("-").map((w3) => w3.charAt(0).toUpperCase() + w3.slice(1)).join(" ");
1710
+ const template = `---
1711
+ ${frontmatterLines.join(`
1712
+ `)}
1713
+ ---
1714
+
1715
+ # ${displayName} Agent
1716
+
1717
+ ${description}
1718
+
1719
+ ## Role
1720
+
1721
+ Describe the specific role and responsibilities of this agent.
1722
+
1723
+ ## Capabilities
1724
+
1725
+ - Capability 1
1726
+ - Capability 2
1727
+ - Capability 3
1728
+
1729
+ ## Instructions
1730
+
1731
+ Provide specific instructions for how this agent should behave:
1732
+
1733
+ 1. First, understand the context
1734
+ 2. Then, analyze the problem
1735
+ 3. Finally, provide a solution
1736
+
1737
+ ## Constraints
1738
+
1739
+ - What this agent should NOT do
1740
+ - Boundaries and limitations
1741
+
1742
+ ## Output Format
1743
+
1744
+ Describe the expected output format for this agent.
1745
+ `;
1746
+ const agentFile = join(agentPath, `${name}.md`);
1747
+ writeFileSync(agentFile, template);
1580
1748
  f2.success(`Created ${import_picocolors4.default.cyan(`.opencode/agent/${name}.md`)}`);
1581
- if (agentType === "primary") {
1749
+ if (mode === "primary") {
1582
1750
  le(`Add to opencode.json:
1583
1751
 
1584
- "agents": {
1752
+ "agent": {
1585
1753
  "${name}": {
1586
- "model": "...",
1587
- "systemPrompt": ".opencode/agent/${name}.md"
1754
+ "model": "claude-sonnet-4-20250514"
1588
1755
  }
1589
- }`, "Next step");
1756
+ }`, "Configure model (optional)");
1590
1757
  }
1591
1758
  $e(import_picocolors4.default.green("Done! Edit the file to customize."));
1592
1759
  }
1593
- async function viewAgent(agentPath, agentName) {
1760
+ async function viewAgent(agentPath, agentNameArg) {
1761
+ const agents = collectAgents(agentPath);
1762
+ if (agents.length === 0) {
1763
+ showEmpty("agents", "ock agent create");
1764
+ return;
1765
+ }
1766
+ let agentName = agentNameArg;
1594
1767
  if (!agentName) {
1595
- if (!existsSync(agentPath)) {
1596
- showEmpty("agents", "ock agent add");
1597
- return;
1598
- }
1599
- const entries = readdirSync(agentPath);
1600
- const agents = entries.filter((entry) => {
1601
- const entryPath = join(agentPath, entry);
1602
- return lstatSync(entryPath).isFile() && entry.endsWith(".md");
1603
- }).map((file) => file.replace(".md", ""));
1604
- if (agents.length === 0) {
1605
- showEmpty("agents", "ock agent add");
1606
- return;
1607
- }
1768
+ const options = agents.map((a3) => ({
1769
+ value: a3.name,
1770
+ label: a3.name,
1771
+ hint: a3.frontmatter.description || ""
1772
+ }));
1608
1773
  const selected = await ie({
1609
1774
  message: "Select agent to view",
1610
- options: agents.map((a3) => ({ value: a3, label: a3 }))
1775
+ options
1611
1776
  });
1612
1777
  if (lD(selected)) {
1613
1778
  return;
1614
1779
  }
1615
1780
  agentName = selected;
1616
1781
  }
1617
- const agentFile = join(agentPath, `${agentName}.md`);
1618
- if (!existsSync(agentFile)) {
1782
+ const agent = agents.find((a3) => a3.name === agentName);
1783
+ if (!agent) {
1619
1784
  notFound("Agent", agentName);
1620
1785
  return;
1621
1786
  }
1622
- const content = readFileSync(agentFile, "utf-8");
1787
+ const content = readFileSync(agent.path, "utf-8");
1623
1788
  console.log();
1624
- console.log(import_picocolors4.default.dim("─".repeat(40)));
1789
+ console.log(import_picocolors4.default.dim("─".repeat(60)));
1625
1790
  console.log(content);
1626
- console.log(import_picocolors4.default.dim("─".repeat(40)));
1791
+ console.log(import_picocolors4.default.dim("─".repeat(60)));
1792
+ }
1793
+ async function removeAgent(agentPath, agentNameArg) {
1794
+ const agents = collectAgents(agentPath);
1795
+ if (agents.length === 0) {
1796
+ showEmpty("agents", "ock agent create");
1797
+ return;
1798
+ }
1799
+ let agentName = agentNameArg;
1800
+ if (!agentName) {
1801
+ const options = agents.map((a3) => ({
1802
+ value: a3.name,
1803
+ label: a3.name,
1804
+ hint: a3.frontmatter.description || ""
1805
+ }));
1806
+ const selected = await ie({
1807
+ message: "Select agent to remove",
1808
+ options
1809
+ });
1810
+ if (lD(selected)) {
1811
+ return;
1812
+ }
1813
+ agentName = selected;
1814
+ }
1815
+ const agent = agents.find((a3) => a3.name === agentName);
1816
+ if (!agent) {
1817
+ notFound("Agent", agentName);
1818
+ return;
1819
+ }
1820
+ const confirm = await se({
1821
+ message: `Remove agent "${agent.name}"?`,
1822
+ initialValue: false
1823
+ });
1824
+ if (lD(confirm) || !confirm) {
1825
+ ue("Cancelled");
1826
+ return;
1827
+ }
1828
+ unlinkSync(agent.path);
1829
+ f2.success(`Removed agent "${agent.name}"`);
1830
+ const opencodePath = join(process.cwd(), ".opencode");
1831
+ const configPath = join(opencodePath, "opencode.json");
1832
+ if (existsSync(configPath)) {
1833
+ try {
1834
+ const configContent = readFileSync(configPath, "utf-8");
1835
+ const config = JSON.parse(configContent);
1836
+ if (config.agent?.[agent.name]) {
1837
+ f2.warn(import_picocolors4.default.yellow(`Note: Agent "${agent.name}" is still configured in opencode.json`));
1838
+ }
1839
+ } catch {}
1840
+ }
1627
1841
  }
1628
1842
 
1629
- // src/commands/completion.ts
1843
+ // src/commands/command.ts
1844
+ import {
1845
+ existsSync as existsSync2,
1846
+ mkdirSync as mkdirSync2,
1847
+ readFileSync as readFileSync2,
1848
+ readdirSync as readdirSync2,
1849
+ unlinkSync as unlinkSync2,
1850
+ writeFileSync as writeFileSync2
1851
+ } from "node:fs";
1852
+ import { basename, join as join2 } from "node:path";
1630
1853
  var import_picocolors5 = __toESM(require_picocolors(), 1);
1631
- var BASH_COMPLETION = `# ock bash completion
1632
- _ock_completion() {
1633
- local cur prev commands
1634
- COMPREPLY=()
1635
- cur="\${COMP_WORDS[COMP_CWORD]}"
1636
- prev="\${COMP_WORDS[COMP_CWORD-1]}"
1637
-
1638
- commands="init config upgrade agent skill doctor status help"
1639
-
1640
- case "\${prev}" in
1641
- ock)
1642
- COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
1643
- return 0
1644
- ;;
1645
- agent)
1646
- COMPREPLY=( $(compgen -W "list add view" -- "\${cur}") )
1647
- return 0
1648
- ;;
1649
- skill)
1650
- COMPREPLY=( $(compgen -W "list add view" -- "\${cur}") )
1651
- return 0
1652
- ;;
1653
- config)
1654
- COMPREPLY=( $(compgen -W "model mcp permission keybinds show" -- "\${cur}") )
1655
- return 0
1656
- ;;
1657
- init)
1658
- COMPREPLY=( $(compgen -W "--force" -- "\${cur}") )
1659
- return 0
1660
- ;;
1661
- upgrade)
1662
- COMPREPLY=( $(compgen -W "--force --check" -- "\${cur}") )
1663
- return 0
1664
- ;;
1665
- *)
1666
- ;;
1667
- esac
1854
+ async function commandCommand(action) {
1855
+ const opencodePath = join2(process.cwd(), ".opencode");
1856
+ if (!existsSync2(opencodePath)) {
1857
+ notInitialized();
1858
+ return;
1859
+ }
1860
+ const commandDir = join2(opencodePath, "command");
1861
+ switch (action) {
1862
+ case "list":
1863
+ listCommands(commandDir);
1864
+ break;
1865
+ case "create":
1866
+ await createCommand(commandDir);
1867
+ break;
1868
+ case "view": {
1869
+ const cmdName = process.argv[4];
1870
+ await viewCommand(commandDir, cmdName);
1871
+ break;
1872
+ }
1873
+ case "remove": {
1874
+ const cmdName = process.argv[4];
1875
+ await removeCommand(commandDir, cmdName);
1876
+ break;
1877
+ }
1878
+ default:
1879
+ unknownAction(action, ["list", "create", "view", "remove"]);
1880
+ }
1668
1881
  }
1669
- complete -F _ock_completion ock
1670
- `;
1671
- var ZSH_COMPLETION = `#compdef ock
1672
-
1673
- _ock() {
1674
- local -a commands
1675
- local -a agent_actions
1676
- local -a skill_actions
1677
- local -a config_actions
1678
-
1679
- commands=(
1680
- 'init:Initialize OpenCodeKit in current directory'
1681
- 'config:Edit opencode.json'
1682
- 'upgrade:Update templates to latest version'
1683
- 'agent:Manage agents'
1684
- 'skill:Manage skills'
1685
- 'doctor:Check project health'
1686
- 'status:Show project overview'
1687
- 'help:Show help'
1688
- )
1689
-
1690
- agent_actions=(
1691
- 'list:List all agents'
1692
- 'add:Create a new agent'
1693
- 'view:View agent details'
1694
- )
1695
-
1696
- skill_actions=(
1697
- 'list:List all skills'
1698
- 'add:Install a skill'
1699
- 'view:View skill details'
1700
- )
1701
-
1702
- config_actions=(
1703
- 'model:Change default model'
1704
- 'mcp:Manage MCP servers'
1705
- 'permission:Edit permissions'
1706
- 'keybinds:Edit keyboard shortcuts'
1707
- 'show:View current config'
1882
+ function parseFrontmatter2(content) {
1883
+ const match = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
1884
+ if (!match) {
1885
+ return { frontmatter: {}, body: content };
1886
+ }
1887
+ const frontmatter = {};
1888
+ const lines = match[1].split(`
1889
+ `);
1890
+ for (const line of lines) {
1891
+ const colonIndex = line.indexOf(":");
1892
+ if (colonIndex > 0) {
1893
+ const key = line.slice(0, colonIndex).trim();
1894
+ let value = line.slice(colonIndex + 1).trim();
1895
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1896
+ value = value.slice(1, -1);
1897
+ }
1898
+ if (value === "true")
1899
+ value = true;
1900
+ if (value === "false")
1901
+ value = false;
1902
+ frontmatter[key] = value;
1903
+ }
1904
+ }
1905
+ return { frontmatter, body: match[2] };
1906
+ }
1907
+ function listCommands(commandDir) {
1908
+ if (!existsSync2(commandDir)) {
1909
+ showEmpty("slash commands", "ock command create");
1910
+ return;
1911
+ }
1912
+ const files = readdirSync2(commandDir).filter((f3) => f3.endsWith(".md"));
1913
+ if (files.length === 0) {
1914
+ showEmpty("slash commands", "ock command create");
1915
+ return;
1916
+ }
1917
+ f2.info(import_picocolors5.default.bold("Slash Commands"));
1918
+ for (const file of files.sort()) {
1919
+ const name = basename(file, ".md");
1920
+ const content = readFileSync2(join2(commandDir, file), "utf-8");
1921
+ const { frontmatter } = parseFrontmatter2(content);
1922
+ const agent = frontmatter.agent ? import_picocolors5.default.dim(`[${frontmatter.agent}]`) : import_picocolors5.default.dim("[build]");
1923
+ const desc = frontmatter.description ? import_picocolors5.default.dim(` - ${frontmatter.description}`) : "";
1924
+ const hint = frontmatter["argument-hint"] ? import_picocolors5.default.yellow(` ${frontmatter["argument-hint"]}`) : "";
1925
+ const subtask = frontmatter.subtask ? import_picocolors5.default.magenta(" (subtask)") : "";
1926
+ console.log(` ${import_picocolors5.default.cyan(`/${name}`)}${hint}${subtask} ${agent}${desc}`);
1927
+ }
1928
+ console.log();
1929
+ f2.info(import_picocolors5.default.dim(`Found ${files.length} command${files.length === 1 ? "" : "s"}`));
1930
+ }
1931
+ async function createCommand(commandDir) {
1932
+ oe(import_picocolors5.default.bgCyan(import_picocolors5.default.black(" Create Slash Command ")));
1933
+ if (!existsSync2(commandDir)) {
1934
+ mkdirSync2(commandDir, { recursive: true });
1935
+ }
1936
+ const name = await te({
1937
+ message: "Command name (without /)",
1938
+ placeholder: "my-command",
1939
+ validate: (value) => {
1940
+ if (!value)
1941
+ return "Name is required";
1942
+ if (!/^[a-z][a-z0-9-]*$/.test(value)) {
1943
+ return "Use lowercase letters, numbers, and hyphens only";
1944
+ }
1945
+ if (existsSync2(join2(commandDir, `${value}.md`))) {
1946
+ return "Command already exists";
1947
+ }
1948
+ return;
1949
+ }
1950
+ });
1951
+ if (lD(name)) {
1952
+ ue("Cancelled");
1953
+ return;
1954
+ }
1955
+ const description = await te({
1956
+ message: "Description",
1957
+ placeholder: "What does this command do?"
1958
+ });
1959
+ if (lD(description)) {
1960
+ ue("Cancelled");
1961
+ return;
1962
+ }
1963
+ const agent = await ie({
1964
+ message: "Which agent runs this command?",
1965
+ options: [
1966
+ { value: "build", label: "build", hint: "Implementation work" },
1967
+ { value: "planner", label: "planner", hint: "Architecture & design" },
1968
+ { value: "scout", label: "scout", hint: "External research" },
1969
+ { value: "explore", label: "explore", hint: "Codebase search" },
1970
+ { value: "review", label: "review", hint: "Code review & debugging" },
1971
+ { value: "vision", label: "vision", hint: "UI/UX & visual analysis" }
1972
+ ]
1973
+ });
1974
+ if (lD(agent)) {
1975
+ ue("Cancelled");
1976
+ return;
1977
+ }
1978
+ const argumentHint = await te({
1979
+ message: "Argument hint (optional)",
1980
+ placeholder: "<required> [optional]"
1981
+ });
1982
+ if (lD(argumentHint)) {
1983
+ ue("Cancelled");
1984
+ return;
1985
+ }
1986
+ const isSubtask = await se({
1987
+ message: "Run as subtask? (doesn't pollute primary context)",
1988
+ initialValue: false
1989
+ });
1990
+ if (lD(isSubtask)) {
1991
+ ue("Cancelled");
1992
+ return;
1993
+ }
1994
+ const frontmatterLines = [`description: "${description}"`];
1995
+ if (argumentHint) {
1996
+ frontmatterLines.push(`argument-hint: "${argumentHint}"`);
1997
+ }
1998
+ frontmatterLines.push(`agent: ${agent}`);
1999
+ if (isSubtask) {
2000
+ frontmatterLines.push("subtask: true");
2001
+ }
2002
+ const template = `---
2003
+ ${frontmatterLines.join(`
2004
+ `)}
2005
+ ---
2006
+
2007
+ # ${String(name).charAt(0).toUpperCase() + String(name).slice(1).replace(/-/g, " ")}: $ARGUMENTS
2008
+
2009
+ ${description}
2010
+
2011
+ ## Context
2012
+
2013
+ \`\`\`typescript
2014
+ // Load any required skills
2015
+ // skill({ name: "example" });
2016
+ \`\`\`
2017
+
2018
+ ## Instructions
2019
+
2020
+ <!-- Add your command instructions here -->
2021
+
2022
+ 1. Step one
2023
+ 2. Step two
2024
+ 3. Step three
2025
+
2026
+ ## Output
2027
+
2028
+ <!-- Define expected output format -->
2029
+ `;
2030
+ const filePath = join2(commandDir, `${name}.md`);
2031
+ writeFileSync2(filePath, template);
2032
+ $e(import_picocolors5.default.green(`Created /${name} at .opencode/command/${name}.md`));
2033
+ }
2034
+ async function viewCommand(commandDir, cmdNameArg) {
2035
+ if (!existsSync2(commandDir)) {
2036
+ showEmpty("slash commands", "ock command create");
2037
+ return;
2038
+ }
2039
+ const files = readdirSync2(commandDir).filter((f3) => f3.endsWith(".md"));
2040
+ if (files.length === 0) {
2041
+ showEmpty("slash commands", "ock command create");
2042
+ return;
2043
+ }
2044
+ let cmdName = cmdNameArg;
2045
+ if (!cmdName) {
2046
+ const options = files.sort().map((f3) => {
2047
+ const name = basename(f3, ".md");
2048
+ const content2 = readFileSync2(join2(commandDir, f3), "utf-8");
2049
+ const { frontmatter } = parseFrontmatter2(content2);
2050
+ return {
2051
+ value: name,
2052
+ label: `/${name}`,
2053
+ hint: frontmatter.description || ""
2054
+ };
2055
+ });
2056
+ const selected = await ie({
2057
+ message: "Select command to view",
2058
+ options
2059
+ });
2060
+ if (lD(selected)) {
2061
+ return;
2062
+ }
2063
+ cmdName = selected;
2064
+ }
2065
+ if (cmdName.startsWith("/")) {
2066
+ cmdName = cmdName.slice(1);
2067
+ }
2068
+ const filePath = join2(commandDir, `${cmdName}.md`);
2069
+ if (!existsSync2(filePath)) {
2070
+ f2.error(`Command /${cmdName} not found`);
2071
+ return;
2072
+ }
2073
+ const content = readFileSync2(filePath, "utf-8");
2074
+ console.log();
2075
+ console.log(import_picocolors5.default.dim("─".repeat(60)));
2076
+ console.log(content);
2077
+ console.log(import_picocolors5.default.dim("─".repeat(60)));
2078
+ }
2079
+ async function removeCommand(commandDir, cmdNameArg) {
2080
+ if (!existsSync2(commandDir)) {
2081
+ showEmpty("slash commands", "ock command create");
2082
+ return;
2083
+ }
2084
+ const files = readdirSync2(commandDir).filter((f3) => f3.endsWith(".md"));
2085
+ if (files.length === 0) {
2086
+ showEmpty("slash commands", "ock command create");
2087
+ return;
2088
+ }
2089
+ let cmdName = cmdNameArg;
2090
+ if (!cmdName) {
2091
+ const options = files.sort().map((f3) => {
2092
+ const name = basename(f3, ".md");
2093
+ const content = readFileSync2(join2(commandDir, f3), "utf-8");
2094
+ const { frontmatter } = parseFrontmatter2(content);
2095
+ return {
2096
+ value: name,
2097
+ label: `/${name}`,
2098
+ hint: frontmatter.description || ""
2099
+ };
2100
+ });
2101
+ const selected = await ie({
2102
+ message: "Select command to remove",
2103
+ options
2104
+ });
2105
+ if (lD(selected)) {
2106
+ return;
2107
+ }
2108
+ cmdName = selected;
2109
+ }
2110
+ if (cmdName.startsWith("/")) {
2111
+ cmdName = cmdName.slice(1);
2112
+ }
2113
+ const filePath = join2(commandDir, `${cmdName}.md`);
2114
+ if (!existsSync2(filePath)) {
2115
+ f2.error(`Command /${cmdName} not found`);
2116
+ return;
2117
+ }
2118
+ const confirm = await se({
2119
+ message: `Remove /${cmdName}?`,
2120
+ initialValue: false
2121
+ });
2122
+ if (lD(confirm) || !confirm) {
2123
+ ue("Cancelled");
2124
+ return;
2125
+ }
2126
+ unlinkSync2(filePath);
2127
+ f2.success(`Removed /${cmdName}`);
2128
+ }
2129
+
2130
+ // src/commands/completion.ts
2131
+ var import_picocolors6 = __toESM(require_picocolors(), 1);
2132
+ var BASH_COMPLETION = `# ock bash completion
2133
+ _ock_completion() {
2134
+ local cur prev commands
2135
+ COMPREPLY=()
2136
+ cur="\${COMP_WORDS[COMP_CWORD]}"
2137
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
2138
+
2139
+ commands="init config upgrade agent skill doctor status help"
2140
+
2141
+ case "\${prev}" in
2142
+ ock)
2143
+ COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
2144
+ return 0
2145
+ ;;
2146
+ agent)
2147
+ COMPREPLY=( $(compgen -W "list add view" -- "\${cur}") )
2148
+ return 0
2149
+ ;;
2150
+ skill)
2151
+ COMPREPLY=( $(compgen -W "list add view" -- "\${cur}") )
2152
+ return 0
2153
+ ;;
2154
+ config)
2155
+ COMPREPLY=( $(compgen -W "model mcp permission keybinds show" -- "\${cur}") )
2156
+ return 0
2157
+ ;;
2158
+ init)
2159
+ COMPREPLY=( $(compgen -W "--force" -- "\${cur}") )
2160
+ return 0
2161
+ ;;
2162
+ upgrade)
2163
+ COMPREPLY=( $(compgen -W "--force --check" -- "\${cur}") )
2164
+ return 0
2165
+ ;;
2166
+ *)
2167
+ ;;
2168
+ esac
2169
+ }
2170
+ complete -F _ock_completion ock
2171
+ `;
2172
+ var ZSH_COMPLETION = `#compdef ock
2173
+
2174
+ _ock() {
2175
+ local -a commands
2176
+ local -a agent_actions
2177
+ local -a skill_actions
2178
+ local -a config_actions
2179
+
2180
+ commands=(
2181
+ 'init:Initialize OpenCodeKit in current directory'
2182
+ 'config:Edit opencode.json'
2183
+ 'upgrade:Update templates to latest version'
2184
+ 'agent:Manage agents'
2185
+ 'skill:Manage skills'
2186
+ 'doctor:Check project health'
2187
+ 'status:Show project overview'
2188
+ 'help:Show help'
2189
+ )
2190
+
2191
+ agent_actions=(
2192
+ 'list:List all agents'
2193
+ 'add:Create a new agent'
2194
+ 'view:View agent details'
2195
+ )
2196
+
2197
+ skill_actions=(
2198
+ 'list:List all skills'
2199
+ 'add:Install a skill'
2200
+ 'view:View skill details'
2201
+ )
2202
+
2203
+ config_actions=(
2204
+ 'model:Change default model'
2205
+ 'mcp:Manage MCP servers'
2206
+ 'permission:Edit permissions'
2207
+ 'keybinds:Edit keyboard shortcuts'
2208
+ 'show:View current config'
1708
2209
  )
1709
2210
 
1710
2211
  case "$words[2]" in
@@ -1767,8 +2268,9 @@ complete -c ock -n "__fish_seen_subcommand_from upgrade" -l force -d "Force upgr
1767
2268
  complete -c ock -n "__fish_seen_subcommand_from upgrade" -l check -d "Check only"
1768
2269
  `;
1769
2270
  async function completionCommand(shell) {
1770
- if (!shell) {
1771
- oe(import_picocolors5.default.bgCyan(import_picocolors5.default.black(" Shell Completion ")));
2271
+ let selectedShell = shell;
2272
+ if (!selectedShell) {
2273
+ oe(import_picocolors6.default.bgCyan(import_picocolors6.default.black(" Shell Completion ")));
1772
2274
  const selected = await ie({
1773
2275
  message: "Select your shell",
1774
2276
  options: [
@@ -1781,11 +2283,11 @@ async function completionCommand(shell) {
1781
2283
  ue("Cancelled");
1782
2284
  return;
1783
2285
  }
1784
- shell = selected;
2286
+ selectedShell = selected;
1785
2287
  }
1786
2288
  let script;
1787
2289
  let setupInstructions;
1788
- switch (shell) {
2290
+ switch (selectedShell) {
1789
2291
  case "bash":
1790
2292
  script = BASH_COMPLETION;
1791
2293
  setupInstructions = `Add to ~/.bashrc:
@@ -1814,14 +2316,14 @@ Or save to fpath:
1814
2316
  }
1815
2317
  console.log(script);
1816
2318
  console.error();
1817
- console.error(import_picocolors5.default.bold("Setup:"));
1818
- console.error(import_picocolors5.default.dim(setupInstructions));
2319
+ console.error(import_picocolors6.default.bold("Setup:"));
2320
+ console.error(import_picocolors6.default.dim(setupInstructions));
1819
2321
  }
1820
2322
 
1821
2323
  // src/commands/config.ts
1822
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
1823
- import { join as join2 } from "node:path";
1824
- var import_picocolors6 = __toESM(require_picocolors(), 1);
2324
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
2325
+ import { join as join3 } from "node:path";
2326
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
1825
2327
  var DEFAULT_SERVER_PORT = 4096;
1826
2328
  var MODELS_DEV_URL = "https://models.dev/api.json";
1827
2329
  async function fetchFromServer(endpoint) {
@@ -1851,13 +2353,13 @@ async function getAgentsFromServer() {
1851
2353
  return fetchFromServer("/agent");
1852
2354
  }
1853
2355
  async function configCommand(action) {
1854
- const opencodePath = join2(process.cwd(), ".opencode");
1855
- const configPath = join2(opencodePath, "opencode.json");
1856
- if (!existsSync2(opencodePath)) {
2356
+ const opencodePath = join3(process.cwd(), ".opencode");
2357
+ const configPath = join3(opencodePath, "opencode.json");
2358
+ if (!existsSync3(opencodePath)) {
1857
2359
  notInitialized();
1858
2360
  return;
1859
2361
  }
1860
- if (!existsSync2(configPath)) {
2362
+ if (!existsSync3(configPath)) {
1861
2363
  showError("opencode.json not found", "ock init --force");
1862
2364
  return;
1863
2365
  }
@@ -1896,6 +2398,9 @@ async function configCommand(action) {
1896
2398
  case "show":
1897
2399
  showConfig(configPath);
1898
2400
  break;
2401
+ case "validate":
2402
+ await validateConfig(configPath);
2403
+ break;
1899
2404
  default:
1900
2405
  unknownAction(action, [
1901
2406
  "model",
@@ -1907,71 +2412,255 @@ async function configCommand(action) {
1907
2412
  "tools",
1908
2413
  "share",
1909
2414
  "autoupdate",
1910
- "show"
2415
+ "show",
2416
+ "validate"
1911
2417
  ]);
1912
2418
  }
1913
2419
  }
1914
2420
  function loadConfig(configPath) {
1915
- const content = readFileSync2(configPath, "utf-8");
2421
+ const content = readFileSync3(configPath, "utf-8");
1916
2422
  const jsonWithoutComments = content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
1917
2423
  return JSON.parse(jsonWithoutComments);
1918
2424
  }
1919
2425
  function saveConfig(configPath, config) {
1920
- writeFileSync2(configPath, JSON.stringify(config, null, 2) + `
2426
+ writeFileSync3(configPath, `${JSON.stringify(config, null, 2)}
1921
2427
  `);
1922
2428
  }
2429
+ var OPENCODE_SCHEMA_URL = "https://opencode.ai/config.json";
2430
+ var KNOWN_PROPERTIES = new Set([
2431
+ "$schema",
2432
+ "model",
2433
+ "small_model",
2434
+ "theme",
2435
+ "share",
2436
+ "autoupdate",
2437
+ "mcp",
2438
+ "permission",
2439
+ "keybinds",
2440
+ "provider",
2441
+ "agent",
2442
+ "formatter",
2443
+ "plugin",
2444
+ "tools",
2445
+ "tui",
2446
+ "command",
2447
+ "watcher",
2448
+ "snapshot",
2449
+ "disabled_providers",
2450
+ "enabled_providers",
2451
+ "username",
2452
+ "lsp",
2453
+ "instructions",
2454
+ "enterprise",
2455
+ "experimental",
2456
+ "compaction"
2457
+ ]);
2458
+ async function validateConfig(configPath) {
2459
+ oe(import_picocolors7.default.bgBlue(import_picocolors7.default.white(" Validate Configuration ")));
2460
+ const issues = [];
2461
+ let config;
2462
+ try {
2463
+ config = loadConfig(configPath);
2464
+ f2.success("Valid JSON syntax");
2465
+ } catch (err) {
2466
+ f2.error(`Invalid JSON: ${err instanceof Error ? err.message : String(err)}`);
2467
+ $e(import_picocolors7.default.red("Validation failed"));
2468
+ return;
2469
+ }
2470
+ if (!config.$schema) {
2471
+ issues.push({
2472
+ type: "warning",
2473
+ path: "$schema",
2474
+ message: 'Missing $schema reference. Add: "$schema": "https://opencode.ai/config.json"'
2475
+ });
2476
+ } else if (!config.$schema.includes("opencode.ai")) {
2477
+ issues.push({
2478
+ type: "warning",
2479
+ path: "$schema",
2480
+ message: `Unexpected schema URL: ${config.$schema}`
2481
+ });
2482
+ }
2483
+ for (const key of Object.keys(config)) {
2484
+ if (!KNOWN_PROPERTIES.has(key)) {
2485
+ issues.push({
2486
+ type: "warning",
2487
+ path: key,
2488
+ message: `Unknown property "${key}" - may be ignored by OpenCode`
2489
+ });
2490
+ }
2491
+ }
2492
+ if (config.model !== undefined && typeof config.model !== "string") {
2493
+ issues.push({
2494
+ type: "error",
2495
+ path: "model",
2496
+ message: "model must be a string"
2497
+ });
2498
+ }
2499
+ if (config.theme !== undefined && typeof config.theme !== "string") {
2500
+ issues.push({
2501
+ type: "error",
2502
+ path: "theme",
2503
+ message: "theme must be a string"
2504
+ });
2505
+ }
2506
+ if (config.share !== undefined) {
2507
+ const validShare = ["manual", "auto", "disabled"];
2508
+ if (!validShare.includes(config.share)) {
2509
+ issues.push({
2510
+ type: "error",
2511
+ path: "share",
2512
+ message: `share must be one of: ${validShare.join(", ")}`
2513
+ });
2514
+ }
2515
+ }
2516
+ if (config.autoupdate !== undefined) {
2517
+ if (typeof config.autoupdate !== "boolean" && config.autoupdate !== "notify") {
2518
+ issues.push({
2519
+ type: "error",
2520
+ path: "autoupdate",
2521
+ message: 'autoupdate must be boolean or "notify"'
2522
+ });
2523
+ }
2524
+ }
2525
+ if (config.plugin !== undefined && !Array.isArray(config.plugin)) {
2526
+ issues.push({
2527
+ type: "error",
2528
+ path: "plugin",
2529
+ message: "plugin must be an array of strings"
2530
+ });
2531
+ }
2532
+ if (config.instructions !== undefined && !Array.isArray(config.instructions)) {
2533
+ issues.push({
2534
+ type: "error",
2535
+ path: "instructions",
2536
+ message: "instructions must be an array of strings"
2537
+ });
2538
+ }
2539
+ if (config.mcp) {
2540
+ for (const [name, server] of Object.entries(config.mcp)) {
2541
+ if (!server.command && !server.url) {
2542
+ issues.push({
2543
+ type: "error",
2544
+ path: `mcp.${name}`,
2545
+ message: "MCP server must have either 'command' or 'url'"
2546
+ });
2547
+ }
2548
+ if (server.command && !Array.isArray(server.command)) {
2549
+ issues.push({
2550
+ type: "error",
2551
+ path: `mcp.${name}.command`,
2552
+ message: "command must be an array of strings"
2553
+ });
2554
+ }
2555
+ }
2556
+ }
2557
+ if (config.agent) {
2558
+ for (const [name, agent] of Object.entries(config.agent)) {
2559
+ if (agent.mode !== undefined) {
2560
+ const validModes = ["subagent", "primary", "all"];
2561
+ if (!validModes.includes(agent.mode)) {
2562
+ issues.push({
2563
+ type: "error",
2564
+ path: `agent.${name}.mode`,
2565
+ message: `mode must be one of: ${validModes.join(", ")}`
2566
+ });
2567
+ }
2568
+ }
2569
+ if (agent.temperature !== undefined) {
2570
+ if (typeof agent.temperature !== "number" || agent.temperature < 0 || agent.temperature > 2) {
2571
+ issues.push({
2572
+ type: "warning",
2573
+ path: `agent.${name}.temperature`,
2574
+ message: "temperature should be a number between 0 and 2"
2575
+ });
2576
+ }
2577
+ }
2578
+ }
2579
+ }
2580
+ try {
2581
+ const response = await fetch(OPENCODE_SCHEMA_URL);
2582
+ if (response.ok) {
2583
+ f2.success("Schema fetched from opencode.ai");
2584
+ }
2585
+ } catch {
2586
+ f2.warn("Could not fetch schema from opencode.ai (offline?)");
2587
+ }
2588
+ const errors = issues.filter((i) => i.type === "error");
2589
+ const warnings = issues.filter((i) => i.type === "warning");
2590
+ if (errors.length > 0) {
2591
+ console.log();
2592
+ f2.error(import_picocolors7.default.bold(`${errors.length} error${errors.length === 1 ? "" : "s"}`));
2593
+ for (const issue of errors) {
2594
+ console.log(` ${import_picocolors7.default.red("✗")} ${import_picocolors7.default.yellow(issue.path)}: ${issue.message}`);
2595
+ }
2596
+ }
2597
+ if (warnings.length > 0) {
2598
+ console.log();
2599
+ f2.warn(import_picocolors7.default.bold(`${warnings.length} warning${warnings.length === 1 ? "" : "s"}`));
2600
+ for (const issue of warnings) {
2601
+ console.log(` ${import_picocolors7.default.yellow("!")} ${import_picocolors7.default.dim(issue.path)}: ${issue.message}`);
2602
+ }
2603
+ }
2604
+ if (issues.length === 0) {
2605
+ $e(import_picocolors7.default.green("Configuration is valid!"));
2606
+ } else if (errors.length > 0) {
2607
+ $e(import_picocolors7.default.red(`Validation failed with ${errors.length} error${errors.length === 1 ? "" : "s"}`));
2608
+ } else {
2609
+ $e(import_picocolors7.default.yellow(`Valid with ${warnings.length} warning${warnings.length === 1 ? "" : "s"}`));
2610
+ }
2611
+ }
1923
2612
  function showConfig(configPath) {
1924
2613
  const config = loadConfig(configPath);
1925
2614
  console.log();
1926
- console.log(import_picocolors6.default.bold(" Model"));
1927
- console.log(` ${import_picocolors6.default.cyan(config.model || "not set")}`);
2615
+ console.log(import_picocolors7.default.bold(" Model"));
2616
+ console.log(` ${import_picocolors7.default.cyan(config.model || "not set")}`);
1928
2617
  if (config.small_model) {
1929
- console.log(import_picocolors6.default.bold(" Small Model"));
1930
- console.log(` ${import_picocolors6.default.cyan(config.small_model)}`);
2618
+ console.log(import_picocolors7.default.bold(" Small Model"));
2619
+ console.log(` ${import_picocolors7.default.cyan(config.small_model)}`);
1931
2620
  }
1932
- console.log(import_picocolors6.default.bold(" Theme"));
1933
- console.log(` ${import_picocolors6.default.cyan(config.theme || "system")}`);
1934
- console.log(import_picocolors6.default.bold(" Share"));
1935
- console.log(` ${import_picocolors6.default.cyan(config.share || "manual")}`);
2621
+ console.log(import_picocolors7.default.bold(" Theme"));
2622
+ console.log(` ${import_picocolors7.default.cyan(config.theme || "system")}`);
2623
+ console.log(import_picocolors7.default.bold(" Share"));
2624
+ console.log(` ${import_picocolors7.default.cyan(config.share || "manual")}`);
1936
2625
  const autoupdateDisplay = config.autoupdate === "notify" ? "notify" : config.autoupdate === false ? "disabled" : "enabled";
1937
- console.log(import_picocolors6.default.bold(" Autoupdate"));
1938
- console.log(` ${import_picocolors6.default.cyan(autoupdateDisplay)}`);
2626
+ console.log(import_picocolors7.default.bold(" Autoupdate"));
2627
+ console.log(` ${import_picocolors7.default.cyan(autoupdateDisplay)}`);
1939
2628
  if (config.tui && Object.keys(config.tui).length > 0) {
1940
- console.log(import_picocolors6.default.bold(" TUI"));
2629
+ console.log(import_picocolors7.default.bold(" TUI"));
1941
2630
  if (config.tui.scroll_speed !== undefined) {
1942
- console.log(` scroll_speed: ${import_picocolors6.default.cyan(String(config.tui.scroll_speed))}`);
2631
+ console.log(` scroll_speed: ${import_picocolors7.default.cyan(String(config.tui.scroll_speed))}`);
1943
2632
  }
1944
2633
  if (config.tui.scroll_acceleration !== undefined) {
1945
- console.log(` scroll_acceleration: ${import_picocolors6.default.cyan(String(config.tui.scroll_acceleration))}`);
2634
+ console.log(` scroll_acceleration: ${import_picocolors7.default.cyan(String(config.tui.scroll_acceleration))}`);
1946
2635
  }
1947
2636
  if (config.tui.diff_style) {
1948
- console.log(` diff_style: ${import_picocolors6.default.cyan(config.tui.diff_style)}`);
2637
+ console.log(` diff_style: ${import_picocolors7.default.cyan(config.tui.diff_style)}`);
1949
2638
  }
1950
2639
  }
1951
2640
  if (config.mcp && Object.keys(config.mcp).length > 0) {
1952
- console.log(import_picocolors6.default.bold(" MCP Servers"));
2641
+ console.log(import_picocolors7.default.bold(" MCP Servers"));
1953
2642
  for (const [name, server] of Object.entries(config.mcp)) {
1954
- const status = server.enabled === false ? import_picocolors6.default.dim("off") : import_picocolors6.default.green("on");
2643
+ const status = server.enabled === false ? import_picocolors7.default.dim("off") : import_picocolors7.default.green("on");
1955
2644
  console.log(` ${status} ${name}`);
1956
2645
  }
1957
2646
  }
1958
2647
  if (config.tools && Object.keys(config.tools).length > 0) {
1959
- console.log(import_picocolors6.default.bold(" Tools"));
2648
+ console.log(import_picocolors7.default.bold(" Tools"));
1960
2649
  for (const [tool, enabled] of Object.entries(config.tools)) {
1961
- const status = enabled === false ? import_picocolors6.default.red("off") : import_picocolors6.default.green("on");
2650
+ const status = enabled === false ? import_picocolors7.default.red("off") : import_picocolors7.default.green("on");
1962
2651
  console.log(` ${status} ${tool}`);
1963
2652
  }
1964
2653
  }
1965
2654
  if (config.keybinds && Object.keys(config.keybinds).length > 0) {
1966
- console.log(import_picocolors6.default.bold(" Keybinds"));
2655
+ console.log(import_picocolors7.default.bold(" Keybinds"));
1967
2656
  for (const [action, key] of Object.entries(config.keybinds)) {
1968
- console.log(` ${import_picocolors6.default.dim(action)}: ${import_picocolors6.default.cyan(key)}`);
2657
+ console.log(` ${import_picocolors7.default.dim(action)}: ${import_picocolors7.default.cyan(key)}`);
1969
2658
  }
1970
2659
  }
1971
2660
  console.log();
1972
2661
  }
1973
2662
  async function configMenu(configPath) {
1974
- oe(import_picocolors6.default.bgCyan(import_picocolors6.default.black(" Configuration ")));
2663
+ oe(import_picocolors7.default.bgCyan(import_picocolors7.default.black(" Configuration ")));
1975
2664
  while (true) {
1976
2665
  const action = await ie({
1977
2666
  message: "What do you want to configure?",
@@ -2046,8 +2735,8 @@ async function editModel(configPath, modelType = "model") {
2046
2735
  const config = loadConfig(configPath);
2047
2736
  const currentModel = config[modelType] || "not set";
2048
2737
  const modelLabel = modelType === "small_model" ? "Small Model" : "Model";
2049
- f2.info(`Current ${modelLabel}: ${import_picocolors6.default.cyan(currentModel)}`);
2050
- f2.info(import_picocolors6.default.dim("Fetching models from models.dev..."));
2738
+ f2.info(`Current ${modelLabel}: ${import_picocolors7.default.cyan(currentModel)}`);
2739
+ f2.info(import_picocolors7.default.dim("Fetching models from models.dev..."));
2051
2740
  const modelsDevData = await fetchFromModelsDev();
2052
2741
  if (!modelsDevData || Object.keys(modelsDevData).length === 0) {
2053
2742
  f2.warn("models.dev unavailable - please enter manually");
@@ -2068,7 +2757,7 @@ async function editModel(configPath, modelType = "model") {
2068
2757
  }
2069
2758
  config[modelType] = newModel;
2070
2759
  saveConfig(configPath, config);
2071
- f2.success(`${modelLabel} set to ${import_picocolors6.default.cyan(newModel)}`);
2760
+ f2.success(`${modelLabel} set to ${import_picocolors7.default.cyan(newModel)}`);
2072
2761
  return;
2073
2762
  }
2074
2763
  f2.success("Fetched models from models.dev");
@@ -2131,7 +2820,7 @@ async function editModel(configPath, modelType = "model") {
2131
2820
  }
2132
2821
  config[modelType] = customModel;
2133
2822
  saveConfig(configPath, config);
2134
- f2.success(`${modelLabel} set to ${import_picocolors6.default.cyan(customModel)}`);
2823
+ f2.success(`${modelLabel} set to ${import_picocolors7.default.cyan(customModel)}`);
2135
2824
  return;
2136
2825
  }
2137
2826
  if (providerId === "__search__") {
@@ -2196,7 +2885,7 @@ async function editModel(configPath, modelType = "model") {
2196
2885
  const fullModelId = `${providerId}/${selectedModel}`;
2197
2886
  config[modelType] = fullModelId;
2198
2887
  saveConfig(configPath, config);
2199
- f2.success(`${modelLabel} set to ${import_picocolors6.default.cyan(fullModelId)}`);
2888
+ f2.success(`${modelLabel} set to ${import_picocolors7.default.cyan(fullModelId)}`);
2200
2889
  }
2201
2890
  function getModelsForProvider(provider) {
2202
2891
  const options = [];
@@ -2244,7 +2933,7 @@ async function editAgentModel(configPath) {
2244
2933
  let agentNames = [];
2245
2934
  const agentInfoMap = new Map;
2246
2935
  if (serverAgents && serverAgents.length > 0) {
2247
- f2.info(import_picocolors6.default.dim("Fetched agents from OpenCode server"));
2936
+ f2.info(import_picocolors7.default.dim("Fetched agents from OpenCode server"));
2248
2937
  for (const agent of serverAgents) {
2249
2938
  agentNames.push(agent.name);
2250
2939
  agentInfoMap.set(agent.name, {
@@ -2268,14 +2957,14 @@ async function editAgentModel(configPath) {
2268
2957
  return;
2269
2958
  }
2270
2959
  console.log();
2271
- console.log(import_picocolors6.default.bold(" Agent Models"));
2960
+ console.log(import_picocolors7.default.bold(" Agent Models"));
2272
2961
  for (const name of agentNames) {
2273
2962
  const info = agentInfoMap.get(name);
2274
- const model = info?.model || import_picocolors6.default.dim("(default)");
2275
- const builtIn = info?.builtIn ? import_picocolors6.default.dim(" [built-in]") : "";
2963
+ const model = info?.model || import_picocolors7.default.dim("(default)");
2964
+ const builtIn = info?.builtIn ? import_picocolors7.default.dim(" [built-in]") : "";
2276
2965
  const localConfig = config.agent[name];
2277
- const disabled = localConfig?.disable ? import_picocolors6.default.red(" [disabled]") : "";
2278
- console.log(` ${import_picocolors6.default.cyan(name)}: ${model}${builtIn}${disabled}`);
2966
+ const disabled = localConfig?.disable ? import_picocolors7.default.red(" [disabled]") : "";
2967
+ console.log(` ${import_picocolors7.default.cyan(name)}: ${model}${builtIn}${disabled}`);
2279
2968
  }
2280
2969
  console.log();
2281
2970
  const agentOptions = [
@@ -2323,7 +3012,7 @@ async function editAgentModel(configPath) {
2323
3012
  }
2324
3013
  config.agent[agentName].disable = !currentAgent.disable;
2325
3014
  saveConfig(configPath, config);
2326
- const status = config.agent[agentName].disable ? import_picocolors6.default.red("disabled") : import_picocolors6.default.green("enabled");
3015
+ const status = config.agent[agentName].disable ? import_picocolors7.default.red("disabled") : import_picocolors7.default.green("enabled");
2327
3016
  f2.success(`Agent ${agentName}: ${status}`);
2328
3017
  return;
2329
3018
  }
@@ -2344,8 +3033,8 @@ async function editAgentModel(configPath) {
2344
3033
  return;
2345
3034
  }
2346
3035
  const agentInfo = agentInfoMap.get(agentName);
2347
- f2.info(`Current model: ${import_picocolors6.default.cyan(agentInfo?.model || currentAgent.model || "(default)")}`);
2348
- f2.info(import_picocolors6.default.dim("Fetching models from models.dev..."));
3036
+ f2.info(`Current model: ${import_picocolors7.default.cyan(agentInfo?.model || currentAgent.model || "(default)")}`);
3037
+ f2.info(import_picocolors7.default.dim("Fetching models from models.dev..."));
2349
3038
  const modelsDevData = await fetchFromModelsDev();
2350
3039
  if (!modelsDevData || Object.keys(modelsDevData).length === 0) {
2351
3040
  f2.warn("models.dev unavailable - please enter manually");
@@ -2367,7 +3056,7 @@ async function editAgentModel(configPath) {
2367
3056
  }
2368
3057
  config.agent[agentName].model = newModel;
2369
3058
  saveConfig(configPath, config);
2370
- f2.success(`${agentName} model set to ${import_picocolors6.default.cyan(newModel)}`);
3059
+ f2.success(`${agentName} model set to ${import_picocolors7.default.cyan(newModel)}`);
2371
3060
  return;
2372
3061
  }
2373
3062
  const localProviders = config.provider || {};
@@ -2425,7 +3114,7 @@ async function editAgentModel(configPath) {
2425
3114
  agentConfig.model = undefined;
2426
3115
  }
2427
3116
  saveConfig(configPath, config);
2428
- f2.success(`${agentName} model set to ${import_picocolors6.default.cyan("(default)")}`);
3117
+ f2.success(`${agentName} model set to ${import_picocolors7.default.cyan("(default)")}`);
2429
3118
  return;
2430
3119
  }
2431
3120
  if (providerId === "__custom__") {
@@ -2447,7 +3136,7 @@ async function editAgentModel(configPath) {
2447
3136
  }
2448
3137
  config.agent[agentName].model = customModel;
2449
3138
  saveConfig(configPath, config);
2450
- f2.success(`${agentName} model set to ${import_picocolors6.default.cyan(customModel)}`);
3139
+ f2.success(`${agentName} model set to ${import_picocolors7.default.cyan(customModel)}`);
2451
3140
  return;
2452
3141
  }
2453
3142
  if (providerId === "__search__") {
@@ -2502,7 +3191,7 @@ async function editAgentModel(configPath) {
2502
3191
  }
2503
3192
  config.agent[agentName].model = fullModelId;
2504
3193
  saveConfig(configPath, config);
2505
- f2.success(`${agentName} model set to ${import_picocolors6.default.cyan(fullModelId)}`);
3194
+ f2.success(`${agentName} model set to ${import_picocolors7.default.cyan(fullModelId)}`);
2506
3195
  }
2507
3196
  async function editMCP(configPath) {
2508
3197
  const config = loadConfig(configPath);
@@ -2521,11 +3210,11 @@ async function editMCP(configPath) {
2521
3210
  return;
2522
3211
  }
2523
3212
  console.log();
2524
- console.log(import_picocolors6.default.bold(" MCP Servers"));
3213
+ console.log(import_picocolors7.default.bold(" MCP Servers"));
2525
3214
  for (const [name, server] of Object.entries(config.mcp)) {
2526
- const status = server.enabled === false ? import_picocolors6.default.red("off") : import_picocolors6.default.green("on");
2527
- const type = server.type === "remote" ? import_picocolors6.default.dim("remote") : import_picocolors6.default.dim("local");
2528
- const endpoint = server.type === "remote" ? import_picocolors6.default.dim(` → ${server.url}`) : import_picocolors6.default.dim(` → ${(server.command || []).join(" ")}`);
3215
+ const status = server.enabled === false ? import_picocolors7.default.red("off") : import_picocolors7.default.green("on");
3216
+ const type = server.type === "remote" ? import_picocolors7.default.dim("remote") : import_picocolors7.default.dim("local");
3217
+ const endpoint = server.type === "remote" ? import_picocolors7.default.dim(` → ${server.url}`) : import_picocolors7.default.dim(` → ${(server.command || []).join(" ")}`);
2529
3218
  console.log(` ${status} ${name} ${type}${endpoint}`);
2530
3219
  }
2531
3220
  console.log();
@@ -2567,11 +3256,15 @@ async function toggleMCP(configPath, config) {
2567
3256
  return;
2568
3257
  }
2569
3258
  const serverName = selected;
2570
- const server = config.mcp[serverName];
3259
+ const server = config.mcp?.[serverName];
3260
+ if (!server) {
3261
+ f2.error(`Server "${serverName}" not found`);
3262
+ return;
3263
+ }
2571
3264
  const wasEnabled = server.enabled !== false;
2572
3265
  server.enabled = !wasEnabled;
2573
3266
  saveConfig(configPath, config);
2574
- const status = server.enabled ? import_picocolors6.default.green("enabled") : import_picocolors6.default.dim("disabled");
3267
+ const status = server.enabled ? import_picocolors7.default.green("enabled") : import_picocolors7.default.dim("disabled");
2575
3268
  f2.success(`${serverName}: ${status}`);
2576
3269
  }
2577
3270
  async function addMCPServer(configPath, config) {
@@ -2613,6 +3306,7 @@ async function addMCPServer(configPath, config) {
2613
3306
  if (lD(url)) {
2614
3307
  return;
2615
3308
  }
3309
+ config.mcp = config.mcp ?? {};
2616
3310
  config.mcp[serverName] = {
2617
3311
  type: "remote",
2618
3312
  url,
@@ -2630,6 +3324,7 @@ async function addMCPServer(configPath, config) {
2630
3324
  if (lD(command)) {
2631
3325
  return;
2632
3326
  }
3327
+ config.mcp = config.mcp ?? {};
2633
3328
  config.mcp[serverName] = {
2634
3329
  type: "local",
2635
3330
  command: command.split(" "),
@@ -2637,7 +3332,7 @@ async function addMCPServer(configPath, config) {
2637
3332
  };
2638
3333
  }
2639
3334
  saveConfig(configPath, config);
2640
- f2.success(`Added MCP server: ${import_picocolors6.default.cyan(serverName)}`);
3335
+ f2.success(`Added MCP server: ${import_picocolors7.default.cyan(serverName)}`);
2641
3336
  }
2642
3337
  async function removeMCP(configPath, config) {
2643
3338
  const servers = Object.keys(config.mcp || {}).map((name) => ({
@@ -2658,7 +3353,7 @@ async function removeMCP(configPath, config) {
2658
3353
  if (lD(confirm) || !confirm) {
2659
3354
  return;
2660
3355
  }
2661
- delete config.mcp[serverName];
3356
+ delete config.mcp?.[serverName];
2662
3357
  saveConfig(configPath, config);
2663
3358
  f2.success(`Removed: ${serverName}`);
2664
3359
  }
@@ -2668,13 +3363,13 @@ async function editPermissions(configPath) {
2668
3363
  config.permission = {};
2669
3364
  }
2670
3365
  console.log();
2671
- console.log(import_picocolors6.default.bold(" Current Permissions"));
2672
- console.log(` edit: ${import_picocolors6.default.cyan(config.permission.edit || "ask")}`);
2673
- console.log(` external_directory: ${import_picocolors6.default.cyan(config.permission.external_directory || "ask")}`);
3366
+ console.log(import_picocolors7.default.bold(" Current Permissions"));
3367
+ console.log(` edit: ${import_picocolors7.default.cyan(config.permission.edit || "ask")}`);
3368
+ console.log(` external_directory: ${import_picocolors7.default.cyan(config.permission.external_directory || "ask")}`);
2674
3369
  if (config.permission.bash) {
2675
- console.log(import_picocolors6.default.bold(" Bash"));
3370
+ console.log(import_picocolors7.default.bold(" Bash"));
2676
3371
  for (const [pattern, perm] of Object.entries(config.permission.bash)) {
2677
- console.log(` ${import_picocolors6.default.dim(pattern)}: ${import_picocolors6.default.cyan(perm)}`);
3372
+ console.log(` ${import_picocolors7.default.dim(pattern)}: ${import_picocolors7.default.cyan(perm)}`);
2678
3373
  }
2679
3374
  }
2680
3375
  console.log();
@@ -2736,9 +3431,9 @@ async function editKeybinds(configPath) {
2736
3431
  }
2737
3432
  if (Object.keys(config.keybinds).length > 0) {
2738
3433
  console.log();
2739
- console.log(import_picocolors6.default.bold(" Current Keybinds"));
3434
+ console.log(import_picocolors7.default.bold(" Current Keybinds"));
2740
3435
  for (const [action2, key] of Object.entries(config.keybinds)) {
2741
- console.log(` ${import_picocolors6.default.dim(action2)}: ${import_picocolors6.default.cyan(key)}`);
3436
+ console.log(` ${import_picocolors7.default.dim(action2)}: ${import_picocolors7.default.cyan(key)}`);
2742
3437
  }
2743
3438
  console.log();
2744
3439
  }
@@ -2791,7 +3486,7 @@ async function editKeybinds(configPath) {
2791
3486
  }
2792
3487
  config.keybinds[keybindName] = newKey;
2793
3488
  saveConfig(configPath, config);
2794
- f2.success(`${keybindName} → ${import_picocolors6.default.cyan(newKey)}`);
3489
+ f2.success(`${keybindName} → ${import_picocolors7.default.cyan(newKey)}`);
2795
3490
  }
2796
3491
  var OPENCODE_THEMES = [
2797
3492
  { value: "system", label: "System", hint: "follows OS preference" },
@@ -2831,7 +3526,7 @@ var OPENCODE_THEMES = [
2831
3526
  async function editTheme(configPath) {
2832
3527
  const config = loadConfig(configPath);
2833
3528
  const currentTheme = config.theme || "system";
2834
- f2.info(`Current theme: ${import_picocolors6.default.cyan(currentTheme)}`);
3529
+ f2.info(`Current theme: ${import_picocolors7.default.cyan(currentTheme)}`);
2835
3530
  const validTheme = OPENCODE_THEMES.some((t) => t.value === currentTheme) ? currentTheme : "system";
2836
3531
  const selectedTheme = await ie({
2837
3532
  message: "Select theme",
@@ -2848,7 +3543,7 @@ async function editTheme(configPath) {
2848
3543
  }
2849
3544
  config.theme = selectedTheme;
2850
3545
  saveConfig(configPath, config);
2851
- f2.success(`Theme set to ${import_picocolors6.default.cyan(selectedTheme)}`);
3546
+ f2.success(`Theme set to ${import_picocolors7.default.cyan(selectedTheme)}`);
2852
3547
  }
2853
3548
  async function editTui(configPath) {
2854
3549
  const config = loadConfig(configPath);
@@ -2896,7 +3591,7 @@ async function editTui(configPath) {
2896
3591
  }
2897
3592
  config.tui.diff_style = style;
2898
3593
  saveConfig(configPath, config);
2899
- f2.success(`Diff style set to ${import_picocolors6.default.cyan(style)}`);
3594
+ f2.success(`Diff style set to ${import_picocolors7.default.cyan(style)}`);
2900
3595
  } else {
2901
3596
  const settingKey = setting;
2902
3597
  const value = await te({
@@ -2913,7 +3608,7 @@ async function editTui(configPath) {
2913
3608
  }
2914
3609
  config.tui[settingKey] = value ? Number(value) : undefined;
2915
3610
  saveConfig(configPath, config);
2916
- f2.success(`${settingKey} set to ${import_picocolors6.default.cyan(value || "default")}`);
3611
+ f2.success(`${settingKey} set to ${import_picocolors7.default.cyan(value || "default")}`);
2917
3612
  }
2918
3613
  }
2919
3614
  async function editTools(configPath) {
@@ -2933,9 +3628,9 @@ async function editTools(configPath) {
2933
3628
  "todoread"
2934
3629
  ];
2935
3630
  console.log();
2936
- console.log(import_picocolors6.default.bold(" Tool Status"));
3631
+ console.log(import_picocolors7.default.bold(" Tool Status"));
2937
3632
  for (const tool of commonTools) {
2938
- const status = config.tools[tool] === false ? import_picocolors6.default.red("off") : import_picocolors6.default.green("on");
3633
+ const status = config.tools[tool] === false ? import_picocolors7.default.red("off") : import_picocolors7.default.green("on");
2939
3634
  console.log(` ${status} ${tool}`);
2940
3635
  }
2941
3636
  console.log();
@@ -2969,7 +3664,7 @@ async function editTools(configPath) {
2969
3664
  const wasEnabled = config.tools[toolName] !== false;
2970
3665
  config.tools[toolName] = !wasEnabled;
2971
3666
  saveConfig(configPath, config);
2972
- const status = config.tools[toolName] ? import_picocolors6.default.green("enabled") : import_picocolors6.default.red("disabled");
3667
+ const status = config.tools[toolName] ? import_picocolors7.default.green("enabled") : import_picocolors7.default.red("disabled");
2973
3668
  f2.success(`${toolName}: ${status}`);
2974
3669
  } else {
2975
3670
  const toolName = await te({
@@ -2992,13 +3687,13 @@ async function editTools(configPath) {
2992
3687
  }
2993
3688
  config.tools[toolName] = enabled;
2994
3689
  saveConfig(configPath, config);
2995
- f2.success(`Added ${toolName}: ${enabled ? import_picocolors6.default.green("on") : import_picocolors6.default.red("off")}`);
3690
+ f2.success(`Added ${toolName}: ${enabled ? import_picocolors7.default.green("on") : import_picocolors7.default.red("off")}`);
2996
3691
  }
2997
3692
  }
2998
3693
  async function editShare(configPath) {
2999
3694
  const config = loadConfig(configPath);
3000
3695
  const current = config.share || "manual";
3001
- f2.info(`Current share setting: ${import_picocolors6.default.cyan(current)}`);
3696
+ f2.info(`Current share setting: ${import_picocolors7.default.cyan(current)}`);
3002
3697
  const selected = await ie({
3003
3698
  message: "Share setting",
3004
3699
  options: [
@@ -3021,13 +3716,13 @@ async function editShare(configPath) {
3021
3716
  const shareValue = selected;
3022
3717
  config.share = shareValue;
3023
3718
  saveConfig(configPath, config);
3024
- f2.success(`Share set to ${import_picocolors6.default.cyan(shareValue)}`);
3719
+ f2.success(`Share set to ${import_picocolors7.default.cyan(shareValue)}`);
3025
3720
  }
3026
3721
  async function editAutoupdate(configPath) {
3027
3722
  const config = loadConfig(configPath);
3028
3723
  const current = config.autoupdate ?? true;
3029
3724
  const display = current === "notify" ? "notify" : current ? "enabled" : "disabled";
3030
- f2.info(`Current autoupdate: ${import_picocolors6.default.cyan(display)}`);
3725
+ f2.info(`Current autoupdate: ${import_picocolors7.default.cyan(display)}`);
3031
3726
  const selected = await ie({
3032
3727
  message: "Autoupdate behavior",
3033
3728
  options: [
@@ -3058,21 +3753,21 @@ async function editAutoupdate(configPath) {
3058
3753
  config.autoupdate = selectedValue === "true";
3059
3754
  }
3060
3755
  saveConfig(configPath, config);
3061
- f2.success(`Autoupdate set to ${import_picocolors6.default.cyan(selectedValue)}`);
3756
+ f2.success(`Autoupdate set to ${import_picocolors7.default.cyan(selectedValue)}`);
3062
3757
  }
3063
3758
 
3064
3759
  // src/commands/init.ts
3065
3760
  import { execSync } from "node:child_process";
3066
3761
  import {
3067
- existsSync as existsSync3,
3068
- mkdirSync as mkdirSync2,
3069
- readFileSync as readFileSync3,
3070
- readdirSync as readdirSync2,
3071
- writeFileSync as writeFileSync3
3762
+ existsSync as existsSync4,
3763
+ mkdirSync as mkdirSync3,
3764
+ readFileSync as readFileSync4,
3765
+ readdirSync as readdirSync3,
3766
+ writeFileSync as writeFileSync4
3072
3767
  } from "node:fs";
3073
- import { basename, dirname, join as join3 } from "node:path";
3768
+ import { basename as basename2, dirname, join as join4 } from "node:path";
3074
3769
  import { fileURLToPath } from "node:url";
3075
- var import_picocolors7 = __toESM(require_picocolors(), 1);
3770
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
3076
3771
  var EXCLUDED_DIRS = [
3077
3772
  "node_modules",
3078
3773
  ".git",
@@ -3089,12 +3784,12 @@ var EXCLUDED_FILES = [
3089
3784
  "pnpm-lock.yaml"
3090
3785
  ];
3091
3786
  function detectMode(targetDir) {
3092
- const opencodeDir = join3(targetDir, ".opencode");
3093
- if (existsSync3(opencodeDir)) {
3787
+ const opencodeDir = join4(targetDir, ".opencode");
3788
+ if (existsSync4(opencodeDir)) {
3094
3789
  return "already-initialized";
3095
3790
  }
3096
- if (existsSync3(targetDir)) {
3097
- const entries = readdirSync2(targetDir);
3791
+ if (existsSync4(targetDir)) {
3792
+ const entries = readdirSync3(targetDir);
3098
3793
  const hasCode = entries.some((e2) => !e2.startsWith(".") && !EXCLUDED_DIRS.includes(e2) && e2 !== "node_modules");
3099
3794
  if (hasCode) {
3100
3795
  return "add-config";
@@ -3106,12 +3801,12 @@ function getTemplateRoot() {
3106
3801
  const __filename2 = fileURLToPath(import.meta.url);
3107
3802
  const __dirname2 = dirname(__filename2);
3108
3803
  const possiblePaths = [
3109
- join3(__dirname2, "template"),
3110
- join3(__dirname2, "..", "..", ".opencode")
3804
+ join4(__dirname2, "template"),
3805
+ join4(__dirname2, "..", "..", ".opencode")
3111
3806
  ];
3112
3807
  for (const path of possiblePaths) {
3113
- const opencodeDir = join3(path, ".opencode");
3114
- if (existsSync3(opencodeDir)) {
3808
+ const opencodeDir = join4(path, ".opencode");
3809
+ if (existsSync4(opencodeDir)) {
3115
3810
  return path;
3116
3811
  }
3117
3812
  }
@@ -3125,20 +3820,20 @@ async function copyDir(src, dest) {
3125
3820
  continue;
3126
3821
  if (!entry.isDirectory() && EXCLUDED_FILES.includes(entry.name))
3127
3822
  continue;
3128
- const srcPath = join3(src, entry.name);
3129
- const destPath = join3(dest, entry.name);
3823
+ const srcPath = join4(src, entry.name);
3824
+ const destPath = join4(dest, entry.name);
3130
3825
  if (entry.isSymbolicLink()) {} else if (entry.isDirectory()) {
3131
3826
  await copyDir(srcPath, destPath);
3132
3827
  } else {
3133
- const content = readFileSync3(srcPath, "utf-8");
3134
- writeFileSync3(destPath, content);
3828
+ const content = readFileSync4(srcPath, "utf-8");
3829
+ writeFileSync4(destPath, content);
3135
3830
  }
3136
3831
  }
3137
3832
  }
3138
3833
  async function copyOpenCodeOnly(templateRoot, targetDir) {
3139
- const opencodeSrc = join3(templateRoot, ".opencode");
3140
- const opencodeDest = join3(targetDir, ".opencode");
3141
- if (!existsSync3(opencodeSrc)) {
3834
+ const opencodeSrc = join4(templateRoot, ".opencode");
3835
+ const opencodeDest = join4(targetDir, ".opencode");
3836
+ if (!existsSync4(opencodeSrc)) {
3142
3837
  return false;
3143
3838
  }
3144
3839
  await copyDir(opencodeSrc, opencodeDest);
@@ -3149,20 +3844,20 @@ async function initCommand(options = {}) {
3149
3844
  return;
3150
3845
  const targetDir = process.cwd();
3151
3846
  const mode = detectMode(targetDir);
3152
- oe(import_picocolors7.default.bgCyan(import_picocolors7.default.black(" OpenCodeKit ")));
3847
+ oe(import_picocolors8.default.bgCyan(import_picocolors8.default.black(" OpenCodeKit ")));
3153
3848
  if (mode === "already-initialized" && !options.force) {
3154
3849
  f2.warn("Already initialized (.opencode/ exists)");
3155
- f2.info(`Use ${import_picocolors7.default.cyan("--force")} to reinitialize`);
3850
+ f2.info(`Use ${import_picocolors8.default.cyan("--force")} to reinitialize`);
3156
3851
  $e("Nothing to do");
3157
3852
  return;
3158
3853
  }
3159
3854
  const templateRoot = getTemplateRoot();
3160
3855
  if (!templateRoot) {
3161
3856
  f2.error("Template not found. Please reinstall opencodekit.");
3162
- $e(import_picocolors7.default.red("Failed"));
3857
+ $e(import_picocolors8.default.red("Failed"));
3163
3858
  process.exit(1);
3164
3859
  }
3165
- let projectName = basename(targetDir);
3860
+ let projectName = basename2(targetDir);
3166
3861
  if (mode === "scaffold") {
3167
3862
  const name = await te({
3168
3863
  message: "Project name",
@@ -3178,7 +3873,7 @@ async function initCommand(options = {}) {
3178
3873
  const s = de();
3179
3874
  if (mode === "scaffold") {
3180
3875
  s.start("Scaffolding project");
3181
- mkdirSync2(targetDir, { recursive: true });
3876
+ mkdirSync3(targetDir, { recursive: true });
3182
3877
  } else if (mode === "add-config") {
3183
3878
  s.start("Adding OpenCodeKit");
3184
3879
  } else {
@@ -3187,25 +3882,25 @@ async function initCommand(options = {}) {
3187
3882
  const success = await copyOpenCodeOnly(templateRoot, targetDir);
3188
3883
  if (!success) {
3189
3884
  s.stop("Failed");
3190
- $e(import_picocolors7.default.red("Template copy failed"));
3885
+ $e(import_picocolors8.default.red("Template copy failed"));
3191
3886
  process.exit(1);
3192
3887
  }
3193
3888
  s.stop("Done");
3194
3889
  if (options.beads) {
3195
- const beadsDir = join3(targetDir, ".beads");
3196
- if (!existsSync3(beadsDir)) {
3890
+ const beadsDir = join4(targetDir, ".beads");
3891
+ if (!existsSync4(beadsDir)) {
3197
3892
  const bs = de();
3198
3893
  bs.start("Initializing .beads/");
3199
3894
  try {
3200
3895
  execSync("bd init", { cwd: targetDir, stdio: "ignore" });
3201
3896
  bs.stop("Beads initialized");
3202
3897
  } catch {
3203
- mkdirSync2(beadsDir, { recursive: true });
3204
- writeFileSync3(join3(beadsDir, "config.yaml"), `# Beads configuration
3898
+ mkdirSync3(beadsDir, { recursive: true });
3899
+ writeFileSync4(join4(beadsDir, "config.yaml"), `# Beads configuration
3205
3900
  version: 1
3206
3901
  `);
3207
- writeFileSync3(join3(beadsDir, "issues.jsonl"), "");
3208
- writeFileSync3(join3(beadsDir, "metadata.json"), JSON.stringify({ created: new Date().toISOString() }, null, 2));
3902
+ writeFileSync4(join4(beadsDir, "issues.jsonl"), "");
3903
+ writeFileSync4(join4(beadsDir, "metadata.json"), JSON.stringify({ created: new Date().toISOString() }, null, 2));
3209
3904
  bs.stop("Beads initialized (manual)");
3210
3905
  }
3211
3906
  } else {
@@ -3213,213 +3908,352 @@ version: 1
3213
3908
  }
3214
3909
  }
3215
3910
  le("cd .opencode && bun install", "Next steps");
3216
- $e(import_picocolors7.default.green("Ready to code!"));
3911
+ $e(import_picocolors8.default.green("Ready to code!"));
3217
3912
  }
3218
3913
 
3219
3914
  // src/commands/menu.ts
3220
- import { existsSync as existsSync6, lstatSync as lstatSync3, readdirSync as readdirSync5 } from "node:fs";
3221
- import { basename as basename2, join as join6 } from "node:path";
3222
- var import_picocolors10 = __toESM(require_picocolors(), 1);
3915
+ import { existsSync as existsSync7, lstatSync as lstatSync2, readFileSync as readFileSync7, readdirSync as readdirSync6 } from "node:fs";
3916
+ import { basename as basename4, join as join7 } from "node:path";
3917
+ var import_picocolors11 = __toESM(require_picocolors(), 1);
3223
3918
 
3224
3919
  // src/commands/skill.ts
3225
- import { join as join4 } from "node:path";
3226
- import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync4, lstatSync as lstatSync2 } from "node:fs";
3227
- var import_picocolors8 = __toESM(require_picocolors(), 1);
3920
+ import {
3921
+ existsSync as existsSync5,
3922
+ mkdirSync as mkdirSync4,
3923
+ readFileSync as readFileSync5,
3924
+ readdirSync as readdirSync4,
3925
+ rmSync,
3926
+ writeFileSync as writeFileSync5
3927
+ } from "node:fs";
3928
+ import { basename as basename3, join as join5 } from "node:path";
3929
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
3228
3930
  async function skillCommand(action) {
3229
- const opencodePath = join4(process.cwd(), ".opencode");
3230
- if (!existsSync4(opencodePath)) {
3931
+ const opencodePath = join5(process.cwd(), ".opencode");
3932
+ if (!existsSync5(opencodePath)) {
3231
3933
  notInitialized();
3232
3934
  return;
3233
3935
  }
3936
+ const skillDir = join5(opencodePath, "skill");
3234
3937
  switch (action) {
3235
3938
  case "list":
3236
- await listSkills(opencodePath);
3939
+ listSkills(skillDir);
3940
+ break;
3941
+ case "create":
3942
+ await createSkill(skillDir);
3237
3943
  break;
3238
3944
  case "add":
3239
- await addSkill(opencodePath);
3945
+ await createSkill(skillDir);
3240
3946
  break;
3241
3947
  case "view": {
3242
3948
  const skillName = process.argv[4];
3243
- await viewSkill(opencodePath, skillName);
3949
+ await viewSkill(skillDir, skillName);
3950
+ break;
3951
+ }
3952
+ case "remove": {
3953
+ const skillName = process.argv[4];
3954
+ await removeSkill(skillDir, skillName);
3244
3955
  break;
3245
3956
  }
3246
3957
  default:
3247
- unknownAction(action, ["list", "add", "view"]);
3958
+ unknownAction(action, ["list", "create", "view", "remove"]);
3248
3959
  }
3249
3960
  }
3250
- function collectSkills(basePath) {
3251
- const skills = [];
3252
- if (!existsSync4(basePath))
3253
- return skills;
3254
- const tiers = ["core", "stack", "specialized"];
3255
- let hasTiers = false;
3256
- for (const tier of tiers) {
3257
- const tierPath = join4(basePath, tier);
3258
- if (existsSync4(tierPath)) {
3259
- hasTiers = true;
3260
- const entries = readdirSync3(tierPath);
3261
- for (const entry of entries) {
3262
- const entryPath = join4(tierPath, entry);
3263
- if (lstatSync2(entryPath).isDirectory()) {
3264
- skills.push({ name: entry, path: entryPath, tier });
3265
- }
3266
- }
3961
+ function parseFrontmatter3(content) {
3962
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
3963
+ if (!match)
3964
+ return {};
3965
+ const frontmatter = {};
3966
+ const lines = match[1].split(`
3967
+ `);
3968
+ let currentKey = null;
3969
+ let multilineValue = "";
3970
+ for (const line of lines) {
3971
+ if (currentKey && line.match(/^\s+\S/)) {
3972
+ multilineValue += ` ${line.trim()}`;
3973
+ continue;
3267
3974
  }
3268
- }
3269
- if (!hasTiers) {
3270
- const entries = readdirSync3(basePath);
3271
- for (const entry of entries) {
3272
- const entryPath = join4(basePath, entry);
3273
- if (lstatSync2(entryPath).isDirectory()) {
3274
- skills.push({ name: entry, path: entryPath });
3975
+ if (currentKey && multilineValue) {
3976
+ frontmatter[currentKey] = multilineValue.trim();
3977
+ currentKey = null;
3978
+ multilineValue = "";
3979
+ }
3980
+ const colonIndex = line.indexOf(":");
3981
+ if (colonIndex > 0) {
3982
+ const key = line.slice(0, colonIndex).trim();
3983
+ let value = line.slice(colonIndex + 1).trim();
3984
+ if (value === ">" || value === "|") {
3985
+ currentKey = key;
3986
+ multilineValue = "";
3987
+ continue;
3988
+ }
3989
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
3990
+ value = value.slice(1, -1);
3275
3991
  }
3992
+ frontmatter[key] = value;
3276
3993
  }
3277
3994
  }
3278
- return skills;
3995
+ if (currentKey && multilineValue) {
3996
+ frontmatter[currentKey] = multilineValue.trim();
3997
+ }
3998
+ return frontmatter;
3279
3999
  }
3280
- async function listSkills(opencodePath) {
3281
- const locations = [
3282
- { path: join4(opencodePath, "superpowers", "skills"), name: "Superpowers" },
3283
- { path: join4(opencodePath, "skills"), name: "Project" }
3284
- ];
3285
- let totalSkills = 0;
3286
- for (const loc of locations) {
3287
- const skills = collectSkills(loc.path);
3288
- if (skills.length > 0) {
3289
- f2.info(import_picocolors8.default.bold(loc.name));
3290
- for (const skill of skills) {
3291
- const label = skill.tier ? `${skill.tier}/${skill.name}` : skill.name;
3292
- console.log(` ${import_picocolors8.default.cyan("•")} ${label}`);
3293
- }
3294
- totalSkills += skills.length;
4000
+ function collectSkills(skillDir) {
4001
+ const skills = [];
4002
+ if (!existsSync5(skillDir))
4003
+ return skills;
4004
+ const entries = readdirSync4(skillDir, { withFileTypes: true });
4005
+ for (const entry of entries) {
4006
+ if (!entry.isDirectory())
4007
+ continue;
4008
+ const skillPath = join5(skillDir, entry.name);
4009
+ const skillMdPath = join5(skillPath, "SKILL.md");
4010
+ if (existsSync5(skillMdPath)) {
4011
+ const content = readFileSync5(skillMdPath, "utf-8");
4012
+ const frontmatter = parseFrontmatter3(content);
4013
+ skills.push({
4014
+ name: frontmatter.name || entry.name,
4015
+ path: skillPath,
4016
+ frontmatter
4017
+ });
3295
4018
  }
3296
4019
  }
3297
- if (totalSkills === 0) {
3298
- showEmpty("skills", "ock skill add");
4020
+ return skills.sort((a3, b3) => a3.name.localeCompare(b3.name));
4021
+ }
4022
+ function validateSkillName(name) {
4023
+ if (!name)
4024
+ return "Name is required";
4025
+ if (name.length < 1 || name.length > 64) {
4026
+ return "Name must be 1-64 characters";
4027
+ }
4028
+ if (!/^[a-z][a-z0-9-]*$/.test(name)) {
4029
+ return "Name must be lowercase letters, numbers, and hyphens (start with letter)";
3299
4030
  }
4031
+ return;
3300
4032
  }
3301
- async function addSkill(opencodePath) {
3302
- oe(import_picocolors8.default.bgYellow(import_picocolors8.default.black(" Add Skill ")));
3303
- const superpowersPath = join4(opencodePath, "superpowers", "skills");
3304
- const skills = collectSkills(superpowersPath);
4033
+ function validateDescription(desc) {
4034
+ if (!desc)
4035
+ return "Description is required";
4036
+ if (desc.length < 1 || desc.length > 1024) {
4037
+ return "Description must be 1-1024 characters";
4038
+ }
4039
+ return;
4040
+ }
4041
+ function listSkills(skillDir) {
4042
+ const skills = collectSkills(skillDir);
3305
4043
  if (skills.length === 0) {
3306
- showEmpty("skills available to install");
3307
- $e("Done");
4044
+ showEmpty("skills", "ock skill create");
3308
4045
  return;
3309
4046
  }
3310
- const selected = await ie({
3311
- message: "Select skill to install",
3312
- options: skills.map((s) => ({
3313
- value: s.name,
3314
- label: s.tier ? `${s.name} (${s.tier})` : s.name
3315
- }))
4047
+ f2.info(import_picocolors9.default.bold("Skills"));
4048
+ for (const skill of skills) {
4049
+ const desc = skill.frontmatter.description ? import_picocolors9.default.dim(` - ${skill.frontmatter.description.slice(0, 60)}${skill.frontmatter.description.length > 60 ? "..." : ""}`) : "";
4050
+ const version = skill.frontmatter.version ? import_picocolors9.default.yellow(` v${skill.frontmatter.version}`) : "";
4051
+ console.log(` ${import_picocolors9.default.cyan(skill.name)}${version}${desc}`);
4052
+ }
4053
+ console.log();
4054
+ f2.info(import_picocolors9.default.dim(`Found ${skills.length} skill${skills.length === 1 ? "" : "s"} in .opencode/skill/`));
4055
+ }
4056
+ async function createSkill(skillDir) {
4057
+ oe(import_picocolors9.default.bgYellow(import_picocolors9.default.black(" Create Skill ")));
4058
+ if (!existsSync5(skillDir)) {
4059
+ mkdirSync4(skillDir, { recursive: true });
4060
+ }
4061
+ const name = await te({
4062
+ message: "Skill name",
4063
+ placeholder: "my-skill",
4064
+ validate: (value) => {
4065
+ const error = validateSkillName(value);
4066
+ if (error)
4067
+ return error;
4068
+ if (existsSync5(join5(skillDir, value))) {
4069
+ return "Skill already exists";
4070
+ }
4071
+ return;
4072
+ }
3316
4073
  });
3317
- if (lD(selected)) {
4074
+ if (lD(name)) {
3318
4075
  ue("Cancelled");
3319
4076
  return;
3320
4077
  }
3321
- const skill = skills.find((s) => s.name === selected);
3322
- if (!skill)
4078
+ const description = await te({
4079
+ message: "Description (when should this skill be used?)",
4080
+ placeholder: "Use when implementing authentication flows...",
4081
+ validate: validateDescription
4082
+ });
4083
+ if (lD(description)) {
4084
+ ue("Cancelled");
3323
4085
  return;
3324
- const skillMdPath = join4(skill.path, "SKILL.md");
3325
- if (!existsSync4(skillMdPath)) {
3326
- notFound("SKILL.md in", skill.name);
3327
- $e(import_picocolors8.default.red("Failed"));
4086
+ }
4087
+ const version = await te({
4088
+ message: "Version",
4089
+ placeholder: "1.0.0",
4090
+ initialValue: "1.0.0"
4091
+ });
4092
+ if (lD(version)) {
4093
+ ue("Cancelled");
3328
4094
  return;
3329
4095
  }
3330
- const content = readFileSync4(skillMdPath, "utf-8");
3331
- const lines = content.split(`
3332
- `).slice(0, 10);
3333
- console.log();
3334
- console.log(import_picocolors8.default.dim("─".repeat(40)));
3335
- console.log(lines.join(`
3336
- `));
3337
- if (content.split(`
3338
- `).length > 10) {
3339
- console.log(import_picocolors8.default.dim("..."));
4096
+ const skillPath = join5(skillDir, String(name));
4097
+ mkdirSync4(skillPath, { recursive: true });
4098
+ const template = `---
4099
+ name: ${name}
4100
+ description: >
4101
+ ${description}
4102
+ version: "${version}"
4103
+ license: MIT
4104
+ ---
4105
+
4106
+ # ${String(name).split("-").map((w3) => w3.charAt(0).toUpperCase() + w3.slice(1)).join(" ")}
4107
+
4108
+ ## Overview
4109
+
4110
+ ${description}
4111
+
4112
+ ## When to Use
4113
+
4114
+ Use this skill when:
4115
+ - Condition 1
4116
+ - Condition 2
4117
+
4118
+ ## Instructions
4119
+
4120
+ <!-- Add your skill instructions here -->
4121
+
4122
+ 1. Step one
4123
+ 2. Step two
4124
+ 3. Step three
4125
+
4126
+ ## Examples
4127
+
4128
+ \`\`\`typescript
4129
+ // Example usage
4130
+ \`\`\`
4131
+
4132
+ ## Rules
4133
+
4134
+ 1. Rule one
4135
+ 2. Rule two
4136
+ `;
4137
+ writeFileSync5(join5(skillPath, "SKILL.md"), template);
4138
+ $e(import_picocolors9.default.green(`Created ${name} at .opencode/skill/${name}/SKILL.md`));
4139
+ }
4140
+ async function viewSkill(skillDir, skillNameArg) {
4141
+ const skills = collectSkills(skillDir);
4142
+ if (skills.length === 0) {
4143
+ showEmpty("skills", "ock skill create");
4144
+ return;
4145
+ }
4146
+ let skillName = skillNameArg;
4147
+ if (!skillName) {
4148
+ const options = skills.map((s) => ({
4149
+ value: s.name,
4150
+ label: s.name,
4151
+ hint: s.frontmatter.description?.slice(0, 50) || ""
4152
+ }));
4153
+ const selected = await ie({
4154
+ message: "Select skill to view",
4155
+ options
4156
+ });
4157
+ if (lD(selected)) {
4158
+ return;
4159
+ }
4160
+ skillName = selected;
3340
4161
  }
3341
- console.log(import_picocolors8.default.dim("─".repeat(40)));
4162
+ const skill = skills.find((s) => s.name === skillName || basename3(s.path) === skillName);
4163
+ if (!skill) {
4164
+ notFound("Skill", skillName);
4165
+ return;
4166
+ }
4167
+ const skillMdPath = join5(skill.path, "SKILL.md");
4168
+ const content = readFileSync5(skillMdPath, "utf-8");
3342
4169
  console.log();
3343
- f2.success(`Skill "${skill.name}" is available at:`);
3344
- f2.info(import_picocolors8.default.cyan(skill.path));
3345
- le(`Skills in superpowers/ are auto-loaded.
3346
- Use ${import_picocolors8.default.cyan("find_skills")} + ${import_picocolors8.default.cyan("use_skill")} in opencode.`, "Usage");
3347
- $e(import_picocolors8.default.green("Done!"));
3348
- }
3349
- async function viewSkill(opencodePath, skillName) {
3350
- const superpowersPath = join4(opencodePath, "superpowers", "skills");
3351
- const skills = collectSkills(superpowersPath);
4170
+ console.log(import_picocolors9.default.dim("─".repeat(60)));
4171
+ console.log(content);
4172
+ console.log(import_picocolors9.default.dim("─".repeat(60)));
4173
+ }
4174
+ async function removeSkill(skillDir, skillNameArg) {
4175
+ const skills = collectSkills(skillDir);
3352
4176
  if (skills.length === 0) {
3353
- showEmpty("skills");
4177
+ showEmpty("skills", "ock skill create");
3354
4178
  return;
3355
4179
  }
4180
+ let skillName = skillNameArg;
3356
4181
  if (!skillName) {
4182
+ const options = skills.map((s) => ({
4183
+ value: basename3(s.path),
4184
+ label: s.name,
4185
+ hint: s.frontmatter.description?.slice(0, 50) || ""
4186
+ }));
3357
4187
  const selected = await ie({
3358
- message: "Select skill to view",
3359
- options: skills.map((s) => ({ value: s.name, label: s.name }))
4188
+ message: "Select skill to remove",
4189
+ options
3360
4190
  });
3361
4191
  if (lD(selected)) {
3362
4192
  return;
3363
4193
  }
3364
4194
  skillName = selected;
3365
4195
  }
3366
- const skill = skills.find((s) => s.name === skillName);
4196
+ const skill = skills.find((s) => s.name === skillName || basename3(s.path) === skillName);
3367
4197
  if (!skill) {
3368
4198
  notFound("Skill", skillName);
3369
4199
  return;
3370
4200
  }
3371
- const skillMdPath = join4(skill.path, "SKILL.md");
3372
- if (!existsSync4(skillMdPath)) {
3373
- notFound("SKILL.md");
4201
+ const confirm = await se({
4202
+ message: `Remove skill "${skill.name}"? This will delete the entire folder.`,
4203
+ initialValue: false
4204
+ });
4205
+ if (lD(confirm) || !confirm) {
4206
+ ue("Cancelled");
3374
4207
  return;
3375
4208
  }
3376
- const content = readFileSync4(skillMdPath, "utf-8");
3377
- console.log();
3378
- console.log(import_picocolors8.default.dim("─".repeat(40)));
3379
- console.log(content);
3380
- console.log(import_picocolors8.default.dim("─".repeat(40)));
4209
+ rmSync(skill.path, { recursive: true, force: true });
4210
+ f2.success(`Removed skill "${skill.name}"`);
3381
4211
  }
3382
4212
 
3383
4213
  // src/commands/upgrade.ts
3384
4214
  import {
3385
4215
  copyFileSync,
3386
- existsSync as existsSync5,
3387
- mkdirSync as mkdirSync3,
3388
- readFileSync as readFileSync5,
3389
- readdirSync as readdirSync4,
3390
- writeFileSync as writeFileSync4
4216
+ existsSync as existsSync6,
4217
+ mkdirSync as mkdirSync5,
4218
+ readFileSync as readFileSync6,
4219
+ readdirSync as readdirSync5,
4220
+ rmSync as rmSync2,
4221
+ writeFileSync as writeFileSync6
3391
4222
  } from "node:fs";
3392
- import { dirname as dirname2, join as join5 } from "node:path";
4223
+ import { dirname as dirname2, join as join6 } from "node:path";
3393
4224
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3394
- var import_picocolors9 = __toESM(require_picocolors(), 1);
4225
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
3395
4226
  var PRESERVE_FILES = [
3396
- "opencode.json"
4227
+ "opencode.json",
4228
+ ".env"
3397
4229
  ];
3398
4230
  var PRESERVE_DIRS = [
3399
4231
  "agent",
3400
4232
  "command",
3401
- "memory"
4233
+ "memory",
4234
+ "skill",
4235
+ "tool"
3402
4236
  ];
3403
4237
  var SKIP_DIRS = ["node_modules", ".git", "dist", "coverage"];
3404
4238
  function getTemplateRoot2() {
3405
4239
  const __filename2 = fileURLToPath2(import.meta.url);
3406
4240
  const __dirname2 = dirname2(__filename2);
3407
4241
  const possiblePaths = [
3408
- join5(__dirname2, "template"),
3409
- join5(__dirname2, "..", "..", ".opencode")
4242
+ join6(__dirname2, "template"),
4243
+ join6(__dirname2, "..", "..", ".opencode")
3410
4244
  ];
3411
4245
  for (const path of possiblePaths) {
3412
- const opencodeDir = join5(path, ".opencode");
3413
- if (existsSync5(opencodeDir)) {
4246
+ const opencodeDir = join6(path, ".opencode");
4247
+ if (existsSync6(opencodeDir)) {
3414
4248
  return path;
3415
4249
  }
3416
4250
  }
3417
4251
  return null;
3418
4252
  }
3419
4253
  function getCurrentVersion(opencodeDir) {
3420
- const versionFile = join5(opencodeDir, ".version");
3421
- if (existsSync5(versionFile)) {
3422
- return readFileSync5(versionFile, "utf-8").trim();
4254
+ const versionFile = join6(opencodeDir, ".version");
4255
+ if (existsSync6(versionFile)) {
4256
+ return readFileSync6(versionFile, "utf-8").trim();
3423
4257
  }
3424
4258
  return null;
3425
4259
  }
@@ -3427,12 +4261,12 @@ function getPackageVersion() {
3427
4261
  const __filename2 = fileURLToPath2(import.meta.url);
3428
4262
  const __dirname2 = dirname2(__filename2);
3429
4263
  const pkgPaths = [
3430
- join5(__dirname2, "..", "..", "package.json"),
3431
- join5(__dirname2, "..", "package.json")
4264
+ join6(__dirname2, "..", "..", "package.json"),
4265
+ join6(__dirname2, "..", "package.json")
3432
4266
  ];
3433
4267
  for (const pkgPath of pkgPaths) {
3434
- if (existsSync5(pkgPath)) {
3435
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
4268
+ if (existsSync6(pkgPath)) {
4269
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
3436
4270
  return pkg.version;
3437
4271
  }
3438
4272
  }
@@ -3450,19 +4284,19 @@ async function checkVersion(opencodeDir) {
3450
4284
  function copyDirWithPreserve(src, dest, preserveFiles, preserveDirs) {
3451
4285
  const updated = [];
3452
4286
  const preserved = [];
3453
- if (!existsSync5(dest)) {
3454
- mkdirSync3(dest, { recursive: true });
4287
+ if (!existsSync6(dest)) {
4288
+ mkdirSync5(dest, { recursive: true });
3455
4289
  }
3456
- const entries = readdirSync4(src, { withFileTypes: true });
4290
+ const entries = readdirSync5(src, { withFileTypes: true });
3457
4291
  for (const entry of entries) {
3458
4292
  if (SKIP_DIRS.includes(entry.name))
3459
4293
  continue;
3460
- const srcPath = join5(src, entry.name);
3461
- const destPath = join5(dest, entry.name);
4294
+ const srcPath = join6(src, entry.name);
4295
+ const destPath = join6(dest, entry.name);
3462
4296
  if (entry.isDirectory()) {
3463
4297
  if (preserveDirs.includes(entry.name)) {
3464
- if (!existsSync5(destPath)) {
3465
- mkdirSync3(destPath, { recursive: true });
4298
+ if (!existsSync6(destPath)) {
4299
+ mkdirSync5(destPath, { recursive: true });
3466
4300
  }
3467
4301
  const subResult = copyDirPreserveExisting(srcPath, destPath);
3468
4302
  updated.push(...subResult.updated);
@@ -3472,7 +4306,7 @@ function copyDirWithPreserve(src, dest, preserveFiles, preserveDirs) {
3472
4306
  updated.push(...subResult.updated);
3473
4307
  }
3474
4308
  } else {
3475
- if (preserveFiles.includes(entry.name) && existsSync5(destPath)) {
4309
+ if (preserveFiles.includes(entry.name) && existsSync6(destPath)) {
3476
4310
  preserved.push(entry.name);
3477
4311
  } else {
3478
4312
  copyFileSync(srcPath, destPath);
@@ -3485,20 +4319,20 @@ function copyDirWithPreserve(src, dest, preserveFiles, preserveDirs) {
3485
4319
  function copyDirPreserveExisting(src, dest) {
3486
4320
  const updated = [];
3487
4321
  const preserved = [];
3488
- const entries = readdirSync4(src, { withFileTypes: true });
4322
+ const entries = readdirSync5(src, { withFileTypes: true });
3489
4323
  for (const entry of entries) {
3490
- const srcPath = join5(src, entry.name);
3491
- const destPath = join5(dest, entry.name);
4324
+ const srcPath = join6(src, entry.name);
4325
+ const destPath = join6(dest, entry.name);
3492
4326
  if (entry.isDirectory()) {
3493
- if (!existsSync5(destPath)) {
3494
- mkdirSync3(destPath, { recursive: true });
4327
+ if (!existsSync6(destPath)) {
4328
+ mkdirSync5(destPath, { recursive: true });
3495
4329
  const subResult = copyDirPreserveExisting(srcPath, destPath);
3496
4330
  updated.push(...subResult.updated);
3497
4331
  } else {
3498
- preserved.push(entry.name + "/");
4332
+ preserved.push(`${entry.name}/`);
3499
4333
  }
3500
4334
  } else {
3501
- if (!existsSync5(destPath)) {
4335
+ if (!existsSync6(destPath)) {
3502
4336
  copyFileSync(srcPath, destPath);
3503
4337
  updated.push(entry.name);
3504
4338
  } else {
@@ -3508,32 +4342,48 @@ function copyDirPreserveExisting(src, dest) {
3508
4342
  }
3509
4343
  return { updated, preserved };
3510
4344
  }
4345
+ function getAllFiles(dir, base = "") {
4346
+ const files = [];
4347
+ const entries = readdirSync5(dir, { withFileTypes: true });
4348
+ for (const entry of entries) {
4349
+ if (SKIP_DIRS.includes(entry.name))
4350
+ continue;
4351
+ const relativePath = join6(base, entry.name);
4352
+ const fullPath = join6(dir, entry.name);
4353
+ if (entry.isDirectory()) {
4354
+ files.push(...getAllFiles(fullPath, relativePath));
4355
+ } else {
4356
+ files.push(relativePath);
4357
+ }
4358
+ }
4359
+ return files;
4360
+ }
3511
4361
  async function upgradeCommand(options = {}) {
3512
4362
  if (process.argv.includes("--quiet"))
3513
4363
  return;
3514
4364
  const cwd = process.cwd();
3515
- const opencodeDir = join5(cwd, ".opencode");
3516
- if (!existsSync5(opencodeDir)) {
4365
+ const opencodeDir = join6(cwd, ".opencode");
4366
+ if (!existsSync6(opencodeDir)) {
3517
4367
  notInitialized();
3518
4368
  return;
3519
4369
  }
3520
- oe(import_picocolors9.default.bgCyan(import_picocolors9.default.black(" Upgrade ")));
4370
+ oe(import_picocolors10.default.bgCyan(import_picocolors10.default.black(" Upgrade ")));
3521
4371
  const versionInfo = await checkVersion(opencodeDir);
3522
4372
  console.log();
3523
- console.log(` ${import_picocolors9.default.bold("Installed")} ${import_picocolors9.default.cyan(versionInfo.current || "unknown")}`);
3524
- console.log(` ${import_picocolors9.default.bold("Latest")} ${import_picocolors9.default.cyan(versionInfo.latest || "unknown")}`);
4373
+ console.log(` ${import_picocolors10.default.bold("Installed")} ${import_picocolors10.default.cyan(versionInfo.current || "unknown")}`);
4374
+ console.log(` ${import_picocolors10.default.bold("Latest")} ${import_picocolors10.default.cyan(versionInfo.latest || "unknown")}`);
3525
4375
  console.log();
3526
4376
  if (options.check) {
3527
4377
  if (versionInfo.needsUpdate) {
3528
- f2.info(`Update available: ${import_picocolors9.default.cyan(versionInfo.latest)}`);
3529
- $e(`Run ${import_picocolors9.default.cyan("ock upgrade")} to update`);
4378
+ f2.info(`Update available: ${import_picocolors10.default.cyan(versionInfo.latest)}`);
4379
+ $e(`Run ${import_picocolors10.default.cyan("ock upgrade")} to update`);
3530
4380
  } else {
3531
- $e(import_picocolors9.default.green("Already up to date"));
4381
+ $e(import_picocolors10.default.green("Already up to date"));
3532
4382
  }
3533
4383
  return;
3534
4384
  }
3535
4385
  if (!versionInfo.needsUpdate && !options.force) {
3536
- $e(import_picocolors9.default.green("Already up to date"));
4386
+ $e(import_picocolors10.default.green("Already up to date"));
3537
4387
  return;
3538
4388
  }
3539
4389
  const templateRoot = getTemplateRoot2();
@@ -3541,8 +4391,8 @@ async function upgradeCommand(options = {}) {
3541
4391
  showError("Template not found", "Reinstall opencodekit");
3542
4392
  return;
3543
4393
  }
3544
- const templateOpencode = join5(templateRoot, ".opencode");
3545
- if (!existsSync5(templateOpencode)) {
4394
+ const templateOpencode = join6(templateRoot, ".opencode");
4395
+ if (!existsSync6(templateOpencode)) {
3546
4396
  showError("Template .opencode not found");
3547
4397
  return;
3548
4398
  }
@@ -3559,7 +4409,7 @@ async function upgradeCommand(options = {}) {
3559
4409
  s.start("Upgrading");
3560
4410
  const result = copyDirWithPreserve(templateOpencode, opencodeDir, PRESERVE_FILES, PRESERVE_DIRS);
3561
4411
  if (versionInfo.latest) {
3562
- writeFileSync4(join5(opencodeDir, ".version"), versionInfo.latest);
4412
+ writeFileSync6(join6(opencodeDir, ".version"), versionInfo.latest);
3563
4413
  }
3564
4414
  s.stop("Done");
3565
4415
  if (result.updated.length > 0) {
@@ -3568,13 +4418,55 @@ async function upgradeCommand(options = {}) {
3568
4418
  if (result.preserved.length > 0) {
3569
4419
  f2.info(`Preserved ${result.preserved.length} user files`);
3570
4420
  }
4421
+ const installedFiles = getAllFiles(opencodeDir);
4422
+ const templateFiles = getAllFiles(templateOpencode);
4423
+ const orphans = installedFiles.filter((file) => {
4424
+ if (file === ".version")
4425
+ return false;
4426
+ if (PRESERVE_FILES.includes(file))
4427
+ return false;
4428
+ if (file === ".env" || file.endsWith(".env"))
4429
+ return false;
4430
+ return !templateFiles.includes(file);
4431
+ });
4432
+ if (orphans.length > 0) {
4433
+ f2.info(`${import_picocolors10.default.yellow(orphans.length.toString())} files found that are not in the template (orphans).`);
4434
+ if (options.prune) {
4435
+ const s2 = de();
4436
+ s2.start("Pruning orphans");
4437
+ for (const orphan of orphans) {
4438
+ rmSync2(join6(opencodeDir, orphan));
4439
+ }
4440
+ s2.stop(`Removed ${orphans.length} files`);
4441
+ } else {
4442
+ const shouldPrune = await se({
4443
+ message: "Do you want to review and delete them?",
4444
+ initialValue: false
4445
+ });
4446
+ if (!lD(shouldPrune) && shouldPrune) {
4447
+ const selected = await ae({
4448
+ message: "Select files to delete",
4449
+ options: orphans.map((o2) => ({ value: o2, label: o2 })),
4450
+ required: false
4451
+ });
4452
+ if (!lD(selected) && selected.length > 0) {
4453
+ const s2 = de();
4454
+ s2.start("Deleting files");
4455
+ for (const file of selected) {
4456
+ rmSync2(join6(opencodeDir, file));
4457
+ }
4458
+ s2.stop(`Deleted ${selected.length} files`);
4459
+ }
4460
+ }
4461
+ }
4462
+ }
3571
4463
  le("cd .opencode && bun install", "Run if dependencies changed");
3572
- $e(import_picocolors9.default.green(`Upgraded to ${versionInfo.latest}`));
4464
+ $e(import_picocolors10.default.green(`Upgraded to ${versionInfo.latest}`));
3573
4465
  }
3574
4466
 
3575
4467
  // src/commands/menu.ts
3576
4468
  async function interactiveMenu(version) {
3577
- oe(import_picocolors10.default.bgCyan(import_picocolors10.default.black(` OpenCodeKit v${version} `)));
4469
+ oe(import_picocolors11.default.bgCyan(import_picocolors11.default.black(` OpenCodeKit v${version} `)));
3578
4470
  const action = await ie({
3579
4471
  message: "What do you want to do?",
3580
4472
  options: [
@@ -3625,115 +4517,1437 @@ async function interactiveMenu(version) {
3625
4517
  break;
3626
4518
  }
3627
4519
  }
4520
+ var KNOWN_CONFIG_PROPERTIES = new Set([
4521
+ "$schema",
4522
+ "model",
4523
+ "small_model",
4524
+ "theme",
4525
+ "share",
4526
+ "autoupdate",
4527
+ "mcp",
4528
+ "permission",
4529
+ "keybinds",
4530
+ "provider",
4531
+ "agent",
4532
+ "formatter",
4533
+ "plugin",
4534
+ "tools",
4535
+ "tui",
4536
+ "command",
4537
+ "watcher",
4538
+ "snapshot",
4539
+ "disabled_providers",
4540
+ "enabled_providers",
4541
+ "username",
4542
+ "lsp",
4543
+ "instructions",
4544
+ "enterprise",
4545
+ "experimental",
4546
+ "compaction"
4547
+ ]);
3628
4548
  async function doctorCommand() {
3629
4549
  if (process.argv.includes("--quiet"))
3630
4550
  return;
3631
4551
  const cwd = process.cwd();
3632
- const opencodeDir = join6(cwd, ".opencode");
4552
+ const opencodeDir = join7(cwd, ".opencode");
4553
+ oe(import_picocolors11.default.bgBlue(import_picocolors11.default.white(" Doctor - Health Check ")));
3633
4554
  const checks = [];
4555
+ const warnings = [];
4556
+ console.log();
4557
+ console.log(import_picocolors11.default.bold(" Structure"));
3634
4558
  checks.push({
3635
4559
  name: ".opencode/ exists",
3636
- ok: existsSync6(opencodeDir),
4560
+ ok: existsSync7(opencodeDir),
3637
4561
  fix: "ock init"
3638
4562
  });
4563
+ const configPath = join7(opencodeDir, "opencode.json");
3639
4564
  checks.push({
3640
4565
  name: "opencode.json exists",
3641
- ok: existsSync6(join6(opencodeDir, "opencode.json")),
4566
+ ok: existsSync7(configPath),
3642
4567
  fix: "ock init --force"
3643
4568
  });
3644
4569
  checks.push({
3645
4570
  name: "package.json exists",
3646
- ok: existsSync6(join6(opencodeDir, "package.json")),
4571
+ ok: existsSync7(join7(opencodeDir, "package.json")),
3647
4572
  fix: "ock init --force"
3648
4573
  });
3649
4574
  checks.push({
3650
4575
  name: "Dependencies installed",
3651
- ok: existsSync6(join6(opencodeDir, "node_modules")),
4576
+ ok: existsSync7(join7(opencodeDir, "node_modules")),
3652
4577
  fix: "cd .opencode && bun install"
3653
4578
  });
3654
- const agentDir = join6(opencodeDir, "agent");
3655
- const hasAgents = existsSync6(agentDir) && readdirSync5(agentDir).some((f3) => f3.endsWith(".md"));
3656
- checks.push({
3657
- name: "Agents configured",
3658
- ok: hasAgents,
3659
- fix: "ock agent add"
3660
- });
3661
- f2.info(import_picocolors10.default.bold("Health Check"));
4579
+ displayChecks(checks.slice(-4));
4580
+ if (existsSync7(configPath)) {
4581
+ console.log();
4582
+ console.log(import_picocolors11.default.bold(" Configuration"));
4583
+ const configChecks = [];
4584
+ try {
4585
+ const configContent = readFileSync7(configPath, "utf-8");
4586
+ const config = JSON.parse(configContent);
4587
+ configChecks.push({
4588
+ name: "$schema reference",
4589
+ ok: !!config.$schema,
4590
+ fix: 'Add "$schema": "https://opencode.ai/config.json"',
4591
+ warn: true
4592
+ });
4593
+ const unknownProps = Object.keys(config).filter((k2) => !KNOWN_CONFIG_PROPERTIES.has(k2));
4594
+ if (unknownProps.length > 0) {
4595
+ warnings.push({
4596
+ name: `Unknown config properties: ${unknownProps.join(", ")}`,
4597
+ ok: false,
4598
+ warn: true
4599
+ });
4600
+ }
4601
+ configChecks.push({
4602
+ name: "Model configured",
4603
+ ok: !!config.model,
4604
+ fix: "ock config model",
4605
+ warn: true
4606
+ });
4607
+ if (config.mcp) {
4608
+ const mcpServers = Object.keys(config.mcp);
4609
+ const invalidMcp = mcpServers.filter((name) => {
4610
+ const server = config.mcp[name];
4611
+ return !server.command && !server.url;
4612
+ });
4613
+ configChecks.push({
4614
+ name: `MCP servers (${mcpServers.length} configured)`,
4615
+ ok: invalidMcp.length === 0,
4616
+ fix: invalidMcp.length > 0 ? `Fix: ${invalidMcp.join(", ")} need command or url` : undefined
4617
+ });
4618
+ }
4619
+ if (config.agent) {
4620
+ const configuredAgents = Object.keys(config.agent);
4621
+ const agentDir2 = join7(opencodeDir, "agent");
4622
+ const missingFiles = configuredAgents.filter((name) => !existsSync7(join7(agentDir2, `${name}.md`)));
4623
+ if (missingFiles.length > 0) {
4624
+ warnings.push({
4625
+ name: `Agent config without files: ${missingFiles.join(", ")}`,
4626
+ ok: false,
4627
+ warn: true
4628
+ });
4629
+ }
4630
+ }
4631
+ checks.push(...configChecks);
4632
+ displayChecks(configChecks);
4633
+ } catch (err) {
4634
+ checks.push({
4635
+ name: "Valid JSON syntax",
4636
+ ok: false,
4637
+ fix: "Fix JSON syntax errors in opencode.json"
4638
+ });
4639
+ displayChecks([checks[checks.length - 1]]);
4640
+ }
4641
+ }
3662
4642
  console.log();
3663
- let issues = 0;
4643
+ console.log(import_picocolors11.default.bold(" Agents"));
4644
+ const agentDir = join7(opencodeDir, "agent");
4645
+ const agentChecks = [];
4646
+ if (existsSync7(agentDir)) {
4647
+ const agentFiles = readdirSync6(agentDir).filter((f3) => f3.endsWith(".md"));
4648
+ agentChecks.push({
4649
+ name: `Agent files (${agentFiles.length} found)`,
4650
+ ok: agentFiles.length > 0,
4651
+ fix: "ock agent create"
4652
+ });
4653
+ for (const file of agentFiles) {
4654
+ const content = readFileSync7(join7(agentDir, file), "utf-8");
4655
+ const hasFrontmatter = content.startsWith("---");
4656
+ const hasMode = content.includes("mode:");
4657
+ if (!hasFrontmatter) {
4658
+ warnings.push({
4659
+ name: `${file}: Missing frontmatter`,
4660
+ ok: false,
4661
+ warn: true
4662
+ });
4663
+ } else if (!hasMode) {
4664
+ warnings.push({
4665
+ name: `${file}: Missing mode in frontmatter`,
4666
+ ok: false,
4667
+ warn: true
4668
+ });
4669
+ }
4670
+ }
4671
+ } else {
4672
+ agentChecks.push({
4673
+ name: "agent/ directory",
4674
+ ok: false,
4675
+ fix: "ock agent create"
4676
+ });
4677
+ }
4678
+ checks.push(...agentChecks);
4679
+ displayChecks(agentChecks);
4680
+ console.log();
4681
+ console.log(import_picocolors11.default.bold(" Skills"));
4682
+ const skillDir = join7(opencodeDir, "skill");
4683
+ const skillChecks = [];
4684
+ if (existsSync7(skillDir)) {
4685
+ const skillFolders = readdirSync6(skillDir).filter((f3) => lstatSync2(join7(skillDir, f3)).isDirectory());
4686
+ const validSkills = skillFolders.filter((folder) => existsSync7(join7(skillDir, folder, "SKILL.md")));
4687
+ skillChecks.push({
4688
+ name: `Skills (${validSkills.length} valid)`,
4689
+ ok: validSkills.length > 0,
4690
+ fix: "ock skill create"
4691
+ });
4692
+ const invalidFolders = skillFolders.filter((f3) => !existsSync7(join7(skillDir, f3, "SKILL.md")));
4693
+ if (invalidFolders.length > 0) {
4694
+ warnings.push({
4695
+ name: `Skill folders missing SKILL.md: ${invalidFolders.slice(0, 3).join(", ")}${invalidFolders.length > 3 ? "..." : ""}`,
4696
+ ok: false,
4697
+ warn: true
4698
+ });
4699
+ }
4700
+ for (const folder of validSkills.slice(0, 5)) {
4701
+ const content = readFileSync7(join7(skillDir, folder, "SKILL.md"), "utf-8");
4702
+ const hasFrontmatter = content.startsWith("---");
4703
+ const hasName = content.includes("name:");
4704
+ const hasDescription = content.includes("description:");
4705
+ if (!hasFrontmatter || !hasName || !hasDescription) {
4706
+ warnings.push({
4707
+ name: `${folder}: Missing required frontmatter (name, description)`,
4708
+ ok: false,
4709
+ warn: true
4710
+ });
4711
+ }
4712
+ }
4713
+ } else {
4714
+ skillChecks.push({
4715
+ name: "skill/ directory",
4716
+ ok: false,
4717
+ fix: "ock skill create",
4718
+ warn: true
4719
+ });
4720
+ }
4721
+ checks.push(...skillChecks);
4722
+ displayChecks(skillChecks);
4723
+ console.log();
4724
+ console.log(import_picocolors11.default.bold(" Tools"));
4725
+ const toolDir = join7(opencodeDir, "tool");
4726
+ const toolChecks = [];
4727
+ if (existsSync7(toolDir)) {
4728
+ const toolFiles = readdirSync6(toolDir).filter((f3) => f3.endsWith(".ts"));
4729
+ toolChecks.push({
4730
+ name: `Custom tools (${toolFiles.length} found)`,
4731
+ ok: true
4732
+ });
4733
+ for (const file of toolFiles.slice(0, 5)) {
4734
+ const content = readFileSync7(join7(toolDir, file), "utf-8");
4735
+ const hasExport = content.includes("export default") || content.includes("export const");
4736
+ const hasTool = content.includes("tool(");
4737
+ if (!hasExport || !hasTool) {
4738
+ warnings.push({
4739
+ name: `${file}: Missing tool() export`,
4740
+ ok: false,
4741
+ warn: true
4742
+ });
4743
+ }
4744
+ }
4745
+ } else {
4746
+ toolChecks.push({
4747
+ name: "tool/ directory (optional)",
4748
+ ok: true
4749
+ });
4750
+ }
4751
+ checks.push(...toolChecks);
4752
+ displayChecks(toolChecks);
4753
+ const errors = checks.filter((c2) => !c2.ok && !c2.warn);
4754
+ const warningCount = warnings.length + checks.filter((c2) => !c2.ok && c2.warn).length;
4755
+ if (warnings.length > 0) {
4756
+ console.log();
4757
+ console.log(import_picocolors11.default.bold(" Warnings"));
4758
+ for (const warn of warnings.slice(0, 5)) {
4759
+ console.log(` ${import_picocolors11.default.yellow("!")} ${warn.name}`);
4760
+ }
4761
+ if (warnings.length > 5) {
4762
+ console.log(import_picocolors11.default.dim(` ... and ${warnings.length - 5} more`));
4763
+ }
4764
+ }
4765
+ console.log();
4766
+ if (errors.length === 0 && warningCount === 0) {
4767
+ $e(import_picocolors11.default.green("All checks passed!"));
4768
+ } else if (errors.length === 0) {
4769
+ $e(import_picocolors11.default.yellow(`Passed with ${warningCount} warning${warningCount > 1 ? "s" : ""}`));
4770
+ } else {
4771
+ $e(import_picocolors11.default.red(`${errors.length} error${errors.length > 1 ? "s" : ""}, ${warningCount} warning${warningCount > 1 ? "s" : ""}`));
4772
+ }
4773
+ }
4774
+ function displayChecks(checks) {
3664
4775
  for (const check of checks) {
3665
4776
  if (check.ok) {
3666
- console.log(` ${import_picocolors10.default.green("✓")} ${check.name}`);
4777
+ console.log(` ${import_picocolors11.default.green("✓")} ${check.name}`);
4778
+ } else if (check.warn) {
4779
+ console.log(` ${import_picocolors11.default.yellow("!")} ${check.name}`);
4780
+ if (check.fix) {
4781
+ console.log(` ${import_picocolors11.default.dim("→")} ${import_picocolors11.default.dim(check.fix)}`);
4782
+ }
3667
4783
  } else {
3668
- console.log(` ${import_picocolors10.default.red("✗")} ${check.name}`);
4784
+ console.log(` ${import_picocolors11.default.red("✗")} ${check.name}`);
3669
4785
  if (check.fix) {
3670
- console.log(` ${import_picocolors10.default.dim("→ Run:")} ${import_picocolors10.default.cyan(check.fix)}`);
4786
+ console.log(` ${import_picocolors11.default.dim("→ Run:")} ${import_picocolors11.default.cyan(check.fix)}`);
3671
4787
  }
3672
- issues++;
3673
4788
  }
3674
4789
  }
3675
- console.log();
3676
- if (issues === 0) {
3677
- $e(import_picocolors10.default.green("All checks passed!"));
3678
- } else {
3679
- $e(import_picocolors10.default.yellow(`${issues} issue${issues > 1 ? "s" : ""} found`));
3680
- }
3681
4790
  }
3682
4791
  async function statusCommand() {
3683
4792
  if (process.argv.includes("--quiet"))
3684
4793
  return;
3685
4794
  const cwd = process.cwd();
3686
- const opencodeDir = join6(cwd, ".opencode");
3687
- if (!existsSync6(opencodeDir)) {
4795
+ const opencodeDir = join7(cwd, ".opencode");
4796
+ if (!existsSync7(opencodeDir)) {
3688
4797
  notInitialized();
3689
4798
  return;
3690
4799
  }
3691
- const projectName = basename2(cwd);
3692
- oe(import_picocolors10.default.bgCyan(import_picocolors10.default.black(` ${projectName} `)));
3693
- const agentDir = join6(opencodeDir, "agent");
4800
+ const projectName = basename4(cwd);
4801
+ oe(import_picocolors11.default.bgCyan(import_picocolors11.default.black(` ${projectName} `)));
4802
+ const agentDir = join7(opencodeDir, "agent");
3694
4803
  let agentNames = [];
3695
- if (existsSync6(agentDir)) {
3696
- agentNames = readdirSync5(agentDir).filter((f3) => f3.endsWith(".md") && lstatSync3(join6(agentDir, f3)).isFile()).map((f3) => f3.replace(".md", ""));
4804
+ if (existsSync7(agentDir)) {
4805
+ agentNames = readdirSync6(agentDir).filter((f3) => f3.endsWith(".md") && lstatSync2(join7(agentDir, f3)).isFile()).map((f3) => f3.replace(".md", ""));
3697
4806
  }
3698
- const superpowersPath = join6(opencodeDir, "superpowers", "skills");
4807
+ const skillDir = join7(opencodeDir, "skill");
3699
4808
  let skillCount = 0;
3700
- if (existsSync6(superpowersPath)) {
3701
- const tiers = ["core", "stack", "specialized"];
3702
- let hasTiers = false;
3703
- for (const tier of tiers) {
3704
- const tierPath = join6(superpowersPath, tier);
3705
- if (existsSync6(tierPath)) {
3706
- hasTiers = true;
3707
- skillCount += readdirSync5(tierPath).filter((e2) => lstatSync3(join6(tierPath, e2)).isDirectory()).length;
3708
- }
3709
- }
3710
- if (!hasTiers) {
3711
- skillCount = readdirSync5(superpowersPath).filter((e2) => lstatSync3(join6(superpowersPath, e2)).isDirectory()).length;
3712
- }
4809
+ if (existsSync7(skillDir)) {
4810
+ skillCount = readdirSync6(skillDir).filter((f3) => lstatSync2(join7(skillDir, f3)).isDirectory() && existsSync7(join7(skillDir, f3, "SKILL.md"))).length;
3713
4811
  }
3714
- const commandDir = join6(opencodeDir, "command");
4812
+ const commandDir = join7(opencodeDir, "command");
3715
4813
  let commandCount = 0;
3716
- if (existsSync6(commandDir)) {
3717
- commandCount = readdirSync5(commandDir).filter((f3) => f3.endsWith(".md")).length;
4814
+ if (existsSync7(commandDir)) {
4815
+ commandCount = readdirSync6(commandDir).filter((f3) => f3.endsWith(".md")).length;
4816
+ }
4817
+ const toolDir = join7(opencodeDir, "tool");
4818
+ let toolCount = 0;
4819
+ if (existsSync7(toolDir)) {
4820
+ toolCount = readdirSync6(toolDir).filter((f3) => f3.endsWith(".ts")).length;
4821
+ }
4822
+ const configPath = join7(opencodeDir, "opencode.json");
4823
+ let mcpCount = 0;
4824
+ if (existsSync7(configPath)) {
4825
+ try {
4826
+ const config = JSON.parse(readFileSync7(configPath, "utf-8"));
4827
+ mcpCount = Object.keys(config.mcp || {}).length;
4828
+ } catch {}
3718
4829
  }
3719
4830
  console.log();
3720
- console.log(` ${import_picocolors10.default.bold("Agents")} ${import_picocolors10.default.cyan(String(agentNames.length))}`);
4831
+ console.log(` ${import_picocolors11.default.bold("Agents")} ${import_picocolors11.default.cyan(String(agentNames.length))}`);
3721
4832
  if (agentNames.length > 0) {
3722
- console.log(` ${import_picocolors10.default.dim(agentNames.join(", "))}`);
4833
+ console.log(` ${import_picocolors11.default.dim(agentNames.join(", "))}`);
3723
4834
  }
3724
4835
  console.log();
3725
- console.log(` ${import_picocolors10.default.bold("Skills")} ${import_picocolors10.default.cyan(String(skillCount))}`);
4836
+ console.log(` ${import_picocolors11.default.bold("Skills")} ${import_picocolors11.default.cyan(String(skillCount))}`);
4837
+ console.log();
4838
+ console.log(` ${import_picocolors11.default.bold("Commands")} ${import_picocolors11.default.cyan(String(commandCount))}`);
3726
4839
  console.log();
3727
- console.log(` ${import_picocolors10.default.bold("Commands")} ${import_picocolors10.default.cyan(String(commandCount))}`);
4840
+ console.log(` ${import_picocolors11.default.bold("Tools")} ${import_picocolors11.default.cyan(String(toolCount))}`);
3728
4841
  console.log();
3729
- if (!existsSync6(join6(opencodeDir, "node_modules"))) {
4842
+ console.log(` ${import_picocolors11.default.bold("MCP")} ${import_picocolors11.default.cyan(String(mcpCount))}`);
4843
+ console.log();
4844
+ if (!existsSync7(join7(opencodeDir, "node_modules"))) {
3730
4845
  showWarning("Dependencies not installed", "cd .opencode && bun install");
3731
4846
  }
3732
- $e(import_picocolors10.default.dim(".opencode/"));
4847
+ $e(import_picocolors11.default.dim(".opencode/"));
4848
+ }
4849
+
4850
+ // src/tui/components/Browser.ts
4851
+ var import_picocolors12 = __toESM(require_picocolors(), 1);
4852
+ import { spawn } from "node:child_process";
4853
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "node:fs";
4854
+ import { join as join8 } from "node:path";
4855
+
4856
+ // src/tui/utils/keyboard.ts
4857
+ import * as readline from "node:readline";
4858
+ function createKeyboardController() {
4859
+ let rl = null;
4860
+ let handler = null;
4861
+ const keyListener = (_3, key) => {
4862
+ if (!handler)
4863
+ return;
4864
+ const ctrl = key.ctrl ?? false;
4865
+ if (key.name === "up")
4866
+ handler("up", ctrl);
4867
+ else if (key.name === "down")
4868
+ handler("down", ctrl);
4869
+ else if (key.name === "left")
4870
+ handler("left", ctrl);
4871
+ else if (key.name === "right")
4872
+ handler("right", ctrl);
4873
+ else if (key.name === "return")
4874
+ handler("enter", ctrl);
4875
+ else if (key.name === "escape")
4876
+ handler("escape", ctrl);
4877
+ else if (key.name === "backspace")
4878
+ handler("backspace", ctrl);
4879
+ else if (key.name === "tab")
4880
+ handler("tab", ctrl);
4881
+ else if (key.name === "c" && ctrl)
4882
+ handler("quit", ctrl);
4883
+ else if (key.sequence)
4884
+ handler(key.sequence, ctrl);
4885
+ };
4886
+ return {
4887
+ start(h2) {
4888
+ handler = h2;
4889
+ rl = readline.createInterface({
4890
+ input: process.stdin,
4891
+ output: process.stdout
4892
+ });
4893
+ if (process.stdin.isTTY) {
4894
+ process.stdin.setRawMode(true);
4895
+ }
4896
+ readline.emitKeypressEvents(process.stdin);
4897
+ process.stdin.on("keypress", keyListener);
4898
+ },
4899
+ stop() {
4900
+ if (process.stdin.isTTY) {
4901
+ process.stdin.setRawMode(false);
4902
+ }
4903
+ process.stdin.removeListener("keypress", keyListener);
4904
+ if (rl) {
4905
+ rl.close();
4906
+ rl = null;
4907
+ }
4908
+ handler = null;
4909
+ }
4910
+ };
4911
+ }
4912
+
4913
+ // src/tui/components/Browser.ts
4914
+ async function runBrowser(options) {
4915
+ const {
4916
+ title,
4917
+ items,
4918
+ icon = "●",
4919
+ color: itemColor = import_picocolors12.default.cyan,
4920
+ onSelect,
4921
+ onBack
4922
+ } = options;
4923
+ if (items.length === 0) {
4924
+ console.clear();
4925
+ console.log();
4926
+ printEmptyBox(title);
4927
+ console.log();
4928
+ console.log(import_picocolors12.default.dim(" Press any key to go back..."));
4929
+ return new Promise((resolve) => {
4930
+ const kb = createKeyboardController();
4931
+ kb.start(() => {
4932
+ kb.stop();
4933
+ resolve();
4934
+ });
4935
+ });
4936
+ }
4937
+ let selectedIndex = 0;
4938
+ const pageSize = 14;
4939
+ const render = () => {
4940
+ console.clear();
4941
+ const totalWidth = Math.min(process.stdout.columns || 80, 120) - 4;
4942
+ const listWidth = Math.floor(totalWidth * 0.35);
4943
+ const previewWidth = totalWidth - listWidth - 3;
4944
+ console.log();
4945
+ console.log(` ${import_picocolors12.default.bgCyan(import_picocolors12.default.black(` ${title} Browser `))} ${import_picocolors12.default.dim(`${items.length} items`)}`);
4946
+ console.log();
4947
+ const startIdx = Math.max(0, Math.min(selectedIndex - Math.floor(pageSize / 2), items.length - pageSize));
4948
+ const endIdx = Math.min(startIdx + pageSize, items.length);
4949
+ const visibleItems = items.slice(startIdx, endIdx);
4950
+ const selected = items[selectedIndex];
4951
+ const previewLines = getPreviewLines(selected, previewWidth - 2);
4952
+ const maxRows = Math.max(visibleItems.length, previewLines.length, pageSize);
4953
+ const listTitle = ` ${items.length} items `;
4954
+ const previewTitle = " Preview ";
4955
+ console.log(import_picocolors12.default.dim(` ┌${listTitle}${"─".repeat(listWidth - listTitle.length)}┬${previewTitle}${"─".repeat(previewWidth - previewTitle.length)}┐`));
4956
+ for (let i = 0;i < maxRows; i++) {
4957
+ let leftContent = "";
4958
+ let rightContent = "";
4959
+ if (i < visibleItems.length) {
4960
+ const itemIdx = startIdx + i;
4961
+ const item = visibleItems[i];
4962
+ const isSelected = itemIdx === selectedIndex;
4963
+ const pointer = isSelected ? import_picocolors12.default.cyan("▶") : " ";
4964
+ const itemIcon = isSelected ? itemColor(icon) : import_picocolors12.default.dim(icon);
4965
+ const name = isSelected ? import_picocolors12.default.bold(itemColor(truncate(item.name, listWidth - 6))) : truncate(item.name, listWidth - 6);
4966
+ leftContent = `${pointer}${itemIcon} ${name}`;
4967
+ }
4968
+ if (i < previewLines.length) {
4969
+ rightContent = previewLines[i];
4970
+ }
4971
+ const leftPadded = padRight2(leftContent, listWidth - 1);
4972
+ const rightPadded = padRight2(rightContent, previewWidth - 1);
4973
+ console.log(` ${import_picocolors12.default.dim("│")} ${leftPadded}${import_picocolors12.default.dim("│")} ${rightPadded}${import_picocolors12.default.dim("│")}`);
4974
+ }
4975
+ console.log(import_picocolors12.default.dim(` └${"─".repeat(listWidth)}┴${"─".repeat(previewWidth)}┘`));
4976
+ if (items.length > pageSize) {
4977
+ const scrollPos = Math.round(selectedIndex / (items.length - 1) * 100);
4978
+ console.log(import_picocolors12.default.dim(` ${selectedIndex + 1}/${items.length} (${scrollPos}%)`));
4979
+ }
4980
+ console.log();
4981
+ console.log(` ${import_picocolors12.default.dim("[")}${import_picocolors12.default.cyan("↑↓")}${import_picocolors12.default.dim("] Navigate")} ` + `${import_picocolors12.default.dim("[")}${import_picocolors12.default.green("Enter")}${import_picocolors12.default.dim("] View")} ` + `${import_picocolors12.default.dim("[")}${import_picocolors12.default.yellow("e")}${import_picocolors12.default.dim("] Edit")} ` + `${import_picocolors12.default.dim("[")}${import_picocolors12.default.red("q")}${import_picocolors12.default.dim("] Back")}`);
4982
+ console.log();
4983
+ };
4984
+ return new Promise((resolve) => {
4985
+ const kb = createKeyboardController();
4986
+ const handleKey = async (key) => {
4987
+ switch (key) {
4988
+ case "up":
4989
+ selectedIndex = Math.max(0, selectedIndex - 1);
4990
+ render();
4991
+ break;
4992
+ case "down":
4993
+ selectedIndex = Math.min(items.length - 1, selectedIndex + 1);
4994
+ render();
4995
+ break;
4996
+ case "enter":
4997
+ kb.stop();
4998
+ await showFileViewer(items[selectedIndex]);
4999
+ render();
5000
+ kb.start(handleKey);
5001
+ break;
5002
+ case "e":
5003
+ kb.stop();
5004
+ await openInEditor(items[selectedIndex]);
5005
+ render();
5006
+ kb.start(handleKey);
5007
+ break;
5008
+ case "escape":
5009
+ case "q":
5010
+ kb.stop();
5011
+ if (onBack)
5012
+ onBack();
5013
+ resolve();
5014
+ break;
5015
+ case "quit":
5016
+ kb.stop();
5017
+ process.exit(0);
5018
+ }
5019
+ };
5020
+ render();
5021
+ kb.start(handleKey);
5022
+ });
5023
+ }
5024
+ function printEmptyBox(title) {
5025
+ const width = Math.min(process.stdout.columns || 80, 120) - 4;
5026
+ console.log(import_picocolors12.default.dim(` ┌${"─".repeat(width - 2)}┐`));
5027
+ console.log(` ${import_picocolors12.default.dim("│")} ${padRight2(import_picocolors12.default.yellow(`No ${title.toLowerCase()} found`), width - 4)}${import_picocolors12.default.dim("│")}`);
5028
+ console.log(` ${import_picocolors12.default.dim("│")} ${padRight2("", width - 4)}${import_picocolors12.default.dim("│")}`);
5029
+ console.log(` ${import_picocolors12.default.dim("│")} ${padRight2(import_picocolors12.default.dim("Check your .opencode/ directory"), width - 4)}${import_picocolors12.default.dim("│")}`);
5030
+ console.log(import_picocolors12.default.dim(` └${"─".repeat(width - 2)}┘`));
5031
+ }
5032
+ function getPreviewLines(item, maxWidth) {
5033
+ const lines = [];
5034
+ lines.push(import_picocolors12.default.bold(import_picocolors12.default.cyan(item.name)));
5035
+ lines.push(import_picocolors12.default.dim("─".repeat(Math.min(maxWidth, 40))));
5036
+ const shortPath = item.path.replace(process.cwd(), ".");
5037
+ lines.push(import_picocolors12.default.dim(`Path: ${truncate(shortPath, maxWidth - 6)}`));
5038
+ lines.push("");
5039
+ if (!existsSync8(item.path)) {
5040
+ lines.push(import_picocolors12.default.red("(file not found)"));
5041
+ return lines;
5042
+ }
5043
+ try {
5044
+ const content = readFileSync8(item.path, "utf-8");
5045
+ const contentLines = content.split(`
5046
+ `);
5047
+ let inFrontmatter = false;
5048
+ let description = "";
5049
+ let startLine = 0;
5050
+ for (let i = 0;i < Math.min(contentLines.length, 20); i++) {
5051
+ const line = contentLines[i].trim();
5052
+ if (i === 0 && line === "---") {
5053
+ inFrontmatter = true;
5054
+ continue;
5055
+ }
5056
+ if (inFrontmatter && line === "---") {
5057
+ inFrontmatter = false;
5058
+ startLine = i + 1;
5059
+ continue;
5060
+ }
5061
+ if (inFrontmatter && line.startsWith("description:")) {
5062
+ description = line.replace("description:", "").trim();
5063
+ }
5064
+ }
5065
+ if (description) {
5066
+ lines.push(import_picocolors12.default.green("Description:"));
5067
+ const descWords = description.split(" ");
5068
+ let currentLine = "";
5069
+ for (const word of descWords) {
5070
+ if (`${currentLine} ${word}`.length > maxWidth - 2) {
5071
+ lines.push(` ${currentLine.trim()}`);
5072
+ currentLine = word;
5073
+ } else {
5074
+ currentLine += ` ${word}`;
5075
+ }
5076
+ }
5077
+ if (currentLine.trim()) {
5078
+ lines.push(` ${currentLine.trim()}`);
5079
+ }
5080
+ lines.push("");
5081
+ }
5082
+ lines.push(import_picocolors12.default.dim("Content:"));
5083
+ const previewLines = contentLines.slice(startLine, startLine + 8);
5084
+ for (const line of previewLines) {
5085
+ if (line.length > maxWidth - 2) {
5086
+ lines.push(import_picocolors12.default.dim(` ${line.substring(0, maxWidth - 5)}...`));
5087
+ } else {
5088
+ lines.push(import_picocolors12.default.dim(` ${line}`));
5089
+ }
5090
+ }
5091
+ if (contentLines.length > startLine + 8) {
5092
+ lines.push("");
5093
+ lines.push(import_picocolors12.default.dim(` ... ${contentLines.length - startLine - 8} more lines`));
5094
+ }
5095
+ } catch {
5096
+ lines.push(import_picocolors12.default.red("(could not read file)"));
5097
+ }
5098
+ return lines;
5099
+ }
5100
+ function truncate(str, maxLen) {
5101
+ if (str.length <= maxLen)
5102
+ return str;
5103
+ return `${str.substring(0, maxLen - 1)}…`;
5104
+ }
5105
+ function padRight2(str, len) {
5106
+ const stripped = stripAnsi(str);
5107
+ const pad = Math.max(0, len - stripped.length);
5108
+ return str + " ".repeat(pad);
5109
+ }
5110
+ function stripAnsi(str) {
5111
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
5112
+ }
5113
+ async function showFileViewer(item) {
5114
+ if (!existsSync8(item.path)) {
5115
+ console.clear();
5116
+ console.log();
5117
+ console.log(import_picocolors12.default.red(` File not found: ${item.path}`));
5118
+ console.log(import_picocolors12.default.dim(" Press any key to go back..."));
5119
+ await waitForKey();
5120
+ return;
5121
+ }
5122
+ const content = readFileSync8(item.path, "utf-8");
5123
+ const lines = content.split(`
5124
+ `);
5125
+ let scrollOffset = 0;
5126
+ const pageSize = (process.stdout.rows || 24) - 6;
5127
+ const renderViewer = () => {
5128
+ console.clear();
5129
+ const width = Math.min(process.stdout.columns || 80, 120) - 4;
5130
+ console.log();
5131
+ console.log(` ${import_picocolors12.default.bgGreen(import_picocolors12.default.black(" View "))} ${import_picocolors12.default.cyan(item.name)}`);
5132
+ console.log(import_picocolors12.default.dim(` ${item.path.replace(process.cwd(), ".")}`));
5133
+ console.log();
5134
+ console.log(import_picocolors12.default.dim(` ┌${"─".repeat(width - 2)}┐`));
5135
+ const visibleLines = lines.slice(scrollOffset, scrollOffset + pageSize);
5136
+ for (let i = 0;i < pageSize; i++) {
5137
+ const lineNum = scrollOffset + i + 1;
5138
+ const lineContent = visibleLines[i] ?? "";
5139
+ const numStr = import_picocolors12.default.dim(`${String(lineNum).padStart(4, " ")} │ `);
5140
+ const truncated = lineContent.length > width - 10 ? `${lineContent.substring(0, width - 13)}...` : lineContent;
5141
+ console.log(` ${import_picocolors12.default.dim("│")}${numStr}${truncated.padEnd(width - 10)}${import_picocolors12.default.dim("│")}`);
5142
+ }
5143
+ console.log(import_picocolors12.default.dim(` └${"─".repeat(width - 2)}┘`));
5144
+ console.log();
5145
+ console.log(` ${import_picocolors12.default.dim("[")}${import_picocolors12.default.cyan("↑↓")}${import_picocolors12.default.dim("] Scroll")} ` + `${import_picocolors12.default.dim("[")}${import_picocolors12.default.yellow("PgUp/PgDn")}${import_picocolors12.default.dim("] Page")} ` + `${import_picocolors12.default.dim("Line")} ${scrollOffset + 1}-${Math.min(scrollOffset + pageSize, lines.length)}/${lines.length} ` + `${import_picocolors12.default.dim("[")}${import_picocolors12.default.red("q")}${import_picocolors12.default.dim("] Back")}`);
5146
+ };
5147
+ return new Promise((resolve) => {
5148
+ const kb = createKeyboardController();
5149
+ const handleKey = (key) => {
5150
+ switch (key) {
5151
+ case "up":
5152
+ scrollOffset = Math.max(0, scrollOffset - 1);
5153
+ renderViewer();
5154
+ break;
5155
+ case "down":
5156
+ scrollOffset = Math.min(lines.length - pageSize, scrollOffset + 1);
5157
+ renderViewer();
5158
+ break;
5159
+ case "pageup":
5160
+ case "left":
5161
+ scrollOffset = Math.max(0, scrollOffset - pageSize);
5162
+ renderViewer();
5163
+ break;
5164
+ case "pagedown":
5165
+ case "right":
5166
+ scrollOffset = Math.min(lines.length - pageSize, scrollOffset + pageSize);
5167
+ renderViewer();
5168
+ break;
5169
+ case "escape":
5170
+ case "q":
5171
+ kb.stop();
5172
+ resolve();
5173
+ break;
5174
+ }
5175
+ };
5176
+ renderViewer();
5177
+ kb.start(handleKey);
5178
+ });
5179
+ }
5180
+ async function openInEditor(item) {
5181
+ const editor = process.env.EDITOR || process.env.VISUAL || "vim";
5182
+ console.clear();
5183
+ console.log();
5184
+ console.log(import_picocolors12.default.cyan(` Opening ${item.name} in ${editor}...`));
5185
+ return new Promise((resolve) => {
5186
+ const child = spawn(editor, [item.path], {
5187
+ stdio: "inherit"
5188
+ });
5189
+ child.on("exit", () => {
5190
+ resolve();
5191
+ });
5192
+ child.on("error", async () => {
5193
+ console.log(import_picocolors12.default.red(` Failed to open editor: ${editor}`));
5194
+ console.log(import_picocolors12.default.dim(" Set $EDITOR environment variable"));
5195
+ console.log(import_picocolors12.default.dim(" Press any key to continue..."));
5196
+ await waitForKey();
5197
+ resolve();
5198
+ });
5199
+ });
5200
+ }
5201
+ function waitForKey() {
5202
+ return new Promise((resolve) => {
5203
+ const kb = createKeyboardController();
5204
+ kb.start(() => {
5205
+ kb.stop();
5206
+ resolve();
5207
+ });
5208
+ });
5209
+ }
5210
+ function loadAgentItems() {
5211
+ const cwd = process.cwd();
5212
+ const agentDir = join8(cwd, ".opencode", "agent");
5213
+ if (!existsSync8(agentDir))
5214
+ return [];
5215
+ const { readdirSync: readdirSync7, lstatSync: lstatSync3 } = __require("node:fs");
5216
+ return readdirSync7(agentDir).filter((f3) => f3.endsWith(".md") && lstatSync3(join8(agentDir, f3)).isFile()).map((f3) => ({
5217
+ name: f3.replace(".md", ""),
5218
+ path: join8(agentDir, f3)
5219
+ }));
5220
+ }
5221
+ function loadSkillItems() {
5222
+ const cwd = process.cwd();
5223
+ const skillDir = join8(cwd, ".opencode", "skill");
5224
+ if (!existsSync8(skillDir))
5225
+ return [];
5226
+ const { readdirSync: readdirSync7, lstatSync: lstatSync3 } = __require("node:fs");
5227
+ return readdirSync7(skillDir).filter((f3) => lstatSync3(join8(skillDir, f3)).isDirectory() && existsSync8(join8(skillDir, f3, "SKILL.md"))).map((f3) => ({
5228
+ name: f3,
5229
+ path: join8(skillDir, f3, "SKILL.md")
5230
+ }));
5231
+ }
5232
+
5233
+ // src/tui/components/ConfigEditor.ts
5234
+ var import_picocolors13 = __toESM(require_picocolors(), 1);
5235
+ import { existsSync as existsSync9, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "node:fs";
5236
+ import { join as join9 } from "node:path";
5237
+ async function runConfigEditor() {
5238
+ const configPath = join9(process.cwd(), ".opencode", "opencode.json");
5239
+ if (!existsSync9(configPath)) {
5240
+ console.clear();
5241
+ console.log();
5242
+ console.log(import_picocolors13.default.red(" Config file not found: .opencode/opencode.json"));
5243
+ console.log(import_picocolors13.default.dim(" Press any key to go back..."));
5244
+ return new Promise((resolve) => {
5245
+ const kb = createKeyboardController();
5246
+ kb.start(() => {
5247
+ kb.stop();
5248
+ resolve();
5249
+ });
5250
+ });
5251
+ }
5252
+ let config;
5253
+ try {
5254
+ config = JSON.parse(readFileSync9(configPath, "utf-8"));
5255
+ } catch {
5256
+ console.clear();
5257
+ console.log();
5258
+ console.log(import_picocolors13.default.red(" Failed to parse config file"));
5259
+ console.log(import_picocolors13.default.dim(" Press any key to go back..."));
5260
+ return new Promise((resolve) => {
5261
+ const kb = createKeyboardController();
5262
+ kb.start(() => {
5263
+ kb.stop();
5264
+ resolve();
5265
+ });
5266
+ });
5267
+ }
5268
+ const state = {
5269
+ path: [],
5270
+ selectedIndex: 0,
5271
+ editMode: false,
5272
+ editBuffer: "",
5273
+ message: "",
5274
+ messageType: "info"
5275
+ };
5276
+ const getSections = () => {
5277
+ let current = config;
5278
+ for (const key of state.path) {
5279
+ if (typeof current === "object" && current !== null) {
5280
+ current = current[key];
5281
+ }
5282
+ }
5283
+ if (typeof current !== "object" || current === null) {
5284
+ return [];
5285
+ }
5286
+ return Object.entries(current).map(([key, value]) => ({
5287
+ key,
5288
+ value,
5289
+ type: getType(value)
5290
+ }));
5291
+ };
5292
+ const getType = (value) => {
5293
+ if (value === null)
5294
+ return "null";
5295
+ if (Array.isArray(value))
5296
+ return "array";
5297
+ return typeof value;
5298
+ };
5299
+ const formatValue = (section) => {
5300
+ if (section.type === "object") {
5301
+ const keys = Object.keys(section.value);
5302
+ return import_picocolors13.default.dim(`{${keys.length} keys}`);
5303
+ }
5304
+ if (section.type === "array") {
5305
+ const arr = section.value;
5306
+ return import_picocolors13.default.dim(`[${arr.length} items]`);
5307
+ }
5308
+ if (section.type === "string") {
5309
+ const str = section.value;
5310
+ if (str.length > 35) {
5311
+ return import_picocolors13.default.green(`"${str.substring(0, 32)}..."`);
5312
+ }
5313
+ return import_picocolors13.default.green(`"${str}"`);
5314
+ }
5315
+ if (section.type === "number") {
5316
+ return import_picocolors13.default.yellow(String(section.value));
5317
+ }
5318
+ if (section.type === "boolean") {
5319
+ return section.value ? import_picocolors13.default.green("true") : import_picocolors13.default.red("false");
5320
+ }
5321
+ return import_picocolors13.default.dim("null");
5322
+ };
5323
+ const getTypeIcon = (section) => {
5324
+ switch (section.type) {
5325
+ case "object":
5326
+ return import_picocolors13.default.cyan("{}");
5327
+ case "array":
5328
+ return import_picocolors13.default.cyan("[]");
5329
+ case "string":
5330
+ return import_picocolors13.default.green('""');
5331
+ case "number":
5332
+ return import_picocolors13.default.yellow("#");
5333
+ case "boolean":
5334
+ return import_picocolors13.default.magenta("◉");
5335
+ default:
5336
+ return import_picocolors13.default.dim("○");
5337
+ }
5338
+ };
5339
+ const setValue = (newValue) => {
5340
+ const sections = getSections();
5341
+ const section = sections[state.selectedIndex];
5342
+ if (!section)
5343
+ return false;
5344
+ let target = config;
5345
+ for (const key of state.path) {
5346
+ target = target[key];
5347
+ }
5348
+ target[section.key] = newValue;
5349
+ return true;
5350
+ };
5351
+ const saveConfig2 = () => {
5352
+ try {
5353
+ writeFileSync7(configPath, JSON.stringify(config, null, 2));
5354
+ return true;
5355
+ } catch {
5356
+ return false;
5357
+ }
5358
+ };
5359
+ const render = () => {
5360
+ console.clear();
5361
+ const width = Math.min(process.stdout.columns || 80, 120) - 4;
5362
+ const pathStr = state.path.length > 0 ? state.path.join(" → ") : "root";
5363
+ console.log();
5364
+ console.log(` ${import_picocolors13.default.bgCyan(import_picocolors13.default.black(" Config Editor "))} ${import_picocolors13.default.dim(pathStr)}`);
5365
+ console.log();
5366
+ const sections = getSections();
5367
+ const pageSize = 15;
5368
+ const startIdx = Math.max(0, Math.min(state.selectedIndex - Math.floor(pageSize / 2), sections.length - pageSize));
5369
+ const endIdx = Math.min(startIdx + pageSize, sections.length);
5370
+ const boxTitle = ` ${sections.length} items `;
5371
+ console.log(import_picocolors13.default.dim(` ┌${boxTitle}${"─".repeat(width - boxTitle.length - 3)}┐`));
5372
+ if (sections.length === 0) {
5373
+ console.log(` ${import_picocolors13.default.dim("│")} ${padRight3(import_picocolors13.default.dim("(empty)"), width - 4)}${import_picocolors13.default.dim("│")}`);
5374
+ } else {
5375
+ for (let i = startIdx;i < endIdx; i++) {
5376
+ const section = sections[i];
5377
+ const isSelected = i === state.selectedIndex;
5378
+ const prefix = isSelected ? import_picocolors13.default.cyan("▶") : " ";
5379
+ const typeIcon = getTypeIcon(section);
5380
+ const keyPart = isSelected ? import_picocolors13.default.bold(import_picocolors13.default.cyan(section.key)) : section.key;
5381
+ let content;
5382
+ if (state.editMode && isSelected) {
5383
+ content = `${prefix} ${typeIcon} ${keyPart}: ${import_picocolors13.default.bgBlue(import_picocolors13.default.white(state.editBuffer))}█`;
5384
+ } else {
5385
+ const valuePart = formatValue(section);
5386
+ content = `${prefix} ${typeIcon} ${keyPart}: ${valuePart}`;
5387
+ }
5388
+ console.log(` ${import_picocolors13.default.dim("│")} ${padRight3(content, width - 4)}${import_picocolors13.default.dim("│")}`);
5389
+ }
5390
+ }
5391
+ console.log(import_picocolors13.default.dim(` └${"─".repeat(width - 2)}┘`));
5392
+ if (state.message) {
5393
+ console.log();
5394
+ const msgColor = state.messageType === "error" ? import_picocolors13.default.red : state.messageType === "success" ? import_picocolors13.default.green : import_picocolors13.default.dim;
5395
+ console.log(` ${msgColor(state.message)}`);
5396
+ }
5397
+ console.log();
5398
+ if (state.editMode) {
5399
+ console.log(` ${import_picocolors13.default.dim("Type value,")} ${import_picocolors13.default.green("Enter")} ${import_picocolors13.default.dim("to save,")} ${import_picocolors13.default.red("Esc")} ${import_picocolors13.default.dim("to cancel")}`);
5400
+ } else {
5401
+ console.log(` ${import_picocolors13.default.dim("[")}${import_picocolors13.default.cyan("↑↓")}${import_picocolors13.default.dim("] Nav")} ` + `${import_picocolors13.default.dim("[")}${import_picocolors13.default.green("Enter")}${import_picocolors13.default.dim("] Edit/Drill")} ` + `${import_picocolors13.default.dim("[")}${import_picocolors13.default.yellow("←")}${import_picocolors13.default.dim("] Back")} ` + `${import_picocolors13.default.dim("[")}${import_picocolors13.default.magenta("s")}${import_picocolors13.default.dim("] Save")} ` + `${import_picocolors13.default.dim("[")}${import_picocolors13.default.red("q")}${import_picocolors13.default.dim("] Quit")}`);
5402
+ }
5403
+ console.log();
5404
+ };
5405
+ return new Promise((resolve) => {
5406
+ const kb = createKeyboardController();
5407
+ const handleKey = (key) => {
5408
+ const sections = getSections();
5409
+ if (state.editMode) {
5410
+ if (key === "escape") {
5411
+ state.editMode = false;
5412
+ state.editBuffer = "";
5413
+ state.message = "";
5414
+ render();
5415
+ } else if (key === "enter") {
5416
+ const section = sections[state.selectedIndex];
5417
+ if (section) {
5418
+ let newValue;
5419
+ const buf = state.editBuffer.trim();
5420
+ if (section.type === "boolean") {
5421
+ newValue = buf === "true";
5422
+ } else if (section.type === "number") {
5423
+ newValue = Number(buf);
5424
+ if (Number.isNaN(newValue)) {
5425
+ state.message = "Invalid number";
5426
+ state.messageType = "error";
5427
+ render();
5428
+ return;
5429
+ }
5430
+ } else if (section.type === "string") {
5431
+ newValue = buf;
5432
+ } else {
5433
+ state.message = "Cannot edit complex types inline";
5434
+ state.messageType = "error";
5435
+ state.editMode = false;
5436
+ render();
5437
+ return;
5438
+ }
5439
+ if (setValue(newValue)) {
5440
+ state.message = "Updated (press 's' to save)";
5441
+ state.messageType = "success";
5442
+ } else {
5443
+ state.message = "Failed to update";
5444
+ state.messageType = "error";
5445
+ }
5446
+ }
5447
+ state.editMode = false;
5448
+ state.editBuffer = "";
5449
+ render();
5450
+ } else if (key === "backspace") {
5451
+ state.editBuffer = state.editBuffer.slice(0, -1);
5452
+ render();
5453
+ } else if (key.length === 1 && key.charCodeAt(0) >= 32) {
5454
+ state.editBuffer += key;
5455
+ render();
5456
+ }
5457
+ return;
5458
+ }
5459
+ switch (key) {
5460
+ case "up":
5461
+ state.selectedIndex = Math.max(0, state.selectedIndex - 1);
5462
+ state.message = "";
5463
+ render();
5464
+ break;
5465
+ case "down":
5466
+ state.selectedIndex = Math.min(sections.length - 1, state.selectedIndex + 1);
5467
+ state.message = "";
5468
+ render();
5469
+ break;
5470
+ case "enter":
5471
+ case "right": {
5472
+ const section = sections[state.selectedIndex];
5473
+ if (section) {
5474
+ if (section.type === "object" || section.type === "array") {
5475
+ state.path.push(section.key);
5476
+ state.selectedIndex = 0;
5477
+ state.message = "";
5478
+ } else {
5479
+ state.editMode = true;
5480
+ state.editBuffer = String(section.value);
5481
+ state.message = "";
5482
+ }
5483
+ }
5484
+ render();
5485
+ break;
5486
+ }
5487
+ case "left":
5488
+ case "backspace":
5489
+ if (state.path.length > 0) {
5490
+ state.path.pop();
5491
+ state.selectedIndex = 0;
5492
+ state.message = "";
5493
+ render();
5494
+ }
5495
+ break;
5496
+ case "s":
5497
+ if (saveConfig2()) {
5498
+ state.message = "Config saved!";
5499
+ state.messageType = "success";
5500
+ } else {
5501
+ state.message = "Failed to save";
5502
+ state.messageType = "error";
5503
+ }
5504
+ render();
5505
+ break;
5506
+ case "escape":
5507
+ case "q":
5508
+ kb.stop();
5509
+ resolve();
5510
+ break;
5511
+ case "quit":
5512
+ kb.stop();
5513
+ process.exit(0);
5514
+ }
5515
+ };
5516
+ render();
5517
+ kb.start(handleKey);
5518
+ });
5519
+ }
5520
+ function padRight3(str, len) {
5521
+ const stripped = stripAnsi2(str);
5522
+ const pad = Math.max(0, len - stripped.length);
5523
+ return str + " ".repeat(pad);
5524
+ }
5525
+ function stripAnsi2(str) {
5526
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
5527
+ }
5528
+
5529
+ // src/tui/components/Dashboard.tsx
5530
+ var import_picocolors14 = __toESM(require_picocolors(), 1);
5531
+ function renderDashboard(data) {
5532
+ const width = Math.min(process.stdout.columns || 80, 120) - 4;
5533
+ console.clear();
5534
+ console.log();
5535
+ console.log(` ${import_picocolors14.default.bgCyan(import_picocolors14.default.black(" OpenCodeKit Dashboard "))} ${import_picocolors14.default.dim(data.projectName)}`);
5536
+ console.log();
5537
+ printBox("Overview", width, () => {
5538
+ const stats = [{
5539
+ label: "Agents",
5540
+ value: data.agents.length,
5541
+ col: import_picocolors14.default.cyan
5542
+ }, {
5543
+ label: "Skills",
5544
+ value: data.skills.length,
5545
+ col: import_picocolors14.default.cyan
5546
+ }, {
5547
+ label: "Commands",
5548
+ value: data.commands.length,
5549
+ col: import_picocolors14.default.yellow
5550
+ }, {
5551
+ label: "Tools",
5552
+ value: data.tools.length,
5553
+ col: import_picocolors14.default.yellow
5554
+ }, {
5555
+ label: "MCP",
5556
+ value: data.mcpServers.length,
5557
+ col: import_picocolors14.default.magenta
5558
+ }];
5559
+ const statLine = stats.map((s) => `${import_picocolors14.default.dim(s.label)} ${s.col(import_picocolors14.default.bold(String(s.value)))}`).join(" ");
5560
+ console.log(` ${statLine}`);
5561
+ });
5562
+ console.log();
5563
+ printTwoColumnBox({
5564
+ title: "Agents",
5565
+ items: data.agents,
5566
+ icon: "●",
5567
+ col: import_picocolors14.default.cyan
5568
+ }, {
5569
+ title: "Skills",
5570
+ items: data.skills,
5571
+ icon: "◆",
5572
+ col: import_picocolors14.default.cyan
5573
+ }, width, 8);
5574
+ console.log();
5575
+ printTwoColumnBox({
5576
+ title: "Commands",
5577
+ items: data.commands.map((c2) => `/${c2}`),
5578
+ icon: "›",
5579
+ col: import_picocolors14.default.yellow
5580
+ }, {
5581
+ title: "MCP Servers",
5582
+ items: data.mcpServers,
5583
+ icon: "◈",
5584
+ col: import_picocolors14.default.magenta
5585
+ }, width, 5);
5586
+ console.log();
5587
+ console.log(import_picocolors14.default.dim(` ${"─".repeat(width - 4)}`));
5588
+ console.log();
5589
+ console.log(` ${import_picocolors14.default.dim("[")}${import_picocolors14.default.cyan("a")}${import_picocolors14.default.dim("] Agents")} ` + `${import_picocolors14.default.dim("[")}${import_picocolors14.default.cyan("s")}${import_picocolors14.default.dim("] Skills")} ` + `${import_picocolors14.default.dim("[")}${import_picocolors14.default.yellow("c")}${import_picocolors14.default.dim("] Config")} ` + `${import_picocolors14.default.dim("[")}${import_picocolors14.default.magenta("m")}${import_picocolors14.default.dim("] MCP")} ` + `${import_picocolors14.default.dim("[")}${import_picocolors14.default.red("q")}${import_picocolors14.default.dim("] Quit")}`);
5590
+ console.log();
5591
+ }
5592
+ function printBox(title, width, content) {
5593
+ const innerWidth = width - 4;
5594
+ const titlePadded = ` ${title} `;
5595
+ const topLine = `┌${titlePadded}${"─".repeat(innerWidth - titlePadded.length)}┐`;
5596
+ const bottomLine = `└${"─".repeat(innerWidth)}┘`;
5597
+ console.log(import_picocolors14.default.dim(` ${topLine}`));
5598
+ content();
5599
+ console.log(import_picocolors14.default.dim(` ${bottomLine}`));
5600
+ }
5601
+ function printTwoColumnBox(left, right, totalWidth, maxItems) {
5602
+ const colWidth = Math.floor((totalWidth - 7) / 2);
5603
+ const leftTitle = ` ${left.title} `;
5604
+ const rightTitle = ` ${right.title} `;
5605
+ const leftBorder = `┌${leftTitle}${"─".repeat(colWidth - leftTitle.length)}`;
5606
+ const rightBorder = `┬${rightTitle}${"─".repeat(colWidth - rightTitle.length)}┐`;
5607
+ console.log(import_picocolors14.default.dim(` ${leftBorder}${rightBorder}`));
5608
+ const leftItems = formatItems(left.items, left.icon, left.col, maxItems);
5609
+ const rightItems = formatItems(right.items, right.icon, right.col, maxItems);
5610
+ const maxRows = Math.max(leftItems.length, rightItems.length);
5611
+ for (let i = 0;i < maxRows; i++) {
5612
+ const leftContent = leftItems[i] || "";
5613
+ const rightContent = rightItems[i] || "";
5614
+ const leftPadded = padRight4(leftContent, colWidth - 1);
5615
+ const rightPadded = padRight4(rightContent, colWidth - 1);
5616
+ console.log(` ${import_picocolors14.default.dim("│")} ${leftPadded}${import_picocolors14.default.dim("│")} ${rightPadded}${import_picocolors14.default.dim("│")}`);
5617
+ }
5618
+ const bottomLine = `└${"─".repeat(colWidth)}┴${"─".repeat(colWidth)}┘`;
5619
+ console.log(import_picocolors14.default.dim(` ${bottomLine}`));
5620
+ }
5621
+ function formatItems(items, icon, col, maxItems) {
5622
+ const result = [];
5623
+ if (items.length === 0) {
5624
+ result.push(import_picocolors14.default.dim("(none)"));
5625
+ return result;
5626
+ }
5627
+ for (const item of items.slice(0, maxItems)) {
5628
+ result.push(`${col(icon)} ${item}`);
5629
+ }
5630
+ if (items.length > maxItems) {
5631
+ result.push(import_picocolors14.default.dim(` +${items.length - maxItems} more`));
5632
+ }
5633
+ return result;
5634
+ }
5635
+ function padRight4(str, len) {
5636
+ const stripped = stripAnsi3(str);
5637
+ const pad = Math.max(0, len - stripped.length);
5638
+ return str + " ".repeat(pad);
5639
+ }
5640
+ function stripAnsi3(str) {
5641
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
5642
+ }
5643
+
5644
+ // src/tui/components/MCPMonitor.ts
5645
+ var import_picocolors15 = __toESM(require_picocolors(), 1);
5646
+ import { existsSync as existsSync10, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "node:fs";
5647
+ import { join as join10 } from "node:path";
5648
+ async function runMCPMonitor() {
5649
+ const configPath = join10(process.cwd(), ".opencode", "opencode.json");
5650
+ if (!existsSync10(configPath)) {
5651
+ console.clear();
5652
+ console.log();
5653
+ console.log(import_picocolors15.default.red(" Config file not found: .opencode/opencode.json"));
5654
+ console.log(import_picocolors15.default.dim(" Press any key to go back..."));
5655
+ return new Promise((resolve) => {
5656
+ const kb = createKeyboardController();
5657
+ kb.start(() => {
5658
+ kb.stop();
5659
+ resolve();
5660
+ });
5661
+ });
5662
+ }
5663
+ let config;
5664
+ try {
5665
+ config = JSON.parse(readFileSync10(configPath, "utf-8"));
5666
+ } catch {
5667
+ console.clear();
5668
+ console.log();
5669
+ console.log(import_picocolors15.default.red(" Failed to parse config file"));
5670
+ console.log(import_picocolors15.default.dim(" Press any key to go back..."));
5671
+ return new Promise((resolve) => {
5672
+ const kb = createKeyboardController();
5673
+ kb.start(() => {
5674
+ kb.stop();
5675
+ resolve();
5676
+ });
5677
+ });
5678
+ }
5679
+ const state = {
5680
+ selectedIndex: 0,
5681
+ message: "",
5682
+ messageType: "info"
5683
+ };
5684
+ const getMCPServers = () => {
5685
+ const mcp = config.mcp;
5686
+ if (!mcp || typeof mcp !== "object")
5687
+ return [];
5688
+ return Object.entries(mcp).map(([name, value]) => {
5689
+ const server = value;
5690
+ return {
5691
+ name,
5692
+ type: server.type || "local",
5693
+ enabled: server.enabled !== false,
5694
+ command: server.command,
5695
+ url: server.url
5696
+ };
5697
+ });
5698
+ };
5699
+ const toggleServer = (index) => {
5700
+ const servers = getMCPServers();
5701
+ const server = servers[index];
5702
+ if (!server)
5703
+ return false;
5704
+ const mcp = config.mcp;
5705
+ mcp[server.name].enabled = !server.enabled;
5706
+ return true;
5707
+ };
5708
+ const saveConfig2 = () => {
5709
+ try {
5710
+ writeFileSync8(configPath, JSON.stringify(config, null, 2));
5711
+ return true;
5712
+ } catch {
5713
+ return false;
5714
+ }
5715
+ };
5716
+ const render = () => {
5717
+ console.clear();
5718
+ const width = Math.min(process.stdout.columns || 80, 120) - 4;
5719
+ console.log();
5720
+ console.log(` ${import_picocolors15.default.bgMagenta(import_picocolors15.default.white(" MCP Monitor "))} ${import_picocolors15.default.dim("Model Context Protocol")}`);
5721
+ console.log();
5722
+ const servers = getMCPServers();
5723
+ const boxTitle = ` ${servers.length} servers `;
5724
+ console.log(import_picocolors15.default.dim(` ┌${boxTitle}${"─".repeat(width - boxTitle.length - 3)}┐`));
5725
+ if (servers.length === 0) {
5726
+ console.log(` ${import_picocolors15.default.dim("│")} ${padRight5(import_picocolors15.default.dim("No MCP servers configured"), width - 4)}${import_picocolors15.default.dim("│")}`);
5727
+ console.log(` ${import_picocolors15.default.dim("│")} ${padRight5("", width - 4)}${import_picocolors15.default.dim("│")}`);
5728
+ console.log(` ${import_picocolors15.default.dim("│")} ${padRight5(import_picocolors15.default.dim("Add servers to .opencode/opencode.json:"), width - 4)}${import_picocolors15.default.dim("│")}`);
5729
+ console.log(` ${import_picocolors15.default.dim("│")} ${padRight5(import_picocolors15.default.dim(' "mcp": { "name": { "command": [...] } }'), width - 4)}${import_picocolors15.default.dim("│")}`);
5730
+ } else {
5731
+ const headerContent = ` ${import_picocolors15.default.bold("Status")} ${import_picocolors15.default.bold("Type")} ${import_picocolors15.default.bold("Name")} ${import_picocolors15.default.bold("Details")}`;
5732
+ console.log(` ${import_picocolors15.default.dim("│")} ${padRight5(headerContent, width - 4)}${import_picocolors15.default.dim("│")}`);
5733
+ console.log(` ${import_picocolors15.default.dim("│")} ${padRight5(import_picocolors15.default.dim("─".repeat(width - 6)), width - 4)}${import_picocolors15.default.dim("│")}`);
5734
+ for (let i = 0;i < servers.length; i++) {
5735
+ const server = servers[i];
5736
+ const isSelected = i === state.selectedIndex;
5737
+ const prefix = isSelected ? import_picocolors15.default.cyan("▶") : " ";
5738
+ const status = server.enabled ? import_picocolors15.default.green("●") : import_picocolors15.default.dim("○");
5739
+ const typeIcon = server.type === "remote" ? import_picocolors15.default.blue("☁") : import_picocolors15.default.yellow("⚡");
5740
+ const name = isSelected ? import_picocolors15.default.bold(import_picocolors15.default.cyan(server.name.padEnd(18))) : server.name.padEnd(18);
5741
+ let details = "";
5742
+ if (server.type === "remote" && server.url) {
5743
+ details = import_picocolors15.default.dim(truncate2(server.url, 25));
5744
+ } else if (server.command) {
5745
+ details = import_picocolors15.default.dim(truncate2(server.command.join(" "), 25));
5746
+ }
5747
+ const rowContent = `${prefix} ${status} ${typeIcon} ${name} ${details}`;
5748
+ console.log(` ${import_picocolors15.default.dim("│")} ${padRight5(rowContent, width - 4)}${import_picocolors15.default.dim("│")}`);
5749
+ }
5750
+ }
5751
+ console.log(import_picocolors15.default.dim(` └${"─".repeat(width - 2)}┘`));
5752
+ console.log();
5753
+ console.log(` ${import_picocolors15.default.green("●")} ${import_picocolors15.default.dim("Enabled")} ${import_picocolors15.default.dim("○")} ${import_picocolors15.default.dim("Disabled")} ${import_picocolors15.default.yellow("⚡")} ${import_picocolors15.default.dim("Local")} ${import_picocolors15.default.blue("☁")} ${import_picocolors15.default.dim("Remote")}`);
5754
+ if (state.message) {
5755
+ console.log();
5756
+ const msgColor = state.messageType === "error" ? import_picocolors15.default.red : state.messageType === "success" ? import_picocolors15.default.green : import_picocolors15.default.dim;
5757
+ console.log(` ${msgColor(state.message)}`);
5758
+ }
5759
+ console.log();
5760
+ console.log(` ${import_picocolors15.default.dim("[")}${import_picocolors15.default.cyan("↑↓")}${import_picocolors15.default.dim("] Nav")} ` + `${import_picocolors15.default.dim("[")}${import_picocolors15.default.green("Space")}${import_picocolors15.default.dim("] Toggle")} ` + `${import_picocolors15.default.dim("[")}${import_picocolors15.default.magenta("s")}${import_picocolors15.default.dim("] Save")} ` + `${import_picocolors15.default.dim("[")}${import_picocolors15.default.red("q")}${import_picocolors15.default.dim("] Back")}`);
5761
+ console.log();
5762
+ };
5763
+ return new Promise((resolve) => {
5764
+ const kb = createKeyboardController();
5765
+ const handleKey = (key) => {
5766
+ const currentServers = getMCPServers();
5767
+ switch (key) {
5768
+ case "up":
5769
+ state.selectedIndex = Math.max(0, state.selectedIndex - 1);
5770
+ state.message = "";
5771
+ render();
5772
+ break;
5773
+ case "down":
5774
+ state.selectedIndex = Math.min(currentServers.length - 1, state.selectedIndex + 1);
5775
+ state.message = "";
5776
+ render();
5777
+ break;
5778
+ case " ":
5779
+ case "enter":
5780
+ if (currentServers.length > 0) {
5781
+ if (toggleServer(state.selectedIndex)) {
5782
+ const server = currentServers[state.selectedIndex];
5783
+ const newState = !server.enabled;
5784
+ state.message = `${server.name}: ${newState ? "enabled" : "disabled"} (press 's' to save)`;
5785
+ state.messageType = "success";
5786
+ }
5787
+ }
5788
+ render();
5789
+ break;
5790
+ case "s":
5791
+ if (saveConfig2()) {
5792
+ state.message = "Config saved!";
5793
+ state.messageType = "success";
5794
+ } else {
5795
+ state.message = "Failed to save";
5796
+ state.messageType = "error";
5797
+ }
5798
+ render();
5799
+ break;
5800
+ case "escape":
5801
+ case "q":
5802
+ kb.stop();
5803
+ resolve();
5804
+ break;
5805
+ case "quit":
5806
+ kb.stop();
5807
+ process.exit(0);
5808
+ }
5809
+ };
5810
+ render();
5811
+ kb.start(handleKey);
5812
+ });
5813
+ }
5814
+ function truncate2(str, maxLen) {
5815
+ if (str.length <= maxLen)
5816
+ return str;
5817
+ return `${str.substring(0, maxLen - 3)}...`;
5818
+ }
5819
+ function padRight5(str, len) {
5820
+ const stripped = stripAnsi4(str);
5821
+ const pad = Math.max(0, len - stripped.length);
5822
+ return str + " ".repeat(pad);
5823
+ }
5824
+ function stripAnsi4(str) {
5825
+ return str.replace(/\x1b\[[0-9;]*m/g, "");
5826
+ }
5827
+
5828
+ // src/tui/hooks/useData.ts
5829
+ import { existsSync as existsSync11, lstatSync as lstatSync3, readFileSync as readFileSync11, readdirSync as readdirSync7 } from "node:fs";
5830
+ import { basename as basename5, join as join11 } from "node:path";
5831
+ function loadProjectData() {
5832
+ const cwd = process.cwd();
5833
+ const opencodeDir = join11(cwd, ".opencode");
5834
+ const projectName = basename5(cwd);
5835
+ const agentDir = join11(opencodeDir, "agent");
5836
+ let agents = [];
5837
+ if (existsSync11(agentDir)) {
5838
+ agents = readdirSync7(agentDir).filter((f3) => f3.endsWith(".md") && lstatSync3(join11(agentDir, f3)).isFile()).map((f3) => f3.replace(".md", ""));
5839
+ }
5840
+ const skillDir = join11(opencodeDir, "skill");
5841
+ let skills = [];
5842
+ if (existsSync11(skillDir)) {
5843
+ skills = readdirSync7(skillDir).filter((f3) => lstatSync3(join11(skillDir, f3)).isDirectory() && existsSync11(join11(skillDir, f3, "SKILL.md")));
5844
+ }
5845
+ const commandDir = join11(opencodeDir, "command");
5846
+ let commands = [];
5847
+ if (existsSync11(commandDir)) {
5848
+ commands = readdirSync7(commandDir).filter((f3) => f3.endsWith(".md")).map((f3) => f3.replace(".md", ""));
5849
+ }
5850
+ const toolDir = join11(opencodeDir, "tool");
5851
+ let tools = [];
5852
+ if (existsSync11(toolDir)) {
5853
+ tools = readdirSync7(toolDir).filter((f3) => f3.endsWith(".ts")).map((f3) => f3.replace(".ts", ""));
5854
+ }
5855
+ const configPath = join11(opencodeDir, "opencode.json");
5856
+ let mcpServers = [];
5857
+ if (existsSync11(configPath)) {
5858
+ try {
5859
+ const config = JSON.parse(readFileSync11(configPath, "utf-8"));
5860
+ mcpServers = Object.keys(config.mcp || {});
5861
+ } catch {}
5862
+ }
5863
+ return {
5864
+ projectName,
5865
+ agents,
5866
+ skills,
5867
+ commands,
5868
+ tools,
5869
+ mcpServers
5870
+ };
5871
+ }
5872
+ function isInitialized() {
5873
+ const cwd = process.cwd();
5874
+ return existsSync11(join11(cwd, ".opencode"));
5875
+ }
5876
+
5877
+ // src/tui/index.ts
5878
+ async function launchTUI() {
5879
+ if (!isInitialized()) {
5880
+ notInitialized();
5881
+ return;
5882
+ }
5883
+ await mainMenu();
5884
+ }
5885
+ async function mainMenu() {
5886
+ const data = loadProjectData();
5887
+ renderDashboard(data);
5888
+ return new Promise((resolve) => {
5889
+ const kb = createKeyboardController();
5890
+ const handleKey = async (key) => {
5891
+ switch (key) {
5892
+ case "a":
5893
+ kb.stop();
5894
+ await browseAgents();
5895
+ await mainMenu();
5896
+ resolve();
5897
+ break;
5898
+ case "s":
5899
+ kb.stop();
5900
+ await browseSkills();
5901
+ await mainMenu();
5902
+ resolve();
5903
+ break;
5904
+ case "c":
5905
+ kb.stop();
5906
+ await runConfigEditor();
5907
+ await mainMenu();
5908
+ resolve();
5909
+ break;
5910
+ case "m":
5911
+ kb.stop();
5912
+ await runMCPMonitor();
5913
+ await mainMenu();
5914
+ resolve();
5915
+ break;
5916
+ case "q":
5917
+ case "escape":
5918
+ case "quit":
5919
+ kb.stop();
5920
+ console.clear();
5921
+ resolve();
5922
+ break;
5923
+ }
5924
+ };
5925
+ kb.start(handleKey);
5926
+ });
5927
+ }
5928
+ async function browseAgents() {
5929
+ const items = loadAgentItems();
5930
+ await runBrowser({
5931
+ title: "Agents",
5932
+ items,
5933
+ onSelect: (item) => {
5934
+ console.log(`Selected: ${item.name}`);
5935
+ }
5936
+ });
5937
+ }
5938
+ async function browseSkills() {
5939
+ const items = loadSkillItems();
5940
+ await runBrowser({
5941
+ title: "Skills",
5942
+ items,
5943
+ onSelect: (item) => {
5944
+ console.log(`Selected: ${item.name}`);
5945
+ }
5946
+ });
3733
5947
  }
3734
5948
 
3735
5949
  // src/utils/logger.ts
3736
- var import_picocolors11 = __toESM(require_picocolors(), 1);
5950
+ var import_picocolors16 = __toESM(require_picocolors(), 1);
3737
5951
  var logger = {
3738
5952
  log: (message) => {
3739
5953
  if (!process.argv.includes("--quiet")) {
@@ -3742,32 +5956,32 @@ var logger = {
3742
5956
  },
3743
5957
  info: (message) => {
3744
5958
  if (!process.argv.includes("--quiet")) {
3745
- console.log(import_picocolors11.default.blue(`[i] ${message}`));
5959
+ console.log(import_picocolors16.default.blue(`[i] ${message}`));
3746
5960
  }
3747
5961
  },
3748
5962
  success: (message) => {
3749
5963
  if (!process.argv.includes("--quiet")) {
3750
- console.log(import_picocolors11.default.green(`[+] ${message}`));
5964
+ console.log(import_picocolors16.default.green(`[+] ${message}`));
3751
5965
  }
3752
5966
  },
3753
5967
  warn: (message) => {
3754
5968
  if (!process.argv.includes("--quiet")) {
3755
- console.warn(import_picocolors11.default.yellow(`[!] ${message}`));
5969
+ console.warn(import_picocolors16.default.yellow(`[!] ${message}`));
3756
5970
  }
3757
5971
  },
3758
5972
  error: (message) => {
3759
5973
  if (!process.argv.includes("--quiet")) {
3760
- console.error(import_picocolors11.default.red(`[x] ${message}`));
5974
+ console.error(import_picocolors16.default.red(`[x] ${message}`));
3761
5975
  }
3762
5976
  },
3763
5977
  debug: (message) => {
3764
5978
  if ((process.env.DEBUG || process.argv.includes("--verbose")) && !process.argv.includes("--quiet")) {
3765
- console.log(import_picocolors11.default.dim(`[DEBUG] ${message}`));
5979
+ console.log(import_picocolors16.default.dim(`[DEBUG] ${message}`));
3766
5980
  }
3767
5981
  },
3768
5982
  verbose: (message) => {
3769
5983
  if (process.argv.includes("--verbose") && !process.argv.includes("--quiet")) {
3770
- console.log(import_picocolors11.default.dim(message));
5984
+ console.log(import_picocolors16.default.dim(message));
3771
5985
  }
3772
5986
  }
3773
5987
  };
@@ -3799,23 +6013,39 @@ Usage: ock agent <action>
3799
6013
  }
3800
6014
  await agentCommand(action);
3801
6015
  });
3802
- cli.command("skill [action]", "Manage skills (list, add, view)").action(async (action) => {
6016
+ cli.command("command [action]", "Manage slash commands (list, create, view, remove)").action(async (action) => {
6017
+ if (!action) {
6018
+ console.log(`
6019
+ Usage: ock command <action>
6020
+ `);
6021
+ console.log("Actions:");
6022
+ console.log(" list List all slash commands");
6023
+ console.log(" create Create a new command");
6024
+ console.log(" view View command content");
6025
+ console.log(` remove Remove a command
6026
+ `);
6027
+ return;
6028
+ }
6029
+ await commandCommand(action);
6030
+ });
6031
+ cli.command("agent [action]", "Manage agents (list, create, view, remove)").action(async (action) => {
3803
6032
  if (!action) {
3804
6033
  console.log(`
3805
- Usage: ock skill <action>
6034
+ Usage: ock agent <action>
3806
6035
  `);
3807
6036
  console.log("Actions:");
3808
- console.log(" list List all skills");
3809
- console.log(" add Install a skill");
3810
- console.log(` view View skill details
6037
+ console.log(" list List all agents");
6038
+ console.log(" create Create a new agent");
6039
+ console.log(" view View agent details");
6040
+ console.log(` remove Remove an agent
3811
6041
  `);
3812
6042
  return;
3813
6043
  }
3814
- await skillCommand(action);
6044
+ await agentCommand(action);
3815
6045
  });
3816
6046
  cli.command("doctor", "Check project health").action(doctorCommand);
3817
6047
  cli.command("status", "Show project overview").action(statusCommand);
3818
- cli.command("config [action]", "Edit opencode.json (model, mcp, permission, keybinds, show)").action(async (action) => {
6048
+ cli.command("config [action]", "Edit opencode.json (model, mcp, permission, validate, show)").action(async (action) => {
3819
6049
  await configCommand(action);
3820
6050
  });
3821
6051
  cli.command("upgrade", "Update .opencode/ templates to latest version").option("--force", "Force upgrade even if already up to date").option("--check", "Check for updates without upgrading").action(async (options) => {
@@ -3824,6 +6054,9 @@ cli.command("upgrade", "Update .opencode/ templates to latest version").option("
3824
6054
  cli.command("completion [shell]", "Generate shell completion script (bash, zsh, fish)").action(async (shell) => {
3825
6055
  await completionCommand(shell);
3826
6056
  });
6057
+ cli.command("tui", "Launch interactive dashboard").action(async () => {
6058
+ await launchTUI();
6059
+ });
3827
6060
  cli.command("", "Interactive menu").action(async () => {
3828
6061
  await interactiveMenu(packageVersion);
3829
6062
  });