noninteractive 0.3.1 → 0.3.3

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/package.json +1 -1
  2. package/src/index.ts +83 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noninteractive",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "noninteractive": "./src/index.ts"
package/src/index.ts CHANGED
@@ -5,13 +5,32 @@ import { existsSync } from "node:fs";
5
5
  import { socketPath, ensureSessionsDir } from "./paths";
6
6
  import { sendMessage } from "./client";
7
7
 
8
- const HELP = `usage: noninteractive <command> [args]
8
+ const HELP = `noninteractive run interactive CLI commands non-interactively.
9
9
 
10
- start <name> [args...] start a session (runs npx <name>)
11
- read <name> read terminal output
12
- send <name> <text> send keystrokes to session
13
- stop <name> stop a session
14
- list show active sessions`;
10
+ usage: npx noninteractive <command> [args]
11
+
12
+ commands:
13
+ start <cmd> [args...] start a session running <cmd>
14
+ read <session> read current terminal output
15
+ send <session> <text> send keystrokes (use "" for Enter)
16
+ stop <session> stop a session
17
+ list show active sessions
18
+
19
+ the first argument to "start" is the command to run, NOT a session name.
20
+ the session name is auto-derived from the command (e.g. "npx vercel" → session "vercel").
21
+
22
+ example workflow:
23
+ npx noninteractive start npx vercel # starts "npx vercel", session name = "vercel"
24
+ npx noninteractive read vercel # see what's on screen
25
+ npx noninteractive send vercel "" # press Enter
26
+ npx noninteractive send vercel "y" # type "y" and press Enter
27
+ npx noninteractive read vercel # see updated output
28
+ npx noninteractive stop vercel # done, stop the session
29
+
30
+ more examples:
31
+ npx noninteractive start npx workos # session "workos"
32
+ npx noninteractive start vercel login # session "vercel"
33
+ npx noninteractive start npx supabase init # session "supabase"`;
15
34
 
16
35
  function getSelfCommand(): string[] {
17
36
  if (process.argv[1] && /\.(ts|js)$/.test(process.argv[1])) {
@@ -20,15 +39,39 @@ function getSelfCommand(): string[] {
20
39
  return [process.argv[0]];
21
40
  }
22
41
 
23
- async function start(name: string, args: string[]) {
42
+ function deriveSessionName(cmd: string, args: string[]): string {
43
+ const parts = [cmd, ...args];
44
+ // skip npx/bunx prefix to get the real command name
45
+ let i = 0;
46
+ if (parts[i] === "npx" || parts[i] === "bunx") i++;
47
+ // skip flags like -y, --yes
48
+ while (i < parts.length && parts[i].startsWith("-")) i++;
49
+ const name = parts[i] || cmd;
50
+ // strip npm scope @foo/bar -> bar
51
+ return name.replace(/^@[^/]+\//, "").replace(/[^a-zA-Z0-9_-]/g, "");
52
+ }
53
+
54
+ async function start(cmdArgs: string[]) {
55
+ const executable = cmdArgs[0];
56
+ const args = cmdArgs.slice(1);
57
+ const name = deriveSessionName(executable, args);
24
58
  const sock = socketPath(name);
25
59
 
26
60
  try {
27
61
  const res = await sendMessage(sock, { action: "read" });
28
62
  if (res.ok) {
29
63
  process.stdout.write(res.output ?? "");
30
- console.log(`\n[session '${name}' already running]`);
31
- return;
64
+ if (res.exited) {
65
+ console.log(`\n[session '${name}' already exists but exited ${res.exitCode} — stopping it]`);
66
+ try { await sendMessage(sock, { action: "stop" }); } catch {}
67
+ // fall through to start a new session
68
+ } else {
69
+ console.log(`\n[session '${name}' already running — read the output above, then use:]`);
70
+ console.log(` npx noninteractive send ${name} "<text>" # send keystrokes (use "" for Enter)`);
71
+ console.log(` npx noninteractive read ${name} # read updated output`);
72
+ console.log(` npx noninteractive stop ${name} # stop the session`);
73
+ return;
74
+ }
32
75
  }
33
76
  } catch {}
34
77
 
@@ -36,7 +79,7 @@ async function start(name: string, args: string[]) {
36
79
  try { const { unlinkSync } = await import("node:fs"); unlinkSync(sock); } catch {}
37
80
 
38
81
  const self = getSelfCommand();
39
- const child = spawn(self[0], [...self.slice(1), "__daemon__", name, "npx", name, ...args], {
82
+ const child = spawn(self[0], [...self.slice(1), "__daemon__", name, executable, ...args], {
40
83
  detached: true,
41
84
  stdio: "ignore",
42
85
  });
@@ -49,7 +92,11 @@ async function start(name: string, args: string[]) {
49
92
  }
50
93
 
51
94
  if (!existsSync(sock)) {
52
- console.error("timeout: failed to start session");
95
+ console.error(`error: failed to start session '${name}'.`);
96
+ console.error(`the command was: ${executable} ${args.join(" ")}`);
97
+ console.error(`\nmake sure the command exists. examples:`);
98
+ console.error(` npx noninteractive start npx vercel # run an npx package`);
99
+ console.error(` npx noninteractive start vercel login # run a command directly`);
53
100
  process.exit(1);
54
101
  }
55
102
 
@@ -62,21 +109,34 @@ async function start(name: string, args: string[]) {
62
109
  const clean = stripAnsi(res.output ?? "").trim();
63
110
  if (clean.length > 10) {
64
111
  process.stdout.write(res.output);
65
- console.log(`\n[session '${name}' started]`);
112
+ if (res.exited) {
113
+ console.log(`\n[session '${name}' exited ${res.exitCode} — the command failed]`);
114
+ console.log(`hint: the first argument to "start" is the command to run, NOT a session name.`);
115
+ console.log(` npx noninteractive start npx vercel # run an npx package`);
116
+ console.log(` npx noninteractive start vercel login # run a command directly`);
117
+ } else {
118
+ console.log(`\n[session '${name}' started — read the output above, then use:]`);
119
+ console.log(` npx noninteractive send ${name} "<text>" # send keystrokes (use "" for Enter)`);
120
+ console.log(` npx noninteractive read ${name} # read updated output`);
121
+ console.log(` npx noninteractive stop ${name} # stop the session`);
122
+ }
66
123
  return;
67
124
  }
68
125
  if (res.exited) {
69
126
  process.stdout.write(res.output ?? "");
70
- console.log(`\n[session '${name}' exited ${res.exitCode}]`);
127
+ console.log(`\n[session '${name}' exited ${res.exitCode} — the command failed]`);
128
+ console.log(`hint: the first argument to "start" is the command to run, NOT a session name.`);
129
+ console.log(` npx noninteractive start npx vercel # run an npx package`);
130
+ console.log(` npx noninteractive start vercel login # run a command directly`);
71
131
  return;
72
132
  }
73
133
  } catch {}
74
134
  }
75
135
 
76
- console.log(`[session '${name}' started]`);
77
-
78
- console.error("timeout: failed to start session");
79
- process.exit(1);
136
+ console.log(`[session '${name}' started but no output yet — use:]`);
137
+ console.log(` npx noninteractive read ${name} # read output`);
138
+ console.log(` npx noninteractive send ${name} "<text>" # send keystrokes (use "" for Enter)`);
139
+ console.log(` npx noninteractive stop ${name} # stop the session`);
80
140
  }
81
141
 
82
142
  async function read(name: string) {
@@ -89,6 +149,7 @@ async function read(name: string) {
89
149
  async function send(name: string, text: string) {
90
150
  const sock = socketPath(name);
91
151
  await sendMessage(sock, { action: "send", data: text });
152
+ console.log(`[sent to '${name}' — run "npx noninteractive read ${name}" to see the result]`);
92
153
  }
93
154
 
94
155
  async function stop(name: string) {
@@ -132,24 +193,23 @@ async function main() {
132
193
 
133
194
  switch (cmd) {
134
195
  case "start": {
135
- const name = args[1];
136
- if (!name) { console.error("usage: noninteractive start <name> [args...]"); process.exit(1); }
137
- return start(name, args.slice(2));
196
+ if (args.length < 2) { console.error("usage: noninteractive start <cmd> [args...]\n\nexample: npx noninteractive start npx vercel"); process.exit(1); }
197
+ return start(args.slice(1));
138
198
  }
139
199
  case "read": {
140
200
  const name = args[1];
141
- if (!name) { console.error("usage: noninteractive read <name>"); process.exit(1); }
201
+ if (!name) { console.error("usage: noninteractive read <session>\n\nexample: npx noninteractive read vercel"); process.exit(1); }
142
202
  return read(name);
143
203
  }
144
204
  case "send": {
145
205
  const name = args[1];
146
206
  const text = args[2];
147
- if (!name || text === undefined) { console.error("usage: noninteractive send <name> <text>"); process.exit(1); }
207
+ if (!name || text === undefined) { console.error("usage: noninteractive send <session> <text>\n\nexample: npx noninteractive send vercel \"y\""); process.exit(1); }
148
208
  return send(name, text);
149
209
  }
150
210
  case "stop": {
151
211
  const name = args[1];
152
- if (!name) { console.error("usage: noninteractive stop <name>"); process.exit(1); }
212
+ if (!name) { console.error("usage: noninteractive stop <session>\n\nexample: npx noninteractive stop vercel"); process.exit(1); }
153
213
  return stop(name);
154
214
  }
155
215
  case "list":