@wrongstack/cli 0.305.0 → 0.306.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/dist/index.js CHANGED
@@ -11,17 +11,38 @@ import {
11
11
  theme
12
12
  } from "./chunk-QD544J2B.js";
13
13
  import {
14
+ fmtTok,
14
15
  patchConfig
15
16
  } from "./chunk-TYO2OVAD.js";
16
17
  import {
17
18
  parseWebuiSessionChildOptions,
18
19
  writeWebuiSessionChildError
19
20
  } from "./chunk-3JPTNADJ.js";
21
+ import {
22
+ helpCmd,
23
+ versionCmd
24
+ } from "./chunk-ARWGQUB3.js";
25
+ import {
26
+ CLI_VERSION
27
+ } from "./chunk-XJXDOF63.js";
28
+ import {
29
+ discoverAndMergeProviders
30
+ } from "./chunk-52C7PD2H.js";
31
+ import {
32
+ runUpdateCommand
33
+ } from "./chunk-53STH6CX.js";
34
+ import "./chunk-U3SS4NRH.js";
35
+ import "./chunk-NUIHRGPY.js";
36
+ import {
37
+ DEFAULT_PORT
38
+ } from "./chunk-RHLQT3EV.js";
39
+ import "./chunk-KE7E7DPX.js";
40
+ import "./chunk-Q5GTM25S.js";
20
41
  import {
21
42
  parseArgs,
22
43
  runPicker,
23
44
  saveToGlobalConfig
24
- } from "./chunk-C5NXM2SI.js";
45
+ } from "./chunk-5JW3XCRA.js";
25
46
  import {
26
47
  isKeylessLocalProvider,
27
48
  visibleModelIds
@@ -36,26 +57,6 @@ import "./chunk-B4JLSXZB.js";
36
57
  import {
37
58
  activeProfileConfigPath
38
59
  } from "./chunk-YMXXOOFN.js";
39
- import {
40
- runUpdateCommand
41
- } from "./chunk-53STH6CX.js";
42
- import "./chunk-U3SS4NRH.js";
43
- import "./chunk-NUIHRGPY.js";
44
- import {
45
- DEFAULT_PORT
46
- } from "./chunk-RHLQT3EV.js";
47
- import "./chunk-KE7E7DPX.js";
48
- import "./chunk-Q5GTM25S.js";
49
- import {
50
- helpCmd,
51
- versionCmd
52
- } from "./chunk-ARWGQUB3.js";
53
- import {
54
- CLI_VERSION
55
- } from "./chunk-XJXDOF63.js";
56
- import {
57
- discoverAndMergeProviders
58
- } from "./chunk-52C7PD2H.js";
59
60
  import "./chunk-7OCVIDC7.js";
60
61
 
61
62
  // src/cli-context.ts
@@ -713,12 +714,58 @@ async function launchDesktop(args) {
713
714
  });
714
715
  }
715
716
 
717
+ // src/subcommands/index.ts
718
+ var loaders = {
719
+ acp: async () => (await import("./acp-5ZLGFHWP.js")).acpCmd,
720
+ init: async () => (await import("./init-E2NDDOHI.js")).initCmd,
721
+ auth: async () => (await import("./auth-GVPYLJPS.js")).authCmd,
722
+ update: async () => (await import("./update-VZSOZGYC.js")).updateCmd,
723
+ sessions: async () => (await import("./sessions-config-YX2ZPAFM.js")).sessionsCmd,
724
+ config: async () => (await import("./sessions-config-YX2ZPAFM.js")).configCmd,
725
+ rewind: async () => (await import("./rewind-7RE6ONIC.js")).rewindCmd,
726
+ replay: async () => (await import("./replay-WBRMEWQ5.js")).replayCmd,
727
+ audit: async () => (await import("./audit-BBR22QH3.js")).auditCmd,
728
+ tools: async () => (await import("./tools-skills-UNKLOB7M.js")).toolsCmd,
729
+ skills: async () => (await import("./tools-skills-UNKLOB7M.js")).skillsCmd,
730
+ providers: async () => (await import("./providers-models-VV74TRQ2.js")).providersCmd,
731
+ models: async () => (await import("./providers-models-VV74TRQ2.js")).modelsCmd,
732
+ mcp: async () => (await import("./mcp-MSARYOPN.js")).mcpCmd,
733
+ plugin: async () => (await import("./plugin-usage-PTEETL3J.js")).pluginCmd,
734
+ plugins: async () => (await import("./plugin-usage-PTEETL3J.js")).pluginCmd,
735
+ diag: async () => (await import("./diag-doctor-WTBHPOMB.js")).diagCmd,
736
+ doctor: async () => (await import("./diag-doctor-WTBHPOMB.js")).doctorCmd,
737
+ export: async () => (await import("./export-CCUXRJOU.js")).exportCmd,
738
+ usage: async () => (await import("./plugin-usage-PTEETL3J.js")).usageCmd,
739
+ version: async () => (await import("./version-help-ASKQYXDJ.js")).versionCmd,
740
+ help: async () => (await import("./version-help-ASKQYXDJ.js")).helpCmd,
741
+ projects: async () => (await import("./projects-XE76NFNZ.js")).projectsCmd,
742
+ modeldiag: async () => (await import("./modeldiag-6VS5KDP5.js")).modeldiagCmd,
743
+ quick: async () => (await import("./quick-VIO2BEHS.js")).quickCmd,
744
+ bench: async () => (await import("./bench-XNDUJOSZ.js")).benchCmd,
745
+ chronicle: async () => (await import("./chronicle-63MFCZN6.js")).chronicleCmd,
746
+ hq: async () => (await import("./hq-ZK6PGN3W.js")).hqCmd,
747
+ mailbox: async () => (await import("./mailbox-serve-V3QIJBDH.js")).mailboxServeCmd,
748
+ permissions: async () => (await import("./permissions-2PWBL4YU.js")).permissionsCmd,
749
+ project: async () => (await import("./project-BLQ3ALVL.js")).projectCmd,
750
+ governance: async () => (await import("./governance-V2Z42RBE.js")).governanceCmd
751
+ };
752
+ var subcommands = Object.fromEntries(
753
+ Object.entries(loaders).map(([name, load]) => [
754
+ name,
755
+ async (args, deps) => (await load())(args, deps)
756
+ ])
757
+ );
758
+ var subcommandNames = Object.keys(loaders);
759
+
716
760
  // src/boot/short-circuit-flags.ts
717
761
  async function handleHelpVersionShortCircuit(argv) {
718
- const earlyFlags = parseArgs(argv).flags;
762
+ const { flags: earlyFlags, positional } = parseArgs(argv);
719
763
  if (earlyFlags["help"] !== true && earlyFlags["version"] !== true) {
720
764
  return null;
721
765
  }
766
+ if (earlyFlags["help"] === true && positional.length > 0 && subcommandNames.includes(positional[0])) {
767
+ return null;
768
+ }
722
769
  const stubRenderer = {
723
770
  write: (line) => {
724
771
  process.stdout.write(line);
@@ -920,7 +967,7 @@ ${color3.green("Cloudflare Quick Tunnel ready:")} ${browserUrl}
920
967
  }
921
968
 
922
969
  // src/boot.ts
923
- import * as fs7 from "node:fs/promises";
970
+ import * as fs8 from "node:fs/promises";
924
971
  import { createRequire as createRequire4 } from "node:module";
925
972
  import * as os from "node:os";
926
973
  import * as path8 from "node:path";
@@ -931,7 +978,7 @@ import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
931
978
  import { DefaultModelsRegistry } from "@wrongstack/core/models";
932
979
  import { ToolRegistry } from "@wrongstack/core/registry";
933
980
  import { normalizeTokenSavingTier } from "@wrongstack/core/types";
934
- import { color as color9, isStdinTTY as isStdinTTY2, writeErr } from "@wrongstack/core/utils";
981
+ import { color as color10, isStdinTTY as isStdinTTY2, writeErr } from "@wrongstack/core/utils";
935
982
  import { createDefaultContainer as createDefaultContainer2 } from "@wrongstack/runtime";
936
983
  import { registerBuiltinToolTier } from "@wrongstack/tools/tool-tier";
937
984
 
@@ -1048,84 +1095,520 @@ async function maybeRestoreDefaultProfileFromBackup(opts) {
1048
1095
  }
1049
1096
  }
1050
1097
 
1051
- // src/boot-config.ts
1052
- import { bootConfig as coreBootConfig } from "@wrongstack/core/infrastructure";
1053
- async function bootConfig(flags) {
1054
- const isWebui = !!flags["webui"];
1055
- const opts = {
1056
- flags,
1057
- appLabel: "wstack",
1058
- ...isWebui ? { skipIdentityValidation: true } : {}
1059
- };
1060
- const { cwd, projectRoot, userHome, wpaths, pathResolver, config, vault } = await coreBootConfig(opts);
1061
- return {
1062
- paths: { cwd, projectRoot, userHome, wpaths, pathResolver },
1063
- config,
1064
- vault
1065
- };
1066
- }
1098
+ // src/boot/system-prompt-menu.ts
1099
+ import * as fs5 from "node:fs/promises";
1100
+ import {
1101
+ buildIdentityLayer,
1102
+ loadInstructionBundle
1103
+ } from "@wrongstack/core/agent";
1104
+ import { atomicWrite as atomicWrite4, color as color7, estimateTextTokens } from "@wrongstack/core/utils";
1067
1105
 
1068
- // src/input-reader.ts
1106
+ // src/pre-launch/launch-prompts.ts
1069
1107
  import * as fs3 from "node:fs/promises";
1070
- import * as path5 from "node:path";
1071
- import * as readline from "node:readline";
1072
- import {
1073
- resolveWstackPaths,
1074
- setOutputLineGuard,
1075
- setRawMode as setRawMode2,
1076
- writeOut
1077
- } from "@wrongstack/core/utils";
1078
- var ReadlineInputReader = class {
1079
- rl;
1080
- historyFile;
1081
- history = [];
1082
- pending = false;
1083
- suppressNextHistory = false;
1084
- constructor(opts = {}) {
1085
- this.historyFile = opts.historyFile ?? resolveWstackPaths({ projectRoot: process.cwd() }).historyFile;
1108
+ import { atomicWrite as atomicWrite3, color as color5 } from "@wrongstack/core/utils";
1109
+ var LaunchAbortedError = class extends Error {
1110
+ exitCode = 0;
1111
+ constructor() {
1112
+ super("Launch cancelled by user");
1113
+ this.name = "LaunchAbortedError";
1086
1114
  }
1087
- async loadHistory() {
1088
- try {
1089
- const raw = await fs3.readFile(this.historyFile, "utf8");
1090
- this.history = raw.split("\n").filter(Boolean).slice(-1e3);
1091
- } catch {
1092
- this.history = [];
1115
+ };
1116
+ async function runLaunchPrompts(opts) {
1117
+ const { renderer, reader, modePinned, yoloPinned, autonomyPinned, lastChoices } = opts;
1118
+ if (modePinned !== void 0 && yoloPinned !== void 0 && autonomyPinned !== void 0) {
1119
+ return {
1120
+ mode: modePinned,
1121
+ yolo: yoloPinned,
1122
+ autonomy: autonomyPinned
1123
+ };
1124
+ }
1125
+ if (!lastChoices) {
1126
+ return {
1127
+ mode: modePinned ?? "tui",
1128
+ yolo: yoloPinned ?? true,
1129
+ autonomy: autonomyPinned ?? "auto"
1130
+ };
1131
+ }
1132
+ const hasOverride = modePinned !== void 0 && modePinned !== lastChoices.mode || yoloPinned !== void 0 && yoloPinned !== lastChoices.yolo || autonomyPinned !== void 0 && autonomyPinned !== lastChoices.autonomy;
1133
+ if (lastChoices && !hasOverride) {
1134
+ const effective = {
1135
+ mode: modePinned ?? lastChoices.mode,
1136
+ yolo: yoloPinned ?? lastChoices.yolo,
1137
+ autonomy: autonomyPinned ?? lastChoices.autonomy
1138
+ };
1139
+ const onOff = (v) => v ? color5.green("on") : color5.dim("off");
1140
+ const modeLabel = effective.mode.toUpperCase();
1141
+ renderer.write(
1142
+ `
1143
+ ${color5.dim("Last settings:")} ${color5.bold(modeLabel)} \xB7 YOLO ${onOff(effective.yolo)} \xB7 Autonomy ${effective.autonomy === "auto" ? color5.green("auto") : color5.dim("off")}
1144
+ `
1145
+ );
1146
+ const answer = (await reader.readLine(
1147
+ ` ${color5.amber("?")} Continue with these? ${color5.dim("[Y/n/q]")} ${color5.dim("(auto Y in 5s)")} `,
1148
+ { timeoutMs: 5e3, defaultAnswer: "y" }
1149
+ )).trim().toLowerCase();
1150
+ if (answer === "q") {
1151
+ renderer.write(color5.dim(" Goodbye!\n"));
1152
+ throw new LaunchAbortedError();
1153
+ }
1154
+ if (answer !== "n" && answer !== "no") {
1155
+ const badges2 = buildBadges(effective);
1156
+ const badgeStr2 = badges2.length > 0 ? ` (${badges2.join(" \xB7 ")})` : "";
1157
+ renderer.write(
1158
+ `
1159
+ ${color5.green("\u25B6")} Launching in ${color5.bold(modeLabel)} mode${badgeStr2}
1160
+
1161
+ `
1162
+ );
1163
+ return effective;
1093
1164
  }
1094
1165
  }
1095
- async saveHistory() {
1096
- try {
1097
- await fs3.mkdir(path5.dirname(this.historyFile), { recursive: true });
1098
- await fs3.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
1099
- } catch {
1166
+ let mode;
1167
+ if (modePinned) {
1168
+ mode = modePinned;
1169
+ } else {
1170
+ const answer = (await reader.readLine(
1171
+ `
1172
+ ${color5.amber("?")} Interactive mode: ${color5.bold("T")}UI / ${color5.bold("R")}EPL ${color5.dim("[T/r/q]")} `
1173
+ )).trim().toLowerCase();
1174
+ if (answer === "q") {
1175
+ renderer.write(color5.dim(" Goodbye!\n"));
1176
+ throw new LaunchAbortedError();
1100
1177
  }
1178
+ mode = answer === "r" || answer === "repl" ? "repl" : "tui";
1101
1179
  }
1102
- shouldPersistHistory(line) {
1103
- return !/^\s*\/(?:telegram-setup|tg-setup)\s+\d+:[A-Za-z0-9_-]+(?:\s|$)/i.test(line);
1180
+ let yolo;
1181
+ if (yoloPinned !== void 0 && yoloPinned === lastChoices?.yolo) {
1182
+ yolo = yoloPinned;
1183
+ } else {
1184
+ const answer = (await reader.readLine(
1185
+ ` ${color5.amber("?")} YOLO mode ${color5.dim("(auto-approve tool calls)")} ${color5.dim("[Y/n/q]")} `
1186
+ )).trim().toLowerCase();
1187
+ if (answer === "q") {
1188
+ renderer.write(color5.dim(" Goodbye!\n"));
1189
+ throw new LaunchAbortedError();
1190
+ }
1191
+ yolo = answer !== "n" && answer !== "no";
1104
1192
  }
1105
- ensure() {
1106
- if (!this.rl) {
1107
- this.rl = readline.createInterface({
1108
- input: process.stdin,
1109
- output: process.stdout,
1110
- history: this.history,
1111
- terminal: process.stdin.isTTY
1112
- });
1193
+ let autonomy;
1194
+ if (autonomyPinned !== void 0 && autonomyPinned === lastChoices?.autonomy) {
1195
+ autonomy = autonomyPinned;
1196
+ } else {
1197
+ const answer = (await reader.readLine(
1198
+ ` ${color5.amber("?")} Autonomy mode ${color5.dim("(auto-continue \u2014 agent picks next step)")} ${color5.dim("[Y/n/q]")} `
1199
+ )).trim().toLowerCase();
1200
+ if (answer === "q") {
1201
+ renderer.write(color5.dim(" Goodbye!\n"));
1202
+ throw new LaunchAbortedError();
1113
1203
  }
1114
- return this.rl;
1204
+ autonomy = answer !== "n" && answer !== "no" ? "auto" : "off";
1115
1205
  }
1116
- async readLine(prompt, timeoutOpts) {
1117
- if (this.history.length === 0) await this.loadHistory();
1118
- while (this.pending) {
1119
- await new Promise((resolve2) => setTimeout(resolve2, 50));
1206
+ const badges = buildBadges({ mode, yolo, autonomy });
1207
+ const badgeStr = badges.length > 0 ? ` (${badges.join(" \xB7 ")})` : "";
1208
+ renderer.write(
1209
+ `
1210
+ ${color5.green("\u25B6")} Launching in ${color5.bold(mode.toUpperCase())} mode${badgeStr}
1211
+
1212
+ `
1213
+ );
1214
+ return { mode, yolo, autonomy };
1215
+ }
1216
+ function buildBadges(chosen) {
1217
+ const badges = [];
1218
+ if (chosen.yolo) badges.push(color5.yellow("YOLO"));
1219
+ if (chosen.autonomy !== "off")
1220
+ badges.push(color5.magenta(`AUTONOMY:${chosen.autonomy.toUpperCase()}`));
1221
+ return badges;
1222
+ }
1223
+ async function persistLaunchChoices(configPath, choices) {
1224
+ let fileExists = false;
1225
+ try {
1226
+ await fs3.access(configPath);
1227
+ fileExists = true;
1228
+ } catch {
1229
+ }
1230
+ let existing = {};
1231
+ try {
1232
+ const raw = await fs3.readFile(configPath, "utf8");
1233
+ existing = JSON.parse(raw);
1234
+ } catch (err) {
1235
+ if (fileExists) {
1236
+ throw new Error(
1237
+ `Refusing to overwrite corrupt config at ${configPath} (${err.message}). Fix or move the file aside before retrying.`,
1238
+ { cause: err }
1239
+ );
1120
1240
  }
1121
- this.pending = true;
1122
- try {
1123
- if (this.rl) {
1124
- const old = this.rl;
1125
- this.rl = void 0;
1126
- await new Promise((resolve2) => {
1127
- if (old.closed) {
1128
- resolve2();
1241
+ existing = {};
1242
+ }
1243
+ existing.yolo = choices.yolo;
1244
+ existing.launch = {
1245
+ mode: choices.mode,
1246
+ autonomy: choices.autonomy
1247
+ };
1248
+ await atomicWrite3(configPath, JSON.stringify(existing, null, 2), { mode: 384 });
1249
+ }
1250
+
1251
+ // src/pre-launch/project-check.ts
1252
+ import * as fs4 from "node:fs/promises";
1253
+ import * as path5 from "node:path";
1254
+ import { color as color6, toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
1255
+ var MANIFESTS = [
1256
+ "package.json",
1257
+ "pyproject.toml",
1258
+ "Cargo.toml",
1259
+ "go.mod",
1260
+ "Makefile",
1261
+ "pom.xml",
1262
+ "build.gradle",
1263
+ "build.gradle.kts",
1264
+ "composer.json",
1265
+ "Gemfile"
1266
+ ];
1267
+ async function detectProjectKind(projectRoot) {
1268
+ try {
1269
+ await fs4.access(path5.join(projectRoot, ".wrongstack", "AGENTS.md"));
1270
+ return "initialized";
1271
+ } catch {
1272
+ }
1273
+ for (const m of MANIFESTS) {
1274
+ try {
1275
+ await fs4.access(path5.join(projectRoot, m));
1276
+ return "project";
1277
+ } catch {
1278
+ }
1279
+ }
1280
+ return "empty";
1281
+ }
1282
+ async function scaffoldAgentsMd(projectRoot) {
1283
+ const dir = path5.join(projectRoot, ".wrongstack");
1284
+ const file = path5.join(dir, "AGENTS.md");
1285
+ const facts = await detectProjectFacts(projectRoot);
1286
+ const body = renderAgentsTemplate(facts);
1287
+ await fs4.mkdir(dir, { recursive: true });
1288
+ await fs4.writeFile(file, body, "utf8");
1289
+ return file;
1290
+ }
1291
+ async function runProjectCheck(opts) {
1292
+ const { projectRoot, cwd, renderer, reader } = opts;
1293
+ const kind = await detectProjectKind(projectRoot);
1294
+ if (kind === "initialized") {
1295
+ renderer.write(
1296
+ `
1297
+ ${color6.green("\u2713")} Project initialized ${color6.dim(`(${path5.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
1298
+ `
1299
+ );
1300
+ return true;
1301
+ }
1302
+ if (kind === "project") {
1303
+ renderer.write(
1304
+ `
1305
+ ${color6.amber("\u25CF")} Project detected ${color6.dim(`(${projectRoot})`)} but ${color6.bold(".wrongstack/AGENTS.md")} is missing.
1306
+ `
1307
+ );
1308
+ const answer2 = (await reader.readLine(
1309
+ ` ${color6.amber("?")} Scaffold ${color6.bold("AGENTS.md")} now? ${color6.dim("[y/N/q]")} `
1310
+ )).trim().toLowerCase();
1311
+ if (answer2 === "q") {
1312
+ renderer.write(color6.dim(" Cancelled.\n"));
1313
+ return false;
1314
+ }
1315
+ if (answer2 === "y" || answer2 === "yes") {
1316
+ try {
1317
+ const file = await scaffoldAgentsMd(projectRoot);
1318
+ renderer.write(` ${color6.green("\u2713")} Wrote ${color6.dim(file)}
1319
+ `);
1320
+ } catch (err) {
1321
+ renderer.writeError(`Failed to scaffold AGENTS.md: ${toErrorMessage2(err)}`);
1322
+ }
1323
+ }
1324
+ return true;
1325
+ }
1326
+ const gitDir = path5.join(projectRoot, ".git");
1327
+ let hasGit = false;
1328
+ try {
1329
+ await fs4.access(gitDir);
1330
+ hasGit = true;
1331
+ } catch {
1332
+ }
1333
+ if (!hasGit) {
1334
+ renderer.write(
1335
+ `
1336
+ ${color6.dim("\u25CB")} ${color6.dim(`No project manifest in ${projectRoot} \u2014 running in a scratch directory.`)}
1337
+ `
1338
+ );
1339
+ const answer2 = (await reader.readLine(
1340
+ ` ${color6.amber("?")} No git repo found. ${color6.bold("Initialize git?")} ${color6.dim("[y/N/q]")} `
1341
+ )).trim().toLowerCase();
1342
+ if (answer2 === "q") {
1343
+ renderer.write(color6.dim(" Cancelled.\n"));
1344
+ return false;
1345
+ }
1346
+ if (answer2 === "y" || answer2 === "yes") {
1347
+ try {
1348
+ const { spawn: spawn2 } = await import("node:child_process");
1349
+ await new Promise((resolve2, reject) => {
1350
+ const child = spawn2("git", ["init"], {
1351
+ cwd,
1352
+ signal: AbortSignal.timeout(1e4),
1353
+ windowsHide: true
1354
+ });
1355
+ child.on("error", reject);
1356
+ child.on(
1357
+ "close",
1358
+ (code) => code === 0 ? resolve2() : reject(new Error(`git init failed with ${code}`))
1359
+ );
1360
+ });
1361
+ renderer.write(` ${color6.green("\u2713")} Git repository initialized
1362
+ `);
1363
+ } catch (err) {
1364
+ renderer.writeError(`git init failed: ${toErrorMessage2(err)}
1365
+ `);
1366
+ }
1367
+ }
1368
+ } else {
1369
+ renderer.write(
1370
+ `
1371
+ ${color6.dim("\u25CB")} ${color6.dim(`No project manifest in ${projectRoot} \u2014 running in a scratch directory.`)}
1372
+ `
1373
+ );
1374
+ }
1375
+ const answer = (await reader.readLine(` ${color6.amber("?")} Continue anyway? ${color6.dim("[Y/n/q]")} `)).trim().toLowerCase();
1376
+ if (answer === "q" || answer === "n" || answer === "no") {
1377
+ renderer.write(color6.dim(" Cancelled.\n"));
1378
+ return false;
1379
+ }
1380
+ return true;
1381
+ }
1382
+
1383
+ // src/boot/system-prompt-menu.ts
1384
+ var SYSTEM_PROMPT_OPTIONS = [
1385
+ { variant: "lite", label: "Lite", hint: "leanest \u2014 best for small context windows" },
1386
+ { variant: "default", label: "Standard", hint: "balanced \u2014 the default" },
1387
+ { variant: "pro", label: "Pro", hint: "most detailed guidance \u2014 uses more tokens" }
1388
+ ];
1389
+ var SYSTEM_PROMPT_VARIANTS = SYSTEM_PROMPT_OPTIONS.map((o) => o.variant);
1390
+ var SUMMARY_TIMEOUT_MS2 = 5e3;
1391
+ function flagOn(value) {
1392
+ if (value === true) return true;
1393
+ if (typeof value !== "string") return false;
1394
+ const normalized = value.trim().toLowerCase();
1395
+ return normalized === "true" || normalized === "1" || normalized === "yes" || normalized === "on";
1396
+ }
1397
+ function isSystemPromptPinned(flags) {
1398
+ return flagOn(flags["system-pro"]) || flagOn(flags["system-lite"]) || typeof flags["system-prompt"] === "string";
1399
+ }
1400
+ function shouldSkipSystemPromptMenu(flags) {
1401
+ if (isSystemPromptPinned(flags)) return true;
1402
+ if (flags["no-menu"] === true) return true;
1403
+ if (flags["no-interactive"] === true) return true;
1404
+ if (flags["skip"] === true) return true;
1405
+ if (flags["webui"] === true) return true;
1406
+ return false;
1407
+ }
1408
+ async function countSystemPromptTokens(paths) {
1409
+ const counts = {};
1410
+ for (const variant of SYSTEM_PROMPT_VARIANTS) {
1411
+ const bundle = await loadInstructionBundle({
1412
+ globalDir: paths.globalDir,
1413
+ projectDir: paths.projectDir,
1414
+ systemVariant: variant
1415
+ });
1416
+ const identity = buildIdentityLayer(
1417
+ bundle.system?.identity,
1418
+ bundle.system?.identitySource,
1419
+ void 0
1420
+ );
1421
+ counts[variant] = estimateTextTokens(identity);
1422
+ }
1423
+ return counts;
1424
+ }
1425
+ function labelFor(variant) {
1426
+ return SYSTEM_PROMPT_OPTIONS.find((o) => o.variant === variant)?.label ?? variant;
1427
+ }
1428
+ var VALID_VARIANTS = new Set(SYSTEM_PROMPT_VARIANTS);
1429
+ async function readSavedSystemPromptVariant(configPath) {
1430
+ let raw;
1431
+ try {
1432
+ raw = await fs5.readFile(configPath, "utf8");
1433
+ } catch {
1434
+ return void 0;
1435
+ }
1436
+ try {
1437
+ const parsed = JSON.parse(raw);
1438
+ const variant = parsed.systemPrompt?.variant;
1439
+ return typeof variant === "string" && VALID_VARIANTS.has(variant) ? variant : void 0;
1440
+ } catch {
1441
+ return void 0;
1442
+ }
1443
+ }
1444
+ async function runSystemPromptMenu(deps) {
1445
+ const { renderer, reader, paths, lastVariant } = deps;
1446
+ const tokens = await countSystemPromptTokens(paths);
1447
+ const defaultVariant = lastVariant ?? "default";
1448
+ if (lastVariant) {
1449
+ const answer2 = (await reader.readLine(
1450
+ ` ${color7.amber("?")} Continue with ${color7.bold(labelFor(lastVariant))} system prompt ${color7.dim(`(~${fmtTok(tokens[lastVariant])} tokens)`)}? ${color7.dim("[Y/n/q]")} ${color7.dim("(auto Y in 5s)")} `,
1451
+ { timeoutMs: SUMMARY_TIMEOUT_MS2, defaultAnswer: "y" }
1452
+ )).trim().toLowerCase();
1453
+ if (answer2 === "q" || answer2 === "quit") throw new LaunchAbortedError();
1454
+ if (answer2 !== "n" && answer2 !== "no") return lastVariant;
1455
+ }
1456
+ renderer.write(`
1457
+ ${color7.amber("?")} System prompt:
1458
+ `);
1459
+ for (const [index, opt] of SYSTEM_PROMPT_OPTIONS.entries()) {
1460
+ const num2 = index + 1;
1461
+ const isDefault = opt.variant === defaultVariant;
1462
+ const marker = isDefault ? color7.cyan("\u25CF") : color7.dim("\u25CB");
1463
+ const label = isDefault ? color7.bold(opt.label) : opt.label;
1464
+ renderer.write(
1465
+ ` ${color7.dim(`${num2}.`)} ${marker} ${label.padEnd(9)} ${color7.dim(`~${fmtTok(tokens[opt.variant])} tokens`)} ${color7.dim(opt.hint)}
1466
+ `
1467
+ );
1468
+ }
1469
+ const answer = (await reader.readLine(
1470
+ `
1471
+ ${color7.amber("?")} Select system prompt ${color7.dim(`[1-${SYSTEM_PROMPT_OPTIONS.length}, Enter = ${labelFor(defaultVariant)}, q to quit]`)}: `
1472
+ )).trim().toLowerCase();
1473
+ if (answer === "q" || answer === "quit") throw new LaunchAbortedError();
1474
+ if (!answer) return defaultVariant;
1475
+ const num = Number.parseInt(answer, 10);
1476
+ if (!Number.isNaN(num) && num >= 1 && num <= SYSTEM_PROMPT_OPTIONS.length) {
1477
+ return SYSTEM_PROMPT_OPTIONS[num - 1].variant;
1478
+ }
1479
+ const byName = SYSTEM_PROMPT_OPTIONS.find(
1480
+ (o) => o.label.toLowerCase() === answer || o.variant === answer
1481
+ );
1482
+ if (byName) return byName.variant;
1483
+ return defaultVariant;
1484
+ }
1485
+ async function persistSystemPromptVariant(configPath, variant) {
1486
+ let fileExists = false;
1487
+ try {
1488
+ await fs5.access(configPath);
1489
+ fileExists = true;
1490
+ } catch {
1491
+ }
1492
+ let existing = {};
1493
+ try {
1494
+ const raw = await fs5.readFile(configPath, "utf8");
1495
+ existing = JSON.parse(raw);
1496
+ } catch (err) {
1497
+ if (fileExists) {
1498
+ throw new Error(
1499
+ `Refusing to overwrite corrupt config at ${configPath} (${err.message}). Fix or move the file aside before retrying.`,
1500
+ { cause: err }
1501
+ );
1502
+ }
1503
+ existing = {};
1504
+ }
1505
+ const systemPrompt = existing.systemPrompt ?? {};
1506
+ existing.systemPrompt = { ...systemPrompt, variant };
1507
+ await atomicWrite4(configPath, JSON.stringify(existing, null, 2), { mode: 384 });
1508
+ }
1509
+ async function maybeRunSystemPromptMenu(opts) {
1510
+ if (!opts.isInteractiveTTY) return { aborted: false, changed: false };
1511
+ if (shouldSkipSystemPromptMenu(opts.flags)) return { aborted: false, changed: false };
1512
+ let savedVariant;
1513
+ try {
1514
+ savedVariant = await readSavedSystemPromptVariant(opts.profileConfigPath);
1515
+ const chosen = await runSystemPromptMenu({
1516
+ renderer: opts.renderer,
1517
+ reader: opts.reader,
1518
+ lastVariant: savedVariant,
1519
+ paths: opts.paths
1520
+ });
1521
+ if (chosen === savedVariant) return { variant: chosen, aborted: false, changed: false };
1522
+ try {
1523
+ await persistSystemPromptVariant(opts.profileConfigPath, chosen);
1524
+ } catch (err) {
1525
+ return { variant: chosen, aborted: false, changed: true, persistError: err };
1526
+ }
1527
+ return { variant: chosen, aborted: false, changed: true };
1528
+ } catch (err) {
1529
+ if (err instanceof LaunchAbortedError) return { aborted: true, changed: false };
1530
+ throw err;
1531
+ }
1532
+ }
1533
+
1534
+ // src/boot-config.ts
1535
+ import { bootConfig as coreBootConfig } from "@wrongstack/core/infrastructure";
1536
+ async function bootConfig(flags) {
1537
+ const isWebui = !!flags["webui"];
1538
+ const opts = {
1539
+ flags,
1540
+ appLabel: "wstack",
1541
+ ...isWebui ? { skipIdentityValidation: true } : {}
1542
+ };
1543
+ const { cwd, projectRoot, userHome, wpaths, pathResolver, config, vault } = await coreBootConfig(opts);
1544
+ return {
1545
+ paths: { cwd, projectRoot, userHome, wpaths, pathResolver },
1546
+ config,
1547
+ vault
1548
+ };
1549
+ }
1550
+
1551
+ // src/input-reader.ts
1552
+ import * as fs6 from "node:fs/promises";
1553
+ import * as path6 from "node:path";
1554
+ import * as readline from "node:readline";
1555
+ import {
1556
+ resolveWstackPaths,
1557
+ setOutputLineGuard,
1558
+ setRawMode as setRawMode2,
1559
+ writeOut
1560
+ } from "@wrongstack/core/utils";
1561
+ var ReadlineInputReader = class {
1562
+ rl;
1563
+ historyFile;
1564
+ history = [];
1565
+ pending = false;
1566
+ suppressNextHistory = false;
1567
+ constructor(opts = {}) {
1568
+ this.historyFile = opts.historyFile ?? resolveWstackPaths({ projectRoot: process.cwd() }).historyFile;
1569
+ }
1570
+ async loadHistory() {
1571
+ try {
1572
+ const raw = await fs6.readFile(this.historyFile, "utf8");
1573
+ this.history = raw.split("\n").filter(Boolean).slice(-1e3);
1574
+ } catch {
1575
+ this.history = [];
1576
+ }
1577
+ }
1578
+ async saveHistory() {
1579
+ try {
1580
+ await fs6.mkdir(path6.dirname(this.historyFile), { recursive: true });
1581
+ await fs6.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
1582
+ } catch {
1583
+ }
1584
+ }
1585
+ shouldPersistHistory(line) {
1586
+ return !/^\s*\/(?:telegram-setup|tg-setup)\s+\d+:[A-Za-z0-9_-]+(?:\s|$)/i.test(line);
1587
+ }
1588
+ ensure() {
1589
+ if (!this.rl) {
1590
+ this.rl = readline.createInterface({
1591
+ input: process.stdin,
1592
+ output: process.stdout,
1593
+ history: this.history,
1594
+ terminal: process.stdin.isTTY
1595
+ });
1596
+ }
1597
+ return this.rl;
1598
+ }
1599
+ async readLine(prompt, timeoutOpts) {
1600
+ if (this.history.length === 0) await this.loadHistory();
1601
+ while (this.pending) {
1602
+ await new Promise((resolve2) => setTimeout(resolve2, 50));
1603
+ }
1604
+ this.pending = true;
1605
+ try {
1606
+ if (this.rl) {
1607
+ const old = this.rl;
1608
+ this.rl = void 0;
1609
+ await new Promise((resolve2) => {
1610
+ if (old.closed) {
1611
+ resolve2();
1129
1612
  } else {
1130
1613
  old.once("close", resolve2);
1131
1614
  old.close();
@@ -1343,9 +1826,9 @@ function dim(s) {
1343
1826
  }
1344
1827
 
1345
1828
  // src/launch-hints.ts
1346
- import * as fs4 from "node:fs/promises";
1347
- import * as path6 from "node:path";
1348
- import { color as color5 } from "@wrongstack/core/utils";
1829
+ import * as fs7 from "node:fs/promises";
1830
+ import * as path7 from "node:path";
1831
+ import { color as color8 } from "@wrongstack/core/utils";
1349
1832
  var GROUPS = [
1350
1833
  {
1351
1834
  title: "Autonomy",
@@ -1468,404 +1951,127 @@ var HINT_CONTINUATION_INDENT = " ";
1468
1951
  var wrap = (n) => (n % GROUPS.length + GROUPS.length) % GROUPS.length;
1469
1952
  function displayWidth(value) {
1470
1953
  return [...value].length;
1471
- }
1472
- function wrapWords(value, firstWidth, continuationWidth = firstWidth) {
1473
- const words = value.trim().split(/\s+/).filter(Boolean);
1474
- const lines = [];
1475
- let line = "";
1476
- let width = Math.max(1, firstWidth);
1477
- for (const sourceWord of words) {
1478
- let word = sourceWord;
1479
- while (word) {
1480
- if (line) {
1481
- if (displayWidth(line) + 1 + displayWidth(word) <= width) {
1482
- line += ` ${word}`;
1483
- word = "";
1484
- } else {
1485
- lines.push(line);
1486
- line = "";
1487
- width = Math.max(1, continuationWidth);
1488
- }
1489
- continue;
1490
- }
1491
- if (displayWidth(word) <= width) {
1492
- line = word;
1493
- word = "";
1494
- } else {
1495
- const characters = [...word];
1496
- lines.push(characters.slice(0, width).join(""));
1497
- word = characters.slice(width).join("");
1498
- width = Math.max(1, continuationWidth);
1499
- }
1500
- }
1501
- }
1502
- if (line) lines.push(line);
1503
- return lines;
1504
- }
1505
- function renderHintLines(item, termWidth) {
1506
- const inline = `${HINT_INDENT}${item.key} \u2014 ${item.blurb}`;
1507
- if (displayWidth(inline) <= termWidth) {
1508
- return [`${HINT_INDENT}${color5.bold(item.key)} ${color5.dim("\u2014")} ${color5.dim(item.blurb)}`];
1509
- }
1510
- const lines = [];
1511
- const keyLines = wrapWords(
1512
- item.key,
1513
- termWidth - displayWidth(HINT_INDENT),
1514
- termWidth - displayWidth(HINT_CONTINUATION_INDENT)
1515
- );
1516
- keyLines.forEach((line, index) => {
1517
- const indent = index === 0 ? HINT_INDENT : HINT_CONTINUATION_INDENT;
1518
- lines.push(`${indent}${color5.bold(line)}`);
1519
- });
1520
- const blurbLines = wrapWords(
1521
- `\u2014 ${item.blurb}`,
1522
- termWidth - displayWidth(HINT_CONTINUATION_INDENT)
1523
- );
1524
- for (const line of blurbLines) lines.push(`${HINT_CONTINUATION_INDENT}${color5.dim(line)}`);
1525
- return lines;
1526
- }
1527
- function renderHeaderLines(group, groupIndex, termWidth) {
1528
- const progress = `${groupIndex + 1}/${GROUPS.length}`;
1529
- const plain = ` ${group.title} ${progress}`;
1530
- if (displayWidth(plain) <= termWidth) {
1531
- return [
1532
- ` ${color5.cyan("\u25C7")} ${color5.bold(group.title)} ${color5.dim(progress)}`,
1533
- ` ${color5.dim("\u2500".repeat(Math.min(termWidth - 2, 48)))}`
1534
- ];
1535
- }
1536
- return [
1537
- ` ${color5.cyan("\u25C7")} ${color5.bold(group.title)}`,
1538
- ` ${color5.dim("\u2500".repeat(Math.min(termWidth - 2, 48)))}`
1539
- ];
1540
- }
1541
- function renderFooterLines(termWidth) {
1542
- const footer = "/help lists everything \xB7 hide with --no-hints";
1543
- if (displayWidth(` ${footer}`) <= termWidth) {
1544
- return [
1545
- ` ${color5.dim("\u2500".repeat(Math.min(termWidth - 2, 48)))}`,
1546
- ` ${color5.dim(`${color5.bold("/help")} lists everything \xB7 hide with ${color5.bold("--no-hints")}`)}`
1547
- ];
1548
- }
1549
- return [...wrapWords(footer, termWidth - 2).map((line) => ` ${color5.dim(line)}`)];
1550
- }
1551
- async function pickGroupIndex(opts) {
1552
- if (typeof opts.groupIndex === "number") return wrap(opts.groupIndex);
1553
- if (opts.cursorFile) {
1554
- try {
1555
- let current = 0;
1556
- try {
1557
- const raw = await fs4.readFile(opts.cursorFile, "utf8");
1558
- const parsed = Number.parseInt(raw.trim(), 10);
1559
- if (Number.isFinite(parsed)) current = wrap(parsed);
1560
- } catch {
1561
- }
1562
- await fs4.mkdir(path6.dirname(opts.cursorFile), { recursive: true });
1563
- await fs4.writeFile(opts.cursorFile, String(wrap(current + 1)));
1564
- return current;
1565
- } catch {
1566
- }
1567
- }
1568
- return Math.floor(Math.random() * GROUPS.length);
1569
- }
1570
- async function printLaunchHints(renderer, flags, opts = {}) {
1571
- if (shouldSuppress(flags)) return;
1572
- const idx = await pickGroupIndex(opts);
1573
- const group = GROUPS[idx];
1574
- if (!group) return;
1575
- const termWidth = Math.max(
1576
- MIN_TERM_WIDTH,
1577
- Math.floor(opts.termWidth ?? process.stdout.columns ?? DEFAULT_TERM_WIDTH)
1578
- );
1579
- const lines = [];
1580
- lines.push("");
1581
- lines.push(...renderHeaderLines(group, idx, termWidth));
1582
- for (const item of group.items) lines.push(...renderHintLines(item, termWidth));
1583
- lines.push("");
1584
- lines.push(...renderFooterLines(termWidth));
1585
- lines.push("");
1586
- renderer.write(`${lines.join("\n")}
1587
- `);
1588
- }
1589
-
1590
- // src/pre-launch/launch-prompts.ts
1591
- import * as fs5 from "node:fs/promises";
1592
- import { atomicWrite as atomicWrite3, color as color6 } from "@wrongstack/core/utils";
1593
- var LaunchAbortedError = class extends Error {
1594
- exitCode = 0;
1595
- constructor() {
1596
- super("Launch cancelled by user");
1597
- this.name = "LaunchAbortedError";
1598
- }
1599
- };
1600
- async function runLaunchPrompts(opts) {
1601
- const { renderer, reader, modePinned, yoloPinned, autonomyPinned, lastChoices } = opts;
1602
- if (modePinned !== void 0 && yoloPinned !== void 0 && autonomyPinned !== void 0) {
1603
- return {
1604
- mode: modePinned,
1605
- yolo: yoloPinned,
1606
- autonomy: autonomyPinned
1607
- };
1608
- }
1609
- if (!lastChoices) {
1610
- return {
1611
- mode: modePinned ?? "tui",
1612
- yolo: yoloPinned ?? true,
1613
- autonomy: autonomyPinned ?? "auto"
1614
- };
1615
- }
1616
- const hasOverride = modePinned !== void 0 && modePinned !== lastChoices.mode || yoloPinned !== void 0 && yoloPinned !== lastChoices.yolo || autonomyPinned !== void 0 && autonomyPinned !== lastChoices.autonomy;
1617
- if (lastChoices && !hasOverride) {
1618
- const effective = {
1619
- mode: modePinned ?? lastChoices.mode,
1620
- yolo: yoloPinned ?? lastChoices.yolo,
1621
- autonomy: autonomyPinned ?? lastChoices.autonomy
1622
- };
1623
- const onOff = (v) => v ? color6.green("on") : color6.dim("off");
1624
- const modeLabel = effective.mode.toUpperCase();
1625
- renderer.write(
1626
- `
1627
- ${color6.dim("Last settings:")} ${color6.bold(modeLabel)} \xB7 YOLO ${onOff(effective.yolo)} \xB7 Autonomy ${effective.autonomy === "auto" ? color6.green("auto") : color6.dim("off")}
1628
- `
1629
- );
1630
- const answer = (await reader.readLine(
1631
- ` ${color6.amber("?")} Continue with these? ${color6.dim("[Y/n/q]")} ${color6.dim("(auto Y in 5s)")} `,
1632
- { timeoutMs: 5e3, defaultAnswer: "y" }
1633
- )).trim().toLowerCase();
1634
- if (answer === "q") {
1635
- renderer.write(color6.dim(" Goodbye!\n"));
1636
- throw new LaunchAbortedError();
1637
- }
1638
- if (answer !== "n" && answer !== "no") {
1639
- const badges2 = buildBadges(effective);
1640
- const badgeStr2 = badges2.length > 0 ? ` (${badges2.join(" \xB7 ")})` : "";
1641
- renderer.write(
1642
- `
1643
- ${color6.green("\u25B6")} Launching in ${color6.bold(modeLabel)} mode${badgeStr2}
1644
-
1645
- `
1646
- );
1647
- return effective;
1648
- }
1649
- }
1650
- let mode;
1651
- if (modePinned) {
1652
- mode = modePinned;
1653
- } else {
1654
- const answer = (await reader.readLine(
1655
- `
1656
- ${color6.amber("?")} Interactive mode: ${color6.bold("T")}UI / ${color6.bold("R")}EPL ${color6.dim("[T/r/q]")} `
1657
- )).trim().toLowerCase();
1658
- if (answer === "q") {
1659
- renderer.write(color6.dim(" Goodbye!\n"));
1660
- throw new LaunchAbortedError();
1661
- }
1662
- mode = answer === "r" || answer === "repl" ? "repl" : "tui";
1663
- }
1664
- let yolo;
1665
- if (yoloPinned !== void 0 && yoloPinned === lastChoices?.yolo) {
1666
- yolo = yoloPinned;
1667
- } else {
1668
- const answer = (await reader.readLine(
1669
- ` ${color6.amber("?")} YOLO mode ${color6.dim("(auto-approve tool calls)")} ${color6.dim("[Y/n/q]")} `
1670
- )).trim().toLowerCase();
1671
- if (answer === "q") {
1672
- renderer.write(color6.dim(" Goodbye!\n"));
1673
- throw new LaunchAbortedError();
1674
- }
1675
- yolo = answer !== "n" && answer !== "no";
1676
- }
1677
- let autonomy;
1678
- if (autonomyPinned !== void 0 && autonomyPinned === lastChoices?.autonomy) {
1679
- autonomy = autonomyPinned;
1680
- } else {
1681
- const answer = (await reader.readLine(
1682
- ` ${color6.amber("?")} Autonomy mode ${color6.dim("(auto-continue \u2014 agent picks next step)")} ${color6.dim("[Y/n/q]")} `
1683
- )).trim().toLowerCase();
1684
- if (answer === "q") {
1685
- renderer.write(color6.dim(" Goodbye!\n"));
1686
- throw new LaunchAbortedError();
1687
- }
1688
- autonomy = answer !== "n" && answer !== "no" ? "auto" : "off";
1689
- }
1690
- const badges = buildBadges({ mode, yolo, autonomy });
1691
- const badgeStr = badges.length > 0 ? ` (${badges.join(" \xB7 ")})` : "";
1692
- renderer.write(
1693
- `
1694
- ${color6.green("\u25B6")} Launching in ${color6.bold(mode.toUpperCase())} mode${badgeStr}
1695
-
1696
- `
1697
- );
1698
- return { mode, yolo, autonomy };
1699
- }
1700
- function buildBadges(chosen) {
1701
- const badges = [];
1702
- if (chosen.yolo) badges.push(color6.yellow("YOLO"));
1703
- if (chosen.autonomy !== "off")
1704
- badges.push(color6.magenta(`AUTONOMY:${chosen.autonomy.toUpperCase()}`));
1705
- return badges;
1706
- }
1707
- async function persistLaunchChoices(configPath, choices) {
1708
- let fileExists = false;
1709
- try {
1710
- await fs5.access(configPath);
1711
- fileExists = true;
1712
- } catch {
1713
- }
1714
- let existing = {};
1715
- try {
1716
- const raw = await fs5.readFile(configPath, "utf8");
1717
- existing = JSON.parse(raw);
1718
- } catch (err) {
1719
- if (fileExists) {
1720
- throw new Error(
1721
- `Refusing to overwrite corrupt config at ${configPath} (${err.message}). Fix or move the file aside before retrying.`,
1722
- { cause: err }
1723
- );
1954
+ }
1955
+ function wrapWords(value, firstWidth, continuationWidth = firstWidth) {
1956
+ const words = value.trim().split(/\s+/).filter(Boolean);
1957
+ const lines = [];
1958
+ let line = "";
1959
+ let width = Math.max(1, firstWidth);
1960
+ for (const sourceWord of words) {
1961
+ let word = sourceWord;
1962
+ while (word) {
1963
+ if (line) {
1964
+ if (displayWidth(line) + 1 + displayWidth(word) <= width) {
1965
+ line += ` ${word}`;
1966
+ word = "";
1967
+ } else {
1968
+ lines.push(line);
1969
+ line = "";
1970
+ width = Math.max(1, continuationWidth);
1971
+ }
1972
+ continue;
1973
+ }
1974
+ if (displayWidth(word) <= width) {
1975
+ line = word;
1976
+ word = "";
1977
+ } else {
1978
+ const characters = [...word];
1979
+ lines.push(characters.slice(0, width).join(""));
1980
+ word = characters.slice(width).join("");
1981
+ width = Math.max(1, continuationWidth);
1982
+ }
1724
1983
  }
1725
- existing = {};
1726
1984
  }
1727
- existing.yolo = choices.yolo;
1728
- existing.launch = {
1729
- mode: choices.mode,
1730
- autonomy: choices.autonomy
1731
- };
1732
- await atomicWrite3(configPath, JSON.stringify(existing, null, 2), { mode: 384 });
1985
+ if (line) lines.push(line);
1986
+ return lines;
1733
1987
  }
1734
-
1735
- // src/pre-launch/project-check.ts
1736
- import * as fs6 from "node:fs/promises";
1737
- import * as path7 from "node:path";
1738
- import { color as color7, toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
1739
- var MANIFESTS = [
1740
- "package.json",
1741
- "pyproject.toml",
1742
- "Cargo.toml",
1743
- "go.mod",
1744
- "Makefile",
1745
- "pom.xml",
1746
- "build.gradle",
1747
- "build.gradle.kts",
1748
- "composer.json",
1749
- "Gemfile"
1750
- ];
1751
- async function detectProjectKind(projectRoot) {
1752
- try {
1753
- await fs6.access(path7.join(projectRoot, ".wrongstack", "AGENTS.md"));
1754
- return "initialized";
1755
- } catch {
1756
- }
1757
- for (const m of MANIFESTS) {
1758
- try {
1759
- await fs6.access(path7.join(projectRoot, m));
1760
- return "project";
1761
- } catch {
1762
- }
1988
+ function renderHintLines(item, termWidth) {
1989
+ const inline = `${HINT_INDENT}${item.key} \u2014 ${item.blurb}`;
1990
+ if (displayWidth(inline) <= termWidth) {
1991
+ return [`${HINT_INDENT}${color8.bold(item.key)} ${color8.dim("\u2014")} ${color8.dim(item.blurb)}`];
1763
1992
  }
1764
- return "empty";
1993
+ const lines = [];
1994
+ const keyLines = wrapWords(
1995
+ item.key,
1996
+ termWidth - displayWidth(HINT_INDENT),
1997
+ termWidth - displayWidth(HINT_CONTINUATION_INDENT)
1998
+ );
1999
+ keyLines.forEach((line, index) => {
2000
+ const indent = index === 0 ? HINT_INDENT : HINT_CONTINUATION_INDENT;
2001
+ lines.push(`${indent}${color8.bold(line)}`);
2002
+ });
2003
+ const blurbLines = wrapWords(
2004
+ `\u2014 ${item.blurb}`,
2005
+ termWidth - displayWidth(HINT_CONTINUATION_INDENT)
2006
+ );
2007
+ for (const line of blurbLines) lines.push(`${HINT_CONTINUATION_INDENT}${color8.dim(line)}`);
2008
+ return lines;
1765
2009
  }
1766
- async function scaffoldAgentsMd(projectRoot) {
1767
- const dir = path7.join(projectRoot, ".wrongstack");
1768
- const file = path7.join(dir, "AGENTS.md");
1769
- const facts = await detectProjectFacts(projectRoot);
1770
- const body = renderAgentsTemplate(facts);
1771
- await fs6.mkdir(dir, { recursive: true });
1772
- await fs6.writeFile(file, body, "utf8");
1773
- return file;
2010
+ function renderHeaderLines(group, groupIndex, termWidth) {
2011
+ const progress = `${groupIndex + 1}/${GROUPS.length}`;
2012
+ const plain = ` ${group.title} ${progress}`;
2013
+ if (displayWidth(plain) <= termWidth) {
2014
+ return [
2015
+ ` ${color8.cyan("\u25C7")} ${color8.bold(group.title)} ${color8.dim(progress)}`,
2016
+ ` ${color8.dim("\u2500".repeat(Math.min(termWidth - 2, 48)))}`
2017
+ ];
2018
+ }
2019
+ return [
2020
+ ` ${color8.cyan("\u25C7")} ${color8.bold(group.title)}`,
2021
+ ` ${color8.dim("\u2500".repeat(Math.min(termWidth - 2, 48)))}`
2022
+ ];
1774
2023
  }
1775
- async function runProjectCheck(opts) {
1776
- const { projectRoot, cwd, renderer, reader } = opts;
1777
- const kind = await detectProjectKind(projectRoot);
1778
- if (kind === "initialized") {
1779
- renderer.write(
1780
- `
1781
- ${color7.green("\u2713")} Project initialized ${color7.dim(`(${path7.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
1782
- `
1783
- );
1784
- return true;
2024
+ function renderFooterLines(termWidth) {
2025
+ const footer = "/help lists everything \xB7 hide with --no-hints";
2026
+ if (displayWidth(` ${footer}`) <= termWidth) {
2027
+ return [
2028
+ ` ${color8.dim("\u2500".repeat(Math.min(termWidth - 2, 48)))}`,
2029
+ ` ${color8.dim(`${color8.bold("/help")} lists everything \xB7 hide with ${color8.bold("--no-hints")}`)}`
2030
+ ];
1785
2031
  }
1786
- if (kind === "project") {
1787
- renderer.write(
1788
- `
1789
- ${color7.amber("\u25CF")} Project detected ${color7.dim(`(${projectRoot})`)} but ${color7.bold(".wrongstack/AGENTS.md")} is missing.
1790
- `
1791
- );
1792
- const answer2 = (await reader.readLine(
1793
- ` ${color7.amber("?")} Scaffold ${color7.bold("AGENTS.md")} now? ${color7.dim("[y/N/q]")} `
1794
- )).trim().toLowerCase();
1795
- if (answer2 === "q") {
1796
- renderer.write(color7.dim(" Cancelled.\n"));
1797
- return false;
1798
- }
1799
- if (answer2 === "y" || answer2 === "yes") {
2032
+ return [...wrapWords(footer, termWidth - 2).map((line) => ` ${color8.dim(line)}`)];
2033
+ }
2034
+ async function pickGroupIndex(opts) {
2035
+ if (typeof opts.groupIndex === "number") return wrap(opts.groupIndex);
2036
+ if (opts.cursorFile) {
2037
+ try {
2038
+ let current = 0;
1800
2039
  try {
1801
- const file = await scaffoldAgentsMd(projectRoot);
1802
- renderer.write(` ${color7.green("\u2713")} Wrote ${color7.dim(file)}
1803
- `);
1804
- } catch (err) {
1805
- renderer.writeError(`Failed to scaffold AGENTS.md: ${toErrorMessage2(err)}`);
2040
+ const raw = await fs7.readFile(opts.cursorFile, "utf8");
2041
+ const parsed = Number.parseInt(raw.trim(), 10);
2042
+ if (Number.isFinite(parsed)) current = wrap(parsed);
2043
+ } catch {
1806
2044
  }
2045
+ await fs7.mkdir(path7.dirname(opts.cursorFile), { recursive: true });
2046
+ await fs7.writeFile(opts.cursorFile, String(wrap(current + 1)));
2047
+ return current;
2048
+ } catch {
1807
2049
  }
1808
- return true;
1809
2050
  }
1810
- const gitDir = path7.join(projectRoot, ".git");
1811
- let hasGit = false;
1812
- try {
1813
- await fs6.access(gitDir);
1814
- hasGit = true;
1815
- } catch {
1816
- }
1817
- if (!hasGit) {
1818
- renderer.write(
1819
- `
1820
- ${color7.dim("\u25CB")} ${color7.dim(`No project manifest in ${projectRoot} \u2014 running in a scratch directory.`)}
1821
- `
1822
- );
1823
- const answer2 = (await reader.readLine(
1824
- ` ${color7.amber("?")} No git repo found. ${color7.bold("Initialize git?")} ${color7.dim("[y/N/q]")} `
1825
- )).trim().toLowerCase();
1826
- if (answer2 === "q") {
1827
- renderer.write(color7.dim(" Cancelled.\n"));
1828
- return false;
1829
- }
1830
- if (answer2 === "y" || answer2 === "yes") {
1831
- try {
1832
- const { spawn: spawn2 } = await import("node:child_process");
1833
- await new Promise((resolve2, reject) => {
1834
- const child = spawn2("git", ["init"], {
1835
- cwd,
1836
- signal: AbortSignal.timeout(1e4),
1837
- windowsHide: true
1838
- });
1839
- child.on("error", reject);
1840
- child.on(
1841
- "close",
1842
- (code) => code === 0 ? resolve2() : reject(new Error(`git init failed with ${code}`))
1843
- );
1844
- });
1845
- renderer.write(` ${color7.green("\u2713")} Git repository initialized
1846
- `);
1847
- } catch (err) {
1848
- renderer.writeError(`git init failed: ${toErrorMessage2(err)}
2051
+ return Math.floor(Math.random() * GROUPS.length);
2052
+ }
2053
+ async function printLaunchHints(renderer, flags, opts = {}) {
2054
+ if (shouldSuppress(flags)) return;
2055
+ const idx = await pickGroupIndex(opts);
2056
+ const group = GROUPS[idx];
2057
+ if (!group) return;
2058
+ const termWidth = Math.max(
2059
+ MIN_TERM_WIDTH,
2060
+ Math.floor(opts.termWidth ?? process.stdout.columns ?? DEFAULT_TERM_WIDTH)
2061
+ );
2062
+ const lines = [];
2063
+ lines.push("");
2064
+ lines.push(...renderHeaderLines(group, idx, termWidth));
2065
+ for (const item of group.items) lines.push(...renderHintLines(item, termWidth));
2066
+ lines.push("");
2067
+ lines.push(...renderFooterLines(termWidth));
2068
+ lines.push("");
2069
+ renderer.write(`${lines.join("\n")}
1849
2070
  `);
1850
- }
1851
- }
1852
- } else {
1853
- renderer.write(
1854
- `
1855
- ${color7.dim("\u25CB")} ${color7.dim(`No project manifest in ${projectRoot} \u2014 running in a scratch directory.`)}
1856
- `
1857
- );
1858
- }
1859
- const answer = (await reader.readLine(` ${color7.amber("?")} Continue anyway? ${color7.dim("[Y/n/q]")} `)).trim().toLowerCase();
1860
- if (answer === "q" || answer === "n" || answer === "no") {
1861
- renderer.write(color7.dim(" Cancelled.\n"));
1862
- return false;
1863
- }
1864
- return true;
1865
2071
  }
1866
2072
 
1867
2073
  // src/renderer.ts
1868
- import { color as color8 } from "@wrongstack/core/utils";
2074
+ import { color as color9 } from "@wrongstack/core/utils";
1869
2075
  var TerminalRenderer = class _TerminalRenderer {
1870
2076
  out;
1871
2077
  err;
@@ -1978,7 +2184,7 @@ var TerminalRenderer = class _TerminalRenderer {
1978
2184
  if (!this.lineStart) this.out.write("\n");
1979
2185
  const arrow = theme.primary("\u2192");
1980
2186
  const display = formatInputSummary(input);
1981
- this.out.write(`${arrow} ${theme.bold(name)}${display ? color8.dim(` ${display}`) : ""}
2187
+ this.out.write(`${arrow} ${theme.bold(name)}${display ? color9.dim(` ${display}`) : ""}
1982
2188
  `);
1983
2189
  this.lineStart = true;
1984
2190
  }
@@ -1995,7 +2201,7 @@ var TerminalRenderer = class _TerminalRenderer {
1995
2201
  if (isError) {
1996
2202
  const firstLine = txt.split("\n")[0] ?? "";
1997
2203
  const truncated = firstLine.length > 200 ? `${firstLine.slice(0, 197)}\u2026` : firstLine;
1998
- this.out.write(` ${prefix} ${color8.dim(truncated)}
2204
+ this.out.write(` ${prefix} ${color9.dim(truncated)}
1999
2205
  `);
2000
2206
  this.lineStart = true;
2001
2207
  return;
@@ -2003,7 +2209,7 @@ var TerminalRenderer = class _TerminalRenderer {
2003
2209
  const isEditLike = name === "edit" || name === "write";
2004
2210
  const diff = extractDiff(content);
2005
2211
  if (isEditLike && diff) {
2006
- this.out.write(` ${prefix} ${color8.dim(summarize(content, name))}
2212
+ this.out.write(` ${prefix} ${color9.dim(summarize(content, name))}
2007
2213
  `);
2008
2214
  const rendered = renderDiff(diff).split("\n").map((l) => ` ${l}`).join("\n");
2009
2215
  this.out.write(`${rendered}
@@ -2012,9 +2218,9 @@ var TerminalRenderer = class _TerminalRenderer {
2012
2218
  return;
2013
2219
  }
2014
2220
  const summary = summarize(content, name);
2015
- const isReadLike = name === "read" || name === "grep" || name === "glob" || name === "bash";
2221
+ const isReadLike = name === "read" || name === "grep" || name === "glob" || name === "bash" || name === "exec";
2016
2222
  if (renderMode === "simple") {
2017
- this.out.write(` ${prefix} ${color8.dim(summary || compactSingleLine(bodyText))}
2223
+ this.out.write(` ${prefix} ${color9.dim(summary || compactSingleLine(bodyText))}
2018
2224
  `);
2019
2225
  this.lineStart = true;
2020
2226
  return;
@@ -2023,16 +2229,16 @@ var TerminalRenderer = class _TerminalRenderer {
2023
2229
  const lines = bodyText.split("\n");
2024
2230
  const head = lines.slice(0, previewLines).map((l) => l.replace(/\s+$/, ""));
2025
2231
  const moreCount = Math.max(0, lines.length - head.length);
2026
- this.out.write(` ${prefix} ${color8.dim(summary)}
2232
+ this.out.write(` ${prefix} ${color9.dim(summary)}
2027
2233
  `);
2028
2234
  for (const l of head) {
2029
2235
  const capped = l.length > 200 ? `${l.slice(0, 197)}\u2026` : l;
2030
- this.out.write(` ${color8.dim(capped)}
2236
+ this.out.write(` ${color9.dim(capped)}
2031
2237
  `);
2032
2238
  }
2033
2239
  if (moreCount > 0) {
2034
2240
  this.out.write(
2035
- ` ${color8.dim(`+${moreCount} more line${moreCount === 1 ? "" : "s"}`)}
2241
+ ` ${color9.dim(`+${moreCount} more line${moreCount === 1 ? "" : "s"}`)}
2036
2242
  `
2037
2243
  );
2038
2244
  }
@@ -2152,7 +2358,7 @@ function renderMarkdown(s) {
2152
2358
  return theme.primary(theme.bold(`${hashes} ${text}`));
2153
2359
  });
2154
2360
  out = out.replace(/```([a-zA-Z0-9_+-]*)\n([\s\S]*?)```/g, (_m, _lang, code) => {
2155
- return color8.gray(`
2361
+ return color9.gray(`
2156
2362
  \u250C\u2500
2157
2363
  ${code.replace(/^/gm, "\u2502 ")}\u2514\u2500`);
2158
2364
  });
@@ -2160,7 +2366,7 @@ ${code.replace(/^/gm, "\u2502 ")}\u2514\u2500`);
2160
2366
  out = out.replace(/\*\*([^*]+)\*\*/g, (_m, text) => theme.bold(text));
2161
2367
  out = out.replace(
2162
2368
  /(^|[^*])\*([^*\n]+)\*([^*]|$)/g,
2163
- (_m, l, t, r) => `${l}${color8.italic(t)}${r}`
2369
+ (_m, l, t, r) => `${l}${color9.italic(t)}${r}`
2164
2370
  );
2165
2371
  return out;
2166
2372
  }
@@ -2204,6 +2410,13 @@ function extractBodyText(value, name) {
2204
2410
  const o = v;
2205
2411
  if (name === "read" && typeof o["text"] === "string") return o["text"];
2206
2412
  if (name === "bash") {
2413
+ const output = typeof o["output"] === "string" ? o["output"] : "";
2414
+ const error = typeof o["error"] === "string" ? o["error"] : "";
2415
+ if (output && error) return `${output}
2416
+ ${error}`;
2417
+ return output || error || "";
2418
+ }
2419
+ if (name === "exec") {
2207
2420
  const stdout = typeof o["stdout"] === "string" ? o["stdout"] : "";
2208
2421
  const stderr = typeof o["stderr"] === "string" ? o["stderr"] : "";
2209
2422
  if (stdout && stderr) return `${stdout}
@@ -2261,6 +2474,9 @@ function summarize(value, name) {
2261
2474
  if (name === "bash") {
2262
2475
  return summarizeBash(o);
2263
2476
  }
2477
+ if (name === "exec") {
2478
+ return summarizeExec(o);
2479
+ }
2264
2480
  if (typeof o["count"] === "number") {
2265
2481
  return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
2266
2482
  }
@@ -2285,13 +2501,29 @@ function summarizeRead(o) {
2285
2501
  return summary;
2286
2502
  }
2287
2503
  function summarizeBash(o) {
2288
- const exit = typeof o["exitCode"] === "number" ? o["exitCode"] : typeof o["exit_code"] === "number" ? o["exit_code"] : void 0;
2504
+ const exit = typeof o["exit_code"] === "number" ? o["exit_code"] : void 0;
2505
+ const output = typeof o["output"] === "string" ? o["output"] : "";
2506
+ const lines = output ? output.split("\n").length : 0;
2507
+ const parts = [];
2508
+ if (exit !== void 0) parts.push(`exit=${exit}`);
2509
+ else if (o["exit_code"] === null) parts.push("backgrounded");
2510
+ if (o["timed_out"] === true) parts.push("timed out");
2511
+ if (lines > 0) parts.push(`${lines} output line${lines === 1 ? "" : "s"}`);
2512
+ if (output.length > 0) parts.push(`${output.length}B`);
2513
+ return parts.join(" ");
2514
+ }
2515
+ function summarizeExec(o) {
2516
+ const command = typeof o["command"] === "string" ? o["command"] : "";
2517
+ const args = Array.isArray(o["args"]) ? o["args"].filter((a) => typeof a === "string") : [];
2518
+ const exit = typeof o["exitCode"] === "number" ? o["exitCode"] : void 0;
2289
2519
  const stdout = typeof o["stdout"] === "string" ? o["stdout"] : "";
2290
2520
  const stderr = typeof o["stderr"] === "string" ? o["stderr"] : "";
2291
2521
  const outLines = stdout ? stdout.split("\n").length : 0;
2292
2522
  const errLines = stderr ? stderr.split("\n").length : 0;
2293
2523
  const totalBytes = stdout.length + stderr.length;
2294
2524
  const parts = [];
2525
+ const invocation = [command, ...args].filter(Boolean).join(" ");
2526
+ if (invocation) parts.push(`$ ${invocation}`);
2295
2527
  if (exit !== void 0) parts.push(`exit=${exit}`);
2296
2528
  if (outLines > 0) parts.push(`${outLines} stdout line${outLines === 1 ? "" : "s"}`);
2297
2529
  if (errLines > 0) parts.push(`${errLines} stderr line${errLines === 1 ? "" : "s"}`);
@@ -2304,49 +2536,6 @@ function compactSingleLine(text) {
2304
2536
  return first.length > 200 ? `${first.slice(0, 197)}\u2026` : first;
2305
2537
  }
2306
2538
 
2307
- // src/subcommands/index.ts
2308
- var loaders = {
2309
- acp: async () => (await import("./acp-5ZLGFHWP.js")).acpCmd,
2310
- init: async () => (await import("./init-E2NDDOHI.js")).initCmd,
2311
- auth: async () => (await import("./auth-K7CYVVKH.js")).authCmd,
2312
- update: async () => (await import("./update-VZSOZGYC.js")).updateCmd,
2313
- sessions: async () => (await import("./sessions-config-YX2ZPAFM.js")).sessionsCmd,
2314
- config: async () => (await import("./sessions-config-YX2ZPAFM.js")).configCmd,
2315
- rewind: async () => (await import("./rewind-7RE6ONIC.js")).rewindCmd,
2316
- replay: async () => (await import("./replay-WBRMEWQ5.js")).replayCmd,
2317
- audit: async () => (await import("./audit-BBR22QH3.js")).auditCmd,
2318
- tools: async () => (await import("./tools-skills-UNKLOB7M.js")).toolsCmd,
2319
- skills: async () => (await import("./tools-skills-UNKLOB7M.js")).skillsCmd,
2320
- providers: async () => (await import("./providers-models-VV74TRQ2.js")).providersCmd,
2321
- models: async () => (await import("./providers-models-VV74TRQ2.js")).modelsCmd,
2322
- mcp: async () => (await import("./mcp-MSARYOPN.js")).mcpCmd,
2323
- plugin: async () => (await import("./plugin-usage-PTEETL3J.js")).pluginCmd,
2324
- plugins: async () => (await import("./plugin-usage-PTEETL3J.js")).pluginCmd,
2325
- diag: async () => (await import("./diag-doctor-ZF5BF2MK.js")).diagCmd,
2326
- doctor: async () => (await import("./diag-doctor-ZF5BF2MK.js")).doctorCmd,
2327
- export: async () => (await import("./export-CCUXRJOU.js")).exportCmd,
2328
- usage: async () => (await import("./plugin-usage-PTEETL3J.js")).usageCmd,
2329
- version: async () => (await import("./version-help-ASKQYXDJ.js")).versionCmd,
2330
- help: async () => (await import("./version-help-ASKQYXDJ.js")).helpCmd,
2331
- projects: async () => (await import("./projects-XE76NFNZ.js")).projectsCmd,
2332
- modeldiag: async () => (await import("./modeldiag-SIVUOX2Y.js")).modeldiagCmd,
2333
- quick: async () => (await import("./quick-VIO2BEHS.js")).quickCmd,
2334
- bench: async () => (await import("./bench-XNDUJOSZ.js")).benchCmd,
2335
- chronicle: async () => (await import("./chronicle-63MFCZN6.js")).chronicleCmd,
2336
- hq: async () => (await import("./hq-5G6D5YIU.js")).hqCmd,
2337
- mailbox: async () => (await import("./mailbox-serve-V3QIJBDH.js")).mailboxServeCmd,
2338
- permissions: async () => (await import("./permissions-2PWBL4YU.js")).permissionsCmd,
2339
- project: async () => (await import("./project-BLQ3ALVL.js")).projectCmd,
2340
- governance: async () => (await import("./governance-V2Z42RBE.js")).governanceCmd
2341
- };
2342
- var subcommands = Object.fromEntries(
2343
- Object.entries(loaders).map(([name, load]) => [
2344
- name,
2345
- async (args, deps) => (await load())(args, deps)
2346
- ])
2347
- );
2348
- var subcommandNames = Object.keys(loaders);
2349
-
2350
2539
  // src/boot.ts
2351
2540
  var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
2352
2541
  function resolveBundledOverlayFile() {
@@ -2555,8 +2744,8 @@ async function boot(argv) {
2555
2744
  if (isHomeDirectory(cwd, userHome)) {
2556
2745
  writeErr(
2557
2746
  `
2558
- ${color9.red(color9.bold("\u26A0 This is not a working directory."))}
2559
- ${color9.red("Please open wstack in a project folder.")}
2747
+ ${color10.red(color10.bold("\u26A0 This is not a working directory."))}
2748
+ ${color10.red("Please open wstack in a project folder.")}
2560
2749
 
2561
2750
  `
2562
2751
  );
@@ -2617,7 +2806,7 @@ async function boot(argv) {
2617
2806
  const savedStatus = await validateSavedProviderModel(config, modelsRegistry);
2618
2807
  renderer.write(
2619
2808
  `
2620
- ${color9.dim("Last settings:")} ${color9.bold(savedProvider)} / ${color9.bold(savedModel)}
2809
+ ${color10.dim("Last settings:")} ${color10.bold(savedProvider)} / ${color10.bold(savedModel)}
2621
2810
  `
2622
2811
  );
2623
2812
  if (!savedStatus.ok) {
@@ -2627,11 +2816,11 @@ async function boot(argv) {
2627
2816
  );
2628
2817
  } else {
2629
2818
  const answer = (await reader.readLine(
2630
- ` ${color9.amber("?")} Continue with these? ${color9.dim("[Y/n/q]")} ${color9.dim("(auto Y in 5s)")} `,
2819
+ ` ${color10.amber("?")} Continue with these? ${color10.dim("[Y/n/q]")} ${color10.dim("(auto Y in 5s)")} `,
2631
2820
  { timeoutMs: 5e3, defaultAnswer: "y" }
2632
2821
  )).trim().toLowerCase();
2633
2822
  if (answer === "q") {
2634
- renderer.write(color9.dim(" Goodbye!\n"));
2823
+ renderer.write(color10.dim(" Goodbye!\n"));
2635
2824
  await reader.close();
2636
2825
  return 0;
2637
2826
  }
@@ -2639,7 +2828,7 @@ async function boot(argv) {
2639
2828
  skipPicker = true;
2640
2829
  renderer.write(
2641
2830
  `
2642
- ${color9.green("\u25B6")} ${color9.bold(savedProvider)} / ${color9.bold(savedModel)}
2831
+ ${color10.green("\u25B6")} ${color10.bold(savedProvider)} / ${color10.bold(savedModel)}
2643
2832
 
2644
2833
  `
2645
2834
  );
@@ -2702,6 +2891,30 @@ async function boot(argv) {
2702
2891
  flags["no-tui"] = true;
2703
2892
  }
2704
2893
  if (isInteractiveTTY) {
2894
+ const promptMenu = await maybeRunSystemPromptMenu({
2895
+ isInteractiveTTY,
2896
+ flags,
2897
+ renderer,
2898
+ reader,
2899
+ profileConfigPath,
2900
+ paths: {
2901
+ globalDir: wpaths.globalInstructions,
2902
+ projectDir: wpaths.inProjectInstructions
2903
+ }
2904
+ });
2905
+ if (promptMenu.aborted) {
2906
+ await reader.close();
2907
+ return 0;
2908
+ }
2909
+ if (promptMenu.changed && promptMenu.variant) {
2910
+ config = patchConfig(config, { systemPrompt: { variant: promptMenu.variant } });
2911
+ }
2912
+ if (promptMenu.persistError) {
2913
+ renderer.writeWarning(
2914
+ `Could not save system prompt variant to config: ${toErrorMessage3(promptMenu.persistError)}
2915
+ `
2916
+ );
2917
+ }
2705
2918
  let modePinned;
2706
2919
  if (flags["no-tui"]) modePinned = "repl";
2707
2920
  else if (flags["tui"]) modePinned = "tui";
@@ -2750,8 +2963,8 @@ async function boot(argv) {
2750
2963
  if (shouldPrintYoloNotice(lastChoices, yoloPinned, choices.yolo)) {
2751
2964
  writeErr(
2752
2965
  `
2753
- ${color9.yellow("YOLO is on")}: non-denied tool calls, including shell and file writes, run without confirmation.
2754
- ${color9.dim("Explicit deny rules still apply. Use")} --no-yolo ${color9.dim("or")} /yolo off ${color9.dim("to require prompts.")}
2966
+ ${color10.yellow("YOLO is on")}: non-denied tool calls, including shell and file writes, run without confirmation.
2967
+ ${color10.dim("Explicit deny rules still apply. Use")} --no-yolo ${color10.dim("or")} /yolo off ${color10.dim("to require prompts.")}
2755
2968
 
2756
2969
  `
2757
2970
  );
@@ -2810,17 +3023,17 @@ async function checkGitInCwd(opts) {
2810
3023
  const cwdGit = path8.join(cwd, ".git");
2811
3024
  let hasCwdGit = false;
2812
3025
  try {
2813
- await fs7.access(cwdGit);
3026
+ await fs8.access(cwdGit);
2814
3027
  hasCwdGit = true;
2815
3028
  } catch {
2816
3029
  }
2817
3030
  if (!hasCwdGit) {
2818
3031
  renderer.write(
2819
3032
  `
2820
- ${color9.amber("\u25CB")} This folder has no ${color9.bold(".git")} repository.
3033
+ ${color10.amber("\u25CB")} This folder has no ${color10.bold(".git")} repository.
2821
3034
  `
2822
3035
  );
2823
- const answer = (await reader.readLine(` ${color9.amber("?")} Initialize one here? ${color9.dim("[y/N]")} `)).trim().toLowerCase();
3036
+ const answer = (await reader.readLine(` ${color10.amber("?")} Initialize one here? ${color10.dim("[y/N]")} `)).trim().toLowerCase();
2824
3037
  if (answer === "y" || answer === "yes") {
2825
3038
  try {
2826
3039
  const { spawn: spawn2 } = await import("node:child_process");
@@ -2836,7 +3049,7 @@ async function checkGitInCwd(opts) {
2836
3049
  (code) => code === 0 ? resolve2() : reject(new Error(`git init failed with ${code}`))
2837
3050
  );
2838
3051
  });
2839
- renderer.write(` ${color9.green("\u2713")} Git repository initialized
3052
+ renderer.write(` ${color10.green("\u2713")} Git repository initialized
2840
3053
  `);
2841
3054
  hasCwdGit = true;
2842
3055
  } catch (err) {
@@ -2848,9 +3061,9 @@ async function checkGitInCwd(opts) {
2848
3061
  const parentDir = path8.dirname(cwd);
2849
3062
  if (parentDir !== cwd) {
2850
3063
  try {
2851
- await fs7.access(path8.join(parentDir, ".git"));
3064
+ await fs8.access(path8.join(parentDir, ".git"));
2852
3065
  renderer.write(
2853
- ` ${color9.dim("\u2139")} A ${color9.bold(".git")} repo exists in the parent directory: ${color9.dim(parentDir)}
3066
+ ` ${color10.dim("\u2139")} A ${color10.bold(".git")} repo exists in the parent directory: ${color10.dim(parentDir)}
2854
3067
  `
2855
3068
  );
2856
3069
  } catch {
@@ -3089,7 +3302,7 @@ async function initializeCli(argv) {
3089
3302
  async function main(argv) {
3090
3303
  const cliCtx = await initializeCli(argv);
3091
3304
  if (typeof cliCtx === "number") return cliCtx;
3092
- const { runInteractive } = await import("./cli-main-W3T56YCY.js");
3305
+ const { runInteractive } = await import("./cli-main-DHNFGP3Z.js");
3093
3306
  return runInteractive(cliCtx);
3094
3307
  }
3095
3308