@glrs-dev/cli 2.3.0 → 2.4.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/CHANGELOG.md +2 -0
- package/dist/{chunk-EM4MJBOD.js → chunk-2AZKRWC6.js} +4 -4
- package/dist/{chunk-UXBOTMDY.js → chunk-2P3ETOT2.js} +2 -2
- package/dist/chunk-2VMFXAJH.js +795 -0
- package/dist/chunk-5ZVUFNCP.js +140 -0
- package/dist/{chunk-W37UX3U2.js → chunk-6Y27RQQL.js} +2 -2
- package/dist/{chunk-RZWOWTKF.js → chunk-EKNRKZWR.js} +4 -4
- package/dist/{chunk-YGNDPKIW.js → chunk-HQUCVJ4G.js} +3 -1
- package/dist/{chunk-OABVEBWW.js → chunk-MBEVC327.js} +1 -1
- package/dist/{chunk-MIWZLETC.js → chunk-MCM47HH4.js} +1 -1
- package/dist/{chunk-F3AFRUT2.js → chunk-PTIO556V.js} +2 -2
- package/dist/{chunk-E2UNZIZT.js → chunk-R2WXQ54P.js} +1 -1
- package/dist/{chunk-I2KUXY3I.js → chunk-SMDIOB5B.js} +2 -2
- package/dist/{chunk-SPULDN7P.js → chunk-YY7EWHMA.js} +5 -3
- package/dist/cli.js +31 -20
- package/dist/commands/autopilot-interactive.d.ts +89 -0
- package/dist/commands/autopilot-interactive.js +248 -0
- package/dist/commands/autopilot-raw.d.ts +1 -0
- package/dist/commands/autopilot-raw.js +368 -0
- package/dist/commands/autopilot-tui.d.ts +7 -0
- package/dist/commands/autopilot-tui.js +7 -0
- package/dist/commands/autopilot.d.ts +39 -0
- package/dist/commands/autopilot.js +395 -0
- package/dist/commands/cleanup.js +3 -3
- package/dist/commands/create.js +4 -4
- package/dist/commands/dashboard.d.ts +3 -0
- package/dist/commands/dashboard.js +1549 -0
- package/dist/commands/debrief.d.ts +57 -0
- package/dist/commands/debrief.js +9 -0
- package/dist/commands/delete.js +3 -3
- package/dist/commands/go.js +2 -2
- package/dist/commands/list.js +3 -3
- package/dist/commands/loop.d.ts +42 -0
- package/dist/commands/loop.js +133 -0
- package/dist/commands/plan-picker.d.ts +15 -0
- package/dist/commands/plan-picker.js +76 -0
- package/dist/commands/scoper.d.ts +54 -0
- package/dist/{vendor/harness-opencode/dist/scoper-S77SOK7X.js → commands/scoper.js} +30 -15
- package/dist/commands/switch.js +3 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/lib/auto-update.js +1 -1
- package/dist/lib/config.d.ts +3 -2
- package/dist/lib/config.js +1 -1
- package/dist/lib/registry.d.ts +2 -0
- package/dist/lib/registry.js +1 -1
- package/dist/lib/worktree.js +3 -3
- package/dist/vendor/harness-opencode/dist/agents/prompts/plan.md +7 -0
- package/dist/vendor/harness-opencode/dist/chunk-GILWWWMB.js +66 -0
- package/dist/vendor/harness-opencode/dist/cli.js +335 -639
- package/dist/vendor/harness-opencode/dist/index.js +35 -8
- package/dist/vendor/harness-opencode/dist/plugin-check-GJRD2OK6.js +14 -0
- package/dist/vendor/harness-opencode/package.json +1 -1
- package/package.json +8 -2
- package/dist/vendor/harness-opencode/dist/autopilot/prompt-template.md +0 -104
- package/dist/vendor/harness-opencode/dist/chunk-GCWHRUOK.js +0 -259
- package/dist/vendor/harness-opencode/dist/chunk-MJSMBY2Y.js +0 -87
- package/dist/vendor/harness-opencode/dist/chunk-NIFAVPNN.js +0 -544
- package/dist/vendor/harness-opencode/dist/loop-session-J35NILUZ.js +0 -30
- package/dist/vendor/harness-opencode/dist/opencode-server-KPCDFYAX.js +0 -22
- package/dist/vendor/harness-opencode/dist/plan-parser-TMHEKT22.js +0 -6
- package/dist/vendor/harness-opencode/dist/plan-session-7VS32P52.js +0 -117
|
@@ -7,31 +7,24 @@ import {
|
|
|
7
7
|
validateModelOverride
|
|
8
8
|
} from "./chunk-PDMXYZM4.js";
|
|
9
9
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from "./chunk-NIFAVPNN.js";
|
|
14
|
-
import "./chunk-MJSMBY2Y.js";
|
|
15
|
-
import {
|
|
16
|
-
createSession,
|
|
17
|
-
getLastAssistantMessage,
|
|
18
|
-
sendAndWait
|
|
19
|
-
} from "./chunk-GCWHRUOK.js";
|
|
10
|
+
promptChoice,
|
|
11
|
+
promptMulti
|
|
12
|
+
} from "./chunk-GILWWWMB.js";
|
|
20
13
|
|
|
21
14
|
// src/cli.ts
|
|
22
15
|
import {
|
|
23
16
|
binary,
|
|
24
|
-
command as
|
|
25
|
-
flag
|
|
26
|
-
positional
|
|
17
|
+
command as command2,
|
|
18
|
+
flag,
|
|
19
|
+
positional,
|
|
27
20
|
subcommands,
|
|
28
21
|
run
|
|
29
22
|
} from "cmd-ts";
|
|
30
23
|
|
|
31
24
|
// src/cli/install.ts
|
|
32
|
-
import * as
|
|
33
|
-
import * as
|
|
34
|
-
import * as
|
|
25
|
+
import * as fs2 from "fs";
|
|
26
|
+
import * as path2 from "path";
|
|
27
|
+
import * as os from "os";
|
|
35
28
|
import { fileURLToPath } from "url";
|
|
36
29
|
|
|
37
30
|
// src/cli/merge-config.ts
|
|
@@ -199,42 +192,6 @@ function seedConfig(srcJson, dstPath) {
|
|
|
199
192
|
fs.writeFileSync(dstPath, JSON.stringify(srcJson, null, 2) + "\n");
|
|
200
193
|
}
|
|
201
194
|
|
|
202
|
-
// src/cli/plugin-check.ts
|
|
203
|
-
import * as fs2 from "fs";
|
|
204
|
-
import * as path2 from "path";
|
|
205
|
-
import * as os from "os";
|
|
206
|
-
import { select, checkbox, confirm } from "@inquirer/prompts";
|
|
207
|
-
async function promptChoice(question, choices, defaultIndex = 0) {
|
|
208
|
-
if (!process.stdin.isTTY) return defaultIndex;
|
|
209
|
-
const answer = await select({
|
|
210
|
-
message: question,
|
|
211
|
-
choices: choices.map((label, i) => ({
|
|
212
|
-
name: label,
|
|
213
|
-
value: i
|
|
214
|
-
})),
|
|
215
|
-
default: defaultIndex
|
|
216
|
-
});
|
|
217
|
-
return answer;
|
|
218
|
-
}
|
|
219
|
-
async function promptMulti(question, choices) {
|
|
220
|
-
if (!process.stdin.isTTY) {
|
|
221
|
-
const defaults = /* @__PURE__ */ new Set();
|
|
222
|
-
choices.forEach((c3, i) => {
|
|
223
|
-
if (c3.defaultOn) defaults.add(i);
|
|
224
|
-
});
|
|
225
|
-
return defaults;
|
|
226
|
-
}
|
|
227
|
-
const answers = await checkbox({
|
|
228
|
-
message: question,
|
|
229
|
-
choices: choices.map((c3, i) => ({
|
|
230
|
-
name: c3.label,
|
|
231
|
-
value: i,
|
|
232
|
-
checked: c3.defaultOn
|
|
233
|
-
}))
|
|
234
|
-
});
|
|
235
|
-
return new Set(answers);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
195
|
// src/cli/models-dev.ts
|
|
239
196
|
var MODELS_DEV_URL = "https://models.dev/api.json";
|
|
240
197
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
@@ -393,14 +350,14 @@ function extractPluginOptions(config) {
|
|
|
393
350
|
return null;
|
|
394
351
|
}
|
|
395
352
|
function readPackageVersion() {
|
|
396
|
-
const here =
|
|
353
|
+
const here = path2.dirname(fileURLToPath(import.meta.url));
|
|
397
354
|
const candidates = [
|
|
398
|
-
|
|
399
|
-
|
|
355
|
+
path2.join(here, "..", "package.json"),
|
|
356
|
+
path2.join(here, "..", "..", "package.json")
|
|
400
357
|
];
|
|
401
358
|
for (const candidate of candidates) {
|
|
402
359
|
try {
|
|
403
|
-
const raw =
|
|
360
|
+
const raw = fs2.readFileSync(candidate, "utf8");
|
|
404
361
|
const parsed = JSON.parse(raw);
|
|
405
362
|
if (parsed.name === PLUGIN_NAME && typeof parsed.version === "string") {
|
|
406
363
|
return parsed.version;
|
|
@@ -413,8 +370,8 @@ function readPackageVersion() {
|
|
|
413
370
|
);
|
|
414
371
|
}
|
|
415
372
|
function getOpencodeConfigPath() {
|
|
416
|
-
const configHome = process.env["XDG_CONFIG_HOME"] ??
|
|
417
|
-
return
|
|
373
|
+
const configHome = process.env["XDG_CONFIG_HOME"] ?? path2.join(os.homedir(), ".config");
|
|
374
|
+
return path2.join(configHome, "opencode", "opencode.json");
|
|
418
375
|
}
|
|
419
376
|
async function refreshPluginCacheIfStale() {
|
|
420
377
|
try {
|
|
@@ -431,9 +388,9 @@ async function refreshPluginCacheIfStale() {
|
|
|
431
388
|
}
|
|
432
389
|
}
|
|
433
390
|
function readExistingConfig(configPath) {
|
|
434
|
-
if (!
|
|
391
|
+
if (!fs2.existsSync(configPath)) return null;
|
|
435
392
|
try {
|
|
436
|
-
return JSON.parse(
|
|
393
|
+
return JSON.parse(fs2.readFileSync(configPath, "utf8"));
|
|
437
394
|
} catch {
|
|
438
395
|
return null;
|
|
439
396
|
}
|
|
@@ -474,14 +431,14 @@ function detectEnabledPluginToggles(existing) {
|
|
|
474
431
|
}
|
|
475
432
|
function migrateHarnessKeyToPluginOptions(configPath) {
|
|
476
433
|
try {
|
|
477
|
-
if (!
|
|
478
|
-
const raw =
|
|
434
|
+
if (!fs2.existsSync(configPath)) return;
|
|
435
|
+
const raw = fs2.readFileSync(configPath, "utf8");
|
|
479
436
|
const config = JSON.parse(raw);
|
|
480
437
|
if (!config.harness || typeof config.harness !== "object") return;
|
|
481
438
|
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
482
439
|
const pluginIdx = plugins.findIndex((entry) => {
|
|
483
440
|
const name = typeof entry === "string" ? entry : Array.isArray(entry) ? entry[0] : null;
|
|
484
|
-
return name === PLUGIN_NAME || String(name ?? "").startsWith(`${PLUGIN_NAME}@`);
|
|
441
|
+
return name === PLUGIN_NAME || String(name ?? "").startsWith(`${PLUGIN_NAME}@`) || String(name ?? "").includes("harness-opencode");
|
|
485
442
|
});
|
|
486
443
|
if (pluginIdx < 0) return;
|
|
487
444
|
const current = plugins[pluginIdx];
|
|
@@ -491,8 +448,8 @@ function migrateHarnessKeyToPluginOptions(configPath) {
|
|
|
491
448
|
plugins[pluginIdx] = [existingName, merged];
|
|
492
449
|
delete config.harness;
|
|
493
450
|
const bakPath = `${configPath}.bak.${Date.now()}-${process.pid}`;
|
|
494
|
-
|
|
495
|
-
|
|
451
|
+
fs2.copyFileSync(configPath, bakPath);
|
|
452
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
496
453
|
ok("Migrated legacy `harness` config into plugin options");
|
|
497
454
|
info(`Backup: ${bakPath}`);
|
|
498
455
|
} catch {
|
|
@@ -516,17 +473,17 @@ function deepEqual(a, b) {
|
|
|
516
473
|
}
|
|
517
474
|
function writePluginOption(configPath, subKey, value, opts) {
|
|
518
475
|
try {
|
|
519
|
-
if (!
|
|
476
|
+
if (!fs2.existsSync(configPath)) {
|
|
520
477
|
return { changed: false };
|
|
521
478
|
}
|
|
522
|
-
const raw =
|
|
479
|
+
const raw = fs2.readFileSync(configPath, "utf8");
|
|
523
480
|
const config = JSON.parse(raw);
|
|
524
481
|
if (!Array.isArray(config.plugin)) {
|
|
525
482
|
return { changed: false };
|
|
526
483
|
}
|
|
527
484
|
const pluginIdx = config.plugin.findIndex((entry) => {
|
|
528
485
|
const name = typeof entry === "string" ? entry : Array.isArray(entry) ? entry[0] : null;
|
|
529
|
-
return name === PLUGIN_NAME || String(name ?? "").startsWith(`${PLUGIN_NAME}@`);
|
|
486
|
+
return name === PLUGIN_NAME || String(name ?? "").startsWith(`${PLUGIN_NAME}@`) || String(name ?? "").includes("harness-opencode");
|
|
530
487
|
});
|
|
531
488
|
if (pluginIdx < 0) {
|
|
532
489
|
return { changed: false };
|
|
@@ -543,9 +500,9 @@ function writePluginOption(configPath, subKey, value, opts) {
|
|
|
543
500
|
return { changed: true };
|
|
544
501
|
}
|
|
545
502
|
const bakPath = `${configPath}.bak.${Date.now()}-${process.pid}`;
|
|
546
|
-
|
|
503
|
+
fs2.copyFileSync(configPath, bakPath);
|
|
547
504
|
config.plugin[pluginIdx] = [existingName, newOpts];
|
|
548
|
-
|
|
505
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
549
506
|
ok(`Reconfigured ${subKey}`);
|
|
550
507
|
info(`Backup: ${bakPath}`);
|
|
551
508
|
return { changed: true, bakPath };
|
|
@@ -555,10 +512,10 @@ function writePluginOption(configPath, subKey, value, opts) {
|
|
|
555
512
|
}
|
|
556
513
|
function writeMcpToggles(configPath, enabledSet, opts) {
|
|
557
514
|
try {
|
|
558
|
-
if (!
|
|
515
|
+
if (!fs2.existsSync(configPath)) {
|
|
559
516
|
return { changed: false };
|
|
560
517
|
}
|
|
561
|
-
const raw =
|
|
518
|
+
const raw = fs2.readFileSync(configPath, "utf8");
|
|
562
519
|
const config = JSON.parse(raw);
|
|
563
520
|
const toggleNames = new Set(MCP_TOGGLES.map((t) => t.name));
|
|
564
521
|
const existingMcp = config.mcp && typeof config.mcp === "object" ? { ...config.mcp } : {};
|
|
@@ -594,13 +551,13 @@ function writeMcpToggles(configPath, enabledSet, opts) {
|
|
|
594
551
|
return { changed: true };
|
|
595
552
|
}
|
|
596
553
|
const bakPath = `${configPath}.bak.${Date.now()}-${process.pid}`;
|
|
597
|
-
|
|
554
|
+
fs2.copyFileSync(configPath, bakPath);
|
|
598
555
|
if (Object.keys(newMcp).length > 0) {
|
|
599
556
|
config.mcp = newMcp;
|
|
600
557
|
} else {
|
|
601
558
|
delete config.mcp;
|
|
602
559
|
}
|
|
603
|
-
|
|
560
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
604
561
|
ok("Reconfigured MCPs");
|
|
605
562
|
info(`Backup: ${bakPath}`);
|
|
606
563
|
return { changed: true, bakPath };
|
|
@@ -610,10 +567,10 @@ function writeMcpToggles(configPath, enabledSet, opts) {
|
|
|
610
567
|
}
|
|
611
568
|
function writePluginToggles(configPath, enabledSet, opts) {
|
|
612
569
|
try {
|
|
613
|
-
if (!
|
|
570
|
+
if (!fs2.existsSync(configPath)) {
|
|
614
571
|
return { changed: false };
|
|
615
572
|
}
|
|
616
|
-
const raw =
|
|
573
|
+
const raw = fs2.readFileSync(configPath, "utf8");
|
|
617
574
|
const config = JSON.parse(raw);
|
|
618
575
|
const toggleNames = new Set(PLUGIN_TOGGLES.map((t) => t.name));
|
|
619
576
|
const existingPlugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
@@ -641,7 +598,7 @@ function writePluginToggles(configPath, enabledSet, opts) {
|
|
|
641
598
|
return { changed: true };
|
|
642
599
|
}
|
|
643
600
|
const bakPath = `${configPath}.bak.${Date.now()}-${process.pid}`;
|
|
644
|
-
|
|
601
|
+
fs2.copyFileSync(configPath, bakPath);
|
|
645
602
|
const newPlugins = existingPlugins.filter((entry) => {
|
|
646
603
|
const name = typeof entry === "string" ? entry : Array.isArray(entry) ? entry[0] : null;
|
|
647
604
|
return !(typeof name === "string" && toRemove.has(name));
|
|
@@ -650,7 +607,7 @@ function writePluginToggles(configPath, enabledSet, opts) {
|
|
|
650
607
|
newPlugins.push(name);
|
|
651
608
|
}
|
|
652
609
|
config.plugin = newPlugins;
|
|
653
|
-
|
|
610
|
+
fs2.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
654
611
|
ok("Reconfigured plugin add-ons");
|
|
655
612
|
info(`Backup: ${bakPath}`);
|
|
656
613
|
return { changed: true, bakPath };
|
|
@@ -667,7 +624,7 @@ async function install(opts = {}) {
|
|
|
667
624
|
const hasPlugin = existing ? (Array.isArray(existing.plugin) ? existing.plugin : []).some(
|
|
668
625
|
(p) => {
|
|
669
626
|
const name = typeof p === "string" ? p : Array.isArray(p) ? p[0] : null;
|
|
670
|
-
return name === PLUGIN_NAME || String(name ?? "").startsWith(`${PLUGIN_NAME}@`);
|
|
627
|
+
return name === PLUGIN_NAME || String(name ?? "").startsWith(`${PLUGIN_NAME}@`) || String(name ?? "").includes("harness-opencode");
|
|
671
628
|
}
|
|
672
629
|
) : false;
|
|
673
630
|
const existingProvider = detectModelProvider(existing);
|
|
@@ -964,7 +921,7 @@ ${c.bold}Ready.${c.reset} Run ${c.green}opencode${c.reset} to start.
|
|
|
964
921
|
if (reconfigurePluginToggles) {
|
|
965
922
|
writePluginToggles(configPath, newPluginToggleEnabledSet, { dryRun });
|
|
966
923
|
}
|
|
967
|
-
if (!
|
|
924
|
+
if (!fs2.existsSync(configPath)) {
|
|
968
925
|
if (dryRun) {
|
|
969
926
|
info(`[dry-run] Would create ${configPath}`);
|
|
970
927
|
info(`[dry-run] Config: ${JSON.stringify(config, null, 2)}`);
|
|
@@ -1009,36 +966,36 @@ ${c.bold}Ready.${c.reset} Run ${c.green}opencode${c.reset} to start.
|
|
|
1009
966
|
}
|
|
1010
967
|
|
|
1011
968
|
// src/cli/uninstall.ts
|
|
1012
|
-
import * as
|
|
1013
|
-
import * as
|
|
1014
|
-
import * as
|
|
969
|
+
import * as fs3 from "fs";
|
|
970
|
+
import * as path3 from "path";
|
|
971
|
+
import * as os2 from "os";
|
|
1015
972
|
var PLUGIN_NAME2 = "@glrs-dev/harness-plugin-opencode";
|
|
1016
973
|
function getOpencodeConfigPath2() {
|
|
1017
|
-
const configHome = process.env["XDG_CONFIG_HOME"] ??
|
|
1018
|
-
return
|
|
974
|
+
const configHome = process.env["XDG_CONFIG_HOME"] ?? path3.join(os2.homedir(), ".config");
|
|
975
|
+
return path3.join(configHome, "opencode", "opencode.json");
|
|
1019
976
|
}
|
|
1020
977
|
function uninstall(opts = {}) {
|
|
1021
978
|
const { dryRun = false } = opts;
|
|
1022
979
|
const configPath = getOpencodeConfigPath2();
|
|
1023
|
-
const
|
|
980
|
+
const c4 = {
|
|
1024
981
|
reset: "\x1B[0m",
|
|
1025
982
|
green: "\x1B[32m",
|
|
1026
983
|
yellow: "\x1B[33m",
|
|
1027
984
|
blue: "\x1B[34m"
|
|
1028
985
|
};
|
|
1029
|
-
const
|
|
1030
|
-
const
|
|
1031
|
-
const warn2 = (msg) => console.log(`${
|
|
986
|
+
const ok3 = (msg) => console.log(`${c4.green}\u2713${c4.reset} ${msg}`);
|
|
987
|
+
const info3 = (msg) => console.log(`${c4.blue}\u2022${c4.reset} ${msg}`);
|
|
988
|
+
const warn2 = (msg) => console.log(`${c4.yellow}!${c4.reset} ${msg}`);
|
|
1032
989
|
console.log(`
|
|
1033
|
-
${
|
|
990
|
+
${c4.blue}Uninstalling ${PLUGIN_NAME2}${c4.reset}
|
|
1034
991
|
`);
|
|
1035
|
-
if (!
|
|
992
|
+
if (!fs3.existsSync(configPath)) {
|
|
1036
993
|
warn2(`No opencode.json found at ${configPath} \u2014 nothing to do`);
|
|
1037
994
|
return;
|
|
1038
995
|
}
|
|
1039
996
|
let raw;
|
|
1040
997
|
try {
|
|
1041
|
-
raw =
|
|
998
|
+
raw = fs3.readFileSync(configPath, "utf8");
|
|
1042
999
|
} catch (e) {
|
|
1043
1000
|
console.error(`\x1B[31m\u2717\x1B[0m Failed to read ${configPath}: ${e.message}`);
|
|
1044
1001
|
process.exit(1);
|
|
@@ -1060,12 +1017,12 @@ ${c3.blue}Uninstalling ${PLUGIN_NAME2}${c3.reset}
|
|
|
1060
1017
|
return;
|
|
1061
1018
|
}
|
|
1062
1019
|
if (dryRun) {
|
|
1063
|
-
|
|
1020
|
+
info3(`[dry-run] Would remove "${PLUGIN_NAME2}" from plugin array in ${configPath}`);
|
|
1064
1021
|
return;
|
|
1065
1022
|
}
|
|
1066
1023
|
const bakPath = `${configPath}.bak.${Date.now()}-${process.pid}`;
|
|
1067
1024
|
try {
|
|
1068
|
-
|
|
1025
|
+
fs3.copyFileSync(configPath, bakPath);
|
|
1069
1026
|
} catch (e) {
|
|
1070
1027
|
console.error(`\x1B[31m\u2717\x1B[0m Failed to write backup: ${e.message}`);
|
|
1071
1028
|
process.exit(1);
|
|
@@ -1073,36 +1030,36 @@ ${c3.blue}Uninstalling ${PLUGIN_NAME2}${c3.reset}
|
|
|
1073
1030
|
config.plugin = filtered;
|
|
1074
1031
|
const tmpPath = `${configPath}.uninstall.tmp.${Date.now()}-${process.pid}`;
|
|
1075
1032
|
try {
|
|
1076
|
-
|
|
1077
|
-
|
|
1033
|
+
fs3.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
|
|
1034
|
+
fs3.renameSync(tmpPath, configPath);
|
|
1078
1035
|
} catch (e) {
|
|
1079
1036
|
try {
|
|
1080
|
-
|
|
1037
|
+
fs3.unlinkSync(tmpPath);
|
|
1081
1038
|
} catch {
|
|
1082
1039
|
}
|
|
1083
1040
|
console.error(`\x1B[31m\u2717\x1B[0m Failed to write config: ${e.message}`);
|
|
1084
1041
|
process.exit(1);
|
|
1085
1042
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1043
|
+
ok3(`Removed "${PLUGIN_NAME2}" from ${configPath}`);
|
|
1044
|
+
info3(`Backup: ${bakPath}`);
|
|
1088
1045
|
console.log(`
|
|
1089
1046
|
To fully remove the package: bun remove @glrs-dev/harness-plugin-opencode
|
|
1090
1047
|
`);
|
|
1091
1048
|
}
|
|
1092
1049
|
|
|
1093
1050
|
// src/cli/doctor.ts
|
|
1094
|
-
import * as
|
|
1095
|
-
import * as
|
|
1096
|
-
import * as
|
|
1051
|
+
import * as fs4 from "fs";
|
|
1052
|
+
import * as path4 from "path";
|
|
1053
|
+
import * as os3 from "os";
|
|
1097
1054
|
import { execSync } from "child_process";
|
|
1098
1055
|
var PLUGIN_NAME3 = "@glrs-dev/harness-plugin-opencode";
|
|
1099
1056
|
function getOpencodeConfigPath3() {
|
|
1100
|
-
const configHome = process.env["XDG_CONFIG_HOME"] ??
|
|
1101
|
-
return
|
|
1057
|
+
const configHome = process.env["XDG_CONFIG_HOME"] ?? path4.join(os3.homedir(), ".config");
|
|
1058
|
+
return path4.join(configHome, "opencode", "opencode.json");
|
|
1102
1059
|
}
|
|
1103
|
-
function cmd(
|
|
1060
|
+
function cmd(command3) {
|
|
1104
1061
|
try {
|
|
1105
|
-
return execSync(
|
|
1062
|
+
return execSync(command3, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
1106
1063
|
} catch {
|
|
1107
1064
|
return null;
|
|
1108
1065
|
}
|
|
@@ -1111,29 +1068,29 @@ function which(bin) {
|
|
|
1111
1068
|
return cmd(`which ${bin}`) !== null;
|
|
1112
1069
|
}
|
|
1113
1070
|
function doctor() {
|
|
1114
|
-
const
|
|
1071
|
+
const c4 = {
|
|
1115
1072
|
reset: "\x1B[0m",
|
|
1116
1073
|
green: "\x1B[32m",
|
|
1117
1074
|
yellow: "\x1B[33m",
|
|
1118
1075
|
red: "\x1B[31m",
|
|
1119
1076
|
bold: "\x1B[1m"
|
|
1120
1077
|
};
|
|
1121
|
-
const
|
|
1122
|
-
const warn2 = (msg) => console.log(`${
|
|
1123
|
-
const fail = (msg) => console.log(`${
|
|
1078
|
+
const ok3 = (msg) => console.log(`${c4.green}\u2713${c4.reset} ${msg}`);
|
|
1079
|
+
const warn2 = (msg) => console.log(`${c4.yellow}!${c4.reset} ${msg}`);
|
|
1080
|
+
const fail = (msg) => console.log(`${c4.red}\u2717${c4.reset} ${msg}`);
|
|
1124
1081
|
console.log(`
|
|
1125
|
-
${
|
|
1082
|
+
${c4.bold}Doctor \u2014 ${PLUGIN_NAME3}${c4.reset}
|
|
1126
1083
|
`);
|
|
1127
1084
|
const ocVersion = cmd("opencode --version 2>/dev/null | head -1");
|
|
1128
1085
|
if (ocVersion) {
|
|
1129
|
-
|
|
1086
|
+
ok3(`opencode ${ocVersion}`);
|
|
1130
1087
|
} else {
|
|
1131
1088
|
fail("opencode CLI not found \u2014 install from https://opencode.ai");
|
|
1132
1089
|
}
|
|
1133
1090
|
const configPath = getOpencodeConfigPath3();
|
|
1134
|
-
if (
|
|
1091
|
+
if (fs4.existsSync(configPath)) {
|
|
1135
1092
|
try {
|
|
1136
|
-
const config = JSON.parse(
|
|
1093
|
+
const config = JSON.parse(fs4.readFileSync(configPath, "utf8"));
|
|
1137
1094
|
const plugins = Array.isArray(config.plugin) ? config.plugin : [];
|
|
1138
1095
|
let pluginOptions = null;
|
|
1139
1096
|
const hasPlugin = plugins.some((p) => {
|
|
@@ -1151,7 +1108,7 @@ ${c3.bold}Doctor \u2014 ${PLUGIN_NAME3}${c3.reset}
|
|
|
1151
1108
|
return false;
|
|
1152
1109
|
});
|
|
1153
1110
|
if (hasPlugin) {
|
|
1154
|
-
|
|
1111
|
+
ok3(`"${PLUGIN_NAME3}" present in opencode.json plugin array`);
|
|
1155
1112
|
} else {
|
|
1156
1113
|
warn2(`"${PLUGIN_NAME3}" NOT in opencode.json plugin array \u2014 run: bunx ${PLUGIN_NAME3} install`);
|
|
1157
1114
|
}
|
|
@@ -1188,20 +1145,20 @@ ${c3.bold}Doctor \u2014 ${PLUGIN_NAME3}${c3.reset}
|
|
|
1188
1145
|
}
|
|
1189
1146
|
}
|
|
1190
1147
|
if (invalid.length === 0) {
|
|
1191
|
-
|
|
1148
|
+
ok3("model overrides look valid");
|
|
1192
1149
|
} else {
|
|
1193
1150
|
for (const entry of invalid) {
|
|
1194
1151
|
fail(`invalid model override at ${entry.keyPath}: "${entry.value}"`);
|
|
1195
1152
|
if (entry.reason) {
|
|
1196
|
-
console.log(` ${
|
|
1153
|
+
console.log(` ${c4.yellow}reason:${c4.reset} ${entry.reason}`);
|
|
1197
1154
|
}
|
|
1198
1155
|
if (entry.suggestion) {
|
|
1199
1156
|
console.log(
|
|
1200
|
-
` ${
|
|
1157
|
+
` ${c4.yellow}fix:${c4.reset} remove this key, or replace with \`${entry.suggestion}\``
|
|
1201
1158
|
);
|
|
1202
1159
|
} else {
|
|
1203
1160
|
console.log(
|
|
1204
|
-
` ${
|
|
1161
|
+
` ${c4.yellow}fix:${c4.reset} remove this key, or run \`bunx ${PLUGIN_NAME3} install\` to pick a current preset`
|
|
1205
1162
|
);
|
|
1206
1163
|
}
|
|
1207
1164
|
}
|
|
@@ -1214,550 +1171,292 @@ ${c3.bold}Doctor \u2014 ${PLUGIN_NAME3}${c3.reset}
|
|
|
1214
1171
|
warn2(`No opencode.json at ${configPath} \u2014 run: bunx ${PLUGIN_NAME3} install`);
|
|
1215
1172
|
}
|
|
1216
1173
|
if (which("uvx")) {
|
|
1217
|
-
|
|
1174
|
+
ok3("uvx (serena + git MCPs)");
|
|
1218
1175
|
} else {
|
|
1219
1176
|
warn2("uvx not found \u2014 serena and git MCPs won't work. Install: brew install uv");
|
|
1220
1177
|
}
|
|
1221
1178
|
if (which("node") && which("npx")) {
|
|
1222
|
-
|
|
1179
|
+
ok3(`node ${cmd("node --version") ?? ""} + npx (memory MCP)`);
|
|
1223
1180
|
} else {
|
|
1224
1181
|
warn2("node/npx not found \u2014 memory MCP won't work");
|
|
1225
1182
|
}
|
|
1226
1183
|
if (which("bun")) {
|
|
1227
|
-
|
|
1184
|
+
ok3(`bun ${cmd("bun --version") ?? ""}`);
|
|
1228
1185
|
} else if (which("npm")) {
|
|
1229
|
-
|
|
1186
|
+
ok3(`npm ${cmd("npm --version") ?? ""} (install bun for faster installs)`);
|
|
1230
1187
|
} else {
|
|
1231
1188
|
fail("Neither bun nor npm found \u2014 cannot install plugins");
|
|
1232
1189
|
}
|
|
1233
1190
|
console.log();
|
|
1234
1191
|
}
|
|
1235
1192
|
|
|
1236
|
-
// src/
|
|
1237
|
-
import { command
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1193
|
+
// src/cli/configure.ts
|
|
1194
|
+
import { command } from "cmd-ts";
|
|
1195
|
+
import * as fs5 from "fs";
|
|
1196
|
+
import * as path5 from "path";
|
|
1197
|
+
import * as os4 from "os";
|
|
1198
|
+
var PLUGIN_NAME4 = "@glrs-dev/harness-plugin-opencode";
|
|
1199
|
+
var c2 = {
|
|
1200
|
+
reset: "\x1B[0m",
|
|
1201
|
+
green: "\x1B[32m",
|
|
1202
|
+
yellow: "\x1B[33m",
|
|
1203
|
+
blue: "\x1B[34m",
|
|
1204
|
+
dim: "\x1B[2m",
|
|
1205
|
+
bold: "\x1B[1m",
|
|
1206
|
+
cyan: "\x1B[36m"
|
|
1207
|
+
};
|
|
1208
|
+
var ok2 = (msg) => console.log(`${c2.green}\u2713${c2.reset} ${msg}`);
|
|
1209
|
+
var info2 = (msg) => console.log(`${c2.blue}\u2022${c2.reset} ${msg}`);
|
|
1210
|
+
function getOpencodeConfigPath4() {
|
|
1211
|
+
const configHome = process.env["XDG_CONFIG_HOME"] ?? path5.join(os4.homedir(), ".config");
|
|
1212
|
+
return path5.join(configHome, "opencode", "opencode.json");
|
|
1245
1213
|
}
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
const { promisify } = await import("util");
|
|
1249
|
-
const execFile2 = promisify(execFileCb);
|
|
1214
|
+
function readConfig(configPath) {
|
|
1215
|
+
if (!fs5.existsSync(configPath)) return null;
|
|
1250
1216
|
try {
|
|
1251
|
-
|
|
1252
|
-
return stdout.trim();
|
|
1217
|
+
return JSON.parse(fs5.readFileSync(configPath, "utf8"));
|
|
1253
1218
|
} catch {
|
|
1254
|
-
|
|
1255
|
-
const { stdout } = await execFile2("git", ["diff", "--stat"], { cwd });
|
|
1256
|
-
return stdout.trim() || "(no uncommitted changes)";
|
|
1257
|
-
} catch {
|
|
1258
|
-
return "(git diff unavailable)";
|
|
1259
|
-
}
|
|
1219
|
+
return null;
|
|
1260
1220
|
}
|
|
1261
1221
|
}
|
|
1262
|
-
function
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
`**Exit reason:** ${loopResult.exitReason}`,
|
|
1269
|
-
`**Iterations completed:** ${loopResult.iterations}`,
|
|
1270
|
-
`**Exit message:** ${loopResult.message}`,
|
|
1271
|
-
`**Cumulative cost:** ${cost}`,
|
|
1272
|
-
`**Session ID:** ${sessionId}`,
|
|
1273
|
-
"",
|
|
1274
|
-
"## Original prompt",
|
|
1275
|
-
"",
|
|
1276
|
-
prompt,
|
|
1277
|
-
"",
|
|
1278
|
-
"## Git diff stat (last commit vs HEAD~1)",
|
|
1279
|
-
"",
|
|
1280
|
-
gitDiffStat || "(no changes)",
|
|
1281
|
-
"",
|
|
1282
|
-
"---",
|
|
1283
|
-
"",
|
|
1284
|
-
"Please produce the five-section debrief as instructed in your system prompt."
|
|
1285
|
-
].join("\n");
|
|
1286
|
-
}
|
|
1287
|
-
async function runDebrief(opts) {
|
|
1288
|
-
const _createSession = opts._deps?.createSession ?? createSession;
|
|
1289
|
-
const _sendAndWait = opts._deps?.sendAndWait ?? sendAndWait;
|
|
1290
|
-
const _getLastAssistantMessage = opts._deps?.getLastAssistantMessage ?? getLastAssistantMessage;
|
|
1291
|
-
const _execGitDiffStat = opts._deps?.execGitDiffStat ?? defaultExecGitDiffStat;
|
|
1292
|
-
try {
|
|
1293
|
-
const gitDiffStat = await _execGitDiffStat(opts.cwd).catch(() => "(git diff unavailable)");
|
|
1294
|
-
const contextMessage = buildContextMessage(opts.loopResult, opts.prompt, gitDiffStat);
|
|
1295
|
-
const sessionId = await _createSession(opts.server.client, {
|
|
1296
|
-
cwd: opts.cwd,
|
|
1297
|
-
agentName: "debriefer"
|
|
1298
|
-
});
|
|
1299
|
-
await _sendAndWait(opts.server.client, {
|
|
1300
|
-
sessionId,
|
|
1301
|
-
message: contextMessage,
|
|
1302
|
-
stallMs: 5 * 60 * 1e3
|
|
1303
|
-
// 5 min stall timeout for debrief
|
|
1304
|
-
});
|
|
1305
|
-
const debriefOutput = await _getLastAssistantMessage(opts.server.client, sessionId);
|
|
1306
|
-
if (debriefOutput) {
|
|
1307
|
-
process.stdout.write("\n\x1B[1m\u2500\u2500\u2500 Autopilot Debrief \u2500\u2500\u2500\x1B[0m\n\n");
|
|
1308
|
-
process.stdout.write(debriefOutput);
|
|
1309
|
-
process.stdout.write("\n\n");
|
|
1222
|
+
function extractPluginOptions2(config) {
|
|
1223
|
+
const plugins = config.plugin;
|
|
1224
|
+
if (!Array.isArray(plugins)) return null;
|
|
1225
|
+
for (const entry of plugins) {
|
|
1226
|
+
if (Array.isArray(entry) && entry.length >= 2 && (entry[0] === PLUGIN_NAME4 || String(entry[0]).startsWith(`${PLUGIN_NAME4}@`) || String(entry[0]).startsWith("file://"))) {
|
|
1227
|
+
return entry[1];
|
|
1310
1228
|
}
|
|
1311
|
-
} catch (err) {
|
|
1312
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1313
|
-
process.stderr.write(`\x1B[33m\u26A0 Debrief failed (non-fatal): ${msg}\x1B[0m
|
|
1314
|
-
`);
|
|
1315
1229
|
}
|
|
1230
|
+
return null;
|
|
1316
1231
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
maxIterations: option({
|
|
1329
|
-
long: "max-iterations",
|
|
1330
|
-
type: optional(numberType),
|
|
1331
|
-
description: `Maximum number of loop iterations (default: ${MAX_ITERATIONS}).`
|
|
1332
|
-
}),
|
|
1333
|
-
timeout: option({
|
|
1334
|
-
long: "timeout",
|
|
1335
|
-
type: optional(numberType),
|
|
1336
|
-
description: `Total wall-clock timeout in milliseconds (default: ${TIMEOUT_MS} = 4 hours).`
|
|
1337
|
-
}),
|
|
1338
|
-
noDebrief: flag({
|
|
1339
|
-
long: "no-debrief",
|
|
1340
|
-
description: "Skip the post-run debrief session."
|
|
1341
|
-
})
|
|
1342
|
-
},
|
|
1343
|
-
handler: async ({ prompt, maxIterations, timeout, noDebrief }) => {
|
|
1344
|
-
const cwd = process.cwd();
|
|
1345
|
-
process.stdout.write("\n\x1B[1mAutopilot \u2014 Ralph loop\x1B[0m\n");
|
|
1346
|
-
process.stdout.write(`Prompt: ${prompt.slice(0, 80)}${prompt.length > 80 ? "..." : ""}
|
|
1232
|
+
var TIER_LABELS = {
|
|
1233
|
+
deep: "@plan, @prime, @architecture-advisor",
|
|
1234
|
+
mid: "@build, @docs-maintainer, @lib-reader",
|
|
1235
|
+
"mid-execute": "@build, @spec-reviewer, @code-reviewer (overrides mid when set)",
|
|
1236
|
+
"autopilot-execute": "autopilot --fast",
|
|
1237
|
+
fast: "@code-searcher"
|
|
1238
|
+
};
|
|
1239
|
+
var TIERS = ["deep", "mid", "mid-execute", "autopilot-execute", "fast"];
|
|
1240
|
+
async function configureModels(configPath, currentModels) {
|
|
1241
|
+
console.log(`
|
|
1242
|
+
${c2.bold}Current model configuration:${c2.reset}
|
|
1347
1243
|
`);
|
|
1348
|
-
|
|
1244
|
+
for (const tier2 of TIERS) {
|
|
1245
|
+
const model = currentModels[tier2]?.[0] ?? "(not set)";
|
|
1246
|
+
const label = TIER_LABELS[tier2] ?? tier2;
|
|
1247
|
+
console.log(` ${c2.cyan}${label}${c2.reset}`);
|
|
1248
|
+
console.log(` ${model}
|
|
1349
1249
|
`);
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1250
|
+
}
|
|
1251
|
+
console.log();
|
|
1252
|
+
const tierChoices = [
|
|
1253
|
+
...TIERS.map((t) => {
|
|
1254
|
+
const label = TIER_LABELS[t] ?? t;
|
|
1255
|
+
const model = currentModels[t]?.[0] ?? "(not set)";
|
|
1256
|
+
return `${label} \u2192 ${model}`;
|
|
1257
|
+
}),
|
|
1258
|
+
"\u2190 Back"
|
|
1259
|
+
];
|
|
1260
|
+
const tierIdx = await promptChoice("Which tier to change?", tierChoices, tierChoices.length - 1);
|
|
1261
|
+
if (tierIdx >= TIERS.length) return;
|
|
1262
|
+
const tier = TIERS[tierIdx];
|
|
1263
|
+
info2("Fetching available models\u2026");
|
|
1264
|
+
const providers = await fetchModelsDevProviders();
|
|
1265
|
+
if (!providers || providers.length === 0) {
|
|
1266
|
+
console.log(`${c2.yellow}!${c2.reset} Could not reach Models.dev API. Enter model ID manually.`);
|
|
1267
|
+
const { input } = await import("@inquirer/prompts");
|
|
1268
|
+
const modelId = await input({
|
|
1269
|
+
message: ` ${tier} model ID:`,
|
|
1270
|
+
default: currentModels[tier]?.[0] ?? ""
|
|
1358
1271
|
});
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
`);
|
|
1363
|
-
process.stdout.write(` Iterations: ${result.iterations}
|
|
1364
|
-
|
|
1365
|
-
`);
|
|
1366
|
-
if (shouldRunDebrief({ noDebrief, env: process.env })) {
|
|
1367
|
-
const { startServer } = await import("./opencode-server-KPCDFYAX.js");
|
|
1368
|
-
let debriefServer;
|
|
1369
|
-
try {
|
|
1370
|
-
debriefServer = await startServer({ cwd });
|
|
1371
|
-
await runDebrief({
|
|
1372
|
-
server: debriefServer,
|
|
1373
|
-
loopResult: result,
|
|
1374
|
-
prompt,
|
|
1375
|
-
cwd
|
|
1376
|
-
});
|
|
1377
|
-
} catch {
|
|
1378
|
-
process.stderr.write("\x1B[33m\u26A0 Debrief server failed to start (non-fatal)\x1B[0m\n");
|
|
1379
|
-
} finally {
|
|
1380
|
-
await debriefServer?.shutdown().catch(() => {
|
|
1381
|
-
});
|
|
1382
|
-
}
|
|
1272
|
+
if (modelId) {
|
|
1273
|
+
const newModels2 = { ...currentModels, [tier]: [modelId] };
|
|
1274
|
+
writePluginOption(configPath, "models", newModels2, { dryRun: false });
|
|
1383
1275
|
}
|
|
1384
|
-
|
|
1385
|
-
process.exit(1);
|
|
1386
|
-
}
|
|
1387
|
-
process.exit(0);
|
|
1388
|
-
}
|
|
1389
|
-
});
|
|
1390
|
-
|
|
1391
|
-
// src/autopilot/autopilot-cmd.ts
|
|
1392
|
-
import { command as command2, option as option2, optional as optional2, string as stringType2 } from "cmd-ts";
|
|
1393
|
-
|
|
1394
|
-
// src/autopilot/interactive.ts
|
|
1395
|
-
import * as fs7 from "fs";
|
|
1396
|
-
import * as path7 from "path";
|
|
1397
|
-
|
|
1398
|
-
// src/plan-paths.ts
|
|
1399
|
-
import { execFile } from "child_process";
|
|
1400
|
-
import * as fs6 from "fs/promises";
|
|
1401
|
-
import * as os5 from "os";
|
|
1402
|
-
import * as path6 from "path";
|
|
1403
|
-
function execFileP(file, args, opts = {}) {
|
|
1404
|
-
const { cwd, timeoutMs = 5e3 } = opts;
|
|
1405
|
-
return new Promise((resolve2, reject) => {
|
|
1406
|
-
const controller = new AbortController();
|
|
1407
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
1408
|
-
execFile(
|
|
1409
|
-
file,
|
|
1410
|
-
args,
|
|
1411
|
-
{ signal: controller.signal, cwd, encoding: "utf8" },
|
|
1412
|
-
(err, stdout) => {
|
|
1413
|
-
clearTimeout(timer);
|
|
1414
|
-
if (err) {
|
|
1415
|
-
reject(err);
|
|
1416
|
-
return;
|
|
1417
|
-
}
|
|
1418
|
-
resolve2(stdout ?? "");
|
|
1419
|
-
}
|
|
1420
|
-
);
|
|
1421
|
-
});
|
|
1422
|
-
}
|
|
1423
|
-
function expandTilde(p) {
|
|
1424
|
-
if (p === "~") return os5.homedir();
|
|
1425
|
-
if (p.startsWith("~/")) return path6.join(os5.homedir(), p.slice(2));
|
|
1426
|
-
return p;
|
|
1427
|
-
}
|
|
1428
|
-
async function getRepoFolder(worktreeDir) {
|
|
1429
|
-
let stdout;
|
|
1430
|
-
try {
|
|
1431
|
-
stdout = await execFileP(
|
|
1432
|
-
"git",
|
|
1433
|
-
["rev-parse", "--git-common-dir"],
|
|
1434
|
-
{ cwd: worktreeDir }
|
|
1435
|
-
);
|
|
1436
|
-
} catch (err) {
|
|
1437
|
-
const msg = err instanceof Error ? err.message : "unknown error running `git rev-parse --git-common-dir`";
|
|
1438
|
-
throw new Error(
|
|
1439
|
-
`getRepoFolder: failed to resolve git-common-dir for ${worktreeDir}: ${msg}`
|
|
1440
|
-
);
|
|
1276
|
+
return;
|
|
1441
1277
|
}
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1278
|
+
const allModels = [];
|
|
1279
|
+
for (const provider of providers) {
|
|
1280
|
+
for (const [modelId, model] of Object.entries(provider.models)) {
|
|
1281
|
+
allModels.push({
|
|
1282
|
+
id: `${provider.id}/${modelId}`,
|
|
1283
|
+
provider: provider.name,
|
|
1284
|
+
name: model.name ?? modelId
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1447
1287
|
}
|
|
1448
|
-
const
|
|
1449
|
-
const
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
const
|
|
1454
|
-
|
|
1455
|
-
const
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1288
|
+
const byProvider = /* @__PURE__ */ new Map();
|
|
1289
|
+
for (const m of allModels) {
|
|
1290
|
+
if (!byProvider.has(m.provider)) byProvider.set(m.provider, []);
|
|
1291
|
+
byProvider.get(m.provider).push(m);
|
|
1292
|
+
}
|
|
1293
|
+
const providerNames = [...byProvider.keys()];
|
|
1294
|
+
providerNames.push("\u2190 Back");
|
|
1295
|
+
const providerIdx = await promptChoice(`Provider for ${tier}:`, providerNames, 0);
|
|
1296
|
+
if (providerIdx >= providerNames.length - 1) return;
|
|
1297
|
+
const providerModels = byProvider.get(providerNames[providerIdx]);
|
|
1298
|
+
const modelChoices = providerModels.map((m) => m.id);
|
|
1299
|
+
modelChoices.push("\u2190 Back");
|
|
1300
|
+
const currentModel = currentModels[tier]?.[0] ?? "";
|
|
1301
|
+
const currentIdx = modelChoices.indexOf(currentModel);
|
|
1302
|
+
const modelIdx = await promptChoice(
|
|
1303
|
+
`${tier} model:`,
|
|
1304
|
+
modelChoices,
|
|
1305
|
+
currentIdx >= 0 ? currentIdx : 0
|
|
1306
|
+
);
|
|
1307
|
+
if (modelIdx >= modelChoices.length - 1) return;
|
|
1308
|
+
const selectedModel = modelChoices[modelIdx];
|
|
1309
|
+
const newModels = { ...currentModels, [tier]: [selectedModel] };
|
|
1310
|
+
writePluginOption(configPath, "models", newModels, { dryRun: false });
|
|
1311
|
+
ok2(`${tier} \u2192 ${selectedModel}`);
|
|
1459
1312
|
}
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
process.stdout.write(`
|
|
1464
|
-
${message}
|
|
1313
|
+
async function configureNotifications(configPath, currentNotifyUrl) {
|
|
1314
|
+
console.log(`
|
|
1315
|
+
${c2.bold}Notifications configuration:${c2.reset}
|
|
1465
1316
|
`);
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
const scoperResult = await deps.runScoper({
|
|
1472
|
-
planDir: opts.planDir,
|
|
1473
|
-
slug: opts.slug,
|
|
1474
|
-
initialGoal: opts.initialGoal
|
|
1475
|
-
});
|
|
1476
|
-
banner(`\u2713 Scope captured at ${scoperResult.scopePath}`);
|
|
1477
|
-
const actualSlug = path7.basename(path7.dirname(scoperResult.scopePath));
|
|
1478
|
-
banner("\u2192 Phase 2/3: Planning (headless)...");
|
|
1479
|
-
const planResult = await deps.runPlan({
|
|
1480
|
-
scopePath: scoperResult.scopePath,
|
|
1481
|
-
planDir: opts.planDir,
|
|
1482
|
-
slug: actualSlug || opts.slug
|
|
1483
|
-
});
|
|
1484
|
-
banner(`\u2713 Plan written at ${planResult.planPath}`);
|
|
1485
|
-
banner("\u2192 Phase 3/3: Executing (headless loop)...");
|
|
1486
|
-
const loopResult = await deps.runLoop({
|
|
1487
|
-
planPath: planResult.planPath,
|
|
1488
|
-
cwd
|
|
1489
|
-
});
|
|
1490
|
-
return {
|
|
1491
|
-
scopePath: scoperResult.scopePath,
|
|
1492
|
-
planPath: planResult.planPath,
|
|
1493
|
-
loopResult
|
|
1494
|
-
};
|
|
1495
|
-
}
|
|
1496
|
-
function deriveSlug(goal) {
|
|
1497
|
-
const slug = goal.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
|
|
1498
|
-
return slug.length > 0 ? slug : `feature-${Date.now()}`;
|
|
1499
|
-
}
|
|
1500
|
-
async function browsePlansDir(planDir, _readdirSync) {
|
|
1501
|
-
const { select: select2 } = await import("@inquirer/prompts");
|
|
1502
|
-
const readdir2 = _readdirSync ?? ((p, o) => fs7.readdirSync(p, o));
|
|
1503
|
-
let currentDir = planDir;
|
|
1504
|
-
while (true) {
|
|
1505
|
-
const entries = readdir2(currentDir, { withFileTypes: true });
|
|
1506
|
-
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
1507
|
-
const files = entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name).sort();
|
|
1508
|
-
if (dirs.length === 0 && files.length === 0) {
|
|
1509
|
-
process.stderr.write(`
|
|
1510
|
-
No plans found in ${currentDir}
|
|
1511
|
-
|
|
1317
|
+
if (currentNotifyUrl) {
|
|
1318
|
+
console.log(` Current webhook URL: ${c2.cyan}${currentNotifyUrl}${c2.reset}
|
|
1319
|
+
`);
|
|
1320
|
+
} else {
|
|
1321
|
+
console.log(` ${c2.dim}No webhook URL configured.${c2.reset}
|
|
1512
1322
|
`);
|
|
1513
|
-
return null;
|
|
1514
|
-
}
|
|
1515
|
-
const choices = [];
|
|
1516
|
-
for (const d of dirs) {
|
|
1517
|
-
const dirPath = path7.join(currentDir, d);
|
|
1518
|
-
const hasMain = fs7.existsSync(path7.join(dirPath, "main.md"));
|
|
1519
|
-
const fileCount = readdir2(dirPath, { withFileTypes: true }).filter((e) => e.isFile()).length;
|
|
1520
|
-
choices.push({
|
|
1521
|
-
name: hasMain ? `${d}/ (multi-file plan \u2014 ${fileCount} files)` : `${d}/ (${fileCount} files)`,
|
|
1522
|
-
value: `dir:${dirPath}`
|
|
1523
|
-
});
|
|
1524
|
-
}
|
|
1525
|
-
for (const f of files) {
|
|
1526
|
-
choices.push({
|
|
1527
|
-
name: `${f}`,
|
|
1528
|
-
value: `file:${path7.join(currentDir, f)}`
|
|
1529
|
-
});
|
|
1530
|
-
}
|
|
1531
|
-
if (currentDir !== planDir) {
|
|
1532
|
-
choices.push({ name: "\u21A9 Back", value: "back" });
|
|
1533
|
-
}
|
|
1534
|
-
choices.push({ name: "\u2715 Cancel (scope a new feature instead)", value: "cancel" });
|
|
1535
|
-
const answer = await select2({
|
|
1536
|
-
message: "Select a plan:",
|
|
1537
|
-
choices
|
|
1538
|
-
});
|
|
1539
|
-
if (answer === "cancel") return null;
|
|
1540
|
-
if (answer === "back") {
|
|
1541
|
-
currentDir = path7.dirname(currentDir);
|
|
1542
|
-
continue;
|
|
1543
|
-
}
|
|
1544
|
-
if (answer.startsWith("file:")) {
|
|
1545
|
-
return answer.slice("file:".length);
|
|
1546
|
-
}
|
|
1547
|
-
if (answer.startsWith("dir:")) {
|
|
1548
|
-
const dirPath = answer.slice("dir:".length);
|
|
1549
|
-
const hasMain = fs7.existsSync(path7.join(dirPath, "main.md"));
|
|
1550
|
-
if (hasMain) {
|
|
1551
|
-
const dirAction = await select2({
|
|
1552
|
-
message: `${path7.basename(dirPath)}/ has a main.md. What do you want?`,
|
|
1553
|
-
choices: [
|
|
1554
|
-
{ name: "Select this as a multi-file plan", value: "select" },
|
|
1555
|
-
{ name: "Browse files inside", value: "browse" },
|
|
1556
|
-
{ name: "\u21A9 Back", value: "back" }
|
|
1557
|
-
]
|
|
1558
|
-
});
|
|
1559
|
-
if (dirAction === "select") return dirPath;
|
|
1560
|
-
if (dirAction === "browse") {
|
|
1561
|
-
currentDir = dirPath;
|
|
1562
|
-
continue;
|
|
1563
|
-
}
|
|
1564
|
-
continue;
|
|
1565
|
-
}
|
|
1566
|
-
currentDir = dirPath;
|
|
1567
|
-
continue;
|
|
1568
|
-
}
|
|
1569
1323
|
}
|
|
1324
|
+
const choices = [
|
|
1325
|
+
"Set Slack incoming webhook URL",
|
|
1326
|
+
"Set custom webhook URL",
|
|
1327
|
+
"Clear webhook URL",
|
|
1328
|
+
"\u2190 Back"
|
|
1329
|
+
];
|
|
1330
|
+
const choice = await promptChoice("Notifications:", choices, choices.length - 1);
|
|
1331
|
+
if (choice === choices.length - 1) return currentNotifyUrl;
|
|
1332
|
+
if (choice === 2) {
|
|
1333
|
+
writeNotifyUrl(configPath, void 0);
|
|
1334
|
+
ok2("Webhook URL cleared.");
|
|
1335
|
+
return void 0;
|
|
1336
|
+
}
|
|
1337
|
+
const { input } = await import("@inquirer/prompts");
|
|
1338
|
+
const prompt = choice === 0 ? " Slack incoming webhook URL (https://hooks.slack.com/...):" : " Webhook URL:";
|
|
1339
|
+
const url = await input({
|
|
1340
|
+
message: prompt,
|
|
1341
|
+
default: currentNotifyUrl ?? ""
|
|
1342
|
+
});
|
|
1343
|
+
if (url) {
|
|
1344
|
+
writeNotifyUrl(configPath, url);
|
|
1345
|
+
ok2(`Webhook URL set: ${url}`);
|
|
1346
|
+
return url;
|
|
1347
|
+
}
|
|
1348
|
+
return currentNotifyUrl;
|
|
1570
1349
|
}
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
message: "Do you have an existing plan?",
|
|
1581
|
-
default: false
|
|
1350
|
+
function writeNotifyUrl(configPath, url) {
|
|
1351
|
+
try {
|
|
1352
|
+
if (!fs5.existsSync(configPath)) return;
|
|
1353
|
+
const raw = fs5.readFileSync(configPath, "utf8");
|
|
1354
|
+
const config = JSON.parse(raw);
|
|
1355
|
+
if (!Array.isArray(config.plugin)) return;
|
|
1356
|
+
const pluginIdx = config.plugin.findIndex((entry) => {
|
|
1357
|
+
const name = typeof entry === "string" ? entry : Array.isArray(entry) ? entry[0] : null;
|
|
1358
|
+
return name === PLUGIN_NAME4 || String(name ?? "").startsWith(`${PLUGIN_NAME4}@`) || String(name ?? "").includes("harness-opencode");
|
|
1582
1359
|
});
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
const
|
|
1586
|
-
const
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
if (hasRepoLocal && hasShared) {
|
|
1590
|
-
const { select: select2 } = await import("@inquirer/prompts");
|
|
1591
|
-
const which2 = await select2({
|
|
1592
|
-
message: "Where are your plans?",
|
|
1593
|
-
choices: [
|
|
1594
|
-
{ name: `./plans/ (repo-local)`, value: repoLocalPlansDir },
|
|
1595
|
-
{ name: `${planDir} (harness-shared)`, value: planDir }
|
|
1596
|
-
]
|
|
1597
|
-
});
|
|
1598
|
-
browseRoot = which2;
|
|
1599
|
-
} else if (hasRepoLocal) {
|
|
1600
|
-
browseRoot = repoLocalPlansDir;
|
|
1360
|
+
if (pluginIdx < 0) return;
|
|
1361
|
+
const current = config.plugin[pluginIdx];
|
|
1362
|
+
const pluginName2 = Array.isArray(current) ? current[0] : current;
|
|
1363
|
+
const existingOpts = Array.isArray(current) && current.length >= 2 ? { ...current[1] } : {};
|
|
1364
|
+
if (url === void 0) {
|
|
1365
|
+
delete existingOpts.notifyUrl;
|
|
1601
1366
|
} else {
|
|
1602
|
-
|
|
1367
|
+
existingOpts.notifyUrl = url;
|
|
1603
1368
|
}
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1369
|
+
config.plugin[pluginIdx] = [pluginName2, existingOpts];
|
|
1370
|
+
const bakPath = `${configPath}.bak.${Date.now()}-${process.pid}`;
|
|
1371
|
+
fs5.copyFileSync(configPath, bakPath);
|
|
1372
|
+
fs5.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
1373
|
+
} catch (err) {
|
|
1374
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1375
|
+
process.stderr.write(`\x1B[33m\u26A0 Failed to write notifyUrl: ${msg}\x1B[0m
|
|
1376
|
+
`);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
var configureCmd = command({
|
|
1380
|
+
name: "configure",
|
|
1381
|
+
description: "Interactively edit opencode.json settings \u2014 models, MCPs, plugin add-ons.",
|
|
1382
|
+
args: {},
|
|
1383
|
+
handler: async () => {
|
|
1384
|
+
const configPath = getOpencodeConfigPath4();
|
|
1385
|
+
const config = readConfig(configPath);
|
|
1386
|
+
if (!config) {
|
|
1387
|
+
console.log(`No config found at ${configPath}. Run ${c2.green}glrs oc install${c2.reset} first.`);
|
|
1388
|
+
process.exit(1);
|
|
1609
1389
|
}
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
})
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
fs7.writeFileSync(file, unchecked);
|
|
1636
|
-
}
|
|
1637
|
-
process.stderr.write(`
|
|
1638
|
-
\u2713 Unchecked all items in ${uncheckFiles.length} file(s).
|
|
1639
|
-
|
|
1390
|
+
const opts = extractPluginOptions2(config);
|
|
1391
|
+
const models = opts?.models ?? {};
|
|
1392
|
+
let notifyUrl = opts?.notifyUrl;
|
|
1393
|
+
console.log(`
|
|
1394
|
+
${c2.bold}${c2.blue}glrs oc configure${c2.reset}
|
|
1395
|
+
`);
|
|
1396
|
+
while (true) {
|
|
1397
|
+
const deepModel = models.deep?.[0] ?? "(not set)";
|
|
1398
|
+
const midModel = models.mid?.[0] ?? "(not set)";
|
|
1399
|
+
const midExecModel = models["mid-execute"]?.[0] ?? "(not set)";
|
|
1400
|
+
const autopilotExecModel = models["autopilot-execute"]?.[0] ?? `(falls back to ${midExecModel})`;
|
|
1401
|
+
const fastModel = models.fast?.[0] ?? "(not set)";
|
|
1402
|
+
const mcpEnabled = Object.entries(config.mcp ?? {}).filter(([, v]) => v?.enabled).map(([k]) => k);
|
|
1403
|
+
const slackConfigured = notifyUrl?.includes("hooks.slack.com/") ?? false;
|
|
1404
|
+
const notifyLabel = notifyUrl ? slackConfigured ? "Slack" : "custom webhook" : "none";
|
|
1405
|
+
const sections = [
|
|
1406
|
+
`Models \u2014 deep: ${deepModel.split("/").pop()}, autopilot --fast: ${autopilotExecModel.split("/").pop()}`,
|
|
1407
|
+
`MCPs \u2014 ${mcpEnabled.length > 0 ? mcpEnabled.join(", ") : "none"}`,
|
|
1408
|
+
`Notifications \u2014 ${notifyLabel}`,
|
|
1409
|
+
"Done"
|
|
1410
|
+
];
|
|
1411
|
+
const choice = await promptChoice("What to configure?", sections, sections.length - 1);
|
|
1412
|
+
if (choice === sections.length - 1) {
|
|
1413
|
+
console.log(`
|
|
1414
|
+
${c2.bold}Done.${c2.reset} Restart opencode to pick up changes.
|
|
1640
1415
|
`);
|
|
1416
|
+
break;
|
|
1417
|
+
}
|
|
1418
|
+
if (choice === 0) {
|
|
1419
|
+
await configureModels(configPath, models);
|
|
1420
|
+
const updated = readConfig(configPath);
|
|
1421
|
+
if (updated) {
|
|
1422
|
+
const updatedOpts = extractPluginOptions2(updated);
|
|
1423
|
+
if (updatedOpts?.models) {
|
|
1424
|
+
Object.assign(models, updatedOpts.models);
|
|
1641
1425
|
}
|
|
1642
|
-
const banner = _deps?.onBanner ?? ((msg) => process.stdout.write(`
|
|
1643
|
-
${msg}
|
|
1644
|
-
`));
|
|
1645
|
-
banner(`\u2192 Running loop against plan: ${planPath}`);
|
|
1646
|
-
const { runLoopSession: runLoopSession2 } = await import("./loop-session-J35NILUZ.js");
|
|
1647
|
-
const _runLoop = _deps?.runLoop ?? runLoopSession2;
|
|
1648
|
-
const loopResult = await _runLoop({ planPath, cwd });
|
|
1649
|
-
return {
|
|
1650
|
-
scopePath: "",
|
|
1651
|
-
planPath,
|
|
1652
|
-
loopResult
|
|
1653
|
-
};
|
|
1654
1426
|
}
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1427
|
+
}
|
|
1428
|
+
if (choice === 1) {
|
|
1429
|
+
const { promptMulti: promptMulti2 } = await import("./plugin-check-GJRD2OK6.js");
|
|
1430
|
+
const MCP_TOGGLES2 = [
|
|
1431
|
+
{ name: "playwright", label: "Playwright \u2014 browser automation" },
|
|
1432
|
+
{ name: "linear", label: "Linear \u2014 issue tracker" }
|
|
1433
|
+
];
|
|
1434
|
+
const currentMcps = new Set(
|
|
1435
|
+
Object.entries(config.mcp ?? {}).filter(([, v]) => v?.enabled).map(([k]) => k)
|
|
1662
1436
|
);
|
|
1663
|
-
const
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
const
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
planPath,
|
|
1673
|
-
loopResult
|
|
1674
|
-
};
|
|
1437
|
+
const selected = await promptMulti2(
|
|
1438
|
+
"Enable MCPs:",
|
|
1439
|
+
MCP_TOGGLES2.map((t) => ({ label: t.label, defaultOn: currentMcps.has(t.name) }))
|
|
1440
|
+
);
|
|
1441
|
+
const newEnabled = new Set([...selected].map((i) => MCP_TOGGLES2[i].name));
|
|
1442
|
+
writeMcpToggles(configPath, newEnabled, { dryRun: false });
|
|
1443
|
+
}
|
|
1444
|
+
if (choice === 2) {
|
|
1445
|
+
notifyUrl = await configureNotifications(configPath, notifyUrl);
|
|
1675
1446
|
}
|
|
1676
1447
|
}
|
|
1677
1448
|
}
|
|
1678
|
-
let goal;
|
|
1679
|
-
let ticketRef;
|
|
1680
|
-
if (_deps?.promptGoal) {
|
|
1681
|
-
goal = await _deps.promptGoal();
|
|
1682
|
-
} else {
|
|
1683
|
-
const { input } = await import("@inquirer/prompts");
|
|
1684
|
-
goal = await input({
|
|
1685
|
-
message: "What do you want to build? (one sentence, free-form)",
|
|
1686
|
-
validate: (v) => v.trim().length > 0 ? true : "Please describe what you want to build."
|
|
1687
|
-
});
|
|
1688
|
-
}
|
|
1689
|
-
if (_deps?.promptTicketRef) {
|
|
1690
|
-
ticketRef = await _deps.promptTicketRef();
|
|
1691
|
-
} else {
|
|
1692
|
-
const { input } = await import("@inquirer/prompts");
|
|
1693
|
-
ticketRef = await input({
|
|
1694
|
-
message: "Optional ticket or issue ref (Linear ID, GitHub issue URL, etc.)",
|
|
1695
|
-
default: ""
|
|
1696
|
-
});
|
|
1697
|
-
}
|
|
1698
|
-
const slug = deriveSlug(goal);
|
|
1699
|
-
const seedDir = path7.join(planDir, slug);
|
|
1700
|
-
const seedPath = path7.join(seedDir, "scope-seed.md");
|
|
1701
|
-
const _mkdirSync = _deps?.mkdirSync ?? ((p, o) => fs7.mkdirSync(p, o));
|
|
1702
|
-
const _writeFileSync = _deps?.writeFileSync ?? fs7.writeFileSync;
|
|
1703
|
-
_mkdirSync(seedDir, { recursive: true });
|
|
1704
|
-
const seedContent = [
|
|
1705
|
-
`# Scope Seed: ${slug}`,
|
|
1706
|
-
"",
|
|
1707
|
-
`## Goal`,
|
|
1708
|
-
"",
|
|
1709
|
-
goal,
|
|
1710
|
-
"",
|
|
1711
|
-
...ticketRef.trim() ? [`## Ticket / Issue Ref`, "", ticketRef.trim(), ""] : []
|
|
1712
|
-
].join("\n");
|
|
1713
|
-
_writeFileSync(seedPath, seedContent);
|
|
1714
|
-
const { runScoperSession } = await import("./scoper-S77SOK7X.js");
|
|
1715
|
-
const { runPlanSession } = await import("./plan-session-7VS32P52.js");
|
|
1716
|
-
const { runLoopSession } = await import("./loop-session-J35NILUZ.js");
|
|
1717
|
-
return orchestrateAutopilot(
|
|
1718
|
-
{ slug, planDir, cwd, initialGoal: goal },
|
|
1719
|
-
{
|
|
1720
|
-
runScoper: _deps?.runScoper ?? runScoperSession,
|
|
1721
|
-
runPlan: _deps?.runPlan ?? runPlanSession,
|
|
1722
|
-
runLoop: _deps?.runLoop ?? runLoopSession,
|
|
1723
|
-
onBanner: _deps?.onBanner
|
|
1724
|
-
}
|
|
1725
|
-
);
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
// src/autopilot/autopilot-cmd.ts
|
|
1729
|
-
var autopilotInteractiveCmd = command2({
|
|
1730
|
-
name: "autopilot",
|
|
1731
|
-
description: "Interactive three-phase autopilot: scope with @scoper, plan with @plan, then execute with the Ralph loop. Produces a structured plan before running.",
|
|
1732
|
-
args: {
|
|
1733
|
-
slug: option2({
|
|
1734
|
-
long: "slug",
|
|
1735
|
-
type: optional2(stringType2),
|
|
1736
|
-
description: "Plan slug (kebab-case, \u22645 words). If omitted, you will be prompted during the scoping session."
|
|
1737
|
-
})
|
|
1738
|
-
},
|
|
1739
|
-
handler: async ({ slug: _slug }) => {
|
|
1740
|
-
const result = await runInteractiveAutopilot(process.cwd());
|
|
1741
|
-
process.stdout.write(
|
|
1742
|
-
`
|
|
1743
|
-
\x1B[1m\u2713 Autopilot complete\x1B[0m
|
|
1744
|
-
Scope: ${result.scopePath}
|
|
1745
|
-
Plan: ${result.planPath}
|
|
1746
|
-
Loop: ${result.loopResult.exitReason} after ${result.loopResult.iterations} iteration(s)
|
|
1747
|
-
`
|
|
1748
|
-
);
|
|
1749
|
-
}
|
|
1750
1449
|
});
|
|
1751
1450
|
|
|
1752
1451
|
// src/cli/cli-update.ts
|
|
1753
|
-
import * as
|
|
1754
|
-
import * as
|
|
1755
|
-
import * as
|
|
1452
|
+
import * as fs6 from "fs";
|
|
1453
|
+
import * as path6 from "path";
|
|
1454
|
+
import * as os5 from "os";
|
|
1756
1455
|
import { spawn } from "child_process";
|
|
1757
1456
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1758
1457
|
var PACKAGE_NAME = "@glrs-dev/harness-plugin-opencode";
|
|
1759
1458
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
1760
|
-
var
|
|
1459
|
+
var c3 = {
|
|
1761
1460
|
reset: "\x1B[0m",
|
|
1762
1461
|
green: "\x1B[32m",
|
|
1763
1462
|
yellow: "\x1B[33m",
|
|
@@ -1779,12 +1478,12 @@ function isMajorBump(current, latest) {
|
|
|
1779
1478
|
return latest.major > current.major;
|
|
1780
1479
|
}
|
|
1781
1480
|
function getStateFilePath() {
|
|
1782
|
-
const cacheHome = process.env["XDG_CACHE_HOME"] ??
|
|
1783
|
-
return
|
|
1481
|
+
const cacheHome = process.env["XDG_CACHE_HOME"] ?? path6.join(os5.homedir(), ".cache");
|
|
1482
|
+
return path6.join(cacheHome, "harness-opencode", "cli-update.json");
|
|
1784
1483
|
}
|
|
1785
1484
|
function readState() {
|
|
1786
1485
|
try {
|
|
1787
|
-
const raw =
|
|
1486
|
+
const raw = fs6.readFileSync(getStateFilePath(), "utf8");
|
|
1788
1487
|
return JSON.parse(raw);
|
|
1789
1488
|
} catch {
|
|
1790
1489
|
return null;
|
|
@@ -1793,21 +1492,21 @@ function readState() {
|
|
|
1793
1492
|
function writeState(state) {
|
|
1794
1493
|
try {
|
|
1795
1494
|
const statePath = getStateFilePath();
|
|
1796
|
-
|
|
1797
|
-
|
|
1495
|
+
fs6.mkdirSync(path6.dirname(statePath), { recursive: true });
|
|
1496
|
+
fs6.writeFileSync(statePath, JSON.stringify(state));
|
|
1798
1497
|
} catch {
|
|
1799
1498
|
}
|
|
1800
1499
|
}
|
|
1801
1500
|
function readInstalledVersion() {
|
|
1802
|
-
const here =
|
|
1501
|
+
const here = path6.dirname(fileURLToPath2(import.meta.url));
|
|
1803
1502
|
const candidates = [
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1503
|
+
path6.join(here, "..", "package.json"),
|
|
1504
|
+
path6.join(here, "..", "..", "package.json"),
|
|
1505
|
+
path6.join(here, "package.json")
|
|
1807
1506
|
];
|
|
1808
1507
|
for (const candidate of candidates) {
|
|
1809
1508
|
try {
|
|
1810
|
-
const raw =
|
|
1509
|
+
const raw = fs6.readFileSync(candidate, "utf8");
|
|
1811
1510
|
const parsed = JSON.parse(raw);
|
|
1812
1511
|
if (parsed.name === PACKAGE_NAME && typeof parsed.version === "string") {
|
|
1813
1512
|
return parsed.version;
|
|
@@ -1878,7 +1577,7 @@ function startUpdateCheck() {
|
|
|
1878
1577
|
action = () => {
|
|
1879
1578
|
process.stderr.write(
|
|
1880
1579
|
`
|
|
1881
|
-
${
|
|
1580
|
+
${c3.blue}\u2022${c3.reset} Updating ${PACKAGE_NAME} ${c3.dim}${currentVersionStr}${c3.reset} \u2192 ${c3.green}${latestStr}${c3.reset} in the background...
|
|
1882
1581
|
`
|
|
1883
1582
|
);
|
|
1884
1583
|
spawnBackgroundUpdate();
|
|
@@ -1893,8 +1592,8 @@ ${c2.blue}\u2022${c2.reset} Updating ${PACKAGE_NAME} ${c2.dim}${currentVersionSt
|
|
|
1893
1592
|
function printMajorNotice(current, latest) {
|
|
1894
1593
|
process.stderr.write(
|
|
1895
1594
|
`
|
|
1896
|
-
${
|
|
1897
|
-
${
|
|
1595
|
+
${c3.yellow}${c3.bold}Major update available:${c3.reset} ${current} \u2192 ${c3.green}${latest}${c3.reset}
|
|
1596
|
+
${c3.dim}Review the changelog before upgrading:${c3.reset}
|
|
1898
1597
|
bun update -g ${PACKAGE_NAME}
|
|
1899
1598
|
`
|
|
1900
1599
|
);
|
|
@@ -1933,15 +1632,15 @@ Upgrade Node or run via a compatible Bun runtime. See the "engines" field in pac
|
|
|
1933
1632
|
}
|
|
1934
1633
|
}
|
|
1935
1634
|
var VERSION = "0.1.0";
|
|
1936
|
-
var installCmd =
|
|
1635
|
+
var installCmd = command2({
|
|
1937
1636
|
name: "install",
|
|
1938
1637
|
description: 'Add "@glrs-dev/harness-plugin-opencode" to your opencode.json plugin array.',
|
|
1939
1638
|
args: {
|
|
1940
|
-
dryRun:
|
|
1639
|
+
dryRun: flag({
|
|
1941
1640
|
long: "dry-run",
|
|
1942
1641
|
description: "Preview changes without writing."
|
|
1943
1642
|
}),
|
|
1944
|
-
pin:
|
|
1643
|
+
pin: flag({
|
|
1945
1644
|
long: "pin",
|
|
1946
1645
|
description: "Pin to the current exact version (e.g. @0.1.0)."
|
|
1947
1646
|
})
|
|
@@ -1950,11 +1649,11 @@ var installCmd = command3({
|
|
|
1950
1649
|
await install({ dryRun, pin });
|
|
1951
1650
|
}
|
|
1952
1651
|
});
|
|
1953
|
-
var uninstallCmd =
|
|
1652
|
+
var uninstallCmd = command2({
|
|
1954
1653
|
name: "uninstall",
|
|
1955
1654
|
description: 'Remove "@glrs-dev/harness-plugin-opencode" from your opencode.json plugin array.',
|
|
1956
1655
|
args: {
|
|
1957
|
-
dryRun:
|
|
1656
|
+
dryRun: flag({
|
|
1958
1657
|
long: "dry-run",
|
|
1959
1658
|
description: "Preview changes without writing."
|
|
1960
1659
|
})
|
|
@@ -1963,7 +1662,7 @@ var uninstallCmd = command3({
|
|
|
1963
1662
|
uninstall({ dryRun });
|
|
1964
1663
|
}
|
|
1965
1664
|
});
|
|
1966
|
-
var doctorCmd =
|
|
1665
|
+
var doctorCmd = command2({
|
|
1967
1666
|
name: "doctor",
|
|
1968
1667
|
description: "Check installation health (OpenCode CLI, plugin registration, MCP backends).",
|
|
1969
1668
|
args: {},
|
|
@@ -1971,15 +1670,15 @@ var doctorCmd = command3({
|
|
|
1971
1670
|
doctor();
|
|
1972
1671
|
}
|
|
1973
1672
|
});
|
|
1974
|
-
var installPluginCmd =
|
|
1673
|
+
var installPluginCmd = command2({
|
|
1975
1674
|
name: "install-plugin",
|
|
1976
1675
|
description: 'Add "@glrs-dev/harness-plugin-opencode" to your opencode.json plugin array.',
|
|
1977
1676
|
args: {
|
|
1978
|
-
dryRun:
|
|
1677
|
+
dryRun: flag({
|
|
1979
1678
|
long: "dry-run",
|
|
1980
1679
|
description: "Preview changes without writing."
|
|
1981
1680
|
}),
|
|
1982
|
-
pin:
|
|
1681
|
+
pin: flag({
|
|
1983
1682
|
long: "pin",
|
|
1984
1683
|
description: "Pin to the current exact version (e.g. @0.1.0)."
|
|
1985
1684
|
})
|
|
@@ -1996,12 +1695,9 @@ var cli = subcommands({
|
|
|
1996
1695
|
"install-plugin": installPluginCmd,
|
|
1997
1696
|
install: installCmd,
|
|
1998
1697
|
uninstall: uninstallCmd,
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
// `
|
|
2002
|
-
// PR 3 diverged them: they are now separate subcommands.
|
|
2003
|
-
loop: loopCmd,
|
|
2004
|
-
autopilot: autopilotInteractiveCmd
|
|
1698
|
+
configure: configureCmd,
|
|
1699
|
+
doctor: doctorCmd
|
|
1700
|
+
// Note: `loop` and `autopilot` commands have moved to @glrs-dev/cli.
|
|
2005
1701
|
}
|
|
2006
1702
|
});
|
|
2007
1703
|
var printUpdate = startUpdateCheck();
|