opencodekit 0.12.7 → 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 (28) hide show
  1. package/README.md +2 -2
  2. package/dist/index.js +2753 -508
  3. package/dist/template/.opencode/AGENTS.md +35 -128
  4. package/dist/template/.opencode/README.md +4 -3
  5. package/dist/template/.opencode/command/design.md +1 -0
  6. package/dist/template/.opencode/command/fix.md +28 -1
  7. package/dist/template/.opencode/command/research.md +0 -4
  8. package/dist/template/.opencode/command/start.md +106 -0
  9. package/dist/template/.opencode/command/triage.md +66 -12
  10. package/dist/template/.opencode/memory/project/beads-workflow.md +278 -0
  11. package/dist/template/.opencode/memory/session-context.md +40 -0
  12. package/dist/template/.opencode/opencode.json +557 -496
  13. package/dist/template/.opencode/package.json +1 -1
  14. package/dist/template/.opencode/plugin/compaction.ts +62 -18
  15. package/dist/template/.opencode/plugin/lib/notify.ts +2 -3
  16. package/dist/template/.opencode/plugin/sessions.ts +1 -1
  17. package/dist/template/.opencode/plugin/skill-mcp.ts +11 -12
  18. package/dist/template/.opencode/skill/beads/SKILL.md +44 -0
  19. package/dist/template/.opencode/tool/ast-grep.ts +3 -3
  20. package/dist/template/.opencode/tool/bd-inbox.ts +7 -6
  21. package/dist/template/.opencode/tool/bd-msg.ts +3 -3
  22. package/dist/template/.opencode/tool/bd-release.ts +2 -2
  23. package/dist/template/.opencode/tool/bd-reserve.ts +5 -4
  24. package/dist/template/.opencode/tool/memory-read.ts +2 -2
  25. package/dist/template/.opencode/tool/memory-search.ts +2 -2
  26. package/dist/template/.opencode/tool/memory-update.ts +11 -12
  27. package/dist/template/.opencode/tool/observation.ts +6 -6
  28. package/package.json +5 -2
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.7",
753
+ version: "0.13.0",
754
754
  description: "CLI tool for bootstrapping and managing OpenCodeKit projects",
755
755
  type: "module",
756
756
  repository: {
@@ -767,7 +767,7 @@ var package_default = {
767
767
  files: ["dist", "README.md"],
768
768
  scripts: {
769
769
  dev: "bun run src/index.ts",
770
- 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/",
771
771
  compile: "bun build src/index.ts --compile --outfile ock",
772
772
  "compile:binary": "bun build src/index.ts --compile --outfile bin/ock",
773
773
  typecheck: "tsc --noEmit",
@@ -785,11 +785,14 @@ var package_default = {
785
785
  dependencies: {
786
786
  "@clack/prompts": "^0.7.0",
787
787
  "@opencode-ai/plugin": "^1.1.2",
788
+ "@opentui/core": "^0.1.69",
789
+ "@opentui/solid": "^0.1.69",
788
790
  "beads-village": "^1.3.3",
789
791
  cac: "^6.7.14",
790
792
  "cli-table3": "^0.6.5",
791
793
  ora: "^9.0.0",
792
794
  picocolors: "^1.1.1",
795
+ "solid-js": "^1.9.10",
793
796
  zod: "^3.23.8"
794
797
  },
795
798
  devDependencies: {
@@ -809,6 +812,7 @@ import {
809
812
  mkdirSync,
810
813
  readFileSync,
811
814
  readdirSync,
815
+ unlinkSync,
812
816
  writeFileSync
813
817
  } from "node:fs";
814
818
  import { join } from "node:path";
@@ -1144,6 +1148,41 @@ class BD extends x {
1144
1148
  });
1145
1149
  }
1146
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
+ };
1147
1186
  var wD = Object.defineProperty;
1148
1187
  var yD = (e, u, F) => (u in e) ? wD(e, u, { enumerable: true, configurable: true, writable: true, value: F }) : e[u] = F;
1149
1188
  var Z = (e, u, F) => (yD(e, typeof u != "symbol" ? u + "" : u, F), F);
@@ -1227,7 +1266,7 @@ var H = o("◆", "*");
1227
1266
  var I2 = o("■", "x");
1228
1267
  var x2 = o("▲", "x");
1229
1268
  var S2 = o("◇", "o");
1230
- var K = o("┌", "T");
1269
+ var K2 = o("┌", "T");
1231
1270
  var a2 = o("│", "|");
1232
1271
  var d2 = o("└", "—");
1233
1272
  var b2 = o("●", ">");
@@ -1325,6 +1364,50 @@ ${import_picocolors2.default.cyan(d2)}
1325
1364
  }
1326
1365
  } }).prompt();
1327
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
+ };
1328
1411
  var R2 = (r2) => r2.replace(me(), "");
1329
1412
  var le = (r2 = "", n = "") => {
1330
1413
  const i = `
@@ -1344,7 +1427,7 @@ var ue = (r2 = "") => {
1344
1427
  `);
1345
1428
  };
1346
1429
  var oe = (r2 = "") => {
1347
- process.stdout.write(`${import_picocolors2.default.gray(K)} ${r2}
1430
+ process.stdout.write(`${import_picocolors2.default.gray(K2)} ${r2}
1348
1431
  `);
1349
1432
  };
1350
1433
  var $e = (r2 = "") => {
@@ -1412,9 +1495,9 @@ var import_picocolors4 = __toESM(require_picocolors(), 1);
1412
1495
  var import_picocolors3 = __toESM(require_picocolors(), 1);
1413
1496
  function showError(message, fix) {
1414
1497
  console.log();
1415
- f2.error(import_picocolors3.default.red("✗") + " " + message);
1498
+ f2.error(`${import_picocolors3.default.red("✗")} ${message}`);
1416
1499
  if (fix) {
1417
- f2.info(" " + import_picocolors3.default.dim("→ " + fix));
1500
+ f2.info(` ${import_picocolors3.default.dim(`→ ${fix}`)}`);
1418
1501
  }
1419
1502
  console.log();
1420
1503
  }
@@ -1425,16 +1508,13 @@ function notFound(resource, name) {
1425
1508
  const msg = name ? `${resource} "${name}" not found` : `${resource} not found`;
1426
1509
  showError(msg);
1427
1510
  }
1428
- function alreadyExists(resource, name) {
1429
- showError(`${resource} "${name}" already exists`);
1430
- }
1431
1511
  function unknownAction(action, available) {
1432
1512
  showError(`Unknown action: ${action}`, `Available: ${available.map((a3) => import_picocolors3.default.cyan(a3)).join(", ")}`);
1433
1513
  }
1434
1514
  function showWarning(message, suggestion) {
1435
- f2.warn(import_picocolors3.default.yellow("!") + " " + message);
1515
+ f2.warn(`${import_picocolors3.default.yellow("!")} ${message}`);
1436
1516
  if (suggestion) {
1437
- f2.info(" " + import_picocolors3.default.dim("→ " + suggestion));
1517
+ f2.info(` ${import_picocolors3.default.dim(`→ ${suggestion}`)}`);
1438
1518
  }
1439
1519
  }
1440
1520
  function showEmpty(resource, createCmd) {
@@ -1445,18 +1525,6 @@ function showEmpty(resource, createCmd) {
1445
1525
  }
1446
1526
 
1447
1527
  // src/commands/agent.ts
1448
- var AGENT_TEMPLATE = `# {{NAME}} Agent
1449
-
1450
- ## Role
1451
- Describe what this agent does.
1452
-
1453
- ## Capabilities
1454
- - Capability 1
1455
- - Capability 2
1456
-
1457
- ## Instructions
1458
- Provide specific instructions for this agent.
1459
- `;
1460
1528
  async function agentCommand(action) {
1461
1529
  const opencodePath = join(process.cwd(), ".opencode");
1462
1530
  if (!existsSync(opencodePath)) {
@@ -1468,23 +1536,54 @@ async function agentCommand(action) {
1468
1536
  case "list":
1469
1537
  await listAgents(agentPath);
1470
1538
  break;
1539
+ case "create":
1540
+ await createAgent(agentPath);
1541
+ break;
1471
1542
  case "add":
1472
- await addAgent(agentPath);
1543
+ await createAgent(agentPath);
1473
1544
  break;
1474
1545
  case "view": {
1475
1546
  const agentName = process.argv[4];
1476
1547
  await viewAgent(agentPath, agentName);
1477
1548
  break;
1478
1549
  }
1550
+ case "remove": {
1551
+ const agentName = process.argv[4];
1552
+ await removeAgent(agentPath, agentName);
1553
+ break;
1554
+ }
1479
1555
  default:
1480
- unknownAction(action, ["list", "add", "view"]);
1556
+ unknownAction(action, ["list", "create", "view", "remove"]);
1481
1557
  }
1482
1558
  }
1483
- async function listAgents(agentPath) {
1484
- if (!existsSync(agentPath)) {
1485
- showEmpty("agents", "ock agent add");
1486
- 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
+ }
1487
1581
  }
1582
+ return frontmatter;
1583
+ }
1584
+ function collectAgents(agentPath) {
1585
+ if (!existsSync(agentPath))
1586
+ return [];
1488
1587
  const opencodePath = join(process.cwd(), ".opencode");
1489
1588
  const configPath = join(opencodePath, "opencode.json");
1490
1589
  let agentConfigs = {};
@@ -1496,38 +1595,48 @@ async function listAgents(agentPath) {
1496
1595
  } catch {}
1497
1596
  }
1498
1597
  const entries = readdirSync(agentPath);
1499
- const agents = entries.filter((entry) => {
1598
+ const agents = [];
1599
+ for (const entry of entries) {
1500
1600
  const entryPath = join(agentPath, entry);
1501
- return lstatSync(entryPath).isFile() && entry.endsWith(".md");
1502
- }).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);
1503
1618
  if (agents.length === 0) {
1504
- showEmpty("agents", "ock agent add");
1619
+ showEmpty("agents", "ock agent create");
1505
1620
  return;
1506
1621
  }
1507
1622
  f2.info(import_picocolors4.default.bold("Agents"));
1508
1623
  for (const agent of agents) {
1509
- let model = agentConfigs[agent]?.model;
1510
- const disabled = agentConfigs[agent]?.disable;
1511
- if (!model) {
1512
- const agentFile = join(agentPath, `${agent}.md`);
1513
- try {
1514
- const content = readFileSync(agentFile, "utf-8");
1515
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
1516
- if (frontmatterMatch) {
1517
- const modelMatch = frontmatterMatch[1].match(/^model:\s*(.+)$/m);
1518
- if (modelMatch) {
1519
- model = modelMatch[1].trim();
1520
- }
1521
- }
1522
- } catch {}
1523
- }
1624
+ const model = agent.configModel || agent.frontmatter.model;
1625
+ const mode = agent.frontmatter.mode;
1626
+ const modeDisplay = mode ? import_picocolors4.default.yellow(`[${mode}]`) : "";
1524
1627
  const modelDisplay = model ? import_picocolors4.default.dim(` → ${model}`) : import_picocolors4.default.dim(" → (default)");
1525
- const disabledDisplay = disabled ? import_picocolors4.default.red(" [disabled]") : "";
1526
- 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}`);
1527
1631
  }
1632
+ console.log();
1633
+ f2.info(import_picocolors4.default.dim(`Found ${agents.length} agent${agents.length === 1 ? "" : "s"}`));
1528
1634
  }
1529
- async function addAgent(agentPath) {
1530
- 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
+ }
1531
1640
  const name = await te({
1532
1641
  message: "Agent name",
1533
1642
  placeholder: "e.g. reviewer, planner, researcher",
@@ -1537,162 +1646,566 @@ async function addAgent(agentPath) {
1537
1646
  if (!/^[a-z][a-z0-9-]*$/.test(value)) {
1538
1647
  return "Use lowercase letters, numbers, and hyphens only";
1539
1648
  }
1649
+ if (existsSync(join(agentPath, `${value}.md`))) {
1650
+ return "Agent already exists";
1651
+ }
1652
+ return;
1540
1653
  }
1541
1654
  });
1542
1655
  if (lD(name)) {
1543
1656
  ue("Cancelled");
1544
1657
  return;
1545
1658
  }
1546
- const agentType = await ie({
1547
- 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",
1548
1674
  options: [
1549
- { value: "primary", label: "Primary agent" },
1550
- { 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
+ }
1551
1690
  ]
1552
1691
  });
1553
- if (lD(agentType)) {
1692
+ if (lD(mode)) {
1554
1693
  ue("Cancelled");
1555
1694
  return;
1556
1695
  }
1557
- if (!existsSync(agentPath)) {
1558
- mkdirSync(agentPath, { recursive: true });
1559
- }
1560
- const agentFile = join(agentPath, `${name}.md`);
1561
- if (existsSync(agentFile)) {
1562
- alreadyExists("Agent", String(name));
1563
- $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");
1564
1702
  return;
1565
1703
  }
1566
- const content = AGENT_TEMPLATE.replace("{{NAME}}", String(name).charAt(0).toUpperCase() + String(name).slice(1));
1567
- 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);
1568
1748
  f2.success(`Created ${import_picocolors4.default.cyan(`.opencode/agent/${name}.md`)}`);
1569
- if (agentType === "primary") {
1749
+ if (mode === "primary") {
1570
1750
  le(`Add to opencode.json:
1571
1751
 
1572
- "agents": {
1752
+ "agent": {
1573
1753
  "${name}": {
1574
- "model": "...",
1575
- "systemPrompt": ".opencode/agent/${name}.md"
1754
+ "model": "claude-sonnet-4-20250514"
1576
1755
  }
1577
- }`, "Next step");
1756
+ }`, "Configure model (optional)");
1578
1757
  }
1579
1758
  $e(import_picocolors4.default.green("Done! Edit the file to customize."));
1580
1759
  }
1581
- 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;
1582
1767
  if (!agentName) {
1583
- if (!existsSync(agentPath)) {
1584
- showEmpty("agents", "ock agent add");
1585
- return;
1586
- }
1587
- const entries = readdirSync(agentPath);
1588
- const agents = entries.filter((entry) => {
1589
- const entryPath = join(agentPath, entry);
1590
- return lstatSync(entryPath).isFile() && entry.endsWith(".md");
1591
- }).map((file) => file.replace(".md", ""));
1592
- if (agents.length === 0) {
1593
- showEmpty("agents", "ock agent add");
1594
- return;
1595
- }
1768
+ const options = agents.map((a3) => ({
1769
+ value: a3.name,
1770
+ label: a3.name,
1771
+ hint: a3.frontmatter.description || ""
1772
+ }));
1596
1773
  const selected = await ie({
1597
1774
  message: "Select agent to view",
1598
- options: agents.map((a3) => ({ value: a3, label: a3 }))
1775
+ options
1599
1776
  });
1600
1777
  if (lD(selected)) {
1601
1778
  return;
1602
1779
  }
1603
1780
  agentName = selected;
1604
1781
  }
1605
- const agentFile = join(agentPath, `${agentName}.md`);
1606
- if (!existsSync(agentFile)) {
1782
+ const agent = agents.find((a3) => a3.name === agentName);
1783
+ if (!agent) {
1607
1784
  notFound("Agent", agentName);
1608
1785
  return;
1609
1786
  }
1610
- const content = readFileSync(agentFile, "utf-8");
1787
+ const content = readFileSync(agent.path, "utf-8");
1611
1788
  console.log();
1612
- console.log(import_picocolors4.default.dim("─".repeat(40)));
1789
+ console.log(import_picocolors4.default.dim("─".repeat(60)));
1613
1790
  console.log(content);
1614
- 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
+ }
1615
1841
  }
1616
1842
 
1617
- // 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";
1618
1853
  var import_picocolors5 = __toESM(require_picocolors(), 1);
1619
- var BASH_COMPLETION = `# ock bash completion
1620
- _ock_completion() {
1621
- local cur prev commands
1622
- COMPREPLY=()
1623
- cur="\${COMP_WORDS[COMP_CWORD]}"
1624
- prev="\${COMP_WORDS[COMP_CWORD-1]}"
1625
-
1626
- commands="init config upgrade agent skill doctor status help"
1627
-
1628
- case "\${prev}" in
1629
- ock)
1630
- COMPREPLY=( $(compgen -W "\${commands}" -- "\${cur}") )
1631
- return 0
1632
- ;;
1633
- agent)
1634
- COMPREPLY=( $(compgen -W "list add view" -- "\${cur}") )
1635
- return 0
1636
- ;;
1637
- skill)
1638
- COMPREPLY=( $(compgen -W "list add view" -- "\${cur}") )
1639
- return 0
1640
- ;;
1641
- config)
1642
- COMPREPLY=( $(compgen -W "model mcp permission keybinds show" -- "\${cur}") )
1643
- return 0
1644
- ;;
1645
- init)
1646
- COMPREPLY=( $(compgen -W "--force" -- "\${cur}") )
1647
- return 0
1648
- ;;
1649
- upgrade)
1650
- COMPREPLY=( $(compgen -W "--force --check" -- "\${cur}") )
1651
- return 0
1652
- ;;
1653
- *)
1654
- ;;
1655
- 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
+ }
1656
1881
  }
1657
- complete -F _ock_completion ock
1658
- `;
1659
- var ZSH_COMPLETION = `#compdef ock
1660
-
1661
- _ock() {
1662
- local -a commands
1663
- local -a agent_actions
1664
- local -a skill_actions
1665
- local -a config_actions
1666
-
1667
- commands=(
1668
- 'init:Initialize OpenCodeKit in current directory'
1669
- 'config:Edit opencode.json'
1670
- 'upgrade:Update templates to latest version'
1671
- 'agent:Manage agents'
1672
- 'skill:Manage skills'
1673
- 'doctor:Check project health'
1674
- 'status:Show project overview'
1675
- 'help:Show help'
1676
- )
1677
-
1678
- agent_actions=(
1679
- 'list:List all agents'
1680
- 'add:Create a new agent'
1681
- 'view:View agent details'
1682
- )
1683
-
1684
- skill_actions=(
1685
- 'list:List all skills'
1686
- 'add:Install a skill'
1687
- 'view:View skill details'
1688
- )
1689
-
1690
- config_actions=(
1691
- 'model:Change default model'
1692
- 'mcp:Manage MCP servers'
1693
- 'permission:Edit permissions'
1694
- 'keybinds:Edit keyboard shortcuts'
1695
- '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'
1696
2209
  )
1697
2210
 
1698
2211
  case "$words[2]" in
@@ -1755,8 +2268,9 @@ complete -c ock -n "__fish_seen_subcommand_from upgrade" -l force -d "Force upgr
1755
2268
  complete -c ock -n "__fish_seen_subcommand_from upgrade" -l check -d "Check only"
1756
2269
  `;
1757
2270
  async function completionCommand(shell) {
1758
- if (!shell) {
1759
- 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 ")));
1760
2274
  const selected = await ie({
1761
2275
  message: "Select your shell",
1762
2276
  options: [
@@ -1769,11 +2283,11 @@ async function completionCommand(shell) {
1769
2283
  ue("Cancelled");
1770
2284
  return;
1771
2285
  }
1772
- shell = selected;
2286
+ selectedShell = selected;
1773
2287
  }
1774
2288
  let script;
1775
2289
  let setupInstructions;
1776
- switch (shell) {
2290
+ switch (selectedShell) {
1777
2291
  case "bash":
1778
2292
  script = BASH_COMPLETION;
1779
2293
  setupInstructions = `Add to ~/.bashrc:
@@ -1802,14 +2316,14 @@ Or save to fpath:
1802
2316
  }
1803
2317
  console.log(script);
1804
2318
  console.error();
1805
- console.error(import_picocolors5.default.bold("Setup:"));
1806
- console.error(import_picocolors5.default.dim(setupInstructions));
2319
+ console.error(import_picocolors6.default.bold("Setup:"));
2320
+ console.error(import_picocolors6.default.dim(setupInstructions));
1807
2321
  }
1808
2322
 
1809
2323
  // src/commands/config.ts
1810
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
1811
- import { join as join2 } from "node:path";
1812
- 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);
1813
2327
  var DEFAULT_SERVER_PORT = 4096;
1814
2328
  var MODELS_DEV_URL = "https://models.dev/api.json";
1815
2329
  async function fetchFromServer(endpoint) {
@@ -1839,13 +2353,13 @@ async function getAgentsFromServer() {
1839
2353
  return fetchFromServer("/agent");
1840
2354
  }
1841
2355
  async function configCommand(action) {
1842
- const opencodePath = join2(process.cwd(), ".opencode");
1843
- const configPath = join2(opencodePath, "opencode.json");
1844
- if (!existsSync2(opencodePath)) {
2356
+ const opencodePath = join3(process.cwd(), ".opencode");
2357
+ const configPath = join3(opencodePath, "opencode.json");
2358
+ if (!existsSync3(opencodePath)) {
1845
2359
  notInitialized();
1846
2360
  return;
1847
2361
  }
1848
- if (!existsSync2(configPath)) {
2362
+ if (!existsSync3(configPath)) {
1849
2363
  showError("opencode.json not found", "ock init --force");
1850
2364
  return;
1851
2365
  }
@@ -1884,6 +2398,9 @@ async function configCommand(action) {
1884
2398
  case "show":
1885
2399
  showConfig(configPath);
1886
2400
  break;
2401
+ case "validate":
2402
+ await validateConfig(configPath);
2403
+ break;
1887
2404
  default:
1888
2405
  unknownAction(action, [
1889
2406
  "model",
@@ -1895,71 +2412,255 @@ async function configCommand(action) {
1895
2412
  "tools",
1896
2413
  "share",
1897
2414
  "autoupdate",
1898
- "show"
2415
+ "show",
2416
+ "validate"
1899
2417
  ]);
1900
2418
  }
1901
2419
  }
1902
2420
  function loadConfig(configPath) {
1903
- const content = readFileSync2(configPath, "utf-8");
2421
+ const content = readFileSync3(configPath, "utf-8");
1904
2422
  const jsonWithoutComments = content.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
1905
2423
  return JSON.parse(jsonWithoutComments);
1906
2424
  }
1907
2425
  function saveConfig(configPath, config) {
1908
- writeFileSync2(configPath, JSON.stringify(config, null, 2) + `
2426
+ writeFileSync3(configPath, `${JSON.stringify(config, null, 2)}
1909
2427
  `);
1910
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
+ }
1911
2612
  function showConfig(configPath) {
1912
2613
  const config = loadConfig(configPath);
1913
2614
  console.log();
1914
- console.log(import_picocolors6.default.bold(" Model"));
1915
- 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")}`);
1916
2617
  if (config.small_model) {
1917
- console.log(import_picocolors6.default.bold(" Small Model"));
1918
- 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)}`);
1919
2620
  }
1920
- console.log(import_picocolors6.default.bold(" Theme"));
1921
- console.log(` ${import_picocolors6.default.cyan(config.theme || "system")}`);
1922
- console.log(import_picocolors6.default.bold(" Share"));
1923
- 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")}`);
1924
2625
  const autoupdateDisplay = config.autoupdate === "notify" ? "notify" : config.autoupdate === false ? "disabled" : "enabled";
1925
- console.log(import_picocolors6.default.bold(" Autoupdate"));
1926
- console.log(` ${import_picocolors6.default.cyan(autoupdateDisplay)}`);
2626
+ console.log(import_picocolors7.default.bold(" Autoupdate"));
2627
+ console.log(` ${import_picocolors7.default.cyan(autoupdateDisplay)}`);
1927
2628
  if (config.tui && Object.keys(config.tui).length > 0) {
1928
- console.log(import_picocolors6.default.bold(" TUI"));
2629
+ console.log(import_picocolors7.default.bold(" TUI"));
1929
2630
  if (config.tui.scroll_speed !== undefined) {
1930
- 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))}`);
1931
2632
  }
1932
2633
  if (config.tui.scroll_acceleration !== undefined) {
1933
- 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))}`);
1934
2635
  }
1935
2636
  if (config.tui.diff_style) {
1936
- 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)}`);
1937
2638
  }
1938
2639
  }
1939
2640
  if (config.mcp && Object.keys(config.mcp).length > 0) {
1940
- console.log(import_picocolors6.default.bold(" MCP Servers"));
2641
+ console.log(import_picocolors7.default.bold(" MCP Servers"));
1941
2642
  for (const [name, server] of Object.entries(config.mcp)) {
1942
- 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");
1943
2644
  console.log(` ${status} ${name}`);
1944
2645
  }
1945
2646
  }
1946
2647
  if (config.tools && Object.keys(config.tools).length > 0) {
1947
- console.log(import_picocolors6.default.bold(" Tools"));
2648
+ console.log(import_picocolors7.default.bold(" Tools"));
1948
2649
  for (const [tool, enabled] of Object.entries(config.tools)) {
1949
- 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");
1950
2651
  console.log(` ${status} ${tool}`);
1951
2652
  }
1952
2653
  }
1953
2654
  if (config.keybinds && Object.keys(config.keybinds).length > 0) {
1954
- console.log(import_picocolors6.default.bold(" Keybinds"));
2655
+ console.log(import_picocolors7.default.bold(" Keybinds"));
1955
2656
  for (const [action, key] of Object.entries(config.keybinds)) {
1956
- 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)}`);
1957
2658
  }
1958
2659
  }
1959
2660
  console.log();
1960
2661
  }
1961
2662
  async function configMenu(configPath) {
1962
- oe(import_picocolors6.default.bgCyan(import_picocolors6.default.black(" Configuration ")));
2663
+ oe(import_picocolors7.default.bgCyan(import_picocolors7.default.black(" Configuration ")));
1963
2664
  while (true) {
1964
2665
  const action = await ie({
1965
2666
  message: "What do you want to configure?",
@@ -2034,8 +2735,8 @@ async function editModel(configPath, modelType = "model") {
2034
2735
  const config = loadConfig(configPath);
2035
2736
  const currentModel = config[modelType] || "not set";
2036
2737
  const modelLabel = modelType === "small_model" ? "Small Model" : "Model";
2037
- f2.info(`Current ${modelLabel}: ${import_picocolors6.default.cyan(currentModel)}`);
2038
- 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..."));
2039
2740
  const modelsDevData = await fetchFromModelsDev();
2040
2741
  if (!modelsDevData || Object.keys(modelsDevData).length === 0) {
2041
2742
  f2.warn("models.dev unavailable - please enter manually");
@@ -2056,7 +2757,7 @@ async function editModel(configPath, modelType = "model") {
2056
2757
  }
2057
2758
  config[modelType] = newModel;
2058
2759
  saveConfig(configPath, config);
2059
- f2.success(`${modelLabel} set to ${import_picocolors6.default.cyan(newModel)}`);
2760
+ f2.success(`${modelLabel} set to ${import_picocolors7.default.cyan(newModel)}`);
2060
2761
  return;
2061
2762
  }
2062
2763
  f2.success("Fetched models from models.dev");
@@ -2119,7 +2820,7 @@ async function editModel(configPath, modelType = "model") {
2119
2820
  }
2120
2821
  config[modelType] = customModel;
2121
2822
  saveConfig(configPath, config);
2122
- f2.success(`${modelLabel} set to ${import_picocolors6.default.cyan(customModel)}`);
2823
+ f2.success(`${modelLabel} set to ${import_picocolors7.default.cyan(customModel)}`);
2123
2824
  return;
2124
2825
  }
2125
2826
  if (providerId === "__search__") {
@@ -2184,7 +2885,7 @@ async function editModel(configPath, modelType = "model") {
2184
2885
  const fullModelId = `${providerId}/${selectedModel}`;
2185
2886
  config[modelType] = fullModelId;
2186
2887
  saveConfig(configPath, config);
2187
- f2.success(`${modelLabel} set to ${import_picocolors6.default.cyan(fullModelId)}`);
2888
+ f2.success(`${modelLabel} set to ${import_picocolors7.default.cyan(fullModelId)}`);
2188
2889
  }
2189
2890
  function getModelsForProvider(provider) {
2190
2891
  const options = [];
@@ -2232,7 +2933,7 @@ async function editAgentModel(configPath) {
2232
2933
  let agentNames = [];
2233
2934
  const agentInfoMap = new Map;
2234
2935
  if (serverAgents && serverAgents.length > 0) {
2235
- f2.info(import_picocolors6.default.dim("Fetched agents from OpenCode server"));
2936
+ f2.info(import_picocolors7.default.dim("Fetched agents from OpenCode server"));
2236
2937
  for (const agent of serverAgents) {
2237
2938
  agentNames.push(agent.name);
2238
2939
  agentInfoMap.set(agent.name, {
@@ -2256,14 +2957,14 @@ async function editAgentModel(configPath) {
2256
2957
  return;
2257
2958
  }
2258
2959
  console.log();
2259
- console.log(import_picocolors6.default.bold(" Agent Models"));
2960
+ console.log(import_picocolors7.default.bold(" Agent Models"));
2260
2961
  for (const name of agentNames) {
2261
2962
  const info = agentInfoMap.get(name);
2262
- const model = info?.model || import_picocolors6.default.dim("(default)");
2263
- 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]") : "";
2264
2965
  const localConfig = config.agent[name];
2265
- const disabled = localConfig?.disable ? import_picocolors6.default.red(" [disabled]") : "";
2266
- 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}`);
2267
2968
  }
2268
2969
  console.log();
2269
2970
  const agentOptions = [
@@ -2311,7 +3012,7 @@ async function editAgentModel(configPath) {
2311
3012
  }
2312
3013
  config.agent[agentName].disable = !currentAgent.disable;
2313
3014
  saveConfig(configPath, config);
2314
- 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");
2315
3016
  f2.success(`Agent ${agentName}: ${status}`);
2316
3017
  return;
2317
3018
  }
@@ -2332,8 +3033,8 @@ async function editAgentModel(configPath) {
2332
3033
  return;
2333
3034
  }
2334
3035
  const agentInfo = agentInfoMap.get(agentName);
2335
- f2.info(`Current model: ${import_picocolors6.default.cyan(agentInfo?.model || currentAgent.model || "(default)")}`);
2336
- 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..."));
2337
3038
  const modelsDevData = await fetchFromModelsDev();
2338
3039
  if (!modelsDevData || Object.keys(modelsDevData).length === 0) {
2339
3040
  f2.warn("models.dev unavailable - please enter manually");
@@ -2355,7 +3056,7 @@ async function editAgentModel(configPath) {
2355
3056
  }
2356
3057
  config.agent[agentName].model = newModel;
2357
3058
  saveConfig(configPath, config);
2358
- f2.success(`${agentName} model set to ${import_picocolors6.default.cyan(newModel)}`);
3059
+ f2.success(`${agentName} model set to ${import_picocolors7.default.cyan(newModel)}`);
2359
3060
  return;
2360
3061
  }
2361
3062
  const localProviders = config.provider || {};
@@ -2413,7 +3114,7 @@ async function editAgentModel(configPath) {
2413
3114
  agentConfig.model = undefined;
2414
3115
  }
2415
3116
  saveConfig(configPath, config);
2416
- f2.success(`${agentName} model set to ${import_picocolors6.default.cyan("(default)")}`);
3117
+ f2.success(`${agentName} model set to ${import_picocolors7.default.cyan("(default)")}`);
2417
3118
  return;
2418
3119
  }
2419
3120
  if (providerId === "__custom__") {
@@ -2435,7 +3136,7 @@ async function editAgentModel(configPath) {
2435
3136
  }
2436
3137
  config.agent[agentName].model = customModel;
2437
3138
  saveConfig(configPath, config);
2438
- f2.success(`${agentName} model set to ${import_picocolors6.default.cyan(customModel)}`);
3139
+ f2.success(`${agentName} model set to ${import_picocolors7.default.cyan(customModel)}`);
2439
3140
  return;
2440
3141
  }
2441
3142
  if (providerId === "__search__") {
@@ -2490,7 +3191,7 @@ async function editAgentModel(configPath) {
2490
3191
  }
2491
3192
  config.agent[agentName].model = fullModelId;
2492
3193
  saveConfig(configPath, config);
2493
- f2.success(`${agentName} model set to ${import_picocolors6.default.cyan(fullModelId)}`);
3194
+ f2.success(`${agentName} model set to ${import_picocolors7.default.cyan(fullModelId)}`);
2494
3195
  }
2495
3196
  async function editMCP(configPath) {
2496
3197
  const config = loadConfig(configPath);
@@ -2509,11 +3210,11 @@ async function editMCP(configPath) {
2509
3210
  return;
2510
3211
  }
2511
3212
  console.log();
2512
- console.log(import_picocolors6.default.bold(" MCP Servers"));
3213
+ console.log(import_picocolors7.default.bold(" MCP Servers"));
2513
3214
  for (const [name, server] of Object.entries(config.mcp)) {
2514
- const status = server.enabled === false ? import_picocolors6.default.red("off") : import_picocolors6.default.green("on");
2515
- const type = server.type === "remote" ? import_picocolors6.default.dim("remote") : import_picocolors6.default.dim("local");
2516
- 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(" ")}`);
2517
3218
  console.log(` ${status} ${name} ${type}${endpoint}`);
2518
3219
  }
2519
3220
  console.log();
@@ -2555,11 +3256,15 @@ async function toggleMCP(configPath, config) {
2555
3256
  return;
2556
3257
  }
2557
3258
  const serverName = selected;
2558
- const server = config.mcp[serverName];
3259
+ const server = config.mcp?.[serverName];
3260
+ if (!server) {
3261
+ f2.error(`Server "${serverName}" not found`);
3262
+ return;
3263
+ }
2559
3264
  const wasEnabled = server.enabled !== false;
2560
3265
  server.enabled = !wasEnabled;
2561
3266
  saveConfig(configPath, config);
2562
- 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");
2563
3268
  f2.success(`${serverName}: ${status}`);
2564
3269
  }
2565
3270
  async function addMCPServer(configPath, config) {
@@ -2601,6 +3306,7 @@ async function addMCPServer(configPath, config) {
2601
3306
  if (lD(url)) {
2602
3307
  return;
2603
3308
  }
3309
+ config.mcp = config.mcp ?? {};
2604
3310
  config.mcp[serverName] = {
2605
3311
  type: "remote",
2606
3312
  url,
@@ -2618,6 +3324,7 @@ async function addMCPServer(configPath, config) {
2618
3324
  if (lD(command)) {
2619
3325
  return;
2620
3326
  }
3327
+ config.mcp = config.mcp ?? {};
2621
3328
  config.mcp[serverName] = {
2622
3329
  type: "local",
2623
3330
  command: command.split(" "),
@@ -2625,7 +3332,7 @@ async function addMCPServer(configPath, config) {
2625
3332
  };
2626
3333
  }
2627
3334
  saveConfig(configPath, config);
2628
- f2.success(`Added MCP server: ${import_picocolors6.default.cyan(serverName)}`);
3335
+ f2.success(`Added MCP server: ${import_picocolors7.default.cyan(serverName)}`);
2629
3336
  }
2630
3337
  async function removeMCP(configPath, config) {
2631
3338
  const servers = Object.keys(config.mcp || {}).map((name) => ({
@@ -2646,7 +3353,7 @@ async function removeMCP(configPath, config) {
2646
3353
  if (lD(confirm) || !confirm) {
2647
3354
  return;
2648
3355
  }
2649
- delete config.mcp[serverName];
3356
+ delete config.mcp?.[serverName];
2650
3357
  saveConfig(configPath, config);
2651
3358
  f2.success(`Removed: ${serverName}`);
2652
3359
  }
@@ -2656,13 +3363,13 @@ async function editPermissions(configPath) {
2656
3363
  config.permission = {};
2657
3364
  }
2658
3365
  console.log();
2659
- console.log(import_picocolors6.default.bold(" Current Permissions"));
2660
- console.log(` edit: ${import_picocolors6.default.cyan(config.permission.edit || "ask")}`);
2661
- 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")}`);
2662
3369
  if (config.permission.bash) {
2663
- console.log(import_picocolors6.default.bold(" Bash"));
3370
+ console.log(import_picocolors7.default.bold(" Bash"));
2664
3371
  for (const [pattern, perm] of Object.entries(config.permission.bash)) {
2665
- 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)}`);
2666
3373
  }
2667
3374
  }
2668
3375
  console.log();
@@ -2724,9 +3431,9 @@ async function editKeybinds(configPath) {
2724
3431
  }
2725
3432
  if (Object.keys(config.keybinds).length > 0) {
2726
3433
  console.log();
2727
- console.log(import_picocolors6.default.bold(" Current Keybinds"));
3434
+ console.log(import_picocolors7.default.bold(" Current Keybinds"));
2728
3435
  for (const [action2, key] of Object.entries(config.keybinds)) {
2729
- 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)}`);
2730
3437
  }
2731
3438
  console.log();
2732
3439
  }
@@ -2779,7 +3486,7 @@ async function editKeybinds(configPath) {
2779
3486
  }
2780
3487
  config.keybinds[keybindName] = newKey;
2781
3488
  saveConfig(configPath, config);
2782
- f2.success(`${keybindName} → ${import_picocolors6.default.cyan(newKey)}`);
3489
+ f2.success(`${keybindName} → ${import_picocolors7.default.cyan(newKey)}`);
2783
3490
  }
2784
3491
  var OPENCODE_THEMES = [
2785
3492
  { value: "system", label: "System", hint: "follows OS preference" },
@@ -2819,7 +3526,7 @@ var OPENCODE_THEMES = [
2819
3526
  async function editTheme(configPath) {
2820
3527
  const config = loadConfig(configPath);
2821
3528
  const currentTheme = config.theme || "system";
2822
- f2.info(`Current theme: ${import_picocolors6.default.cyan(currentTheme)}`);
3529
+ f2.info(`Current theme: ${import_picocolors7.default.cyan(currentTheme)}`);
2823
3530
  const validTheme = OPENCODE_THEMES.some((t) => t.value === currentTheme) ? currentTheme : "system";
2824
3531
  const selectedTheme = await ie({
2825
3532
  message: "Select theme",
@@ -2836,7 +3543,7 @@ async function editTheme(configPath) {
2836
3543
  }
2837
3544
  config.theme = selectedTheme;
2838
3545
  saveConfig(configPath, config);
2839
- f2.success(`Theme set to ${import_picocolors6.default.cyan(selectedTheme)}`);
3546
+ f2.success(`Theme set to ${import_picocolors7.default.cyan(selectedTheme)}`);
2840
3547
  }
2841
3548
  async function editTui(configPath) {
2842
3549
  const config = loadConfig(configPath);
@@ -2884,7 +3591,7 @@ async function editTui(configPath) {
2884
3591
  }
2885
3592
  config.tui.diff_style = style;
2886
3593
  saveConfig(configPath, config);
2887
- f2.success(`Diff style set to ${import_picocolors6.default.cyan(style)}`);
3594
+ f2.success(`Diff style set to ${import_picocolors7.default.cyan(style)}`);
2888
3595
  } else {
2889
3596
  const settingKey = setting;
2890
3597
  const value = await te({
@@ -2901,7 +3608,7 @@ async function editTui(configPath) {
2901
3608
  }
2902
3609
  config.tui[settingKey] = value ? Number(value) : undefined;
2903
3610
  saveConfig(configPath, config);
2904
- f2.success(`${settingKey} set to ${import_picocolors6.default.cyan(value || "default")}`);
3611
+ f2.success(`${settingKey} set to ${import_picocolors7.default.cyan(value || "default")}`);
2905
3612
  }
2906
3613
  }
2907
3614
  async function editTools(configPath) {
@@ -2921,9 +3628,9 @@ async function editTools(configPath) {
2921
3628
  "todoread"
2922
3629
  ];
2923
3630
  console.log();
2924
- console.log(import_picocolors6.default.bold(" Tool Status"));
3631
+ console.log(import_picocolors7.default.bold(" Tool Status"));
2925
3632
  for (const tool of commonTools) {
2926
- 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");
2927
3634
  console.log(` ${status} ${tool}`);
2928
3635
  }
2929
3636
  console.log();
@@ -2957,7 +3664,7 @@ async function editTools(configPath) {
2957
3664
  const wasEnabled = config.tools[toolName] !== false;
2958
3665
  config.tools[toolName] = !wasEnabled;
2959
3666
  saveConfig(configPath, config);
2960
- 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");
2961
3668
  f2.success(`${toolName}: ${status}`);
2962
3669
  } else {
2963
3670
  const toolName = await te({
@@ -2980,13 +3687,13 @@ async function editTools(configPath) {
2980
3687
  }
2981
3688
  config.tools[toolName] = enabled;
2982
3689
  saveConfig(configPath, config);
2983
- 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")}`);
2984
3691
  }
2985
3692
  }
2986
3693
  async function editShare(configPath) {
2987
3694
  const config = loadConfig(configPath);
2988
3695
  const current = config.share || "manual";
2989
- f2.info(`Current share setting: ${import_picocolors6.default.cyan(current)}`);
3696
+ f2.info(`Current share setting: ${import_picocolors7.default.cyan(current)}`);
2990
3697
  const selected = await ie({
2991
3698
  message: "Share setting",
2992
3699
  options: [
@@ -3009,13 +3716,13 @@ async function editShare(configPath) {
3009
3716
  const shareValue = selected;
3010
3717
  config.share = shareValue;
3011
3718
  saveConfig(configPath, config);
3012
- f2.success(`Share set to ${import_picocolors6.default.cyan(shareValue)}`);
3719
+ f2.success(`Share set to ${import_picocolors7.default.cyan(shareValue)}`);
3013
3720
  }
3014
3721
  async function editAutoupdate(configPath) {
3015
3722
  const config = loadConfig(configPath);
3016
3723
  const current = config.autoupdate ?? true;
3017
3724
  const display = current === "notify" ? "notify" : current ? "enabled" : "disabled";
3018
- f2.info(`Current autoupdate: ${import_picocolors6.default.cyan(display)}`);
3725
+ f2.info(`Current autoupdate: ${import_picocolors7.default.cyan(display)}`);
3019
3726
  const selected = await ie({
3020
3727
  message: "Autoupdate behavior",
3021
3728
  options: [
@@ -3046,21 +3753,21 @@ async function editAutoupdate(configPath) {
3046
3753
  config.autoupdate = selectedValue === "true";
3047
3754
  }
3048
3755
  saveConfig(configPath, config);
3049
- f2.success(`Autoupdate set to ${import_picocolors6.default.cyan(selectedValue)}`);
3756
+ f2.success(`Autoupdate set to ${import_picocolors7.default.cyan(selectedValue)}`);
3050
3757
  }
3051
3758
 
3052
3759
  // src/commands/init.ts
3053
3760
  import { execSync } from "node:child_process";
3054
3761
  import {
3055
- existsSync as existsSync3,
3056
- mkdirSync as mkdirSync2,
3057
- readFileSync as readFileSync3,
3058
- readdirSync as readdirSync2,
3059
- writeFileSync as writeFileSync3
3762
+ existsSync as existsSync4,
3763
+ mkdirSync as mkdirSync3,
3764
+ readFileSync as readFileSync4,
3765
+ readdirSync as readdirSync3,
3766
+ writeFileSync as writeFileSync4
3060
3767
  } from "node:fs";
3061
- import { basename, dirname, join as join3 } from "node:path";
3768
+ import { basename as basename2, dirname, join as join4 } from "node:path";
3062
3769
  import { fileURLToPath } from "node:url";
3063
- var import_picocolors7 = __toESM(require_picocolors(), 1);
3770
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
3064
3771
  var EXCLUDED_DIRS = [
3065
3772
  "node_modules",
3066
3773
  ".git",
@@ -3077,12 +3784,12 @@ var EXCLUDED_FILES = [
3077
3784
  "pnpm-lock.yaml"
3078
3785
  ];
3079
3786
  function detectMode(targetDir) {
3080
- const opencodeDir = join3(targetDir, ".opencode");
3081
- if (existsSync3(opencodeDir)) {
3787
+ const opencodeDir = join4(targetDir, ".opencode");
3788
+ if (existsSync4(opencodeDir)) {
3082
3789
  return "already-initialized";
3083
3790
  }
3084
- if (existsSync3(targetDir)) {
3085
- const entries = readdirSync2(targetDir);
3791
+ if (existsSync4(targetDir)) {
3792
+ const entries = readdirSync3(targetDir);
3086
3793
  const hasCode = entries.some((e2) => !e2.startsWith(".") && !EXCLUDED_DIRS.includes(e2) && e2 !== "node_modules");
3087
3794
  if (hasCode) {
3088
3795
  return "add-config";
@@ -3094,12 +3801,12 @@ function getTemplateRoot() {
3094
3801
  const __filename2 = fileURLToPath(import.meta.url);
3095
3802
  const __dirname2 = dirname(__filename2);
3096
3803
  const possiblePaths = [
3097
- join3(__dirname2, "template"),
3098
- join3(__dirname2, "..", "..", ".opencode")
3804
+ join4(__dirname2, "template"),
3805
+ join4(__dirname2, "..", "..", ".opencode")
3099
3806
  ];
3100
3807
  for (const path of possiblePaths) {
3101
- const opencodeDir = join3(path, ".opencode");
3102
- if (existsSync3(opencodeDir)) {
3808
+ const opencodeDir = join4(path, ".opencode");
3809
+ if (existsSync4(opencodeDir)) {
3103
3810
  return path;
3104
3811
  }
3105
3812
  }
@@ -3113,20 +3820,20 @@ async function copyDir(src, dest) {
3113
3820
  continue;
3114
3821
  if (!entry.isDirectory() && EXCLUDED_FILES.includes(entry.name))
3115
3822
  continue;
3116
- const srcPath = join3(src, entry.name);
3117
- const destPath = join3(dest, entry.name);
3823
+ const srcPath = join4(src, entry.name);
3824
+ const destPath = join4(dest, entry.name);
3118
3825
  if (entry.isSymbolicLink()) {} else if (entry.isDirectory()) {
3119
3826
  await copyDir(srcPath, destPath);
3120
3827
  } else {
3121
- const content = readFileSync3(srcPath, "utf-8");
3122
- writeFileSync3(destPath, content);
3828
+ const content = readFileSync4(srcPath, "utf-8");
3829
+ writeFileSync4(destPath, content);
3123
3830
  }
3124
3831
  }
3125
3832
  }
3126
3833
  async function copyOpenCodeOnly(templateRoot, targetDir) {
3127
- const opencodeSrc = join3(templateRoot, ".opencode");
3128
- const opencodeDest = join3(targetDir, ".opencode");
3129
- if (!existsSync3(opencodeSrc)) {
3834
+ const opencodeSrc = join4(templateRoot, ".opencode");
3835
+ const opencodeDest = join4(targetDir, ".opencode");
3836
+ if (!existsSync4(opencodeSrc)) {
3130
3837
  return false;
3131
3838
  }
3132
3839
  await copyDir(opencodeSrc, opencodeDest);
@@ -3137,20 +3844,20 @@ async function initCommand(options = {}) {
3137
3844
  return;
3138
3845
  const targetDir = process.cwd();
3139
3846
  const mode = detectMode(targetDir);
3140
- oe(import_picocolors7.default.bgCyan(import_picocolors7.default.black(" OpenCodeKit ")));
3847
+ oe(import_picocolors8.default.bgCyan(import_picocolors8.default.black(" OpenCodeKit ")));
3141
3848
  if (mode === "already-initialized" && !options.force) {
3142
3849
  f2.warn("Already initialized (.opencode/ exists)");
3143
- f2.info(`Use ${import_picocolors7.default.cyan("--force")} to reinitialize`);
3850
+ f2.info(`Use ${import_picocolors8.default.cyan("--force")} to reinitialize`);
3144
3851
  $e("Nothing to do");
3145
3852
  return;
3146
3853
  }
3147
3854
  const templateRoot = getTemplateRoot();
3148
3855
  if (!templateRoot) {
3149
3856
  f2.error("Template not found. Please reinstall opencodekit.");
3150
- $e(import_picocolors7.default.red("Failed"));
3857
+ $e(import_picocolors8.default.red("Failed"));
3151
3858
  process.exit(1);
3152
3859
  }
3153
- let projectName = basename(targetDir);
3860
+ let projectName = basename2(targetDir);
3154
3861
  if (mode === "scaffold") {
3155
3862
  const name = await te({
3156
3863
  message: "Project name",
@@ -3166,7 +3873,7 @@ async function initCommand(options = {}) {
3166
3873
  const s = de();
3167
3874
  if (mode === "scaffold") {
3168
3875
  s.start("Scaffolding project");
3169
- mkdirSync2(targetDir, { recursive: true });
3876
+ mkdirSync3(targetDir, { recursive: true });
3170
3877
  } else if (mode === "add-config") {
3171
3878
  s.start("Adding OpenCodeKit");
3172
3879
  } else {
@@ -3175,25 +3882,25 @@ async function initCommand(options = {}) {
3175
3882
  const success = await copyOpenCodeOnly(templateRoot, targetDir);
3176
3883
  if (!success) {
3177
3884
  s.stop("Failed");
3178
- $e(import_picocolors7.default.red("Template copy failed"));
3885
+ $e(import_picocolors8.default.red("Template copy failed"));
3179
3886
  process.exit(1);
3180
3887
  }
3181
3888
  s.stop("Done");
3182
3889
  if (options.beads) {
3183
- const beadsDir = join3(targetDir, ".beads");
3184
- if (!existsSync3(beadsDir)) {
3890
+ const beadsDir = join4(targetDir, ".beads");
3891
+ if (!existsSync4(beadsDir)) {
3185
3892
  const bs = de();
3186
3893
  bs.start("Initializing .beads/");
3187
3894
  try {
3188
3895
  execSync("bd init", { cwd: targetDir, stdio: "ignore" });
3189
3896
  bs.stop("Beads initialized");
3190
3897
  } catch {
3191
- mkdirSync2(beadsDir, { recursive: true });
3192
- writeFileSync3(join3(beadsDir, "config.yaml"), `# Beads configuration
3898
+ mkdirSync3(beadsDir, { recursive: true });
3899
+ writeFileSync4(join4(beadsDir, "config.yaml"), `# Beads configuration
3193
3900
  version: 1
3194
3901
  `);
3195
- writeFileSync3(join3(beadsDir, "issues.jsonl"), "");
3196
- 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));
3197
3904
  bs.stop("Beads initialized (manual)");
3198
3905
  }
3199
3906
  } else {
@@ -3201,213 +3908,352 @@ version: 1
3201
3908
  }
3202
3909
  }
3203
3910
  le("cd .opencode && bun install", "Next steps");
3204
- $e(import_picocolors7.default.green("Ready to code!"));
3911
+ $e(import_picocolors8.default.green("Ready to code!"));
3205
3912
  }
3206
3913
 
3207
3914
  // src/commands/menu.ts
3208
- import { existsSync as existsSync6, lstatSync as lstatSync3, readdirSync as readdirSync5 } from "node:fs";
3209
- import { basename as basename2, join as join6 } from "node:path";
3210
- 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);
3211
3918
 
3212
3919
  // src/commands/skill.ts
3213
- import { existsSync as existsSync4, lstatSync as lstatSync2, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "node:fs";
3214
- import { join as join4 } from "node:path";
3215
- 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);
3216
3930
  async function skillCommand(action) {
3217
- const opencodePath = join4(process.cwd(), ".opencode");
3218
- if (!existsSync4(opencodePath)) {
3931
+ const opencodePath = join5(process.cwd(), ".opencode");
3932
+ if (!existsSync5(opencodePath)) {
3219
3933
  notInitialized();
3220
3934
  return;
3221
3935
  }
3936
+ const skillDir = join5(opencodePath, "skill");
3222
3937
  switch (action) {
3223
3938
  case "list":
3224
- await listSkills(opencodePath);
3939
+ listSkills(skillDir);
3940
+ break;
3941
+ case "create":
3942
+ await createSkill(skillDir);
3225
3943
  break;
3226
3944
  case "add":
3227
- await addSkill(opencodePath);
3945
+ await createSkill(skillDir);
3228
3946
  break;
3229
3947
  case "view": {
3230
3948
  const skillName = process.argv[4];
3231
- 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);
3232
3955
  break;
3233
3956
  }
3234
3957
  default:
3235
- unknownAction(action, ["list", "add", "view"]);
3958
+ unknownAction(action, ["list", "create", "view", "remove"]);
3236
3959
  }
3237
3960
  }
3238
- function collectSkills(basePath) {
3239
- const skills = [];
3240
- if (!existsSync4(basePath))
3241
- return skills;
3242
- const tiers = ["core", "stack", "specialized"];
3243
- let hasTiers = false;
3244
- for (const tier of tiers) {
3245
- const tierPath = join4(basePath, tier);
3246
- if (existsSync4(tierPath)) {
3247
- hasTiers = true;
3248
- const entries = readdirSync3(tierPath);
3249
- for (const entry of entries) {
3250
- const entryPath = join4(tierPath, entry);
3251
- if (lstatSync2(entryPath).isDirectory()) {
3252
- skills.push({ name: entry, path: entryPath, tier });
3253
- }
3254
- }
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;
3255
3974
  }
3256
- }
3257
- if (!hasTiers) {
3258
- const entries = readdirSync3(basePath);
3259
- for (const entry of entries) {
3260
- const entryPath = join4(basePath, entry);
3261
- if (lstatSync2(entryPath).isDirectory()) {
3262
- 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);
3263
3991
  }
3992
+ frontmatter[key] = value;
3264
3993
  }
3265
3994
  }
3266
- return skills;
3995
+ if (currentKey && multilineValue) {
3996
+ frontmatter[currentKey] = multilineValue.trim();
3997
+ }
3998
+ return frontmatter;
3267
3999
  }
3268
- async function listSkills(opencodePath) {
3269
- const locations = [
3270
- { path: join4(opencodePath, "superpowers", "skills"), name: "Superpowers" },
3271
- { path: join4(opencodePath, "skills"), name: "Project" }
3272
- ];
3273
- let totalSkills = 0;
3274
- for (const loc of locations) {
3275
- const skills = collectSkills(loc.path);
3276
- if (skills.length > 0) {
3277
- f2.info(import_picocolors8.default.bold(loc.name));
3278
- for (const skill of skills) {
3279
- const label = skill.tier ? `${skill.tier}/${skill.name}` : skill.name;
3280
- console.log(` ${import_picocolors8.default.cyan("•")} ${label}`);
3281
- }
3282
- 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
+ });
3283
4018
  }
3284
4019
  }
3285
- if (totalSkills === 0) {
3286
- 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)";
3287
4030
  }
4031
+ return;
3288
4032
  }
3289
- async function addSkill(opencodePath) {
3290
- oe(import_picocolors8.default.bgYellow(import_picocolors8.default.black(" Add Skill ")));
3291
- const superpowersPath = join4(opencodePath, "superpowers", "skills");
3292
- 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);
3293
4043
  if (skills.length === 0) {
3294
- showEmpty("skills available to install");
3295
- $e("Done");
4044
+ showEmpty("skills", "ock skill create");
3296
4045
  return;
3297
4046
  }
3298
- const selected = await ie({
3299
- message: "Select skill to install",
3300
- options: skills.map((s) => ({
3301
- value: s.name,
3302
- label: s.tier ? `${s.name} (${s.tier})` : s.name
3303
- }))
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
+ }
3304
4073
  });
3305
- if (lD(selected)) {
4074
+ if (lD(name)) {
3306
4075
  ue("Cancelled");
3307
4076
  return;
3308
4077
  }
3309
- const skill = skills.find((s) => s.name === selected);
3310
- 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");
3311
4085
  return;
3312
- const skillMdPath = join4(skill.path, "SKILL.md");
3313
- if (!existsSync4(skillMdPath)) {
3314
- notFound("SKILL.md in", skill.name);
3315
- $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");
3316
4094
  return;
3317
4095
  }
3318
- const content = readFileSync4(skillMdPath, "utf-8");
3319
- const lines = content.split(`
3320
- `).slice(0, 10);
3321
- console.log();
3322
- console.log(import_picocolors8.default.dim("─".repeat(40)));
3323
- console.log(lines.join(`
3324
- `));
3325
- if (content.split(`
3326
- `).length > 10) {
3327
- 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;
3328
4161
  }
3329
- 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");
3330
4169
  console.log();
3331
- f2.success(`Skill "${skill.name}" is available at:`);
3332
- f2.info(import_picocolors8.default.cyan(skill.path));
3333
- le(`Skills in superpowers/ are auto-loaded.
3334
- Use ${import_picocolors8.default.cyan("find_skills")} + ${import_picocolors8.default.cyan("use_skill")} in opencode.`, "Usage");
3335
- $e(import_picocolors8.default.green("Done!"));
3336
- }
3337
- async function viewSkill(opencodePath, skillName) {
3338
- const superpowersPath = join4(opencodePath, "superpowers", "skills");
3339
- 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);
3340
4176
  if (skills.length === 0) {
3341
- showEmpty("skills");
4177
+ showEmpty("skills", "ock skill create");
3342
4178
  return;
3343
4179
  }
4180
+ let skillName = skillNameArg;
3344
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
+ }));
3345
4187
  const selected = await ie({
3346
- message: "Select skill to view",
3347
- options: skills.map((s) => ({ value: s.name, label: s.name }))
4188
+ message: "Select skill to remove",
4189
+ options
3348
4190
  });
3349
4191
  if (lD(selected)) {
3350
4192
  return;
3351
4193
  }
3352
4194
  skillName = selected;
3353
4195
  }
3354
- const skill = skills.find((s) => s.name === skillName);
4196
+ const skill = skills.find((s) => s.name === skillName || basename3(s.path) === skillName);
3355
4197
  if (!skill) {
3356
4198
  notFound("Skill", skillName);
3357
4199
  return;
3358
4200
  }
3359
- const skillMdPath = join4(skill.path, "SKILL.md");
3360
- if (!existsSync4(skillMdPath)) {
3361
- 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");
3362
4207
  return;
3363
4208
  }
3364
- const content = readFileSync4(skillMdPath, "utf-8");
3365
- console.log();
3366
- console.log(import_picocolors8.default.dim("─".repeat(40)));
3367
- console.log(content);
3368
- console.log(import_picocolors8.default.dim("─".repeat(40)));
4209
+ rmSync(skill.path, { recursive: true, force: true });
4210
+ f2.success(`Removed skill "${skill.name}"`);
3369
4211
  }
3370
4212
 
3371
4213
  // src/commands/upgrade.ts
3372
4214
  import {
3373
4215
  copyFileSync,
3374
- existsSync as existsSync5,
3375
- mkdirSync as mkdirSync3,
3376
- readFileSync as readFileSync5,
3377
- readdirSync as readdirSync4,
3378
- 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
3379
4222
  } from "node:fs";
3380
- import { dirname as dirname2, join as join5 } from "node:path";
4223
+ import { dirname as dirname2, join as join6 } from "node:path";
3381
4224
  import { fileURLToPath as fileURLToPath2 } from "node:url";
3382
- var import_picocolors9 = __toESM(require_picocolors(), 1);
4225
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
3383
4226
  var PRESERVE_FILES = [
3384
- "opencode.json"
4227
+ "opencode.json",
4228
+ ".env"
3385
4229
  ];
3386
4230
  var PRESERVE_DIRS = [
3387
4231
  "agent",
3388
4232
  "command",
3389
- "memory"
4233
+ "memory",
4234
+ "skill",
4235
+ "tool"
3390
4236
  ];
3391
4237
  var SKIP_DIRS = ["node_modules", ".git", "dist", "coverage"];
3392
4238
  function getTemplateRoot2() {
3393
4239
  const __filename2 = fileURLToPath2(import.meta.url);
3394
4240
  const __dirname2 = dirname2(__filename2);
3395
4241
  const possiblePaths = [
3396
- join5(__dirname2, "template"),
3397
- join5(__dirname2, "..", "..", ".opencode")
4242
+ join6(__dirname2, "template"),
4243
+ join6(__dirname2, "..", "..", ".opencode")
3398
4244
  ];
3399
4245
  for (const path of possiblePaths) {
3400
- const opencodeDir = join5(path, ".opencode");
3401
- if (existsSync5(opencodeDir)) {
4246
+ const opencodeDir = join6(path, ".opencode");
4247
+ if (existsSync6(opencodeDir)) {
3402
4248
  return path;
3403
4249
  }
3404
4250
  }
3405
4251
  return null;
3406
4252
  }
3407
4253
  function getCurrentVersion(opencodeDir) {
3408
- const versionFile = join5(opencodeDir, ".version");
3409
- if (existsSync5(versionFile)) {
3410
- return readFileSync5(versionFile, "utf-8").trim();
4254
+ const versionFile = join6(opencodeDir, ".version");
4255
+ if (existsSync6(versionFile)) {
4256
+ return readFileSync6(versionFile, "utf-8").trim();
3411
4257
  }
3412
4258
  return null;
3413
4259
  }
@@ -3415,12 +4261,12 @@ function getPackageVersion() {
3415
4261
  const __filename2 = fileURLToPath2(import.meta.url);
3416
4262
  const __dirname2 = dirname2(__filename2);
3417
4263
  const pkgPaths = [
3418
- join5(__dirname2, "..", "..", "package.json"),
3419
- join5(__dirname2, "..", "package.json")
4264
+ join6(__dirname2, "..", "..", "package.json"),
4265
+ join6(__dirname2, "..", "package.json")
3420
4266
  ];
3421
4267
  for (const pkgPath of pkgPaths) {
3422
- if (existsSync5(pkgPath)) {
3423
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf-8"));
4268
+ if (existsSync6(pkgPath)) {
4269
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
3424
4270
  return pkg.version;
3425
4271
  }
3426
4272
  }
@@ -3438,19 +4284,19 @@ async function checkVersion(opencodeDir) {
3438
4284
  function copyDirWithPreserve(src, dest, preserveFiles, preserveDirs) {
3439
4285
  const updated = [];
3440
4286
  const preserved = [];
3441
- if (!existsSync5(dest)) {
3442
- mkdirSync3(dest, { recursive: true });
4287
+ if (!existsSync6(dest)) {
4288
+ mkdirSync5(dest, { recursive: true });
3443
4289
  }
3444
- const entries = readdirSync4(src, { withFileTypes: true });
4290
+ const entries = readdirSync5(src, { withFileTypes: true });
3445
4291
  for (const entry of entries) {
3446
4292
  if (SKIP_DIRS.includes(entry.name))
3447
4293
  continue;
3448
- const srcPath = join5(src, entry.name);
3449
- const destPath = join5(dest, entry.name);
4294
+ const srcPath = join6(src, entry.name);
4295
+ const destPath = join6(dest, entry.name);
3450
4296
  if (entry.isDirectory()) {
3451
4297
  if (preserveDirs.includes(entry.name)) {
3452
- if (!existsSync5(destPath)) {
3453
- mkdirSync3(destPath, { recursive: true });
4298
+ if (!existsSync6(destPath)) {
4299
+ mkdirSync5(destPath, { recursive: true });
3454
4300
  }
3455
4301
  const subResult = copyDirPreserveExisting(srcPath, destPath);
3456
4302
  updated.push(...subResult.updated);
@@ -3460,7 +4306,7 @@ function copyDirWithPreserve(src, dest, preserveFiles, preserveDirs) {
3460
4306
  updated.push(...subResult.updated);
3461
4307
  }
3462
4308
  } else {
3463
- if (preserveFiles.includes(entry.name) && existsSync5(destPath)) {
4309
+ if (preserveFiles.includes(entry.name) && existsSync6(destPath)) {
3464
4310
  preserved.push(entry.name);
3465
4311
  } else {
3466
4312
  copyFileSync(srcPath, destPath);
@@ -3473,20 +4319,20 @@ function copyDirWithPreserve(src, dest, preserveFiles, preserveDirs) {
3473
4319
  function copyDirPreserveExisting(src, dest) {
3474
4320
  const updated = [];
3475
4321
  const preserved = [];
3476
- const entries = readdirSync4(src, { withFileTypes: true });
4322
+ const entries = readdirSync5(src, { withFileTypes: true });
3477
4323
  for (const entry of entries) {
3478
- const srcPath = join5(src, entry.name);
3479
- const destPath = join5(dest, entry.name);
4324
+ const srcPath = join6(src, entry.name);
4325
+ const destPath = join6(dest, entry.name);
3480
4326
  if (entry.isDirectory()) {
3481
- if (!existsSync5(destPath)) {
3482
- mkdirSync3(destPath, { recursive: true });
4327
+ if (!existsSync6(destPath)) {
4328
+ mkdirSync5(destPath, { recursive: true });
3483
4329
  const subResult = copyDirPreserveExisting(srcPath, destPath);
3484
4330
  updated.push(...subResult.updated);
3485
4331
  } else {
3486
- preserved.push(entry.name + "/");
4332
+ preserved.push(`${entry.name}/`);
3487
4333
  }
3488
4334
  } else {
3489
- if (!existsSync5(destPath)) {
4335
+ if (!existsSync6(destPath)) {
3490
4336
  copyFileSync(srcPath, destPath);
3491
4337
  updated.push(entry.name);
3492
4338
  } else {
@@ -3496,32 +4342,48 @@ function copyDirPreserveExisting(src, dest) {
3496
4342
  }
3497
4343
  return { updated, preserved };
3498
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
+ }
3499
4361
  async function upgradeCommand(options = {}) {
3500
4362
  if (process.argv.includes("--quiet"))
3501
4363
  return;
3502
4364
  const cwd = process.cwd();
3503
- const opencodeDir = join5(cwd, ".opencode");
3504
- if (!existsSync5(opencodeDir)) {
4365
+ const opencodeDir = join6(cwd, ".opencode");
4366
+ if (!existsSync6(opencodeDir)) {
3505
4367
  notInitialized();
3506
4368
  return;
3507
4369
  }
3508
- oe(import_picocolors9.default.bgCyan(import_picocolors9.default.black(" Upgrade ")));
4370
+ oe(import_picocolors10.default.bgCyan(import_picocolors10.default.black(" Upgrade ")));
3509
4371
  const versionInfo = await checkVersion(opencodeDir);
3510
4372
  console.log();
3511
- console.log(` ${import_picocolors9.default.bold("Installed")} ${import_picocolors9.default.cyan(versionInfo.current || "unknown")}`);
3512
- 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")}`);
3513
4375
  console.log();
3514
4376
  if (options.check) {
3515
4377
  if (versionInfo.needsUpdate) {
3516
- f2.info(`Update available: ${import_picocolors9.default.cyan(versionInfo.latest)}`);
3517
- $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`);
3518
4380
  } else {
3519
- $e(import_picocolors9.default.green("Already up to date"));
4381
+ $e(import_picocolors10.default.green("Already up to date"));
3520
4382
  }
3521
4383
  return;
3522
4384
  }
3523
4385
  if (!versionInfo.needsUpdate && !options.force) {
3524
- $e(import_picocolors9.default.green("Already up to date"));
4386
+ $e(import_picocolors10.default.green("Already up to date"));
3525
4387
  return;
3526
4388
  }
3527
4389
  const templateRoot = getTemplateRoot2();
@@ -3529,8 +4391,8 @@ async function upgradeCommand(options = {}) {
3529
4391
  showError("Template not found", "Reinstall opencodekit");
3530
4392
  return;
3531
4393
  }
3532
- const templateOpencode = join5(templateRoot, ".opencode");
3533
- if (!existsSync5(templateOpencode)) {
4394
+ const templateOpencode = join6(templateRoot, ".opencode");
4395
+ if (!existsSync6(templateOpencode)) {
3534
4396
  showError("Template .opencode not found");
3535
4397
  return;
3536
4398
  }
@@ -3547,7 +4409,7 @@ async function upgradeCommand(options = {}) {
3547
4409
  s.start("Upgrading");
3548
4410
  const result = copyDirWithPreserve(templateOpencode, opencodeDir, PRESERVE_FILES, PRESERVE_DIRS);
3549
4411
  if (versionInfo.latest) {
3550
- writeFileSync4(join5(opencodeDir, ".version"), versionInfo.latest);
4412
+ writeFileSync6(join6(opencodeDir, ".version"), versionInfo.latest);
3551
4413
  }
3552
4414
  s.stop("Done");
3553
4415
  if (result.updated.length > 0) {
@@ -3556,13 +4418,55 @@ async function upgradeCommand(options = {}) {
3556
4418
  if (result.preserved.length > 0) {
3557
4419
  f2.info(`Preserved ${result.preserved.length} user files`);
3558
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
+ }
3559
4463
  le("cd .opencode && bun install", "Run if dependencies changed");
3560
- $e(import_picocolors9.default.green(`Upgraded to ${versionInfo.latest}`));
4464
+ $e(import_picocolors10.default.green(`Upgraded to ${versionInfo.latest}`));
3561
4465
  }
3562
4466
 
3563
4467
  // src/commands/menu.ts
3564
4468
  async function interactiveMenu(version) {
3565
- 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} `)));
3566
4470
  const action = await ie({
3567
4471
  message: "What do you want to do?",
3568
4472
  options: [
@@ -3613,115 +4517,1437 @@ async function interactiveMenu(version) {
3613
4517
  break;
3614
4518
  }
3615
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
+ ]);
3616
4548
  async function doctorCommand() {
3617
4549
  if (process.argv.includes("--quiet"))
3618
4550
  return;
3619
4551
  const cwd = process.cwd();
3620
- const opencodeDir = join6(cwd, ".opencode");
4552
+ const opencodeDir = join7(cwd, ".opencode");
4553
+ oe(import_picocolors11.default.bgBlue(import_picocolors11.default.white(" Doctor - Health Check ")));
3621
4554
  const checks = [];
4555
+ const warnings = [];
4556
+ console.log();
4557
+ console.log(import_picocolors11.default.bold(" Structure"));
3622
4558
  checks.push({
3623
4559
  name: ".opencode/ exists",
3624
- ok: existsSync6(opencodeDir),
4560
+ ok: existsSync7(opencodeDir),
3625
4561
  fix: "ock init"
3626
4562
  });
4563
+ const configPath = join7(opencodeDir, "opencode.json");
3627
4564
  checks.push({
3628
4565
  name: "opencode.json exists",
3629
- ok: existsSync6(join6(opencodeDir, "opencode.json")),
4566
+ ok: existsSync7(configPath),
3630
4567
  fix: "ock init --force"
3631
4568
  });
3632
4569
  checks.push({
3633
4570
  name: "package.json exists",
3634
- ok: existsSync6(join6(opencodeDir, "package.json")),
4571
+ ok: existsSync7(join7(opencodeDir, "package.json")),
3635
4572
  fix: "ock init --force"
3636
4573
  });
3637
4574
  checks.push({
3638
4575
  name: "Dependencies installed",
3639
- ok: existsSync6(join6(opencodeDir, "node_modules")),
4576
+ ok: existsSync7(join7(opencodeDir, "node_modules")),
3640
4577
  fix: "cd .opencode && bun install"
3641
4578
  });
3642
- const agentDir = join6(opencodeDir, "agent");
3643
- const hasAgents = existsSync6(agentDir) && readdirSync5(agentDir).some((f3) => f3.endsWith(".md"));
3644
- checks.push({
3645
- name: "Agents configured",
3646
- ok: hasAgents,
3647
- fix: "ock agent add"
3648
- });
3649
- 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
+ }
3650
4642
  console.log();
3651
- 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) {
3652
4775
  for (const check of checks) {
3653
4776
  if (check.ok) {
3654
- 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
+ }
3655
4783
  } else {
3656
- console.log(` ${import_picocolors10.default.red("✗")} ${check.name}`);
4784
+ console.log(` ${import_picocolors11.default.red("✗")} ${check.name}`);
3657
4785
  if (check.fix) {
3658
- 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)}`);
3659
4787
  }
3660
- issues++;
3661
4788
  }
3662
4789
  }
3663
- console.log();
3664
- if (issues === 0) {
3665
- $e(import_picocolors10.default.green("All checks passed!"));
3666
- } else {
3667
- $e(import_picocolors10.default.yellow(`${issues} issue${issues > 1 ? "s" : ""} found`));
3668
- }
3669
4790
  }
3670
4791
  async function statusCommand() {
3671
4792
  if (process.argv.includes("--quiet"))
3672
4793
  return;
3673
4794
  const cwd = process.cwd();
3674
- const opencodeDir = join6(cwd, ".opencode");
3675
- if (!existsSync6(opencodeDir)) {
4795
+ const opencodeDir = join7(cwd, ".opencode");
4796
+ if (!existsSync7(opencodeDir)) {
3676
4797
  notInitialized();
3677
4798
  return;
3678
4799
  }
3679
- const projectName = basename2(cwd);
3680
- oe(import_picocolors10.default.bgCyan(import_picocolors10.default.black(` ${projectName} `)));
3681
- 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");
3682
4803
  let agentNames = [];
3683
- if (existsSync6(agentDir)) {
3684
- 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", ""));
3685
4806
  }
3686
- const superpowersPath = join6(opencodeDir, "superpowers", "skills");
4807
+ const skillDir = join7(opencodeDir, "skill");
3687
4808
  let skillCount = 0;
3688
- if (existsSync6(superpowersPath)) {
3689
- const tiers = ["core", "stack", "specialized"];
3690
- let hasTiers = false;
3691
- for (const tier of tiers) {
3692
- const tierPath = join6(superpowersPath, tier);
3693
- if (existsSync6(tierPath)) {
3694
- hasTiers = true;
3695
- skillCount += readdirSync5(tierPath).filter((e2) => lstatSync3(join6(tierPath, e2)).isDirectory()).length;
3696
- }
3697
- }
3698
- if (!hasTiers) {
3699
- skillCount = readdirSync5(superpowersPath).filter((e2) => lstatSync3(join6(superpowersPath, e2)).isDirectory()).length;
3700
- }
4809
+ if (existsSync7(skillDir)) {
4810
+ skillCount = readdirSync6(skillDir).filter((f3) => lstatSync2(join7(skillDir, f3)).isDirectory() && existsSync7(join7(skillDir, f3, "SKILL.md"))).length;
3701
4811
  }
3702
- const commandDir = join6(opencodeDir, "command");
4812
+ const commandDir = join7(opencodeDir, "command");
3703
4813
  let commandCount = 0;
3704
- if (existsSync6(commandDir)) {
3705
- 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 {}
3706
4829
  }
3707
4830
  console.log();
3708
- 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))}`);
3709
4832
  if (agentNames.length > 0) {
3710
- console.log(` ${import_picocolors10.default.dim(agentNames.join(", "))}`);
4833
+ console.log(` ${import_picocolors11.default.dim(agentNames.join(", "))}`);
3711
4834
  }
3712
4835
  console.log();
3713
- 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))}`);
3714
4839
  console.log();
3715
- 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))}`);
3716
4841
  console.log();
3717
- 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"))) {
3718
4845
  showWarning("Dependencies not installed", "cd .opencode && bun install");
3719
4846
  }
3720
- $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
+ });
3721
5947
  }
3722
5948
 
3723
5949
  // src/utils/logger.ts
3724
- var import_picocolors11 = __toESM(require_picocolors(), 1);
5950
+ var import_picocolors16 = __toESM(require_picocolors(), 1);
3725
5951
  var logger = {
3726
5952
  log: (message) => {
3727
5953
  if (!process.argv.includes("--quiet")) {
@@ -3730,32 +5956,32 @@ var logger = {
3730
5956
  },
3731
5957
  info: (message) => {
3732
5958
  if (!process.argv.includes("--quiet")) {
3733
- console.log(import_picocolors11.default.blue(`[i] ${message}`));
5959
+ console.log(import_picocolors16.default.blue(`[i] ${message}`));
3734
5960
  }
3735
5961
  },
3736
5962
  success: (message) => {
3737
5963
  if (!process.argv.includes("--quiet")) {
3738
- console.log(import_picocolors11.default.green(`[+] ${message}`));
5964
+ console.log(import_picocolors16.default.green(`[+] ${message}`));
3739
5965
  }
3740
5966
  },
3741
5967
  warn: (message) => {
3742
5968
  if (!process.argv.includes("--quiet")) {
3743
- console.warn(import_picocolors11.default.yellow(`[!] ${message}`));
5969
+ console.warn(import_picocolors16.default.yellow(`[!] ${message}`));
3744
5970
  }
3745
5971
  },
3746
5972
  error: (message) => {
3747
5973
  if (!process.argv.includes("--quiet")) {
3748
- console.error(import_picocolors11.default.red(`[x] ${message}`));
5974
+ console.error(import_picocolors16.default.red(`[x] ${message}`));
3749
5975
  }
3750
5976
  },
3751
5977
  debug: (message) => {
3752
5978
  if ((process.env.DEBUG || process.argv.includes("--verbose")) && !process.argv.includes("--quiet")) {
3753
- console.log(import_picocolors11.default.dim(`[DEBUG] ${message}`));
5979
+ console.log(import_picocolors16.default.dim(`[DEBUG] ${message}`));
3754
5980
  }
3755
5981
  },
3756
5982
  verbose: (message) => {
3757
5983
  if (process.argv.includes("--verbose") && !process.argv.includes("--quiet")) {
3758
- console.log(import_picocolors11.default.dim(message));
5984
+ console.log(import_picocolors16.default.dim(message));
3759
5985
  }
3760
5986
  }
3761
5987
  };
@@ -3787,23 +6013,39 @@ Usage: ock agent <action>
3787
6013
  }
3788
6014
  await agentCommand(action);
3789
6015
  });
3790
- 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) => {
3791
6032
  if (!action) {
3792
6033
  console.log(`
3793
- Usage: ock skill <action>
6034
+ Usage: ock agent <action>
3794
6035
  `);
3795
6036
  console.log("Actions:");
3796
- console.log(" list List all skills");
3797
- console.log(" add Install a skill");
3798
- 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
3799
6041
  `);
3800
6042
  return;
3801
6043
  }
3802
- await skillCommand(action);
6044
+ await agentCommand(action);
3803
6045
  });
3804
6046
  cli.command("doctor", "Check project health").action(doctorCommand);
3805
6047
  cli.command("status", "Show project overview").action(statusCommand);
3806
- 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) => {
3807
6049
  await configCommand(action);
3808
6050
  });
3809
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) => {
@@ -3812,6 +6054,9 @@ cli.command("upgrade", "Update .opencode/ templates to latest version").option("
3812
6054
  cli.command("completion [shell]", "Generate shell completion script (bash, zsh, fish)").action(async (shell) => {
3813
6055
  await completionCommand(shell);
3814
6056
  });
6057
+ cli.command("tui", "Launch interactive dashboard").action(async () => {
6058
+ await launchTUI();
6059
+ });
3815
6060
  cli.command("", "Interactive menu").action(async () => {
3816
6061
  await interactiveMenu(packageVersion);
3817
6062
  });