@wrongstack/cli 0.306.0 → 0.306.2

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.
@@ -148,6 +148,20 @@ export interface ProviderDeps {
148
148
  id: string;
149
149
  family: string;
150
150
  models: string[];
151
+ modelDetails?: Record<string, {
152
+ name?: string | undefined;
153
+ description?: string | undefined;
154
+ tools?: boolean | undefined;
155
+ vision?: boolean | undefined;
156
+ reasoning?: boolean | undefined;
157
+ maxContext?: number | undefined;
158
+ maxOutput?: number | undefined;
159
+ inputCost?: number | undefined;
160
+ outputCost?: number | undefined;
161
+ cacheReadCost?: number | undefined;
162
+ knowledge?: string | undefined;
163
+ releaseDate?: string | undefined;
164
+ }> | undefined;
151
165
  }>>;
152
166
  switchProviderAndModel: (providerId: string, modelId: string) => string | null | Promise<string | null>;
153
167
  onModelContextResolved?: ((providerId: string, modelId: string, maxContext: number) => void) | undefined;
@@ -5,9 +5,10 @@ import {
5
5
  normalizeTuiThinkingWord,
6
6
  resolveActualTarget,
7
7
  resolvePersistPath,
8
+ runGit,
8
9
  setAutoSuggestions,
9
10
  setSuggestions
10
- } from "./chunk-V3XH6XBJ.js";
11
+ } from "./chunk-T6YAVFXA.js";
11
12
  import {
12
13
  startCliHqConnection
13
14
  } from "./chunk-FKWHSFX4.js";
@@ -5024,6 +5025,373 @@ function createTuiNextStepCallbacks(input) {
5024
5025
  };
5025
5026
  }
5026
5027
 
5028
+ // src/tui-resource-menus.ts
5029
+ import { readdir } from "node:fs/promises";
5030
+ import { readJsonObjectFile } from "@wrongstack/core/utils";
5031
+ function createTuiResourceMenuGetter(ctx) {
5032
+ return async (id) => {
5033
+ switch (id) {
5034
+ case "fallback":
5035
+ return fallbackMenu(ctx.configStore);
5036
+ case "profile":
5037
+ return profileMenu(ctx.configStore, ctx.paths);
5038
+ case "provider-status":
5039
+ return providerStatusMenu(ctx.statusTracker);
5040
+ case "memory":
5041
+ return memoryMenu(ctx.memoryStore);
5042
+ case "worktree":
5043
+ return worktreeMenu(ctx.projectRoot);
5044
+ case "git":
5045
+ return gitMenu(ctx.projectRoot);
5046
+ }
5047
+ };
5048
+ }
5049
+ function fallbackMenu(store) {
5050
+ const config = store.get();
5051
+ const chain = config.fallbackModels ?? [];
5052
+ const profiles = config.fallbackProfiles ?? {};
5053
+ const favorites = config.favoriteModels ?? [];
5054
+ const auto = config.fallbackAuto !== false;
5055
+ const items = [
5056
+ {
5057
+ id: "leader",
5058
+ label: "Leader",
5059
+ status: "good",
5060
+ summary: `${config.provider}/${config.model}`,
5061
+ details: [
5062
+ { label: "provider", value: config.provider },
5063
+ { label: "model", value: config.model },
5064
+ { label: "bridge", value: config.fallbackBridge || "disabled" }
5065
+ ],
5066
+ body: "The active session model. The bridge, when configured, is tried before the ordered fallback chain."
5067
+ },
5068
+ {
5069
+ id: "auto",
5070
+ label: "Smart fallback",
5071
+ status: auto ? "good" : "muted",
5072
+ summary: auto ? "enabled" : "disabled",
5073
+ details: [
5074
+ { label: "mode", value: auto ? "auto-derived when chain is empty" : "explicit chain only" },
5075
+ { label: "favorites only", value: config.favoriteModelsOnly ? "yes" : "no" },
5076
+ { label: "favorites", value: String(favorites.length) }
5077
+ ],
5078
+ actions: [
5079
+ {
5080
+ key: "t",
5081
+ label: auto ? "turn off" : "turn on",
5082
+ command: `/fallback auto ${auto ? "off" : "on"}`
5083
+ }
5084
+ ]
5085
+ },
5086
+ ...chain.map((ref, index) => ({
5087
+ id: `chain:${index}`,
5088
+ label: `${index + 1}. ${ref}`,
5089
+ status: "warn",
5090
+ summary: "explicit fallback",
5091
+ details: [
5092
+ { label: "position", value: String(index + 1) },
5093
+ { label: "model ref", value: ref },
5094
+ { label: "after", value: index === 0 ? "leader/bridge" : chain[index - 1] ?? "leader" }
5095
+ ],
5096
+ actions: [
5097
+ { key: "x", label: "remove", command: `/fallback remove ${index + 1}`, confirm: true }
5098
+ ]
5099
+ })),
5100
+ ...Object.entries(profiles).sort(([a], [b]) => a.localeCompare(b)).map(([name, refs]) => ({
5101
+ id: `profile:${name}`,
5102
+ label: `Profile: ${name}`,
5103
+ status: refs.length > 0 ? "good" : "muted",
5104
+ summary: `${refs.length} model${refs.length === 1 ? "" : "s"}`,
5105
+ details: [
5106
+ { label: "name", value: name },
5107
+ { label: "length", value: String(refs.length) }
5108
+ ],
5109
+ body: refs.join(" \u2192 ") || "(empty profile)",
5110
+ actions: [
5111
+ { key: "u", label: "use", command: `/fallback profile use ${name}`, confirm: true },
5112
+ { key: "x", label: "delete", command: `/fallback profile remove ${name}`, confirm: true }
5113
+ ]
5114
+ })),
5115
+ ...favorites.map((ref, index) => ({
5116
+ id: `favorite:${index}`,
5117
+ label: `\u2605 ${ref}`,
5118
+ status: "good",
5119
+ summary: "favorite model",
5120
+ details: [{ label: "model ref", value: ref }],
5121
+ actions: [
5122
+ {
5123
+ key: "x",
5124
+ label: "unfavorite",
5125
+ command: `/fallback fav remove ${index + 1}`,
5126
+ confirm: true
5127
+ }
5128
+ ]
5129
+ }))
5130
+ ];
5131
+ return {
5132
+ id: "fallback",
5133
+ title: "Fallback routing",
5134
+ subtitle: `${chain.length} explicit \xB7 ${Object.keys(profiles).length} profiles \xB7 ${favorites.length} favorites`,
5135
+ items
5136
+ };
5137
+ }
5138
+ async function profileMenu(store, paths) {
5139
+ const active = store.get().activeProfile ?? paths.profileName ?? "default";
5140
+ let names = [];
5141
+ try {
5142
+ names = (await readdir(paths.profilesDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
5143
+ } catch {
5144
+ }
5145
+ const items = await Promise.all(
5146
+ names.map(async (name) => {
5147
+ const path14 = paths.profileConfig(name);
5148
+ const data = await readJsonObjectFile(path14);
5149
+ const provider = typeof data["provider"] === "string" ? data["provider"] : "unset";
5150
+ const model = typeof data["model"] === "string" ? data["model"] : "unset";
5151
+ const fallbackCount = Array.isArray(data["fallbackModels"]) ? data["fallbackModels"].length : 0;
5152
+ const profileCount = isRecord(data["fallbackProfiles"]) ? Object.keys(data["fallbackProfiles"]).length : 0;
5153
+ const isActive = name === active;
5154
+ return {
5155
+ id: name,
5156
+ label: name,
5157
+ status: isActive ? "good" : "muted",
5158
+ summary: isActive ? "active" : `${provider}/${model}`,
5159
+ details: [
5160
+ { label: "active", value: isActive ? "yes" : "no" },
5161
+ { label: "provider", value: provider },
5162
+ { label: "model", value: model },
5163
+ {
5164
+ label: "theme",
5165
+ value: typeof data["themePreset"] === "string" ? data["themePreset"] : "default"
5166
+ },
5167
+ { label: "fallbacks", value: String(fallbackCount) },
5168
+ { label: "fallback profiles", value: String(profileCount) },
5169
+ { label: "config", value: path14 }
5170
+ ],
5171
+ body: isActive ? "This profile owns the current session configuration." : "Switching profiles closes this session so every service restarts with the selected configuration.",
5172
+ actions: isActive ? [] : [{ key: "s", label: "switch", command: `/profile switch ${name}`, confirm: true }]
5173
+ };
5174
+ })
5175
+ );
5176
+ return {
5177
+ id: "profile",
5178
+ title: "Profiles",
5179
+ subtitle: `active: ${active}`,
5180
+ emptyText: "No profiles found.",
5181
+ items
5182
+ };
5183
+ }
5184
+ function providerStatusMenu(tracker) {
5185
+ const snapshot = tracker?.getSnapshot();
5186
+ const items = snapshot?.statuses.map((status) => ({
5187
+ id: `${status.providerId}/${status.model}`,
5188
+ label: `${status.providerId}/${status.model}`,
5189
+ status: status.state === "healthy" ? "good" : status.state === "degraded" ? "warn" : "bad",
5190
+ summary: status.state,
5191
+ details: [
5192
+ { label: "state", value: status.state },
5193
+ { label: "successes", value: String(status.totalSuccesses) },
5194
+ { label: "failures", value: String(status.totalFailures) },
5195
+ { label: "rate limits", value: String(status.rateLimitHits) },
5196
+ { label: "consecutive failures", value: String(status.consecutiveFailures) },
5197
+ {
5198
+ label: "cooldown",
5199
+ value: status.stateExpiresAt ? new Date(status.stateExpiresAt).toLocaleString() : "none"
5200
+ },
5201
+ { label: "last error kind", value: status.lastErrorKind ?? "none" }
5202
+ ],
5203
+ body: status.lastErrorMessage ?? "No recorded error.",
5204
+ actions: [
5205
+ ...status.state !== "healthy" ? [
5206
+ {
5207
+ key: "r",
5208
+ label: "retry now",
5209
+ command: `/provider-status retry ${status.providerId} ${status.model}`
5210
+ }
5211
+ ] : [],
5212
+ {
5213
+ key: "x",
5214
+ label: "clear history",
5215
+ command: `/provider-status clear ${status.providerId} ${status.model}`,
5216
+ confirm: true
5217
+ }
5218
+ ]
5219
+ })) ?? [];
5220
+ return {
5221
+ id: "provider-status",
5222
+ title: "Provider health",
5223
+ subtitle: snapshot ? `${snapshot.healthy} healthy \xB7 ${snapshot.degraded} degraded \xB7 ${snapshot.blocked} blocked` : "tracker unavailable",
5224
+ emptyText: "No provider/model activity has been recorded.",
5225
+ items
5226
+ };
5227
+ }
5228
+ async function memoryMenu(store) {
5229
+ if (!store)
5230
+ return {
5231
+ id: "memory",
5232
+ title: "Memory",
5233
+ emptyText: "Memory is disabled in this host.",
5234
+ items: []
5235
+ };
5236
+ const [health, entries] = await Promise.all([store.health(), store.list(void 0, 100)]);
5237
+ return {
5238
+ id: "memory",
5239
+ title: "Memory",
5240
+ subtitle: `${health.status} \xB7 ${health.backend} \xB7 newest 100`,
5241
+ emptyText: "No memories found.",
5242
+ items: entries.map((entry, index) => ({
5243
+ id: `${entry.ts}:${index}`,
5244
+ label: truncate(entry.text.replace(/\s+/g, " "), 52),
5245
+ status: entry.priority === "critical" || entry.priority === "high" ? "warn" : "muted",
5246
+ summary: `${entry.scope} \xB7 ${entry.type ?? "fact"}`,
5247
+ details: [
5248
+ { label: "scope", value: entry.scope },
5249
+ { label: "type", value: entry.type ?? "fact" },
5250
+ { label: "priority", value: entry.priority ?? "medium" },
5251
+ {
5252
+ label: "confidence",
5253
+ value: entry.confidence === void 0 ? "unspecified" : entry.confidence.toFixed(2)
5254
+ },
5255
+ { label: "created", value: entry.ts },
5256
+ { label: "last accessed", value: entry.lastAccessed ?? "never" },
5257
+ { label: "tags", value: entry.tags?.join(", ") || "none" },
5258
+ { label: "source", value: entry.source ?? "unknown" }
5259
+ ],
5260
+ body: entry.text,
5261
+ actions: entry.scope === "project-memory" ? [
5262
+ {
5263
+ key: "x",
5264
+ label: "forget matching text",
5265
+ command: `/memory forget ${entry.text.replace(/\s+/g, " ").trim()} --exact`,
5266
+ confirm: true
5267
+ }
5268
+ ] : []
5269
+ }))
5270
+ };
5271
+ }
5272
+ async function gitMenu(projectRoot) {
5273
+ const [branch, head, status] = await Promise.all([
5274
+ runGit(["branch", "--show-current"], projectRoot),
5275
+ runGit(["rev-parse", "--short", "HEAD"], projectRoot),
5276
+ runGit(["status", "--porcelain=v1"], projectRoot)
5277
+ ]);
5278
+ if (status.code !== 0)
5279
+ return { id: "git", title: "Git", emptyText: "Not a git repository.", items: [] };
5280
+ const lines = status.stdout.split(/\r?\n/).filter(Boolean);
5281
+ const items = [
5282
+ {
5283
+ id: "repo",
5284
+ label: branch.stdout.trim() || "(detached)",
5285
+ status: lines.length === 0 ? "good" : "warn",
5286
+ summary: `${head.stdout.trim() || "no HEAD"} \xB7 ${lines.length === 0 ? "clean" : `${lines.length} changes`}`,
5287
+ details: [
5288
+ { label: "branch", value: branch.stdout.trim() || "(detached)" },
5289
+ { label: "HEAD", value: head.stdout.trim() || "(unborn)" },
5290
+ {
5291
+ label: "working tree",
5292
+ value: lines.length === 0 ? "clean" : `${lines.length} changed paths`
5293
+ }
5294
+ ],
5295
+ actions: [{ key: "c", label: "commit workflow", command: "/commit", confirm: true }]
5296
+ },
5297
+ ...lines.map((line, index) => {
5298
+ const code = line.slice(0, 2);
5299
+ const path14 = line.slice(3);
5300
+ const staged = code[0] !== " " && code[0] !== "?";
5301
+ const unstaged = code[1] !== " " || code === "??";
5302
+ return {
5303
+ id: `change:${index}:${path14}`,
5304
+ label: path14,
5305
+ status: staged ? "good" : "warn",
5306
+ summary: `${code} \xB7 ${staged ? "staged" : "not staged"}${unstaged ? " \xB7 working tree" : ""}`,
5307
+ details: [
5308
+ { label: "status", value: code },
5309
+ { label: "staged", value: staged ? "yes" : "no" },
5310
+ { label: "unstaged", value: unstaged ? "yes" : "no" },
5311
+ { label: "path", value: path14 }
5312
+ ],
5313
+ actions: [
5314
+ { key: "d", label: "diff summary", command: staged ? "/git diff --staged" : "/git diff" }
5315
+ ]
5316
+ };
5317
+ })
5318
+ ];
5319
+ return { id: "git", title: "Git", subtitle: projectRoot, items };
5320
+ }
5321
+ async function worktreeMenu(projectRoot) {
5322
+ const result = await runGit(["worktree", "list", "--porcelain"], projectRoot);
5323
+ if (result.code !== 0) {
5324
+ return {
5325
+ id: "worktree",
5326
+ title: "Worktrees",
5327
+ emptyText: result.stderr || "Not a git repository.",
5328
+ items: []
5329
+ };
5330
+ }
5331
+ const records = result.stdout.trim().split(/\r?\n\r?\n/).map((block) => block.split(/\r?\n/).filter(Boolean)).filter((lines) => lines.length > 0);
5332
+ const rows = await Promise.all(
5333
+ records.map(async (lines, index) => {
5334
+ const fields = Object.fromEntries(
5335
+ lines.map((line) => {
5336
+ const split = line.indexOf(" ");
5337
+ return split < 0 ? [line, "yes"] : [line.slice(0, split), line.slice(split + 1)];
5338
+ })
5339
+ );
5340
+ const path14 = fields["worktree"] ?? "(unknown path)";
5341
+ const branchRef = fields["branch"];
5342
+ const branch = branchRef?.replace(/^refs\/heads\//, "") ?? (fields["detached"] ? "(detached)" : "(bare)");
5343
+ const status = path14 === "(unknown path)" || fields["prunable"] ? null : await runGit(["status", "--porcelain=v1"], path14);
5344
+ const changes = status?.code === 0 ? status.stdout.split(/\r?\n/).filter(Boolean) : [];
5345
+ const dirty = changes.length > 0;
5346
+ const isMain = index === 0;
5347
+ return {
5348
+ id: path14,
5349
+ label: branch,
5350
+ status: fields["prunable"] ? "bad" : dirty ? "warn" : "good",
5351
+ summary: `${isMain ? "main" : "linked"} \xB7 ${dirty ? `${changes.length} changes` : "clean"}`,
5352
+ details: [
5353
+ { label: "path", value: path14 },
5354
+ { label: "branch", value: branch },
5355
+ { label: "HEAD", value: fields["HEAD"]?.slice(0, 12) ?? "unknown" },
5356
+ { label: "working tree", value: dirty ? `${changes.length} changed paths` : "clean" },
5357
+ { label: "locked", value: fields["locked"] ?? "no" },
5358
+ { label: "prunable", value: fields["prunable"] ?? "no" }
5359
+ ],
5360
+ body: changes.slice(0, 30).join("\n") || (isMain ? "Primary checkout." : "Linked checkout."),
5361
+ actions: isMain ? [
5362
+ { key: "p", label: "prune stale metadata", command: "/worktree prune" },
5363
+ {
5364
+ key: "x",
5365
+ label: "clean managed worktrees",
5366
+ command: "/worktree clean --yes",
5367
+ confirm: true
5368
+ }
5369
+ ] : branchRef ? [
5370
+ {
5371
+ key: "m",
5372
+ label: "squash merge",
5373
+ command: `/worktree merge ${branch} --yes`,
5374
+ confirm: true
5375
+ }
5376
+ ] : []
5377
+ };
5378
+ })
5379
+ );
5380
+ return {
5381
+ id: "worktree",
5382
+ title: "Worktrees",
5383
+ subtitle: `${rows.length} checkout${rows.length === 1 ? "" : "s"}`,
5384
+ emptyText: "No worktrees found.",
5385
+ items: rows
5386
+ };
5387
+ }
5388
+ function isRecord(value) {
5389
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
5390
+ }
5391
+ function truncate(value, max) {
5392
+ return value.length <= max ? value : `${value.slice(0, max - 1)}\u2026`;
5393
+ }
5394
+
5027
5395
  // src/execution.ts
5028
5396
  async function execute(deps) {
5029
5397
  const {
@@ -5306,6 +5674,14 @@ async function execute(deps) {
5306
5674
  agent,
5307
5675
  events,
5308
5676
  slashRegistry,
5677
+ skillLoader,
5678
+ getResourceMenu: createTuiResourceMenuGetter({
5679
+ configStore,
5680
+ paths: wpaths,
5681
+ memoryStore,
5682
+ statusTracker,
5683
+ projectRoot
5684
+ }),
5309
5685
  secretInputController,
5310
5686
  attachments,
5311
5687
  tokenCounter,
@@ -5622,4 +5998,4 @@ export {
5622
5998
  execute,
5623
5999
  resolveReviewerFallbackModels
5624
6000
  };
5625
- //# sourceMappingURL=execution-TTE7R5UY.js.map
6001
+ //# sourceMappingURL=execution-4IG5XKOL.js.map
package/dist/index.js CHANGED
@@ -42,11 +42,11 @@ import {
42
42
  parseArgs,
43
43
  runPicker,
44
44
  saveToGlobalConfig
45
- } from "./chunk-5JW3XCRA.js";
45
+ } from "./chunk-KXN5HZDL.js";
46
46
  import {
47
47
  isKeylessLocalProvider,
48
48
  visibleModelIds
49
- } from "./chunk-3IC4IEZC.js";
49
+ } from "./chunk-VUVOMXWP.js";
50
50
  import {
51
51
  mutateConfigProviders,
52
52
  normalizeKeys,
@@ -718,7 +718,7 @@ async function launchDesktop(args) {
718
718
  var loaders = {
719
719
  acp: async () => (await import("./acp-5ZLGFHWP.js")).acpCmd,
720
720
  init: async () => (await import("./init-E2NDDOHI.js")).initCmd,
721
- auth: async () => (await import("./auth-GVPYLJPS.js")).authCmd,
721
+ auth: async () => (await import("./auth-L3WGSGDX.js")).authCmd,
722
722
  update: async () => (await import("./update-VZSOZGYC.js")).updateCmd,
723
723
  sessions: async () => (await import("./sessions-config-YX2ZPAFM.js")).sessionsCmd,
724
724
  config: async () => (await import("./sessions-config-YX2ZPAFM.js")).configCmd,
@@ -727,8 +727,8 @@ var loaders = {
727
727
  audit: async () => (await import("./audit-BBR22QH3.js")).auditCmd,
728
728
  tools: async () => (await import("./tools-skills-UNKLOB7M.js")).toolsCmd,
729
729
  skills: async () => (await import("./tools-skills-UNKLOB7M.js")).skillsCmd,
730
- providers: async () => (await import("./providers-models-VV74TRQ2.js")).providersCmd,
731
- models: async () => (await import("./providers-models-VV74TRQ2.js")).modelsCmd,
730
+ providers: async () => (await import("./providers-models-HQN3AT3K.js")).providersCmd,
731
+ models: async () => (await import("./providers-models-HQN3AT3K.js")).modelsCmd,
732
732
  mcp: async () => (await import("./mcp-MSARYOPN.js")).mcpCmd,
733
733
  plugin: async () => (await import("./plugin-usage-PTEETL3J.js")).pluginCmd,
734
734
  plugins: async () => (await import("./plugin-usage-PTEETL3J.js")).pluginCmd,
@@ -3302,7 +3302,7 @@ async function initializeCli(argv) {
3302
3302
  async function main(argv) {
3303
3303
  const cliCtx = await initializeCli(argv);
3304
3304
  if (typeof cliCtx === "number") return cliCtx;
3305
- const { runInteractive } = await import("./cli-main-DHNFGP3Z.js");
3305
+ const { runInteractive } = await import("./cli-main-3PUWFKSL.js");
3306
3306
  return runInteractive(cliCtx);
3307
3307
  }
3308
3308
 
@@ -49,11 +49,7 @@ export declare function hasApiKey(provider: ResolvedProvider, config?: Config):
49
49
  * Models are inlined from the catalog (or from `cfg.models` for custom
50
50
  * entries) so the picker can show a real selection.
51
51
  */
52
- export declare function buildPickableProviders(modelsRegistry: ModelsRegistry, config: Config): Promise<Array<{
53
- id: string;
54
- family: string;
55
- models: string[];
56
- }>>;
52
+ export declare function buildPickableProviders(modelsRegistry: ModelsRegistry, config: Config): Promise<Array<import('@wrongstack/tui').ProviderOption>>;
57
53
  /**
58
54
  * Resolve a provider id that may be an alias. When the user has
59
55
  * `providers[id].type` pointing at a different catalog entry, return
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  visibleModelIds
3
- } from "./chunk-3IC4IEZC.js";
3
+ } from "./chunk-VUVOMXWP.js";
4
4
  import {
5
5
  mutateConfigProviders
6
6
  } from "./chunk-SZ42FYPT.js";
@@ -661,4 +661,4 @@ export {
661
661
  modelsCmd,
662
662
  providersCmd
663
663
  };
664
- //# sourceMappingURL=providers-models-VV74TRQ2.js.map
664
+ //# sourceMappingURL=providers-models-HQN3AT3K.js.map
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Run a `git` subcommand and capture its output.
3
+ *
4
+ * Lives in `services/` rather than beside the `/git` slash command because it
5
+ * is shared infrastructure, not command logic: it has no `SlashCommandContext`
6
+ * and no rendering. The TUI resource menus need the same child process, and a
7
+ * non-command module importing `slash-commands/git.ts` is exactly the boundary
8
+ * the architecture check rejects.
9
+ *
10
+ * Never rejects on a non-zero exit — the caller inspects `code`. It rejects
11
+ * only when git could not be spawned at all, or when the 30s timeout fires.
12
+ */
13
+ export declare function runGit(args: string[], cwd: string): Promise<{
14
+ stdout: string;
15
+ stderr: string;
16
+ code: number;
17
+ }>;
18
+ //# sourceMappingURL=run-git.d.ts.map
@@ -1,10 +1,5 @@
1
1
  import type { SlashCommand } from '@wrongstack/core/types';
2
2
  import type { SlashCommandContext } from './command-context.js';
3
- export declare function runGit(args: string[], cwd: string): Promise<{
4
- stdout: string;
5
- stderr: string;
6
- code: number;
7
- }>;
8
3
  /**
9
4
  * Read-only operational Git command. Mutating workflows remain on `/commit`,
10
5
  * `/push`, and the permission-gated `git` tool.
@@ -1,5 +1,37 @@
1
1
  import type { InputReader, SlashCommand } from '@wrongstack/core/types';
2
2
  import type { SlashCommandContext } from './command-context.js';
3
+ /**
4
+ * Windowed slice of the theme list for the raw-terminal picker — mirrors the
5
+ * math in the TUI's `useWindowedPicker` (same centering + marker rules) so
6
+ * both pickers agree on how many presets fit and which rows are visible.
7
+ * `rows` is the raw terminal height; the picker reserves its chrome (header,
8
+ * hint, blanks) plus worst-case marker rows up front, so a 24-row terminal
9
+ * never renders all 40 presets.
10
+ *
11
+ * Exported for direct unit testing (packages/cli/tests/slash-theme.test.ts);
12
+ * the picker itself treats it as private.
13
+ */
14
+ export declare function computeWindow(total: number, selected: number, rows: number): {
15
+ start: number;
16
+ end: number;
17
+ hasAbove: boolean;
18
+ hasBelow: boolean;
19
+ };
20
+ /**
21
+ * Truncate an option's description so the rendered row never wraps on a
22
+ * narrow terminal. `computeWindow` budgets ONE terminal row per option; a
23
+ * description that wrapped onto a second physical line would silently break
24
+ * that budget and re-introduce the vertical overflow this picker was fixed
25
+ * to avoid. The description is plain text here (color-wrapped by the caller
26
+ * AFTER truncation), so measuring its length is ANSI-safe.
27
+ *
28
+ * Fixed chrome per row: 2 indent + 2 cursor + 21 name + 1 gap = 26 columns,
29
+ * plus the ` [active]` mark (9 columns) when the option is the active preset.
30
+ *
31
+ * Exported for direct unit testing (packages/cli/tests/slash-theme.test.ts);
32
+ * the picker itself treats it as private.
33
+ */
34
+ export declare function truncateDesc(desc: string, columns: number, active: boolean): string;
3
35
  export declare function buildThemeCommand(opts: SlashCommandContext & {
4
36
  inputReader?: InputReader | undefined;
5
37
  }): SlashCommand;
@@ -0,0 +1,13 @@
1
+ import type { ProviderModelStatusTracker } from '@wrongstack/core/coordination';
2
+ import type { ConfigStore, MemoryPort } from '@wrongstack/core/types';
3
+ import type { WstackPaths } from '@wrongstack/core/utils';
4
+ import type { ResourceMenuId, ResourceMenuSnapshot } from '@wrongstack/tui';
5
+ export interface TuiResourceMenuContext {
6
+ configStore: ConfigStore;
7
+ paths: WstackPaths;
8
+ memoryStore?: MemoryPort | undefined;
9
+ statusTracker?: ProviderModelStatusTracker | undefined;
10
+ projectRoot: string;
11
+ }
12
+ export declare function createTuiResourceMenuGetter(ctx: TuiResourceMenuContext): (id: ResourceMenuId) => Promise<ResourceMenuSnapshot>;
13
+ //# sourceMappingURL=tui-resource-menus.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/cli",
3
- "version": "0.306.0",
3
+ "version": "0.306.2",
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,31 +42,31 @@
42
42
  ],
43
43
  "dependencies": {
44
44
  "ws": "^8.21.1",
45
- "@wrongstack/bench": "0.306.0",
46
- "@wrongstack/acp": "0.306.0",
47
- "@wrongstack/mcp": "0.306.0",
48
- "@wrongstack/core": "0.306.0",
49
- "@wrongstack/plugins": "0.306.0",
50
- "@wrongstack/providers": "0.306.0",
51
- "@wrongstack/persistence": "0.306.0",
52
- "@wrongstack/plug-lsp": "0.306.0",
53
- "@wrongstack/requirement-intake": "0.306.0",
54
- "@wrongstack/kanban": "0.306.0",
55
- "@wrongstack/runtime": "0.306.0",
56
- "@wrongstack/simpleui": "0.306.0",
57
- "@wrongstack/sage": "0.306.0",
58
- "@wrongstack/security-scanner": "0.306.0",
59
- "@wrongstack/tools": "0.306.0",
60
- "@wrongstack/sdd": "0.306.0",
61
- "@wrongstack/webui": "0.306.0",
62
- "@wrongstack/techstack": "0.306.0",
63
- "@wrongstack/webui-server": "0.306.0",
64
- "@wrongstack/telegram": "0.306.0",
65
- "@wrongstack/tui": "0.306.0",
66
- "@wrongstack/webui-hq": "0.306.0"
45
+ "@wrongstack/acp": "0.306.2",
46
+ "@wrongstack/mcp": "0.306.2",
47
+ "@wrongstack/kanban": "0.306.2",
48
+ "@wrongstack/core": "0.306.2",
49
+ "@wrongstack/providers": "0.306.2",
50
+ "@wrongstack/bench": "0.306.2",
51
+ "@wrongstack/plug-lsp": "0.306.2",
52
+ "@wrongstack/persistence": "0.306.2",
53
+ "@wrongstack/requirement-intake": "0.306.2",
54
+ "@wrongstack/plugins": "0.306.2",
55
+ "@wrongstack/sdd": "0.306.2",
56
+ "@wrongstack/security-scanner": "0.306.2",
57
+ "@wrongstack/sage": "0.306.2",
58
+ "@wrongstack/runtime": "0.306.2",
59
+ "@wrongstack/telegram": "0.306.2",
60
+ "@wrongstack/simpleui": "0.306.2",
61
+ "@wrongstack/techstack": "0.306.2",
62
+ "@wrongstack/webui-hq": "0.306.2",
63
+ "@wrongstack/webui": "0.306.2",
64
+ "@wrongstack/tui": "0.306.2",
65
+ "@wrongstack/webui-server": "0.306.2",
66
+ "@wrongstack/tools": "0.306.2"
67
67
  },
68
68
  "optionalDependencies": {
69
- "@wrongstack/desktop": "0.306.0"
69
+ "@wrongstack/desktop": "0.306.2"
70
70
  },
71
71
  "devDependencies": {
72
72
  "@types/node": "^26.1.2",