nova-control-command 0.0.1
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/LICENSE.md +9 -0
- package/README.md +227 -0
- package/dist/nova-control-command.js +215 -0
- package/package.json +44 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present Andreas Rozek
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
# nova-control-command
|
|
2
|
+
|
|
3
|
+
CLI for controlling a **NOVA DIY Artificial Intelligence Robot** by Creoqode. Provides one-shot commands, an interactive REPL shell, and a batch script runner for moving servos, running motion sequences, and inspecting the current servo state.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
| requirement | details |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| **Node.js 22+** | required to run `nova-control`. Download from [nodejs.org](https://nodejs.org). |
|
|
12
|
+
| **NOVA robot** | connected via USB serial. The Arduino sketch must be flashed and running before issuing commands. |
|
|
13
|
+
| **Linux serial permissions** | on Linux, add your user to the `dialout` group: `sudo usermod -aG dialout $USER` (re-login required). |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install nova-control-command
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
After installation the `nova-control` binary is available in the project's `node_modules/.bin/` directory and — when installed globally — directly on the `PATH`.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Concepts
|
|
28
|
+
|
|
29
|
+
| mode | how to trigger |
|
|
30
|
+
| --- | --- |
|
|
31
|
+
| one-shot | `nova-control --port <path> <command> [args]` — runs a single command and exits |
|
|
32
|
+
| script | `nova-control --port <path> --script <file>` — reads commands line-by-line from a file or stdin (`-`) |
|
|
33
|
+
| REPL | `nova-control --port <path> shell` — opens an interactive read-eval-print loop |
|
|
34
|
+
|
|
35
|
+
`nova-control` without any arguments prints the help text and exits.
|
|
36
|
+
|
|
37
|
+
In script and REPL mode the serial connection is opened once on the first servo command and kept alive for the duration of the session — `wait` and `help` do not touch the port.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Global options
|
|
42
|
+
|
|
43
|
+
| option | description |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `--port <path>` | serial port path (e.g. `/dev/ttyACM0` on Linux/macOS, `COM3` on Windows) |
|
|
46
|
+
| `--baud <rate>` | baud rate (default: `9600`) |
|
|
47
|
+
| `--on-error <mode>` | script error mode: `stop` (default), `continue`, or `ask` |
|
|
48
|
+
| `--script <file>` | run commands from a file (`-` reads from stdin) |
|
|
49
|
+
| `--version` | print version number and exit |
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Command reference
|
|
54
|
+
|
|
55
|
+
### `home`
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
nova-control --port <path> home
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Sends all five servos to their home positions simultaneously. Home positions are baked into the firmware (`s1=90°`, `s2=90°`, `s3=110°`, `s4=90°`, `s5=95°`).
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
### Individual servo commands
|
|
66
|
+
|
|
67
|
+
Each command sets exactly one servo and leaves all others at their last-sent positions.
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
nova-control --port <path> shift-to <deg> # head forward (>90°) or back (<90°) — s1
|
|
71
|
+
nova-control --port <path> roll-to <deg> # head clockwise (>90°) or counter-clockwise (<90°) — s2
|
|
72
|
+
nova-control --port <path> pitch-to <deg> # head up (>110°) or down (<110°) — s3
|
|
73
|
+
nova-control --port <path> rotate-to <deg> # body rotation around Z-axis — s4
|
|
74
|
+
nova-control --port <path> lift-to <deg> # secondary head lift, range 20°–150° — s5
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`<deg>` is a number in degrees. The firmware clamps out-of-range values silently.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### `move`
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
nova-control --port <path> move [--shift-to <deg>] [--roll-to <deg>] [--pitch-to <deg>]
|
|
85
|
+
[--rotate-to <deg>] [--lift-to <deg>]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Sets multiple servos in a single packet. At least one option is required. Servos not mentioned stay at their last-sent positions. Useful when two or more joints must move simultaneously.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### `wait`
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
nova-control --port <path> wait <ms>
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Pauses execution for `<ms>` milliseconds before the next command. Does not open the serial connection. Particularly useful in scripts and the REPL to let the robot settle between moves.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### `state`
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
nova-control --port <path> state
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Prints the current servo state as a single JSON object to stdout. The state reflects what was last *sent* to the Arduino — there is no read-back channel in the protocol.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
### `shell`
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
nova-control --port <path> shell
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Opens an interactive REPL. Each line is parsed and executed as a `nova-control` command without the `nova-control` prefix. Blank lines and lines starting with `#` are ignored. Type `exit` or `quit` to close the session.
|
|
119
|
+
|
|
120
|
+
The serial port is opened on the first command that needs it and held open for the entire session.
|
|
121
|
+
|
|
122
|
+
**Getting help inside the REPL:**
|
|
123
|
+
|
|
124
|
+
| what you type | what you get |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| `help` | list of all available commands |
|
|
127
|
+
| `<command> --help` | help for a specific command (e.g. `move --help`) |
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
### `--script`
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
nova-control --port <path> --script <file>
|
|
135
|
+
nova-control --port <path> --script - # read from stdin
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Reads commands from a file (or stdin), executing them one per line. Lines starting with `#` and blank lines are ignored. Error handling follows `--on-error`: `stop` aborts on the first error (default), `continue` keeps going, `ask` prompts interactively (TTY only, falls back to `stop` in pipes).
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Examples
|
|
143
|
+
|
|
144
|
+
### One-shot commands
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# send all servos home
|
|
148
|
+
nova-control --port /dev/ttyACM0 home
|
|
149
|
+
|
|
150
|
+
# move the head forward and look up
|
|
151
|
+
nova-control --port /dev/ttyACM0 shift-to 110
|
|
152
|
+
nova-control --port /dev/ttyACM0 pitch-to 130
|
|
153
|
+
|
|
154
|
+
# rotate the body 30° clockwise from centre
|
|
155
|
+
nova-control --port /dev/ttyACM0 rotate-to 120
|
|
156
|
+
|
|
157
|
+
# move head and body simultaneously in one packet
|
|
158
|
+
nova-control --port /dev/ttyACM0 move --shift-to 100 --rotate-to 120
|
|
159
|
+
|
|
160
|
+
# check the current state
|
|
161
|
+
nova-control --port /dev/ttyACM0 state
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### Motion script
|
|
167
|
+
|
|
168
|
+
A script file runs a complete motion sequence from a file, pausing between moves with `wait`:
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
# greeting.nova — wave the head left and right
|
|
172
|
+
home
|
|
173
|
+
wait 500
|
|
174
|
+
shift-to 110
|
|
175
|
+
wait 400
|
|
176
|
+
shift-to 70
|
|
177
|
+
wait 400
|
|
178
|
+
shift-to 110
|
|
179
|
+
wait 400
|
|
180
|
+
home
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Run it:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
nova-control --port /dev/ttyACM0 --script greeting.nova
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Pipe a script from stdin:
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
printf 'home\nwait 500\nshift-to 110\nwait 1000\nhome\n' \
|
|
193
|
+
| nova-control --port /dev/ttyACM0 --script -
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
### Interactive REPL session
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
$ nova-control --port /dev/ttyACM0 shell
|
|
202
|
+
NOVA interactive shell — type "help [command]" for help, "exit" to quit
|
|
203
|
+
nova-control> home
|
|
204
|
+
nova-control> wait 500
|
|
205
|
+
nova-control> shift-to 100
|
|
206
|
+
nova-control> roll-to 60
|
|
207
|
+
nova-control> state
|
|
208
|
+
{"s1":100,"s2":60,"s3":110,"s4":90,"s5":95}
|
|
209
|
+
nova-control> home
|
|
210
|
+
nova-control> exit
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Exit codes
|
|
216
|
+
|
|
217
|
+
| code | meaning |
|
|
218
|
+
| --- | --- |
|
|
219
|
+
| `0` | success |
|
|
220
|
+
| `1` | unspecified runtime error (e.g. serial port refused to open) |
|
|
221
|
+
| `2` | bad arguments or missing required option |
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## License
|
|
226
|
+
|
|
227
|
+
[MIT License](../../LICENSE.md) © Andreas Rozek
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath as e } from "node:url";
|
|
3
|
+
import { Command as t } from "commander";
|
|
4
|
+
import { openNova as n } from "nova-control-node";
|
|
5
|
+
import r from "node:readline";
|
|
6
|
+
import i from "node:fs/promises";
|
|
7
|
+
//#region src/CommandTokenizer.ts
|
|
8
|
+
function a(e) {
|
|
9
|
+
let t = [], n = "", r = 0;
|
|
10
|
+
for (; r < e.length;) {
|
|
11
|
+
let i = e[r];
|
|
12
|
+
switch (!0) {
|
|
13
|
+
case i === " " || i === " ":
|
|
14
|
+
n.length > 0 && (t.push(n), n = ""), r++;
|
|
15
|
+
break;
|
|
16
|
+
case i === "'":
|
|
17
|
+
for (r++; r < e.length && e[r] !== "'";) n += e[r], r++;
|
|
18
|
+
r++;
|
|
19
|
+
break;
|
|
20
|
+
case i === "\"":
|
|
21
|
+
for (r++; r < e.length && e[r] !== "\"";) if (e[r] === "\\" && r + 1 < e.length) {
|
|
22
|
+
let t = e[r + 1];
|
|
23
|
+
n += t === "\"" || t === "\\" ? t : "\\" + t, r += 2;
|
|
24
|
+
} else n += e[r], r++;
|
|
25
|
+
r++;
|
|
26
|
+
break;
|
|
27
|
+
case i === "#" && n.length === 0:
|
|
28
|
+
r = e.length;
|
|
29
|
+
break;
|
|
30
|
+
default: i === "\\" && r + 1 < e.length ? (n += e[r + 1], r += 2) : (n += i, r++);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return n.length > 0 && t.push(n), t;
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
36
|
+
//#region src/REPL.ts
|
|
37
|
+
async function o(e, t) {
|
|
38
|
+
let n = process.stdin.isTTY, i = n ? `\x1b[1m${e}>\x1b[0m ` : `${e}> `, o = r.createInterface({
|
|
39
|
+
input: process.stdin,
|
|
40
|
+
output: process.stdout,
|
|
41
|
+
terminal: n,
|
|
42
|
+
prompt: i
|
|
43
|
+
});
|
|
44
|
+
n && (process.stdout.write("NOVA interactive shell — type \"help [command]\" for help, \"exit\" to quit\n"), o.prompt());
|
|
45
|
+
for await (let r of o) {
|
|
46
|
+
let i = r.trim();
|
|
47
|
+
if (i === "" || i.startsWith("#")) {
|
|
48
|
+
n && o.prompt();
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (i === "exit" || i === "quit") break;
|
|
52
|
+
let s = a(i);
|
|
53
|
+
if (s.length === 0) {
|
|
54
|
+
n && o.prompt();
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
await t(s);
|
|
59
|
+
} catch (t) {
|
|
60
|
+
process.stderr.write(`${e}: ${t.message}\n`);
|
|
61
|
+
}
|
|
62
|
+
n && o.prompt();
|
|
63
|
+
}
|
|
64
|
+
o.close();
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
//#region src/ScriptRunner.ts
|
|
68
|
+
async function s(e, t, n) {
|
|
69
|
+
let o;
|
|
70
|
+
if (t === "-") o = process.stdin;
|
|
71
|
+
else try {
|
|
72
|
+
o = (await i.open(t)).createReadStream();
|
|
73
|
+
} catch {
|
|
74
|
+
return process.stderr.write(`nova-control: cannot open script '${t}'\n`), 2;
|
|
75
|
+
}
|
|
76
|
+
let s = r.createInterface({
|
|
77
|
+
input: o,
|
|
78
|
+
terminal: !1
|
|
79
|
+
}), l = 0;
|
|
80
|
+
for await (let t of s) {
|
|
81
|
+
let r = t.trim();
|
|
82
|
+
if (r === "" || r.startsWith("#")) continue;
|
|
83
|
+
let i = a(r);
|
|
84
|
+
if (i.length === 0) continue;
|
|
85
|
+
let o = 0;
|
|
86
|
+
try {
|
|
87
|
+
o = await n(i);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
o = 1, process.stderr.write(`nova-control: ${e.message}\n`);
|
|
90
|
+
}
|
|
91
|
+
if (o !== 0) switch (l = o, e) {
|
|
92
|
+
case "stop": return s.close(), o;
|
|
93
|
+
case "continue": break;
|
|
94
|
+
case "ask":
|
|
95
|
+
if (!await c()) return s.close(), o;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return s.close(), l;
|
|
100
|
+
}
|
|
101
|
+
async function c() {
|
|
102
|
+
return process.stdin.isTTY ? new Promise((e) => {
|
|
103
|
+
let t = r.createInterface({
|
|
104
|
+
input: process.stdin,
|
|
105
|
+
output: process.stdout
|
|
106
|
+
});
|
|
107
|
+
t.question("error — continue? [y/N] ", (n) => {
|
|
108
|
+
t.close(), e(n.trim().toLowerCase() === "y");
|
|
109
|
+
});
|
|
110
|
+
}) : !1;
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/nova-control-command.ts
|
|
114
|
+
var l = {
|
|
115
|
+
OK: 0,
|
|
116
|
+
GeneralError: 1,
|
|
117
|
+
UsageError: 2
|
|
118
|
+
}, u = class extends Error {
|
|
119
|
+
ExitCode;
|
|
120
|
+
constructor(e, t = l.GeneralError) {
|
|
121
|
+
super(e), this.name = "NovaCommandError", this.ExitCode = t;
|
|
122
|
+
}
|
|
123
|
+
}, d = "nova-control", f, p = 9600, m = "stop", h;
|
|
124
|
+
async function g() {
|
|
125
|
+
if (f == null) throw new u("--port is required — specify the serial port (e.g. /dev/ttyACM0 or COM3)", l.UsageError);
|
|
126
|
+
return h ??= await n(f, p), h;
|
|
127
|
+
}
|
|
128
|
+
function _() {
|
|
129
|
+
h != null && (h.destroy(), h = void 0);
|
|
130
|
+
}
|
|
131
|
+
function v(e, t = 9600, n = "stop") {
|
|
132
|
+
f = e, p = t, m = n;
|
|
133
|
+
}
|
|
134
|
+
function y() {
|
|
135
|
+
_(), f = void 0, p = 9600, m = "stop";
|
|
136
|
+
}
|
|
137
|
+
function b(e) {
|
|
138
|
+
e.exitOverride(), e.configureOutput({ writeErr: () => {} });
|
|
139
|
+
for (let t of e.commands) b(t);
|
|
140
|
+
}
|
|
141
|
+
function x(e = !1) {
|
|
142
|
+
let n = new t(d);
|
|
143
|
+
return n.description("NOVA robot arm CLI").allowUnknownOption(!1).configureOutput({ writeErr: () => {} }), e || (n.option("--port <path>", "serial port path (e.g. /dev/ttyACM0 on Linux/macOS, COM3 on Windows)").option("--baud <rate>", "baud rate (default: 9600)", "9600").option("--on-error <mode>", "script error mode: stop | continue | ask (default: stop)"), n.hook("preAction", (e, t) => {
|
|
144
|
+
let n = t.optsWithGlobals();
|
|
145
|
+
f = n.port, p = Number(n.baud ?? "9600"), m = n.onError ?? "stop";
|
|
146
|
+
})), n.command("home").description("send all servos to their home positions").action(async () => {
|
|
147
|
+
await (await g()).home();
|
|
148
|
+
}), n.command("move").description("set one or more servo positions without interrupting the others").option("--shift-to <deg>", "shift head forward (>90°) or back (<90°) — s1").option("--roll-to <deg>", "roll head clockwise (>90°) or counter-clockwise (<90°) — s2").option("--pitch-to <deg>", "pitch head up (>110°) or down (<110°) — s3").option("--rotate-to <deg>", "rotate body around Z-axis — s4").option("--lift-to <deg>", "lift head on secondary axis, range 20°–150° — s5").action(async (e) => {
|
|
149
|
+
let t = {};
|
|
150
|
+
if (e.shiftTo != null && (t.s1 = Number(e.shiftTo)), e.rollTo != null && (t.s2 = Number(e.rollTo)), e.pitchTo != null && (t.s3 = Number(e.pitchTo)), e.rotateTo != null && (t.s4 = Number(e.rotateTo)), e.liftTo != null && (t.s5 = Number(e.liftTo)), Object.keys(t).length === 0) throw new u("move: specify at least one servo option (--shift-to, --roll-to, --pitch-to, --rotate-to, --lift-to)", l.UsageError);
|
|
151
|
+
let n = await g();
|
|
152
|
+
n.State = t, await n.sendServoState();
|
|
153
|
+
}), n.command("shift-to").description("shift head forward (>90°) or back (<90°) — s1").argument("<deg>", "target angle in degrees").action(async (e) => {
|
|
154
|
+
let t = await g();
|
|
155
|
+
t.State = { s1: Number(e) }, await t.sendServoState();
|
|
156
|
+
}), n.command("roll-to").description("roll head clockwise (>90°) or counter-clockwise (<90°) — s2").argument("<deg>", "target angle in degrees").action(async (e) => {
|
|
157
|
+
let t = await g();
|
|
158
|
+
t.State = { s2: Number(e) }, await t.sendServoState();
|
|
159
|
+
}), n.command("pitch-to").description("pitch head up (>110°) or down (<110°) — s3").argument("<deg>", "target angle in degrees").action(async (e) => {
|
|
160
|
+
let t = await g();
|
|
161
|
+
t.State = { s3: Number(e) }, await t.sendServoState();
|
|
162
|
+
}), n.command("rotate-to").description("rotate body around Z-axis — s4").argument("<deg>", "target angle in degrees").action(async (e) => {
|
|
163
|
+
let t = await g();
|
|
164
|
+
t.State = { s4: Number(e) }, await t.sendServoState();
|
|
165
|
+
}), n.command("lift-to").description("lift head on secondary axis, range 20°–150° — s5").argument("<deg>", "target angle in degrees").action(async (e) => {
|
|
166
|
+
let t = await g();
|
|
167
|
+
t.State = { s5: Number(e) }, await t.sendServoState();
|
|
168
|
+
}), n.command("wait").description("pause for <ms> milliseconds before the next command").argument("<ms>", "duration in milliseconds (non-negative number)").action(async (e) => {
|
|
169
|
+
let t = Number(e);
|
|
170
|
+
if (isNaN(t) || t < 0) throw new u(`wait: invalid duration '${e}' — expected a non-negative number`, l.UsageError);
|
|
171
|
+
await new Promise((e) => setTimeout(e, t));
|
|
172
|
+
}), n.command("state").description("print the current servo state as JSON").action(async () => {
|
|
173
|
+
let e = await g();
|
|
174
|
+
process.stdout.write(JSON.stringify(e.State) + "\n");
|
|
175
|
+
}), e || (n.command("shell").description("start an interactive REPL").action(async () => {
|
|
176
|
+
await o(d, (e) => S(e));
|
|
177
|
+
}), n.option("--script <file>", "run commands from a script file (use - for stdin)").action(async (e) => {
|
|
178
|
+
if (e.script != null) {
|
|
179
|
+
let t = await s(m, e.script, S);
|
|
180
|
+
process.exit(t);
|
|
181
|
+
} else process.stdout.write(n.helpInformation()), process.exit(l.OK);
|
|
182
|
+
}), n.addHelpCommand(!0)), n;
|
|
183
|
+
}
|
|
184
|
+
async function S(e) {
|
|
185
|
+
if (e.length === 0) return l.OK;
|
|
186
|
+
let t = x(!0);
|
|
187
|
+
b(t);
|
|
188
|
+
try {
|
|
189
|
+
return await t.parseAsync([
|
|
190
|
+
"node",
|
|
191
|
+
d,
|
|
192
|
+
...e
|
|
193
|
+
]), l.OK;
|
|
194
|
+
} catch (t) {
|
|
195
|
+
let n = t;
|
|
196
|
+
return n.code === "commander.help" || n.code === "commander.helpDisplayed" ? l.OK : n.code === "commander.unknownCommand" ? (process.stderr.write(`${d}: unknown command '${e[0]}' — try '${d} help'\n`), l.UsageError) : n.code === "commander.unknownOption" || n.code === "commander.missingArgument" || n.code === "commander.missingMandatoryOptionValue" ? (process.stderr.write(`${d}: ${n.message}\n`), l.UsageError) : t instanceof u ? (process.stderr.write(`${d}: ${t.message}\n`), t.ExitCode) : (process.stderr.write(`${d}: ${t.message ?? String(t)}\n`), l.GeneralError);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
async function C() {
|
|
200
|
+
let e = x();
|
|
201
|
+
b(e);
|
|
202
|
+
try {
|
|
203
|
+
await e.parseAsync(process.argv);
|
|
204
|
+
} catch (t) {
|
|
205
|
+
let n = t;
|
|
206
|
+
(n.code === "commander.help" || n.code === "commander.helpDisplayed" || n.code === "commander.version") && process.exit(l.OK), (n.code === "commander.unknownCommand" || n.code === "commander.unknownOption" || n.code === "commander.missingArgument" || n.code === "commander.missingMandatoryOptionValue") && (process.stderr.write(`${d}: ${n.message}\n\n`), process.stderr.write(e.helpInformation()), process.exit(l.UsageError)), t instanceof u && (process.stderr.write(`${d}: ${t.message}\n`), process.exit(t.ExitCode)), process.stderr.write(`${d}: ${t.message ?? String(t)}\n`), process.exit(l.GeneralError);
|
|
207
|
+
} finally {
|
|
208
|
+
_();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
process.argv[1] === e(import.meta.url) && C().catch((e) => {
|
|
212
|
+
process.stderr.write(`${d}: fatal: ${e.message ?? e}\n`), process.exit(l.GeneralError);
|
|
213
|
+
});
|
|
214
|
+
//#endregion
|
|
215
|
+
export { y as _destroyForTests, v as _setupForTests, S as executeTokens };
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nova-control-command",
|
|
3
|
+
"description": "CLI for controlling a NOVA DIY Artificial Intelligence Robot by Creoqode",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"nova",
|
|
8
|
+
"creoqode",
|
|
9
|
+
"robot",
|
|
10
|
+
"cli"
|
|
11
|
+
],
|
|
12
|
+
"author": "Andreas Rozek",
|
|
13
|
+
"homepage": "https://github.com/rozek/nova-control#readme",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/rozek/nova-control.git",
|
|
17
|
+
"directory": "packages/nova-control-command"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/rozek/nova-control/issues"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"bin": {
|
|
24
|
+
"nova-control": "./dist/nova-control-command.js"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"commander": "^12.0.0",
|
|
31
|
+
"nova-control-node": "*"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^22.0.0",
|
|
35
|
+
"typescript": "^5.9.3",
|
|
36
|
+
"vite": "^8.0.0",
|
|
37
|
+
"vitest": "^4.1.0"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "vite build",
|
|
41
|
+
"test": "vitest",
|
|
42
|
+
"test:run": "vitest run"
|
|
43
|
+
}
|
|
44
|
+
}
|