Skip to content

Commit 61ca6a8

Browse files
committed
undo terminal stuff
1 parent d1d65fa commit 61ca6a8

File tree

2 files changed

+98
-208
lines changed

2 files changed

+98
-208
lines changed

codebuff.json

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,46 +15,19 @@
1515
"fileChangeHooks": [
1616
{
1717
"name": "backend-unit-tests",
18-
"command": "set -o pipefail && bun test $(find src -name *.test.ts ! -name *.integration.test.ts) 2>&1 | grep -Ev '^\\(pass\\)|\\(skip\\)'",
18+
"command": "set -o pipefail && bun test $(find src -name *.test.ts ! -name *.integration.test.ts) 2>&1 | grep -Ev $'\\x1b\\[[0-9;]*m' | grep -Ev '^s*[0-9]+s+(pass|skip)|(pass)|(skip)' && bun run typecheck-only",
1919
"cwd": "backend",
2020
"filePattern": "backend/**/*.ts"
2121
},
22-
{
23-
"name": "backend-typecheck",
24-
"command": "bun run typecheck-only",
25-
"cwd": "backend",
26-
"filePattern": "backend/**/*.ts"
27-
},
28-
2922
{
3023
"name": "npm-app-unit-tests",
31-
"command": "set -o pipefail && bun test $(find src -name *.test.ts ! -name *.integration.test.ts) 2>&1 | grep -Ev '^\\(pass\\)|\\(skip\\)'",
32-
"cwd": "npm-app",
33-
"filePattern": "npm-app/**/*.ts"
34-
},
35-
{
36-
"name": "npm-typecheck",
37-
"command": "bun run typecheck-only",
24+
"command": "set -o pipefail && bun test $(find src -name *.test.ts ! -name *.integration.test.ts) 2>&1 | grep -Ev $'\\x1b\\[[0-9;]*m' | grep -Ev '^s*[0-9]+s+(pass|skip)|(pass)|(skip)' && bun run typecheck-only",
3825
"cwd": "npm-app",
3926
"filePattern": "npm-app/**/*.ts"
4027
},
41-
42-
{
43-
"name": "web-typecheck",
44-
"command": "bun run typecheck-only",
45-
"cwd": "web",
46-
"filePattern": "web/**/*.ts"
47-
},
48-
4928
{
5029
"name": "common-unit-tests",
51-
"command": "set -o pipefail && bun test $(find src -name *.test.ts ! -name *.integration.test.ts) 2>&1 | grep -Ev '^\\(pass\\)|\\(skip\\)'",
52-
"cwd": "common",
53-
"filePattern": "common/**/*.ts"
54-
},
55-
{
56-
"name": "common-typecheck",
57-
"command": "bun run typecheck-only",
30+
"command": "set -o pipefail && bun test $(find src -name *.test.ts ! -name *.integration.test.ts) 2>&1 | grep -Ev $'\\x1b\\[[0-9;]*m' | grep -Ev '^s*[0-9]+s+(pass|skip)|(pass)|(skip)' && bun run typecheck-only",
5831
"cwd": "common",
5932
"filePattern": "common/**/*.ts"
6033
}

npm-app/src/terminal/base.ts

Lines changed: 95 additions & 178 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ try {
3434

3535
const COMMAND_OUTPUT_LIMIT = 10_000
3636
const promptIdentifier = '@36261@'
37-
const cwdIdentifier = '@76593@'
3837

3938
type PersistentProcess =
4039
| {
@@ -340,122 +339,8 @@ export const runTerminalCommand = async (
340339

341340
const echoLinePattern = new RegExp(`${promptIdentifier}[^\n]*\n`, 'g')
342341
const commandDonePattern = new RegExp(
343-
`^${cwdIdentifier}(.*)${cwdIdentifier}[\\s\\S]*${promptIdentifier}`
342+
`^${promptIdentifier}(.*)${promptIdentifier}[\\s\\S]*${promptIdentifier}`
344343
)
345-
/**
346-
* Executes a single command in a PTY process and returns the result when complete.
347-
*
348-
* This function handles the low-level details of running a command in a pseudo-terminal,
349-
* including parsing the output to separate command echoes from actual output, detecting
350-
* command completion, and extracting the exit code and final working directory.
351-
*
352-
* @param ptyProcess - The IPty instance to execute the command in
353-
* @param command - The shell command to execute
354-
* @param cwd - The working directory to change to before executing the command (relative to project root)
355-
* @param onChunk - Callback function called for each chunk of output as it's received
356-
*
357-
* @returns Promise that resolves with:
358-
* - `commandOutput`: The complete output from the command (excluding echo lines)
359-
* - `finalCwd`: The working directory after command execution
360-
* - `exitCode`: The command's exit code (0 for success, non-zero for failure, null if os is Windows)
361-
*
362-
* @example
363-
* ```typescript
364-
* const result = await runSinglePtyCommand(
365-
* ptyProcess,
366-
* 'ls -la',
367-
* '.',
368-
* (chunk) => console.log('Output chunk:', chunk)
369-
* );
370-
* console.log('Exit code:', result.exitCode);
371-
* console.log('Final directory:', result.finalCwd);
372-
* ```
373-
*
374-
* @internal This is a low-level utility function used by other terminal command runners.
375-
* It handles platform-specific differences between Windows and Unix-like systems.
376-
*
377-
* The function works by:
378-
* 1. Setting up a data listener on the PTY process
379-
* 2. Filtering out command echo lines (the command being typed)
380-
* 3. Detecting command completion markers (cwdIdentifier and promptIdentifier)
381-
* 4. Parsing exit codes from the shell's status messages
382-
* 5. Extracting the final working directory from the output
383-
*/
384-
function runSinglePtyCommand(
385-
ptyProcess: IPty,
386-
command: string,
387-
cwd: string,
388-
onChunk: (data: string) => void
389-
): Promise<{
390-
commandOutput: string
391-
finalCwd: string
392-
exitCode: number | null
393-
}> {
394-
const isWindows = os.platform() === 'win32'
395-
let commandOutput = ''
396-
let buffer = promptIdentifier
397-
let echoLinesRemaining = isWindows ? 1 : command.split('\n').length
398-
399-
const resultPromise = new Promise<{
400-
commandOutput: string
401-
finalCwd: string
402-
exitCode: number | null
403-
}>((resolve) => {
404-
const dataDisposable = ptyProcess.onData((data: string) => {
405-
buffer += data
406-
const suffix = suffixPrefixOverlap(buffer, promptIdentifier)
407-
let toProcess = buffer.slice(0, buffer.length - suffix.length)
408-
buffer = suffix
409-
410-
const matches = toProcess.match(echoLinePattern)
411-
if (matches) {
412-
for (let i = 0; i < matches.length && echoLinesRemaining > 0; i++) {
413-
echoLinesRemaining = Math.max(echoLinesRemaining - 1, 0)
414-
// Process normal output line
415-
toProcess = toProcess.replace(echoLinePattern, '')
416-
}
417-
}
418-
419-
const indexOfPromptIdentifier = toProcess.indexOf(cwdIdentifier)
420-
if (indexOfPromptIdentifier !== -1) {
421-
buffer = toProcess.slice(indexOfPromptIdentifier) + buffer
422-
toProcess = toProcess.slice(0, indexOfPromptIdentifier)
423-
}
424-
425-
onChunk(toProcess)
426-
commandOutput += toProcess
427-
428-
const commandDone = buffer.match(commandDonePattern)
429-
if (commandDone && echoLinesRemaining === 0) {
430-
// Command is done
431-
dataDisposable.dispose()
432-
433-
const exitCode = buffer.includes('Command completed')
434-
? 0
435-
: (() => {
436-
const match = buffer.match(
437-
/Command failed with exit code (\d+)\./
438-
)
439-
return match ? parseInt(match[1]) : null
440-
})()
441-
442-
const newWorkingDirectory = commandDone[1]
443-
444-
resolve({ commandOutput, finalCwd: newWorkingDirectory, exitCode })
445-
}
446-
})
447-
})
448-
449-
// Write the command
450-
const cdCommand = `cd ${path.resolve(getProjectRoot(), cwd)}`
451-
const commandWithCheck = isWindows
452-
? `${cdCommand} & ${command} & echo ${cwdIdentifier}%cd%${cwdIdentifier}`
453-
: `${cdCommand}; ${command}; ec=$?; printf "${cwdIdentifier}$(pwd)${cwdIdentifier}"; if [ $ec -eq 0 ]; then printf "Command completed."; else printf "Command failed with exit code $ec."; fi`
454-
ptyProcess.write(`${commandWithCheck}\r\n`)
455-
456-
return resultPromise
457-
}
458-
459344
export const runCommandPty = (
460345
persistentProcess: PersistentProcess & {
461346
type: 'pty'
@@ -519,82 +404,114 @@ export const runCommandPty = (
519404

520405
persistentProcess.timerId = timer
521406

522-
runSinglePtyCommand(ptyProcess, command, cwd, (data: string) => {
523-
commandOutput += data
524-
process.stdout.write(data)
525-
}).then(async ({ finalCwd: newWorkingDirectory, exitCode }) => {
526-
const statusMessage =
527-
exitCode === null
528-
? ''
529-
: exitCode === 0
530-
? 'Complete'
531-
: `Failed with exit code: ${exitCode}`
407+
const dataDisposable = ptyProcess.onData((data: string) => {
408+
buffer += data
409+
const suffix = suffixPrefixOverlap(buffer, promptIdentifier)
410+
let toProcess = buffer.slice(0, buffer.length - suffix.length)
411+
buffer = suffix
532412

533-
if (timer) {
534-
clearTimeout(timer)
413+
const matches = toProcess.match(echoLinePattern)
414+
if (matches) {
415+
for (let i = 0; i < matches.length && echoLinesRemaining > 0; i++) {
416+
echoLinesRemaining = Math.max(echoLinesRemaining - 1, 0)
417+
// Process normal output line
418+
toProcess = toProcess.replace(echoLinePattern, '')
419+
}
535420
}
536-
if (mode === 'assistant') {
421+
422+
const indexOfPromptIdentifier = toProcess.indexOf(promptIdentifier)
423+
if (indexOfPromptIdentifier !== -1) {
424+
buffer = toProcess.slice(indexOfPromptIdentifier) + buffer
425+
toProcess = toProcess.slice(0, indexOfPromptIdentifier)
426+
}
427+
428+
process.stdout.write(toProcess)
429+
commandOutput += toProcess
430+
431+
const commandDone = buffer.match(commandDonePattern)
432+
if (commandDone && echoLinesRemaining === 0) {
433+
// Command is done
434+
if (timer) {
435+
clearTimeout(timer)
436+
}
437+
dataDisposable.dispose()
438+
439+
const exitCode = buffer.includes('Command completed')
440+
? 0
441+
: (() => {
442+
const match = buffer.match(/Command failed with exit code (\d+)\./)
443+
return match ? parseInt(match[1]) : null
444+
})()
445+
const statusMessage = buffer.includes('Command completed')
446+
? 'Complete'
447+
: `Failed with exit code: ${exitCode}`
448+
449+
const newWorkingDirectory = commandDone[1]
450+
if (mode === 'assistant') {
451+
ptyProcess.write(`cd ${getWorkingDirectory()}\r\n`)
452+
453+
resolve({
454+
result: formatResult(
455+
command,
456+
commandOutput,
457+
`cwd: ${path.resolve(projectRoot, cwd)}\n\n${statusMessage}`
458+
),
459+
stdout: commandOutput,
460+
exitCode,
461+
})
462+
return
463+
}
464+
465+
let outsideProject = false
466+
const currentWorkingDirectory = getWorkingDirectory()
467+
let finalCwd = currentWorkingDirectory
468+
if (newWorkingDirectory !== currentWorkingDirectory) {
469+
trackEvent(AnalyticsEvent.CHANGE_DIRECTORY, {
470+
from: currentWorkingDirectory,
471+
to: newWorkingDirectory,
472+
isSubdir: isSubdir(currentWorkingDirectory, newWorkingDirectory),
473+
})
474+
if (path.relative(projectRoot, newWorkingDirectory).startsWith('..')) {
475+
outsideProject = true
476+
console.log(`
477+
Unable to cd outside of the project root (${projectRoot})
478+
479+
If you want to change the project root:
480+
1. Exit Codebuff (type "exit")
481+
2. Navigate into the target directory (type "cd ${newWorkingDirectory}")
482+
3. Restart Codebuff`)
483+
ptyProcess.write(`cd ${currentWorkingDirectory}\r\n`)
484+
} else {
485+
setWorkingDirectory(newWorkingDirectory)
486+
finalCwd = newWorkingDirectory
487+
}
488+
}
489+
537490
resolve({
538491
result: formatResult(
539492
command,
540493
commandOutput,
541494
buildArray([
542-
`cwd: ${path.resolve(projectRoot, cwd)}`,
543-
statusMessage,
544-
]).join('\n\n')
495+
`cwd: ${currentWorkingDirectory}`,
496+
`${statusMessage}\n`,
497+
outsideProject &&
498+
`Detected final cwd outside project root. Reset cwd to ${currentWorkingDirectory}`,
499+
`Final **user** cwd: ${finalCwd} (Assistant's cwd is still project root)`,
500+
]).join('\n')
545501
),
546502
stdout: commandOutput,
547503
exitCode,
548504
})
505+
return
549506
}
550-
let outsideProject = false
551-
const currentWorkingDirectory = getWorkingDirectory()
552-
let finalCwd = currentWorkingDirectory
553-
if (newWorkingDirectory !== currentWorkingDirectory) {
554-
trackEvent(AnalyticsEvent.CHANGE_DIRECTORY, {
555-
from: currentWorkingDirectory,
556-
to: newWorkingDirectory,
557-
isSubdir: isSubdir(currentWorkingDirectory, newWorkingDirectory),
558-
})
559-
if (path.relative(projectRoot, newWorkingDirectory).startsWith('..')) {
560-
outsideProject = true
561-
console.log(`
562-
Unable to cd outside of the project root (${projectRoot})
563-
564-
If you want to change the project root:
565-
1. Exit Codebuff (type "exit")
566-
2. Navigate into the target directory (type "cd ${newWorkingDirectory}")
567-
3. Restart Codebuff`)
568-
await runSinglePtyCommand(
569-
ptyProcess,
570-
`cd ${currentWorkingDirectory}`,
571-
'.',
572-
() => {}
573-
)
574-
} else {
575-
setWorkingDirectory(newWorkingDirectory)
576-
finalCwd = newWorkingDirectory
577-
}
578-
}
579-
580-
resolve({
581-
result: formatResult(
582-
command,
583-
commandOutput,
584-
buildArray([
585-
`cwd: ${currentWorkingDirectory}`,
586-
`${statusMessage}\n`,
587-
outsideProject &&
588-
`Detected final cwd outside project root. Reset cwd to ${currentWorkingDirectory}`,
589-
`Final **user** cwd: ${finalCwd} (Assistant's cwd is still project root)`,
590-
]).join('\n')
591-
),
592-
stdout: commandOutput,
593-
exitCode,
594-
})
595507
})
596508

597-
return
509+
// Write the command
510+
const cdCommand = `cd ${path.resolve(projectRoot, cwd)}`
511+
const commandWithCheck = isWindows
512+
? `${cdCommand} & ${command} & echo ${promptIdentifier}%cd%${promptIdentifier}`
513+
: `${cdCommand}; ${command}; ec=$?; printf "${promptIdentifier}$(pwd)${promptIdentifier}"; if [ $ec -eq 0 ]; then printf "Command completed."; else printf "Command failed with exit code $ec."; fi`
514+
ptyProcess.write(`${commandWithCheck}\r`)
598515
}
599516

600517
const runCommandChildProcess = (

0 commit comments

Comments
 (0)