Skip to content

Commit f510252

Browse files
committed
feat: expose foregroundPid accessor on UnixTerminal
Add a `foregroundPid` getter that returns `tcgetpgrp(fd)` on the pty master fd, exposing the pid of the foreground process group attached to the pty (or `undefined` on failure). This is the same syscall the existing `pty_getproc` already invokes internally to resolve the foreground process *name* — it's just returning the pid instead of throwing it away. Useful for callers that need to identify which process is currently in the foreground of a terminal (e.g. to detect that an agent like claude-code is running, where the process name resolves to "node"). Cross-platform: works on both Linux and macOS via tcgetpgrp(3). Not exposed on Windows (no equivalent ConPTY concept).
1 parent 6d10f37 commit f510252

6 files changed

Lines changed: 54 additions & 0 deletions

File tree

src/interfaces.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ export interface ITerminal {
1818
*/
1919
pid: number;
2020

21+
/**
22+
* Gets the foreground process group ID, if available.
23+
*/
24+
readonly foregroundPid?: number;
25+
2126
/**
2227
* Writes data to the socket.
2328
* @param data The data to write.

src/native.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ interface IUnixNative {
1414
fork(file: string, args: string[], parsedEnv: string[], cwd: string, cols: number, rows: number, uid: number, gid: number, useUtf8: boolean, helperPath: string, onExitCallback: (code: number, signal: number) => void): IUnixProcess;
1515
open(cols: number, rows: number): IUnixOpenProcess;
1616
process(fd: number, pty?: string): string;
17+
foregroundPid(fd: number): number | undefined;
1718
resize(fd: number, cols: number, rows: number, pixelWidth: number, pixelHeight: number): void;
1819
}
1920

src/unix/pty.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ Napi::Value PtyFork(const Napi::CallbackInfo& info);
256256
Napi::Value PtyOpen(const Napi::CallbackInfo& info);
257257
Napi::Value PtyResize(const Napi::CallbackInfo& info);
258258
Napi::Value PtyGetProc(const Napi::CallbackInfo& info);
259+
Napi::Value PtyGetForegroundPid(const Napi::CallbackInfo& info);
259260

260261
/**
261262
* Functions
@@ -619,6 +620,30 @@ Napi::Value PtyGetProc(const Napi::CallbackInfo& info) {
619620
return name_;
620621
}
621622

623+
/**
624+
* Foreground Process Group PID
625+
*
626+
* Returns the pid of the foreground process group attached to the pty
627+
* referenced by `fd`, or undefined if the lookup fails. Cross-platform
628+
* (Linux + macOS) — wraps tcgetpgrp(3), which is what `process` already
629+
* calls internally to resolve the foreground process name.
630+
*/
631+
Napi::Value PtyGetForegroundPid(const Napi::CallbackInfo& info) {
632+
Napi::Env env(info.Env());
633+
Napi::HandleScope scope(env);
634+
635+
if (info.Length() != 1 || !info[0].IsNumber()) {
636+
throw Napi::Error::New(env, "Usage: pty.foregroundPid(fd)");
637+
}
638+
639+
int fd = info[0].As<Napi::Number>().Int32Value();
640+
pid_t pgrp = tcgetpgrp(fd);
641+
if (pgrp == -1) {
642+
return env.Undefined();
643+
}
644+
return Napi::Number::New(env, static_cast<double>(pgrp));
645+
}
646+
622647
/**
623648
* Nonblocking FD
624649
*/
@@ -869,6 +894,7 @@ Napi::Object init(Napi::Env env, Napi::Object exports) {
869894
exports.Set("open", Napi::Function::New(env, PtyOpen));
870895
exports.Set("resize", Napi::Function::New(env, PtyResize));
871896
exports.Set("process", Napi::Function::New(env, PtyGetProc));
897+
exports.Set("foregroundPid", Napi::Function::New(env, PtyGetForegroundPid));
872898
return exports;
873899
}
874900

src/unixTerminal.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,18 @@ if (process.platform !== 'win32') {
288288
}, 1000);
289289
});
290290
}
291+
it('should return the foreground process group pid', (done) => {
292+
const term = new UnixTerminal('node', ['-e', 'console.log("ready"); setTimeout(() => null, 200);']);
293+
term.onData((data) => {
294+
if (!data.includes('ready')) {
295+
return;
296+
}
297+
assert.strictEqual(typeof term.foregroundPid, 'number');
298+
assert.strictEqual(term.foregroundPid, term.pid);
299+
term.on('exit', () => done());
300+
term.kill();
301+
});
302+
});
291303
if (process.platform === 'darwin') {
292304
it('should return the name of the process', (done) => {
293305
const term = new UnixTerminal('/bin/echo');

src/unixTerminal.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,11 @@ export class UnixTerminal extends Terminal {
173173
/* Accessors */
174174
get fd(): number { return this._fd; }
175175
get ptsName(): string { return this._pty; }
176+
/**
177+
* Pid of the foreground process group attached to the pty, or undefined
178+
* if the lookup fails (e.g. the pty has exited). Wraps tcgetpgrp(3).
179+
*/
180+
get foregroundPid(): number | undefined { return pty.foregroundPid(this._fd); }
176181

177182
/**
178183
* openpty

typings/node-pty.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@ declare module 'node-pty' {
120120
*/
121121
readonly pid: number;
122122

123+
/**
124+
* The foreground process group ID, if available.
125+
*/
126+
readonly foregroundPid?: number;
127+
123128
/**
124129
* The column size in characters.
125130
*/

0 commit comments

Comments
 (0)