@fungi.computer/oyster 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1082 -2
- package/dist/agents.d.ts +114 -0
- package/dist/agents.js +322 -0
- package/dist/app-builds.d.ts +151 -0
- package/dist/app-builds.js +355 -0
- package/dist/app-sandbox.d.ts +2 -0
- package/dist/app-sandbox.js +2 -0
- package/dist/app-sources.d.ts +46 -0
- package/dist/app-sources.js +335 -0
- package/dist/app-storage.d.ts +141 -0
- package/dist/app-storage.js +281 -0
- package/dist/apps/gate.d.ts +105 -0
- package/dist/apps/gate.js +273 -0
- package/dist/apps/index.d.ts +5 -0
- package/dist/apps/index.js +5 -0
- package/dist/apps/installation.d.ts +248 -0
- package/dist/apps/installation.js +307 -0
- package/dist/apps/operations.d.ts +8 -0
- package/dist/apps/operations.js +98 -0
- package/dist/apps/tools.d.ts +33 -0
- package/dist/apps/tools.js +17 -0
- package/dist/auth.d.ts +11 -0
- package/dist/auth.js +93 -0
- package/dist/backend-request.d.ts +3 -0
- package/dist/backend-request.js +53 -0
- package/dist/build/build-jobs.d.ts +128 -0
- package/dist/build/build-jobs.js +197 -0
- package/dist/build/build-step.d.ts +26 -0
- package/dist/build/build-step.js +20 -0
- package/dist/build/bundled-html.d.ts +7 -0
- package/dist/build/bundled-html.js +64 -0
- package/dist/build/definition-emission.d.ts +34 -0
- package/dist/build/definition-emission.js +201 -0
- package/dist/build/effect.d.ts +231 -0
- package/dist/build/effect.js +396 -0
- package/dist/build/git-environment.d.ts +6 -0
- package/dist/build/git-environment.js +48 -0
- package/dist/build/html-recipe.d.ts +20 -0
- package/dist/build/html-recipe.js +51 -0
- package/dist/build/local-build.d.ts +57 -0
- package/dist/build/local-build.js +1078 -0
- package/dist/build/r2-artifact-writer.d.ts +31 -0
- package/dist/build/r2-artifact-writer.js +179 -0
- package/dist/build/snapshot-package-store.d.ts +2 -0
- package/dist/build/snapshot-package-store.js +21 -0
- package/dist/build/snapshot-program-scripts.d.ts +6 -0
- package/dist/build/snapshot-program-scripts.js +2 -0
- package/dist/build/submission-contract.d.ts +384 -0
- package/dist/build/submission-contract.js +70 -0
- package/dist/build/workspace-build.d.ts +50 -0
- package/dist/build/workspace-build.js +156 -0
- package/dist/build-contract.d.ts +4 -0
- package/dist/build-contract.js +4 -0
- package/dist/build-jobs.d.ts +2 -0
- package/dist/build-jobs.js +2 -0
- package/dist/build.d.ts +11 -0
- package/dist/build.js +13 -0
- package/dist/capability-descriptor.d.ts +46 -0
- package/dist/capability-descriptor.js +51 -0
- package/dist/catalog-frame.d.ts +39 -0
- package/dist/catalog-frame.js +72 -0
- package/dist/cause-class.d.ts +19 -0
- package/dist/cause-class.js +54 -0
- package/dist/cloudflare.d.ts +123 -0
- package/dist/cloudflare.js +391 -0
- package/dist/computer-terminal.d.ts +38 -0
- package/dist/computer-terminal.js +410 -0
- package/dist/computer.d.ts +60 -0
- package/dist/computer.js +33 -0
- package/dist/event-recipients.d.ts +57 -0
- package/dist/event-recipients.js +144 -0
- package/dist/facet-invocation.d.ts +4 -0
- package/dist/facet-invocation.js +45 -0
- package/dist/files.d.ts +42 -0
- package/dist/files.js +123 -0
- package/dist/frame-binding-wire-script.d.ts +2 -0
- package/dist/frame-binding-wire-script.js +1 -0
- package/dist/frame-binding-wire.d.ts +28 -0
- package/dist/frame-binding-wire.js +54 -0
- package/dist/git-admission.d.ts +52 -0
- package/dist/git-admission.js +188 -0
- package/dist/git-auth.d.ts +11 -0
- package/dist/git-auth.js +27 -0
- package/dist/git-door.d.ts +35 -0
- package/dist/git-door.js +108 -0
- package/dist/git-journal.d.ts +31 -0
- package/dist/git-journal.js +188 -0
- package/dist/git.d.ts +31 -0
- package/dist/git.js +64 -0
- package/dist/guest-policy.d.ts +9 -0
- package/dist/guest-policy.js +13 -0
- package/dist/host.d.ts +244 -0
- package/dist/host.js +1043 -0
- package/dist/index.d.ts +166 -0
- package/dist/index.js +320 -0
- package/dist/installation-consent.d.ts +79 -0
- package/dist/installation-consent.js +82 -0
- package/dist/internal/background-origins.d.ts +3 -0
- package/dist/internal/background-origins.js +8 -0
- package/dist/internal/delivery.d.ts +12 -0
- package/dist/internal/delivery.js +167 -0
- package/dist/internal/host-gate.d.ts +14 -0
- package/dist/internal/host-gate.js +62 -0
- package/dist/internal/registry-installations.d.ts +6 -0
- package/dist/internal/registry-installations.js +12 -0
- package/dist/invocation.d.ts +251 -0
- package/dist/invocation.js +377 -0
- package/dist/keys.d.ts +48 -0
- package/dist/keys.js +52 -0
- package/dist/mcp-owner.d.ts +27 -0
- package/dist/mcp-owner.js +143 -0
- package/dist/mcp.d.ts +89 -0
- package/dist/mcp.js +429 -0
- package/dist/media.d.ts +23 -0
- package/dist/media.js +12 -0
- package/dist/pack.d.ts +76 -0
- package/dist/pack.js +134 -0
- package/dist/page/capabilities.d.ts +23 -0
- package/dist/page/capabilities.js +256 -0
- package/dist/page/computer-handles.d.ts +79 -0
- package/dist/page/computer-handles.js +44 -0
- package/dist/page/lifetime.d.ts +6 -0
- package/dist/page/lifetime.js +19 -0
- package/dist/page/mount.d.ts +163 -0
- package/dist/page/mount.js +390 -0
- package/dist/page/storage.d.ts +3 -0
- package/dist/page/storage.js +82 -0
- package/dist/page/terminal.d.ts +5 -0
- package/dist/page/terminal.js +7 -0
- package/dist/page-script.d.ts +14 -0
- package/dist/page-script.js +5 -0
- package/dist/r2-files.d.ts +3 -0
- package/dist/r2-files.js +79 -0
- package/dist/release.d.ts +72 -0
- package/dist/release.js +164 -0
- package/dist/root-data.d.ts +86 -0
- package/dist/root-data.js +69 -0
- package/dist/shell/app-directory.d.ts +537 -0
- package/dist/shell/app-directory.js +195 -0
- package/dist/shell/browser-actions.d.ts +7 -0
- package/dist/shell/browser-actions.js +56 -0
- package/dist/shell/command-host.d.ts +112 -0
- package/dist/shell/command-host.js +502 -0
- package/dist/shell/commands.d.ts +25 -0
- package/dist/shell/commands.js +78 -0
- package/dist/shell/index.d.ts +8 -0
- package/dist/shell/index.js +8 -0
- package/dist/shell/keys.d.ts +41 -0
- package/dist/shell/keys.js +134 -0
- package/dist/shell/shell-module.d.ts +31 -0
- package/dist/shell/shell-module.js +37 -0
- package/dist/shell/window-manager.d.ts +124 -0
- package/dist/shell/window-manager.js +711 -0
- package/dist/shell/workbench-contract.d.ts +68 -0
- package/dist/shell/workbench-contract.js +1 -0
- package/dist/shell/workbench-host-types.d.ts +96 -0
- package/dist/shell/workbench-host-types.js +1 -0
- package/dist/shell/workbench-host.d.ts +34 -0
- package/dist/shell/workbench-host.js +347 -0
- package/dist/shell/workbench-model.d.ts +192 -0
- package/dist/shell/workbench-model.js +53 -0
- package/dist/shell/workbench.d.ts +4 -0
- package/dist/shell/workbench.js +2 -0
- package/dist/source/fork.d.ts +27 -0
- package/dist/source/fork.js +46 -0
- package/dist/source/index.d.ts +11 -0
- package/dist/source/index.js +11 -0
- package/dist/source/seed-receipt.d.ts +53 -0
- package/dist/source/seed-receipt.js +18 -0
- package/dist/source/team-source.d.ts +32 -0
- package/dist/source/team-source.js +42 -0
- package/dist/source/template-repository.d.ts +2 -0
- package/dist/source/template-repository.js +8 -0
- package/dist/source/template-write.d.ts +69 -0
- package/dist/source/template-write.js +344 -0
- package/dist/storage-frame.d.ts +64 -0
- package/dist/storage-frame.js +28 -0
- package/dist/tenant.d.ts +81 -0
- package/dist/tenant.js +71 -0
- package/dist/testing/capabilities.d.ts +29 -0
- package/dist/testing/capabilities.js +130 -0
- package/dist/testing/catalog.d.ts +3 -0
- package/dist/testing/catalog.js +53 -0
- package/dist/testing/index.d.ts +10 -0
- package/dist/testing/index.js +10 -0
- package/dist/testing/installation.d.ts +11 -0
- package/dist/testing/installation.js +25 -0
- package/dist/testing/native-worker.d.ts +21 -0
- package/dist/testing/native-worker.js +106 -0
- package/dist/testing/worker.d.ts +18 -0
- package/dist/testing/worker.js +67 -0
- package/dist/testing/workers-runtime.d.ts +24 -0
- package/dist/testing/workers-runtime.js +33 -0
- package/dist/webhook-delivery.d.ts +14 -0
- package/dist/webhook-delivery.js +17 -0
- package/dist/workers-git.d.ts +285 -0
- package/dist/workers-git.js +1174 -0
- package/dist/workers.d.ts +352 -0
- package/dist/workers.js +866 -0
- package/docs/api/README.md +82 -0
- package/docs/api/agents.md +332 -0
- package/docs/api/app-storage.md +209 -0
- package/docs/api/apps.md +1375 -0
- package/docs/api/auth.md +63 -0
- package/docs/api/build-contract.md +507 -0
- package/docs/api/build.md +1476 -0
- package/docs/api/cloudflare.md +421 -0
- package/docs/api/computer.md +203 -0
- package/docs/api/files.md +117 -0
- package/docs/api/git.md +470 -0
- package/docs/api/host.md +1328 -0
- package/docs/api/index.md +430 -0
- package/docs/api/invocation.md +738 -0
- package/docs/api/keys.md +175 -0
- package/docs/api/mcp.md +301 -0
- package/docs/api/media.md +40 -0
- package/docs/api/modules.md +42 -0
- package/docs/api/pack.md +138 -0
- package/docs/api/page/mount.md +298 -0
- package/docs/api/root-data.md +111 -0
- package/docs/api/shell/app-directory.md +332 -0
- package/docs/api/shell/command-host.md +224 -0
- package/docs/api/shell/commands.md +197 -0
- package/docs/api/shell/keys.md +183 -0
- package/docs/api/shell/shell-module.md +143 -0
- package/docs/api/shell/window-manager.md +267 -0
- package/docs/api/shell/workbench.md +186 -0
- package/docs/api/shell.md +1788 -0
- package/docs/api/tenant.md +194 -0
- package/docs/api/workers-git.md +1527 -0
- package/docs/api/workers.md +1678 -0
- package/docs/media/architecture.mmd +32 -0
- package/docs/media/architecture.png +0 -0
- package/docs/media/bonsai-network.png +0 -0
- package/docs/media/bonsai-repository.png +0 -0
- package/docs/site/agent-quickstart.mdx +349 -0
- package/examples/git/worker.ts +39 -0
- package/examples/git/wrangler.jsonc +32 -0
- package/examples/standalone/worker.ts +14 -0
- package/examples/standalone/wrangler.jsonc +21 -0
- package/package.json +249 -3
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
flowchart LR
|
|
2
|
+
people["Browsers<br/>git clients<br/>MCP agents"]
|
|
3
|
+
|
|
4
|
+
subgraph oyster["Oyster Worker · Cloudflare Workers or celld"]
|
|
5
|
+
direction TB
|
|
6
|
+
router["Router<br/>/apps · /api · /git · /mcp"]
|
|
7
|
+
home["Tourtois home<br/>launcher · windows · settings"]
|
|
8
|
+
registry[("OysterRegistry DO<br/>installations")]
|
|
9
|
+
instance["App Instance DO<br/>one per installation"]
|
|
10
|
+
facet["App backend<br/>Worker Loader facet<br/>with its own SQLite"]
|
|
11
|
+
door["git door<br/>per-agent keys"]
|
|
12
|
+
router --> home
|
|
13
|
+
router --> registry
|
|
14
|
+
router --> instance --> facet
|
|
15
|
+
router --> door
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
apps["Apps as immutable Releases<br/>Bonsai · Workflows · Shed<br/>Inkcap · Grove · Prints"]
|
|
19
|
+
|
|
20
|
+
subgraph services["Bindings the host provides"]
|
|
21
|
+
direction TB
|
|
22
|
+
r2[("R2<br/>Release artifacts")]
|
|
23
|
+
artifacts[("Artifacts<br/>git repositories")]
|
|
24
|
+
platform["Braid Workflows · Computers<br/>Agents · Workers AI"]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
people --> router
|
|
28
|
+
home -. "UI in sandboxed frames" .-> apps
|
|
29
|
+
registry -- "Release refs" --> r2
|
|
30
|
+
door --> artifacts
|
|
31
|
+
facet -- "declared only" --> platform
|
|
32
|
+
facet -- "GIT binding" --> artifacts
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Agent quick start
|
|
3
|
+
description:
|
|
4
|
+
Hand this guide to a coding agent to install Oyster from npm, run it locally,
|
|
5
|
+
install an App, connect over MCP and deploy.
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
You are an agent setting up Oyster for a person. Follow these steps in order.
|
|
9
|
+
Run every command exactly as written, from the directory each step names.
|
|
10
|
+
Compare its output with the expected output before you go on. When a step fails,
|
|
11
|
+
read the "If it fails" notes under it before trying anything else.
|
|
12
|
+
|
|
13
|
+
## What Oyster is
|
|
14
|
+
|
|
15
|
+
- Oyster hosts Apps on one Cloudflare Worker, or on celld, a Workers-compatible
|
|
16
|
+
runtime you host yourself.
|
|
17
|
+
- Each App is an immutable Release built from an exact git commit. Its backend
|
|
18
|
+
runs in its own Durable Object with its own SQLite, and it gets only the
|
|
19
|
+
bindings it declares.
|
|
20
|
+
- People use Apps in a browser. Agents call the same App methods over HTTP or
|
|
21
|
+
through one MCP endpoint, `/mcp`.
|
|
22
|
+
|
|
23
|
+
## 1. Check prerequisites
|
|
24
|
+
|
|
25
|
+
You need Node 24 or newer, pnpm, Git, curl and openssl. You do not need a
|
|
26
|
+
Cloudflare account until step 7, and you never need Docker for these steps.
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
node --version
|
|
30
|
+
pnpm --version
|
|
31
|
+
git --version
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Expected output: a `v24.` or later Node version, any pnpm version, then any
|
|
35
|
+
`git version 2.x` line.
|
|
36
|
+
|
|
37
|
+
If it fails:
|
|
38
|
+
|
|
39
|
+
- Wrong Node: install Node 24 with your version manager, for example
|
|
40
|
+
`mise use node@24` or `nvm install 24`.
|
|
41
|
+
- No pnpm: run `corepack enable`, or use `npm install` wherever this guide says
|
|
42
|
+
`pnpm add`.
|
|
43
|
+
|
|
44
|
+
## 2. Create a project
|
|
45
|
+
|
|
46
|
+
Make a new directory, install Oyster and Wrangler, and copy the standalone
|
|
47
|
+
example Worker out of the package:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
mkdir my-oyster
|
|
51
|
+
cd my-oyster
|
|
52
|
+
pnpm add @fungi.computer/oyster
|
|
53
|
+
pnpm add -D wrangler
|
|
54
|
+
cp node_modules/@fungi.computer/oyster/examples/standalone/worker.ts .
|
|
55
|
+
cp node_modules/@fungi.computer/oyster/examples/standalone/wrangler.jsonc .
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Expected output: pnpm lists the added package after each `add`, and `ls` now
|
|
59
|
+
shows `node_modules`, `package.json`, `worker.ts` and `wrangler.jsonc`.
|
|
60
|
+
|
|
61
|
+
Every later command runs from this `my-oyster` directory unless the step says
|
|
62
|
+
otherwise.
|
|
63
|
+
|
|
64
|
+
If it fails:
|
|
65
|
+
|
|
66
|
+
- `ERR_PNPM_IGNORED_BUILDS` naming packages such as `esbuild`, `workerd` or
|
|
67
|
+
`protobufjs`: pnpm installed them without running their install scripts, and
|
|
68
|
+
nothing in this guide needs those scripts. Go on.
|
|
69
|
+
- `cp: cannot stat …/examples/standalone/…`: the install did not finish. Run
|
|
70
|
+
`pnpm add @fungi.computer/oyster` again and read its first error.
|
|
71
|
+
|
|
72
|
+
## 3. Run a local Oyster
|
|
73
|
+
|
|
74
|
+
`worker.ts` and `wrangler.jsonc` are a complete single-owner Oyster. Run it
|
|
75
|
+
under `wrangler dev` on your machine. Choose an owner key and keep this terminal
|
|
76
|
+
open:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
export OYSTER_OWNER_KEY=$(openssl rand -hex 24)
|
|
80
|
+
export OYSTER_URL=http://127.0.0.1:8787
|
|
81
|
+
echo "$OYSTER_OWNER_KEY" > .oyster-owner-key
|
|
82
|
+
npx wrangler dev --local --ip 127.0.0.1 --port 8787 \
|
|
83
|
+
--var OYSTER_OWNER_KEY:$OYSTER_OWNER_KEY --var OYSTER_ORIGIN:$OYSTER_URL
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Expected output: a bindings table listing `env.MCP (OysterMcp)`,
|
|
87
|
+
`env.REGISTRY (OysterRegistry)`, `env.INSTANCES (OysterInstance)`,
|
|
88
|
+
`env.RELEASES (oyster-releases)` and `env.LOADER`, then
|
|
89
|
+
`Ready on http://127.0.0.1:8787`.
|
|
90
|
+
|
|
91
|
+
Keep it running. Open a second terminal in `my-oyster`, set the same two
|
|
92
|
+
variables there, and check that Oyster answers:
|
|
93
|
+
|
|
94
|
+
```sh
|
|
95
|
+
export OYSTER_OWNER_KEY=$(cat .oyster-owner-key)
|
|
96
|
+
export OYSTER_URL=http://127.0.0.1:8787
|
|
97
|
+
curl -s -o /dev/null -w '%{http_code}\n' $OYSTER_URL/login
|
|
98
|
+
curl -s -o /dev/null -w '%{http_code}\n' $OYSTER_URL/api/installations
|
|
99
|
+
curl -s -H "Authorization: Bearer $OYSTER_OWNER_KEY" $OYSTER_URL/api/installations
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Expected output: `200`, then `401` (no key), then `[]` (no Apps installed yet).
|
|
103
|
+
|
|
104
|
+
If it fails:
|
|
105
|
+
|
|
106
|
+
- `Address already in use`: another process holds port 8787. Pick another port
|
|
107
|
+
and use it in both `--port` and `OYSTER_URL`.
|
|
108
|
+
- `curl` prints `000`: the server is not ready yet. Wait for the `Ready on`
|
|
109
|
+
line.
|
|
110
|
+
- `401` with the key: the two terminals hold different keys. Set
|
|
111
|
+
`OYSTER_OWNER_KEY` from `.oyster-owner-key` in both.
|
|
112
|
+
- An agent runtime that cannot keep a process in the foreground starts the same
|
|
113
|
+
command in the background with its output in a log, then waits until the
|
|
114
|
+
`/login` check prints `200`:
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
npx wrangler dev --local --ip 127.0.0.1 --port 8787 \
|
|
118
|
+
--var OYSTER_OWNER_KEY:$OYSTER_OWNER_KEY --var OYSTER_ORIGIN:$OYSTER_URL \
|
|
119
|
+
> oyster-dev.log 2>&1 &
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
To stop the server, press Ctrl-C in its terminal. A background server runs as
|
|
123
|
+
several processes (`npm exec`, `wrangler.js` and `workerd`); stop them all by
|
|
124
|
+
ending `wrangler.js`, then check that `/login` prints `000`:
|
|
125
|
+
|
|
126
|
+
```sh
|
|
127
|
+
pkill -f 'wrangler[.]js dev .*--port 8787'
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 4. Add an App
|
|
131
|
+
|
|
132
|
+
An App is a git repository with a `package.json` that declares it under
|
|
133
|
+
`fungi.app`. Make the smallest one, a single committed HTML page:
|
|
134
|
+
|
|
135
|
+
```sh
|
|
136
|
+
mkdir -p hello-app/bundle
|
|
137
|
+
cat > hello-app/package.json <<'EOF'
|
|
138
|
+
{
|
|
139
|
+
"name": "hello-app",
|
|
140
|
+
"private": true,
|
|
141
|
+
"fungi": { "app": { "displayName": "Hello", "capabilities": [] } }
|
|
142
|
+
}
|
|
143
|
+
EOF
|
|
144
|
+
cat > hello-app/bundle/index.html <<'EOF'
|
|
145
|
+
<!doctype html>
|
|
146
|
+
<title>Hello</title>
|
|
147
|
+
<h1>Hello from my first Oyster App</h1>
|
|
148
|
+
EOF
|
|
149
|
+
git -C hello-app init -q
|
|
150
|
+
git -C hello-app add .
|
|
151
|
+
git -C hello-app -c user.name=Oyster -c user.email=oyster@example.invalid \
|
|
152
|
+
commit -qm "Hello App"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Then pack that commit into a Release and install it as installation `hello` with
|
|
156
|
+
`@fungi.computer/oyster/pack`:
|
|
157
|
+
|
|
158
|
+
```sh
|
|
159
|
+
cat > install-hello.mjs <<'EOF'
|
|
160
|
+
import { execFileSync } from "node:child_process";
|
|
161
|
+
import { mkdtemp, rm } from "node:fs/promises";
|
|
162
|
+
import { tmpdir } from "node:os";
|
|
163
|
+
import { join, resolve } from "node:path";
|
|
164
|
+
import process from "node:process";
|
|
165
|
+
import { installRelease, packRelease } from "@fungi.computer/oyster/pack";
|
|
166
|
+
|
|
167
|
+
const source = resolve("hello-app");
|
|
168
|
+
const commit = execFileSync("git", ["-C", source, "rev-parse", "HEAD"], {
|
|
169
|
+
encoding: "utf8",
|
|
170
|
+
}).trim();
|
|
171
|
+
const out = await mkdtemp(join(tmpdir(), "oyster-hello-"));
|
|
172
|
+
try {
|
|
173
|
+
await packRelease({
|
|
174
|
+
appId: "app_hello",
|
|
175
|
+
sourceDirectory: source,
|
|
176
|
+
sourceRevision: `git:${commit}`,
|
|
177
|
+
recipe: {
|
|
178
|
+
kind: "bundled-html",
|
|
179
|
+
entry: "bundle/index.html",
|
|
180
|
+
assets: {
|
|
181
|
+
maxManifestBytes: 65_536,
|
|
182
|
+
maxObjects: 16,
|
|
183
|
+
maxObjectBytes: 1_048_576,
|
|
184
|
+
maxTotalBytes: 4_194_304,
|
|
185
|
+
},
|
|
186
|
+
assetBase: "./",
|
|
187
|
+
backend: null,
|
|
188
|
+
maxOutputBytes: 4_194_304,
|
|
189
|
+
},
|
|
190
|
+
outDirectory: join(out, "release"),
|
|
191
|
+
});
|
|
192
|
+
const response = await installRelease({
|
|
193
|
+
directory: join(out, "release"),
|
|
194
|
+
url: process.env.OYSTER_URL,
|
|
195
|
+
key: process.env.OYSTER_OWNER_KEY,
|
|
196
|
+
installationId: "hello",
|
|
197
|
+
});
|
|
198
|
+
console.log(`installed hello: ${response.status} ${await response.text()}`);
|
|
199
|
+
} finally {
|
|
200
|
+
await rm(out, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
EOF
|
|
203
|
+
node install-hello.mjs
|
|
204
|
+
curl -s -o /dev/null -w '%{http_code}\n' \
|
|
205
|
+
-H "Authorization: Bearer $OYSTER_OWNER_KEY" $OYSTER_URL/apps/hello/
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Expected output: one line starting
|
|
209
|
+
`installed hello: 201 {"installationId":"hello",` and containing
|
|
210
|
+
`"status":"enabled"`, then `200`. Running `node install-hello.mjs` again prints
|
|
211
|
+
`installed hello: 200`: the installation already exists, and Oyster switches it
|
|
212
|
+
to the Release just packed.
|
|
213
|
+
|
|
214
|
+
A person can open `$OYSTER_URL/login`, sign in with the owner key, and open
|
|
215
|
+
Hello from the installation list at `$OYSTER_URL/`.
|
|
216
|
+
|
|
217
|
+
If it fails:
|
|
218
|
+
|
|
219
|
+
- `installed hello: 401`: `OYSTER_OWNER_KEY` in this terminal differs from the
|
|
220
|
+
server's. Set it from `.oyster-owner-key`.
|
|
221
|
+
- `fetch failed` or `ECONNREFUSED`: the server from step 3 is not running.
|
|
222
|
+
- The build reads committed files only. To install an edit, commit it in
|
|
223
|
+
`hello-app` and run `node install-hello.mjs` again; Oyster switches the
|
|
224
|
+
installation to the new Release and keeps its data.
|
|
225
|
+
|
|
226
|
+
An App with a backend, typed methods and its own SQLite adds
|
|
227
|
+
`fungi.backendDefinition` and a `backend` entry to its recipe. The
|
|
228
|
+
[Notes example App](https://github.com/fungi-computer/oyster/tree/main/examples/notes-app)
|
|
229
|
+
is one, and the Oyster README's "Pack an App" and "Build App Releases" sections
|
|
230
|
+
describe the recipe.
|
|
231
|
+
|
|
232
|
+
## 5. Connect over MCP
|
|
233
|
+
|
|
234
|
+
`/mcp` serves every installed App to MCP clients through one `execute` tool, in
|
|
235
|
+
which each App's methods are `env.<APP>`. The owner key signs in. For Claude
|
|
236
|
+
Code:
|
|
237
|
+
|
|
238
|
+
```sh
|
|
239
|
+
claude mcp add --transport http oyster "$OYSTER_URL/mcp" \
|
|
240
|
+
--header "Authorization: Bearer $OYSTER_OWNER_KEY"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
To check `/mcp` without an MCP client, open a session and list its tools:
|
|
244
|
+
|
|
245
|
+
```sh
|
|
246
|
+
MCP=(-H "Authorization: Bearer $OYSTER_OWNER_KEY" -H 'content-type: application/json' -H 'accept: application/json, text/event-stream')
|
|
247
|
+
SESSION=$(curl -s "${MCP[@]}" -D - -o /dev/null \
|
|
248
|
+
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' \
|
|
249
|
+
$OYSTER_URL/mcp | grep -i '^mcp-session-id' | cut -d' ' -f2 | tr -d '\r')
|
|
250
|
+
curl -s "${MCP[@]}" -H "mcp-session-id: $SESSION" -H 'mcp-protocol-version: 2025-11-25' \
|
|
251
|
+
-d '{"jsonrpc":"2.0","method":"notifications/initialized"}' $OYSTER_URL/mcp
|
|
252
|
+
curl -s "${MCP[@]}" -H "mcp-session-id: $SESSION" -H 'mcp-protocol-version: 2025-11-25' \
|
|
253
|
+
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' $OYSTER_URL/mcp
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Expected output: the last command prints an `event: message` block whose `data:`
|
|
257
|
+
line contains `"name":"execute"`.
|
|
258
|
+
|
|
259
|
+
If it fails:
|
|
260
|
+
|
|
261
|
+
- `SESSION` is empty: the `initialize` call was refused. Check the key as in
|
|
262
|
+
step 3.
|
|
263
|
+
- Named keys for each agent (`POST /api/keys`) come with the Git worker in
|
|
264
|
+
step 6. The plain standalone Worker answers `404` there.
|
|
265
|
+
|
|
266
|
+
## 6. Add Git
|
|
267
|
+
|
|
268
|
+
The package also ships `examples/git`: the same Oyster with the git door, so
|
|
269
|
+
people and agents `clone`, `fetch` and `push` App repositories with ordinary
|
|
270
|
+
`git` at `/git/<installation>/<repo>.git`. Repositories live in Cloudflare
|
|
271
|
+
Artifacts, which `wrangler dev` cannot emulate, so the Git worker runs once it
|
|
272
|
+
is deployed in step 7. To use it, copy it over the standalone files:
|
|
273
|
+
|
|
274
|
+
```sh
|
|
275
|
+
cp node_modules/@fungi.computer/oyster/examples/git/worker.ts .
|
|
276
|
+
cp node_modules/@fungi.computer/oyster/examples/git/wrangler.jsonc .
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Compared with `examples/standalone`, `examples/git/worker.ts` builds one
|
|
280
|
+
`oysterGitRoot` with `ownerOrKeyTenancy` and adds:
|
|
281
|
+
|
|
282
|
+
- the git door, mounted by `createOysterGitWorker(root)`;
|
|
283
|
+
- owner and named keys that sign in to git, plus `/api/keys` and
|
|
284
|
+
`/api/webhooks`;
|
|
285
|
+
- `OysterGitInstance` as `OysterInstance`, which hands Apps `env.GIT` and
|
|
286
|
+
`env.EVENTS`;
|
|
287
|
+
- the `ARTIFACTS` binding and the `KEYS` and `WEBHOOKS` Durable Objects in its
|
|
288
|
+
`wrangler.jsonc`, which names the Worker `oyster-git`.
|
|
289
|
+
|
|
290
|
+
With the Git worker deployed, give each agent its own key:
|
|
291
|
+
|
|
292
|
+
```sh
|
|
293
|
+
curl -s -X POST -H "Authorization: Bearer $OYSTER_OWNER_KEY" \
|
|
294
|
+
-H 'content-type: application/json' \
|
|
295
|
+
-d '{"name":"morel","grants":[{"capability":"apps.use","targets":{"kind":"all"}}]}' \
|
|
296
|
+
$OYSTER_URL/api/keys
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Expected output: JSON with a `plaintext` field starting `oyk_`. Oyster shows it
|
|
300
|
+
once; that key signs in to `git` and to `/mcp`.
|
|
301
|
+
|
|
302
|
+
If it fails:
|
|
303
|
+
|
|
304
|
+
- `404`: the deployed Worker is the standalone one. Copy the `examples/git`
|
|
305
|
+
files and deploy again.
|
|
306
|
+
|
|
307
|
+
## 7. Deploy to Cloudflare Workers
|
|
308
|
+
|
|
309
|
+
This step needs a Cloudflare account on Workers Paid; Git also needs Artifacts
|
|
310
|
+
enabled on it. Ask the person before you deploy anything.
|
|
311
|
+
|
|
312
|
+
`wrangler.jsonc` names the Worker (`oyster`, or `oyster-git` for Git) and its R2
|
|
313
|
+
bucket, `oyster-releases`. The Worker's address is
|
|
314
|
+
`https://<worker name>.<your-subdomain>.workers.dev`, or a custom domain. For
|
|
315
|
+
the standalone Worker:
|
|
316
|
+
|
|
317
|
+
```sh
|
|
318
|
+
npx wrangler login
|
|
319
|
+
npx wrangler r2 bucket create oyster-releases
|
|
320
|
+
export OYSTER_URL=https://oyster.<your-subdomain>.workers.dev
|
|
321
|
+
npx wrangler deploy --var OYSTER_ORIGIN:$OYSTER_URL
|
|
322
|
+
printf '%s' "$OYSTER_OWNER_KEY" | npx wrangler secret put OYSTER_OWNER_KEY
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Expected output: `wrangler deploy` prints the deployed `https://` URL, and
|
|
326
|
+
`wrangler secret put` reports the secret uploaded. Then install Hello exactly as
|
|
327
|
+
in step 4, with `OYSTER_URL` set to the deployed address.
|
|
328
|
+
|
|
329
|
+
To keep `OYSTER_ORIGIN` across later deploys, set `vars.OYSTER_ORIGIN` in
|
|
330
|
+
`wrangler.jsonc`; every deploy replaces the Worker's variables.
|
|
331
|
+
|
|
332
|
+
If it fails:
|
|
333
|
+
|
|
334
|
+
- `R2 bucket … already exists`: use that bucket, or pick a new name and put it
|
|
335
|
+
in `wrangler.jsonc`.
|
|
336
|
+
- A secret prompt on stdin: pipe the key as shown; never type it into a log.
|
|
337
|
+
- Bindings or migrations refused: deploy the copied `wrangler.jsonc` unchanged
|
|
338
|
+
except for `vars` and the bucket name.
|
|
339
|
+
|
|
340
|
+
## Where to go next
|
|
341
|
+
|
|
342
|
+
- The Oyster README (`node_modules/@fungi.computer/oyster/README.md`) explains
|
|
343
|
+
the host, Apps, capabilities, MCP and the shell in depth.
|
|
344
|
+
- [oyster.shiit.app](https://oyster.shiit.app/) is a complete deployment of the
|
|
345
|
+
Git worker with Fungi's App set: Bonsai, Workflows, Computers, Agents and
|
|
346
|
+
media.
|
|
347
|
+
- Oyster is built from the other Fungi packages, each its own repository under
|
|
348
|
+
[github.com/fungi-computer](https://github.com/fungi-computer) and on npm as
|
|
349
|
+
`@fungi.computer/<name>`. The README's "Part of Fungi" section lists them.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Add Git to an Oyster Worker. `host.git` is the whole seam: `createOyster`
|
|
2
|
+
// mounts the git door at `/git/<installation>/<repo>.git`, and every clone,
|
|
3
|
+
// fetch and push passes host authentication, live installation admission and
|
|
4
|
+
// the App's own push policy before Artifacts sees a byte.
|
|
5
|
+
//
|
|
6
|
+
// One root composes every tenant host. HTTP, MCP and background wakes all reach
|
|
7
|
+
// it through `root.hostFor`, so they share one auth, one workspace and one
|
|
8
|
+
// capability set.
|
|
9
|
+
import type { TenantScope } from "@fungi.computer/oyster/tenant";
|
|
10
|
+
import {
|
|
11
|
+
createOysterGitMcpOwner,
|
|
12
|
+
createOysterGitWorker,
|
|
13
|
+
OysterGitInstance,
|
|
14
|
+
oysterGitRoot,
|
|
15
|
+
ownerOrKeyTenancy,
|
|
16
|
+
type OysterGitWorkerEnv,
|
|
17
|
+
} from "@fungi.computer/oyster/workers-git";
|
|
18
|
+
|
|
19
|
+
export { OysterRegistry } from "@fungi.computer/oyster/workers";
|
|
20
|
+
// The one binding entrypoint, whose table also hands Apps `env.GIT` and `env.EVENTS`.
|
|
21
|
+
export {
|
|
22
|
+
OysterBinding,
|
|
23
|
+
OysterKeys,
|
|
24
|
+
OysterWebhooks,
|
|
25
|
+
} from "@fungi.computer/oyster/workers-git";
|
|
26
|
+
|
|
27
|
+
// The owner key, or a named key minted at POST /api/keys, signs `git` in.
|
|
28
|
+
const root = oysterGitRoot<OysterGitWorkerEnv>({ tenancy: ownerOrKeyTenancy });
|
|
29
|
+
|
|
30
|
+
export class OysterMcp extends createOysterGitMcpOwner(root) {}
|
|
31
|
+
|
|
32
|
+
/** Instances that hand Apps `env.GIT` and `env.EVENTS`; wakes use the root's host. */
|
|
33
|
+
export class OysterInstance extends OysterGitInstance {
|
|
34
|
+
protected hostFor(scope: TenantScope) {
|
|
35
|
+
return root.hostFor(this.env, scope);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export default createOysterGitWorker(root);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../../node_modules/wrangler/config-schema.json",
|
|
3
|
+
"name": "oyster-git",
|
|
4
|
+
"main": "worker.ts",
|
|
5
|
+
"compatibility_date": "2026-09-04",
|
|
6
|
+
"compatibility_flags": ["nodejs_compat"],
|
|
7
|
+
"vars": { "OYSTER_ORIGIN": "https://oyster.example" },
|
|
8
|
+
"worker_loaders": [{ "binding": "LOADER" }],
|
|
9
|
+
"r2_buckets": [{ "binding": "RELEASES", "bucket_name": "oyster-releases" }],
|
|
10
|
+
"artifacts": [{ "binding": "ARTIFACTS", "namespace": "oyster" }],
|
|
11
|
+
"durable_objects": {
|
|
12
|
+
"bindings": [
|
|
13
|
+
{ "name": "MCP", "class_name": "OysterMcp" },
|
|
14
|
+
{ "name": "REGISTRY", "class_name": "OysterRegistry" },
|
|
15
|
+
{ "name": "INSTANCES", "class_name": "OysterInstance" },
|
|
16
|
+
{ "name": "KEYS", "class_name": "OysterKeys" },
|
|
17
|
+
{ "name": "WEBHOOKS", "class_name": "OysterWebhooks" },
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
"migrations": [
|
|
21
|
+
{
|
|
22
|
+
"tag": "v1",
|
|
23
|
+
"new_sqlite_classes": [
|
|
24
|
+
"OysterMcp",
|
|
25
|
+
"OysterRegistry",
|
|
26
|
+
"OysterInstance",
|
|
27
|
+
"OysterKeys",
|
|
28
|
+
"OysterWebhooks",
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// A standalone Oyster: one owner, Apps installed from Releases, each App's
|
|
2
|
+
// backend in its own Durable Object with SQLite. Runs on Cloudflare Workers
|
|
3
|
+
// (`wrangler deploy`) and on celld (`celld dev`) from the same code.
|
|
4
|
+
import { createOysterWorker } from "@fungi.computer/oyster/workers";
|
|
5
|
+
|
|
6
|
+
// Every App binding in every invocation frame is the one `OysterBinding` entrypoint.
|
|
7
|
+
export {
|
|
8
|
+
OysterBinding,
|
|
9
|
+
OysterInstance,
|
|
10
|
+
OysterRegistry,
|
|
11
|
+
OysterMcp,
|
|
12
|
+
} from "@fungi.computer/oyster/workers";
|
|
13
|
+
|
|
14
|
+
export default createOysterWorker();
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../../../node_modules/wrangler/config-schema.json",
|
|
3
|
+
"name": "oyster",
|
|
4
|
+
"main": "worker.ts",
|
|
5
|
+
"compatibility_date": "2026-09-04",
|
|
6
|
+
"compatibility_flags": ["nodejs_compat"],
|
|
7
|
+
"vars": { "OYSTER_ORIGIN": "https://oyster.example" },
|
|
8
|
+
"worker_loaders": [{ "binding": "LOADER" }],
|
|
9
|
+
"r2_buckets": [{ "binding": "RELEASES", "bucket_name": "oyster-releases" }],
|
|
10
|
+
"durable_objects": {
|
|
11
|
+
"bindings": [
|
|
12
|
+
{ "name": "MCP", "class_name": "OysterMcp" },
|
|
13
|
+
{ "name": "REGISTRY", "class_name": "OysterRegistry" },
|
|
14
|
+
{ "name": "INSTANCES", "class_name": "OysterInstance" },
|
|
15
|
+
],
|
|
16
|
+
},
|
|
17
|
+
"migrations": [
|
|
18
|
+
{ "tag": "v1", "new_sqlite_classes": ["OysterRegistry", "OysterInstance"] },
|
|
19
|
+
{ "tag": "mcp-session-owner-v1", "new_sqlite_classes": ["OysterMcp"] },
|
|
20
|
+
],
|
|
21
|
+
}
|