@hardfin/cli 0.0.2-dev.3 → 0.0.2-dev.6

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/dist/cli.js ADDED
@@ -0,0 +1,1552 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ import { Command, Option } from "commander";
4
+ import { z } from "zod";
5
+ import { readFileSync } from "node:fs";
6
+ //#region src/command/registry.ts
7
+ /** ExitCode is what the process returns, and what an agent branches on. */
8
+ const ExitCode = {
9
+ OK: 0,
10
+ ERROR: 1,
11
+ USAGE: 2,
12
+ NOT_AUTHENTICATED: 4
13
+ };
14
+ /** defineCommand records one command so every surface reads the same declaration. */
15
+ function defineCommand(command) {
16
+ return command;
17
+ }
18
+ //#endregion
19
+ //#region src/config/settings.ts
20
+ /** The API version this build was written against, sent on every request. */
21
+ const API_VERSION = "2026-09-17";
22
+ const DEFAULT_API_URL = "https://api.hardfin.com/v2";
23
+ /** toApiUrl picks the API this invocation talks to. */
24
+ function toApiUrl(override) {
25
+ return (override ?? process.env["HARDFIN_API_URL"] ?? DEFAULT_API_URL).replace(/\/+$/, "");
26
+ }
27
+ /** toApiKey reads the API key an unattended caller set. */
28
+ function toApiKey() {
29
+ return process.env["HARDFIN_API_KEY"] || void 0;
30
+ }
31
+ //#endregion
32
+ //#region src/output/writer.ts
33
+ /** writeData prints a command's result on stdout. */
34
+ function writeData(value) {
35
+ if (typeof value === "string") {
36
+ process.stdout.write(value.endsWith("\n") ? value : `${value}\n`);
37
+ return;
38
+ }
39
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
40
+ }
41
+ /** writeFailure prints why a command failed on stderr, as text or as JSON. */
42
+ function writeFailure(message, isJSON, errors, requestId) {
43
+ if (!isJSON) {
44
+ process.stderr.write(`error: ${message}\n`);
45
+ for (const entry of errors?.slice(1) ?? []) process.stderr.write(` ${entry.error} (${entry.statusCode})\n`);
46
+ if (requestId) process.stderr.write(` request ${requestId}\n`);
47
+ return;
48
+ }
49
+ const payload = {
50
+ errors: errors ?? [{
51
+ error: message,
52
+ statusCode: 0
53
+ }],
54
+ requestId: requestId ?? null
55
+ };
56
+ process.stderr.write(`${JSON.stringify(payload, null, 2)}\n`);
57
+ }
58
+ /** isJSONOutput decides whether this invocation prints machine-readable output. */
59
+ function isJSONOutput(flags) {
60
+ if (flags["json"] === true) return true;
61
+ return !process.stdout.isTTY;
62
+ }
63
+ /** version is what this build reports, read from the published package manifest. */
64
+ const version = createRequire(import.meta.url)("../package.json").version;
65
+ //#endregion
66
+ //#region src/command/agent-guide.ts
67
+ const agentGuideCommand = defineCommand({
68
+ name: "agent-guide",
69
+ summary: "Print instructions an agent can follow to use this CLI",
70
+ description: "Writes Markdown describing every command, its flags, and its exit codes. Save it as a skill file, an AGENTS.md section, or paste it into a system prompt.",
71
+ arguments: [],
72
+ flags: [{
73
+ name: "json",
74
+ description: "Print the command registry as JSON instead of Markdown",
75
+ schema: z.boolean().optional()
76
+ }],
77
+ examples: [{
78
+ description: "Write a skill file",
79
+ command: "hardfin agent-guide > SKILL.md"
80
+ }, {
81
+ description: "Inspect the registry",
82
+ command: "hardfin agent-guide --json"
83
+ }],
84
+ run: runAgentGuide
85
+ });
86
+ /** toGuide renders the registry as the Markdown an agent reads. */
87
+ function toGuide(commands, version) {
88
+ const lines = [
89
+ "# Hardfin CLI",
90
+ "",
91
+ `The \`hardfin\` command calls the Hardfin API. This guide describes version ${version} of the CLI, which speaks API version ${API_VERSION}.`,
92
+ "",
93
+ "## Before calling",
94
+ "",
95
+ "- Authenticate by setting `HARDFIN_API_KEY`, or by running `hardfin login`",
96
+ "- Every command prints JSON on stdout and diagnostics on stderr",
97
+ "- Output is JSON whenever stdout is not a terminal, so no flag is needed when calling from code",
98
+ "",
99
+ "## Exit codes",
100
+ "",
101
+ "| Code | Means |",
102
+ "| --- | --- |",
103
+ "| 0 | The command succeeded |",
104
+ "| 1 | The command failed |",
105
+ "| 2 | The command was called wrongly |",
106
+ "| 4 | No usable credential |",
107
+ "",
108
+ "## Commands",
109
+ ""
110
+ ];
111
+ for (const command of commands) {
112
+ lines.push(`### \`hardfin ${command.name}\``, "", command.description ?? command.summary, "");
113
+ if (command.arguments.length > 0) {
114
+ lines.push("| Argument | Required | Holds |", "| --- | --- | --- |");
115
+ for (const argument of command.arguments) lines.push(`| \`${argument.name}\` | ${argument.required ? "yes" : "no"} | ${argument.description} |`);
116
+ lines.push("");
117
+ }
118
+ if (command.flags.length > 0) {
119
+ lines.push("| Flag | Takes | Does |", "| --- | --- | --- |");
120
+ for (const flag of command.flags) {
121
+ const name = flag.short ? `-${flag.short}, --${flag.name}` : `--${flag.name}`;
122
+ lines.push(`| \`${name}\` | ${flag.valueName ?? "nothing"} | ${flag.description} |`);
123
+ }
124
+ lines.push("");
125
+ }
126
+ for (const example of command.examples) lines.push(`${example.description}:`, "", "```sh", example.command, "```", "");
127
+ }
128
+ return lines.join("\n");
129
+ }
130
+ async function runAgentGuide(input) {
131
+ if (input.flags["json"] === true) {
132
+ writeData(input.commands.map(toSummary));
133
+ return ExitCode.OK;
134
+ }
135
+ writeData(toGuide(input.commands, version));
136
+ return ExitCode.OK;
137
+ }
138
+ function toSummary(command) {
139
+ return {
140
+ name: command.name,
141
+ summary: command.summary,
142
+ description: command.description ?? command.summary,
143
+ arguments: command.arguments,
144
+ flags: command.flags.map((flag) => ({
145
+ name: flag.name,
146
+ short: flag.short ?? null,
147
+ description: flag.description,
148
+ valueName: flag.valueName ?? null,
149
+ repeatable: flag.repeatable ?? false
150
+ })),
151
+ examples: command.examples
152
+ };
153
+ }
154
+ //#endregion
155
+ //#region src/http/client.ts
156
+ /** RequestFailure is a call the API refused, carrying what the envelope said. */
157
+ var RequestFailure = class extends Error {
158
+ status;
159
+ errors;
160
+ requestId;
161
+ constructor(status, errors, requestId) {
162
+ super(errors[0]?.error ?? `the request failed with status ${status}`);
163
+ this.name = "RequestFailure";
164
+ this.status = status;
165
+ this.errors = errors;
166
+ this.requestId = requestId;
167
+ }
168
+ };
169
+ /** request calls one /v2 endpoint and returns the envelope it answered with. */
170
+ async function request(options) {
171
+ const url = new URL(`${options.apiUrl}${toLeadingSlash(options.path)}`);
172
+ if (options.query) url.search = options.query.toString();
173
+ const headers = {
174
+ "X-API-Key": options.apiKey,
175
+ "X-API-Version": API_VERSION,
176
+ Accept: "application/json"
177
+ };
178
+ if (options.body !== void 0) headers["Content-Type"] = "application/json";
179
+ const response = await fetch(url, {
180
+ method: options.method,
181
+ headers,
182
+ body: options.body === void 0 ? void 0 : JSON.stringify(options.body)
183
+ });
184
+ const envelope = toEnvelope(await response.text());
185
+ if (!response.ok && envelope === void 0) throw new RequestFailure(response.status, [{
186
+ error: toStatusMessage(response.status),
187
+ statusCode: response.status
188
+ }]);
189
+ if (!response.ok) {
190
+ const errors = envelope?.metadata?.errors ?? [];
191
+ throw new RequestFailure(response.status, errors.length > 0 ? errors : [{
192
+ error: toStatusMessage(response.status),
193
+ statusCode: response.status
194
+ }], envelope?.metadata?.requestId);
195
+ }
196
+ return envelope ?? { data: null };
197
+ }
198
+ function toLeadingSlash(path) {
199
+ return path.startsWith("/") ? path : `/${path}`;
200
+ }
201
+ function toEnvelope(text) {
202
+ if (text.trim() === "") return;
203
+ try {
204
+ return JSON.parse(text);
205
+ } catch {
206
+ return;
207
+ }
208
+ }
209
+ function toStatusMessage(status) {
210
+ if (status === 401) return "the credential is missing or is not valid";
211
+ if (status === 403) return "the organization is deactivated, or its access has ended";
212
+ return `the request failed with status ${status}`;
213
+ }
214
+ const apiCommand = defineCommand({
215
+ name: "api",
216
+ summary: "Call a Hardfin API endpoint and print what it answers",
217
+ description: "Reaches every endpoint the API publishes. The path is everything after /v2, and the response envelope is printed as it arrived.",
218
+ arguments: [{
219
+ name: "path",
220
+ description: "The endpoint path, such as /customer or /item/item_V1StGXR8Z5jdHi6B",
221
+ required: true
222
+ }],
223
+ flags: [
224
+ {
225
+ name: "method",
226
+ short: "X",
227
+ description: "The HTTP method to use",
228
+ valueName: "method",
229
+ schema: z.enum([
230
+ "GET",
231
+ "POST",
232
+ "PATCH",
233
+ "PUT",
234
+ "DELETE"
235
+ ]),
236
+ defaultValue: "GET"
237
+ },
238
+ {
239
+ name: "field",
240
+ short: "f",
241
+ description: "A query parameter as key=value, repeatable",
242
+ valueName: "key=value",
243
+ repeatable: true,
244
+ schema: z.array(z.string()).default([])
245
+ },
246
+ {
247
+ name: "input",
248
+ description: "A file holding the JSON request body, or - for stdin",
249
+ valueName: "file",
250
+ schema: z.string().optional()
251
+ },
252
+ {
253
+ name: "json",
254
+ description: "Print machine-readable output, which is the default when stdout is not a terminal",
255
+ schema: z.boolean().optional()
256
+ }
257
+ ],
258
+ examples: [
259
+ {
260
+ description: "List customers",
261
+ command: "hardfin api /customer"
262
+ },
263
+ {
264
+ description: "Take the second page",
265
+ command: "hardfin api /customer -f page=2 -f limit=50"
266
+ },
267
+ {
268
+ description: "Change an item",
269
+ command: "hardfin api -X PATCH /item/item_V1StGXR8Z5jdHi6B --input body.json"
270
+ }
271
+ ],
272
+ run: runApi
273
+ });
274
+ async function runApi(input) {
275
+ const apiKey = toApiKey();
276
+ if (!apiKey) {
277
+ writeFailure("not authenticated. Set HARDFIN_API_KEY to an API key for your organization", input.isJSON);
278
+ return ExitCode.NOT_AUTHENTICATED;
279
+ }
280
+ const path = input.args[0];
281
+ if (!path) {
282
+ writeFailure("a path is required, such as /customer", input.isJSON);
283
+ return ExitCode.USAGE;
284
+ }
285
+ const query = toQuery$1(input.flags["field"]);
286
+ if (query === void 0) {
287
+ writeFailure("each --field is key=value, such as -f limit=50", input.isJSON);
288
+ return ExitCode.USAGE;
289
+ }
290
+ let body;
291
+ if (typeof input.flags["input"] === "string") {
292
+ body = toBody$1(input.flags["input"]);
293
+ if (body === void 0) {
294
+ writeFailure(`${input.flags["input"]} does not hold JSON`, input.isJSON);
295
+ return ExitCode.USAGE;
296
+ }
297
+ }
298
+ try {
299
+ writeData((await request({
300
+ apiUrl: toApiUrl(),
301
+ apiKey,
302
+ method: String(input.flags["method"] ?? "GET").toUpperCase(),
303
+ path,
304
+ query,
305
+ body
306
+ })).data);
307
+ return ExitCode.OK;
308
+ } catch (error) {
309
+ if (error instanceof RequestFailure) {
310
+ writeFailure(error.message, input.isJSON, error.errors, error.requestId);
311
+ return error.status === 401 ? ExitCode.NOT_AUTHENTICATED : ExitCode.ERROR;
312
+ }
313
+ writeFailure(error instanceof Error ? error.message : String(error), input.isJSON);
314
+ return ExitCode.ERROR;
315
+ }
316
+ }
317
+ /** toQuery folds the repeated --field flags into query parameters. */
318
+ function toQuery$1(fields) {
319
+ const query = new URLSearchParams();
320
+ for (const field of Array.isArray(fields) ? fields : []) {
321
+ const split = field.indexOf("=");
322
+ if (split < 1) return;
323
+ query.append(field.slice(0, split), field.slice(split + 1));
324
+ }
325
+ return query;
326
+ }
327
+ function toBody$1(source) {
328
+ const text = source === "-" ? readFileSync(0, "utf8") : readFileSync(source, "utf8");
329
+ try {
330
+ return JSON.parse(text);
331
+ } catch {
332
+ return;
333
+ }
334
+ }
335
+ //#endregion
336
+ //#region src/command/operation.ts
337
+ const INPUT_FLAG = {
338
+ name: "input",
339
+ description: "A file holding the JSON request body, or - for stdin",
340
+ valueName: "file",
341
+ schema: z.string()
342
+ };
343
+ const JSON_FLAG = {
344
+ name: "json",
345
+ description: "Print machine-readable output, which is the default when stdout is not a terminal",
346
+ schema: z.boolean()
347
+ };
348
+ /** defineOperation turns one endpoint into the command that calls it. */
349
+ function defineOperation(operation) {
350
+ const flags = [...operation.queryFlags, JSON_FLAG];
351
+ if (operation.takesBody) flags.splice(flags.length - 1, 0, INPUT_FLAG);
352
+ return {
353
+ name: operation.name,
354
+ summary: operation.summary,
355
+ description: operation.description ?? operation.summary,
356
+ arguments: operation.pathParameters,
357
+ flags,
358
+ examples: [],
359
+ run: (input) => runOperation(operation, input)
360
+ };
361
+ }
362
+ async function runOperation(operation, input) {
363
+ const apiKey = toApiKey();
364
+ if (!apiKey) {
365
+ writeFailure("not authenticated. Set HARDFIN_API_KEY to an API key for your organization", input.isJSON);
366
+ return ExitCode.NOT_AUTHENTICATED;
367
+ }
368
+ const path = toPath(operation, input.args);
369
+ if (path === void 0) {
370
+ writeFailure(`this command takes ${operation.pathParameters.length} argument(s)`, input.isJSON);
371
+ return ExitCode.USAGE;
372
+ }
373
+ let body;
374
+ if (typeof input.flags["input"] === "string") {
375
+ body = toBody(input.flags["input"]);
376
+ if (body === void 0) {
377
+ writeFailure(`${input.flags["input"]} does not hold JSON`, input.isJSON);
378
+ return ExitCode.USAGE;
379
+ }
380
+ }
381
+ try {
382
+ writeData((await request({
383
+ apiUrl: toApiUrl(),
384
+ apiKey,
385
+ method: operation.method,
386
+ path,
387
+ query: toQuery(operation, input.flags),
388
+ body
389
+ })).data);
390
+ return ExitCode.OK;
391
+ } catch (error) {
392
+ if (error instanceof RequestFailure) {
393
+ writeFailure(error.message, input.isJSON, error.errors, error.requestId);
394
+ return error.status === 401 ? ExitCode.NOT_AUTHENTICATED : ExitCode.ERROR;
395
+ }
396
+ writeFailure(error instanceof Error ? error.message : String(error), input.isJSON);
397
+ return ExitCode.ERROR;
398
+ }
399
+ }
400
+ /** toPath fills the path template from the positional arguments, in order. */
401
+ function toPath(operation, args) {
402
+ if (args.length !== operation.pathParameters.length) return;
403
+ let path = operation.path;
404
+ for (const [index, parameter] of operation.pathParameters.entries()) path = path.replace(`{${parameter.name}}`, encodeURIComponent(args[index] ?? ""));
405
+ return path;
406
+ }
407
+ /** toQuery carries only the flags this invocation actually set. */
408
+ function toQuery(operation, flags) {
409
+ const query = new URLSearchParams();
410
+ for (const flag of operation.queryFlags) {
411
+ const value = flags[toOptionKey(flag.name)];
412
+ if (value === void 0) continue;
413
+ for (const entry of Array.isArray(value) ? value : [value]) query.append(flag.queryName, String(entry));
414
+ }
415
+ return query;
416
+ }
417
+ /** toOptionKey names the parsed flag, which the parser reports in camel case. */
418
+ function toOptionKey(name) {
419
+ return name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
420
+ }
421
+ function toBody(source) {
422
+ const text = source === "-" ? readFileSync(0, "utf8") : readFileSync(source, "utf8");
423
+ try {
424
+ return JSON.parse(text);
425
+ } catch {
426
+ return;
427
+ }
428
+ }
429
+ //#endregion
430
+ //#region src/command/commands.ts
431
+ /** commands is every command the CLI offers, and drives help and the agent guide. */
432
+ const commands = [
433
+ ...[
434
+ {
435
+ name: "asset",
436
+ summary: "Asset commands",
437
+ arguments: [],
438
+ flags: [],
439
+ examples: [],
440
+ subcommands: [
441
+ defineOperation({
442
+ name: "list",
443
+ summary: "Get asset listing",
444
+ method: "GET",
445
+ path: "/asset",
446
+ pathParameters: [],
447
+ queryFlags: [
448
+ {
449
+ name: "page",
450
+ queryName: "page",
451
+ description: "The page to return, starting at 1",
452
+ valueName: "number",
453
+ schema: z.coerce.number()
454
+ },
455
+ {
456
+ name: "limit",
457
+ queryName: "limit",
458
+ description: "The number of records per page, from 1 to 100",
459
+ valueName: "number",
460
+ schema: z.coerce.number()
461
+ },
462
+ {
463
+ name: "sort-by",
464
+ queryName: "sortBy",
465
+ description: "The field to sort by",
466
+ valueName: "value",
467
+ schema: z.enum([
468
+ "serial",
469
+ "project",
470
+ "item",
471
+ "location",
472
+ "owner",
473
+ "activity"
474
+ ])
475
+ },
476
+ {
477
+ name: "sort-order",
478
+ queryName: "sortOrder",
479
+ description: "The sort direction",
480
+ valueName: "value",
481
+ schema: z.enum(["ASC", "DESC"])
482
+ },
483
+ {
484
+ name: "archived",
485
+ queryName: "archived",
486
+ description: "Whether to return unarchived records, archived records, or all of them",
487
+ valueName: "value",
488
+ schema: z.enum([
489
+ "all",
490
+ "false",
491
+ "true"
492
+ ])
493
+ },
494
+ {
495
+ name: "search-query",
496
+ queryName: "searchQuery",
497
+ description: "Text to match against the serial, description, item, project, owner, and location, ignored when shorter than three characters",
498
+ valueName: "value",
499
+ schema: z.string()
500
+ },
501
+ {
502
+ name: "for-asset-id",
503
+ queryName: "forAssetId",
504
+ description: "The IDs of the assets to list",
505
+ valueName: "value",
506
+ repeatable: true,
507
+ schema: z.array(z.string())
508
+ },
509
+ {
510
+ name: "for-asset-key",
511
+ queryName: "forAssetKey",
512
+ description: "The keys of the assets to list",
513
+ valueName: "value",
514
+ repeatable: true,
515
+ schema: z.array(z.string())
516
+ },
517
+ {
518
+ name: "for-customer",
519
+ queryName: "forCustomer",
520
+ description: "The IDs of the customers whose assets to list",
521
+ valueName: "value",
522
+ repeatable: true,
523
+ schema: z.array(z.string())
524
+ },
525
+ {
526
+ name: "for-item",
527
+ queryName: "forItem",
528
+ description: "The IDs of the items whose assets to list",
529
+ valueName: "value",
530
+ repeatable: true,
531
+ schema: z.array(z.string())
532
+ },
533
+ {
534
+ name: "at-site",
535
+ queryName: "atSite",
536
+ description: "The IDs of the locations whose assets to list",
537
+ valueName: "value",
538
+ repeatable: true,
539
+ schema: z.array(z.string())
540
+ },
541
+ {
542
+ name: "at-customer-sites",
543
+ queryName: "atCustomerSites",
544
+ description: "The IDs of the customers whose sites to list assets at",
545
+ valueName: "value",
546
+ repeatable: true,
547
+ schema: z.array(z.string())
548
+ },
549
+ {
550
+ name: "with-functional-statuses",
551
+ queryName: "withFunctionalStatuses",
552
+ description: "The functional statuses to list, where SCRAPPED lists scrapped assets",
553
+ valueName: "value",
554
+ repeatable: true,
555
+ schema: z.array(z.enum([
556
+ "FUNCTIONAL",
557
+ "NEEDS_REVIEW",
558
+ "NON-FUNCTIONAL",
559
+ "SCRAPPED"
560
+ ]))
561
+ },
562
+ {
563
+ name: "with-transit-statuses",
564
+ queryName: "withTransitStatuses",
565
+ description: "The transit statuses to list",
566
+ valueName: "value",
567
+ repeatable: true,
568
+ schema: z.array(z.enum([
569
+ "IN_TRANSIT",
570
+ "IN_TRANSIT_TO_FIELD",
571
+ "IN_TRANSIT_TO_INVENTORY",
572
+ "NOT_IN_TRANSIT"
573
+ ]))
574
+ },
575
+ {
576
+ name: "with-inventory-statuses",
577
+ queryName: "withInventoryStatuses",
578
+ description: "In_inventory, not_in_inventory, or both, which filters only when one is sent",
579
+ valueName: "value",
580
+ repeatable: true,
581
+ schema: z.array(z.string())
582
+ },
583
+ {
584
+ name: "with-location-company-type",
585
+ queryName: "withLocationCompanyType",
586
+ description: "Customer, manufacturer, or both, for assets at customer sites or your own, which filters only when one is sent",
587
+ valueName: "value",
588
+ repeatable: true,
589
+ schema: z.array(z.string())
590
+ },
591
+ {
592
+ name: "with-project-status",
593
+ queryName: "withProjectStatus",
594
+ description: "The project assignments to list: upcoming, active, past, or none",
595
+ valueName: "value",
596
+ repeatable: true,
597
+ schema: z.array(z.string())
598
+ },
599
+ {
600
+ name: "with-owner",
601
+ queryName: "withOwner",
602
+ description: "Manufacturer for assets your organization owns, customer for assets customers own, or both",
603
+ valueName: "value",
604
+ repeatable: true,
605
+ schema: z.array(z.string())
606
+ },
607
+ {
608
+ name: "for-project",
609
+ queryName: "forProject",
610
+ description: "The IDs of the projects whose assets to list",
611
+ valueName: "value",
612
+ repeatable: true,
613
+ schema: z.array(z.string())
614
+ },
615
+ {
616
+ name: "scrapped",
617
+ queryName: "scrapped",
618
+ description: "Whether to list unscrapped assets, scrapped assets, or all of them",
619
+ valueName: "value",
620
+ schema: z.enum([
621
+ "all",
622
+ "false",
623
+ "true"
624
+ ])
625
+ }
626
+ ],
627
+ takesBody: false
628
+ }),
629
+ defineOperation({
630
+ name: "create",
631
+ summary: "Create asset",
632
+ method: "POST",
633
+ path: "/asset",
634
+ pathParameters: [],
635
+ queryFlags: [],
636
+ takesBody: true
637
+ }),
638
+ defineOperation({
639
+ name: "get",
640
+ summary: "Get asset",
641
+ method: "GET",
642
+ path: "/asset/{assetKey}",
643
+ pathParameters: [{
644
+ name: "assetKey",
645
+ description: "The asset's key",
646
+ required: true
647
+ }],
648
+ queryFlags: [],
649
+ takesBody: false
650
+ }),
651
+ defineOperation({
652
+ name: "update",
653
+ summary: "Patch asset",
654
+ method: "PATCH",
655
+ path: "/asset/{assetKey}",
656
+ pathParameters: [{
657
+ name: "assetKey",
658
+ description: "The asset's key",
659
+ required: true
660
+ }],
661
+ queryFlags: [],
662
+ takesBody: true
663
+ }),
664
+ {
665
+ name: "accounting",
666
+ summary: "Accounting commands",
667
+ arguments: [],
668
+ flags: [],
669
+ examples: [],
670
+ subcommands: [defineOperation({
671
+ name: "update",
672
+ summary: "Update asset accounting",
673
+ method: "PATCH",
674
+ path: "/asset/{assetKey}/accounting",
675
+ pathParameters: [{
676
+ name: "assetKey",
677
+ description: "The asset's key",
678
+ required: true
679
+ }],
680
+ queryFlags: [],
681
+ takesBody: true
682
+ }), {
683
+ name: "in-service-management",
684
+ summary: "In service management commands",
685
+ arguments: [],
686
+ flags: [],
687
+ examples: [],
688
+ subcommands: [defineOperation({
689
+ name: "update",
690
+ summary: "Toggle in service date management",
691
+ method: "PATCH",
692
+ path: "/asset/{assetKey}/accounting/in-service-management",
693
+ pathParameters: [{
694
+ name: "assetKey",
695
+ description: "The asset's key",
696
+ required: true
697
+ }],
698
+ queryFlags: [],
699
+ takesBody: true
700
+ })]
701
+ }]
702
+ },
703
+ {
704
+ name: "cost-adjustment",
705
+ summary: "Cost adjustment commands",
706
+ arguments: [],
707
+ flags: [],
708
+ examples: [],
709
+ subcommands: [defineOperation({
710
+ name: "create",
711
+ summary: "Create asset cost adjustment",
712
+ method: "POST",
713
+ path: "/asset/{assetKey}/cost-adjustment",
714
+ pathParameters: [{
715
+ name: "assetKey",
716
+ description: "The asset's key",
717
+ required: true
718
+ }],
719
+ queryFlags: [],
720
+ takesBody: true
721
+ })]
722
+ },
723
+ {
724
+ name: "file",
725
+ summary: "File commands",
726
+ arguments: [],
727
+ flags: [],
728
+ examples: [],
729
+ subcommands: [defineOperation({
730
+ name: "list",
731
+ summary: "Get asset files",
732
+ method: "GET",
733
+ path: "/asset/{assetKey}/file",
734
+ pathParameters: [{
735
+ name: "assetKey",
736
+ description: "The asset's key",
737
+ required: true
738
+ }],
739
+ queryFlags: [],
740
+ takesBody: false
741
+ }), defineOperation({
742
+ name: "delete",
743
+ summary: "Delete asset file",
744
+ method: "DELETE",
745
+ path: "/asset/{assetKey}/file/{fileKey}",
746
+ pathParameters: [{
747
+ name: "assetKey",
748
+ description: "The asset's key",
749
+ required: true
750
+ }, {
751
+ name: "fileKey",
752
+ description: "The file's key",
753
+ required: true
754
+ }],
755
+ queryFlags: [],
756
+ takesBody: false
757
+ })]
758
+ },
759
+ {
760
+ name: "ownership",
761
+ summary: "Ownership commands",
762
+ arguments: [],
763
+ flags: [],
764
+ examples: [],
765
+ subcommands: [
766
+ defineOperation({
767
+ name: "list",
768
+ summary: "Get asset ownership history",
769
+ method: "GET",
770
+ path: "/asset/{assetKey}/ownership",
771
+ pathParameters: [{
772
+ name: "assetKey",
773
+ description: "The asset's key",
774
+ required: true
775
+ }],
776
+ queryFlags: [],
777
+ takesBody: false
778
+ }),
779
+ defineOperation({
780
+ name: "create",
781
+ summary: "Create asset ownership",
782
+ method: "POST",
783
+ path: "/asset/{assetKey}/ownership",
784
+ pathParameters: [{
785
+ name: "assetKey",
786
+ description: "The asset's key",
787
+ required: true
788
+ }],
789
+ queryFlags: [],
790
+ takesBody: true
791
+ }),
792
+ defineOperation({
793
+ name: "clear",
794
+ summary: "Clear the ownership an asset holds today",
795
+ method: "DELETE",
796
+ path: "/asset/{assetKey}/ownership",
797
+ pathParameters: [{
798
+ name: "assetKey",
799
+ description: "The asset's key",
800
+ required: true
801
+ }],
802
+ queryFlags: [],
803
+ takesBody: true
804
+ }),
805
+ defineOperation({
806
+ name: "get",
807
+ summary: "Get asset ownership segment",
808
+ method: "GET",
809
+ path: "/asset/{assetKey}/ownership/{segmentKey}",
810
+ pathParameters: [{
811
+ name: "assetKey",
812
+ description: "The asset's key",
813
+ required: true
814
+ }, {
815
+ name: "segmentKey",
816
+ description: "The ownership segment's key",
817
+ required: true
818
+ }],
819
+ queryFlags: [],
820
+ takesBody: false
821
+ }),
822
+ defineOperation({
823
+ name: "update",
824
+ summary: "Patch asset ownership segment",
825
+ method: "PATCH",
826
+ path: "/asset/{assetKey}/ownership/{segmentKey}",
827
+ pathParameters: [{
828
+ name: "assetKey",
829
+ description: "The asset's key",
830
+ required: true
831
+ }, {
832
+ name: "segmentKey",
833
+ description: "The ownership segment's key",
834
+ required: true
835
+ }],
836
+ queryFlags: [],
837
+ takesBody: true
838
+ }),
839
+ defineOperation({
840
+ name: "delete",
841
+ summary: "Delete asset ownership segment",
842
+ method: "DELETE",
843
+ path: "/asset/{assetKey}/ownership/{segmentKey}",
844
+ pathParameters: [{
845
+ name: "assetKey",
846
+ description: "The asset's key",
847
+ required: true
848
+ }, {
849
+ name: "segmentKey",
850
+ description: "The ownership segment's key",
851
+ required: true
852
+ }],
853
+ queryFlags: [],
854
+ takesBody: false
855
+ })
856
+ ]
857
+ },
858
+ {
859
+ name: "url-link",
860
+ summary: "URL link commands",
861
+ arguments: [],
862
+ flags: [],
863
+ examples: [],
864
+ subcommands: [defineOperation({
865
+ name: "list",
866
+ summary: "Get asset URL links",
867
+ method: "GET",
868
+ path: "/asset/{assetKey}/url-link",
869
+ pathParameters: [{
870
+ name: "assetKey",
871
+ description: "The asset's key",
872
+ required: true
873
+ }],
874
+ queryFlags: [],
875
+ takesBody: false
876
+ }), defineOperation({
877
+ name: "create",
878
+ summary: "Create asset URL link",
879
+ method: "POST",
880
+ path: "/asset/{assetKey}/url-link",
881
+ pathParameters: [{
882
+ name: "assetKey",
883
+ description: "The asset's key",
884
+ required: true
885
+ }],
886
+ queryFlags: [],
887
+ takesBody: true
888
+ })]
889
+ },
890
+ {
891
+ name: "useful-life-revision",
892
+ summary: "Useful life revision commands",
893
+ arguments: [],
894
+ flags: [],
895
+ examples: [],
896
+ subcommands: [defineOperation({
897
+ name: "create",
898
+ summary: "Create asset useful life revision",
899
+ method: "POST",
900
+ path: "/asset/{assetKey}/useful-life-revision",
901
+ pathParameters: [{
902
+ name: "assetKey",
903
+ description: "The asset's key",
904
+ required: true
905
+ }],
906
+ queryFlags: [],
907
+ takesBody: true
908
+ })]
909
+ }
910
+ ]
911
+ },
912
+ {
913
+ name: "customer",
914
+ summary: "Customer commands",
915
+ arguments: [],
916
+ flags: [],
917
+ examples: [],
918
+ subcommands: [
919
+ defineOperation({
920
+ name: "list",
921
+ summary: "Get customers",
922
+ method: "GET",
923
+ path: "/customer",
924
+ pathParameters: [],
925
+ queryFlags: [
926
+ {
927
+ name: "page",
928
+ queryName: "page",
929
+ description: "The page to return, starting at 1",
930
+ valueName: "number",
931
+ schema: z.coerce.number()
932
+ },
933
+ {
934
+ name: "limit",
935
+ queryName: "limit",
936
+ description: "The number of records per page, from 1 to 100",
937
+ valueName: "number",
938
+ schema: z.coerce.number()
939
+ },
940
+ {
941
+ name: "sort-by",
942
+ queryName: "sortBy",
943
+ description: "The field to sort by",
944
+ valueName: "value",
945
+ schema: z.string()
946
+ },
947
+ {
948
+ name: "sort-order",
949
+ queryName: "sortOrder",
950
+ description: "The sort direction",
951
+ valueName: "value",
952
+ schema: z.enum(["ASC", "DESC"])
953
+ },
954
+ {
955
+ name: "archived",
956
+ queryName: "archived",
957
+ description: "Whether to return unarchived records, archived records, or all of them",
958
+ valueName: "value",
959
+ schema: z.enum([
960
+ "all",
961
+ "false",
962
+ "true"
963
+ ])
964
+ },
965
+ {
966
+ name: "is-customer",
967
+ queryName: "isCustomer",
968
+ description: "True to return only customers, or false to return only non-customers",
969
+ schema: z.boolean()
970
+ },
971
+ {
972
+ name: "is-supplier",
973
+ queryName: "isSupplier",
974
+ description: "True to return only suppliers, or false to return only non-suppliers",
975
+ schema: z.boolean()
976
+ },
977
+ {
978
+ name: "sync-statuses",
979
+ queryName: "syncStatuses",
980
+ description: "The CRM sync statuses to return: in-sync, out-of-sync, or not-linked",
981
+ valueName: "value",
982
+ repeatable: true,
983
+ schema: z.array(z.string())
984
+ },
985
+ {
986
+ name: "search",
987
+ queryName: "search",
988
+ description: "Text to match against customer names",
989
+ valueName: "value",
990
+ schema: z.string()
991
+ }
992
+ ],
993
+ takesBody: false
994
+ }),
995
+ defineOperation({
996
+ name: "create",
997
+ summary: "Create customer",
998
+ method: "POST",
999
+ path: "/customer",
1000
+ pathParameters: [],
1001
+ queryFlags: [],
1002
+ takesBody: true
1003
+ }),
1004
+ defineOperation({
1005
+ name: "get",
1006
+ summary: "Get customer",
1007
+ method: "GET",
1008
+ path: "/customer/{customerKey}",
1009
+ pathParameters: [{
1010
+ name: "customerKey",
1011
+ description: "The customer's key",
1012
+ required: true
1013
+ }],
1014
+ queryFlags: [],
1015
+ takesBody: false
1016
+ }),
1017
+ defineOperation({
1018
+ name: "update",
1019
+ summary: "Patch customer",
1020
+ method: "PATCH",
1021
+ path: "/customer/{customerKey}",
1022
+ pathParameters: [{
1023
+ name: "customerKey",
1024
+ description: "The customer's key",
1025
+ required: true
1026
+ }],
1027
+ queryFlags: [],
1028
+ takesBody: true
1029
+ })
1030
+ ]
1031
+ },
1032
+ {
1033
+ name: "file",
1034
+ summary: "File commands",
1035
+ arguments: [],
1036
+ flags: [],
1037
+ examples: [],
1038
+ subcommands: [defineOperation({
1039
+ name: "create",
1040
+ summary: "Upload file",
1041
+ method: "POST",
1042
+ path: "/file",
1043
+ pathParameters: [],
1044
+ queryFlags: [],
1045
+ takesBody: true
1046
+ }), defineOperation({
1047
+ name: "get",
1048
+ summary: "Get file",
1049
+ method: "GET",
1050
+ path: "/file/{fileKey}",
1051
+ pathParameters: [{
1052
+ name: "fileKey",
1053
+ description: "The file's key, and a public file is readable with any organization's API key while any other file is readable only with its own organization's",
1054
+ required: true
1055
+ }],
1056
+ queryFlags: [{
1057
+ name: "attachment",
1058
+ queryName: "attachment",
1059
+ description: "Present, with any value or none, when the file should download as an attachment rather than open inline",
1060
+ valueName: "value",
1061
+ schema: z.string()
1062
+ }],
1063
+ takesBody: false
1064
+ })]
1065
+ },
1066
+ {
1067
+ name: "item",
1068
+ summary: "Item commands",
1069
+ arguments: [],
1070
+ flags: [],
1071
+ examples: [],
1072
+ subcommands: [
1073
+ defineOperation({
1074
+ name: "list",
1075
+ summary: "Get items",
1076
+ method: "GET",
1077
+ path: "/item",
1078
+ pathParameters: [],
1079
+ queryFlags: [
1080
+ {
1081
+ name: "type",
1082
+ queryName: "type",
1083
+ description: "The item type to list, SERVICE, DEVICE, or BULK, or absent for every type",
1084
+ valueName: "value",
1085
+ schema: z.enum([
1086
+ "BULK",
1087
+ "DEVICE",
1088
+ "SERVICE"
1089
+ ])
1090
+ },
1091
+ {
1092
+ name: "search",
1093
+ queryName: "search",
1094
+ description: "Text to match against item names, SKUs, and descriptions, in any case",
1095
+ valueName: "value",
1096
+ schema: z.string()
1097
+ },
1098
+ {
1099
+ name: "page",
1100
+ queryName: "page",
1101
+ description: "The page to return, starting at 1",
1102
+ valueName: "number",
1103
+ schema: z.coerce.number()
1104
+ },
1105
+ {
1106
+ name: "limit",
1107
+ queryName: "limit",
1108
+ description: "The number of records per page, from 1 to 100",
1109
+ valueName: "number",
1110
+ schema: z.coerce.number()
1111
+ },
1112
+ {
1113
+ name: "sort-by",
1114
+ queryName: "sortBy",
1115
+ description: "The field to sort by",
1116
+ valueName: "value",
1117
+ schema: z.enum([
1118
+ "name",
1119
+ "sku",
1120
+ "type",
1121
+ "lastUpdatedAt"
1122
+ ])
1123
+ },
1124
+ {
1125
+ name: "sort-order",
1126
+ queryName: "sortOrder",
1127
+ description: "The sort direction",
1128
+ valueName: "value",
1129
+ schema: z.enum(["ASC", "DESC"])
1130
+ },
1131
+ {
1132
+ name: "archived",
1133
+ queryName: "archived",
1134
+ description: "Whether to return unarchived records, archived records, or all of them",
1135
+ valueName: "value",
1136
+ schema: z.enum([
1137
+ "all",
1138
+ "false",
1139
+ "true"
1140
+ ])
1141
+ },
1142
+ {
1143
+ name: "exclude-linked-integration",
1144
+ queryName: "excludeLinkedIntegration",
1145
+ description: "An integration whose already-linked items to leave out",
1146
+ valueName: "value",
1147
+ schema: z.string()
1148
+ }
1149
+ ],
1150
+ takesBody: false
1151
+ }),
1152
+ defineOperation({
1153
+ name: "create",
1154
+ summary: "Create item",
1155
+ method: "POST",
1156
+ path: "/item",
1157
+ pathParameters: [],
1158
+ queryFlags: [],
1159
+ takesBody: true
1160
+ }),
1161
+ defineOperation({
1162
+ name: "get",
1163
+ summary: "Get item",
1164
+ method: "GET",
1165
+ path: "/item/{itemKey}",
1166
+ pathParameters: [{
1167
+ name: "itemKey",
1168
+ description: "The item's key",
1169
+ required: true
1170
+ }],
1171
+ queryFlags: [],
1172
+ takesBody: false
1173
+ }),
1174
+ defineOperation({
1175
+ name: "update",
1176
+ summary: "Update item",
1177
+ method: "PATCH",
1178
+ path: "/item/{itemKey}",
1179
+ pathParameters: [{
1180
+ name: "itemKey",
1181
+ description: "The item's key",
1182
+ required: true
1183
+ }],
1184
+ queryFlags: [],
1185
+ takesBody: true
1186
+ }),
1187
+ {
1188
+ name: "accounting",
1189
+ summary: "Accounting commands",
1190
+ arguments: [],
1191
+ flags: [],
1192
+ examples: [],
1193
+ subcommands: [defineOperation({
1194
+ name: "update",
1195
+ summary: "Update item accounting",
1196
+ method: "PATCH",
1197
+ path: "/item/{itemKey}/accounting",
1198
+ pathParameters: [{
1199
+ name: "itemKey",
1200
+ description: "The item's key",
1201
+ required: true
1202
+ }],
1203
+ queryFlags: [],
1204
+ takesBody: true
1205
+ })]
1206
+ },
1207
+ {
1208
+ name: "field",
1209
+ summary: "Field commands",
1210
+ arguments: [],
1211
+ flags: [],
1212
+ examples: [],
1213
+ subcommands: [
1214
+ defineOperation({
1215
+ name: "create",
1216
+ summary: "Create item field",
1217
+ method: "POST",
1218
+ path: "/item/{itemKey}/field",
1219
+ pathParameters: [{
1220
+ name: "itemKey",
1221
+ description: "The item's key",
1222
+ required: true
1223
+ }],
1224
+ queryFlags: [],
1225
+ takesBody: true
1226
+ }),
1227
+ defineOperation({
1228
+ name: "update",
1229
+ summary: "Update item field",
1230
+ method: "PATCH",
1231
+ path: "/item/{itemKey}/field/{fieldKey}",
1232
+ pathParameters: [{
1233
+ name: "itemKey",
1234
+ description: "The item's key",
1235
+ required: true
1236
+ }, {
1237
+ name: "fieldKey",
1238
+ description: "The field's key",
1239
+ required: true
1240
+ }],
1241
+ queryFlags: [],
1242
+ takesBody: true
1243
+ }),
1244
+ defineOperation({
1245
+ name: "delete",
1246
+ summary: "Delete item field",
1247
+ method: "DELETE",
1248
+ path: "/item/{itemKey}/field/{fieldKey}",
1249
+ pathParameters: [{
1250
+ name: "itemKey",
1251
+ description: "The item's key",
1252
+ required: true
1253
+ }, {
1254
+ name: "fieldKey",
1255
+ description: "The field's key",
1256
+ required: true
1257
+ }],
1258
+ queryFlags: [],
1259
+ takesBody: false
1260
+ })
1261
+ ]
1262
+ }
1263
+ ]
1264
+ },
1265
+ {
1266
+ name: "location",
1267
+ summary: "Location commands",
1268
+ arguments: [],
1269
+ flags: [],
1270
+ examples: [],
1271
+ subcommands: [
1272
+ defineOperation({
1273
+ name: "list",
1274
+ summary: "Get location listing",
1275
+ method: "GET",
1276
+ path: "/location",
1277
+ pathParameters: [],
1278
+ queryFlags: [
1279
+ {
1280
+ name: "page",
1281
+ queryName: "page",
1282
+ description: "The page to return, starting at 1",
1283
+ valueName: "number",
1284
+ schema: z.coerce.number()
1285
+ },
1286
+ {
1287
+ name: "limit",
1288
+ queryName: "limit",
1289
+ description: "The number of records per page, from 1 to 100",
1290
+ valueName: "number",
1291
+ schema: z.coerce.number()
1292
+ },
1293
+ {
1294
+ name: "sort-by",
1295
+ queryName: "sortBy",
1296
+ description: "The field to sort by",
1297
+ valueName: "value",
1298
+ schema: z.enum([
1299
+ "name",
1300
+ "company",
1301
+ "assetCount"
1302
+ ])
1303
+ },
1304
+ {
1305
+ name: "sort-order",
1306
+ queryName: "sortOrder",
1307
+ description: "The sort direction",
1308
+ valueName: "value",
1309
+ schema: z.enum(["ASC", "DESC"])
1310
+ },
1311
+ {
1312
+ name: "archived",
1313
+ queryName: "archived",
1314
+ description: "Whether to return unarchived records, archived records, or all of them",
1315
+ valueName: "value",
1316
+ schema: z.enum([
1317
+ "all",
1318
+ "false",
1319
+ "true"
1320
+ ])
1321
+ },
1322
+ {
1323
+ name: "is-transient",
1324
+ queryName: "isTransient",
1325
+ description: "Whether to return permanent locations (false), transient locations (true), or both (all)",
1326
+ valueName: "value",
1327
+ schema: z.enum([
1328
+ "all",
1329
+ "false",
1330
+ "true"
1331
+ ])
1332
+ },
1333
+ {
1334
+ name: "search",
1335
+ queryName: "search",
1336
+ description: "Text to match against location and customer names",
1337
+ valueName: "value",
1338
+ schema: z.string()
1339
+ },
1340
+ {
1341
+ name: "for-customer-ids",
1342
+ queryName: "forCustomerIds",
1343
+ description: "The IDs of the customers whose locations to return",
1344
+ valueName: "value",
1345
+ repeatable: true,
1346
+ schema: z.array(z.string())
1347
+ },
1348
+ {
1349
+ name: "include-organization",
1350
+ queryName: "includeOrganization",
1351
+ description: "Whether the customer filter also matches your organization's own locations, which on its own returns only those",
1352
+ schema: z.boolean()
1353
+ },
1354
+ {
1355
+ name: "sync-statuses",
1356
+ queryName: "syncStatuses",
1357
+ description: "The CRM sync statuses to return: in-sync, out-of-sync, or not-linked",
1358
+ valueName: "value",
1359
+ repeatable: true,
1360
+ schema: z.array(z.string())
1361
+ }
1362
+ ],
1363
+ takesBody: false
1364
+ }),
1365
+ defineOperation({
1366
+ name: "create",
1367
+ summary: "Create location",
1368
+ method: "POST",
1369
+ path: "/location",
1370
+ pathParameters: [],
1371
+ queryFlags: [],
1372
+ takesBody: true
1373
+ }),
1374
+ defineOperation({
1375
+ name: "get",
1376
+ summary: "Get location",
1377
+ method: "GET",
1378
+ path: "/location/{locationKey}",
1379
+ pathParameters: [{
1380
+ name: "locationKey",
1381
+ description: "The location's key",
1382
+ required: true
1383
+ }],
1384
+ queryFlags: [],
1385
+ takesBody: false
1386
+ }),
1387
+ defineOperation({
1388
+ name: "update",
1389
+ summary: "Patch location",
1390
+ method: "PATCH",
1391
+ path: "/location/{locationKey}",
1392
+ pathParameters: [{
1393
+ name: "locationKey",
1394
+ description: "The location's key",
1395
+ required: true
1396
+ }],
1397
+ queryFlags: [],
1398
+ takesBody: true
1399
+ }),
1400
+ {
1401
+ name: "zones",
1402
+ summary: "Zones commands",
1403
+ arguments: [],
1404
+ flags: [],
1405
+ examples: [],
1406
+ subcommands: [defineOperation({
1407
+ name: "list",
1408
+ summary: "Get zones",
1409
+ method: "GET",
1410
+ path: "/location/{locationKey}/zones",
1411
+ pathParameters: [{
1412
+ name: "locationKey",
1413
+ description: "The key of the site whose zones to list",
1414
+ required: true
1415
+ }],
1416
+ queryFlags: [{
1417
+ name: "archived",
1418
+ queryName: "archived",
1419
+ description: "Whether to return unarchived zones, archived zones, or all of them",
1420
+ valueName: "value",
1421
+ schema: z.enum([
1422
+ "all",
1423
+ "false",
1424
+ "true"
1425
+ ])
1426
+ }],
1427
+ takesBody: false
1428
+ })]
1429
+ }
1430
+ ]
1431
+ },
1432
+ {
1433
+ name: "url-link",
1434
+ summary: "URL link commands",
1435
+ arguments: [],
1436
+ flags: [],
1437
+ examples: [],
1438
+ subcommands: [
1439
+ defineOperation({
1440
+ name: "get",
1441
+ summary: "Get URL link by key",
1442
+ method: "GET",
1443
+ path: "/url-link/{linkKey}",
1444
+ pathParameters: [{
1445
+ name: "linkKey",
1446
+ description: "The URL link's key",
1447
+ required: true
1448
+ }],
1449
+ queryFlags: [],
1450
+ takesBody: false
1451
+ }),
1452
+ defineOperation({
1453
+ name: "update",
1454
+ summary: "Update URL link",
1455
+ method: "PATCH",
1456
+ path: "/url-link/{linkKey}",
1457
+ pathParameters: [{
1458
+ name: "linkKey",
1459
+ description: "The URL link's key",
1460
+ required: true
1461
+ }],
1462
+ queryFlags: [],
1463
+ takesBody: true
1464
+ }),
1465
+ defineOperation({
1466
+ name: "delete",
1467
+ summary: "Delete URL link",
1468
+ method: "DELETE",
1469
+ path: "/url-link/{linkKey}",
1470
+ pathParameters: [{
1471
+ name: "linkKey",
1472
+ description: "The URL link's key",
1473
+ required: true
1474
+ }],
1475
+ queryFlags: [],
1476
+ takesBody: false
1477
+ })
1478
+ ]
1479
+ }
1480
+ ],
1481
+ apiCommand,
1482
+ agentGuideCommand
1483
+ ];
1484
+ //#endregion
1485
+ //#region src/command/validate.ts
1486
+ /** toRejectedFlag names the first flag whose value its schema refuses. */
1487
+ function toRejectedFlag(command, flags) {
1488
+ for (const flag of command.flags) {
1489
+ const value = flags[toOptionKey(flag.name)];
1490
+ if (value === void 0) continue;
1491
+ if (!flag.schema.safeParse(value).success) return `--${flag.name} does not accept ${JSON.stringify(value)}`;
1492
+ }
1493
+ }
1494
+ //#endregion
1495
+ //#region src/cli.ts
1496
+ const program = new Command();
1497
+ program.name("hardfin").description("Call the Hardfin API from a terminal or an agent").version(version, "-v, --version").showHelpAfterError().enablePositionalOptions();
1498
+ for (const command of commands) program.addCommand(toProgram(command));
1499
+ await program.parseAsync(process.argv);
1500
+ /** toProgram wires one registry command into the parser. */
1501
+ function toProgram(command) {
1502
+ const program = new Command(command.name).summary(command.summary).description(command.description ?? command.summary);
1503
+ for (const argument of command.arguments) {
1504
+ const name = argument.variadic ? `${argument.name}...` : argument.name;
1505
+ program.argument(argument.required ? `<${name}>` : `[${name}]`, argument.description);
1506
+ }
1507
+ for (const flag of command.flags) {
1508
+ const short = flag.short ? `-${flag.short}, ` : "";
1509
+ const value = flag.valueName ? ` <${flag.valueName}>` : "";
1510
+ const option = new Option(`${short}--${flag.name}${value}`, flag.description);
1511
+ if (flag.repeatable) option.argParser(collect);
1512
+ if (flag.defaultValue !== void 0) option.default(flag.defaultValue);
1513
+ program.addOption(option);
1514
+ }
1515
+ for (const example of command.examples) program.addHelpText("after", `\n${example.description}:\n $ ${example.command}`);
1516
+ for (const subcommand of command.subcommands ?? []) program.addCommand(toProgram(subcommand));
1517
+ if (!command.run) return program;
1518
+ program.action(async (...parsed) => {
1519
+ const flags = parsed[parsed.length - 2] ?? {};
1520
+ const args = parsed.slice(0, parsed.length - 2).flatMap(toArgumentList);
1521
+ process.exitCode = await toExitCode(command, args, flags);
1522
+ });
1523
+ return program;
1524
+ }
1525
+ async function toExitCode(command, args, flags) {
1526
+ const isJSON = isJSONOutput(flags);
1527
+ const rejected = toRejectedFlag(command, flags);
1528
+ if (rejected) {
1529
+ writeFailure(rejected, isJSON);
1530
+ return ExitCode.USAGE;
1531
+ }
1532
+ try {
1533
+ return await command.run?.({
1534
+ args,
1535
+ flags,
1536
+ isJSON,
1537
+ commands
1538
+ }) ?? ExitCode.OK;
1539
+ } catch (error) {
1540
+ writeFailure(error instanceof Error ? error.message : String(error), isJSON);
1541
+ return ExitCode.ERROR;
1542
+ }
1543
+ }
1544
+ function toArgumentList(value) {
1545
+ if (Array.isArray(value)) return value.map(String);
1546
+ return value === void 0 ? [] : [String(value)];
1547
+ }
1548
+ function collect(value, previous) {
1549
+ return [...previous ?? [], value];
1550
+ }
1551
+ //#endregion
1552
+ export {};