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.
- package/package.json +1 -1
- package/src/index.ts +83 -23
package/package.json
CHANGED
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 = `
|
|
8
|
+
const HELP = `noninteractive — run interactive CLI commands non-interactively.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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.
|
|
79
|
-
|
|
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
|
-
|
|
136
|
-
|
|
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 <
|
|
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 <
|
|
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 <
|
|
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":
|