@aexol/spectral 0.8.0 → 0.8.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.
Files changed (43) hide show
  1. package/dist/extensions/kanban-bridge.js +668 -0
  2. package/dist/extensions/spectral-vision-fallback.js +3 -2
  3. package/dist/mcp/init.js +1 -9
  4. package/dist/memory/index.js +2 -0
  5. package/dist/memory/tools/write-project-observation.js +60 -0
  6. package/dist/relay/auto-research.js +34 -0
  7. package/dist/sdk/ai/env-api-keys.js +9 -49
  8. package/dist/sdk/ai/utils/oauth/anthropic.js +1 -1
  9. package/dist/sdk/ai/utils/oauth/openai-codex.js +1 -1
  10. package/dist/sdk/coding-agent/config.js +2 -69
  11. package/dist/sdk/coding-agent/core/extensions/loader.js +2 -35
  12. package/dist/sdk/coding-agent/core/extensions/runner.js +1 -2
  13. package/dist/sdk/coding-agent/core/model-resolver-utils.js +8 -0
  14. package/dist/sdk/coding-agent/core/model-resolver.js +1 -1
  15. package/dist/sdk/coding-agent/core/resource-loader.js +1 -1
  16. package/dist/sdk/coding-agent/core/settings-manager.js +1 -170
  17. package/dist/sdk/coding-agent/core/system-prompt.js +3 -1
  18. package/dist/sdk/coding-agent/core/theme.js +202 -0
  19. package/dist/sdk/coding-agent/core/tools/bash.js +17 -18
  20. package/dist/sdk/coding-agent/core/tools/edit.js +7 -8
  21. package/dist/sdk/coding-agent/core/tools/find.js +9 -13
  22. package/dist/sdk/coding-agent/core/tools/grep.js +10 -14
  23. package/dist/sdk/coding-agent/core/tools/ls.js +9 -10
  24. package/dist/sdk/coding-agent/core/tools/read.js +15 -25
  25. package/dist/sdk/coding-agent/{modes/interactive/components/diff.js → core/tools/render-diff.js} +18 -31
  26. package/dist/sdk/coding-agent/core/tools/write.js +10 -11
  27. package/dist/sdk/coding-agent/index.js +7 -5
  28. package/dist/sdk/coding-agent/modes/index.js +0 -1
  29. package/dist/sdk/coding-agent/modes/rpc/rpc-mode.js +2 -2
  30. package/dist/sdk/coding-agent/utils/photon.js +2 -10
  31. package/dist/sdk/coding-agent/utils/pi-user-agent.js +1 -2
  32. package/dist/server/agent-bridge.js +2 -1
  33. package/package.json +1 -1
  34. package/dist/sdk/coding-agent/bun/cli.js +0 -7
  35. package/dist/sdk/coding-agent/bun/restore-sandbox-env.js +0 -31
  36. package/dist/sdk/coding-agent/cli/args.js +0 -340
  37. package/dist/sdk/coding-agent/cli/file-processor.js +0 -82
  38. package/dist/sdk/coding-agent/cli/initial-message.js +0 -21
  39. package/dist/sdk/coding-agent/core/footer-data-provider.js +0 -309
  40. package/dist/sdk/coding-agent/modes/interactive/components/keybinding-hints.js +0 -35
  41. package/dist/sdk/coding-agent/modes/interactive/components/visual-truncate.js +0 -26
  42. package/dist/sdk/coding-agent/modes/interactive/interactive-mode.js +0 -3
  43. package/dist/sdk/coding-agent/modes/interactive/theme/theme.js +0 -1022
@@ -193,9 +193,7 @@ export class SettingsManager {
193
193
  settings.skills !== null &&
194
194
  !Array.isArray(settings.skills)) {
195
195
  const skillsSettings = settings.skills;
196
- if (skillsSettings.enableSkillCommands !== undefined && settings.enableSkillCommands === undefined) {
197
- settings.enableSkillCommands = skillsSettings.enableSkillCommands;
198
- }
196
+ // enableSkillCommands is deprecated only migrate the custom directories
199
197
  if (Array.isArray(skillsSettings.customDirectories) && skillsSettings.customDirectories.length > 0) {
200
198
  settings.skills = skillsSettings.customDirectories;
201
199
  }
@@ -367,14 +365,6 @@ export class SettingsManager {
367
365
  this.errors = [];
368
366
  return drained;
369
367
  }
370
- getLastChangelogVersion() {
371
- return this.settings.lastChangelogVersion;
372
- }
373
- setLastChangelogVersion(version) {
374
- this.globalSettings.lastChangelogVersion = version;
375
- this.markModified("lastChangelogVersion");
376
- this.save();
377
- }
378
368
  getSessionDir() {
379
369
  const sessionDir = this.settings.sessionDir;
380
370
  return sessionDir ? normalizePath(sessionDir) : sessionDir;
@@ -438,14 +428,6 @@ export class SettingsManager {
438
428
  this.markModified("followUpMode");
439
429
  this.save();
440
430
  }
441
- getTheme() {
442
- return this.settings.theme;
443
- }
444
- setTheme(theme) {
445
- this.globalSettings.theme = theme;
446
- this.markModified("theme");
447
- this.save();
448
- }
449
431
  getDefaultThinkingLevel() {
450
432
  return this.settings.defaultThinkingLevel;
451
433
  }
@@ -539,14 +521,6 @@ export class SettingsManager {
539
521
  maxRetryDelayMs: this.settings.retry?.provider?.maxRetryDelayMs ?? 60000,
540
522
  };
541
523
  }
542
- getHideThinkingBlock() {
543
- return this.settings.hideThinkingBlock ?? false;
544
- }
545
- setHideThinkingBlock(hide) {
546
- this.globalSettings.hideThinkingBlock = hide;
547
- this.markModified("hideThinkingBlock");
548
- this.save();
549
- }
550
524
  getShellPath() {
551
525
  return this.settings.shellPath;
552
526
  }
@@ -555,14 +529,6 @@ export class SettingsManager {
555
529
  this.markModified("shellPath");
556
530
  this.save();
557
531
  }
558
- getQuietStartup() {
559
- return this.settings.quietStartup ?? false;
560
- }
561
- setQuietStartup(quiet) {
562
- this.globalSettings.quietStartup = quiet;
563
- this.markModified("quietStartup");
564
- this.save();
565
- }
566
532
  getShellCommandPrefix() {
567
533
  return this.settings.shellCommandPrefix;
568
534
  }
@@ -579,14 +545,6 @@ export class SettingsManager {
579
545
  this.markModified("npmCommand");
580
546
  this.save();
581
547
  }
582
- getCollapseChangelog() {
583
- return this.settings.collapseChangelog ?? false;
584
- }
585
- setCollapseChangelog(collapse) {
586
- this.globalSettings.collapseChangelog = collapse;
587
- this.markModified("collapseChangelog");
588
- this.save();
589
- }
590
548
  getEnableInstallTelemetry() {
591
549
  return this.settings.enableInstallTelemetry ?? true;
592
550
  }
@@ -651,83 +609,9 @@ export class SettingsManager {
651
609
  this.markProjectModified("prompts");
652
610
  this.saveProjectSettings(projectSettings);
653
611
  }
654
- getThemePaths() {
655
- return [...(this.settings.themes ?? [])];
656
- }
657
- setThemePaths(paths) {
658
- this.globalSettings.themes = paths;
659
- this.markModified("themes");
660
- this.save();
661
- }
662
- setProjectThemePaths(paths) {
663
- const projectSettings = structuredClone(this.projectSettings);
664
- projectSettings.themes = paths;
665
- this.markProjectModified("themes");
666
- this.saveProjectSettings(projectSettings);
667
- }
668
- getEnableSkillCommands() {
669
- return this.settings.enableSkillCommands ?? true;
670
- }
671
- setEnableSkillCommands(enabled) {
672
- this.globalSettings.enableSkillCommands = enabled;
673
- this.markModified("enableSkillCommands");
674
- this.save();
675
- }
676
612
  getThinkingBudgets() {
677
613
  return this.settings.thinkingBudgets;
678
614
  }
679
- getShowImages() {
680
- return this.settings.terminal?.showImages ?? true;
681
- }
682
- setShowImages(show) {
683
- if (!this.globalSettings.terminal) {
684
- this.globalSettings.terminal = {};
685
- }
686
- this.globalSettings.terminal.showImages = show;
687
- this.markModified("terminal", "showImages");
688
- this.save();
689
- }
690
- getImageWidthCells() {
691
- const width = this.settings.terminal?.imageWidthCells;
692
- if (typeof width !== "number" || !Number.isFinite(width)) {
693
- return 60;
694
- }
695
- return Math.max(1, Math.floor(width));
696
- }
697
- setImageWidthCells(width) {
698
- if (!this.globalSettings.terminal) {
699
- this.globalSettings.terminal = {};
700
- }
701
- this.globalSettings.terminal.imageWidthCells = Math.max(1, Math.floor(width));
702
- this.markModified("terminal", "imageWidthCells");
703
- this.save();
704
- }
705
- getClearOnShrink() {
706
- // Settings takes precedence, then env var, then default false
707
- if (this.settings.terminal?.clearOnShrink !== undefined) {
708
- return this.settings.terminal.clearOnShrink;
709
- }
710
- return process.env.PI_CLEAR_ON_SHRINK === "1";
711
- }
712
- setClearOnShrink(enabled) {
713
- if (!this.globalSettings.terminal) {
714
- this.globalSettings.terminal = {};
715
- }
716
- this.globalSettings.terminal.clearOnShrink = enabled;
717
- this.markModified("terminal", "clearOnShrink");
718
- this.save();
719
- }
720
- getShowTerminalProgress() {
721
- return this.settings.terminal?.showTerminalProgress ?? false;
722
- }
723
- setShowTerminalProgress(enabled) {
724
- if (!this.globalSettings.terminal) {
725
- this.globalSettings.terminal = {};
726
- }
727
- this.globalSettings.terminal.showTerminalProgress = enabled;
728
- this.markModified("terminal", "showTerminalProgress");
729
- this.save();
730
- }
731
615
  getImageAutoResize() {
732
616
  return this.settings.images?.autoResize ?? true;
733
617
  }
@@ -758,57 +642,4 @@ export class SettingsManager {
758
642
  this.markModified("enabledModels");
759
643
  this.save();
760
644
  }
761
- getDoubleEscapeAction() {
762
- return this.settings.doubleEscapeAction ?? "tree";
763
- }
764
- setDoubleEscapeAction(action) {
765
- this.globalSettings.doubleEscapeAction = action;
766
- this.markModified("doubleEscapeAction");
767
- this.save();
768
- }
769
- getTreeFilterMode() {
770
- const mode = this.settings.treeFilterMode;
771
- const valid = ["default", "no-tools", "user-only", "labeled-only", "all"];
772
- return mode && valid.includes(mode) ? mode : "default";
773
- }
774
- setTreeFilterMode(mode) {
775
- this.globalSettings.treeFilterMode = mode;
776
- this.markModified("treeFilterMode");
777
- this.save();
778
- }
779
- getShowHardwareCursor() {
780
- return this.settings.showHardwareCursor ?? process.env.PI_HARDWARE_CURSOR === "1";
781
- }
782
- setShowHardwareCursor(enabled) {
783
- this.globalSettings.showHardwareCursor = enabled;
784
- this.markModified("showHardwareCursor");
785
- this.save();
786
- }
787
- getEditorPaddingX() {
788
- return this.settings.editorPaddingX ?? 0;
789
- }
790
- setEditorPaddingX(padding) {
791
- this.globalSettings.editorPaddingX = Math.max(0, Math.min(3, Math.floor(padding)));
792
- this.markModified("editorPaddingX");
793
- this.save();
794
- }
795
- getAutocompleteMaxVisible() {
796
- return this.settings.autocompleteMaxVisible ?? 5;
797
- }
798
- setAutocompleteMaxVisible(maxVisible) {
799
- this.globalSettings.autocompleteMaxVisible = Math.max(3, Math.min(20, Math.floor(maxVisible)));
800
- this.markModified("autocompleteMaxVisible");
801
- this.save();
802
- }
803
- getCodeBlockIndent() {
804
- return this.settings.markdown?.codeBlockIndent ?? " ";
805
- }
806
- getWarnings() {
807
- return { ...(this.settings.warnings ?? {}) };
808
- }
809
- setWarnings(warnings) {
810
- this.globalSettings.warnings = { ...warnings };
811
- this.markModified("warnings");
812
- this.save();
813
- }
814
645
  }
@@ -78,10 +78,12 @@ export function buildSystemPrompt(options) {
78
78
  }
79
79
  }
80
80
  // Always include these
81
+ addGuideline("Never assign coding or implementation work to people — you write the code. Non-coding tasks (review, design, ops, coordination) can be assigned");
82
+ addGuideline("When estimating effort, frame it as a feature scope (e.g. 'this is a ~20-hour coding enhancement') rather than using traditional time measures. Tie every estimate to a clearly defined feature boundary");
81
83
  addGuideline("Be concise in your responses");
82
84
  addGuideline("Show file paths clearly when working with files");
83
85
  const guidelines = guidelinesList.map((g) => `- ${g}`).join("\n");
84
- let prompt = `You are an expert coding assistant operating inside pi, a coding agent harness. You help users by reading files, executing commands, editing code, and writing new files.
86
+ let prompt = `You are an expert coding agent operating inside pi, a coding agent harness. You read files, execute commands, edit code, and write new files. All coding and implementation work is yours — never delegate it. Non-coding tasks (review, design decisions, ops) may be assigned to people. When discussing effort, frame each enhancement around a ~20-hour coding scope tied to a concrete feature boundary, avoiding calendar-day or week-based estimates.
85
87
 
86
88
  Available tools:
87
89
  ${toolsList}
@@ -0,0 +1,202 @@
1
+ /**
2
+ * Minimal shared theme for syntax highlighting and extension API compatibility.
3
+ *
4
+ * This replaces the interactive/mode TUI theme system with a stripped-down
5
+ * version that supports the core utilities used by the serve/relay path:
6
+ * - getLanguageFromPath(): maps file extensions to highlight.js language IDs
7
+ * - highlightCode(): syntax-highlights code blocks
8
+ * - loadThemeFromPath(): loads a theme JSON file from disk
9
+ * - Theme class: used by resource-loader and extension types
10
+ *
11
+ * All ANSI color formatting (theme.fg(), theme.bold(), etc.) is removed —
12
+ * tool output is plain text for LLM consumption in serve/relay mode.
13
+ */
14
+ import * as fs from "node:fs";
15
+ import { highlight, supportsLanguage } from "../utils/syntax-highlight.js";
16
+ // ============================================================================
17
+ // Language detection
18
+ // ============================================================================
19
+ const extToLang = {
20
+ ts: "typescript",
21
+ tsx: "typescript",
22
+ js: "javascript",
23
+ jsx: "javascript",
24
+ mjs: "javascript",
25
+ cjs: "javascript",
26
+ py: "python",
27
+ rb: "ruby",
28
+ rs: "rust",
29
+ go: "go",
30
+ java: "java",
31
+ kt: "kotlin",
32
+ swift: "swift",
33
+ c: "c",
34
+ h: "c",
35
+ cpp: "cpp",
36
+ cc: "cpp",
37
+ cxx: "cpp",
38
+ hpp: "cpp",
39
+ cs: "csharp",
40
+ php: "php",
41
+ sh: "bash",
42
+ bash: "bash",
43
+ zsh: "bash",
44
+ fish: "fish",
45
+ ps1: "powershell",
46
+ sql: "sql",
47
+ html: "html",
48
+ htm: "html",
49
+ css: "css",
50
+ scss: "scss",
51
+ sass: "sass",
52
+ less: "less",
53
+ json: "json",
54
+ yaml: "yaml",
55
+ yml: "yaml",
56
+ toml: "toml",
57
+ xml: "xml",
58
+ md: "markdown",
59
+ markdown: "markdown",
60
+ dockerfile: "dockerfile",
61
+ makefile: "makefile",
62
+ cmake: "cmake",
63
+ lua: "lua",
64
+ perl: "perl",
65
+ r: "r",
66
+ scala: "scala",
67
+ clj: "clojure",
68
+ ex: "elixir",
69
+ exs: "elixir",
70
+ erl: "erlang",
71
+ hs: "haskell",
72
+ ml: "ocaml",
73
+ vim: "vim",
74
+ graphql: "graphql",
75
+ proto: "protobuf",
76
+ tf: "hcl",
77
+ hcl: "hcl",
78
+ };
79
+ export function getLanguageFromPath(filePath) {
80
+ const ext = filePath.split(".").pop()?.toLowerCase();
81
+ if (!ext)
82
+ return undefined;
83
+ return extToLang[ext];
84
+ }
85
+ // ============================================================================
86
+ // Syntax highlighting
87
+ // ============================================================================
88
+ /** Identity formatter — returns text as-is (no ANSI styling in serve mode). */
89
+ function identity(text) {
90
+ return text;
91
+ }
92
+ const neutralHighlightTheme = {
93
+ keyword: identity,
94
+ built_in: identity,
95
+ literal: identity,
96
+ number: identity,
97
+ string: identity,
98
+ comment: identity,
99
+ function: identity,
100
+ title: identity,
101
+ class: identity,
102
+ type: identity,
103
+ attr: identity,
104
+ variable: identity,
105
+ params: identity,
106
+ operator: identity,
107
+ punctuation: identity,
108
+ };
109
+ /**
110
+ * Highlight code with syntax coloring based on file extension or language.
111
+ * In serve mode, returns plain text (no ANSI coloring for LLM consumption).
112
+ */
113
+ export function highlightCode(code, lang) {
114
+ const validLang = lang && supportsLanguage(lang) ? lang : undefined;
115
+ if (!validLang) {
116
+ return code.split("\n");
117
+ }
118
+ try {
119
+ return highlight(code, {
120
+ language: validLang,
121
+ ignoreIllegals: true,
122
+ theme: neutralHighlightTheme,
123
+ }).split("\n");
124
+ }
125
+ catch {
126
+ return code.split("\n");
127
+ }
128
+ }
129
+ // ============================================================================
130
+ // Theme loading (for resource-loader)
131
+ // ============================================================================
132
+ /** Resolve built-in theme directory using config path helpers. */
133
+ function getThemesDir() {
134
+ try {
135
+ // This function is exported from config.ts and uses the actual package directory
136
+ const { getThemesDir: fn } = require("../../config.js");
137
+ return fn();
138
+ }
139
+ catch {
140
+ // Fallback: themes were in modes/interactive/theme/, try to find them
141
+ try {
142
+ const { join } = require("node:path");
143
+ const { existsSync } = require("node:fs");
144
+ const packageDir = join(__dirname, "../../..");
145
+ for (const base of ["src", "dist"]) {
146
+ const candidates = [
147
+ join(packageDir, base, "modes", "interactive", "theme"),
148
+ ];
149
+ for (const dir of candidates) {
150
+ if (existsSync(dir))
151
+ return dir;
152
+ }
153
+ }
154
+ }
155
+ catch {
156
+ // ignore
157
+ }
158
+ return __dirname;
159
+ }
160
+ }
161
+ function getBuiltinThemes() {
162
+ const themesDir = getThemesDir();
163
+ if (!themesDir)
164
+ return {};
165
+ try {
166
+ const { join } = require("node:path");
167
+ const { readFileSync } = require("node:fs");
168
+ return {
169
+ dark: JSON.parse(readFileSync(join(themesDir, "dark.json"), "utf-8")),
170
+ light: JSON.parse(readFileSync(join(themesDir, "light.json"), "utf-8")),
171
+ };
172
+ }
173
+ catch {
174
+ return {};
175
+ }
176
+ }
177
+ export function loadThemeFromPath(themePath) {
178
+ const content = fs.readFileSync(themePath, "utf-8");
179
+ const json = JSON.parse(content);
180
+ return {
181
+ name: json.name,
182
+ sourcePath: themePath,
183
+ };
184
+ }
185
+ // ============================================================================
186
+ // Registered themes (for extension API compatibility)
187
+ // ============================================================================
188
+ const registeredThemes = new Map();
189
+ export function setRegisteredThemes(themes) {
190
+ registeredThemes.clear();
191
+ for (const t of themes) {
192
+ if (t.name) {
193
+ registeredThemes.set(t.name, t);
194
+ }
195
+ }
196
+ }
197
+ export function getAvailableThemes() {
198
+ return Array.from(new Set([
199
+ ...Object.keys(getBuiltinThemes()),
200
+ ...registeredThemes.keys(),
201
+ ])).sort();
202
+ }
@@ -1,12 +1,10 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { spawn } from "child_process";
3
3
  import { Type } from "typebox";
4
- import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
5
- import { theme } from "../../modes/interactive/theme/theme.js";
6
4
  import { waitForChildProcess } from "../../utils/child-process.js";
7
5
  import { getShellConfig, getShellEnv, killProcessTree, trackDetachedChildPid, untrackDetachedChildPid, } from "../../utils/shell.js";
8
6
  import { OutputAccumulator } from "./output-accumulator.js";
9
- import { getTextOutput, invalidArgText, str } from "./render-utils.js";
7
+ import { getTextOutput, str } from "./render-utils.js";
10
8
  import { wrapToolDefinition } from "./tool-definition-wrapper.js";
11
9
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from "./truncate.js";
12
10
  const bashSchema = Type.Object({
@@ -103,12 +101,17 @@ const BASH_UPDATE_THROTTLE_MS = 100;
103
101
  function formatDuration(ms) {
104
102
  return `${(ms / 1000).toFixed(1)}s`;
105
103
  }
104
+ function plainText(s) { return s; }
105
+ const noopTheme = {
106
+ fg: (_name, text) => text,
107
+ bold: plainText,
108
+ };
106
109
  function formatBashCall(args) {
107
110
  const command = str(args?.command);
108
111
  const timeout = args?.timeout;
109
- const timeoutSuffix = timeout ? theme.fg("muted", ` (timeout ${timeout}s)`) : "";
110
- const commandDisplay = command === null ? invalidArgText(theme) : command ? command : theme.fg("toolOutput", "...");
111
- return theme.fg("toolTitle", theme.bold(`$ ${commandDisplay}`)) + timeoutSuffix;
112
+ const timeoutSuffix = timeout ? ` (timeout ${timeout}s)` : "";
113
+ const commandDisplay = command === null ? "[invalid]" : command || "...";
114
+ return `$ ${commandDisplay}${timeoutSuffix}`;
112
115
  }
113
116
  function formatBashResult(result, options, showImages, startedAt, endedAt) {
114
117
  let output = getTextOutput(result, showImages).trim();
@@ -122,24 +125,20 @@ function formatBashResult(result, options, showImages, startedAt, endedAt) {
122
125
  }
123
126
  const parts = [];
124
127
  if (output) {
125
- const styledOutput = output
126
- .split("\n")
127
- .map((line) => theme.fg("toolOutput", line))
128
- .join("\n");
129
128
  if (options.expanded) {
130
- parts.push(`\n${styledOutput}`);
129
+ parts.push(`\n${output}`);
131
130
  }
132
131
  else {
133
- const styledLines = styledOutput.split("\n");
134
- if (styledLines.length > BASH_PREVIEW_LINES) {
135
- const hint = theme.fg("muted", `... (${styledLines.length - BASH_PREVIEW_LINES} earlier lines, ${keyHint("app.tools.expand", "to expand")})`);
132
+ const lines = output.split("\n");
133
+ if (lines.length > BASH_PREVIEW_LINES) {
134
+ const hint = `... (${lines.length - BASH_PREVIEW_LINES} earlier lines)`;
136
135
  parts.push("");
137
136
  parts.push(hint);
138
- parts.push(...styledLines.slice(-BASH_PREVIEW_LINES));
137
+ parts.push(...lines.slice(-BASH_PREVIEW_LINES));
139
138
  }
140
139
  else {
141
140
  parts.push("");
142
- parts.push(...styledLines);
141
+ parts.push(...lines);
143
142
  }
144
143
  }
145
144
  }
@@ -156,12 +155,12 @@ function formatBashResult(result, options, showImages, startedAt, endedAt) {
156
155
  warnings.push(`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`);
157
156
  }
158
157
  }
159
- parts.push(`\n${theme.fg("warning", `[${warnings.join(". ")}]`)}`);
158
+ parts.push(`\n[${warnings.join(". ")}]`);
160
159
  }
161
160
  if (startedAt !== undefined) {
162
161
  const label = options.isPartial ? "Elapsed" : "Took";
163
162
  const endTime = endedAt ?? Date.now();
164
- parts.push(`\n${theme.fg("muted", `${label} ${formatDuration(endTime - startedAt)}`)}`);
163
+ parts.push(`\n${label} ${formatDuration(endTime - startedAt)}`);
165
164
  }
166
165
  return parts.join("");
167
166
  }
@@ -1,11 +1,11 @@
1
1
  import { constants } from "fs";
2
2
  import { access as fsAccess, readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
3
3
  import { Type } from "typebox";
4
- import { renderDiff } from "../../modes/interactive/components/diff.js";
4
+ import { renderDiff } from "./render-diff.js";
5
5
  import { applyEditsToNormalizedContent, detectLineEnding, generateDiffString, generateUnifiedPatch, normalizeToLF, restoreLineEndings, stripBom, } from "./edit-diff.js";
6
6
  import { withFileMutationQueue } from "./file-mutation-queue.js";
7
7
  import { resolveToCwd } from "./path-utils.js";
8
- import { invalidArgText, shortenPath, str } from "./render-utils.js";
8
+ import { shortenPath, str } from "./render-utils.js";
9
9
  import { wrapToolDefinition } from "./tool-definition-wrapper.js";
10
10
  const replaceEditSchema = Type.Object({
11
11
  oldText: Type.String({
@@ -53,14 +53,13 @@ function validateEditInput(input) {
53
53
  }
54
54
  return { path: input.path, edits: input.edits };
55
55
  }
56
- function formatEditCall(args, theme) {
57
- const invalidArg = invalidArgText(theme);
56
+ function formatEditCall(args, _theme) {
58
57
  const rawPath = str(args?.file_path ?? args?.path);
59
58
  const path = rawPath !== null ? shortenPath(rawPath) : null;
60
- const pathDisplay = path === null ? invalidArg : path ? theme.fg("accent", path) : theme.fg("toolOutput", "...");
61
- return `${theme.fg("toolTitle", theme.bold("edit"))} ${pathDisplay}`;
59
+ const pathDisplay = path === null ? "[invalid]" : path || "...";
60
+ return `edit ${pathDisplay}`;
62
61
  }
63
- function formatEditResult(args, result, theme, isError) {
62
+ function formatEditResult(args, result, _theme, isError) {
64
63
  const rawPath = str(args?.file_path ?? args?.path);
65
64
  if (isError) {
66
65
  const errorText = result.content
@@ -70,7 +69,7 @@ function formatEditResult(args, result, theme, isError) {
70
69
  if (!errorText) {
71
70
  return undefined;
72
71
  }
73
- return theme.fg("error", errorText);
72
+ return errorText;
74
73
  }
75
74
  const resultDiff = result.details?.diff;
76
75
  if (resultDiff) {
@@ -3,10 +3,9 @@ import { spawn } from "child_process";
3
3
  import { existsSync } from "fs";
4
4
  import path from "path";
5
5
  import { Type } from "typebox";
6
- import { keyHint } from "../../modes/interactive/components/keybinding-hints.js";
7
6
  import { ensureTool } from "../../utils/tools-manager.js";
8
7
  import { resolveToCwd } from "./path-utils.js";
9
- import { getTextOutput, invalidArgText, shortenPath, str } from "./render-utils.js";
8
+ import { getTextOutput, shortenPath, str } from "./render-utils.js";
10
9
  import { wrapToolDefinition } from "./tool-definition-wrapper.js";
11
10
  import { DEFAULT_MAX_BYTES, formatSize, truncateHead } from "./truncate.js";
12
11
  function toPosixPath(value) {
@@ -25,22 +24,19 @@ const defaultFindOperations = {
25
24
  // This is a placeholder. Actual fd execution happens in execute() when no custom glob is provided.
26
25
  glob: () => [],
27
26
  };
28
- function formatFindCall(args, theme) {
27
+ function formatFindCall(args, _theme) {
29
28
  const pattern = str(args?.pattern);
30
29
  const rawPath = str(args?.path);
31
30
  const path = rawPath !== null ? shortenPath(rawPath || ".") : null;
32
31
  const limit = args?.limit;
33
- const invalidArg = invalidArgText(theme);
34
- let text = theme.fg("toolTitle", theme.bold("find")) +
35
- " " +
36
- (pattern === null ? invalidArg : theme.fg("accent", pattern || "")) +
37
- theme.fg("toolOutput", ` in ${path === null ? invalidArg : path}`);
32
+ const invalidArg = "[invalid]";
33
+ let text = `find ${pattern === null ? invalidArg : pattern || ""} in ${path === null ? invalidArg : path}`;
38
34
  if (limit !== undefined) {
39
- text += theme.fg("toolOutput", ` (limit ${limit})`);
35
+ text += ` (limit ${limit})`;
40
36
  }
41
37
  return text;
42
38
  }
43
- function formatFindResult(result, options, theme, showImages) {
39
+ function formatFindResult(result, options, _theme, showImages) {
44
40
  const output = getTextOutput(result, showImages).trim();
45
41
  let text = "";
46
42
  if (output) {
@@ -48,9 +44,9 @@ function formatFindResult(result, options, theme, showImages) {
48
44
  const maxLines = options.expanded ? lines.length : 20;
49
45
  const displayLines = lines.slice(0, maxLines);
50
46
  const remaining = lines.length - maxLines;
51
- text += `\n${displayLines.map((line) => theme.fg("toolOutput", line)).join("\n")}`;
47
+ text += `\n${displayLines.join("\n")}`;
52
48
  if (remaining > 0) {
53
- text += `${theme.fg("muted", `\n... (${remaining} more lines,`)} ${keyHint("app.tools.expand", "to expand")})`;
49
+ text += `\n... (${remaining} more lines)`;
54
50
  }
55
51
  }
56
52
  const resultLimit = result.details?.resultLimitReached;
@@ -61,7 +57,7 @@ function formatFindResult(result, options, theme, showImages) {
61
57
  warnings.push(`${resultLimit} results limit`);
62
58
  if (truncation?.truncated)
63
59
  warnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);
64
- text += `\n${theme.fg("warning", `[Truncated: ${warnings.join(", ")}]`)}`;
60
+ text += `\n[Truncated: ${warnings.join(", ")}]`;
65
61
  }
66
62
  return text;
67
63
  }