withruntime 0.5.0 → 0.5.1
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/DAYTONA.md +25 -25
- package/E2B.md +1 -1
- package/dist/cli.js +29 -9
- package/dist/daytona/context.d.ts +5 -0
- package/dist/daytona/context.js +3 -0
- package/dist/daytona/filesystem.js +2 -2
- package/dist/daytona/process.js +3 -1
- package/dist/e2b/commands.js +6 -2
- package/dist/products/switching.d.ts +7 -0
- package/dist/ssh.js +13 -1
- package/dist/transport.d.ts +1 -1
- package/dist/transport.js +1 -1
- package/dist/tunnel.d.ts +4 -1
- package/dist/tunnel.js +8 -3
- package/package.json +1 -1
package/DAYTONA.md
CHANGED
|
@@ -54,31 +54,31 @@ package talks only to Runtime.
|
|
|
54
54
|
|
|
55
55
|
## Mapping
|
|
56
56
|
|
|
57
|
-
| Daytona | On Runtime
|
|
58
|
-
| ----------------------------------------------------------------------------- |
|
|
59
|
-
| `daytona.create()`, `create({ snapshot })` | `sandboxes.create`. Daytona's own snapshots (`daytona-*`) are Runtime's stock image. Any other name is a ready Runtime image of that name, then a Runtime snapshot of that name.
|
|
60
|
-
| `create({ image: "python:3.12" })` | Built once as a Runtime image from the registry reference (named after it), then reused. `onSnapshotCreateLogs` receives the build log.
|
|
61
|
-
| `create({ image: Image.debianSlim(...).pipInstall(...)... })` | The Dockerfile Daytona's `Image` stands for, with its local files, built as a Runtime image named after its content and reused.
|
|
62
|
-
| `resources`, `envVars`, `labels`, `name`, `volumes` | `vcpu`, `memoryMiB`, `diskMiB`; environment given to every command; labels; name; Runtime volumes by id or by name.
|
|
63
|
-
| `networkBlockAll`, `networkAllowList`, `domainAllowList`, `outboundProxyUrl` | Runtime network rules (`internet: false`, or an `allow` list); the proxy as `HTTP_PROXY` and `HTTPS_PROXY` for commands.
|
|
64
|
-
| `public: true` | `getPreviewLink(port)` shares the port publicly; without it, privately with a token.
|
|
65
|
-
| `language` | The language of `codeRun`, kept in Daytona's `code-toolbox-language` label so a sandbox found later keeps it.
|
|
66
|
-
| `daytona.get(idOrName)`, `daytona.list({ labels, states, name })` | `sandboxes.get`, or the newest live sandbox with that name; `sandboxes.list`. Awaiting `list()` gives the older `{ items, total }` shape.
|
|
67
|
-
| `process.executeCommand(cmd, cwd, env, timeout)` | `exec` under bash with stderr joined to stdout, as Daytona's `result
|
|
68
|
-
| `process.codeRun(code, { argv, env })` | `python3 -c`, `node -e` or `bun -e` by the sandbox's language. `artifacts.charts` is always empty.
|
|
69
|
-
| `createSession`, `executeSessionCommand`, `getSessionCommandLogs` | One bash per session, kept running: directory, variables and functions carry from one command to the next. `runAsync` returns at once; logs stream to callbacks.
|
|
70
|
-
| `sendSessionCommandInput`, `getSession`, `getSessionCommand`, `deleteSession` | Input to the session's shell, which the running command reads; the commands sent through this object; killing the shell.
|
|
71
|
-
| `fs.uploadFile(s)`, `downloadFile(s)`, `listFiles`, `getFileDetails` | Runtime's files API. Relative paths resolve from `/workspace`. `owner` and `group` are always `""`.
|
|
72
|
-
| `fs.createFolder`, `deleteFile`, `moveFiles`, `setFilePermissions` | `mkdir -m`, `files.remove`, `files.rename`, `chmod` and `sudo chown`.
|
|
73
|
-
| `fs.findFiles`, `searchFiles`, `replaceInFiles` | `grep -rn`, a files glob, and a read and write of each file.
|
|
74
|
-
| `git.clone`, `status`, `add`, `commit`, `push`, `pull`, branches | The sandbox's git. Credentials reach git through the environment, never the command line.
|
|
75
|
-
| `codeInterpreter.runCode`, contexts | Runtime's Python interpreter. The sandbox's `envVars` reach code through a context made once for them.
|
|
76
|
-
| `start`, `stop`, `pause`, `archive`, `delete`, `fork`, `createSnapshot` | `wake`, `pause` (or `stop` when ephemeral), `pause`, `pause`, `stop`, Runtime's fork with memory, a Runtime snapshot with that name.
|
|
77
|
-
| `getPreviewLink(port)` | A Runtime preview. Send its token as `x-runtime-preview-token`; Daytona's header is `x-daytona-preview-token`.
|
|
78
|
-
| `getUserHomeDir`, `getWorkDir`, `user` | `/workspace`, and `daytona`, so `/home/daytona` (linked to `/workspace` the first time something names it) works.
|
|
79
|
-
| `daytona.snapshot.create`, `get`, `list`, `delete` | Runtime images built, found, listed and deleted by name.
|
|
80
|
-
| `daytona.volume.get`, `list`, `delete` | Runtime volumes by name.
|
|
81
|
-
| Error classes | Daytona's names and parents, by HTTP status. Each also carries Runtime's `code`, `hint` and `requestId`, with the original error as `cause`.
|
|
57
|
+
| Daytona | On Runtime |
|
|
58
|
+
| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `daytona.create()`, `create({ snapshot })` | `sandboxes.create`. Daytona's own snapshots (`daytona-*`) are Runtime's stock image. Any other name is a ready Runtime image of that name, then a Runtime snapshot of that name. |
|
|
60
|
+
| `create({ image: "python:3.12" })` | Built once as a Runtime image from the registry reference (named after it), then reused. `onSnapshotCreateLogs` receives the build log. |
|
|
61
|
+
| `create({ image: Image.debianSlim(...).pipInstall(...)... })` | The Dockerfile Daytona's `Image` stands for, with its local files, built as a Runtime image named after its content and reused. |
|
|
62
|
+
| `resources`, `envVars`, `labels`, `name`, `volumes` | `vcpu`, `memoryMiB`, `diskMiB`; environment given to every command; labels; name; Runtime volumes by id or by name. |
|
|
63
|
+
| `networkBlockAll`, `networkAllowList`, `domainAllowList`, `outboundProxyUrl` | Runtime network rules (`internet: false`, or an `allow` list); the proxy as `HTTP_PROXY` and `HTTPS_PROXY` for commands. |
|
|
64
|
+
| `public: true` | `getPreviewLink(port)` shares the port publicly; without it, privately with a token. |
|
|
65
|
+
| `language` | The language of `codeRun`, kept in Daytona's `code-toolbox-language` label so a sandbox found later keeps it. |
|
|
66
|
+
| `daytona.get(idOrName)`, `daytona.list({ labels, states, name })` | `sandboxes.get`, or the newest live sandbox with that name; `sandboxes.list`. Awaiting `list()` gives the older `{ items, total }` shape. |
|
|
67
|
+
| `process.executeCommand(cmd, cwd, env, timeout)` | `exec` under bash with stderr joined to stdout, as Daytona's `result`, streamed so the whole output comes back. A non-zero exit is a result. Past `timeout` (seconds): `DaytonaProcessExecutionTimeoutError`. |
|
|
68
|
+
| `process.codeRun(code, { argv, env })` | `python3 -c`, `node -e` or `bun -e` by the sandbox's language. `artifacts.charts` is always empty. |
|
|
69
|
+
| `createSession`, `executeSessionCommand`, `getSessionCommandLogs` | One bash per session, kept running: directory, variables and functions carry from one command to the next. `runAsync` returns at once; logs stream to callbacks. |
|
|
70
|
+
| `sendSessionCommandInput`, `getSession`, `getSessionCommand`, `deleteSession` | Input to the session's shell, which the running command reads; the commands sent through this object; killing the shell. |
|
|
71
|
+
| `fs.uploadFile(s)`, `downloadFile(s)`, `listFiles`, `getFileDetails` | Runtime's files API. Relative paths resolve from `/workspace`. `owner` and `group` are always `""`. |
|
|
72
|
+
| `fs.createFolder`, `deleteFile`, `moveFiles`, `setFilePermissions` | `mkdir -m`, `files.remove`, `files.rename`, `chmod` and `sudo chown`. |
|
|
73
|
+
| `fs.findFiles`, `searchFiles`, `replaceInFiles` | `grep -rn`, a files glob, and a read and write of each file. |
|
|
74
|
+
| `git.clone`, `status`, `add`, `commit`, `push`, `pull`, branches | The sandbox's git. Credentials reach git through the environment, never the command line. |
|
|
75
|
+
| `codeInterpreter.runCode`, contexts | Runtime's Python interpreter. The sandbox's `envVars` reach code through a context made once for them. |
|
|
76
|
+
| `start`, `stop`, `pause`, `archive`, `delete`, `fork`, `createSnapshot` | `wake`, `pause` (or `stop` when ephemeral), `pause`, `pause`, `stop`, Runtime's fork with memory, a Runtime snapshot with that name. |
|
|
77
|
+
| `getPreviewLink(port)` | A Runtime preview. Send its token as `x-runtime-preview-token`; Daytona's header is `x-daytona-preview-token`. |
|
|
78
|
+
| `getUserHomeDir`, `getWorkDir`, `user` | `/workspace`, and `daytona`, so `/home/daytona` (linked to `/workspace` the first time something names it) works. |
|
|
79
|
+
| `daytona.snapshot.create`, `get`, `list`, `delete` | Runtime images built, found, listed and deleted by name. |
|
|
80
|
+
| `daytona.volume.get`, `list`, `delete` | Runtime volumes by name. |
|
|
81
|
+
| Error classes | Daytona's names and parents, by HTTP status. Each also carries Runtime's `code`, `hint` and `requestId`, with the original error as `cause`. |
|
|
82
82
|
|
|
83
83
|
## Gaps
|
|
84
84
|
|
package/E2B.md
CHANGED
|
@@ -58,7 +58,7 @@ machine. An E2B key (`e2b_...`) is never sent anywhere. If one is passed as
|
|
|
58
58
|
| `envs` | Given to every command and code run through this object. |
|
|
59
59
|
| `allowInternetAccess: false` | `network: { internet: false }`. |
|
|
60
60
|
| `lifecycle.onTimeout` | `onLeaseEnd`: `kill` is `stop`, `pause` is `pause`. |
|
|
61
|
-
| `commands.run(cmd, { cwd, envs, timeoutMs, onStdout, onStderr })` | `exec
|
|
61
|
+
| `commands.run(cmd, { cwd, envs, timeoutMs, onStdout, onStderr })` | `exec`, streamed, so the whole output comes back however large. Default timeout 60 s. `0` means no limit (Runtime's longest, 24 hours). |
|
|
62
62
|
| Non-zero exit | Throws `CommandExitError` with `exitCode`, `stdout`, `stderr` and `error` (`exit status N`). A command killed by a signal has `exitCode` -1. |
|
|
63
63
|
| Command past its timeout | Throws `TimeoutError`. |
|
|
64
64
|
| `commands.run(cmd, { background: true })` | `spawn`. Returns a `CommandHandle` with `pid`, `wait()`, `kill()` (SIGKILL), `sendStdin`, `closeStdin`, `disconnect`, `stdout`, `stderr`, `exitCode`. |
|
package/dist/cli.js
CHANGED
|
@@ -684,7 +684,16 @@ const RIVAL_NAMES = {
|
|
|
684
684
|
cloudflare: "Cloudflare Sandbox",
|
|
685
685
|
fly: "Fly",
|
|
686
686
|
};
|
|
687
|
-
/**
|
|
687
|
+
/** A date as the CLI writes one: 23 September 2026. */
|
|
688
|
+
const longDate = (iso) => new Date(iso).toLocaleDateString("en-GB", {
|
|
689
|
+
day: "numeric",
|
|
690
|
+
month: "long",
|
|
691
|
+
year: "numeric",
|
|
692
|
+
timeZone: "UTC",
|
|
693
|
+
});
|
|
694
|
+
/** One line on the switching credit: how to claim it, or what it did. A
|
|
695
|
+
* switch already on record is named with its provider and date, so it never
|
|
696
|
+
* reads as if this command recorded it, or recorded the provider it names. */
|
|
688
697
|
function switchingLine(summary, provider) {
|
|
689
698
|
const cap = dollars(summary.maxMicros);
|
|
690
699
|
const sw = summary.switch;
|
|
@@ -694,21 +703,32 @@ function switchingLine(summary, provider) {
|
|
|
694
703
|
return `Switching from ${RIVAL_NAMES[provider]}? Run \`${me} switch --from ${provider}\` before your first top-up and we match that top-up, up to ${cap}.`;
|
|
695
704
|
}
|
|
696
705
|
const from = RIVAL_NAMES[sw.provider];
|
|
706
|
+
const recorded = `This organization's switch from ${from} was recorded on ${longDate(sw.recordedAt)}`;
|
|
707
|
+
const once = provider && provider !== sw.provider
|
|
708
|
+
? ` A switch is recorded once, so it stays ${from}, not ${RIVAL_NAMES[provider]}.`
|
|
709
|
+
: "";
|
|
697
710
|
switch (sw.status) {
|
|
698
711
|
case "pending":
|
|
699
|
-
return
|
|
712
|
+
return `${recorded}: its first top-up is matched, up to ${cap}, once the payment settles.${once}`;
|
|
700
713
|
case "paid":
|
|
701
|
-
return
|
|
714
|
+
return `${recorded} and matched its first top-up: ${dollars(sw.creditMicros)} of credit.${once}`;
|
|
702
715
|
case "superseded":
|
|
703
|
-
return
|
|
716
|
+
return `${recorded}; its first top-up was matched by a referral instead, which is never less than the switching credit.${once}`;
|
|
704
717
|
case "reversed":
|
|
705
|
-
return
|
|
718
|
+
return `${recorded}; the top-up it matched was refunded, so the credit was taken back.${once}`;
|
|
706
719
|
case "refused":
|
|
707
720
|
return sw.refusedReason === "program_off"
|
|
708
|
-
?
|
|
709
|
-
:
|
|
721
|
+
? `${recorded}; the switching credit was paused when its first top-up arrived.${once}`
|
|
722
|
+
: `${recorded}; its first top-up was not matched. Ask support if you think that is wrong.${once}`;
|
|
710
723
|
}
|
|
711
724
|
}
|
|
725
|
+
/** The sandboxes the free trial paid for, said beside what was priced. */
|
|
726
|
+
function trialNote(trial, all) {
|
|
727
|
+
if (!trial)
|
|
728
|
+
return "";
|
|
729
|
+
const which = trial === all ? (all === 1 ? "It" : "All of them") : `${trial} of them`;
|
|
730
|
+
return ` ${which} ran on the free trial, which charged nothing; Runtime's side prices ${trial === 1 ? "it" : "them"} at the standard rates, what the same work costs on paid credit.`;
|
|
731
|
+
}
|
|
712
732
|
async function switching(command, argv, env, out) {
|
|
713
733
|
const args = parse(argv);
|
|
714
734
|
const print = (human, value) => out.write(out.json ? JSON.stringify(value) : human);
|
|
@@ -756,13 +776,13 @@ async function switching(command, argv, env, out) {
|
|
|
756
776
|
: month >= 0n
|
|
757
777
|
? `, about ${dollars(month)} a month.`
|
|
758
778
|
: `, about ${dollars(-month)} a month more on Runtime.`}`,
|
|
759
|
-
`Priced: ${c.usage.sandboxes.toLocaleString("en-US")} sandbox${c.usage.sandboxes === 1 ? "" : "es"}, ${(c.usage.runSeconds / 3600).toLocaleString("en-US", { maximumFractionDigits: 2 })} hours running.${c.usage.unpricedSandboxes ? ` ${c.usage.unpricedSandboxes} left out: ${c.rival.name} publishes no price for their size.` : ""}`,
|
|
779
|
+
`Priced: ${c.usage.sandboxes.toLocaleString("en-US")} sandbox${c.usage.sandboxes === 1 ? "" : "es"}, ${(c.usage.runSeconds / 3600).toLocaleString("en-US", { maximumFractionDigits: 2 })} hours running.${trialNote(c.usage.trialSandboxes, c.usage.sandboxes)}${c.usage.unpricedSandboxes ? ` ${c.usage.unpricedSandboxes} left out: ${c.rival.name} publishes no price for their size.` : ""}`,
|
|
760
780
|
]
|
|
761
781
|
: [
|
|
762
782
|
`No settled sandboxes in your last ${c.window.days} days yet, so this prices an example: ${c.usage.sandboxes.toLocaleString("en-US")} runs of ${(c.usage.runSeconds / c.usage.sandboxes).toLocaleString("en-US")} seconds using ${(c.usage.activeCpuSeconds / c.usage.sandboxes).toLocaleString("en-US")} CPU-seconds each.`,
|
|
763
783
|
`The example: ${figures}.`,
|
|
764
784
|
];
|
|
765
|
-
lines.push(`${c.rival.name}'s published rates, checked ${
|
|
785
|
+
lines.push(`${c.rival.name}'s published rates, checked ${longDate(`${c.rival.checked}T12:00:00Z`)}: ${c.rival.rates.join("; ")}. Compute only: storage, network, plan fees and free allowances are left out.`);
|
|
766
786
|
const line = switchingLine(c.switching, Object.hasOwn(SWITCH_FROM, from) ? SWITCH_FROM[from] : undefined);
|
|
767
787
|
if (line)
|
|
768
788
|
lines.push("", line);
|
|
@@ -19,6 +19,11 @@ export declare const HOME = "/workspace";
|
|
|
19
19
|
export declare const HOME_LINK = "[ -e /home/daytona ] || sudo ln -s /workspace /home/daytona";
|
|
20
20
|
/** Commands without a timeout run until the sandbox ends: Runtime's longest. */
|
|
21
21
|
export declare const LONGEST_MS = 86400000;
|
|
22
|
+
/** Given to exec so it streams and returns the whole output, as Daytona does:
|
|
23
|
+
* an exec with no output callback returns at most 64 KiB of each stream. */
|
|
24
|
+
export declare const WHOLE_OUTPUT: {
|
|
25
|
+
readonly onStdout: () => undefined;
|
|
26
|
+
};
|
|
22
27
|
/** A Daytona path as a Runtime one: relative paths (and `~`) resolve from the
|
|
23
28
|
* working directory, /workspace on Runtime. */
|
|
24
29
|
export declare function resolvePath(path: string): string;
|
package/dist/daytona/context.js
CHANGED
|
@@ -5,6 +5,9 @@ export const HOME = "/workspace";
|
|
|
5
5
|
export const HOME_LINK = `[ -e ${DAYTONA_HOME} ] || sudo ln -s ${HOME} ${DAYTONA_HOME}`;
|
|
6
6
|
/** Commands without a timeout run until the sandbox ends: Runtime's longest. */
|
|
7
7
|
export const LONGEST_MS = 86_400_000;
|
|
8
|
+
/** Given to exec so it streams and returns the whole output, as Daytona does:
|
|
9
|
+
* an exec with no output callback returns at most 64 KiB of each stream. */
|
|
10
|
+
export const WHOLE_OUTPUT = { onStdout: () => undefined };
|
|
8
11
|
/** A Daytona path as a Runtime one: relative paths (and `~`) resolve from the
|
|
9
12
|
* working directory, /workspace on Runtime. */
|
|
10
13
|
export function resolvePath(path) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolvePath } from "./context.js";
|
|
1
|
+
import { resolvePath, WHOLE_OUTPUT } from "./context.js";
|
|
2
2
|
import { DaytonaError, DaytonaFileNotFoundError, guard } from "./errors.js";
|
|
3
3
|
const PERMISSIONS = ["---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"];
|
|
4
4
|
function fileInfo(entry) {
|
|
@@ -112,7 +112,7 @@ export class FileSystem {
|
|
|
112
112
|
async findFiles(path, pattern) {
|
|
113
113
|
const target = await this.#path(path);
|
|
114
114
|
const runtime = await this.#ctx.live();
|
|
115
|
-
const result = await guard("sandbox", () => runtime.exec(["grep", "-rnI", "--", pattern, target]));
|
|
115
|
+
const result = await guard("sandbox", () => runtime.exec(["grep", "-rnI", "--", pattern, target], WHOLE_OUTPUT));
|
|
116
116
|
if (result.exitCode !== 0 && result.exitCode !== 1)
|
|
117
117
|
throw new DaytonaError(result.stderr.trim() || "grep failed", 400);
|
|
118
118
|
return result.stdout
|
package/dist/daytona/process.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { HOME, LONGEST_MS, quote, resolvePath } from "./context.js";
|
|
2
|
+
import { HOME, LONGEST_MS, quote, resolvePath, WHOLE_OUTPUT, } from "./context.js";
|
|
3
3
|
import { DaytonaCommandAlreadyCompletedError, DaytonaConflictError, DaytonaNotFoundError, DaytonaProcessExecutionTimeoutError, DaytonaSessionEndedError, guard, NotSupportedError, } from "./errors.js";
|
|
4
4
|
export class CodeRunParams {
|
|
5
5
|
argv;
|
|
@@ -76,6 +76,7 @@ export class Process {
|
|
|
76
76
|
cwd: cwd === undefined ? HOME : resolvePath(cwd),
|
|
77
77
|
...this.#env(env),
|
|
78
78
|
timeoutMs,
|
|
79
|
+
...WHOLE_OUTPUT,
|
|
79
80
|
}));
|
|
80
81
|
if (result.timedOut)
|
|
81
82
|
throw new DaytonaProcessExecutionTimeoutError(`Command timed out after ${timeout} s. Pass a larger timeout, or 0 for none.`, 408);
|
|
@@ -97,6 +98,7 @@ export class Process {
|
|
|
97
98
|
cwd: HOME,
|
|
98
99
|
...this.#env(params.env),
|
|
99
100
|
timeoutMs,
|
|
101
|
+
...WHOLE_OUTPUT,
|
|
100
102
|
}));
|
|
101
103
|
if (result.timedOut)
|
|
102
104
|
throw new DaytonaProcessExecutionTimeoutError(`Code run timed out after ${timeout} s.`, 408);
|
package/dist/e2b/commands.js
CHANGED
|
@@ -68,12 +68,16 @@ export class Commands {
|
|
|
68
68
|
const handle = await this.#start(cmd, opts, timeoutMs);
|
|
69
69
|
return opts.background ? handle : handle.wait();
|
|
70
70
|
}
|
|
71
|
+
/* Always streamed, so the result holds the whole output as E2B's does: an
|
|
72
|
+
exec with no output callback returns at most 64 KiB of each stream. */
|
|
73
|
+
const onStdout = opts.onStdout;
|
|
74
|
+
const onStderr = opts.onStderr;
|
|
71
75
|
const result = await guard("sandbox", () => this.#ctx.runtime.exec(cmd, {
|
|
72
76
|
...(opts.cwd === undefined ? {} : { cwd: opts.cwd }),
|
|
73
77
|
...this.#env(opts.envs),
|
|
74
78
|
timeoutMs,
|
|
75
|
-
|
|
76
|
-
...(
|
|
79
|
+
onStdout: (text) => void onStdout?.(text),
|
|
80
|
+
...(onStderr ? { onStderr: (text) => void onStderr(text) } : {}),
|
|
77
81
|
...(opts.signal ? { signal: opts.signal } : {}),
|
|
78
82
|
}));
|
|
79
83
|
return settle(result, timeoutMs);
|
|
@@ -52,7 +52,14 @@ export type UsageComparison = {
|
|
|
52
52
|
runSeconds: number;
|
|
53
53
|
activeCpuSeconds: number;
|
|
54
54
|
unpricedSandboxes: number;
|
|
55
|
+
/** Of `sandboxes`, how many the free trial paid for, and their running
|
|
56
|
+
* time. The trial charged nothing for them; `runtimeMicros` prices them at
|
|
57
|
+
* Runtime's standard rates, what the same work costs on paid credit. */
|
|
58
|
+
trialSandboxes: number;
|
|
59
|
+
trialRunSeconds: number;
|
|
55
60
|
};
|
|
61
|
+
/** What the usage costs on Runtime on paid credit: each paid sandbox at the
|
|
62
|
+
* rates it was quoted, a trial sandbox at the standard rates. */
|
|
56
63
|
runtimeMicros: string;
|
|
57
64
|
rivalMicros: string;
|
|
58
65
|
/** Rival less Runtime: negative when the rival would cost less. */
|
package/dist/ssh.js
CHANGED
|
@@ -265,9 +265,21 @@ export async function portForwardCommand(argv, out, connect, me) {
|
|
|
265
265
|
const sbx = await resolveSandbox(await connect(), target);
|
|
266
266
|
const tunnel = await sbx.tunnel();
|
|
267
267
|
const forwards = [];
|
|
268
|
+
/* A connection the sandbox refused is closed at once, which a browser or
|
|
269
|
+
curl reports only as an empty reply, so say why here, once per reason. */
|
|
270
|
+
const said = new Set();
|
|
271
|
+
const refused = (remote) => (error) => {
|
|
272
|
+
const line = `Port ${remote}: ${error.message}${error.hint ? ` ${error.hint}` : ""}`;
|
|
273
|
+
if (said.has(line))
|
|
274
|
+
return;
|
|
275
|
+
said.add(line);
|
|
276
|
+
out.error(out.json
|
|
277
|
+
? JSON.stringify({ port: remote, error: { code: error.code, message: error.message } })
|
|
278
|
+
: line);
|
|
279
|
+
};
|
|
268
280
|
try {
|
|
269
281
|
for (const { local, remote } of ports)
|
|
270
|
-
forwards.push(await tunnel.forward(remote, { localPort: local, host: address }));
|
|
282
|
+
forwards.push(await tunnel.forward(remote, { localPort: local, host: address, onError: refused(remote) }));
|
|
271
283
|
}
|
|
272
284
|
catch (error) {
|
|
273
285
|
tunnel.close();
|
package/dist/transport.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RuntimeError } from "./errors.js";
|
|
2
|
-
export declare const VERSION = "0.5.
|
|
2
|
+
export declare const VERSION = "0.5.1";
|
|
3
3
|
export declare const DEFAULT_BASE_URL = "https://api.withruntime.com";
|
|
4
4
|
export type RequestOptions = {
|
|
5
5
|
/** Sent as Idempotency-Key. Omit it: the SDK makes one per call and keeps
|
package/dist/transport.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ConnectionError, errorFor, RuntimeError, WAITS_FOR_ROOM } from "./errors.js";
|
|
2
2
|
import { describeRoute, envFetch, openWebSocket } from "./proxy.js";
|
|
3
|
-
export const VERSION = "0.5.
|
|
3
|
+
export const VERSION = "0.5.1";
|
|
4
4
|
export const DEFAULT_BASE_URL = "https://api.withruntime.com";
|
|
5
5
|
export function apiOrigin(value) {
|
|
6
6
|
const url = new URL(value);
|
package/dist/tunnel.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RuntimeError } from "./errors.js";
|
|
1
2
|
/** One TCP connection (or SSH login) inside a tunnel. Set the handlers before
|
|
2
3
|
* data can arrive, which is as soon as the promise that gave you this resolves. */
|
|
3
4
|
export declare class TunnelStream {
|
|
@@ -49,10 +50,12 @@ export declare class Tunnel {
|
|
|
49
50
|
* (the text of an OpenSSH `.pub` file). Speak SSH over the stream. */
|
|
50
51
|
ssh(publicKey: string): Promise<TunnelStream>;
|
|
51
52
|
/** Listens on a local port and carries each connection to `port` in the
|
|
52
|
-
* sandbox. Node and Bun only.
|
|
53
|
+
* sandbox. Node and Bun only. A connection the sandbox cannot open, such as
|
|
54
|
+
* one to a port nothing listens on, is closed at once; `onError` says why. */
|
|
53
55
|
forward(port: number, options?: {
|
|
54
56
|
localPort?: number;
|
|
55
57
|
host?: string;
|
|
58
|
+
onError?: (error: RuntimeError) => void;
|
|
56
59
|
}): Promise<PortForward>;
|
|
57
60
|
/** Closes the tunnel and every connection in it. */
|
|
58
61
|
close(): void;
|
package/dist/tunnel.js
CHANGED
|
@@ -160,7 +160,8 @@ export class Tunnel {
|
|
|
160
160
|
return this.#open(`ssh ${publicKey.trim()}`);
|
|
161
161
|
}
|
|
162
162
|
/** Listens on a local port and carries each connection to `port` in the
|
|
163
|
-
* sandbox. Node and Bun only.
|
|
163
|
+
* sandbox. Node and Bun only. A connection the sandbox cannot open, such as
|
|
164
|
+
* one to a port nothing listens on, is closed at once; `onError` says why. */
|
|
164
165
|
async forward(port, options = {}) {
|
|
165
166
|
const net = await import("node:net");
|
|
166
167
|
const host = options.host ?? "127.0.0.1";
|
|
@@ -183,7 +184,11 @@ export class Tunnel {
|
|
|
183
184
|
socket.on("end", () => stream.end());
|
|
184
185
|
socket.on("close", () => stream.close());
|
|
185
186
|
socket.resume();
|
|
186
|
-
}, () =>
|
|
187
|
+
}, (error) => {
|
|
188
|
+
socket.destroy();
|
|
189
|
+
if (error instanceof RuntimeError)
|
|
190
|
+
options.onError?.(error);
|
|
191
|
+
});
|
|
187
192
|
});
|
|
188
193
|
await new Promise((resolve, reject) => {
|
|
189
194
|
server.once("error", reject);
|
|
@@ -217,7 +222,7 @@ export class Tunnel {
|
|
|
217
222
|
message: `Nothing is listening on port ${port} in the sandbox.`,
|
|
218
223
|
code: "port_closed",
|
|
219
224
|
status: 0,
|
|
220
|
-
hint: "Start the server
|
|
225
|
+
hint: "Start the server with spawn (`runtime sandbox spawn <id> -- <command>`, sbx.spawn()), which keeps it running: a process started by exec ends with its command. Let it listen on 127.0.0.1, ::1 or every address.",
|
|
221
226
|
})
|
|
222
227
|
: error.message === "reserved"
|
|
223
228
|
? new RuntimeError({
|