@stupify/cli 0.0.9 → 0.0.10

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/analysis.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { cachedJson, fingerprint } from "./cache.js";
2
2
  import { searchPrompt } from "./prompts.js";
3
+ import { diagnostic, diagnosticError } from "./ui.js";
3
4
  export async function runSearch(model, request) {
4
5
  const raw = await runJsonPrompt(model, request.prompt, request.schema, 0);
5
6
  return uncheckedSearchMatches(raw, request.contexts);
@@ -100,8 +101,8 @@ Your previous response was not valid JSON. Return the requested JSON object only
100
101
  const retryParsed = parseJson(retry);
101
102
  if (retryParsed.ok)
102
103
  return retryParsed.value;
103
- console.error("Raw model output:");
104
- console.error(retry);
104
+ diagnosticError("Raw model output:");
105
+ diagnostic(retry);
105
106
  throw new Error("Model returned invalid JSON.");
106
107
  }
107
108
  async function complete(model, prompt, schema, temperature) {
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.0.9";
1
+ export declare const VERSION = "0.0.10";
2
2
  import type { ModelConfig, ModelId } from "./types.ts";
3
3
  export declare const DEFAULT_MODEL_ID: ModelId;
4
4
  export declare const MODEL_REGISTRY: Record<ModelId, ModelConfig>;
package/dist/constants.js CHANGED
@@ -1,4 +1,4 @@
1
- export const VERSION = "0.0.9";
1
+ export const VERSION = "0.0.10";
2
2
  export const DEFAULT_MODEL_ID = "gemma-4-e2b";
3
3
  export const MODEL_REGISTRY = {
4
4
  "gemma-4-e2b": {
package/dist/model.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { ModelId } from "./types.ts";
2
+ import type { CliUi } from "./ui.ts";
2
3
  export type ModelProfile = "scout";
3
4
  export type LocalModel = Readonly<{
4
5
  id: ModelId;
@@ -6,5 +7,5 @@ export type LocalModel = Readonly<{
6
7
  baseUrl: string;
7
8
  profile: ModelProfile;
8
9
  }>;
9
- export declare function firstRunModelBootstrap(modelId: ModelId): Promise<string>;
10
- export declare function loadLocalModel(modelPath: string, modelId: ModelId, profile?: ModelProfile): Promise<LocalModel>;
10
+ export declare function firstRunModelBootstrap(modelId: ModelId, ui: CliUi): Promise<string>;
11
+ export declare function loadLocalModel(modelPath: string, modelId: ModelId, profile: ModelProfile, ui: CliUi): Promise<LocalModel>;
package/dist/model.js CHANGED
@@ -1,42 +1,39 @@
1
1
  import { execFile, spawn } from "node:child_process";
2
- import { createReadStream, createWriteStream } from "node:fs";
3
2
  import { mkdir, open, readFile, rename, rm, stat, writeFile, } from "node:fs/promises";
4
3
  import { homedir, platform } from "node:os";
5
- import { stdin as input, stderr as statusOutput, stdout as output, } from "node:process";
6
- import { createInterface } from "node:readline/promises";
7
4
  import path from "node:path";
8
5
  import { promisify } from "node:util";
9
6
  import { MODEL_REGISTRY } from "./constants.js";
10
7
  const execFileAsync = promisify(execFile);
11
8
  const LLAMA_SERVER_HOST = "127.0.0.1";
12
- export async function firstRunModelBootstrap(modelId) {
9
+ export async function firstRunModelBootstrap(modelId, ui) {
13
10
  const selectedModel = MODEL_REGISTRY[modelId];
14
11
  const modelDir = path.join(cacheDir(), "models");
15
12
  const modelPath = path.join(modelDir, selectedModel.file);
16
13
  if (await exists(modelPath))
17
14
  return modelPath;
18
- console.error(`No local Stupify model found.
15
+ ui.note(`No local Stupify model found.
19
16
  Stupify runs locally.
20
17
  Download this model now?
21
18
  Model: ${selectedModel.name}
22
- Size: ${selectedModel.size}`);
23
- if (!(await confirm("Continue? y/N ")))
19
+ Size: ${selectedModel.size}`, "Setup", { force: true });
20
+ if (!(await ui.confirm("Continue?")))
24
21
  throw new Error("Setup cancelled.");
25
22
  await mkdir(modelDir, { recursive: true });
26
- await downloadModel(modelPath, selectedModel.url);
23
+ await downloadModel(modelPath, selectedModel.url, ui);
27
24
  if (!(await exists(modelPath)))
28
25
  throw new Error("Model download failed: file was not created.");
29
26
  return modelPath;
30
27
  }
31
- export async function loadLocalModel(modelPath, modelId, profile = "scout") {
28
+ export async function loadLocalModel(modelPath, modelId, profile, ui) {
32
29
  const selectedModel = MODEL_REGISTRY[modelId];
33
30
  const runtime = modelRuntime(profile);
34
31
  const runningModel = await runningServerModel(runtime.baseUrl);
35
32
  if (runningModel) {
36
33
  if (runningModel !== modelId)
37
- await stopManagedServer(runtime);
34
+ await stopManagedServer(runtime, ui);
38
35
  if (runningModel === modelId) {
39
- console.error(`Using local model: ${selectedModel.name}`);
36
+ ui.info(`Using local model: ${selectedModel.name}`);
40
37
  return {
41
38
  id: modelId,
42
39
  name: selectedModel.name,
@@ -46,8 +43,16 @@ export async function loadLocalModel(modelPath, modelId, profile = "scout") {
46
43
  }
47
44
  }
48
45
  await ensureLlamaServerBinary();
49
- await startLlamaServer(modelPath, modelId, selectedModel.name, runtime);
50
- await waitForServer(runtime.baseUrl, modelId);
46
+ await startLlamaServer(modelPath, modelId, selectedModel.name, runtime, ui);
47
+ const ready = ui.spinner(`Waiting for local ${profile} model`);
48
+ try {
49
+ await waitForServer(runtime.baseUrl, modelId);
50
+ ready.stop(`Local ${profile} model ready`);
51
+ }
52
+ catch (error) {
53
+ ready.error(`Local ${profile} model failed to start`);
54
+ throw error;
55
+ }
51
56
  return {
52
57
  id: modelId,
53
58
  name: selectedModel.name,
@@ -101,14 +106,14 @@ Install llama.cpp first:
101
106
  brew install llama.cpp`);
102
107
  }
103
108
  }
104
- async function startLlamaServer(modelPath, modelId, modelName, runtime) {
109
+ async function startLlamaServer(modelPath, modelId, modelName, runtime, ui) {
105
110
  const logDir = path.join(cacheDir(), "logs");
106
111
  await mkdir(logDir, { recursive: true });
107
112
  const logPath = path.join(logDir, "llama-server.log");
108
113
  const out = await open(logPath, "a");
109
114
  const err = await open(logPath, "a");
110
- console.error(`Starting local model server: ${modelName}`);
111
- console.error(`llama-server log: ${logPath}`);
115
+ ui.step(`Starting local model server: ${modelName}`);
116
+ ui.info(`llama-server log: ${logPath}`);
112
117
  const args = [
113
118
  "-m",
114
119
  modelPath,
@@ -151,14 +156,14 @@ async function startLlamaServer(modelPath, modelId, modelName, runtime) {
151
156
  await out.close();
152
157
  await err.close();
153
158
  }
154
- async function stopManagedServer(runtime) {
159
+ async function stopManagedServer(runtime, ui) {
155
160
  const pid = await managedServerPid(runtime);
156
161
  if (!pid) {
157
162
  const runningModel = await runningServerModel(runtime.baseUrl);
158
163
  throw new Error(`A llama-server is already running with ${runningModel ?? "another model"}.
159
164
  Stop it before switching models, or use STUPIFY_LLAMA_SERVER_URL for that server.`);
160
165
  }
161
- console.error("Restarting local model server for selected model.");
166
+ ui.step("Restarting local model server for selected model.");
162
167
  try {
163
168
  process.kill(pid, "SIGTERM");
164
169
  }
@@ -214,10 +219,11 @@ async function waitForServer(baseUrl, modelId) {
214
219
  function sleep(ms) {
215
220
  return new Promise((resolve) => setTimeout(resolve, ms));
216
221
  }
217
- async function downloadModel(modelPath, modelUrl) {
222
+ async function downloadModel(modelPath, modelUrl, ui) {
218
223
  const tempPath = `${modelPath}.download`;
219
224
  await rm(tempPath, { force: true });
220
- console.error("Downloading model...");
225
+ const downloadSpinner = ui.spinner("Downloading model");
226
+ let downloadProgress = null;
221
227
  try {
222
228
  const response = await fetch(modelUrl);
223
229
  if (!response.ok || !response.body)
@@ -225,8 +231,13 @@ async function downloadModel(modelPath, modelUrl) {
225
231
  const total = Number(response.headers.get("content-length") ?? 0);
226
232
  let received = 0;
227
233
  let lastPrint = 0;
234
+ let lastProgressBytes = 0;
228
235
  const reader = response.body.getReader();
229
236
  const file = await open(tempPath, "wx");
237
+ if (total > 0) {
238
+ downloadSpinner.clear();
239
+ downloadProgress = ui.progress("Downloading model", total);
240
+ }
230
241
  try {
231
242
  while (true) {
232
243
  const { done, value } = await reader.read();
@@ -237,42 +248,30 @@ async function downloadModel(modelPath, modelUrl) {
237
248
  const now = Date.now();
238
249
  if (total > 0 && now - lastPrint > 500) {
239
250
  lastPrint = now;
240
- statusOutput.write(`\r${formatBytes(received)} / ${formatBytes(total)}`);
251
+ downloadProgress?.advance(received - lastProgressBytes, `${formatBytes(received)} / ${formatBytes(total)}`);
252
+ lastProgressBytes = received;
241
253
  }
242
254
  }
243
255
  }
244
256
  finally {
245
257
  await file.close();
246
258
  }
247
- if (total > 0)
248
- statusOutput.write(`\r${formatBytes(received)} / ${formatBytes(total)}\n`);
259
+ if (downloadProgress && received > lastProgressBytes) {
260
+ downloadProgress.advance(received - lastProgressBytes, `${formatBytes(received)} / ${formatBytes(total)}`);
261
+ }
262
+ const activeProgress = downloadProgress ?? downloadSpinner;
263
+ activeProgress.stop(total > 0
264
+ ? `Downloaded ${formatBytes(received)} / ${formatBytes(total)}`
265
+ : "Downloaded model");
249
266
  await rename(tempPath, modelPath);
250
267
  }
251
268
  catch (error) {
269
+ const activeProgress = downloadProgress ?? downloadSpinner;
270
+ activeProgress.error("Model download failed");
252
271
  await rm(tempPath, { force: true });
253
272
  throw error;
254
273
  }
255
274
  }
256
- async function confirm(question) {
257
- const rl = createInterface(terminalIo());
258
- try {
259
- const answer = (await rl.question(question)).trim().toLowerCase();
260
- return answer === "y" || answer === "yes";
261
- }
262
- finally {
263
- rl.close();
264
- }
265
- }
266
- function terminalIo() {
267
- if (input.isTTY)
268
- return { input, output };
269
- if (platform() !== "win32")
270
- return {
271
- input: createReadStream("/dev/tty"),
272
- output: createWriteStream("/dev/tty"),
273
- };
274
- throw new Error("No local Stupify model found. Run `stupify` once in an interactive terminal to set up the model.");
275
- }
276
275
  function cacheDir() {
277
276
  if (process.env.STUPIFY_CACHE_DIR)
278
277
  return process.env.STUPIFY_CACHE_DIR;
package/dist/render.js CHANGED
@@ -1,40 +1,37 @@
1
1
  import { VERSION } from "./constants.js";
2
+ import { format } from "./ui.js";
2
3
  export function renderSearchRun(run, command) {
3
4
  if (command.json)
4
5
  return JSON.stringify(run, null, 2);
5
6
  if (run.stats.skipped && run.stats.skipReason === "input_too_large") {
6
- return `🧙 stupify 🪄
7
- Search input is too large for precise local search.
8
- Size:
7
+ return `${format.heading("Search input is too large for precise local search.")}
8
+ ${format.heading("Size:")}
9
9
  ~${run.stats.inputTokens ?? "unknown"} tokens
10
- Limit:
10
+ ${format.heading("Limit:")}
11
11
  ${run.stats.inputTokenCap ?? "unknown"} tokens
12
12
  Stupify skipped the search rather than review truncated context.
13
13
  Nothing was blocked.
14
- Try:
14
+ ${format.heading("Try:")}
15
15
  rerun with ${sourceHint(command)} --max-search-input-tokens ${Math.max((run.stats.inputTokens ?? 12_000) + 1, (run.stats.inputTokenCap ?? 12_000) * 2)}`;
16
16
  }
17
17
  if (run.stats.skipped && run.stats.skipReason === "no_candidates") {
18
- return `🧙 stupify 🪄
19
- Search complete.
20
- Patterns: ${run.patterns.join(", ")}
21
- No search targets found.`;
18
+ return `${format.heading("Search complete.")}
19
+ ${format.label("Patterns:")} ${run.patterns.join(", ")}
20
+ ${format.success("No search targets found.")}`;
22
21
  }
23
22
  if (run.matches.length === 0) {
24
- return `🧙 stupify 🪄
25
- Search complete.
26
- Patterns: ${run.patterns.join(", ")}
27
- No judgment-offload signals found.`;
23
+ return `${format.heading("Search complete.")}
24
+ ${format.label("Patterns:")} ${run.patterns.join(", ")}
25
+ ${format.success("No judgment-offload signals found.")}`;
28
26
  }
29
- return `🧙 stupify 🪄
30
- AI SLOP DETECTED
27
+ return `${format.warn("AI SLOP DETECTED")}
31
28
  ${run.matches.map((match, index) => `${index + 1}.
32
- who: ${committerLabel(run)}
33
- what: ${match.patternId} - ${match.reason}
34
- when: ${sourceLabel(command)}
35
- where: ${match.proof}
36
- why: ${match.checkWhy ?? "This pattern may indicate judgment-offload."}`).join("\n")}
37
- Search mode is warn-only.`;
29
+ ${format.muted("who:")} ${committerLabel(run)}
30
+ ${format.muted("what:")} ${match.patternId} - ${match.reason}
31
+ ${format.muted("when:")} ${sourceLabel(command)}
32
+ ${format.muted("where:")} ${match.proof}
33
+ ${format.muted("why:")} ${match.checkWhy ?? "This pattern may indicate judgment-offload."}`).join("\n")}
34
+ ${format.muted("Search mode is warn-only.")}`;
38
35
  }
39
36
  export function helpText() {
40
37
  return `Stupify ${VERSION}
@@ -7,6 +7,7 @@ import { promisify } from "node:util";
7
7
  import { cachedJson, fingerprint } from "./cache.js";
8
8
  import { readDiffFromStdin } from "./diff.js";
9
9
  import { gitUserLabel, sourceRangeForCommit, sourceRangeForRecentCommits, sourceRangeSince, stagedDiff, } from "./git.js";
10
+ import { diagnostic } from "./ui.js";
10
11
  import { sourceId } from "./types.js";
11
12
  const execFileAsync = promisify(execFile);
12
13
  export async function semChangeSetForCommand(command) {
@@ -108,7 +109,7 @@ async function withContextWorkspace(changeSet, debugSem) {
108
109
  }
109
110
  async function runSem(args, debugSem, cwd = process.cwd()) {
110
111
  if (debugSem)
111
- console.error(`sem ${args.join(" ")}`);
112
+ diagnostic(`sem ${args.join(" ")}`);
112
113
  const { command, commandArgs } = resolveSemCommand(args);
113
114
  try {
114
115
  const { stdout, stderr } = await execFileAsync(command, commandArgs, {
@@ -116,7 +117,7 @@ async function runSem(args, debugSem, cwd = process.cwd()) {
116
117
  maxBuffer: 128 * 1024 * 1024,
117
118
  });
118
119
  if (debugSem && stderr.trim())
119
- console.error(stderr.trim());
120
+ diagnostic(stderr.trim());
120
121
  return JSON.parse(stdout);
121
122
  }
122
123
  catch (error) {
@@ -126,7 +127,7 @@ async function runSem(args, debugSem, cwd = process.cwd()) {
126
127
  }
127
128
  async function runSemWithInput(args, stdin, debugSem) {
128
129
  if (debugSem)
129
- console.error(`sem ${args.join(" ")}`);
130
+ diagnostic(`sem ${args.join(" ")}`);
130
131
  const { command, commandArgs } = resolveSemCommand(args);
131
132
  return new Promise((resolve, reject) => {
132
133
  const child = spawn(command, commandArgs, { stdio: ["pipe", "pipe", "pipe"] });
@@ -138,7 +139,7 @@ async function runSemWithInput(args, stdin, debugSem) {
138
139
  child.on("close", (code) => {
139
140
  const stderrText = Buffer.concat(stderr).toString("utf8");
140
141
  if (debugSem && stderrText.trim())
141
- console.error(stderrText.trim());
142
+ diagnostic(stderrText.trim());
142
143
  if (code !== 0) {
143
144
  reject(new Error(`sem failed with exit code ${code}${stderrText ? `: ${stderrText.trim()}` : ""}`));
144
145
  return;
@@ -155,7 +156,7 @@ async function runSemWithInput(args, stdin, debugSem) {
155
156
  }
156
157
  async function git(args, debugSem) {
157
158
  if (debugSem)
158
- console.error(`git ${args.join(" ")}`);
159
+ diagnostic(`git ${args.join(" ")}`);
159
160
  await execFileAsync("git", [...args], { maxBuffer: 128 * 1024 * 1024 });
160
161
  }
161
162
  async function cleanupWorktree(tempDir, worktreeAdded, debugSem) {
package/dist/stupify.d.ts CHANGED
@@ -1,4 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  import type { SearchCommand, SearchRunJson } from "./types.ts";
3
3
  export declare function main(argv?: string[]): Promise<number>;
4
- export declare function runSearchCommand(command: SearchCommand, startedAt: number): Promise<SearchRunJson>;
4
+ export declare function runSearchCommand(command: SearchCommand, startedAt: number, ui?: {
5
+ intro(title: string, logOptions?: Readonly<{
6
+ force?: boolean;
7
+ }>): void;
8
+ outro(message: string, logOptions?: Readonly<{
9
+ force?: boolean;
10
+ }>): void;
11
+ note(message: string, title?: string, logOptions?: Readonly<{
12
+ force?: boolean;
13
+ }>): void;
14
+ info(message: string, logOptions?: Readonly<{
15
+ force?: boolean;
16
+ }>): void;
17
+ step(message: string, logOptions?: Readonly<{
18
+ force?: boolean;
19
+ }>): void;
20
+ success(message: string, logOptions?: Readonly<{
21
+ force?: boolean;
22
+ }>): void;
23
+ warn(message: string, logOptions?: Readonly<{
24
+ force?: boolean;
25
+ }>): void;
26
+ error(message: string, logOptions?: Readonly<{
27
+ force?: boolean;
28
+ }>): void;
29
+ debug(message: string): void;
30
+ confirm(message: string): Promise<boolean>;
31
+ spinner(message: string, logOptions?: Readonly<{
32
+ force?: boolean;
33
+ }>): import("@clack/prompts").SpinnerResult;
34
+ progress(message: string, max: number, logOptions?: Readonly<{
35
+ force?: boolean;
36
+ }>): import("@clack/prompts").ProgressResult;
37
+ writeStdout(text: string): void;
38
+ }): Promise<SearchRunJson>;
package/dist/stupify.js CHANGED
@@ -13,44 +13,47 @@ import { helpText, renderSearchRun } from "./render.js";
13
13
  import { effectiveMaxCandidates, effectiveMaxSearchInputTokens, effectiveRepomixConfig, effectiveSearchChecks, loadSearchProfile, } from "./search-profile.js";
14
14
  import { semChangeSetForCommand } from "./sem-provider.js";
15
15
  import { createTracer } from "./trace.js";
16
+ import { createCliUi } from "./ui.js";
16
17
  export async function main(argv = process.argv.slice(2)) {
17
18
  const startedAt = Date.now();
19
+ let ui = createCliUi();
18
20
  try {
19
21
  const command = parseCommand(argv);
20
22
  if (command.kind === "help") {
21
- console.log(helpText());
23
+ ui.writeStdout(helpText());
22
24
  return 0;
23
25
  }
24
26
  if (command.kind === "hook") {
25
- console.log(await runHookCommand(command.action));
27
+ ui.writeStdout(await runHookCommand(command.action));
26
28
  return 0;
27
29
  }
28
30
  if (command.kind === "doctor") {
29
31
  const result = await runDoctor();
30
- console.log(result.text);
32
+ ui.writeStdout(result.text);
31
33
  return result.exitCode;
32
34
  }
33
35
  if (command.kind === "bench-search") {
34
36
  const { runSearchBench } = await import("./search-bench.js");
35
- console.log(await runSearchBench(command.configPath));
37
+ ui.writeStdout(await runSearchBench(command.configPath));
36
38
  return 0;
37
39
  }
38
- const run = await runSearchCommand(command, startedAt);
39
- console.log(renderSearchRun(run, command));
40
+ ui = createCliUi({ quiet: command.json });
41
+ const run = await runSearchCommand(command, startedAt, ui);
42
+ ui.writeStdout(renderSearchRun(run, command));
40
43
  return 0;
41
44
  }
42
45
  catch (error) {
43
- console.error(error instanceof Error ? error.message : String(error));
46
+ ui.error(error instanceof Error ? error.message : String(error), { force: true });
44
47
  return 1;
45
48
  }
46
49
  }
47
- export async function runSearchCommand(command, startedAt) {
50
+ export async function runSearchCommand(command, startedAt, ui = createCliUi({ quiet: command.json })) {
48
51
  const t = createTracer({
49
52
  writeLine: () => undefined,
50
53
  onEvent: (event) => {
51
54
  if (command.json)
52
55
  return;
53
- console.error(formatStep(event.name, event.ms, event.count, event.detail));
56
+ ui.step(formatStep(event.name, event.ms, event.count, event.detail));
54
57
  },
55
58
  });
56
59
  const profile = await loadSearchProfile(command.searchProfilePath);
@@ -58,7 +61,7 @@ export async function runSearchCommand(command, startedAt) {
58
61
  const patternIds = checks.map((check) => check.id);
59
62
  const maxCandidates = effectiveMaxCandidates(command.maxCandidates, profile);
60
63
  const maxSearchInputTokens = effectiveMaxSearchInputTokens(command.maxSearchInputTokens, profile);
61
- printRunPlan(command, patternIds);
64
+ printRunPlan(command, patternIds, ui);
62
65
  const { value: changeSet } = await t.trace("entity.diff", () => semChangeSetForCommand(command), {
63
66
  count: (v) => v.summary.total,
64
67
  detail: (v) => `${v.summary.fileCount} files`,
@@ -178,13 +181,13 @@ export async function runSearchCommand(command, startedAt) {
178
181
  };
179
182
  }
180
183
  if (batches.wasSplit && !command.json) {
181
- console.error(`Search input is large; queued ${batches.batches.length} smaller search batches.`);
184
+ ui.warn(`Search input is large; queued ${batches.batches.length} smaller search batches.`);
182
185
  if (batches.skippedTargets > 0) {
183
- console.error(`Skipped ${batches.skippedTargets} oversized targets that could not fit alone.`);
186
+ ui.warn(`Skipped ${batches.skippedTargets} oversized targets that could not fit alone.`);
184
187
  }
185
188
  }
186
- const modelPath = await firstRunModelBootstrap(command.model);
187
- const model = await loadLocalModel(modelPath, command.model, "scout");
189
+ const modelPath = await firstRunModelBootstrap(command.model, ui);
190
+ const model = await loadLocalModel(modelPath, command.model, "scout", ui);
188
191
  const matches = [];
189
192
  let modelCalls = 0;
190
193
  let inputTokens = 0;
@@ -195,7 +198,7 @@ export async function runSearchCommand(command, startedAt) {
195
198
  if (batchInputTokens > maxSearchInputTokens) {
196
199
  exactSkippedTargets += batch.contexts.length;
197
200
  if (!command.json) {
198
- console.error(`Skipped ${batch.contexts.length} targets after exact token count exceeded the limit.`);
201
+ ui.warn(`Skipped ${batch.contexts.length} targets after exact token count exceeded the limit.`);
199
202
  }
200
203
  continue;
201
204
  }
@@ -324,12 +327,14 @@ function buildSearchRequest(changeSet, contexts, pack, patterns, profile, includ
324
327
  includeCounterReasonInPrompt: profile?.includeCounterReasonInPrompt ?? includeCounterReasonInPrompt,
325
328
  });
326
329
  }
327
- function printRunPlan(command, patternIds) {
330
+ function printRunPlan(command, patternIds, ui) {
328
331
  if (command.json)
329
332
  return;
330
- console.error("🧙 stupify 🪄");
331
- console.error(`Search: ${sourceLabel(command)}`);
332
- console.error(`Patterns: ${patternIds.join(", ")}`);
333
+ ui.intro("stupify");
334
+ ui.note([
335
+ `Search: ${sourceLabel(command)}`,
336
+ `Patterns: ${patternIds.join(", ")}`,
337
+ ].join("\n"), "Run");
333
338
  }
334
339
  function formatStep(name, ms, count, detail) {
335
340
  if (name === "entity.diff")
package/dist/ui.d.ts ADDED
@@ -0,0 +1,34 @@
1
+ import { type SpinnerResult } from "@clack/prompts";
2
+ export type CliUi = ReturnType<typeof createCliUi>;
3
+ export type CliUiOptions = Readonly<{
4
+ quiet?: boolean;
5
+ }>;
6
+ type LogOptions = Readonly<{
7
+ force?: boolean;
8
+ }>;
9
+ export declare function createCliUi(options?: CliUiOptions): {
10
+ intro(title: string, logOptions?: LogOptions): void;
11
+ outro(message: string, logOptions?: LogOptions): void;
12
+ note(message: string, title?: string, logOptions?: LogOptions): void;
13
+ info(message: string, logOptions?: LogOptions): void;
14
+ step(message: string, logOptions?: LogOptions): void;
15
+ success(message: string, logOptions?: LogOptions): void;
16
+ warn(message: string, logOptions?: LogOptions): void;
17
+ error(message: string, logOptions?: LogOptions): void;
18
+ debug(message: string): void;
19
+ confirm(message: string): Promise<boolean>;
20
+ spinner(message: string, logOptions?: LogOptions): SpinnerResult;
21
+ progress(message: string, max: number, logOptions?: LogOptions): import("@clack/prompts").ProgressResult;
22
+ writeStdout(text: string): void;
23
+ };
24
+ export declare const format: {
25
+ heading: (value: string) => string;
26
+ label: (value: string) => string;
27
+ muted: (value: string) => string;
28
+ success: (value: string) => string;
29
+ warn: (value: string) => string;
30
+ error: (value: string) => string;
31
+ };
32
+ export declare function diagnostic(message: string): void;
33
+ export declare function diagnosticError(message: string): void;
34
+ export {};