Skip to content

Commit aee901f

Browse files
Merge pull request #6001 from chezsmithy/feature-add-terminal-ansi
Feat ✨ Give the terminal color and escape sequence rendering
2 parents 3238c77 + 1b86c71 commit aee901f

File tree

5 files changed

+401
-68
lines changed

5 files changed

+401
-68
lines changed

core/tools/implementations/runTerminalCommand.ts

Lines changed: 63 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,16 @@ import {
1010

1111
const asyncExec = util.promisify(childProcess.exec);
1212

13+
// Add color-supporting environment variables
14+
const getColorEnv = () => ({
15+
...process.env,
16+
FORCE_COLOR: "1",
17+
COLORTERM: "truecolor",
18+
TERM: "xterm-256color",
19+
CLICOLOR: "1",
20+
CLICOLOR_FORCE: "1",
21+
});
22+
1323
const ENABLED_FOR_REMOTES = [
1424
"",
1525
"local",
@@ -56,10 +66,11 @@ export const runTerminalCommandImpl: ToolImpl = async (args, extras) => {
5666
}
5767
}
5868

59-
// Use spawn instead of exec to get streaming output
69+
// Use spawn with color environment
6070
const childProc = childProcess.spawn(args.command, {
6171
cwd,
6272
shell: true,
73+
env: getColorEnv(), // Add enhanced environment for colors
6374
});
6475

6576
childProc.stdout?.on("data", (data) => {
@@ -130,26 +141,7 @@ export const runTerminalCommandImpl: ToolImpl = async (args, extras) => {
130141
return;
131142
}
132143

133-
if (!waitForCompletion) {
134-
// Already resolved, just update the UI with final output
135-
if (extras.onPartialOutput) {
136-
const status =
137-
code === 0 || !code
138-
? "\nBackground command completed"
139-
: `\nBackground command failed with exit code ${code}`;
140-
extras.onPartialOutput({
141-
toolCallId,
142-
contextItems: [
143-
{
144-
name: "Terminal",
145-
description: "Terminal command output",
146-
content: terminalOutput,
147-
status: status,
148-
},
149-
],
150-
});
151-
}
152-
} else {
144+
if (waitForCompletion) {
153145
// Normal completion, resolve now
154146
if (code === 0) {
155147
const status = "Command completed";
@@ -172,6 +164,25 @@ export const runTerminalCommandImpl: ToolImpl = async (args, extras) => {
172164
},
173165
]);
174166
}
167+
} else {
168+
// Already resolved, just update the UI with final output
169+
if (extras.onPartialOutput) {
170+
const status =
171+
code === 0 || !code
172+
? "\nBackground command completed"
173+
: `\nBackground command failed with exit code ${code}`;
174+
extras.onPartialOutput({
175+
toolCallId,
176+
contextItems: [
177+
{
178+
name: "Terminal",
179+
description: "Terminal command output",
180+
content: terminalOutput,
181+
status: status,
182+
},
183+
],
184+
});
185+
}
175186
}
176187
});
177188

@@ -197,14 +208,43 @@ export const runTerminalCommandImpl: ToolImpl = async (args, extras) => {
197208
const workspaceDirs = await extras.ide.getWorkspaceDirs();
198209
const cwd = fileURLToPath(workspaceDirs[0]);
199210

200-
if (!waitForCompletion) {
211+
if (waitForCompletion) {
212+
// Standard execution, waiting for completion
213+
try {
214+
// Use color environment for exec as well
215+
const output = await asyncExec(args.command, {
216+
cwd,
217+
env: getColorEnv(),
218+
});
219+
const status = "Command completed";
220+
return [
221+
{
222+
name: "Terminal",
223+
description: "Terminal command output",
224+
content: output.stdout ?? "",
225+
status: status,
226+
},
227+
];
228+
} catch (error: any) {
229+
const status = `Command failed with: ${error.message || error.toString()}`;
230+
return [
231+
{
232+
name: "Terminal",
233+
description: "Terminal command output",
234+
content: error.stderr ?? error.toString(),
235+
status: status,
236+
},
237+
];
238+
}
239+
} else {
201240
// For non-streaming but also not waiting for completion, use spawn
202241
// but don't attach any listeners other than error
203242
try {
204-
// Use spawn instead of exec but don't wait
243+
// Use spawn with color environment
205244
const childProc = childProcess.spawn(args.command, {
206245
cwd,
207246
shell: true,
247+
env: getColorEnv(), // Add color environment
208248
// Detach the process so it's not tied to the parent
209249
detached: true,
210250
// Redirect to /dev/null equivalent (works cross-platform)
@@ -246,30 +286,6 @@ export const runTerminalCommandImpl: ToolImpl = async (args, extras) => {
246286
},
247287
];
248288
}
249-
} else {
250-
// Standard execution, waiting for completion
251-
try {
252-
const output = await asyncExec(args.command, { cwd });
253-
const status = "Command completed";
254-
return [
255-
{
256-
name: "Terminal",
257-
description: "Terminal command output",
258-
content: output.stdout ?? "",
259-
status: status,
260-
},
261-
];
262-
} catch (error: any) {
263-
const status = `Command failed with: ${error.message || error.toString()}`;
264-
return [
265-
{
266-
name: "Terminal",
267-
description: "Terminal command output",
268-
content: error.stderr ?? error.toString(),
269-
status: status,
270-
},
271-
];
272-
}
273289
}
274290
}
275291
}

gui/package-lock.json

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

gui/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@
3434
"@tiptap/starter-kit": "^2.1.13",
3535
"@tiptap/suggestion": "^2.1.13",
3636
"@types/uuid": "^10.0.0",
37+
"anser": "^2.3.2",
3738
"clsx": "^2.1.1",
3839
"core": "file:../core",
3940
"dompurify": "^3.0.6",
4041
"downshift": "^7.6.0",
42+
"escape-carriage": "^1.3.1",
4143
"lodash": "^4.17.21",
4244
"lowlight": "^3.3.0",
4345
"minisearch": "^7.0.2",

0 commit comments

Comments
 (0)