noninteractive 0.3.11 → 0.3.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/noninteractive.js +159 -21
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,7 +61,7 @@ npx noninteractive stop workos
|
|
|
61
61
|
|
|
62
62
|
## The `--wait` flag
|
|
63
63
|
|
|
64
|
-
Both `send` and `read` support `--wait` (`-w`), which blocks until new output appears instead of returning immediately. This eliminates polling loops and reduces tool calls by ~7-10x.
|
|
64
|
+
Both `send` and `read` support `--wait` (`-w`), which blocks until new output appears instead of returning immediately. This eliminates polling loops and reduces tool calls by ~7-10x. Output is returned as clean text with ANSI escape codes stripped by default, so agents get readable content without terminal formatting noise.
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
67
|
# Old way (polling): send + read + read + read...
|
package/bin/noninteractive.js
CHANGED
|
@@ -34,6 +34,9 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
34
34
|
var exports_paths = {};
|
|
35
35
|
__export(exports_paths, {
|
|
36
36
|
socketPath: () => socketPath,
|
|
37
|
+
sessionUrlsFile: () => sessionUrlsFile,
|
|
38
|
+
sessionDir: () => sessionDir,
|
|
39
|
+
sessionBinDir: () => sessionBinDir,
|
|
37
40
|
ensureSessionsDir: () => ensureSessionsDir,
|
|
38
41
|
SESSIONS_DIR: () => SESSIONS_DIR
|
|
39
42
|
});
|
|
@@ -46,6 +49,15 @@ function ensureSessionsDir() {
|
|
|
46
49
|
function socketPath(name) {
|
|
47
50
|
return resolve(SESSIONS_DIR, `${name}.sock`);
|
|
48
51
|
}
|
|
52
|
+
function sessionDir(name) {
|
|
53
|
+
return resolve(SESSIONS_DIR, name);
|
|
54
|
+
}
|
|
55
|
+
function sessionBinDir(name) {
|
|
56
|
+
return resolve(SESSIONS_DIR, name, "bin");
|
|
57
|
+
}
|
|
58
|
+
function sessionUrlsFile(name) {
|
|
59
|
+
return resolve(SESSIONS_DIR, name, "urls");
|
|
60
|
+
}
|
|
49
61
|
var SESSIONS_DIR;
|
|
50
62
|
var init_paths = __esm(() => {
|
|
51
63
|
SESSIONS_DIR = resolve(homedir(), ".noninteractive", "sessions");
|
|
@@ -61,6 +73,15 @@ function ensureSessionsDir2() {
|
|
|
61
73
|
function socketPath2(name) {
|
|
62
74
|
return resolve2(SESSIONS_DIR2, `${name}.sock`);
|
|
63
75
|
}
|
|
76
|
+
function sessionDir2(name) {
|
|
77
|
+
return resolve2(SESSIONS_DIR2, name);
|
|
78
|
+
}
|
|
79
|
+
function sessionBinDir2(name) {
|
|
80
|
+
return resolve2(SESSIONS_DIR2, name, "bin");
|
|
81
|
+
}
|
|
82
|
+
function sessionUrlsFile2(name) {
|
|
83
|
+
return resolve2(SESSIONS_DIR2, name, "urls");
|
|
84
|
+
}
|
|
64
85
|
var SESSIONS_DIR2;
|
|
65
86
|
var init_paths2 = __esm(() => {
|
|
66
87
|
SESSIONS_DIR2 = resolve2(homedir2(), ".noninteractive", "sessions");
|
|
@@ -72,7 +93,15 @@ __export(exports_daemon, {
|
|
|
72
93
|
runDaemon: () => runDaemon
|
|
73
94
|
});
|
|
74
95
|
import { spawn } from "node:child_process";
|
|
75
|
-
import {
|
|
96
|
+
import {
|
|
97
|
+
chmodSync,
|
|
98
|
+
existsSync,
|
|
99
|
+
mkdirSync as mkdirSync3,
|
|
100
|
+
readFileSync,
|
|
101
|
+
rmSync,
|
|
102
|
+
unlinkSync,
|
|
103
|
+
writeFileSync
|
|
104
|
+
} from "node:fs";
|
|
76
105
|
import { createServer } from "node:net";
|
|
77
106
|
import { dirname, resolve as resolve3 } from "node:path";
|
|
78
107
|
function getPtyBridge() {
|
|
@@ -95,15 +124,48 @@ function getPtyBridge() {
|
|
|
95
124
|
}
|
|
96
125
|
return candidates[0];
|
|
97
126
|
}
|
|
127
|
+
function createInterceptorScripts(name) {
|
|
128
|
+
const binDir = sessionBinDir2(name);
|
|
129
|
+
const urlsFile = sessionUrlsFile2(name);
|
|
130
|
+
mkdirSync3(binDir, { recursive: true });
|
|
131
|
+
const openScript = `#!/bin/sh
|
|
132
|
+
case "$1" in
|
|
133
|
+
http://*|https://*) echo "$1" >> "${urlsFile}" ;;
|
|
134
|
+
*) /usr/bin/open "$@" ;;
|
|
135
|
+
esac
|
|
136
|
+
`;
|
|
137
|
+
const xdgOpenScript = `#!/bin/sh
|
|
138
|
+
echo "$1" >> "${urlsFile}"
|
|
139
|
+
`;
|
|
140
|
+
const browserOpenScript = `#!/bin/sh
|
|
141
|
+
echo "$1" >> "${urlsFile}"
|
|
142
|
+
`;
|
|
143
|
+
for (const [file, content] of [
|
|
144
|
+
["open", openScript],
|
|
145
|
+
["xdg-open", xdgOpenScript],
|
|
146
|
+
["browser-open", browserOpenScript]
|
|
147
|
+
]) {
|
|
148
|
+
const path = resolve3(binDir, file);
|
|
149
|
+
writeFileSync(path, content);
|
|
150
|
+
chmodSync(path, 493);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function cleanupSession(name) {
|
|
154
|
+
try {
|
|
155
|
+
rmSync(sessionDir2(name), { recursive: true, force: true });
|
|
156
|
+
} catch {}
|
|
157
|
+
}
|
|
98
158
|
function runDaemon(sessionName, executable, args) {
|
|
99
159
|
ensureSessionsDir2();
|
|
100
160
|
const sock = socketPath2(sessionName);
|
|
101
161
|
try {
|
|
102
162
|
unlinkSync(sock);
|
|
103
163
|
} catch {}
|
|
164
|
+
createInterceptorScripts(sessionName);
|
|
104
165
|
let outputBuffer = "";
|
|
105
166
|
let processExited = false;
|
|
106
167
|
let exitCode = null;
|
|
168
|
+
const detectedUrls = new Set;
|
|
107
169
|
const waiters = [];
|
|
108
170
|
function notifyWaiters() {
|
|
109
171
|
let w = waiters.shift();
|
|
@@ -113,18 +175,50 @@ function runDaemon(sessionName, executable, args) {
|
|
|
113
175
|
w = waiters.shift();
|
|
114
176
|
}
|
|
115
177
|
}
|
|
178
|
+
const binDir = sessionBinDir2(sessionName);
|
|
116
179
|
const ptyBridge = getPtyBridge();
|
|
117
180
|
const proc = spawn(ptyBridge, [executable, ...args], {
|
|
118
181
|
stdio: ["pipe", "pipe", "pipe"],
|
|
119
|
-
env: {
|
|
182
|
+
env: {
|
|
183
|
+
...process.env,
|
|
184
|
+
TERM: "xterm-256color",
|
|
185
|
+
BROWSER: resolve3(binDir, "browser-open"),
|
|
186
|
+
PATH: `${binDir}:${process.env.PATH}`
|
|
187
|
+
}
|
|
120
188
|
});
|
|
121
189
|
const { stdout, stderr, stdin } = proc;
|
|
190
|
+
function scanForUrls(text) {
|
|
191
|
+
const matches = text.match(URL_RE);
|
|
192
|
+
if (matches) {
|
|
193
|
+
for (const url of matches)
|
|
194
|
+
detectedUrls.add(url);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function readInterceptedUrls() {
|
|
198
|
+
const urlsFile = sessionUrlsFile2(sessionName);
|
|
199
|
+
try {
|
|
200
|
+
if (!existsSync(urlsFile))
|
|
201
|
+
return;
|
|
202
|
+
const content = readFileSync(urlsFile, "utf-8");
|
|
203
|
+
const lines = content.split(`
|
|
204
|
+
`);
|
|
205
|
+
for (const line of lines) {
|
|
206
|
+
const trimmed = line.trim();
|
|
207
|
+
if (trimmed)
|
|
208
|
+
detectedUrls.add(trimmed);
|
|
209
|
+
}
|
|
210
|
+
} catch {}
|
|
211
|
+
}
|
|
122
212
|
stdout?.on("data", (chunk) => {
|
|
123
|
-
|
|
213
|
+
const text = chunk.toString();
|
|
214
|
+
outputBuffer += text;
|
|
215
|
+
scanForUrls(text);
|
|
124
216
|
notifyWaiters();
|
|
125
217
|
});
|
|
126
218
|
stderr?.on("data", (chunk) => {
|
|
127
|
-
|
|
219
|
+
const text = chunk.toString();
|
|
220
|
+
outputBuffer += text;
|
|
221
|
+
scanForUrls(text);
|
|
128
222
|
notifyWaiters();
|
|
129
223
|
});
|
|
130
224
|
proc.on("exit", (code) => {
|
|
@@ -138,6 +232,7 @@ function runDaemon(sessionName, executable, args) {
|
|
|
138
232
|
try {
|
|
139
233
|
unlinkSync(sock);
|
|
140
234
|
} catch {}
|
|
235
|
+
cleanupSession(sessionName);
|
|
141
236
|
process.exit(0);
|
|
142
237
|
}, 60000);
|
|
143
238
|
});
|
|
@@ -158,11 +253,14 @@ function runDaemon(sessionName, executable, args) {
|
|
|
158
253
|
});
|
|
159
254
|
});
|
|
160
255
|
function respondWithOutput(socket) {
|
|
256
|
+
readInterceptedUrls();
|
|
257
|
+
const urls = Array.from(detectedUrls);
|
|
161
258
|
socket.end(JSON.stringify({
|
|
162
259
|
ok: true,
|
|
163
260
|
output: outputBuffer,
|
|
164
261
|
exited: processExited,
|
|
165
|
-
exitCode
|
|
262
|
+
exitCode,
|
|
263
|
+
...urls.length > 0 ? { urls } : {}
|
|
166
264
|
}));
|
|
167
265
|
}
|
|
168
266
|
function waitForNewOutput(socket, sinceLength, timeout) {
|
|
@@ -218,6 +316,7 @@ function runDaemon(sessionName, executable, args) {
|
|
|
218
316
|
try {
|
|
219
317
|
unlinkSync(sock);
|
|
220
318
|
} catch {}
|
|
319
|
+
cleanupSession(sessionName);
|
|
221
320
|
process.exit(0);
|
|
222
321
|
}, 500);
|
|
223
322
|
break;
|
|
@@ -235,15 +334,17 @@ function runDaemon(sessionName, executable, args) {
|
|
|
235
334
|
}
|
|
236
335
|
server.listen(sock);
|
|
237
336
|
}
|
|
337
|
+
var URL_RE;
|
|
238
338
|
var init_daemon = __esm(() => {
|
|
239
339
|
init_paths2();
|
|
340
|
+
URL_RE = /https?:\/\/[^\s<>"')\]]+/g;
|
|
240
341
|
});
|
|
241
342
|
|
|
242
343
|
// package.json
|
|
243
344
|
var require_package = __commonJS((exports, module) => {
|
|
244
345
|
module.exports = {
|
|
245
346
|
name: "noninteractive",
|
|
246
|
-
version: "0.3.
|
|
347
|
+
version: "0.3.13",
|
|
247
348
|
type: "module",
|
|
248
349
|
bin: {
|
|
249
350
|
noninteractive: "./bin/noninteractive.js"
|
|
@@ -272,8 +373,8 @@ var require_package = __commonJS((exports, module) => {
|
|
|
272
373
|
});
|
|
273
374
|
|
|
274
375
|
// src/index.ts
|
|
275
|
-
import { spawn as spawn2 } from "child_process";
|
|
276
|
-
import { existsSync } from "fs";
|
|
376
|
+
import { execSync, spawn as spawn2 } from "child_process";
|
|
377
|
+
import { existsSync as existsSync2 } from "fs";
|
|
277
378
|
|
|
278
379
|
// src/client.ts
|
|
279
380
|
import { createConnection } from "node:net";
|
|
@@ -348,6 +449,7 @@ commands:
|
|
|
348
449
|
flags:
|
|
349
450
|
--wait, -w block until new output appears (for send and read)
|
|
350
451
|
--timeout <ms> max wait time in ms (default: 30000, used with --wait)
|
|
452
|
+
--no-open don't auto-open URLs in browser (still shown in output)
|
|
351
453
|
|
|
352
454
|
the session name is auto-derived from the tool (e.g. "workos" \u2192 session "workos").
|
|
353
455
|
|
|
@@ -364,6 +466,30 @@ more examples:
|
|
|
364
466
|
npx noninteractive start vercel login # explicit start for non-npx commands`;
|
|
365
467
|
var stripAnsi = (s) => s.replace(/\x1b\[[\x20-\x3f]*[\x40-\x7e]|\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[()][A-Z0-9]|\x1b[\x20-\x2f]*[\x30-\x7e]|\x07/g, "").replace(/\r\n?/g, `
|
|
366
468
|
`);
|
|
469
|
+
var seenUrls = new Set;
|
|
470
|
+
function openUrl(url) {
|
|
471
|
+
try {
|
|
472
|
+
const cmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
473
|
+
execSync(`${cmd} ${JSON.stringify(url)}`, { stdio: "ignore" });
|
|
474
|
+
} catch {}
|
|
475
|
+
}
|
|
476
|
+
function handleUrls(res, noOpen) {
|
|
477
|
+
if (!res.urls || res.urls.length === 0)
|
|
478
|
+
return;
|
|
479
|
+
for (const url of res.urls) {
|
|
480
|
+
if (seenUrls.has(url))
|
|
481
|
+
continue;
|
|
482
|
+
seenUrls.add(url);
|
|
483
|
+
if (!noOpen) {
|
|
484
|
+
openUrl(url);
|
|
485
|
+
process.stderr.write(`[opened: ${url}]
|
|
486
|
+
`);
|
|
487
|
+
} else {
|
|
488
|
+
process.stderr.write(`[url: ${url}]
|
|
489
|
+
`);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
367
493
|
function getSelfCommand() {
|
|
368
494
|
const script = process.argv[1];
|
|
369
495
|
if (!script)
|
|
@@ -388,9 +514,10 @@ function deriveSessionName(cmd, args) {
|
|
|
388
514
|
while (i < parts.length && parts[i].startsWith("-"))
|
|
389
515
|
i++;
|
|
390
516
|
const name = parts[i] || cmd;
|
|
391
|
-
|
|
517
|
+
const stripped = name.replace(/(?<=.)@[^/].*$/, "");
|
|
518
|
+
return (stripped || name).replace(/^@[^/]+\//, "").replace(/[^a-zA-Z0-9_-]/g, "");
|
|
392
519
|
}
|
|
393
|
-
async function start(cmdArgs) {
|
|
520
|
+
async function start(cmdArgs, noOpen = false) {
|
|
394
521
|
const executable = cmdArgs[0];
|
|
395
522
|
const args = cmdArgs.slice(1);
|
|
396
523
|
const name = deriveSessionName(executable, args);
|
|
@@ -399,6 +526,7 @@ async function start(cmdArgs) {
|
|
|
399
526
|
const res = await sendMessage(sock, { action: "read" });
|
|
400
527
|
if (res.ok) {
|
|
401
528
|
process.stdout.write(stripAnsi(res.output ?? ""));
|
|
529
|
+
handleUrls(res, noOpen);
|
|
402
530
|
if (res.exited) {
|
|
403
531
|
console.log(`
|
|
404
532
|
[session '${name}' already exists but exited ${res.exitCode} \u2014 stopping it]`);
|
|
@@ -427,11 +555,11 @@ async function start(cmdArgs) {
|
|
|
427
555
|
});
|
|
428
556
|
child.unref();
|
|
429
557
|
for (let i = 0;i < 50; i++) {
|
|
430
|
-
if (
|
|
558
|
+
if (existsSync2(sock))
|
|
431
559
|
break;
|
|
432
560
|
await new Promise((r) => setTimeout(r, 100));
|
|
433
561
|
}
|
|
434
|
-
if (!
|
|
562
|
+
if (!existsSync2(sock)) {
|
|
435
563
|
console.error(`error: failed to start session '${name}'.`);
|
|
436
564
|
console.error(`the command was: ${executable} ${args.join(" ")}`);
|
|
437
565
|
console.error(`
|
|
@@ -444,6 +572,7 @@ make sure the command exists. examples:`);
|
|
|
444
572
|
await new Promise((r) => setTimeout(r, 200));
|
|
445
573
|
try {
|
|
446
574
|
const res = await sendMessage(sock, { action: "read" });
|
|
575
|
+
handleUrls(res, noOpen);
|
|
447
576
|
const clean = stripAnsi(res.output ?? "").trim();
|
|
448
577
|
if (clean.length > 10) {
|
|
449
578
|
process.stdout.write(stripAnsi(res.output));
|
|
@@ -478,7 +607,7 @@ make sure the command exists. examples:`);
|
|
|
478
607
|
console.log(` npx noninteractive read ${name} --wait # wait for new output`);
|
|
479
608
|
console.log(` npx noninteractive stop ${name} # stop the session`);
|
|
480
609
|
}
|
|
481
|
-
async function read(name, wait, timeout) {
|
|
610
|
+
async function read(name, wait, timeout, noOpen = false) {
|
|
482
611
|
const sock = socketPath(name);
|
|
483
612
|
const msg = { action: "read" };
|
|
484
613
|
if (wait) {
|
|
@@ -489,16 +618,18 @@ async function read(name, wait, timeout) {
|
|
|
489
618
|
const res = await sendMessage(sock, msg, clientTimeout);
|
|
490
619
|
if (res.output !== undefined)
|
|
491
620
|
process.stdout.write(stripAnsi(res.output));
|
|
621
|
+
handleUrls(res, noOpen);
|
|
492
622
|
if (res.exited)
|
|
493
623
|
console.log(`
|
|
494
624
|
[exited ${res.exitCode}]`);
|
|
495
625
|
}
|
|
496
|
-
async function send(name, text, wait, timeout) {
|
|
626
|
+
async function send(name, text, wait, timeout, noOpen = false) {
|
|
497
627
|
const sock = socketPath(name);
|
|
498
628
|
if (wait) {
|
|
499
629
|
const res = await sendMessage(sock, { action: "sendread", data: text, timeout }, timeout + 5000);
|
|
500
630
|
if (res.output !== undefined)
|
|
501
631
|
process.stdout.write(stripAnsi(res.output));
|
|
632
|
+
handleUrls(res, noOpen);
|
|
502
633
|
if (res.exited)
|
|
503
634
|
console.log(`
|
|
504
635
|
[exited ${res.exitCode}]`);
|
|
@@ -541,16 +672,19 @@ async function main() {
|
|
|
541
672
|
const cmd = args[0];
|
|
542
673
|
switch (cmd) {
|
|
543
674
|
case "start": {
|
|
544
|
-
|
|
675
|
+
const startArgs = args.slice(1).filter((a) => a !== "--no-open");
|
|
676
|
+
const noOpen = args.includes("--no-open");
|
|
677
|
+
if (startArgs.length < 1) {
|
|
545
678
|
console.error(`usage: noninteractive start <cmd> [args...]
|
|
546
679
|
|
|
547
680
|
example: npx noninteractive start npx vercel`);
|
|
548
681
|
process.exit(1);
|
|
549
682
|
}
|
|
550
|
-
return start(
|
|
683
|
+
return start(startArgs, noOpen);
|
|
551
684
|
}
|
|
552
685
|
case "read": {
|
|
553
686
|
const readArgs = args.slice(1);
|
|
687
|
+
const noOpen = readArgs.includes("--no-open");
|
|
554
688
|
const name = readArgs.find((a) => !a.startsWith("-"));
|
|
555
689
|
if (!name) {
|
|
556
690
|
console.error(`usage: noninteractive read <session> [-w|--wait] [--timeout <ms>]
|
|
@@ -561,11 +695,12 @@ example: npx noninteractive read vercel --wait`);
|
|
|
561
695
|
const wait = readArgs.includes("-w") || readArgs.includes("--wait");
|
|
562
696
|
const timeoutIdx = readArgs.indexOf("--timeout");
|
|
563
697
|
const timeout = timeoutIdx !== -1 ? Number(readArgs[timeoutIdx + 1]) : 30000;
|
|
564
|
-
return read(name, wait, timeout);
|
|
698
|
+
return read(name, wait, timeout, noOpen);
|
|
565
699
|
}
|
|
566
700
|
case "sendread":
|
|
567
701
|
case "send": {
|
|
568
702
|
const sendArgs = args.slice(1);
|
|
703
|
+
const noOpen = sendArgs.includes("--no-open");
|
|
569
704
|
const positional = sendArgs.filter((a) => !a.startsWith("-"));
|
|
570
705
|
const name = positional[0];
|
|
571
706
|
const text = positional[1];
|
|
@@ -578,7 +713,7 @@ example: npx noninteractive send workos "" --wait`);
|
|
|
578
713
|
const wait = cmd === "sendread" || sendArgs.includes("-w") || sendArgs.includes("--wait");
|
|
579
714
|
const timeoutIdx = sendArgs.indexOf("--timeout");
|
|
580
715
|
const timeout = timeoutIdx !== -1 ? Number(sendArgs[timeoutIdx + 1]) : 30000;
|
|
581
|
-
return send(name, text, wait, timeout);
|
|
716
|
+
return send(name, text, wait, timeout, noOpen);
|
|
582
717
|
}
|
|
583
718
|
case "stop": {
|
|
584
719
|
const name = args[1];
|
|
@@ -606,9 +741,12 @@ example: npx noninteractive stop vercel`);
|
|
|
606
741
|
case "-h":
|
|
607
742
|
console.log(HELP);
|
|
608
743
|
break;
|
|
609
|
-
default:
|
|
610
|
-
|
|
611
|
-
|
|
744
|
+
default: {
|
|
745
|
+
const noOpen = args.includes("--no-open");
|
|
746
|
+
const filteredArgs = args.filter((a) => a !== "--no-open");
|
|
747
|
+
console.log(`[installing and running: npx ${filteredArgs.join(" ")}]`);
|
|
748
|
+
return start(["npx", "--yes", ...filteredArgs], noOpen);
|
|
749
|
+
}
|
|
612
750
|
}
|
|
613
751
|
}
|
|
614
752
|
main().catch((err) => {
|