@hardfin/cli 0.1.0-dev.33 → 0.1.0-dev.35

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
@@ -60,7 +60,7 @@ Everything an endpoint takes becomes a flag or an argument. No command asks for
60
60
 
61
61
  ```sh
62
62
  hardfin asset ownership create ast_4f9xk2mq7plr8stz \
63
- --customer-id cst_a1 --date 2026-09-23 --sale-price 1500.00
63
+ --customer-id 0192f7d4-3b6a-7c1e-9f2d-5a8b4c6e7d73 --date 2026-09-23 --sale-price 1500.00
64
64
 
65
65
  hardfin asset move execute create \
66
66
  --move assetId=0192f7d4-3b6a-7c1e-9f2d-5a8b4c6e7d70,originId=0192f7d4-3b6a-7c1e-9f2d-5a8b4c6e7d71,destinationId=0192f7d4-3b6a-7c1e-9f2d-5a8b4c6e7d72,shipAt=2026-09-24T10:00:00Z
@@ -320,22 +320,33 @@ other client can run Hardfin commands.
320
320
  claude mcp add hardfin -- hardfin mcp
321
321
  ```
322
322
 
323
- ### One tool, and resources for the rest
323
+ ### Two tools, and resources for the rest
324
324
 
325
325
  Every tool a server lists sits in the agent's context for the entire session, so this server
326
- offers one.
326
+ offers two. They differ only in what they may run, which lets a client run a read without
327
+ asking and ask before anything changes.
327
328
 
328
- | Offered as | Holds | Costs context |
329
- | -------------------- | ---------------------------------------------------------------- | --------------------------------- |
330
- | The `hardfin` tool | A list of arguments, such as `["asset", "list", "--limit", "5"]` | Always, and it is about 400 bytes |
331
- | `hardfin://guide` | Every command, its flags, and the exit codes | Only when the agent reads it |
332
- | `hardfin://commands` | The command tree as JSON | Only when the agent reads it |
329
+ | Offered as | Holds | Costs context |
330
+ | -------------------- | ---------------------------------------------------------------------- | ---------------------------- |
331
+ | `hardfin_read` | A command that only reads, such as `["asset", "list", "--limit", "5"]` | Always, about 800 bytes |
332
+ | `hardfin_write` | A command that creates, changes or deletes, with an optional JSON body | Always, about 800 bytes |
333
+ | `hardfin://guide` | Every command, its flags, the exit codes and the error codes | Only when the agent reads it |
334
+ | `hardfin://commands` | The command tree as JSON | Only when the agent reads it |
333
335
 
334
- Fifty tools would describe the same surface and crowd out the work. An agent that needs the
335
- list reads a resource, or runs `["--help"]`.
336
+ A command is a read when its endpoint's method is GET, or for `hardfin api` when no other
337
+ `--method` is given. Anything else is a write, and `hardfin_read` refuses it by naming
338
+ `hardfin_write`. Both tools carry a title, the read-only or destructive hint, and a link to
339
+ the API reference, which Claude's connector directory requires.
336
340
 
337
341
  A tool call runs the CLI itself, so an agent and a person get identical parsing, output and
338
- exit codes.
342
+ exit codes. Each call has these limits:
343
+
344
+ | Limit | Value | Past it |
345
+ | ---------------- | --------------------------------------- | ------------------------------------------ |
346
+ | Time | 120 seconds | The command is stopped, and the call fails |
347
+ | Commands running | 4 at once | The next call waits for one to finish |
348
+ | One argument | 128 KiB | Refused, pointing at the `input` argument |
349
+ | Standard input | The call's `input` argument, or nothing | Always closed, so `--input -` never waits |
339
350
 
340
351
  ### What it signs in as
341
352
 
package/dist/cli.js CHANGED
@@ -1,11 +1,11 @@
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]="3f5ddeb4-6ae9-5ded-b447-15c8483fc583")}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]="e0ebce65-52b6-5732-b4b2-1becfe72bdef")}catch(e){}}();
4
4
  import module from "node:module";
5
5
  //#region src/cli.ts
6
6
  module.enableCompileCache?.();
7
- process.stdout.on("error", (error) => {
8
- if (error.code === "EPIPE") process.exit(0);
7
+ for (const stream of [process.stdout, process.stderr]) stream.on("error", (error) => {
8
+ if (error.code === "EPIPE") process.exit(process.exitCode ?? 0);
9
9
  throw error;
10
10
  });
11
11
  let isCrashing = false;
@@ -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-ByvcKNri.js");
17
+ const { toCli, toParseExitCode, wasReportAttempted } = await import("./program-BdRDIa3u.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
  process.stderr.write(`error: ${error instanceof Error ? error.message : String(error)}\n`);
35
35
  try {
36
- const { toReported } = await import("./program-ByvcKNri.js");
36
+ const { toReported } = await import("./program-BdRDIa3u.js");
37
37
  await toReported(error, false, void 0, "fatal");
38
38
  } finally {
39
39
  process.exit(1);
@@ -51,4 +51,4 @@ async function toQuickAnswer(args) {
51
51
  export {};
52
52
 
53
53
  //# sourceMappingURL=cli.js.map
54
- //# debugId=3f5ddeb4-6ae9-5ded-b447-15c8483fc583
54
+ //# debugId=e0ebce65-52b6-5732-b4b2-1becfe72bdef
@@ -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]="93b4bc8d-ebeb-59ca-967b-9fd10484e4a5")}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]="6057a656-73c2-55b1-9f09-873e3b83b368")}catch(e){}}();
4
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";
@@ -1157,8 +1157,8 @@ async function runApi(input) {
1157
1157
  writeFailure("invalid_arguments", "a path is required, such as /customer", input.isJSON);
1158
1158
  return ExitCode.USAGE;
1159
1159
  }
1160
- if (path.split("/").includes("..")) {
1161
- writeFailure("invalid_arguments", "a path stays inside the API, so it cannot contain ..", input.isJSON);
1160
+ if (!isInsideApi(input.resolved.settings.apiUrl, path)) {
1161
+ writeFailure("invalid_arguments", "a path stays inside the API, so it cannot climb out of it with ..", input.isJSON);
1162
1162
  return ExitCode.USAGE;
1163
1163
  }
1164
1164
  const query = toQuery$1(input.flags["field"]);
@@ -1196,6 +1196,17 @@ async function runApi(input) {
1196
1196
  throw error;
1197
1197
  }
1198
1198
  }
1199
+ /** isInsideApi reports whether a path, once parsed, still lands under the API's own path. */
1200
+ function isInsideApi(apiUrl, path) {
1201
+ try {
1202
+ const base = new URL(apiUrl);
1203
+ const target = new URL(`${apiUrl}${path.startsWith("/") ? "" : "/"}${path}`);
1204
+ const root = base.pathname.replace(/\/+$/, "");
1205
+ return target.origin === base.origin && (target.pathname === root || target.pathname.startsWith(`${root}/`));
1206
+ } catch {
1207
+ return false;
1208
+ }
1209
+ }
1199
1210
  /** toQuery folds the repeated --field flags into query parameters. */
1200
1211
  function toQuery$1(fields) {
1201
1212
  const query = new URLSearchParams();
@@ -2797,23 +2808,52 @@ function wasReportAttempted() {
2797
2808
  const PROTOCOL_VERSION = "2025-06-18";
2798
2809
  const GUIDE_URI = "hardfin://guide";
2799
2810
  const COMMANDS_URI = "hardfin://commands";
2800
- /**
2801
- * One tool, because every tool a server lists sits in the agent's context for the whole
2802
- * session. The commands themselves are resources, which cost nothing until one is read.
2803
- */
2804
- const TOOL = {
2805
- name: "hardfin",
2806
- description: "Run a Hardfin CLI command against the Hardfin API. Pass the arguments as a list, such as [\"asset\", \"list\", \"--limit\", \"5\"]. Run [\"--help\"] for the commands, or read the hardfin://guide resource.",
2807
- inputSchema: {
2808
- type: "object",
2809
- properties: { args: {
2811
+ /** The public reference for the API every command calls. */
2812
+ const API_REFERENCE = "https://docs.hardfin.com";
2813
+ const ARGUMENTS_SCHEMA = {
2814
+ type: "object",
2815
+ properties: {
2816
+ args: {
2810
2817
  type: "array",
2811
2818
  items: { type: "string" },
2812
2819
  description: "The arguments to hardfin, without the program name"
2813
- } },
2814
- required: ["args"]
2820
+ },
2821
+ input: {
2822
+ type: "string",
2823
+ description: "JSON sent to the command on standard input, which it reads when given --input -"
2824
+ }
2825
+ },
2826
+ required: ["args"]
2827
+ };
2828
+ /**
2829
+ * Two tools, so a client can let a read run without asking and ask before anything changes.
2830
+ * The commands themselves are resources, which cost nothing until one is read.
2831
+ */
2832
+ const READ_TOOL = {
2833
+ name: "hardfin_read",
2834
+ title: "Read Hardfin data",
2835
+ description: `Runs a Hardfin CLI command that only reads, against the Hardfin API (${API_REFERENCE}). Takes the arguments as a list, such as ["asset", "list", "--limit", "5"]. ["--help"] lists the commands, and the hardfin://guide resource describes each one.`,
2836
+ inputSchema: ARGUMENTS_SCHEMA,
2837
+ annotations: {
2838
+ title: "Read Hardfin data",
2839
+ readOnlyHint: true,
2840
+ destructiveHint: false,
2841
+ openWorldHint: true
2842
+ }
2843
+ };
2844
+ const WRITE_TOOL = {
2845
+ name: "hardfin_write",
2846
+ title: "Change Hardfin data",
2847
+ description: `Runs a Hardfin CLI command that creates, changes or deletes records through the Hardfin API (${API_REFERENCE}). Takes the arguments as a list, such as ["customer", "create", "--name", "Acme"]. A JSON body goes in the input argument, with --input - in the list.`,
2848
+ inputSchema: ARGUMENTS_SCHEMA,
2849
+ annotations: {
2850
+ title: "Change Hardfin data",
2851
+ readOnlyHint: false,
2852
+ destructiveHint: true,
2853
+ openWorldHint: true
2815
2854
  }
2816
2855
  };
2856
+ const INSTRUCTIONS = "hardfin_read looks records up and hardfin_write changes them. Both run Hardfin CLI commands, so the arguments are what a person would type after hardfin. The hardfin://guide resource lists every command, flag and error code.";
2817
2857
  /** ProtocolFailure is a line this server answers with a JSON-RPC error rather than a result. */
2818
2858
  var ProtocolFailure = class extends Error {
2819
2859
  code;
@@ -2841,10 +2881,12 @@ async function toResponse(request, commands, run) {
2841
2881
  },
2842
2882
  serverInfo: {
2843
2883
  name: "hardfin",
2884
+ title: "Hardfin",
2844
2885
  version: version$1
2845
- }
2886
+ },
2887
+ instructions: INSTRUCTIONS
2846
2888
  });
2847
- case "tools/list": return answer({ tools: [TOOL] });
2889
+ case "tools/list": return answer({ tools: [READ_TOOL, WRITE_TOOL] });
2848
2890
  case "resources/list": return answer({ resources: [{
2849
2891
  uri: GUIDE_URI,
2850
2892
  name: "Hardfin CLI guide",
@@ -2857,7 +2899,8 @@ async function toResponse(request, commands, run) {
2857
2899
  mimeType: "application/json"
2858
2900
  }] });
2859
2901
  case "resources/read": {
2860
- const uri = toText(request.params?.["uri"] ?? "");
2902
+ const uri = request.params?.["uri"];
2903
+ if (typeof uri !== "string" || uri === "") throw new ProtocolFailure(-32602, "resources/read needs a uri, such as hardfin://guide", request.id ?? null);
2861
2904
  const resource = toResource(uri, commands);
2862
2905
  if (!resource) return {
2863
2906
  jsonrpc: "2.0",
@@ -2869,7 +2912,7 @@ async function toResponse(request, commands, run) {
2869
2912
  };
2870
2913
  return answer(resource);
2871
2914
  }
2872
- case "tools/call": return answer(await toToolResult(request.params, run));
2915
+ case "tools/call": return answer(await toToolResult(request, commands, run));
2873
2916
  case "ping": return answer({});
2874
2917
  default:
2875
2918
  if (request.id === void 0 || request.id === null) return;
@@ -2943,80 +2986,170 @@ function toCommandName(args) {
2943
2986
  function toIsAskingForHelp(args) {
2944
2987
  return args.includes("--help") || args.includes("-h") || args[0] === "help";
2945
2988
  }
2946
- async function toToolResult(params, run) {
2947
- if (params?.["name"] !== TOOL.name) return {
2948
- content: [{
2949
- type: "text",
2950
- text: `a call names its tool, and this server offers one, ${TOOL.name}`
2951
- }],
2952
- isError: true
2953
- };
2954
- const args = (params?.["arguments"])?.args;
2955
- if (!Array.isArray(args) || args.some((entry) => typeof entry !== "string")) return {
2956
- content: [{
2957
- type: "text",
2958
- text: "args must be a list of strings, such as [\"asset\", \"list\"]"
2959
- }],
2960
- isError: true
2961
- };
2989
+ async function toToolResult(request, commands, run) {
2990
+ const params = request.params;
2991
+ const name = params?.["name"];
2992
+ if (name !== READ_TOOL.name && name !== WRITE_TOOL.name) throw new ProtocolFailure(-32602, `a call names its tool, and this server offers ${READ_TOOL.name} and ${WRITE_TOOL.name}`, request.id ?? null);
2993
+ const called = params?.["arguments"];
2994
+ const args = called?.args;
2995
+ if (!Array.isArray(args) || args.some((entry) => typeof entry !== "string")) return toError("args must be a list of strings, such as [\"asset\", \"list\"]");
2996
+ if (called?.input !== void 0 && typeof called.input !== "string") return toError("input must be a string holding JSON, such as {\"name\": \"Acme\"}");
2962
2997
  const refused = toRefusedArgument(args);
2963
- if (refused) return {
2964
- content: [{
2965
- type: "text",
2966
- text: refused
2967
- }],
2968
- isError: true
2969
- };
2998
+ if (refused) return toError(refused);
2970
2999
  const asked = args;
2971
3000
  const command = toCommandName(asked) ?? "";
2972
- if (REFUSED_COMMANDS.has(command) && !toIsAskingForHelp(asked)) return {
3001
+ if (REFUSED_COMMANDS.has(command) && !toIsAskingForHelp(asked)) return toError(`${command} is run by a person at a terminal, not through this server. Run hardfin ${command} yourself, then call this tool again`);
3002
+ if (name === READ_TOOL.name && toAccess(asked, commands) === "write") return toError(`this command changes data, so it runs through ${WRITE_TOOL.name}`);
3003
+ const outcome = await run(asked, called?.input);
3004
+ return {
2973
3005
  content: [{
2974
3006
  type: "text",
2975
- text: `${command} is run by a person at a terminal, not through this server. Run hardfin ${command} yourself, then call this tool again`
3007
+ text: [outcome.stdout, outcome.stderr].filter((part) => part.trim() !== "").join("\n") || `hardfin exited ${outcome.code}`
2976
3008
  }],
2977
- isError: true
3009
+ isError: outcome.code !== 0
2978
3010
  };
2979
- const outcome = await run(asked);
3011
+ }
3012
+ function toError(text) {
2980
3013
  return {
2981
3014
  content: [{
2982
3015
  type: "text",
2983
- text: [outcome.stdout, outcome.stderr].filter((part) => part.trim() !== "").join("\n") || `hardfin exited ${outcome.code}`
3016
+ text
2984
3017
  }],
2985
- isError: outcome.code !== 0
3018
+ isError: true
2986
3019
  };
2987
3020
  }
2988
- /** The most a command line can carry, below what the operating system refuses outright. */
2989
- const ARGUMENT_LIMIT = 1e5;
3021
+ /** Commands with no endpoint that only show something, which a read may run. */
3022
+ const LOCAL_READS = /* @__PURE__ */ new Set([
3023
+ "status",
3024
+ "config",
3025
+ "agent-guide",
3026
+ "help"
3027
+ ]);
3028
+ /**
3029
+ * toAccess decides whether a call reads or changes data. A generated command answers by its
3030
+ * endpoint's method, hardfin api by the method it was given, and anything it cannot place is
3031
+ * treated as a change, so it is asked about.
3032
+ */
3033
+ function toAccess(args, commands) {
3034
+ if (toIsAskingForHelp(args)) return "read";
3035
+ let level = commands;
3036
+ let found;
3037
+ for (let index = 0; index < args.length; index += 1) {
3038
+ const word = args[index] ?? "";
3039
+ if (GLOBAL_OPTIONS_WITH_VALUES.has(word)) {
3040
+ index += 1;
3041
+ continue;
3042
+ }
3043
+ if (word.startsWith("-")) continue;
3044
+ const next = level.find((entry) => entry.name === word);
3045
+ if (!next) break;
3046
+ found = next;
3047
+ level = next.subcommands ?? [];
3048
+ if (level.length === 0) break;
3049
+ }
3050
+ if (!found) return "read";
3051
+ if (found.endpoint) return found.endpoint.method === "GET" ? "read" : "write";
3052
+ if (found.name === "api") return toApiMethod(args) === "GET" ? "read" : "write";
3053
+ if (LOCAL_READS.has(found.name) || (found.subcommands?.length ?? 0) > 0) return "read";
3054
+ return "write";
3055
+ }
3056
+ /** toApiMethod reads the method hardfin api was given, which is GET when none was. */
3057
+ function toApiMethod(args) {
3058
+ for (let index = 0; index < args.length; index += 1) {
3059
+ const word = args[index] ?? "";
3060
+ if (word === "-X" || word === "--method") return (args[index + 1] ?? "GET").toUpperCase();
3061
+ if (word.startsWith("--method=")) return word.slice(9).toUpperCase();
3062
+ if (word.startsWith("-X") && word.length > 2) return word.slice(2).toUpperCase();
3063
+ }
3064
+ return "GET";
3065
+ }
3066
+ /** The most one argument can hold, which Linux refuses past 128 KiB. */
3067
+ const ARGUMENT_BYTES = 131071;
3068
+ /** The most a whole command line can carry, below what the operating system refuses outright. */
3069
+ const COMMAND_LINE_BYTES = 1e6;
2990
3070
  /** toRefusedArgument names an argument a command line cannot carry, rather than failing later. */
2991
3071
  function toRefusedArgument(args) {
2992
3072
  const withNul = args.findIndex((argument) => argument.includes("\0"));
2993
3073
  if (withNul >= 0) return `args[${withNul}] holds a NUL byte, which a command line cannot carry`;
2994
- const total = args.reduce((carried, argument) => carried + argument.length, 0);
2995
- if (total > ARGUMENT_LIMIT) return `these arguments are ${total} characters, and a command line carries at most ${ARGUMENT_LIMIT}`;
2996
- }
3074
+ const oversized = args.findIndex((argument) => Buffer.byteLength(argument) > ARGUMENT_BYTES);
3075
+ if (oversized >= 0) return `args[${oversized}] is ${Buffer.byteLength(args[oversized] ?? "")} bytes, and one argument carries at most ${ARGUMENT_BYTES}. Send a large body in the input argument instead`;
3076
+ const total = args.reduce((carried, argument) => carried + Buffer.byteLength(argument), 0);
3077
+ if (total > COMMAND_LINE_BYTES) return `these arguments are ${total} bytes, and a command line carries at most ${COMMAND_LINE_BYTES}`;
3078
+ }
3079
+ /** How long one command may run before it is stopped, so a hung call cannot hold its client. */
3080
+ const CALL_TIMEOUT_MS = 12e4;
3081
+ /** How many commands run at once, so a burst of calls cannot start a process each. */
3082
+ const MAX_RUNNING = 4;
2997
3083
  /** toCliRunner runs the CLI itself, so a tool call parses exactly as a terminal would. */
2998
- function toCliRunner() {
2999
- return (args) => new Promise((resolve) => {
3000
- const child = spawn(process.execPath, [process.argv[1] ?? "", ...args], { env: {
3001
- ...process.env,
3002
- HARDFIN_NO_BROWSER: "1",
3003
- HARDFIN_MCP: "1"
3004
- } });
3084
+ function toCliRunner(timeoutMs = CALL_TIMEOUT_MS, entry = process.argv[1] ?? "") {
3085
+ const waiting = [];
3086
+ let running = 0;
3087
+ const acquire = async () => {
3088
+ if (running < MAX_RUNNING) {
3089
+ running += 1;
3090
+ return;
3091
+ }
3092
+ await new Promise((resolve) => waiting.push(resolve));
3093
+ };
3094
+ const release = () => {
3095
+ const next = waiting.shift();
3096
+ if (next) next();
3097
+ else running -= 1;
3098
+ };
3099
+ return async (args, input) => {
3100
+ await acquire();
3101
+ try {
3102
+ return await toRun(entry, args, input, timeoutMs);
3103
+ } finally {
3104
+ release();
3105
+ }
3106
+ };
3107
+ }
3108
+ /** toRun runs one command, feeding it input on stdin and stopping it at the deadline. */
3109
+ function toRun(entry, args, input, timeoutMs) {
3110
+ return new Promise((resolve) => {
3111
+ let child;
3112
+ try {
3113
+ child = spawn(process.execPath, [entry, ...args], { env: {
3114
+ ...process.env,
3115
+ HARDFIN_NO_BROWSER: "1",
3116
+ HARDFIN_MCP: "1"
3117
+ } });
3118
+ } catch (error) {
3119
+ resolve({
3120
+ stdout: "",
3121
+ stderr: `the command could not start: ${error instanceof Error ? error.message : String(error)}`,
3122
+ code: 1
3123
+ });
3124
+ return;
3125
+ }
3005
3126
  let stdout = "";
3006
3127
  let stderr = "";
3128
+ let isTimedOut = false;
3129
+ const timer = setTimeout(() => {
3130
+ isTimedOut = true;
3131
+ child.kill("SIGKILL");
3132
+ }, timeoutMs);
3007
3133
  child.stdout.on("data", (chunk) => {
3008
3134
  stdout += chunk.toString();
3009
3135
  });
3010
3136
  child.stderr.on("data", (chunk) => {
3011
3137
  stderr += chunk.toString();
3012
3138
  });
3139
+ child.on("error", (error) => {
3140
+ stderr += `the command could not start: ${error.message}\n`;
3141
+ });
3013
3142
  child.on("close", (code) => {
3143
+ clearTimeout(timer);
3144
+ if (isTimedOut) stderr += `this command took longer than ${timeoutMs / 1e3} seconds, so it was stopped\n`;
3014
3145
  resolve({
3015
3146
  stdout,
3016
3147
  stderr,
3017
- code: code ?? 1
3148
+ code: isTimedOut ? 1 : code ?? 1
3018
3149
  });
3019
3150
  });
3151
+ child.stdin.on("error", () => void 0);
3152
+ child.stdin.end(input ?? "");
3020
3153
  });
3021
3154
  }
3022
3155
  /** serve answers requests on stdin until the client closes it. */
@@ -3371,7 +3504,9 @@ function toElements(flag, values) {
3371
3504
  const key = pair.slice(0, split).trim();
3372
3505
  if (flag.element && !flag.element.includes(key)) return /* @__PURE__ */ new Error(`--${flag.name} has no field ${key}. It takes ${flag.element.join(", ")}`);
3373
3506
  if (key in element) return /* @__PURE__ */ new Error(`--${flag.name} names ${key} twice in one element, so which one it takes is not clear`);
3374
- element[key] = pair.slice(split + 1).trim();
3507
+ const value = pair.slice(split + 1).trim();
3508
+ 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`);
3509
+ element[key] = value;
3375
3510
  }
3376
3511
  elements.push(element);
3377
3512
  }
@@ -3803,6 +3938,11 @@ const commands = [
3803
3938
  "shipAt",
3804
3939
  "shipAtTimezone"
3805
3940
  ],
3941
+ elementUuids: [
3942
+ "assetId",
3943
+ "destinationId",
3944
+ "originId"
3945
+ ],
3806
3946
  schema: z.array(z.string())
3807
3947
  }]
3808
3948
  })]
@@ -3832,6 +3972,7 @@ const commands = [
3832
3972
  "id",
3833
3973
  "shipAt"
3834
3974
  ],
3975
+ elementUuids: ["assetId"],
3835
3976
  schema: z.array(z.string())
3836
3977
  }]
3837
3978
  })]
@@ -3908,6 +4049,7 @@ const commands = [
3908
4049
  valueName: "fieldId=,value=",
3909
4050
  repeatable: true,
3910
4051
  element: ["fieldId", "value"],
4052
+ elementUuids: ["fieldId"],
3911
4053
  schema: z.array(z.string())
3912
4054
  },
3913
4055
  {
@@ -5251,6 +5393,7 @@ const commands = [
5251
5393
  "order",
5252
5394
  "section"
5253
5395
  ],
5396
+ elementUuids: ["fieldId"],
5254
5397
  schema: z.array(z.string())
5255
5398
  },
5256
5399
  {
@@ -6187,5 +6330,5 @@ async function toReported(error, isJSON, resolved, level = "error") {
6187
6330
  //#endregion
6188
6331
  export { toCli, toParseExitCode, toReported, wasReportAttempted };
6189
6332
 
6190
- //# sourceMappingURL=program-ByvcKNri.js.map
6191
- //# debugId=93b4bc8d-ebeb-59ca-967b-9fd10484e4a5
6333
+ //# sourceMappingURL=program-BdRDIa3u.js.map
6334
+ //# debugId=6057a656-73c2-55b1-9f09-873e3b83b368
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardfin/cli",
3
- "version": "0.1.0-dev.33",
3
+ "version": "0.1.0-dev.35",
4
4
  "description": "Command line interface for the Hardfin API",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Hardfin, Inc.",