@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.
Files changed (62) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/{chunk-EM4MJBOD.js → chunk-2AZKRWC6.js} +4 -4
  3. package/dist/{chunk-UXBOTMDY.js → chunk-2P3ETOT2.js} +2 -2
  4. package/dist/chunk-2VMFXAJH.js +795 -0
  5. package/dist/chunk-5ZVUFNCP.js +140 -0
  6. package/dist/{chunk-W37UX3U2.js → chunk-6Y27RQQL.js} +2 -2
  7. package/dist/{chunk-RZWOWTKF.js → chunk-EKNRKZWR.js} +4 -4
  8. package/dist/{chunk-YGNDPKIW.js → chunk-HQUCVJ4G.js} +3 -1
  9. package/dist/{chunk-OABVEBWW.js → chunk-MBEVC327.js} +1 -1
  10. package/dist/{chunk-MIWZLETC.js → chunk-MCM47HH4.js} +1 -1
  11. package/dist/{chunk-F3AFRUT2.js → chunk-PTIO556V.js} +2 -2
  12. package/dist/{chunk-E2UNZIZT.js → chunk-R2WXQ54P.js} +1 -1
  13. package/dist/{chunk-I2KUXY3I.js → chunk-SMDIOB5B.js} +2 -2
  14. package/dist/{chunk-SPULDN7P.js → chunk-YY7EWHMA.js} +5 -3
  15. package/dist/cli.js +31 -20
  16. package/dist/commands/autopilot-interactive.d.ts +89 -0
  17. package/dist/commands/autopilot-interactive.js +248 -0
  18. package/dist/commands/autopilot-raw.d.ts +1 -0
  19. package/dist/commands/autopilot-raw.js +368 -0
  20. package/dist/commands/autopilot-tui.d.ts +7 -0
  21. package/dist/commands/autopilot-tui.js +7 -0
  22. package/dist/commands/autopilot.d.ts +39 -0
  23. package/dist/commands/autopilot.js +395 -0
  24. package/dist/commands/cleanup.js +3 -3
  25. package/dist/commands/create.js +4 -4
  26. package/dist/commands/dashboard.d.ts +3 -0
  27. package/dist/commands/dashboard.js +1549 -0
  28. package/dist/commands/debrief.d.ts +57 -0
  29. package/dist/commands/debrief.js +9 -0
  30. package/dist/commands/delete.js +3 -3
  31. package/dist/commands/go.js +2 -2
  32. package/dist/commands/list.js +3 -3
  33. package/dist/commands/loop.d.ts +42 -0
  34. package/dist/commands/loop.js +133 -0
  35. package/dist/commands/plan-picker.d.ts +15 -0
  36. package/dist/commands/plan-picker.js +76 -0
  37. package/dist/commands/scoper.d.ts +54 -0
  38. package/dist/{vendor/harness-opencode/dist/scoper-S77SOK7X.js → commands/scoper.js} +30 -15
  39. package/dist/commands/switch.js +3 -3
  40. package/dist/index.d.ts +2 -2
  41. package/dist/index.js +1 -1
  42. package/dist/lib/auto-update.js +1 -1
  43. package/dist/lib/config.d.ts +3 -2
  44. package/dist/lib/config.js +1 -1
  45. package/dist/lib/registry.d.ts +2 -0
  46. package/dist/lib/registry.js +1 -1
  47. package/dist/lib/worktree.js +3 -3
  48. package/dist/vendor/harness-opencode/dist/agents/prompts/plan.md +7 -0
  49. package/dist/vendor/harness-opencode/dist/chunk-GILWWWMB.js +66 -0
  50. package/dist/vendor/harness-opencode/dist/cli.js +335 -639
  51. package/dist/vendor/harness-opencode/dist/index.js +35 -8
  52. package/dist/vendor/harness-opencode/dist/plugin-check-GJRD2OK6.js +14 -0
  53. package/dist/vendor/harness-opencode/package.json +1 -1
  54. package/package.json +8 -2
  55. package/dist/vendor/harness-opencode/dist/autopilot/prompt-template.md +0 -104
  56. package/dist/vendor/harness-opencode/dist/chunk-GCWHRUOK.js +0 -259
  57. package/dist/vendor/harness-opencode/dist/chunk-MJSMBY2Y.js +0 -87
  58. package/dist/vendor/harness-opencode/dist/chunk-NIFAVPNN.js +0 -544
  59. package/dist/vendor/harness-opencode/dist/loop-session-J35NILUZ.js +0 -30
  60. package/dist/vendor/harness-opencode/dist/opencode-server-KPCDFYAX.js +0 -22
  61. package/dist/vendor/harness-opencode/dist/plan-parser-TMHEKT22.js +0 -6
  62. 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
- MAX_ITERATIONS,
11
- TIMEOUT_MS,
12
- runRalphLoop
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 command3,
25
- flag as flag2,
26
- positional as positional2,
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 fs3 from "fs";
33
- import * as path3 from "path";
34
- import * as os2 from "os";
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 = path3.dirname(fileURLToPath(import.meta.url));
353
+ const here = path2.dirname(fileURLToPath(import.meta.url));
397
354
  const candidates = [
398
- path3.join(here, "..", "package.json"),
399
- path3.join(here, "..", "..", "package.json")
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 = fs3.readFileSync(candidate, "utf8");
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"] ?? path3.join(os2.homedir(), ".config");
417
- return path3.join(configHome, "opencode", "opencode.json");
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 (!fs3.existsSync(configPath)) return null;
391
+ if (!fs2.existsSync(configPath)) return null;
435
392
  try {
436
- return JSON.parse(fs3.readFileSync(configPath, "utf8"));
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 (!fs3.existsSync(configPath)) return;
478
- const raw = fs3.readFileSync(configPath, "utf8");
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
- fs3.copyFileSync(configPath, bakPath);
495
- fs3.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
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 (!fs3.existsSync(configPath)) {
476
+ if (!fs2.existsSync(configPath)) {
520
477
  return { changed: false };
521
478
  }
522
- const raw = fs3.readFileSync(configPath, "utf8");
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
- fs3.copyFileSync(configPath, bakPath);
503
+ fs2.copyFileSync(configPath, bakPath);
547
504
  config.plugin[pluginIdx] = [existingName, newOpts];
548
- fs3.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
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 (!fs3.existsSync(configPath)) {
515
+ if (!fs2.existsSync(configPath)) {
559
516
  return { changed: false };
560
517
  }
561
- const raw = fs3.readFileSync(configPath, "utf8");
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
- fs3.copyFileSync(configPath, bakPath);
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
- fs3.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
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 (!fs3.existsSync(configPath)) {
570
+ if (!fs2.existsSync(configPath)) {
614
571
  return { changed: false };
615
572
  }
616
- const raw = fs3.readFileSync(configPath, "utf8");
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
- fs3.copyFileSync(configPath, bakPath);
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
- fs3.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
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 (!fs3.existsSync(configPath)) {
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 fs4 from "fs";
1013
- import * as path4 from "path";
1014
- import * as os3 from "os";
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"] ?? path4.join(os3.homedir(), ".config");
1018
- return path4.join(configHome, "opencode", "opencode.json");
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 c3 = {
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 ok2 = (msg) => console.log(`${c3.green}\u2713${c3.reset} ${msg}`);
1030
- const info2 = (msg) => console.log(`${c3.blue}\u2022${c3.reset} ${msg}`);
1031
- const warn2 = (msg) => console.log(`${c3.yellow}!${c3.reset} ${msg}`);
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
- ${c3.blue}Uninstalling ${PLUGIN_NAME2}${c3.reset}
990
+ ${c4.blue}Uninstalling ${PLUGIN_NAME2}${c4.reset}
1034
991
  `);
1035
- if (!fs4.existsSync(configPath)) {
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 = fs4.readFileSync(configPath, "utf8");
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
- info2(`[dry-run] Would remove "${PLUGIN_NAME2}" from plugin array in ${configPath}`);
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
- fs4.copyFileSync(configPath, bakPath);
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
- fs4.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
1077
- fs4.renameSync(tmpPath, configPath);
1033
+ fs3.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + "\n");
1034
+ fs3.renameSync(tmpPath, configPath);
1078
1035
  } catch (e) {
1079
1036
  try {
1080
- fs4.unlinkSync(tmpPath);
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
- ok2(`Removed "${PLUGIN_NAME2}" from ${configPath}`);
1087
- info2(`Backup: ${bakPath}`);
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 fs5 from "fs";
1095
- import * as path5 from "path";
1096
- import * as os4 from "os";
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"] ?? path5.join(os4.homedir(), ".config");
1101
- return path5.join(configHome, "opencode", "opencode.json");
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(command4) {
1060
+ function cmd(command3) {
1104
1061
  try {
1105
- return execSync(command4, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
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 c3 = {
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 ok2 = (msg) => console.log(`${c3.green}\u2713${c3.reset} ${msg}`);
1122
- const warn2 = (msg) => console.log(`${c3.yellow}!${c3.reset} ${msg}`);
1123
- const fail = (msg) => console.log(`${c3.red}\u2717${c3.reset} ${msg}`);
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
- ${c3.bold}Doctor \u2014 ${PLUGIN_NAME3}${c3.reset}
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
- ok2(`opencode ${ocVersion}`);
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 (fs5.existsSync(configPath)) {
1091
+ if (fs4.existsSync(configPath)) {
1135
1092
  try {
1136
- const config = JSON.parse(fs5.readFileSync(configPath, "utf8"));
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
- ok2(`"${PLUGIN_NAME3}" present in opencode.json plugin array`);
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
- ok2("model overrides look valid");
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(` ${c3.yellow}reason:${c3.reset} ${entry.reason}`);
1153
+ console.log(` ${c4.yellow}reason:${c4.reset} ${entry.reason}`);
1197
1154
  }
1198
1155
  if (entry.suggestion) {
1199
1156
  console.log(
1200
- ` ${c3.yellow}fix:${c3.reset} remove this key, or replace with \`${entry.suggestion}\``
1157
+ ` ${c4.yellow}fix:${c4.reset} remove this key, or replace with \`${entry.suggestion}\``
1201
1158
  );
1202
1159
  } else {
1203
1160
  console.log(
1204
- ` ${c3.yellow}fix:${c3.reset} remove this key, or run \`bunx ${PLUGIN_NAME3} install\` to pick a current preset`
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
- ok2("uvx (serena + git MCPs)");
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
- ok2(`node ${cmd("node --version") ?? ""} + npx (memory MCP)`);
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
- ok2(`bun ${cmd("bun --version") ?? ""}`);
1184
+ ok3(`bun ${cmd("bun --version") ?? ""}`);
1228
1185
  } else if (which("npm")) {
1229
- ok2(`npm ${cmd("npm --version") ?? ""} (install bun for faster installs)`);
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/autopilot/cli.ts
1237
- import { command, option, positional, string as stringType, optional, number as numberType, flag } from "cmd-ts";
1238
-
1239
- // src/autopilot/debrief.ts
1240
- function shouldRunDebrief(opts) {
1241
- if (opts.noDebrief) return false;
1242
- const envVal = opts.env["GLRS_AUTOPILOT_DEBRIEF"];
1243
- if (envVal !== void 0 && envVal.toLowerCase() === "off") return false;
1244
- return true;
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
- async function defaultExecGitDiffStat(cwd) {
1247
- const { execFile: execFileCb } = await import("child_process");
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
- const { stdout } = await execFile2("git", ["diff", "--stat", "HEAD~1", "HEAD"], { cwd });
1252
- return stdout.trim();
1217
+ return JSON.parse(fs5.readFileSync(configPath, "utf8"));
1253
1218
  } catch {
1254
- try {
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 buildContextMessage(loopResult, prompt, gitDiffStat) {
1263
- const cost = loopResult.cumulativeCostUsd !== void 0 ? `$${loopResult.cumulativeCostUsd.toFixed(4)}` : "not available";
1264
- const sessionId = loopResult.sessionId ?? "not available";
1265
- return [
1266
- "## Autopilot session context",
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
- // src/autopilot/cli.ts
1319
- var loopCmd = command({
1320
- name: "loop",
1321
- description: "Run the Ralph loop: send a prompt to PRIME repeatedly until it emits <autopilot-done> or a budget is exhausted.",
1322
- args: {
1323
- prompt: positional({
1324
- type: stringType,
1325
- displayName: "prompt",
1326
- description: "The prompt to send to PRIME each iteration (e.g. a Linear issue ref or free-form task)."
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
- process.stdout.write(`Max iterations: ${maxIterations ?? MAX_ITERATIONS}
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
- process.stdout.write(`Timeout: ${((timeout ?? TIMEOUT_MS) / 36e5).toFixed(1)}h
1351
-
1352
- `);
1353
- const result = await runRalphLoop({
1354
- prompt,
1355
- cwd,
1356
- maxIterations: maxIterations ?? void 0,
1357
- timeoutMs: timeout ?? void 0
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
- const icon = result.exitReason === "sentinel" ? "\x1B[32m\u2713\x1B[0m" : result.exitReason === "kill-switch" ? "\x1B[33m\u2298\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
1360
- process.stdout.write(`
1361
- ${icon} ${result.message}
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
- if (result.exitReason !== "sentinel" && result.exitReason !== "kill-switch") {
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 gitCommonDir = stdout.trim();
1443
- if (!gitCommonDir) {
1444
- throw new Error(
1445
- `getRepoFolder: \`git rev-parse --git-common-dir\` returned empty for ${worktreeDir}`
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 absCommonDir = path6.isAbsolute(gitCommonDir) ? gitCommonDir : path6.resolve(worktreeDir, gitCommonDir);
1449
- const repoRoot = path6.dirname(absCommonDir);
1450
- return path6.basename(repoRoot);
1451
- }
1452
- async function getPlanDir(worktreeDir) {
1453
- const override = process.env.GLORIOUS_PLAN_DIR;
1454
- const base = override ? expandTilde(override) : path6.join(os5.homedir(), ".glorious", "opencode");
1455
- const repoFolder = await getRepoFolder(worktreeDir);
1456
- const planDir = path6.join(base, repoFolder, "plans");
1457
- await fs6.mkdir(planDir, { recursive: true });
1458
- return planDir;
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
- // src/autopilot/interactive.ts
1462
- function defaultBanner(message) {
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
- async function orchestrateAutopilot(opts, deps) {
1468
- const banner = deps.onBanner ?? defaultBanner;
1469
- const cwd = opts.cwd ?? process.cwd();
1470
- banner("\u2192 Phase 1/3: Scoping (interactive)...");
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
- async function runInteractiveAutopilot(cwd, _deps) {
1572
- const _getPlanDir = _deps?.getPlanDir ?? getPlanDir;
1573
- const planDir = await _getPlanDir(cwd);
1574
- let hasExistingPlan;
1575
- if (_deps?.promptExistingPlan) {
1576
- hasExistingPlan = await _deps.promptExistingPlan();
1577
- } else {
1578
- const { confirm: confirm2 } = await import("@inquirer/prompts");
1579
- hasExistingPlan = await confirm2({
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
- if (hasExistingPlan) {
1585
- const repoLocalPlansDir = path7.join(cwd, "plans");
1586
- const hasRepoLocal = fs7.existsSync(repoLocalPlansDir) && fs7.statSync(repoLocalPlansDir).isDirectory();
1587
- const hasShared = fs7.existsSync(planDir) && fs7.statSync(planDir).isDirectory();
1588
- let browseRoot;
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
- browseRoot = planDir;
1367
+ existingOpts.notifyUrl = url;
1603
1368
  }
1604
- let selectedPlan;
1605
- if (_deps?.browsePlans) {
1606
- selectedPlan = await _deps.browsePlans(browseRoot);
1607
- } else {
1608
- selectedPlan = await browsePlansDir(browseRoot, _deps?.readdirSync);
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
- if (!selectedPlan) {
1611
- process.stderr.write("\n No plan selected. Starting new feature scoping.\n\n");
1612
- } else {
1613
- const isDir = fs7.statSync(selectedPlan).isDirectory();
1614
- const planPath = isDir ? selectedPlan : selectedPlan;
1615
- const { parsePlanState } = await import("./plan-parser-TMHEKT22.js");
1616
- const planState = parsePlanState(planPath);
1617
- if (planState.totalItems > 0 && planState.checkedItems === planState.totalItems) {
1618
- const { select: selectAction } = await import("@inquirer/prompts");
1619
- const action = await selectAction({
1620
- message: `All ${planState.totalItems} items in this plan are already checked. What do you want to do?`,
1621
- choices: [
1622
- { name: "Uncheck all items and run from scratch", value: "uncheck" },
1623
- { name: "Run anyway (agent will verify/audit the checked items)", value: "run" },
1624
- { name: "Cancel and pick a different plan", value: "cancel" }
1625
- ]
1626
- });
1627
- if (action === "cancel") {
1628
- process.stderr.write("\n Cancelled. Starting new feature scoping.\n\n");
1629
- } else {
1630
- if (action === "uncheck") {
1631
- const uncheckFiles = isDir ? fs7.readdirSync(planPath).filter((f) => f.endsWith(".md")).map((f) => path7.join(planPath, f)) : [planPath];
1632
- for (const file of uncheckFiles) {
1633
- const content = fs7.readFileSync(file, "utf-8");
1634
- const unchecked = content.replace(/- \[x\]/g, "- [ ]");
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
- } else {
1656
- const unchecked = planState.totalItems - planState.checkedItems;
1657
- process.stderr.write(
1658
- `
1659
- Plan: ${planState.totalItems} items, ${unchecked} remaining.
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 banner = _deps?.onBanner ?? ((msg) => process.stdout.write(`
1664
- ${msg}
1665
- `));
1666
- banner(`\u2192 Running loop against plan: ${planPath}`);
1667
- const { runLoopSession: runLoopSession2 } = await import("./loop-session-J35NILUZ.js");
1668
- const _runLoop = _deps?.runLoop ?? runLoopSession2;
1669
- const loopResult = await _runLoop({ planPath, cwd });
1670
- return {
1671
- scopePath: "",
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 fs8 from "fs";
1754
- import * as path8 from "path";
1755
- import * as os6 from "os";
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 c2 = {
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"] ?? path8.join(os6.homedir(), ".cache");
1783
- return path8.join(cacheHome, "harness-opencode", "cli-update.json");
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 = fs8.readFileSync(getStateFilePath(), "utf8");
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
- fs8.mkdirSync(path8.dirname(statePath), { recursive: true });
1797
- fs8.writeFileSync(statePath, JSON.stringify(state));
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 = path8.dirname(fileURLToPath2(import.meta.url));
1501
+ const here = path6.dirname(fileURLToPath2(import.meta.url));
1803
1502
  const candidates = [
1804
- path8.join(here, "..", "package.json"),
1805
- path8.join(here, "..", "..", "package.json"),
1806
- path8.join(here, "package.json")
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 = fs8.readFileSync(candidate, "utf8");
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
- ${c2.blue}\u2022${c2.reset} Updating ${PACKAGE_NAME} ${c2.dim}${currentVersionStr}${c2.reset} \u2192 ${c2.green}${latestStr}${c2.reset} in the background...
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
- ${c2.yellow}${c2.bold}Major update available:${c2.reset} ${current} \u2192 ${c2.green}${latest}${c2.reset}
1897
- ${c2.dim}Review the changelog before upgrading:${c2.reset}
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 = command3({
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: flag2({
1639
+ dryRun: flag({
1941
1640
  long: "dry-run",
1942
1641
  description: "Preview changes without writing."
1943
1642
  }),
1944
- pin: flag2({
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 = command3({
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: flag2({
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 = command3({
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 = command3({
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: flag2({
1677
+ dryRun: flag({
1979
1678
  long: "dry-run",
1980
1679
  description: "Preview changes without writing."
1981
1680
  }),
1982
- pin: flag2({
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
- doctor: doctorCmd,
2000
- // `loop` is the raw-prompt Ralph loop runner.
2001
- // `autopilot` is the interactive three-phase orchestrator (scope → plan → loop).
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();