@fungi.computer/truffle 0.0.0-stage → 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.
Files changed (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +542 -2
  3. package/computerd-loopback.cjs +16 -0
  4. package/dist/auth-cli.d.ts +46 -0
  5. package/dist/auth-cli.js +373 -0
  6. package/dist/fungi-account-command.d.ts +18 -0
  7. package/dist/fungi-account-command.js +95 -0
  8. package/dist/index.d.ts +27 -0
  9. package/dist/index.js +23 -0
  10. package/dist/internal/image-fit-metafile.json +106 -0
  11. package/dist/internal/local-image-fit-worker.mjs +960 -0
  12. package/dist/internal/private-workerd-sandbox-worker.mjs +27487 -0
  13. package/dist/internal/sandbox-metafile.json +6726 -0
  14. package/dist/linear-auth.d.ts +4 -0
  15. package/dist/linear-auth.js +72 -0
  16. package/dist/local-archive-handoff.d.ts +12 -0
  17. package/dist/local-archive-handoff.js +21 -0
  18. package/dist/local-extensions.d.ts +143 -0
  19. package/dist/local-extensions.js +624 -0
  20. package/dist/local-image-fit.d.ts +30 -0
  21. package/dist/local-image-fit.js +105 -0
  22. package/dist/local-login.d.ts +55 -0
  23. package/dist/local-login.js +167 -0
  24. package/dist/local-models.d.ts +16 -0
  25. package/dist/local-models.js +82 -0
  26. package/dist/local-session-archives.d.ts +8 -0
  27. package/dist/local-session-archives.js +152 -0
  28. package/dist/local-skill-source.d.ts +21 -0
  29. package/dist/local-skill-source.js +433 -0
  30. package/dist/log-event.d.ts +9 -0
  31. package/dist/log-event.js +13 -0
  32. package/dist/private-bounded-response.d.ts +2 -0
  33. package/dist/private-bounded-response.js +41 -0
  34. package/dist/private-bun-shiitake-host.d.ts +11 -0
  35. package/dist/private-bun-shiitake-host.js +66 -0
  36. package/dist/private-file-credential-store.d.ts +58 -0
  37. package/dist/private-file-credential-store.js +331 -0
  38. package/dist/private-files-patch.d.ts +4 -0
  39. package/dist/private-files-patch.js +104 -0
  40. package/dist/private-fungi-account.d.ts +252 -0
  41. package/dist/private-fungi-account.js +1329 -0
  42. package/dist/private-fungi-auth-error.d.ts +5 -0
  43. package/dist/private-fungi-auth-error.js +7 -0
  44. package/dist/private-fungi-mcp-connection.d.ts +26 -0
  45. package/dist/private-fungi-mcp-connection.js +162 -0
  46. package/dist/private-fungi-mcp-endpoint.d.ts +2 -0
  47. package/dist/private-fungi-mcp-endpoint.js +15 -0
  48. package/dist/private-fungi-mcp-selection-schema.d.ts +7 -0
  49. package/dist/private-fungi-mcp-selection-schema.js +15 -0
  50. package/dist/private-fungi-mcp-selection.d.ts +14 -0
  51. package/dist/private-fungi-mcp-selection.js +49 -0
  52. package/dist/private-fungi-model-api.d.ts +6 -0
  53. package/dist/private-fungi-model-api.js +125 -0
  54. package/dist/private-fungi-model-catalog.d.ts +16 -0
  55. package/dist/private-fungi-model-catalog.js +86 -0
  56. package/dist/private-fungi-model-provider.d.ts +6 -0
  57. package/dist/private-fungi-model-provider.js +51 -0
  58. package/dist/private-fungi-pending-logouts.d.ts +34 -0
  59. package/dist/private-fungi-pending-logouts.js +135 -0
  60. package/dist/private-fungi-service-logout.d.ts +44 -0
  61. package/dist/private-fungi-service-logout.js +59 -0
  62. package/dist/private-invocation-computer.d.ts +58 -0
  63. package/dist/private-invocation-computer.js +498 -0
  64. package/dist/private-invocation-files.d.ts +30 -0
  65. package/dist/private-invocation-files.js +520 -0
  66. package/dist/private-local-acp-stdio.d.ts +17 -0
  67. package/dist/private-local-acp-stdio.js +145 -0
  68. package/dist/private-local-admission-gate.d.ts +14 -0
  69. package/dist/private-local-admission-gate.js +52 -0
  70. package/dist/private-local-agent-control.d.ts +101 -0
  71. package/dist/private-local-agent-control.js +30 -0
  72. package/dist/private-local-agent-default-model.d.ts +12 -0
  73. package/dist/private-local-agent-default-model.js +31 -0
  74. package/dist/private-local-auth-rpc.d.ts +129 -0
  75. package/dist/private-local-auth-rpc.js +396 -0
  76. package/dist/private-local-bwrap.d.ts +13 -0
  77. package/dist/private-local-bwrap.js +70 -0
  78. package/dist/private-local-client-version.d.ts +5 -0
  79. package/dist/private-local-client-version.js +43 -0
  80. package/dist/private-local-computer-attach.d.ts +49 -0
  81. package/dist/private-local-computer-attach.js +230 -0
  82. package/dist/private-local-computer-backend.d.ts +282 -0
  83. package/dist/private-local-computer-backend.js +402 -0
  84. package/dist/private-local-computer-processes.d.ts +202 -0
  85. package/dist/private-local-computer-processes.js +613 -0
  86. package/dist/private-local-config-root.d.ts +21 -0
  87. package/dist/private-local-config-root.js +19 -0
  88. package/dist/private-local-containment.d.ts +51 -0
  89. package/dist/private-local-containment.js +161 -0
  90. package/dist/private-local-default-model.d.ts +11 -0
  91. package/dist/private-local-default-model.js +45 -0
  92. package/dist/private-local-delegation.d.ts +36 -0
  93. package/dist/private-local-delegation.js +133 -0
  94. package/dist/private-local-effective-model.d.ts +27 -0
  95. package/dist/private-local-effective-model.js +17 -0
  96. package/dist/private-local-exec-environment.d.ts +16 -0
  97. package/dist/private-local-exec-environment.js +11 -0
  98. package/dist/private-local-exec-launch.d.ts +41 -0
  99. package/dist/private-local-exec-launch.js +51 -0
  100. package/dist/private-local-exec-path.d.ts +6 -0
  101. package/dist/private-local-exec-path.js +40 -0
  102. package/dist/private-local-git-exclude.d.ts +2 -0
  103. package/dist/private-local-git-exclude.js +33 -0
  104. package/dist/private-local-http-dispatch.d.ts +5 -0
  105. package/dist/private-local-http-dispatch.js +14 -0
  106. package/dist/private-local-linux-sandbox.d.ts +8 -0
  107. package/dist/private-local-linux-sandbox.js +67 -0
  108. package/dist/private-local-mail.d.ts +13 -0
  109. package/dist/private-local-mail.js +77 -0
  110. package/dist/private-local-mesh-enrollment.d.ts +34 -0
  111. package/dist/private-local-mesh-enrollment.js +188 -0
  112. package/dist/private-local-mesh-links.d.ts +21 -0
  113. package/dist/private-local-mesh-links.js +229 -0
  114. package/dist/private-local-mesh-record.d.ts +120 -0
  115. package/dist/private-local-mesh-record.js +55 -0
  116. package/dist/private-local-process-identity.d.ts +20 -0
  117. package/dist/private-local-process-identity.js +40 -0
  118. package/dist/private-local-runtime-host.d.ts +192 -0
  119. package/dist/private-local-runtime-host.js +1177 -0
  120. package/dist/private-local-sandbox-policy.d.ts +21 -0
  121. package/dist/private-local-sandbox-policy.js +121 -0
  122. package/dist/private-local-sandbox.d.ts +37 -0
  123. package/dist/private-local-sandbox.js +114 -0
  124. package/dist/private-local-seatbelt.d.ts +24 -0
  125. package/dist/private-local-seatbelt.js +68 -0
  126. package/dist/private-local-service-contract.d.ts +170 -0
  127. package/dist/private-local-service-contract.js +1 -0
  128. package/dist/private-local-service-discovery.d.ts +217 -0
  129. package/dist/private-local-service-discovery.js +322 -0
  130. package/dist/private-local-service-ids.d.ts +3 -0
  131. package/dist/private-local-service-ids.js +8 -0
  132. package/dist/private-local-service-lease.d.ts +16 -0
  133. package/dist/private-local-service-lease.js +105 -0
  134. package/dist/private-local-service-listener.d.ts +22 -0
  135. package/dist/private-local-service-listener.js +407 -0
  136. package/dist/private-local-service-log.d.ts +128 -0
  137. package/dist/private-local-service-log.js +321 -0
  138. package/dist/private-local-service-owner.d.ts +135 -0
  139. package/dist/private-local-service-owner.js +1369 -0
  140. package/dist/private-local-service-process.d.ts +1 -0
  141. package/dist/private-local-service-process.js +8 -0
  142. package/dist/private-local-service-termination.d.ts +7 -0
  143. package/dist/private-local-service-termination.js +67 -0
  144. package/dist/private-local-service.d.ts +46 -0
  145. package/dist/private-local-service.js +606 -0
  146. package/dist/private-local-session-discovery.d.ts +66 -0
  147. package/dist/private-local-session-discovery.js +278 -0
  148. package/dist/private-local-shiitake-host.d.ts +44 -0
  149. package/dist/private-local-shiitake-host.js +143 -0
  150. package/dist/private-local-startup-model.d.ts +51 -0
  151. package/dist/private-local-startup-model.js +86 -0
  152. package/dist/private-local-stop-all.d.ts +27 -0
  153. package/dist/private-local-stop-all.js +26 -0
  154. package/dist/private-local-ui-attachment.d.ts +19 -0
  155. package/dist/private-local-ui-attachment.js +71 -0
  156. package/dist/private-local-worker-server.d.ts +16 -0
  157. package/dist/private-local-worker-server.js +144 -0
  158. package/dist/private-mcp-form-contract.d.ts +186 -0
  159. package/dist/private-mcp-form-contract.js +53 -0
  160. package/dist/private-mcp-form-elicitation.d.ts +18 -0
  161. package/dist/private-mcp-form-elicitation.js +97 -0
  162. package/dist/private-mcp-form-session.d.ts +172 -0
  163. package/dist/private-mcp-form-session.js +148 -0
  164. package/dist/private-openai-claims.d.ts +30 -0
  165. package/dist/private-openai-claims.js +69 -0
  166. package/dist/private-oyster-mcp-auth.d.ts +13 -0
  167. package/dist/private-oyster-mcp-auth.js +57 -0
  168. package/dist/private-provider-rejection.d.ts +15 -0
  169. package/dist/private-provider-rejection.js +24 -0
  170. package/dist/private-sqlite-owner.d.ts +39 -0
  171. package/dist/private-sqlite-owner.js +169 -0
  172. package/dist/private-team-computers.d.ts +38 -0
  173. package/dist/private-team-computers.js +110 -0
  174. package/dist/private-workerd-sandbox.d.ts +3 -0
  175. package/dist/private-workerd-sandbox.js +347 -0
  176. package/dist/seatbelt/safe-base.sbpl +79 -0
  177. package/dist/subscription-activation.d.ts +9 -0
  178. package/dist/subscription-activation.js +62 -0
  179. package/dist/subscription-identity.d.ts +23 -0
  180. package/dist/subscription-identity.js +124 -0
  181. package/dist/subscription-turn.d.ts +17 -0
  182. package/dist/subscription-turn.js +143 -0
  183. package/dist/team-apps.d.ts +24 -0
  184. package/dist/team-apps.js +128 -0
  185. package/dist/tracker-git-credential.d.ts +15 -0
  186. package/dist/tracker-git-credential.js +87 -0
  187. package/native/bin/LICENSE.bubblewrap +502 -0
  188. package/native/bin/LICENSE.libcap +112 -0
  189. package/native/bin/LICENSE.sandbox-runtime +201 -0
  190. package/native/bin/SOURCE.bubblewrap.tar.xz +0 -0
  191. package/native/bin/build.json +4037 -0
  192. package/native/bin/bwrap-build-source/Dockerfile +48 -0
  193. package/native/bin/bwrap-build-source/README.md +96 -0
  194. package/native/bin/bwrap-build-source/build.mjs +52 -0
  195. package/native/bin/bwrap-build-source/linux-arm64.ini +14 -0
  196. package/native/bin/bwrap-build-source/pin.mjs +21 -0
  197. package/native/bin/bwrap-build-source/record.mjs +74 -0
  198. package/native/bin/bwrap-build-source/vendor/LICENSE.sandbox-runtime +201 -0
  199. package/native/bin/bwrap-build-source/vendor/README.md +23 -0
  200. package/native/bin/bwrap-build-source/vendor/seccomp-unix-block.c +280 -0
  201. package/native/bin/bwrap-build.json +44 -0
  202. package/native/bin/fungi-apply-patch +0 -0
  203. package/native/bin/fungi-bwrap +0 -0
  204. package/native/bin/fungi-bwrap.sha256 +1 -0
  205. package/native/bin/fungi-seccomp.bpf +0 -0
  206. package/native/bin/fungi-seccomp.bpf.sha256 +1 -0
  207. package/package.json +161 -3
@@ -0,0 +1,230 @@
1
+ import { generateKeyPairSync } from "node:crypto";
2
+ import { realpath, stat } from "node:fs/promises";
3
+ import { hostname } from "node:os";
4
+ import { basename, resolve } from "node:path";
5
+ import { defineIdFamily } from "@fungi.computer/cairn/id";
6
+ import { z } from "zod";
7
+ import { capturePrivateLocalConfigRoot } from "./private-local-config-root.js";
8
+ import { callFungiMcpTeamControl, fungiMcpTeamEndpoint, readFungiMcpLocalBackends, writeFungiMcpLocalBackends, } from "./private-fungi-account.js";
9
+ import { runLocalComputerBackend, startLocalComputerd, } from "./private-local-computer-backend.js";
10
+ /**
11
+ * `fungi computer attach|status|detach`: this machine's folders as execution
12
+ * backends of Team Computers. Attaching generates a device key here, stores
13
+ * the folder path and private key in Local's owner-only credential file, and
14
+ * sends the Hub only the public key and two labels through the member's
15
+ * existing Team MCP login. Every request id is persisted before the network
16
+ * call so an uncertain outcome is retried, never duplicated.
17
+ */
18
+ const requestIds = defineIdFamily({
19
+ family: "local-computer-backend",
20
+ version: 1,
21
+ kinds: { attach: { prefix: "local_attach_request" } },
22
+ });
23
+ const computerPageSchema = z.looseObject({
24
+ computers: z.array(z.looseObject({ computerId: z.string(), name: z.string() })),
25
+ nextOffset: z.number().int().nonnegative().nullable(),
26
+ });
27
+ const attachResultSchema = z.discriminatedUnion("kind", [
28
+ z.looseObject({
29
+ kind: z.literal("attached"),
30
+ requestId: z.string(),
31
+ backendId: z.string().regex(/^local-[a-z2-7]{16}$/),
32
+ }),
33
+ z.looseObject({
34
+ kind: z.enum(["uncertain", "denied", "conflict", "invalid"]),
35
+ requestId: z.string(),
36
+ }),
37
+ ]);
38
+ const detachResultSchema = z.discriminatedUnion("kind", [
39
+ z.looseObject({ kind: z.literal("detached"), detached: z.boolean() }),
40
+ z.looseObject({ kind: z.enum(["denied", "uncertain", "invalid"]) }),
41
+ ]);
42
+ async function control(teamEndpoint, call, schema, options) {
43
+ const text = await callFungiMcpTeamControl(teamEndpoint, `return await ${call};`, options);
44
+ if (text === undefined)
45
+ return undefined;
46
+ const parsed = schema.safeParse(JSON.parse(text));
47
+ return parsed.success ? parsed.data : undefined;
48
+ }
49
+ /** A Team Computer by id or name, through the member's own directory view. */
50
+ async function findComputer(teamEndpoint, computer, options) {
51
+ let offset = 0;
52
+ while (offset !== null) {
53
+ const page = await control(teamEndpoint, `computers.list(${JSON.stringify({ limit: 100, offset })})`, computerPageSchema, options);
54
+ if (page === undefined)
55
+ throw new Error("The Hub did not list this Team's Computers; retry");
56
+ const found = page.computers.find((candidate) => candidate.computerId === computer || candidate.name === computer);
57
+ if (found !== undefined)
58
+ return found;
59
+ offset = page.nextOffset;
60
+ }
61
+ return undefined;
62
+ }
63
+ function sameComputer(record, computer) {
64
+ return record.computerId === computer || record.computerName === computer;
65
+ }
66
+ /** A fresh Ed25519 device key: raw public key and PKCS#8 private key, base64. */
67
+ function deviceKey() {
68
+ const pair = generateKeyPairSync("ed25519");
69
+ const jwk = z
70
+ .object({ x: z.string() })
71
+ .parse(pair.publicKey.export({ format: "jwk" }));
72
+ return {
73
+ publicKey: Buffer.from(jwk.x, "base64url").toString("base64"),
74
+ privateKey: pair.privateKey
75
+ .export({ format: "der", type: "pkcs8" })
76
+ .toString("base64"),
77
+ };
78
+ }
79
+ /** Attach `folderPath` to a Team Computer, or return the existing attachment. */
80
+ export async function attachPrivateLocalComputer(rawEndpoint, input, controlOptions = {}) {
81
+ const options = {
82
+ ...controlOptions,
83
+ credentialPath: controlOptions.credentialPath ??
84
+ capturePrivateLocalConfigRoot().credentialPath,
85
+ };
86
+ const { endpoint, teamId } = fungiMcpTeamEndpoint(rawEndpoint);
87
+ const rootPath = await realpath(resolve(input.folderPath));
88
+ if (!(await stat(rootPath)).isDirectory())
89
+ throw new Error("The folder to attach is not a directory");
90
+ const saved = await readFungiMcpLocalBackends(options);
91
+ if (saved.issuer !== endpoint.origin)
92
+ throw new Error(`Fungi MCP sign-in is required; run fungi login --fungi-mcp ${endpoint.href}`);
93
+ let record = saved.backends.find((candidate) => candidate.teamId === teamId && sameComputer(candidate, input.computer));
94
+ if (record !== undefined && record.rootPath !== rootPath)
95
+ throw new Error(`This machine is already attached to ${record.computerName} with another folder; run fungi computer detach first`);
96
+ if (record === undefined) {
97
+ const computer = await findComputer(endpoint, input.computer, options);
98
+ if (computer === undefined)
99
+ return { kind: "not-found" };
100
+ record = {
101
+ requestId: requestIds.attach.generate(),
102
+ teamId,
103
+ computerId: computer.computerId,
104
+ computerName: computer.name,
105
+ rootPath,
106
+ hostLabel: z
107
+ .string()
108
+ .trim()
109
+ .min(1)
110
+ .max(256)
111
+ .parse(input.hostLabel ?? hostname()),
112
+ folderLabel: z.string().min(1).max(256).parse(basename(rootPath)),
113
+ ...deviceKey(),
114
+ };
115
+ const pending = record;
116
+ await writeFungiMcpLocalBackends((current) => [...current, pending], options);
117
+ }
118
+ if (record.backendId !== undefined)
119
+ return {
120
+ kind: "attached",
121
+ computerId: record.computerId,
122
+ computerName: record.computerName,
123
+ backendId: record.backendId,
124
+ rootPath,
125
+ };
126
+ const result = await control(endpoint, `computers.attachLocal(${JSON.stringify({
127
+ computerId: record.computerId,
128
+ publicKey: record.publicKey,
129
+ hostLabel: record.hostLabel,
130
+ folderLabel: record.folderLabel,
131
+ requestId: record.requestId,
132
+ })})`, attachResultSchema, options);
133
+ if (result === undefined)
134
+ return { kind: "uncertain" };
135
+ if (result.kind !== "attached")
136
+ return { kind: result.kind };
137
+ const attached = { ...record, backendId: result.backendId };
138
+ await writeFungiMcpLocalBackends((current) => current.map((candidate) => candidate.requestId === attached.requestId ? attached : candidate), options);
139
+ return {
140
+ kind: "attached",
141
+ computerId: attached.computerId,
142
+ computerName: attached.computerName,
143
+ backendId: result.backendId,
144
+ rootPath,
145
+ };
146
+ }
147
+ /**
148
+ * Detach a Computer's attachment on the Hub, then forget it here. A pending
149
+ * attach is replayed first so the Hub never keeps an attachment Local forgot.
150
+ */
151
+ export async function detachPrivateLocalComputer(rawEndpoint, computer, input = {}) {
152
+ const options = {
153
+ ...input,
154
+ credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
155
+ };
156
+ const { endpoint, teamId } = fungiMcpTeamEndpoint(rawEndpoint);
157
+ const saved = await readFungiMcpLocalBackends(options);
158
+ const record = saved.backends.find((candidate) => candidate.teamId === teamId && sameComputer(candidate, computer));
159
+ if (record === undefined)
160
+ return { kind: "not-attached" };
161
+ let backendId = record.backendId;
162
+ if (backendId === undefined) {
163
+ const replay = await attachPrivateLocalComputer(endpoint, { computer: record.computerId, folderPath: record.rootPath }, options);
164
+ if (replay.kind !== "attached")
165
+ return { kind: "uncertain" };
166
+ backendId = replay.backendId;
167
+ }
168
+ const result = await control(endpoint, `computers.detachLocal(${JSON.stringify({
169
+ computerId: record.computerId,
170
+ backendId,
171
+ })})`, detachResultSchema, options);
172
+ if (result === undefined)
173
+ return { kind: "uncertain" };
174
+ if (result.kind === "denied")
175
+ return { kind: "denied" };
176
+ if (result.kind !== "detached")
177
+ return { kind: "uncertain" };
178
+ await writeFungiMcpLocalBackends((current) => current.filter((candidate) => candidate.requestId !== record.requestId), options);
179
+ return { kind: "detached" };
180
+ }
181
+ /** What `fungi computer status` shows: never a key. */
182
+ export async function listPrivateLocalComputers(options = {}) {
183
+ const saved = await readFungiMcpLocalBackends(options);
184
+ return saved.backends.map((record) => ({
185
+ teamId: record.teamId,
186
+ computerId: record.computerId,
187
+ computerName: record.computerName,
188
+ rootPath: record.rootPath,
189
+ backendId: record.backendId ?? null,
190
+ }));
191
+ }
192
+ /**
193
+ * Serve one attached folder until `signal` aborts or the Hub refuses the
194
+ * machine (it was detached elsewhere), in which case Local forgets it.
195
+ */
196
+ export async function runPrivateLocalComputer(rawEndpoint, computer, input) {
197
+ const options = {
198
+ ...input,
199
+ credentialPath: input.credentialPath ?? capturePrivateLocalConfigRoot().credentialPath,
200
+ };
201
+ const { endpoint, teamId } = fungiMcpTeamEndpoint(rawEndpoint);
202
+ const saved = await readFungiMcpLocalBackends(options);
203
+ const record = saved.backends.find((candidate) => candidate.teamId === teamId && sameComputer(candidate, computer));
204
+ if (record?.backendId === undefined)
205
+ throw new Error(`This machine is not attached to ${computer}`);
206
+ const daemon = await startLocalComputerd({ root: record.rootPath });
207
+ try {
208
+ const outcome = await runLocalComputerBackend({
209
+ target: {
210
+ hubOrigin: endpoint.origin,
211
+ teamId,
212
+ computerId: record.computerId,
213
+ backendId: record.backendId,
214
+ deviceId: record.backendId.slice("local-".length),
215
+ privateKey: record.privateKey,
216
+ },
217
+ root: record.rootPath,
218
+ computerdPort: daemon.port,
219
+ signal: options.signal,
220
+ onStatus: options.onStatus,
221
+ });
222
+ if (outcome === "stopped")
223
+ return "stopped";
224
+ await writeFungiMcpLocalBackends((current) => current.filter((candidate) => candidate.requestId !== record.requestId), options);
225
+ return "detached";
226
+ }
227
+ finally {
228
+ await daemon.stop();
229
+ }
230
+ }
@@ -0,0 +1,282 @@
1
+ import { RpcTarget } from "@fungi.computer/capnweb";
2
+ /**
3
+ * A member's folder as an execution backend of a Team Computer.
4
+ *
5
+ * Local runs the same `computerd` 0.2.1 as the Computer container image,
6
+ * built from vendored source and run under the user's Node in shim mode, with its shim mount rooted at the chosen folder, and dials out to
7
+ * the Hub. The Computer then syncs and runs commands against this folder with
8
+ * the library's own push/pull, exactly as it does against a container.
9
+ *
10
+ * `computerd` names files by their absolute path on this machine. The Hub
11
+ * must only ever see `/workspace/...`, so Local serves the Hub a
12
+ * `WorkspaceRPC` of its own that forwards to `computerd` and translates paths
13
+ * at this one boundary. Entries outside the folder never cross it.
14
+ */
15
+ /** The Computer-side root every backend's files live under. */
16
+ export declare const computerWorkspaceRoot = "/workspace";
17
+ export type ChangeCursor = {
18
+ rev: number;
19
+ path: string | null;
20
+ };
21
+ export type ChangeEntry = {
22
+ kind: "file";
23
+ rev: number;
24
+ path: string;
25
+ mode: number;
26
+ mtime: number;
27
+ size: number;
28
+ chunks: {
29
+ hash: Uint8Array;
30
+ size: number;
31
+ }[];
32
+ } | {
33
+ kind: "dir";
34
+ rev: number;
35
+ path: string;
36
+ mode: number;
37
+ mtime: number;
38
+ } | {
39
+ kind: "symlink";
40
+ rev: number;
41
+ path: string;
42
+ target: string;
43
+ mode: number;
44
+ mtime: number;
45
+ } | {
46
+ kind: "delete";
47
+ rev: number;
48
+ path: string;
49
+ };
50
+ export type BlobObject = {
51
+ hash: Uint8Array;
52
+ bytes: Uint8Array;
53
+ };
54
+ export type ExecEvent = {
55
+ id: string;
56
+ seq: number;
57
+ name: "stdout" | "stderr";
58
+ value: Uint8Array;
59
+ } | {
60
+ id: string;
61
+ seq: number;
62
+ name: "exit";
63
+ code: number;
64
+ };
65
+ export type FetchChangesInput = {
66
+ after?: ChangeCursor;
67
+ ignore?: string[];
68
+ };
69
+ export type ExecInput = {
70
+ source: string;
71
+ cwd?: string;
72
+ id?: string;
73
+ input?: unknown;
74
+ timeoutMs?: number;
75
+ env?: Record<string, string>;
76
+ stdin?: Uint8Array;
77
+ };
78
+ export type KillSignal = "SIGTERM" | "SIGKILL" | "SIGINT" | "SIGHUP";
79
+ /** computerd's `WorkspaceRPC`, as `@cloudflare/computer` 0.2.1 speaks it. */
80
+ export type ComputerdWorkspaceRpc = {
81
+ sync: {
82
+ push(input: {
83
+ senderRev: number;
84
+ changes: ReadableStream<ChangeEntry>;
85
+ }): Promise<{
86
+ rev: number;
87
+ appliedPushCursor: ChangeCursor;
88
+ }>;
89
+ fetchChanges(input: FetchChangesInput): Promise<{
90
+ currentCursor: ChangeCursor;
91
+ appliedPushCursor: ChangeCursor;
92
+ stream: ReadableStream<ChangeEntry>;
93
+ }>;
94
+ watermarks(): Promise<{
95
+ currentRev: number;
96
+ pushRev: number;
97
+ fetchCursor: ChangeCursor;
98
+ }>;
99
+ readEntry(path: string): Promise<ChangeEntry | null>;
100
+ hasObjects(hashes: Uint8Array[]): Promise<Uint8Array[]>;
101
+ /** Synchronous in computerd; a promise across the RPC session. */
102
+ fetchObjects(hashes: Uint8Array[]): Promise<ReadableStream<BlobObject>>;
103
+ pushObjects(objects: ReadableStream<BlobObject>): Promise<void>;
104
+ };
105
+ shell: {
106
+ exec(input: ExecInput): Promise<{
107
+ id: string;
108
+ events: ReadableStream<ExecEvent>;
109
+ }>;
110
+ getExec(input: {
111
+ id: string;
112
+ after?: number | "tail";
113
+ }): Promise<{
114
+ id: string;
115
+ events: ReadableStream<ExecEvent>;
116
+ }>;
117
+ killExec(input: {
118
+ id: string;
119
+ signal?: KillSignal;
120
+ }): Promise<void>;
121
+ disposeExec(input: {
122
+ id: string;
123
+ }): Promise<void>;
124
+ };
125
+ };
126
+ /** Bidirectional path mapping between the folder and `/workspace`. */
127
+ export declare class FolderPathMap {
128
+ #private;
129
+ constructor(root: string);
130
+ /** A folder path as the Computer names it, or undefined outside the folder. */
131
+ toComputer(localPath: string): string | undefined;
132
+ /** A Computer path on this machine, or undefined outside `/workspace`. */
133
+ toLocal(computerPath: string): string | undefined;
134
+ }
135
+ declare class FolderSync extends RpcTarget {
136
+ #private;
137
+ constructor(remote: ComputerdWorkspaceRpc["sync"], paths: FolderPathMap);
138
+ push(input: {
139
+ senderRev: number;
140
+ changes: ReadableStream<ChangeEntry>;
141
+ }): Promise<{
142
+ rev: number;
143
+ appliedPushCursor: ChangeCursor;
144
+ }>;
145
+ fetchChanges(input: FetchChangesInput): Promise<{
146
+ currentCursor: ChangeCursor;
147
+ appliedPushCursor: ChangeCursor;
148
+ stream: ReadableStream<ChangeEntry>;
149
+ }>;
150
+ watermarks(): Promise<{
151
+ currentRev: number;
152
+ pushRev: number;
153
+ fetchCursor: ChangeCursor;
154
+ }>;
155
+ readEntry(path: string): Promise<{
156
+ path: string;
157
+ kind: "file";
158
+ rev: number;
159
+ mode: number;
160
+ mtime: number;
161
+ size: number;
162
+ chunks: {
163
+ hash: Uint8Array;
164
+ size: number;
165
+ }[];
166
+ } | {
167
+ path: string;
168
+ kind: "dir";
169
+ rev: number;
170
+ mode: number;
171
+ mtime: number;
172
+ } | {
173
+ path: string;
174
+ kind: "symlink";
175
+ rev: number;
176
+ target: string;
177
+ mode: number;
178
+ mtime: number;
179
+ } | {
180
+ path: string;
181
+ kind: "delete";
182
+ rev: number;
183
+ } | null>;
184
+ hasObjects(hashes: Uint8Array[]): Promise<Uint8Array<ArrayBufferLike>[]>;
185
+ fetchObjects(hashes: Uint8Array[]): Promise<ReadableStream<BlobObject>>;
186
+ pushObjects(objects: ReadableStream<BlobObject>): Promise<void>;
187
+ }
188
+ /** A command's working directory outside the folder is refused, not widened. */
189
+ export declare class FolderCwdError extends Error {
190
+ constructor(cwd: string);
191
+ }
192
+ declare class FolderShell extends RpcTarget {
193
+ #private;
194
+ constructor(remote: ComputerdWorkspaceRpc["shell"], paths: FolderPathMap);
195
+ exec(input: ExecInput): Promise<{
196
+ id: string;
197
+ events: ReadableStream<ExecEvent>;
198
+ }>;
199
+ getExec(input: {
200
+ id: string;
201
+ after?: number | "tail";
202
+ }): Promise<{
203
+ id: string;
204
+ events: ReadableStream<ExecEvent>;
205
+ }>;
206
+ killExec(input: {
207
+ id: string;
208
+ signal?: KillSignal;
209
+ }): Promise<void>;
210
+ disposeExec(input: {
211
+ id: string;
212
+ }): Promise<void>;
213
+ }
214
+ /** The `WorkspaceRPC` the Hub's Workspace sees for this folder. */
215
+ export declare class FolderWorkspaceRpc extends RpcTarget {
216
+ #private;
217
+ constructor(remote: ComputerdWorkspaceRpc, root: string);
218
+ get sync(): FolderSync;
219
+ get shell(): FolderShell;
220
+ }
221
+ /** One running computerd rooted at a folder, reachable on loopback only. */
222
+ export type LocalComputerd = Readonly<{
223
+ port: number;
224
+ stop: () => Promise<void>;
225
+ }>;
226
+ /** Thrown when this machine has no Node new enough to run computerd. */
227
+ export declare class ComputerdNodeError extends Error {
228
+ constructor(found: string);
229
+ }
230
+ /**
231
+ * The Node that runs computerd: FUNGI_NODE, else this process when it is
232
+ * Node, else `node` on the PATH (the fungi CLI itself runs on Bun). The
233
+ * same bundle and shim mode run on Linux and macOS; nothing native loads.
234
+ */
235
+ export declare function resolveComputerdNode(): string;
236
+ /**
237
+ * Start computerd for `root`. It gets a scrubbed environment: the executable
238
+ * search path, a home, a locale and a terminal type, never this process's
239
+ * ambient variables or credentials.
240
+ */
241
+ export declare function startLocalComputerd(options: Readonly<{
242
+ root: string;
243
+ /** Defaults to {@link resolveComputerdNode}. */
244
+ node?: string;
245
+ startTimeoutMs?: number;
246
+ }>): Promise<LocalComputerd>;
247
+ /** Where one attached backend dials. */
248
+ export type LocalBackendTarget = Readonly<{
249
+ hubOrigin: string;
250
+ teamId: string;
251
+ computerId: string;
252
+ backendId: string;
253
+ deviceId: string;
254
+ /** The device's Ed25519 private key, base64 PKCS#8. Never sent. */
255
+ privateKey: string;
256
+ }>;
257
+ export type LocalBackendStatus = Readonly<{
258
+ kind: "connecting";
259
+ }> | Readonly<{
260
+ kind: "online";
261
+ }> | Readonly<{
262
+ kind: "offline";
263
+ retryInMs: number;
264
+ reason: string;
265
+ }> | Readonly<{
266
+ kind: "refused";
267
+ }>;
268
+ /**
269
+ * Keep one attached folder connected until `signal` aborts: dial out, serve
270
+ * the folder over the socket, and redial with backoff whenever it drops. A
271
+ * Hub that refuses the dial (a detached backend) stops the loop.
272
+ */
273
+ export declare function runLocalComputerBackend(options: Readonly<{
274
+ target: LocalBackendTarget;
275
+ root: string;
276
+ computerdPort: number;
277
+ signal: AbortSignal;
278
+ onStatus?: ((status: LocalBackendStatus) => void) | undefined;
279
+ /** Consecutive 404 refusals before the backend counts as detached. */
280
+ refusedLimit?: number;
281
+ }>): Promise<"stopped" | "refused">;
282
+ export type { FolderSync, FolderShell };