nova-control-command 0.0.1 → 0.0.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/README.md CHANGED
@@ -16,11 +16,27 @@ CLI for controlling a **NOVA DIY Artificial Intelligence Robot** by Creoqode. Pr
16
16
 
17
17
  ## Installation
18
18
 
19
+ **Global install** — installs the `nova-control` binary on the `PATH`:
20
+
19
21
  ```bash
20
- npm install nova-control-command
22
+ npm install -g nova-control-command
23
+ nova-control --port /dev/ttyACM0 home
24
+ ```
25
+
26
+ **No install — run directly with `npx`:**
27
+
28
+ ```bash
29
+ npx nova-control-command --port /dev/ttyACM0 home
30
+ npx nova-control-command --port /dev/ttyACM0 shell
21
31
  ```
22
32
 
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`.
33
+ `npx` downloads the package on first use and caches it locally. All examples in this document use the `nova-control` binary name (global install); substitute `npx nova-control-command` when running via npx.
34
+
35
+ **Local install** — adds the binary to `node_modules/.bin/`:
36
+
37
+ ```bash
38
+ npm install nova-control-command
39
+ ```
24
40
 
25
41
  ---
26
42
 
@@ -1,11 +1,12 @@
1
1
  #!/usr/bin/env node
2
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";
3
+ import { realpathSync as t } from "node:fs";
4
+ import { Command as n } from "commander";
5
+ import { openNova as r } from "nova-control-node";
6
+ import i from "node:readline";
7
+ import a from "node:fs/promises";
7
8
  //#region src/CommandTokenizer.ts
8
- function a(e) {
9
+ function o(e) {
9
10
  let t = [], n = "", r = 0;
10
11
  for (; r < e.length;) {
11
12
  let i = e[r];
@@ -34,24 +35,24 @@ function a(e) {
34
35
  }
35
36
  //#endregion
36
37
  //#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({
38
+ async function s(e, t) {
39
+ let n = process.stdin.isTTY, r = n ? `\x1b[1m${e}>\x1b[0m ` : `${e}> `, a = i.createInterface({
39
40
  input: process.stdin,
40
41
  output: process.stdout,
41
42
  terminal: n,
42
- prompt: i
43
+ prompt: r
43
44
  });
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) {
45
+ n && (process.stdout.write("NOVA interactive shell — type \"help [command]\" for help, \"exit\" to quit\n"), a.prompt());
46
+ for await (let r of a) {
46
47
  let i = r.trim();
47
48
  if (i === "" || i.startsWith("#")) {
48
- n && o.prompt();
49
+ n && a.prompt();
49
50
  continue;
50
51
  }
51
52
  if (i === "exit" || i === "quit") break;
52
- let s = a(i);
53
+ let s = o(i);
53
54
  if (s.length === 0) {
54
- n && o.prompt();
55
+ n && a.prompt();
55
56
  continue;
56
57
  }
57
58
  try {
@@ -59,48 +60,48 @@ async function o(e, t) {
59
60
  } catch (t) {
60
61
  process.stderr.write(`${e}: ${t.message}\n`);
61
62
  }
62
- n && o.prompt();
63
+ n && a.prompt();
63
64
  }
64
- o.close();
65
+ a.close();
65
66
  }
66
67
  //#endregion
67
68
  //#region src/ScriptRunner.ts
68
- async function s(e, t, n) {
69
- let o;
70
- if (t === "-") o = process.stdin;
69
+ async function c(e, t, n) {
70
+ let r;
71
+ if (t === "-") r = process.stdin;
71
72
  else try {
72
- o = (await i.open(t)).createReadStream();
73
+ r = (await a.open(t)).createReadStream();
73
74
  } catch {
74
75
  return process.stderr.write(`nova-control: cannot open script '${t}'\n`), 2;
75
76
  }
76
- let s = r.createInterface({
77
- input: o,
77
+ let s = i.createInterface({
78
+ input: r,
78
79
  terminal: !1
79
- }), l = 0;
80
+ }), c = 0;
80
81
  for await (let t of s) {
81
82
  let r = t.trim();
82
83
  if (r === "" || r.startsWith("#")) continue;
83
- let i = a(r);
84
+ let i = o(r);
84
85
  if (i.length === 0) continue;
85
- let o = 0;
86
+ let a = 0;
86
87
  try {
87
- o = await n(i);
88
+ a = await n(i);
88
89
  } catch (e) {
89
- o = 1, process.stderr.write(`nova-control: ${e.message}\n`);
90
+ a = 1, process.stderr.write(`nova-control: ${e.message}\n`);
90
91
  }
91
- if (o !== 0) switch (l = o, e) {
92
- case "stop": return s.close(), o;
92
+ if (a !== 0) switch (c = a, e) {
93
+ case "stop": return s.close(), a;
93
94
  case "continue": break;
94
95
  case "ask":
95
- if (!await c()) return s.close(), o;
96
+ if (!await l()) return s.close(), a;
96
97
  break;
97
98
  }
98
99
  }
99
- return s.close(), l;
100
+ return s.close(), c;
100
101
  }
101
- async function c() {
102
+ async function l() {
102
103
  return process.stdin.isTTY ? new Promise((e) => {
103
- let t = r.createInterface({
104
+ let t = i.createInterface({
104
105
  input: process.stdin,
105
106
  output: process.stdout
106
107
  });
@@ -111,105 +112,105 @@ async function c() {
111
112
  }
112
113
  //#endregion
113
114
  //#region src/nova-control-command.ts
114
- var l = {
115
+ var u = {
115
116
  OK: 0,
116
117
  GeneralError: 1,
117
118
  UsageError: 2
118
- }, u = class extends Error {
119
+ }, d = class extends Error {
119
120
  ExitCode;
120
- constructor(e, t = l.GeneralError) {
121
+ constructor(e, t = u.GeneralError) {
121
122
  super(e), this.name = "NovaCommandError", this.ExitCode = t;
122
123
  }
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;
124
+ }, f = "nova-control", p, m = 9600, h = "stop", g;
125
+ async function _() {
126
+ if (p == null) throw new d("--port is required — specify the serial port (e.g. /dev/ttyACM0 or COM3)", u.UsageError);
127
+ return g ??= await r(p, m), g;
127
128
  }
128
- function _() {
129
- h != null && (h.destroy(), h = void 0);
129
+ function v() {
130
+ g != null && (g.destroy(), g = void 0);
130
131
  }
131
- function v(e, t = 9600, n = "stop") {
132
- f = e, p = t, m = n;
132
+ function y(e, t = 9600, n = "stop") {
133
+ p = e, m = t, h = n;
133
134
  }
134
- function y() {
135
- _(), f = void 0, p = 9600, m = "stop";
135
+ function b() {
136
+ v(), p = void 0, m = 9600, h = "stop";
136
137
  }
137
- function b(e) {
138
+ function x(e) {
138
139
  e.exitOverride(), e.configureOutput({ writeErr: () => {} });
139
- for (let t of e.commands) b(t);
140
+ for (let t of e.commands) x(t);
140
141
  }
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) => {
142
+ function S(e = !1) {
143
+ let t = new n(f);
144
+ return t.description("NOVA robot arm CLI").allowUnknownOption(!1).configureOutput({ writeErr: () => {} }), e || (t.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)"), t.hook("preAction", (e, t) => {
144
145
  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) => {
146
+ p = n.port, m = Number(n.baud ?? "9600"), h = n.onError ?? "stop";
147
+ })), t.command("home").description("send all servos to their home positions").action(async () => {
148
+ await (await _()).home();
149
+ }), t.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
150
  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();
151
+ 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 d("move: specify at least one servo option (--shift-to, --roll-to, --pitch-to, --rotate-to, --lift-to)", u.UsageError);
152
+ let n = await _();
152
153
  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();
154
+ }), t.command("shift-to").description("shift head forward (>90°) or back (<90°) — s1").argument("<deg>", "target angle in degrees").action(async (e) => {
155
+ let t = await _();
155
156
  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();
157
+ }), t.command("roll-to").description("roll head clockwise (>90°) or counter-clockwise (<90°) — s2").argument("<deg>", "target angle in degrees").action(async (e) => {
158
+ let t = await _();
158
159
  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();
160
+ }), t.command("pitch-to").description("pitch head up (>110°) or down (<110°) — s3").argument("<deg>", "target angle in degrees").action(async (e) => {
161
+ let t = await _();
161
162
  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();
163
+ }), t.command("rotate-to").description("rotate body around Z-axis — s4").argument("<deg>", "target angle in degrees").action(async (e) => {
164
+ let t = await _();
164
165
  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();
166
+ }), t.command("lift-to").description("lift head on secondary axis, range 20°–150° — s5").argument("<deg>", "target angle in degrees").action(async (e) => {
167
+ let t = await _();
167
168
  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
+ }), t.command("wait").description("pause for <ms> milliseconds before the next command").argument("<ms>", "duration in milliseconds (non-negative number)").action(async (e) => {
169
170
  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
+ if (isNaN(t) || t < 0) throw new d(`wait: invalid duration '${e}' — expected a non-negative number`, u.UsageError);
171
172
  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();
173
+ }), t.command("state").description("print the current servo state as JSON").action(async () => {
174
+ let e = await _();
174
175
  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) => {
176
+ }), e || (t.command("shell").description("start an interactive REPL").action(async () => {
177
+ await s(f, (e) => C(e));
178
+ }), t.option("--script <file>", "run commands from a script file (use - for stdin)").action(async (e) => {
178
179
  if (e.script != null) {
179
- let t = await s(m, e.script, S);
180
+ let t = await c(h, e.script, C);
180
181
  process.exit(t);
181
- } else process.stdout.write(n.helpInformation()), process.exit(l.OK);
182
- }), n.addHelpCommand(!0)), n;
182
+ } else process.stdout.write(t.helpInformation()), process.exit(u.OK);
183
+ }), t.addHelpCommand(!0)), t;
183
184
  }
184
- async function S(e) {
185
- if (e.length === 0) return l.OK;
186
- let t = x(!0);
187
- b(t);
185
+ async function C(e) {
186
+ if (e.length === 0) return u.OK;
187
+ let t = S(!0);
188
+ x(t);
188
189
  try {
189
190
  return await t.parseAsync([
190
191
  "node",
191
- d,
192
+ f,
192
193
  ...e
193
- ]), l.OK;
194
+ ]), u.OK;
194
195
  } catch (t) {
195
196
  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
+ return n.code === "commander.help" || n.code === "commander.helpDisplayed" ? u.OK : n.code === "commander.unknownCommand" ? (process.stderr.write(`${f}: unknown command '${e[0]}' — try '${f} help'\n`), u.UsageError) : n.code === "commander.unknownOption" || n.code === "commander.missingArgument" || n.code === "commander.missingMandatoryOptionValue" ? (process.stderr.write(`${f}: ${n.message}\n`), u.UsageError) : t instanceof d ? (process.stderr.write(`${f}: ${t.message}\n`), t.ExitCode) : (process.stderr.write(`${f}: ${t.message ?? String(t)}\n`), u.GeneralError);
197
198
  }
198
199
  }
199
- async function C() {
200
- let e = x();
201
- b(e);
200
+ async function w() {
201
+ let e = S();
202
+ x(e);
202
203
  try {
203
204
  await e.parseAsync(process.argv);
204
205
  } catch (t) {
205
206
  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
+ (n.code === "commander.help" || n.code === "commander.helpDisplayed" || n.code === "commander.version") && process.exit(u.OK), (n.code === "commander.unknownCommand" || n.code === "commander.unknownOption" || n.code === "commander.missingArgument" || n.code === "commander.missingMandatoryOptionValue") && (process.stderr.write(`${f}: ${n.message}\n\n`), process.stderr.write(e.helpInformation()), process.exit(u.UsageError)), t instanceof d && (process.stderr.write(`${f}: ${t.message}\n`), process.exit(t.ExitCode)), process.stderr.write(`${f}: ${t.message ?? String(t)}\n`), process.exit(u.GeneralError);
207
208
  } finally {
208
- _();
209
+ v();
209
210
  }
210
211
  }
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);
212
+ t(process.argv[1]) === e(import.meta.url) && w().catch((e) => {
213
+ process.stderr.write(`${f}: fatal: ${e.message ?? e}\n`), process.exit(u.GeneralError);
213
214
  });
214
215
  //#endregion
215
- export { y as _destroyForTests, v as _setupForTests, S as executeTokens };
216
+ export { b as _destroyForTests, y as _setupForTests, C as executeTokens };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nova-control-command",
3
3
  "description": "CLI for controlling a NOVA DIY Artificial Intelligence Robot by Creoqode",
4
- "version": "0.0.1",
4
+ "version": "0.0.3",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "nova",
@@ -21,7 +21,8 @@
21
21
  },
22
22
  "license": "MIT",
23
23
  "bin": {
24
- "nova-control": "./dist/nova-control-command.js"
24
+ "nova-control": "./dist/nova-control-command.js",
25
+ "nova-control-command": "./dist/nova-control-command.js"
25
26
  },
26
27
  "files": [
27
28
  "dist"