@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.
- package/dist/extensions/kanban-bridge.js +668 -0
- package/dist/extensions/spectral-vision-fallback.js +3 -2
- package/dist/mcp/init.js +1 -9
- package/dist/memory/index.js +2 -0
- package/dist/memory/tools/write-project-observation.js +60 -0
- package/dist/relay/auto-research.js +34 -0
- package/dist/sdk/ai/env-api-keys.js +9 -49
- package/dist/sdk/ai/utils/oauth/anthropic.js +1 -1
- package/dist/sdk/ai/utils/oauth/openai-codex.js +1 -1
- package/dist/sdk/coding-agent/config.js +2 -69
- package/dist/sdk/coding-agent/core/extensions/loader.js +2 -35
- package/dist/sdk/coding-agent/core/extensions/runner.js +1 -2
- package/dist/sdk/coding-agent/core/model-resolver-utils.js +8 -0
- package/dist/sdk/coding-agent/core/model-resolver.js +1 -1
- package/dist/sdk/coding-agent/core/resource-loader.js +1 -1
- package/dist/sdk/coding-agent/core/settings-manager.js +1 -170
- package/dist/sdk/coding-agent/core/system-prompt.js +3 -1
- package/dist/sdk/coding-agent/core/theme.js +202 -0
- package/dist/sdk/coding-agent/core/tools/bash.js +17 -18
- package/dist/sdk/coding-agent/core/tools/edit.js +7 -8
- package/dist/sdk/coding-agent/core/tools/find.js +9 -13
- package/dist/sdk/coding-agent/core/tools/grep.js +10 -14
- package/dist/sdk/coding-agent/core/tools/ls.js +9 -10
- package/dist/sdk/coding-agent/core/tools/read.js +15 -25
- package/dist/sdk/coding-agent/{modes/interactive/components/diff.js → core/tools/render-diff.js} +18 -31
- package/dist/sdk/coding-agent/core/tools/write.js +10 -11
- package/dist/sdk/coding-agent/index.js +7 -5
- package/dist/sdk/coding-agent/modes/index.js +0 -1
- package/dist/sdk/coding-agent/modes/rpc/rpc-mode.js +2 -2
- package/dist/sdk/coding-agent/utils/photon.js +2 -10
- package/dist/sdk/coding-agent/utils/pi-user-agent.js +1 -2
- package/dist/server/agent-bridge.js +2 -1
- package/package.json +1 -1
- package/dist/sdk/coding-agent/bun/cli.js +0 -7
- package/dist/sdk/coding-agent/bun/restore-sandbox-env.js +0 -31
- package/dist/sdk/coding-agent/cli/args.js +0 -340
- package/dist/sdk/coding-agent/cli/file-processor.js +0 -82
- package/dist/sdk/coding-agent/cli/initial-message.js +0 -21
- package/dist/sdk/coding-agent/core/footer-data-provider.js +0 -309
- package/dist/sdk/coding-agent/modes/interactive/components/keybinding-hints.js +0 -35
- package/dist/sdk/coding-agent/modes/interactive/components/visual-truncate.js +0 -26
- package/dist/sdk/coding-agent/modes/interactive/interactive-mode.js +0 -3
- 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
|
-
|
|
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
|
|
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,
|
|
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 ?
|
|
110
|
-
const commandDisplay = command === null ?
|
|
111
|
-
return
|
|
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${
|
|
129
|
+
parts.push(`\n${output}`);
|
|
131
130
|
}
|
|
132
131
|
else {
|
|
133
|
-
const
|
|
134
|
-
if (
|
|
135
|
-
const hint =
|
|
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(...
|
|
137
|
+
parts.push(...lines.slice(-BASH_PREVIEW_LINES));
|
|
139
138
|
}
|
|
140
139
|
else {
|
|
141
140
|
parts.push("");
|
|
142
|
-
parts.push(...
|
|
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
|
|
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${
|
|
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 "
|
|
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 {
|
|
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,
|
|
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 ?
|
|
61
|
-
return
|
|
59
|
+
const pathDisplay = path === null ? "[invalid]" : path || "...";
|
|
60
|
+
return `edit ${pathDisplay}`;
|
|
62
61
|
}
|
|
63
|
-
function formatEditResult(args, result,
|
|
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
|
|
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,
|
|
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,
|
|
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 =
|
|
34
|
-
let text =
|
|
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 +=
|
|
35
|
+
text += ` (limit ${limit})`;
|
|
40
36
|
}
|
|
41
37
|
return text;
|
|
42
38
|
}
|
|
43
|
-
function formatFindResult(result, options,
|
|
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.
|
|
47
|
+
text += `\n${displayLines.join("\n")}`;
|
|
52
48
|
if (remaining > 0) {
|
|
53
|
-
text +=
|
|
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
|
|
60
|
+
text += `\n[Truncated: ${warnings.join(", ")}]`;
|
|
65
61
|
}
|
|
66
62
|
return text;
|
|
67
63
|
}
|