noninteractive 0.3.12 → 0.3.14

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.
Files changed (2) hide show
  1. package/bin/noninteractive.js +157 -20
  2. package/package.json +1 -1
@@ -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.12",
347
+ version: "0.3.14",
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)
@@ -391,7 +517,7 @@ function deriveSessionName(cmd, args) {
391
517
  const stripped = name.replace(/(?<=.)@[^/].*$/, "");
392
518
  return (stripped || name).replace(/^@[^/]+\//, "").replace(/[^a-zA-Z0-9_-]/g, "");
393
519
  }
394
- async function start(cmdArgs) {
520
+ async function start(cmdArgs, noOpen = false) {
395
521
  const executable = cmdArgs[0];
396
522
  const args = cmdArgs.slice(1);
397
523
  const name = deriveSessionName(executable, args);
@@ -400,6 +526,7 @@ async function start(cmdArgs) {
400
526
  const res = await sendMessage(sock, { action: "read" });
401
527
  if (res.ok) {
402
528
  process.stdout.write(stripAnsi(res.output ?? ""));
529
+ handleUrls(res, noOpen);
403
530
  if (res.exited) {
404
531
  console.log(`
405
532
  [session '${name}' already exists but exited ${res.exitCode} \u2014 stopping it]`);
@@ -428,11 +555,11 @@ async function start(cmdArgs) {
428
555
  });
429
556
  child.unref();
430
557
  for (let i = 0;i < 50; i++) {
431
- if (existsSync(sock))
558
+ if (existsSync2(sock))
432
559
  break;
433
560
  await new Promise((r) => setTimeout(r, 100));
434
561
  }
435
- if (!existsSync(sock)) {
562
+ if (!existsSync2(sock)) {
436
563
  console.error(`error: failed to start session '${name}'.`);
437
564
  console.error(`the command was: ${executable} ${args.join(" ")}`);
438
565
  console.error(`
@@ -445,6 +572,7 @@ make sure the command exists. examples:`);
445
572
  await new Promise((r) => setTimeout(r, 200));
446
573
  try {
447
574
  const res = await sendMessage(sock, { action: "read" });
575
+ handleUrls(res, noOpen);
448
576
  const clean = stripAnsi(res.output ?? "").trim();
449
577
  if (clean.length > 10) {
450
578
  process.stdout.write(stripAnsi(res.output));
@@ -479,7 +607,7 @@ make sure the command exists. examples:`);
479
607
  console.log(` npx noninteractive read ${name} --wait # wait for new output`);
480
608
  console.log(` npx noninteractive stop ${name} # stop the session`);
481
609
  }
482
- async function read(name, wait, timeout) {
610
+ async function read(name, wait, timeout, noOpen = false) {
483
611
  const sock = socketPath(name);
484
612
  const msg = { action: "read" };
485
613
  if (wait) {
@@ -490,16 +618,18 @@ async function read(name, wait, timeout) {
490
618
  const res = await sendMessage(sock, msg, clientTimeout);
491
619
  if (res.output !== undefined)
492
620
  process.stdout.write(stripAnsi(res.output));
621
+ handleUrls(res, noOpen);
493
622
  if (res.exited)
494
623
  console.log(`
495
624
  [exited ${res.exitCode}]`);
496
625
  }
497
- async function send(name, text, wait, timeout) {
626
+ async function send(name, text, wait, timeout, noOpen = false) {
498
627
  const sock = socketPath(name);
499
628
  if (wait) {
500
629
  const res = await sendMessage(sock, { action: "sendread", data: text, timeout }, timeout + 5000);
501
630
  if (res.output !== undefined)
502
631
  process.stdout.write(stripAnsi(res.output));
632
+ handleUrls(res, noOpen);
503
633
  if (res.exited)
504
634
  console.log(`
505
635
  [exited ${res.exitCode}]`);
@@ -542,16 +672,19 @@ async function main() {
542
672
  const cmd = args[0];
543
673
  switch (cmd) {
544
674
  case "start": {
545
- 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) {
546
678
  console.error(`usage: noninteractive start <cmd> [args...]
547
679
 
548
680
  example: npx noninteractive start npx vercel`);
549
681
  process.exit(1);
550
682
  }
551
- return start(args.slice(1));
683
+ return start(startArgs, noOpen);
552
684
  }
553
685
  case "read": {
554
686
  const readArgs = args.slice(1);
687
+ const noOpen = readArgs.includes("--no-open");
555
688
  const name = readArgs.find((a) => !a.startsWith("-"));
556
689
  if (!name) {
557
690
  console.error(`usage: noninteractive read <session> [-w|--wait] [--timeout <ms>]
@@ -562,11 +695,12 @@ example: npx noninteractive read vercel --wait`);
562
695
  const wait = readArgs.includes("-w") || readArgs.includes("--wait");
563
696
  const timeoutIdx = readArgs.indexOf("--timeout");
564
697
  const timeout = timeoutIdx !== -1 ? Number(readArgs[timeoutIdx + 1]) : 30000;
565
- return read(name, wait, timeout);
698
+ return read(name, wait, timeout, noOpen);
566
699
  }
567
700
  case "sendread":
568
701
  case "send": {
569
702
  const sendArgs = args.slice(1);
703
+ const noOpen = sendArgs.includes("--no-open");
570
704
  const positional = sendArgs.filter((a) => !a.startsWith("-"));
571
705
  const name = positional[0];
572
706
  const text = positional[1];
@@ -579,7 +713,7 @@ example: npx noninteractive send workos "" --wait`);
579
713
  const wait = cmd === "sendread" || sendArgs.includes("-w") || sendArgs.includes("--wait");
580
714
  const timeoutIdx = sendArgs.indexOf("--timeout");
581
715
  const timeout = timeoutIdx !== -1 ? Number(sendArgs[timeoutIdx + 1]) : 30000;
582
- return send(name, text, wait, timeout);
716
+ return send(name, text, wait, timeout, noOpen);
583
717
  }
584
718
  case "stop": {
585
719
  const name = args[1];
@@ -607,9 +741,12 @@ example: npx noninteractive stop vercel`);
607
741
  case "-h":
608
742
  console.log(HELP);
609
743
  break;
610
- default:
611
- console.log(`[installing and running: npx ${args.join(" ")}]`);
612
- 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
+ }
613
750
  }
614
751
  }
615
752
  main().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noninteractive",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "noninteractive": "./bin/noninteractive.js"