bmweb-cli 0.1.1 → 0.1.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 +10 -6
- package/dist/bmweb.js +174 -78
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -309,15 +309,19 @@ status line and the script's progress window are the two bottom lines.
|
|
|
309
309
|
With no arguments it starts on the app's own home, an INPA script of the
|
|
310
310
|
project's own (`home/bmweb-home.ips`): F1 picks a chassis then a module,
|
|
311
311
|
F2 the chassis's whole-vehicle script, and `scriptchange` hands the screen
|
|
312
|
-
to that script.
|
|
313
|
-
|
|
314
|
-
cable.
|
|
312
|
+
to that script. A pick is a list the keyboard walks: Up/Down move the bar,
|
|
313
|
+
typing narrows the list to the rows containing the text, Enter picks, Esc
|
|
314
|
+
cancels. The home starts with or without a cable.
|
|
315
|
+
|
|
316
|
+
The TUI runs on the terminal's alternate screen (the buffer vim and htop
|
|
317
|
+
use), so the shell's scrollback is never touched and quitting restores it;
|
|
318
|
+
a screen redraws in place, only the lines that changed.
|
|
315
319
|
|
|
316
320
|
Every dialog INPA opens is a prompt: a message waits for Enter, an input
|
|
317
321
|
asks for the number (or hex, or text) within the declared range, the
|
|
318
|
-
two-word box takes y/n, the component picker (togglelist)
|
|
319
|
-
|
|
320
|
-
file name. **Every write is asked first**, exactly as the app asks: a key
|
|
322
|
+
two-word box takes y/n, the component picker (togglelist) and Select are
|
|
323
|
+
the same list picker (Space marks several where several may be picked),
|
|
324
|
+
save-as asks for a file name. **Every write is asked first**, exactly as the app asks: a key
|
|
321
325
|
whose body can send a write names the jobs and waits for y; a screen that
|
|
322
326
|
sends one on every refresh asks once for as long as it is open; n or
|
|
323
327
|
Enter abandons the key. On quit the leaving menu's Back job (the script's
|
package/dist/bmweb.js
CHANGED
|
@@ -1867,6 +1867,7 @@ function terminalHomeHost(api, status) {
|
|
|
1867
1867
|
var SHIFTED_DIGITS = "!@#$%^&*()";
|
|
1868
1868
|
var DIGITS = "1234567890";
|
|
1869
1869
|
var GAUGE_WIDTH = 12;
|
|
1870
|
+
var QUIT_MS = 4e3;
|
|
1870
1871
|
function keyToPress(k) {
|
|
1871
1872
|
if (k.ctrl && k.name === "c") return "quit";
|
|
1872
1873
|
if (k.name === "escape") return "back";
|
|
@@ -1892,6 +1893,7 @@ function nodeTerminal() {
|
|
|
1892
1893
|
};
|
|
1893
1894
|
raw(true);
|
|
1894
1895
|
input.resume();
|
|
1896
|
+
output.write("\x1B[?1049h\x1B[H");
|
|
1895
1897
|
const subs = /* @__PURE__ */ new Set();
|
|
1896
1898
|
let paused = false;
|
|
1897
1899
|
input.on(
|
|
@@ -1940,11 +1942,13 @@ function nodeTerminal() {
|
|
|
1940
1942
|
});
|
|
1941
1943
|
} finally {
|
|
1942
1944
|
rl.close();
|
|
1945
|
+
input.resume();
|
|
1943
1946
|
raw(true);
|
|
1944
1947
|
paused = false;
|
|
1945
1948
|
}
|
|
1946
1949
|
},
|
|
1947
1950
|
close() {
|
|
1951
|
+
output.write("\x1B[?1049l");
|
|
1948
1952
|
raw(false);
|
|
1949
1953
|
input.pause();
|
|
1950
1954
|
}
|
|
@@ -1960,6 +1964,8 @@ var TuiUi = class {
|
|
|
1960
1964
|
program = null;
|
|
1961
1965
|
/** the lines of the frame on screen, when the cursor sits right below it */
|
|
1962
1966
|
frame = [];
|
|
1967
|
+
/** a picker owns the keyboard: the program's key handler must stand back */
|
|
1968
|
+
modal = false;
|
|
1963
1969
|
/** the terminal size the frame was drawn for; a change draws fresh */
|
|
1964
1970
|
frameSize = "";
|
|
1965
1971
|
leftResolve = null;
|
|
@@ -1988,10 +1994,7 @@ var TuiUi = class {
|
|
|
1988
1994
|
* and drawn again for the new size.
|
|
1989
1995
|
*/
|
|
1990
1996
|
resized() {
|
|
1991
|
-
|
|
1992
|
-
this.term.write(`\x1B[${this.frame.length}A\r\x1B[J`);
|
|
1993
|
-
this.frame = [];
|
|
1994
|
-
}
|
|
1997
|
+
this.frame = [];
|
|
1995
1998
|
if (this.program) this.paint(this.program);
|
|
1996
1999
|
}
|
|
1997
2000
|
/**
|
|
@@ -2014,26 +2017,116 @@ var TuiUi = class {
|
|
|
2014
2017
|
return null;
|
|
2015
2018
|
}
|
|
2016
2019
|
const title = step.what === "module" ? `Modules of ${step.arg}` : "Vehicles";
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2020
|
+
const picked = await this.pickList(title, options);
|
|
2021
|
+
return picked ? picked[0] : null;
|
|
2022
|
+
}
|
|
2023
|
+
/**
|
|
2024
|
+
* The picker: a list the keyboard walks. Up/Down (and PageUp/PageDown)
|
|
2025
|
+
* move the bar, typing narrows the list to the rows containing the text,
|
|
2026
|
+
* Backspace widens it again, Enter picks the row under the bar (Space
|
|
2027
|
+
* marks a row when several may be picked, Enter then takes the marked
|
|
2028
|
+
* ones, or the bar's row when none is marked), Esc cancels. Drawn as the
|
|
2029
|
+
* frame, so it repaints in place like a screen.
|
|
2030
|
+
* @param title - what is being picked
|
|
2031
|
+
* @param options - the rows
|
|
2032
|
+
* @param multiple - whether several rows may be picked
|
|
2033
|
+
* @returns the picked values (one, unless multiple), or null for cancel
|
|
2034
|
+
*/
|
|
2035
|
+
async pickList(title, options, multiple = false) {
|
|
2036
|
+
let filter = "";
|
|
2037
|
+
let cursor = 0;
|
|
2038
|
+
let top = 0;
|
|
2039
|
+
const marked = /* @__PURE__ */ new Set();
|
|
2040
|
+
const shown = () => {
|
|
2041
|
+
const q = filter.toLowerCase();
|
|
2042
|
+
return q ? options.filter(
|
|
2034
2043
|
(o) => `${o.label} ${o.meta || ""} ${o.value}`.toLowerCase().includes(q)
|
|
2044
|
+
) : options;
|
|
2045
|
+
};
|
|
2046
|
+
const draw = () => {
|
|
2047
|
+
const rows = shown();
|
|
2048
|
+
const w = this.term.columns;
|
|
2049
|
+
const window = Math.max(3, this.term.rows - 7);
|
|
2050
|
+
if (cursor >= rows.length) cursor = Math.max(0, rows.length - 1);
|
|
2051
|
+
if (cursor < top) top = cursor;
|
|
2052
|
+
if (cursor >= top + window) top = cursor - window + 1;
|
|
2053
|
+
const count = rows.length === options.length ? `${options.length}` : `${rows.length} of ${options.length}`;
|
|
2054
|
+
const lines = [
|
|
2055
|
+
`${title} \x1B[2m(${count})\x1B[0m`,
|
|
2056
|
+
`\x1B[2mFilter:\x1B[0m ${filter}\x1B[7m \x1B[0m`,
|
|
2057
|
+
""
|
|
2058
|
+
];
|
|
2059
|
+
for (const o of rows.slice(top, top + window)) {
|
|
2060
|
+
const i = rows.indexOf(o);
|
|
2061
|
+
const mark = multiple ? marked.has(o.value) ? "[x] " : "[ ] " : "";
|
|
2062
|
+
const meta = o.meta ? ` ${o.meta}` : "";
|
|
2063
|
+
const text = `${mark}${o.label}${meta}`.slice(0, w - 4);
|
|
2064
|
+
lines.push(
|
|
2065
|
+
i === cursor ? `\x1B[7m > ${text.padEnd(w - 4)} \x1B[0m` : ` ${o.meta ? `${mark}${o.label}\x1B[2m${meta}\x1B[0m` : text}`
|
|
2066
|
+
);
|
|
2067
|
+
}
|
|
2068
|
+
if (!rows.length) lines.push(" \x1B[2m(nothing matches)\x1B[0m");
|
|
2069
|
+
if (rows.length > top + window)
|
|
2070
|
+
lines.push(` \x1B[2m... ${rows.length - top - window} more\x1B[0m`);
|
|
2071
|
+
lines.push("");
|
|
2072
|
+
lines.push(
|
|
2073
|
+
`\x1B[2m\u2191\u2193 move type to filter Enter picks${multiple ? " Space marks" : ""} Esc cancels\x1B[0m`
|
|
2035
2074
|
);
|
|
2036
|
-
|
|
2075
|
+
this.flush(lines);
|
|
2076
|
+
};
|
|
2077
|
+
this.modal = true;
|
|
2078
|
+
try {
|
|
2079
|
+
return await new Promise((resolve2) => {
|
|
2080
|
+
const off = this.term.onKey((k) => {
|
|
2081
|
+
const rows = shown();
|
|
2082
|
+
const finish = (v) => {
|
|
2083
|
+
off();
|
|
2084
|
+
resolve2(v);
|
|
2085
|
+
};
|
|
2086
|
+
if (k.name === "escape" || k.ctrl && k.name === "c") {
|
|
2087
|
+
finish(null);
|
|
2088
|
+
return;
|
|
2089
|
+
}
|
|
2090
|
+
if (k.name === "return" || k.name === "enter") {
|
|
2091
|
+
if (multiple && marked.size) {
|
|
2092
|
+
finish(
|
|
2093
|
+
options.filter((o2) => marked.has(o2.value)).map((o2) => o2.value)
|
|
2094
|
+
);
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
2097
|
+
const o = rows[cursor];
|
|
2098
|
+
finish(o ? [o.value] : null);
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
if (k.name === "up" || k.ctrl && k.name === "p")
|
|
2102
|
+
cursor = Math.max(0, cursor - 1);
|
|
2103
|
+
else if (k.name === "down" || k.ctrl && k.name === "n")
|
|
2104
|
+
cursor = Math.min(rows.length - 1, cursor + 1);
|
|
2105
|
+
else if (k.name === "pageup")
|
|
2106
|
+
cursor = Math.max(0, cursor - (this.term.rows - 7));
|
|
2107
|
+
else if (k.name === "pagedown")
|
|
2108
|
+
cursor = Math.min(rows.length - 1, cursor + (this.term.rows - 7));
|
|
2109
|
+
else if (k.name === "home") cursor = 0;
|
|
2110
|
+
else if (k.name === "end") cursor = Math.max(0, rows.length - 1);
|
|
2111
|
+
else if (k.name === "backspace") filter = filter.slice(0, -1);
|
|
2112
|
+
else if (multiple && k.name === "space") {
|
|
2113
|
+
const o = rows[cursor];
|
|
2114
|
+
if (o) {
|
|
2115
|
+
if (marked.has(o.value)) marked.delete(o.value);
|
|
2116
|
+
else marked.add(o.value);
|
|
2117
|
+
}
|
|
2118
|
+
} else if (k.ch && !k.ctrl && k.ch >= " " && k.ch !== "\x7F") {
|
|
2119
|
+
filter += k.ch;
|
|
2120
|
+
cursor = 0;
|
|
2121
|
+
} else return;
|
|
2122
|
+
draw();
|
|
2123
|
+
});
|
|
2124
|
+
draw();
|
|
2125
|
+
});
|
|
2126
|
+
} finally {
|
|
2127
|
+
this.modal = false;
|
|
2128
|
+
this.frame = [];
|
|
2129
|
+
if (this.program) this.paint(this.program);
|
|
2037
2130
|
}
|
|
2038
2131
|
}
|
|
2039
2132
|
/** Note the program once it exists, for the key handler. */
|
|
@@ -2041,10 +2134,9 @@ Number to open, text to filter, Enter to cancel: `
|
|
|
2041
2134
|
this.program = p;
|
|
2042
2135
|
}
|
|
2043
2136
|
/**
|
|
2044
|
-
* A prompt
|
|
2045
|
-
* cursor, so the frame is forgotten and the next paint
|
|
2046
|
-
*
|
|
2047
|
-
* tool call does).
|
|
2137
|
+
* A typed prompt under the frame (readline, raw mode off). It moves the
|
|
2138
|
+
* cursor, so the frame is forgotten and the next paint clears the screen
|
|
2139
|
+
* and draws fresh.
|
|
2048
2140
|
* @param prompt - the prompt text
|
|
2049
2141
|
* @returns the answer, or null when cancelled
|
|
2050
2142
|
*/
|
|
@@ -2164,8 +2256,8 @@ Send ${job}${arg ? ` ${arg}` : ""} to the module (${ctx.label})?${every} [y/N] `
|
|
|
2164
2256
|
* flag, every picked key ';'-joined).
|
|
2165
2257
|
*/
|
|
2166
2258
|
async pickComponent(p, step) {
|
|
2167
|
-
const rows = this.R.ipoScreenComponents(p.exec, p.screen).map((l,
|
|
2168
|
-
key: step.argnum ? String(
|
|
2259
|
+
const rows = this.R.ipoScreenComponents(p.exec, p.screen).map((l, i) => ({
|
|
2260
|
+
key: step.argnum ? String(i + 1) : String(l.keys).split(";")[0] || "",
|
|
2169
2261
|
caption: l.label || String(l.keys).split(";")[0] || ""
|
|
2170
2262
|
}));
|
|
2171
2263
|
if (!rows.length) {
|
|
@@ -2175,30 +2267,27 @@ Send ${job}${arg ? ` ${arg}` : ""} to the module (${ctx.label})?${every} [y/N] `
|
|
|
2175
2267
|
);
|
|
2176
2268
|
return null;
|
|
2177
2269
|
}
|
|
2178
|
-
const
|
|
2270
|
+
const options = rows.map((r) => ({
|
|
2271
|
+
value: r.key,
|
|
2272
|
+
label: r.caption,
|
|
2273
|
+
meta: r.key !== r.caption ? r.key : ""
|
|
2274
|
+
}));
|
|
2179
2275
|
if (step.multiple) {
|
|
2180
|
-
const
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
)
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
if (i == null) return null;
|
|
2196
|
-
const onOff = await this.ask(`On or off? [on/off] `);
|
|
2197
|
-
if (onOff == null || !onOff.trim()) return null;
|
|
2198
|
-
return {
|
|
2199
|
-
ort: rows[i].key,
|
|
2200
|
-
ein: /^on/i.test(onOff.trim()) ? 0 : 1
|
|
2201
|
-
};
|
|
2276
|
+
const picked2 = await this.pickList("Components", options, true);
|
|
2277
|
+
if (!picked2 || !picked2.length) return null;
|
|
2278
|
+
return { ort: picked2.join(";"), ein: 0 };
|
|
2279
|
+
}
|
|
2280
|
+
const picked = await this.pickList("Component", options);
|
|
2281
|
+
if (!picked) return null;
|
|
2282
|
+
const onOff = await this.pickList(
|
|
2283
|
+
`${options.find((o) => o.value === picked[0])?.label || picked[0]}`,
|
|
2284
|
+
[
|
|
2285
|
+
{ value: "on", label: "On" },
|
|
2286
|
+
{ value: "off", label: "Off" }
|
|
2287
|
+
]
|
|
2288
|
+
);
|
|
2289
|
+
if (!onOff) return null;
|
|
2290
|
+
return { ort: picked[0], ein: onOff[0] === "on" ? 0 : 1 };
|
|
2202
2291
|
}
|
|
2203
2292
|
/** INPA's Select: which named logical lines to keep on screen. */
|
|
2204
2293
|
async pickLines(_p, names, multiple, _current, hints) {
|
|
@@ -2210,16 +2299,14 @@ Component number (Enter cancels): `);
|
|
|
2210
2299
|
);
|
|
2211
2300
|
return null;
|
|
2212
2301
|
}
|
|
2213
|
-
const
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
);
|
|
2219
|
-
if (
|
|
2220
|
-
|
|
2221
|
-
const picked = pickNumbers(a, names.length).map((i) => names[i]);
|
|
2222
|
-
return picked.length ? picked : null;
|
|
2302
|
+
const options = [
|
|
2303
|
+
{ value: "\0all", label: "(all lines)" },
|
|
2304
|
+
...names.map((n) => ({ value: n, label: n }))
|
|
2305
|
+
];
|
|
2306
|
+
const picked = await this.pickList("Lines to show", options, multiple);
|
|
2307
|
+
if (!picked || !picked.length) return null;
|
|
2308
|
+
if (picked.includes("\0all")) return [];
|
|
2309
|
+
return picked;
|
|
2223
2310
|
}
|
|
2224
2311
|
/** INPA's save-as dialog: a file name, written when the body ends. */
|
|
2225
2312
|
async saveFile() {
|
|
@@ -2233,10 +2320,9 @@ Save as [fault-memory.txt]: `);
|
|
|
2233
2320
|
this.status(_p, `wrote ${picked.name}`);
|
|
2234
2321
|
}
|
|
2235
2322
|
printScreen(p) {
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
`);
|
|
2239
|
-
this.frame = [];
|
|
2323
|
+
const name = `bmweb-screen-${Date.now()}.txt`;
|
|
2324
|
+
writeFileSync4(name, this.gridLines(p).join("\n") + "\n", "utf8");
|
|
2325
|
+
this.status(p, `printed the screen to ${name}`);
|
|
2240
2326
|
}
|
|
2241
2327
|
/**
|
|
2242
2328
|
* The module a scriptchange names. From the home script it is the car's
|
|
@@ -2322,12 +2408,13 @@ ${this.gridLines(p).join("\n")}
|
|
|
2322
2408
|
);
|
|
2323
2409
|
}
|
|
2324
2410
|
/**
|
|
2325
|
-
* Put a frame on the
|
|
2326
|
-
* the
|
|
2411
|
+
* Put a frame on the screen over the last one. The frame always starts
|
|
2412
|
+
* at the top-left of the alternate screen: go there, rewrite the lines
|
|
2327
2413
|
* that differ (each erased to the end of the row), step over the ones
|
|
2328
2414
|
* that match, and erase whatever the old frame had below the new one.
|
|
2329
|
-
*
|
|
2330
|
-
*
|
|
2415
|
+
* With no frame to build on (first paint, after a prompt, after a
|
|
2416
|
+
* resize) the screen is cleared first -- on the alternate screen that
|
|
2417
|
+
* costs no scrollback.
|
|
2331
2418
|
* @param lines - the new frame
|
|
2332
2419
|
*/
|
|
2333
2420
|
flush(lines) {
|
|
@@ -2337,14 +2424,14 @@ ${this.gridLines(p).join("\n")}
|
|
|
2337
2424
|
const prev = this.frame;
|
|
2338
2425
|
if (prev.length === lines.length && prev.every((l, i) => l === lines[i]))
|
|
2339
2426
|
return;
|
|
2340
|
-
let s = prev.length ?
|
|
2427
|
+
let s = prev.length ? "\x1B[H" : "\x1B[2J\x1B[H";
|
|
2341
2428
|
lines.forEach((line, i) => {
|
|
2342
2429
|
if (i < prev.length && prev[i] === line) s += "\x1B[B";
|
|
2343
2430
|
else s += `\r${line}\x1B[K\r
|
|
2344
2431
|
`;
|
|
2345
2432
|
});
|
|
2346
2433
|
if (lines.length < prev.length) s += "\r\x1B[J";
|
|
2347
|
-
|
|
2434
|
+
this.term.write(s);
|
|
2348
2435
|
this.frame = lines;
|
|
2349
2436
|
}
|
|
2350
2437
|
/**
|
|
@@ -2404,10 +2491,6 @@ ${this.progressText}\r
|
|
|
2404
2491
|
if (this.leftResolve) this.leftResolve();
|
|
2405
2492
|
}
|
|
2406
2493
|
};
|
|
2407
|
-
function pickNumbers(a, n) {
|
|
2408
|
-
if (a == null) return [];
|
|
2409
|
-
return a.split(/[,\s]+/).map((s) => Number(s)).filter((i) => Number.isInteger(i) && i >= 1 && i <= n).map((i) => i - 1);
|
|
2410
|
-
}
|
|
2411
2494
|
function cellText(c) {
|
|
2412
2495
|
const text = String(c.text || "");
|
|
2413
2496
|
if (c.kind === "lamp") {
|
|
@@ -2495,15 +2578,27 @@ async function tuiCommand(chassis, sgbd, opts = {}) {
|
|
|
2495
2578
|
if (opts.menu !== p.menu) await p.openMenu(opts.menu);
|
|
2496
2579
|
}
|
|
2497
2580
|
const program = p;
|
|
2581
|
+
let leaving = false;
|
|
2498
2582
|
const unsubscribe = term.onKey((k) => {
|
|
2583
|
+
if (ui.modal) return;
|
|
2499
2584
|
const what = keyToPress(k);
|
|
2500
2585
|
if (what === null) return;
|
|
2501
2586
|
if (what === "quit") {
|
|
2502
|
-
|
|
2503
|
-
|
|
2587
|
+
if (leaving) {
|
|
2588
|
+
if (k.ctrl && !opts.term) {
|
|
2589
|
+
term.close();
|
|
2590
|
+
process.exit(130);
|
|
2591
|
+
}
|
|
2592
|
+
return;
|
|
2593
|
+
}
|
|
2594
|
+
leaving = true;
|
|
2595
|
+
const done = program.leaveModule().catch(() => {
|
|
2504
2596
|
});
|
|
2597
|
+
const late = new Promise((r2) => setTimeout(r2, QUIT_MS));
|
|
2598
|
+
Promise.race([done, late]).then(() => ui.left());
|
|
2505
2599
|
return;
|
|
2506
2600
|
}
|
|
2601
|
+
if (leaving) return;
|
|
2507
2602
|
if (what === "back") {
|
|
2508
2603
|
ui.requestStop();
|
|
2509
2604
|
program.back().catch(() => {
|
|
@@ -2525,7 +2620,7 @@ async function tuiCommand(chassis, sgbd, opts = {}) {
|
|
|
2525
2620
|
}
|
|
2526
2621
|
|
|
2527
2622
|
// src/bmweb.ts
|
|
2528
|
-
var VERSION = true ? "0.1.
|
|
2623
|
+
var VERSION = true ? "0.1.3" : "0.0.0-dev";
|
|
2529
2624
|
var INCLUDE = {
|
|
2530
2625
|
include: {
|
|
2531
2626
|
kind: "list",
|
|
@@ -2858,6 +2953,7 @@ async function main(argv, out = (l) => process.stdout.write(l + "\n"), err = (l)
|
|
|
2858
2953
|
if (typeof process !== "undefined" && process.argv[1] && /bmweb(\.js)?$/.test(process.argv[1])) {
|
|
2859
2954
|
main(process.argv.slice(2)).then((code) => {
|
|
2860
2955
|
process.exitCode = code;
|
|
2956
|
+
process.stdout.write("", () => process.exit(code));
|
|
2861
2957
|
});
|
|
2862
2958
|
}
|
|
2863
2959
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bmweb-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "BMWeb's tools as a command line: read INPA .IPO scripts, compile .IPS sources, search the corpus job index, decode and diff shared Garage reports, and, over a K+DCAN cable, run jobs, whole-car scans and INPA screens in the terminal.",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"type": "module",
|