interactive shell mode

This commit is contained in:
olcan
2025-05-22 00:48:33 -07:00
parent 174fdce7d8
commit 99289463ea
5 changed files with 53 additions and 23 deletions

17
package-lock.json generated
View File

@@ -6772,6 +6772,12 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/nan": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz",
"integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==",
"license": "MIT"
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
@@ -6901,6 +6907,16 @@
"node": ">=10"
}
},
"node_modules/node-pty": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz",
"integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"nan": "^2.17.0"
}
},
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
@@ -10344,6 +10360,7 @@
"ink-text-input": "^6.0.0",
"lowlight": "^3.3.0",
"mime-types": "^2.1.4",
"node-pty": "^1.0.0",
"open": "^10.1.2",
"react": "^18.3.1",
"read-package-up": "^11.0.0",

View File

@@ -41,8 +41,9 @@
"ink-spinner": "^5.0.0",
"ink-text-input": "^6.0.0",
"lowlight": "^3.3.0",
"open": "^10.1.2",
"mime-types": "^2.1.4",
"node-pty": "^1.0.0",
"open": "^10.1.2",
"react": "^18.3.1",
"read-package-up": "^11.0.0",
"shell-quote": "^1.8.2",

View File

@@ -4,7 +4,6 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { spawn } from 'child_process';
import type { HistoryItemWithoutId } from '../types.js';
import type { exec as ExecType } from 'child_process';
import { useCallback } from 'react';
@@ -15,6 +14,7 @@ import crypto from 'crypto';
import path from 'path';
import os from 'os';
import fs from 'fs';
import pty from 'node-pty';
/**
* Hook to process shell commands (e.g., !ls, $pwd).
@@ -115,35 +115,32 @@ export const useShellCommandProcessor = (
},
);
} else {
const child = spawn('bash', ['-c', commandToExecute], {
const child = pty.spawn('bash', ['-c', commandToExecute], {
name: 'xterm-color',
cols: process.stdout.columns,
rows: Math.min(20, process.stdout.rows),
cwd: targetDir,
stdio: ['ignore', 'pipe', 'pipe'],
env: process.env,
});
let output = '';
const handleOutput = (data: string) => {
child.onData((data: string) => {
output += data;
setPendingHistoryItem({
type: 'info',
text: output,
});
};
child.stdout.on('data', handleOutput);
child.stderr.on('data', handleOutput);
let error: Error | null = null;
child.on('error', (err: Error) => {
error = err;
setPendingHistoryItem({ type: 'info', text: output });
});
child.on('close', (code, signal) => {
const stdinListener = (data: Buffer) => {
child.write(data.toString());
};
process.stdin.on('data', stdinListener);
child.onExit(({ exitCode, signal }) => {
process.stdin.removeListener('data', stdinListener);
setPendingHistoryItem(null);
output = output.trim() || '(Command produced no output)';
if (error) {
const text = `${error.message.replace(commandToExecute, rawQuery)}\n${output}`;
addItemToHistory({ type: 'error', text }, userMessageTimestamp);
} else if (code !== 0) {
const text = `Command exited with code ${code}\n${output}`;
if (exitCode !== 0) {
const text = `Command exited with code ${exitCode}\n${output}`;
addItemToHistory({ type: 'error', text }, userMessageTimestamp);
} else if (signal) {
const text = `Command terminated with signal ${signal}\n${output}`;

View File

@@ -14,4 +14,10 @@
(literal "/dev/stdout")
(literal "/dev/stderr")
(literal "/dev/null")
)
)
;; additional permissions for node-pty
(allow file-write*
(regex #"^/dev/tty.*")
(regex #"^/dev/ptmx$")
)

View File

@@ -72,6 +72,8 @@
(literal "/dev/stdout")
(literal "/dev/stderr")
(literal "/dev/null")
(regex #"^/dev/tty.*")
(regex #"^/dev/ptmx$")
)
;; allow outbound network connections
@@ -86,3 +88,10 @@
;; enable terminal access required by ink
;; fixes setRawMode EPERM failure (at node:tty:81:24)
(allow file-ioctl (regex #"^/dev/tty.*"))
;; additional permissions for node-pty
(allow file-write*
(regex #"^/dev/tty.*")
(regex #"^/dev/ptmx$")
)
(allow file-ioctl (regex #"^/dev/ptmx$"))