@hardfin/cli 0.0.2-dev.16 → 0.0.2-dev.18
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 +7 -0
- package/dist/cli.js +129 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,6 +71,13 @@ A value is sent as the type the document names. `--useful-life 36` sends the num
|
|
|
71
71
|
decimal such as `--salvage-value 1500.00` stays a string, which is how the API takes money
|
|
72
72
|
without losing precision.
|
|
73
73
|
|
|
74
|
+
A download is written where you name it, and a terminal is never filled with a file's bytes.
|
|
75
|
+
|
|
76
|
+
```sh
|
|
77
|
+
hardfin file get file_7hq2mx9pkr4stz8w --output photo.jpg
|
|
78
|
+
hardfin file get file_7hq2mx9pkr4stz8w --output - | wc -c
|
|
79
|
+
```
|
|
80
|
+
|
|
74
81
|
Clear a field with `--unset`, naming the flag:
|
|
75
82
|
|
|
76
83
|
```sh
|
package/dist/cli.js
CHANGED
|
@@ -136,7 +136,7 @@ function writeData(value) {
|
|
|
136
136
|
process.stdout.write(value.endsWith("\n") ? value : `${value}\n`);
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
|
-
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
139
|
+
process.stdout.write(`${JSON.stringify(value ?? null, null, 2)}\n`);
|
|
140
140
|
}
|
|
141
141
|
/** writeFailure prints why a command failed on stderr, as text or as JSON. */
|
|
142
142
|
function writeFailure(message, isJSON, errors, requestId) {
|
|
@@ -258,7 +258,8 @@ function toSummary(command) {
|
|
|
258
258
|
valueName: flag.valueName ?? null,
|
|
259
259
|
repeatable: flag.repeatable ?? false
|
|
260
260
|
})),
|
|
261
|
-
examples: command.examples
|
|
261
|
+
examples: command.examples,
|
|
262
|
+
subcommands: command.subcommands?.filter((entry) => !entry.hidden).map(toSummary)
|
|
262
263
|
};
|
|
263
264
|
}
|
|
264
265
|
//#endregion
|
|
@@ -719,11 +720,11 @@ var RequestFailure = class extends Error {
|
|
|
719
720
|
/** request calls one /v2 endpoint and returns the envelope it answered with. */
|
|
720
721
|
async function request(options) {
|
|
721
722
|
const url = new URL(`${options.apiUrl}${toLeadingSlash(options.path)}`);
|
|
722
|
-
|
|
723
|
+
for (const [name, value] of options.query ?? []) url.searchParams.append(name, value);
|
|
723
724
|
const headers = {
|
|
724
725
|
[options.credential.header]: options.credential.value,
|
|
725
726
|
"X-API-Version": API_VERSION,
|
|
726
|
-
Accept: "application/json"
|
|
727
|
+
Accept: options.downloads ? "*/*" : "application/json"
|
|
727
728
|
};
|
|
728
729
|
if (options.body !== void 0 && options.form === void 0) headers["Content-Type"] = "application/json";
|
|
729
730
|
const response = await fetch(url, {
|
|
@@ -731,6 +732,11 @@ async function request(options) {
|
|
|
731
732
|
headers,
|
|
732
733
|
body: options.form ?? (options.body === void 0 ? void 0 : JSON.stringify(options.body))
|
|
733
734
|
});
|
|
735
|
+
if (options.downloads && response.ok) return { data: {
|
|
736
|
+
bytes: Buffer.from(await response.arrayBuffer()),
|
|
737
|
+
contentType: response.headers.get("content-type"),
|
|
738
|
+
fileName: toFileName(response.headers.get("content-disposition"))
|
|
739
|
+
} };
|
|
734
740
|
const envelope = toEnvelope(await response.text());
|
|
735
741
|
if (!response.ok && envelope === void 0) throw new RequestFailure(response.status, [{
|
|
736
742
|
error: toStatusMessage(response.status),
|
|
@@ -745,6 +751,11 @@ async function request(options) {
|
|
|
745
751
|
}
|
|
746
752
|
return envelope ?? { data: null };
|
|
747
753
|
}
|
|
754
|
+
/** toFileName reads the name a download was offered under, when the server names one. */
|
|
755
|
+
function toFileName(disposition) {
|
|
756
|
+
const matched = /filename\*?=(?:UTF-8'')?"?([^";]+)"?/i.exec(disposition ?? "");
|
|
757
|
+
return matched?.[1] ? decodeURIComponent(matched[1]) : void 0;
|
|
758
|
+
}
|
|
748
759
|
function toLeadingSlash(path) {
|
|
749
760
|
return path.startsWith("/") ? path : `/${path}`;
|
|
750
761
|
}
|
|
@@ -1090,8 +1101,8 @@ function toOpeners(url) {
|
|
|
1090
1101
|
return [["xdg-open", [url]]];
|
|
1091
1102
|
}
|
|
1092
1103
|
/** openBrowser asks the desktop to show a URL, and reports whether anything took it. */
|
|
1093
|
-
function openBrowser(url) {
|
|
1094
|
-
for (const [command, args] of toOpeners(url)) if (
|
|
1104
|
+
function openBrowser(url, launch = spawnSync) {
|
|
1105
|
+
for (const [command, args] of toOpeners(url)) if (launch(command, args, {
|
|
1095
1106
|
stdio: "ignore",
|
|
1096
1107
|
cwd: command.endsWith(".exe") ? "/mnt/c" : void 0,
|
|
1097
1108
|
timeout: 1e4
|
|
@@ -1261,11 +1272,11 @@ function toClipboardCommand() {
|
|
|
1261
1272
|
return ["xclip", ["-selection", "clipboard"]];
|
|
1262
1273
|
}
|
|
1263
1274
|
/** copyToClipboard puts text on the clipboard, and reports whether anything took it. */
|
|
1264
|
-
function copyToClipboard(text) {
|
|
1275
|
+
function copyToClipboard(text, copy = spawnSync) {
|
|
1265
1276
|
const command = toClipboardCommand();
|
|
1266
1277
|
if (!command) return false;
|
|
1267
1278
|
const [name, args] = command;
|
|
1268
|
-
const result =
|
|
1279
|
+
const result = copy(name, args, { input: text });
|
|
1269
1280
|
return result.error === void 0 && result.status === 0;
|
|
1270
1281
|
}
|
|
1271
1282
|
//#endregion
|
|
@@ -1301,14 +1312,13 @@ function toPastedCallback(pasted) {
|
|
|
1301
1312
|
* toPrompt watches the keyboard while the browser is away. Pressing c copies the URL, and
|
|
1302
1313
|
* pasting a code finishes the sign in on a machine whose browser cannot reach this listener.
|
|
1303
1314
|
*/
|
|
1304
|
-
function toPrompt(url) {
|
|
1315
|
+
function toPrompt(url, input = process.stdin, copy = copyToClipboard) {
|
|
1305
1316
|
let settle = () => {};
|
|
1306
1317
|
let fail = () => {};
|
|
1307
1318
|
const pasted = new Promise((resolve, reject) => {
|
|
1308
1319
|
settle = resolve;
|
|
1309
1320
|
fail = reject;
|
|
1310
1321
|
});
|
|
1311
|
-
const input = process.stdin;
|
|
1312
1322
|
pasted.catch(() => {});
|
|
1313
1323
|
if (!input.isTTY) return {
|
|
1314
1324
|
pasted,
|
|
@@ -1316,6 +1326,7 @@ function toPrompt(url) {
|
|
|
1316
1326
|
};
|
|
1317
1327
|
let typed = "";
|
|
1318
1328
|
const onData = (chunk) => {
|
|
1329
|
+
const isKeystroke = chunk.length === 1;
|
|
1319
1330
|
for (const character of chunk) {
|
|
1320
1331
|
if (character === CTRL_C) {
|
|
1321
1332
|
fail(/* @__PURE__ */ new Error("sign in was cancelled"));
|
|
@@ -1332,8 +1343,8 @@ function toPrompt(url) {
|
|
|
1332
1343
|
process.stderr.write("\b \b");
|
|
1333
1344
|
continue;
|
|
1334
1345
|
}
|
|
1335
|
-
if ((character === "c" || character === "C") && typed === "") {
|
|
1336
|
-
process.stderr.write(
|
|
1346
|
+
if (isKeystroke && (character === "c" || character === "C") && typed === "") {
|
|
1347
|
+
process.stderr.write(copy(url) ? "Copied the URL to your clipboard\n" : "Nothing on this host takes a clipboard\n");
|
|
1337
1348
|
continue;
|
|
1338
1349
|
}
|
|
1339
1350
|
typed += character;
|
|
@@ -1674,7 +1685,21 @@ function toTree(commands) {
|
|
|
1674
1685
|
subcommands: command.subcommands ? toTree(command.subcommands) : void 0
|
|
1675
1686
|
}));
|
|
1676
1687
|
}
|
|
1688
|
+
/** Signing in needs a browser and a person, neither of which an agent's session has. */
|
|
1689
|
+
const REFUSED_COMMANDS = /* @__PURE__ */ new Set([
|
|
1690
|
+
"login",
|
|
1691
|
+
"logout",
|
|
1692
|
+
"mcp"
|
|
1693
|
+
]);
|
|
1677
1694
|
async function toToolResult(params, run) {
|
|
1695
|
+
const name = params?.["name"];
|
|
1696
|
+
if (name !== void 0 && name !== TOOL.name) return {
|
|
1697
|
+
content: [{
|
|
1698
|
+
type: "text",
|
|
1699
|
+
text: `this server offers one tool, ${TOOL.name}`
|
|
1700
|
+
}],
|
|
1701
|
+
isError: true
|
|
1702
|
+
};
|
|
1678
1703
|
const args = (params?.["arguments"])?.args;
|
|
1679
1704
|
if (!Array.isArray(args) || args.some((entry) => typeof entry !== "string")) return {
|
|
1680
1705
|
content: [{
|
|
@@ -1683,7 +1708,15 @@ async function toToolResult(params, run) {
|
|
|
1683
1708
|
}],
|
|
1684
1709
|
isError: true
|
|
1685
1710
|
};
|
|
1686
|
-
const
|
|
1711
|
+
const asked = args;
|
|
1712
|
+
if (REFUSED_COMMANDS.has(asked[0] ?? "")) return {
|
|
1713
|
+
content: [{
|
|
1714
|
+
type: "text",
|
|
1715
|
+
text: `${asked[0] ?? ""} is run by a person at a terminal, not through this server. Run hardfin ${asked[0] ?? ""} yourself, then call this tool again`
|
|
1716
|
+
}],
|
|
1717
|
+
isError: true
|
|
1718
|
+
};
|
|
1719
|
+
const outcome = await run(asked);
|
|
1687
1720
|
return {
|
|
1688
1721
|
content: [{
|
|
1689
1722
|
type: "text",
|
|
@@ -1692,6 +1725,14 @@ async function toToolResult(params, run) {
|
|
|
1692
1725
|
isError: outcome.code !== 0
|
|
1693
1726
|
};
|
|
1694
1727
|
}
|
|
1728
|
+
/** toRequestId reads the id of a line that could not be answered, so a client is not left waiting. */
|
|
1729
|
+
function toRequestId(line) {
|
|
1730
|
+
try {
|
|
1731
|
+
return JSON.parse(line).id ?? null;
|
|
1732
|
+
} catch {
|
|
1733
|
+
return null;
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1695
1736
|
/** toCliRunner runs the CLI itself, so a tool call parses exactly as a terminal would. */
|
|
1696
1737
|
function toCliRunner() {
|
|
1697
1738
|
return (args) => new Promise((resolve) => {
|
|
@@ -1727,9 +1768,9 @@ async function serve(commands, run = toCliRunner()) {
|
|
|
1727
1768
|
} catch (error) {
|
|
1728
1769
|
response = {
|
|
1729
1770
|
jsonrpc: "2.0",
|
|
1730
|
-
id:
|
|
1771
|
+
id: toRequestId(line),
|
|
1731
1772
|
error: {
|
|
1732
|
-
code: -
|
|
1773
|
+
code: -32603,
|
|
1733
1774
|
message: error instanceof Error ? error.message : String(error)
|
|
1734
1775
|
}
|
|
1735
1776
|
};
|
|
@@ -1784,6 +1825,12 @@ const FILE_FLAG = {
|
|
|
1784
1825
|
valueName: "path",
|
|
1785
1826
|
schema: z.string()
|
|
1786
1827
|
};
|
|
1828
|
+
const OUTPUT_FLAG = {
|
|
1829
|
+
name: "output",
|
|
1830
|
+
description: "Where to write the file, or - for standard output",
|
|
1831
|
+
valueName: "path",
|
|
1832
|
+
schema: z.string()
|
|
1833
|
+
};
|
|
1787
1834
|
const JSON_FLAG = {
|
|
1788
1835
|
name: "json",
|
|
1789
1836
|
description: "Print machine-readable output, which is the default when stdout is not a terminal",
|
|
@@ -1796,6 +1843,7 @@ function defineOperation(operation) {
|
|
|
1796
1843
|
...operation.bodyFlags,
|
|
1797
1844
|
...operation.bodyFlags.some((flag) => flag.nullable) ? [UNSET_FLAG] : [],
|
|
1798
1845
|
...operation.upload ? [...operation.upload.fields, FILE_FLAG] : [],
|
|
1846
|
+
...operation.downloads ? [OUTPUT_FLAG] : [],
|
|
1799
1847
|
JSON_FLAG
|
|
1800
1848
|
];
|
|
1801
1849
|
return {
|
|
@@ -1841,15 +1889,18 @@ async function runOperation(operation, input) {
|
|
|
1841
1889
|
form = built;
|
|
1842
1890
|
}
|
|
1843
1891
|
try {
|
|
1844
|
-
|
|
1892
|
+
const envelope = await request({
|
|
1845
1893
|
apiUrl: input.resolved.settings.apiUrl,
|
|
1846
1894
|
credential: await toRequestCredential(input.resolved.settings),
|
|
1847
1895
|
method: operation.method,
|
|
1848
1896
|
path,
|
|
1849
1897
|
query: toQuery(operation, input.flags),
|
|
1850
1898
|
body,
|
|
1851
|
-
form
|
|
1852
|
-
|
|
1899
|
+
form,
|
|
1900
|
+
downloads: operation.downloads
|
|
1901
|
+
});
|
|
1902
|
+
if (operation.downloads) return toWritten(envelope.data, input);
|
|
1903
|
+
writeData(envelope.data);
|
|
1853
1904
|
return ExitCode.OK;
|
|
1854
1905
|
} catch (error) {
|
|
1855
1906
|
if (error instanceof NoCredential) {
|
|
@@ -1864,6 +1915,29 @@ async function runOperation(operation, input) {
|
|
|
1864
1915
|
return ExitCode.ERROR;
|
|
1865
1916
|
}
|
|
1866
1917
|
}
|
|
1918
|
+
/**
|
|
1919
|
+
* toWritten puts a downloaded file where it was asked for. A terminal is never written to,
|
|
1920
|
+
* because a person would otherwise have their session filled with a file's bytes.
|
|
1921
|
+
*/
|
|
1922
|
+
function toWritten(download, input) {
|
|
1923
|
+
const asked = input.flags["output"];
|
|
1924
|
+
const path = typeof asked === "string" ? asked : download.fileName ?? "-";
|
|
1925
|
+
if (path === "-") {
|
|
1926
|
+
if (process.stdout.isTTY) {
|
|
1927
|
+
writeFailure("this answer is a file, so name where to write it with --output, or send it on with a pipe", input.isJSON);
|
|
1928
|
+
return ExitCode.USAGE;
|
|
1929
|
+
}
|
|
1930
|
+
process.stdout.write(download.bytes);
|
|
1931
|
+
return ExitCode.OK;
|
|
1932
|
+
}
|
|
1933
|
+
writeFileSync(path, download.bytes);
|
|
1934
|
+
writeData({
|
|
1935
|
+
written: path,
|
|
1936
|
+
bytes: download.bytes.length,
|
|
1937
|
+
contentType: download.contentType
|
|
1938
|
+
});
|
|
1939
|
+
return ExitCode.OK;
|
|
1940
|
+
}
|
|
1867
1941
|
/** toPath fills the path template from the positional arguments, in order. */
|
|
1868
1942
|
function toPath(operation, args) {
|
|
1869
1943
|
if (args.length !== operation.pathParameters.length) return;
|
|
@@ -2593,6 +2667,7 @@ const surfaceCommands = [
|
|
|
2593
2667
|
name: "is-in-service-date-managed-automatically",
|
|
2594
2668
|
jsonPath: ["isInServiceDateManagedAutomatically"],
|
|
2595
2669
|
description: "Whether Hardfin sets the in-service date itself, which sending an in-service date turns off",
|
|
2670
|
+
negatable: true,
|
|
2596
2671
|
nullable: true,
|
|
2597
2672
|
schema: z.boolean()
|
|
2598
2673
|
},
|
|
@@ -2659,6 +2734,7 @@ const surfaceCommands = [
|
|
|
2659
2734
|
name: "automatic",
|
|
2660
2735
|
jsonPath: ["automatic"],
|
|
2661
2736
|
description: "Whether Hardfin sets the asset's in-service date itself",
|
|
2737
|
+
negatable: true,
|
|
2662
2738
|
required: true,
|
|
2663
2739
|
schema: z.boolean()
|
|
2664
2740
|
}]
|
|
@@ -3361,12 +3437,14 @@ const surfaceCommands = [
|
|
|
3361
3437
|
name: "is-customer",
|
|
3362
3438
|
jsonPath: ["isCustomer"],
|
|
3363
3439
|
description: "Whether the company is a customer",
|
|
3440
|
+
negatable: true,
|
|
3364
3441
|
schema: z.boolean()
|
|
3365
3442
|
},
|
|
3366
3443
|
{
|
|
3367
3444
|
name: "is-supplier",
|
|
3368
3445
|
jsonPath: ["isSupplier"],
|
|
3369
3446
|
description: "Whether the company is a supplier",
|
|
3447
|
+
negatable: true,
|
|
3370
3448
|
schema: z.boolean()
|
|
3371
3449
|
},
|
|
3372
3450
|
{
|
|
@@ -3466,6 +3544,7 @@ const surfaceCommands = [
|
|
|
3466
3544
|
name: "is-archived",
|
|
3467
3545
|
jsonPath: ["isArchived"],
|
|
3468
3546
|
description: "Whether the customer is archived",
|
|
3547
|
+
negatable: true,
|
|
3469
3548
|
nullable: true,
|
|
3470
3549
|
schema: z.boolean()
|
|
3471
3550
|
},
|
|
@@ -3473,6 +3552,7 @@ const surfaceCommands = [
|
|
|
3473
3552
|
name: "is-customer",
|
|
3474
3553
|
jsonPath: ["isCustomer"],
|
|
3475
3554
|
description: "Whether the company is a customer",
|
|
3555
|
+
negatable: true,
|
|
3476
3556
|
nullable: true,
|
|
3477
3557
|
schema: z.boolean()
|
|
3478
3558
|
},
|
|
@@ -3480,6 +3560,7 @@ const surfaceCommands = [
|
|
|
3480
3560
|
name: "is-supplier",
|
|
3481
3561
|
jsonPath: ["isSupplier"],
|
|
3482
3562
|
description: "Whether the company is a supplier",
|
|
3563
|
+
negatable: true,
|
|
3483
3564
|
nullable: true,
|
|
3484
3565
|
schema: z.boolean()
|
|
3485
3566
|
},
|
|
@@ -3554,7 +3635,8 @@ const surfaceCommands = [
|
|
|
3554
3635
|
description: "True when the file downloads as an attachment rather than opening inline",
|
|
3555
3636
|
schema: z.boolean()
|
|
3556
3637
|
}],
|
|
3557
|
-
bodyFlags: []
|
|
3638
|
+
bodyFlags: [],
|
|
3639
|
+
downloads: true
|
|
3558
3640
|
})]
|
|
3559
3641
|
},
|
|
3560
3642
|
{
|
|
@@ -3657,6 +3739,7 @@ const surfaceCommands = [
|
|
|
3657
3739
|
name: "accepts-bulk-serials",
|
|
3658
3740
|
jsonPath: ["acceptsBulkSerials"],
|
|
3659
3741
|
description: "Whether a BULK item records serial numbers on its units, which SERVICE and DEVICE items ignore",
|
|
3742
|
+
negatable: true,
|
|
3660
3743
|
schema: z.boolean()
|
|
3661
3744
|
},
|
|
3662
3745
|
{
|
|
@@ -3793,6 +3876,7 @@ const surfaceCommands = [
|
|
|
3793
3876
|
name: "accepts-bulk-serials",
|
|
3794
3877
|
jsonPath: ["acceptsBulkSerials"],
|
|
3795
3878
|
description: "Whether a BULK item records serial numbers on its units, read only beside type",
|
|
3879
|
+
negatable: true,
|
|
3796
3880
|
nullable: true,
|
|
3797
3881
|
schema: z.boolean()
|
|
3798
3882
|
},
|
|
@@ -3821,6 +3905,7 @@ const surfaceCommands = [
|
|
|
3821
3905
|
name: "is-archived",
|
|
3822
3906
|
jsonPath: ["isArchived"],
|
|
3823
3907
|
description: "Whether the item is archived, which cannot be null",
|
|
3908
|
+
negatable: true,
|
|
3824
3909
|
nullable: true,
|
|
3825
3910
|
schema: z.boolean()
|
|
3826
3911
|
},
|
|
@@ -4290,18 +4375,21 @@ const surfaceCommands = [
|
|
|
4290
4375
|
name: "is-inventory",
|
|
4291
4376
|
jsonPath: ["isInventory"],
|
|
4292
4377
|
description: "Whether assets at the location count as inventory for reporting",
|
|
4378
|
+
negatable: true,
|
|
4293
4379
|
schema: z.boolean()
|
|
4294
4380
|
},
|
|
4295
4381
|
{
|
|
4296
4382
|
name: "is-inventory-override",
|
|
4297
4383
|
jsonPath: ["isInventoryOverride"],
|
|
4298
4384
|
description: "Whether a zone sets its own isInventory rather than inheriting its parent site's, which a site refuses",
|
|
4385
|
+
negatable: true,
|
|
4299
4386
|
schema: z.boolean()
|
|
4300
4387
|
},
|
|
4301
4388
|
{
|
|
4302
4389
|
name: "is-transient",
|
|
4303
4390
|
jsonPath: ["isTransient"],
|
|
4304
4391
|
description: "Whether assets make only occasional or temporary stops at the location, which hides it from location lists by default",
|
|
4392
|
+
negatable: true,
|
|
4305
4393
|
schema: z.boolean()
|
|
4306
4394
|
},
|
|
4307
4395
|
{
|
|
@@ -4443,6 +4531,7 @@ const surfaceCommands = [
|
|
|
4443
4531
|
name: "is-archived",
|
|
4444
4532
|
jsonPath: ["isArchived"],
|
|
4445
4533
|
description: "Whether the location is archived, and archiving a site archives its zones",
|
|
4534
|
+
negatable: true,
|
|
4446
4535
|
nullable: true,
|
|
4447
4536
|
schema: z.boolean()
|
|
4448
4537
|
},
|
|
@@ -4450,6 +4539,7 @@ const surfaceCommands = [
|
|
|
4450
4539
|
name: "is-inventory",
|
|
4451
4540
|
jsonPath: ["isInventory"],
|
|
4452
4541
|
description: "Whether assets at the location count as inventory for reporting",
|
|
4542
|
+
negatable: true,
|
|
4453
4543
|
nullable: true,
|
|
4454
4544
|
schema: z.boolean()
|
|
4455
4545
|
},
|
|
@@ -4457,6 +4547,7 @@ const surfaceCommands = [
|
|
|
4457
4547
|
name: "is-inventory-override",
|
|
4458
4548
|
jsonPath: ["isInventoryOverride"],
|
|
4459
4549
|
description: "Whether a zone sets its own isInventory rather than inheriting its parent site's, which a site refuses",
|
|
4550
|
+
negatable: true,
|
|
4460
4551
|
nullable: true,
|
|
4461
4552
|
schema: z.boolean()
|
|
4462
4553
|
},
|
|
@@ -4464,6 +4555,7 @@ const surfaceCommands = [
|
|
|
4464
4555
|
name: "is-transient",
|
|
4465
4556
|
jsonPath: ["isTransient"],
|
|
4466
4557
|
description: "Whether assets make only occasional or temporary stops at the location, which hides it from location lists by default",
|
|
4558
|
+
negatable: true,
|
|
4467
4559
|
nullable: true,
|
|
4468
4560
|
schema: z.boolean()
|
|
4469
4561
|
},
|
|
@@ -4871,13 +4963,16 @@ function toRejectedFlag(command, flags) {
|
|
|
4871
4963
|
}
|
|
4872
4964
|
}
|
|
4873
4965
|
//#endregion
|
|
4874
|
-
//#region src/cli.ts
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4966
|
+
//#region src/cli/program.ts
|
|
4967
|
+
/** toCli builds the parser from the registry, which is what every surface reads. */
|
|
4968
|
+
function toCli(entries = commands) {
|
|
4969
|
+
const program = new Command();
|
|
4970
|
+
program.name("hardfin").description("Call the Hardfin API from a terminal or an agent").version(version$1, "-v, --version").option("--api-url <url>", "The API to call, whose host also holds the authorization server").option("--issuer-url <url>", "The authorization server, when it does not sit at the API's host").showHelpAfterError().enablePositionalOptions();
|
|
4971
|
+
for (const command of entries) program.addCommand(toProgram(program, command), { hidden: command.hidden });
|
|
4972
|
+
return program;
|
|
4973
|
+
}
|
|
4879
4974
|
/** toProgram wires one registry command into the parser. */
|
|
4880
|
-
function toProgram(command) {
|
|
4975
|
+
function toProgram(root, command) {
|
|
4881
4976
|
const program = new Command(command.name).summary(command.summary).description(command.description ?? command.summary);
|
|
4882
4977
|
for (const argument of command.arguments) {
|
|
4883
4978
|
const name = argument.variadic ? `${argument.name}...` : argument.name;
|
|
@@ -4887,21 +4982,22 @@ function toProgram(command) {
|
|
|
4887
4982
|
const short = flag.short ? `-${flag.short}, ` : "";
|
|
4888
4983
|
const value = flag.valueName ? ` <${flag.valueName}>` : "";
|
|
4889
4984
|
const option = new Option(`${short}--${flag.name}${value}`, flag.description);
|
|
4985
|
+
if (flag.negatable) program.addOption(new Option(`--no-${flag.name}`, `${flag.description}, turned off`));
|
|
4890
4986
|
if (flag.repeatable) option.argParser(collect);
|
|
4891
4987
|
if (flag.defaultValue !== void 0) option.default(flag.defaultValue);
|
|
4892
4988
|
program.addOption(option);
|
|
4893
4989
|
}
|
|
4894
4990
|
for (const example of command.examples) program.addHelpText("after", `\n${example.description}:\n $ ${example.command}`);
|
|
4895
|
-
for (const subcommand of command.subcommands ?? []) program.addCommand(toProgram(subcommand), { hidden: subcommand.hidden });
|
|
4991
|
+
for (const subcommand of command.subcommands ?? []) program.addCommand(toProgram(root, subcommand), { hidden: subcommand.hidden });
|
|
4896
4992
|
if (!command.run) return program;
|
|
4897
4993
|
program.action(async (...parsed) => {
|
|
4898
4994
|
const flags = parsed[parsed.length - 2] ?? {};
|
|
4899
4995
|
const args = parsed.slice(0, parsed.length - 2).flatMap(toArgumentList);
|
|
4900
|
-
process.exitCode = await toExitCode(command, args, flags);
|
|
4996
|
+
process.exitCode = await toExitCode(root, command, args, flags);
|
|
4901
4997
|
});
|
|
4902
4998
|
return program;
|
|
4903
4999
|
}
|
|
4904
|
-
async function toExitCode(command, args, flags) {
|
|
5000
|
+
async function toExitCode(root, command, args, flags) {
|
|
4905
5001
|
const isJSON = isJSONOutput(flags);
|
|
4906
5002
|
const rejected = toRejectedFlag(command, flags);
|
|
4907
5003
|
if (rejected) {
|
|
@@ -4910,8 +5006,8 @@ async function toExitCode(command, args, flags) {
|
|
|
4910
5006
|
}
|
|
4911
5007
|
try {
|
|
4912
5008
|
const resolved = toSettings({
|
|
4913
|
-
apiUrl:
|
|
4914
|
-
issuerUrl:
|
|
5009
|
+
apiUrl: root.opts()["apiUrl"],
|
|
5010
|
+
issuerUrl: root.opts()["issuerUrl"]
|
|
4915
5011
|
});
|
|
4916
5012
|
return await command.run?.({
|
|
4917
5013
|
args,
|
|
@@ -4933,4 +5029,7 @@ function collect(value, previous) {
|
|
|
4933
5029
|
return [...previous ?? [], value];
|
|
4934
5030
|
}
|
|
4935
5031
|
//#endregion
|
|
5032
|
+
//#region src/cli.ts
|
|
5033
|
+
await toCli().parseAsync(process.argv);
|
|
5034
|
+
//#endregion
|
|
4936
5035
|
export {};
|