@moinulmoin/eden 0.10.0 → 0.11.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 CHANGED
@@ -12,15 +12,15 @@ If you already have an Eve project, start with `eden deploy`.
12
12
  Use any one of these package managers:
13
13
 
14
14
  ```sh
15
- npm install --global @moinulmoin/eden@0.10.0
15
+ npm install --global @moinulmoin/eden@0.11.0
16
16
  ```
17
17
 
18
18
  ```sh
19
- pnpm add --global @moinulmoin/eden@0.10.0
19
+ pnpm add --global @moinulmoin/eden@0.11.0
20
20
  ```
21
21
 
22
22
  ```sh
23
- bun add --global @moinulmoin/eden@0.10.0
23
+ bun add --global @moinulmoin/eden@0.11.0
24
24
  ```
25
25
 
26
26
  Then confirm the CLI is available:
package/dist/SKILL.md ADDED
@@ -0,0 +1,215 @@
1
+ ---
2
+ name: eden
3
+ description: Deploy, update, check, or remove a Vercel Eve agent on the user's own Cloudflare account with the Eden CLI. Use when someone wants to run an Eve project off Vercel, check whether an Eve project is ready for Cloudflare, ship a change to an Eden deployment, upgrade Eve on one, or tear one down.
4
+ ---
5
+
6
+ # Eden: run an Eve agent on Cloudflare
7
+
8
+ Eden (`@moinulmoin/eden`) builds an existing Eve project as-is and runs it in a
9
+ Cloudflare Container behind a Worker on the user's own account. It never
10
+ rewrites the project; you make any needed changes yourself, guided by
11
+ preflight.
12
+
13
+ `npx @moinulmoin/eden skill` prints this guide for the installed CLI version.
14
+ Docs: https://github.com/moinulmoin/eden (failure and warning codes:
15
+ `docs/troubleshooting.md`).
16
+
17
+ ## The loop
18
+
19
+ 1. Check the machine and account (below).
20
+ 2. Write the env file (below).
21
+ 3. Run preflight and read the JSON:
22
+
23
+ ```sh
24
+ npx @moinulmoin/eden preflight --env-file ~/.config/<agent>/preview.env --json
25
+ ```
26
+
27
+ Preflight builds and boots the agent locally and checks Cloudflare access.
28
+ It changes nothing on Cloudflare. It prints one JSON object:
29
+ `ok`, `checks[]` (`id`, `status`, `message`, `remediation`) and
30
+ `warnings[]` (`id`, `message`, `remediation`).
31
+ 4. Fix every failed check, then every warning, using its `remediation`. Re-run
32
+ until `ok` is `true` and no warning applies. A warning does not block a
33
+ deploy, but each one names something that breaks or loses data on
34
+ Cloudflare; only leave one if the user accepts that.
35
+ 5. A clean preflight means the project can run on Cloudflare. It does not
36
+ check that keys are real: before deploying, make sure the user has replaced
37
+ every placeholder secret in the env file.
38
+ 6. Deploy, then verify with a real message (below). Confirm with the user
39
+ first for production.
40
+
41
+ ## Machine and account
42
+
43
+ - macOS or Linux, Node `>=24.17.0`
44
+ - Docker or OrbStack running, with linux/amd64 support
45
+ - Cloudflare Workers Paid plan (Containers need it), and Wrangler logged in:
46
+ `npx wrangler@4.147.0 login` (the user must do this in a browser)
47
+
48
+ ## Project setup Cloudflare needs
49
+
50
+ These are what preflight checks most often. Fix them before the first run to
51
+ save a build.
52
+
53
+ - **One lockfile.** Either a root `package-lock.json`, or an exact
54
+ `"packageManager": "pnpm@x.y.z"` with a root `pnpm-lock.yaml`. Not both, not
55
+ Bun. Dependency install scripts do not run.
56
+ - **Sandbox.** Add `just-bash` as a production dependency. On Eve `0.83.0` and
57
+ later, also select it explicitly in `agent/sandbox.ts` (or
58
+ `agent/sandbox/sandbox.ts` if an `agent/sandbox/` folder exists):
59
+
60
+ ```ts
61
+ import { defineSandbox } from 'eve/sandbox';
62
+ import { JustBashSandbox } from 'eve/sandbox/just-bash';
63
+
64
+ export const environment = JustBashSandbox.environment();
65
+ export default defineSandbox(() => environment.open());
66
+ ```
67
+
68
+ Vercel Sandbox, Docker, and other providers do not run on Eden.
69
+ - **Channel auth (`EVE_AUTH_SCAFFOLD`).** The `eve init` auth in
70
+ `agent/channels/eve.ts` (`vercelOidc()`, `localDev()`, `placeholderAuth()`)
71
+ rejects every Cloudflare caller with 401. Replace it with a real
72
+ authenticator, for example a bearer token read from the env file. Keep
73
+ `localDev()` so `eve dev` still works locally; it is ignored in production:
74
+
75
+ ```ts
76
+ import { localDev, type AuthFn } from "eve/channels/auth";
77
+ import { eveChannel } from "eve/channels/eve";
78
+
79
+ const authenticate: AuthFn<Request> = (request) => {
80
+ const expected = process.env.AGENT_AUTH_TOKEN;
81
+ if (!expected || request.headers.get("authorization") !== `Bearer ${expected}`) {
82
+ return null;
83
+ }
84
+ return {
85
+ attributes: {},
86
+ authenticator: "bearer-token",
87
+ principalId: "app-client",
88
+ principalType: "app",
89
+ };
90
+ };
91
+
92
+ export default eveChannel({ auth: [authenticate, localDev()] });
93
+ ```
94
+
95
+ - **Model key (`EVE_AI_GATEWAY_KEY_MISSING`).** A string model such as
96
+ `"openai/gpt-5"` goes through Vercel AI Gateway. On Vercel that
97
+ authenticates automatically; on Cloudflare it needs `AI_GATEWAY_API_KEY` in
98
+ the env file. A direct provider such as `openai("…")` from `@ai-sdk/openai`
99
+ needs that provider's key instead.
100
+ - **Vercel Connect (`EVE_VERCEL_CONNECT`).** `connect()` from
101
+ `@vercel/connect/eve` gets credentials using an identity only Vercel
102
+ provides, so those connections fail on Cloudflare. Put the service's own key
103
+ or bot token in the env file and replace `connect(...)` in the connection
104
+ with `auth: { getToken: async () => ({ token: process.env.SERVICE_TOKEN! }) }`.
105
+ For per-user sign-in, use `defineInteractiveAuthorization` from
106
+ `eve/connections` with your own OAuth app. Having `@vercel/connect` in
107
+ `package.json` without importing it is fine.
108
+ - **Durable state (`EVE_WORLD_LOCAL`).** The default local Workflow World loses
109
+ sessions and pending approvals on every update. Use one of:
110
+ - `@moinulmoin/eden-world-cloudflare`: no database; sessions also survive Eve
111
+ upgrades. Add it as a production dependency and set in `agent/agent.ts`:
112
+
113
+ ```ts
114
+ experimental: { workflow: { world: "@moinulmoin/eden-world-cloudflare" } },
115
+ ```
116
+
117
+ - `@workflow/world-postgres` with a direct, unpooled Postgres URL in the env
118
+ file.
119
+
120
+ ## Env file and secrets
121
+
122
+ - Lives **outside the project**, mode 600, for example
123
+ `~/.config/<agent>/preview.env`. Passed with `--env-file`.
124
+ - Exactly `KEY=value` per line: no quotes (they become part of the value), no
125
+ spaces around the name, `#` comment lines allowed.
126
+ - Holds the model key and every secret the agent reads (auth token, service
127
+ keys, database URL).
128
+ - Never print, log, or commit it, and never pass a secret as a command
129
+ argument. Ask the user to fill in values you do not have.
130
+
131
+ ## Commands
132
+
133
+ ```sh
134
+ # deploy to preview; the name comes from package.json
135
+ npx @moinulmoin/eden deploy --env-file ~/.config/<agent>/preview.env --json
136
+
137
+ # ship a change: the same command updates in place, on the same URL
138
+ npx @moinulmoin/eden deploy --env-file ~/.config/<agent>/preview.env --json
139
+
140
+ # production always needs an explicit --name
141
+ npx @moinulmoin/eden deploy --env production --name <name> --env-file <file> --json
142
+
143
+ # remove one target (always needs the exact --name deploy printed;
144
+ # add --env production for a production target)
145
+ npx @moinulmoin/eden destroy --name <name> --json
146
+ ```
147
+
148
+ - `--project <dir>` selects a project other than the current directory.
149
+ - A fresh deploy takes a few minutes, mostly building the container. The
150
+ result's `deployment.stableWorkersDevOrigin` is the live URL.
151
+ - An `eve init` project's `npm run deploy` script still deploys to Vercel, not
152
+ Eden.
153
+ - Confirm with the user before any `destroy` or production deploy. Destroy
154
+ deletes the agent immediately, even mid-conversation, and with the Cloudflare
155
+ World also its stored sessions. An external Postgres database is not touched.
156
+
157
+ ## Verify
158
+
159
+ Health only proves the server started; it needs no auth:
160
+
161
+ ```sh
162
+ curl -s https://<url>/eve/v1/health # expect "status":"ready"
163
+ ```
164
+
165
+ Then prove auth, the model key, and the agent work with a real message, using
166
+ the channel's credential (the bearer token from the env file):
167
+
168
+ ```sh
169
+ curl -s -X POST https://<url>/eve/v1/session \
170
+ -H "Authorization: Bearer $AGENT_AUTH_TOKEN" \
171
+ -H "Content-Type: application/json" \
172
+ -d '{"message":"Reply with one word: ready"}'
173
+ # {"ok":true,"sessionId":"wrun_…"}; then read the reply:
174
+ curl -s -N https://<url>/eve/v1/session/<sessionId>/stream \
175
+ -H "Authorization: Bearer $AGENT_AUTH_TOKEN"
176
+ ```
177
+
178
+ Load the token into the shell from the env file without printing it. The
179
+ first request after the agent sleeps takes about 10–15 seconds.
180
+
181
+ ## Upgrading Eve
182
+
183
+ - Bump `eve`, then reinstall so the lockfile matches. Keep
184
+ `@moinulmoin/eden-world-cloudflare` on its latest version.
185
+ - Moving an `eve init` project to Eve `1.0` also needs `@vercel/connect`
186
+ `2.7.0` or later; otherwise npm fails with `ERESOLVE`.
187
+ - Then deploy as usual. With the current Cloudflare World, existing sessions
188
+ continue on the new version. With the Postgres World or an older Cloudflare
189
+ World they return HTTP 409 `session_stranded` and users start new ones.
190
+
191
+ ## Limits to tell the user
192
+
193
+ - The sandbox is `just-bash`, a simulated shell: files, grep, sed, jq,
194
+ sqlite3, and curl work; python, git, and npm do not.
195
+ - One container per agent, `workers.dev` URL only, no custom domains yet.
196
+ - The container sleeps after 1 hour idle and wakes on the next request.
197
+ Standard 5-field cron schedules still fire while it sleeps; other cron forms
198
+ only fire while it is awake (`EVE_SCHEDULE_UNSUPPORTED`).
199
+
200
+ ## When something fails
201
+
202
+ - Checks run before anything changes on Cloudflare. Fix the first failed
203
+ check by its `remediation` and run again.
204
+ - `VAL-CLI-007-TARGET-CONFLICT`: a resource with that name exists that Eden
205
+ cannot prove it owns. For a brand-new agent, pick another `--name`; never
206
+ delete the resource to make room.
207
+ - `VAL-CLI-007-CLOUDFLARE-ACCESS`: Wrangler is not logged in or the account
208
+ lacks Workers Paid/Containers. The user has to fix this
209
+ (`npx wrangler@4.147.0 whoami`).
210
+ - `DEPLOY_INDETERMINATE`: Eden could not prove the result; the old version
211
+ usually keeps serving. Check the URL's health, then retry with the exact
212
+ same `--project`, `--env`, and `--name`. Never switch to a new name to get
213
+ past it, never delete resources by prefix, and never delete the project's
214
+ `.eden/` folder: it is the ownership proof `destroy` needs.
215
+ - Look up any other code in `docs/troubleshooting.md`.
@@ -263,6 +263,56 @@ function pooledPostgresReason(connectionString) {
263
263
  }
264
264
  return undefined;
265
265
  }
266
+ const EVE_WORLD_LOCAL_REMEDIATION = 'For durable state without a database, add @moinulmoin/eden-world-cloudflare and set experimental: { workflow: { world: "@moinulmoin/eden-world-cloudflare" } } in agent.ts; sessions survive Eve upgrades. Alternatively, add @workflow/world-postgres, select it as the workflow world, and put WORKFLOW_POSTGRES_URL with a direct, unpooled Postgres URL in --env-file.';
267
+ /** Static, dynamic, side-effect, or require imports of @vercel/connect or a subpath. */
268
+ function importsVercelConnect(source) {
269
+ return /\b(?:from|import|require)\s*\(?\s*["']@vercel\/connect(?:\/[^"']*)?["']/u
270
+ .test(source.replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/gu, ""));
271
+ }
272
+ async function vercelConnectSourcePaths(projectRoot) {
273
+ const paths = [];
274
+ const visit = async (path) => {
275
+ const entries = await readdir(join(projectRoot, path), { withFileTypes: true })
276
+ .catch(() => []);
277
+ for (const entry of entries) {
278
+ if (entry.name === "node_modules" || entry.isSymbolicLink())
279
+ continue;
280
+ const child = `${path}/${entry.name}`;
281
+ if (entry.isDirectory())
282
+ await visit(child);
283
+ else if (entry.isFile() && /\.[cm]?[jt]sx?$/u.test(entry.name)) {
284
+ const source = await readFile(join(projectRoot, child), "utf8").catch(() => "");
285
+ if (importsVercelConnect(source))
286
+ paths.push(child);
287
+ }
288
+ }
289
+ };
290
+ await visit("agent");
291
+ await visit("agents");
292
+ return paths.sort();
293
+ }
294
+ /** Eve persists its compile-time routing decision, including its default model. */
295
+ async function collectEveGatewayWarnings(candidate, runtimeConfig) {
296
+ if (runtimeConfig?.withProtectedValues((values) => (values.AI_GATEWAY_API_KEY?.trim().length ?? 0) > 0) === true)
297
+ return [];
298
+ try {
299
+ const manifest = JSON.parse(await readFile(join(candidate.snapshotRoot, EVE_COMPILED_MANIFEST_PATH), "utf8"));
300
+ if (manifest?.config?.model?.routing?.kind !== "gateway")
301
+ return [];
302
+ const source = typeof manifest.config.source?.logicalPath === "string"
303
+ ? manifest.config.source.logicalPath
304
+ : "agent/agent.ts";
305
+ return [{
306
+ id: "EVE_AI_GATEWAY_KEY_MISSING",
307
+ message: `The model in ${source} routes through Vercel AI Gateway, but --env-file has no non-blank AI_GATEWAY_API_KEY. Cloudflare has no Vercel OIDC project identity, so model calls fail.`,
308
+ remediation: 'Add AI_GATEWAY_API_KEY from https://vercel.com/dashboard/ai/api-keys to your --env-file, or use a direct provider such as openai("gpt-5") from @ai-sdk/openai with OPENAI_API_KEY in that file.',
309
+ }];
310
+ }
311
+ catch {
312
+ // Unknown or older compiled formats are not evidence of Gateway routing.
313
+ return [];
314
+ }
315
+ }
266
316
  /**
267
317
  * Recognizes the generated channel, not arbitrary user authentication policies.
268
318
  * The calls below match the Eve 0.82 initializer's agent/channels/eve.ts.
@@ -277,6 +327,14 @@ async function collectEveSetupWarnings(projectRoot) {
277
327
  return "";
278
328
  }
279
329
  };
330
+ const connectPaths = await vercelConnectSourcePaths(projectRoot);
331
+ if (connectPaths.length > 0) {
332
+ warnings.push({
333
+ id: "EVE_VERCEL_CONNECT",
334
+ message: `Vercel Connect imports in ${connectPaths.join(", ")} get credentials using a Vercel project identity (OIDC) that only Vercel injects; these connections fail on Cloudflare.`,
335
+ remediation: "Put each service's own API key or bot token in --env-file and replace connect(...) in the connection with auth: { getToken: async () => ({ token: process.env.SERVICE_TOKEN! }) }. For per-user sign-in, use defineInteractiveAuthorization from eve/connections with your own OAuth app.",
336
+ });
337
+ }
280
338
  const channel = (await source("agent/channels/eve.ts"))
281
339
  .replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/gu, "")
282
340
  .replace(/\s+/gu, "");
@@ -295,7 +353,7 @@ async function collectEveSetupWarnings(projectRoot) {
295
353
  warnings.push({
296
354
  id: "EVE_WORLD_LOCAL",
297
355
  message: "Local World stores memory on container disk; updates or a lost sleep snapshot reset sessions and approvals.",
298
- remediation: "Configure @workflow/world-postgres; see docs/deploy.md#durable-state-postgres-world.",
356
+ remediation: EVE_WORLD_LOCAL_REMEDIATION,
299
357
  });
300
358
  }
301
359
  if (manifest.dependencies?.["just-bash"] === undefined &&
@@ -350,7 +408,7 @@ async function collectEveWorldWarnings(candidate, runtimeConfig) {
350
408
  warnings.push({
351
409
  id: "EVE_WORLD_LOCAL",
352
410
  message: "Local World stores memory on container disk; updates or a lost sleep snapshot reset sessions and approvals.",
353
- remediation: "Configure a durable Workflow World such as @workflow/world-postgres; see docs/deploy.md#durable-state-postgres-world.",
411
+ remediation: EVE_WORLD_LOCAL_REMEDIATION,
354
412
  });
355
413
  }
356
414
  }
@@ -2412,6 +2470,7 @@ async function collectEvePreflight(request, options) {
2412
2470
  if (localHint !== -1)
2413
2471
  warnings.splice(localHint, 1);
2414
2472
  warnings.push(...(await collectEveWorldWarnings(candidate, undefined)));
2473
+ warnings.push(...await collectEveGatewayWarnings(candidate, runtimeConfig));
2415
2474
  const schedules = await readCompiledSchedules(candidate);
2416
2475
  const expressible = schedules.filter((schedule) => parseEveScheduleCron(schedule.cron) !== undefined);
2417
2476
  const unexpressible = schedules.filter((schedule) => parseEveScheduleCron(schedule.cron) === undefined);
package/dist/index.js CHANGED
@@ -17,6 +17,7 @@ Commands:
17
17
  preflight Build and inspect a local Eve candidate without remote mutation
18
18
  deploy Deploy the selected Eve project to the exact named target
19
19
  destroy Remove the exact owned Eve target
20
+ skill Print the bundled Eden skill for coding agents
20
21
 
21
22
  preflight and deploy default to the current directory, the preview
22
23
  environment, and a target name derived from package.json. destroy and
@@ -177,6 +178,24 @@ export async function runEdenCli(args, options = {}) {
177
178
  stdout(edenVersion());
178
179
  return 0;
179
180
  }
181
+ if (args[0] === "skill") {
182
+ if (args.length === 2 && (args[1] === "--help" || args[1] === "-h")) {
183
+ stdout("Usage: eden skill");
184
+ return 0;
185
+ }
186
+ if (args.length !== 1) {
187
+ throw new EveCliError({
188
+ code: "EVE_ARGUMENT_UNKNOWN",
189
+ message: "eden skill takes no arguments or options.",
190
+ });
191
+ }
192
+ const skill = readFileSync(new URL("./SKILL.md", import.meta.url), "utf8");
193
+ if (options.stdout === undefined)
194
+ process.stdout.write(skill);
195
+ else
196
+ options.stdout(skill);
197
+ return 0;
198
+ }
180
199
  if (!EVE_CLI_COMMANDS.includes(args[0] ?? "")) {
181
200
  throw new EveCliError({
182
201
  code: "COMMAND_UNKNOWN",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moinulmoin/eden",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Deploy existing Eve projects to your own Cloudflare account.",
5
5
  "keywords": [
6
6
  "ai",
@@ -27,11 +27,12 @@
27
27
  "dist/**/*.js",
28
28
  "dist/**/*.mjs",
29
29
  "dist/**/*.d.ts",
30
+ "dist/SKILL.md",
30
31
  "LICENSE",
31
32
  "NOTICE"
32
33
  ],
33
34
  "dependencies": {
34
- "@moinulmoin/eden-runtime-cloudflare": "0.10.0",
35
+ "@moinulmoin/eden-runtime-cloudflare": "0.11.0",
35
36
  "wrangler": "4.147.0"
36
37
  },
37
38
  "devDependencies": {
@@ -54,7 +55,7 @@
54
55
  },
55
56
  "scripts": {
56
57
  "build": "tsc -b",
57
- "postbuild": "chmod 755 dist/index.js",
58
+ "postbuild": "chmod 755 dist/index.js && cp ../../skills/eden/SKILL.md dist/SKILL.md",
58
59
  "typecheck": "tsc -b --pretty false",
59
60
  "lint": "eslint src --max-warnings 0",
60
61
  "test": "pnpm --dir ../.. exec vitest run packages/cli/test --passWithNoTests --maxWorkers=1"