@wrongstack/cli 1.0.32 → 1.0.35
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/data/providers.json +322 -2
- package/dist/{acp-EDGUWU7N.js → acp-T77EAEDB.js} +469 -465
- package/dist/{auth-SUHKFFO4.js → auth-M6LXZTDA.js} +4 -4
- package/dist/auth-menu/openai-codex-oauth.d.ts +1 -1
- package/dist/auth-menu/panel-provider-edits.d.ts +15 -0
- package/dist/auth-menu/panel-service.d.ts +1 -1
- package/dist/auto-discover-providers-JX346GOM.js +7 -0
- package/dist/boot/governance-evidence-candidate.d.ts +1 -2
- package/dist/boot/system-prompt-builder.d.ts +1 -1
- package/dist/boot/tui-project-spawn.d.ts +0 -1
- package/dist/boot/tui-session-resume-contracts.d.ts +95 -0
- package/dist/boot/tui-session-resume.d.ts +2 -88
- package/dist/boot/tui-settings-read.d.ts +3 -0
- package/dist/{boot-auto-thin-K2DHHYIW.js → boot-auto-thin-YMAZI3QL.js} +2 -2
- package/dist/boot-launch-choices.d.ts +22 -0
- package/dist/boot-provider-gate.d.ts +28 -0
- package/dist/boot-subcommand-dispatch.d.ts +24 -0
- package/dist/boot.d.ts +1 -1
- package/dist/chimera-cascade-evidence.d.ts +1 -1
- package/dist/{chunk-TI7KLHYX.js → chunk-2CMMWRQH.js} +81 -80
- package/dist/{chunk-3LEFXLAW.js → chunk-6SIUTX75.js} +36 -36
- package/dist/{chunk-RQ4UBKYH.js → chunk-6Z6C6ZHH.js} +3 -3
- package/dist/{chunk-5EA6OTLJ.js → chunk-AFOH6CHO.js} +2 -2
- package/dist/{chunk-WHEYHDIE.js → chunk-AKESWZH5.js} +2 -1
- package/dist/{chunk-JHUJUIOH.js → chunk-AT2A7RJZ.js} +4 -16
- package/dist/{chunk-U3SS4NRH.js → chunk-BHXAKTBE.js} +1 -1
- package/dist/{chunk-MPQCHBDJ.js → chunk-CJSHS2K7.js} +5 -1
- package/dist/{chunk-P3GRLV47.js → chunk-GZSKLXYG.js} +208 -141
- package/dist/{chunk-4MDRHMX4.js → chunk-KMU7GF7X.js} +3 -26
- package/dist/{chunk-WXJH4F6K.js → chunk-LDV4ERNV.js} +4 -4
- package/dist/{chunk-LIZXJHZQ.js → chunk-LDXXMPMS.js} +13 -13
- package/dist/{chunk-UQUYBDMG.js → chunk-M4G5FO7F.js} +1 -1
- package/dist/{chunk-CFHWIFXL.js → chunk-PA4VBCXS.js} +25 -33
- package/dist/{chunk-5UZBUV2H.js → chunk-RKU66D2O.js} +6 -4
- package/dist/{chunk-PGNERAZL.js → chunk-RWNI5OWT.js} +2 -2
- package/dist/chunk-TGSPCFBT.js +1 -0
- package/dist/{cli-context-ITHGP2CE.js → cli-context-II57EHD2.js} +1555 -1451
- package/dist/cli-initial-tools.d.ts +54 -0
- package/dist/{cli-main-TQGLKB6D.js → cli-main-YR2WJCPL.js} +38062 -37035
- package/dist/cli-main-boot.d.ts +133 -0
- package/dist/cli-main-orchestration.d.ts +170 -0
- package/dist/cli-main-state.d.ts +19 -0
- package/dist/config-doctor.d.ts +1 -1
- package/dist/{config-transfer-UE37X4B5.js → config-transfer-VOIFY7U4.js} +2 -2
- package/dist/{execution-C2IECWF7.js → execution-IBAZ2EIU.js} +138 -124
- package/dist/fleet/host-director-setup.d.ts +41 -0
- package/dist/fleet/host-spawn-types.d.ts +17 -0
- package/dist/fleet/host.d.ts +2 -1
- package/dist/goal-host-prompts.d.ts +5 -0
- package/dist/goal-host-types.d.ts +94 -0
- package/dist/goal-host.d.ts +2 -91
- package/dist/{hq-KBFDKTDD.js → hq-5OIEKB4V.js} +5 -6
- package/dist/hq-server/handle-types.d.ts +1 -1
- package/dist/hq-server/routes/auth-handlers.d.ts +1 -1
- package/dist/hq-server/routes.d.ts +1 -13
- package/dist/hq-server/utils.d.ts +1 -1
- package/dist/{hq-server-AVNKZ2PV.js → hq-server-DU3ZH3X7.js} +3 -3
- package/dist/{hq-service-FGB7BY2D.js → hq-service-LYZV7EK7.js} +3 -3
- package/dist/index.js +4 -4
- package/dist/live-settings-input.d.ts +2 -0
- package/dist/mailbox-bridge-bootstrap.d.ts +1 -4
- package/dist/{modeldiag-PQILD7TN.js → modeldiag-NEPTLFZW.js} +2 -2
- package/dist/picker-effort.d.ts +32 -0
- package/dist/picker-model-picker.d.ts +13 -2
- package/dist/picker-provider-list.d.ts +66 -0
- package/dist/picker.d.ts +12 -12
- package/dist/{providers-models-W57I3QOA.js → providers-models-37DUV2GW.js} +297 -253
- package/dist/repl-suggestions.d.ts +0 -5
- package/dist/{sdd-runtime-PTOSPYI4.js → sdd-runtime-PF72WHLO.js} +3 -20
- package/dist/services/sdd/task-manager.d.ts +1 -12
- package/dist/services/sdd-runtime.d.ts +3 -4
- package/dist/services/statusline-config.d.ts +0 -2
- package/dist/settings-behavior-sections.d.ts +0 -1
- package/dist/short-circuit-flags-EXJBJ5GT.js +10 -0
- package/dist/{skill-suggest-L2EVWB6R.js → skill-suggest-THANK3LV.js} +1 -1
- package/dist/slash-commands/command-context.d.ts +2 -0
- package/dist/slash-commands/fallback-chain-view.d.ts +3 -0
- package/dist/slash-commands/fallback-ref-validation.d.ts +34 -0
- package/dist/slash-commands/goal-refiner.d.ts +10 -2
- package/dist/slash-commands/goals.d.ts +6 -0
- package/dist/slash-commands/kanban-task-completion.d.ts +17 -0
- package/dist/slash-commands/mcp.d.ts +0 -1
- package/dist/slash-commands/memory-purge-domain-terms.d.ts +5 -0
- package/dist/slash-commands/nextsteps-mode.d.ts +13 -0
- package/dist/slash-commands/session-live-status.d.ts +21 -0
- package/dist/slash-commands/session.d.ts +1 -1
- package/dist/slash-commands/settings-feature-ui-mutations.d.ts +8 -0
- package/dist/slash-commands/settings-hq-autonomy-mutations.d.ts +8 -0
- package/dist/slash-commands/settings-persist-deps.d.ts +10 -0
- package/dist/slash-commands/settings-runtime-mutations.d.ts +12 -0
- package/dist/slash-commands/shadow.d.ts +0 -1
- package/dist/slash-commands/tool-auto-thin-commands.d.ts +15 -0
- package/dist/subcommands/handlers/acp-agents.d.ts +9 -0
- package/dist/subcommands/handlers/acp-server.d.ts +7 -0
- package/dist/subcommands/handlers/hq.d.ts +0 -1
- package/dist/subcommands/handlers/providers-models-custom.d.ts +33 -0
- package/dist/subcommands/handlers/providers-models-visibility.d.ts +29 -0
- package/dist/subcommands/handlers/skill-suggest.d.ts +0 -2
- package/dist/{subcommands-QO7VYTSS.js → subcommands-DTKBXLPU.js} +2 -2
- package/dist/{update-J5WHAFPE.js → update-6OEPAEUV.js} +3 -3
- package/dist/utils/win32-cmd.d.ts +1 -1
- package/dist/{version-help-ATOKGMTK.js → version-help-JQGRPDJW.js} +2 -2
- package/dist/webui-server/host-settings.d.ts +17 -0
- package/dist/webui-server/http-bridge.d.ts +32 -0
- package/dist/webui-server/instance-registration.d.ts +19 -0
- package/dist/webui-server/kanban-services.d.ts +9 -0
- package/dist/webui-server/message-router.d.ts +14 -0
- package/dist/webui-server/prefs-seeding.d.ts +1 -2
- package/dist/webui-server/provider-config.d.ts +0 -1
- package/dist/webui-server/provider-plane.d.ts +14 -0
- package/dist/webui-server/session-agents.d.ts +18 -0
- package/dist/webui-server/session-start-live.d.ts +11 -0
- package/dist/webui-server/stream-coalescer.d.ts +1 -1
- package/dist/webui-server/transport.d.ts +12 -0
- package/dist/{webui-server-GQOVLQS3.js → webui-server-6IC3SLZG.js} +1600 -1435
- package/dist/webui-session-maintenance.d.ts +16 -0
- package/dist/wiring/brain-and-orchestration.d.ts +1 -1
- package/dist/wiring/cli-slash-commands-setup.d.ts +1 -0
- package/dist/wiring/command-host-state.d.ts +11 -2
- package/dist/wiring/dep-watcher-bridge.d.ts +8 -1
- package/dist/wiring/session-command-handlers.d.ts +2 -1
- package/dist/worktree-command-host.d.ts +31 -0
- package/package.json +30 -30
- package/dist/auto-discover-providers-QIL34K3U.js +0 -7
- package/dist/chunk-WHOIR577.js +0 -1
- package/dist/short-circuit-flags-6LB3EGF7.js +0 -10
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
renderDiff,
|
|
18
18
|
resolveLaunchAllowedTools,
|
|
19
19
|
resolveToolRestriction
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-2CMMWRQH.js";
|
|
21
21
|
import {
|
|
22
22
|
theme as theme2
|
|
23
23
|
} from "./chunk-QD544J2B.js";
|
|
@@ -30,24 +30,24 @@ import {
|
|
|
30
30
|
} from "./chunk-HLSHFXTO.js";
|
|
31
31
|
import {
|
|
32
32
|
subcommands
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-LDXXMPMS.js";
|
|
34
34
|
import {
|
|
35
35
|
renderDeepHelp,
|
|
36
36
|
renderFocusedHelp
|
|
37
37
|
} from "./chunk-2EZ2DHOW.js";
|
|
38
38
|
import {
|
|
39
39
|
handleHelpVersionShortCircuit
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-LDV4ERNV.js";
|
|
41
41
|
import {
|
|
42
42
|
DEFAULT_PORT
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-6Z6C6ZHH.js";
|
|
44
44
|
import "./chunk-HXJYHAR4.js";
|
|
45
45
|
import {
|
|
46
46
|
resolveHqPasswordInput
|
|
47
47
|
} from "./chunk-AUQDGU5D.js";
|
|
48
48
|
import {
|
|
49
49
|
normalizeHqPublicOrigin
|
|
50
|
-
} from "./chunk-
|
|
50
|
+
} from "./chunk-AFOH6CHO.js";
|
|
51
51
|
import {
|
|
52
52
|
parseHqIpAllowlist
|
|
53
53
|
} from "./chunk-Q7E3BPDU.js";
|
|
@@ -56,7 +56,7 @@ import {
|
|
|
56
56
|
} from "./chunk-TWSQTQKR.js";
|
|
57
57
|
import {
|
|
58
58
|
discoverAndMergeProviders
|
|
59
|
-
} from "./chunk-
|
|
59
|
+
} from "./chunk-PA4VBCXS.js";
|
|
60
60
|
import {
|
|
61
61
|
addFromCatalog,
|
|
62
62
|
boxBottom,
|
|
@@ -69,12 +69,12 @@ import {
|
|
|
69
69
|
runPicker,
|
|
70
70
|
saveToGlobalConfig,
|
|
71
71
|
theme
|
|
72
|
-
} from "./chunk-
|
|
72
|
+
} from "./chunk-GZSKLXYG.js";
|
|
73
73
|
import "./chunk-YTEOAWOJ.js";
|
|
74
74
|
import {
|
|
75
75
|
isKeylessLocalProvider,
|
|
76
76
|
visibleModelIds
|
|
77
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-RKU66D2O.js";
|
|
78
78
|
import {
|
|
79
79
|
resolveActiveApiKey
|
|
80
80
|
} from "./chunk-ZYACRPLN.js";
|
|
@@ -84,8 +84,8 @@ import {
|
|
|
84
84
|
import "./chunk-4BX2YDTQ.js";
|
|
85
85
|
import {
|
|
86
86
|
runUpdateCommand
|
|
87
|
-
} from "./chunk-
|
|
88
|
-
import "./chunk-
|
|
87
|
+
} from "./chunk-RWNI5OWT.js";
|
|
88
|
+
import "./chunk-BHXAKTBE.js";
|
|
89
89
|
import "./chunk-DAKVBMD4.js";
|
|
90
90
|
import {
|
|
91
91
|
applyNodeEnvDefault,
|
|
@@ -94,19 +94,19 @@ import {
|
|
|
94
94
|
} from "./chunk-WTJDSTYD.js";
|
|
95
95
|
import {
|
|
96
96
|
parseArgs
|
|
97
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-CJSHS2K7.js";
|
|
98
98
|
import "./chunk-DIOODAYM.js";
|
|
99
99
|
import {
|
|
100
100
|
applyAsciiMode
|
|
101
101
|
} from "./chunk-PZQBGUVZ.js";
|
|
102
|
-
import "./chunk-
|
|
102
|
+
import "./chunk-AKESWZH5.js";
|
|
103
103
|
import {
|
|
104
104
|
CLI_VERSION
|
|
105
105
|
} from "./chunk-F5DCFAKI.js";
|
|
106
106
|
|
|
107
107
|
// src/cli-context.ts
|
|
108
108
|
import { TOKENS as TOKENS3 } from "@wrongstack/core/kernel";
|
|
109
|
-
import { writeErr as
|
|
109
|
+
import { writeErr as writeErr4 } from "@wrongstack/core/utils";
|
|
110
110
|
|
|
111
111
|
// src/boot/container-wiring.ts
|
|
112
112
|
import { DefaultPathResolver } from "@wrongstack/core/infrastructure";
|
|
@@ -929,7 +929,7 @@ async function isPortInUse(host, port) {
|
|
|
929
929
|
}
|
|
930
930
|
async function handleHqShortCircuit(flags) {
|
|
931
931
|
if (flags["hq"] !== true) return null;
|
|
932
|
-
const { startHqServer } = await import("./hq-server-
|
|
932
|
+
const { startHqServer } = await import("./hq-server-DU3ZH3X7.js");
|
|
933
933
|
const tunnelRequested = flags["tunnel"] === true;
|
|
934
934
|
const rawPublicUrl = typeof flags["hq-public-url"] === "string" ? flags["hq-public-url"] : process.env.WRONGSTACK_HQ_PUBLIC_URL;
|
|
935
935
|
let publicOrigin;
|
|
@@ -1174,167 +1174,18 @@ ${color3.green("Cloudflare Quick Tunnel ready:")} ${browserUrl}
|
|
|
1174
1174
|
// src/boot.ts
|
|
1175
1175
|
import * as os from "node:os";
|
|
1176
1176
|
import * as path9 from "node:path";
|
|
1177
|
-
import { toErrorMessage as toErrorMessage5 } from "@wrongstack/core/utils";
|
|
1178
|
-
|
|
1179
|
-
// src/boot-provider-selection.ts
|
|
1180
|
-
import * as fs2 from "node:fs/promises";
|
|
1181
|
-
import { createRequire as createRequire4 } from "node:module";
|
|
1182
|
-
import * as path4 from "node:path";
|
|
1183
|
-
import { fileURLToPath } from "node:url";
|
|
1184
|
-
import {
|
|
1185
|
-
color as color4,
|
|
1186
|
-
moduleUrlFor,
|
|
1187
|
-
toErrorMessage,
|
|
1188
|
-
wrongstackPackageJsonPath as wrongstackPackageJsonPath3
|
|
1189
|
-
} from "@wrongstack/core/utils";
|
|
1190
|
-
import { isSetupProvider } from "@wrongstack/providers";
|
|
1191
|
-
function resolveBundledOverlayFile() {
|
|
1192
|
-
try {
|
|
1193
|
-
return fileURLToPath(
|
|
1194
|
-
new URL("../data/providers.json", moduleUrlFor(import.meta.url, "@wrongstack/cli"))
|
|
1195
|
-
);
|
|
1196
|
-
} catch {
|
|
1197
|
-
return void 0;
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
async function validateSavedProviderModel(config, modelsRegistry) {
|
|
1201
|
-
const providerId = config.provider;
|
|
1202
|
-
const modelId = config.model;
|
|
1203
|
-
if (!providerId || !modelId) return { ok: false, reason: "missing provider/model" };
|
|
1204
|
-
if (isSetupProvider(providerId)) return { ok: true };
|
|
1205
|
-
const saved = config.providers?.[providerId];
|
|
1206
|
-
const lookupId = saved?.type && saved.type !== providerId ? saved.type : providerId;
|
|
1207
|
-
const catalogProvider = await modelsRegistry.getProvider(lookupId).catch(() => void 0);
|
|
1208
|
-
if (!catalogProvider && !saved?.family)
|
|
1209
|
-
return { ok: false, reason: `provider "${providerId}" is no longer available` };
|
|
1210
|
-
const hasCredential = (catalogProvider?.envVars ?? saved?.envVars ?? []).some(
|
|
1211
|
-
(envVar) => Boolean(process.env[envVar])
|
|
1212
|
-
) || saved !== void 0 && resolveActiveApiKey(saved) !== void 0 || isKeylessLocalProvider({
|
|
1213
|
-
apiBase: saved?.baseUrl ?? catalogProvider?.apiBase,
|
|
1214
|
-
envVars: saved?.envVars ?? catalogProvider?.envVars
|
|
1215
|
-
});
|
|
1216
|
-
if (!hasCredential) return { ok: false, reason: `provider "${providerId}" has no usable key` };
|
|
1217
|
-
const visible = visibleModelIds(
|
|
1218
|
-
providerId,
|
|
1219
|
-
config,
|
|
1220
|
-
(catalogProvider?.models ?? []).map((m) => m.id),
|
|
1221
|
-
saved
|
|
1222
|
-
);
|
|
1223
|
-
if (visible.length > 0 && !visible.includes(modelId)) {
|
|
1224
|
-
return {
|
|
1225
|
-
ok: false,
|
|
1226
|
-
reason: `model "${modelId}" is no longer available for provider "${providerId}"`
|
|
1227
|
-
};
|
|
1228
|
-
}
|
|
1229
|
-
return { ok: true };
|
|
1230
|
-
}
|
|
1231
|
-
async function autoSelectSavedProvider(config, modelsRegistry) {
|
|
1232
|
-
const providers = config.providers ?? {};
|
|
1233
|
-
const entries = config.provider && providers[config.provider] ? [[config.provider, providers[config.provider]]] : Object.entries(providers);
|
|
1234
|
-
for (const [id, cfg] of entries) {
|
|
1235
|
-
if (!cfg) continue;
|
|
1236
|
-
let model = cfg.models?.[0];
|
|
1237
|
-
if (!model) {
|
|
1238
|
-
const catalogId = cfg.type && cfg.type !== id ? cfg.type : id;
|
|
1239
|
-
const catalog = await modelsRegistry.getProvider(catalogId).catch(() => void 0);
|
|
1240
|
-
model = catalog?.models?.[0]?.id;
|
|
1241
|
-
}
|
|
1242
|
-
if (model) return { provider: id, model };
|
|
1243
|
-
}
|
|
1244
|
-
return void 0;
|
|
1245
|
-
}
|
|
1246
|
-
function resolveBundledSkillsDir2() {
|
|
1247
|
-
try {
|
|
1248
|
-
const req = createRequire4(import.meta.url);
|
|
1249
|
-
const corePkg = wrongstackPackageJsonPath3("@wrongstack/core", (id) => req.resolve(id));
|
|
1250
|
-
return path4.join(path4.dirname(corePkg), "skills");
|
|
1251
|
-
} catch {
|
|
1252
|
-
return void 0;
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
function resolveBundledPromptsDir2() {
|
|
1256
|
-
try {
|
|
1257
|
-
const req = createRequire4(import.meta.url);
|
|
1258
|
-
const corePkg = wrongstackPackageJsonPath3("@wrongstack/core", (id) => req.resolve(id));
|
|
1259
|
-
return path4.join(path4.dirname(corePkg), "data", "prompts");
|
|
1260
|
-
} catch {
|
|
1261
|
-
return void 0;
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
function isHomeDirectory(cwd, userHome) {
|
|
1265
|
-
if (!cwd || !userHome) return false;
|
|
1266
|
-
return path4.resolve(cwd) === path4.resolve(userHome);
|
|
1267
|
-
}
|
|
1268
|
-
function shouldPrintYoloNotice(lastChoices, yoloPinned, yolo) {
|
|
1269
|
-
return !lastChoices && yoloPinned === void 0 && yolo;
|
|
1270
|
-
}
|
|
1271
|
-
async function checkGitInCwd(opts) {
|
|
1272
|
-
const { cwd, renderer, reader } = opts;
|
|
1273
|
-
const cwdGit = path4.join(cwd, ".git");
|
|
1274
|
-
let hasCwdGit = false;
|
|
1275
|
-
try {
|
|
1276
|
-
await fs2.access(cwdGit);
|
|
1277
|
-
hasCwdGit = true;
|
|
1278
|
-
} catch {
|
|
1279
|
-
}
|
|
1280
|
-
if (!hasCwdGit) {
|
|
1281
|
-
renderer.write(
|
|
1282
|
-
`
|
|
1283
|
-
${color4.amber("\u25CB")} This folder has no ${color4.bold(".git")} repository.
|
|
1284
|
-
`
|
|
1285
|
-
);
|
|
1286
|
-
const answer = (await reader.readLine(` ${color4.amber("?")} Initialize one here? ${color4.dim("[y/N]")} `)).trim().toLowerCase();
|
|
1287
|
-
if (answer === "y" || answer === "yes") {
|
|
1288
|
-
try {
|
|
1289
|
-
const { spawn: spawn2 } = await import("node:child_process");
|
|
1290
|
-
await new Promise((resolve3, reject) => {
|
|
1291
|
-
const child = spawn2("git", ["init"], {
|
|
1292
|
-
cwd,
|
|
1293
|
-
signal: AbortSignal.timeout(1e4),
|
|
1294
|
-
windowsHide: true
|
|
1295
|
-
});
|
|
1296
|
-
child.on("error", reject);
|
|
1297
|
-
child.on(
|
|
1298
|
-
"close",
|
|
1299
|
-
(code) => code === 0 ? resolve3() : reject(new Error(`git init failed with ${code}`))
|
|
1300
|
-
);
|
|
1301
|
-
});
|
|
1302
|
-
renderer.write(` ${color4.green("\u2713")} Git repository initialized
|
|
1303
|
-
`);
|
|
1304
|
-
hasCwdGit = true;
|
|
1305
|
-
} catch (err) {
|
|
1306
|
-
renderer.writeError(`git init failed: ${toErrorMessage(err)}
|
|
1307
|
-
`);
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
const parentDir = path4.dirname(cwd);
|
|
1312
|
-
if (parentDir !== cwd) {
|
|
1313
|
-
try {
|
|
1314
|
-
await fs2.access(path4.join(parentDir, ".git"));
|
|
1315
|
-
renderer.write(
|
|
1316
|
-
` ${color4.dim("\u2139")} A ${color4.bold(".git")} repo exists in the parent directory: ${color4.dim(parentDir)}
|
|
1317
|
-
`
|
|
1318
|
-
);
|
|
1319
|
-
} catch {
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
|
|
1324
|
-
// src/boot.ts
|
|
1325
1177
|
import { DefaultLogger } from "@wrongstack/core/infrastructure";
|
|
1326
|
-
import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
|
|
1327
1178
|
import { DefaultModelsRegistry, startCatalog } from "@wrongstack/core/models";
|
|
1328
|
-
import {
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1179
|
+
import {
|
|
1180
|
+
color as color13,
|
|
1181
|
+
isStdinTTY as isStdinTTY3,
|
|
1182
|
+
toErrorMessage as toErrorMessage7,
|
|
1183
|
+
writeErr as writeErr3
|
|
1184
|
+
} from "@wrongstack/core/utils";
|
|
1334
1185
|
|
|
1335
1186
|
// src/boot/append-system-prompt.ts
|
|
1336
|
-
import * as
|
|
1337
|
-
import * as
|
|
1187
|
+
import * as fs2 from "node:fs/promises";
|
|
1188
|
+
import * as path4 from "node:path";
|
|
1338
1189
|
async function resolveAppendedSystemPrompt(flags, cwd) {
|
|
1339
1190
|
const parts = [];
|
|
1340
1191
|
const inline = flags["append-system-prompt"];
|
|
@@ -1344,10 +1195,10 @@ async function resolveAppendedSystemPrompt(flags, cwd) {
|
|
|
1344
1195
|
throw new Error("--append-system-prompt-file needs a path");
|
|
1345
1196
|
}
|
|
1346
1197
|
if (typeof file === "string" && file.trim()) {
|
|
1347
|
-
const resolved =
|
|
1198
|
+
const resolved = path4.resolve(cwd, file.trim());
|
|
1348
1199
|
let text;
|
|
1349
1200
|
try {
|
|
1350
|
-
text = await
|
|
1201
|
+
text = await fs2.readFile(resolved, "utf8");
|
|
1351
1202
|
} catch (err) {
|
|
1352
1203
|
const reason = err.code ?? String(err);
|
|
1353
1204
|
throw new Error(`--append-system-prompt-file: cannot read ${resolved} (${reason})`);
|
|
@@ -1358,9 +1209,9 @@ async function resolveAppendedSystemPrompt(flags, cwd) {
|
|
|
1358
1209
|
}
|
|
1359
1210
|
|
|
1360
1211
|
// src/boot/config-backup-recovery.ts
|
|
1361
|
-
import * as
|
|
1362
|
-
import * as
|
|
1363
|
-
import { atomicWrite as atomicWrite2, backupConfigFile, color as
|
|
1212
|
+
import * as fs3 from "node:fs/promises";
|
|
1213
|
+
import * as path5 from "node:path";
|
|
1214
|
+
import { atomicWrite as atomicWrite2, backupConfigFile, color as color4, toErrorMessage } from "@wrongstack/core/utils";
|
|
1364
1215
|
function isRecord(value) {
|
|
1365
1216
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1366
1217
|
}
|
|
@@ -1371,7 +1222,7 @@ function providerIds(value) {
|
|
|
1371
1222
|
}
|
|
1372
1223
|
async function defaultProfileProvidersAreEmpty(profilePath) {
|
|
1373
1224
|
try {
|
|
1374
|
-
const parsed = JSON.parse(await
|
|
1225
|
+
const parsed = JSON.parse(await fs3.readFile(profilePath, "utf8"));
|
|
1375
1226
|
return providerIds(parsed).length === 0;
|
|
1376
1227
|
} catch {
|
|
1377
1228
|
return false;
|
|
@@ -1394,7 +1245,7 @@ function profileSnapshotTimestamp(fileName, fallback) {
|
|
|
1394
1245
|
);
|
|
1395
1246
|
}
|
|
1396
1247
|
async function findLatestProviderBackup(globalRoot, profileName = "default") {
|
|
1397
|
-
const directory =
|
|
1248
|
+
const directory = path5.join(globalRoot, "config-history");
|
|
1398
1249
|
const escapedProfile = profileName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1399
1250
|
const filePattern = new RegExp(
|
|
1400
1251
|
`^profiles-${escapedProfile}-config-\\d{4}-\\d{2}-\\d{2}T\\d{2}-\\d{2}-\\d{2}-\\d{3}\\.json$`,
|
|
@@ -1403,7 +1254,7 @@ async function findLatestProviderBackup(globalRoot, profileName = "default") {
|
|
|
1403
1254
|
const candidates = [];
|
|
1404
1255
|
let entries;
|
|
1405
1256
|
try {
|
|
1406
|
-
entries = await
|
|
1257
|
+
entries = await fs3.readdir(directory, { withFileTypes: true });
|
|
1407
1258
|
} catch {
|
|
1408
1259
|
return void 0;
|
|
1409
1260
|
}
|
|
@@ -1411,9 +1262,9 @@ async function findLatestProviderBackup(globalRoot, profileName = "default") {
|
|
|
1411
1262
|
if (!entry.isFile() || !filePattern.test(entry.name)) {
|
|
1412
1263
|
continue;
|
|
1413
1264
|
}
|
|
1414
|
-
const filePath =
|
|
1265
|
+
const filePath = path5.join(directory, entry.name);
|
|
1415
1266
|
try {
|
|
1416
|
-
const stat2 = await
|
|
1267
|
+
const stat2 = await fs3.stat(filePath);
|
|
1417
1268
|
candidates.push({
|
|
1418
1269
|
filePath,
|
|
1419
1270
|
fileName: entry.name,
|
|
@@ -1425,7 +1276,7 @@ async function findLatestProviderBackup(globalRoot, profileName = "default") {
|
|
|
1425
1276
|
candidates.sort((a, b) => b.modifiedAtMs - a.modifiedAtMs);
|
|
1426
1277
|
for (const candidate of candidates) {
|
|
1427
1278
|
try {
|
|
1428
|
-
const raw = await
|
|
1279
|
+
const raw = await fs3.readFile(candidate.filePath, "utf8");
|
|
1429
1280
|
const parsed = JSON.parse(raw);
|
|
1430
1281
|
const ids = providerIds(parsed);
|
|
1431
1282
|
if (ids.length === 0) continue;
|
|
@@ -1437,7 +1288,7 @@ async function findLatestProviderBackup(globalRoot, profileName = "default") {
|
|
|
1437
1288
|
}
|
|
1438
1289
|
async function restoreDefaultProfileBackup(opts) {
|
|
1439
1290
|
await backupConfigFile(opts.profilePath, { globalRoot: opts.globalRoot });
|
|
1440
|
-
await
|
|
1291
|
+
await fs3.mkdir(path5.dirname(opts.profilePath), { recursive: true });
|
|
1441
1292
|
await atomicWrite2(opts.profilePath, opts.backup.raw, { mode: 384 });
|
|
1442
1293
|
}
|
|
1443
1294
|
async function maybeRestoreDefaultProfileFromBackup(opts) {
|
|
@@ -1450,12 +1301,12 @@ async function maybeRestoreDefaultProfileFromBackup(opts) {
|
|
|
1450
1301
|
const providerSummary = remainder > 0 ? `${shownProviders} +${remainder}` : shownProviders;
|
|
1451
1302
|
opts.renderer.write(
|
|
1452
1303
|
`
|
|
1453
|
-
${
|
|
1454
|
-
${
|
|
1304
|
+
${color4.amber("\u25CB")} ${color4.bold(profileName)} profile has no saved providers.
|
|
1305
|
+
${color4.dim("Latest usable backup:")} ${color4.bold(backup.fileName)} ${color4.dim(`(${backup.providerIds.length} provider: ${providerSummary})`)}
|
|
1455
1306
|
`
|
|
1456
1307
|
);
|
|
1457
1308
|
const answer = (await opts.reader.readLine(
|
|
1458
|
-
` ${
|
|
1309
|
+
` ${color4.amber("?")} Restore all settings from this backup? ${color4.dim("[Y/n]")} `
|
|
1459
1310
|
)).trim().toLowerCase();
|
|
1460
1311
|
if (answer === "n" || answer === "no") return false;
|
|
1461
1312
|
try {
|
|
@@ -1465,12 +1316,12 @@ async function maybeRestoreDefaultProfileFromBackup(opts) {
|
|
|
1465
1316
|
profilePath: opts.profilePath
|
|
1466
1317
|
});
|
|
1467
1318
|
opts.renderer.write(
|
|
1468
|
-
` ${
|
|
1319
|
+
` ${color4.green("\u2713")} Restored all settings from ${color4.dim(backup.fileName)}
|
|
1469
1320
|
`
|
|
1470
1321
|
);
|
|
1471
1322
|
return true;
|
|
1472
1323
|
} catch (err) {
|
|
1473
|
-
opts.renderer.writeWarning(`Could not restore config backup: ${
|
|
1324
|
+
opts.renderer.writeWarning(`Could not restore config backup: ${toErrorMessage(err)}
|
|
1474
1325
|
`);
|
|
1475
1326
|
return false;
|
|
1476
1327
|
}
|
|
@@ -1484,6 +1335,26 @@ function applyGoalTuiDefault(flags, positional) {
|
|
|
1484
1335
|
}
|
|
1485
1336
|
}
|
|
1486
1337
|
|
|
1338
|
+
// src/boot-config.ts
|
|
1339
|
+
import { bootConfig as coreBootConfig } from "@wrongstack/core/infrastructure";
|
|
1340
|
+
async function bootConfig(flags) {
|
|
1341
|
+
const isWebui = !!flags["webui"];
|
|
1342
|
+
const opts = {
|
|
1343
|
+
flags,
|
|
1344
|
+
appLabel: "wstack",
|
|
1345
|
+
...isWebui ? { skipIdentityValidation: true } : {}
|
|
1346
|
+
};
|
|
1347
|
+
const { cwd, projectRoot, userHome, wpaths, pathResolver, config, vault } = await coreBootConfig(opts);
|
|
1348
|
+
return {
|
|
1349
|
+
paths: { cwd, projectRoot, userHome, wpaths, pathResolver },
|
|
1350
|
+
config,
|
|
1351
|
+
vault
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// src/boot-launch-choices.ts
|
|
1356
|
+
import { color as color10, toErrorMessage as toErrorMessage5, writeErr } from "@wrongstack/core/utils";
|
|
1357
|
+
|
|
1487
1358
|
// src/boot/system-prompt-menu.ts
|
|
1488
1359
|
import {
|
|
1489
1360
|
countSystemPromptTokens,
|
|
@@ -1492,11 +1363,11 @@ import {
|
|
|
1492
1363
|
SYSTEM_PROMPT_VARIANT_OPTIONS,
|
|
1493
1364
|
SYSTEM_PROMPT_VARIANTS
|
|
1494
1365
|
} from "@wrongstack/core/agent";
|
|
1495
|
-
import { color as
|
|
1366
|
+
import { color as color8 } from "@wrongstack/core/utils";
|
|
1496
1367
|
|
|
1497
1368
|
// src/pre-launch/first-run.ts
|
|
1498
1369
|
import { hasProviderCredential } from "@wrongstack/core/models";
|
|
1499
|
-
import { color as
|
|
1370
|
+
import { color as color5, toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
1500
1371
|
import { PROVIDER_DEFINITIONS } from "@wrongstack/providers/definitions";
|
|
1501
1372
|
async function hasAnyCredential(config, modelsRegistry, env = process.env) {
|
|
1502
1373
|
for (const cfg of Object.values(config.providers ?? {})) {
|
|
@@ -1522,39 +1393,39 @@ function renderWelcome(renderer) {
|
|
|
1522
1393
|
renderer.write(`${boxTop("Welcome to WrongStack")}
|
|
1523
1394
|
`);
|
|
1524
1395
|
renderer.write(
|
|
1525
|
-
`${boxRow(
|
|
1396
|
+
`${boxRow(color5.dim("No model is connected yet. Pick how you want to connect one:"))}
|
|
1526
1397
|
`
|
|
1527
1398
|
);
|
|
1528
1399
|
renderer.write(`${boxDivider()}
|
|
1529
1400
|
`);
|
|
1530
1401
|
renderer.write(
|
|
1531
1402
|
`${boxRow(
|
|
1532
|
-
`${theme.accent("1")} ${
|
|
1403
|
+
`${theme.accent("1")} ${color5.bold("Sign in with a subscription")} ${color5.dim("ChatGPT \xB7 Claude \xB7 GitHub Copilot")}`
|
|
1533
1404
|
)}
|
|
1534
1405
|
`
|
|
1535
1406
|
);
|
|
1536
1407
|
renderer.write(
|
|
1537
1408
|
`${boxRow(
|
|
1538
|
-
`${theme.accent("2")} ${
|
|
1409
|
+
`${theme.accent("2")} ${color5.bold("Add an API key")} ${color5.dim("Anthropic \xB7 OpenAI \xB7 Google \xB7 OpenRouter \xB7 \u2026")}`
|
|
1539
1410
|
)}
|
|
1540
1411
|
`
|
|
1541
1412
|
);
|
|
1542
1413
|
renderer.write(
|
|
1543
1414
|
`${boxRow(
|
|
1544
|
-
`${theme.accent("3")} ${
|
|
1415
|
+
`${theme.accent("3")} ${color5.bold("Use a local server")} ${color5.dim("Ollama \xB7 LM Studio \xB7 vLLM \xB7 OmniRoute")}`
|
|
1545
1416
|
)}
|
|
1546
1417
|
`
|
|
1547
1418
|
);
|
|
1548
1419
|
renderer.write(
|
|
1549
1420
|
`${boxRow(
|
|
1550
|
-
`${theme.accent("4")} ${
|
|
1421
|
+
`${theme.accent("4")} ${color5.bold("Continue without a key")} ${color5.dim("setup mode \u2014 explore the app, no model")}`
|
|
1551
1422
|
)}
|
|
1552
1423
|
`
|
|
1553
1424
|
);
|
|
1554
1425
|
renderer.write(`${boxDivider()}
|
|
1555
1426
|
`);
|
|
1556
1427
|
renderer.write(
|
|
1557
|
-
`${boxRow(
|
|
1428
|
+
`${boxRow(color5.dim("You can change this any time with /auth inside the app, or `wstack auth`."))}
|
|
1558
1429
|
`
|
|
1559
1430
|
);
|
|
1560
1431
|
renderer.write(`${boxBottom()}
|
|
@@ -1573,12 +1444,12 @@ async function runFirstRunSetup(deps) {
|
|
|
1573
1444
|
renderWelcome(renderer);
|
|
1574
1445
|
const answer = (await reader.readLine(
|
|
1575
1446
|
`
|
|
1576
|
-
${
|
|
1447
|
+
${color5.amber("?")} Choose ${color5.dim("(1-4)")} ${color5.dim("[q to quit]")}: `
|
|
1577
1448
|
)).trim().toLowerCase();
|
|
1578
1449
|
if (!answer || answer === "q" || answer === "quit" || answer === "exit") {
|
|
1579
1450
|
renderer.write(
|
|
1580
1451
|
`
|
|
1581
|
-
${
|
|
1452
|
+
${color5.dim("No provider configured. Run")} ${color5.bold("wstack auth")} ${color5.dim("when you are ready.")}
|
|
1582
1453
|
|
|
1583
1454
|
`
|
|
1584
1455
|
);
|
|
@@ -1595,14 +1466,14 @@ ${color6.amber("?")} Choose ${color6.dim("(1-4)")} ${color6.dim("[q to quit]")}:
|
|
|
1595
1466
|
try {
|
|
1596
1467
|
await flow();
|
|
1597
1468
|
} catch (err) {
|
|
1598
|
-
renderer.writeWarning(`Setup step did not complete: ${
|
|
1469
|
+
renderer.writeWarning(`Setup step did not complete: ${toErrorMessage2(err)}
|
|
1599
1470
|
`);
|
|
1600
1471
|
}
|
|
1601
1472
|
let updated;
|
|
1602
1473
|
try {
|
|
1603
1474
|
updated = await deps.reloadConfig();
|
|
1604
1475
|
} catch (err) {
|
|
1605
|
-
renderer.writeWarning(`Could not re-read config: ${
|
|
1476
|
+
renderer.writeWarning(`Could not re-read config: ${toErrorMessage2(err)}
|
|
1606
1477
|
`);
|
|
1607
1478
|
continue;
|
|
1608
1479
|
}
|
|
@@ -1614,8 +1485,8 @@ ${color6.amber("?")} Choose ${color6.dim("(1-4)")} ${color6.dim("[q to quit]")}:
|
|
|
1614
1485
|
}
|
|
1615
1486
|
|
|
1616
1487
|
// src/pre-launch/launch-prompts.ts
|
|
1617
|
-
import * as
|
|
1618
|
-
import { atomicWrite as atomicWrite3, color as
|
|
1488
|
+
import * as fs4 from "node:fs/promises";
|
|
1489
|
+
import { atomicWrite as atomicWrite3, color as color6 } from "@wrongstack/core/utils";
|
|
1619
1490
|
var LaunchAbortedError = class extends Error {
|
|
1620
1491
|
exitCode = 0;
|
|
1621
1492
|
constructor() {
|
|
@@ -1646,19 +1517,19 @@ async function runLaunchPrompts(opts) {
|
|
|
1646
1517
|
yolo: yoloPinned ?? lastChoices.yolo,
|
|
1647
1518
|
autonomy: autonomyPinned ?? lastChoices.autonomy
|
|
1648
1519
|
};
|
|
1649
|
-
const onOff = (v) => v ?
|
|
1520
|
+
const onOff = (v) => v ? color6.green("on") : color6.dim("off");
|
|
1650
1521
|
const modeLabel = effective.mode.toUpperCase();
|
|
1651
1522
|
renderer.write(
|
|
1652
1523
|
`
|
|
1653
|
-
${
|
|
1524
|
+
${color6.dim("Last settings:")} ${color6.bold(modeLabel)} \xB7 YOLO ${onOff(effective.yolo)} \xB7 Autonomy ${effective.autonomy === "auto" ? color6.green("auto") : color6.dim("off")}
|
|
1654
1525
|
`
|
|
1655
1526
|
);
|
|
1656
1527
|
const answer = (await reader.readLine(
|
|
1657
|
-
` ${
|
|
1528
|
+
` ${color6.amber("?")} Continue with these? ${color6.dim("[Y/n/q]")} ${color6.dim("(auto Y in 5s)")} `,
|
|
1658
1529
|
{ timeoutMs: 5e3, defaultAnswer: "y" }
|
|
1659
1530
|
)).trim().toLowerCase();
|
|
1660
1531
|
if (answer === "q") {
|
|
1661
|
-
renderer.write(
|
|
1532
|
+
renderer.write(color6.dim(" Goodbye!\n"));
|
|
1662
1533
|
throw new LaunchAbortedError();
|
|
1663
1534
|
}
|
|
1664
1535
|
if (answer !== "n" && answer !== "no") {
|
|
@@ -1666,7 +1537,7 @@ async function runLaunchPrompts(opts) {
|
|
|
1666
1537
|
const badgeStr2 = badges2.length > 0 ? ` (${badges2.join(" \xB7 ")})` : "";
|
|
1667
1538
|
renderer.write(
|
|
1668
1539
|
`
|
|
1669
|
-
${
|
|
1540
|
+
${color6.green("\u25B6")} Launching in ${color6.bold(modeLabel)} mode${badgeStr2}
|
|
1670
1541
|
|
|
1671
1542
|
`
|
|
1672
1543
|
);
|
|
@@ -1679,10 +1550,10 @@ async function runLaunchPrompts(opts) {
|
|
|
1679
1550
|
} else {
|
|
1680
1551
|
const answer = (await reader.readLine(
|
|
1681
1552
|
`
|
|
1682
|
-
${
|
|
1553
|
+
${color6.amber("?")} Interactive mode: ${color6.bold("T")}UI / ${color6.bold("R")}EPL ${color6.dim("[T/r/q]")} `
|
|
1683
1554
|
)).trim().toLowerCase();
|
|
1684
1555
|
if (answer === "q") {
|
|
1685
|
-
renderer.write(
|
|
1556
|
+
renderer.write(color6.dim(" Goodbye!\n"));
|
|
1686
1557
|
throw new LaunchAbortedError();
|
|
1687
1558
|
}
|
|
1688
1559
|
mode = answer === "r" || answer === "repl" ? "repl" : "tui";
|
|
@@ -1692,10 +1563,10 @@ async function runLaunchPrompts(opts) {
|
|
|
1692
1563
|
yolo = yoloPinned;
|
|
1693
1564
|
} else {
|
|
1694
1565
|
const answer = (await reader.readLine(
|
|
1695
|
-
` ${
|
|
1566
|
+
` ${color6.amber("?")} YOLO mode ${color6.dim("(auto-approve tool calls)")} ${color6.dim("[Y/n/q]")} `
|
|
1696
1567
|
)).trim().toLowerCase();
|
|
1697
1568
|
if (answer === "q") {
|
|
1698
|
-
renderer.write(
|
|
1569
|
+
renderer.write(color6.dim(" Goodbye!\n"));
|
|
1699
1570
|
throw new LaunchAbortedError();
|
|
1700
1571
|
}
|
|
1701
1572
|
yolo = answer !== "n" && answer !== "no";
|
|
@@ -1705,10 +1576,10 @@ async function runLaunchPrompts(opts) {
|
|
|
1705
1576
|
autonomy = autonomyPinned;
|
|
1706
1577
|
} else {
|
|
1707
1578
|
const answer = (await reader.readLine(
|
|
1708
|
-
` ${
|
|
1579
|
+
` ${color6.amber("?")} Autonomy mode ${color6.dim("(auto-continue \u2014 agent picks next step)")} ${color6.dim("[Y/n/q]")} `
|
|
1709
1580
|
)).trim().toLowerCase();
|
|
1710
1581
|
if (answer === "q") {
|
|
1711
|
-
renderer.write(
|
|
1582
|
+
renderer.write(color6.dim(" Goodbye!\n"));
|
|
1712
1583
|
throw new LaunchAbortedError();
|
|
1713
1584
|
}
|
|
1714
1585
|
autonomy = answer !== "n" && answer !== "no" ? "auto" : "off";
|
|
@@ -1717,7 +1588,7 @@ async function runLaunchPrompts(opts) {
|
|
|
1717
1588
|
const badgeStr = badges.length > 0 ? ` (${badges.join(" \xB7 ")})` : "";
|
|
1718
1589
|
renderer.write(
|
|
1719
1590
|
`
|
|
1720
|
-
${
|
|
1591
|
+
${color6.green("\u25B6")} Launching in ${color6.bold(mode.toUpperCase())} mode${badgeStr}
|
|
1721
1592
|
|
|
1722
1593
|
`
|
|
1723
1594
|
);
|
|
@@ -1725,21 +1596,21 @@ async function runLaunchPrompts(opts) {
|
|
|
1725
1596
|
}
|
|
1726
1597
|
function buildBadges(chosen) {
|
|
1727
1598
|
const badges = [];
|
|
1728
|
-
if (chosen.yolo) badges.push(
|
|
1599
|
+
if (chosen.yolo) badges.push(color6.yellow("YOLO"));
|
|
1729
1600
|
if (chosen.autonomy !== "off")
|
|
1730
|
-
badges.push(
|
|
1601
|
+
badges.push(color6.magenta(`AUTONOMY:${chosen.autonomy.toUpperCase()}`));
|
|
1731
1602
|
return badges;
|
|
1732
1603
|
}
|
|
1733
1604
|
async function persistLaunchChoices(configPath, choices) {
|
|
1734
1605
|
let fileExists = false;
|
|
1735
1606
|
try {
|
|
1736
|
-
await
|
|
1607
|
+
await fs4.access(configPath);
|
|
1737
1608
|
fileExists = true;
|
|
1738
1609
|
} catch {
|
|
1739
1610
|
}
|
|
1740
1611
|
let existing = {};
|
|
1741
1612
|
try {
|
|
1742
|
-
const raw = await
|
|
1613
|
+
const raw = await fs4.readFile(configPath, "utf8");
|
|
1743
1614
|
existing = JSON.parse(raw);
|
|
1744
1615
|
} catch (err) {
|
|
1745
1616
|
if (fileExists) {
|
|
@@ -1759,21 +1630,21 @@ async function persistLaunchChoices(configPath, choices) {
|
|
|
1759
1630
|
}
|
|
1760
1631
|
|
|
1761
1632
|
// src/pre-launch/project-check.ts
|
|
1762
|
-
import * as
|
|
1763
|
-
import * as
|
|
1633
|
+
import * as fs5 from "node:fs/promises";
|
|
1634
|
+
import * as path6 from "node:path";
|
|
1764
1635
|
import {
|
|
1765
|
-
color as
|
|
1636
|
+
color as color7,
|
|
1766
1637
|
detectProjectKind,
|
|
1767
1638
|
isOutsideProject,
|
|
1768
|
-
toErrorMessage as
|
|
1639
|
+
toErrorMessage as toErrorMessage3
|
|
1769
1640
|
} from "@wrongstack/core/utils";
|
|
1770
1641
|
async function scaffoldAgentsMd(projectRoot) {
|
|
1771
|
-
const dir =
|
|
1772
|
-
const file =
|
|
1642
|
+
const dir = path6.join(projectRoot, ".wrongstack");
|
|
1643
|
+
const file = path6.join(dir, "AGENTS.md");
|
|
1773
1644
|
const facts = await detectProjectFacts(projectRoot);
|
|
1774
1645
|
const body = renderAgentsTemplate(facts);
|
|
1775
|
-
await
|
|
1776
|
-
await
|
|
1646
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
1647
|
+
await fs5.writeFile(file, body, "utf8");
|
|
1777
1648
|
return file;
|
|
1778
1649
|
}
|
|
1779
1650
|
async function runProjectCheck(opts) {
|
|
@@ -1782,7 +1653,7 @@ async function runProjectCheck(opts) {
|
|
|
1782
1653
|
if (kind === "initialized") {
|
|
1783
1654
|
renderer.write(
|
|
1784
1655
|
`
|
|
1785
|
-
${
|
|
1656
|
+
${color7.green("\u2713")} Project initialized ${color7.dim(`(${path6.join(projectRoot, ".wrongstack", "AGENTS.md")})`)}
|
|
1786
1657
|
`
|
|
1787
1658
|
);
|
|
1788
1659
|
return true;
|
|
@@ -1790,45 +1661,45 @@ async function runProjectCheck(opts) {
|
|
|
1790
1661
|
if (kind === "project") {
|
|
1791
1662
|
renderer.write(
|
|
1792
1663
|
`
|
|
1793
|
-
${
|
|
1664
|
+
${color7.amber("\u25CF")} Project detected ${color7.dim(`(${projectRoot})`)} but ${color7.bold(".wrongstack/AGENTS.md")} is missing.
|
|
1794
1665
|
`
|
|
1795
1666
|
);
|
|
1796
1667
|
const answer2 = (await reader.readLine(
|
|
1797
|
-
` ${
|
|
1668
|
+
` ${color7.amber("?")} Scaffold ${color7.bold("AGENTS.md")} now? ${color7.dim("[y/N/q]")} `
|
|
1798
1669
|
)).trim().toLowerCase();
|
|
1799
1670
|
if (answer2 === "q") {
|
|
1800
|
-
renderer.write(
|
|
1671
|
+
renderer.write(color7.dim(" Cancelled.\n"));
|
|
1801
1672
|
return false;
|
|
1802
1673
|
}
|
|
1803
1674
|
if (answer2 === "y" || answer2 === "yes") {
|
|
1804
1675
|
try {
|
|
1805
1676
|
const file = await scaffoldAgentsMd(projectRoot);
|
|
1806
|
-
renderer.write(` ${
|
|
1677
|
+
renderer.write(` ${color7.green("\u2713")} Wrote ${color7.dim(file)}
|
|
1807
1678
|
`);
|
|
1808
1679
|
} catch (err) {
|
|
1809
|
-
renderer.writeError(`Failed to scaffold AGENTS.md: ${
|
|
1680
|
+
renderer.writeError(`Failed to scaffold AGENTS.md: ${toErrorMessage3(err)}`);
|
|
1810
1681
|
}
|
|
1811
1682
|
}
|
|
1812
1683
|
return true;
|
|
1813
1684
|
}
|
|
1814
|
-
const gitDir =
|
|
1685
|
+
const gitDir = path6.join(projectRoot, ".git");
|
|
1815
1686
|
let hasGit = false;
|
|
1816
1687
|
try {
|
|
1817
|
-
await
|
|
1688
|
+
await fs5.access(gitDir);
|
|
1818
1689
|
hasGit = true;
|
|
1819
1690
|
} catch {
|
|
1820
1691
|
}
|
|
1821
1692
|
if (!hasGit) {
|
|
1822
1693
|
renderer.write(
|
|
1823
1694
|
`
|
|
1824
|
-
${
|
|
1695
|
+
${color7.dim("\u25CB")} ${color7.dim(`No project manifest in ${projectRoot} \u2014 running in a scratch directory.`)}
|
|
1825
1696
|
`
|
|
1826
1697
|
);
|
|
1827
1698
|
const answer2 = (await reader.readLine(
|
|
1828
|
-
` ${
|
|
1699
|
+
` ${color7.amber("?")} No git repo found. ${color7.bold("Initialize git?")} ${color7.dim("[y/N/q]")} `
|
|
1829
1700
|
)).trim().toLowerCase();
|
|
1830
1701
|
if (answer2 === "q") {
|
|
1831
|
-
renderer.write(
|
|
1702
|
+
renderer.write(color7.dim(" Cancelled.\n"));
|
|
1832
1703
|
return false;
|
|
1833
1704
|
}
|
|
1834
1705
|
if (answer2 === "y" || answer2 === "yes") {
|
|
@@ -1846,23 +1717,23 @@ async function runProjectCheck(opts) {
|
|
|
1846
1717
|
(code) => code === 0 ? resolve3() : reject(new Error(`git init failed with ${code}`))
|
|
1847
1718
|
);
|
|
1848
1719
|
});
|
|
1849
|
-
renderer.write(` ${
|
|
1720
|
+
renderer.write(` ${color7.green("\u2713")} Git repository initialized
|
|
1850
1721
|
`);
|
|
1851
1722
|
} catch (err) {
|
|
1852
|
-
renderer.writeError(`git init failed: ${
|
|
1723
|
+
renderer.writeError(`git init failed: ${toErrorMessage3(err)}
|
|
1853
1724
|
`);
|
|
1854
1725
|
}
|
|
1855
1726
|
}
|
|
1856
1727
|
} else {
|
|
1857
1728
|
renderer.write(
|
|
1858
1729
|
`
|
|
1859
|
-
${
|
|
1730
|
+
${color7.dim("\u25CB")} ${color7.dim(`No project manifest in ${projectRoot} \u2014 running in a scratch directory.`)}
|
|
1860
1731
|
`
|
|
1861
1732
|
);
|
|
1862
1733
|
}
|
|
1863
|
-
const answer = (await reader.readLine(` ${
|
|
1734
|
+
const answer = (await reader.readLine(` ${color7.amber("?")} Continue anyway? ${color7.dim("[Y/n/q]")} `)).trim().toLowerCase();
|
|
1864
1735
|
if (answer === "q" || answer === "n" || answer === "no") {
|
|
1865
|
-
renderer.write(
|
|
1736
|
+
renderer.write(color7.dim(" Cancelled.\n"));
|
|
1866
1737
|
return false;
|
|
1867
1738
|
}
|
|
1868
1739
|
return true;
|
|
@@ -1899,39 +1770,39 @@ async function runSystemPromptMenu(deps) {
|
|
|
1899
1770
|
if (suggestScout && lastVariant !== "scout") {
|
|
1900
1771
|
renderer.write(
|
|
1901
1772
|
`
|
|
1902
|
-
${
|
|
1773
|
+
${color8.dim("\u25CB")} ${color8.dim(`Not a project folder \u2014 ${labelFor("scout")} (general-purpose) may fit better; answer n to choose it.`)}
|
|
1903
1774
|
`
|
|
1904
1775
|
);
|
|
1905
1776
|
}
|
|
1906
1777
|
const answer2 = (await reader.readLine(
|
|
1907
|
-
` ${
|
|
1778
|
+
` ${color8.amber("?")} Continue with ${color8.bold(labelFor(lastVariant))} system prompt ${color8.dim(`(~${fmtTok(tokens[lastVariant])} tokens)`)}? ${color8.dim("[Y/n/q]")} ${color8.dim("(auto Y in 5s)")} `,
|
|
1908
1779
|
{ timeoutMs: SUMMARY_TIMEOUT_MS2, defaultAnswer: "y" }
|
|
1909
1780
|
)).trim().toLowerCase();
|
|
1910
1781
|
if (answer2 === "q" || answer2 === "quit") throw new LaunchAbortedError();
|
|
1911
1782
|
if (answer2 !== "n" && answer2 !== "no") return lastVariant;
|
|
1912
1783
|
}
|
|
1913
1784
|
renderer.write(`
|
|
1914
|
-
${
|
|
1785
|
+
${color8.amber("?")} System prompt:
|
|
1915
1786
|
`);
|
|
1916
1787
|
if (suggestScout) {
|
|
1917
1788
|
renderer.write(
|
|
1918
|
-
` ${
|
|
1789
|
+
` ${color8.dim(`Not a project folder \u2014 ${labelFor("scout")} is preselected.`)}
|
|
1919
1790
|
`
|
|
1920
1791
|
);
|
|
1921
1792
|
}
|
|
1922
1793
|
for (const [index, opt] of SYSTEM_PROMPT_OPTIONS.entries()) {
|
|
1923
1794
|
const num2 = index + 1;
|
|
1924
1795
|
const isDefault = opt.variant === defaultVariant;
|
|
1925
|
-
const marker = isDefault ?
|
|
1926
|
-
const label = isDefault ?
|
|
1796
|
+
const marker = isDefault ? color8.cyan("\u25CF") : color8.dim("\u25CB");
|
|
1797
|
+
const label = isDefault ? color8.bold(opt.label) : opt.label;
|
|
1927
1798
|
renderer.write(
|
|
1928
|
-
` ${
|
|
1799
|
+
` ${color8.dim(`${num2}.`)} ${marker} ${label.padEnd(9)} ${color8.dim(`~${fmtTok(tokens[opt.variant])} tokens`)} ${color8.dim(opt.hint)}
|
|
1929
1800
|
`
|
|
1930
1801
|
);
|
|
1931
1802
|
}
|
|
1932
1803
|
const answer = (await reader.readLine(
|
|
1933
1804
|
`
|
|
1934
|
-
${
|
|
1805
|
+
${color8.amber("?")} Select system prompt ${color8.dim(`[1-${SYSTEM_PROMPT_OPTIONS.length}, Enter = ${labelFor(defaultVariant)}, q to quit]`)}: `
|
|
1935
1806
|
)).trim().toLowerCase();
|
|
1936
1807
|
if (answer === "q" || answer === "quit") throw new LaunchAbortedError();
|
|
1937
1808
|
if (!answer) return defaultVariant;
|
|
@@ -1961,7 +1832,7 @@ async function maybeRunSystemPromptMenu(opts) {
|
|
|
1961
1832
|
if (chosen === savedVariant) return { variant: chosen, aborted: false, changed: false };
|
|
1962
1833
|
if (opts.outsideProject && chosen === "scout") {
|
|
1963
1834
|
opts.renderer.write(
|
|
1964
|
-
` ${
|
|
1835
|
+
` ${color8.dim(`${labelFor("scout")} for this launch only; the saved default stays ${labelFor(savedVariant ?? "default")}.`)}
|
|
1965
1836
|
`
|
|
1966
1837
|
);
|
|
1967
1838
|
return { variant: chosen, aborted: false, changed: true };
|
|
@@ -1978,1294 +1849,1527 @@ async function maybeRunSystemPromptMenu(opts) {
|
|
|
1978
1849
|
}
|
|
1979
1850
|
}
|
|
1980
1851
|
|
|
1981
|
-
// src/boot-
|
|
1982
|
-
import
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
flags,
|
|
1987
|
-
appLabel: "wstack",
|
|
1988
|
-
...isWebui ? { skipIdentityValidation: true } : {}
|
|
1989
|
-
};
|
|
1990
|
-
const { cwd, projectRoot, userHome, wpaths, pathResolver, config, vault } = await coreBootConfig(opts);
|
|
1991
|
-
return {
|
|
1992
|
-
paths: { cwd, projectRoot, userHome, wpaths, pathResolver },
|
|
1993
|
-
config,
|
|
1994
|
-
vault
|
|
1995
|
-
};
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
// src/input-reader.ts
|
|
1999
|
-
import * as fs7 from "node:fs/promises";
|
|
2000
|
-
import * as path8 from "node:path";
|
|
2001
|
-
import * as readline from "node:readline";
|
|
1852
|
+
// src/boot-provider-selection.ts
|
|
1853
|
+
import * as fs6 from "node:fs/promises";
|
|
1854
|
+
import { createRequire as createRequire4 } from "node:module";
|
|
1855
|
+
import * as path7 from "node:path";
|
|
1856
|
+
import { fileURLToPath } from "node:url";
|
|
2002
1857
|
import {
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
1858
|
+
color as color9,
|
|
1859
|
+
moduleUrlFor,
|
|
1860
|
+
toErrorMessage as toErrorMessage4,
|
|
1861
|
+
wrongstackPackageJsonPath as wrongstackPackageJsonPath3
|
|
2007
1862
|
} from "@wrongstack/core/utils";
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
}
|
|
2017
|
-
async loadHistory() {
|
|
2018
|
-
try {
|
|
2019
|
-
const raw = await fs7.readFile(this.historyFile, "utf8");
|
|
2020
|
-
this.history = raw.split("\n").filter(Boolean).slice(-1e3);
|
|
2021
|
-
} catch {
|
|
2022
|
-
this.history = [];
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
async saveHistory() {
|
|
2026
|
-
try {
|
|
2027
|
-
await fs7.mkdir(path8.dirname(this.historyFile), { recursive: true });
|
|
2028
|
-
await fs7.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
2029
|
-
} catch {
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
shouldPersistHistory(line) {
|
|
2033
|
-
return !/^\s*\/(?:telegram-setup|tg-setup)\s+\d+:[A-Za-z0-9_-]+(?:\s|$)/i.test(line);
|
|
2034
|
-
}
|
|
2035
|
-
ensure() {
|
|
2036
|
-
if (!this.rl) {
|
|
2037
|
-
this.rl = readline.createInterface({
|
|
2038
|
-
input: process.stdin,
|
|
2039
|
-
output: process.stdout,
|
|
2040
|
-
history: this.history,
|
|
2041
|
-
terminal: process.stdin.isTTY
|
|
2042
|
-
});
|
|
2043
|
-
}
|
|
2044
|
-
return this.rl;
|
|
1863
|
+
import { isSetupProvider } from "@wrongstack/providers";
|
|
1864
|
+
function resolveBundledOverlayFile() {
|
|
1865
|
+
try {
|
|
1866
|
+
return fileURLToPath(
|
|
1867
|
+
new URL("../data/providers.json", moduleUrlFor(import.meta.url, "@wrongstack/cli"))
|
|
1868
|
+
);
|
|
1869
|
+
} catch {
|
|
1870
|
+
return void 0;
|
|
2045
1871
|
}
|
|
2046
|
-
async readLine(prompt, timeoutOpts) {
|
|
2047
|
-
if (this.history.length === 0) await this.loadHistory();
|
|
2048
|
-
while (this.pending) {
|
|
2049
|
-
await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
2050
|
-
}
|
|
2051
|
-
this.pending = true;
|
|
2052
|
-
try {
|
|
2053
|
-
if (this.rl) {
|
|
2054
|
-
const old = this.rl;
|
|
2055
|
-
this.rl = void 0;
|
|
2056
|
-
await new Promise((resolve3) => {
|
|
2057
|
-
if (old.closed) {
|
|
2058
|
-
resolve3();
|
|
2059
|
-
} else {
|
|
2060
|
-
old.once("close", resolve3);
|
|
2061
|
-
old.close();
|
|
2062
|
-
}
|
|
2063
|
-
});
|
|
2064
|
-
}
|
|
2065
|
-
const fresh = this.ensure();
|
|
2066
|
-
this.installPromptGuard(fresh);
|
|
2067
|
-
const line = await new Promise((resolve3) => {
|
|
2068
|
-
let settled = false;
|
|
2069
|
-
let timeoutHandle;
|
|
2070
|
-
const settle = (line2) => {
|
|
2071
|
-
if (settled) return;
|
|
2072
|
-
settled = true;
|
|
2073
|
-
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
2074
|
-
setOutputLineGuard(null);
|
|
2075
|
-
resolve3(line2);
|
|
2076
|
-
};
|
|
2077
|
-
if (timeoutOpts) {
|
|
2078
|
-
timeoutHandle = setTimeout(() => {
|
|
2079
|
-
if (!settled) {
|
|
2080
|
-
fresh.write(`${timeoutOpts.defaultAnswer}
|
|
2081
|
-
`);
|
|
2082
|
-
}
|
|
2083
|
-
}, timeoutOpts.timeoutMs);
|
|
2084
|
-
}
|
|
2085
|
-
fresh.question(prompt ?? "> ", (line2) => {
|
|
2086
|
-
const persistHistory = !this.suppressNextHistory && this.shouldPersistHistory(line2);
|
|
2087
|
-
this.suppressNextHistory = false;
|
|
2088
|
-
if (line2.trim() && persistHistory) {
|
|
2089
|
-
this.history.push(line2);
|
|
2090
|
-
this.saveHistory().then(void 0, () => {
|
|
2091
|
-
});
|
|
2092
|
-
}
|
|
2093
|
-
settle(line2);
|
|
2094
|
-
});
|
|
2095
|
-
fresh.once("close", () => settle(""));
|
|
2096
|
-
fresh.on?.("error", (_e) => settle(""));
|
|
2097
|
-
}).then((result) => {
|
|
2098
|
-
if (this.rl === fresh) this.rl = void 0;
|
|
2099
|
-
fresh.close();
|
|
2100
|
-
return result;
|
|
2101
|
-
});
|
|
2102
|
-
return line;
|
|
2103
|
-
} finally {
|
|
2104
|
-
this.pending = false;
|
|
2105
|
-
}
|
|
2106
|
-
}
|
|
2107
|
-
/**
|
|
2108
|
-
* Install the out-of-band write guard for the active prompt. When a log
|
|
2109
|
-
* line or other async output lands while the user is mid-type, the guard
|
|
2110
|
-
* clears the draft row, lets the message print, then repaints the prompt
|
|
2111
|
-
* and the in-progress draft (cursor preserved) via readline's own
|
|
2112
|
-
* refresh. Without it, each async write leaves the half-typed line
|
|
2113
|
-
* stranded as a fresh scrollback row.
|
|
2114
|
-
*
|
|
2115
|
-
* No-op on non-TTY output (piped/redirected) — there's no draft to
|
|
2116
|
-
* protect and the ANSI clear/repaint would be noise in a file.
|
|
2117
|
-
*/
|
|
2118
|
-
installPromptGuard(rl) {
|
|
2119
|
-
const out = process.stdout;
|
|
2120
|
-
if (!out.isTTY) {
|
|
2121
|
-
setOutputLineGuard(null);
|
|
2122
|
-
return;
|
|
2123
|
-
}
|
|
2124
|
-
setOutputLineGuard({
|
|
2125
|
-
suspend() {
|
|
2126
|
-
readline.cursorTo(out, 0);
|
|
2127
|
-
readline.clearLine(out, 0);
|
|
2128
|
-
},
|
|
2129
|
-
resume() {
|
|
2130
|
-
try {
|
|
2131
|
-
rl.prompt(true);
|
|
2132
|
-
} catch {
|
|
2133
|
-
}
|
|
2134
|
-
}
|
|
2135
|
-
});
|
|
2136
|
-
}
|
|
2137
|
-
async readKey(prompt, options, opts = {}) {
|
|
2138
|
-
setOutputLineGuard(null);
|
|
2139
|
-
const signal = opts.signal;
|
|
2140
|
-
if (signal?.aborted) return "";
|
|
2141
|
-
writeOut(prompt);
|
|
2142
|
-
return new Promise((resolve3) => {
|
|
2143
|
-
const stdin = process.stdin;
|
|
2144
|
-
const wasRaw = stdin.isRaw;
|
|
2145
|
-
const wasPaused = stdin.isPaused();
|
|
2146
|
-
setRawMode2(stdin, true);
|
|
2147
|
-
stdin.resume();
|
|
2148
|
-
const onData = (buf) => {
|
|
2149
|
-
const key = buf.toString();
|
|
2150
|
-
if (key === "") {
|
|
2151
|
-
cleanup();
|
|
2152
|
-
writeOut("\n");
|
|
2153
|
-
resolve3("");
|
|
2154
|
-
return;
|
|
2155
|
-
}
|
|
2156
|
-
const opt = options.find(
|
|
2157
|
-
(o) => o.key.toLowerCase() === key.toLowerCase() || o.value === key
|
|
2158
|
-
);
|
|
2159
|
-
if (opt) {
|
|
2160
|
-
cleanup();
|
|
2161
|
-
writeOut(`${opt.key}
|
|
2162
|
-
`);
|
|
2163
|
-
resolve3(opt.value);
|
|
2164
|
-
}
|
|
2165
|
-
};
|
|
2166
|
-
const onClose = () => {
|
|
2167
|
-
cleanup();
|
|
2168
|
-
resolve3("");
|
|
2169
|
-
};
|
|
2170
|
-
const onAbort = () => {
|
|
2171
|
-
cleanup();
|
|
2172
|
-
writeOut("\n");
|
|
2173
|
-
resolve3("");
|
|
2174
|
-
};
|
|
2175
|
-
const cleanup = () => {
|
|
2176
|
-
stdin.off("data", onData);
|
|
2177
|
-
stdin.off("close", onClose);
|
|
2178
|
-
signal?.removeEventListener("abort", onAbort);
|
|
2179
|
-
setRawMode2(stdin, wasRaw);
|
|
2180
|
-
if (wasPaused) stdin.pause();
|
|
2181
|
-
};
|
|
2182
|
-
stdin.on("data", onData);
|
|
2183
|
-
stdin.on("close", onClose);
|
|
2184
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2185
|
-
});
|
|
2186
|
-
}
|
|
2187
|
-
/**
|
|
2188
|
-
* Read a line of input while masking each character with a bullet so the
|
|
2189
|
-
* user gets visual confirmation that bytes are arriving (especially on
|
|
2190
|
-
* paste, which previously felt like nothing happened). Pasted chunks
|
|
2191
|
-
* are echoed as a run of bullets, Backspace/DEL erases one bullet, and
|
|
2192
|
-
* Ctrl+U / Ctrl+T are honored. Non-TTY input is read normally — there's
|
|
2193
|
-
* nothing to hide when piped, and echoing bullets to a file is noise.
|
|
2194
|
-
*
|
|
2195
|
-
* Returns the raw entered string (no trim — caller decides).
|
|
2196
|
-
*/
|
|
2197
|
-
async readSecret(prompt) {
|
|
2198
|
-
const stdin = process.stdin;
|
|
2199
|
-
if (!stdin.isTTY) {
|
|
2200
|
-
this.suppressNextHistory = true;
|
|
2201
|
-
return this.readLine(prompt);
|
|
2202
|
-
}
|
|
2203
|
-
setOutputLineGuard(null);
|
|
2204
|
-
this.rl?.close();
|
|
2205
|
-
this.rl = void 0;
|
|
2206
|
-
writeOut(prompt);
|
|
2207
|
-
return new Promise((resolve3) => {
|
|
2208
|
-
let buf = "";
|
|
2209
|
-
const wasRaw = stdin.isRaw;
|
|
2210
|
-
const cleanup = () => {
|
|
2211
|
-
stdin.off("data", onData);
|
|
2212
|
-
setRawMode2(stdin, wasRaw);
|
|
2213
|
-
stdin.pause();
|
|
2214
|
-
};
|
|
2215
|
-
try {
|
|
2216
|
-
setRawMode2(stdin, true);
|
|
2217
|
-
stdin.resume();
|
|
2218
|
-
stdin.setEncoding("utf8");
|
|
2219
|
-
} catch (setupErr) {
|
|
2220
|
-
setRawMode2(stdin, wasRaw);
|
|
2221
|
-
stdin.pause();
|
|
2222
|
-
throw setupErr;
|
|
2223
|
-
}
|
|
2224
|
-
const eraseChar = () => {
|
|
2225
|
-
writeOut("\b \b");
|
|
2226
|
-
};
|
|
2227
|
-
const eraseAll = () => {
|
|
2228
|
-
for (let i = 0; i < buf.length; i++) eraseChar();
|
|
2229
|
-
};
|
|
2230
|
-
let escState = 0;
|
|
2231
|
-
const onData = (chunk) => {
|
|
2232
|
-
for (const ch of chunk) {
|
|
2233
|
-
if (escState === 1) {
|
|
2234
|
-
escState = ch === "[" ? 2 : ch === "O" ? 3 : 0;
|
|
2235
|
-
continue;
|
|
2236
|
-
}
|
|
2237
|
-
if (escState === 2) {
|
|
2238
|
-
if (ch >= "@" && ch <= "~") escState = 0;
|
|
2239
|
-
continue;
|
|
2240
|
-
}
|
|
2241
|
-
if (escState === 3) {
|
|
2242
|
-
escState = 0;
|
|
2243
|
-
continue;
|
|
2244
|
-
}
|
|
2245
|
-
if (ch === "\x1B") {
|
|
2246
|
-
escState = 1;
|
|
2247
|
-
continue;
|
|
2248
|
-
}
|
|
2249
|
-
if (ch === "\r" || ch === "\n") {
|
|
2250
|
-
cleanup();
|
|
2251
|
-
writeOut(` ${dim(`[${buf.length} chars]`)}
|
|
2252
|
-
`);
|
|
2253
|
-
resolve3(buf);
|
|
2254
|
-
return;
|
|
2255
|
-
}
|
|
2256
|
-
if (ch === "") {
|
|
2257
|
-
cleanup();
|
|
2258
|
-
writeOut("\n");
|
|
2259
|
-
resolve3("");
|
|
2260
|
-
return;
|
|
2261
|
-
}
|
|
2262
|
-
if (ch === "") {
|
|
2263
|
-
eraseAll();
|
|
2264
|
-
buf = "";
|
|
2265
|
-
continue;
|
|
2266
|
-
}
|
|
2267
|
-
if (ch === "") {
|
|
2268
|
-
const m = buf.match(/(\S+\s*)$/);
|
|
2269
|
-
const drop = m ? m[0].length : buf.length;
|
|
2270
|
-
for (let i = 0; i < drop; i++) eraseChar();
|
|
2271
|
-
buf = buf.slice(0, buf.length - drop);
|
|
2272
|
-
continue;
|
|
2273
|
-
}
|
|
2274
|
-
if (ch === "\x7F" || ch === "\b") {
|
|
2275
|
-
if (buf.length > 0) {
|
|
2276
|
-
buf = buf.slice(0, -1);
|
|
2277
|
-
eraseChar();
|
|
2278
|
-
}
|
|
2279
|
-
continue;
|
|
2280
|
-
}
|
|
2281
|
-
if (ch < " ") continue;
|
|
2282
|
-
buf += ch;
|
|
2283
|
-
writeOut("\u2022");
|
|
2284
|
-
}
|
|
2285
|
-
if (escState === 1) escState = 0;
|
|
2286
|
-
};
|
|
2287
|
-
stdin.on("data", onData);
|
|
2288
|
-
});
|
|
2289
|
-
}
|
|
2290
|
-
async close() {
|
|
2291
|
-
setOutputLineGuard(null);
|
|
2292
|
-
await this.saveHistory();
|
|
2293
|
-
this.rl?.close();
|
|
2294
|
-
this.rl = void 0;
|
|
2295
|
-
}
|
|
2296
|
-
};
|
|
2297
|
-
function dim(s) {
|
|
2298
|
-
if (!process.stdout.isTTY) return s;
|
|
2299
|
-
return `\x1B[2m${s}\x1B[22m`;
|
|
2300
1872
|
}
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
/** Lines kept per tool family in `extend` mode. */
|
|
2330
|
-
static EXTEND_PREVIEW_LINES = 10;
|
|
2331
|
-
constructor(opts = {}) {
|
|
2332
|
-
this.out = opts.out ?? process.stdout;
|
|
2333
|
-
this.err = opts.err ?? process.stderr;
|
|
2334
|
-
}
|
|
2335
|
-
/**
|
|
2336
|
-
* Toggle stdout suppression for non-terminal UI surfaces such as WebUI.
|
|
2337
|
-
* Diagnostics continue to flow to stderr. Idempotent.
|
|
2338
|
-
*/
|
|
2339
|
-
setSilent(silent) {
|
|
2340
|
-
this.silent = silent;
|
|
2341
|
-
}
|
|
2342
|
-
isSilent() {
|
|
2343
|
-
return this.silent;
|
|
2344
|
-
}
|
|
2345
|
-
/**
|
|
2346
|
-
* Toggle exclusive TUI ownership of both terminal streams. Unlike
|
|
2347
|
-
* {@link setSilent}, TUI mode suppresses stderr as well as stdout and clears
|
|
2348
|
-
* any CLI-only result mode staged before Ink took ownership. While active,
|
|
2349
|
-
* new result-mode hints are ignored. The two flags compose with OR semantics
|
|
2350
|
-
* for stdout suppression only; TUI ownership adds the other behaviors.
|
|
2351
|
-
* Enable immediately before starting Ink and disable in the paired
|
|
2352
|
-
* `finally`, only after the TUI has fully exited.
|
|
2353
|
-
*/
|
|
2354
|
-
setTuiActive(active) {
|
|
2355
|
-
if (active) this.renderModes.clear();
|
|
2356
|
-
this.tuiActive = active;
|
|
2357
|
-
}
|
|
2358
|
-
isTuiActive() {
|
|
2359
|
-
return this.tuiActive;
|
|
2360
|
-
}
|
|
2361
|
-
suppressStdout() {
|
|
2362
|
-
return this.silent || this.tuiActive;
|
|
1873
|
+
async function validateSavedProviderModel(config, modelsRegistry) {
|
|
1874
|
+
const providerId = config.provider;
|
|
1875
|
+
const modelId = config.model;
|
|
1876
|
+
if (!providerId || !modelId) return { ok: false, reason: "missing provider/model" };
|
|
1877
|
+
if (isSetupProvider(providerId)) return { ok: true };
|
|
1878
|
+
const saved = config.providers?.[providerId];
|
|
1879
|
+
const lookupId = saved?.type && saved.type !== providerId ? saved.type : providerId;
|
|
1880
|
+
const catalogProvider = await modelsRegistry.getProvider(lookupId).catch(() => void 0);
|
|
1881
|
+
if (!catalogProvider && !saved?.family)
|
|
1882
|
+
return { ok: false, reason: `provider "${providerId}" is no longer available` };
|
|
1883
|
+
const hasCredential = (catalogProvider?.envVars ?? saved?.envVars ?? []).some(
|
|
1884
|
+
(envVar) => Boolean(process.env[envVar])
|
|
1885
|
+
) || saved !== void 0 && resolveActiveApiKey(saved) !== void 0 || isKeylessLocalProvider({
|
|
1886
|
+
apiBase: saved?.baseUrl ?? catalogProvider?.apiBase,
|
|
1887
|
+
envVars: saved?.envVars ?? catalogProvider?.envVars
|
|
1888
|
+
});
|
|
1889
|
+
if (!hasCredential) return { ok: false, reason: `provider "${providerId}" has no usable key` };
|
|
1890
|
+
const visible = visibleModelIds(
|
|
1891
|
+
providerId,
|
|
1892
|
+
config,
|
|
1893
|
+
(catalogProvider?.models ?? []).map((m) => m.id),
|
|
1894
|
+
saved
|
|
1895
|
+
);
|
|
1896
|
+
if (visible.length > 0 && !visible.includes(modelId)) {
|
|
1897
|
+
return {
|
|
1898
|
+
ok: false,
|
|
1899
|
+
reason: `model "${modelId}" is no longer available for provider "${providerId}"`
|
|
1900
|
+
};
|
|
2363
1901
|
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
if (
|
|
2371
|
-
|
|
2372
|
-
|
|
1902
|
+
return { ok: true };
|
|
1903
|
+
}
|
|
1904
|
+
async function autoSelectSavedProvider(config, modelsRegistry) {
|
|
1905
|
+
const providers = config.providers ?? {};
|
|
1906
|
+
const entries = config.provider && providers[config.provider] ? [[config.provider, providers[config.provider]]] : Object.entries(providers);
|
|
1907
|
+
for (const [id, cfg] of entries) {
|
|
1908
|
+
if (!cfg) continue;
|
|
1909
|
+
let model = cfg.models?.[0];
|
|
1910
|
+
if (!model) {
|
|
1911
|
+
const catalogId = cfg.type && cfg.type !== id ? cfg.type : id;
|
|
1912
|
+
const catalog = await modelsRegistry.getProvider(catalogId).catch(() => void 0);
|
|
1913
|
+
model = catalog?.models?.[0]?.id;
|
|
2373
1914
|
}
|
|
2374
|
-
|
|
1915
|
+
if (model) return { provider: id, model };
|
|
2375
1916
|
}
|
|
2376
|
-
|
|
2377
|
-
|
|
1917
|
+
return void 0;
|
|
1918
|
+
}
|
|
1919
|
+
function resolveBundledSkillsDir2() {
|
|
1920
|
+
try {
|
|
1921
|
+
const req = createRequire4(import.meta.url);
|
|
1922
|
+
const corePkg = wrongstackPackageJsonPath3("@wrongstack/core", (id) => req.resolve(id));
|
|
1923
|
+
return path7.join(path7.dirname(corePkg), "skills");
|
|
1924
|
+
} catch {
|
|
1925
|
+
return void 0;
|
|
2378
1926
|
}
|
|
2379
|
-
|
|
2380
|
-
|
|
1927
|
+
}
|
|
1928
|
+
function resolveBundledPromptsDir2() {
|
|
1929
|
+
try {
|
|
1930
|
+
const req = createRequire4(import.meta.url);
|
|
1931
|
+
const corePkg = wrongstackPackageJsonPath3("@wrongstack/core", (id) => req.resolve(id));
|
|
1932
|
+
return path7.join(path7.dirname(corePkg), "data", "prompts");
|
|
1933
|
+
} catch {
|
|
1934
|
+
return void 0;
|
|
2381
1935
|
}
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
1936
|
+
}
|
|
1937
|
+
function isHomeDirectory(cwd, userHome) {
|
|
1938
|
+
if (!cwd || !userHome) return false;
|
|
1939
|
+
return path7.resolve(cwd) === path7.resolve(userHome);
|
|
1940
|
+
}
|
|
1941
|
+
function shouldPrintYoloNotice(lastChoices, yoloPinned, yolo) {
|
|
1942
|
+
return !lastChoices && yoloPinned === void 0 && yolo;
|
|
1943
|
+
}
|
|
1944
|
+
async function checkGitInCwd(opts) {
|
|
1945
|
+
const { cwd, renderer, reader } = opts;
|
|
1946
|
+
const cwdGit = path7.join(cwd, ".git");
|
|
1947
|
+
let hasCwdGit = false;
|
|
1948
|
+
try {
|
|
1949
|
+
await fs6.access(cwdGit);
|
|
1950
|
+
hasCwdGit = true;
|
|
1951
|
+
} catch {
|
|
2389
1952
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
1953
|
+
if (!hasCwdGit) {
|
|
1954
|
+
renderer.write(
|
|
1955
|
+
`
|
|
1956
|
+
${color9.amber("\u25CB")} This folder has no ${color9.bold(".git")} repository.
|
|
1957
|
+
`
|
|
1958
|
+
);
|
|
1959
|
+
const answer = (await reader.readLine(` ${color9.amber("?")} Initialize one here? ${color9.dim("[y/N]")} `)).trim().toLowerCase();
|
|
1960
|
+
if (answer === "y" || answer === "yes") {
|
|
1961
|
+
try {
|
|
1962
|
+
const { spawn: spawn2 } = await import("node:child_process");
|
|
1963
|
+
await new Promise((resolve3, reject) => {
|
|
1964
|
+
const child = spawn2("git", ["init"], {
|
|
1965
|
+
cwd,
|
|
1966
|
+
signal: AbortSignal.timeout(1e4),
|
|
1967
|
+
windowsHide: true
|
|
1968
|
+
});
|
|
1969
|
+
child.on("error", reject);
|
|
1970
|
+
child.on(
|
|
1971
|
+
"close",
|
|
1972
|
+
(code) => code === 0 ? resolve3() : reject(new Error(`git init failed with ${code}`))
|
|
1973
|
+
);
|
|
1974
|
+
});
|
|
1975
|
+
renderer.write(` ${color9.green("\u2713")} Git repository initialized
|
|
2394
1976
|
`);
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
const text = typeof block.content === "string" ? block.content : JSON.stringify(block.content);
|
|
2401
|
-
this.writeToolResult(block.name ?? "result", text, !!block.is_error);
|
|
2402
|
-
return;
|
|
2403
|
-
}
|
|
2404
|
-
if (this.suppressStdout()) return;
|
|
2405
|
-
if (block.type === "text") {
|
|
2406
|
-
this.write(block);
|
|
2407
|
-
} else if (block.type === "tool_use") {
|
|
2408
|
-
this.writeToolCall(block.name, block.input);
|
|
1977
|
+
hasCwdGit = true;
|
|
1978
|
+
} catch (err) {
|
|
1979
|
+
renderer.writeError(`git init failed: ${toErrorMessage4(err)}
|
|
1980
|
+
`);
|
|
1981
|
+
}
|
|
2409
1982
|
}
|
|
2410
1983
|
}
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
`
|
|
2418
|
-
|
|
1984
|
+
const parentDir = path7.dirname(cwd);
|
|
1985
|
+
if (parentDir !== cwd) {
|
|
1986
|
+
try {
|
|
1987
|
+
await fs6.access(path7.join(parentDir, ".git"));
|
|
1988
|
+
renderer.write(
|
|
1989
|
+
` ${color9.dim("\u2139")} A ${color9.bold(".git")} repo exists in the parent directory: ${color9.dim(parentDir)}
|
|
1990
|
+
`
|
|
1991
|
+
);
|
|
1992
|
+
} catch {
|
|
1993
|
+
}
|
|
2419
1994
|
}
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// src/boot-launch-choices.ts
|
|
1998
|
+
async function applyBootLaunchChoices(input) {
|
|
1999
|
+
let { config } = input;
|
|
2000
|
+
const {
|
|
2001
|
+
isInteractiveTTY,
|
|
2002
|
+
simpleUiFullAuto,
|
|
2003
|
+
flags,
|
|
2004
|
+
renderer,
|
|
2005
|
+
reader,
|
|
2006
|
+
profileConfigPath,
|
|
2007
|
+
wpaths,
|
|
2008
|
+
projectRoot
|
|
2009
|
+
} = input;
|
|
2010
|
+
if (isInteractiveTTY) {
|
|
2011
|
+
const promptMenu = await maybeRunSystemPromptMenu({
|
|
2012
|
+
isInteractiveTTY,
|
|
2013
|
+
flags,
|
|
2014
|
+
renderer,
|
|
2015
|
+
reader,
|
|
2016
|
+
profileConfigPath,
|
|
2017
|
+
paths: {
|
|
2018
|
+
globalDir: wpaths.globalInstructions,
|
|
2019
|
+
projectDir: wpaths.inProjectInstructions
|
|
2020
|
+
},
|
|
2021
|
+
outsideProject: await isOutsideProject(projectRoot)
|
|
2022
|
+
});
|
|
2023
|
+
if (promptMenu.aborted) {
|
|
2024
|
+
await reader.close();
|
|
2025
|
+
return { kind: "exit", code: 0 };
|
|
2424
2026
|
}
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
const renderMode = this.getRenderMode(name);
|
|
2428
|
-
this.clearRenderMode(name);
|
|
2429
|
-
const bodyText = extractBodyText(content, name);
|
|
2430
|
-
if (isError) {
|
|
2431
|
-
const firstLine = txt.split("\n")[0] ?? "";
|
|
2432
|
-
const truncated = firstLine.length > 200 ? `${firstLine.slice(0, 197)}\u2026` : firstLine;
|
|
2433
|
-
this.out.write(` ${prefix} ${color10.dim(truncated)}
|
|
2434
|
-
`);
|
|
2435
|
-
this.lineStart = true;
|
|
2436
|
-
return;
|
|
2027
|
+
if (promptMenu.changed && promptMenu.variant) {
|
|
2028
|
+
config = patchConfig(config, { systemPrompt: { variant: promptMenu.variant } });
|
|
2437
2029
|
}
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
const rendered = renderDiff(diff).split("\n").map((l) => ` ${l}`).join("\n");
|
|
2444
|
-
this.out.write(`${rendered}
|
|
2445
|
-
`);
|
|
2446
|
-
this.lineStart = true;
|
|
2447
|
-
return;
|
|
2030
|
+
if (promptMenu.persistError) {
|
|
2031
|
+
renderer.writeWarning(
|
|
2032
|
+
`Could not save system prompt variant to config: ${toErrorMessage5(promptMenu.persistError)}
|
|
2033
|
+
`
|
|
2034
|
+
);
|
|
2448
2035
|
}
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
if (
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2036
|
+
let modePinned;
|
|
2037
|
+
if (flags["no-tui"]) modePinned = "repl";
|
|
2038
|
+
else if (flags["tui"]) modePinned = "tui";
|
|
2039
|
+
const yoloPinned = flags["no-yolo"] === true ? false : flags["yolo"] === true ? true : void 0;
|
|
2040
|
+
let autonomyPinned;
|
|
2041
|
+
if (flags["no-autonomy"] === true) autonomyPinned = "off";
|
|
2042
|
+
else if (typeof flags["eternal"] === "string" && flags["eternal"].trim() !== "")
|
|
2043
|
+
autonomyPinned = "off";
|
|
2044
|
+
else if (typeof flags["autonomy"] === "string") {
|
|
2045
|
+
const v = flags["autonomy"].toLowerCase();
|
|
2046
|
+
autonomyPinned = v === "off" || v === "no" || v === "false" ? "off" : "auto";
|
|
2047
|
+
} else if (flags["autonomy"] === true) {
|
|
2048
|
+
autonomyPinned = "auto";
|
|
2456
2049
|
}
|
|
2457
|
-
const
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2050
|
+
const lastChoices = config.launch ? {
|
|
2051
|
+
mode: config.launch.mode ?? "tui",
|
|
2052
|
+
yolo: config.yolo ?? true,
|
|
2053
|
+
autonomy: config.launch.autonomy ?? "auto"
|
|
2054
|
+
} : void 0;
|
|
2055
|
+
let choices;
|
|
2056
|
+
try {
|
|
2057
|
+
choices = await runLaunchPrompts({
|
|
2058
|
+
renderer,
|
|
2059
|
+
reader,
|
|
2060
|
+
modePinned,
|
|
2061
|
+
yoloPinned,
|
|
2062
|
+
autonomyPinned,
|
|
2063
|
+
lastChoices
|
|
2064
|
+
});
|
|
2065
|
+
} catch (err) {
|
|
2066
|
+
if (err instanceof LaunchAbortedError) {
|
|
2067
|
+
await reader.close();
|
|
2068
|
+
return { kind: "exit", code: 0 };
|
|
2069
|
+
}
|
|
2070
|
+
throw err;
|
|
2071
|
+
}
|
|
2072
|
+
if (choices.mode === "tui") {
|
|
2073
|
+
flags["tui"] = true;
|
|
2074
|
+
flags["no-tui"] = false;
|
|
2075
|
+
} else {
|
|
2076
|
+
flags["tui"] = false;
|
|
2077
|
+
flags["no-tui"] = true;
|
|
2078
|
+
}
|
|
2079
|
+
if (choices.yolo !== config.yolo) config = patchConfig(config, { yolo: choices.yolo });
|
|
2080
|
+
flags["autonomy"] = choices.autonomy;
|
|
2081
|
+
if (shouldPrintYoloNotice(lastChoices, yoloPinned, choices.yolo)) {
|
|
2082
|
+
writeErr(
|
|
2083
|
+
`
|
|
2084
|
+
${color10.yellow("YOLO is on")}: non-denied tool calls, including shell and file writes, run without confirmation.
|
|
2085
|
+
${color10.dim("Damaging calls and calls your deny rules forbid still ask. Use")} --no-yolo ${color10.dim("or")} /yolo off ${color10.dim("to require prompts.")}
|
|
2086
|
+
|
|
2087
|
+
`
|
|
2088
|
+
);
|
|
2089
|
+
}
|
|
2090
|
+
if ((flags["skip-index"] || flags["skip"]) && config.indexing) {
|
|
2091
|
+
config = patchConfig(config, {
|
|
2092
|
+
indexing: { ...config.indexing, onSessionStart: false }
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
if (!simpleUiFullAuto) {
|
|
2096
|
+
try {
|
|
2097
|
+
const toPersist = flags["webui"] ? { ...choices, mode: lastChoices?.mode ?? config.launch?.mode ?? "tui" } : choices;
|
|
2098
|
+
await persistLaunchChoices(profileConfigPath, toPersist);
|
|
2099
|
+
} catch {
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
} else {
|
|
2103
|
+
const nonInteractiveAutonomy = !simpleUiFullAuto && (flags["no-autonomy"] === true || config.autonomy?.defaultMode === "off") ? "off" : "auto";
|
|
2104
|
+
const effectiveChoices = config.launch ? {
|
|
2105
|
+
mode: flags["no-tui"] ? "repl" : config.launch.mode ?? "tui",
|
|
2106
|
+
yolo: config.yolo ?? true,
|
|
2107
|
+
autonomy: nonInteractiveAutonomy
|
|
2108
|
+
} : {
|
|
2109
|
+
mode: "repl",
|
|
2110
|
+
yolo: true,
|
|
2111
|
+
autonomy: nonInteractiveAutonomy
|
|
2112
|
+
};
|
|
2113
|
+
if (effectiveChoices.mode === "repl") {
|
|
2114
|
+
flags["tui"] = false;
|
|
2115
|
+
flags["no-tui"] = true;
|
|
2116
|
+
}
|
|
2117
|
+
flags["autonomy"] = effectiveChoices.autonomy;
|
|
2118
|
+
}
|
|
2119
|
+
return { kind: "ok", config };
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
// src/boot-provider-gate.ts
|
|
2123
|
+
import { color as color11, isStdinTTY as isStdinTTY2, toErrorMessage as toErrorMessage6, writeErr as writeErr2 } from "@wrongstack/core/utils";
|
|
2124
|
+
import { isSetupProvider as isSetupProvider2, SETUP_MODEL_ID, SETUP_PROVIDER_ID } from "@wrongstack/providers";
|
|
2125
|
+
async function resolveBootProviderModel(input) {
|
|
2126
|
+
let { config, vault } = input;
|
|
2127
|
+
const { flags, modelsRegistry, renderer, reader, profileConfigPath, noInteractiveMode } = input;
|
|
2128
|
+
const providerFlag = typeof flags["provider"] === "string" ? flags["provider"] : void 0;
|
|
2129
|
+
const modelFlag = typeof flags["model"] === "string" ? flags["model"] : void 0;
|
|
2130
|
+
if (noInteractiveMode && (!config.provider || !config.model)) {
|
|
2131
|
+
if (providerFlag && modelFlag) {
|
|
2132
|
+
config = patchConfig(config, { provider: providerFlag, model: modelFlag });
|
|
2133
|
+
} else {
|
|
2134
|
+
const picked = await autoSelectSavedProvider(config, modelsRegistry);
|
|
2135
|
+
if (picked) config = patchConfig(config, picked);
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
if (!(!!providerFlag && !!modelFlag)) {
|
|
2139
|
+
if (isStdinTTY2() && !noInteractiveMode) {
|
|
2140
|
+
let picked;
|
|
2141
|
+
let skipPicker = false;
|
|
2142
|
+
if (isSetupProvider2(config.provider)) {
|
|
2143
|
+
skipPicker = true;
|
|
2144
|
+
renderer.write(
|
|
2145
|
+
`
|
|
2146
|
+
${color11.amber("\u25B6")} ${color11.bold("Setup mode")} ${color11.dim("\u2014 no model connected. Run")} ${color11.bold("/auth")} ${color11.dim("to connect one.")}
|
|
2147
|
+
|
|
2148
|
+
`
|
|
2149
|
+
);
|
|
2150
|
+
} else if (!await hasAnyCredential(config, modelsRegistry)) {
|
|
2151
|
+
const outcome = await runFirstRunSetup({
|
|
2152
|
+
renderer,
|
|
2153
|
+
reader,
|
|
2154
|
+
modelsRegistry,
|
|
2155
|
+
vault,
|
|
2156
|
+
profileConfigPath,
|
|
2157
|
+
reloadConfig: async () => (await bootConfig(flags)).config
|
|
2158
|
+
});
|
|
2159
|
+
if (outcome.kind === "quit") {
|
|
2160
|
+
await reader.close();
|
|
2161
|
+
return { kind: "exit", code: 0 };
|
|
2162
|
+
}
|
|
2163
|
+
if (outcome.kind === "setup-mode") {
|
|
2164
|
+
config = patchConfig(config, { provider: SETUP_PROVIDER_ID, model: SETUP_MODEL_ID });
|
|
2165
|
+
await saveToGlobalConfig(profileConfigPath, SETUP_PROVIDER_ID, SETUP_MODEL_ID);
|
|
2166
|
+
skipPicker = true;
|
|
2167
|
+
} else {
|
|
2168
|
+
try {
|
|
2169
|
+
const reloaded = await bootConfig(flags);
|
|
2170
|
+
config = reloaded.config;
|
|
2171
|
+
vault = reloaded.vault;
|
|
2172
|
+
} catch (err) {
|
|
2173
|
+
writeErr2(`Config error after setup: ${toErrorMessage6(err)}
|
|
2462
2174
|
`);
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2175
|
+
await reader.close();
|
|
2176
|
+
return { kind: "exit", code: 2 };
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
const savedProvider = config.provider;
|
|
2181
|
+
const savedModel = config.model;
|
|
2182
|
+
if (!skipPicker && savedProvider && savedModel) {
|
|
2183
|
+
const savedStatus = await validateSavedProviderModel(config, modelsRegistry);
|
|
2184
|
+
renderer.write(
|
|
2185
|
+
`
|
|
2186
|
+
${color11.dim("Last settings:")} ${color11.bold(savedProvider)} / ${color11.bold(savedModel)}
|
|
2187
|
+
`
|
|
2188
|
+
);
|
|
2189
|
+
if (!savedStatus.ok) {
|
|
2190
|
+
renderer.writeWarning(
|
|
2191
|
+
`Saved provider/model is no longer usable (${savedStatus.reason ?? "unknown reason"}); choose a provider.
|
|
2192
|
+
`
|
|
2193
|
+
);
|
|
2194
|
+
} else {
|
|
2195
|
+
const answer = (await reader.readLine(
|
|
2196
|
+
` ${color11.amber("?")} Continue with these? ${color11.dim("[Y/n/q]")} ${color11.dim("(auto Y in 5s)")} `,
|
|
2197
|
+
{ timeoutMs: 5e3, defaultAnswer: "y" }
|
|
2198
|
+
)).trim().toLowerCase();
|
|
2199
|
+
if (answer === "q") {
|
|
2200
|
+
renderer.write(color11.dim(" Goodbye!\n"));
|
|
2201
|
+
await reader.close();
|
|
2202
|
+
return { kind: "exit", code: 0 };
|
|
2203
|
+
}
|
|
2204
|
+
if (answer !== "n" && answer !== "no") {
|
|
2205
|
+
skipPicker = true;
|
|
2206
|
+
renderer.write(
|
|
2207
|
+
`
|
|
2208
|
+
${color11.green("\u25B6")} ${color11.bold(savedProvider)} / ${color11.bold(savedModel)}
|
|
2209
|
+
|
|
2210
|
+
`
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
if (!skipPicker) {
|
|
2216
|
+
picked = await runPicker({
|
|
2217
|
+
modelsRegistry,
|
|
2218
|
+
renderer,
|
|
2219
|
+
reader,
|
|
2220
|
+
config,
|
|
2221
|
+
defaultProvider: providerFlag ?? config.provider,
|
|
2222
|
+
defaultModel: modelFlag ?? config.model
|
|
2223
|
+
});
|
|
2224
|
+
}
|
|
2225
|
+
if (!picked && !skipPicker) {
|
|
2226
|
+
if (!config.provider || !config.model) {
|
|
2227
|
+
renderer.write(
|
|
2228
|
+
`
|
|
2229
|
+
${color11.dim("No provider selected.")}
|
|
2230
|
+
${color11.dim("Run")} ${color11.bold("wstack auth")} ${color11.dim("to add a key or sign in, or start with no model:")}
|
|
2231
|
+
${color11.bold(`wstack --provider ${SETUP_PROVIDER_ID} --model ${SETUP_MODEL_ID}`)}
|
|
2232
|
+
|
|
2233
|
+
`
|
|
2234
|
+
);
|
|
2235
|
+
await reader.close();
|
|
2236
|
+
return { kind: "exit", code: 2 };
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
if (picked) {
|
|
2240
|
+
const prevProvider = config.provider;
|
|
2241
|
+
const prevModel = config.model;
|
|
2242
|
+
const prevEffort = config.modelRuntime?.reasoning?.effort;
|
|
2243
|
+
config = patchConfig(config, {
|
|
2244
|
+
provider: picked.provider,
|
|
2245
|
+
model: picked.model,
|
|
2246
|
+
...picked.effort ? {
|
|
2247
|
+
modelRuntime: {
|
|
2248
|
+
...config.modelRuntime,
|
|
2249
|
+
reasoning: { ...config.modelRuntime?.reasoning, effort: picked.effort }
|
|
2250
|
+
}
|
|
2251
|
+
} : {}
|
|
2252
|
+
});
|
|
2253
|
+
const effortChanged = picked.effort !== void 0 && picked.effort !== prevEffort;
|
|
2254
|
+
if (picked.provider !== prevProvider || picked.model !== prevModel || effortChanged) {
|
|
2255
|
+
const label = `${picked.provider}/${picked.model}${picked.effort ? ` (effort ${picked.effort})` : ""}`;
|
|
2256
|
+
const saved = await saveToGlobalConfig(profileConfigPath, picked.provider, picked.model, {
|
|
2257
|
+
effort: picked.effort
|
|
2258
|
+
});
|
|
2259
|
+
if (saved) {
|
|
2260
|
+
renderer.writeInfo(`Saved ${label} as default.
|
|
2466
2261
|
`);
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2262
|
+
} else {
|
|
2263
|
+
renderer.writeWarning(
|
|
2264
|
+
`Could not save ${label} to config. Check permissions or disk space.
|
|
2265
|
+
`
|
|
2266
|
+
);
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
}
|
|
2270
|
+
} else if (!config.provider || !config.model) {
|
|
2271
|
+
writeErr2(
|
|
2272
|
+
`No provider or model configured. Run \`wstack auth\`, or pass --provider <id> --model <id>.
|
|
2273
|
+
To start the app with no model connected, pass --provider ${SETUP_PROVIDER_ID} --model ${SETUP_MODEL_ID}.
|
|
2471
2274
|
`
|
|
2472
2275
|
);
|
|
2276
|
+
await reader.close();
|
|
2277
|
+
return { kind: "exit", code: 2 };
|
|
2278
|
+
} else {
|
|
2279
|
+
const savedStatus = await validateSavedProviderModel(config, modelsRegistry);
|
|
2280
|
+
if (!savedStatus.ok) {
|
|
2281
|
+
writeErr2(
|
|
2282
|
+
`Saved provider/model is no longer usable (${savedStatus.reason ?? "unknown reason"}). Run \`wstack auth\` or pass --provider <id> --model <id>.
|
|
2283
|
+
`
|
|
2284
|
+
);
|
|
2285
|
+
await reader.close();
|
|
2286
|
+
return { kind: "exit", code: 2 };
|
|
2287
|
+
}
|
|
2473
2288
|
}
|
|
2474
|
-
this.lineStart = true;
|
|
2475
2289
|
}
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2290
|
+
return { kind: "ok", config, vault };
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
// src/boot-subcommand-dispatch.ts
|
|
2294
|
+
import { TOKENS as TOKENS2 } from "@wrongstack/core/kernel";
|
|
2295
|
+
import { ToolRegistry } from "@wrongstack/core/registry";
|
|
2296
|
+
import { normalizeTokenSavingTier } from "@wrongstack/core/types";
|
|
2297
|
+
import { createDefaultContainer as createDefaultContainer2 } from "@wrongstack/runtime";
|
|
2298
|
+
import { registerBuiltinToolTier } from "@wrongstack/tools/tool-tier";
|
|
2299
|
+
async function dispatchBootSubcommand(input) {
|
|
2300
|
+
const {
|
|
2301
|
+
first,
|
|
2302
|
+
positional,
|
|
2303
|
+
flags,
|
|
2304
|
+
config,
|
|
2305
|
+
vault,
|
|
2306
|
+
wpaths,
|
|
2307
|
+
cwd,
|
|
2308
|
+
projectRoot,
|
|
2309
|
+
userHome,
|
|
2310
|
+
renderer,
|
|
2311
|
+
reader,
|
|
2312
|
+
logger,
|
|
2313
|
+
modelsRegistry
|
|
2314
|
+
} = input;
|
|
2315
|
+
const subcommandHandler = first ? subcommands[first] : void 0;
|
|
2316
|
+
if (first && subcommandHandler) {
|
|
2317
|
+
if (flags["help"] === true || flags["h"] === true) {
|
|
2318
|
+
const deepSub = positional[1];
|
|
2319
|
+
if (deepSub && renderDeepHelp(`${first}:${deepSub}`, renderer)) {
|
|
2320
|
+
await reader.close();
|
|
2321
|
+
return 0;
|
|
2322
|
+
}
|
|
2323
|
+
if (renderFocusedHelp(first, renderer)) {
|
|
2324
|
+
await reader.close();
|
|
2325
|
+
return 0;
|
|
2326
|
+
}
|
|
2327
|
+
}
|
|
2328
|
+
const container = createDefaultContainer2({
|
|
2329
|
+
config,
|
|
2330
|
+
wpaths,
|
|
2331
|
+
logger,
|
|
2332
|
+
modelsRegistry,
|
|
2333
|
+
bundledSkillsDir: config.features.skills ? resolveBundledSkillsDir2() : void 0,
|
|
2334
|
+
bundledPromptsDir: config.features.prompts === false ? void 0 : resolveBundledPromptsDir2()
|
|
2335
|
+
});
|
|
2336
|
+
const sessionStore = container.resolve(TOKENS2.SessionStore);
|
|
2337
|
+
const skillLoader = container.resolve(TOKENS2.SkillLoader);
|
|
2338
|
+
const toolRegistryForSubcmd = new ToolRegistry();
|
|
2339
|
+
registerBuiltinToolTier({
|
|
2340
|
+
registry: toolRegistryForSubcmd,
|
|
2341
|
+
tier: normalizeTokenSavingTier(config.features.tokenSavingMode)
|
|
2342
|
+
});
|
|
2343
|
+
const code = await subcommandHandler(positional.slice(1), {
|
|
2344
|
+
config,
|
|
2345
|
+
renderer,
|
|
2346
|
+
reader,
|
|
2347
|
+
sessionStore,
|
|
2348
|
+
skillLoader,
|
|
2349
|
+
toolRegistry: toolRegistryForSubcmd,
|
|
2350
|
+
modelsRegistry,
|
|
2351
|
+
paths: wpaths,
|
|
2352
|
+
vault,
|
|
2353
|
+
cwd,
|
|
2354
|
+
projectRoot,
|
|
2355
|
+
userHome,
|
|
2356
|
+
flags
|
|
2357
|
+
});
|
|
2358
|
+
await reader.close();
|
|
2359
|
+
return code;
|
|
2482
2360
|
}
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2361
|
+
return void 0;
|
|
2362
|
+
}
|
|
2363
|
+
|
|
2364
|
+
// src/input-reader.ts
|
|
2365
|
+
import * as fs7 from "node:fs/promises";
|
|
2366
|
+
import * as path8 from "node:path";
|
|
2367
|
+
import * as readline from "node:readline";
|
|
2368
|
+
import {
|
|
2369
|
+
resolveWstackPaths,
|
|
2370
|
+
setOutputLineGuard,
|
|
2371
|
+
setRawMode as setRawMode2,
|
|
2372
|
+
writeOut
|
|
2373
|
+
} from "@wrongstack/core/utils";
|
|
2374
|
+
var ReadlineInputReader = class {
|
|
2375
|
+
rl;
|
|
2376
|
+
historyFile;
|
|
2377
|
+
history = [];
|
|
2378
|
+
pending = false;
|
|
2379
|
+
suppressNextHistory = false;
|
|
2380
|
+
constructor(opts = {}) {
|
|
2381
|
+
this.historyFile = opts.historyFile ?? resolveWstackPaths({ projectRoot: process.cwd() }).historyFile;
|
|
2487
2382
|
}
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2383
|
+
async loadHistory() {
|
|
2384
|
+
try {
|
|
2385
|
+
const raw = await fs7.readFile(this.historyFile, "utf8");
|
|
2386
|
+
this.history = raw.split("\n").filter(Boolean).slice(-1e3);
|
|
2387
|
+
} catch {
|
|
2388
|
+
this.history = [];
|
|
2389
|
+
}
|
|
2492
2390
|
}
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2391
|
+
async saveHistory() {
|
|
2392
|
+
try {
|
|
2393
|
+
await fs7.mkdir(path8.dirname(this.historyFile), { recursive: true });
|
|
2394
|
+
await fs7.writeFile(this.historyFile, this.history.slice(-1e3).join("\n"));
|
|
2395
|
+
} catch {
|
|
2396
|
+
}
|
|
2497
2397
|
}
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
*
|
|
2501
|
-
* Deliberately stricter than writeWarning/writeError/writeInfo, which emit
|
|
2502
|
-
* *diagnostics* and are suppressed only when a TUI owns the terminal (so
|
|
2503
|
-
* they survive silent mode). This method is for informational mirrors of
|
|
2504
|
-
* stdout output, where the stdout copy already carries the full content —
|
|
2505
|
-
* in silent mode the operator asked for machine-only output, so the mirror
|
|
2506
|
-
* is dropped rather than leaked onto a quieter stream. It therefore honors
|
|
2507
|
-
* the full {@link suppressStdout} gate.
|
|
2508
|
-
*
|
|
2509
|
-
* Used by `wstack mailbox serve` to route the human-readable startup banner
|
|
2510
|
-
* away from stdout, keeping the mailbox_serve_started JSON line cleanly
|
|
2511
|
-
* pipeable (`mailbox serve | jq`).
|
|
2512
|
-
*/
|
|
2513
|
-
writeStderr(text) {
|
|
2514
|
-
if (this.suppressStdout()) return;
|
|
2515
|
-
this.err.write(text);
|
|
2398
|
+
shouldPersistHistory(line) {
|
|
2399
|
+
return !/^\s*\/(?:telegram-setup|tg-setup)\s+\d+:[A-Za-z0-9_-]+(?:\s|$)/i.test(line);
|
|
2516
2400
|
}
|
|
2517
|
-
|
|
2518
|
-
if (this.
|
|
2519
|
-
|
|
2520
|
-
|
|
2401
|
+
ensure() {
|
|
2402
|
+
if (!this.rl) {
|
|
2403
|
+
this.rl = readline.createInterface({
|
|
2404
|
+
input: process.stdin,
|
|
2405
|
+
output: process.stdout,
|
|
2406
|
+
history: this.history,
|
|
2407
|
+
terminal: process.stdin.isTTY
|
|
2408
|
+
});
|
|
2409
|
+
}
|
|
2410
|
+
return this.rl;
|
|
2521
2411
|
}
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
* │ ✓ [role] done in 4m 32s │
|
|
2527
|
-
* │ 127 iterations · 341 tools │
|
|
2528
|
-
* │ Found 14 bugs across 6 files... │
|
|
2529
|
-
* └─────────────────────────────────────┘
|
|
2530
|
-
*/
|
|
2531
|
-
writeAgentSummary(summary, ok) {
|
|
2532
|
-
if (this.suppressStdout()) return;
|
|
2533
|
-
if (!this.lineStart) this.out.write("\n");
|
|
2534
|
-
const lines = summary.split("\n");
|
|
2535
|
-
const icon = ok ? theme2.success("\u2713") : theme2.error("\u2718");
|
|
2536
|
-
const firstLine = `${icon} ${lines[0] ?? summary}`;
|
|
2537
|
-
const body = lines.slice(1);
|
|
2538
|
-
const maxWidth = Math.min(process.stdout.columns ?? 80, 120);
|
|
2539
|
-
const contentWidth = Math.max(
|
|
2540
|
-
firstLine.length,
|
|
2541
|
-
body.reduce((a, l) => Math.max(a, l.length), 0)
|
|
2542
|
-
);
|
|
2543
|
-
const boxWidth = Math.min(Math.max(contentWidth + 4, 44), maxWidth);
|
|
2544
|
-
const thick = "\u2501".repeat(boxWidth - 2);
|
|
2545
|
-
const thin = "\u2500".repeat(boxWidth - 2);
|
|
2546
|
-
this.out.write(`
|
|
2547
|
-
${theme2.primary("\u250C")}${thick}${theme2.primary("\u2510")}
|
|
2548
|
-
`);
|
|
2549
|
-
const centre = (s) => {
|
|
2550
|
-
const inner = ` ${s} `;
|
|
2551
|
-
const padLen = Math.max(0, boxWidth - 2 - s.length);
|
|
2552
|
-
const left = Math.floor(padLen / 2);
|
|
2553
|
-
const right = padLen - left;
|
|
2554
|
-
return `${" ".repeat(left)}${inner}${" ".repeat(right)}`;
|
|
2555
|
-
};
|
|
2556
|
-
this.out.write(` ${theme2.primary("\u2502")}${centre(firstLine)}${theme2.primary("\u2502")}
|
|
2557
|
-
`);
|
|
2558
|
-
for (const l of body) {
|
|
2559
|
-
this.out.write(
|
|
2560
|
-
` ${theme2.primary("\u2502")} ${l}${" ".repeat(Math.max(0, boxWidth - 3 - l.length))}${theme2.primary("\u2502")}
|
|
2561
|
-
`
|
|
2562
|
-
);
|
|
2412
|
+
async readLine(prompt, timeoutOpts) {
|
|
2413
|
+
if (this.history.length === 0) await this.loadHistory();
|
|
2414
|
+
while (this.pending) {
|
|
2415
|
+
await new Promise((resolve3) => setTimeout(resolve3, 50));
|
|
2563
2416
|
}
|
|
2564
|
-
this.
|
|
2417
|
+
this.pending = true;
|
|
2418
|
+
try {
|
|
2419
|
+
if (this.rl) {
|
|
2420
|
+
const old = this.rl;
|
|
2421
|
+
this.rl = void 0;
|
|
2422
|
+
await new Promise((resolve3) => {
|
|
2423
|
+
if (old.closed) {
|
|
2424
|
+
resolve3();
|
|
2425
|
+
} else {
|
|
2426
|
+
old.once("close", resolve3);
|
|
2427
|
+
old.close();
|
|
2428
|
+
}
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
const fresh = this.ensure();
|
|
2432
|
+
this.installPromptGuard(fresh);
|
|
2433
|
+
const line = await new Promise((resolve3) => {
|
|
2434
|
+
let settled = false;
|
|
2435
|
+
let timeoutHandle;
|
|
2436
|
+
const settle = (line2) => {
|
|
2437
|
+
if (settled) return;
|
|
2438
|
+
settled = true;
|
|
2439
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
2440
|
+
setOutputLineGuard(null);
|
|
2441
|
+
resolve3(line2);
|
|
2442
|
+
};
|
|
2443
|
+
if (timeoutOpts) {
|
|
2444
|
+
timeoutHandle = setTimeout(() => {
|
|
2445
|
+
if (!settled) {
|
|
2446
|
+
fresh.write(`${timeoutOpts.defaultAnswer}
|
|
2565
2447
|
`);
|
|
2566
|
-
|
|
2448
|
+
}
|
|
2449
|
+
}, timeoutOpts.timeoutMs);
|
|
2450
|
+
}
|
|
2451
|
+
fresh.question(prompt ?? "> ", (line2) => {
|
|
2452
|
+
const persistHistory = !this.suppressNextHistory && this.shouldPersistHistory(line2);
|
|
2453
|
+
this.suppressNextHistory = false;
|
|
2454
|
+
if (line2.trim() && persistHistory) {
|
|
2455
|
+
this.history.push(line2);
|
|
2456
|
+
this.saveHistory().then(void 0, () => {
|
|
2457
|
+
});
|
|
2458
|
+
}
|
|
2459
|
+
settle(line2);
|
|
2460
|
+
});
|
|
2461
|
+
fresh.once("close", () => settle(""));
|
|
2462
|
+
fresh.on?.("error", (_e) => settle(""));
|
|
2463
|
+
}).then((result) => {
|
|
2464
|
+
if (this.rl === fresh) this.rl = void 0;
|
|
2465
|
+
fresh.close();
|
|
2466
|
+
return result;
|
|
2467
|
+
});
|
|
2468
|
+
return line;
|
|
2469
|
+
} finally {
|
|
2470
|
+
this.pending = false;
|
|
2471
|
+
}
|
|
2567
2472
|
}
|
|
2568
2473
|
/**
|
|
2569
|
-
*
|
|
2570
|
-
*
|
|
2571
|
-
*
|
|
2474
|
+
* Install the out-of-band write guard for the active prompt. When a log
|
|
2475
|
+
* line or other async output lands while the user is mid-type, the guard
|
|
2476
|
+
* clears the draft row, lets the message print, then repaints the prompt
|
|
2477
|
+
* and the in-progress draft (cursor preserved) via readline's own
|
|
2478
|
+
* refresh. Without it, each async write leaves the half-typed line
|
|
2479
|
+
* stranded as a fresh scrollback row.
|
|
2480
|
+
*
|
|
2481
|
+
* No-op on non-TTY output (piped/redirected) — there's no draft to
|
|
2482
|
+
* protect and the ANSI clear/repaint would be noise in a file.
|
|
2572
2483
|
*/
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
if (
|
|
2576
|
-
|
|
2577
|
-
if (!summary) continue;
|
|
2578
|
-
this.writeAgentSummary(summary, ok);
|
|
2579
|
-
}
|
|
2484
|
+
installPromptGuard(rl) {
|
|
2485
|
+
const out = process.stdout;
|
|
2486
|
+
if (!out.isTTY) {
|
|
2487
|
+
setOutputLineGuard(null);
|
|
2580
2488
|
return;
|
|
2581
2489
|
}
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
if (b.type !== "tool_result" || b.name !== "delegate") continue;
|
|
2589
|
-
let obj;
|
|
2490
|
+
setOutputLineGuard({
|
|
2491
|
+
suspend() {
|
|
2492
|
+
readline.cursorTo(out, 0);
|
|
2493
|
+
readline.clearLine(out, 0);
|
|
2494
|
+
},
|
|
2495
|
+
resume() {
|
|
2590
2496
|
try {
|
|
2591
|
-
|
|
2497
|
+
rl.prompt(true);
|
|
2592
2498
|
} catch {
|
|
2593
|
-
continue;
|
|
2594
|
-
}
|
|
2595
|
-
const o = obj;
|
|
2596
|
-
if (o.summary) {
|
|
2597
|
-
this.writeAgentSummary(o.summary, o.ok ?? true);
|
|
2598
2499
|
}
|
|
2599
2500
|
}
|
|
2600
|
-
}
|
|
2601
|
-
}
|
|
2602
|
-
};
|
|
2603
|
-
function renderMarkdown(s) {
|
|
2604
|
-
let out = s;
|
|
2605
|
-
out = out.replace(/^(#{1,6}) (.+)$/gm, (_m, hashes, text) => {
|
|
2606
|
-
return theme2.primary(theme2.bold(`${hashes} ${text}`));
|
|
2607
|
-
});
|
|
2608
|
-
out = out.replace(/```([a-zA-Z0-9_+-]*)\n([\s\S]*?)```/g, (_m, _lang, code) => {
|
|
2609
|
-
return color10.gray(`
|
|
2610
|
-
\u250C\u2500
|
|
2611
|
-
${code.replace(/^/gm, "\u2502 ")}\u2514\u2500`);
|
|
2612
|
-
});
|
|
2613
|
-
out = out.replace(/`([^`\n]+)`/g, (_m, code) => theme2.accent(code));
|
|
2614
|
-
out = out.replace(/\*\*([^*]+)\*\*/g, (_m, text) => theme2.bold(text));
|
|
2615
|
-
out = out.replace(
|
|
2616
|
-
/(^|[^*])\*([^*\n]+)\*([^*]|$)/g,
|
|
2617
|
-
(_m, l, t, r) => `${l}${color10.italic(t)}${r}`
|
|
2618
|
-
);
|
|
2619
|
-
return out;
|
|
2620
|
-
}
|
|
2621
|
-
function formatInputSummary(input) {
|
|
2622
|
-
if (!input || typeof input !== "object") return "";
|
|
2623
|
-
const obj = input;
|
|
2624
|
-
if (typeof obj["path"] === "string") return obj["path"];
|
|
2625
|
-
if (typeof obj["url"] === "string") return obj["url"];
|
|
2626
|
-
if (typeof obj["command"] === "string") {
|
|
2627
|
-
const cmd = obj["command"];
|
|
2628
|
-
return cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
|
|
2629
|
-
}
|
|
2630
|
-
if (typeof obj["pattern"] === "string") return obj["pattern"];
|
|
2631
|
-
return "";
|
|
2632
|
-
}
|
|
2633
|
-
function safeStringify(value) {
|
|
2634
|
-
if (typeof value === "string") return value;
|
|
2635
|
-
try {
|
|
2636
|
-
return JSON.stringify(value);
|
|
2637
|
-
} catch {
|
|
2638
|
-
return String(value);
|
|
2639
|
-
}
|
|
2640
|
-
}
|
|
2641
|
-
function extractBodyText(value, name) {
|
|
2642
|
-
let v = value;
|
|
2643
|
-
if (typeof value === "string") {
|
|
2644
|
-
const trimmed = value.trimStart();
|
|
2645
|
-
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
2646
|
-
try {
|
|
2647
|
-
v = JSON.parse(value);
|
|
2648
|
-
} catch {
|
|
2649
|
-
return value;
|
|
2650
|
-
}
|
|
2651
|
-
} else {
|
|
2652
|
-
return value;
|
|
2653
|
-
}
|
|
2654
|
-
}
|
|
2655
|
-
if (typeof v !== "object" || v === null) {
|
|
2656
|
-
return safeStringify(value);
|
|
2657
|
-
}
|
|
2658
|
-
const o = v;
|
|
2659
|
-
if (name === "read" && typeof o["text"] === "string") return o["text"];
|
|
2660
|
-
if (name === "bash") {
|
|
2661
|
-
const output = typeof o["output"] === "string" ? o["output"] : "";
|
|
2662
|
-
const error = typeof o["error"] === "string" ? o["error"] : "";
|
|
2663
|
-
if (output && error) return `${output}
|
|
2664
|
-
${error}`;
|
|
2665
|
-
return output || error || "";
|
|
2666
|
-
}
|
|
2667
|
-
if (name === "exec") {
|
|
2668
|
-
const stdout = typeof o["stdout"] === "string" ? o["stdout"] : "";
|
|
2669
|
-
const stderr = typeof o["stderr"] === "string" ? o["stderr"] : "";
|
|
2670
|
-
if (stdout && stderr) return `${stdout}
|
|
2671
|
-
${stderr}`;
|
|
2672
|
-
return stdout || stderr || "";
|
|
2673
|
-
}
|
|
2674
|
-
return safeStringify(value);
|
|
2675
|
-
}
|
|
2676
|
-
function extractDiff(value) {
|
|
2677
|
-
if (typeof value === "object" && value !== null) {
|
|
2678
|
-
const d = value.diff;
|
|
2679
|
-
if (typeof d === "string" && d.length > 0) return d;
|
|
2501
|
+
});
|
|
2680
2502
|
}
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2503
|
+
async readKey(prompt, options, opts = {}) {
|
|
2504
|
+
setOutputLineGuard(null);
|
|
2505
|
+
const signal = opts.signal;
|
|
2506
|
+
if (signal?.aborted) return "";
|
|
2507
|
+
writeOut(prompt);
|
|
2508
|
+
return new Promise((resolve3) => {
|
|
2509
|
+
const stdin = process.stdin;
|
|
2510
|
+
const wasRaw = stdin.isRaw;
|
|
2511
|
+
const wasPaused = stdin.isPaused();
|
|
2512
|
+
setRawMode2(stdin, true);
|
|
2513
|
+
stdin.resume();
|
|
2514
|
+
const onData = (buf) => {
|
|
2515
|
+
const key = buf.toString();
|
|
2516
|
+
if (key === "") {
|
|
2517
|
+
cleanup();
|
|
2518
|
+
writeOut("\n");
|
|
2519
|
+
resolve3("");
|
|
2520
|
+
return;
|
|
2688
2521
|
}
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2522
|
+
const opt = options.find(
|
|
2523
|
+
(o) => o.key.toLowerCase() === key.toLowerCase() || o.value === key
|
|
2524
|
+
);
|
|
2525
|
+
if (opt) {
|
|
2526
|
+
cleanup();
|
|
2527
|
+
writeOut(`${opt.key}
|
|
2528
|
+
`);
|
|
2529
|
+
resolve3(opt.value);
|
|
2530
|
+
}
|
|
2531
|
+
};
|
|
2532
|
+
const onClose = () => {
|
|
2533
|
+
cleanup();
|
|
2534
|
+
resolve3("");
|
|
2535
|
+
};
|
|
2536
|
+
const onAbort = () => {
|
|
2537
|
+
cleanup();
|
|
2538
|
+
writeOut("\n");
|
|
2539
|
+
resolve3("");
|
|
2540
|
+
};
|
|
2541
|
+
const cleanup = () => {
|
|
2542
|
+
stdin.off("data", onData);
|
|
2543
|
+
stdin.off("close", onClose);
|
|
2544
|
+
signal?.removeEventListener("abort", onAbort);
|
|
2545
|
+
setRawMode2(stdin, wasRaw);
|
|
2546
|
+
if (wasPaused) stdin.pause();
|
|
2547
|
+
};
|
|
2548
|
+
stdin.on("data", onData);
|
|
2549
|
+
stdin.on("close", onClose);
|
|
2550
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2551
|
+
});
|
|
2693
2552
|
}
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2553
|
+
/**
|
|
2554
|
+
* Read a line of input while masking each character with a bullet so the
|
|
2555
|
+
* user gets visual confirmation that bytes are arriving (especially on
|
|
2556
|
+
* paste, which previously felt like nothing happened). Pasted chunks
|
|
2557
|
+
* are echoed as a run of bullets, Backspace/DEL erases one bullet, and
|
|
2558
|
+
* Ctrl+U / Ctrl+T are honored. Non-TTY input is read normally — there's
|
|
2559
|
+
* nothing to hide when piped, and echoing bullets to a file is noise.
|
|
2560
|
+
*
|
|
2561
|
+
* Returns the raw entered string (no trim — caller decides).
|
|
2562
|
+
*/
|
|
2563
|
+
async readSecret(prompt) {
|
|
2564
|
+
const stdin = process.stdin;
|
|
2565
|
+
if (!stdin.isTTY) {
|
|
2566
|
+
this.suppressNextHistory = true;
|
|
2567
|
+
return this.readLine(prompt);
|
|
2568
|
+
}
|
|
2569
|
+
setOutputLineGuard(null);
|
|
2570
|
+
this.rl?.close();
|
|
2571
|
+
this.rl = void 0;
|
|
2572
|
+
writeOut(prompt);
|
|
2573
|
+
return new Promise((resolve3) => {
|
|
2574
|
+
let buf = "";
|
|
2575
|
+
const wasRaw = stdin.isRaw;
|
|
2576
|
+
const cleanup = () => {
|
|
2577
|
+
stdin.off("data", onData);
|
|
2578
|
+
setRawMode2(stdin, wasRaw);
|
|
2579
|
+
stdin.pause();
|
|
2580
|
+
};
|
|
2701
2581
|
try {
|
|
2702
|
-
|
|
2703
|
-
|
|
2582
|
+
setRawMode2(stdin, true);
|
|
2583
|
+
stdin.resume();
|
|
2584
|
+
stdin.setEncoding("utf8");
|
|
2585
|
+
} catch (setupErr) {
|
|
2586
|
+
setRawMode2(stdin, wasRaw);
|
|
2587
|
+
stdin.pause();
|
|
2588
|
+
throw setupErr;
|
|
2704
2589
|
}
|
|
2705
|
-
|
|
2590
|
+
const eraseChar = () => {
|
|
2591
|
+
writeOut("\b \b");
|
|
2592
|
+
};
|
|
2593
|
+
const eraseAll = () => {
|
|
2594
|
+
for (let i = 0; i < buf.length; i++) eraseChar();
|
|
2595
|
+
};
|
|
2596
|
+
let escState = 0;
|
|
2597
|
+
const onData = (chunk) => {
|
|
2598
|
+
for (const ch of chunk) {
|
|
2599
|
+
if (escState === 1) {
|
|
2600
|
+
escState = ch === "[" ? 2 : ch === "O" ? 3 : 0;
|
|
2601
|
+
continue;
|
|
2602
|
+
}
|
|
2603
|
+
if (escState === 2) {
|
|
2604
|
+
if (ch >= "@" && ch <= "~") escState = 0;
|
|
2605
|
+
continue;
|
|
2606
|
+
}
|
|
2607
|
+
if (escState === 3) {
|
|
2608
|
+
escState = 0;
|
|
2609
|
+
continue;
|
|
2610
|
+
}
|
|
2611
|
+
if (ch === "\x1B") {
|
|
2612
|
+
escState = 1;
|
|
2613
|
+
continue;
|
|
2614
|
+
}
|
|
2615
|
+
if (ch === "\r" || ch === "\n") {
|
|
2616
|
+
cleanup();
|
|
2617
|
+
writeOut(` ${dim(`[${buf.length} chars]`)}
|
|
2618
|
+
`);
|
|
2619
|
+
resolve3(buf);
|
|
2620
|
+
return;
|
|
2621
|
+
}
|
|
2622
|
+
if (ch === "") {
|
|
2623
|
+
cleanup();
|
|
2624
|
+
writeOut("\n");
|
|
2625
|
+
resolve3("");
|
|
2626
|
+
return;
|
|
2627
|
+
}
|
|
2628
|
+
if (ch === "") {
|
|
2629
|
+
eraseAll();
|
|
2630
|
+
buf = "";
|
|
2631
|
+
continue;
|
|
2632
|
+
}
|
|
2633
|
+
if (ch === "") {
|
|
2634
|
+
const m = buf.match(/(\S+\s*)$/);
|
|
2635
|
+
const drop = m ? m[0].length : buf.length;
|
|
2636
|
+
for (let i = 0; i < drop; i++) eraseChar();
|
|
2637
|
+
buf = buf.slice(0, buf.length - drop);
|
|
2638
|
+
continue;
|
|
2639
|
+
}
|
|
2640
|
+
if (ch === "\x7F" || ch === "\b") {
|
|
2641
|
+
if (buf.length > 0) {
|
|
2642
|
+
buf = buf.slice(0, -1);
|
|
2643
|
+
eraseChar();
|
|
2644
|
+
}
|
|
2645
|
+
continue;
|
|
2646
|
+
}
|
|
2647
|
+
if (ch < " ") continue;
|
|
2648
|
+
buf += ch;
|
|
2649
|
+
writeOut("\u2022");
|
|
2650
|
+
}
|
|
2651
|
+
if (escState === 1) escState = 0;
|
|
2652
|
+
};
|
|
2653
|
+
stdin.on("data", onData);
|
|
2654
|
+
});
|
|
2706
2655
|
}
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
return `${path10} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
2713
|
-
}
|
|
2714
|
-
if (name === "write") {
|
|
2715
|
-
const path10 = typeof o["path"] === "string" ? o["path"] : "";
|
|
2716
|
-
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
2717
|
-
return bytes !== void 0 ? `${path10} ${bytes}B` : path10;
|
|
2718
|
-
}
|
|
2719
|
-
if (name === "read") {
|
|
2720
|
-
return summarizeRead(o);
|
|
2721
|
-
}
|
|
2722
|
-
if (name === "bash") {
|
|
2723
|
-
return summarizeBash(o);
|
|
2724
|
-
}
|
|
2725
|
-
if (name === "exec") {
|
|
2726
|
-
return summarizeExec(o);
|
|
2727
|
-
}
|
|
2728
|
-
if (typeof o["count"] === "number") {
|
|
2729
|
-
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
2730
|
-
}
|
|
2656
|
+
async close() {
|
|
2657
|
+
setOutputLineGuard(null);
|
|
2658
|
+
await this.saveHistory();
|
|
2659
|
+
this.rl?.close();
|
|
2660
|
+
this.rl = void 0;
|
|
2731
2661
|
}
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
|
|
2736
|
-
const total = typeof o["total_lines"] === "number" ? o["total_lines"] : void 0;
|
|
2737
|
-
const truncated = o["truncated"] === true;
|
|
2738
|
-
const cached = o["cached"] === true;
|
|
2739
|
-
const note = typeof o["note"] === "string" ? o["note"] : void 0;
|
|
2740
|
-
const parts = [];
|
|
2741
|
-
if (path10) parts.push(path10);
|
|
2742
|
-
if (total !== void 0) parts.push(`${total} line${total === 1 ? "" : "s"}`);
|
|
2743
|
-
const flags = [];
|
|
2744
|
-
if (truncated) flags.push("truncated");
|
|
2745
|
-
if (cached) flags.push("cached");
|
|
2746
|
-
if (flags.length > 0) parts.push(`(${flags.join(", ")})`);
|
|
2747
|
-
let summary = parts.join(" ").trim();
|
|
2748
|
-
if (note) summary = summary ? `${summary} \u2014 ${note}` : note;
|
|
2749
|
-
return summary;
|
|
2750
|
-
}
|
|
2751
|
-
function summarizeBash(o) {
|
|
2752
|
-
const exit = typeof o["exit_code"] === "number" ? o["exit_code"] : void 0;
|
|
2753
|
-
const output = typeof o["output"] === "string" ? o["output"] : "";
|
|
2754
|
-
const lines = output ? output.split("\n").length : 0;
|
|
2755
|
-
const parts = [];
|
|
2756
|
-
if (exit !== void 0) parts.push(`exit=${exit}`);
|
|
2757
|
-
else if (o["exit_code"] === null) parts.push("backgrounded");
|
|
2758
|
-
if (o["timed_out"] === true) parts.push("timed out");
|
|
2759
|
-
if (lines > 0) parts.push(`${lines} output line${lines === 1 ? "" : "s"}`);
|
|
2760
|
-
if (output.length > 0) parts.push(`${output.length}B`);
|
|
2761
|
-
return parts.join(" ");
|
|
2762
|
-
}
|
|
2763
|
-
function summarizeExec(o) {
|
|
2764
|
-
const command = typeof o["command"] === "string" ? o["command"] : "";
|
|
2765
|
-
const args = Array.isArray(o["args"]) ? o["args"].filter((a) => typeof a === "string") : [];
|
|
2766
|
-
const exit = typeof o["exitCode"] === "number" ? o["exitCode"] : void 0;
|
|
2767
|
-
const stdout = typeof o["stdout"] === "string" ? o["stdout"] : "";
|
|
2768
|
-
const stderr = typeof o["stderr"] === "string" ? o["stderr"] : "";
|
|
2769
|
-
const outLines = stdout ? stdout.split("\n").length : 0;
|
|
2770
|
-
const errLines = stderr ? stderr.split("\n").length : 0;
|
|
2771
|
-
const totalBytes = stdout.length + stderr.length;
|
|
2772
|
-
const parts = [];
|
|
2773
|
-
const invocation = [command, ...args].filter(Boolean).join(" ");
|
|
2774
|
-
if (invocation) parts.push(`$ ${invocation}`);
|
|
2775
|
-
if (exit !== void 0) parts.push(`exit=${exit}`);
|
|
2776
|
-
if (outLines > 0) parts.push(`${outLines} stdout line${outLines === 1 ? "" : "s"}`);
|
|
2777
|
-
if (errLines > 0) parts.push(`${errLines} stderr line${errLines === 1 ? "" : "s"}`);
|
|
2778
|
-
if (totalBytes > 0) parts.push(`${totalBytes}B`);
|
|
2779
|
-
return parts.join(" ");
|
|
2780
|
-
}
|
|
2781
|
-
function compactSingleLine(text) {
|
|
2782
|
-
const first = text.split("\n")[0]?.trim() ?? "";
|
|
2783
|
-
if (!first) return "(no output)";
|
|
2784
|
-
return first.length > 200 ? `${first.slice(0, 197)}\u2026` : first;
|
|
2662
|
+
};
|
|
2663
|
+
function dim(s) {
|
|
2664
|
+
if (!process.stdout.isTTY) return s;
|
|
2665
|
+
return `\x1B[2m${s}\x1B[22m`;
|
|
2785
2666
|
}
|
|
2786
2667
|
|
|
2787
|
-
// src/
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
|
|
2668
|
+
// src/renderer.ts
|
|
2669
|
+
import { color as color12 } from "@wrongstack/core/utils";
|
|
2670
|
+
var TerminalRenderer = class _TerminalRenderer {
|
|
2671
|
+
out;
|
|
2672
|
+
err;
|
|
2673
|
+
lineStart = true;
|
|
2674
|
+
/** When true, stdout-bound rendering is disabled (for non-terminal UI surfaces). */
|
|
2675
|
+
silent = false;
|
|
2676
|
+
/**
|
|
2677
|
+
* When true, Ink exclusively owns the terminal. Both stdout and stderr
|
|
2678
|
+
* rendering must be disabled: either stream can interleave with Ink's
|
|
2679
|
+
* cursor updates and make the TUI flicker or leave stray debug notes.
|
|
2680
|
+
*/
|
|
2681
|
+
tuiActive = false;
|
|
2682
|
+
/**
|
|
2683
|
+
* Per-tool on-screen result render mode. Defaults to `'extend'` so
|
|
2684
|
+
* existing callers without `setResultRenderMode` see full content.
|
|
2685
|
+
* `setResultRenderMode(name, mode)` overrides the entry for `name`; the
|
|
2686
|
+
* override is one-shot for that name (the default reasserts after the
|
|
2687
|
+
* next write) so callers can set the mode right before each tool call
|
|
2688
|
+
* without leaking state across unrelated tool calls.
|
|
2689
|
+
*/
|
|
2690
|
+
renderModes = /* @__PURE__ */ new Map();
|
|
2691
|
+
/** Mode applied when no entry is set for a given tool name. */
|
|
2692
|
+
static DEFAULT_RENDER_MODE = "extend";
|
|
2693
|
+
/** Maximum number of one-shot hints retained when callers abandon a result. */
|
|
2694
|
+
static MAX_RENDER_MODES = 256;
|
|
2695
|
+
/** Lines kept per tool family in `extend` mode. */
|
|
2696
|
+
static EXTEND_PREVIEW_LINES = 10;
|
|
2697
|
+
constructor(opts = {}) {
|
|
2698
|
+
this.out = opts.out ?? process.stdout;
|
|
2699
|
+
this.err = opts.err ?? process.stderr;
|
|
2794
2700
|
}
|
|
2795
|
-
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
|
|
2801
|
-
flags
|
|
2802
|
-
});
|
|
2701
|
+
/**
|
|
2702
|
+
* Toggle stdout suppression for non-terminal UI surfaces such as WebUI.
|
|
2703
|
+
* Diagnostics continue to flow to stderr. Idempotent.
|
|
2704
|
+
*/
|
|
2705
|
+
setSilent(silent) {
|
|
2706
|
+
this.silent = silent;
|
|
2803
2707
|
}
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
return runRemoteCommand(
|
|
2807
|
-
positional[0] === "remote" ? positional.slice(1) : positional,
|
|
2808
|
-
flags,
|
|
2809
|
-
new TerminalRenderer()
|
|
2810
|
-
);
|
|
2708
|
+
isSilent() {
|
|
2709
|
+
return this.silent;
|
|
2811
2710
|
}
|
|
2812
|
-
|
|
2813
|
-
|
|
2814
|
-
|
|
2711
|
+
/**
|
|
2712
|
+
* Toggle exclusive TUI ownership of both terminal streams. Unlike
|
|
2713
|
+
* {@link setSilent}, TUI mode suppresses stderr as well as stdout and clears
|
|
2714
|
+
* any CLI-only result mode staged before Ink took ownership. While active,
|
|
2715
|
+
* new result-mode hints are ignored. The two flags compose with OR semantics
|
|
2716
|
+
* for stdout suppression only; TUI ownership adds the other behaviors.
|
|
2717
|
+
* Enable immediately before starting Ink and disable in the paired
|
|
2718
|
+
* `finally`, only after the TUI has fully exited.
|
|
2719
|
+
*/
|
|
2720
|
+
setTuiActive(active) {
|
|
2721
|
+
if (active) this.renderModes.clear();
|
|
2722
|
+
this.tuiActive = active;
|
|
2815
2723
|
}
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2724
|
+
isTuiActive() {
|
|
2725
|
+
return this.tuiActive;
|
|
2726
|
+
}
|
|
2727
|
+
suppressStdout() {
|
|
2728
|
+
return this.silent || this.tuiActive;
|
|
2729
|
+
}
|
|
2730
|
+
setResultRenderMode(name, mode) {
|
|
2731
|
+
if (this.suppressStdout()) return;
|
|
2732
|
+
if (this.renderModes.delete(name)) {
|
|
2733
|
+
this.renderModes.set(name, mode);
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
if (this.renderModes.size >= _TerminalRenderer.MAX_RENDER_MODES) {
|
|
2737
|
+
const oldestName = this.renderModes.keys().next().value;
|
|
2738
|
+
if (oldestName !== void 0) this.renderModes.delete(oldestName);
|
|
2739
|
+
}
|
|
2740
|
+
this.renderModes.set(name, mode);
|
|
2741
|
+
}
|
|
2742
|
+
getRenderMode(name) {
|
|
2743
|
+
return this.renderModes.get(name) ?? _TerminalRenderer.DEFAULT_RENDER_MODE;
|
|
2744
|
+
}
|
|
2745
|
+
clearRenderMode(name) {
|
|
2746
|
+
this.renderModes.delete(name);
|
|
2747
|
+
}
|
|
2748
|
+
write(input) {
|
|
2749
|
+
if (this.suppressStdout()) return;
|
|
2750
|
+
const text = typeof input === "string" ? input : input.text;
|
|
2751
|
+
if (!text) return;
|
|
2752
|
+
const rendered = renderMarkdown(text);
|
|
2753
|
+
this.out.write(rendered);
|
|
2754
|
+
this.lineStart = rendered.endsWith("\n");
|
|
2755
|
+
}
|
|
2756
|
+
writeLine(text = "") {
|
|
2757
|
+
if (this.suppressStdout()) return;
|
|
2758
|
+
if (!this.lineStart) this.out.write("\n");
|
|
2759
|
+
if (text) this.out.write(`${text}
|
|
2821
2760
|
`);
|
|
2822
|
-
|
|
2761
|
+
else this.out.write("\n");
|
|
2762
|
+
this.lineStart = true;
|
|
2823
2763
|
}
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2839
|
-
|
|
2840
|
-
|
|
2764
|
+
writeBlock(block) {
|
|
2765
|
+
if (block.type === "tool_result") {
|
|
2766
|
+
const text = typeof block.content === "string" ? block.content : JSON.stringify(block.content);
|
|
2767
|
+
this.writeToolResult(block.name ?? "result", text, !!block.is_error);
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
if (this.suppressStdout()) return;
|
|
2771
|
+
if (block.type === "text") {
|
|
2772
|
+
this.write(block);
|
|
2773
|
+
} else if (block.type === "tool_use") {
|
|
2774
|
+
this.writeToolCall(block.name, block.input);
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
writeToolCall(name, input) {
|
|
2778
|
+
if (this.suppressStdout()) return;
|
|
2779
|
+
if (!this.lineStart) this.out.write("\n");
|
|
2780
|
+
const arrow = theme2.primary("\u2192");
|
|
2781
|
+
const display = formatInputSummary(input);
|
|
2782
|
+
this.out.write(`${arrow} ${theme2.bold(name)}${display ? color12.dim(` ${display}`) : ""}
|
|
2841
2783
|
`);
|
|
2842
|
-
|
|
2784
|
+
this.lineStart = true;
|
|
2843
2785
|
}
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
const
|
|
2851
|
-
const
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
if (restored) {
|
|
2859
|
-
try {
|
|
2860
|
-
bootResult = await bootConfig(flags);
|
|
2861
|
-
({ paths, config, vault } = bootResult);
|
|
2862
|
-
} catch (err) {
|
|
2863
|
-
writeErr(`Config error after backup restore: ${toErrorMessage5(err)}
|
|
2786
|
+
writeToolResult(name, content, isError) {
|
|
2787
|
+
if (this.suppressStdout()) {
|
|
2788
|
+
this.clearRenderMode(name);
|
|
2789
|
+
return;
|
|
2790
|
+
}
|
|
2791
|
+
const txt = typeof content === "string" ? content : safeStringify(content);
|
|
2792
|
+
const prefix = isError ? theme2.error("\u2718") : theme2.success("\u2713");
|
|
2793
|
+
const renderMode = this.getRenderMode(name);
|
|
2794
|
+
this.clearRenderMode(name);
|
|
2795
|
+
const bodyText = extractBodyText(content, name);
|
|
2796
|
+
if (isError) {
|
|
2797
|
+
const firstLine = txt.split("\n")[0] ?? "";
|
|
2798
|
+
const truncated = firstLine.length > 200 ? `${firstLine.slice(0, 197)}\u2026` : firstLine;
|
|
2799
|
+
this.out.write(` ${prefix} ${color12.dim(truncated)}
|
|
2864
2800
|
`);
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
}
|
|
2801
|
+
this.lineStart = true;
|
|
2802
|
+
return;
|
|
2868
2803
|
}
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
first = void 0;
|
|
2880
|
-
const plugins = config?.plugins ?? [];
|
|
2881
|
-
if (plugins.length === 0) {
|
|
2882
|
-
console.debug("[wrongstack:quick] No plugins configured");
|
|
2883
|
-
} else {
|
|
2884
|
-
for (const p of plugins) {
|
|
2885
|
-
const name = typeof p === "string" ? p : p.name;
|
|
2886
|
-
const enabled = typeof p === "object" && p.enabled === false ? " (disabled)" : "";
|
|
2887
|
-
console.debug(`[wrongstack:quick] plugin: ${name}${enabled}`);
|
|
2888
|
-
}
|
|
2804
|
+
const isEditLike = name === "edit" || name === "write";
|
|
2805
|
+
const diff = extractDiff(content);
|
|
2806
|
+
if (isEditLike && diff) {
|
|
2807
|
+
this.out.write(` ${prefix} ${color12.dim(summarize(content, name))}
|
|
2808
|
+
`);
|
|
2809
|
+
const rendered = renderDiff(diff).split("\n").map((l) => ` ${l}`).join("\n");
|
|
2810
|
+
this.out.write(`${rendered}
|
|
2811
|
+
`);
|
|
2812
|
+
this.lineStart = true;
|
|
2813
|
+
return;
|
|
2889
2814
|
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
installProviderPersisters({ config, paths: wpaths, vault, logger });
|
|
2915
|
-
const subcommandHandler = first ? subcommands[first] : void 0;
|
|
2916
|
-
if (first && subcommandHandler) {
|
|
2917
|
-
if (flags["help"] === true || flags["h"] === true) {
|
|
2918
|
-
const deepSub = positional[1];
|
|
2919
|
-
if (deepSub && renderDeepHelp(`${first}:${deepSub}`, renderer)) {
|
|
2920
|
-
await reader.close();
|
|
2921
|
-
return 0;
|
|
2922
|
-
}
|
|
2923
|
-
if (renderFocusedHelp(first, renderer)) {
|
|
2924
|
-
await reader.close();
|
|
2925
|
-
return 0;
|
|
2926
|
-
}
|
|
2815
|
+
const summary = summarize(content, name);
|
|
2816
|
+
const isReadLike = name === "read" || name === "grep" || name === "glob" || name === "bash" || name === "exec";
|
|
2817
|
+
if (renderMode === "simple") {
|
|
2818
|
+
this.out.write(` ${prefix} ${color12.dim(summary || compactSingleLine(bodyText))}
|
|
2819
|
+
`);
|
|
2820
|
+
this.lineStart = true;
|
|
2821
|
+
return;
|
|
2822
|
+
}
|
|
2823
|
+
const previewLines = isEditLike ? 0 : isReadLike ? _TerminalRenderer.EXTEND_PREVIEW_LINES : 2;
|
|
2824
|
+
const lines = bodyText.split("\n");
|
|
2825
|
+
const head = lines.slice(0, previewLines).map((l) => l.replace(/\s+$/, ""));
|
|
2826
|
+
const moreCount = Math.max(0, lines.length - head.length);
|
|
2827
|
+
this.out.write(` ${prefix} ${color12.dim(summary)}
|
|
2828
|
+
`);
|
|
2829
|
+
for (const l of head) {
|
|
2830
|
+
const capped = l.length > 200 ? `${l.slice(0, 197)}\u2026` : l;
|
|
2831
|
+
this.out.write(` ${color12.dim(capped)}
|
|
2832
|
+
`);
|
|
2833
|
+
}
|
|
2834
|
+
if (moreCount > 0) {
|
|
2835
|
+
this.out.write(
|
|
2836
|
+
` ${color12.dim(`+${moreCount} more line${moreCount === 1 ? "" : "s"}`)}
|
|
2837
|
+
`
|
|
2838
|
+
);
|
|
2927
2839
|
}
|
|
2928
|
-
|
|
2929
|
-
config,
|
|
2930
|
-
wpaths,
|
|
2931
|
-
logger,
|
|
2932
|
-
modelsRegistry,
|
|
2933
|
-
bundledSkillsDir: config.features.skills ? resolveBundledSkillsDir2() : void 0,
|
|
2934
|
-
bundledPromptsDir: config.features.prompts === false ? void 0 : resolveBundledPromptsDir2()
|
|
2935
|
-
});
|
|
2936
|
-
const sessionStore = container.resolve(TOKENS2.SessionStore);
|
|
2937
|
-
const skillLoader = container.resolve(TOKENS2.SkillLoader);
|
|
2938
|
-
const toolRegistryForSubcmd = new ToolRegistry();
|
|
2939
|
-
registerBuiltinToolTier({
|
|
2940
|
-
registry: toolRegistryForSubcmd,
|
|
2941
|
-
tier: normalizeTokenSavingTier(config.features.tokenSavingMode)
|
|
2942
|
-
});
|
|
2943
|
-
const code = await subcommandHandler(positional.slice(1), {
|
|
2944
|
-
config,
|
|
2945
|
-
renderer,
|
|
2946
|
-
reader,
|
|
2947
|
-
sessionStore,
|
|
2948
|
-
skillLoader,
|
|
2949
|
-
toolRegistry: toolRegistryForSubcmd,
|
|
2950
|
-
modelsRegistry,
|
|
2951
|
-
paths: wpaths,
|
|
2952
|
-
vault,
|
|
2953
|
-
cwd,
|
|
2954
|
-
projectRoot,
|
|
2955
|
-
userHome,
|
|
2956
|
-
flags
|
|
2957
|
-
});
|
|
2958
|
-
await reader.close();
|
|
2959
|
-
return code;
|
|
2840
|
+
this.lineStart = true;
|
|
2960
2841
|
}
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
`
|
|
2968
|
-
);
|
|
2969
|
-
await reader.close();
|
|
2970
|
-
return 1;
|
|
2842
|
+
writeDiff(diff) {
|
|
2843
|
+
if (this.suppressStdout()) return;
|
|
2844
|
+
if (!this.lineStart) this.out.write("\n");
|
|
2845
|
+
this.out.write(`${renderDiff(diff)}
|
|
2846
|
+
`);
|
|
2847
|
+
this.lineStart = true;
|
|
2971
2848
|
}
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
shortLived: positional.length > 0 || typeof flags["prompt"] === "string"
|
|
2977
|
-
});
|
|
2849
|
+
writeWarning(text) {
|
|
2850
|
+
if (this.tuiActive) return;
|
|
2851
|
+
this.err.write(`${theme2.warn("\u26A0")} ${text}
|
|
2852
|
+
`);
|
|
2978
2853
|
}
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
cacheDir: path9.dirname(wpaths.modelsCache),
|
|
2984
|
-
logger
|
|
2985
|
-
});
|
|
2986
|
-
} catch (err) {
|
|
2987
|
-
logger.debug(`provider auto-discovery skipped: ${toErrorMessage5(err)}`);
|
|
2854
|
+
writeError(text) {
|
|
2855
|
+
if (this.tuiActive) return;
|
|
2856
|
+
this.err.write(`${theme2.error("\u2718")} ${text}
|
|
2857
|
+
`);
|
|
2988
2858
|
}
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
const cont = await runProjectCheck({ projectRoot, cwd, renderer, reader });
|
|
2994
|
-
if (!cont) {
|
|
2995
|
-
await reader.close();
|
|
2996
|
-
return 0;
|
|
2997
|
-
}
|
|
2859
|
+
writeInfo(text) {
|
|
2860
|
+
if (this.tuiActive) return;
|
|
2861
|
+
this.err.write(`${theme2.info("\u2139")} ${text}
|
|
2862
|
+
`);
|
|
2998
2863
|
}
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
2864
|
+
/**
|
|
2865
|
+
* Write text to stderr verbatim: no glyph prefix, no markdown processing.
|
|
2866
|
+
*
|
|
2867
|
+
* Deliberately stricter than writeWarning/writeError/writeInfo, which emit
|
|
2868
|
+
* *diagnostics* and are suppressed only when a TUI owns the terminal (so
|
|
2869
|
+
* they survive silent mode). This method is for informational mirrors of
|
|
2870
|
+
* stdout output, where the stdout copy already carries the full content —
|
|
2871
|
+
* in silent mode the operator asked for machine-only output, so the mirror
|
|
2872
|
+
* is dropped rather than leaked onto a quieter stream. It therefore honors
|
|
2873
|
+
* the full {@link suppressStdout} gate.
|
|
2874
|
+
*
|
|
2875
|
+
* Used by `wstack mailbox serve` to route the human-readable startup banner
|
|
2876
|
+
* away from stdout, keeping the mailbox_serve_started JSON line cleanly
|
|
2877
|
+
* pipeable (`mailbox serve | jq`).
|
|
2878
|
+
*/
|
|
2879
|
+
writeStderr(text) {
|
|
2880
|
+
if (this.suppressStdout()) return;
|
|
2881
|
+
this.err.write(text);
|
|
3006
2882
|
}
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
if (providerFlag && modelFlag) {
|
|
3012
|
-
config = patchConfig(config, { provider: providerFlag, model: modelFlag });
|
|
3013
|
-
} else {
|
|
3014
|
-
const picked = await autoSelectSavedProvider(config, modelsRegistry);
|
|
3015
|
-
if (picked) config = patchConfig(config, picked);
|
|
3016
|
-
}
|
|
2883
|
+
clear() {
|
|
2884
|
+
if (this.suppressStdout()) return;
|
|
2885
|
+
this.out.write("\x1B[2J\x1B[H");
|
|
2886
|
+
this.lineStart = true;
|
|
3017
2887
|
}
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
2888
|
+
/**
|
|
2889
|
+
* Write a flashy agent completion banner for delegate tool results.
|
|
2890
|
+
* Renders a box like:
|
|
2891
|
+
* ┌─────────────────────────────────────┐
|
|
2892
|
+
* │ ✓ [role] done in 4m 32s │
|
|
2893
|
+
* │ 127 iterations · 341 tools │
|
|
2894
|
+
* │ Found 14 bugs across 6 files... │
|
|
2895
|
+
* └─────────────────────────────────────┘
|
|
2896
|
+
*/
|
|
2897
|
+
writeAgentSummary(summary, ok) {
|
|
2898
|
+
if (this.suppressStdout()) return;
|
|
2899
|
+
if (!this.lineStart) this.out.write("\n");
|
|
2900
|
+
const lines = summary.split("\n");
|
|
2901
|
+
const icon = ok ? theme2.success("\u2713") : theme2.error("\u2718");
|
|
2902
|
+
const firstLine = `${icon} ${lines[0] ?? summary}`;
|
|
2903
|
+
const body = lines.slice(1);
|
|
2904
|
+
const maxWidth = Math.min(process.stdout.columns ?? 80, 120);
|
|
2905
|
+
const contentWidth = Math.max(
|
|
2906
|
+
firstLine.length,
|
|
2907
|
+
body.reduce((a, l) => Math.max(a, l.length), 0)
|
|
2908
|
+
);
|
|
2909
|
+
const boxWidth = Math.min(Math.max(contentWidth + 4, 44), maxWidth);
|
|
2910
|
+
const thick = "\u2501".repeat(boxWidth - 2);
|
|
2911
|
+
const thin = "\u2500".repeat(boxWidth - 2);
|
|
2912
|
+
this.out.write(`
|
|
2913
|
+
${theme2.primary("\u250C")}${thick}${theme2.primary("\u2510")}
|
|
2914
|
+
`);
|
|
2915
|
+
const centre = (s) => {
|
|
2916
|
+
const inner = ` ${s} `;
|
|
2917
|
+
const padLen = Math.max(0, boxWidth - 2 - s.length);
|
|
2918
|
+
const left = Math.floor(padLen / 2);
|
|
2919
|
+
const right = padLen - left;
|
|
2920
|
+
return `${" ".repeat(left)}${inner}${" ".repeat(right)}`;
|
|
2921
|
+
};
|
|
2922
|
+
this.out.write(` ${theme2.primary("\u2502")}${centre(firstLine)}${theme2.primary("\u2502")}
|
|
2923
|
+
`);
|
|
2924
|
+
for (const l of body) {
|
|
2925
|
+
this.out.write(
|
|
2926
|
+
` ${theme2.primary("\u2502")} ${l}${" ".repeat(Math.max(0, boxWidth - 3 - l.length))}${theme2.primary("\u2502")}
|
|
3028
2927
|
`
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
renderer,
|
|
3033
|
-
reader,
|
|
3034
|
-
modelsRegistry,
|
|
3035
|
-
vault,
|
|
3036
|
-
profileConfigPath,
|
|
3037
|
-
reloadConfig: async () => (await bootConfig(flags)).config
|
|
3038
|
-
});
|
|
3039
|
-
if (outcome.kind === "quit") {
|
|
3040
|
-
await reader.close();
|
|
3041
|
-
return 0;
|
|
3042
|
-
}
|
|
3043
|
-
if (outcome.kind === "setup-mode") {
|
|
3044
|
-
config = patchConfig(config, { provider: SETUP_PROVIDER_ID, model: SETUP_MODEL_ID });
|
|
3045
|
-
await saveToGlobalConfig(profileConfigPath, SETUP_PROVIDER_ID, SETUP_MODEL_ID);
|
|
3046
|
-
skipPicker = true;
|
|
3047
|
-
} else {
|
|
3048
|
-
try {
|
|
3049
|
-
const reloaded = await bootConfig(flags);
|
|
3050
|
-
config = reloaded.config;
|
|
3051
|
-
vault = reloaded.vault;
|
|
3052
|
-
} catch (err) {
|
|
3053
|
-
writeErr(`Config error after setup: ${toErrorMessage5(err)}
|
|
2928
|
+
);
|
|
2929
|
+
}
|
|
2930
|
+
this.out.write(` ${theme2.primary("\u2514")}${thin}${theme2.primary("\u2518")}
|
|
3054
2931
|
`);
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
2932
|
+
this.lineStart = true;
|
|
2933
|
+
}
|
|
2934
|
+
/**
|
|
2935
|
+
* Render subagent completion banners from a RunResult.
|
|
2936
|
+
* Uses `delegateSummaries` when available (populated by delegate tool),
|
|
2937
|
+
* otherwise falls back to scanning message history.
|
|
2938
|
+
*/
|
|
2939
|
+
writeDelegateSummaries(result) {
|
|
2940
|
+
if (this.suppressStdout()) return;
|
|
2941
|
+
if (result.delegateSummaries) {
|
|
2942
|
+
for (const { summary, ok } of result.delegateSummaries) {
|
|
2943
|
+
if (!summary) continue;
|
|
2944
|
+
this.writeAgentSummary(summary, ok);
|
|
3059
2945
|
}
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
);
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
} else {
|
|
3075
|
-
const answer = (await reader.readLine(
|
|
3076
|
-
` ${color11.amber("?")} Continue with these? ${color11.dim("[Y/n/q]")} ${color11.dim("(auto Y in 5s)")} `,
|
|
3077
|
-
{ timeoutMs: 5e3, defaultAnswer: "y" }
|
|
3078
|
-
)).trim().toLowerCase();
|
|
3079
|
-
if (answer === "q") {
|
|
3080
|
-
renderer.write(color11.dim(" Goodbye!\n"));
|
|
3081
|
-
await reader.close();
|
|
3082
|
-
return 0;
|
|
3083
|
-
}
|
|
3084
|
-
if (answer !== "n" && answer !== "no") {
|
|
3085
|
-
skipPicker = true;
|
|
3086
|
-
renderer.write(
|
|
3087
|
-
`
|
|
3088
|
-
${color11.green("\u25B6")} ${color11.bold(savedProvider)} / ${color11.bold(savedModel)}
|
|
3089
|
-
|
|
3090
|
-
`
|
|
3091
|
-
);
|
|
3092
|
-
}
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
if (!result.messages) return;
|
|
2949
|
+
for (const msg of result.messages) {
|
|
2950
|
+
const m = msg;
|
|
2951
|
+
if (!Array.isArray(m.content)) continue;
|
|
2952
|
+
for (const block of m.content) {
|
|
2953
|
+
const b = block;
|
|
2954
|
+
if (b.type !== "tool_result" || b.name !== "delegate") continue;
|
|
2955
|
+
let obj;
|
|
2956
|
+
try {
|
|
2957
|
+
obj = typeof b.content === "string" ? JSON.parse(b.content) : b.content;
|
|
2958
|
+
} catch {
|
|
2959
|
+
continue;
|
|
3093
2960
|
}
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
modelsRegistry,
|
|
3098
|
-
renderer,
|
|
3099
|
-
reader,
|
|
3100
|
-
config,
|
|
3101
|
-
defaultProvider: providerFlag ?? config.provider,
|
|
3102
|
-
defaultModel: modelFlag ?? config.model
|
|
3103
|
-
});
|
|
3104
|
-
}
|
|
3105
|
-
if (!picked && !skipPicker) {
|
|
3106
|
-
if (!config.provider || !config.model) {
|
|
3107
|
-
renderer.write(
|
|
3108
|
-
`
|
|
3109
|
-
${color11.dim("No provider selected.")}
|
|
3110
|
-
${color11.dim("Run")} ${color11.bold("wstack auth")} ${color11.dim("to add a key or sign in, or start with no model:")}
|
|
3111
|
-
${color11.bold(`wstack --provider ${SETUP_PROVIDER_ID} --model ${SETUP_MODEL_ID}`)}
|
|
3112
|
-
|
|
3113
|
-
`
|
|
3114
|
-
);
|
|
3115
|
-
await reader.close();
|
|
3116
|
-
return 2;
|
|
2961
|
+
const o = obj;
|
|
2962
|
+
if (o.summary) {
|
|
2963
|
+
this.writeAgentSummary(o.summary, o.ok ?? true);
|
|
3117
2964
|
}
|
|
3118
2965
|
}
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
};
|
|
2969
|
+
function renderMarkdown(s) {
|
|
2970
|
+
let out = s;
|
|
2971
|
+
out = out.replace(/^(#{1,6}) (.+)$/gm, (_m, hashes, text) => {
|
|
2972
|
+
return theme2.primary(theme2.bold(`${hashes} ${text}`));
|
|
2973
|
+
});
|
|
2974
|
+
out = out.replace(/```([a-zA-Z0-9_+-]*)\n([\s\S]*?)```/g, (_m, _lang, code) => {
|
|
2975
|
+
return color12.gray(`
|
|
2976
|
+
\u250C\u2500
|
|
2977
|
+
${code.replace(/^/gm, "\u2502 ")}\u2514\u2500`);
|
|
2978
|
+
});
|
|
2979
|
+
out = out.replace(/`([^`\n]+)`/g, (_m, code) => theme2.accent(code));
|
|
2980
|
+
out = out.replace(/\*\*([^*]+)\*\*/g, (_m, text) => theme2.bold(text));
|
|
2981
|
+
out = out.replace(
|
|
2982
|
+
/(^|[^*])\*([^*\n]+)\*([^*]|$)/g,
|
|
2983
|
+
(_m, l, t, r) => `${l}${color12.italic(t)}${r}`
|
|
2984
|
+
);
|
|
2985
|
+
return out;
|
|
2986
|
+
}
|
|
2987
|
+
function formatInputSummary(input) {
|
|
2988
|
+
if (!input || typeof input !== "object") return "";
|
|
2989
|
+
const obj = input;
|
|
2990
|
+
if (typeof obj["path"] === "string") return obj["path"];
|
|
2991
|
+
if (typeof obj["url"] === "string") return obj["url"];
|
|
2992
|
+
if (typeof obj["command"] === "string") {
|
|
2993
|
+
const cmd = obj["command"];
|
|
2994
|
+
return cmd.length > 60 ? cmd.slice(0, 57) + "..." : cmd;
|
|
2995
|
+
}
|
|
2996
|
+
if (typeof obj["pattern"] === "string") return obj["pattern"];
|
|
2997
|
+
return "";
|
|
2998
|
+
}
|
|
2999
|
+
function safeStringify(value) {
|
|
3000
|
+
if (typeof value === "string") return value;
|
|
3001
|
+
try {
|
|
3002
|
+
return JSON.stringify(value);
|
|
3003
|
+
} catch {
|
|
3004
|
+
return String(value);
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
function extractBodyText(value, name) {
|
|
3008
|
+
let v = value;
|
|
3009
|
+
if (typeof value === "string") {
|
|
3010
|
+
const trimmed = value.trimStart();
|
|
3011
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
3012
|
+
try {
|
|
3013
|
+
v = JSON.parse(value);
|
|
3014
|
+
} catch {
|
|
3015
|
+
return value;
|
|
3135
3016
|
}
|
|
3136
|
-
} else if (!config.provider || !config.model) {
|
|
3137
|
-
writeErr(
|
|
3138
|
-
`No provider or model configured. Run \`wstack auth\`, or pass --provider <id> --model <id>.
|
|
3139
|
-
To start the app with no model connected, pass --provider ${SETUP_PROVIDER_ID} --model ${SETUP_MODEL_ID}.
|
|
3140
|
-
`
|
|
3141
|
-
);
|
|
3142
|
-
await reader.close();
|
|
3143
|
-
return 2;
|
|
3144
3017
|
} else {
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3018
|
+
return value;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
if (typeof v !== "object" || v === null) {
|
|
3022
|
+
return safeStringify(value);
|
|
3023
|
+
}
|
|
3024
|
+
const o = v;
|
|
3025
|
+
if (name === "read" && typeof o["text"] === "string") return o["text"];
|
|
3026
|
+
if (name === "bash") {
|
|
3027
|
+
const output = typeof o["output"] === "string" ? o["output"] : "";
|
|
3028
|
+
const error = typeof o["error"] === "string" ? o["error"] : "";
|
|
3029
|
+
if (output && error) return `${output}
|
|
3030
|
+
${error}`;
|
|
3031
|
+
return output || error || "";
|
|
3032
|
+
}
|
|
3033
|
+
if (name === "exec") {
|
|
3034
|
+
const stdout = typeof o["stdout"] === "string" ? o["stdout"] : "";
|
|
3035
|
+
const stderr = typeof o["stderr"] === "string" ? o["stderr"] : "";
|
|
3036
|
+
if (stdout && stderr) return `${stdout}
|
|
3037
|
+
${stderr}`;
|
|
3038
|
+
return stdout || stderr || "";
|
|
3039
|
+
}
|
|
3040
|
+
return safeStringify(value);
|
|
3041
|
+
}
|
|
3042
|
+
function extractDiff(value) {
|
|
3043
|
+
if (typeof value === "object" && value !== null) {
|
|
3044
|
+
const d = value.diff;
|
|
3045
|
+
if (typeof d === "string" && d.length > 0) return d;
|
|
3046
|
+
}
|
|
3047
|
+
if (typeof value === "string") {
|
|
3048
|
+
const trimmed = value.trimStart();
|
|
3049
|
+
if (trimmed.startsWith("{")) {
|
|
3050
|
+
try {
|
|
3051
|
+
const parsed = JSON.parse(value);
|
|
3052
|
+
if (typeof parsed.diff === "string" && parsed.diff.length > 0) {
|
|
3053
|
+
return parsed.diff;
|
|
3054
|
+
}
|
|
3055
|
+
} catch {
|
|
3056
|
+
}
|
|
3057
|
+
}
|
|
3058
|
+
if (/^---[^\n]*\n\+\+\+/m.test(value)) return value;
|
|
3059
|
+
}
|
|
3060
|
+
return null;
|
|
3061
|
+
}
|
|
3062
|
+
function summarize(value, name) {
|
|
3063
|
+
let v = value;
|
|
3064
|
+
if (typeof value === "string") {
|
|
3065
|
+
const trimmed = value.trimStart();
|
|
3066
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
|
|
3067
|
+
try {
|
|
3068
|
+
v = JSON.parse(value);
|
|
3069
|
+
} catch {
|
|
3153
3070
|
}
|
|
3154
3071
|
}
|
|
3155
3072
|
}
|
|
3156
|
-
if (
|
|
3157
|
-
|
|
3158
|
-
|
|
3073
|
+
if (typeof v === "object" && v !== null) {
|
|
3074
|
+
const o = v;
|
|
3075
|
+
if (name === "edit") {
|
|
3076
|
+
const path10 = typeof o["path"] === "string" ? o["path"] : "";
|
|
3077
|
+
const reps = typeof o["replacements"] === "number" ? o["replacements"] : 0;
|
|
3078
|
+
return `${path10} ${reps} replacement${reps === 1 ? "" : "s"}`.trim();
|
|
3079
|
+
}
|
|
3080
|
+
if (name === "write") {
|
|
3081
|
+
const path10 = typeof o["path"] === "string" ? o["path"] : "";
|
|
3082
|
+
const bytes = typeof o["bytes"] === "number" ? o["bytes"] : void 0;
|
|
3083
|
+
return bytes !== void 0 ? `${path10} ${bytes}B` : path10;
|
|
3084
|
+
}
|
|
3085
|
+
if (name === "read") {
|
|
3086
|
+
return summarizeRead(o);
|
|
3087
|
+
}
|
|
3088
|
+
if (name === "bash") {
|
|
3089
|
+
return summarizeBash(o);
|
|
3090
|
+
}
|
|
3091
|
+
if (name === "exec") {
|
|
3092
|
+
return summarizeExec(o);
|
|
3093
|
+
}
|
|
3094
|
+
if (typeof o["count"] === "number") {
|
|
3095
|
+
return `${o["count"]} match${o["count"] === 1 ? "" : "es"}`;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
3098
|
+
return "";
|
|
3099
|
+
}
|
|
3100
|
+
function summarizeRead(o) {
|
|
3101
|
+
const path10 = typeof o["path"] === "string" ? o["path"] : "";
|
|
3102
|
+
const total = typeof o["total_lines"] === "number" ? o["total_lines"] : void 0;
|
|
3103
|
+
const truncated = o["truncated"] === true;
|
|
3104
|
+
const cached = o["cached"] === true;
|
|
3105
|
+
const note = typeof o["note"] === "string" ? o["note"] : void 0;
|
|
3106
|
+
const parts = [];
|
|
3107
|
+
if (path10) parts.push(path10);
|
|
3108
|
+
if (total !== void 0) parts.push(`${total} line${total === 1 ? "" : "s"}`);
|
|
3109
|
+
const flags = [];
|
|
3110
|
+
if (truncated) flags.push("truncated");
|
|
3111
|
+
if (cached) flags.push("cached");
|
|
3112
|
+
if (flags.length > 0) parts.push(`(${flags.join(", ")})`);
|
|
3113
|
+
let summary = parts.join(" ").trim();
|
|
3114
|
+
if (note) summary = summary ? `${summary} \u2014 ${note}` : note;
|
|
3115
|
+
return summary;
|
|
3116
|
+
}
|
|
3117
|
+
function summarizeBash(o) {
|
|
3118
|
+
const exit = typeof o["exit_code"] === "number" ? o["exit_code"] : void 0;
|
|
3119
|
+
const output = typeof o["output"] === "string" ? o["output"] : "";
|
|
3120
|
+
const lines = output ? output.split("\n").length : 0;
|
|
3121
|
+
const parts = [];
|
|
3122
|
+
if (exit !== void 0) parts.push(`exit=${exit}`);
|
|
3123
|
+
else if (o["exit_code"] === null) parts.push("backgrounded");
|
|
3124
|
+
if (o["timed_out"] === true) parts.push("timed out");
|
|
3125
|
+
if (lines > 0) parts.push(`${lines} output line${lines === 1 ? "" : "s"}`);
|
|
3126
|
+
if (output.length > 0) parts.push(`${output.length}B`);
|
|
3127
|
+
return parts.join(" ");
|
|
3128
|
+
}
|
|
3129
|
+
function summarizeExec(o) {
|
|
3130
|
+
const command = typeof o["command"] === "string" ? o["command"] : "";
|
|
3131
|
+
const args = Array.isArray(o["args"]) ? o["args"].filter((a) => typeof a === "string") : [];
|
|
3132
|
+
const exit = typeof o["exitCode"] === "number" ? o["exitCode"] : void 0;
|
|
3133
|
+
const stdout = typeof o["stdout"] === "string" ? o["stdout"] : "";
|
|
3134
|
+
const stderr = typeof o["stderr"] === "string" ? o["stderr"] : "";
|
|
3135
|
+
const outLines = stdout ? stdout.split("\n").length : 0;
|
|
3136
|
+
const errLines = stderr ? stderr.split("\n").length : 0;
|
|
3137
|
+
const totalBytes = stdout.length + stderr.length;
|
|
3138
|
+
const parts = [];
|
|
3139
|
+
const invocation = [command, ...args].filter(Boolean).join(" ");
|
|
3140
|
+
if (invocation) parts.push(`$ ${invocation}`);
|
|
3141
|
+
if (exit !== void 0) parts.push(`exit=${exit}`);
|
|
3142
|
+
if (outLines > 0) parts.push(`${outLines} stdout line${outLines === 1 ? "" : "s"}`);
|
|
3143
|
+
if (errLines > 0) parts.push(`${errLines} stderr line${errLines === 1 ? "" : "s"}`);
|
|
3144
|
+
if (totalBytes > 0) parts.push(`${totalBytes}B`);
|
|
3145
|
+
return parts.join(" ");
|
|
3146
|
+
}
|
|
3147
|
+
function compactSingleLine(text) {
|
|
3148
|
+
const first = text.split("\n")[0]?.trim() ?? "";
|
|
3149
|
+
if (!first) return "(no output)";
|
|
3150
|
+
return first.length > 200 ? `${first.slice(0, 197)}\u2026` : first;
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
// src/boot.ts
|
|
3154
|
+
var GITHUB_PROVIDERS_OVERLAY_URL = "https://raw.githubusercontent.com/WrongStack/WrongStack/main/packages/cli/data/providers.json";
|
|
3155
|
+
async function boot(argv) {
|
|
3156
|
+
const { flags, positional } = parseArgs(argv);
|
|
3157
|
+
if (positional[0] === "sandbox") {
|
|
3158
|
+
const { runSandboxCommand } = await import("./sandbox-TYGD6FI2.js");
|
|
3159
|
+
return runSandboxCommand(positional.slice(1), flags, new TerminalRenderer());
|
|
3160
|
+
}
|
|
3161
|
+
if (positional[0] === "update") {
|
|
3162
|
+
const renderer2 = new TerminalRenderer();
|
|
3163
|
+
return runUpdateCommand(positional.slice(1), {
|
|
3164
|
+
cwd: process.cwd(),
|
|
3165
|
+
userHome: os.homedir(),
|
|
3166
|
+
renderer: renderer2,
|
|
3167
|
+
flags
|
|
3168
|
+
});
|
|
3169
|
+
}
|
|
3170
|
+
if (positional[0] === "remote" || typeof flags["remote"] === "string") {
|
|
3171
|
+
const { runRemoteCommand } = await import("./remote-C74BCNND.js");
|
|
3172
|
+
return runRemoteCommand(
|
|
3173
|
+
positional[0] === "remote" ? positional.slice(1) : positional,
|
|
3174
|
+
flags,
|
|
3175
|
+
new TerminalRenderer()
|
|
3176
|
+
);
|
|
3177
|
+
}
|
|
3178
|
+
if (positional[0] === "resume" && positional[1] && !subcommands["__noop_resume_marker"]) {
|
|
3179
|
+
flags["resume"] = positional[1];
|
|
3180
|
+
positional.splice(0, 2);
|
|
3159
3181
|
}
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3182
|
+
let bootResult;
|
|
3183
|
+
try {
|
|
3184
|
+
bootResult = await bootConfig(flags);
|
|
3185
|
+
} catch (err) {
|
|
3186
|
+
writeErr3(`Config error: ${toErrorMessage7(err)}
|
|
3187
|
+
`);
|
|
3188
|
+
return 2;
|
|
3189
|
+
}
|
|
3190
|
+
let { paths, config, vault } = bootResult;
|
|
3191
|
+
try {
|
|
3192
|
+
resolveToolRestriction(flags);
|
|
3193
|
+
resolveLaunchAllowedTools(flags);
|
|
3194
|
+
parseOutputFormat(flags["output-format"]);
|
|
3195
|
+
announceSafeMode(flags, writeErr3);
|
|
3196
|
+
activateRestrictedMode(flags, writeErr3);
|
|
3197
|
+
const launchMcp = await resolveLaunchMcpServers(flags, paths.cwd);
|
|
3198
|
+
if (launchMcp) flags["mcp-config"] = JSON.stringify({ mcpServers: launchMcp });
|
|
3199
|
+
const schema = await resolveJsonSchemaFlag(flags["json-schema"], paths.cwd);
|
|
3200
|
+
if (schema) flags["json-schema"] = JSON.stringify(schema);
|
|
3201
|
+
const appended = await resolveAppendedSystemPrompt(flags, paths.cwd);
|
|
3202
|
+
delete flags["append-system-prompt-file"];
|
|
3203
|
+
if (appended) flags["append-system-prompt"] = appended;
|
|
3204
|
+
else delete flags["append-system-prompt"];
|
|
3205
|
+
} catch (err) {
|
|
3206
|
+
writeErr3(`wstack: ${toErrorMessage7(err)}
|
|
3207
|
+
`);
|
|
3208
|
+
return 2;
|
|
3209
|
+
}
|
|
3210
|
+
let first = positional[0];
|
|
3211
|
+
const mayOfferConfigRecovery = isStdinTTY3() && (positional.length === 0 || first === "quick") && typeof flags["prompt"] !== "string" && !flags["webui"] && !flags["no-interactive"] && !flags["skip"];
|
|
3212
|
+
const machineOutput = flags["output-json"] === true || flags["output-format"] === "json" || flags["output-format"] === "stream-json";
|
|
3213
|
+
const renderer = new TerminalRenderer(machineOutput ? { out: process.stderr } : void 0);
|
|
3214
|
+
const reader = new ReadlineInputReader({ historyFile: paths.wpaths.historyFile });
|
|
3215
|
+
if (mayOfferConfigRecovery) {
|
|
3216
|
+
const recoveryProfile = config.activeProfile ?? "default";
|
|
3217
|
+
const restored = await maybeRestoreDefaultProfileFromBackup({
|
|
3218
|
+
globalRoot: paths.wpaths.globalRoot,
|
|
3219
|
+
profilePath: paths.wpaths.profileConfig(recoveryProfile),
|
|
3220
|
+
profileName: recoveryProfile,
|
|
3164
3221
|
renderer,
|
|
3165
|
-
reader
|
|
3166
|
-
profileConfigPath,
|
|
3167
|
-
paths: {
|
|
3168
|
-
globalDir: wpaths.globalInstructions,
|
|
3169
|
-
projectDir: wpaths.inProjectInstructions
|
|
3170
|
-
},
|
|
3171
|
-
outsideProject: await isOutsideProject(projectRoot)
|
|
3222
|
+
reader
|
|
3172
3223
|
});
|
|
3173
|
-
if (
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
if (promptMenu.persistError) {
|
|
3181
|
-
renderer.writeWarning(
|
|
3182
|
-
`Could not save system prompt variant to config: ${toErrorMessage5(promptMenu.persistError)}
|
|
3183
|
-
`
|
|
3184
|
-
);
|
|
3185
|
-
}
|
|
3186
|
-
let modePinned;
|
|
3187
|
-
if (flags["no-tui"]) modePinned = "repl";
|
|
3188
|
-
else if (flags["tui"]) modePinned = "tui";
|
|
3189
|
-
const yoloPinned = flags["no-yolo"] === true ? false : flags["yolo"] === true ? true : void 0;
|
|
3190
|
-
let autonomyPinned;
|
|
3191
|
-
if (flags["no-autonomy"] === true) autonomyPinned = "off";
|
|
3192
|
-
else if (flags["eternal"] === true)
|
|
3193
|
-
autonomyPinned = "off";
|
|
3194
|
-
else if (typeof flags["autonomy"] === "string") {
|
|
3195
|
-
const v = flags["autonomy"].toLowerCase();
|
|
3196
|
-
autonomyPinned = v === "off" || v === "no" || v === "false" ? "off" : "auto";
|
|
3197
|
-
} else if (flags["autonomy"] === true) {
|
|
3198
|
-
autonomyPinned = "auto";
|
|
3199
|
-
}
|
|
3200
|
-
const lastChoices = config.launch ? {
|
|
3201
|
-
mode: config.launch.mode ?? "tui",
|
|
3202
|
-
yolo: config.yolo ?? true,
|
|
3203
|
-
autonomy: config.launch.autonomy ?? "auto"
|
|
3204
|
-
} : void 0;
|
|
3205
|
-
let choices;
|
|
3206
|
-
try {
|
|
3207
|
-
choices = await runLaunchPrompts({
|
|
3208
|
-
renderer,
|
|
3209
|
-
reader,
|
|
3210
|
-
modePinned,
|
|
3211
|
-
yoloPinned,
|
|
3212
|
-
autonomyPinned,
|
|
3213
|
-
lastChoices
|
|
3214
|
-
});
|
|
3215
|
-
} catch (err) {
|
|
3216
|
-
if (err instanceof LaunchAbortedError) {
|
|
3224
|
+
if (restored) {
|
|
3225
|
+
try {
|
|
3226
|
+
bootResult = await bootConfig(flags);
|
|
3227
|
+
({ paths, config, vault } = bootResult);
|
|
3228
|
+
} catch (err) {
|
|
3229
|
+
writeErr3(`Config error after backup restore: ${toErrorMessage7(err)}
|
|
3230
|
+
`);
|
|
3217
3231
|
await reader.close();
|
|
3218
|
-
return
|
|
3232
|
+
return 2;
|
|
3219
3233
|
}
|
|
3220
|
-
throw err;
|
|
3221
3234
|
}
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3235
|
+
}
|
|
3236
|
+
config = applySimpleUiFullAutoProfile(config, flags);
|
|
3237
|
+
const simpleUiFullAuto = isSimpleUiFullAuto(flags);
|
|
3238
|
+
const { cwd, projectRoot, userHome, wpaths, pathResolver } = paths;
|
|
3239
|
+
const profileConfigPath = activeProfileConfigPath(wpaths, config);
|
|
3240
|
+
void pathResolver;
|
|
3241
|
+
if (first === "quick") {
|
|
3242
|
+
flags["quick"] = true;
|
|
3243
|
+
flags["tui"] = true;
|
|
3244
|
+
positional.splice(0, 1);
|
|
3245
|
+
first = void 0;
|
|
3246
|
+
const plugins = config?.plugins ?? [];
|
|
3247
|
+
if (plugins.length === 0) {
|
|
3248
|
+
console.debug("[wrongstack:quick] No plugins configured");
|
|
3225
3249
|
} else {
|
|
3226
|
-
|
|
3227
|
-
|
|
3250
|
+
for (const p of plugins) {
|
|
3251
|
+
const name = typeof p === "string" ? p : p.name;
|
|
3252
|
+
const enabled = typeof p === "object" && p.enabled === false ? " (disabled)" : "";
|
|
3253
|
+
console.debug(`[wrongstack:quick] plugin: ${name}${enabled}`);
|
|
3254
|
+
}
|
|
3228
3255
|
}
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3256
|
+
}
|
|
3257
|
+
const logger = new DefaultLogger({
|
|
3258
|
+
level: config.log.level,
|
|
3259
|
+
file: wpaths.logFile,
|
|
3260
|
+
// Suppress stderr output in TUI mode: plugin/library log messages
|
|
3261
|
+
// (e.g. Telegram "getUpdates failed") write directly to stderr and
|
|
3262
|
+
// bypass Ink, which breaks the Static/live boundary.
|
|
3263
|
+
// Logs still go to the disk file for post-hoc debugging.
|
|
3264
|
+
stderr: !flags.tui
|
|
3265
|
+
});
|
|
3266
|
+
const modelsRegistry = new DefaultModelsRegistry({
|
|
3267
|
+
cacheFile: wpaths.modelsCache,
|
|
3268
|
+
// Force a refresh attempt once per CLI process. Model metadata changes faster
|
|
3269
|
+
// than releases (new model ids, corrected context windows), and stale cache
|
|
3270
|
+
// here directly affects runtime behavior like context bars and compaction.
|
|
3271
|
+
// If the network is unavailable, DefaultModelsRegistry still falls back to
|
|
3272
|
+
// stale cache or the bundled overlay instead of failing startup.
|
|
3273
|
+
ttlSeconds: 0,
|
|
3274
|
+
// Curated overlay merged on top of models.dev: fetched from GitHub raw for
|
|
3275
|
+
// freshness, with the bundled file as the offline floor.
|
|
3276
|
+
overlayUrl: GITHUB_PROVIDERS_OVERLAY_URL,
|
|
3277
|
+
overlayFile: resolveBundledOverlayFile(),
|
|
3278
|
+
overlayCacheFile: wpaths.modelsOverlayCache
|
|
3279
|
+
});
|
|
3280
|
+
installProviderPersisters({ config, paths: wpaths, vault, logger });
|
|
3281
|
+
const subcommandCode = await dispatchBootSubcommand({
|
|
3282
|
+
first,
|
|
3283
|
+
positional,
|
|
3284
|
+
flags,
|
|
3285
|
+
config,
|
|
3286
|
+
vault,
|
|
3287
|
+
wpaths,
|
|
3288
|
+
cwd,
|
|
3289
|
+
projectRoot,
|
|
3290
|
+
userHome,
|
|
3291
|
+
renderer,
|
|
3292
|
+
reader,
|
|
3293
|
+
logger,
|
|
3294
|
+
modelsRegistry
|
|
3295
|
+
});
|
|
3296
|
+
if (subcommandCode !== void 0) return subcommandCode;
|
|
3297
|
+
if (isHomeDirectory(cwd, userHome)) {
|
|
3298
|
+
writeErr3(
|
|
3299
|
+
`
|
|
3300
|
+
${color13.red(color13.bold("\u26A0 This is not a working directory."))}
|
|
3301
|
+
${color13.red("Please open wstack in a project folder.")}
|
|
3236
3302
|
|
|
3237
3303
|
`
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3262
|
-
};
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
|
|
3304
|
+
);
|
|
3305
|
+
await reader.close();
|
|
3306
|
+
return 1;
|
|
3307
|
+
}
|
|
3308
|
+
if (!flags["no-models-refresh"]) {
|
|
3309
|
+
await startCatalog({
|
|
3310
|
+
registry: modelsRegistry,
|
|
3311
|
+
logger,
|
|
3312
|
+
shortLived: positional.length > 0 || typeof flags["prompt"] === "string"
|
|
3313
|
+
});
|
|
3314
|
+
}
|
|
3315
|
+
try {
|
|
3316
|
+
await discoverAndMergeProviders({
|
|
3317
|
+
config,
|
|
3318
|
+
registry: modelsRegistry,
|
|
3319
|
+
cacheDir: path9.dirname(wpaths.modelsCache),
|
|
3320
|
+
logger
|
|
3321
|
+
});
|
|
3322
|
+
} catch (err) {
|
|
3323
|
+
logger.debug(`provider auto-discovery skipped: ${toErrorMessage7(err)}`);
|
|
3324
|
+
}
|
|
3325
|
+
const isSingleShot = positional.length > 0 || typeof flags["prompt"] === "string";
|
|
3326
|
+
const isInteractiveTTY = isStdinTTY3() && !isSingleShot && !flags["webui"] && !flags["no-interactive"] && !flags["skip"];
|
|
3327
|
+
if (isInteractiveTTY) {
|
|
3328
|
+
await checkGitInCwd({ cwd, renderer, reader });
|
|
3329
|
+
const cont = await runProjectCheck({ projectRoot, cwd, renderer, reader });
|
|
3330
|
+
if (!cont) {
|
|
3331
|
+
await reader.close();
|
|
3332
|
+
return 0;
|
|
3266
3333
|
}
|
|
3267
|
-
flags["autonomy"] = effectiveChoices.autonomy;
|
|
3268
3334
|
}
|
|
3335
|
+
const wantsTui = flags["tui"] === true && flags["no-tui"] !== true;
|
|
3336
|
+
if (wantsTui && (!process.stdout.isTTY || !process.stdin.isTTY)) {
|
|
3337
|
+
writeErr3(
|
|
3338
|
+
"wstack: --tui requires an interactive terminal on both stdin and stdout.\n Drop the flag (use the plain REPL) or run wstack directly without piping.\n"
|
|
3339
|
+
);
|
|
3340
|
+
await reader.close();
|
|
3341
|
+
return 2;
|
|
3342
|
+
}
|
|
3343
|
+
const noInteractiveMode = flags["webui"] || flags["no-interactive"];
|
|
3344
|
+
const providerGate = await resolveBootProviderModel({
|
|
3345
|
+
flags,
|
|
3346
|
+
config,
|
|
3347
|
+
vault,
|
|
3348
|
+
modelsRegistry,
|
|
3349
|
+
renderer,
|
|
3350
|
+
reader,
|
|
3351
|
+
profileConfigPath,
|
|
3352
|
+
noInteractiveMode
|
|
3353
|
+
});
|
|
3354
|
+
if (providerGate.kind === "exit") return providerGate.code;
|
|
3355
|
+
({ config, vault } = providerGate);
|
|
3356
|
+
if (flags["webui"]) {
|
|
3357
|
+
flags["tui"] = false;
|
|
3358
|
+
flags["no-tui"] = true;
|
|
3359
|
+
}
|
|
3360
|
+
const launch = await applyBootLaunchChoices({
|
|
3361
|
+
isInteractiveTTY,
|
|
3362
|
+
simpleUiFullAuto,
|
|
3363
|
+
flags,
|
|
3364
|
+
config,
|
|
3365
|
+
renderer,
|
|
3366
|
+
reader,
|
|
3367
|
+
profileConfigPath,
|
|
3368
|
+
wpaths,
|
|
3369
|
+
projectRoot
|
|
3370
|
+
});
|
|
3371
|
+
if (launch.kind === "exit") return launch.code;
|
|
3372
|
+
config = launch.config;
|
|
3269
3373
|
applyGoalTuiDefault(flags, positional);
|
|
3270
3374
|
flags["director"] = true;
|
|
3271
3375
|
return {
|
|
@@ -3311,7 +3415,7 @@ async function initializeCli(argv, options = {}) {
|
|
|
3311
3415
|
error: err
|
|
3312
3416
|
}).catch(() => void 0);
|
|
3313
3417
|
}
|
|
3314
|
-
|
|
3418
|
+
writeErr4(
|
|
3315
3419
|
`WebUI session child argument error: ${err instanceof Error ? err.message : String(err)}
|
|
3316
3420
|
`
|
|
3317
3421
|
);
|
|
@@ -3370,7 +3474,7 @@ async function initializeCli(argv, options = {}) {
|
|
|
3370
3474
|
error: err
|
|
3371
3475
|
}).catch(() => void 0);
|
|
3372
3476
|
}
|
|
3373
|
-
|
|
3477
|
+
writeErr4(
|
|
3374
3478
|
`WebUI session child argument error: ${err instanceof Error ? err.message : String(err)}
|
|
3375
3479
|
`
|
|
3376
3480
|
);
|
|
@@ -3399,7 +3503,7 @@ async function initializeCli(argv, options = {}) {
|
|
|
3399
3503
|
recoverable: false,
|
|
3400
3504
|
error: err
|
|
3401
3505
|
}).catch(() => void 0);
|
|
3402
|
-
|
|
3506
|
+
writeErr4(
|
|
3403
3507
|
`WebUI session child argument error: ${err instanceof Error ? err.message : String(err)}
|
|
3404
3508
|
`
|
|
3405
3509
|
);
|
|
@@ -3449,4 +3553,4 @@ async function initializeCli(argv, options = {}) {
|
|
|
3449
3553
|
export {
|
|
3450
3554
|
initializeCli
|
|
3451
3555
|
};
|
|
3452
|
-
//# sourceMappingURL=cli-context-
|
|
3556
|
+
//# sourceMappingURL=cli-context-II57EHD2.js.map
|