@exedev/sdk 0.0.6 → 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bold Software, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -15,27 +15,21 @@ import exedev from "@exedev/sdk";
15
15
 
16
16
  const exe = exedev({ token: process.env.EXE_TOKEN! });
17
17
 
18
- // List all VMs
19
- const vms = await exe.ls();
20
- console.log(vms);
21
-
22
- // Get your identity
23
- const me = await exe.whoami();
24
- console.log(me.email);
25
-
26
- // Create a VM
27
- await exe.new("my-vm", { region: "lon" });
28
-
29
- // Get VM details
30
- const info = await exe.stat("my-vm");
31
- console.log(info);
18
+ // Every exe.dev command is a method. Positional arguments are parameters;
19
+ // flags go in an options object, spelled as on the command line.
20
+ const vm = await exe.new({ name: "my-vm", image: "ubuntu:24.04", tag: ["dev"] });
21
+ console.log(vm.https_url);
22
+
23
+ const { vms } = await exe.ls();
24
+ await exe.resize("my-vm", { memory: "8GB" });
25
+ await exe.pause("my-vm");
26
+ await exe.resume("my-vm");
27
+ await exe.shareAdd("my-vm", "friend@example.com", { message: "have a look" });
28
+ await exe.rm("my-vm");
32
29
 
33
- // Run any command
30
+ // Run a raw command line.
34
31
  const result = await exe.exec("help");
35
32
  console.log(result.body);
36
-
37
- // Delete a VM
38
- await exe.rm("my-vm");
39
33
  ```
40
34
 
41
35
  ## Authentication
@@ -43,7 +37,7 @@ await exe.rm("my-vm");
43
37
  Generate an API token via SSH:
44
38
 
45
39
  ```sh
46
- ssh exe.dev token
40
+ ssh exe.dev ssh-key generate-api-key --label=my-app --exp=30d
47
41
  ```
48
42
 
49
43
  See the [HTTPS API docs](https://exe.dev/docs/https-api) for full details on
@@ -57,23 +51,24 @@ authentication, token permissions, and scoping.
57
51
  | --------- | ------ | -------- | ------------------- |
58
52
  | `token` | string | Yes | |
59
53
  | `baseURL` | string | No | `https://exe.dev` |
54
+ | `fetch` | function | No | global `fetch` |
60
55
 
61
- ### Methods
56
+ ### Commands
62
57
 
63
- | Method | Description |
64
- | --- | --- |
65
- | `exec(command)` | Run any command (raw) |
66
- | `ls()` | List VMs |
67
- | `whoami()` | Get authenticated user |
68
- | `stat(vmName)` | Get VM details |
69
- | `new(name, options?)` | Create a VM |
70
- | `rm(vmName)` | Delete a VM |
71
- | `restart(vmName)` | Restart a VM |
72
- | `rename(oldName, newName)` | Rename a VM |
73
- | `sshKeyList()` | List SSH keys |
74
- | `shareShow(vmName)` | Show sharing settings |
58
+ Each public exe.dev command that runs over the HTTPS API is a method, named by
59
+ camel-casing its path: `ls`, `new`, `ssh-key list` is `sshKeyList`,
60
+ `team settings auto-join` is `teamSettingsAutoJoin`. Each method's doc comment
61
+ is the command's own help text, so `help <command>` and your editor agree.
75
62
 
76
- All methods accept an optional `{ signal: AbortSignal }` as the last argument for cancellation.
63
+ Methods resolve to the command's JSON output, typed for the commands listed in
64
+ `CommandResults` (`src/types.ts`) and `unknown` otherwise. They reject with
65
+ `ExeAPIError` (`status`, `command`, `body`) when a command fails. Every
66
+ options object accepts `signal` (an `AbortSignal`) for cancellation.
67
+
68
+ The command methods live in `src/commands.gen.ts`, which is generated from the
69
+ exe.dev command tree; don't edit it by hand. To pick up new commands, run
70
+ `npm run sync-commands -- path/to/exe` (default `../exe`) with an up-to-date
71
+ checkout of the exe.dev repository, then `npm test`.
77
72
 
78
73
  ### Named import
79
74
 
@@ -81,6 +76,30 @@ All methods accept an optional `{ signal: AbortSignal }` as the last argument fo
81
76
  import { exedev } from "@exedev/sdk";
82
77
  ```
83
78
 
79
+ ## Development
80
+
81
+ ```sh
82
+ npm install
83
+ npm test
84
+ ```
85
+
86
+ `npm test` builds the package, then runs the unit tests and the integration
87
+ tests. The integration tests run against the real exe.dev API and create,
88
+ restart, rename and delete a VM. They need a tag-scoped API token, which can
89
+ only see and touch VMs with its tag, and they fail without one (set
90
+ `EXE_SKIP_INTEGRATION=1` to skip them). To create the token:
91
+
92
+ ```sh
93
+ mkdir -p ~/.config/exedev
94
+ ssh exe.dev 'ssh-key generate-api-key --label=sdk-test --tag=sdk-test --exp=30d --cmds="help,ls,new,whoami,stat,rm,restart,rename,ssh-key list,share show" --json' \
95
+ | jq -r .token > ~/.config/exedev/sdk-test-token
96
+ chmod 600 ~/.config/exedev/sdk-test-token
97
+ ```
98
+
99
+ The tests read the token from `$EXE_TOKEN` or `~/.config/exedev/sdk-test-token`.
100
+ They check that the VM they create is tagged `sdk-test` (override with
101
+ `$EXE_TEST_TAG`). Revoke the token with `ssh exe.dev ssh-key remove sdk-test`.
102
+
84
103
  ## License
85
104
 
86
105
  MIT
package/dist/client.d.ts CHANGED
@@ -1,14 +1,15 @@
1
- import type { VM, WhoamiResponse, StatResponse } from "./types.js";
1
+ import { Commands } from "./commands.gen.js";
2
+ import type { Arg, CallOptions, FlagKind } from "./commands.gen.js";
2
3
  export interface ExedevOptions {
3
4
  /** Bearer token (exe0.* or exe1.*). */
4
5
  token: string;
5
6
  /** Base URL for the API. Defaults to "https://exe.dev". */
6
7
  baseURL?: string;
8
+ /** fetch implementation. Defaults to the global fetch. */
9
+ fetch?: typeof fetch;
7
10
  }
8
- export interface ExecOptions {
9
- /** AbortSignal for cancellation. */
10
- signal?: AbortSignal;
11
- }
11
+ /** @deprecated Use CallOptions. */
12
+ export type ExecOptions = CallOptions;
12
13
  export interface ExecResult {
13
14
  /** HTTP status code. */
14
15
  status: number;
@@ -17,29 +18,19 @@ export interface ExecResult {
17
18
  /** Whether the request succeeded (2xx). */
18
19
  ok: boolean;
19
20
  }
20
- export interface ExedevClient {
21
- /** Execute a raw command against the exe.dev API. */
22
- exec(command: string, opts?: ExecOptions): Promise<ExecResult>;
23
- /** List all VMs. */
24
- ls(opts?: ExecOptions): Promise<VM[]>;
25
- /** Get the authenticated user's identity. */
26
- whoami(opts?: ExecOptions): Promise<WhoamiResponse>;
27
- /** Get detailed status of a VM. */
28
- stat(vmName: string, opts?: ExecOptions): Promise<StatResponse>;
29
- /** Create a new VM. Returns the VM name. */
30
- "new"(name: string, options?: {
31
- region?: string;
32
- }, opts?: ExecOptions): Promise<string>;
33
- /** Delete a VM. */
34
- rm(vmName: string, opts?: ExecOptions): Promise<void>;
35
- /** Restart a VM. */
36
- restart(vmName: string, opts?: ExecOptions): Promise<void>;
37
- /** Rename a VM. */
38
- rename(oldName: string, newName: string, opts?: ExecOptions): Promise<void>;
39
- /** List SSH keys. */
40
- sshKeyList(opts?: ExecOptions): Promise<string>;
41
- /** Show sharing settings for a VM. */
42
- shareShow(vmName: string, opts?: ExecOptions): Promise<string>;
21
+ /**
22
+ * An exe.dev API client. Each exe.dev command is a method (see Commands);
23
+ * exec runs a raw command line.
24
+ *
25
+ * Command methods resolve to the command's parsed JSON output and reject with
26
+ * ExeAPIError when the command fails.
27
+ */
28
+ export declare class ExedevClient extends Commands {
29
+ #private;
30
+ constructor(opts: ExedevOptions);
31
+ /** Executes a raw command line against the exe.dev API. */
32
+ exec(command: string, options?: CallOptions): Promise<ExecResult>;
33
+ protected run(path: string, args: readonly Arg[], options: CallOptions | undefined, flags: Readonly<Record<string, FlagKind>>): Promise<any>;
43
34
  }
44
35
  /**
45
36
  * Create an exe.dev API client.
@@ -49,12 +40,16 @@ export interface ExedevClient {
49
40
  * import exedev from "@exedev/sdk";
50
41
  *
51
42
  * const exe = exedev({ token: process.env.EXE_TOKEN! });
52
- * const vms = await exe.ls();
43
+ * const { vms } = await exe.ls();
53
44
  * console.log(vms);
54
45
  * ```
55
46
  */
56
47
  export default function exedev(opts: ExedevOptions): ExedevClient;
57
- /** Error thrown when the exe.dev API returns a non-2xx status. */
48
+ /** Builds the command line for a generated command method. */
49
+ export declare function commandLine(path: string, args: readonly Arg[], options: CallOptions | undefined, flags: Readonly<Record<string, FlagKind>>): string;
50
+ /** Quotes s for the exe.dev command-line parser (POSIX shell rules). */
51
+ export declare function quote(s: string): string;
52
+ /** Error thrown when the exe.dev API reports a failed command. */
58
53
  export declare class ExeAPIError extends Error {
59
54
  readonly command: string;
60
55
  readonly status: number;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAkB,cAAc,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAEnF,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,EAAE,EAAE,OAAO,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,qDAAqD;IACrD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC/D,oBAAoB;IACpB,EAAE,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;IACtC,6CAA6C;IAC7C,MAAM,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;IACpD,mCAAmC;IACnC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;IAChE,4CAA4C;IAC5C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACxF,mBAAmB;IACnB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,oBAAoB;IACpB,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,mBAAmB;IACnB,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,qBAAqB;IACrB,UAAU,CAAC,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD,sCAAsC;IACtC,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAChE;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CA8GhE;AAED,kEAAkE;AAClE,qBAAa,WAAY,SAAQ,KAAK;IACpC,SAAgB,OAAO,EAAE,MAAM,CAAC;IAChC,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAO1D"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEpE,MAAM,WAAW,aAAa;IAC5B,uCAAuC;IACvC,KAAK,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACtB;AAED,mCAAmC;AACnC,MAAM,MAAM,WAAW,GAAG,WAAW,CAAC;AAEtC,MAAM,WAAW,UAAU;IACzB,wBAAwB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,2CAA2C;IAC3C,EAAE,EAAE,OAAO,CAAC;CACb;AAED;;;;;;GAMG;AACH,qBAAa,YAAa,SAAQ,QAAQ;;gBAK5B,IAAI,EAAE,aAAa;IAU/B,2DAA2D;IACrD,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;cAcvD,GAAG,CACjB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,GAAG,EAAE,EACpB,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,GACxC,OAAO,CAAC,GAAG,CAAC;CAiBhB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,OAAO,UAAU,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CAEhE;AAED,8DAA8D;AAC9D,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,SAAS,GAAG,EAAE,EACpB,OAAO,EAAE,WAAW,GAAG,SAAS,EAChC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,GACxC,MAAM,CA2CR;AAED,wEAAwE;AACxE,wBAAgB,KAAK,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAKvC;AA4BD,kEAAkE;AAClE,qBAAa,WAAY,SAAQ,KAAK;IACpC,SAAgB,OAAO,EAAE,MAAM,CAAC;IAChC,SAAgB,MAAM,EAAE,MAAM,CAAC;IAC/B,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEjB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;CAO1D"}
package/dist/client.js CHANGED
@@ -1,109 +1,168 @@
1
+ import { Commands } from "./commands.gen.js";
1
2
  /**
2
- * Create an exe.dev API client.
3
- *
4
- * @example
5
- * ```ts
6
- * import exedev from "@exedev/sdk";
3
+ * An exe.dev API client. Each exe.dev command is a method (see Commands);
4
+ * exec runs a raw command line.
7
5
  *
8
- * const exe = exedev({ token: process.env.EXE_TOKEN! });
9
- * const vms = await exe.ls();
10
- * console.log(vms);
11
- * ```
6
+ * Command methods resolve to the command's parsed JSON output and reject with
7
+ * ExeAPIError when the command fails.
12
8
  */
13
- export default function exedev(opts) {
14
- if (!opts.token) {
15
- throw new Error("exedev: token is required");
9
+ export class ExedevClient extends Commands {
10
+ #token;
11
+ #baseURL;
12
+ #fetch;
13
+ constructor(opts) {
14
+ super();
15
+ if (!opts?.token) {
16
+ throw new Error("exedev: token is required");
17
+ }
18
+ this.#token = opts.token;
19
+ this.#baseURL = (opts.baseURL ?? "https://exe.dev").replace(/\/$/, "");
20
+ this.#fetch = opts.fetch ?? ((input, init) => fetch(input, init));
16
21
  }
17
- const token = opts.token;
18
- const baseURL = (opts.baseURL ?? "https://exe.dev").replace(/\/$/, "");
19
- async function exec(command, execOpts) {
20
- const res = await fetch(`${baseURL}/exec`, {
22
+ /** Executes a raw command line against the exe.dev API. */
23
+ async exec(command, options) {
24
+ const res = await this.#fetch(`${this.#baseURL}/exec`, {
21
25
  method: "POST",
22
26
  headers: {
23
- Authorization: `Bearer ${token}`,
27
+ Authorization: `Bearer ${this.#token}`,
24
28
  "Content-Type": "text/plain",
25
29
  },
26
30
  body: command,
27
- signal: execOpts?.signal,
31
+ signal: options?.signal,
28
32
  });
29
33
  const body = await res.text();
30
34
  return { status: res.status, body, ok: res.ok };
31
35
  }
32
- async function execJSON(command, execOpts) {
33
- const result = await exec(command, execOpts);
36
+ async run(path, args, options, flags) {
37
+ const command = commandLine(path, args, options, flags);
38
+ const result = await this.exec(command, options);
34
39
  if (!result.ok) {
35
40
  throw new ExeAPIError(command, result.status, result.body);
36
41
  }
37
- return JSON.parse(result.body);
38
- }
39
- return {
40
- exec,
41
- async ls(execOpts) {
42
- const res = await execJSON("ls", execOpts);
43
- return res.vms;
44
- },
45
- async whoami(execOpts) {
46
- return execJSON("whoami", execOpts);
47
- },
48
- async stat(vmName, execOpts) {
49
- return execJSON(`stat ${vmName}`, execOpts);
50
- },
51
- async new(name, options, execOpts) {
52
- let cmd = `new ${name}`;
53
- if (options?.region) {
54
- cmd += ` --region=${options.region}`;
55
- }
56
- const result = await exec(cmd, execOpts);
57
- if (!result.ok) {
58
- throw new ExeAPIError(cmd, result.status, result.body);
59
- }
60
- return result.body.trim();
61
- },
62
- async rm(vmName, execOpts) {
63
- const result = await exec(`rm ${vmName}`, execOpts);
64
- if (!result.ok) {
65
- throw new ExeAPIError(`rm ${vmName}`, result.status, result.body);
66
- }
67
- },
68
- async restart(vmName, execOpts) {
69
- const result = await exec(`restart ${vmName}`, execOpts);
70
- if (!result.ok) {
71
- throw new ExeAPIError(`restart ${vmName}`, result.status, result.body);
72
- }
73
- },
74
- async rename(oldName, newName, execOpts) {
75
- const result = await exec(`rename ${oldName} ${newName}`, execOpts);
76
- if (!result.ok) {
77
- throw new ExeAPIError(`rename ${oldName} ${newName}`, result.status, result.body);
78
- }
79
- },
80
- async sshKeyList(execOpts) {
81
- const result = await exec("ssh-key list", execOpts);
82
- if (!result.ok) {
83
- throw new ExeAPIError("ssh-key list", result.status, result.body);
84
- }
42
+ const docs = parseDocuments(result.body);
43
+ if (docs === undefined) {
85
44
  return result.body;
86
- },
87
- async shareShow(vmName, execOpts) {
88
- const result = await exec(`share show ${vmName}`, execOpts);
89
- if (!result.ok) {
90
- throw new ExeAPIError(`share show ${vmName}`, result.status, result.body);
45
+ }
46
+ // Some commands report a failure as an {"error": ...} document with a
47
+ // 200 status (e.g. rm of a missing VM).
48
+ if (docs.some(isErrorDocument)) {
49
+ throw new ExeAPIError(command, result.status, result.body);
50
+ }
51
+ return docs.length === 1 ? docs[0] : docs;
52
+ }
53
+ }
54
+ /**
55
+ * Create an exe.dev API client.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * import exedev from "@exedev/sdk";
60
+ *
61
+ * const exe = exedev({ token: process.env.EXE_TOKEN! });
62
+ * const { vms } = await exe.ls();
63
+ * console.log(vms);
64
+ * ```
65
+ */
66
+ export default function exedev(opts) {
67
+ return new ExedevClient(opts);
68
+ }
69
+ /** Builds the command line for a generated command method. */
70
+ export function commandLine(path, args, options, flags) {
71
+ const words = [path];
72
+ for (const [key, value] of Object.entries(options ?? {})) {
73
+ if (key === "signal" || value === undefined) {
74
+ continue;
75
+ }
76
+ const kind = Object.hasOwn(flags, key) ? flags[key] : undefined;
77
+ switch (kind) {
78
+ case "bool":
79
+ words.push(value ? `--${key}` : `--${key}=false`);
80
+ break;
81
+ case "number":
82
+ case "string":
83
+ words.push(`--${key}=${quote(String(value))}`);
84
+ break;
85
+ case "repeated":
86
+ for (const v of [value].flat()) {
87
+ words.push(`--${key}=${quote(String(v))}`);
88
+ }
89
+ break;
90
+ default:
91
+ throw new TypeError(`${path}: unknown option ${JSON.stringify(key)}`);
92
+ }
93
+ }
94
+ const positionals = [];
95
+ let skipped = false;
96
+ for (const arg of args) {
97
+ if (arg === undefined) {
98
+ skipped = true;
99
+ continue;
100
+ }
101
+ for (const a of [arg].flat()) {
102
+ if (skipped) {
103
+ throw new TypeError(`${path}: argument ${JSON.stringify(a)} follows an omitted argument`);
91
104
  }
92
- return result.body;
93
- },
94
- };
105
+ positionals.push(a);
106
+ }
107
+ }
108
+ if (positionals.some((p) => p.startsWith("-"))) {
109
+ words.push("--");
110
+ }
111
+ words.push(...positionals.map(quote));
112
+ return words.join(" ");
95
113
  }
96
- /** Error thrown when the exe.dev API returns a non-2xx status. */
114
+ /** Quotes s for the exe.dev command-line parser (POSIX shell rules). */
115
+ export function quote(s) {
116
+ if (/^[A-Za-z0-9_@%+=:,./-]+$/.test(s)) {
117
+ return s;
118
+ }
119
+ return `'${s.replaceAll("'", `'"'"'`)}'`;
120
+ }
121
+ /**
122
+ * Parses a response body as one JSON document, or as newline-separated
123
+ * documents. Returns undefined for an empty or non-JSON body.
124
+ */
125
+ function parseDocuments(body) {
126
+ const text = body.trim();
127
+ if (text === "") {
128
+ return undefined;
129
+ }
130
+ try {
131
+ return [JSON.parse(text)];
132
+ }
133
+ catch {
134
+ // Not a single document.
135
+ }
136
+ try {
137
+ return text.split("\n").filter((line) => line.trim() !== "").map((line) => JSON.parse(line));
138
+ }
139
+ catch {
140
+ return undefined;
141
+ }
142
+ }
143
+ function isErrorDocument(doc) {
144
+ return typeof doc === "object" && doc !== null && !Array.isArray(doc) &&
145
+ Object.keys(doc).length === 1 && "error" in doc;
146
+ }
147
+ /** Error thrown when the exe.dev API reports a failed command. */
97
148
  export class ExeAPIError extends Error {
98
149
  command;
99
150
  status;
100
151
  body;
101
152
  constructor(command, status, body) {
102
- super(`exe.dev API error (${status}): ${body.trim()}`);
153
+ super(`exe.dev API error (${status}): ${errorMessage(body)}`);
103
154
  this.name = "ExeAPIError";
104
155
  this.command = command;
105
156
  this.status = status;
106
157
  this.body = body;
107
158
  }
108
159
  }
160
+ function errorMessage(body) {
161
+ for (const doc of parseDocuments(body) ?? []) {
162
+ if (isErrorDocument(doc)) {
163
+ return String(doc.error);
164
+ }
165
+ }
166
+ return body.trim();
167
+ }
109
168
  //# sourceMappingURL=client.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}