@hardfin/cli 0.1.0-dev.28 → 0.1.0-dev.30

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 CHANGED
@@ -87,6 +87,23 @@ Three things fail locally with exit code 2, before any request is made: a value
87
87
  enum, a required field that is missing, and `--unset` on a field the API will not take null
88
88
  for.
89
89
 
90
+ ### When a command fails
91
+
92
+ A person at a terminal reads one plain line on stderr. Anything else reads JSON, also on
93
+ stderr, so a pipe still carries only the result.
94
+
95
+ | Field | Holds |
96
+ | ----------- | ------------------------------------------------------------------------ |
97
+ | `code` | A fixed name for the failure, such as `not_found` or `missing_flag` |
98
+ | `message` | The same plain sentence a person reads |
99
+ | `errors` | Every error the API's envelope named, with its own numeric `statusCode` |
100
+ | `requestId` | The API's request ID, or null when the request never reached it |
101
+ | `status` | The HTTP status, or null for a failure caught before any request is made |
102
+
103
+ An agent branches on `code`. A code is never renamed and never changes meaning, while the
104
+ message may be reworded at any time. `hardfin agent-guide` lists every code with what it
105
+ means, and `src/output/failure.ts` defines them.
106
+
90
107
  ### Overrides
91
108
 
92
109
  Two endpoints sometimes want one name. `DELETE /asset/{assetKey}/ownership` and
@@ -455,7 +472,7 @@ if that version is already on npm it increments the patch, so a preview always s
455
472
  the last release.
456
473
 
457
474
  Before publishing, the workflow stamps each file in `dist` and its source map with a matching
458
- debug ID, and uploads the maps to Sentry under the release `@hardfin/cli@<version>`. An error
475
+ debug ID, and uploads the maps to Sentry under the release `hardfin-cli@<version>`. An error
459
476
  report's stack trace then reads as the TypeScript source. The maps are not in the package, and
460
477
  publishing skips `prepublishOnly`, so the stamped files are the ones published. The upload
461
478
  needs the `SENTRY_AUTH_TOKEN` secret.
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="497b0264-fbd1-51d6-bd23-fb6c7eed397b")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="29560915-e7ae-5a74-b5f6-f4a6988e1b43")}catch(e){}}();
4
4
  import module from "node:module";
5
5
  //#region src/cli.ts
6
6
  module.enableCompileCache?.();
@@ -14,8 +14,14 @@ process.on("unhandledRejection", (error) => void toCrash(error));
14
14
  try {
15
15
  const answer = await toQuickAnswer(process.argv.slice(2));
16
16
  if (answer === void 0) {
17
- const { toCli, wasReportAttempted } = await import("./program-C3SGdPbf.js");
18
- await toCli().parseAsync(process.argv);
17
+ const { toCli, toParseExitCode, wasReportAttempted } = await import("./program-BZW_50HP.js");
18
+ try {
19
+ await toCli().parseAsync(process.argv);
20
+ } catch (error) {
21
+ const code = toParseExitCode(error, process.argv.slice(2));
22
+ if (code === void 0) throw error;
23
+ process.exitCode = code;
24
+ }
19
25
  if (wasReportAttempted()) process.exit();
20
26
  } else process.stdout.write(`${answer}\n`);
21
27
  } catch (error) {
@@ -27,7 +33,7 @@ async function toCrash(error) {
27
33
  isCrashing = true;
28
34
  process.stderr.write(`error: ${error instanceof Error ? error.message : String(error)}\n`);
29
35
  try {
30
- const { toReported } = await import("./program-C3SGdPbf.js");
36
+ const { toReported } = await import("./program-BZW_50HP.js");
31
37
  await toReported(error, false, void 0, "fatal");
32
38
  } finally {
33
39
  process.exit(1);
@@ -45,4 +51,4 @@ async function toQuickAnswer(args) {
45
51
  export {};
46
52
 
47
53
  //# sourceMappingURL=cli.js.map
48
- //# debugId=497b0264-fbd1-51d6-bd23-fb6c7eed397b
54
+ //# debugId=29560915-e7ae-5a74-b5f6-f4a6988e1b43
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="c531fbbe-2dd5-53cf-87a6-723b2926146f")}catch(e){}}();
3
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5c189832-33ce-5032-b0e0-fc3a33cf5b1d")}catch(e){}}();
4
4
  import { a as defineCommand, i as GLOBAL_FLAGS, r as ExitCode, t as toInvocation } from "./scrub-VtQmxap1.js";
5
5
  import { n as toCompletion, t as toAnswer$1 } from "./completion-tree-rR_Rmeij.js";
6
6
  import { createRequire } from "node:module";
7
- import { Command, Option } from "commander";
7
+ import { Command, CommanderError, Option } from "commander";
8
8
  import { z } from "zod";
9
9
  import { accessSync, chmodSync, closeSync, constants, existsSync, mkdirSync, openAsBlob, openSync, readFileSync, renameSync, rmSync, statSync, writeFileSync, writeSync } from "node:fs";
10
10
  import { basename, delimiter, dirname, join, resolve } from "node:path";
@@ -144,152 +144,6 @@ function toOrigin(apiUrl) {
144
144
  }
145
145
  }
146
146
  //#endregion
147
- //#region src/output/writer.ts
148
- /**
149
- * toText reads a value a caller supplied, which arrives typed as unknown. An object would
150
- * otherwise reach a request as the text "[object Object]".
151
- */
152
- function toText(value) {
153
- if (typeof value === "string") return value;
154
- if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
155
- if (value === void 0 || value === null) return "";
156
- return JSON.stringify(value);
157
- }
158
- /** writeData prints a command's result on stdout. */
159
- function writeData(value) {
160
- if (typeof value === "string") {
161
- process.stdout.write(value.endsWith("\n") ? value : `${value}\n`);
162
- return;
163
- }
164
- process.stdout.write(`${JSON.stringify(value ?? null, null, 2)}\n`);
165
- }
166
- /** writeFailure prints why a command failed on stderr, as text or as JSON. */
167
- function writeFailure(message, isJSON, errors, requestId, status) {
168
- if (!isJSON) {
169
- process.stderr.write(`error: ${message}${status === void 0 ? "" : ` (HTTP ${status})`}\n`);
170
- for (const entry of errors?.slice(1) ?? []) process.stderr.write(` ${entry.error} (${entry.statusCode})\n`);
171
- if (requestId) process.stderr.write(` request ${requestId}\n`);
172
- return;
173
- }
174
- const payload = {
175
- errors: errors ?? [{
176
- error: message,
177
- statusCode: 0
178
- }],
179
- requestId: requestId ?? null,
180
- status: status ?? null
181
- };
182
- process.stderr.write(`${JSON.stringify(payload, null, 2)}\n`);
183
- }
184
- /** isJSONOutput decides whether this invocation prints machine-readable output. */
185
- function isJSONOutput(flags) {
186
- if (flags["json"] === true) return true;
187
- return !process.stdout.isTTY;
188
- }
189
- /** version is what this build reports, read from the published package manifest. */
190
- const version$1 = createRequire(import.meta.url)("../package.json").version;
191
- //#endregion
192
- //#region src/command/agent-guide.ts
193
- const agentGuideCommand = defineCommand({
194
- name: "agent-guide",
195
- summary: "Print instructions an agent can follow to use this CLI",
196
- 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.",
197
- arguments: [],
198
- flags: [{
199
- name: "json",
200
- description: "Print the command registry as JSON instead of Markdown",
201
- schema: z.boolean().optional()
202
- }],
203
- examples: [{
204
- description: "Write a skill file",
205
- command: "hardfin agent-guide > SKILL.md"
206
- }, {
207
- description: "Inspect the registry",
208
- command: "hardfin agent-guide --json"
209
- }],
210
- run: runAgentGuide
211
- });
212
- /** toGuide renders the registry as the Markdown an agent reads. */
213
- function toGuide(commands, version) {
214
- const lines = [
215
- "# Hardfin CLI",
216
- "",
217
- `The \`hardfin\` command calls the Hardfin API. This guide describes version ${version} of the CLI, which speaks API version ${API_VERSION}.`,
218
- "",
219
- "## Before calling",
220
- "",
221
- "- Authenticate by setting `HARDFIN_API_KEY`, or by running `hardfin login`",
222
- "- Every command prints JSON on stdout and diagnostics on stderr",
223
- "- Output is JSON whenever stdout is not a terminal, so no flag is needed when calling from code",
224
- "",
225
- "## Exit codes",
226
- "",
227
- "| Code | Means |",
228
- "| --- | --- |",
229
- "| 0 | The command succeeded |",
230
- "| 1 | The command failed |",
231
- "| 2 | The command was called wrongly |",
232
- "| 4 | No usable credential |",
233
- "| 130 | The person pressed Ctrl-C |",
234
- "",
235
- "## Commands",
236
- ""
237
- ];
238
- for (const command of commands.filter((command) => !command.hidden)) lines.push(...toCommandLines(command, []));
239
- return lines.join("\n");
240
- }
241
- /** toCommandLines describes one command and everything nested under it. */
242
- function toCommandLines(command, parents) {
243
- const path = [...parents, command.name];
244
- const lines = [
245
- `### \`hardfin ${path.join(" ")}\``,
246
- "",
247
- command.description ?? command.summary,
248
- ""
249
- ];
250
- if (command.arguments.length > 0) {
251
- lines.push("| Argument | Required | Holds |", "| --- | --- | --- |");
252
- for (const argument of command.arguments) lines.push(`| \`${argument.name}\` | ${argument.required ? "yes" : "no"} | ${argument.description} |`);
253
- lines.push("");
254
- }
255
- if (command.flags.length > 0) {
256
- lines.push("| Flag | Takes | Does |", "| --- | --- | --- |");
257
- for (const flag of command.flags) {
258
- const name = flag.short ? `-${flag.short}, --${flag.name}` : `--${flag.name}`;
259
- lines.push(`| \`${name}\` | ${flag.valueName ?? "nothing"} | ${flag.description} |`);
260
- }
261
- lines.push("");
262
- }
263
- for (const example of command.examples) lines.push(`${example.description}:`, "", "```sh", example.command, "```", "");
264
- for (const subcommand of command.subcommands ?? []) lines.push(...toCommandLines(subcommand, path));
265
- return lines;
266
- }
267
- async function runAgentGuide(input) {
268
- if (input.flags["json"] === true) {
269
- writeData(input.commands.map(toSummary));
270
- return ExitCode.OK;
271
- }
272
- writeData(toGuide(input.commands, version$1));
273
- return ExitCode.OK;
274
- }
275
- function toSummary(command) {
276
- return {
277
- name: command.name,
278
- summary: command.summary,
279
- description: command.description ?? command.summary,
280
- arguments: command.arguments,
281
- flags: command.flags.map((flag) => ({
282
- name: flag.name,
283
- short: flag.short ?? null,
284
- description: flag.description,
285
- valueName: flag.valueName ?? null,
286
- repeatable: flag.repeatable ?? false
287
- })),
288
- examples: command.examples,
289
- subcommands: command.subcommands?.filter((entry) => !entry.hidden).map(toSummary)
290
- };
291
- }
292
- //#endregion
293
147
  //#region src/auth/jwt.ts
294
148
  /**
295
149
  * toClaims reads an access token's payload for display. Nothing here verifies the
@@ -620,6 +474,51 @@ function keep(issuer, document) {
620
474
  } catch {}
621
475
  }
622
476
  //#endregion
477
+ //#region src/output/writer.ts
478
+ /**
479
+ * toText reads a value a caller supplied, which arrives typed as unknown. An object would
480
+ * otherwise reach a request as the text "[object Object]".
481
+ */
482
+ function toText(value) {
483
+ if (typeof value === "string") return value;
484
+ if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
485
+ if (value === void 0 || value === null) return "";
486
+ return JSON.stringify(value);
487
+ }
488
+ /** writeData prints a command's result on stdout. */
489
+ function writeData(value) {
490
+ if (typeof value === "string") {
491
+ process.stdout.write(value.endsWith("\n") ? value : `${value}\n`);
492
+ return;
493
+ }
494
+ process.stdout.write(`${JSON.stringify(value ?? null, null, 2)}\n`);
495
+ }
496
+ /** writeFailure prints why a command failed on stderr, with a code only a machine reading JSON sees. */
497
+ function writeFailure(code, message, isJSON, errors, requestId, status) {
498
+ if (!isJSON) {
499
+ process.stderr.write(`error: ${message}${status === void 0 ? "" : ` (HTTP ${status})`}\n`);
500
+ for (const entry of errors?.slice(1) ?? []) process.stderr.write(` ${entry.error} (${entry.statusCode})\n`);
501
+ if (requestId) process.stderr.write(` request ${requestId}\n`);
502
+ return;
503
+ }
504
+ const payload = {
505
+ code,
506
+ message,
507
+ errors: errors ?? [{
508
+ error: message,
509
+ statusCode: 0
510
+ }],
511
+ requestId: requestId ?? null,
512
+ status: status ?? null
513
+ };
514
+ process.stderr.write(`${JSON.stringify(payload, null, 2)}\n`);
515
+ }
516
+ /** isJSONOutput decides whether this invocation prints machine-readable output. */
517
+ function isJSONOutput(flags) {
518
+ if (flags["json"] === true) return true;
519
+ return !process.stdout.isTTY;
520
+ }
521
+ //#endregion
623
522
  //#region src/auth/grant.ts
624
523
  const TokenResponse = z.looseObject({
625
524
  access_token: z.string(),
@@ -980,6 +879,172 @@ function toStatusMessage(status) {
980
879
  if (status === 403) return "the organization is deactivated, or its access has ended";
981
880
  return `the request failed with status ${status}`;
982
881
  }
882
+ //#endregion
883
+ //#region src/output/failure.ts
884
+ /** FAILURE_CODES is every code a failure can carry, and none is ever renamed or given a new meaning. */
885
+ const FAILURE_CODES = {
886
+ unknown_command: "No command has that name. Run hardfin --help for the list",
887
+ invalid_arguments: "The command was given the wrong positional arguments",
888
+ missing_flag: "A flag the command needs was not given",
889
+ invalid_flag: "A flag was given a value it does not take",
890
+ invalid_input: "A file the command was given cannot be read, or does not hold JSON",
891
+ empty_request: "The command would change nothing, because no field was given",
892
+ output_required: "The answer is a file, and needs --output or a pipe",
893
+ not_confirmed: "The change was not confirmed. Pass --yes to confirm it without a terminal",
894
+ shell_unavailable: "The shell did not say where its startup file is",
895
+ invalid_config: "A config file or setting cannot be read",
896
+ not_signed_in: "No usable credential. Run hardfin login, or set HARDFIN_API_KEY",
897
+ sign_in_failed: "Signing in did not finish",
898
+ unreachable: "The CLI could not connect to the server",
899
+ timed_out: "Something the CLI waited on took too long",
900
+ busy: "Another hardfin command is holding the credential lock",
901
+ bad_request: "The API refused the request as written (HTTP 400)",
902
+ unauthorized: "The API did not accept the credential (HTTP 401)",
903
+ forbidden: "The credential may not do this (HTTP 403)",
904
+ not_found: "The record or path does not exist (HTTP 404)",
905
+ conflict: "The request conflicts with the record's current state (HTTP 409)",
906
+ too_large: "The request is larger than the API accepts (HTTP 413)",
907
+ invalid_request: "The API could not process the request (HTTP 422)",
908
+ rate_limited: "Too many requests. Wait, then try again (HTTP 429)",
909
+ server_error: "The API failed to answer (HTTP 5xx)",
910
+ api_error: "The API refused the request with another status",
911
+ internal_error: "A problem in hardfin itself, which has been reported"
912
+ };
913
+ /** toStatusCode names a refusal by the HTTP status the API answered with. */
914
+ function toStatusCode(status) {
915
+ switch (status) {
916
+ case 400: return "bad_request";
917
+ case 401: return "unauthorized";
918
+ case 403: return "forbidden";
919
+ case 404: return "not_found";
920
+ case 409: return "conflict";
921
+ case 413: return "too_large";
922
+ case 422: return "invalid_request";
923
+ case 429: return "rate_limited";
924
+ default: return status >= 500 ? "server_error" : "api_error";
925
+ }
926
+ }
927
+ /** toErrorCode names a failure nothing handled closer to where it happened. */
928
+ function toErrorCode(error) {
929
+ if (error instanceof RequestFailure) return toStatusCode(error.status);
930
+ if (error instanceof NoCredential) return "not_signed_in";
931
+ if (error instanceof ConfigFailure) return "invalid_config";
932
+ if (error instanceof Unreachable) return "unreachable";
933
+ if (error instanceof TimedOut) return "timed_out";
934
+ if (error instanceof LockHeld) return "busy";
935
+ return "internal_error";
936
+ }
937
+ /** version is what this build reports, read from the published package manifest. */
938
+ const version$1 = createRequire(import.meta.url)("../package.json").version;
939
+ //#endregion
940
+ //#region src/command/agent-guide.ts
941
+ const agentGuideCommand = defineCommand({
942
+ name: "agent-guide",
943
+ summary: "Print instructions an agent can follow to use this CLI",
944
+ 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.",
945
+ arguments: [],
946
+ flags: [{
947
+ name: "json",
948
+ description: "Print the command registry as JSON instead of Markdown",
949
+ schema: z.boolean().optional()
950
+ }],
951
+ examples: [{
952
+ description: "Write a skill file",
953
+ command: "hardfin agent-guide > SKILL.md"
954
+ }, {
955
+ description: "Inspect the registry",
956
+ command: "hardfin agent-guide --json"
957
+ }],
958
+ run: runAgentGuide
959
+ });
960
+ /** toGuide renders the registry as the Markdown an agent reads. */
961
+ function toGuide(commands, version) {
962
+ const lines = [
963
+ "# Hardfin CLI",
964
+ "",
965
+ `The \`hardfin\` command calls the Hardfin API. This guide describes version ${version} of the CLI, which speaks API version ${API_VERSION}.`,
966
+ "",
967
+ "## Before calling",
968
+ "",
969
+ "- Authenticate by setting `HARDFIN_API_KEY`, or by running `hardfin login`",
970
+ "- Every command prints JSON on stdout and diagnostics on stderr",
971
+ "- Output is JSON whenever stdout is not a terminal, so no flag is needed when calling from code",
972
+ "",
973
+ "## Exit codes",
974
+ "",
975
+ "| Code | Means |",
976
+ "| --- | --- |",
977
+ "| 0 | The command succeeded |",
978
+ "| 1 | The command failed |",
979
+ "| 2 | The command was called wrongly |",
980
+ "| 4 | No usable credential |",
981
+ "| 130 | The person pressed Ctrl-C |",
982
+ "",
983
+ "## Error codes",
984
+ "",
985
+ "A failed command prints JSON on stderr with a `code`, a `message`, the API's `errors`, its `requestId` and the HTTP `status`. Branch on `code`, which never changes meaning. The message is for a person.",
986
+ "",
987
+ "| Code | Means |",
988
+ "| --- | --- |",
989
+ ...Object.entries(FAILURE_CODES).map(([code, meaning]) => `| \`${code}\` | ${meaning} |`),
990
+ "",
991
+ "## Commands",
992
+ ""
993
+ ];
994
+ for (const command of commands.filter((command) => !command.hidden)) lines.push(...toCommandLines(command, []));
995
+ return lines.join("\n");
996
+ }
997
+ /** toCommandLines describes one command and everything nested under it. */
998
+ function toCommandLines(command, parents) {
999
+ const path = [...parents, command.name];
1000
+ const lines = [
1001
+ `### \`hardfin ${path.join(" ")}\``,
1002
+ "",
1003
+ command.description ?? command.summary,
1004
+ ""
1005
+ ];
1006
+ if (command.arguments.length > 0) {
1007
+ lines.push("| Argument | Required | Holds |", "| --- | --- | --- |");
1008
+ for (const argument of command.arguments) lines.push(`| \`${argument.name}\` | ${argument.required ? "yes" : "no"} | ${argument.description} |`);
1009
+ lines.push("");
1010
+ }
1011
+ if (command.flags.length > 0) {
1012
+ lines.push("| Flag | Takes | Does |", "| --- | --- | --- |");
1013
+ for (const flag of command.flags) {
1014
+ const name = flag.short ? `-${flag.short}, --${flag.name}` : `--${flag.name}`;
1015
+ lines.push(`| \`${name}\` | ${flag.valueName ?? "nothing"} | ${flag.description} |`);
1016
+ }
1017
+ lines.push("");
1018
+ }
1019
+ for (const example of command.examples) lines.push(`${example.description}:`, "", "```sh", example.command, "```", "");
1020
+ for (const subcommand of command.subcommands ?? []) lines.push(...toCommandLines(subcommand, path));
1021
+ return lines;
1022
+ }
1023
+ async function runAgentGuide(input) {
1024
+ if (input.flags["json"] === true) {
1025
+ writeData(input.commands.map(toSummary));
1026
+ return ExitCode.OK;
1027
+ }
1028
+ writeData(toGuide(input.commands, version$1));
1029
+ return ExitCode.OK;
1030
+ }
1031
+ function toSummary(command) {
1032
+ return {
1033
+ name: command.name,
1034
+ summary: command.summary,
1035
+ description: command.description ?? command.summary,
1036
+ arguments: command.arguments,
1037
+ flags: command.flags.map((flag) => ({
1038
+ name: flag.name,
1039
+ short: flag.short ?? null,
1040
+ description: flag.description,
1041
+ valueName: flag.valueName ?? null,
1042
+ repeatable: flag.repeatable ?? false
1043
+ })),
1044
+ examples: command.examples,
1045
+ subcommands: command.subcommands?.filter((entry) => !entry.hidden).map(toSummary)
1046
+ };
1047
+ }
983
1048
  const apiCommand = defineCommand({
984
1049
  name: "api",
985
1050
  summary: "Call a Hardfin API endpoint and print what it answers",
@@ -1043,23 +1108,23 @@ const apiCommand = defineCommand({
1043
1108
  async function runApi(input) {
1044
1109
  const path = input.args[0];
1045
1110
  if (!path) {
1046
- writeFailure("a path is required, such as /customer", input.isJSON);
1111
+ writeFailure("invalid_arguments", "a path is required, such as /customer", input.isJSON);
1047
1112
  return ExitCode.USAGE;
1048
1113
  }
1049
1114
  if (path.split("/").includes("..")) {
1050
- writeFailure("a path stays inside the API, so it cannot contain ..", input.isJSON);
1115
+ writeFailure("invalid_arguments", "a path stays inside the API, so it cannot contain ..", input.isJSON);
1051
1116
  return ExitCode.USAGE;
1052
1117
  }
1053
1118
  const query = toQuery$1(input.flags["field"]);
1054
1119
  if (query === void 0) {
1055
- writeFailure("each --field is key=value, such as -f limit=50", input.isJSON);
1120
+ writeFailure("invalid_flag", "each --field is key=value, such as -f limit=50", input.isJSON);
1056
1121
  return ExitCode.USAGE;
1057
1122
  }
1058
1123
  let body;
1059
1124
  if (typeof input.flags["input"] === "string") {
1060
1125
  body = toBody$1(input.flags["input"]);
1061
1126
  if (body === void 0) {
1062
- writeFailure(`${input.flags["input"]} does not hold JSON`, input.isJSON);
1127
+ writeFailure("invalid_input", `${input.flags["input"]} does not hold JSON`, input.isJSON);
1063
1128
  return ExitCode.USAGE;
1064
1129
  }
1065
1130
  }
@@ -1075,11 +1140,11 @@ async function runApi(input) {
1075
1140
  return ExitCode.OK;
1076
1141
  } catch (error) {
1077
1142
  if (error instanceof NoCredential) {
1078
- writeFailure(error.message, input.isJSON);
1143
+ writeFailure("not_signed_in", error.message, input.isJSON);
1079
1144
  return ExitCode.NOT_AUTHENTICATED;
1080
1145
  }
1081
1146
  if (error instanceof RequestFailure) {
1082
- writeFailure(error.message, input.isJSON, error.errors, error.requestId, error.status);
1147
+ writeFailure(toStatusCode(error.status), error.message, input.isJSON, error.errors, error.requestId, error.status);
1083
1148
  return error.status === 401 ? ExitCode.NOT_AUTHENTICATED : ExitCode.ERROR;
1084
1149
  }
1085
1150
  throw error;
@@ -1485,17 +1550,17 @@ async function runChange(input, change) {
1485
1550
  const place = toPlace();
1486
1551
  const shell = input.args[0] ?? toShell(place);
1487
1552
  if (!shell || !SHELLS.includes(shell)) {
1488
- writeFailure(`name the shell to ${change} for: ${SHELLS.join(", ")}`, input.isJSON);
1553
+ writeFailure("invalid_arguments", `name the shell to ${change} for: ${SHELLS.join(", ")}`, input.isJSON);
1489
1554
  return ExitCode.USAGE;
1490
1555
  }
1491
1556
  const plan = toPlan(shell, place);
1492
1557
  if (change === "install" && shell === "powershell" && !plan.startup) {
1493
- writeFailure("PowerShell did not report its profile, so there is nowhere to load the script from", input.isJSON);
1558
+ writeFailure("shell_unavailable", "PowerShell did not report its profile, so there is nowhere to load the script from", input.isJSON);
1494
1559
  return ExitCode.ERROR;
1495
1560
  }
1496
1561
  process.stderr.write(toDescription(plan, change));
1497
1562
  if (!await isConfirmed(input.flags["yes"] === true)) {
1498
- writeFailure(process.stdin.isTTY ? "nothing was changed" : "nothing was changed. Pass --yes to change it without a terminal", input.isJSON);
1563
+ writeFailure("not_confirmed", process.stdin.isTTY ? "nothing was changed" : "nothing was changed. Pass --yes to change it without a terminal", input.isJSON);
1499
1564
  return ExitCode.USAGE;
1500
1565
  }
1501
1566
  if (change === "install") {
@@ -1658,7 +1723,7 @@ function toSchemaValues(schema) {
1658
1723
  async function runCompletion(input) {
1659
1724
  const shell = input.args[0] ?? "";
1660
1725
  if (!SHELLS.includes(shell)) {
1661
- writeFailure(`${shell || "no shell"} is not one this CLI writes for. Choose ${SHELLS.join(", ")}`, input.isJSON);
1726
+ writeFailure("invalid_arguments", `${shell || "no shell"} is not one this CLI writes for. Choose ${SHELLS.join(", ")}`, input.isJSON);
1662
1727
  return ExitCode.USAGE;
1663
1728
  }
1664
1729
  writeData(toScript(shell));
@@ -2064,25 +2129,25 @@ async function runLogin(input) {
2064
2129
  }
2065
2130
  process.stderr.write(callback.error ? "\n" : "\nApproved, finishing the sign in\n");
2066
2131
  if (callback.error) {
2067
- writeFailure(`sign in was refused: ${callback.error}${callback.errorDescription ? `, ${callback.errorDescription}` : ""}`, input.isJSON);
2132
+ writeFailure("sign_in_failed", `sign in was refused: ${callback.error}${callback.errorDescription ? `, ${callback.errorDescription}` : ""}`, input.isJSON);
2068
2133
  return ExitCode.ERROR;
2069
2134
  }
2070
2135
  if (callback.source === "listener" ? callback.state !== state : callback.state !== void 0 && callback.state !== state) {
2071
- writeFailure("the browser came back with a state this sign in did not send", input.isJSON);
2136
+ writeFailure("sign_in_failed", "the browser came back with a state this sign in did not send", input.isJSON);
2072
2137
  return ExitCode.ERROR;
2073
2138
  }
2074
2139
  if (callback.issuer !== void 0 && callback.issuer.replace(/\/+$/, "") !== metadata.issuer.replace(/\/+$/, "")) {
2075
- writeFailure(`the browser came back from ${callback.issuer}, which is not ${metadata.issuer}`, input.isJSON);
2140
+ writeFailure("sign_in_failed", `the browser came back from ${callback.issuer}, which is not ${metadata.issuer}`, input.isJSON);
2076
2141
  return ExitCode.ERROR;
2077
2142
  }
2078
2143
  if (!callback.code) {
2079
- writeFailure("the browser came back without an authorization code", input.isJSON);
2144
+ writeFailure("sign_in_failed", "the browser came back without an authorization code", input.isJSON);
2080
2145
  return ExitCode.ERROR;
2081
2146
  }
2082
2147
  return await toSignedIn(input, metadata, await toTokensFromCode(metadata.token_endpoint, clientId, callback.code, listener.redirectUri, pkce));
2083
2148
  } catch (error) {
2084
2149
  if (error instanceof DiscoveryFailure || error instanceof GrantFailure) {
2085
- writeFailure(error.message, input.isJSON);
2150
+ writeFailure("sign_in_failed", error.message, input.isJSON);
2086
2151
  return ExitCode.ERROR;
2087
2152
  }
2088
2153
  throw error;
@@ -2092,7 +2157,7 @@ async function runLogin(input) {
2092
2157
  async function runDeviceLogin(input, metadata, scope) {
2093
2158
  const settings = input.resolved.settings;
2094
2159
  if (!metadata.device_authorization_endpoint) {
2095
- writeFailure(`${metadata.issuer} does not offer the device grant, so sign in without --device`, input.isJSON);
2160
+ writeFailure("sign_in_failed", `${metadata.issuer} does not offer the device grant, so sign in without --device`, input.isJSON);
2096
2161
  return ExitCode.ERROR;
2097
2162
  }
2098
2163
  const device = await toDeviceAuthorization(metadata.device_authorization_endpoint, settings.clientId, scope);
@@ -2126,7 +2191,7 @@ async function toDeviceTokens(approved, typed) {
2126
2191
  async function toSignedIn(input, metadata, tokens) {
2127
2192
  const refreshToken = tokens.refreshToken;
2128
2193
  if (!refreshToken) {
2129
- writeFailure("the authorization server issued no refresh token, so this sign in cannot be kept", input.isJSON);
2194
+ writeFailure("sign_in_failed", "the authorization server issued no refresh token, so this sign in cannot be kept", input.isJSON);
2130
2195
  return ExitCode.ERROR;
2131
2196
  }
2132
2197
  const backend = await withLock(() => keep$1(input.resolved.settings.issuerUrl, refreshToken, tokens.refreshExpiresAt));
@@ -2227,7 +2292,7 @@ function toTelemetry(env = process.env, isCheckout = isRunFromCheckout()) {
2227
2292
  const base = {
2228
2293
  dsn,
2229
2294
  environment: isCheckout ? "local" : version$1.includes("-dev.") ? "preview" : "production",
2230
- release: `@hardfin/cli@${version$1}`
2295
+ release: toRelease(version$1)
2231
2296
  };
2232
2297
  if (env["DO_NOT_TRACK"] && !OFF.has(env["DO_NOT_TRACK"].toLowerCase())) return {
2233
2298
  ...base,
@@ -2275,6 +2340,13 @@ function isDsn(dsn) {
2275
2340
  return false;
2276
2341
  }
2277
2342
  }
2343
+ /**
2344
+ * toRelease names a version as Sentry knows it. Sentry refuses a slash in a release name, so
2345
+ * the scoped package name @hardfin/cli cannot be used, and hardfin-cli stands in for it.
2346
+ */
2347
+ function toRelease(of) {
2348
+ return `hardfin-cli@${of}`;
2349
+ }
2278
2350
  //#endregion
2279
2351
  //#region src/system/host.ts
2280
2352
  const BYTES_PER_GB = 1024 ** 3;
@@ -3052,28 +3124,28 @@ function defineOperation(operation) {
3052
3124
  async function runOperation(operation, input) {
3053
3125
  const path = toPath(operation, input.args);
3054
3126
  if (path === void 0) {
3055
- writeFailure(`this command takes ${operation.pathParameters.length} argument(s)`, input.isJSON);
3127
+ writeFailure("invalid_arguments", `this command takes ${operation.pathParameters.length} argument(s)`, input.isJSON);
3056
3128
  return ExitCode.USAGE;
3057
3129
  }
3058
3130
  const missing = operation.bodyFlags.concat(operation.upload?.fields ?? []).filter((flag) => flag.required && input.flags[toOptionKey(flag.name)] === void 0);
3059
3131
  if (missing.length > 0) {
3060
- writeFailure(`this command needs ${missing.map((flag) => `--${flag.name}`).join(", ")}`, input.isJSON);
3132
+ writeFailure("missing_flag", `this command needs ${missing.map((flag) => `--${flag.name}`).join(", ")}`, input.isJSON);
3061
3133
  return ExitCode.USAGE;
3062
3134
  }
3063
3135
  const body = toBody(operation, input.flags);
3064
3136
  if (body instanceof Error) {
3065
- writeFailure(body.message, input.isJSON);
3137
+ writeFailure("invalid_flag", body.message, input.isJSON);
3066
3138
  return ExitCode.USAGE;
3067
3139
  }
3068
3140
  if (operation.bodyFlags.length > 0 && body === void 0) {
3069
- writeFailure(`this command changes nothing unless a flag is given, such as ${operation.bodyFlags.slice(0, 3).map((flag) => `--${flag.name}`).join(", ")}. Run it with --help for the rest`, input.isJSON);
3141
+ writeFailure("empty_request", `this command changes nothing unless a flag is given, such as ${operation.bodyFlags.slice(0, 3).map((flag) => `--${flag.name}`).join(", ")}. Run it with --help for the rest`, input.isJSON);
3070
3142
  return ExitCode.USAGE;
3071
3143
  }
3072
3144
  let form;
3073
3145
  if (operation.upload) {
3074
3146
  const built = await toForm(operation.upload, input.flags);
3075
3147
  if (built instanceof Error) {
3076
- writeFailure(built.message, input.isJSON);
3148
+ writeFailure("invalid_input", built.message, input.isJSON);
3077
3149
  return ExitCode.USAGE;
3078
3150
  }
3079
3151
  form = built;
@@ -3094,11 +3166,11 @@ async function runOperation(operation, input) {
3094
3166
  return ExitCode.OK;
3095
3167
  } catch (error) {
3096
3168
  if (error instanceof NoCredential) {
3097
- writeFailure(error.message, input.isJSON);
3169
+ writeFailure("not_signed_in", error.message, input.isJSON);
3098
3170
  return ExitCode.NOT_AUTHENTICATED;
3099
3171
  }
3100
3172
  if (error instanceof RequestFailure) {
3101
- writeFailure(error.message, input.isJSON, error.errors, error.requestId, error.status);
3173
+ writeFailure(toStatusCode(error.status), error.message, input.isJSON, error.errors, error.requestId, error.status);
3102
3174
  return error.status === 401 ? ExitCode.NOT_AUTHENTICATED : ExitCode.ERROR;
3103
3175
  }
3104
3176
  throw error;
@@ -3113,7 +3185,7 @@ function toWritten(download, input) {
3113
3185
  const path = typeof asked === "string" ? asked : download.fileName ?? "-";
3114
3186
  if (path === "-") {
3115
3187
  if (process.stdout.isTTY) {
3116
- writeFailure("this answer is a file, so name where to write it with --output, or send it on with a pipe", input.isJSON);
3188
+ writeFailure("output_required", "this answer is a file, so name where to write it with --output, or send it on with a pipe", input.isJSON);
3117
3189
  return ExitCode.USAGE;
3118
3190
  }
3119
3191
  process.stdout.write(download.bytes);
@@ -5914,7 +5986,8 @@ function toRejectedFlag(command, flags) {
5914
5986
  /** toCli builds the parser from the registry, which is what every surface reads. */
5915
5987
  function toCli(entries = commands) {
5916
5988
  const program = new Command();
5917
- program.name("hardfin").description("Call the Hardfin API from a terminal or an agent").version(version$1, "-v, --version").showHelpAfterError().enablePositionalOptions();
5989
+ program.name("hardfin").description("Call the Hardfin API from a terminal or an agent").version(version$1, "-v, --version").enablePositionalOptions();
5990
+ toQuietParser(program);
5918
5991
  for (const flag of GLOBAL_FLAGS) program.option(`--${flag.name} <${flag.valueName}>`, flag.description);
5919
5992
  for (const command of entries) program.addCommand(toProgram(program, command), { hidden: command.hidden });
5920
5993
  return program;
@@ -5922,6 +5995,7 @@ function toCli(entries = commands) {
5922
5995
  /** toProgram wires one registry command into the parser. */
5923
5996
  function toProgram(root, command) {
5924
5997
  const program = new Command(command.name).summary(command.summary).description(command.description ?? command.summary);
5998
+ toQuietParser(program);
5925
5999
  for (const argument of command.arguments) {
5926
6000
  const name = argument.variadic ? `${argument.name}...` : argument.name;
5927
6001
  program.argument(argument.required ? `<${name}>` : `[${name}]`, argument.description);
@@ -5945,11 +6019,41 @@ function toProgram(root, command) {
5945
6019
  });
5946
6020
  return program;
5947
6021
  }
6022
+ /** toQuietParser has the parser hand back its mistakes, so they are answered like any other failure. */
6023
+ function toQuietParser(program) {
6024
+ program.exitOverride().configureOutput({ outputError: () => void 0 });
6025
+ }
6026
+ /** The parser's codes for a run that only showed help or the version, which is not a failure. */
6027
+ const SHOWN = /* @__PURE__ */ new Set([
6028
+ "commander.helpDisplayed",
6029
+ "commander.help",
6030
+ "commander.version"
6031
+ ]);
6032
+ /** toParseExitCode answers a mistake the parser caught before any command ran. */
6033
+ function toParseExitCode(error, args) {
6034
+ if (!(error instanceof CommanderError)) return;
6035
+ if (SHOWN.has(error.code)) return error.exitCode;
6036
+ const isJSON = args.includes("--json") || !process.stdout.isTTY;
6037
+ writeFailure(toParseCode(error.code), error.message.replace(/^error: /, ""), isJSON);
6038
+ if (!isJSON) process.stderr.write(" Run the command with --help to see what it takes\n");
6039
+ return ExitCode.USAGE;
6040
+ }
6041
+ function toParseCode(code) {
6042
+ switch (code) {
6043
+ case "commander.unknownCommand": return "unknown_command";
6044
+ case "commander.unknownOption":
6045
+ case "commander.optionMissingArgument":
6046
+ case "commander.missingMandatoryOptionValue":
6047
+ case "commander.conflictingOption":
6048
+ case "commander.invalidArgument": return "invalid_flag";
6049
+ default: return "invalid_arguments";
6050
+ }
6051
+ }
5948
6052
  async function toExitCode(root, command, args, flags) {
5949
6053
  const isJSON = isJSONOutput(flags);
5950
6054
  const rejected = toRejectedFlag(command, flags);
5951
6055
  if (rejected) {
5952
- writeFailure(rejected, isJSON);
6056
+ writeFailure("invalid_flag", rejected, isJSON);
5953
6057
  return ExitCode.USAGE;
5954
6058
  }
5955
6059
  let resolved;
@@ -5970,7 +6074,7 @@ async function toExitCode(root, command, args, flags) {
5970
6074
  process.stderr.write("\n");
5971
6075
  return ExitCode.INTERRUPTED;
5972
6076
  }
5973
- writeFailure(error instanceof Error ? error.message : String(error), isJSON);
6077
+ writeFailure(toErrorCode(error), error instanceof Error ? error.message : String(error), isJSON);
5974
6078
  await toReported(error, isJSON, resolved);
5975
6079
  return error instanceof ConfigFailure ? ExitCode.USAGE : ExitCode.ERROR;
5976
6080
  }
@@ -5995,7 +6099,7 @@ async function toReported(error, isJSON, resolved, level = "error") {
5995
6099
  if (id && !isJSON) process.stderr.write(`This looks like a problem in hardfin itself, and it has been reported to the Hardfin team. Reference: ${id}\n`);
5996
6100
  }
5997
6101
  //#endregion
5998
- export { toCli, toReported, wasReportAttempted };
6102
+ export { toCli, toParseExitCode, toReported, wasReportAttempted };
5999
6103
 
6000
- //# sourceMappingURL=program-C3SGdPbf.js.map
6001
- //# debugId=c531fbbe-2dd5-53cf-87a6-723b2926146f
6104
+ //# sourceMappingURL=program-BZW_50HP.js.map
6105
+ //# debugId=5c189832-33ce-5032-b0e0-fc3a33cf5b1d
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardfin/cli",
3
- "version": "0.1.0-dev.28",
3
+ "version": "0.1.0-dev.30",
4
4
  "description": "Command line interface for the Hardfin API",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hardfin, Inc.",