@hardfin/cli 0.1.1-dev.49 → 0.1.1-dev.51

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
@@ -129,6 +129,35 @@ An agent branches on `code`. A code is never renamed and never changes meaning,
129
129
  message may be reworded at any time. `hardfin agent-guide` lists every code with what it
130
130
  means, and `src/output/failure.ts` defines them.
131
131
 
132
+ ### Reading the commands from a program
133
+
134
+ `hardfin agent-guide` describes the CLI to an agent, as Markdown or as JSON. Name a command to
135
+ get that command alone, which is a few kilobytes where the whole guide is two hundred.
136
+
137
+ ```sh
138
+ hardfin agent-guide # every command, as Markdown
139
+ hardfin agent-guide --json # every command, as JSON
140
+ hardfin agent-guide --json customer create # one command, and what is nested under it
141
+ hardfin agent-guide --json customer create | jq -r '.commands[0].flags[] | select(.required) | .name'
142
+ ```
143
+
144
+ The JSON holds `cli`, `apiVersion`, `exitCodes`, `errorCodes`, `globalFlags` and `commands`.
145
+ Each command names its `path`, its `endpoint`, whether it `runs`, and its flags.
146
+
147
+ | A flag's | Holds |
148
+ | ------------ | --------------------------------------------------------------------------- |
149
+ | `required` | Present when the command fails without it |
150
+ | `schema` | The `type`, and the `enum`, `format`, `minimum` or `maximum` that narrow it |
151
+ | `sends` | The query parameter, or the path of the body field, the value goes to |
152
+ | `clearable` | Present when `--unset` can clear the field |
153
+ | `repeatable` | Present when the flag is given once per value |
154
+ | `negatable` | Present when a boolean also takes a `--no-` form |
155
+ | `element` | The fields one value carries as `key=value`, for an array of objects |
156
+ | `default` | The value used when the flag is not given |
157
+
158
+ A part that is absent means no. The description is read from the same declarations the parser
159
+ enforces, so it cannot say one thing while the command does another.
160
+
132
161
  ### Overrides
133
162
 
134
163
  Two endpoints sometimes want one name. `DELETE /asset/{assetKey}/ownership` and
@@ -480,6 +509,10 @@ Nothing secret is printed. An API key and a refresh token are each reported as a
480
509
  `sha256:` fingerprint, which identifies a credential across two machines without disclosing
481
510
  it.
482
511
 
512
+ `authorizationServer.scopesOffered` is every scope the server can grant to anyone. The scopes
513
+ this sign in was granted are `api.accessTokenScopes`, and `api.identity.scopes` is what the
514
+ API says the credential holds.
515
+
483
516
  `api.authenticated` is `true` only when the API answered who the credential is. A 401 or a
484
517
  403 makes it `false`, and `hardfin status` then exits with code 4. It is `null` when the API
485
518
  could not be asked, because no answer says nothing about the credential.
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]="6c054d7b-a0a8-526c-90d7-5bbec53675dd")}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]="d68fe726-8a0c-5521-8d64-6a70ad73fa4f")}catch(e){}}();
4
4
  import module from "node:module";
5
5
  //#region src/cli.ts
6
6
  module.enableCompileCache?.();
@@ -14,7 +14,7 @@ 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, toParseExitCode, wasReportAttempted } = await import("./program-Da7dZSnW.js");
17
+ const { toCli, toParseExitCode, wasReportAttempted } = await import("./program-CXKDQ-E9.js");
18
18
  try {
19
19
  await toCli().parseAsync(process.argv);
20
20
  } catch (error) {
@@ -33,7 +33,7 @@ async function toCrash(error) {
33
33
  isCrashing = true;
34
34
  const isJSON = process.argv.includes("--json") || !process.stdout.isTTY;
35
35
  try {
36
- const { toReported, writeCrash } = await import("./program-Da7dZSnW.js");
36
+ const { toReported, writeCrash } = await import("./program-CXKDQ-E9.js");
37
37
  writeCrash(error, isJSON);
38
38
  await toReported(error, isJSON, void 0, "fatal");
39
39
  } catch {
@@ -54,4 +54,4 @@ async function toQuickAnswer(args) {
54
54
  export {};
55
55
 
56
56
  //# sourceMappingURL=cli.js.map
57
- //# debugId=6c054d7b-a0a8-526c-90d7-5bbec53675dd
57
+ //# debugId=d68fe726-8a0c-5521-8d64-6a70ad73fa4f
@@ -2610,10 +2610,14 @@
2610
2610
  "flags": [
2611
2611
  {
2612
2612
  "name": "json",
2613
- "description": "Print the command registry as JSON instead of Markdown"
2613
+ "description": "Print every command as JSON, with what each flag takes, instead of Markdown"
2614
+ }
2615
+ ],
2616
+ "arguments": [
2617
+ {
2618
+ "variadic": true
2614
2619
  }
2615
2620
  ],
2616
- "arguments": [],
2617
2621
  "subcommands": []
2618
2622
  },
2619
2623
  {
@@ -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]="e0ad2183-1cb1-5831-b5d1-b96dcf795d93")}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]="f3469c6b-5453-551f-9090-b9d18cbba487")}catch(e){}}();
4
4
  import { a as defineCommand, i as GLOBAL_FLAGS, r as ExitCode, t as toInvocation } from "./scrub-BZ0egWxC.js";
5
5
  import { n as toAnswer$1, r as toCompletion, t as PATH_VALUES } from "./completion-tree-DLC6ARLR.js";
6
6
  import { createRequire } from "node:module";
@@ -1296,23 +1296,47 @@ const agentGuideCommand = defineCommand({
1296
1296
  name: "agent-guide",
1297
1297
  summary: "Print instructions an agent can follow to use this CLI",
1298
1298
  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.",
1299
- arguments: [],
1299
+ arguments: [{
1300
+ name: "command",
1301
+ description: "The words of one command, such as customer create, to describe it and what is nested under it alone",
1302
+ required: false,
1303
+ variadic: true
1304
+ }],
1300
1305
  flags: [{
1301
1306
  name: "json",
1302
- description: "Print the command registry as JSON instead of Markdown",
1307
+ description: "Print every command as JSON, with what each flag takes, instead of Markdown",
1303
1308
  schema: z.boolean().optional()
1304
1309
  }],
1305
- examples: [{
1306
- description: "Write a skill file",
1307
- command: "hardfin agent-guide > SKILL.md"
1308
- }, {
1309
- description: "Inspect the registry",
1310
- command: "hardfin agent-guide --json"
1311
- }],
1310
+ examples: [
1311
+ {
1312
+ description: "Write a skill file",
1313
+ command: "hardfin agent-guide > SKILL.md"
1314
+ },
1315
+ {
1316
+ description: "Read what every flag takes",
1317
+ command: "hardfin agent-guide --json"
1318
+ },
1319
+ {
1320
+ description: "Read what one command takes, and nothing else",
1321
+ command: "hardfin agent-guide --json customer create"
1322
+ },
1323
+ {
1324
+ description: "List the flags one command needs",
1325
+ command: "hardfin agent-guide --json customer create | jq -r '.commands[0].flags[] | select(.required) | .name'"
1326
+ }
1327
+ ],
1312
1328
  run: runAgentGuide
1313
1329
  });
1330
+ /** EXIT_CODES is what each exit code means, which both forms of the guide print. */
1331
+ const EXIT_CODES = {
1332
+ [ExitCode.OK]: "The command succeeded",
1333
+ [ExitCode.ERROR]: "The command failed",
1334
+ [ExitCode.USAGE]: "The command was called wrongly",
1335
+ [ExitCode.NOT_AUTHENTICATED]: "No usable credential",
1336
+ [ExitCode.INTERRUPTED]: "The person pressed Ctrl-C"
1337
+ };
1314
1338
  /** toGuide renders the registry as the Markdown an agent reads. */
1315
- function toGuide(commands, version) {
1339
+ function toGuide(commands, version, parents = []) {
1316
1340
  const lines = [
1317
1341
  "# Hardfin CLI",
1318
1342
  "",
@@ -1330,11 +1354,7 @@ function toGuide(commands, version) {
1330
1354
  "",
1331
1355
  "| Code | Means |",
1332
1356
  "| --- | --- |",
1333
- "| 0 | The command succeeded |",
1334
- "| 1 | The command failed |",
1335
- "| 2 | The command was called wrongly |",
1336
- "| 4 | No usable credential |",
1337
- "| 130 | The person pressed Ctrl-C |",
1357
+ ...Object.entries(EXIT_CODES).map(([code, meaning]) => `| ${code} | ${meaning} |`),
1338
1358
  "",
1339
1359
  "## Error codes",
1340
1360
  "",
@@ -1347,7 +1367,7 @@ function toGuide(commands, version) {
1347
1367
  "## Commands",
1348
1368
  ""
1349
1369
  ];
1350
- for (const command of commands.filter((command) => !command.hidden)) lines.push(...toCommandLines(command, []));
1370
+ for (const command of commands.filter((command) => !command.hidden)) lines.push(...toCommandLines(command, parents));
1351
1371
  return lines.join("\n");
1352
1372
  }
1353
1373
  /** toCommandLines describes one command and everything nested under it. */
@@ -1365,10 +1385,11 @@ function toCommandLines(command, parents) {
1365
1385
  lines.push("");
1366
1386
  }
1367
1387
  if (command.flags.length > 0) {
1368
- lines.push("| Flag | Takes | Does |", "| --- | --- | --- |");
1388
+ lines.push("| Flag | Required | Takes | Does |", "| --- | --- | --- | --- |");
1369
1389
  for (const flag of command.flags) {
1370
1390
  const name = flag.short ? `-${flag.short}, --${flag.name}` : `--${flag.name}`;
1371
- lines.push(`| \`${name}\` | ${flag.valueName ?? "nothing"} | ${flag.description} |`);
1391
+ const described = toDescribed(flag);
1392
+ lines.push(`| \`${name}\` | ${described.required ? "yes" : "no"} | ${toTaken(described)} | ${flag.description} |`);
1372
1393
  }
1373
1394
  lines.push("");
1374
1395
  }
@@ -1376,29 +1397,111 @@ function toCommandLines(command, parents) {
1376
1397
  for (const subcommand of command.subcommands ?? []) lines.push(...toCommandLines(subcommand, path));
1377
1398
  return lines;
1378
1399
  }
1400
+ /** toTaken says in a few words what a flag takes, naming the values when only some are allowed. */
1401
+ function toTaken(flag) {
1402
+ const values = flag.schema.enum ?? flag.schema.items?.enum;
1403
+ if (values) return `one of ${values.map((value) => `\`${value}\``).join(", ")}`;
1404
+ if (flag.element) return flag.element.map((field) => `${field}=`).join(",");
1405
+ return flag.valueName ?? "nothing";
1406
+ }
1379
1407
  async function runAgentGuide(input) {
1408
+ const found = toNamed(input.commands, input.args);
1409
+ if (found === void 0) {
1410
+ writeFailure("invalid_arguments", `no command is called ${input.args.join(" ")}. Run hardfin --help for the list`, input.isJSON);
1411
+ return ExitCode.USAGE;
1412
+ }
1380
1413
  if (input.flags["json"] === true) {
1381
- writeData(input.commands.filter((command) => !command.hidden).map(toSummary));
1414
+ writeData(toDescription$1(found.commands, version$1, found.parents));
1382
1415
  return ExitCode.OK;
1383
1416
  }
1384
- writeData(toGuide(input.commands, version$1));
1417
+ writeData(toGuide(found.commands, version$1, found.parents));
1385
1418
  return ExitCode.OK;
1386
1419
  }
1387
- function toSummary(command) {
1420
+ /** toNamed finds the one command a caller named by its words, or every command when none was named. */
1421
+ function toNamed(commands, words) {
1422
+ let level = commands.filter((command) => !command.hidden);
1423
+ let found;
1424
+ for (const word of words) {
1425
+ found = level.find((command) => command.name === word);
1426
+ if (!found) return;
1427
+ level = (found.subcommands ?? []).filter((command) => !command.hidden);
1428
+ }
1429
+ return found ? {
1430
+ commands: [found],
1431
+ parents: words.slice(0, -1)
1432
+ } : {
1433
+ commands: level,
1434
+ parents: []
1435
+ };
1436
+ }
1437
+ /** toDescribed reads one flag from its declaration, which is also what the parser enforces. */
1438
+ function toDescribed(flag) {
1439
+ const sent = flag;
1440
+ const described = {
1441
+ name: flag.name,
1442
+ short: flag.short,
1443
+ description: flag.description,
1444
+ valueName: flag.valueName,
1445
+ required: sent.required === true,
1446
+ repeatable: flag.repeatable === true,
1447
+ negatable: flag.negatable === true,
1448
+ clearable: sent.nullable === true,
1449
+ default: flag.defaultValue,
1450
+ schema: toFlagSchema(flag),
1451
+ sends: sent.queryName !== void 0 ? { query: sent.queryName } : sent.jsonPath !== void 0 ? { body: sent.jsonPath } : void 0,
1452
+ element: sent.element
1453
+ };
1454
+ return Object.fromEntries(Object.entries(described).filter(([, value]) => value !== void 0 && value !== false));
1455
+ }
1456
+ /** The bounds zod states for any whole number, which say nothing about a particular flag. */
1457
+ const ANY_INTEGER = [Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER];
1458
+ /** toFlagSchema states what a value must be, keeping only the parts that narrow it. */
1459
+ function toFlagSchema(flag) {
1460
+ try {
1461
+ return toNarrowed(z.toJSONSchema(flag.schema, {
1462
+ io: "output",
1463
+ unrepresentable: "any"
1464
+ }));
1465
+ } catch {
1466
+ return {};
1467
+ }
1468
+ }
1469
+ function toNarrowed(schema) {
1470
+ const narrowed = {};
1471
+ if (typeof schema["type"] === "string") narrowed.type = schema["type"];
1472
+ if (typeof schema["format"] === "string") narrowed.format = schema["format"];
1473
+ if (Array.isArray(schema["enum"])) narrowed.enum = schema["enum"].map(String);
1474
+ for (const bound of ["minimum", "maximum"]) {
1475
+ const value = schema[bound];
1476
+ if (typeof value === "number" && !ANY_INTEGER.includes(value)) narrowed[bound] = value;
1477
+ }
1478
+ if (typeof schema["items"] === "object" && schema["items"] !== null) narrowed.items = toNarrowed(schema["items"]);
1479
+ return narrowed;
1480
+ }
1481
+ /** toDescription is the whole CLI as data: the codes, and every command with what it takes. */
1482
+ function toDescription$1(commands, version, parents = []) {
1483
+ return {
1484
+ cli: version,
1485
+ apiVersion: API_VERSION,
1486
+ exitCodes: EXIT_CODES,
1487
+ errorCodes: FAILURE_CODES,
1488
+ globalFlags: GLOBAL_FLAGS.map(toDescribed),
1489
+ commands: commands.filter((command) => !command.hidden).map((command) => toSummary(command, parents))
1490
+ };
1491
+ }
1492
+ function toSummary(command, parents) {
1493
+ const path = [...parents, command.name];
1388
1494
  return {
1389
1495
  name: command.name,
1496
+ path,
1390
1497
  summary: command.summary,
1391
1498
  description: command.description ?? command.summary,
1499
+ runs: command.run !== void 0,
1500
+ endpoint: command.endpoint ?? null,
1392
1501
  arguments: command.arguments,
1393
- flags: command.flags.map((flag) => ({
1394
- name: flag.name,
1395
- short: flag.short ?? null,
1396
- description: flag.description,
1397
- valueName: flag.valueName ?? null,
1398
- repeatable: flag.repeatable ?? false
1399
- })),
1502
+ flags: command.flags.map(toDescribed),
1400
1503
  examples: command.examples,
1401
- subcommands: command.subcommands?.filter((entry) => !entry.hidden).map(toSummary)
1504
+ subcommands: command.subcommands?.filter((entry) => !entry.hidden).map((entry) => toSummary(entry, path))
1402
1505
  };
1403
1506
  }
1404
1507
  //#endregion
@@ -3456,8 +3559,8 @@ function toServerReport(metadata) {
3456
3559
  tokenEndpoint: metadata.token_endpoint,
3457
3560
  revocationEndpoint: metadata.revocation_endpoint ?? null,
3458
3561
  deviceEndpoint: metadata.device_authorization_endpoint ?? null,
3459
- grantTypes: metadata.grant_types_supported ?? [],
3460
- scopes: metadata.scopes_supported ?? []
3562
+ grantTypesOffered: metadata.grant_types_supported ?? [],
3563
+ scopesOffered: metadata.scopes_supported ?? []
3461
3564
  };
3462
3565
  }
3463
3566
  /** The statuses the API answers when it will not take the credential. */
@@ -6934,5 +7037,5 @@ async function toReported(error, isJSON, resolved, level = "error") {
6934
7037
  //#endregion
6935
7038
  export { toCli, toParseExitCode, toReported, wasReportAttempted, writeCrash };
6936
7039
 
6937
- //# sourceMappingURL=program-Da7dZSnW.js.map
6938
- //# debugId=e0ad2183-1cb1-5831-b5d1-b96dcf795d93
7040
+ //# sourceMappingURL=program-CXKDQ-E9.js.map
7041
+ //# debugId=f3469c6b-5453-551f-9090-b9d18cbba487
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardfin/cli",
3
- "version": "0.1.1-dev.49",
3
+ "version": "0.1.1-dev.51",
4
4
  "description": "Command line interface for the Hardfin API",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hardfin, Inc.",
@@ -55,6 +55,7 @@ hardfin --help # the nouns
55
55
  hardfin asset --help # what you can do with assets
56
56
  hardfin asset create --help # every flag that command takes
57
57
  hardfin agent-guide # every command, flag, exit code and error code, as Markdown
58
+ hardfin agent-guide --json asset create # one command as JSON: required flags, allowed values, the endpoint
58
59
  ```
59
60
 
60
61
  | Noun | Holds |