@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 +22 -11
- package/dist/cli.js +6 -6
- package/dist/{program-ByvcKNri.js → program-BdRDIa3u.js} +207 -64
- package/package.json +1 -1
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
|
|
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
|
-
###
|
|
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
|
|
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
|
|
329
|
-
| -------------------- |
|
|
330
|
-
|
|
|
331
|
-
| `
|
|
332
|
-
| `hardfin://
|
|
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
|
-
|
|
335
|
-
|
|
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]="
|
|
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-
|
|
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-
|
|
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=
|
|
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]="
|
|
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 (
|
|
1161
|
-
writeFailure("invalid_arguments", "a path stays inside the API, so it cannot
|
|
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
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2805
|
-
|
|
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
|
-
|
|
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: [
|
|
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 =
|
|
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
|
|
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(
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
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:
|
|
3007
|
+
text: [outcome.stdout, outcome.stderr].filter((part) => part.trim() !== "").join("\n") || `hardfin exited ${outcome.code}`
|
|
2976
3008
|
}],
|
|
2977
|
-
isError:
|
|
3009
|
+
isError: outcome.code !== 0
|
|
2978
3010
|
};
|
|
2979
|
-
|
|
3011
|
+
}
|
|
3012
|
+
function toError(text) {
|
|
2980
3013
|
return {
|
|
2981
3014
|
content: [{
|
|
2982
3015
|
type: "text",
|
|
2983
|
-
text
|
|
3016
|
+
text
|
|
2984
3017
|
}],
|
|
2985
|
-
isError:
|
|
3018
|
+
isError: true
|
|
2986
3019
|
};
|
|
2987
3020
|
}
|
|
2988
|
-
/**
|
|
2989
|
-
const
|
|
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
|
|
2995
|
-
if (
|
|
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
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
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
|
-
|
|
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-
|
|
6191
|
-
//# debugId=
|
|
6333
|
+
//# sourceMappingURL=program-BdRDIa3u.js.map
|
|
6334
|
+
//# debugId=6057a656-73c2-55b1-9f09-873e3b83b368
|