@wrongstack/cli 0.305.0 → 0.305.1

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.
@@ -13,14 +13,14 @@ import {
13
13
  runOAuthLoginKind,
14
14
  runOAuthLoginMenu,
15
15
  validateFamily
16
- } from "./chunk-FJVCVZIV.js";
16
+ } from "./chunk-5KFLLTSQ.js";
17
17
  import "./chunk-SSSJQVUY.js";
18
18
  import {
19
19
  restoreFlags
20
20
  } from "./chunk-CSAPOCBP.js";
21
21
  import {
22
22
  parseAuthFlags
23
- } from "./chunk-C5NXM2SI.js";
23
+ } from "./chunk-2H47YMCV.js";
24
24
  import "./chunk-3IC4IEZC.js";
25
25
  import {
26
26
  loadConfigProviders,
@@ -657,4 +657,4 @@ async function runAuthRemove(deps, providerId) {
657
657
  export {
658
658
  authCmd
659
659
  };
660
- //# sourceMappingURL=auth-K7CYVVKH.js.map
660
+ //# sourceMappingURL=auth-EPERLKYI.js.map
@@ -62,6 +62,10 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
62
62
  "insecure-open",
63
63
  "strict-port",
64
64
  "client",
65
+ // `wstack doctor` booleans. Without these, `--daemons --clear-stale` parses
66
+ // as `daemons="--clear-stale"` and the second flag disappears.
67
+ "daemons",
68
+ "clear-stale",
65
69
  // `wstack update` booleans. Keeping these here prevents parseArgs from
66
70
  // consuming a following positional token as an accidental flag value.
67
71
  "check-only",
@@ -1052,4 +1056,4 @@ export {
1052
1056
  runLiveProviderPicker,
1053
1057
  runPicker
1054
1058
  };
1055
- //# sourceMappingURL=chunk-C5NXM2SI.js.map
1059
+ //# sourceMappingURL=chunk-2H47YMCV.js.map
@@ -6,7 +6,7 @@ import {
6
6
  import {
7
7
  LOCAL_LLM_PRESETS,
8
8
  runLiveProviderPicker
9
- } from "./chunk-C5NXM2SI.js";
9
+ } from "./chunk-2H47YMCV.js";
10
10
  import {
11
11
  activeLabel,
12
12
  loadConfigProviders,
@@ -1374,4 +1374,4 @@ export {
1374
1374
  addCustomProvider,
1375
1375
  addKeyForProvider
1376
1376
  };
1377
- //# sourceMappingURL=chunk-FJVCVZIV.js.map
1377
+ //# sourceMappingURL=chunk-5KFLLTSQ.js.map
@@ -59,7 +59,7 @@ import {
59
59
  runClaudeOAuthLogin,
60
60
  runCopilotOAuthLogin,
61
61
  validateFamily
62
- } from "./chunk-FJVCVZIV.js";
62
+ } from "./chunk-5KFLLTSQ.js";
63
63
  import {
64
64
  runCodexOAuthLogin
65
65
  } from "./chunk-SSSJQVUY.js";
@@ -78,7 +78,7 @@ import {
78
78
  import {
79
79
  LOCAL_LLM_PRESETS,
80
80
  parseSpawnFlags
81
- } from "./chunk-C5NXM2SI.js";
81
+ } from "./chunk-2H47YMCV.js";
82
82
  import {
83
83
  buildPickableProviders,
84
84
  visibleModelIds
@@ -9542,7 +9542,11 @@ import {
9542
9542
  sessionScopedPath as sessionScopedPath3,
9543
9543
  toErrorMessage as toErrorMessage5
9544
9544
  } from "@wrongstack/core/utils";
9545
- import { attachSessionKanbanMirror, hydrateSessionKanban } from "@wrongstack/tools/session-kanban";
9545
+ import {
9546
+ attachSessionKanbanMirror,
9547
+ hydrateSessionKanban,
9548
+ sessionKanbanDegradation
9549
+ } from "@wrongstack/tools/session-kanban";
9546
9550
  async function setupSession(params) {
9547
9551
  const {
9548
9552
  config,
@@ -9706,6 +9710,12 @@ async function setupSession(params) {
9706
9710
  const taskPath = sessionScopedPath3(wpaths.projectSessions, sessionId, ".tasks.json");
9707
9711
  context.state.setMeta("task.path", taskPath);
9708
9712
  await hydrateSessionKanban(context);
9713
+ const kanbanDown = sessionKanbanDegradation();
9714
+ if (kanbanDown) {
9715
+ renderer.writeInfo(
9716
+ `Kanban board sync unavailable \u2014 continuing without it (${kanbanDown}). Run \`wstack doctor --daemons\` for detail.`
9717
+ );
9718
+ }
9709
9719
  const detachSessionKanbanMirror = attachSessionKanbanMirror(context);
9710
9720
  const detachTodosCheckpoint = async () => {
9711
9721
  detachSessionKanbanMirror();
@@ -25646,24 +25656,78 @@ function buildWebuiCommand() {
25646
25656
  }
25647
25657
 
25648
25658
  // src/slash-commands/theme.ts
25659
+ import { THEME_PRESET_IDS } from "@wrongstack/core/types";
25649
25660
  import { color as color46, writeOut as writeOut3 } from "@wrongstack/core/utils";
25650
- var THEME_OPTIONS = [
25651
- { id: "catppuccin", name: "Catppuccin Mocha", desc: "Soft pastel dark theme (Default)" },
25652
- { id: "tokyo-night", name: "Tokyo Night", desc: "Deep violet, neon orange & cyan" },
25653
- { id: "nord", name: "Nord", desc: "Cool arctic blue & slate pastels" },
25654
- { id: "cyberpunk", name: "Cyberpunk Neon", desc: "High-contrast magenta, cyan & yellow" },
25655
- { id: "dracula", name: "Dracula", desc: "Vibrant purple, pink & green" },
25656
- { id: "gruvbox-dark", name: "Gruvbox Dark", desc: "Warm earthy retro \u2014 orange, olive & aqua" },
25657
- { id: "solarized-dark", name: "Solarized Dark", desc: "Base16 classic \u2014 teal & ochre precision" },
25658
- { id: "one-dark", name: "One Dark", desc: "Atom's iconic palette \u2014 blue-led, warm accents" },
25659
- { id: "monokai", name: "Monokai", desc: "Sublime classic \u2014 vivid magenta, cyan & lime" },
25660
- { id: "rose-pine", name: "Ros\xE9 Pine", desc: "Aesthetic pine & foam \u2014 soft evening pastels" },
25661
- { id: "kanagawa", name: "Kanagawa", desc: "Hokusai-inspired waves \u2014 sumi ink on washi" },
25662
- { id: "ayu-dark", name: "Ayu Dark", desc: "Simple pleasant dark \u2014 warm orange + cool blue" },
25663
- { id: "everforest", name: "Everforest", desc: "Green-based comfort \u2014 forest greens & warm tans" },
25664
- { id: "night-owl", name: "Night Owl", desc: "Sarah Drasner's night \u2014 deep navy + bold accents" },
25665
- { id: "synthwave", name: "Synthwave '84", desc: "Hot pink + neon cyan on deep purple" }
25666
- ];
25661
+ var THEME_META = {
25662
+ catppuccin: { name: "Catppuccin Mocha", desc: "Soft pastel dark theme (Default)" },
25663
+ "tokyo-night": { name: "Tokyo Night", desc: "Deep violet, neon orange & cyan" },
25664
+ nord: { name: "Nord", desc: "Cool arctic blue & slate pastels" },
25665
+ cyberpunk: { name: "Cyberpunk Neon", desc: "High-contrast magenta, cyan & yellow" },
25666
+ dracula: { name: "Dracula", desc: "Vibrant purple, pink & green" },
25667
+ "gruvbox-dark": { name: "Gruvbox Dark", desc: "Warm earthy retro \u2014 orange, olive & aqua" },
25668
+ "solarized-dark": { name: "Solarized Dark", desc: "Base16 classic \u2014 teal & ochre precision" },
25669
+ "one-dark": { name: "One Dark", desc: "Atom's iconic palette \u2014 blue-led, warm accents" },
25670
+ monokai: { name: "Monokai", desc: "Sublime classic \u2014 vivid magenta, cyan & lime" },
25671
+ "rose-pine": { name: "Ros\xE9 Pine", desc: "Aesthetic pine & foam \u2014 soft evening pastels" },
25672
+ kanagawa: { name: "Kanagawa", desc: "Hokusai-inspired waves \u2014 sumi ink on washi" },
25673
+ "ayu-dark": { name: "Ayu Dark", desc: "Simple pleasant dark \u2014 warm orange + cool blue" },
25674
+ everforest: { name: "Everforest", desc: "Green-based comfort \u2014 forest greens & warm tans" },
25675
+ "night-owl": { name: "Night Owl", desc: "Sarah Drasner's night \u2014 deep navy + bold accents" },
25676
+ synthwave: { name: "Synthwave '84", desc: "Hot pink + neon cyan on deep purple" },
25677
+ "github-dark": { name: "GitHub Dark", desc: "GitHub's default dark \u2014 crisp blue on near-black" },
25678
+ "material-ocean": {
25679
+ name: "Material Ocean",
25680
+ desc: "Deepest Material variant \u2014 ink blue with pastel accents"
25681
+ },
25682
+ nightfox: { name: "Nightfox", desc: "Balanced slate blue with muted sage and rose" },
25683
+ oxocarbon: {
25684
+ name: "Oxocarbon",
25685
+ desc: "IBM Carbon-derived \u2014 neutral greys, electric blue & pink"
25686
+ },
25687
+ "catppuccin-macchiato": {
25688
+ name: "Catppuccin Macchiato",
25689
+ desc: "Warmer, one shade lighter than Mocha"
25690
+ },
25691
+ "catppuccin-frappe": {
25692
+ name: "Catppuccin Frapp\xE9",
25693
+ desc: "The lightest Catppuccin dark \u2014 gentle midday contrast"
25694
+ },
25695
+ "gruvbox-material": {
25696
+ name: "Gruvbox Material",
25697
+ desc: "Softened Gruvbox \u2014 same warmth, lower eye strain"
25698
+ },
25699
+ "tokyo-night-storm": {
25700
+ name: "Tokyo Night Storm",
25701
+ desc: "Tokyo Night on a lifted blue-grey base"
25702
+ },
25703
+ "rose-pine-moon": {
25704
+ name: "Ros\xE9 Pine Moon",
25705
+ desc: "Ros\xE9 Pine at dusk \u2014 deeper base, same soft accents"
25706
+ },
25707
+ zenburn: { name: "Zenburn", desc: "The classic low-contrast grey \u2014 desaturated and calm" },
25708
+ palenight: { name: "Palenight", desc: "Material Palenight \u2014 indigo base, candy accents" },
25709
+ horizon: { name: "Horizon", desc: "Warm coral and mint on charcoal \u2014 sunset gradient" },
25710
+ sonokai: { name: "Sonokai", desc: "Monokai Pro descendant \u2014 punchy on warm graphite" },
25711
+ "edge-dark": { name: "Edge Dark", desc: "Clean, evenly-weighted palette on desaturated navy" },
25712
+ moonfly: { name: "Moonfly", desc: "Near-black base with high-chroma accents \u2014 max contrast" },
25713
+ melange: { name: "Melange", desc: "Warm sepia and clay \u2014 the least blue dark theme here" },
25714
+ poimandres: { name: "Poimandres", desc: "Teal-forward, low-saturation \u2014 mint on deep indigo" },
25715
+ "vitesse-dark": {
25716
+ name: "Vitesse Dark",
25717
+ desc: "Anthony Fu's minimal palette \u2014 muted, print-like"
25718
+ },
25719
+ aura: { name: "Aura Dark", desc: "Vivid purple and spring green on near-black violet" },
25720
+ "dark-plus": { name: "VS Code Dark+", desc: "VS Code's default \u2014 familiar blue/orange/teal" }
25721
+ };
25722
+ var THEME_OPTIONS = THEME_PRESET_IDS.map((id) => ({ id, ...THEME_META[id] }));
25723
+ function presetHelpLines(perLine = 4) {
25724
+ const lines = [];
25725
+ for (let i = 0; i < THEME_PRESET_IDS.length; i += perLine) {
25726
+ const chunk = THEME_PRESET_IDS.slice(i, i + perLine).join(", ");
25727
+ lines.push(` ${chunk}${i + perLine < THEME_PRESET_IDS.length ? "," : ""}`);
25728
+ }
25729
+ return lines;
25730
+ }
25667
25731
  async function runThemePicker(reader, activeId) {
25668
25732
  let cursor = THEME_OPTIONS.findIndex((p) => p.id === activeId);
25669
25733
  if (cursor < 0) cursor = 0;
@@ -25678,7 +25742,7 @@ ${color46.bold(color46.amber("WrongStack") + color46.dim(" \u2014 TUI Theme Sele
25678
25742
  const mark = p.id === activeId ? color46.green(" [active]") : "";
25679
25743
  const prefix = i === currentCursor ? color46.bold("\u276F ") : " ";
25680
25744
  const name = i === currentCursor ? color46.bold(p.name) : p.name;
25681
- lines.push(` ${prefix}${name.padEnd(18)} ${color46.dim(p.desc)}${mark}`);
25745
+ lines.push(` ${prefix}${name.padEnd(21)} ${color46.dim(p.desc)}${mark}`);
25682
25746
  }
25683
25747
  lines.push("");
25684
25748
  return lines.join("\n");
@@ -25708,16 +25772,14 @@ function buildThemeCommand(opts) {
25708
25772
  name: "theme",
25709
25773
  category: "Config",
25710
25774
  description: "Switch or select the TUI color theme preset interactively",
25711
- argsHint: "[catppuccin | tokyo-night | nord | cyberpunk | dracula | gruvbox-dark | solarized-dark | one-dark | monokai | rose-pine | kanagawa | ayu-dark | everforest | night-owl | synthwave]",
25775
+ argsHint: `[<preset>] (${THEME_PRESET_IDS.length} available \u2014 run /theme to pick)`,
25712
25776
  help: [
25713
25777
  "Usage:",
25714
25778
  " /theme Interactive menu selection or view available theme presets",
25715
25779
  " /theme <preset> Switch directly to a theme preset",
25716
25780
  "",
25717
- "Available presets:",
25718
- " catppuccin, tokyo-night, nord, cyberpunk, dracula,",
25719
- " gruvbox-dark, solarized-dark, one-dark, monokai, rose-pine,",
25720
- " kanagawa, ayu-dark, everforest, night-owl, synthwave"
25781
+ `Available presets (${THEME_PRESET_IDS.length}):`,
25782
+ ...presetHelpLines()
25721
25783
  ].join("\n"),
25722
25784
  async run(args) {
25723
25785
  const validPresets = THEME_OPTIONS.map((t) => t.id);
@@ -25747,7 +25809,7 @@ function buildThemeCommand(opts) {
25747
25809
  ];
25748
25810
  for (const t of THEME_OPTIONS) {
25749
25811
  const mark = t.id === activePreset ? color46.green(" [active]") : "";
25750
- lines.push(` \u2022 ${color46.bold(t.id.padEnd(14))} ${t.desc}${mark}`);
25812
+ lines.push(` \u2022 ${color46.bold(t.id.padEnd(21))} ${t.desc}${mark}`);
25751
25813
  }
25752
25814
  lines.push("");
25753
25815
  lines.push(color46.dim("Usage: /theme <preset>"));
@@ -32161,7 +32223,7 @@ async function runInteractive(cliCtx) {
32161
32223
  onEvent: evOn
32162
32224
  });
32163
32225
  const savedProviderCfg = config.providers?.[config.provider];
32164
- const { execute } = await import("./execution-YMBD7YWC.js");
32226
+ const { execute } = await import("./execution-QYFWDD3Y.js");
32165
32227
  const stopHeapWatchdog = startSharedHeapWatchdog({
32166
32228
  collectStats: () => {
32167
32229
  const hqQueue = hqPublisherRef.current?.getQueueStats();
@@ -32399,4 +32461,4 @@ export {
32399
32461
  CLI_VERSION,
32400
32462
  runInteractive
32401
32463
  };
32402
- //# sourceMappingURL=cli-main-W3T56YCY.js.map
32464
+ //# sourceMappingURL=cli-main-6YE423OH.js.map
@@ -0,0 +1,329 @@
1
+ import {
2
+ API_VERSION
3
+ } from "./chunk-XJXDOF63.js";
4
+ import "./chunk-7OCVIDC7.js";
5
+
6
+ // src/subcommands/handlers/diag-doctor.ts
7
+ import * as fs2 from "node:fs/promises";
8
+ import * as os from "node:os";
9
+ import * as path2 from "node:path";
10
+ import { color, toErrorMessage } from "@wrongstack/core/utils";
11
+
12
+ // src/subcommands/handlers/daemon-inventory.ts
13
+ import * as fs from "node:fs/promises";
14
+ import * as path from "node:path";
15
+ import {
16
+ chronicleProjectServerEndpoint,
17
+ chronicleProjectServerMetadataPath
18
+ } from "@wrongstack/core/chronicle";
19
+ import {
20
+ mailboxProjectServerEndpoint,
21
+ mailboxProjectServerMetadataPath
22
+ } from "@wrongstack/core/coordination";
23
+ import {
24
+ sessionCatalogProjectServerEndpoint,
25
+ sessionCatalogProjectServerMetadataPath
26
+ } from "@wrongstack/core/session-catalog";
27
+ import {
28
+ KANBAN_PROJECT_SERVER_METADATA_FILE,
29
+ kanbanProjectServerEndpoint
30
+ } from "@wrongstack/kanban";
31
+ import { isProjectEndpointLive } from "@wrongstack/persistence";
32
+ import { sageProjectServerEndpoint, sageProjectServerMetadataPath } from "@wrongstack/sage";
33
+ import {
34
+ projectIndexServerEndpoint,
35
+ projectIndexServerMetadataPath
36
+ } from "@wrongstack/tools/codebase-index";
37
+ function describeDaemons(options) {
38
+ const { projectRoot, projectDir } = options;
39
+ return [
40
+ {
41
+ name: "kanban",
42
+ // In-repo `<projectRoot>/.wrongstack/`, not the global per-project state
43
+ // directory the other daemons use. Kanban deliberately keeps its server
44
+ // file next to the boards it owns.
45
+ metadataPath: path.join(projectRoot, ".wrongstack", KANBAN_PROJECT_SERVER_METADATA_FILE),
46
+ endpoint: kanbanProjectServerEndpoint(projectRoot)
47
+ },
48
+ {
49
+ name: "sage",
50
+ endpoint: sageProjectServerEndpoint(projectRoot),
51
+ metadataPath: sageProjectServerMetadataPath(projectRoot)
52
+ },
53
+ {
54
+ name: "chronicle",
55
+ endpoint: chronicleProjectServerEndpoint(projectDir),
56
+ metadataPath: chronicleProjectServerMetadataPath(projectDir)
57
+ },
58
+ {
59
+ name: "mailbox",
60
+ endpoint: mailboxProjectServerEndpoint(projectDir),
61
+ metadataPath: mailboxProjectServerMetadataPath(projectDir)
62
+ },
63
+ {
64
+ name: "session-catalog",
65
+ endpoint: sessionCatalogProjectServerEndpoint(projectDir),
66
+ metadataPath: sessionCatalogProjectServerMetadataPath(projectDir)
67
+ },
68
+ {
69
+ name: "codebase-index",
70
+ endpoint: projectIndexServerEndpoint(projectRoot),
71
+ metadataPath: projectIndexServerMetadataPath(projectRoot)
72
+ }
73
+ ];
74
+ }
75
+ async function readPid(metadataPath) {
76
+ if (!metadataPath) return void 0;
77
+ try {
78
+ const parsed = JSON.parse(await fs.readFile(metadataPath, "utf8"));
79
+ return typeof parsed.pid === "number" ? parsed.pid : void 0;
80
+ } catch {
81
+ return void 0;
82
+ }
83
+ }
84
+ async function endpointExists(endpoint) {
85
+ if (process.platform === "win32") return false;
86
+ try {
87
+ await fs.stat(endpoint);
88
+ return true;
89
+ } catch {
90
+ return false;
91
+ }
92
+ }
93
+ async function collectDaemonReports(options) {
94
+ return Promise.all(
95
+ describeDaemons(options).map(async (descriptor) => {
96
+ const live = await isProjectEndpointLive(descriptor.endpoint);
97
+ const status = live ? "live" : await endpointExists(descriptor.endpoint) ? "stale" : "stopped";
98
+ const pid = live ? await readPid(descriptor.metadataPath) : void 0;
99
+ return pid === void 0 ? { name: descriptor.name, endpoint: descriptor.endpoint, status } : { name: descriptor.name, endpoint: descriptor.endpoint, status, pid };
100
+ })
101
+ );
102
+ }
103
+ async function clearStaleDaemonEndpoints(options) {
104
+ const cleared = [];
105
+ for (const report of await collectDaemonReports(options)) {
106
+ if (report.status !== "stale") continue;
107
+ try {
108
+ await fs.rm(report.endpoint, { force: true });
109
+ cleared.push(report.name);
110
+ } catch {
111
+ }
112
+ }
113
+ return cleared;
114
+ }
115
+
116
+ // src/subcommands/handlers/diag-doctor.ts
117
+ var diagCmd = async (_args, deps) => {
118
+ const cfg = deps.config;
119
+ const age = await deps.modelsRegistry.ageSeconds();
120
+ const lines = [
121
+ color.bold("WrongStack diagnostics"),
122
+ ` apiVersion: ${API_VERSION}`,
123
+ ` cwd: ${deps.cwd}`,
124
+ ` projectRoot: ${deps.projectRoot}`,
125
+ ` projectHash: ${deps.paths.projectHash}`,
126
+ ` projectDir: ${deps.paths.projectDir}`,
127
+ ` globalRoot: ${deps.paths.globalRoot}`,
128
+ ` modelsCache: ${deps.paths.modelsCache}`,
129
+ ` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
130
+ ` node: ${process.version}`,
131
+ ` os: ${os.platform()} ${os.release()}`,
132
+ ` provider: ${cfg.provider ?? "<unset>"}`,
133
+ ` model: ${cfg.model ?? "<unset>"}`,
134
+ ` tools: ${deps.toolRegistry?.list().length ?? 0}`,
135
+ ` plugins: ${cfg.plugins?.length ?? 0}`,
136
+ ` mcpServers: ${Object.keys(cfg.mcpServers ?? {}).length}`
137
+ ];
138
+ deps.renderer.write(lines.join("\n") + "\n");
139
+ return 0;
140
+ };
141
+ async function reportDaemons(deps, opts) {
142
+ const inventory = { projectRoot: deps.projectRoot, projectDir: deps.paths.projectDir };
143
+ let reports = await collectDaemonReports(inventory);
144
+ deps.renderer.write(color.bold("WrongStack project daemons\n\n"));
145
+ const icon = (status) => status === "live" ? color.green("\u2713") : status === "stale" ? color.red("\u2717") : color.dim("\xB7");
146
+ for (const report of reports) {
147
+ const suffix = report.pid === void 0 ? "" : ` pid ${report.pid}`;
148
+ deps.renderer.write(
149
+ ` ${icon(report.status)} ${report.name.padEnd(16)} ${report.status.padEnd(8)}${color.dim(report.endpoint + suffix)}
150
+ `
151
+ );
152
+ }
153
+ const stale = reports.filter((report) => report.status === "stale");
154
+ if (stale.length === 0) {
155
+ deps.renderer.write(color.green("\nNo wedged endpoints.\n"));
156
+ return 0;
157
+ }
158
+ if (!opts.clear) {
159
+ deps.renderer.write(
160
+ color.amber(
161
+ `
162
+ ${stale.length} stale endpoint${stale.length === 1 ? "" : "s"}: ${stale.map((report) => report.name).join(", ")}
163
+ `
164
+ )
165
+ );
166
+ deps.renderer.write(
167
+ color.dim(
168
+ " A daemon died without releasing its endpoint. Current daemons reclaim\n this automatically on next start; clear it now with:\n wstack doctor --daemons --clear-stale\n"
169
+ )
170
+ );
171
+ return 1;
172
+ }
173
+ const cleared = await clearStaleDaemonEndpoints(inventory);
174
+ reports = await collectDaemonReports(inventory);
175
+ const remaining = reports.filter((report) => report.status === "stale");
176
+ if (cleared.length > 0) {
177
+ deps.renderer.write(color.green(`
178
+ Cleared: ${cleared.join(", ")}
179
+ `));
180
+ }
181
+ if (remaining.length > 0) {
182
+ deps.renderer.write(
183
+ color.red(`Still wedged: ${remaining.map((report) => report.name).join(", ")}
184
+ `)
185
+ );
186
+ return 1;
187
+ }
188
+ deps.renderer.write(color.dim("Daemons restart on demand \u2014 no further action needed.\n"));
189
+ return 0;
190
+ }
191
+ var doctorCmd = async (args, deps) => {
192
+ const flagged = (name) => deps.flags?.[name] === true || deps.flags?.[name] === "true" || args.includes(`--${name}`);
193
+ if (flagged("daemons")) {
194
+ return reportDaemons(deps, { clear: flagged("clear-stale") });
195
+ }
196
+ const checks = [];
197
+ const cfg = deps.config;
198
+ if (!cfg.provider)
199
+ checks.push({
200
+ name: "provider",
201
+ status: "fail",
202
+ detail: "no provider configured \u2014 run `wstack auth` to set up"
203
+ });
204
+ else checks.push({ name: "provider", status: "ok", detail: cfg.provider });
205
+ if (!cfg.model)
206
+ checks.push({
207
+ name: "model",
208
+ status: "fail",
209
+ detail: "no model configured \u2014 run `wstack auth` to configure"
210
+ });
211
+ else checks.push({ name: "model", status: "ok", detail: cfg.model });
212
+ if (cfg.provider) {
213
+ const providerCfg = cfg.providers?.[cfg.provider];
214
+ const hasVaultKey = typeof providerCfg?.apiKey === "string" && providerCfg.apiKey.length > 0;
215
+ const envHit = providerCfg?.envVars?.some((v) => process.env[v]) ?? false;
216
+ if (hasVaultKey || envHit)
217
+ checks.push({
218
+ name: "api key",
219
+ status: "ok",
220
+ detail: hasVaultKey ? "found in vault" : "found in env"
221
+ });
222
+ else
223
+ checks.push({
224
+ name: "api key",
225
+ status: "fail",
226
+ detail: `no key for "${cfg.provider}" in vault or env \u2014 run \`wstack auth ${cfg.provider}\``
227
+ });
228
+ }
229
+ try {
230
+ const age = await deps.modelsRegistry.ageSeconds();
231
+ if (!isFinite(age))
232
+ checks.push({
233
+ name: "models cache",
234
+ status: "warn",
235
+ detail: "never fetched \u2014 run `wstack models refresh`"
236
+ });
237
+ else if (age > 7 * 24 * 3600)
238
+ checks.push({
239
+ name: "models cache",
240
+ status: "warn",
241
+ detail: `${Math.round(age / 86400)} days old \u2014 run \`wstack models refresh\``
242
+ });
243
+ else
244
+ checks.push({ name: "models cache", status: "ok", detail: `${Math.round(age / 60)}m old` });
245
+ } catch (err) {
246
+ checks.push({
247
+ name: "models cache",
248
+ status: "warn",
249
+ detail: `read failed: ${toErrorMessage(err)}`
250
+ });
251
+ }
252
+ try {
253
+ await fs2.access(deps.paths.secretsKey);
254
+ checks.push({ name: "secret vault", status: "ok", detail: deps.paths.secretsKey });
255
+ } catch {
256
+ checks.push({
257
+ name: "secret vault",
258
+ status: "warn",
259
+ detail: "not yet initialized (created lazily on first encrypt)"
260
+ });
261
+ }
262
+ try {
263
+ await fs2.mkdir(deps.paths.projectSessions, { recursive: true });
264
+ const probe = path2.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
265
+ await fs2.writeFile(probe, "");
266
+ await fs2.unlink(probe);
267
+ checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
268
+ } catch (err) {
269
+ checks.push({
270
+ name: "sessions writable",
271
+ status: "fail",
272
+ detail: `cannot write to ${deps.paths.projectSessions}: ${toErrorMessage(err)}`
273
+ });
274
+ }
275
+ const mcpEntries = Object.entries(cfg.mcpServers ?? {});
276
+ for (const [name, srv] of mcpEntries) {
277
+ if (!srv.enabled) continue;
278
+ if ((srv.transport === "sse" || srv.transport === "streamable-http") && !srv.url)
279
+ checks.push({ name: `mcp:${name}`, status: "fail", detail: "transport requires url" });
280
+ else if (srv.transport === "stdio" && !srv.command)
281
+ checks.push({
282
+ name: `mcp:${name}`,
283
+ status: "fail",
284
+ detail: "stdio transport requires command"
285
+ });
286
+ else
287
+ checks.push({
288
+ name: `mcp:${name}`,
289
+ status: "ok",
290
+ detail: `${srv.transport} ${srv.command ?? srv.url ?? ""}`.trim()
291
+ });
292
+ }
293
+ const major = Number.parseInt(process.version.replace(/^v/, "").split(".")[0] ?? "0", 10);
294
+ if (major < 22)
295
+ checks.push({ name: "node", status: "fail", detail: `${process.version} (need \u226522)` });
296
+ else checks.push({ name: "node", status: "ok", detail: process.version });
297
+ deps.renderer.write(color.bold("WrongStack doctor\n\n"));
298
+ let failed = 0;
299
+ let warned = 0;
300
+ for (const c of checks) {
301
+ const icon = c.status === "ok" ? color.green("\u2713") : c.status === "warn" ? color.amber("\u25CF") : color.red("\u2717");
302
+ deps.renderer.write(` ${icon} ${c.name.padEnd(20)} ${color.dim(c.detail)}
303
+ `);
304
+ if (c.status === "fail") failed++;
305
+ if (c.status === "warn") warned++;
306
+ }
307
+ deps.renderer.write("\n");
308
+ if (failed > 0) {
309
+ deps.renderer.write(
310
+ color.red(`${failed} failed, ${warned} warning${warned === 1 ? "" : "s"}
311
+ `)
312
+ );
313
+ return 1;
314
+ }
315
+ if (warned > 0) {
316
+ deps.renderer.write(
317
+ color.amber(`All checks passed (${warned} warning${warned === 1 ? "" : "s"})
318
+ `)
319
+ );
320
+ return 0;
321
+ }
322
+ deps.renderer.write(color.green("All checks passed.\n"));
323
+ return 0;
324
+ };
325
+ export {
326
+ diagCmd,
327
+ doctorCmd
328
+ };
329
+ //# sourceMappingURL=diag-doctor-WTBHPOMB.js.map
@@ -1873,24 +1873,9 @@ function createSettingsAdapter(ctx) {
1873
1873
  // src/boot/tui-theme-adapter.ts
1874
1874
  import * as fs4 from "node:fs/promises";
1875
1875
  import * as path9 from "node:path";
1876
+ import { THEME_PRESET_IDS } from "@wrongstack/core/types";
1876
1877
  import { atomicWrite as atomicWrite2 } from "@wrongstack/core/utils";
1877
- var VALID_PRESETS = /* @__PURE__ */ new Set([
1878
- "catppuccin",
1879
- "tokyo-night",
1880
- "nord",
1881
- "cyberpunk",
1882
- "dracula",
1883
- "gruvbox-dark",
1884
- "solarized-dark",
1885
- "one-dark",
1886
- "monokai",
1887
- "rose-pine",
1888
- "kanagawa",
1889
- "ayu-dark",
1890
- "everforest",
1891
- "night-owl",
1892
- "synthwave"
1893
- ]);
1878
+ var VALID_PRESETS = new Set(THEME_PRESET_IDS);
1894
1879
  function createThemeAdapter({ configStore, wpaths }) {
1895
1880
  return {
1896
1881
  getThemePreset: () => {
@@ -5362,4 +5347,4 @@ export {
5362
5347
  execute,
5363
5348
  resolveReviewerFallbackModels
5364
5349
  };
5365
- //# sourceMappingURL=execution-YMBD7YWC.js.map
5350
+ //# sourceMappingURL=execution-QYFWDD3Y.js.map
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  parseArgs,
22
22
  runPicker,
23
23
  saveToGlobalConfig
24
- } from "./chunk-C5NXM2SI.js";
24
+ } from "./chunk-2H47YMCV.js";
25
25
  import {
26
26
  isKeylessLocalProvider,
27
27
  visibleModelIds
@@ -2308,7 +2308,7 @@ function compactSingleLine(text) {
2308
2308
  var loaders = {
2309
2309
  acp: async () => (await import("./acp-5ZLGFHWP.js")).acpCmd,
2310
2310
  init: async () => (await import("./init-E2NDDOHI.js")).initCmd,
2311
- auth: async () => (await import("./auth-K7CYVVKH.js")).authCmd,
2311
+ auth: async () => (await import("./auth-EPERLKYI.js")).authCmd,
2312
2312
  update: async () => (await import("./update-VZSOZGYC.js")).updateCmd,
2313
2313
  sessions: async () => (await import("./sessions-config-YX2ZPAFM.js")).sessionsCmd,
2314
2314
  config: async () => (await import("./sessions-config-YX2ZPAFM.js")).configCmd,
@@ -2322,8 +2322,8 @@ var loaders = {
2322
2322
  mcp: async () => (await import("./mcp-MSARYOPN.js")).mcpCmd,
2323
2323
  plugin: async () => (await import("./plugin-usage-PTEETL3J.js")).pluginCmd,
2324
2324
  plugins: async () => (await import("./plugin-usage-PTEETL3J.js")).pluginCmd,
2325
- diag: async () => (await import("./diag-doctor-ZF5BF2MK.js")).diagCmd,
2326
- doctor: async () => (await import("./diag-doctor-ZF5BF2MK.js")).doctorCmd,
2325
+ diag: async () => (await import("./diag-doctor-WTBHPOMB.js")).diagCmd,
2326
+ doctor: async () => (await import("./diag-doctor-WTBHPOMB.js")).doctorCmd,
2327
2327
  export: async () => (await import("./export-CCUXRJOU.js")).exportCmd,
2328
2328
  usage: async () => (await import("./plugin-usage-PTEETL3J.js")).usageCmd,
2329
2329
  version: async () => (await import("./version-help-ASKQYXDJ.js")).versionCmd,
@@ -3089,7 +3089,7 @@ async function initializeCli(argv) {
3089
3089
  async function main(argv) {
3090
3090
  const cliCtx = await initializeCli(argv);
3091
3091
  if (typeof cliCtx === "number") return cliCtx;
3092
- const { runInteractive } = await import("./cli-main-W3T56YCY.js");
3092
+ const { runInteractive } = await import("./cli-main-6YE423OH.js");
3093
3093
  return runInteractive(cliCtx);
3094
3094
  }
3095
3095
 
@@ -0,0 +1,41 @@
1
+ export type DaemonStatus =
2
+ /** Answering on its endpoint. */
3
+ 'live'
4
+ /**
5
+ * The endpoint exists on disk but nothing answers on it. This is the wedge:
6
+ * clients get ECONNREFUSED and conclude "spawn one", the replacement gets
7
+ * EADDRINUSE from the leftover file. Self-healing daemons reclaim it on
8
+ * their next start; seeing this means one has not been started since.
9
+ */
10
+ | 'stale'
11
+ /** No endpoint and no daemon. The normal state before first use. */
12
+ | 'stopped';
13
+ export interface DaemonReport {
14
+ readonly name: string;
15
+ readonly endpoint: string;
16
+ readonly status: DaemonStatus;
17
+ /** Owning process id, when a metadata file was readable. */
18
+ readonly pid?: number;
19
+ }
20
+ export interface DaemonInventoryOptions {
21
+ readonly projectRoot: string;
22
+ /**
23
+ * The project's slot under the global root — `~/.wrongstack/projects/<hash>`,
24
+ * NOT the in-repo `.wrongstack/`. Most daemons key their endpoint and
25
+ * metadata off this; kanban is the exception and uses the repo directory.
26
+ */
27
+ readonly projectDir: string;
28
+ }
29
+ /** Probe every project daemon concurrently. Never spawns one. */
30
+ export declare function collectDaemonReports(options: DaemonInventoryOptions): Promise<readonly DaemonReport[]>;
31
+ /**
32
+ * Delete stale endpoints so the next client spawn can bind.
33
+ *
34
+ * Only ever removes an endpoint that a fresh probe just reported as stale —
35
+ * unlinking a live daemon's socket would sever every connected client and
36
+ * elect a second writer over the same database. Restarting is therefore not
37
+ * an operation this offers: daemons are spawned on demand, so clearing the
38
+ * wedge is the whole job, and the next command that needs one starts it.
39
+ */
40
+ export declare function clearStaleDaemonEndpoints(options: DaemonInventoryOptions): Promise<readonly string[]>;
41
+ //# sourceMappingURL=daemon-inventory.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/cli",
3
- "version": "0.305.0",
3
+ "version": "0.305.1",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack CLI — terminal AI coding agent with provider catalog from models.dev. Provides `wrongstack` and `wstack` binaries.",
6
6
  "keywords": [
@@ -42,30 +42,31 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "ws": "^8.21.1",
45
- "@wrongstack/bench": "0.305.0",
46
- "@wrongstack/core": "0.305.0",
47
- "@wrongstack/acp": "0.305.0",
48
- "@wrongstack/kanban": "0.305.0",
49
- "@wrongstack/plug-lsp": "0.305.0",
50
- "@wrongstack/mcp": "0.305.0",
51
- "@wrongstack/requirement-intake": "0.305.0",
52
- "@wrongstack/sdd": "0.305.0",
53
- "@wrongstack/sage": "0.305.0",
54
- "@wrongstack/providers": "0.305.0",
55
- "@wrongstack/runtime": "0.305.0",
56
- "@wrongstack/plugins": "0.305.0",
57
- "@wrongstack/security-scanner": "0.305.0",
58
- "@wrongstack/techstack": "0.305.0",
59
- "@wrongstack/simpleui": "0.305.0",
60
- "@wrongstack/tools": "0.305.0",
61
- "@wrongstack/telegram": "0.305.0",
62
- "@wrongstack/webui-hq": "0.305.0",
63
- "@wrongstack/webui": "0.305.0",
64
- "@wrongstack/tui": "0.305.0",
65
- "@wrongstack/webui-server": "0.305.0"
45
+ "@wrongstack/core": "0.305.1",
46
+ "@wrongstack/mcp": "0.305.1",
47
+ "@wrongstack/acp": "0.305.1",
48
+ "@wrongstack/bench": "0.305.1",
49
+ "@wrongstack/persistence": "0.305.1",
50
+ "@wrongstack/kanban": "0.305.1",
51
+ "@wrongstack/plugins": "0.305.1",
52
+ "@wrongstack/providers": "0.305.1",
53
+ "@wrongstack/plug-lsp": "0.305.1",
54
+ "@wrongstack/sage": "0.305.1",
55
+ "@wrongstack/runtime": "0.305.1",
56
+ "@wrongstack/requirement-intake": "0.305.1",
57
+ "@wrongstack/simpleui": "0.305.1",
58
+ "@wrongstack/sdd": "0.305.1",
59
+ "@wrongstack/security-scanner": "0.305.1",
60
+ "@wrongstack/telegram": "0.305.1",
61
+ "@wrongstack/techstack": "0.305.1",
62
+ "@wrongstack/tools": "0.305.1",
63
+ "@wrongstack/tui": "0.305.1",
64
+ "@wrongstack/webui-hq": "0.305.1",
65
+ "@wrongstack/webui": "0.305.1",
66
+ "@wrongstack/webui-server": "0.305.1"
66
67
  },
67
68
  "optionalDependencies": {
68
- "@wrongstack/desktop": "0.305.0"
69
+ "@wrongstack/desktop": "0.305.1"
69
70
  },
70
71
  "devDependencies": {
71
72
  "@types/node": "^26.1.2",
@@ -1,169 +0,0 @@
1
- import {
2
- API_VERSION
3
- } from "./chunk-XJXDOF63.js";
4
- import "./chunk-7OCVIDC7.js";
5
-
6
- // src/subcommands/handlers/diag-doctor.ts
7
- import * as fs from "node:fs/promises";
8
- import * as os from "node:os";
9
- import * as path from "node:path";
10
- import { color, toErrorMessage } from "@wrongstack/core/utils";
11
- var diagCmd = async (_args, deps) => {
12
- const cfg = deps.config;
13
- const age = await deps.modelsRegistry.ageSeconds();
14
- const lines = [
15
- color.bold("WrongStack diagnostics"),
16
- ` apiVersion: ${API_VERSION}`,
17
- ` cwd: ${deps.cwd}`,
18
- ` projectRoot: ${deps.projectRoot}`,
19
- ` projectHash: ${deps.paths.projectHash}`,
20
- ` projectDir: ${deps.paths.projectDir}`,
21
- ` globalRoot: ${deps.paths.globalRoot}`,
22
- ` modelsCache: ${deps.paths.modelsCache}`,
23
- ` cacheAge: ${isFinite(age) ? `${Math.round(age / 60)}m` : "never"}`,
24
- ` node: ${process.version}`,
25
- ` os: ${os.platform()} ${os.release()}`,
26
- ` provider: ${cfg.provider ?? "<unset>"}`,
27
- ` model: ${cfg.model ?? "<unset>"}`,
28
- ` tools: ${deps.toolRegistry?.list().length ?? 0}`,
29
- ` plugins: ${cfg.plugins?.length ?? 0}`,
30
- ` mcpServers: ${Object.keys(cfg.mcpServers ?? {}).length}`
31
- ];
32
- deps.renderer.write(lines.join("\n") + "\n");
33
- return 0;
34
- };
35
- var doctorCmd = async (_args, deps) => {
36
- const checks = [];
37
- const cfg = deps.config;
38
- if (!cfg.provider)
39
- checks.push({
40
- name: "provider",
41
- status: "fail",
42
- detail: "no provider configured \u2014 run `wstack auth` to set up"
43
- });
44
- else checks.push({ name: "provider", status: "ok", detail: cfg.provider });
45
- if (!cfg.model)
46
- checks.push({
47
- name: "model",
48
- status: "fail",
49
- detail: "no model configured \u2014 run `wstack auth` to configure"
50
- });
51
- else checks.push({ name: "model", status: "ok", detail: cfg.model });
52
- if (cfg.provider) {
53
- const providerCfg = cfg.providers?.[cfg.provider];
54
- const hasVaultKey = typeof providerCfg?.apiKey === "string" && providerCfg.apiKey.length > 0;
55
- const envHit = providerCfg?.envVars?.some((v) => process.env[v]) ?? false;
56
- if (hasVaultKey || envHit)
57
- checks.push({
58
- name: "api key",
59
- status: "ok",
60
- detail: hasVaultKey ? "found in vault" : "found in env"
61
- });
62
- else
63
- checks.push({
64
- name: "api key",
65
- status: "fail",
66
- detail: `no key for "${cfg.provider}" in vault or env \u2014 run \`wstack auth ${cfg.provider}\``
67
- });
68
- }
69
- try {
70
- const age = await deps.modelsRegistry.ageSeconds();
71
- if (!isFinite(age))
72
- checks.push({
73
- name: "models cache",
74
- status: "warn",
75
- detail: "never fetched \u2014 run `wstack models refresh`"
76
- });
77
- else if (age > 7 * 24 * 3600)
78
- checks.push({
79
- name: "models cache",
80
- status: "warn",
81
- detail: `${Math.round(age / 86400)} days old \u2014 run \`wstack models refresh\``
82
- });
83
- else
84
- checks.push({ name: "models cache", status: "ok", detail: `${Math.round(age / 60)}m old` });
85
- } catch (err) {
86
- checks.push({
87
- name: "models cache",
88
- status: "warn",
89
- detail: `read failed: ${toErrorMessage(err)}`
90
- });
91
- }
92
- try {
93
- await fs.access(deps.paths.secretsKey);
94
- checks.push({ name: "secret vault", status: "ok", detail: deps.paths.secretsKey });
95
- } catch {
96
- checks.push({
97
- name: "secret vault",
98
- status: "warn",
99
- detail: "not yet initialized (created lazily on first encrypt)"
100
- });
101
- }
102
- try {
103
- await fs.mkdir(deps.paths.projectSessions, { recursive: true });
104
- const probe = path.join(deps.paths.projectSessions, `.probe-${Date.now()}`);
105
- await fs.writeFile(probe, "");
106
- await fs.unlink(probe);
107
- checks.push({ name: "sessions writable", status: "ok", detail: deps.paths.projectSessions });
108
- } catch (err) {
109
- checks.push({
110
- name: "sessions writable",
111
- status: "fail",
112
- detail: `cannot write to ${deps.paths.projectSessions}: ${toErrorMessage(err)}`
113
- });
114
- }
115
- const mcpEntries = Object.entries(cfg.mcpServers ?? {});
116
- for (const [name, srv] of mcpEntries) {
117
- if (!srv.enabled) continue;
118
- if ((srv.transport === "sse" || srv.transport === "streamable-http") && !srv.url)
119
- checks.push({ name: `mcp:${name}`, status: "fail", detail: "transport requires url" });
120
- else if (srv.transport === "stdio" && !srv.command)
121
- checks.push({
122
- name: `mcp:${name}`,
123
- status: "fail",
124
- detail: "stdio transport requires command"
125
- });
126
- else
127
- checks.push({
128
- name: `mcp:${name}`,
129
- status: "ok",
130
- detail: `${srv.transport} ${srv.command ?? srv.url ?? ""}`.trim()
131
- });
132
- }
133
- const major = Number.parseInt(process.version.replace(/^v/, "").split(".")[0] ?? "0", 10);
134
- if (major < 22)
135
- checks.push({ name: "node", status: "fail", detail: `${process.version} (need \u226522)` });
136
- else checks.push({ name: "node", status: "ok", detail: process.version });
137
- deps.renderer.write(color.bold("WrongStack doctor\n\n"));
138
- let failed = 0;
139
- let warned = 0;
140
- for (const c of checks) {
141
- const icon = c.status === "ok" ? color.green("\u2713") : c.status === "warn" ? color.amber("\u25CF") : color.red("\u2717");
142
- deps.renderer.write(` ${icon} ${c.name.padEnd(20)} ${color.dim(c.detail)}
143
- `);
144
- if (c.status === "fail") failed++;
145
- if (c.status === "warn") warned++;
146
- }
147
- deps.renderer.write("\n");
148
- if (failed > 0) {
149
- deps.renderer.write(
150
- color.red(`${failed} failed, ${warned} warning${warned === 1 ? "" : "s"}
151
- `)
152
- );
153
- return 1;
154
- }
155
- if (warned > 0) {
156
- deps.renderer.write(
157
- color.amber(`All checks passed (${warned} warning${warned === 1 ? "" : "s"})
158
- `)
159
- );
160
- return 0;
161
- }
162
- deps.renderer.write(color.green("All checks passed.\n"));
163
- return 0;
164
- };
165
- export {
166
- diagCmd,
167
- doctorCmd
168
- };
169
- //# sourceMappingURL=diag-doctor-ZF5BF2MK.js.map