@hardfin/cli 0.1.0-dev.29 → 0.1.0-dev.31

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
@@ -142,8 +159,18 @@ hardfin status # what the CLI is configured with and signed in as
142
159
  hardfin logout # forget this machine, and ask Hardfin to revoke it
143
160
  ```
144
161
 
145
- An API key is still the way to run unattended. `HARDFIN_API_KEY` wins over a stored sign in,
146
- so CI and an agent sandbox need no browser.
162
+ An API key is still the way to run unattended. `--api-key`, or `HARDFIN_API_KEY`, wins over
163
+ a stored sign in, so CI and an agent sandbox need no browser. The flag beats the variable.
164
+
165
+ An API key is held to two rules before it is sent:
166
+
167
+ | Rule | Why |
168
+ | --------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
169
+ | It goes only over https, or to this machine | Anything between here and the host could read it |
170
+ | A key you set, by flag or exported variable, never goes to an API URL that `config.local.json` or `.env` in the working directory chose | A repository you cloned could otherwise send your key to its own server |
171
+
172
+ A key and an API URL that come from the same folder's files still work together, which is
173
+ how a local build is set up. Pass `--api-url` to send a key you set to that folder's address.
147
174
 
148
175
  ### What login does
149
176
 
@@ -356,7 +383,7 @@ settings, and the one nearest the top wins.
356
383
 
357
384
  | Layer | Where | Beats |
358
385
  | ----------- | --------------------------------------------------------------------- | ---------------- |
359
- | Flag | `--api-url` | everything below |
386
+ | Flag | `--api-url`, `--api-key`, `--issuer-url` | everything below |
360
387
  | Environment | an exported `HARDFIN_*` variable | the files below |
361
388
  | Env file | `.env` in the working directory, or the file `HARDFIN_ENV_FILE` names | the config file |
362
389
  | Config file | `config.local.json` in the working directory | the defaults |
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]="39e80611-82c5-527b-ac56-e34310ee122e")}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]="bb3026cb-ab06-5af0-9743-290b61ec91a3")}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-CnXYN9xS.js");
18
- await toCli().parseAsync(process.argv);
17
+ const { toCli, toParseExitCode, wasReportAttempted } = await import("./program-B0vISolG.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-CnXYN9xS.js");
36
+ const { toReported } = await import("./program-B0vISolG.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=39e80611-82c5-527b-ac56-e34310ee122e
54
+ //# debugId=bb3026cb-ab06-5af0-9743-290b61ec91a3
@@ -5,6 +5,11 @@
5
5
  "description": "The API to call, whose host also holds the authorization server",
6
6
  "valueName": "url"
7
7
  },
8
+ {
9
+ "name": "api-key",
10
+ "description": "An API key to call with, in place of HARDFIN_API_KEY or a sign in",
11
+ "valueName": "key"
12
+ },
8
13
  {
9
14
  "name": "issuer-url",
10
15
  "description": "The authorization server, when it does not sit at the API's host",
@@ -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]="8979dc06-d236-502b-bea1-b13868301f52")}catch(e){}}();
4
- import { a as defineCommand, i as GLOBAL_FLAGS, r as ExitCode, t as toInvocation } from "./scrub-VtQmxap1.js";
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]="ab584cf3-443f-569e-8167-fa0cbab9ab68")}catch(e){}}();
4
+ import { a as defineCommand, i as GLOBAL_FLAGS, r as ExitCode, t as toInvocation } from "./scrub-BN3gyBpR.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(),
@@ -795,12 +694,15 @@ const held = /* @__PURE__ */ new Map();
795
694
  * unattended caller sets it deliberately, and otherwise the stored refresh token buys an
796
695
  * access token that lives only in memory.
797
696
  */
798
- async function toRequestCredential(settings) {
799
- if (settings.apiKey) return {
800
- header: "X-API-Key",
801
- value: settings.apiKey,
802
- kind: "api key"
803
- };
697
+ async function toRequestCredential(settings, sources) {
698
+ if (settings.apiKey) {
699
+ refuseForeignKey(settings, sources);
700
+ return {
701
+ header: "X-API-Key",
702
+ value: settings.apiKey,
703
+ kind: "api key"
704
+ };
705
+ }
804
706
  refuseForeignHost(settings);
805
707
  const stored = toCredential(settings.issuerUrl);
806
708
  if (!stored) throw new NoCredential("not authenticated. Run hardfin login, or set HARDFIN_API_KEY");
@@ -876,6 +778,20 @@ function refuseForeignHost(settings) {
876
778
  if (api.host !== issuer.host) throw new NoCredential(`this sign in belongs to ${issuer.host}, so it is not sent to ${api.host}. Set HARDFIN_API_KEY to call another host, or point --issuer-url at the server that signs you in`);
877
779
  if (api.protocol !== "https:" && !isLoopback(api.hostname)) throw new NoCredential(`${settings.apiUrl} is not https, so a sign in is not sent to it`);
878
780
  }
781
+ /** The layers a file in the working directory sets, which a cloned repository can carry. */
782
+ const DIRECTORY_SOURCES = /* @__PURE__ */ new Set(["config file", "env file"]);
783
+ /** refuseForeignKey keeps an API key off plain connections and off hosts a stray file chose. */
784
+ function refuseForeignKey(settings, sources) {
785
+ const api = toHost$1(settings.apiUrl);
786
+ if (api === void 0) throw new NoCredential(`${settings.apiUrl} is not a URL this CLI can call`);
787
+ if (api.protocol !== "https:" && !isLoopback(api.hostname)) throw new NoCredential(`Your API key was not sent, because ${api.host} does not use a secure connection.`);
788
+ const isKeyChosen = sources?.apiKey !== void 0 && !DIRECTORY_SOURCES.has(sources.apiKey);
789
+ const isUrlFromFolder = sources?.apiUrl !== void 0 && DIRECTORY_SOURCES.has(sources.apiUrl);
790
+ if (isKeyChosen && isUrlFromFolder) {
791
+ const file = sources.apiUrl === "config file" ? CONFIG_FILE : ".env";
792
+ throw new NoCredential(`Your API key was not sent to ${api.host}, because that address comes from a file in this folder, not from you.\nDetails: the API URL is set in ${file}. Pass --api-url to use it anyway`);
793
+ }
794
+ }
879
795
  function toHost$1(url) {
880
796
  try {
881
797
  return new URL(url);
@@ -891,12 +807,15 @@ function isLoopback(hostname) {
891
807
  * an access token this process already holds. An error report uses it, because a refresh
892
808
  * cut short by the process ending would spend the refresh token and sign the person out.
893
809
  */
894
- function toHeldCredential(settings) {
895
- if (settings.apiKey) return {
896
- header: "X-API-Key",
897
- value: settings.apiKey,
898
- kind: "api key"
899
- };
810
+ function toHeldCredential(settings, sources) {
811
+ if (settings.apiKey) {
812
+ refuseForeignKey(settings, sources);
813
+ return {
814
+ header: "X-API-Key",
815
+ value: settings.apiKey,
816
+ kind: "api key"
817
+ };
818
+ }
900
819
  const current = held.get(settings.issuerUrl);
901
820
  if (!current || (current.expiresAt ?? 0) - EXPIRY_MARGIN_MS <= Date.now()) return;
902
821
  return {
@@ -980,6 +899,172 @@ function toStatusMessage(status) {
980
899
  if (status === 403) return "the organization is deactivated, or its access has ended";
981
900
  return `the request failed with status ${status}`;
982
901
  }
902
+ //#endregion
903
+ //#region src/output/failure.ts
904
+ /** FAILURE_CODES is every code a failure can carry, and none is ever renamed or given a new meaning. */
905
+ const FAILURE_CODES = {
906
+ unknown_command: "No command has that name. Run hardfin --help for the list",
907
+ invalid_arguments: "The command was given the wrong positional arguments",
908
+ missing_flag: "A flag the command needs was not given",
909
+ invalid_flag: "A flag was given a value it does not take",
910
+ invalid_input: "A file the command was given cannot be read, or does not hold JSON",
911
+ empty_request: "The command would change nothing, because no field was given",
912
+ output_required: "The answer is a file, and needs --output or a pipe",
913
+ not_confirmed: "The change was not confirmed. Pass --yes to confirm it without a terminal",
914
+ shell_unavailable: "The shell did not say where its startup file is",
915
+ invalid_config: "A config file or setting cannot be read",
916
+ not_signed_in: "No usable credential. Run hardfin login, or set HARDFIN_API_KEY",
917
+ sign_in_failed: "Signing in did not finish",
918
+ unreachable: "The CLI could not connect to the server",
919
+ timed_out: "Something the CLI waited on took too long",
920
+ busy: "Another hardfin command is holding the credential lock",
921
+ bad_request: "The API refused the request as written (HTTP 400)",
922
+ unauthorized: "The API did not accept the credential (HTTP 401)",
923
+ forbidden: "The credential may not do this (HTTP 403)",
924
+ not_found: "The record or path does not exist (HTTP 404)",
925
+ conflict: "The request conflicts with the record's current state (HTTP 409)",
926
+ too_large: "The request is larger than the API accepts (HTTP 413)",
927
+ invalid_request: "The API could not process the request (HTTP 422)",
928
+ rate_limited: "Too many requests. Wait, then try again (HTTP 429)",
929
+ server_error: "The API failed to answer (HTTP 5xx)",
930
+ api_error: "The API refused the request with another status",
931
+ internal_error: "A problem in hardfin itself, which has been reported"
932
+ };
933
+ /** toStatusCode names a refusal by the HTTP status the API answered with. */
934
+ function toStatusCode(status) {
935
+ switch (status) {
936
+ case 400: return "bad_request";
937
+ case 401: return "unauthorized";
938
+ case 403: return "forbidden";
939
+ case 404: return "not_found";
940
+ case 409: return "conflict";
941
+ case 413: return "too_large";
942
+ case 422: return "invalid_request";
943
+ case 429: return "rate_limited";
944
+ default: return status >= 500 ? "server_error" : "api_error";
945
+ }
946
+ }
947
+ /** toErrorCode names a failure nothing handled closer to where it happened. */
948
+ function toErrorCode(error) {
949
+ if (error instanceof RequestFailure) return toStatusCode(error.status);
950
+ if (error instanceof NoCredential) return "not_signed_in";
951
+ if (error instanceof ConfigFailure) return "invalid_config";
952
+ if (error instanceof Unreachable) return "unreachable";
953
+ if (error instanceof TimedOut) return "timed_out";
954
+ if (error instanceof LockHeld) return "busy";
955
+ return "internal_error";
956
+ }
957
+ /** version is what this build reports, read from the published package manifest. */
958
+ const version$1 = createRequire(import.meta.url)("../package.json").version;
959
+ //#endregion
960
+ //#region src/command/agent-guide.ts
961
+ const agentGuideCommand = defineCommand({
962
+ name: "agent-guide",
963
+ summary: "Print instructions an agent can follow to use this CLI",
964
+ 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.",
965
+ arguments: [],
966
+ flags: [{
967
+ name: "json",
968
+ description: "Print the command registry as JSON instead of Markdown",
969
+ schema: z.boolean().optional()
970
+ }],
971
+ examples: [{
972
+ description: "Write a skill file",
973
+ command: "hardfin agent-guide > SKILL.md"
974
+ }, {
975
+ description: "Inspect the registry",
976
+ command: "hardfin agent-guide --json"
977
+ }],
978
+ run: runAgentGuide
979
+ });
980
+ /** toGuide renders the registry as the Markdown an agent reads. */
981
+ function toGuide(commands, version) {
982
+ const lines = [
983
+ "# Hardfin CLI",
984
+ "",
985
+ `The \`hardfin\` command calls the Hardfin API. This guide describes version ${version} of the CLI, which speaks API version ${API_VERSION}.`,
986
+ "",
987
+ "## Before calling",
988
+ "",
989
+ "- Authenticate with `--api-key`, by setting `HARDFIN_API_KEY`, or by running `hardfin login`",
990
+ "- Every command prints JSON on stdout and diagnostics on stderr",
991
+ "- Output is JSON whenever stdout is not a terminal, so no flag is needed when calling from code",
992
+ "",
993
+ "## Exit codes",
994
+ "",
995
+ "| Code | Means |",
996
+ "| --- | --- |",
997
+ "| 0 | The command succeeded |",
998
+ "| 1 | The command failed |",
999
+ "| 2 | The command was called wrongly |",
1000
+ "| 4 | No usable credential |",
1001
+ "| 130 | The person pressed Ctrl-C |",
1002
+ "",
1003
+ "## Error codes",
1004
+ "",
1005
+ "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.",
1006
+ "",
1007
+ "| Code | Means |",
1008
+ "| --- | --- |",
1009
+ ...Object.entries(FAILURE_CODES).map(([code, meaning]) => `| \`${code}\` | ${meaning} |`),
1010
+ "",
1011
+ "## Commands",
1012
+ ""
1013
+ ];
1014
+ for (const command of commands.filter((command) => !command.hidden)) lines.push(...toCommandLines(command, []));
1015
+ return lines.join("\n");
1016
+ }
1017
+ /** toCommandLines describes one command and everything nested under it. */
1018
+ function toCommandLines(command, parents) {
1019
+ const path = [...parents, command.name];
1020
+ const lines = [
1021
+ `### \`hardfin ${path.join(" ")}\``,
1022
+ "",
1023
+ command.description ?? command.summary,
1024
+ ""
1025
+ ];
1026
+ if (command.arguments.length > 0) {
1027
+ lines.push("| Argument | Required | Holds |", "| --- | --- | --- |");
1028
+ for (const argument of command.arguments) lines.push(`| \`${argument.name}\` | ${argument.required ? "yes" : "no"} | ${argument.description} |`);
1029
+ lines.push("");
1030
+ }
1031
+ if (command.flags.length > 0) {
1032
+ lines.push("| Flag | Takes | Does |", "| --- | --- | --- |");
1033
+ for (const flag of command.flags) {
1034
+ const name = flag.short ? `-${flag.short}, --${flag.name}` : `--${flag.name}`;
1035
+ lines.push(`| \`${name}\` | ${flag.valueName ?? "nothing"} | ${flag.description} |`);
1036
+ }
1037
+ lines.push("");
1038
+ }
1039
+ for (const example of command.examples) lines.push(`${example.description}:`, "", "```sh", example.command, "```", "");
1040
+ for (const subcommand of command.subcommands ?? []) lines.push(...toCommandLines(subcommand, path));
1041
+ return lines;
1042
+ }
1043
+ async function runAgentGuide(input) {
1044
+ if (input.flags["json"] === true) {
1045
+ writeData(input.commands.map(toSummary));
1046
+ return ExitCode.OK;
1047
+ }
1048
+ writeData(toGuide(input.commands, version$1));
1049
+ return ExitCode.OK;
1050
+ }
1051
+ function toSummary(command) {
1052
+ return {
1053
+ name: command.name,
1054
+ summary: command.summary,
1055
+ description: command.description ?? command.summary,
1056
+ arguments: command.arguments,
1057
+ flags: command.flags.map((flag) => ({
1058
+ name: flag.name,
1059
+ short: flag.short ?? null,
1060
+ description: flag.description,
1061
+ valueName: flag.valueName ?? null,
1062
+ repeatable: flag.repeatable ?? false
1063
+ })),
1064
+ examples: command.examples,
1065
+ subcommands: command.subcommands?.filter((entry) => !entry.hidden).map(toSummary)
1066
+ };
1067
+ }
983
1068
  const apiCommand = defineCommand({
984
1069
  name: "api",
985
1070
  summary: "Call a Hardfin API endpoint and print what it answers",
@@ -1043,30 +1128,30 @@ const apiCommand = defineCommand({
1043
1128
  async function runApi(input) {
1044
1129
  const path = input.args[0];
1045
1130
  if (!path) {
1046
- writeFailure("a path is required, such as /customer", input.isJSON);
1131
+ writeFailure("invalid_arguments", "a path is required, such as /customer", input.isJSON);
1047
1132
  return ExitCode.USAGE;
1048
1133
  }
1049
1134
  if (path.split("/").includes("..")) {
1050
- writeFailure("a path stays inside the API, so it cannot contain ..", input.isJSON);
1135
+ writeFailure("invalid_arguments", "a path stays inside the API, so it cannot contain ..", input.isJSON);
1051
1136
  return ExitCode.USAGE;
1052
1137
  }
1053
1138
  const query = toQuery$1(input.flags["field"]);
1054
1139
  if (query === void 0) {
1055
- writeFailure("each --field is key=value, such as -f limit=50", input.isJSON);
1140
+ writeFailure("invalid_flag", "each --field is key=value, such as -f limit=50", input.isJSON);
1056
1141
  return ExitCode.USAGE;
1057
1142
  }
1058
1143
  let body;
1059
1144
  if (typeof input.flags["input"] === "string") {
1060
1145
  body = toBody$1(input.flags["input"]);
1061
1146
  if (body === void 0) {
1062
- writeFailure(`${input.flags["input"]} does not hold JSON`, input.isJSON);
1147
+ writeFailure("invalid_input", `${input.flags["input"]} does not hold JSON`, input.isJSON);
1063
1148
  return ExitCode.USAGE;
1064
1149
  }
1065
1150
  }
1066
1151
  try {
1067
1152
  writeData((await request$1({
1068
1153
  apiUrl: input.resolved.settings.apiUrl,
1069
- credential: await toRequestCredential(input.resolved.settings),
1154
+ credential: await toRequestCredential(input.resolved.settings, input.resolved.sources),
1070
1155
  method: toText(input.flags["method"] ?? "GET").toUpperCase(),
1071
1156
  path,
1072
1157
  query,
@@ -1075,11 +1160,11 @@ async function runApi(input) {
1075
1160
  return ExitCode.OK;
1076
1161
  } catch (error) {
1077
1162
  if (error instanceof NoCredential) {
1078
- writeFailure(error.message, input.isJSON);
1163
+ writeFailure("not_signed_in", error.message, input.isJSON);
1079
1164
  return ExitCode.NOT_AUTHENTICATED;
1080
1165
  }
1081
1166
  if (error instanceof RequestFailure) {
1082
- writeFailure(error.message, input.isJSON, error.errors, error.requestId, error.status);
1167
+ writeFailure(toStatusCode(error.status), error.message, input.isJSON, error.errors, error.requestId, error.status);
1083
1168
  return error.status === 401 ? ExitCode.NOT_AUTHENTICATED : ExitCode.ERROR;
1084
1169
  }
1085
1170
  throw error;
@@ -1485,17 +1570,17 @@ async function runChange(input, change) {
1485
1570
  const place = toPlace();
1486
1571
  const shell = input.args[0] ?? toShell(place);
1487
1572
  if (!shell || !SHELLS.includes(shell)) {
1488
- writeFailure(`name the shell to ${change} for: ${SHELLS.join(", ")}`, input.isJSON);
1573
+ writeFailure("invalid_arguments", `name the shell to ${change} for: ${SHELLS.join(", ")}`, input.isJSON);
1489
1574
  return ExitCode.USAGE;
1490
1575
  }
1491
1576
  const plan = toPlan(shell, place);
1492
1577
  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);
1578
+ writeFailure("shell_unavailable", "PowerShell did not report its profile, so there is nowhere to load the script from", input.isJSON);
1494
1579
  return ExitCode.ERROR;
1495
1580
  }
1496
1581
  process.stderr.write(toDescription(plan, change));
1497
1582
  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);
1583
+ writeFailure("not_confirmed", process.stdin.isTTY ? "nothing was changed" : "nothing was changed. Pass --yes to change it without a terminal", input.isJSON);
1499
1584
  return ExitCode.USAGE;
1500
1585
  }
1501
1586
  if (change === "install") {
@@ -1658,7 +1743,7 @@ function toSchemaValues(schema) {
1658
1743
  async function runCompletion(input) {
1659
1744
  const shell = input.args[0] ?? "";
1660
1745
  if (!SHELLS.includes(shell)) {
1661
- writeFailure(`${shell || "no shell"} is not one this CLI writes for. Choose ${SHELLS.join(", ")}`, input.isJSON);
1746
+ writeFailure("invalid_arguments", `${shell || "no shell"} is not one this CLI writes for. Choose ${SHELLS.join(", ")}`, input.isJSON);
1662
1747
  return ExitCode.USAGE;
1663
1748
  }
1664
1749
  writeData(toScript(shell));
@@ -2064,25 +2149,25 @@ async function runLogin(input) {
2064
2149
  }
2065
2150
  process.stderr.write(callback.error ? "\n" : "\nApproved, finishing the sign in\n");
2066
2151
  if (callback.error) {
2067
- writeFailure(`sign in was refused: ${callback.error}${callback.errorDescription ? `, ${callback.errorDescription}` : ""}`, input.isJSON);
2152
+ writeFailure("sign_in_failed", `sign in was refused: ${callback.error}${callback.errorDescription ? `, ${callback.errorDescription}` : ""}`, input.isJSON);
2068
2153
  return ExitCode.ERROR;
2069
2154
  }
2070
2155
  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);
2156
+ writeFailure("sign_in_failed", "the browser came back with a state this sign in did not send", input.isJSON);
2072
2157
  return ExitCode.ERROR;
2073
2158
  }
2074
2159
  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);
2160
+ writeFailure("sign_in_failed", `the browser came back from ${callback.issuer}, which is not ${metadata.issuer}`, input.isJSON);
2076
2161
  return ExitCode.ERROR;
2077
2162
  }
2078
2163
  if (!callback.code) {
2079
- writeFailure("the browser came back without an authorization code", input.isJSON);
2164
+ writeFailure("sign_in_failed", "the browser came back without an authorization code", input.isJSON);
2080
2165
  return ExitCode.ERROR;
2081
2166
  }
2082
2167
  return await toSignedIn(input, metadata, await toTokensFromCode(metadata.token_endpoint, clientId, callback.code, listener.redirectUri, pkce));
2083
2168
  } catch (error) {
2084
2169
  if (error instanceof DiscoveryFailure || error instanceof GrantFailure) {
2085
- writeFailure(error.message, input.isJSON);
2170
+ writeFailure("sign_in_failed", error.message, input.isJSON);
2086
2171
  return ExitCode.ERROR;
2087
2172
  }
2088
2173
  throw error;
@@ -2092,7 +2177,7 @@ async function runLogin(input) {
2092
2177
  async function runDeviceLogin(input, metadata, scope) {
2093
2178
  const settings = input.resolved.settings;
2094
2179
  if (!metadata.device_authorization_endpoint) {
2095
- writeFailure(`${metadata.issuer} does not offer the device grant, so sign in without --device`, input.isJSON);
2180
+ writeFailure("sign_in_failed", `${metadata.issuer} does not offer the device grant, so sign in without --device`, input.isJSON);
2096
2181
  return ExitCode.ERROR;
2097
2182
  }
2098
2183
  const device = await toDeviceAuthorization(metadata.device_authorization_endpoint, settings.clientId, scope);
@@ -2126,7 +2211,7 @@ async function toDeviceTokens(approved, typed) {
2126
2211
  async function toSignedIn(input, metadata, tokens) {
2127
2212
  const refreshToken = tokens.refreshToken;
2128
2213
  if (!refreshToken) {
2129
- writeFailure("the authorization server issued no refresh token, so this sign in cannot be kept", input.isJSON);
2214
+ writeFailure("sign_in_failed", "the authorization server issued no refresh token, so this sign in cannot be kept", input.isJSON);
2130
2215
  return ExitCode.ERROR;
2131
2216
  }
2132
2217
  const backend = await withLock(() => keep$1(input.resolved.settings.issuerUrl, refreshToken, tokens.refreshExpiresAt));
@@ -2478,7 +2563,7 @@ function toServerReport(metadata) {
2478
2563
  async function toApiReport(resolved, options) {
2479
2564
  const settings = resolved.settings;
2480
2565
  try {
2481
- const credential = options.isHeldOnly ? toHeldCredential(settings) : await toRequestCredential(settings);
2566
+ const credential = options.isHeldOnly ? toHeldCredential(settings, resolved.sources) : await toRequestCredential(settings, resolved.sources);
2482
2567
  if (!credential) return {
2483
2568
  checked: false,
2484
2569
  url: settings.apiUrl,
@@ -2610,7 +2695,7 @@ async function report(error, occasion, resolved) {
2610
2695
  const settings = resolved ?? toResolved();
2611
2696
  const status = settings ? await toStatus(settings) : void 0;
2612
2697
  const secrets = [settings?.settings.apiKey, settings ? toCredential(settings.settings.issuerUrl)?.refreshToken : void 0];
2613
- return await (await import("./sentry-D5lKx0nm.js")).send({
2698
+ return await (await import("./sentry-CLaeAWNa.js")).send({
2614
2699
  error,
2615
2700
  occasion,
2616
2701
  telemetry,
@@ -2802,7 +2887,7 @@ const REFUSED_COMMANDS = /* @__PURE__ */ new Set([
2802
2887
  "mcp"
2803
2888
  ]);
2804
2889
  /** The global options, which sit before a command and take a value of their own. */
2805
- const GLOBAL_OPTIONS_WITH_VALUES = /* @__PURE__ */ new Set(["--api-url", "--issuer-url"]);
2890
+ const GLOBAL_OPTIONS_WITH_VALUES = new Set(GLOBAL_FLAGS.filter((flag) => flag.valueName).map((flag) => `--${flag.name}`));
2806
2891
  /**
2807
2892
  * toCommandName reads which command a call names. A global option sits before it, so
2808
2893
  * reading the first argument alone would let --issuer-url x logout through.
@@ -3059,28 +3144,28 @@ function defineOperation(operation) {
3059
3144
  async function runOperation(operation, input) {
3060
3145
  const path = toPath(operation, input.args);
3061
3146
  if (path === void 0) {
3062
- writeFailure(`this command takes ${operation.pathParameters.length} argument(s)`, input.isJSON);
3147
+ writeFailure("invalid_arguments", `this command takes ${operation.pathParameters.length} argument(s)`, input.isJSON);
3063
3148
  return ExitCode.USAGE;
3064
3149
  }
3065
3150
  const missing = operation.bodyFlags.concat(operation.upload?.fields ?? []).filter((flag) => flag.required && input.flags[toOptionKey(flag.name)] === void 0);
3066
3151
  if (missing.length > 0) {
3067
- writeFailure(`this command needs ${missing.map((flag) => `--${flag.name}`).join(", ")}`, input.isJSON);
3152
+ writeFailure("missing_flag", `this command needs ${missing.map((flag) => `--${flag.name}`).join(", ")}`, input.isJSON);
3068
3153
  return ExitCode.USAGE;
3069
3154
  }
3070
3155
  const body = toBody(operation, input.flags);
3071
3156
  if (body instanceof Error) {
3072
- writeFailure(body.message, input.isJSON);
3157
+ writeFailure("invalid_flag", body.message, input.isJSON);
3073
3158
  return ExitCode.USAGE;
3074
3159
  }
3075
3160
  if (operation.bodyFlags.length > 0 && body === void 0) {
3076
- 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);
3161
+ 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);
3077
3162
  return ExitCode.USAGE;
3078
3163
  }
3079
3164
  let form;
3080
3165
  if (operation.upload) {
3081
3166
  const built = await toForm(operation.upload, input.flags);
3082
3167
  if (built instanceof Error) {
3083
- writeFailure(built.message, input.isJSON);
3168
+ writeFailure("invalid_input", built.message, input.isJSON);
3084
3169
  return ExitCode.USAGE;
3085
3170
  }
3086
3171
  form = built;
@@ -3088,7 +3173,7 @@ async function runOperation(operation, input) {
3088
3173
  try {
3089
3174
  const envelope = await request$1({
3090
3175
  apiUrl: input.resolved.settings.apiUrl,
3091
- credential: await toRequestCredential(input.resolved.settings),
3176
+ credential: await toRequestCredential(input.resolved.settings, input.resolved.sources),
3092
3177
  method: operation.method,
3093
3178
  path,
3094
3179
  query: toQuery(operation, input.flags),
@@ -3101,11 +3186,11 @@ async function runOperation(operation, input) {
3101
3186
  return ExitCode.OK;
3102
3187
  } catch (error) {
3103
3188
  if (error instanceof NoCredential) {
3104
- writeFailure(error.message, input.isJSON);
3189
+ writeFailure("not_signed_in", error.message, input.isJSON);
3105
3190
  return ExitCode.NOT_AUTHENTICATED;
3106
3191
  }
3107
3192
  if (error instanceof RequestFailure) {
3108
- writeFailure(error.message, input.isJSON, error.errors, error.requestId, error.status);
3193
+ writeFailure(toStatusCode(error.status), error.message, input.isJSON, error.errors, error.requestId, error.status);
3109
3194
  return error.status === 401 ? ExitCode.NOT_AUTHENTICATED : ExitCode.ERROR;
3110
3195
  }
3111
3196
  throw error;
@@ -3120,7 +3205,7 @@ function toWritten(download, input) {
3120
3205
  const path = typeof asked === "string" ? asked : download.fileName ?? "-";
3121
3206
  if (path === "-") {
3122
3207
  if (process.stdout.isTTY) {
3123
- writeFailure("this answer is a file, so name where to write it with --output, or send it on with a pipe", input.isJSON);
3208
+ 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);
3124
3209
  return ExitCode.USAGE;
3125
3210
  }
3126
3211
  process.stdout.write(download.bytes);
@@ -5921,7 +6006,8 @@ function toRejectedFlag(command, flags) {
5921
6006
  /** toCli builds the parser from the registry, which is what every surface reads. */
5922
6007
  function toCli(entries = commands) {
5923
6008
  const program = new Command();
5924
- program.name("hardfin").description("Call the Hardfin API from a terminal or an agent").version(version$1, "-v, --version").showHelpAfterError().enablePositionalOptions();
6009
+ program.name("hardfin").description("Call the Hardfin API from a terminal or an agent").version(version$1, "-v, --version").enablePositionalOptions();
6010
+ toQuietParser(program);
5925
6011
  for (const flag of GLOBAL_FLAGS) program.option(`--${flag.name} <${flag.valueName}>`, flag.description);
5926
6012
  for (const command of entries) program.addCommand(toProgram(program, command), { hidden: command.hidden });
5927
6013
  return program;
@@ -5929,6 +6015,7 @@ function toCli(entries = commands) {
5929
6015
  /** toProgram wires one registry command into the parser. */
5930
6016
  function toProgram(root, command) {
5931
6017
  const program = new Command(command.name).summary(command.summary).description(command.description ?? command.summary);
6018
+ toQuietParser(program);
5932
6019
  for (const argument of command.arguments) {
5933
6020
  const name = argument.variadic ? `${argument.name}...` : argument.name;
5934
6021
  program.argument(argument.required ? `<${name}>` : `[${name}]`, argument.description);
@@ -5952,17 +6039,48 @@ function toProgram(root, command) {
5952
6039
  });
5953
6040
  return program;
5954
6041
  }
6042
+ /** toQuietParser has the parser hand back its mistakes, so they are answered like any other failure. */
6043
+ function toQuietParser(program) {
6044
+ program.exitOverride().configureOutput({ outputError: () => void 0 });
6045
+ }
6046
+ /** The parser's codes for a run that only showed help or the version, which is not a failure. */
6047
+ const SHOWN = /* @__PURE__ */ new Set([
6048
+ "commander.helpDisplayed",
6049
+ "commander.help",
6050
+ "commander.version"
6051
+ ]);
6052
+ /** toParseExitCode answers a mistake the parser caught before any command ran. */
6053
+ function toParseExitCode(error, args) {
6054
+ if (!(error instanceof CommanderError)) return;
6055
+ if (SHOWN.has(error.code)) return error.exitCode;
6056
+ const isJSON = args.includes("--json") || !process.stdout.isTTY;
6057
+ writeFailure(toParseCode(error.code), error.message.replace(/^error: /, ""), isJSON);
6058
+ if (!isJSON) process.stderr.write(" Run the command with --help to see what it takes\n");
6059
+ return ExitCode.USAGE;
6060
+ }
6061
+ function toParseCode(code) {
6062
+ switch (code) {
6063
+ case "commander.unknownCommand": return "unknown_command";
6064
+ case "commander.unknownOption":
6065
+ case "commander.optionMissingArgument":
6066
+ case "commander.missingMandatoryOptionValue":
6067
+ case "commander.conflictingOption":
6068
+ case "commander.invalidArgument": return "invalid_flag";
6069
+ default: return "invalid_arguments";
6070
+ }
6071
+ }
5955
6072
  async function toExitCode(root, command, args, flags) {
5956
6073
  const isJSON = isJSONOutput(flags);
5957
6074
  const rejected = toRejectedFlag(command, flags);
5958
6075
  if (rejected) {
5959
- writeFailure(rejected, isJSON);
6076
+ writeFailure("invalid_flag", rejected, isJSON);
5960
6077
  return ExitCode.USAGE;
5961
6078
  }
5962
6079
  let resolved;
5963
6080
  try {
5964
6081
  resolved = toSettings({
5965
6082
  apiUrl: root.opts()["apiUrl"],
6083
+ apiKey: root.opts()["apiKey"],
5966
6084
  issuerUrl: root.opts()["issuerUrl"]
5967
6085
  });
5968
6086
  return await command.run?.({
@@ -5977,7 +6095,7 @@ async function toExitCode(root, command, args, flags) {
5977
6095
  process.stderr.write("\n");
5978
6096
  return ExitCode.INTERRUPTED;
5979
6097
  }
5980
- writeFailure(error instanceof Error ? error.message : String(error), isJSON);
6098
+ writeFailure(toErrorCode(error), error instanceof Error ? error.message : String(error), isJSON);
5981
6099
  await toReported(error, isJSON, resolved);
5982
6100
  return error instanceof ConfigFailure ? ExitCode.USAGE : ExitCode.ERROR;
5983
6101
  }
@@ -6002,7 +6120,7 @@ async function toReported(error, isJSON, resolved, level = "error") {
6002
6120
  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`);
6003
6121
  }
6004
6122
  //#endregion
6005
- export { toCli, toReported, wasReportAttempted };
6123
+ export { toCli, toParseExitCode, toReported, wasReportAttempted };
6006
6124
 
6007
- //# sourceMappingURL=program-CnXYN9xS.js.map
6008
- //# debugId=8979dc06-d236-502b-bea1-b13868301f52
6125
+ //# sourceMappingURL=program-B0vISolG.js.map
6126
+ //# debugId=ab584cf3-443f-569e-8167-fa0cbab9ab68
@@ -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]="9fab3688-8d6c-599c-94b2-64daa75cd233")}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]="3af5060a-b6fe-5f2e-9661-01f4460a7b04")}catch(e){}}();
4
4
  import { z } from "zod";
5
5
  import { homedir } from "node:os";
6
6
  //#region src/command/registry.ts
@@ -17,17 +17,26 @@ function defineCommand(command) {
17
17
  return command;
18
18
  }
19
19
  /** GLOBAL_FLAGS are the options the CLI takes before any command. */
20
- const GLOBAL_FLAGS = [{
21
- name: "api-url",
22
- description: "The API to call, whose host also holds the authorization server",
23
- valueName: "url",
24
- schema: z.string()
25
- }, {
26
- name: "issuer-url",
27
- description: "The authorization server, when it does not sit at the API's host",
28
- valueName: "url",
29
- schema: z.string()
30
- }];
20
+ const GLOBAL_FLAGS = [
21
+ {
22
+ name: "api-url",
23
+ description: "The API to call, whose host also holds the authorization server",
24
+ valueName: "url",
25
+ schema: z.string()
26
+ },
27
+ {
28
+ name: "api-key",
29
+ description: "An API key to call with, in place of HARDFIN_API_KEY or a sign in",
30
+ valueName: "key",
31
+ schema: z.string()
32
+ },
33
+ {
34
+ name: "issuer-url",
35
+ description: "The authorization server, when it does not sit at the API's host",
36
+ valueName: "url",
37
+ schema: z.string()
38
+ }
39
+ ];
31
40
  //#endregion
32
41
  //#region src/report/scrub.ts
33
42
  const REDACTED = "[redacted]";
@@ -109,5 +118,5 @@ function toFlag(flags, word) {
109
118
  //#endregion
110
119
  export { defineCommand as a, GLOBAL_FLAGS as i, toScrubbed as n, ExitCode as r, toInvocation as t };
111
120
 
112
- //# sourceMappingURL=scrub-VtQmxap1.js.map
113
- //# debugId=9fab3688-8d6c-599c-94b2-64daa75cd233
121
+ //# sourceMappingURL=scrub-BN3gyBpR.js.map
122
+ //# debugId=3af5060a-b6fe-5f2e-9661-01f4460a7b04
@@ -1,7 +1,7 @@
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]="a251c173-a448-5d16-93ea-bc9547e82365")}catch(e){}}();
4
- import { n as toScrubbed } from "./scrub-VtQmxap1.js";
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]="853940c3-9e72-566b-ad70-9f043e6aba8d")}catch(e){}}();
4
+ import { n as toScrubbed } from "./scrub-BN3gyBpR.js";
5
5
  import { dirname } from "node:path";
6
6
  import { request } from "node:http";
7
7
  import { fileURLToPath } from "node:url";
@@ -4385,5 +4385,5 @@ function toTags(delivery) {
4385
4385
  //#endregion
4386
4386
  export { send };
4387
4387
 
4388
- //# sourceMappingURL=sentry-D5lKx0nm.js.map
4389
- //# debugId=a251c173-a448-5d16-93ea-bc9547e82365
4388
+ //# sourceMappingURL=sentry-CLaeAWNa.js.map
4389
+ //# debugId=853940c3-9e72-566b-ad70-9f043e6aba8d
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardfin/cli",
3
- "version": "0.1.0-dev.29",
3
+ "version": "0.1.0-dev.31",
4
4
  "description": "Command line interface for the Hardfin API",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hardfin, Inc.",