hyperbrane 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.
Files changed (123) hide show
  1. package/README.md +41 -0
  2. package/dist/agent/agentService.d.ts +31 -0
  3. package/dist/agent/agentService.js +388 -0
  4. package/dist/agent/agentStorage.d.ts +27 -0
  5. package/dist/agent/agentStorage.js +52 -0
  6. package/dist/agent/api.d.ts +264 -0
  7. package/dist/agent/api.js +14 -0
  8. package/dist/agent/gatewaySignIns.d.ts +5 -0
  9. package/dist/agent/gatewaySignIns.js +11 -0
  10. package/dist/agent/session/client.d.ts +14 -0
  11. package/dist/agent/session/client.js +47 -0
  12. package/dist/agent/session/sessionStorage.d.ts +66 -0
  13. package/dist/agent/session/sessionStorage.js +89 -0
  14. package/dist/agent/session/sharedConnection.d.ts +13 -0
  15. package/dist/agent/session/sharedConnection.js +55 -0
  16. package/dist/agent/session/worker/jsonRpc.d.ts +39 -0
  17. package/dist/agent/session/worker/jsonRpc.js +102 -0
  18. package/dist/agent/session/worker/protocol.d.ts +120 -0
  19. package/dist/agent/session/worker/protocol.js +32 -0
  20. package/dist/agent/session/workerConnection.d.ts +29 -0
  21. package/dist/agent/session/workerConnection.js +44 -0
  22. package/dist/agent/session/workerSseConnection.d.ts +15 -0
  23. package/dist/agent/session/workerSseConnection.js +26 -0
  24. package/dist/agent/workerBundle.d.ts +19 -0
  25. package/dist/agent/workerBundle.generated.d.ts +1 -0
  26. package/dist/agent/workerBundle.generated.js +2 -0
  27. package/dist/agent/workerBundle.js +31 -0
  28. package/dist/cli/agent.d.ts +3 -0
  29. package/dist/cli/agent.js +423 -0
  30. package/dist/cli/command.d.ts +46 -0
  31. package/dist/cli/command.js +98 -0
  32. package/dist/cli/dimension.d.ts +2 -0
  33. package/dist/cli/dimension.js +85 -0
  34. package/dist/cli/gateway.d.ts +6 -0
  35. package/dist/cli/gateway.js +245 -0
  36. package/dist/cli/main.d.ts +2 -0
  37. package/dist/cli/main.js +125 -0
  38. package/dist/cli/portal.d.ts +2 -0
  39. package/dist/cli/portal.js +39 -0
  40. package/dist/cli/services.d.ts +18 -0
  41. package/dist/cli/services.js +33 -0
  42. package/dist/cli/streamPrinter.d.ts +23 -0
  43. package/dist/cli/streamPrinter.js +251 -0
  44. package/dist/cli/table.d.ts +7 -0
  45. package/dist/cli/table.js +54 -0
  46. package/dist/dimension/api.d.ts +67 -0
  47. package/dist/dimension/api.js +81 -0
  48. package/dist/dimension/http.d.ts +7 -0
  49. package/dist/dimension/http.js +77 -0
  50. package/dist/dimension/image.d.ts +13 -0
  51. package/dist/dimension/image.js +43 -0
  52. package/dist/dimension/imageFiles.generated.d.ts +1 -0
  53. package/dist/dimension/imageFiles.generated.js +5 -0
  54. package/dist/dimension/paths.d.ts +5 -0
  55. package/dist/dimension/paths.js +7 -0
  56. package/dist/dimension/service.d.ts +22 -0
  57. package/dist/dimension/service.js +242 -0
  58. package/dist/dimension/storage.d.ts +39 -0
  59. package/dist/dimension/storage.js +78 -0
  60. package/dist/index.d.ts +18 -0
  61. package/dist/index.js +25 -0
  62. package/dist/lib/docker-api.d.ts +13787 -0
  63. package/dist/lib/docker-api.js +5 -0
  64. package/dist/lib/docker.d.ts +77 -0
  65. package/dist/lib/docker.js +214 -0
  66. package/dist/lib/image.d.ts +14 -0
  67. package/dist/lib/image.js +29 -0
  68. package/dist/lib/jsonDirectory.d.ts +28 -0
  69. package/dist/lib/jsonDirectory.js +102 -0
  70. package/dist/lib/sleep.d.ts +2 -0
  71. package/dist/lib/sleep.js +4 -0
  72. package/dist/lib/tar.d.ts +2 -0
  73. package/dist/lib/tar.js +65 -0
  74. package/dist/lib/version.d.ts +1 -0
  75. package/dist/lib/version.js +1 -0
  76. package/dist/portal-gateway/api.d.ts +98 -0
  77. package/dist/portal-gateway/api.js +12 -0
  78. package/dist/portal-gateway/fakes.d.ts +3 -0
  79. package/dist/portal-gateway/fakes.js +11 -0
  80. package/dist/portal-gateway/gatewayService.d.ts +37 -0
  81. package/dist/portal-gateway/gatewayService.js +380 -0
  82. package/dist/portal-gateway/gatewayStorage.d.ts +66 -0
  83. package/dist/portal-gateway/gatewayStorage.js +125 -0
  84. package/dist/portal-gateway/image.d.ts +6 -0
  85. package/dist/portal-gateway/image.js +13 -0
  86. package/dist/portal-gateway/imageFiles.generated.d.ts +1 -0
  87. package/dist/portal-gateway/imageFiles.generated.js +8 -0
  88. package/dist/portal-gateway/portalService.d.ts +48 -0
  89. package/dist/portal-gateway/portalService.js +327 -0
  90. package/dist/portal-gateway/portalStorage.d.ts +34 -0
  91. package/dist/portal-gateway/portalStorage.js +44 -0
  92. package/dist/portal-gateway/upstream.d.ts +157 -0
  93. package/dist/portal-gateway/upstream.js +4 -0
  94. package/dist/sandbox/api.d.ts +184 -0
  95. package/dist/sandbox/api.js +14 -0
  96. package/dist/sandbox/boatService.d.ts +52 -0
  97. package/dist/sandbox/boatService.js +335 -0
  98. package/dist/sandbox/containerService.d.ts +15 -0
  99. package/dist/sandbox/containerService.js +177 -0
  100. package/dist/sandbox/endpointProxy.d.ts +90 -0
  101. package/dist/sandbox/endpointProxy.js +701 -0
  102. package/dist/sandbox/exeDevAuth.d.ts +16 -0
  103. package/dist/sandbox/exeDevAuth.js +49 -0
  104. package/dist/sandbox/localService.d.ts +19 -0
  105. package/dist/sandbox/localService.js +182 -0
  106. package/dist/sandbox/sseConnection.d.ts +41 -0
  107. package/dist/sandbox/sseConnection.js +166 -0
  108. package/dist/sandbox/sshService.d.ts +30 -0
  109. package/dist/sandbox/sshService.js +647 -0
  110. package/dist/upstreams/anthropic.d.ts +7 -0
  111. package/dist/upstreams/anthropic.js +27 -0
  112. package/dist/upstreams/index.d.ts +6 -0
  113. package/dist/upstreams/index.js +7 -0
  114. package/dist/upstreams/openai.d.ts +7 -0
  115. package/dist/upstreams/openai.js +40 -0
  116. package/dist/vendor-types/claude/agent-sdk/agentSdkTypes.d.ts +1 -0
  117. package/dist/vendor-types/claude/agent-sdk/bridge.d.ts +388 -0
  118. package/dist/vendor-types/claude/agent-sdk/browser-sdk.d.ts +184 -0
  119. package/dist/vendor-types/claude/agent-sdk/core.d.ts +20 -0
  120. package/dist/vendor-types/claude/agent-sdk/extractFromBunfs.d.ts +1 -0
  121. package/dist/vendor-types/claude/agent-sdk/sdk-tools.d.ts +4153 -0
  122. package/dist/vendor-types/claude/agent-sdk/sdk.d.ts +9833 -0
  123. package/package.json +55 -0
@@ -0,0 +1,245 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ import { resolve } from "node:path";
10
+ import anthropic from "../upstreams/anthropic.js";
11
+ import openai from "../upstreams/openai.js";
12
+ import { QUIET_FLAG, UsageError, forEach, printList, requireFlag } from "./command.js";
13
+ const BUILTIN_UPSTREAMS = { anthropic, openai };
14
+ const TRAFFIC_ACTIONS = ["BLOCK", "DELAY", "ASK", "INTERCEPT"];
15
+ export const gatewayCommands = {
16
+ summary: "Manage portal gateways: proxies that hold credentials for the dimensions behind them",
17
+ commands: {
18
+ ls: {
19
+ summary: "List gateways",
20
+ flags: QUIET_FLAG,
21
+ async run(_args, flags, { gateways, portals }) {
22
+ const allPortals = await portals.listPortals();
23
+ const rows = await Promise.all((await gateways.listGateways())
24
+ .sort((a, b) => a.name.localeCompare(b.name))
25
+ .map(async ({ name }) => [
26
+ name,
27
+ (await gateways.listUpstreams(name)).map((upstream) => upstream.name).join(", "),
28
+ allPortals
29
+ .filter((portal) => portal.gatewayName === name)
30
+ .map((portal) => portal.name)
31
+ .sort()
32
+ .join(", "),
33
+ ]));
34
+ printList(flags, ["NAME", "UPSTREAMS", "PORTALS"], rows);
35
+ },
36
+ },
37
+ create: {
38
+ summary: "Create a gateway, finish creating one that was interrupted, or upgrade one to the current image",
39
+ args: ["NAME"],
40
+ async run([name], _flags, { gateways, portals }) {
41
+ await gateways.createGateway({ name: name });
42
+ // An upgrade replaces the container, and its portals' links with it.
43
+ await portals.restorePortals({ gatewayName: name });
44
+ console.log(name);
45
+ },
46
+ },
47
+ restart: {
48
+ summary: "Restart one or more gateways",
49
+ args: ["NAME..."],
50
+ async run(names, _flags, { gateways }) {
51
+ await forEach(names, (name) => gateways.restartGateway(name));
52
+ },
53
+ },
54
+ rm: {
55
+ summary: "Remove one or more gateways, with their credentials (remove their portals first)",
56
+ args: ["NAME..."],
57
+ async run(names, _flags, { gateways }) {
58
+ await forEach(names, (name) => gateways.destroyGateway(name));
59
+ },
60
+ },
61
+ },
62
+ };
63
+ async function loadUpstream(source) {
64
+ const builtin = BUILTIN_UPSTREAMS[source];
65
+ if (builtin)
66
+ return builtin;
67
+ if (!/[/\\]|\.(ts|js|mjs|json)$/.test(source)) {
68
+ throw new UsageError(`unknown upstream ${source}: use one of ${Object.keys(BUILTIN_UPSTREAMS).join(", ")}, or a file path`);
69
+ }
70
+ const module = (await import(__rewriteRelativeImportExtension(resolve(source))));
71
+ if (!module.default)
72
+ throw new UsageError(`${source} has no default export`);
73
+ return module.default;
74
+ }
75
+ export const upstreamCommands = {
76
+ summary: "Manage the upstreams (APIs) a gateway holds credentials for",
77
+ commands: {
78
+ ls: {
79
+ summary: "List a gateway's upstreams",
80
+ args: ["GATEWAY"],
81
+ flags: QUIET_FLAG,
82
+ async run([gateway], flags, { gateways }) {
83
+ const rows = (await gateways.listUpstreams(gateway)).map((upstream) => {
84
+ const modes = Object.entries(upstream.modes).filter(([, mode]) => mode);
85
+ const hosts = new Set(modes.flatMap(([, mode]) => mode.hosts));
86
+ return [upstream.name, modes.map(([name]) => name).join(", "), [...hosts].join(", ")];
87
+ });
88
+ printList(flags, ["NAME", "MODES", "HOSTS"], rows);
89
+ },
90
+ },
91
+ add: {
92
+ summary: `Add or replace an upstream: built-in (${Object.keys(BUILTIN_UPSTREAMS).join(", ")}) or a module exporting a definition`,
93
+ args: ["GATEWAY", "UPSTREAM"],
94
+ async run([gateway, source], _flags, { gateways }) {
95
+ const upstream = await gateways.registerUpstream(gateway, await loadUpstream(source));
96
+ console.log(upstream.name);
97
+ },
98
+ },
99
+ rm: {
100
+ summary: "Remove one or more upstreams from a gateway, with their credentials",
101
+ args: ["GATEWAY", "UPSTREAM..."],
102
+ async run([gateway, ...names], _flags, { gateways }) {
103
+ await forEach(names, (name) => gateways.removeUpstream(gateway, name));
104
+ },
105
+ },
106
+ },
107
+ };
108
+ async function readKeyFromStdin() {
109
+ if (process.stdin.isTTY) {
110
+ throw new UsageError("pipe the key on stdin, e.g. `hyperbrane key set GATEWAY UPSTREAM NAME < key.txt`");
111
+ }
112
+ return (await Bun.stdin.text()).replace(/\r?\n$/, "");
113
+ }
114
+ export const keyCommands = {
115
+ summary: "Manage API keys (a client in a dimension uses the key's fake in its place)",
116
+ commands: {
117
+ set: {
118
+ summary: "Set an API key, read from stdin (never pass keys as arguments); prints its fake",
119
+ args: ["GATEWAY", "UPSTREAM", "NAME"],
120
+ async run([gateway, upstream, name], _flags, { gateways }) {
121
+ const { fake } = await gateways.setApiKey(gateway, upstream, name, await readKeyFromStdin());
122
+ console.log(fake);
123
+ },
124
+ },
125
+ rm: {
126
+ summary: "Remove one or more API keys",
127
+ args: ["GATEWAY", "UPSTREAM", "NAME..."],
128
+ async run([gateway, upstream, ...names], _flags, { gateways }) {
129
+ await forEach(names, (name) => gateways.deleteApiKey(gateway, upstream, name));
130
+ },
131
+ },
132
+ },
133
+ };
134
+ export const credentialCommands = {
135
+ summary: "Manage the credentials a gateway holds (secrets are never shown; API keys' fakes are)",
136
+ commands: {
137
+ ls: {
138
+ summary: "List a gateway's credentials",
139
+ args: ["GATEWAY"],
140
+ flags: QUIET_FLAG,
141
+ async run([gateway], flags, { gateways }) {
142
+ const rows = (await gateways.listCredentials(gateway)).map((credential) => [
143
+ credential.name,
144
+ credential.type,
145
+ credential.fake ?? "",
146
+ ]);
147
+ printList(flags, ["NAME", "TYPE", "FAKE"], rows);
148
+ },
149
+ },
150
+ rm: {
151
+ summary: "Remove one or more credentials (<upstream>.oauth or <upstream>.key.<name>)",
152
+ args: ["GATEWAY", "CREDENTIAL..."],
153
+ async run([gateway, ...names], _flags, { gateways }) {
154
+ await forEach(names, (name) => gateways.deleteCredential(gateway, name));
155
+ },
156
+ },
157
+ },
158
+ };
159
+ const RULE_FLAGS = {
160
+ "url-mask": {
161
+ type: "string",
162
+ value: "MASK",
163
+ description: 'What the rule covers: "tcp://<address>:<port>" or a URL glob like "https://*.github.com/*"',
164
+ },
165
+ action: { type: "string", value: "ACTION", description: TRAFFIC_ACTIONS.join(", ") },
166
+ "delay-ms": { type: "string", value: "MS", description: "Delay for DELAY rules (default 0)" },
167
+ };
168
+ function parseAction(value) {
169
+ const action = value.toUpperCase();
170
+ if (!TRAFFIC_ACTIONS.includes(action))
171
+ throw new UsageError(`--action must be one of ${TRAFFIC_ACTIONS.join(", ")}`);
172
+ return action;
173
+ }
174
+ function parseDelay(value) {
175
+ const delayMs = Number(value);
176
+ if (!Number.isInteger(delayMs) || delayMs < 0)
177
+ throw new UsageError("--delay-ms must be a whole number of milliseconds");
178
+ return delayMs;
179
+ }
180
+ function ruleFromFlags(name, flags, base) {
181
+ return {
182
+ name,
183
+ urlMask: typeof flags["url-mask"] === "string" ? flags["url-mask"] : (base?.urlMask ?? requireFlag(flags, "url-mask")),
184
+ action: typeof flags.action === "string" ? parseAction(flags.action) : (base?.action ?? parseAction(requireFlag(flags, "action"))),
185
+ delayMs: typeof flags["delay-ms"] === "string" ? parseDelay(flags["delay-ms"]) : (base?.delayMs ?? 0),
186
+ };
187
+ }
188
+ export const ruleCommands = {
189
+ summary: "Manage traffic rules: what a gateway does with its portals' traffic (checked in name order)",
190
+ commands: {
191
+ ls: {
192
+ summary: "List a gateway's traffic rules, in the order they're checked",
193
+ args: ["GATEWAY"],
194
+ flags: QUIET_FLAG,
195
+ async run([gateway], flags, { gateways }) {
196
+ const rows = (await gateways.listTrafficRules(gateway)).map((rule) => [
197
+ rule.name,
198
+ rule.action,
199
+ rule.action === "DELAY" ? `${rule.delayMs}ms` : "",
200
+ rule.urlMask,
201
+ ]);
202
+ printList(flags, ["NAME", "ACTION", "DELAY", "URL MASK"], rows);
203
+ },
204
+ },
205
+ create: {
206
+ summary: "Create a traffic rule",
207
+ args: ["GATEWAY", "NAME"],
208
+ flags: RULE_FLAGS,
209
+ async run([gateway, name], flags, { gateways }) {
210
+ await gateways.createTrafficRule(gateway, ruleFromFlags(name, flags));
211
+ console.log(name);
212
+ },
213
+ },
214
+ update: {
215
+ summary: "Change a traffic rule; options left out keep their current value",
216
+ args: ["GATEWAY", "NAME"],
217
+ flags: RULE_FLAGS,
218
+ async run([gateway, name], flags, { gateways }) {
219
+ const current = (await gateways.listTrafficRules(gateway)).find((rule) => rule.name === name);
220
+ if (!current)
221
+ throw new UsageError(`traffic rule ${name} does not exist on gateway ${gateway}`);
222
+ await gateways.updateTrafficRule(gateway, ruleFromFlags(name, flags, current));
223
+ console.log(name);
224
+ },
225
+ },
226
+ rm: {
227
+ summary: "Remove one or more traffic rules (requests an ASK rule holds carry on)",
228
+ args: ["GATEWAY", "NAME..."],
229
+ async run([gateway, ...names], _flags, { gateways }) {
230
+ await forEach(names, (name) => gateways.deleteTrafficRule(gateway, name));
231
+ },
232
+ },
233
+ respond: {
234
+ summary: "Answer the requests an ASK rule is holding right now: allow or deny",
235
+ args: ["GATEWAY", "NAME", "ANSWER"],
236
+ async run([gateway, name, answer], _flags, { gateways }) {
237
+ const action = answer.toUpperCase();
238
+ if (action !== "ALLOW" && action !== "DENY")
239
+ throw new UsageError("ANSWER must be allow or deny");
240
+ await gateways.trafficRuleRespond(gateway, name, action);
241
+ console.log(name);
242
+ },
243
+ },
244
+ },
245
+ };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ export {};
@@ -0,0 +1,125 @@
1
+ #!/usr/bin/env bun
2
+ import { createPortalWatcher } from "../portal-gateway/portalService.js";
3
+ import { PartialFailure, UsageError, commandUsage, groupUsage, parseCommand, isGroup, } from "./command.js";
4
+ import { agentCommands, sessionCommands } from "./agent.js";
5
+ import { dimensionCommands } from "./dimension.js";
6
+ import { credentialCommands, gatewayCommands, keyCommands, ruleCommands, upstreamCommands } from "./gateway.js";
7
+ import { portalCommands } from "./portal.js";
8
+ import { createServices } from "./services.js";
9
+ const daemon = {
10
+ summary: "Run the background work until stopped: restoring portals after container restarts",
11
+ async run(_args, _flags, { portals, docker }) {
12
+ const watcher = createPortalWatcher(portals, docker);
13
+ watcher.start();
14
+ console.error("hyperbrane daemon running; Ctrl-C to stop");
15
+ await new Promise((done) => {
16
+ process.once("SIGINT", done);
17
+ process.once("SIGTERM", done);
18
+ });
19
+ await watcher.stop();
20
+ },
21
+ };
22
+ const GROUPS = {
23
+ dimension: dimensionCommands,
24
+ gateway: gatewayCommands,
25
+ upstream: upstreamCommands,
26
+ key: keyCommands,
27
+ credential: credentialCommands,
28
+ rule: ruleCommands,
29
+ portal: portalCommands,
30
+ agent: agentCommands,
31
+ session: sessionCommands,
32
+ };
33
+ const COMMANDS = { daemon };
34
+ function usage() {
35
+ const names = [...Object.keys(GROUPS), ...Object.keys(COMMANDS)];
36
+ const width = Math.max(...names.map((name) => name.length)) + 3;
37
+ return [
38
+ "Usage: hyperbrane COMMAND",
39
+ "",
40
+ "Manage dimensions, the portal gateways they reach the outside through, and the agents running in them",
41
+ "",
42
+ "Management Commands:",
43
+ ...Object.entries(GROUPS).map(([name, group]) => ` ${name.padEnd(width)}${group.summary}`),
44
+ "",
45
+ "Commands:",
46
+ ...Object.entries(COMMANDS).map(([name, command]) => ` ${name.padEnd(width)}${command.summary}`),
47
+ "",
48
+ "Run 'hyperbrane COMMAND --help' for more information on a command.",
49
+ ].join("\n");
50
+ }
51
+ const isHelp = (arg) => arg === "--help" || arg === "-h";
52
+ /** Resolves the command to run, or returns help text to print. Groups nest: each word picks an entry. */
53
+ function resolveCommand(argv) {
54
+ const [first] = argv;
55
+ if (first === undefined || isHelp(first) || first === "help")
56
+ return { help: usage() };
57
+ const standalone = COMMANDS[first];
58
+ if (standalone)
59
+ return { path: first, command: standalone, rest: argv.slice(1) };
60
+ let group = GROUPS[first];
61
+ if (!group)
62
+ return new UsageError(`unknown command ${first}`);
63
+ const path = [first];
64
+ for (let index = 1;; index++) {
65
+ const word = argv[index];
66
+ if (word === undefined || isHelp(word))
67
+ return { help: groupUsage(path.join(" "), group) };
68
+ const entry = group.commands[word];
69
+ if (!entry && group.default)
70
+ return { path: path.join(" "), command: group.default, rest: argv.slice(index) };
71
+ if (!entry)
72
+ return new UsageError(`unknown command ${[...path, word].join(" ")}`);
73
+ path.push(word);
74
+ if (!isGroup(entry))
75
+ return { path: path.join(" "), command: entry, rest: argv.slice(index + 1) };
76
+ group = entry;
77
+ }
78
+ }
79
+ async function main(argv) {
80
+ const resolved = resolveCommand(argv);
81
+ if (resolved instanceof UsageError) {
82
+ console.error(`Error: ${resolved.message}\nRun 'hyperbrane --help' for usage.`);
83
+ return 2;
84
+ }
85
+ if ("help" in resolved) {
86
+ console.log(resolved.help);
87
+ return 0;
88
+ }
89
+ const { path, command, rest } = resolved;
90
+ if (rest.some(isHelp)) {
91
+ console.log(commandUsage(path, command));
92
+ return 0;
93
+ }
94
+ let parsed;
95
+ try {
96
+ parsed = parseCommand(command, rest);
97
+ }
98
+ catch (error) {
99
+ console.error(`Error: ${error.message}\nRun 'hyperbrane ${path} --help' for usage.`);
100
+ return 2;
101
+ }
102
+ const services = createServices();
103
+ // A container restart takes its portals with it, and nothing else may be running to notice: every
104
+ // command re-wires what's gone first (a quick look at each gateway when nothing is).
105
+ await services.portals.restorePortals().catch((error) => {
106
+ console.error(`Warning: restoring portals failed: ${error.message}`);
107
+ });
108
+ try {
109
+ await command.run(parsed.args, parsed.flags, services);
110
+ return 0;
111
+ }
112
+ catch (error) {
113
+ if (error instanceof PartialFailure)
114
+ return 1;
115
+ if (error instanceof UsageError) {
116
+ console.error(`Error: ${error.message}\nRun 'hyperbrane ${path} --help' for usage.`);
117
+ return 2;
118
+ }
119
+ console.error(`Error: ${error.message}`);
120
+ if (process.env.HYPERBRANE_DEBUG)
121
+ console.error(error);
122
+ return 1;
123
+ }
124
+ }
125
+ process.exit(await main(Bun.argv.slice(2)));
@@ -0,0 +1,2 @@
1
+ import { type CommandGroup } from "./command.ts";
2
+ export declare const portalCommands: CommandGroup;
@@ -0,0 +1,39 @@
1
+ import { QUIET_FLAG, forEach, printList, requireFlag } from "./command.js";
2
+ export const portalCommands = {
3
+ summary: "Manage portals: connections from a dimension to a gateway",
4
+ commands: {
5
+ ls: {
6
+ summary: "List portals",
7
+ flags: QUIET_FLAG,
8
+ async run(_args, flags, { portals }) {
9
+ const rows = (await portals.listPortals())
10
+ .sort((a, b) => a.name.localeCompare(b.name))
11
+ .map((portal) => [portal.name, portal.dimensionName, portal.gatewayName]);
12
+ printList(flags, ["NAME", "DIMENSION", "GATEWAY"], rows);
13
+ },
14
+ },
15
+ create: {
16
+ summary: "Connect a dimension to a gateway, or repair an existing connection",
17
+ args: ["NAME"],
18
+ flags: {
19
+ dimension: { type: "string", short: "d", value: "DIMENSION", description: "Dimension to connect (required)" },
20
+ gateway: { type: "string", short: "g", value: "GATEWAY", description: "Gateway to connect it to (required)" },
21
+ },
22
+ async run([name], flags, { portals }) {
23
+ await portals.createPortal({
24
+ name: name,
25
+ dimensionName: requireFlag(flags, "dimension"),
26
+ gatewayName: requireFlag(flags, "gateway"),
27
+ });
28
+ console.log(name);
29
+ },
30
+ },
31
+ rm: {
32
+ summary: "Remove one or more portals",
33
+ args: ["NAME..."],
34
+ async run(names, _flags, { portals }) {
35
+ await forEach(names, (name) => portals.destroyPortal(name));
36
+ },
37
+ },
38
+ },
39
+ };
@@ -0,0 +1,18 @@
1
+ import { type AgentService } from "../agent/agentService.ts";
2
+ import { type DimensionService } from "../dimension/service.ts";
3
+ import { type DockerClient } from "../lib/docker.ts";
4
+ import type { Sandbox } from "../sandbox/api.ts";
5
+ import { type GatewayService } from "../portal-gateway/gatewayService.ts";
6
+ import { type PortalService } from "../portal-gateway/portalService.ts";
7
+ export type Services = {
8
+ docker: DockerClient;
9
+ dimensions: DimensionService;
10
+ gateways: GatewayService;
11
+ portals: PortalService;
12
+ agents: AgentService;
13
+ };
14
+ /**
15
+ * Everything runs against `host`'s docker, with state in ~/.local/share/hyperbrane of its user: this
16
+ * machine by default, or e.g. an SSH sandbox to drive a remote server the same way.
17
+ */
18
+ export declare function createServices(host?: Sandbox): Services;
@@ -0,0 +1,33 @@
1
+ import { createAgentService } from "../agent/agentService.js";
2
+ import { createAgentStorage } from "../agent/agentStorage.js";
3
+ import { createGatewaySignIns } from "../agent/gatewaySignIns.js";
4
+ import { createDimensionImage } from "../dimension/image.js";
5
+ import { createDimensionService } from "../dimension/service.js";
6
+ import { createFileStorage } from "../dimension/storage.js";
7
+ import { createDockerClient } from "../lib/docker.js";
8
+ import { createLocalSandbox } from "../sandbox/localService.js";
9
+ import { createGatewayImage } from "../portal-gateway/image.js";
10
+ import { createGatewayService } from "../portal-gateway/gatewayService.js";
11
+ import { createGatewayStorage } from "../portal-gateway/gatewayStorage.js";
12
+ import { createPortalService } from "../portal-gateway/portalService.js";
13
+ import { createPortalStorage } from "../portal-gateway/portalStorage.js";
14
+ /**
15
+ * Everything runs against `host`'s docker, with state in ~/.local/share/hyperbrane of its user: this
16
+ * machine by default, or e.g. an SSH sandbox to drive a remote server the same way.
17
+ */
18
+ export function createServices(host = createLocalSandbox("/")) {
19
+ const docker = createDockerClient(host);
20
+ const gatewayStorage = createGatewayStorage(host);
21
+ const portalStorage = createPortalStorage(host);
22
+ const gatewayImage = createGatewayImage(docker);
23
+ const portals = createPortalService(portalStorage, gatewayStorage, docker, gatewayImage, host);
24
+ const gateways = createGatewayService(gatewayStorage, portalStorage, docker, gatewayImage, host);
25
+ const dimensions = createDimensionService(createFileStorage(host), docker, createDimensionImage(docker), host);
26
+ return {
27
+ docker,
28
+ dimensions,
29
+ gateways,
30
+ portals,
31
+ agents: createAgentService(createAgentStorage(host), docker, host, createGatewaySignIns(portals, gateways)),
32
+ };
33
+ }
@@ -0,0 +1,23 @@
1
+ import type { AgentStreamingMessage } from "../agent/api.ts";
2
+ export declare function preview(value: unknown): string;
3
+ /** The part of stdout the printer uses. */
4
+ export type TerminalStream = {
5
+ isTTY?: boolean;
6
+ write(chunk: string): unknown;
7
+ };
8
+ export interface TerminalPrinter {
9
+ /** A stream message: partial text and thinking as they arrive, tool calls and results, and how each turn ended. */
10
+ print(message: AgentStreamingMessage): void;
11
+ /** A complete message of a transcript. */
12
+ printTranscript(role: "user" | "assistant", content: unknown): void;
13
+ close(): void;
14
+ }
15
+ /**
16
+ * Prints to `stream` (stdout); `paced` text is typed out steadily (see createPacedOutput) when it's
17
+ * a terminal. A complete assistant message's text is printed only if none of it streamed: claude
18
+ * reports failures it hits before reaching the API (a TLS error, say) as a synthetic message.
19
+ */
20
+ export declare function createTerminalPrinter({ paced, stream }: {
21
+ paced: boolean;
22
+ stream?: TerminalStream;
23
+ }): TerminalPrinter;