@pipelex/sdk 0.33.0 → 0.34.0
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 +20 -1
- package/dist/cli/args.d.ts +41 -0
- package/dist/cli/args.js +96 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/bundle.d.ts +45 -0
- package/dist/cli/bundle.js +148 -0
- package/dist/cli/bundle.js.map +1 -0
- package/dist/cli/environment.d.ts +36 -0
- package/dist/cli/environment.js +72 -0
- package/dist/cli/environment.js.map +1 -0
- package/dist/cli/help.d.ts +4 -0
- package/dist/cli/help.js +56 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/inputs.d.ts +13 -0
- package/dist/cli/inputs.js +70 -0
- package/dist/cli/inputs.js.map +1 -0
- package/dist/cli/io.d.ts +79 -0
- package/dist/cli/io.js +96 -0
- package/dist/cli/io.js.map +1 -0
- package/dist/cli/main.d.ts +18 -0
- package/dist/cli/main.js +58 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/method.d.ts +56 -0
- package/dist/cli/method.js +108 -0
- package/dist/cli/method.js.map +1 -0
- package/dist/cli/present.d.ts +43 -0
- package/dist/cli/present.js +191 -0
- package/dist/cli/present.js.map +1 -0
- package/dist/cli/run.d.ts +17 -0
- package/dist/cli/run.js +290 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/script.d.ts +23 -0
- package/dist/cli/script.js +212 -0
- package/dist/cli/script.js.map +1 -0
- package/dist/cli/source.d.ts +49 -0
- package/dist/cli/source.js +71 -0
- package/dist/cli/source.js.map +1 -0
- package/dist/cli.d.ts +10 -0
- package/dist/cli.js +65 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +49 -7
- package/dist/client.js +152 -26
- package/dist/client.js.map +1 -1
- package/dist/errors.d.ts +42 -4
- package/dist/errors.js +43 -4
- package/dist/errors.js.map +1 -1
- package/dist/hooks/validate-client.d.ts +3 -3
- package/dist/hooks/validate-client.js +3 -3
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/prepare-inputs.d.ts +6 -0
- package/dist/prepare-inputs.js +21 -10
- package/dist/prepare-inputs.js.map +1 -1
- package/dist/runs.d.ts +28 -1
- package/dist/runs.js +2 -1
- package/dist/runs.js.map +1 -1
- package/dist/upload.d.ts +6 -0
- package/dist/upload.js +9 -4
- package/dist/upload.js.map +1 -1
- package/dist/user-agent.d.ts +3 -2
- package/dist/user-agent.js +3 -2
- package/dist/user-agent.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/docs/architecture.md +8 -3
- package/docs/cli.md +123 -0
- package/docs/client-identification.md +1 -1
- package/docs/errors.md +5 -3
- package/docs/input-preparation.md +6 -2
- package/docs/run-results.md +20 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -18,6 +18,24 @@ It also exports `summarizeUsage`, which folds a completed run's usage records in
|
|
|
18
18
|
npm install @pipelex/sdk
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
## Run a method from the command line
|
|
22
|
+
|
|
23
|
+
The package publishes one command, `pipelex-sdk`, so a method runs with nothing installed but Node 22.12 or later and a key in `PIPELEX_API_KEY`:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx @pipelex/sdk run --method github.com/acme/methods/receipt-review@v1.0.0 --inputs-template > inputs.json
|
|
27
|
+
npx @pipelex/sdk run --method github.com/acme/methods/receipt-review@v1.0.0 --inputs inputs.json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`--method` takes a published address, a catalog id (`mt_…`) or a local `.mthds` file or bundle directory. The run's main output is printed on stdout as JSON, and the run id, each uploaded file and every error on stderr. `script` writes a method its own command, a shell script pinned to this SDK's version:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx @pipelex/sdk script --method github.com/acme/methods/receipt-review@v1.0.0
|
|
34
|
+
./receipt-review --inputs inputs.json
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
See [`docs/cli.md`](./docs/cli.md) for every flag, the output, the exit codes and Ctrl-C.
|
|
38
|
+
|
|
21
39
|
## Usage
|
|
22
40
|
|
|
23
41
|
```ts
|
|
@@ -114,7 +132,7 @@ try {
|
|
|
114
132
|
|
|
115
133
|
### Client identification
|
|
116
134
|
|
|
117
|
-
Every request to the API carries a `User-Agent` such as `pipelex-sdk-js/0.21.0 node/22.4.0 (darwin; arm64)`, which the hosted platform reads to attribute traffic to a client surface in its analytics. A program built on the SDK can put its own name in front with `appInfo`, shaped like Stripe's option of that name; an invalid field is refused at construction with a `TypeError`. In a browser the SDK sets no `User-Agent`. A program that also calls the API with its own `fetch` gets the same value from the exported `buildUserAgent(appInfo)`. The convention is the
|
|
135
|
+
Every request to the API carries a `User-Agent` such as `pipelex-sdk-js/0.21.0 node/22.4.0 (darwin; arm64)`, which the hosted platform reads to attribute traffic to a client surface in its analytics. A program built on the SDK can put its own name in front with `appInfo`, shaped like Stripe's option of that name; an invalid field is refused at construction with a `TypeError`. In a browser the SDK sets no `User-Agent`. A program that also calls the API with its own `fetch` gets the same value from the exported `buildUserAgent(appInfo)`. The convention is the spec `conformance/specs/client-identification.md`, in the `conformance` repository, where the cross-repo specs sit beside the tests that verify them, and [`docs/client-identification.md`](./docs/client-identification.md) describes this SDK's side of it.
|
|
118
136
|
|
|
119
137
|
```ts
|
|
120
138
|
const client = new PipelexApiClient({ appInfo: { name: "acme-invoicer", version: "1.4.0" } });
|
|
@@ -147,6 +165,7 @@ These pages ship inside the published package, so a reader who has only installe
|
|
|
147
165
|
| Page | What it covers |
|
|
148
166
|
| --- | --- |
|
|
149
167
|
| [`docs/architecture.md`](./docs/architecture.md) | The whole client surface: the request pipeline, every route, the typed errors |
|
|
168
|
+
| [`docs/cli.md`](./docs/cli.md) | The `pipelex-sdk` command: `run` and `script`, the environment it reads, what it prints, its exit codes, how it reads a bundle, and the case table it shares with the Python SDK |
|
|
150
169
|
| [`docs/run-results.md`](./docs/run-results.md) | Every field of `RunResults` — the run id as a durable handle, `main_stuff`, `working_memory`, `graph_spec`, the usage pair, produced files |
|
|
151
170
|
| [`docs/run-usage.md`](./docs/run-usage.md) | What a run consumed, record by record, and `summarizeUsage` which folds them into one reading |
|
|
152
171
|
| [`docs/artifact-download.md`](./docs/artifact-download.md) | Turning the `pipelex-storage://` references a run produced back into bytes: `locateArtifacts`, `collectArtifacts`, `resolveArtifacts`, `fetchArtifact`, `downloadArtifacts`, and how a saved file is named after the field it fills |
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flags of one subcommand, read with `node:util`'s `parseArgs` and held to rules the Python
|
|
3
|
+
* twin's `argparse` can follow word for word, so both commands refuse the same command lines.
|
|
4
|
+
*
|
|
5
|
+
* `parseArgs` runs in its lenient mode and returns its tokens, and the rules are applied to the
|
|
6
|
+
* tokens in command-line order, the first problem being the one reported:
|
|
7
|
+
*
|
|
8
|
+
* - `--help` or `-h` anywhere before `--` asks for the subcommand's help, whatever else is there,
|
|
9
|
+
* even where a flag's value would be expected: an argument that is exactly one of the two is
|
|
10
|
+
* never taken as a value, so `run --method --help` prints the help. A parser that would take it
|
|
11
|
+
* as the value, as `parseArgs` and `argparse` both do, is followed by a scan for it. The `--`
|
|
12
|
+
* that bounds the scan is the one the parser reads as the end of the options, not a `--` a
|
|
13
|
+
* string flag took as its value.
|
|
14
|
+
* - An option the subcommand does not take is refused, and so is any positional argument.
|
|
15
|
+
* - A flag given twice is refused, rather than the last one winning: a script written by
|
|
16
|
+
* `script` passes its own arguments through, and a second `--method` must not quietly run
|
|
17
|
+
* another method.
|
|
18
|
+
* - A string flag needs a non-empty value, given as the next argument or after `=`. A next
|
|
19
|
+
* argument that starts with `-` is not taken as the value, except `-` alone, which `--inputs`
|
|
20
|
+
* reads as stdin; `--name=-x` is how a value starting with `-` is given.
|
|
21
|
+
* - A boolean flag takes no value.
|
|
22
|
+
*/
|
|
23
|
+
/** One flag a subcommand takes, by its long name. */
|
|
24
|
+
export interface FlagSpec {
|
|
25
|
+
readonly type: "string" | "boolean";
|
|
26
|
+
}
|
|
27
|
+
/** The flags a subcommand takes, by long name, without the `--`. */
|
|
28
|
+
export type FlagSpecs = Readonly<Record<string, FlagSpec>>;
|
|
29
|
+
/** What a subcommand's command line asked for. */
|
|
30
|
+
export interface ParsedFlags {
|
|
31
|
+
/** Whether `--help` was asked for, in which case nothing else was read. */
|
|
32
|
+
readonly help: boolean;
|
|
33
|
+
readonly strings: ReadonlyMap<string, string>;
|
|
34
|
+
readonly booleans: ReadonlySet<string>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read a subcommand's command line against its flags.
|
|
38
|
+
*
|
|
39
|
+
* @throws {CommandError} A usage error naming the first problem, in command-line order.
|
|
40
|
+
*/
|
|
41
|
+
export declare function parseFlags(command: string, args: readonly string[], specs: FlagSpecs): ParsedFlags;
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The flags of one subcommand, read with `node:util`'s `parseArgs` and held to rules the Python
|
|
3
|
+
* twin's `argparse` can follow word for word, so both commands refuse the same command lines.
|
|
4
|
+
*
|
|
5
|
+
* `parseArgs` runs in its lenient mode and returns its tokens, and the rules are applied to the
|
|
6
|
+
* tokens in command-line order, the first problem being the one reported:
|
|
7
|
+
*
|
|
8
|
+
* - `--help` or `-h` anywhere before `--` asks for the subcommand's help, whatever else is there,
|
|
9
|
+
* even where a flag's value would be expected: an argument that is exactly one of the two is
|
|
10
|
+
* never taken as a value, so `run --method --help` prints the help. A parser that would take it
|
|
11
|
+
* as the value, as `parseArgs` and `argparse` both do, is followed by a scan for it. The `--`
|
|
12
|
+
* that bounds the scan is the one the parser reads as the end of the options, not a `--` a
|
|
13
|
+
* string flag took as its value.
|
|
14
|
+
* - An option the subcommand does not take is refused, and so is any positional argument.
|
|
15
|
+
* - A flag given twice is refused, rather than the last one winning: a script written by
|
|
16
|
+
* `script` passes its own arguments through, and a second `--method` must not quietly run
|
|
17
|
+
* another method.
|
|
18
|
+
* - A string flag needs a non-empty value, given as the next argument or after `=`. A next
|
|
19
|
+
* argument that starts with `-` is not taken as the value, except `-` alone, which `--inputs`
|
|
20
|
+
* reads as stdin; `--name=-x` is how a value starting with `-` is given.
|
|
21
|
+
* - A boolean flag takes no value.
|
|
22
|
+
*/
|
|
23
|
+
import { parseArgs } from "node:util";
|
|
24
|
+
import { usageError } from "./io.js";
|
|
25
|
+
/** The two arguments that ask for a subcommand's help. */
|
|
26
|
+
const HELP_ARGUMENTS = ["--help", "-h"];
|
|
27
|
+
/**
|
|
28
|
+
* Read a subcommand's command line against its flags.
|
|
29
|
+
*
|
|
30
|
+
* @throws {CommandError} A usage error naming the first problem, in command-line order.
|
|
31
|
+
*/
|
|
32
|
+
export function parseFlags(command, args, specs) {
|
|
33
|
+
const options = {
|
|
34
|
+
help: { type: "boolean", short: "h" },
|
|
35
|
+
};
|
|
36
|
+
for (const [name, spec] of Object.entries(specs))
|
|
37
|
+
options[name] = { type: spec.type };
|
|
38
|
+
const { tokens } = parseArgs({
|
|
39
|
+
args: [...args],
|
|
40
|
+
options,
|
|
41
|
+
strict: false,
|
|
42
|
+
allowPositionals: true,
|
|
43
|
+
tokens: true,
|
|
44
|
+
});
|
|
45
|
+
// The `--` that ends the options is the one the parser reads as such: a `--` a string flag took
|
|
46
|
+
// as its value ends nothing, so `run --pipe -- --method --help` asks for the help.
|
|
47
|
+
const terminator = tokens.find((token) => token.kind === "option-terminator");
|
|
48
|
+
const beforeTerminator = terminator === undefined ? args : args.slice(0, terminator.index);
|
|
49
|
+
if (beforeTerminator.some((arg) => HELP_ARGUMENTS.includes(arg))) {
|
|
50
|
+
return { help: true, strings: new Map(), booleans: new Set() };
|
|
51
|
+
}
|
|
52
|
+
// `-h` inside a group of short options, such as `-xh`, asks for the help too.
|
|
53
|
+
if (tokens.some((token) => token.kind === "option" && token.name === "help" && token.value === undefined)) {
|
|
54
|
+
return { help: true, strings: new Map(), booleans: new Set() };
|
|
55
|
+
}
|
|
56
|
+
const strings = new Map();
|
|
57
|
+
const booleans = new Set();
|
|
58
|
+
const seen = new Set();
|
|
59
|
+
for (const token of tokens) {
|
|
60
|
+
if (token.kind === "option-terminator")
|
|
61
|
+
continue;
|
|
62
|
+
if (token.kind === "positional") {
|
|
63
|
+
throw misuse(command, `unexpected argument "${token.value}"`);
|
|
64
|
+
}
|
|
65
|
+
if (token.name === "help")
|
|
66
|
+
throw misuse(command, "--help takes no value");
|
|
67
|
+
const spec = Object.hasOwn(specs, token.name) ? specs[token.name] : undefined;
|
|
68
|
+
if (spec === undefined) {
|
|
69
|
+
throw misuse(command, `unknown option ${token.rawName}`);
|
|
70
|
+
}
|
|
71
|
+
const flag = `--${token.name}`;
|
|
72
|
+
if (seen.has(token.name)) {
|
|
73
|
+
throw misuse(command, `${flag} was given more than once`);
|
|
74
|
+
}
|
|
75
|
+
seen.add(token.name);
|
|
76
|
+
if (spec.type === "boolean") {
|
|
77
|
+
if (token.value !== undefined)
|
|
78
|
+
throw misuse(command, `${flag} takes no value`);
|
|
79
|
+
booleans.add(token.name);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const value = token.value;
|
|
83
|
+
const takenFromNext = !token.inlineValue;
|
|
84
|
+
if (value === undefined ||
|
|
85
|
+
value === "" ||
|
|
86
|
+
(takenFromNext && value.startsWith("-") && value !== "-")) {
|
|
87
|
+
throw misuse(command, `${flag} needs a value`);
|
|
88
|
+
}
|
|
89
|
+
strings.set(token.name, value);
|
|
90
|
+
}
|
|
91
|
+
return { help: false, strings, booleans };
|
|
92
|
+
}
|
|
93
|
+
function misuse(command, message) {
|
|
94
|
+
return usageError(`${message}.`, [`Run 'pipelex-sdk ${command} --help' to see its flags.`]);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=args.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../src/cli/args.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bundle reader: a `--method` path read from disk into the files `run` sends inline.
|
|
3
|
+
*
|
|
4
|
+
* The rules are the command's contract, recorded as cases in `tests/fixtures/cli-cases.json` so
|
|
5
|
+
* that the Python twin reads a bundle the same way (`docs/cli.md`, "Reading a bundle"):
|
|
6
|
+
*
|
|
7
|
+
* - **A `.mthds` file** is a one-file bundle, sent under its own file name. A file of any other
|
|
8
|
+
* name is refused.
|
|
9
|
+
* - **A directory** is every `.mthds` file under it, at any depth, each sent under its path
|
|
10
|
+
* relative to the directory, with `/` between segments.
|
|
11
|
+
* - **The order** is the order of those names compared code point by code point, which is the
|
|
12
|
+
* order of their UTF-8 bytes: `Zeta.mthds` before `main.mthds`, and `steps/score.mthds` before
|
|
13
|
+
* `steps2.mthds`. A run sends the contents in this order, and the first of two files to declare
|
|
14
|
+
* the same metadata is the one kept.
|
|
15
|
+
* - **A hidden entry**, a file or a directory whose name starts with `.`, is skipped, so a
|
|
16
|
+
* `.venv/` or a `.git/` inside a bundle is never read.
|
|
17
|
+
* - **Any file not named `.mthds`** is ignored, Python files included: the command sends only
|
|
18
|
+
* the MTHDS sources.
|
|
19
|
+
* - **A symbolic link** is judged by its own name: one named `.mthds` that leads to a file is read
|
|
20
|
+
* through, under the link's name; one that leads to a directory is not descended into, which
|
|
21
|
+
* keeps a link cycle from looping; and one that leads nowhere is refused. The `--method` path
|
|
22
|
+
* itself may be a link, and is followed.
|
|
23
|
+
* - **A directory with no `.mthds` file** under it is refused, as is a file that is not UTF-8
|
|
24
|
+
* text and a directory that cannot be listed. A byte-order mark is kept as the file's first
|
|
25
|
+
* character, as it is on disk.
|
|
26
|
+
*
|
|
27
|
+
* Every refusal is a usage error, raised before any request.
|
|
28
|
+
*/
|
|
29
|
+
/** The extension of a bundle's files. */
|
|
30
|
+
export declare const BUNDLE_SUFFIX = ".mthds";
|
|
31
|
+
/** One file of a bundle: the name it is sent under, and its text. */
|
|
32
|
+
export interface BundleFile {
|
|
33
|
+
readonly name: string;
|
|
34
|
+
readonly content: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read the bundle a `--method` path names, in the order the rules above give.
|
|
38
|
+
*
|
|
39
|
+
* @throws {CommandError} A usage error for a path that names no bundle.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readBundle(given: string): Promise<BundleFile[]>;
|
|
42
|
+
/** Compare two strings code point by code point, which is comparing their UTF-8 bytes. */
|
|
43
|
+
export declare function compareCodePoints(left: string, right: string): number;
|
|
44
|
+
/** The system's reason for a failed file operation: its code, else its message. */
|
|
45
|
+
export declare function systemReason(error: unknown): string;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bundle reader: a `--method` path read from disk into the files `run` sends inline.
|
|
3
|
+
*
|
|
4
|
+
* The rules are the command's contract, recorded as cases in `tests/fixtures/cli-cases.json` so
|
|
5
|
+
* that the Python twin reads a bundle the same way (`docs/cli.md`, "Reading a bundle"):
|
|
6
|
+
*
|
|
7
|
+
* - **A `.mthds` file** is a one-file bundle, sent under its own file name. A file of any other
|
|
8
|
+
* name is refused.
|
|
9
|
+
* - **A directory** is every `.mthds` file under it, at any depth, each sent under its path
|
|
10
|
+
* relative to the directory, with `/` between segments.
|
|
11
|
+
* - **The order** is the order of those names compared code point by code point, which is the
|
|
12
|
+
* order of their UTF-8 bytes: `Zeta.mthds` before `main.mthds`, and `steps/score.mthds` before
|
|
13
|
+
* `steps2.mthds`. A run sends the contents in this order, and the first of two files to declare
|
|
14
|
+
* the same metadata is the one kept.
|
|
15
|
+
* - **A hidden entry**, a file or a directory whose name starts with `.`, is skipped, so a
|
|
16
|
+
* `.venv/` or a `.git/` inside a bundle is never read.
|
|
17
|
+
* - **Any file not named `.mthds`** is ignored, Python files included: the command sends only
|
|
18
|
+
* the MTHDS sources.
|
|
19
|
+
* - **A symbolic link** is judged by its own name: one named `.mthds` that leads to a file is read
|
|
20
|
+
* through, under the link's name; one that leads to a directory is not descended into, which
|
|
21
|
+
* keeps a link cycle from looping; and one that leads nowhere is refused. The `--method` path
|
|
22
|
+
* itself may be a link, and is followed.
|
|
23
|
+
* - **A directory with no `.mthds` file** under it is refused, as is a file that is not UTF-8
|
|
24
|
+
* text and a directory that cannot be listed. A byte-order mark is kept as the file's first
|
|
25
|
+
* character, as it is on disk.
|
|
26
|
+
*
|
|
27
|
+
* Every refusal is a usage error, raised before any request.
|
|
28
|
+
*/
|
|
29
|
+
import { readdir, readFile, stat } from "node:fs/promises";
|
|
30
|
+
import { basename, join, resolve } from "node:path";
|
|
31
|
+
import { usageError } from "./io.js";
|
|
32
|
+
/** The extension of a bundle's files. */
|
|
33
|
+
export const BUNDLE_SUFFIX = ".mthds";
|
|
34
|
+
const UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
35
|
+
/**
|
|
36
|
+
* Read the bundle a `--method` path names, in the order the rules above give.
|
|
37
|
+
*
|
|
38
|
+
* @throws {CommandError} A usage error for a path that names no bundle.
|
|
39
|
+
*/
|
|
40
|
+
export async function readBundle(given) {
|
|
41
|
+
const absolute = resolve(given);
|
|
42
|
+
let found;
|
|
43
|
+
try {
|
|
44
|
+
found = await stat(absolute);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
throw usageError(`--method "${given}" is not a catalog id (mt_...), not an address (github.com/...), and no file or directory of that name exists.`, [`Reason: ${systemReason(error)}`]);
|
|
48
|
+
}
|
|
49
|
+
if (found.isDirectory()) {
|
|
50
|
+
const shown = given.replace(/\/+$/, "") || "/";
|
|
51
|
+
const entries = [];
|
|
52
|
+
await collect(absolute, "", shown, entries);
|
|
53
|
+
if (entries.length === 0) {
|
|
54
|
+
throw usageError(`--method "${given}" is a directory with no .mthds file under it.`);
|
|
55
|
+
}
|
|
56
|
+
entries.sort((left, right) => compareCodePoints(left.name, right.name));
|
|
57
|
+
const files = [];
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
files.push({
|
|
60
|
+
name: entry.name,
|
|
61
|
+
content: await readText(entry.path, under(shown, entry.name)),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return files;
|
|
65
|
+
}
|
|
66
|
+
if (!found.isFile()) {
|
|
67
|
+
throw usageError(`--method "${given}" is neither a file nor a directory.`);
|
|
68
|
+
}
|
|
69
|
+
if (!basename(absolute).endsWith(BUNDLE_SUFFIX)) {
|
|
70
|
+
throw usageError(`--method "${given}" is not a .mthds file.`, [
|
|
71
|
+
"Name a .mthds file, a directory of them, an address (github.com/...) or a catalog id (mt_...).",
|
|
72
|
+
]);
|
|
73
|
+
}
|
|
74
|
+
return [{ name: basename(absolute), content: await readText(absolute, given) }];
|
|
75
|
+
}
|
|
76
|
+
/** Gather the `.mthds` files under `directory`, each with its name relative to the bundle's root. */
|
|
77
|
+
async function collect(directory, prefix, shownRoot, into) {
|
|
78
|
+
let entries;
|
|
79
|
+
try {
|
|
80
|
+
entries = await readdir(directory, { withFileTypes: true });
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
throw usageError(`cannot list "${prefix === "" ? shownRoot : under(shownRoot, prefix.slice(0, -1))}".`, [`Reason: ${systemReason(error)}`]);
|
|
84
|
+
}
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
if (entry.name.startsWith("."))
|
|
87
|
+
continue;
|
|
88
|
+
const name = `${prefix}${entry.name}`;
|
|
89
|
+
const path = join(directory, entry.name);
|
|
90
|
+
if (entry.isDirectory()) {
|
|
91
|
+
await collect(path, `${name}/`, shownRoot, into);
|
|
92
|
+
}
|
|
93
|
+
else if (entry.isSymbolicLink()) {
|
|
94
|
+
if (!entry.name.endsWith(BUNDLE_SUFFIX))
|
|
95
|
+
continue;
|
|
96
|
+
let target;
|
|
97
|
+
try {
|
|
98
|
+
target = await stat(path);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
throw usageError(`"${under(shownRoot, name)}" is a symbolic link that leads to no file.`, [
|
|
102
|
+
`Reason: ${systemReason(error)}`,
|
|
103
|
+
]);
|
|
104
|
+
}
|
|
105
|
+
if (target.isFile())
|
|
106
|
+
into.push({ name, path });
|
|
107
|
+
}
|
|
108
|
+
else if (entry.isFile() && entry.name.endsWith(BUNDLE_SUFFIX)) {
|
|
109
|
+
into.push({ name, path });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* A path inside the bundle as the person would type it: the `--method` path as given, without
|
|
115
|
+
* its trailing `/`, then `/`, then the file's name in the bundle.
|
|
116
|
+
*/
|
|
117
|
+
function under(root, name) {
|
|
118
|
+
return `${root === "/" ? "" : root}/${name}`;
|
|
119
|
+
}
|
|
120
|
+
/** A bundle file's text, which must be UTF-8. */
|
|
121
|
+
async function readText(path, shown) {
|
|
122
|
+
let bytes;
|
|
123
|
+
try {
|
|
124
|
+
bytes = await readFile(path);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
throw usageError(`cannot read "${shown}".`, [`Reason: ${systemReason(error)}`]);
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
return UTF8.decode(bytes);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
throw usageError(`"${shown}" is not UTF-8 text.`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Compare two strings code point by code point, which is comparing their UTF-8 bytes. */
|
|
137
|
+
export function compareCodePoints(left, right) {
|
|
138
|
+
return Buffer.compare(Buffer.from(left, "utf8"), Buffer.from(right, "utf8"));
|
|
139
|
+
}
|
|
140
|
+
/** The system's reason for a failed file operation: its code, else its message. */
|
|
141
|
+
export function systemReason(error) {
|
|
142
|
+
if (error instanceof Error) {
|
|
143
|
+
const code = error.code;
|
|
144
|
+
return code ?? error.message;
|
|
145
|
+
}
|
|
146
|
+
return String(error);
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../src/cli/bundle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the command reads from its environment, and the client it builds from it.
|
|
3
|
+
*
|
|
4
|
+
* The key comes from `PIPELEX_API_KEY` and nowhere else: a flag would put it in the shell's history
|
|
5
|
+
* and the process list. The API's address comes from `PIPELEX_BASE_URL`, the hosted API when it
|
|
6
|
+
* is not set. An empty variable counts as unset, except `PIPELEX_BASE_URL`: an empty one, such as
|
|
7
|
+
* an unfilled CI secret, reaches the SDK, which refuses it, rather than send the key to the hosted
|
|
8
|
+
* API by default. No `.env` file is read: a command started through `npx` from wherever the person
|
|
9
|
+
* stands would otherwise pick a credential from whichever file it found there.
|
|
10
|
+
*/
|
|
11
|
+
import { PipelexApiClient } from "../index.js";
|
|
12
|
+
import type { CommandIO } from "./io.js";
|
|
13
|
+
export declare const API_KEY_VARIABLE = "PIPELEX_API_KEY";
|
|
14
|
+
export declare const BASE_URL_VARIABLE = "PIPELEX_BASE_URL";
|
|
15
|
+
/**
|
|
16
|
+
* The poll interval in milliseconds, for tests only: the recorded case table sets it to `0`, so
|
|
17
|
+
* that a run answered "still running" is polled again at once. A person has no reason to set it;
|
|
18
|
+
* unset, the SDK's own interval applies. The Python command reads the same variable.
|
|
19
|
+
*/
|
|
20
|
+
export declare const POLL_INTERVAL_VARIABLE = "PIPELEX_SDK_POLL_INTERVAL_MS";
|
|
21
|
+
/** The name the command gives itself in the `User-Agent` of every request, before the SDK's own. */
|
|
22
|
+
export declare const CLIENT_APP_NAME = "pipelex-sdk-cli";
|
|
23
|
+
/**
|
|
24
|
+
* The test-only poll interval, or `undefined` for the SDK's own.
|
|
25
|
+
*
|
|
26
|
+
* @throws {CommandError} A usage error when it is set to anything but a whole number.
|
|
27
|
+
*/
|
|
28
|
+
export declare function readPollInterval(io: CommandIO): number | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* The client every request goes through, built from the key and the base URL.
|
|
31
|
+
*
|
|
32
|
+
* @throws {CommandError} A usage error when the key is missing or the base URL is refused. The
|
|
33
|
+
* refusal never quotes the key, and the SDK's refusal of a base URL never quotes the parts of it
|
|
34
|
+
* that can carry a secret.
|
|
35
|
+
*/
|
|
36
|
+
export declare function makeClient(io: CommandIO): PipelexApiClient;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the command reads from its environment, and the client it builds from it.
|
|
3
|
+
*
|
|
4
|
+
* The key comes from `PIPELEX_API_KEY` and nowhere else: a flag would put it in the shell's history
|
|
5
|
+
* and the process list. The API's address comes from `PIPELEX_BASE_URL`, the hosted API when it
|
|
6
|
+
* is not set. An empty variable counts as unset, except `PIPELEX_BASE_URL`: an empty one, such as
|
|
7
|
+
* an unfilled CI secret, reaches the SDK, which refuses it, rather than send the key to the hosted
|
|
8
|
+
* API by default. No `.env` file is read: a command started through `npx` from wherever the person
|
|
9
|
+
* stands would otherwise pick a credential from whichever file it found there.
|
|
10
|
+
*/
|
|
11
|
+
import { DEFAULT_API_BASE_URL, PipelexApiClient, RequestArgumentError, SDK_VERSION, } from "../index.js";
|
|
12
|
+
import { usageError } from "./io.js";
|
|
13
|
+
export const API_KEY_VARIABLE = "PIPELEX_API_KEY";
|
|
14
|
+
export const BASE_URL_VARIABLE = "PIPELEX_BASE_URL";
|
|
15
|
+
/**
|
|
16
|
+
* The poll interval in milliseconds, for tests only: the recorded case table sets it to `0`, so
|
|
17
|
+
* that a run answered "still running" is polled again at once. A person has no reason to set it;
|
|
18
|
+
* unset, the SDK's own interval applies. The Python command reads the same variable.
|
|
19
|
+
*/
|
|
20
|
+
export const POLL_INTERVAL_VARIABLE = "PIPELEX_SDK_POLL_INTERVAL_MS";
|
|
21
|
+
/** The name the command gives itself in the `User-Agent` of every request, before the SDK's own. */
|
|
22
|
+
export const CLIENT_APP_NAME = "pipelex-sdk-cli";
|
|
23
|
+
function variable(io, name) {
|
|
24
|
+
const value = io.env[name];
|
|
25
|
+
return value === undefined || value === "" ? undefined : value;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The test-only poll interval, or `undefined` for the SDK's own.
|
|
29
|
+
*
|
|
30
|
+
* @throws {CommandError} A usage error when it is set to anything but a whole number.
|
|
31
|
+
*/
|
|
32
|
+
export function readPollInterval(io) {
|
|
33
|
+
const value = variable(io, POLL_INTERVAL_VARIABLE);
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
if (!/^[0-9]+$/.test(value)) {
|
|
37
|
+
throw usageError(`${POLL_INTERVAL_VARIABLE} must be a whole number of milliseconds.`);
|
|
38
|
+
}
|
|
39
|
+
return Number(value);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The client every request goes through, built from the key and the base URL.
|
|
43
|
+
*
|
|
44
|
+
* @throws {CommandError} A usage error when the key is missing or the base URL is refused. The
|
|
45
|
+
* refusal never quotes the key, and the SDK's refusal of a base URL never quotes the parts of it
|
|
46
|
+
* that can carry a secret.
|
|
47
|
+
*/
|
|
48
|
+
export function makeClient(io) {
|
|
49
|
+
const apiKey = variable(io, API_KEY_VARIABLE);
|
|
50
|
+
if (apiKey === undefined) {
|
|
51
|
+
throw usageError(`${API_KEY_VARIABLE} is not set.`, [
|
|
52
|
+
`Get a key at https://app.pipelex.com and export it: export ${API_KEY_VARIABLE}=<your key>`,
|
|
53
|
+
"This command reads no .env file; to load one into the shell, run: set -a; . ./.env; set +a",
|
|
54
|
+
]);
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
return new PipelexApiClient({
|
|
58
|
+
apiKey,
|
|
59
|
+
// Absent, not empty, means the hosted API: the SDK refuses an empty base URL on purpose, so
|
|
60
|
+
// an unfilled CI secret never sends the key to the hosted API by default.
|
|
61
|
+
baseUrl: io.env[BASE_URL_VARIABLE] ?? DEFAULT_API_BASE_URL,
|
|
62
|
+
appInfo: { name: CLIENT_APP_NAME, version: SDK_VERSION },
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
if (error instanceof RequestArgumentError) {
|
|
67
|
+
throw usageError(`${BASE_URL_VARIABLE} is refused.`, [`Reason: ${error.message}`]);
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=environment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment.js","sourceRoot":"","sources":["../../src/cli/environment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGrC,MAAM,CAAC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAClD,MAAM,CAAC,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,8BAA8B,CAAC;AAErE,oGAAoG;AACpG,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD,SAAS,QAAQ,CAAC,EAAa,EAAE,IAAY;IAC3C,MAAM,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AACjE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAa;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,EAAE,sBAAsB,CAAC,CAAC;IACnD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,UAAU,CAAC,GAAG,sBAAsB,0CAA0C,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,EAAa;IACtC,MAAM,MAAM,GAAG,QAAQ,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAC9C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,UAAU,CAAC,GAAG,gBAAgB,cAAc,EAAE;YAClD,8DAA8D,gBAAgB,aAAa;YAC3F,4FAA4F;SAC7F,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,gBAAgB,CAAC;YAC1B,MAAM;YACN,4FAA4F;YAC5F,0EAA0E;YAC1E,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,oBAAoB;YAC1D,OAAO,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,WAAW,EAAE;SACzD,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,oBAAoB,EAAE,CAAC;YAC1C,MAAM,UAAU,CAAC,GAAG,iBAAiB,cAAc,EAAE,CAAC,WAAW,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACrF,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** The command's help texts, printed on stdout by `--help`. */
|
|
2
|
+
export declare const MAIN_HELP = "pipelex-sdk: run an MTHDS method on the Pipelex API, with nothing to install.\n\nUsage:\n pipelex-sdk run --method <method> [--pipe <domain.pipe_code>] [--inputs <file> | --inputs-template]\n pipelex-sdk script --method <address | catalog id> [--pipe <domain.pipe_code>] [--name <name>] [--dir <dir>]\n pipelex-sdk --version\n pipelex-sdk <command> --help\n\nCommands:\n run Run a method and print its main output as JSON on stdout.\n script Write a shell script that runs one method with this SDK's version pinned.\n\nA method is a published address, github.com/<owner>/<repo>[/<package>][@<tag>], a catalog\nid, mt_..., or, for run only, a .mthds file or a bundle directory.\n\nThe API key is read from PIPELEX_API_KEY, and the API's address from PIPELEX_BASE_URL\n(https://api.pipelex.com when unset). No .env file is read.\n\nExit codes: 0 done, 1 the run failed or the API refused it, 2 a usage error, 130 interrupted.\n";
|
|
3
|
+
export declare const RUN_HELP = "Usage: pipelex-sdk run --method <method> [--pipe <domain.pipe_code>] [--inputs <file> | --inputs-template]\n\nRun a method on the Pipelex API, wait for it however long it takes, and print its main\noutput as indented JSON on stdout. The run id, each uploaded file and every error go to\nstderr. Ctrl-C stops waiting; the run keeps going on the server.\n\nOptions:\n --method <method> A published address (github.com/<owner>/<repo>[/<package>][@<tag>]),\n a catalog id (mt_...), or a path to a .mthds file or a bundle directory.\n --pipe <domain.pipe_code> The pipe to run, by its qualified ref. Default: the method's main pipe.\n --inputs <file> The inputs, a JSON object with one entry per input; - reads stdin.\n A local path or a data: URL at a file input is uploaded first.\n --inputs-template Print the inputs template, the object --inputs takes, and run nothing.\n -h, --help Show this help.\n\nNeeds PIPELEX_API_KEY in the environment.\n";
|
|
4
|
+
export declare const SCRIPT_HELP = "Usage: pipelex-sdk script --method <address | catalog id> [--pipe <domain.pipe_code>] [--name <name>] [--dir <dir>]\n\nCheck a method with the API, then write an executable shell script that runs it through\nthis SDK, its version pinned. The script passes its own arguments on to run:\n./<name> --inputs inputs.json, or ./<name> --inputs-template. It never overwrites a file.\n\nOptions:\n --method <method> A published address (github.com/<owner>/<repo>[/<package>][@<tag>])\n or a catalog id (mt_...). A local bundle is refused.\n --pipe <domain.pipe_code> The pipe the script runs. Default: the method's main pipe.\n --name <name> The script's file name. Default: the address's last segment, or the\n catalog method's name in kebab-case.\n --dir <dir> Where to write it. Default: the current directory.\n -h, --help Show this help.\n\nNeeds PIPELEX_API_KEY in the environment. The script needs a POSIX shell.\n";
|
package/dist/cli/help.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/** The command's help texts, printed on stdout by `--help`. */
|
|
2
|
+
export const MAIN_HELP = `pipelex-sdk: run an MTHDS method on the Pipelex API, with nothing to install.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
pipelex-sdk run --method <method> [--pipe <domain.pipe_code>] [--inputs <file> | --inputs-template]
|
|
6
|
+
pipelex-sdk script --method <address | catalog id> [--pipe <domain.pipe_code>] [--name <name>] [--dir <dir>]
|
|
7
|
+
pipelex-sdk --version
|
|
8
|
+
pipelex-sdk <command> --help
|
|
9
|
+
|
|
10
|
+
Commands:
|
|
11
|
+
run Run a method and print its main output as JSON on stdout.
|
|
12
|
+
script Write a shell script that runs one method with this SDK's version pinned.
|
|
13
|
+
|
|
14
|
+
A method is a published address, github.com/<owner>/<repo>[/<package>][@<tag>], a catalog
|
|
15
|
+
id, mt_..., or, for run only, a .mthds file or a bundle directory.
|
|
16
|
+
|
|
17
|
+
The API key is read from PIPELEX_API_KEY, and the API's address from PIPELEX_BASE_URL
|
|
18
|
+
(https://api.pipelex.com when unset). No .env file is read.
|
|
19
|
+
|
|
20
|
+
Exit codes: 0 done, 1 the run failed or the API refused it, 2 a usage error, 130 interrupted.
|
|
21
|
+
`;
|
|
22
|
+
export const RUN_HELP = `Usage: pipelex-sdk run --method <method> [--pipe <domain.pipe_code>] [--inputs <file> | --inputs-template]
|
|
23
|
+
|
|
24
|
+
Run a method on the Pipelex API, wait for it however long it takes, and print its main
|
|
25
|
+
output as indented JSON on stdout. The run id, each uploaded file and every error go to
|
|
26
|
+
stderr. Ctrl-C stops waiting; the run keeps going on the server.
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
--method <method> A published address (github.com/<owner>/<repo>[/<package>][@<tag>]),
|
|
30
|
+
a catalog id (mt_...), or a path to a .mthds file or a bundle directory.
|
|
31
|
+
--pipe <domain.pipe_code> The pipe to run, by its qualified ref. Default: the method's main pipe.
|
|
32
|
+
--inputs <file> The inputs, a JSON object with one entry per input; - reads stdin.
|
|
33
|
+
A local path or a data: URL at a file input is uploaded first.
|
|
34
|
+
--inputs-template Print the inputs template, the object --inputs takes, and run nothing.
|
|
35
|
+
-h, --help Show this help.
|
|
36
|
+
|
|
37
|
+
Needs PIPELEX_API_KEY in the environment.
|
|
38
|
+
`;
|
|
39
|
+
export const SCRIPT_HELP = `Usage: pipelex-sdk script --method <address | catalog id> [--pipe <domain.pipe_code>] [--name <name>] [--dir <dir>]
|
|
40
|
+
|
|
41
|
+
Check a method with the API, then write an executable shell script that runs it through
|
|
42
|
+
this SDK, its version pinned. The script passes its own arguments on to run:
|
|
43
|
+
./<name> --inputs inputs.json, or ./<name> --inputs-template. It never overwrites a file.
|
|
44
|
+
|
|
45
|
+
Options:
|
|
46
|
+
--method <method> A published address (github.com/<owner>/<repo>[/<package>][@<tag>])
|
|
47
|
+
or a catalog id (mt_...). A local bundle is refused.
|
|
48
|
+
--pipe <domain.pipe_code> The pipe the script runs. Default: the method's main pipe.
|
|
49
|
+
--name <name> The script's file name. Default: the address's last segment, or the
|
|
50
|
+
catalog method's name in kebab-case.
|
|
51
|
+
--dir <dir> Where to write it. Default: the current directory.
|
|
52
|
+
-h, --help Show this help.
|
|
53
|
+
|
|
54
|
+
Needs PIPELEX_API_KEY in the environment. The script needs a POSIX shell.
|
|
55
|
+
`;
|
|
56
|
+
//# sourceMappingURL=help.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAE/D,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;CAmBxB,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;CAgBvB,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;CAgB1B,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The inputs `--inputs` names: a JSON object, one entry per input, read from a file or, for `-`,
|
|
3
|
+
* from stdin. It is the shape `mthds run --inputs` and `cli-python`'s `--inputs` take, and the
|
|
4
|
+
* shape `--inputs-template` prints.
|
|
5
|
+
*/
|
|
6
|
+
import type { CommandIO } from "./io.js";
|
|
7
|
+
/**
|
|
8
|
+
* Read and parse the inputs. A relative path resolves against the current directory.
|
|
9
|
+
*
|
|
10
|
+
* @throws {CommandError} A usage error for an unreadable file or stdin, text that is not UTF-8 or
|
|
11
|
+
* not JSON, and JSON that is not an object.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readInputs(source: string, io: CommandIO): Promise<Record<string, unknown>>;
|