opencodekit 0.12.7 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.js +2753 -508
- package/dist/template/.opencode/AGENTS.md +35 -128
- package/dist/template/.opencode/README.md +4 -3
- package/dist/template/.opencode/command/design.md +1 -0
- package/dist/template/.opencode/command/fix.md +28 -1
- package/dist/template/.opencode/command/implement.md +195 -39
- package/dist/template/.opencode/command/new-feature.md +229 -188
- package/dist/template/.opencode/command/plan.md +354 -82
- package/dist/template/.opencode/command/research.md +29 -6
- package/dist/template/.opencode/command/start.md +227 -0
- package/dist/template/.opencode/command/triage.md +66 -12
- package/dist/template/.opencode/memory/project/beads-workflow.md +510 -0
- package/dist/template/.opencode/memory/session-context.md +40 -0
- package/dist/template/.opencode/opencode.json +20 -5
- package/dist/template/.opencode/package.json +1 -1
- package/dist/template/.opencode/plugin/compaction.ts +62 -18
- package/dist/template/.opencode/plugin/lib/notify.ts +2 -3
- package/dist/template/.opencode/plugin/sessions.ts +1 -1
- package/dist/template/.opencode/plugin/skill-mcp.ts +11 -12
- package/dist/template/.opencode/skill/beads/SKILL.md +44 -0
- package/dist/template/.opencode/skill/source-code-research/SKILL.md +537 -0
- package/dist/template/.opencode/tool/ast-grep.ts +3 -3
- package/dist/template/.opencode/tool/bd-inbox.ts +7 -6
- package/dist/template/.opencode/tool/bd-msg.ts +3 -3
- package/dist/template/.opencode/tool/bd-release.ts +2 -2
- package/dist/template/.opencode/tool/bd-reserve.ts +5 -4
- package/dist/template/.opencode/tool/memory-read.ts +2 -2
- package/dist/template/.opencode/tool/memory-search.ts +2 -2
- package/dist/template/.opencode/tool/memory-update.ts +11 -12
- package/dist/template/.opencode/tool/observation.ts +6 -6
- 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.
|
|
753
|
+
version: "0.13.1",
|
|
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
|
|
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
|
|
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(
|
|
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("✗")
|
|
1498
|
+
f2.error(`${import_picocolors3.default.red("✗")} ${message}`);
|
|
1416
1499
|
if (fix) {
|
|
1417
|
-
f2.info(
|
|
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("!")
|
|
1515
|
+
f2.warn(`${import_picocolors3.default.yellow("!")} ${message}`);
|
|
1436
1516
|
if (suggestion) {
|
|
1437
|
-
f2.info(
|
|
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
|
|
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", "
|
|
1556
|
+
unknownAction(action, ["list", "create", "view", "remove"]);
|
|
1481
1557
|
}
|
|
1482
1558
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
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 =
|
|
1598
|
+
const agents = [];
|
|
1599
|
+
for (const entry of entries) {
|
|
1500
1600
|
const entryPath = join(agentPath, entry);
|
|
1501
|
-
|
|
1502
|
-
|
|
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
|
|
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
|
-
|
|
1510
|
-
const
|
|
1511
|
-
|
|
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
|
-
|
|
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
|
|
1530
|
-
oe(import_picocolors4.default.bgMagenta(import_picocolors4.default.black("
|
|
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
|
|
1547
|
-
message: "
|
|
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
|
-
{
|
|
1550
|
-
|
|
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(
|
|
1692
|
+
if (lD(mode)) {
|
|
1554
1693
|
ue("Cancelled");
|
|
1555
1694
|
return;
|
|
1556
1695
|
}
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
if (
|
|
1562
|
-
|
|
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
|
|
1567
|
-
|
|
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 (
|
|
1749
|
+
if (mode === "primary") {
|
|
1570
1750
|
le(`Add to opencode.json:
|
|
1571
1751
|
|
|
1572
|
-
"
|
|
1752
|
+
"agent": {
|
|
1573
1753
|
"${name}": {
|
|
1574
|
-
"model": "
|
|
1575
|
-
"systemPrompt": ".opencode/agent/${name}.md"
|
|
1754
|
+
"model": "claude-sonnet-4-20250514"
|
|
1576
1755
|
}
|
|
1577
|
-
}`, "
|
|
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,
|
|
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
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
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
|
|
1775
|
+
options
|
|
1599
1776
|
});
|
|
1600
1777
|
if (lD(selected)) {
|
|
1601
1778
|
return;
|
|
1602
1779
|
}
|
|
1603
1780
|
agentName = selected;
|
|
1604
1781
|
}
|
|
1605
|
-
const
|
|
1606
|
-
if (!
|
|
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(
|
|
1787
|
+
const content = readFileSync(agent.path, "utf-8");
|
|
1611
1788
|
console.log();
|
|
1612
|
-
console.log(import_picocolors4.default.dim("─".repeat(
|
|
1789
|
+
console.log(import_picocolors4.default.dim("─".repeat(60)));
|
|
1613
1790
|
console.log(content);
|
|
1614
|
-
console.log(import_picocolors4.default.dim("─".repeat(
|
|
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/
|
|
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
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
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
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
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
|
-
|
|
1759
|
-
|
|
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
|
-
|
|
2286
|
+
selectedShell = selected;
|
|
1773
2287
|
}
|
|
1774
2288
|
let script;
|
|
1775
2289
|
let setupInstructions;
|
|
1776
|
-
switch (
|
|
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(
|
|
1806
|
-
console.error(
|
|
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
|
|
1811
|
-
import { join as
|
|
1812
|
-
var
|
|
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 =
|
|
1843
|
-
const configPath =
|
|
1844
|
-
if (!
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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(
|
|
1915
|
-
console.log(` ${
|
|
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(
|
|
1918
|
-
console.log(` ${
|
|
2618
|
+
console.log(import_picocolors7.default.bold(" Small Model"));
|
|
2619
|
+
console.log(` ${import_picocolors7.default.cyan(config.small_model)}`);
|
|
1919
2620
|
}
|
|
1920
|
-
console.log(
|
|
1921
|
-
console.log(` ${
|
|
1922
|
-
console.log(
|
|
1923
|
-
console.log(` ${
|
|
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(
|
|
1926
|
-
console.log(` ${
|
|
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(
|
|
2629
|
+
console.log(import_picocolors7.default.bold(" TUI"));
|
|
1929
2630
|
if (config.tui.scroll_speed !== undefined) {
|
|
1930
|
-
console.log(` 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: ${
|
|
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: ${
|
|
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(
|
|
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 ?
|
|
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(
|
|
2648
|
+
console.log(import_picocolors7.default.bold(" Tools"));
|
|
1948
2649
|
for (const [tool, enabled] of Object.entries(config.tools)) {
|
|
1949
|
-
const status = enabled === false ?
|
|
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(
|
|
2655
|
+
console.log(import_picocolors7.default.bold(" Keybinds"));
|
|
1955
2656
|
for (const [action, key] of Object.entries(config.keybinds)) {
|
|
1956
|
-
console.log(` ${
|
|
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(
|
|
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}: ${
|
|
2038
|
-
f2.info(
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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(
|
|
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(
|
|
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 ||
|
|
2263
|
-
const builtIn = info?.builtIn ?
|
|
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 ?
|
|
2266
|
-
console.log(` ${
|
|
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 ?
|
|
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: ${
|
|
2336
|
-
f2.info(
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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(
|
|
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 ?
|
|
2515
|
-
const type = server.type === "remote" ?
|
|
2516
|
-
const endpoint = server.type === "remote" ?
|
|
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 ?
|
|
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: ${
|
|
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(
|
|
2660
|
-
console.log(` edit: ${
|
|
2661
|
-
console.log(` external_directory: ${
|
|
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(
|
|
3370
|
+
console.log(import_picocolors7.default.bold(" Bash"));
|
|
2664
3371
|
for (const [pattern, perm] of Object.entries(config.permission.bash)) {
|
|
2665
|
-
console.log(` ${
|
|
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(
|
|
3434
|
+
console.log(import_picocolors7.default.bold(" Current Keybinds"));
|
|
2728
3435
|
for (const [action2, key] of Object.entries(config.keybinds)) {
|
|
2729
|
-
console.log(` ${
|
|
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} → ${
|
|
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: ${
|
|
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 ${
|
|
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 ${
|
|
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 ${
|
|
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(
|
|
3631
|
+
console.log(import_picocolors7.default.bold(" Tool Status"));
|
|
2925
3632
|
for (const tool of commonTools) {
|
|
2926
|
-
const status = config.tools[tool] === false ?
|
|
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] ?
|
|
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 ?
|
|
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: ${
|
|
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 ${
|
|
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: ${
|
|
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 ${
|
|
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
|
|
3056
|
-
mkdirSync as
|
|
3057
|
-
readFileSync as
|
|
3058
|
-
readdirSync as
|
|
3059
|
-
writeFileSync as
|
|
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
|
|
3768
|
+
import { basename as basename2, dirname, join as join4 } from "node:path";
|
|
3062
3769
|
import { fileURLToPath } from "node:url";
|
|
3063
|
-
var
|
|
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 =
|
|
3081
|
-
if (
|
|
3787
|
+
const opencodeDir = join4(targetDir, ".opencode");
|
|
3788
|
+
if (existsSync4(opencodeDir)) {
|
|
3082
3789
|
return "already-initialized";
|
|
3083
3790
|
}
|
|
3084
|
-
if (
|
|
3085
|
-
const entries =
|
|
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
|
-
|
|
3098
|
-
|
|
3804
|
+
join4(__dirname2, "template"),
|
|
3805
|
+
join4(__dirname2, "..", "..", ".opencode")
|
|
3099
3806
|
];
|
|
3100
3807
|
for (const path of possiblePaths) {
|
|
3101
|
-
const opencodeDir =
|
|
3102
|
-
if (
|
|
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 =
|
|
3117
|
-
const destPath =
|
|
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 =
|
|
3122
|
-
|
|
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 =
|
|
3128
|
-
const opencodeDest =
|
|
3129
|
-
if (!
|
|
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(
|
|
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 ${
|
|
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(
|
|
3857
|
+
$e(import_picocolors8.default.red("Failed"));
|
|
3151
3858
|
process.exit(1);
|
|
3152
3859
|
}
|
|
3153
|
-
let projectName =
|
|
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
|
-
|
|
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(
|
|
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 =
|
|
3184
|
-
if (!
|
|
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
|
-
|
|
3192
|
-
|
|
3898
|
+
mkdirSync3(beadsDir, { recursive: true });
|
|
3899
|
+
writeFileSync4(join4(beadsDir, "config.yaml"), `# Beads configuration
|
|
3193
3900
|
version: 1
|
|
3194
3901
|
`);
|
|
3195
|
-
|
|
3196
|
-
|
|
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(
|
|
3911
|
+
$e(import_picocolors8.default.green("Ready to code!"));
|
|
3205
3912
|
}
|
|
3206
3913
|
|
|
3207
3914
|
// src/commands/menu.ts
|
|
3208
|
-
import { existsSync as
|
|
3209
|
-
import { basename as
|
|
3210
|
-
var
|
|
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 {
|
|
3214
|
-
|
|
3215
|
-
|
|
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 =
|
|
3218
|
-
if (!
|
|
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
|
-
|
|
3939
|
+
listSkills(skillDir);
|
|
3940
|
+
break;
|
|
3941
|
+
case "create":
|
|
3942
|
+
await createSkill(skillDir);
|
|
3225
3943
|
break;
|
|
3226
3944
|
case "add":
|
|
3227
|
-
await
|
|
3945
|
+
await createSkill(skillDir);
|
|
3228
3946
|
break;
|
|
3229
3947
|
case "view": {
|
|
3230
3948
|
const skillName = process.argv[4];
|
|
3231
|
-
await viewSkill(
|
|
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", "
|
|
3958
|
+
unknownAction(action, ["list", "create", "view", "remove"]);
|
|
3236
3959
|
}
|
|
3237
3960
|
}
|
|
3238
|
-
function
|
|
3239
|
-
const
|
|
3240
|
-
if (!
|
|
3241
|
-
return
|
|
3242
|
-
const
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
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
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
|
|
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
|
-
|
|
3995
|
+
if (currentKey && multilineValue) {
|
|
3996
|
+
frontmatter[currentKey] = multilineValue.trim();
|
|
3997
|
+
}
|
|
3998
|
+
return frontmatter;
|
|
3267
3999
|
}
|
|
3268
|
-
|
|
3269
|
-
const
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
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
|
-
|
|
3286
|
-
|
|
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
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
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
|
|
3295
|
-
$e("Done");
|
|
4044
|
+
showEmpty("skills", "ock skill create");
|
|
3296
4045
|
return;
|
|
3297
4046
|
}
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
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(
|
|
4074
|
+
if (lD(name)) {
|
|
3306
4075
|
ue("Cancelled");
|
|
3307
4076
|
return;
|
|
3308
4077
|
}
|
|
3309
|
-
const
|
|
3310
|
-
|
|
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
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
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
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
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
|
|
3347
|
-
options
|
|
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
|
|
3360
|
-
|
|
3361
|
-
|
|
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
|
-
|
|
3365
|
-
|
|
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
|
|
3375
|
-
mkdirSync as
|
|
3376
|
-
readFileSync as
|
|
3377
|
-
readdirSync as
|
|
3378
|
-
|
|
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
|
|
4223
|
+
import { dirname as dirname2, join as join6 } from "node:path";
|
|
3381
4224
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3382
|
-
var
|
|
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
|
-
|
|
3397
|
-
|
|
4242
|
+
join6(__dirname2, "template"),
|
|
4243
|
+
join6(__dirname2, "..", "..", ".opencode")
|
|
3398
4244
|
];
|
|
3399
4245
|
for (const path of possiblePaths) {
|
|
3400
|
-
const opencodeDir =
|
|
3401
|
-
if (
|
|
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 =
|
|
3409
|
-
if (
|
|
3410
|
-
return
|
|
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
|
-
|
|
3419
|
-
|
|
4264
|
+
join6(__dirname2, "..", "..", "package.json"),
|
|
4265
|
+
join6(__dirname2, "..", "package.json")
|
|
3420
4266
|
];
|
|
3421
4267
|
for (const pkgPath of pkgPaths) {
|
|
3422
|
-
if (
|
|
3423
|
-
const pkg = JSON.parse(
|
|
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 (!
|
|
3442
|
-
|
|
4287
|
+
if (!existsSync6(dest)) {
|
|
4288
|
+
mkdirSync5(dest, { recursive: true });
|
|
3443
4289
|
}
|
|
3444
|
-
const entries =
|
|
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 =
|
|
3449
|
-
const destPath =
|
|
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 (!
|
|
3453
|
-
|
|
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) &&
|
|
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 =
|
|
4322
|
+
const entries = readdirSync5(src, { withFileTypes: true });
|
|
3477
4323
|
for (const entry of entries) {
|
|
3478
|
-
const srcPath =
|
|
3479
|
-
const destPath =
|
|
4324
|
+
const srcPath = join6(src, entry.name);
|
|
4325
|
+
const destPath = join6(dest, entry.name);
|
|
3480
4326
|
if (entry.isDirectory()) {
|
|
3481
|
-
if (!
|
|
3482
|
-
|
|
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 (!
|
|
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 =
|
|
3504
|
-
if (!
|
|
4365
|
+
const opencodeDir = join6(cwd, ".opencode");
|
|
4366
|
+
if (!existsSync6(opencodeDir)) {
|
|
3505
4367
|
notInitialized();
|
|
3506
4368
|
return;
|
|
3507
4369
|
}
|
|
3508
|
-
oe(
|
|
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(` ${
|
|
3512
|
-
console.log(` ${
|
|
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: ${
|
|
3517
|
-
$e(`Run ${
|
|
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(
|
|
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(
|
|
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 =
|
|
3533
|
-
if (!
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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 =
|
|
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:
|
|
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:
|
|
4566
|
+
ok: existsSync7(configPath),
|
|
3630
4567
|
fix: "ock init --force"
|
|
3631
4568
|
});
|
|
3632
4569
|
checks.push({
|
|
3633
4570
|
name: "package.json exists",
|
|
3634
|
-
ok:
|
|
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:
|
|
4576
|
+
ok: existsSync7(join7(opencodeDir, "node_modules")),
|
|
3640
4577
|
fix: "cd .opencode && bun install"
|
|
3641
4578
|
});
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
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
|
-
|
|
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(` ${
|
|
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(` ${
|
|
4784
|
+
console.log(` ${import_picocolors11.default.red("✗")} ${check.name}`);
|
|
3657
4785
|
if (check.fix) {
|
|
3658
|
-
console.log(` ${
|
|
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 =
|
|
3675
|
-
if (!
|
|
4795
|
+
const opencodeDir = join7(cwd, ".opencode");
|
|
4796
|
+
if (!existsSync7(opencodeDir)) {
|
|
3676
4797
|
notInitialized();
|
|
3677
4798
|
return;
|
|
3678
4799
|
}
|
|
3679
|
-
const projectName =
|
|
3680
|
-
oe(
|
|
3681
|
-
const agentDir =
|
|
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 (
|
|
3684
|
-
agentNames =
|
|
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
|
|
4807
|
+
const skillDir = join7(opencodeDir, "skill");
|
|
3687
4808
|
let skillCount = 0;
|
|
3688
|
-
if (
|
|
3689
|
-
|
|
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 =
|
|
4812
|
+
const commandDir = join7(opencodeDir, "command");
|
|
3703
4813
|
let commandCount = 0;
|
|
3704
|
-
if (
|
|
3705
|
-
commandCount =
|
|
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(` ${
|
|
4831
|
+
console.log(` ${import_picocolors11.default.bold("Agents")} ${import_picocolors11.default.cyan(String(agentNames.length))}`);
|
|
3709
4832
|
if (agentNames.length > 0) {
|
|
3710
|
-
console.log(` ${
|
|
4833
|
+
console.log(` ${import_picocolors11.default.dim(agentNames.join(", "))}`);
|
|
3711
4834
|
}
|
|
3712
4835
|
console.log();
|
|
3713
|
-
console.log(` ${
|
|
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(` ${
|
|
4840
|
+
console.log(` ${import_picocolors11.default.bold("Tools")} ${import_picocolors11.default.cyan(String(toolCount))}`);
|
|
3716
4841
|
console.log();
|
|
3717
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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("
|
|
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
|
|
6034
|
+
Usage: ock agent <action>
|
|
3794
6035
|
`);
|
|
3795
6036
|
console.log("Actions:");
|
|
3796
|
-
console.log(" list List all
|
|
3797
|
-
console.log("
|
|
3798
|
-
console.log(
|
|
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
|
|
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,
|
|
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
|
});
|