@hardfin/cli 0.1.1-dev.54 → 0.1.1-dev.56

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
@@ -72,6 +72,19 @@ A value is sent as the type the document names. `--useful-life 36` sends the num
72
72
  decimal such as `--salvage-value 1500.00` stays a string, which is how the API takes money
73
73
  without losing precision.
74
74
 
75
+ Inside a `key=value` flag, `=` gives a value as you typed it and `:=` gives it as JSON.
76
+
77
+ | You type | Is sent as |
78
+ | ----------------------- | ------------------------------------------------------------------ |
79
+ | `value=36` | the text "36", because a custom field may hold a serial number |
80
+ | `value:=36` | the number 36 |
81
+ | `value:=true` | the boolean true |
82
+ | `deliverAt:=null` | null |
83
+ | `order=2` or `order:=2` | the number 2, because the document types `order` as a whole number |
84
+
85
+ A value the field does not take fails locally with exit code 2, such as `order=two`, or
86
+ `id:=42` for a field that takes text alone.
87
+
75
88
  A download is written where you name it, and a terminal is never filled with a file's bytes.
76
89
  With no `--output`, the file takes the name it was uploaded under, in the working directory.
77
90
  That name never chooses a folder, a hidden file, or a file that is already there. The command
@@ -411,7 +424,8 @@ projects come ready. Running it again updates only what it wrote.
411
424
  | `.vscode/mcp.json` | The `hardfin` server, for VS Code |
412
425
 
413
426
  The server is the hosted one at the authorization server's host, `https://api.hardfin.com/mcp`
414
- by default. Every other server in an MCP config, and everything in `AGENTS.md` outside the
427
+ by default. [Serving an agent](#serving-an-agent) says how it differs from the local server
428
+ this CLI runs. Every other server in an MCP config, and everything in `AGENTS.md` outside the
415
429
  markers, is left as it was. A config that does not hold JSON is skipped, never overwritten.
416
430
 
417
431
  `init` refuses an address that `config.local.json` or `.env` in the folder chose, because the
@@ -423,17 +437,46 @@ package, so `init` writes the copy that matches the installed CLI.
423
437
 
424
438
  ## Serving an agent
425
439
 
440
+ Hardfin has two MCP servers, and an agent needs only one of them. Hardfin hosts one. The
441
+ other is this CLI, run on your machine. Read this section to choose between them.
442
+
443
+ | | The hosted server | The local server |
444
+ | --------------------- | -------------------------------------------------------------------- | ------------------------------------------------------ |
445
+ | Runs | At Hardfin, at `https://api.hardfin.com/mcp` | On your machine, started by the agent's client |
446
+ | Needs installed | Nothing | This CLI, and Node 20.12 or later |
447
+ | Set up by | `hardfin init` | `claude mcp add hardfin -- hardfin mcp` |
448
+ | Signs in as | Whoever signs in to Hardfin from the client | Whoever ran `hardfin login` here, or `HARDFIN_API_KEY` |
449
+ | Tools | `hardfin_read`, `hardfin_create`, `hardfin_update`, `hardfin_delete` | `hardfin_read`, `hardfin_write`, `hardfin_delete` |
450
+ | A call names | An operation from the API reference, with its parameters and body | A command, as the words typed after `hardfin` |
451
+ | Read-only when | The sign in was not granted `hardfin:write` | The same, or it was started with `--read-only` |
452
+ | Files on your machine | Out of reach | Uploaded and downloaded, inside the folder it runs in |
453
+
454
+ Choose the hosted server unless one of these holds.
455
+
456
+ - The agent has to upload a file from your machine, or download one to it
457
+ - The agent runs where your browser cannot sign in to Hardfin, and you have a stored sign in or an API key on the machine
458
+ - You want a local build of the API, which `hardfin mcp` reaches through `config.local.json`
459
+
460
+ The two servers place one command differently. Scrapping an asset goes through
461
+ `hardfin_delete` on the local server and through `hardfin_update` on the hosted one. Both ask
462
+ before it by default, because both tools carry the destructive hint.
463
+
464
+ Both set-ups name the server `hardfin`, so adding one to a project replaces the other. To run
465
+ both, give the local one another name:
466
+
426
467
  ```sh
427
- hardfin mcp
468
+ claude mcp add hardfin-local -- hardfin mcp
428
469
  ```
429
470
 
430
- It speaks the Model Context Protocol on standard input and output, so Claude, Codex or any
431
- other client can run Hardfin commands.
471
+ The rest of this section is about the local server.
432
472
 
433
473
  ```sh
434
- claude mcp add hardfin -- hardfin mcp
474
+ hardfin mcp
435
475
  ```
436
476
 
477
+ It speaks the Model Context Protocol on standard input and output, so Claude, Codex or any
478
+ other client can run Hardfin commands.
479
+
437
480
  ### Three tools, and resources for the rest
438
481
 
439
482
  Every tool a server lists sits in the agent's context for the entire session, so this server
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]="c6243d33-fcd1-5df5-9405-f1e6448344d6")}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]="ec88336e-026f-5a04-9acd-43c5410c3229")}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-BhH7Zc0y.js");
17
+ const { toCli, toParseExitCode, wasReportAttempted } = await import("./program-CI4W2agw.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-BhH7Zc0y.js");
36
+ const { toReported, writeCrash } = await import("./program-CI4W2agw.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=c6243d33-fcd1-5df5-9405-f1e6448344d6
57
+ //# debugId=ec88336e-026f-5a04-9acd-43c5410c3229
@@ -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]="91a42949-4ebc-5767-bd2f-75c98f7b5603")}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]="cb5319cf-733c-5f06-b5d8-f8f154bf4267")}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";
@@ -1477,7 +1477,8 @@ function toDescribed(flag) {
1477
1477
  default: flag.defaultValue,
1478
1478
  schema: toFlagSchema(flag),
1479
1479
  sends: sent.queryName !== void 0 ? { query: sent.queryName } : sent.jsonPath !== void 0 ? { body: sent.jsonPath } : void 0,
1480
- element: sent.element
1480
+ element: sent.element,
1481
+ elementTypes: sent.elementTypes
1481
1482
  };
1482
1483
  return Object.fromEntries(Object.entries(described).filter(([, value]) => value !== void 0 && value !== false));
1483
1484
  }
@@ -3356,17 +3357,64 @@ function toElements(flag, values) {
3356
3357
  for (const pair of value.split(",")) {
3357
3358
  const split = pair.indexOf("=");
3358
3359
  if (split < 1) return /* @__PURE__ */ new Error(`--${flag.name} takes ${flag.element?.join("=, ") ?? ""}=, such as --${flag.name} ${flag.element?.[0] ?? "field"}=value. A value cannot contain a comma`);
3359
- const key = pair.slice(0, split).trim();
3360
+ const isJSON = pair[split - 1] === ":";
3361
+ const key = pair.slice(0, isJSON ? split - 1 : split).trim();
3360
3362
  if (flag.element && !flag.element.includes(key)) return /* @__PURE__ */ new Error(`--${flag.name} has no field ${key}. It takes ${flag.element.join(", ")}`);
3361
3363
  if (key in element) return /* @__PURE__ */ new Error(`--${flag.name} names ${key} twice in one element, so which one it takes is not clear`);
3362
3364
  const value = pair.slice(split + 1).trim();
3363
3365
  if (flag.elementUuids?.includes(key) && !z.uuid().safeParse(value).success) return /* @__PURE__ */ new Error(`--${flag.name} needs a UUID for ${key}, such as 3f2a9c1e-5b7d-4e8a-9c21-7d4e5f6a8b90, and ${value || "nothing"} is not one`);
3364
- element[key] = value;
3366
+ const types = flag.elementTypes?.[key];
3367
+ const typed = isJSON ? toJSONValue(value, types ?? ["string"]) : toTyped(value, types);
3368
+ if (typed === NOT_TAKEN) return /* @__PURE__ */ new Error(`--${flag.name} needs ${toNamedTypes(types ?? ["string"])} for ${key}, and ${value || "nothing"} is not one`);
3369
+ element[key] = typed;
3365
3370
  }
3366
3371
  elements.push(element);
3367
3372
  }
3368
3373
  return elements;
3369
3374
  }
3375
+ /** Marks text that is none of the types a field takes. */
3376
+ const NOT_TAKEN = Symbol("not taken");
3377
+ const NUMBER = /^-?\d+(\.\d+)?$/;
3378
+ /**
3379
+ * toTyped reads the text after key= as the type the document gives the field. A field that
3380
+ * takes text is sent as typed, whatever it looks like, because 36 may be a serial number. A
3381
+ * field that takes no text has only one reading, so order=2 is sent as the number.
3382
+ */
3383
+ function toTyped(text, types) {
3384
+ if (types === void 0 || types.includes("string")) return text;
3385
+ if (types.includes("null") && text === "null") return null;
3386
+ if ((types.includes("integer") || types.includes("number")) && NUMBER.test(text)) {
3387
+ const value = Number(text);
3388
+ if (types.includes("number") || Number.isInteger(value)) return value;
3389
+ }
3390
+ if (types.includes("boolean") && (text === "true" || text === "false")) return text === "true";
3391
+ return NOT_TAKEN;
3392
+ }
3393
+ /**
3394
+ * toJSONValue reads the text after key:= as JSON, and takes it when the field takes its type.
3395
+ * It is how a custom field is given the number 36 or true, where key= would send the text.
3396
+ */
3397
+ function toJSONValue(text, types) {
3398
+ let value;
3399
+ try {
3400
+ value = JSON.parse(text);
3401
+ } catch {
3402
+ return NOT_TAKEN;
3403
+ }
3404
+ const type = value === null ? "null" : typeof value;
3405
+ return (types.includes(type) || type === "number" && types.includes("integer") && Number.isInteger(value)) && type !== "object" ? value : NOT_TAKEN;
3406
+ }
3407
+ /** toNamedTypes says in words what a field takes, for the message that refuses a value. */
3408
+ function toNamedTypes(types) {
3409
+ const named = {
3410
+ integer: "a whole number",
3411
+ number: "a number",
3412
+ boolean: "true or false",
3413
+ string: "text in quotes",
3414
+ null: "null"
3415
+ };
3416
+ return types.filter((type) => type in named).map((type) => named[type]).join(" or ");
3417
+ }
3370
3418
  /** set writes a value at its path, building the objects a nested field needs. */
3371
3419
  function set(body, path, value) {
3372
3420
  let holder = body;
@@ -4826,6 +4874,12 @@ const commands = [
4826
4874
  "destinationId",
4827
4875
  "originId"
4828
4876
  ],
4877
+ elementTypes: {
4878
+ "deliverAt": ["string", "null"],
4879
+ "deliverAtTimezone": ["string", "null"],
4880
+ "shipAt": ["string", "null"],
4881
+ "shipAtTimezone": ["string", "null"]
4882
+ },
4829
4883
  schema: z.array(z.string())
4830
4884
  }]
4831
4885
  })]
@@ -4856,6 +4910,10 @@ const commands = [
4856
4910
  "shipAt"
4857
4911
  ],
4858
4912
  elementUuids: ["assetId"],
4913
+ elementTypes: {
4914
+ "deliverAt": ["string", "null"],
4915
+ "shipAt": ["string", "null"]
4916
+ },
4859
4917
  schema: z.array(z.string())
4860
4918
  }]
4861
4919
  })]
@@ -4933,6 +4991,14 @@ const commands = [
4933
4991
  repeatable: true,
4934
4992
  element: ["fieldId", "value"],
4935
4993
  elementUuids: ["fieldId"],
4994
+ elementTypes: { "value": [
4995
+ "array",
4996
+ "boolean",
4997
+ "number",
4998
+ "object",
4999
+ "string",
5000
+ "null"
5001
+ ] },
4936
5002
  schema: z.array(z.string())
4937
5003
  },
4938
5004
  {
@@ -6277,6 +6343,10 @@ const commands = [
6277
6343
  "section"
6278
6344
  ],
6279
6345
  elementUuids: ["fieldId"],
6346
+ elementTypes: {
6347
+ "order": ["integer"],
6348
+ "section": ["integer"]
6349
+ },
6280
6350
  schema: z.array(z.string())
6281
6351
  },
6282
6352
  {
@@ -7177,5 +7247,5 @@ async function toReported(error, isJSON, resolved, level = "error") {
7177
7247
  //#endregion
7178
7248
  export { toCli, toParseExitCode, toReported, wasReportAttempted, writeCrash };
7179
7249
 
7180
- //# sourceMappingURL=program-BhH7Zc0y.js.map
7181
- //# debugId=91a42949-4ebc-5767-bd2f-75c98f7b5603
7250
+ //# sourceMappingURL=program-CI4W2agw.js.map
7251
+ //# debugId=cb5319cf-733c-5f06-b5d8-f8f154bf4267
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardfin/cli",
3
- "version": "0.1.1-dev.54",
3
+ "version": "0.1.1-dev.56",
4
4
  "description": "Command line interface for the Hardfin API",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hardfin, Inc.",