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