xp-command 1.5.0 → 1.6.0
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 +16 -3
- package/bin/index.js +45 -19
- package/package.json +1 -1
- package/src/api.js +41 -21
package/README.md
CHANGED
|
@@ -73,15 +73,25 @@ If you changed the Web API port from 8086 to another port, you'll need to start
|
|
|
73
73
|
xp-command --port 8090
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
### One-shot mode
|
|
77
|
+
|
|
78
|
+
Run a single command and exit immediately (useful for scripts or keybindings):
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
xp-command --run x7000
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Exits with code `0` on success, `1` on failure.
|
|
85
|
+
|
|
76
86
|
### Exiting
|
|
77
87
|
|
|
78
88
|
Type `exit` or press `Ctrl+C`
|
|
79
89
|
|
|
80
90
|
---
|
|
81
91
|
|
|
82
|
-
## 📋 Pre-configured
|
|
92
|
+
## 📋 Pre-configured triggers
|
|
83
93
|
|
|
84
|
-
All pre-configured
|
|
94
|
+
All pre-configured triggers work well with most aircraft I currently fly, but you may want to adjust them for **your** aircraft (see _Aircraft-specific profiles_ section below).
|
|
85
95
|
|
|
86
96
|
### Barometric pressure
|
|
87
97
|
|
|
@@ -200,7 +210,10 @@ In addition to reading/writing datarefs, you can trigger X-Plane commands. Comma
|
|
|
200
210
|
**Duration syntax**: Append `[duration]` to specify how long the command stays active (in seconds):
|
|
201
211
|
- `command` or `command[0]`: Press and immediately release (default behavior)
|
|
202
212
|
- `command[5]`: Hold for 5 seconds then release
|
|
203
|
-
|
|
213
|
+
|
|
214
|
+
**Repeat syntax**: Append `[repeatCount]` to specify how often the command should be repeated:
|
|
215
|
+
- `command[0][3]`: Press and immediately release three times
|
|
216
|
+
- `command[0][$]`: Press and immediately release as often as defined by captured value
|
|
204
217
|
|
|
205
218
|
**Examples**:
|
|
206
219
|
|
package/bin/index.js
CHANGED
|
@@ -40,10 +40,12 @@ program
|
|
|
40
40
|
.version(packageJson.version)
|
|
41
41
|
.description(`${PREFIX} ${packageJson.name}\n${packageJson.description}`)
|
|
42
42
|
.option("-p, --port <number>", "server port number")
|
|
43
|
+
.option("-r, --run <command>", "run a single command and exit")
|
|
43
44
|
.helpOption("-h, --help", "display this help text");
|
|
44
45
|
|
|
45
46
|
/**
|
|
46
47
|
* @param {string} command
|
|
48
|
+
* @returns {Promise<boolean>} true if command succeeded, false otherwise
|
|
47
49
|
*/
|
|
48
50
|
const processCommand = async (command) => {
|
|
49
51
|
const spinner = ora(`${PREFIX} ${chalk.cyan(command ?? "")}`).start();
|
|
@@ -60,7 +62,7 @@ const processCommand = async (command) => {
|
|
|
60
62
|
hideCursor();
|
|
61
63
|
await sleep(1500);
|
|
62
64
|
showCursor();
|
|
63
|
-
return;
|
|
65
|
+
return false;
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -70,7 +72,7 @@ const processCommand = async (command) => {
|
|
|
70
72
|
hideCursor();
|
|
71
73
|
await sleep(1500);
|
|
72
74
|
clearLine();
|
|
73
|
-
return;
|
|
75
|
+
return true;
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
/**
|
|
@@ -100,7 +102,7 @@ const processCommand = async (command) => {
|
|
|
100
102
|
return value;
|
|
101
103
|
};
|
|
102
104
|
|
|
103
|
-
/** @type {Array<[RegExp, (regExpResult: Array<string> | null) => Promise<
|
|
105
|
+
/** @type {Array<[RegExp, (regExpResult: Array<string> | null) => Promise<boolean>]>} */
|
|
104
106
|
const matches = config.commands.map((c) => {
|
|
105
107
|
if (!c.type) {
|
|
106
108
|
return [
|
|
@@ -112,7 +114,7 @@ const processCommand = async (command) => {
|
|
|
112
114
|
hideCursor();
|
|
113
115
|
await sleep(1500);
|
|
114
116
|
clearLine();
|
|
115
|
-
return;
|
|
117
|
+
return false;
|
|
116
118
|
},
|
|
117
119
|
];
|
|
118
120
|
}
|
|
@@ -127,7 +129,7 @@ const processCommand = async (command) => {
|
|
|
127
129
|
hideCursor();
|
|
128
130
|
await sleep(1500);
|
|
129
131
|
clearLine();
|
|
130
|
-
return;
|
|
132
|
+
return false;
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
/** @type {number|string|Array<number|string>}*/
|
|
@@ -147,6 +149,7 @@ const processCommand = async (command) => {
|
|
|
147
149
|
hideCursor();
|
|
148
150
|
await sleep(1500);
|
|
149
151
|
clearLine();
|
|
152
|
+
return true;
|
|
150
153
|
},
|
|
151
154
|
];
|
|
152
155
|
case "set":
|
|
@@ -160,11 +163,19 @@ const processCommand = async (command) => {
|
|
|
160
163
|
hideCursor();
|
|
161
164
|
await sleep(1500);
|
|
162
165
|
clearLine();
|
|
163
|
-
return;
|
|
166
|
+
return false;
|
|
164
167
|
}
|
|
165
168
|
|
|
166
169
|
if (c.command) {
|
|
167
|
-
|
|
170
|
+
let value = String(regExpResult[1]);
|
|
171
|
+
if (isNaN(Number(value))) {
|
|
172
|
+
await activateCommands(c.command);
|
|
173
|
+
} else {
|
|
174
|
+
c.transform?.forEach((t) => {
|
|
175
|
+
value = String(getTransformedValue(value, t));
|
|
176
|
+
});
|
|
177
|
+
await activateCommands(c.command, Number(value));
|
|
178
|
+
}
|
|
168
179
|
}
|
|
169
180
|
if (c.dataref) {
|
|
170
181
|
let value = String(regExpResult[1]);
|
|
@@ -182,6 +193,7 @@ const processCommand = async (command) => {
|
|
|
182
193
|
hideCursor();
|
|
183
194
|
await sleep(1500);
|
|
184
195
|
clearLine();
|
|
196
|
+
return true;
|
|
185
197
|
},
|
|
186
198
|
];
|
|
187
199
|
}
|
|
@@ -191,12 +203,15 @@ const processCommand = async (command) => {
|
|
|
191
203
|
const regexpResult = regexp.exec(command);
|
|
192
204
|
if (regexpResult !== null) {
|
|
193
205
|
try {
|
|
194
|
-
await cb(regexpResult);
|
|
206
|
+
const success = await cb(regexpResult);
|
|
195
207
|
|
|
196
|
-
|
|
208
|
+
if (success) {
|
|
209
|
+
spinner.succeed();
|
|
210
|
+
}
|
|
197
211
|
hideCursor();
|
|
198
212
|
|
|
199
213
|
showCursor();
|
|
214
|
+
return success;
|
|
200
215
|
} catch (error) {
|
|
201
216
|
logger.error(error);
|
|
202
217
|
spinner.fail();
|
|
@@ -220,8 +235,8 @@ const processCommand = async (command) => {
|
|
|
220
235
|
|
|
221
236
|
await sleep(500);
|
|
222
237
|
showCursor();
|
|
238
|
+
return false;
|
|
223
239
|
}
|
|
224
|
-
return;
|
|
225
240
|
}
|
|
226
241
|
}
|
|
227
242
|
|
|
@@ -230,6 +245,7 @@ const processCommand = async (command) => {
|
|
|
230
245
|
|
|
231
246
|
await sleep(1500);
|
|
232
247
|
showCursor();
|
|
248
|
+
return false;
|
|
233
249
|
};
|
|
234
250
|
|
|
235
251
|
const sayHello = () => {
|
|
@@ -291,17 +307,27 @@ const askForCommand = async () => {
|
|
|
291
307
|
await askForCommand();
|
|
292
308
|
};
|
|
293
309
|
|
|
294
|
-
program.action(
|
|
295
|
-
|
|
310
|
+
program.action(
|
|
311
|
+
async (
|
|
312
|
+
/** @type {{ port: number | undefined, run: string | undefined }} */ options,
|
|
313
|
+
) => {
|
|
314
|
+
initAPI({ port: options.port ?? 8086 });
|
|
296
315
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
316
|
+
if (options.run) {
|
|
317
|
+
history.addCommand(options.run);
|
|
318
|
+
const success = await processCommand(options.run);
|
|
319
|
+
process.exit(success ? 0 : 1);
|
|
320
|
+
}
|
|
302
321
|
|
|
303
|
-
|
|
304
|
-
|
|
322
|
+
hideCursor();
|
|
323
|
+
clearLine();
|
|
324
|
+
sayHello();
|
|
325
|
+
await sleep(1000);
|
|
326
|
+
showCursor();
|
|
327
|
+
|
|
328
|
+
await askForCommand();
|
|
329
|
+
},
|
|
330
|
+
);
|
|
305
331
|
|
|
306
332
|
program.parse(process.argv);
|
|
307
333
|
|
package/package.json
CHANGED
package/src/api.js
CHANGED
|
@@ -39,18 +39,27 @@ const parseDataref = (datarefString) => {
|
|
|
39
39
|
|
|
40
40
|
/**
|
|
41
41
|
* @param {string} commandString
|
|
42
|
-
* @
|
|
42
|
+
* @param {number|string} [value]
|
|
43
|
+
* @return {[string, number|null, number|null]}
|
|
43
44
|
*/
|
|
44
|
-
const parseCommand = (commandString) => {
|
|
45
|
-
// Regex pattern: captures base name and optional duration
|
|
46
|
-
const pattern = /^(.+?)(?:\[(\d+)\])?$/;
|
|
45
|
+
const parseCommand = (commandString, value) => {
|
|
46
|
+
// Regex pattern: captures base name and optional duration and repeat count values
|
|
47
|
+
const pattern = /^(.+?)(?:\[(\d+)\])?(?:\[(\d+|\$)\])?$/;
|
|
47
48
|
const match = commandString.match(pattern);
|
|
48
49
|
|
|
49
50
|
if (!match) {
|
|
50
51
|
throw new CustomError("invalid_command");
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
|
|
54
|
+
let repeatCount = match[3] ? parseInt(match[3]) : null;
|
|
55
|
+
if (isNaN(repeatCount)) {
|
|
56
|
+
repeatCount = typeof value === "number" ? value : parseInt(value);
|
|
57
|
+
}
|
|
58
|
+
if (isNaN(repeatCount)) {
|
|
59
|
+
repeatCount = 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return [match[1], match[2] ? parseInt(match[2]) : null, repeatCount];
|
|
54
63
|
};
|
|
55
64
|
|
|
56
65
|
/**
|
|
@@ -104,9 +113,16 @@ export const getCommandId = async (commandName) => {
|
|
|
104
113
|
|
|
105
114
|
/**
|
|
106
115
|
* @param {string} commandNameWithOptionalDuration
|
|
116
|
+
* @param {number|string} [value]
|
|
107
117
|
*/
|
|
108
|
-
export const activateCommand = async (
|
|
109
|
-
|
|
118
|
+
export const activateCommand = async (
|
|
119
|
+
commandNameWithOptionalDuration,
|
|
120
|
+
value,
|
|
121
|
+
) => {
|
|
122
|
+
const [commandName, duration, repeatCount] = parseCommand(
|
|
123
|
+
commandNameWithOptionalDuration,
|
|
124
|
+
value,
|
|
125
|
+
);
|
|
110
126
|
|
|
111
127
|
const commandId = await getCommandId(commandName);
|
|
112
128
|
|
|
@@ -114,19 +130,19 @@ export const activateCommand = async (commandNameWithOptionalDuration) => {
|
|
|
114
130
|
`http://localhost:${port}/api/v2/command/${commandId}/activate`,
|
|
115
131
|
);
|
|
116
132
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
133
|
+
for (let i = Math.max(1, repeatCount ?? 1); i--; ) {
|
|
134
|
+
const response = await fetch(url, {
|
|
135
|
+
method: "POST",
|
|
136
|
+
body: JSON.stringify({ duration: duration || 0 }),
|
|
137
|
+
});
|
|
138
|
+
const json = /** @type { {data: number|string } | APIError } */ (
|
|
139
|
+
await response.json()
|
|
140
|
+
);
|
|
124
141
|
|
|
125
|
-
|
|
126
|
-
|
|
142
|
+
if (json && "error_code" in json) {
|
|
143
|
+
throw new CustomError(json.error_code);
|
|
144
|
+
}
|
|
127
145
|
}
|
|
128
|
-
|
|
129
|
-
return json;
|
|
130
146
|
};
|
|
131
147
|
|
|
132
148
|
/**
|
|
@@ -242,16 +258,20 @@ export const setDatarefValues = async (
|
|
|
242
258
|
|
|
243
259
|
/**
|
|
244
260
|
* @param {string|Array<string>} commandNamesWithOptionalDuration
|
|
261
|
+
* @param {number|string} [value]
|
|
245
262
|
* @return {Promise<void>}
|
|
246
263
|
*/
|
|
247
|
-
export const activateCommands = async (
|
|
264
|
+
export const activateCommands = async (
|
|
265
|
+
commandNamesWithOptionalDuration,
|
|
266
|
+
value,
|
|
267
|
+
) => {
|
|
248
268
|
if (Array.isArray(commandNamesWithOptionalDuration)) {
|
|
249
269
|
await Promise.all(
|
|
250
270
|
commandNamesWithOptionalDuration.map((command) =>
|
|
251
|
-
activateCommand(command),
|
|
271
|
+
activateCommand(command, value),
|
|
252
272
|
),
|
|
253
273
|
);
|
|
254
274
|
} else {
|
|
255
|
-
await activateCommand(commandNamesWithOptionalDuration);
|
|
275
|
+
await activateCommand(commandNamesWithOptionalDuration, value);
|
|
256
276
|
}
|
|
257
277
|
};
|