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