@hardfin/cli 0.0.2-dev.16 → 0.0.2-dev.17
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 +103 -10
- 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
|
@@ -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
|
}
|
|
@@ -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
|
},
|
|
@@ -4887,6 +4979,7 @@ function toProgram(command) {
|
|
|
4887
4979
|
const short = flag.short ? `-${flag.short}, ` : "";
|
|
4888
4980
|
const value = flag.valueName ? ` <${flag.valueName}>` : "";
|
|
4889
4981
|
const option = new Option(`${short}--${flag.name}${value}`, flag.description);
|
|
4982
|
+
if (flag.negatable) program.addOption(new Option(`--no-${flag.name}`, `${flag.description}, turned off`));
|
|
4890
4983
|
if (flag.repeatable) option.argParser(collect);
|
|
4891
4984
|
if (flag.defaultValue !== void 0) option.default(flag.defaultValue);
|
|
4892
4985
|
program.addOption(option);
|