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 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...
@@ -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 { unlinkSync } from "node:fs";
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: { ...process.env, TERM: "xterm-256color" }
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
- outputBuffer += chunk.toString();
213
+ const text = chunk.toString();
214
+ outputBuffer += text;
215
+ scanForUrls(text);
124
216
  notifyWaiters();
125
217
  });
126
218
  stderr?.on("data", (chunk) => {
127
- outputBuffer += chunk.toString();
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.11",
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
- return name.replace(/^@[^/]+\//, "").replace(/[^a-zA-Z0-9_-]/g, "");
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 (existsSync(sock))
558
+ if (existsSync2(sock))
431
559
  break;
432
560
  await new Promise((r) => setTimeout(r, 100));
433
561
  }
434
- if (!existsSync(sock)) {
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
- if (args.length < 2) {
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(args.slice(1));
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
- console.log(`[installing and running: npx ${args.join(" ")}]`);
611
- return start(["npx", "--yes", ...args]);
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noninteractive",
3
- "version": "0.3.11",
3
+ "version": "0.3.13",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "noninteractive": "./bin/noninteractive.js"