hyperbrane 0.1.1 → 0.2.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 +81 -11
- package/dist/agent/agentClient.d.ts +5 -0
- package/dist/agent/agentClient.js +4 -0
- package/dist/agent/agentService.d.ts +9 -30
- package/dist/agent/agentService.js +62 -293
- package/dist/agent/agentStorage.d.ts +1 -20
- package/dist/agent/agentStorage.js +0 -30
- package/dist/agent/api.d.ts +89 -133
- package/dist/agent/api.js +4 -4
- package/dist/agent/client.d.ts +3 -1
- package/dist/agent/client.js +2 -3
- package/dist/agent/control.d.ts +23 -0
- package/dist/agent/control.js +479 -0
- package/dist/agent/hostTranscript.d.ts +0 -6
- package/dist/agent/hostTranscript.js +3 -14
- package/dist/agent/session/backend.d.ts +17 -30
- package/dist/agent/session/claude/home.d.ts +0 -1
- package/dist/agent/session/claude/home.js +0 -1
- package/dist/agent/session/claude/provision.d.ts +10 -0
- package/dist/agent/session/claude/provision.js +17 -0
- package/dist/agent/session/claude/transcript.d.ts +2 -7
- package/dist/agent/session/claude/transcript.js +68 -14
- package/dist/agent/session/client.d.ts +1 -10
- package/dist/agent/session/client.js +0 -3
- package/dist/agent/session/codex/provision.d.ts +14 -0
- package/dist/agent/session/codex/provision.js +25 -0
- package/dist/agent/session/fileTranscript.d.ts +6 -49
- package/dist/agent/session/fileTranscript.js +26 -82
- package/dist/agent/session/install.d.ts +5 -0
- package/dist/agent/session/install.js +35 -0
- package/dist/agent/session/sessionStorage.d.ts +4 -35
- package/dist/agent/session/sessionStorage.js +10 -16
- package/dist/agent/session/sharedConnection.d.ts +0 -8
- package/dist/agent/session/sharedConnection.js +0 -7
- package/dist/agent/session/storedTranscripts.d.ts +1 -6
- package/dist/agent/session/storedTranscripts.js +0 -3
- package/dist/agent/session/worker/jsonRpc.d.ts +2 -13
- package/dist/agent/session/worker/jsonRpc.js +2 -11
- package/dist/agent/session/worker/protocol.d.ts +27 -18
- package/dist/agent/session/worker/protocol.js +32 -16
- package/dist/agent/session/worker/workerParams.d.ts +0 -5
- package/dist/agent/session/worker/workerParams.js +15 -14
- package/dist/agent/session/workerConnection.d.ts +2 -13
- package/dist/agent/session/workerConnection.js +7 -12
- package/dist/agent/session/workerGatewayConnection.d.ts +18 -0
- package/dist/agent/session/workerGatewayConnection.js +86 -0
- package/dist/agent/vendor-types/manifest.json +87 -0
- package/dist/agent/workerBundle.d.ts +4 -19
- package/dist/agent/workerBundle.generated.js +1 -1
- package/dist/agent/workerBundle.js +3 -30
- package/dist/agent/workerReplacement.d.ts +3 -0
- package/dist/agent/workerReplacement.js +49 -0
- package/dist/cli/agent.js +44 -16
- package/dist/cli/command.d.ts +10 -11
- package/dist/cli/command.js +0 -4
- package/dist/cli/dimension.js +6 -6
- package/dist/cli/gateway.js +3 -6
- package/dist/cli/main.js +14 -31
- package/dist/cli/streamPrinter.d.ts +2 -10
- package/dist/cli/streamPrinter.js +16 -46
- package/dist/cli/table.d.ts +0 -3
- package/dist/cli/table.js +0 -4
- package/dist/dimension/api.d.ts +39 -27
- package/dist/dimension/api.js +28 -47
- package/dist/dimension/client.d.ts +5 -0
- package/dist/dimension/client.js +4 -0
- package/dist/dimension/control.d.ts +21 -0
- package/dist/dimension/control.js +328 -0
- package/dist/dimension/image.d.ts +0 -5
- package/dist/dimension/image.js +0 -8
- package/dist/dimension/imageFiles.generated.js +1 -1
- package/dist/dimension/paths.d.ts +5 -4
- package/dist/dimension/paths.js +15 -6
- package/dist/dimension/service.d.ts +8 -21
- package/dist/dimension/service.js +60 -224
- package/dist/dimension/transport-worker.d.ts +1 -0
- package/dist/dimension/transport-worker.js +151 -0
- package/dist/dimension/transportBundle.d.ts +2 -0
- package/dist/dimension/transportBundle.generated.d.ts +1 -0
- package/dist/dimension/transportBundle.generated.js +2 -0
- package/dist/dimension/transportBundle.js +4 -0
- package/dist/dimension/transportClient.d.ts +10 -0
- package/dist/dimension/transportClient.js +115 -0
- package/dist/dimension/transportProtocol.d.ts +53 -0
- package/dist/dimension/transportProtocol.js +8 -0
- package/dist/index.d.ts +5 -8
- package/dist/index.js +5 -16
- package/dist/lib/control.d.ts +8 -0
- package/dist/lib/control.js +62 -0
- package/dist/lib/docker.d.ts +0 -22
- package/dist/lib/docker.js +0 -10
- package/dist/lib/image.d.ts +0 -3
- package/dist/lib/image.js +0 -2
- package/dist/lib/jsonDirectory.d.ts +0 -13
- package/dist/lib/jsonDirectory.js +10 -15
- package/dist/lib/scriptBundle.d.ts +6 -0
- package/dist/lib/scriptBundle.js +28 -0
- package/dist/lib/sh.d.ts +2 -0
- package/dist/lib/sh.js +8 -0
- package/dist/lib/sleep.d.ts +0 -1
- package/dist/lib/sleep.js +0 -1
- package/dist/lib/streamFetch.d.ts +15 -0
- package/dist/lib/streamFetch.js +150 -0
- package/dist/lib/tar.d.ts +0 -1
- package/dist/lib/tar.js +5 -11
- package/dist/portal-gateway/api.d.ts +13 -44
- package/dist/portal-gateway/api.js +1 -3
- package/dist/portal-gateway/control.d.ts +42 -0
- package/dist/portal-gateway/control.js +657 -0
- package/dist/portal-gateway/gatewayClient.d.ts +5 -0
- package/dist/portal-gateway/gatewayClient.js +4 -0
- package/dist/portal-gateway/gatewayService.d.ts +8 -35
- package/dist/portal-gateway/gatewayService.js +79 -253
- package/dist/portal-gateway/image.d.ts +1 -2
- package/dist/portal-gateway/image.js +1 -3
- package/dist/portal-gateway/imageFiles.generated.js +3 -3
- package/dist/portal-gateway/portalClient.d.ts +5 -0
- package/dist/portal-gateway/portalClient.js +4 -0
- package/dist/portal-gateway/portalService.d.ts +5 -47
- package/dist/portal-gateway/portalService.js +19 -317
- package/dist/portal-gateway/transport-worker.d.ts +1 -0
- package/dist/portal-gateway/transport-worker.js +205 -0
- package/dist/portal-gateway/transportBundle.d.ts +2 -0
- package/dist/portal-gateway/transportBundle.generated.d.ts +1 -0
- package/dist/portal-gateway/transportBundle.generated.js +2 -0
- package/dist/portal-gateway/transportBundle.js +4 -0
- package/dist/portal-gateway/upstream.d.ts +12 -82
- package/dist/portal-gateway/upstream.js +0 -1
- package/dist/sandbox/api.d.ts +121 -178
- package/dist/sandbox/api.js +5 -7
- package/dist/sandbox/client.d.ts +2 -0
- package/dist/sandbox/client.js +4 -0
- package/dist/sandbox/data-endpoints/dataEndpoints.d.ts +13 -0
- package/dist/sandbox/data-endpoints/dataEndpoints.js +100 -0
- package/dist/sandbox/data-endpoints/firewalledPorts.d.ts +4 -0
- package/dist/sandbox/data-endpoints/firewalledPorts.js +28 -0
- package/dist/sandbox/lib/httpOverStream.d.ts +3 -0
- package/dist/sandbox/lib/httpOverStream.js +21 -0
- package/dist/sandbox/lib/remoteFileOperations.d.ts +10 -0
- package/dist/sandbox/lib/remoteFileOperations.js +70 -0
- package/dist/sandbox/lib/shellCommand.d.ts +3 -0
- package/dist/sandbox/lib/shellCommand.js +7 -0
- package/dist/sandbox/providers/boat/boatService.d.ts +2 -0
- package/dist/sandbox/providers/boat/boatService.js +321 -0
- package/dist/sandbox/providers/exedev/exeDevService.d.ts +2 -0
- package/dist/sandbox/providers/exedev/exeDevService.js +127 -0
- package/dist/sandbox/providers/exedev/exeDevToken.d.ts +1 -0
- package/dist/sandbox/providers/exedev/exeDevToken.js +54 -0
- package/dist/sandbox/providers/local/localService.d.ts +2 -0
- package/dist/sandbox/providers/local/localService.js +219 -0
- package/dist/sandbox/providers/ssh/sshService.d.ts +2 -0
- package/dist/sandbox/providers/ssh/sshService.js +82 -0
- package/dist/sandbox/providers/ssh/sshTransport.d.ts +12 -0
- package/dist/sandbox/providers/ssh/sshTransport.js +216 -0
- package/dist/sandbox/service.d.ts +2 -0
- package/dist/sandbox/service.js +18 -0
- package/dist/sandbox/storage/descriptors.d.ts +6 -0
- package/dist/sandbox/storage/descriptors.js +25 -0
- package/dist/sandbox/storage/sandboxStorage.d.ts +11 -0
- package/dist/sandbox/storage/sandboxStorage.js +42 -0
- package/dist/upstreams/anthropic.d.ts +0 -5
- package/dist/upstreams/anthropic.js +1 -8
- package/dist/upstreams/index.d.ts +2 -6
- package/dist/upstreams/index.js +0 -4
- package/dist/upstreams/openai.d.ts +0 -5
- package/dist/upstreams/openai.js +1 -13
- package/package.json +12 -4
- package/dist/agent/gatewaySignIns.d.ts +0 -5
- package/dist/agent/gatewaySignIns.js +0 -11
- package/dist/agent/session/codex/home.d.ts +0 -2
- package/dist/agent/session/codex/home.js +0 -4
- package/dist/agent/session/codex/messages.d.ts +0 -30
- package/dist/agent/session/codex/messages.js +0 -135
- package/dist/agent/session/codex/transcript.d.ts +0 -12
- package/dist/agent/session/codex/transcript.js +0 -22
- package/dist/agent/session/sdkMessages.d.ts +0 -15
- package/dist/agent/session/sdkMessages.js +0 -28
- package/dist/agent/session/workerSseConnection.d.ts +0 -15
- package/dist/agent/session/workerSseConnection.js +0 -26
- package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.d.ts +0 -10
- package/dist/agent/vendor-types/codex/app-server/AbsolutePathBuf.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.d.ts +0 -2
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputBody.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.d.ts +0 -22
- package/dist/agent/vendor-types/codex/app-server/FunctionCallOutputContentItem.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/ImageDetail.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/ImageDetail.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.d.ts +0 -5
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationFailure.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.d.ts +0 -11
- package/dist/agent/vendor-types/codex/app-server/ImageGenerationItem.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.d.ts +0 -23
- package/dist/agent/vendor-types/codex/app-server/LegacyAppPathString.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/MessagePhase.d.ts +0 -7
- package/dist/agent/vendor-types/codex/app-server/MessagePhase.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.d.ts +0 -4
- package/dist/agent/vendor-types/codex/app-server/ReasoningEffort.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/SleepItem.d.ts +0 -7
- package/dist/agent/vendor-types/codex/app-server/SleepItem.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/WebSearchItem.d.ts +0 -14
- package/dist/agent/vendor-types/codex/app-server/WebSearchItem.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.d.ts +0 -3
- package/dist/agent/vendor-types/codex/app-server/serde_json/JsonValue.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/AgentMessageDelivery.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.d.ts +0 -4
- package/dist/agent/vendor-types/codex/app-server/v2/AsyncUserInputQuestion.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.d.ts +0 -4
- package/dist/agent/vendor-types/codex/app-server/v2/ByteRange.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.d.ts +0 -5
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentState.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentStatus.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentTool.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/CollabAgentToolCallStatus.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.d.ts +0 -19
- package/dist/agent/vendor-types/codex/app-server/v2/CommandAction.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionSource.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/CommandExecutionStatus.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.d.ts +0 -10
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallOutputContentItem.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/DynamicToolCallStatus.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.d.ts +0 -6
- package/dist/agent/vendor-types/codex/app-server/v2/FileUpdateChange.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.d.ts +0 -4
- package/dist/agent/vendor-types/codex/app-server/v2/HookPromptFragment.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppDisplayMode.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.d.ts +0 -8
- package/dist/agent/vendor-types/codex/app-server/v2/McpAppUi.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.d.ts +0 -7
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallAppContext.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.d.ts +0 -3
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallError.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.d.ts +0 -6
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallResult.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/McpToolCallStatus.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.d.ts +0 -5
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitation.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.d.ts +0 -6
- package/dist/agent/vendor-types/codex/app-server/v2/MemoryCitationEntry.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/PatchApplyStatus.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.d.ts +0 -8
- package/dist/agent/vendor-types/codex/app-server/v2/PatchChangeKind.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.d.ts +0 -1
- package/dist/agent/vendor-types/codex/app-server/v2/SubAgentActivityKind.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/TextElement.d.ts +0 -11
- package/dist/agent/vendor-types/codex/app-server/v2/TextElement.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.d.ts +0 -215
- package/dist/agent/vendor-types/codex/app-server/v2/ThreadItem.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/UserInput.d.ts +0 -35
- package/dist/agent/vendor-types/codex/app-server/v2/UserInput.js +0 -2
- package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.d.ts +0 -14
- package/dist/agent/vendor-types/codex/app-server/v2/WebSearchAction.js +0 -2
- package/dist/cli/services.d.ts +0 -19
- package/dist/cli/services.js +0 -33
- package/dist/dimension/http.d.ts +0 -7
- package/dist/dimension/http.js +0 -77
- package/dist/dimension/storage.d.ts +0 -39
- package/dist/dimension/storage.js +0 -79
- package/dist/portal-gateway/fakes.d.ts +0 -3
- package/dist/portal-gateway/fakes.js +0 -11
- package/dist/portal-gateway/gatewayStorage.d.ts +0 -66
- package/dist/portal-gateway/gatewayStorage.js +0 -125
- package/dist/portal-gateway/portalStorage.d.ts +0 -34
- package/dist/portal-gateway/portalStorage.js +0 -44
- package/dist/sandbox/boatService.d.ts +0 -52
- package/dist/sandbox/boatService.js +0 -343
- package/dist/sandbox/containerService.d.ts +0 -15
- package/dist/sandbox/containerService.js +0 -184
- package/dist/sandbox/endpointProxy.d.ts +0 -90
- package/dist/sandbox/endpointProxy.js +0 -701
- package/dist/sandbox/exeDevAuth.d.ts +0 -16
- package/dist/sandbox/exeDevAuth.js +0 -49
- package/dist/sandbox/localService.d.ts +0 -19
- package/dist/sandbox/localService.js +0 -200
- package/dist/sandbox/sseConnection.d.ts +0 -41
- package/dist/sandbox/sseConnection.js +0 -166
- package/dist/sandbox/sshService.d.ts +0 -30
- package/dist/sandbox/sshService.js +0 -680
package/dist/sandbox/api.d.ts
CHANGED
|
@@ -1,212 +1,155 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
1
|
+
/** @sealed */
|
|
2
|
+
export interface SandboxFileOperations {
|
|
3
|
+
readFile(path: string): Promise<Uint8Array>;
|
|
4
|
+
readFileRange(path: string, start: number, end: number): Promise<Uint8Array>;
|
|
5
|
+
writeFile(path: string, data: string | Uint8Array): Promise<void>;
|
|
6
|
+
writeFileExclusive(path: string, data: string | Uint8Array): Promise<boolean>;
|
|
7
|
+
removeFile(path: string): Promise<void>;
|
|
8
|
+
createDirectory(path: string): Promise<void>;
|
|
9
|
+
removeDirectory(path: string): Promise<void>;
|
|
10
|
+
listDirectory(path: string): Promise<string[]>;
|
|
11
|
+
stat(path: string): Promise<SandboxFileInfo>;
|
|
12
|
+
homeDirectory(): Promise<string>;
|
|
13
|
+
}
|
|
14
|
+
/** @sealed */
|
|
15
|
+
export interface SandboxLifecycle {
|
|
16
|
+
create(spec: SandboxSpec): Promise<[SandboxAuthContext, SandboxDescriptor]>;
|
|
17
|
+
open(): Promise<SandboxDescriptor>;
|
|
18
|
+
get(): Promise<SandboxDescriptor>;
|
|
19
|
+
state(): Promise<"running" | "stopped">;
|
|
20
|
+
start(): Promise<void>;
|
|
21
|
+
destroy(): Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
/** @sealed */
|
|
24
|
+
export interface SandboxCommands {
|
|
25
|
+
runCommand(cmd: string[], options?: RunCommandOptions): Promise<RunCommandResult>;
|
|
26
|
+
}
|
|
27
|
+
/** @sealed */
|
|
28
|
+
export interface SandboxDataEndpoint {
|
|
29
|
+
openEndpoint(name: string): Promise<SandboxEndpoint>;
|
|
30
|
+
closeEndpoint(name: string): Promise<void>;
|
|
31
|
+
listEndpoints(): Promise<SandboxEndpoint[]>;
|
|
32
|
+
credentials(name: string, options?: {
|
|
33
|
+
ttlSeconds?: number;
|
|
34
|
+
}): Promise<SandboxCredentials>;
|
|
35
|
+
}
|
|
36
|
+
/** @sealed */
|
|
37
|
+
export interface SandboxExpiration {
|
|
38
|
+
hasExpiration(): boolean;
|
|
39
|
+
getExpiration(): Promise<Date | null>;
|
|
40
|
+
setExpiration(seconds: number | null): Promise<Date | null>;
|
|
41
|
+
}
|
|
42
|
+
/** @sealed */
|
|
43
|
+
export interface SandboxShell {
|
|
44
|
+
hasShell(): boolean;
|
|
45
|
+
sshEndpoint(): Promise<SandboxSshEndpoint>;
|
|
46
|
+
}
|
|
47
|
+
/** @sealed */
|
|
48
|
+
export interface SandboxNetwork {
|
|
49
|
+
fetch(url: string, init?: SandboxFetchInit): Promise<Response>;
|
|
50
|
+
connect(path: string): Promise<SandboxConnection>;
|
|
51
|
+
}
|
|
52
|
+
/** @sealed */
|
|
53
|
+
export interface SandboxClient extends SandboxFileOperations, SandboxCommands, SandboxNetwork, SandboxLifecycle, SandboxDataEndpoint, SandboxShell, SandboxExpiration {
|
|
54
|
+
}
|
|
55
|
+
/** @sealed */
|
|
56
|
+
export interface SandboxService extends SandboxFileOperations, SandboxCommands, SandboxNetwork, SandboxLifecycle, SandboxDataEndpoint, SandboxShell, SandboxExpiration {
|
|
57
|
+
}
|
|
12
58
|
export type SandboxFileInfo = {
|
|
13
59
|
readonly type: "file" | "directory" | "other";
|
|
14
60
|
readonly uid: number;
|
|
15
61
|
readonly gid: number;
|
|
16
|
-
/** In bytes; what the system reports for anything that isn't a file. */
|
|
17
62
|
readonly size: number;
|
|
18
63
|
};
|
|
19
64
|
export type SandboxFetchInit = {
|
|
20
65
|
method?: string;
|
|
21
66
|
headers?: Record<string, string>;
|
|
22
67
|
body?: string | Uint8Array<ArrayBuffer>;
|
|
23
|
-
/** Sandbox path of a unix socket to send the request over (e.g. the docker API socket). */
|
|
24
68
|
unix?: string;
|
|
25
69
|
signal?: AbortSignal;
|
|
26
|
-
/**
|
|
27
|
-
* Idle timeout in milliseconds: the request fails once nothing has been sent or received for this
|
|
28
|
-
* long. `false` disables it, for streams that can stay quiet indefinitely. Defaults to the backend's.
|
|
29
|
-
*/
|
|
30
70
|
timeout?: number | false;
|
|
31
71
|
};
|
|
32
|
-
/**
|
|
33
|
-
* A stream connection to a unix socket inside a sandbox (see Sandbox.connect).
|
|
34
|
-
*
|
|
35
|
-
* @sealed
|
|
36
|
-
*/
|
|
72
|
+
/** @sealed */
|
|
37
73
|
export type SandboxConnection = {
|
|
38
74
|
write(data: Uint8Array): void;
|
|
39
|
-
/** Receives every chunk read from the socket, in order. */
|
|
40
75
|
onData(listener: (data: Uint8Array) => void): void;
|
|
41
|
-
/** Resolves once the connection is closed, by either side or by an error. */
|
|
42
76
|
closed: Promise<void>;
|
|
43
77
|
close(): void;
|
|
44
78
|
};
|
|
45
|
-
/**
|
|
46
|
-
export type
|
|
47
|
-
port: number;
|
|
48
|
-
} | {
|
|
49
|
-
unix: string;
|
|
50
|
-
};
|
|
51
|
-
/**
|
|
52
|
-
* What a client must present to an accessible endpoint. There is deliberately no unauthenticated
|
|
53
|
-
* variant: every endpoint requires one of these.
|
|
54
|
-
*/
|
|
55
|
-
export type SandboxEndpointCredentials =
|
|
56
|
-
/** `Authorization: Bearer <token>`. */
|
|
57
|
-
{
|
|
58
|
-
readonly scheme: "bearer";
|
|
79
|
+
/** @sealed */
|
|
80
|
+
export type SandboxCredentials = {
|
|
59
81
|
readonly token: string;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
readonly scheme: "basic";
|
|
64
|
-
readonly username: string;
|
|
65
|
-
readonly password: string;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Logged in with exe.dev, as a user the VM is shared with; exe.dev's proxy checks it (exeDevAuth.ts).
|
|
69
|
-
* The login is per port: a browser that isn't logged in on the endpoint's goes through `loginUrl`
|
|
70
|
-
* first (exeDevLoginUrl in agent/session/workerSseConnection.ts).
|
|
71
|
-
*/
|
|
72
|
-
| {
|
|
73
|
-
readonly scheme: "exe.dev";
|
|
74
|
-
readonly loginUrl: string;
|
|
75
|
-
};
|
|
76
|
-
export type SandboxEndpointScheme = SandboxEndpointCredentials["scheme"];
|
|
77
|
-
export type SandboxEndpointRequest = {
|
|
78
|
-
target: SandboxEndpointTarget;
|
|
79
|
-
/** Required: the endpoint is never open to unauthenticated clients. */
|
|
80
|
-
scheme: SandboxEndpointScheme;
|
|
81
|
-
/** Port the endpoint listens on; a free one (that its auth allows) if omitted. */
|
|
82
|
-
port?: number;
|
|
83
|
-
/**
|
|
84
|
-
* "http" (the default): the target speaks HTTP, and requests go through to it. "sse": the target
|
|
85
|
-
* is a stream socket speaking lines of text (an agent worker's JSON-RPC); each `GET /` opens a
|
|
86
|
-
* connection to it and streams its lines back as server-sent events, and `POST /<connection>`
|
|
87
|
-
* writes the request body to it (see endpointProxy.ts).
|
|
88
|
-
*/
|
|
89
|
-
transport?: "http" | "sse";
|
|
90
|
-
/**
|
|
91
|
-
* Web origins (e.g. "https://app.example.com") whose pages may call the endpoint from a browser:
|
|
92
|
-
* the proxy answers their CORS preflights and lets them read "sse" responses. None by default.
|
|
93
|
-
*/
|
|
94
|
-
allowedOrigins?: string[];
|
|
82
|
+
readonly exeDev?: {
|
|
83
|
+
readonly token: string;
|
|
84
|
+
};
|
|
95
85
|
};
|
|
96
86
|
export type SandboxEndpoint = {
|
|
97
|
-
readonly
|
|
98
|
-
/**
|
|
99
|
-
* Where the client reaches the endpoint, directly over the network (not through the backend's own
|
|
100
|
-
* connection), at the host the sandbox was created with.
|
|
101
|
-
*/
|
|
87
|
+
readonly name: string;
|
|
102
88
|
readonly url: string;
|
|
103
|
-
|
|
104
|
-
readonly
|
|
105
|
-
/** As requested: fixed for the endpoint's life. */
|
|
106
|
-
readonly allowedOrigins: string[];
|
|
89
|
+
readonly port: number;
|
|
90
|
+
readonly secret: string;
|
|
107
91
|
};
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Makes something listening in the sandbox reachable from the SDK's client over the network, without
|
|
159
|
-
* going through the backend's own connection. Declared when the sandbox is created (with the host
|
|
160
|
-
* clients reach it at), by the backends that can. Always authenticated: a scheme the sandbox's auth
|
|
161
|
-
* can't enforce is refused with SandboxEndpointError. An endpoint lasts until it is closed, past the
|
|
162
|
-
* sandbox object that exposed it.
|
|
163
|
-
*
|
|
164
|
-
* @sealed
|
|
165
|
-
*/
|
|
166
|
-
export interface SandboxAccessibleEndpoint {
|
|
167
|
-
/**
|
|
168
|
-
* How clients reach the machine, as the sandbox was created with ("host" or "host:port"). Several
|
|
169
|
-
* sandboxes may drive the same machine, each reached its own way: their endpoints are their own.
|
|
170
|
-
*/
|
|
171
|
-
readonly accessibleHost: string;
|
|
172
|
-
exposeEndpoint(request: SandboxEndpointRequest): Promise<SandboxEndpoint>;
|
|
173
|
-
/** Stops accepting connections and revokes its credentials. No-op if already closed. */
|
|
174
|
-
closeEndpoint(endpointId: string): Promise<void>;
|
|
175
|
-
/**
|
|
176
|
-
* Makes sure it is served, starting its proxy again if that stopped (a reboot), with the same URL
|
|
177
|
-
* and credentials; false if it was closed, or its port has since been taken (expose another).
|
|
178
|
-
*/
|
|
179
|
-
ensureEndpoint(endpointId: string): Promise<boolean>;
|
|
180
|
-
}
|
|
181
|
-
/**
|
|
182
|
-
* A sandbox that stops itself on a timer (Boat).
|
|
183
|
-
*
|
|
184
|
-
* @sealed
|
|
185
|
-
*/
|
|
186
|
-
export interface SandboxExpiration {
|
|
187
|
-
/** When it stops itself; null if it never does. */
|
|
188
|
-
getExpiration(): Promise<Date | null>;
|
|
189
|
-
/** Makes it stop itself `seconds` from now; null: never. Returns when it now will. */
|
|
190
|
-
setExpiration(seconds: number | null): Promise<Date | null>;
|
|
191
|
-
}
|
|
192
|
-
/** Whether the sandbox has the SandboxExpiration capability. */
|
|
193
|
-
export declare function hasExpiration<T extends object>(sandbox: T): sandbox is T & SandboxExpiration;
|
|
194
|
-
/**
|
|
195
|
-
* What every backend provides.
|
|
196
|
-
*
|
|
197
|
-
* @sealed
|
|
198
|
-
*/
|
|
199
|
-
export type Sandbox = SandboxFileOperations & SandboxCommands & SandboxNetwork & {
|
|
200
|
-
/** Releases what the sandbox holds open (a remote connection). Optional: most hold nothing. */
|
|
201
|
-
close?(): void;
|
|
92
|
+
export type SandboxAccess = {
|
|
93
|
+
readonly url: string;
|
|
94
|
+
readonly credentials: SandboxCredentials;
|
|
95
|
+
};
|
|
96
|
+
export type RunCommandOptions = {
|
|
97
|
+
cwd?: string;
|
|
98
|
+
env?: Record<string, string>;
|
|
99
|
+
stdin?: string | Uint8Array;
|
|
100
|
+
};
|
|
101
|
+
export type RunCommandResult = {
|
|
102
|
+
readonly exitCode: number;
|
|
103
|
+
readonly stdout: string;
|
|
104
|
+
readonly stderr: string;
|
|
105
|
+
};
|
|
106
|
+
export type SandboxSshEndpoint = {
|
|
107
|
+
readonly host: string;
|
|
108
|
+
readonly port: number;
|
|
109
|
+
readonly user: string;
|
|
110
|
+
readonly privateKey: string;
|
|
111
|
+
};
|
|
112
|
+
export type SandboxAuthContext = {
|
|
113
|
+
kind: "boat" | "exe.dev" | "ssh" | "local";
|
|
114
|
+
id?: string;
|
|
115
|
+
sshHost?: string;
|
|
116
|
+
sshUser?: string;
|
|
117
|
+
sshPort?: number;
|
|
118
|
+
sshKey?: string;
|
|
119
|
+
apikey?: string;
|
|
120
|
+
};
|
|
121
|
+
export type SandboxDataEndpointOptions = {
|
|
122
|
+
url?: string;
|
|
123
|
+
ports?: {
|
|
124
|
+
from: number;
|
|
125
|
+
to: number;
|
|
126
|
+
};
|
|
127
|
+
needsFirewall?: boolean;
|
|
128
|
+
};
|
|
129
|
+
export type SandboxSpec = {
|
|
130
|
+
name: string;
|
|
131
|
+
dataEndpointOptions?: SandboxDataEndpointOptions;
|
|
132
|
+
};
|
|
133
|
+
export type SandboxDescriptor = {
|
|
134
|
+
id: string;
|
|
135
|
+
kind: SandboxAuthContext["kind"];
|
|
136
|
+
name: string;
|
|
137
|
+
sshHost?: string;
|
|
138
|
+
sshUser?: string;
|
|
139
|
+
sshPort?: number;
|
|
140
|
+
dataEndpointOptions?: SandboxDataEndpointOptions;
|
|
202
141
|
};
|
|
142
|
+
/** @sealed */
|
|
143
|
+
export type Sandbox = SandboxFileOperations & SandboxCommands & SandboxNetwork & {};
|
|
203
144
|
export declare class SandboxPathError extends Error {
|
|
204
145
|
name: string;
|
|
205
146
|
}
|
|
206
147
|
export declare class SandboxNotFoundError extends Error {
|
|
207
148
|
name: string;
|
|
208
149
|
}
|
|
209
|
-
|
|
210
|
-
|
|
150
|
+
export declare class SandboxStoppedError extends Error {
|
|
151
|
+
name: string;
|
|
152
|
+
}
|
|
153
|
+
export declare class SandboxConnectionRefusedError extends Error {
|
|
211
154
|
name: string;
|
|
212
155
|
}
|
package/dist/sandbox/api.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
/** Whether the sandbox has the SandboxExpiration capability. */
|
|
2
|
-
export function hasExpiration(sandbox) {
|
|
3
|
-
return "getExpiration" in sandbox && "setExpiration" in sandbox;
|
|
4
|
-
}
|
|
5
1
|
export class SandboxPathError extends Error {
|
|
6
2
|
name = "SandboxPathError";
|
|
7
3
|
}
|
|
8
4
|
export class SandboxNotFoundError extends Error {
|
|
9
5
|
name = "SandboxNotFoundError";
|
|
10
6
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
export class SandboxStoppedError extends Error {
|
|
8
|
+
name = "SandboxStoppedError";
|
|
9
|
+
}
|
|
10
|
+
export class SandboxConnectionRefusedError extends Error {
|
|
11
|
+
name = "SandboxConnectionRefusedError";
|
|
14
12
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type Sandbox, type SandboxCredentials, type SandboxDataEndpoint, type SandboxEndpoint } from "../api.ts";
|
|
2
|
+
import type { SandboxStorage } from "../storage/sandboxStorage.ts";
|
|
3
|
+
export type EndpointPorts = {
|
|
4
|
+
ports(): Promise<{
|
|
5
|
+
from: number;
|
|
6
|
+
to: number;
|
|
7
|
+
}>;
|
|
8
|
+
expose(port: number): Promise<string>;
|
|
9
|
+
conceal(port: number): Promise<void>;
|
|
10
|
+
credentials?(endpoint: SandboxEndpoint, ttlSeconds: number): Promise<Omit<SandboxCredentials, "token">>;
|
|
11
|
+
};
|
|
12
|
+
export declare function signToken(secret: string, ttlSeconds: number): Promise<string>;
|
|
13
|
+
export declare function createEndpoints(sandbox: Sandbox, storage: SandboxStorage, ports: EndpointPorts): SandboxDataEndpoint;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { SignJWT } from "jose";
|
|
3
|
+
import { sh } from "../../lib/sh.js";
|
|
4
|
+
import { SandboxNotFoundError } from "../api.js";
|
|
5
|
+
const DEFAULT_TTL_S = 12 * 3600;
|
|
6
|
+
export function signToken(secret, ttlSeconds) {
|
|
7
|
+
return new SignJWT()
|
|
8
|
+
.setProtectedHeader({ alg: "HS256" })
|
|
9
|
+
.setExpirationTime(`${ttlSeconds}s`)
|
|
10
|
+
.sign(new TextEncoder().encode(secret));
|
|
11
|
+
}
|
|
12
|
+
async function listeningPorts(sandbox) {
|
|
13
|
+
const listed = await sandbox.runCommand(["sh", "-c", LISTENING_PORTS]);
|
|
14
|
+
if (listed.exitCode !== 0)
|
|
15
|
+
throw new Error(`listing the ports in use failed: ${listed.stderr.trim()}`);
|
|
16
|
+
return listed.stdout.split("\n").filter(Boolean).map(Number);
|
|
17
|
+
}
|
|
18
|
+
async function withLock(sandbox, body) {
|
|
19
|
+
const lock = `${await sandbox.homeDirectory()}/.local/state/hyperbrane/endpoints.lock`;
|
|
20
|
+
const taken = await sandbox.runCommand(["sh", "-c", TAKE_LOCK, "sh", lock]);
|
|
21
|
+
if (taken.exitCode !== 0)
|
|
22
|
+
throw new Error(`${lock} stayed locked: ${taken.stderr.trim()}`);
|
|
23
|
+
try {
|
|
24
|
+
return await body();
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
await sandbox.runCommand(["rm", "-rf", lock]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function under(base, endpoint) {
|
|
31
|
+
if (base === undefined || new URL(base).pathname === "/")
|
|
32
|
+
return endpoint;
|
|
33
|
+
return { ...endpoint, url: `${base.replace(/\/+$/, "")}/${endpoint.name}` };
|
|
34
|
+
}
|
|
35
|
+
export function createEndpoints(sandbox, storage, ports) {
|
|
36
|
+
const located = async (endpoints) => {
|
|
37
|
+
const base = (await storage.readDescriptor())?.dataEndpointOptions?.url;
|
|
38
|
+
return endpoints.map((endpoint) => under(base, endpoint));
|
|
39
|
+
};
|
|
40
|
+
async function openEndpoint(name) {
|
|
41
|
+
const [endpoint] = await located([await openStored(name)]);
|
|
42
|
+
return endpoint;
|
|
43
|
+
}
|
|
44
|
+
async function openStored(name) {
|
|
45
|
+
return withLock(sandbox, async () => {
|
|
46
|
+
const existing = await storage.readEndpoint(name);
|
|
47
|
+
if (existing)
|
|
48
|
+
return existing;
|
|
49
|
+
const { from, to } = await ports.ports();
|
|
50
|
+
const used = new Set([...(await storage.listEndpoints()).map((endpoint) => endpoint.port), ...(await listeningPorts(sandbox))]);
|
|
51
|
+
let port = from;
|
|
52
|
+
while (port <= to && used.has(port))
|
|
53
|
+
port++;
|
|
54
|
+
if (port > to)
|
|
55
|
+
throw new Error(`no data endpoint ports are left in ${from}-${to}`);
|
|
56
|
+
const endpoint = { name, url: await ports.expose(port), port, secret: randomBytes(32).toString("base64url") };
|
|
57
|
+
await storage.saveEndpoint(endpoint);
|
|
58
|
+
return endpoint;
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
async function closeEndpoint(name) {
|
|
62
|
+
await withLock(sandbox, async () => {
|
|
63
|
+
const endpoint = await storage.readEndpoint(name);
|
|
64
|
+
if (!endpoint)
|
|
65
|
+
return;
|
|
66
|
+
await ports.conceal(endpoint.port);
|
|
67
|
+
await storage.removeEndpoint(name);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async function credentials(name, { ttlSeconds = DEFAULT_TTL_S, } = {}) {
|
|
71
|
+
const endpoint = await storage.readEndpoint(name);
|
|
72
|
+
if (!endpoint)
|
|
73
|
+
throw new SandboxNotFoundError(`there is no data endpoint ${name}`);
|
|
74
|
+
return { token: await signToken(endpoint.secret, ttlSeconds), ...(await ports.credentials?.(endpoint, ttlSeconds)) };
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
openEndpoint,
|
|
78
|
+
closeEndpoint,
|
|
79
|
+
listEndpoints: async () => located(await storage.listEndpoints()),
|
|
80
|
+
credentials,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
const LISTENING_PORTS = sh `
|
|
84
|
+
awk 'FNR > 1 && $4 == "0A" { n = split($2, local, ":"); print local[n] }' /proc/net/tcp /proc/net/tcp6 2> /dev/null | while read -r hex; do
|
|
85
|
+
printf '%d\n' "0x$hex"
|
|
86
|
+
done
|
|
87
|
+
`;
|
|
88
|
+
const TAKE_LOCK = sh `
|
|
89
|
+
mkdir -p "$(dirname "$1")"
|
|
90
|
+
waited=0
|
|
91
|
+
until mkdir "$1" 2> /dev/null; do
|
|
92
|
+
if [ -n "$(find "$1" -maxdepth 0 -mmin +2 2> /dev/null)" ]; then
|
|
93
|
+
rm -rf "$1"
|
|
94
|
+
continue
|
|
95
|
+
fi
|
|
96
|
+
[ "$waited" -lt 1200 ] || exit 1
|
|
97
|
+
sleep 0.1
|
|
98
|
+
waited=$((waited + 1))
|
|
99
|
+
done
|
|
100
|
+
`;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function firewalledPorts(sandbox, storage) {
|
|
2
|
+
const options = async () => (await storage.readDescriptor())?.dataEndpointOptions;
|
|
3
|
+
async function firewall(rule) {
|
|
4
|
+
const result = await sandbox.runCommand(["sudo", "-n", "ufw", ...rule]);
|
|
5
|
+
if (result.exitCode !== 0)
|
|
6
|
+
throw new Error(`ufw ${rule.join(" ")} failed: ${result.stderr.trim()}`);
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
async ports() {
|
|
10
|
+
const ports = (await options())?.ports;
|
|
11
|
+
if (!ports)
|
|
12
|
+
throw new Error("this machine has no data endpoints: it's created with dataEndpointOptions.ports");
|
|
13
|
+
return ports;
|
|
14
|
+
},
|
|
15
|
+
async expose(port) {
|
|
16
|
+
const found = await options();
|
|
17
|
+
if (found?.needsFirewall)
|
|
18
|
+
await firewall(["allow", `${port}/tcp`]);
|
|
19
|
+
const url = new URL(found?.url ?? "http://127.0.0.1");
|
|
20
|
+
url.port = String(port);
|
|
21
|
+
return url.toString().replace(/\/$/, "");
|
|
22
|
+
},
|
|
23
|
+
async conceal(port) {
|
|
24
|
+
if ((await options())?.needsFirewall)
|
|
25
|
+
await firewall(["delete", "allow", `${port}/tcp`]);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { request as httpRequest } from "node:http";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
3
|
+
export function httpOver(url, connection, { method = "GET", headers = {}, body, signal, timeout }) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
const target = new URL(url);
|
|
6
|
+
const request = httpRequest({ createConnection: connection, path: `${target.pathname}${target.search}`, method, headers: { host: target.host, ...headers }, signal }, (response) => {
|
|
7
|
+
const responseHeaders = new Headers();
|
|
8
|
+
for (let i = 0; i + 1 < response.rawHeaders.length; i += 2)
|
|
9
|
+
responseHeaders.append(response.rawHeaders[i], response.rawHeaders[i + 1]);
|
|
10
|
+
const status = response.statusCode ?? 502;
|
|
11
|
+
const nullBody = status === 204 || status === 304 || method === "HEAD";
|
|
12
|
+
if (nullBody)
|
|
13
|
+
response.resume();
|
|
14
|
+
resolve(new Response(nullBody ? null : Readable.toWeb(response), { status, headers: responseHeaders }));
|
|
15
|
+
});
|
|
16
|
+
request.once("error", reject);
|
|
17
|
+
if (typeof timeout === "number")
|
|
18
|
+
request.setTimeout(timeout, () => request.destroy(new Error(`fetch ${url}: no data for ${timeout}ms`)));
|
|
19
|
+
request.end(body === undefined ? undefined : typeof body === "string" ? body : Buffer.from(body));
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type RunCommandOptions, type RunCommandResult, type SandboxFileOperations } from "../api.ts";
|
|
2
|
+
export declare const MISSING = 44;
|
|
3
|
+
export type CommandMachine = {
|
|
4
|
+
runCommand(cmd: string[], options?: RunCommandOptions): Promise<RunCommandResult>;
|
|
5
|
+
readBytes(path: string, start?: number, end?: number): Promise<Uint8Array>;
|
|
6
|
+
putFile(path: string, data: Uint8Array, exclusive: boolean): Promise<boolean>;
|
|
7
|
+
};
|
|
8
|
+
export declare function commandFileOperations(machine: CommandMachine): SandboxFileOperations;
|
|
9
|
+
export declare const PUT_FILE: string;
|
|
10
|
+
export declare const READ_BYTES: string;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { posix } from "node:path";
|
|
2
|
+
import { sh } from "../../lib/sh.js";
|
|
3
|
+
import { SandboxNotFoundError } from "../api.js";
|
|
4
|
+
export const MISSING = 44;
|
|
5
|
+
function remote(path) {
|
|
6
|
+
return posix.resolve("/", path);
|
|
7
|
+
}
|
|
8
|
+
function bytes(data) {
|
|
9
|
+
return typeof data === "string" ? new TextEncoder().encode(data) : data;
|
|
10
|
+
}
|
|
11
|
+
async function script(machine, what, path, body) {
|
|
12
|
+
const result = await machine.runCommand(["sh", "-c", body, "sh", path], { env: { MISSING: String(MISSING) } });
|
|
13
|
+
if (result.exitCode === MISSING)
|
|
14
|
+
throw new SandboxNotFoundError(`Not found: "${path}"`);
|
|
15
|
+
if (result.exitCode !== 0)
|
|
16
|
+
throw new Error(`${what} ${path} failed: ${result.stderr.trim()}`);
|
|
17
|
+
return result.stdout;
|
|
18
|
+
}
|
|
19
|
+
export function commandFileOperations(machine) {
|
|
20
|
+
return {
|
|
21
|
+
readFile: (path) => machine.readBytes(remote(path)),
|
|
22
|
+
readFileRange: (path, start, end) => machine.readBytes(remote(path), start, end),
|
|
23
|
+
writeFile: async (path, data) => void (await machine.putFile(remote(path), bytes(data), false)),
|
|
24
|
+
writeFileExclusive: (path, data) => machine.putFile(remote(path), bytes(data), true),
|
|
25
|
+
removeFile: async (path) => void (await script(machine, "removing", remote(path), REMOVE_FILE)),
|
|
26
|
+
createDirectory: async (path) => void (await script(machine, "creating", remote(path), CREATE_DIRECTORY)),
|
|
27
|
+
removeDirectory: async (path) => void (await script(machine, "removing", remote(path), REMOVE_DIRECTORY)),
|
|
28
|
+
listDirectory: async (path) => (await script(machine, "listing", remote(path), LIST_DIRECTORY)).split("\0").filter(Boolean),
|
|
29
|
+
async stat(path) {
|
|
30
|
+
const [type, uid, gid, size] = (await script(machine, "reading", remote(path), STAT)).trim().split(" ");
|
|
31
|
+
return { type: type, uid: Number(uid), gid: Number(gid), size: Number(size) };
|
|
32
|
+
},
|
|
33
|
+
homeDirectory: async () => (await script(machine, "reading", "~", HOME)).trim(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export const PUT_FILE = sh `
|
|
37
|
+
set -eu
|
|
38
|
+
mkdir -p "$(dirname "$1")"
|
|
39
|
+
staged="$(dirname "$1")/.$(basename "$1").$$.tmp"
|
|
40
|
+
trap 'rm -f "$staged"' EXIT
|
|
41
|
+
cat > "$staged"
|
|
42
|
+
if [ "$EXCLUSIVE" = 1 ]; then
|
|
43
|
+
ln "$staged" "$1" 2> /dev/null || exit "$EXISTS"
|
|
44
|
+
else
|
|
45
|
+
mv -f "$staged" "$1"
|
|
46
|
+
fi
|
|
47
|
+
`;
|
|
48
|
+
export const READ_BYTES = sh `
|
|
49
|
+
[ -f "$1" ] || exit "$MISSING"
|
|
50
|
+
if [ -z "$START" ]; then exec cat "$1"; fi
|
|
51
|
+
tail -c "+$((START + 1))" "$1" | head -c "$((END - START))"
|
|
52
|
+
`;
|
|
53
|
+
const REMOVE_FILE = sh `rm -f "$1"`;
|
|
54
|
+
const CREATE_DIRECTORY = sh `mkdir -p "$1"`;
|
|
55
|
+
const REMOVE_DIRECTORY = sh `rm -rf "$1"`;
|
|
56
|
+
const LIST_DIRECTORY = sh `
|
|
57
|
+
[ -e "$1" ] || exit "$MISSING"
|
|
58
|
+
cd "$1" || exit 1
|
|
59
|
+
for name in .* *; do
|
|
60
|
+
case "$name" in . | ..) continue ;; esac
|
|
61
|
+
[ -e "$name" ] || [ -L "$name" ] || continue
|
|
62
|
+
printf '%s\0' "$name"
|
|
63
|
+
done
|
|
64
|
+
`;
|
|
65
|
+
const STAT = sh `
|
|
66
|
+
[ -e "$1" ] || exit "$MISSING"
|
|
67
|
+
if [ -f "$1" ]; then kind="file"; elif [ -d "$1" ]; then kind="directory"; else kind="other"; fi
|
|
68
|
+
printf '%s %s\n' "$kind" "$(stat -c '%u %g %s' "$1")"
|
|
69
|
+
`;
|
|
70
|
+
const HOME = sh `printf '%s\n' "$HOME"`;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function quote(word) {
|
|
2
|
+
return `'${word.replaceAll("'", `'\\''`)}'`;
|
|
3
|
+
}
|
|
4
|
+
export function commandLine(cmd, { cwd, env = {} } = {}) {
|
|
5
|
+
const words = ["env", ...Object.entries(env).map(([name, value]) => `${name}=${value}`), ...cmd].map(quote).join(" ");
|
|
6
|
+
return cwd === undefined ? `exec ${words}` : `cd ${quote(cwd)} && exec ${words}`;
|
|
7
|
+
}
|