Skip to content

Commit 134bf86

Browse files
authored
fix(linux): support quoted TERMINAL values and absolute paths
1 parent abf959a commit 134bf86

1 file changed

Lines changed: 77 additions & 7 deletions

File tree

src/index.ts

Lines changed: 77 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,69 @@ async function forceKillPort(port: number): Promise<boolean> {
6666
}
6767
}
6868

69+
type LinuxTerminalLauncher = { cmd: string; args: string[] };
70+
71+
function parseCommandLine(value: string): string[] {
72+
// Handles simple quoted values in TERMINAL, e.g. "kitty -1" or '/usr/bin/xfce4-terminal --disable-server'
73+
const matches = value.match(/([^\s"']+|"[^"]*"|'[^']*')+/g);
74+
return (matches || []).map(token => token.replace(/^['"]|['"]$/g, ''));
75+
}
76+
77+
function findLinuxTerminal(): LinuxTerminalLauncher | null {
78+
const pathEnv = process.env.PATH || '';
79+
const pathDirs = pathEnv.split(':').filter(Boolean);
80+
81+
const isExecutablePath = (executablePath: string): boolean => {
82+
try {
83+
fs.accessSync(executablePath, fs.constants.X_OK);
84+
return true;
85+
} catch {
86+
return false;
87+
}
88+
};
89+
90+
const resolveExecutable = (cmd: string): string | null => {
91+
if (!cmd) return null;
92+
93+
if (cmd.includes('/')) {
94+
return isExecutablePath(cmd) ? cmd : null;
95+
}
96+
97+
for (const dir of pathDirs) {
98+
const fullPath = path.join(dir, cmd);
99+
if (isExecutablePath(fullPath)) return fullPath;
100+
}
101+
102+
return null;
103+
};
104+
105+
const terminalFromEnv = process.env.TERMINAL?.trim();
106+
if (terminalFromEnv) {
107+
const [terminalCmd, ...terminalArgs] = parseCommandLine(terminalFromEnv);
108+
const terminalPath = terminalCmd ? resolveExecutable(terminalCmd) : null;
109+
if (terminalPath) {
110+
return { cmd: terminalPath, args: [...terminalArgs, '-e'] };
111+
}
112+
}
113+
114+
const candidates: LinuxTerminalLauncher[] = [
115+
{ cmd: 'x-terminal-emulator', args: ['-e'] },
116+
{ cmd: 'gnome-terminal', args: ['--'] },
117+
{ cmd: 'konsole', args: ['-e'] },
118+
{ cmd: 'xfce4-terminal', args: ['-e'] },
119+
{ cmd: 'xterm', args: ['-e'] },
120+
];
121+
122+
for (const candidate of candidates) {
123+
const resolved = resolveExecutable(candidate.cmd);
124+
if (resolved) {
125+
return { cmd: resolved, args: candidate.args };
126+
}
127+
}
128+
129+
return null;
130+
}
131+
69132
program
70133
.name('clawd-cursor')
71134
.description('🐾 AI Desktop Agent — native screen control')
@@ -334,26 +397,33 @@ done
334397

335398
if (platform === 'win32') {
336399
// Write temp PS1 and open in new Windows Terminal / PowerShell window
337-
const fs = await import('fs');
338-
const path = await import('path');
339400
const tmpScript = path.join(os.tmpdir(), `clawd-task-${Date.now()}.ps1`);
340401
fs.writeFileSync(tmpScript, scriptContent);
341402
spawnExec('powershell.exe', [
342403
'-Command', `Start-Process powershell -ArgumentList '-NoExit','-ExecutionPolicy','Bypass','-File','${tmpScript}'`
343404
], { detached: true, stdio: 'ignore' } as any);
344405
} else if (platform === 'darwin') {
345-
const fs = await import('fs');
346-
const path = await import('path');
347406
const tmpScript = path.join(os.tmpdir(), `clawd-task-${Date.now()}.sh`);
348407
fs.writeFileSync(tmpScript, scriptContent, { mode: 0o755 });
349408
spawnExec('open', ['-a', 'Terminal', tmpScript], { detached: true, stdio: 'ignore' } as any);
350409
} else {
351410
// Linux fallback
352-
const fs = await import('fs');
353-
const path = await import('path');
354411
const tmpScript = path.join(os.tmpdir(), `clawd-task-${Date.now()}.sh`);
355412
fs.writeFileSync(tmpScript, scriptContent, { mode: 0o755 });
356-
spawnExec('x-terminal-emulator', ['-e', tmpScript], { detached: true, stdio: 'ignore' } as any);
413+
414+
const terminal = findLinuxTerminal();
415+
if (terminal) {
416+
spawnExec(terminal.cmd, [...terminal.args, tmpScript], { detached: true, stdio: 'ignore' } as any);
417+
} else {
418+
console.warn('⚠️ No supported Linux terminal emulator found. Running interactive mode in the current terminal.');
419+
process.stdout.write('\n');
420+
await new Promise<void>((resolve) => {
421+
const child = spawnExec('bash', [tmpScript], { stdio: 'inherit' } as any);
422+
child.on('close', () => resolve());
423+
child.on('error', () => resolve());
424+
});
425+
return;
426+
}
357427
}
358428

359429
console.log('🐾 Task console opened in a new terminal window.');

0 commit comments

Comments
 (0)