@hardfin/cli 0.0.2-dev.5 → 0.0.2-dev.7
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 +113 -1
- package/dist/cli.js +1313 -18
- package/package.json +4 -2
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { readFileSync } from "node:fs";
|
|
5
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6
|
+
import { resolve } from "node:path";
|
|
6
7
|
//#region src/command/registry.ts
|
|
7
8
|
/** ExitCode is what the process returns, and what an agent branches on. */
|
|
8
9
|
const ExitCode = {
|
|
@@ -19,14 +20,93 @@ function defineCommand(command) {
|
|
|
19
20
|
//#region src/config/settings.ts
|
|
20
21
|
/** The API version this build was written against, sent on every request. */
|
|
21
22
|
const API_VERSION = "2026-09-17";
|
|
23
|
+
/** The file a local build reads its overrides from, in the working directory. */
|
|
24
|
+
const CONFIG_FILE = "config.local.json";
|
|
22
25
|
const DEFAULT_API_URL = "https://api.hardfin.com/v2";
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
const DEFAULT_ENV_FILE = ".env";
|
|
27
|
+
const FileSettings = z.strictObject({
|
|
28
|
+
apiUrl: z.string().optional(),
|
|
29
|
+
apiKey: z.string().optional(),
|
|
30
|
+
clientId: z.string().optional(),
|
|
31
|
+
auth: z.strictObject({
|
|
32
|
+
authorizeUrl: z.string().optional(),
|
|
33
|
+
tokenUrl: z.string().optional(),
|
|
34
|
+
deviceUrl: z.string().optional(),
|
|
35
|
+
revokeUrl: z.string().optional()
|
|
36
|
+
}).optional()
|
|
37
|
+
});
|
|
38
|
+
/** ConfigFailure is a config file that cannot be read or does not match the schema. */
|
|
39
|
+
var ConfigFailure = class extends Error {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = "ConfigFailure";
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/** toSettings resolves what this invocation talks to, and where each value came from. */
|
|
46
|
+
function toSettings(flags = {}, directory = process.cwd()) {
|
|
47
|
+
const fromEnvFile = loadEnvFile(directory);
|
|
48
|
+
const file = toFileSettings(directory);
|
|
49
|
+
const sources = {};
|
|
50
|
+
const pick = (key, flag, variable, fromFile, fallback) => {
|
|
51
|
+
const [value, source] = toLayer(flag, process.env[variable], fromEnvFile.has(variable), fromFile, fallback);
|
|
52
|
+
sources[key] = source;
|
|
53
|
+
return value;
|
|
54
|
+
};
|
|
55
|
+
const apiUrl = toTrimmedUrl(pick("apiUrl", flags.apiUrl, "HARDFIN_API_URL", file.apiUrl, DEFAULT_API_URL) ?? DEFAULT_API_URL);
|
|
56
|
+
return {
|
|
57
|
+
settings: {
|
|
58
|
+
apiUrl,
|
|
59
|
+
apiKey: pick("apiKey", flags.apiKey, "HARDFIN_API_KEY", file.apiKey),
|
|
60
|
+
clientId: pick("clientId", flags.clientId, "HARDFIN_CLIENT_ID", file.clientId),
|
|
61
|
+
authorizeUrl: pick("authorizeUrl", flags.authorizeUrl, "HARDFIN_AUTHORIZE_URL", file.auth?.authorizeUrl, `${apiUrl}/auth/authorize`) ?? "",
|
|
62
|
+
tokenUrl: pick("tokenUrl", flags.tokenUrl, "HARDFIN_TOKEN_URL", file.auth?.tokenUrl, `${apiUrl}/auth/token`) ?? "",
|
|
63
|
+
deviceUrl: pick("deviceUrl", flags.deviceUrl, "HARDFIN_DEVICE_URL", file.auth?.deviceUrl, `${apiUrl}/auth/device`) ?? "",
|
|
64
|
+
revokeUrl: pick("revokeUrl", flags.revokeUrl, "HARDFIN_REVOKE_URL", file.auth?.revokeUrl, `${apiUrl}/auth/revoke`) ?? ""
|
|
65
|
+
},
|
|
66
|
+
sources
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function toLayer(flag, environment, isFromEnvFile, file, fallback) {
|
|
70
|
+
if (flag) return [flag, "flag"];
|
|
71
|
+
if (environment) return [environment, isFromEnvFile ? "env file" : "environment"];
|
|
72
|
+
if (file) return [file, "config file"];
|
|
73
|
+
return [fallback, "default"];
|
|
74
|
+
}
|
|
75
|
+
/** toFileSettings reads the local override file, which a local build is expected to have. */
|
|
76
|
+
function toFileSettings(directory) {
|
|
77
|
+
const path = resolve(directory, CONFIG_FILE);
|
|
78
|
+
if (!existsSync(path)) return {};
|
|
79
|
+
let parsed;
|
|
80
|
+
try {
|
|
81
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
82
|
+
} catch {
|
|
83
|
+
throw new ConfigFailure(`${CONFIG_FILE} does not hold JSON`);
|
|
84
|
+
}
|
|
85
|
+
const result = FileSettings.safeParse(parsed);
|
|
86
|
+
if (!result.success) {
|
|
87
|
+
const issue = result.error.issues[0];
|
|
88
|
+
throw new ConfigFailure(`${CONFIG_FILE} is not valid: ${issue?.path.join(".") || "root"} ${issue?.message ?? ""}`.trim());
|
|
89
|
+
}
|
|
90
|
+
return result.data;
|
|
26
91
|
}
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
92
|
+
/**
|
|
93
|
+
* loadEnvFile reads a .env beside the command, so a local build needs no exports, and
|
|
94
|
+
* answers which variables it supplied. Node leaves an exported variable alone, so a
|
|
95
|
+
* shell export still wins over the file.
|
|
96
|
+
*/
|
|
97
|
+
function loadEnvFile(directory) {
|
|
98
|
+
const path = process.env["HARDFIN_ENV_FILE"] ?? resolve(directory, DEFAULT_ENV_FILE);
|
|
99
|
+
if (!existsSync(path)) return /* @__PURE__ */ new Set();
|
|
100
|
+
const before = new Set(Object.keys(process.env));
|
|
101
|
+
try {
|
|
102
|
+
process.loadEnvFile(path);
|
|
103
|
+
} catch {
|
|
104
|
+
throw new ConfigFailure(`${path} cannot be read as an env file`);
|
|
105
|
+
}
|
|
106
|
+
return new Set(Object.keys(process.env).filter((name) => !before.has(name)));
|
|
107
|
+
}
|
|
108
|
+
function toTrimmedUrl(url) {
|
|
109
|
+
return url.replace(/\/+$/, "");
|
|
30
110
|
}
|
|
31
111
|
//#endregion
|
|
32
112
|
//#region src/output/writer.ts
|
|
@@ -272,7 +352,7 @@ const apiCommand = defineCommand({
|
|
|
272
352
|
run: runApi
|
|
273
353
|
});
|
|
274
354
|
async function runApi(input) {
|
|
275
|
-
const apiKey =
|
|
355
|
+
const apiKey = input.resolved.settings.apiKey;
|
|
276
356
|
if (!apiKey) {
|
|
277
357
|
writeFailure("not authenticated. Set HARDFIN_API_KEY to an API key for your organization", input.isJSON);
|
|
278
358
|
return ExitCode.NOT_AUTHENTICATED;
|
|
@@ -282,14 +362,14 @@ async function runApi(input) {
|
|
|
282
362
|
writeFailure("a path is required, such as /customer", input.isJSON);
|
|
283
363
|
return ExitCode.USAGE;
|
|
284
364
|
}
|
|
285
|
-
const query = toQuery(input.flags["field"]);
|
|
365
|
+
const query = toQuery$1(input.flags["field"]);
|
|
286
366
|
if (query === void 0) {
|
|
287
367
|
writeFailure("each --field is key=value, such as -f limit=50", input.isJSON);
|
|
288
368
|
return ExitCode.USAGE;
|
|
289
369
|
}
|
|
290
370
|
let body;
|
|
291
371
|
if (typeof input.flags["input"] === "string") {
|
|
292
|
-
body = toBody(input.flags["input"]);
|
|
372
|
+
body = toBody$1(input.flags["input"]);
|
|
293
373
|
if (body === void 0) {
|
|
294
374
|
writeFailure(`${input.flags["input"]} does not hold JSON`, input.isJSON);
|
|
295
375
|
return ExitCode.USAGE;
|
|
@@ -297,7 +377,7 @@ async function runApi(input) {
|
|
|
297
377
|
}
|
|
298
378
|
try {
|
|
299
379
|
writeData((await request({
|
|
300
|
-
apiUrl:
|
|
380
|
+
apiUrl: input.resolved.settings.apiUrl,
|
|
301
381
|
apiKey,
|
|
302
382
|
method: String(input.flags["method"] ?? "GET").toUpperCase(),
|
|
303
383
|
path,
|
|
@@ -315,7 +395,7 @@ async function runApi(input) {
|
|
|
315
395
|
}
|
|
316
396
|
}
|
|
317
397
|
/** toQuery folds the repeated --field flags into query parameters. */
|
|
318
|
-
function toQuery(fields) {
|
|
398
|
+
function toQuery$1(fields) {
|
|
319
399
|
const query = new URLSearchParams();
|
|
320
400
|
for (const field of Array.isArray(fields) ? fields : []) {
|
|
321
401
|
const split = field.indexOf("=");
|
|
@@ -324,6 +404,150 @@ function toQuery(fields) {
|
|
|
324
404
|
}
|
|
325
405
|
return query;
|
|
326
406
|
}
|
|
407
|
+
function toBody$1(source) {
|
|
408
|
+
const text = source === "-" ? readFileSync(0, "utf8") : readFileSync(source, "utf8");
|
|
409
|
+
try {
|
|
410
|
+
return JSON.parse(text);
|
|
411
|
+
} catch {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
//#endregion
|
|
416
|
+
//#region src/command/config.ts
|
|
417
|
+
const configCommand = defineCommand({
|
|
418
|
+
name: "config",
|
|
419
|
+
summary: "Print what this invocation talks to, and where each value came from",
|
|
420
|
+
description: `Resolves the API and authentication endpoints from the flags, the environment, a .env file, and ${CONFIG_FILE} in the working directory. Use it when a local build reaches the wrong server.`,
|
|
421
|
+
arguments: [],
|
|
422
|
+
flags: [{
|
|
423
|
+
name: "json",
|
|
424
|
+
description: "Print machine-readable output, which is the default when stdout is not a terminal",
|
|
425
|
+
schema: z.boolean()
|
|
426
|
+
}],
|
|
427
|
+
examples: [{
|
|
428
|
+
description: "See what a local build is pointed at",
|
|
429
|
+
command: "hardfin config"
|
|
430
|
+
}, {
|
|
431
|
+
description: "Read one value from a script",
|
|
432
|
+
command: "hardfin config --json | jq -r .apiUrl"
|
|
433
|
+
}],
|
|
434
|
+
run: runConfig
|
|
435
|
+
});
|
|
436
|
+
/** toReport pairs each setting with the layer that supplied it. */
|
|
437
|
+
function toReport(resolved) {
|
|
438
|
+
const report = { apiVersion: API_VERSION };
|
|
439
|
+
for (const [key, value] of Object.entries(resolved.settings)) {
|
|
440
|
+
const source = resolved.sources[key];
|
|
441
|
+
report[key] = key === "apiKey" ? {
|
|
442
|
+
set: value !== void 0,
|
|
443
|
+
from: source
|
|
444
|
+
} : {
|
|
445
|
+
value: value ?? null,
|
|
446
|
+
from: source
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
return report;
|
|
450
|
+
}
|
|
451
|
+
async function runConfig(input) {
|
|
452
|
+
const report = toReport(input.resolved);
|
|
453
|
+
if (input.isJSON) {
|
|
454
|
+
writeData(report);
|
|
455
|
+
return ExitCode.OK;
|
|
456
|
+
}
|
|
457
|
+
writeData(Object.entries(report).map(([key, entry]) => {
|
|
458
|
+
if (typeof entry !== "object" || entry === null) return `${key.padEnd(14)} ${String(entry)}`;
|
|
459
|
+
const holder = entry;
|
|
460
|
+
const shown = holder.value ?? (holder.set ? "set" : "not set");
|
|
461
|
+
return `${key.padEnd(14)} ${shown} (${holder.from})`;
|
|
462
|
+
}).join("\n"));
|
|
463
|
+
return ExitCode.OK;
|
|
464
|
+
}
|
|
465
|
+
//#endregion
|
|
466
|
+
//#region src/command/operation.ts
|
|
467
|
+
const INPUT_FLAG = {
|
|
468
|
+
name: "input",
|
|
469
|
+
description: "A file holding the JSON request body, or - for stdin",
|
|
470
|
+
valueName: "file",
|
|
471
|
+
schema: z.string()
|
|
472
|
+
};
|
|
473
|
+
const JSON_FLAG = {
|
|
474
|
+
name: "json",
|
|
475
|
+
description: "Print machine-readable output, which is the default when stdout is not a terminal",
|
|
476
|
+
schema: z.boolean()
|
|
477
|
+
};
|
|
478
|
+
/** defineOperation turns one endpoint into the command that calls it. */
|
|
479
|
+
function defineOperation(operation) {
|
|
480
|
+
const flags = [...operation.queryFlags, JSON_FLAG];
|
|
481
|
+
if (operation.takesBody) flags.splice(flags.length - 1, 0, INPUT_FLAG);
|
|
482
|
+
return {
|
|
483
|
+
name: operation.name,
|
|
484
|
+
summary: operation.summary,
|
|
485
|
+
description: operation.description ?? operation.summary,
|
|
486
|
+
arguments: operation.pathParameters,
|
|
487
|
+
flags,
|
|
488
|
+
examples: [],
|
|
489
|
+
run: (input) => runOperation(operation, input)
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
async function runOperation(operation, input) {
|
|
493
|
+
const apiKey = input.resolved.settings.apiKey;
|
|
494
|
+
if (!apiKey) {
|
|
495
|
+
writeFailure("not authenticated. Set HARDFIN_API_KEY to an API key for your organization", input.isJSON);
|
|
496
|
+
return ExitCode.NOT_AUTHENTICATED;
|
|
497
|
+
}
|
|
498
|
+
const path = toPath(operation, input.args);
|
|
499
|
+
if (path === void 0) {
|
|
500
|
+
writeFailure(`this command takes ${operation.pathParameters.length} argument(s)`, input.isJSON);
|
|
501
|
+
return ExitCode.USAGE;
|
|
502
|
+
}
|
|
503
|
+
let body;
|
|
504
|
+
if (typeof input.flags["input"] === "string") {
|
|
505
|
+
body = toBody(input.flags["input"]);
|
|
506
|
+
if (body === void 0) {
|
|
507
|
+
writeFailure(`${input.flags["input"]} does not hold JSON`, input.isJSON);
|
|
508
|
+
return ExitCode.USAGE;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
try {
|
|
512
|
+
writeData((await request({
|
|
513
|
+
apiUrl: input.resolved.settings.apiUrl,
|
|
514
|
+
apiKey,
|
|
515
|
+
method: operation.method,
|
|
516
|
+
path,
|
|
517
|
+
query: toQuery(operation, input.flags),
|
|
518
|
+
body
|
|
519
|
+
})).data);
|
|
520
|
+
return ExitCode.OK;
|
|
521
|
+
} catch (error) {
|
|
522
|
+
if (error instanceof RequestFailure) {
|
|
523
|
+
writeFailure(error.message, input.isJSON, error.errors, error.requestId);
|
|
524
|
+
return error.status === 401 ? ExitCode.NOT_AUTHENTICATED : ExitCode.ERROR;
|
|
525
|
+
}
|
|
526
|
+
writeFailure(error instanceof Error ? error.message : String(error), input.isJSON);
|
|
527
|
+
return ExitCode.ERROR;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
/** toPath fills the path template from the positional arguments, in order. */
|
|
531
|
+
function toPath(operation, args) {
|
|
532
|
+
if (args.length !== operation.pathParameters.length) return;
|
|
533
|
+
let path = operation.path;
|
|
534
|
+
for (const [index, parameter] of operation.pathParameters.entries()) path = path.replace(`{${parameter.name}}`, encodeURIComponent(args[index] ?? ""));
|
|
535
|
+
return path;
|
|
536
|
+
}
|
|
537
|
+
/** toQuery carries only the flags this invocation actually set. */
|
|
538
|
+
function toQuery(operation, flags) {
|
|
539
|
+
const query = new URLSearchParams();
|
|
540
|
+
for (const flag of operation.queryFlags) {
|
|
541
|
+
const value = flags[toOptionKey(flag.name)];
|
|
542
|
+
if (value === void 0) continue;
|
|
543
|
+
for (const entry of Array.isArray(value) ? value : [value]) query.append(flag.queryName, String(entry));
|
|
544
|
+
}
|
|
545
|
+
return query;
|
|
546
|
+
}
|
|
547
|
+
/** toOptionKey names the parsed flag, which the parser reports in camel case. */
|
|
548
|
+
function toOptionKey(name) {
|
|
549
|
+
return name.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
550
|
+
}
|
|
327
551
|
function toBody(source) {
|
|
328
552
|
const text = source === "-" ? readFileSync(0, "utf8") : readFileSync(source, "utf8");
|
|
329
553
|
try {
|
|
@@ -335,11 +559,1073 @@ function toBody(source) {
|
|
|
335
559
|
//#endregion
|
|
336
560
|
//#region src/command/commands.ts
|
|
337
561
|
/** commands is every command the CLI offers, and drives help and the agent guide. */
|
|
338
|
-
const commands = [
|
|
562
|
+
const commands = [
|
|
563
|
+
...[
|
|
564
|
+
{
|
|
565
|
+
name: "asset",
|
|
566
|
+
summary: "Asset commands",
|
|
567
|
+
arguments: [],
|
|
568
|
+
flags: [],
|
|
569
|
+
examples: [],
|
|
570
|
+
subcommands: [
|
|
571
|
+
defineOperation({
|
|
572
|
+
name: "list",
|
|
573
|
+
summary: "Get asset listing",
|
|
574
|
+
method: "GET",
|
|
575
|
+
path: "/asset",
|
|
576
|
+
pathParameters: [],
|
|
577
|
+
queryFlags: [
|
|
578
|
+
{
|
|
579
|
+
name: "page",
|
|
580
|
+
queryName: "page",
|
|
581
|
+
description: "The page to return, starting at 1",
|
|
582
|
+
valueName: "number",
|
|
583
|
+
schema: z.coerce.number()
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
name: "limit",
|
|
587
|
+
queryName: "limit",
|
|
588
|
+
description: "The number of records per page, from 1 to 100",
|
|
589
|
+
valueName: "number",
|
|
590
|
+
schema: z.coerce.number()
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
name: "sort-by",
|
|
594
|
+
queryName: "sortBy",
|
|
595
|
+
description: "The field to sort by",
|
|
596
|
+
valueName: "value",
|
|
597
|
+
schema: z.enum([
|
|
598
|
+
"serial",
|
|
599
|
+
"project",
|
|
600
|
+
"item",
|
|
601
|
+
"location",
|
|
602
|
+
"owner",
|
|
603
|
+
"activity"
|
|
604
|
+
])
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
name: "sort-order",
|
|
608
|
+
queryName: "sortOrder",
|
|
609
|
+
description: "The sort direction",
|
|
610
|
+
valueName: "value",
|
|
611
|
+
schema: z.enum(["ASC", "DESC"])
|
|
612
|
+
},
|
|
613
|
+
{
|
|
614
|
+
name: "archived",
|
|
615
|
+
queryName: "archived",
|
|
616
|
+
description: "Whether to return unarchived records, archived records, or all of them",
|
|
617
|
+
valueName: "value",
|
|
618
|
+
schema: z.enum([
|
|
619
|
+
"all",
|
|
620
|
+
"false",
|
|
621
|
+
"true"
|
|
622
|
+
])
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
name: "search-query",
|
|
626
|
+
queryName: "searchQuery",
|
|
627
|
+
description: "Text to match against the serial, description, item, project, owner, and location, ignored when shorter than three characters",
|
|
628
|
+
valueName: "value",
|
|
629
|
+
schema: z.string()
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
name: "for-asset-id",
|
|
633
|
+
queryName: "forAssetId",
|
|
634
|
+
description: "The IDs of the assets to list",
|
|
635
|
+
valueName: "value",
|
|
636
|
+
repeatable: true,
|
|
637
|
+
schema: z.array(z.string())
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
name: "for-asset-key",
|
|
641
|
+
queryName: "forAssetKey",
|
|
642
|
+
description: "The keys of the assets to list",
|
|
643
|
+
valueName: "value",
|
|
644
|
+
repeatable: true,
|
|
645
|
+
schema: z.array(z.string())
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
name: "for-customer",
|
|
649
|
+
queryName: "forCustomer",
|
|
650
|
+
description: "The IDs of the customers whose assets to list",
|
|
651
|
+
valueName: "value",
|
|
652
|
+
repeatable: true,
|
|
653
|
+
schema: z.array(z.string())
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
name: "for-item",
|
|
657
|
+
queryName: "forItem",
|
|
658
|
+
description: "The IDs of the items whose assets to list",
|
|
659
|
+
valueName: "value",
|
|
660
|
+
repeatable: true,
|
|
661
|
+
schema: z.array(z.string())
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
name: "at-site",
|
|
665
|
+
queryName: "atSite",
|
|
666
|
+
description: "The IDs of the locations whose assets to list",
|
|
667
|
+
valueName: "value",
|
|
668
|
+
repeatable: true,
|
|
669
|
+
schema: z.array(z.string())
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
name: "at-customer-sites",
|
|
673
|
+
queryName: "atCustomerSites",
|
|
674
|
+
description: "The IDs of the customers whose sites to list assets at",
|
|
675
|
+
valueName: "value",
|
|
676
|
+
repeatable: true,
|
|
677
|
+
schema: z.array(z.string())
|
|
678
|
+
},
|
|
679
|
+
{
|
|
680
|
+
name: "with-functional-statuses",
|
|
681
|
+
queryName: "withFunctionalStatuses",
|
|
682
|
+
description: "The functional statuses to list, where SCRAPPED lists scrapped assets",
|
|
683
|
+
valueName: "value",
|
|
684
|
+
repeatable: true,
|
|
685
|
+
schema: z.array(z.enum([
|
|
686
|
+
"FUNCTIONAL",
|
|
687
|
+
"NEEDS_REVIEW",
|
|
688
|
+
"NON-FUNCTIONAL",
|
|
689
|
+
"SCRAPPED"
|
|
690
|
+
]))
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
name: "with-transit-statuses",
|
|
694
|
+
queryName: "withTransitStatuses",
|
|
695
|
+
description: "The transit statuses to list",
|
|
696
|
+
valueName: "value",
|
|
697
|
+
repeatable: true,
|
|
698
|
+
schema: z.array(z.enum([
|
|
699
|
+
"IN_TRANSIT",
|
|
700
|
+
"IN_TRANSIT_TO_FIELD",
|
|
701
|
+
"IN_TRANSIT_TO_INVENTORY",
|
|
702
|
+
"NOT_IN_TRANSIT"
|
|
703
|
+
]))
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
name: "with-inventory-statuses",
|
|
707
|
+
queryName: "withInventoryStatuses",
|
|
708
|
+
description: "In_inventory, not_in_inventory, or both, which filters only when one is sent",
|
|
709
|
+
valueName: "value",
|
|
710
|
+
repeatable: true,
|
|
711
|
+
schema: z.array(z.string())
|
|
712
|
+
},
|
|
713
|
+
{
|
|
714
|
+
name: "with-location-company-type",
|
|
715
|
+
queryName: "withLocationCompanyType",
|
|
716
|
+
description: "Customer, manufacturer, or both, for assets at customer sites or your own, which filters only when one is sent",
|
|
717
|
+
valueName: "value",
|
|
718
|
+
repeatable: true,
|
|
719
|
+
schema: z.array(z.string())
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
name: "with-project-status",
|
|
723
|
+
queryName: "withProjectStatus",
|
|
724
|
+
description: "The project assignments to list: upcoming, active, past, or none",
|
|
725
|
+
valueName: "value",
|
|
726
|
+
repeatable: true,
|
|
727
|
+
schema: z.array(z.string())
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
name: "with-owner",
|
|
731
|
+
queryName: "withOwner",
|
|
732
|
+
description: "Manufacturer for assets your organization owns, customer for assets customers own, or both",
|
|
733
|
+
valueName: "value",
|
|
734
|
+
repeatable: true,
|
|
735
|
+
schema: z.array(z.string())
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
name: "for-project",
|
|
739
|
+
queryName: "forProject",
|
|
740
|
+
description: "The IDs of the projects whose assets to list",
|
|
741
|
+
valueName: "value",
|
|
742
|
+
repeatable: true,
|
|
743
|
+
schema: z.array(z.string())
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
name: "scrapped",
|
|
747
|
+
queryName: "scrapped",
|
|
748
|
+
description: "Whether to list unscrapped assets, scrapped assets, or all of them",
|
|
749
|
+
valueName: "value",
|
|
750
|
+
schema: z.enum([
|
|
751
|
+
"all",
|
|
752
|
+
"false",
|
|
753
|
+
"true"
|
|
754
|
+
])
|
|
755
|
+
}
|
|
756
|
+
],
|
|
757
|
+
takesBody: false
|
|
758
|
+
}),
|
|
759
|
+
defineOperation({
|
|
760
|
+
name: "create",
|
|
761
|
+
summary: "Create asset",
|
|
762
|
+
method: "POST",
|
|
763
|
+
path: "/asset",
|
|
764
|
+
pathParameters: [],
|
|
765
|
+
queryFlags: [],
|
|
766
|
+
takesBody: true
|
|
767
|
+
}),
|
|
768
|
+
defineOperation({
|
|
769
|
+
name: "get",
|
|
770
|
+
summary: "Get asset",
|
|
771
|
+
method: "GET",
|
|
772
|
+
path: "/asset/{assetKey}",
|
|
773
|
+
pathParameters: [{
|
|
774
|
+
name: "assetKey",
|
|
775
|
+
description: "The asset's key",
|
|
776
|
+
required: true
|
|
777
|
+
}],
|
|
778
|
+
queryFlags: [],
|
|
779
|
+
takesBody: false
|
|
780
|
+
}),
|
|
781
|
+
defineOperation({
|
|
782
|
+
name: "update",
|
|
783
|
+
summary: "Patch asset",
|
|
784
|
+
method: "PATCH",
|
|
785
|
+
path: "/asset/{assetKey}",
|
|
786
|
+
pathParameters: [{
|
|
787
|
+
name: "assetKey",
|
|
788
|
+
description: "The asset's key",
|
|
789
|
+
required: true
|
|
790
|
+
}],
|
|
791
|
+
queryFlags: [],
|
|
792
|
+
takesBody: true
|
|
793
|
+
}),
|
|
794
|
+
{
|
|
795
|
+
name: "accounting",
|
|
796
|
+
summary: "Accounting commands",
|
|
797
|
+
arguments: [],
|
|
798
|
+
flags: [],
|
|
799
|
+
examples: [],
|
|
800
|
+
subcommands: [defineOperation({
|
|
801
|
+
name: "update",
|
|
802
|
+
summary: "Update asset accounting",
|
|
803
|
+
method: "PATCH",
|
|
804
|
+
path: "/asset/{assetKey}/accounting",
|
|
805
|
+
pathParameters: [{
|
|
806
|
+
name: "assetKey",
|
|
807
|
+
description: "The asset's key",
|
|
808
|
+
required: true
|
|
809
|
+
}],
|
|
810
|
+
queryFlags: [],
|
|
811
|
+
takesBody: true
|
|
812
|
+
}), {
|
|
813
|
+
name: "in-service-management",
|
|
814
|
+
summary: "In service management commands",
|
|
815
|
+
arguments: [],
|
|
816
|
+
flags: [],
|
|
817
|
+
examples: [],
|
|
818
|
+
subcommands: [defineOperation({
|
|
819
|
+
name: "update",
|
|
820
|
+
summary: "Toggle in service date management",
|
|
821
|
+
method: "PATCH",
|
|
822
|
+
path: "/asset/{assetKey}/accounting/in-service-management",
|
|
823
|
+
pathParameters: [{
|
|
824
|
+
name: "assetKey",
|
|
825
|
+
description: "The asset's key",
|
|
826
|
+
required: true
|
|
827
|
+
}],
|
|
828
|
+
queryFlags: [],
|
|
829
|
+
takesBody: true
|
|
830
|
+
})]
|
|
831
|
+
}]
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
name: "cost-adjustment",
|
|
835
|
+
summary: "Cost adjustment commands",
|
|
836
|
+
arguments: [],
|
|
837
|
+
flags: [],
|
|
838
|
+
examples: [],
|
|
839
|
+
subcommands: [defineOperation({
|
|
840
|
+
name: "create",
|
|
841
|
+
summary: "Create asset cost adjustment",
|
|
842
|
+
method: "POST",
|
|
843
|
+
path: "/asset/{assetKey}/cost-adjustment",
|
|
844
|
+
pathParameters: [{
|
|
845
|
+
name: "assetKey",
|
|
846
|
+
description: "The asset's key",
|
|
847
|
+
required: true
|
|
848
|
+
}],
|
|
849
|
+
queryFlags: [],
|
|
850
|
+
takesBody: true
|
|
851
|
+
})]
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
name: "file",
|
|
855
|
+
summary: "File commands",
|
|
856
|
+
arguments: [],
|
|
857
|
+
flags: [],
|
|
858
|
+
examples: [],
|
|
859
|
+
subcommands: [defineOperation({
|
|
860
|
+
name: "list",
|
|
861
|
+
summary: "Get asset files",
|
|
862
|
+
method: "GET",
|
|
863
|
+
path: "/asset/{assetKey}/file",
|
|
864
|
+
pathParameters: [{
|
|
865
|
+
name: "assetKey",
|
|
866
|
+
description: "The asset's key",
|
|
867
|
+
required: true
|
|
868
|
+
}],
|
|
869
|
+
queryFlags: [],
|
|
870
|
+
takesBody: false
|
|
871
|
+
}), defineOperation({
|
|
872
|
+
name: "delete",
|
|
873
|
+
summary: "Delete asset file",
|
|
874
|
+
method: "DELETE",
|
|
875
|
+
path: "/asset/{assetKey}/file/{fileKey}",
|
|
876
|
+
pathParameters: [{
|
|
877
|
+
name: "assetKey",
|
|
878
|
+
description: "The asset's key",
|
|
879
|
+
required: true
|
|
880
|
+
}, {
|
|
881
|
+
name: "fileKey",
|
|
882
|
+
description: "The file's key",
|
|
883
|
+
required: true
|
|
884
|
+
}],
|
|
885
|
+
queryFlags: [],
|
|
886
|
+
takesBody: false
|
|
887
|
+
})]
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
name: "ownership",
|
|
891
|
+
summary: "Ownership commands",
|
|
892
|
+
arguments: [],
|
|
893
|
+
flags: [],
|
|
894
|
+
examples: [],
|
|
895
|
+
subcommands: [
|
|
896
|
+
defineOperation({
|
|
897
|
+
name: "list",
|
|
898
|
+
summary: "Get asset ownership history",
|
|
899
|
+
method: "GET",
|
|
900
|
+
path: "/asset/{assetKey}/ownership",
|
|
901
|
+
pathParameters: [{
|
|
902
|
+
name: "assetKey",
|
|
903
|
+
description: "The asset's key",
|
|
904
|
+
required: true
|
|
905
|
+
}],
|
|
906
|
+
queryFlags: [],
|
|
907
|
+
takesBody: false
|
|
908
|
+
}),
|
|
909
|
+
defineOperation({
|
|
910
|
+
name: "create",
|
|
911
|
+
summary: "Create asset ownership",
|
|
912
|
+
method: "POST",
|
|
913
|
+
path: "/asset/{assetKey}/ownership",
|
|
914
|
+
pathParameters: [{
|
|
915
|
+
name: "assetKey",
|
|
916
|
+
description: "The asset's key",
|
|
917
|
+
required: true
|
|
918
|
+
}],
|
|
919
|
+
queryFlags: [],
|
|
920
|
+
takesBody: true
|
|
921
|
+
}),
|
|
922
|
+
defineOperation({
|
|
923
|
+
name: "clear",
|
|
924
|
+
summary: "Clear the ownership an asset holds today",
|
|
925
|
+
method: "DELETE",
|
|
926
|
+
path: "/asset/{assetKey}/ownership",
|
|
927
|
+
pathParameters: [{
|
|
928
|
+
name: "assetKey",
|
|
929
|
+
description: "The asset's key",
|
|
930
|
+
required: true
|
|
931
|
+
}],
|
|
932
|
+
queryFlags: [],
|
|
933
|
+
takesBody: true
|
|
934
|
+
}),
|
|
935
|
+
defineOperation({
|
|
936
|
+
name: "get",
|
|
937
|
+
summary: "Get asset ownership segment",
|
|
938
|
+
method: "GET",
|
|
939
|
+
path: "/asset/{assetKey}/ownership/{segmentKey}",
|
|
940
|
+
pathParameters: [{
|
|
941
|
+
name: "assetKey",
|
|
942
|
+
description: "The asset's key",
|
|
943
|
+
required: true
|
|
944
|
+
}, {
|
|
945
|
+
name: "segmentKey",
|
|
946
|
+
description: "The ownership segment's key",
|
|
947
|
+
required: true
|
|
948
|
+
}],
|
|
949
|
+
queryFlags: [],
|
|
950
|
+
takesBody: false
|
|
951
|
+
}),
|
|
952
|
+
defineOperation({
|
|
953
|
+
name: "update",
|
|
954
|
+
summary: "Patch asset ownership segment",
|
|
955
|
+
method: "PATCH",
|
|
956
|
+
path: "/asset/{assetKey}/ownership/{segmentKey}",
|
|
957
|
+
pathParameters: [{
|
|
958
|
+
name: "assetKey",
|
|
959
|
+
description: "The asset's key",
|
|
960
|
+
required: true
|
|
961
|
+
}, {
|
|
962
|
+
name: "segmentKey",
|
|
963
|
+
description: "The ownership segment's key",
|
|
964
|
+
required: true
|
|
965
|
+
}],
|
|
966
|
+
queryFlags: [],
|
|
967
|
+
takesBody: true
|
|
968
|
+
}),
|
|
969
|
+
defineOperation({
|
|
970
|
+
name: "delete",
|
|
971
|
+
summary: "Delete asset ownership segment",
|
|
972
|
+
method: "DELETE",
|
|
973
|
+
path: "/asset/{assetKey}/ownership/{segmentKey}",
|
|
974
|
+
pathParameters: [{
|
|
975
|
+
name: "assetKey",
|
|
976
|
+
description: "The asset's key",
|
|
977
|
+
required: true
|
|
978
|
+
}, {
|
|
979
|
+
name: "segmentKey",
|
|
980
|
+
description: "The ownership segment's key",
|
|
981
|
+
required: true
|
|
982
|
+
}],
|
|
983
|
+
queryFlags: [],
|
|
984
|
+
takesBody: false
|
|
985
|
+
})
|
|
986
|
+
]
|
|
987
|
+
},
|
|
988
|
+
{
|
|
989
|
+
name: "url-link",
|
|
990
|
+
summary: "URL link commands",
|
|
991
|
+
arguments: [],
|
|
992
|
+
flags: [],
|
|
993
|
+
examples: [],
|
|
994
|
+
subcommands: [defineOperation({
|
|
995
|
+
name: "list",
|
|
996
|
+
summary: "Get asset URL links",
|
|
997
|
+
method: "GET",
|
|
998
|
+
path: "/asset/{assetKey}/url-link",
|
|
999
|
+
pathParameters: [{
|
|
1000
|
+
name: "assetKey",
|
|
1001
|
+
description: "The asset's key",
|
|
1002
|
+
required: true
|
|
1003
|
+
}],
|
|
1004
|
+
queryFlags: [],
|
|
1005
|
+
takesBody: false
|
|
1006
|
+
}), defineOperation({
|
|
1007
|
+
name: "create",
|
|
1008
|
+
summary: "Create asset URL link",
|
|
1009
|
+
method: "POST",
|
|
1010
|
+
path: "/asset/{assetKey}/url-link",
|
|
1011
|
+
pathParameters: [{
|
|
1012
|
+
name: "assetKey",
|
|
1013
|
+
description: "The asset's key",
|
|
1014
|
+
required: true
|
|
1015
|
+
}],
|
|
1016
|
+
queryFlags: [],
|
|
1017
|
+
takesBody: true
|
|
1018
|
+
})]
|
|
1019
|
+
},
|
|
1020
|
+
{
|
|
1021
|
+
name: "useful-life-revision",
|
|
1022
|
+
summary: "Useful life revision commands",
|
|
1023
|
+
arguments: [],
|
|
1024
|
+
flags: [],
|
|
1025
|
+
examples: [],
|
|
1026
|
+
subcommands: [defineOperation({
|
|
1027
|
+
name: "create",
|
|
1028
|
+
summary: "Create asset useful life revision",
|
|
1029
|
+
method: "POST",
|
|
1030
|
+
path: "/asset/{assetKey}/useful-life-revision",
|
|
1031
|
+
pathParameters: [{
|
|
1032
|
+
name: "assetKey",
|
|
1033
|
+
description: "The asset's key",
|
|
1034
|
+
required: true
|
|
1035
|
+
}],
|
|
1036
|
+
queryFlags: [],
|
|
1037
|
+
takesBody: true
|
|
1038
|
+
})]
|
|
1039
|
+
}
|
|
1040
|
+
]
|
|
1041
|
+
},
|
|
1042
|
+
{
|
|
1043
|
+
name: "customer",
|
|
1044
|
+
summary: "Customer commands",
|
|
1045
|
+
arguments: [],
|
|
1046
|
+
flags: [],
|
|
1047
|
+
examples: [],
|
|
1048
|
+
subcommands: [
|
|
1049
|
+
defineOperation({
|
|
1050
|
+
name: "list",
|
|
1051
|
+
summary: "Get customers",
|
|
1052
|
+
method: "GET",
|
|
1053
|
+
path: "/customer",
|
|
1054
|
+
pathParameters: [],
|
|
1055
|
+
queryFlags: [
|
|
1056
|
+
{
|
|
1057
|
+
name: "page",
|
|
1058
|
+
queryName: "page",
|
|
1059
|
+
description: "The page to return, starting at 1",
|
|
1060
|
+
valueName: "number",
|
|
1061
|
+
schema: z.coerce.number()
|
|
1062
|
+
},
|
|
1063
|
+
{
|
|
1064
|
+
name: "limit",
|
|
1065
|
+
queryName: "limit",
|
|
1066
|
+
description: "The number of records per page, from 1 to 100",
|
|
1067
|
+
valueName: "number",
|
|
1068
|
+
schema: z.coerce.number()
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
name: "sort-by",
|
|
1072
|
+
queryName: "sortBy",
|
|
1073
|
+
description: "The field to sort by",
|
|
1074
|
+
valueName: "value",
|
|
1075
|
+
schema: z.string()
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
name: "sort-order",
|
|
1079
|
+
queryName: "sortOrder",
|
|
1080
|
+
description: "The sort direction",
|
|
1081
|
+
valueName: "value",
|
|
1082
|
+
schema: z.enum(["ASC", "DESC"])
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
name: "archived",
|
|
1086
|
+
queryName: "archived",
|
|
1087
|
+
description: "Whether to return unarchived records, archived records, or all of them",
|
|
1088
|
+
valueName: "value",
|
|
1089
|
+
schema: z.enum([
|
|
1090
|
+
"all",
|
|
1091
|
+
"false",
|
|
1092
|
+
"true"
|
|
1093
|
+
])
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
name: "is-customer",
|
|
1097
|
+
queryName: "isCustomer",
|
|
1098
|
+
description: "True to return only customers, or false to return only non-customers",
|
|
1099
|
+
schema: z.boolean()
|
|
1100
|
+
},
|
|
1101
|
+
{
|
|
1102
|
+
name: "is-supplier",
|
|
1103
|
+
queryName: "isSupplier",
|
|
1104
|
+
description: "True to return only suppliers, or false to return only non-suppliers",
|
|
1105
|
+
schema: z.boolean()
|
|
1106
|
+
},
|
|
1107
|
+
{
|
|
1108
|
+
name: "sync-statuses",
|
|
1109
|
+
queryName: "syncStatuses",
|
|
1110
|
+
description: "The CRM sync statuses to return: in-sync, out-of-sync, or not-linked",
|
|
1111
|
+
valueName: "value",
|
|
1112
|
+
repeatable: true,
|
|
1113
|
+
schema: z.array(z.string())
|
|
1114
|
+
},
|
|
1115
|
+
{
|
|
1116
|
+
name: "search",
|
|
1117
|
+
queryName: "search",
|
|
1118
|
+
description: "Text to match against customer names",
|
|
1119
|
+
valueName: "value",
|
|
1120
|
+
schema: z.string()
|
|
1121
|
+
}
|
|
1122
|
+
],
|
|
1123
|
+
takesBody: false
|
|
1124
|
+
}),
|
|
1125
|
+
defineOperation({
|
|
1126
|
+
name: "create",
|
|
1127
|
+
summary: "Create customer",
|
|
1128
|
+
method: "POST",
|
|
1129
|
+
path: "/customer",
|
|
1130
|
+
pathParameters: [],
|
|
1131
|
+
queryFlags: [],
|
|
1132
|
+
takesBody: true
|
|
1133
|
+
}),
|
|
1134
|
+
defineOperation({
|
|
1135
|
+
name: "get",
|
|
1136
|
+
summary: "Get customer",
|
|
1137
|
+
method: "GET",
|
|
1138
|
+
path: "/customer/{customerKey}",
|
|
1139
|
+
pathParameters: [{
|
|
1140
|
+
name: "customerKey",
|
|
1141
|
+
description: "The customer's key",
|
|
1142
|
+
required: true
|
|
1143
|
+
}],
|
|
1144
|
+
queryFlags: [],
|
|
1145
|
+
takesBody: false
|
|
1146
|
+
}),
|
|
1147
|
+
defineOperation({
|
|
1148
|
+
name: "update",
|
|
1149
|
+
summary: "Patch customer",
|
|
1150
|
+
method: "PATCH",
|
|
1151
|
+
path: "/customer/{customerKey}",
|
|
1152
|
+
pathParameters: [{
|
|
1153
|
+
name: "customerKey",
|
|
1154
|
+
description: "The customer's key",
|
|
1155
|
+
required: true
|
|
1156
|
+
}],
|
|
1157
|
+
queryFlags: [],
|
|
1158
|
+
takesBody: true
|
|
1159
|
+
})
|
|
1160
|
+
]
|
|
1161
|
+
},
|
|
1162
|
+
{
|
|
1163
|
+
name: "file",
|
|
1164
|
+
summary: "File commands",
|
|
1165
|
+
arguments: [],
|
|
1166
|
+
flags: [],
|
|
1167
|
+
examples: [],
|
|
1168
|
+
subcommands: [defineOperation({
|
|
1169
|
+
name: "create",
|
|
1170
|
+
summary: "Upload file",
|
|
1171
|
+
method: "POST",
|
|
1172
|
+
path: "/file",
|
|
1173
|
+
pathParameters: [],
|
|
1174
|
+
queryFlags: [],
|
|
1175
|
+
takesBody: true
|
|
1176
|
+
}), defineOperation({
|
|
1177
|
+
name: "get",
|
|
1178
|
+
summary: "Get file",
|
|
1179
|
+
method: "GET",
|
|
1180
|
+
path: "/file/{fileKey}",
|
|
1181
|
+
pathParameters: [{
|
|
1182
|
+
name: "fileKey",
|
|
1183
|
+
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",
|
|
1184
|
+
required: true
|
|
1185
|
+
}],
|
|
1186
|
+
queryFlags: [{
|
|
1187
|
+
name: "attachment",
|
|
1188
|
+
queryName: "attachment",
|
|
1189
|
+
description: "Present, with any value or none, when the file should download as an attachment rather than open inline",
|
|
1190
|
+
valueName: "value",
|
|
1191
|
+
schema: z.string()
|
|
1192
|
+
}],
|
|
1193
|
+
takesBody: false
|
|
1194
|
+
})]
|
|
1195
|
+
},
|
|
1196
|
+
{
|
|
1197
|
+
name: "item",
|
|
1198
|
+
summary: "Item commands",
|
|
1199
|
+
arguments: [],
|
|
1200
|
+
flags: [],
|
|
1201
|
+
examples: [],
|
|
1202
|
+
subcommands: [
|
|
1203
|
+
defineOperation({
|
|
1204
|
+
name: "list",
|
|
1205
|
+
summary: "Get items",
|
|
1206
|
+
method: "GET",
|
|
1207
|
+
path: "/item",
|
|
1208
|
+
pathParameters: [],
|
|
1209
|
+
queryFlags: [
|
|
1210
|
+
{
|
|
1211
|
+
name: "type",
|
|
1212
|
+
queryName: "type",
|
|
1213
|
+
description: "The item type to list, SERVICE, DEVICE, or BULK, or absent for every type",
|
|
1214
|
+
valueName: "value",
|
|
1215
|
+
schema: z.enum([
|
|
1216
|
+
"BULK",
|
|
1217
|
+
"DEVICE",
|
|
1218
|
+
"SERVICE"
|
|
1219
|
+
])
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
name: "search",
|
|
1223
|
+
queryName: "search",
|
|
1224
|
+
description: "Text to match against item names, SKUs, and descriptions, in any case",
|
|
1225
|
+
valueName: "value",
|
|
1226
|
+
schema: z.string()
|
|
1227
|
+
},
|
|
1228
|
+
{
|
|
1229
|
+
name: "page",
|
|
1230
|
+
queryName: "page",
|
|
1231
|
+
description: "The page to return, starting at 1",
|
|
1232
|
+
valueName: "number",
|
|
1233
|
+
schema: z.coerce.number()
|
|
1234
|
+
},
|
|
1235
|
+
{
|
|
1236
|
+
name: "limit",
|
|
1237
|
+
queryName: "limit",
|
|
1238
|
+
description: "The number of records per page, from 1 to 100",
|
|
1239
|
+
valueName: "number",
|
|
1240
|
+
schema: z.coerce.number()
|
|
1241
|
+
},
|
|
1242
|
+
{
|
|
1243
|
+
name: "sort-by",
|
|
1244
|
+
queryName: "sortBy",
|
|
1245
|
+
description: "The field to sort by",
|
|
1246
|
+
valueName: "value",
|
|
1247
|
+
schema: z.enum([
|
|
1248
|
+
"name",
|
|
1249
|
+
"sku",
|
|
1250
|
+
"type",
|
|
1251
|
+
"lastUpdatedAt"
|
|
1252
|
+
])
|
|
1253
|
+
},
|
|
1254
|
+
{
|
|
1255
|
+
name: "sort-order",
|
|
1256
|
+
queryName: "sortOrder",
|
|
1257
|
+
description: "The sort direction",
|
|
1258
|
+
valueName: "value",
|
|
1259
|
+
schema: z.enum(["ASC", "DESC"])
|
|
1260
|
+
},
|
|
1261
|
+
{
|
|
1262
|
+
name: "archived",
|
|
1263
|
+
queryName: "archived",
|
|
1264
|
+
description: "Whether to return unarchived records, archived records, or all of them",
|
|
1265
|
+
valueName: "value",
|
|
1266
|
+
schema: z.enum([
|
|
1267
|
+
"all",
|
|
1268
|
+
"false",
|
|
1269
|
+
"true"
|
|
1270
|
+
])
|
|
1271
|
+
},
|
|
1272
|
+
{
|
|
1273
|
+
name: "exclude-linked-integration",
|
|
1274
|
+
queryName: "excludeLinkedIntegration",
|
|
1275
|
+
description: "An integration whose already-linked items to leave out",
|
|
1276
|
+
valueName: "value",
|
|
1277
|
+
schema: z.string()
|
|
1278
|
+
}
|
|
1279
|
+
],
|
|
1280
|
+
takesBody: false
|
|
1281
|
+
}),
|
|
1282
|
+
defineOperation({
|
|
1283
|
+
name: "create",
|
|
1284
|
+
summary: "Create item",
|
|
1285
|
+
method: "POST",
|
|
1286
|
+
path: "/item",
|
|
1287
|
+
pathParameters: [],
|
|
1288
|
+
queryFlags: [],
|
|
1289
|
+
takesBody: true
|
|
1290
|
+
}),
|
|
1291
|
+
defineOperation({
|
|
1292
|
+
name: "get",
|
|
1293
|
+
summary: "Get item",
|
|
1294
|
+
method: "GET",
|
|
1295
|
+
path: "/item/{itemKey}",
|
|
1296
|
+
pathParameters: [{
|
|
1297
|
+
name: "itemKey",
|
|
1298
|
+
description: "The item's key",
|
|
1299
|
+
required: true
|
|
1300
|
+
}],
|
|
1301
|
+
queryFlags: [],
|
|
1302
|
+
takesBody: false
|
|
1303
|
+
}),
|
|
1304
|
+
defineOperation({
|
|
1305
|
+
name: "update",
|
|
1306
|
+
summary: "Update item",
|
|
1307
|
+
method: "PATCH",
|
|
1308
|
+
path: "/item/{itemKey}",
|
|
1309
|
+
pathParameters: [{
|
|
1310
|
+
name: "itemKey",
|
|
1311
|
+
description: "The item's key",
|
|
1312
|
+
required: true
|
|
1313
|
+
}],
|
|
1314
|
+
queryFlags: [],
|
|
1315
|
+
takesBody: true
|
|
1316
|
+
}),
|
|
1317
|
+
{
|
|
1318
|
+
name: "accounting",
|
|
1319
|
+
summary: "Accounting commands",
|
|
1320
|
+
arguments: [],
|
|
1321
|
+
flags: [],
|
|
1322
|
+
examples: [],
|
|
1323
|
+
subcommands: [defineOperation({
|
|
1324
|
+
name: "update",
|
|
1325
|
+
summary: "Update item accounting",
|
|
1326
|
+
method: "PATCH",
|
|
1327
|
+
path: "/item/{itemKey}/accounting",
|
|
1328
|
+
pathParameters: [{
|
|
1329
|
+
name: "itemKey",
|
|
1330
|
+
description: "The item's key",
|
|
1331
|
+
required: true
|
|
1332
|
+
}],
|
|
1333
|
+
queryFlags: [],
|
|
1334
|
+
takesBody: true
|
|
1335
|
+
})]
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
name: "field",
|
|
1339
|
+
summary: "Field commands",
|
|
1340
|
+
arguments: [],
|
|
1341
|
+
flags: [],
|
|
1342
|
+
examples: [],
|
|
1343
|
+
subcommands: [
|
|
1344
|
+
defineOperation({
|
|
1345
|
+
name: "create",
|
|
1346
|
+
summary: "Create item field",
|
|
1347
|
+
method: "POST",
|
|
1348
|
+
path: "/item/{itemKey}/field",
|
|
1349
|
+
pathParameters: [{
|
|
1350
|
+
name: "itemKey",
|
|
1351
|
+
description: "The item's key",
|
|
1352
|
+
required: true
|
|
1353
|
+
}],
|
|
1354
|
+
queryFlags: [],
|
|
1355
|
+
takesBody: true
|
|
1356
|
+
}),
|
|
1357
|
+
defineOperation({
|
|
1358
|
+
name: "update",
|
|
1359
|
+
summary: "Update item field",
|
|
1360
|
+
method: "PATCH",
|
|
1361
|
+
path: "/item/{itemKey}/field/{fieldKey}",
|
|
1362
|
+
pathParameters: [{
|
|
1363
|
+
name: "itemKey",
|
|
1364
|
+
description: "The item's key",
|
|
1365
|
+
required: true
|
|
1366
|
+
}, {
|
|
1367
|
+
name: "fieldKey",
|
|
1368
|
+
description: "The field's key",
|
|
1369
|
+
required: true
|
|
1370
|
+
}],
|
|
1371
|
+
queryFlags: [],
|
|
1372
|
+
takesBody: true
|
|
1373
|
+
}),
|
|
1374
|
+
defineOperation({
|
|
1375
|
+
name: "delete",
|
|
1376
|
+
summary: "Delete item field",
|
|
1377
|
+
method: "DELETE",
|
|
1378
|
+
path: "/item/{itemKey}/field/{fieldKey}",
|
|
1379
|
+
pathParameters: [{
|
|
1380
|
+
name: "itemKey",
|
|
1381
|
+
description: "The item's key",
|
|
1382
|
+
required: true
|
|
1383
|
+
}, {
|
|
1384
|
+
name: "fieldKey",
|
|
1385
|
+
description: "The field's key",
|
|
1386
|
+
required: true
|
|
1387
|
+
}],
|
|
1388
|
+
queryFlags: [],
|
|
1389
|
+
takesBody: false
|
|
1390
|
+
})
|
|
1391
|
+
]
|
|
1392
|
+
}
|
|
1393
|
+
]
|
|
1394
|
+
},
|
|
1395
|
+
{
|
|
1396
|
+
name: "location",
|
|
1397
|
+
summary: "Location commands",
|
|
1398
|
+
arguments: [],
|
|
1399
|
+
flags: [],
|
|
1400
|
+
examples: [],
|
|
1401
|
+
subcommands: [
|
|
1402
|
+
defineOperation({
|
|
1403
|
+
name: "list",
|
|
1404
|
+
summary: "Get location listing",
|
|
1405
|
+
method: "GET",
|
|
1406
|
+
path: "/location",
|
|
1407
|
+
pathParameters: [],
|
|
1408
|
+
queryFlags: [
|
|
1409
|
+
{
|
|
1410
|
+
name: "page",
|
|
1411
|
+
queryName: "page",
|
|
1412
|
+
description: "The page to return, starting at 1",
|
|
1413
|
+
valueName: "number",
|
|
1414
|
+
schema: z.coerce.number()
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
name: "limit",
|
|
1418
|
+
queryName: "limit",
|
|
1419
|
+
description: "The number of records per page, from 1 to 100",
|
|
1420
|
+
valueName: "number",
|
|
1421
|
+
schema: z.coerce.number()
|
|
1422
|
+
},
|
|
1423
|
+
{
|
|
1424
|
+
name: "sort-by",
|
|
1425
|
+
queryName: "sortBy",
|
|
1426
|
+
description: "The field to sort by",
|
|
1427
|
+
valueName: "value",
|
|
1428
|
+
schema: z.enum([
|
|
1429
|
+
"name",
|
|
1430
|
+
"company",
|
|
1431
|
+
"assetCount"
|
|
1432
|
+
])
|
|
1433
|
+
},
|
|
1434
|
+
{
|
|
1435
|
+
name: "sort-order",
|
|
1436
|
+
queryName: "sortOrder",
|
|
1437
|
+
description: "The sort direction",
|
|
1438
|
+
valueName: "value",
|
|
1439
|
+
schema: z.enum(["ASC", "DESC"])
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
name: "archived",
|
|
1443
|
+
queryName: "archived",
|
|
1444
|
+
description: "Whether to return unarchived records, archived records, or all of them",
|
|
1445
|
+
valueName: "value",
|
|
1446
|
+
schema: z.enum([
|
|
1447
|
+
"all",
|
|
1448
|
+
"false",
|
|
1449
|
+
"true"
|
|
1450
|
+
])
|
|
1451
|
+
},
|
|
1452
|
+
{
|
|
1453
|
+
name: "is-transient",
|
|
1454
|
+
queryName: "isTransient",
|
|
1455
|
+
description: "Whether to return permanent locations (false), transient locations (true), or both (all)",
|
|
1456
|
+
valueName: "value",
|
|
1457
|
+
schema: z.enum([
|
|
1458
|
+
"all",
|
|
1459
|
+
"false",
|
|
1460
|
+
"true"
|
|
1461
|
+
])
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
name: "search",
|
|
1465
|
+
queryName: "search",
|
|
1466
|
+
description: "Text to match against location and customer names",
|
|
1467
|
+
valueName: "value",
|
|
1468
|
+
schema: z.string()
|
|
1469
|
+
},
|
|
1470
|
+
{
|
|
1471
|
+
name: "for-customer-ids",
|
|
1472
|
+
queryName: "forCustomerIds",
|
|
1473
|
+
description: "The IDs of the customers whose locations to return",
|
|
1474
|
+
valueName: "value",
|
|
1475
|
+
repeatable: true,
|
|
1476
|
+
schema: z.array(z.string())
|
|
1477
|
+
},
|
|
1478
|
+
{
|
|
1479
|
+
name: "include-organization",
|
|
1480
|
+
queryName: "includeOrganization",
|
|
1481
|
+
description: "Whether the customer filter also matches your organization's own locations, which on its own returns only those",
|
|
1482
|
+
schema: z.boolean()
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
name: "sync-statuses",
|
|
1486
|
+
queryName: "syncStatuses",
|
|
1487
|
+
description: "The CRM sync statuses to return: in-sync, out-of-sync, or not-linked",
|
|
1488
|
+
valueName: "value",
|
|
1489
|
+
repeatable: true,
|
|
1490
|
+
schema: z.array(z.string())
|
|
1491
|
+
}
|
|
1492
|
+
],
|
|
1493
|
+
takesBody: false
|
|
1494
|
+
}),
|
|
1495
|
+
defineOperation({
|
|
1496
|
+
name: "create",
|
|
1497
|
+
summary: "Create location",
|
|
1498
|
+
method: "POST",
|
|
1499
|
+
path: "/location",
|
|
1500
|
+
pathParameters: [],
|
|
1501
|
+
queryFlags: [],
|
|
1502
|
+
takesBody: true
|
|
1503
|
+
}),
|
|
1504
|
+
defineOperation({
|
|
1505
|
+
name: "get",
|
|
1506
|
+
summary: "Get location",
|
|
1507
|
+
method: "GET",
|
|
1508
|
+
path: "/location/{locationKey}",
|
|
1509
|
+
pathParameters: [{
|
|
1510
|
+
name: "locationKey",
|
|
1511
|
+
description: "The location's key",
|
|
1512
|
+
required: true
|
|
1513
|
+
}],
|
|
1514
|
+
queryFlags: [],
|
|
1515
|
+
takesBody: false
|
|
1516
|
+
}),
|
|
1517
|
+
defineOperation({
|
|
1518
|
+
name: "update",
|
|
1519
|
+
summary: "Patch location",
|
|
1520
|
+
method: "PATCH",
|
|
1521
|
+
path: "/location/{locationKey}",
|
|
1522
|
+
pathParameters: [{
|
|
1523
|
+
name: "locationKey",
|
|
1524
|
+
description: "The location's key",
|
|
1525
|
+
required: true
|
|
1526
|
+
}],
|
|
1527
|
+
queryFlags: [],
|
|
1528
|
+
takesBody: true
|
|
1529
|
+
}),
|
|
1530
|
+
{
|
|
1531
|
+
name: "zones",
|
|
1532
|
+
summary: "Zones commands",
|
|
1533
|
+
arguments: [],
|
|
1534
|
+
flags: [],
|
|
1535
|
+
examples: [],
|
|
1536
|
+
subcommands: [defineOperation({
|
|
1537
|
+
name: "list",
|
|
1538
|
+
summary: "Get zones",
|
|
1539
|
+
method: "GET",
|
|
1540
|
+
path: "/location/{locationKey}/zones",
|
|
1541
|
+
pathParameters: [{
|
|
1542
|
+
name: "locationKey",
|
|
1543
|
+
description: "The key of the site whose zones to list",
|
|
1544
|
+
required: true
|
|
1545
|
+
}],
|
|
1546
|
+
queryFlags: [{
|
|
1547
|
+
name: "archived",
|
|
1548
|
+
queryName: "archived",
|
|
1549
|
+
description: "Whether to return unarchived zones, archived zones, or all of them",
|
|
1550
|
+
valueName: "value",
|
|
1551
|
+
schema: z.enum([
|
|
1552
|
+
"all",
|
|
1553
|
+
"false",
|
|
1554
|
+
"true"
|
|
1555
|
+
])
|
|
1556
|
+
}],
|
|
1557
|
+
takesBody: false
|
|
1558
|
+
})]
|
|
1559
|
+
}
|
|
1560
|
+
]
|
|
1561
|
+
},
|
|
1562
|
+
{
|
|
1563
|
+
name: "url-link",
|
|
1564
|
+
summary: "URL link commands",
|
|
1565
|
+
arguments: [],
|
|
1566
|
+
flags: [],
|
|
1567
|
+
examples: [],
|
|
1568
|
+
subcommands: [
|
|
1569
|
+
defineOperation({
|
|
1570
|
+
name: "get",
|
|
1571
|
+
summary: "Get URL link by key",
|
|
1572
|
+
method: "GET",
|
|
1573
|
+
path: "/url-link/{linkKey}",
|
|
1574
|
+
pathParameters: [{
|
|
1575
|
+
name: "linkKey",
|
|
1576
|
+
description: "The URL link's key",
|
|
1577
|
+
required: true
|
|
1578
|
+
}],
|
|
1579
|
+
queryFlags: [],
|
|
1580
|
+
takesBody: false
|
|
1581
|
+
}),
|
|
1582
|
+
defineOperation({
|
|
1583
|
+
name: "update",
|
|
1584
|
+
summary: "Update URL link",
|
|
1585
|
+
method: "PATCH",
|
|
1586
|
+
path: "/url-link/{linkKey}",
|
|
1587
|
+
pathParameters: [{
|
|
1588
|
+
name: "linkKey",
|
|
1589
|
+
description: "The URL link's key",
|
|
1590
|
+
required: true
|
|
1591
|
+
}],
|
|
1592
|
+
queryFlags: [],
|
|
1593
|
+
takesBody: true
|
|
1594
|
+
}),
|
|
1595
|
+
defineOperation({
|
|
1596
|
+
name: "delete",
|
|
1597
|
+
summary: "Delete URL link",
|
|
1598
|
+
method: "DELETE",
|
|
1599
|
+
path: "/url-link/{linkKey}",
|
|
1600
|
+
pathParameters: [{
|
|
1601
|
+
name: "linkKey",
|
|
1602
|
+
description: "The URL link's key",
|
|
1603
|
+
required: true
|
|
1604
|
+
}],
|
|
1605
|
+
queryFlags: [],
|
|
1606
|
+
takesBody: false
|
|
1607
|
+
})
|
|
1608
|
+
]
|
|
1609
|
+
}
|
|
1610
|
+
],
|
|
1611
|
+
apiCommand,
|
|
1612
|
+
configCommand,
|
|
1613
|
+
agentGuideCommand
|
|
1614
|
+
];
|
|
1615
|
+
//#endregion
|
|
1616
|
+
//#region src/command/validate.ts
|
|
1617
|
+
/** toRejectedFlag names the first flag whose value its schema refuses. */
|
|
1618
|
+
function toRejectedFlag(command, flags) {
|
|
1619
|
+
for (const flag of command.flags) {
|
|
1620
|
+
const value = flags[toOptionKey(flag.name)];
|
|
1621
|
+
if (value === void 0) continue;
|
|
1622
|
+
if (!flag.schema.safeParse(value).success) return `--${flag.name} does not accept ${JSON.stringify(value)}`;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
339
1625
|
//#endregion
|
|
340
1626
|
//#region src/cli.ts
|
|
341
1627
|
const program = new Command();
|
|
342
|
-
program.name("hardfin").description("Call the Hardfin API from a terminal or an agent").version(version, "-v, --version").showHelpAfterError().enablePositionalOptions();
|
|
1628
|
+
program.name("hardfin").description("Call the Hardfin API from a terminal or an agent").version(version, "-v, --version").option("--api-url <url>", "The API to call, which also moves the authentication endpoints").showHelpAfterError().enablePositionalOptions();
|
|
343
1629
|
for (const command of commands) program.addCommand(toProgram(command));
|
|
344
1630
|
await program.parseAsync(process.argv);
|
|
345
1631
|
/** toProgram wires one registry command into the parser. */
|
|
@@ -358,6 +1644,8 @@ function toProgram(command) {
|
|
|
358
1644
|
program.addOption(option);
|
|
359
1645
|
}
|
|
360
1646
|
for (const example of command.examples) program.addHelpText("after", `\n${example.description}:\n $ ${example.command}`);
|
|
1647
|
+
for (const subcommand of command.subcommands ?? []) program.addCommand(toProgram(subcommand));
|
|
1648
|
+
if (!command.run) return program;
|
|
361
1649
|
program.action(async (...parsed) => {
|
|
362
1650
|
const flags = parsed[parsed.length - 2] ?? {};
|
|
363
1651
|
const args = parsed.slice(0, parsed.length - 2).flatMap(toArgumentList);
|
|
@@ -367,16 +1655,23 @@ function toProgram(command) {
|
|
|
367
1655
|
}
|
|
368
1656
|
async function toExitCode(command, args, flags) {
|
|
369
1657
|
const isJSON = isJSONOutput(flags);
|
|
1658
|
+
const rejected = toRejectedFlag(command, flags);
|
|
1659
|
+
if (rejected) {
|
|
1660
|
+
writeFailure(rejected, isJSON);
|
|
1661
|
+
return ExitCode.USAGE;
|
|
1662
|
+
}
|
|
370
1663
|
try {
|
|
371
|
-
|
|
1664
|
+
const resolved = toSettings({ apiUrl: program.opts()["apiUrl"] });
|
|
1665
|
+
return await command.run?.({
|
|
372
1666
|
args,
|
|
373
1667
|
flags,
|
|
374
1668
|
isJSON,
|
|
375
|
-
commands
|
|
376
|
-
|
|
1669
|
+
commands,
|
|
1670
|
+
resolved
|
|
1671
|
+
}) ?? ExitCode.OK;
|
|
377
1672
|
} catch (error) {
|
|
378
1673
|
writeFailure(error instanceof Error ? error.message : String(error), isJSON);
|
|
379
|
-
return ExitCode.ERROR;
|
|
1674
|
+
return error instanceof ConfigFailure ? ExitCode.USAGE : ExitCode.ERROR;
|
|
380
1675
|
}
|
|
381
1676
|
}
|
|
382
1677
|
function toArgumentList(value) {
|