@astrale-os/adapter-cloudflare 0.1.7 → 0.1.9

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 (72) hide show
  1. package/dist/assets-pack.d.ts +1 -1
  2. package/dist/assets-pack.js +1 -1
  3. package/dist/build.d.ts +15 -0
  4. package/dist/build.d.ts.map +1 -0
  5. package/dist/build.js +15 -0
  6. package/dist/build.js.map +1 -0
  7. package/dist/client.d.ts +9 -0
  8. package/dist/client.d.ts.map +1 -1
  9. package/dist/client.js +10 -1
  10. package/dist/client.js.map +1 -1
  11. package/dist/cloudflare.d.ts +15 -3
  12. package/dist/cloudflare.d.ts.map +1 -1
  13. package/dist/cloudflare.js +73 -21
  14. package/dist/cloudflare.js.map +1 -1
  15. package/dist/codegen/worker.d.ts +26 -6
  16. package/dist/codegen/worker.d.ts.map +1 -1
  17. package/dist/codegen/worker.js +70 -54
  18. package/dist/codegen/worker.js.map +1 -1
  19. package/dist/codegen/wrangler.d.ts +11 -2
  20. package/dist/codegen/wrangler.d.ts.map +1 -1
  21. package/dist/codegen/wrangler.js +11 -5
  22. package/dist/codegen/wrangler.js.map +1 -1
  23. package/dist/index.d.ts +6 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +5 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/params.d.ts +30 -30
  28. package/dist/params.d.ts.map +1 -1
  29. package/dist/parse-output.d.ts +1 -1
  30. package/dist/parse-output.js +1 -1
  31. package/package.json +6 -2
  32. package/src/assets-pack.ts +1 -1
  33. package/src/build.ts +15 -0
  34. package/src/client.ts +11 -1
  35. package/src/cloudflare.ts +77 -23
  36. package/src/codegen/worker.ts +79 -59
  37. package/src/codegen/wrangler.ts +15 -5
  38. package/src/index.ts +6 -3
  39. package/src/params.ts +32 -31
  40. package/src/parse-output.ts +1 -1
  41. package/template/.agents/skills/astrale-cli/SKILL.md +25 -11
  42. package/template/.agents/skills/astrale-domain/SKILL.md +60 -32
  43. package/template/.env.example +8 -0
  44. package/template/README.md +26 -10
  45. package/template/astrale.config.ts +22 -29
  46. package/template/client/README.md +2 -2
  47. package/template/client/src/styles.css +4 -1
  48. package/template/client/tsconfig.json +1 -1
  49. package/template/client/vite.config.ts +3 -3
  50. package/template/client/vitest.config.ts +1 -1
  51. package/template/core/keys.ts +28 -0
  52. package/template/{methods → core}/note.ts +42 -25
  53. package/template/deps.ts +25 -0
  54. package/template/domain.ts +33 -0
  55. package/template/env.ts +11 -0
  56. package/template/integrations/summary/heuristic.ts +25 -0
  57. package/template/integrations/summary/http.ts +69 -0
  58. package/template/integrations/summary/port.ts +21 -0
  59. package/template/integrations/summary/registry.ts +52 -0
  60. package/template/package.json +2 -3
  61. package/template/runtime/index.ts +62 -0
  62. package/template/schema/index.ts +2 -0
  63. package/template/schema/note.ts +5 -2
  64. package/template/tsconfig.json +13 -2
  65. package/template/views/note.ts +1 -1
  66. package/dist/astrale.d.ts +0 -27
  67. package/dist/astrale.d.ts.map +0 -1
  68. package/dist/astrale.js +0 -212
  69. package/dist/astrale.js.map +0 -1
  70. package/src/astrale.ts +0 -244
  71. package/template/methods/index.ts +0 -66
  72. package/template/schema/compiled.ts +0 -14
@@ -3,8 +3,8 @@
3
3
  * `wrangler.jsonc` that used to be hand-copied into every domain — now an
4
4
  * internal adapter detail the dev never sees.
5
5
  *
6
- * `main` is `./worker.gen.ts` (same dir). `assets.directory` is `../dist-client`
7
- * (the project's Vite build). A `SELF` service binding enables autobinding
6
+ * `main` is `./worker.gen.ts` (same dir). `assets.directory` points at the
7
+ * project's Vite build (`CLIENT_DIST_DIR`). A `SELF` service binding enables autobinding
8
8
  * (a handler calling back into its own domain). Custom-domain deploys attach a
9
9
  * `custom_domain` route; otherwise the Worker ships to `*.workers.dev`.
10
10
  *
@@ -12,6 +12,7 @@
12
12
  * (extra bindings: KV, R2, D1, queues, …) — see `mergeUserConfig`.
13
13
  */
14
14
 
15
+ import { CLIENT_DIST_DIR } from '../client'
15
16
  import { deepMergeConfig } from './merge'
16
17
 
17
18
  export interface WranglerCodegenOptions {
@@ -24,6 +25,12 @@ export interface WranglerCodegenOptions {
24
25
  vars?: Record<string, string>
25
26
  /** Add the SELF service binding (autobinding). */
26
27
  selfBinding: boolean
28
+ /**
29
+ * Add a service binding to a router Worker for instance-host subrequest
30
+ * routing (see `CloudflareParams.router`). Pairs with the worker
31
+ * entry's `routeSubrequest`.
32
+ */
33
+ router?: { binding: string; service: string }
27
34
  /**
28
35
  * Raw wrangler config to deep-merge over the generated base — the escape
29
36
  * hatch for extra bindings (KV, R2, D1, queues, service bindings, extra
@@ -52,16 +59,19 @@ export function generateWranglerConfig(opts: WranglerCodegenOptions): string {
52
59
 
53
60
  if (opts.hasClient) {
54
61
  config.assets = {
55
- directory: '../dist-client',
62
+ directory: `../${CLIENT_DIST_DIR}`,
56
63
  binding: 'ASSETS',
57
64
  not_found_handling: 'single-page-application',
58
65
  run_worker_first: true,
59
66
  }
60
67
  }
61
68
 
62
- if (opts.selfBinding) {
63
- config.services = [{ binding: 'SELF', service: opts.workerName }]
69
+ const services: Array<{ binding: string; service: string }> = []
70
+ if (opts.selfBinding) services.push({ binding: 'SELF', service: opts.workerName })
71
+ if (opts.router) {
72
+ services.push({ binding: opts.router.binding, service: opts.router.service })
64
73
  }
74
+ if (services.length > 0) config.services = services
65
75
 
66
76
  if (opts.vars && Object.keys(opts.vars).length > 0) {
67
77
  config.vars = opts.vars
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * `@astrale-os/adapter-cloudflare` — deploy an Astrale domain as a standalone
3
- * Cloudflare Worker.
3
+ * Cloudflare Worker on YOUR OWN Cloudflare account.
4
4
  *
5
5
  * import { cloudflare } from '@astrale-os/adapter-cloudflare'
6
6
  *
@@ -11,8 +11,11 @@
11
11
  *
12
12
  * `dev` runs `wrangler dev` locally; an env with no `route` ships to
13
13
  * `*.workers.dev`; an env with a `route` ships to that custom domain.
14
+ *
15
+ * Prefer a managed deploy (no Cloudflare account)? Use
16
+ * `@astrale-os/adapter-astrale`, which builds the same bundle via this package's
17
+ * `/build` toolkit and ships it through the Astrale platform.
14
18
  */
15
19
 
16
- export { astrale } from './astrale'
17
20
  export { cloudflare } from './cloudflare'
18
- export type { AstraleParams, CloudflareParams } from './params'
21
+ export type { CloudflareParams } from './params'
package/src/params.ts CHANGED
@@ -55,37 +55,38 @@ export interface CloudflareParams {
55
55
  * worker-entry codegen and aren't supported through this field yet.
56
56
  */
57
57
  wrangler?: Record<string, unknown>
58
- }
59
-
60
- /** Params for the Astrale-managed adapter (`astrale(envs)`). */
61
- export interface AstraleParams {
62
58
  /**
63
- * Target instance slug the domain installs onto (e.g. `'bryan-e2e5'`).
64
- * Required for `deploy` (managed publish + install); ignored by `watch`
65
- * (local dev)so dev-only envs may omit it.
66
- */
67
- instance?: string
68
- /** Catalog package name. Default: the origin's first DNS label. */
69
- name?: string
70
- /** Admin kernel URL the publish calls target. Default the platform admin. */
71
- adminUrl?: string
72
- /** Routing region of the `.svc` service URL. Default `'eu'`. */
73
- region?: string
74
- /** CLI identity (`--as`) for the publish calls. Default: the CLI's active identity. */
75
- identity?: string
76
- /** Gitignored secrets file. `watch`: injected into local wrangler dev. `deploy`: shipped on the install call and applied to the managed service env (encrypted at rest platform-side; platform keys win precedence). */
77
- secrets?: string
78
- /** Local dev port for `wrangler dev`. Default 8787. */
79
- port?: number
80
- /**
81
- * Public URL the dev server is reached through (tunnel / sandbox preview /
82
- * reverse proxy), e.g. `'https://my-box.preview.dev'`. Sets BOTH effects an
83
- * off-localhost dev needs: wrangler binds 0.0.0.0 (reachable from outside)
84
- * and `WORKER_URL` is pinned to this URL so the worker's per-request-Host
85
- * identity (`iss`) doesn't drift to the proxy's internal hostname.
86
- * Usually injected by `pnpm dev --host <url>` rather than written here.
59
+ * Route subrequests to platform INSTANCE hostnames through a service binding
60
+ * instead of the public edge. A Cloudflare Worker can't fetch a same-zone
61
+ * hostname served by another Worker Cloudflare 522s it so without this the
62
+ * worker can't fetch an instance kernel's JWKS to verify an inbound credential
63
+ * from that instance, and every cross-instance call (install hooks, the GUI's
64
+ * delegated calls, authed views) fails with `2002 Credential verification
65
+ * failed`. Point it at a router Worker that reaches those hosts internally
66
+ * (Astrale: `admin-router`).
67
+ *
68
+ * Wires BOTH halves the SDK needs: the `<binding>` service binding in the
69
+ * generated wrangler config, and a `routeSubrequest` predicate in the worker
70
+ * entry that diverts matching hosts through it.
71
+ *
72
+ * ON BY DEFAULT (targets `admin-router`) so every adapter-deployed worker can
73
+ * verify cross-instance credentials out of the box. Pass `false` to disable
74
+ * (a domain that never talks to another instance, or an account without a
75
+ * router), or an object to override the target / binding name / host match.
76
+ *
77
+ * router: false // opt out
78
+ * router: { service: 'my-router' } // override target
87
79
  */
88
- host?: string
89
- /** Extra plain vars for local `watch`. */
90
- vars?: Record<string, string>
80
+ router?:
81
+ | false
82
+ | {
83
+ /** Router Worker the binding targets. Default `'admin-router'`. */
84
+ service?: string
85
+ /** Service-binding name the worker reads. Default `'ROUTER'`. */
86
+ binding?: string
87
+ /** Host suffix that marks an instance host. Default `'.astrale.ai'`. */
88
+ hostSuffix?: string
89
+ /** Min dot-separated label count for an instance host. Default `4` (`<slug>.<region>.astrale.ai`). */
90
+ minLabels?: number
91
+ }
91
92
  }
@@ -14,7 +14,7 @@ const WORKERS_DEV_RE = /(https:\/\/[^\s]+\.workers\.dev)/i
14
14
  * The fallback (when no explicit "Ready on" line) only accepts a localhost URL
15
15
  * on the EXPECTED port — wrangler may print unrelated localhost URLs (e.g. an
16
16
  * inspector/devtools endpoint on a different port) before readiness, and taking
17
- * one of those would point `astrale instance install <url>` at a dead endpoint.
17
+ * one of those would point `astrale domain install <url>` at a dead endpoint.
18
18
  */
19
19
  export function parseDevReadyUrl(text: string, port: number): string | undefined {
20
20
  const ready = READY_RE.exec(text)
@@ -38,6 +38,7 @@ astrale logs <service>
38
38
  astrale status
39
39
  astrale browser
40
40
  astrale instance ...
41
+ astrale domain ...
41
42
  astrale admin ...
42
43
  astrale identity ...
43
44
  astrale auth ...
@@ -50,7 +51,7 @@ Command groups:
50
51
  |---|---|
51
52
  | Kernel | `call`, `token`, `get`, `ls`, `describe`, `query`, `logs` |
52
53
  | Context | `status`, `whoami`, `use` |
53
- | Management | `admin`, `instance`, `identity`, `auth`, `idp`, `update` |
54
+ | Management | `admin`, `instance`, `domain`, `identity`, `auth`, `idp`, `update` |
54
55
  | Agent | `browser` |
55
56
 
56
57
  Shared kernel options are merged onto kernel-touching commands at registration
@@ -148,7 +149,6 @@ astrale instance use my-app
148
149
  astrale instance active
149
150
  astrale instance bookmark staging --url https://kernel.example.com
150
151
  astrale instance forget staging
151
- astrale instance install https://domain.example.com
152
152
  ```
153
153
 
154
154
  Important distinctions:
@@ -164,23 +164,37 @@ Important distinctions:
164
164
  ## Domain Dev Workflow
165
165
 
166
166
  **The `astrale` CLI is connect-only — it does not build, run, or deploy
167
- domains.** There is no `astrale domain …` command group. Domain development
168
- lives in two separate tools:
167
+ domains.** The `astrale domain` group manages the admin catalog and installs a
168
+ running domain onto an instance (`list`, `publish`, `install`); it does NOT
169
+ build or run domains. `astrale domain list` shows the published catalog (add
170
+ `--check` to probe each URL's reachability, `--default-only` for the
171
+ install-on-every-instance set, `-q` to pipe install URLs). Building, running,
172
+ and deploying live in two separate tools:
169
173
 
170
174
  - **`create-astrale-domain`** scaffolds a new standalone domain project
171
175
  (`pnpm create astrale-domain <slug>`), writing an `astrale.config.ts`.
172
- - **`astrale-domain`** (the `@astrale-os/devkit` bin, behind the project's
176
+ - **`astrale-domain`** (the `@astrale-os/sdk` bin, behind the project's
173
177
  `pnpm dev` / `pnpm prod` scripts) runs `dev | prod | deploy <env> | build`.
174
178
 
175
- Domains are **installed by URL**, never from a file: run or deploy the domain
176
- worker, then `astrale instance install <url> -i <slug>` — the kernel fetches a
177
- signed install bundle from the running worker. There is no committed
178
- `spec.json` and no `astrale domain install`; install lives under the
179
- `instance` group because it operates on an instance graph.
179
+ Domains are **installed by URL or catalog origin**, never from a file (there is
180
+ no committed `spec.json`). `astrale domain install` has two modes:
181
+
182
+ - **default (via admin)** — `astrale domain install <origin|url> -i <slug>`
183
+ installs a PUBLISHED domain through the admin control plane
184
+ (`DomainEntry.install`), addressed by its catalog `origin` (the unique
185
+ registry key) or `url`. Run it bare to pick from the catalog interactively.
186
+ The target instance is the active one or `-i <slug>` and **must be
187
+ admin-managed** (otherwise it fails loudly and points you at `--direct`).
188
+ - **`--direct`** — `astrale domain install <url> --direct` installs a url
189
+ straight onto the instance kernel (`Root.installDomain`), bypassing the
190
+ catalog. Works on ANY instance you can authenticate to (managed, bookmarked,
191
+ or local), using your own authority, and is the only mode that runs the
192
+ identity-override consent gate. Use it for dev/local instances and
193
+ freshly-deployed, not-yet-published workers.
180
194
 
181
195
  With the **managed (`astrale`) adapter**, `pnpm prod` publishes the bundle
182
196
  through the platform AND installs it on the configured instance in one step —
183
- no manual `instance install`. The service serves at
197
+ no manual `domain install`. The service serves at
184
198
  `https://<name>-<hash>.svc.<region>.astrale.ai` (the CLI session is the auth).
185
199
 
186
200
  For authoring domains end-to-end (schema, handlers, external APIs, deploys),
@@ -19,7 +19,9 @@ an RPC layer; you declare a contract and implement handlers.
19
19
  ## 0 · Start
20
20
 
21
21
  ```bash
22
- npx -y create-astrale-domain@latest my-domain --yes # scaffold
22
+ npx -y create-astrale-domain@latest my-domain --yes --instance <slug> # scaffold
23
+ # managed `astrale` adapter is the DEFAULT; `--instance` stamps the
24
+ # target instance into prod (`--adapter cloudflare` = your own CF account)
23
25
  cd my-domain && pnpm install
24
26
  pnpm dev # local wrangler dev (prints a URL; install it on an instance to test)
25
27
  # port 8787 taken? dev AUTO-PICKS a free one and prints it —
@@ -34,14 +36,21 @@ pnpm prod # deploy + (astrale adapter) auto-install on your instance
34
36
  Project anatomy (the scaffold is the reference — read its comments):
35
37
 
36
38
  ```
37
- astrale.config.ts # THE manifest: schema + postInstall + adapter (where it ships)
39
+ domain.ts # THE manifest wires it ALL: defineDomain({ schema, methods,
40
+ # deps, views, functions, client }) + origin/postInstall.
41
+ # Modules are imported & passed EXPLICITLY (no folder magic);
42
+ # a renamed module is a compile error here, not a missing route.
43
+ astrale.config.ts # binds the domain to its deploy adapter (deploy(domain, …)) — node-only
38
44
  schema/ # classes/interfaces/edges — the contract (zod props, fn signatures)
39
- methods/ # handler logic (transport-agnostic fns) + thin wiring (index.ts)
40
- services/ # RECOMMENDED: ports + factories for external APIs (see §4)
45
+ # + compiled.ts (D = compileDomain) the public compiled entry
46
+ core/ # pure, transport-agnostic logic + keys.ts (compiled accessors)
47
+ integrations/ # external-API ports + adapters + a lazy registry (see §4) — what deps is built from
48
+ runtime/index.ts # composition root: the methods map; each execute resolves deps → calls core logic
41
49
  functions/ # standalone remote functions (webhook-shaped endpoints)
42
50
  views/ # iframe-mountable UI declarations (defineView)
43
51
  client/ # the SPA served under /ui (vite)
44
- env.ts # typed worker env config + secrets arrive here, as ctx.deps
52
+ deps.ts # env typed Deps container (the seam defineDomain({ deps }) mounts)
53
+ env.ts # typed worker env — config + secrets arrive here, mapped by deps.ts → ctx.deps
45
54
  ```
46
55
 
47
56
  Iteration loop: edit → `pnpm prod` → call it. Managed redeploys keep the same
@@ -95,9 +104,10 @@ Conventions and rules:
95
104
 
96
105
  ## 2 · Handlers
97
106
 
98
- Separate LOGIC from WIRING. Logic = plain async functions taking the kernel
99
- client + ports + params (testable with fakes). Wiring = `method()` /
100
- `classMethods()` / `interfaceMethods()` in `methods/index.ts`:
107
+ Separate LOGIC from WIRING. Logic = plain async functions in `core/` taking the
108
+ kernel client + ports + params (testable with fakes). Wiring = `method()` /
109
+ `classMethods()` / `interfaceMethods()` in `runtime/index.ts` (the composition
110
+ root — the only place request context + `deps` meet the `core/` logic):
101
111
 
102
112
  ```ts
103
113
  const kickoff = method(schema, 'Project', 'kickoff', {
@@ -111,9 +121,11 @@ const kickoff = method(schema, 'Project', 'kickoff', {
111
121
 
112
122
  Handler context: `kernel` (callback client bound to the composed credential —
113
123
  caller's delegated authority ∪ this function's own), `self` (instance methods),
114
- `params` (zod-validated), `deps` (your typed `Env`), `auth` (principal,
115
- verified claims). Construct external clients from `deps` per request NEVER
116
- at module load (workers must be import-side-effect-free).
124
+ `params` (zod-validated), `deps` (your typed `Deps` from `deps.ts`; raw `Env` if
125
+ you omit the mapper), `auth` (principal, verified claims). Resolve ports from
126
+ `deps` per request (`deps.summarizer()` the registry builds + caches them per
127
+ isolate) — NEVER construct external clients at module load (workers must be
128
+ import-side-effect-free).
117
129
 
118
130
  ## 3 · Talking to the kernel (and other domains)
119
131
 
@@ -153,16 +165,20 @@ Almost every real domain wraps an external API (payment, calendar, LLM
153
165
  gateway, cloud provider). The shape (see admin/domain for the full-scale
154
166
  example — Scaleway/WorkOS/KV):
155
167
 
156
- 1. **Port** — a narrow interface declaring only what the logic needs
157
- (`WeatherClient { forecast(city) }`). Logic depends on the port, never on
158
- fetch/SDKs/env.
159
- 2. **Factory** — `createXClient(env)` in `services/`: reads config + secrets
160
- from `env`, validates LOUDLY (`if (!env.X_API_KEY) throw`), sets base URLs
161
- (overridable so tests point at a stub), timeouts (`AbortSignal.timeout`),
162
- and maps upstream failures to meaningful errors with the upstream detail in
163
- `cause` (the kernel threads cause chains to callers).
164
- 3. **Wiring** — the `execute` hook builds the client from `deps` per request
165
- and passes it to the logic.
168
+ 1. **Port** — a narrow interface in `integrations/<feature>/port.ts` declaring
169
+ only what the logic needs (`WeatherClient { forecast(city) }`, the scaffold's
170
+ `Summarizer { summarize(body) }`). `core/` logic depends on the port, never
171
+ on fetch/SDKs/env.
172
+ 2. **Adapter(s) + registry** `createXClient(config)` in
173
+ `integrations/<feature>/` (one per backend), plus a `registry.ts` that reads
174
+ config + secrets from `env`, validates LOUDLY (`if (!env.X_API_KEY) throw`),
175
+ sets base URLs (overridable so tests point at a stub), timeouts
176
+ (`AbortSignal.timeout`), and maps upstream failures to errors with the
177
+ upstream detail in `cause`. The registry builds the chosen adapter LAZILY +
178
+ caches it per isolate (a worker never validates an unused backend's env).
179
+ 3. **Wiring** — `deps.ts` mounts the registry (`defineDomain({ deps })`); the
180
+ `execute` hook resolves the PORT from `deps` (`deps.summarizer()`) per
181
+ request and passes it to the `core/` logic.
166
182
 
167
183
  Secrets & config:
168
184
  - Declare every var as a typed field on `Env` in `env.ts`. Secrets ship via
@@ -200,8 +216,10 @@ Anti-patterns (all observed in production code — don't):
200
216
  ## 5 · Views & standalone functions (webhooks)
201
217
 
202
218
  - `defineView({ auth, mount: '/ui/contact', viewFor: selfOf(Contact) })` in
203
- `views/` + register in `views/index.ts` — the MAP KEY is the view's node
204
- slug (`'ui-contact'` `/<origin>/core/views/ui-contact`). Installs a View
219
+ `views/`, collected into the `views` map in `views/index.ts`, which
220
+ `astrale.config.ts` imports and passes to `defineDomain({ views })`. The MAP
221
+ KEY is the view's node slug (`'ui-contact'` → `/<origin>/core/views/ui-contact`).
222
+ Installs a View
205
223
  node whose binding URL = `<serving url><mount>` (managed:
206
224
  `https://<slug>.svc.<region>.astrale.ai/ui/contact`). The client/ SPA
207
225
  serves `/ui/*` — BOTH adapters ship it (cloudflare via Workers Assets;
@@ -231,9 +249,17 @@ createNode + USE on the class — narrow, never root).
231
249
  the render ctx exposes `ctx.selfKernel()` (sdk ≥0.1.5) — a session as THE
232
250
  VIEW'S OWN identity. Read with it directly in `render`, template to HTML:
233
251
  `const k = await ctx.selfKernel(); const rows = await k.call('/messages::listChildren', {})`.
234
- Grant the view's function identity the READ-side minimum in your seed:
235
- READ on the folder (+ USE on the methods it calls, e.g. listChildren).
236
- Needs `deps.INSTANCE_KERNEL_URL` (managed deploys set it). Public-input
252
+ Grant the view's function identity the READ-side minimum in your seed
253
+ the grant call is dispatched ON the identity node, with a bitmask:
254
+ ```ts
255
+ import { READ, USE, toMask } from '@astrale-os/kernel-core'
256
+ await kernel.call('/<origin>/core/views/<view-slug>::grantPerm', { node: '/messages', perms: toMask(READ) })
257
+ await kernel.call('/<origin>/core/views/<view-slug>::grantPerm', { node: '/:kernel.astrale.ai:interface.Container:listChildren', perms: toMask(USE) })
258
+ ```
259
+ Needs `deps.INSTANCE_KERNEL_URL` — managed deploys set it automatically; on
260
+ the cloudflare adapter set it yourself: the instance's kernel API base is
261
+ `https://<instance-slug>.eu.astrale.ai/api` (a vars entry or secret).
262
+ Public-input
237
263
  hygiene: HTML-escape every stored string at render. (Prefer `selfKernel`
238
264
  over the `SELF` service binding for graph reads — `SELF` exists on both
239
265
  cloudflare and current managed runtimes, but it costs an extra HTTP hop and
@@ -266,11 +292,13 @@ capture when the sender's shape is fixed (see distribution's proxy functions).
266
292
 
267
293
  ## 7 · Deploy & install
268
294
 
269
- Adapter choice in `astrale.config.ts`:
270
- - `cloudflare({...})` your CF account; `dev` (wrangler dev), `prod` (route or
271
- workers.dev); ships secrets + SPA assets; extra bindings via a deep-merged
272
- `wrangler` block.
273
- - `astrale({ dev: {...}, prod: { instance: '<slug>' } })` — managed: publishes
295
+ Adapter choice in `astrale.config.ts` (each adapter is its OWN package — swap
296
+ BOTH the import and the call):
297
+ - `cloudflare({...})` from `@astrale-os/adapter-cloudflare` your CF account;
298
+ `dev` (wrangler dev), `prod` (route or workers.dev); ships secrets + SPA
299
+ assets; extra bindings via a deep-merged `wrangler` block.
300
+ - `astrale({ dev: {...}, prod: { instance: '<slug>' } })` from
301
+ `@astrale-os/adapter-astrale` — managed (the scaffold's DEFAULT): publishes
274
302
  the bundle THROUGH the platform and installs it as a host-local service next
275
303
  to your instance (`https://<name>-<hash>.svc.<region>.astrale.ai`). No CF
276
304
  account; auth = your `astrale auth login` session. Ships the client SPA
@@ -292,7 +320,7 @@ service URL stable).
292
320
  (`/:origin:class.X:seed`; tree paths are rejected) and MUST be idempotent
293
321
  (catch path-conflicts). Seed folders, defaults, and demo data here.
294
322
 
295
- Manual install of any served domain: `astrale instance install <url>`.
323
+ Manual install of any served domain: `astrale domain install <url> --direct`.
296
324
 
297
325
  The managed catalog surface (what `pnpm prod` shells into) is callable
298
326
  directly — useful for recovery and inspection:
@@ -4,3 +4,11 @@
4
4
  # prod. These files are gitignored — copy this to `.env.dev` and fill in.
5
5
  #
6
6
  # EXAMPLE_API_KEY=sk-...
7
+ #
8
+ # ── Summarizer (the example external-API integration) ──────────────────────
9
+ # The scaffold summarizes notes with a no-network heuristic by default. To use a
10
+ # real OpenAI-compatible model instead, set NOTE_SUMMARIZER=http and provide:
11
+ # NOTE_SUMMARIZER=http
12
+ # SUMMARIZER_API_KEY=sk-...
13
+ # SUMMARIZER_BASE_URL=https://api.openai.com/v1
14
+ # SUMMARIZER_MODEL=gpt-4o-mini
@@ -1,7 +1,8 @@
1
1
  # astrale-domain
2
2
 
3
3
 
4
- A standalone [Astrale](https://astrale.ai) domain, deployed as a Cloudflare Worker.
4
+ A standalone [Astrale](https://astrale.ai) domain, deployed as an Astrale-managed
5
+ service (default) or as a worker on your own Cloudflare account.
5
6
 
6
7
  ## For AI agents
7
8
 
@@ -18,20 +19,35 @@ calls), use the `astrale-cli` skill.
18
19
  ```bash
19
20
  pnpm install
20
21
  pnpm dev # wrangler dev → prints a local URL
21
- astrale instance install <url> # mount it on an instance (CLI)
22
+ astrale domain install <url> --direct # mount it on an instance (CLI)
22
23
  pnpm prod # deploy prod → prints a URL
23
24
  ```
24
25
 
25
- ## What you write (★)
26
+ ## What you write
26
27
 
27
28
  ```
28
- schema/classes + interfaces (the data model)
29
- methods/ the execute() handlers
30
- views/ ★ iframe-mountable UIs
31
- functions/ standalone Functions (e.g. the post-install seed)
32
- astrale.config.ts identity (origin) · requires · postInstall · adapter
29
+ schema/ classes + interfaces (the data model) + compiled accessors
30
+ core/ pure, transport-agnostic logic + keys.ts (never hand-write keys)
31
+ integrations/ external-API ports + adapters + a lazy registry (built into deps)
32
+ runtime/ the execute() handlers the composition root (the methods map)
33
+ views/ iframe-mountable UIs
34
+ functions/ standalone Functions (e.g. webhooks)
35
+ client/ the SPA served under /ui
36
+ deps.ts env → typed Deps container (what handlers read as ctx.deps)
37
+ domain.ts wires it all together — the worker-safe definition
38
+ astrale.config.ts binds the domain to its deploy adapter (node-only)
33
39
  ```
34
40
 
41
+ `domain.ts` is the one place everything is declared: it imports the modules
42
+ above and passes them to `defineDomain({ schema, methods, deps, views,
43
+ functions, client, … })` alongside the identity (`origin`, `requires`,
44
+ `postInstall`). Nothing is discovered by folder name — a renamed or mistyped
45
+ module is a compile error at that call, never a silently-missing route. Drop a
46
+ field (`deps`, `views`, `functions`, `client`) when the domain has none.
47
+ `astrale.config.ts` then binds that domain to a deploy adapter with
48
+ `deploy(domain, cloudflare({ … }))` — keeping the node-only adapter (wrangler,
49
+ filesystem) out of the worker bundle.
50
+
35
51
  Everything else — the Worker entry, the wrangler config, the signing identity —
36
52
  is generated under `.astrale/` (gitignored) by the adapter. You never edit it.
37
53
 
@@ -71,8 +87,8 @@ adapter-owned keys `name`, `main`, `assets`, `routes` are rejected (use
71
87
 
72
88
  ## The loop
73
89
 
74
- - **Edit a handler** (`methods/`) → hot-reloads at the same URL. Nothing to reinstall.
75
- - **Edit the schema** (`schema/`) → rebuilds the graph; reinstall with `astrale instance install <url>`.
90
+ - **Edit a handler** (`core/` logic or `runtime/` wiring) → hot-reloads at the same URL. Nothing to reinstall.
91
+ - **Edit the schema** (`schema/`) → rebuilds the graph; reinstall with `astrale domain install <url> --direct`.
76
92
  - **`postInstall`** (the static `Note.seed` method) runs once after install, as
77
93
  the system identity, so the domain can seed itself and set its own grants. It
78
94
  must be a class-hosted static addressed by a typed colon-path
@@ -1,44 +1,37 @@
1
1
  /**
2
- * astrale.config.ts — the single source of truth for this domain's identity and
3
- * deployment. `origin` defaults to `schema.domain`; `postInstall` points at the
4
- * static `Note.seed` method the kernel runs after install (a typed colon-path —
5
- * the kernel refuses tree paths here, since they can't prove their origin);
6
- * `adapter` chooses the target.
2
+ * astrale.config.ts — binds the worker-safe domain (`domain.ts`) to its deploy
3
+ * adapter for the `astrale-domain` CLI. This is a NODE-only module: it imports
4
+ * the adapter (wrangler + filesystem). The generated worker never imports this
5
+ * file it imports `domain.ts` directly so the adapter stays out of the bundle.
6
+ *
7
+ * This variant deploys to YOUR OWN Cloudflare account (the `cloudflare` adapter).
7
8
  *
8
9
  * pnpm dev # wrangler dev → prints a local URL
9
- * astrale instance install <url> # mount it on an instance
10
- * pnpm prod # deploy prod prints a URL
10
+ * astrale domain install <url> --direct # mount the dev URL on an instance
11
+ * pnpm prod # deploy to your Cloudflare account
11
12
  */
12
13
  import { cloudflare } from '@astrale-os/adapter-cloudflare'
13
- import { defineDomain } from '@astrale-os/devkit'
14
+ import { deploy } from '@astrale-os/sdk'
14
15
  // No Cloudflare account? Swap the adapter for the Astrale-managed one — it
15
16
  // publishes the same bundle THROUGH the platform and installs it on your
16
- // instance as a managed service (auth = your `astrale auth login` session):
17
+ // instance (auth = your `astrale auth login` session):
17
18
  //
18
- // import { astrale } from '@astrale-os/adapter-cloudflare'
19
- // adapter: astrale({
20
- // dev: { secrets: '.env.dev' }, // local wrangler dev, unchanged
21
- // prod: { instance: '<your-instance-slug>' } // pnpm prod → managed deploy
22
- // }),
19
+ // import { astrale } from '@astrale-os/adapter-astrale'
20
+ // export default deploy(domain, astrale({
21
+ // dev: { secrets: '.env.dev' }, // local wrangler dev, unchanged
22
+ // prod: { instance: '<your-instance-slug>' }, // pnpm prod → managed deploy
23
+ // }))
23
24
 
24
- import { schema } from './schema'
25
+ import { domain } from './domain'
25
26
 
26
- export default defineDomain({
27
- schema,
28
- // origin defaults to `schema.domain`. Set it to another domain's origin to
29
- // deliberately alias that identity (you'll get a DANGER prompt at install).
30
- postInstall: `/:${schema.domain}:class.Note:seed`,
31
- adapter: cloudflare({
27
+ export default deploy(
28
+ domain,
29
+ cloudflare({
32
30
  // Local dev: `wrangler dev`. No route → URL is http://localhost:8787.
33
31
  dev: { secrets: '.env.dev' },
34
32
  // Custom-domain prod. Drop `route` to ship to *.workers.dev instead.
35
33
  prod: { route: 'astrale-domain.example.dev', secrets: '.env.prod' },
36
- // Need extra bindings (KV, R2, D1, queues, …)? Add a `wrangler` block to any
37
- // env it's deep-merged into the generated config (arrays concat, so the
38
- // adapter's `SELF` binding and `nodejs_compat` are preserved):
39
- // dev: {
40
- // secrets: '.env.dev',
41
- // wrangler: { kv_namespaces: [{ binding: 'CACHE', id: '<kv-id>' }] },
42
- // },
34
+ // Author secrets ship via `secrets: '.env.prod'` on any env. Extra bindings
35
+ // (KV, R2, D1, queues, …) ride a `wrangler` block on any env (deep-merged).
43
36
  }),
44
- })
37
+ )
@@ -3,7 +3,7 @@
3
3
  A small React + Vite SPA that renders the domain's `ui-note` View. It is loaded
4
4
  inside an iframe mounted by the Astrale shell, runs the shell handshake to learn
5
5
  its target node id and a delegation token, fetches that Note from the kernel,
6
- and renders its title/body. Built into `../dist-client/` and served by the
6
+ and renders its title/body. Built into `../.dist/` and served by the
7
7
  generated worker (`.astrale/`) under `/ui/*` via its `ASSETS` binding.
8
8
 
9
9
  ## Self-contained on purpose
@@ -38,7 +38,7 @@ __tests__/
38
38
  ## Dev loops
39
39
 
40
40
  ```bash
41
- pnpm dev # vite build --watch → ../dist-client/ (worker auto-reloads, no HMR)
41
+ pnpm dev # vite build --watch → ../.dist/ (worker auto-reloads, no HMR)
42
42
  pnpm dev:hmr # vite dev on http://127.0.0.1:5173/ (React fast-refresh)
43
43
  pnpm test # vitest run (happy-dom; fake parent + fake kernel, JSON-only)
44
44
  ```
@@ -18,7 +18,10 @@ body {
18
18
  margin: 0;
19
19
  background: var(--bg);
20
20
  color: var(--fg);
21
- font: 14px/1.6 system-ui, -apple-system, sans-serif;
21
+ font:
22
+ 14px/1.6 system-ui,
23
+ -apple-system,
24
+ sans-serif;
22
25
  -webkit-font-smoothing: antialiased;
23
26
  }
24
27
 
@@ -21,5 +21,5 @@
21
21
  "vite.config.ts",
22
22
  "vitest.config.ts"
23
23
  ],
24
- "exclude": ["node_modules", "../dist-client"]
24
+ "exclude": ["node_modules", "../.dist"]
25
25
  }
@@ -2,10 +2,10 @@ import viteReact from '@vitejs/plugin-react'
2
2
  import { defineConfig } from 'vite'
3
3
 
4
4
  /**
5
- * Client SPA for the domain's `ui-note` View. Built into `../dist-client/`,
5
+ * Client SPA for the domain's `ui-note` View. Built into `../.dist/`,
6
6
  * served by the generated worker via its `ASSETS` binding (`.astrale/`).
7
7
  *
8
- * `base: '/ui/'` + `outDir: '../dist-client'`: Vite emits asset refs as
8
+ * `base: '/ui/'` + `outDir: '../.dist'`: Vite emits asset refs as
9
9
  * `/ui/assets/<hash>.js`; the worker strips the `/ui` prefix before delegating
10
10
  * to `ASSETS`, so files resolve from the directory root. `index.html` is the
11
11
  * SPA fallback for `/ui/<anything>`.
@@ -26,7 +26,7 @@ export default defineConfig({
26
26
  base: '/ui/',
27
27
  plugins: [viteReact()],
28
28
  build: {
29
- outDir: '../dist-client',
29
+ outDir: '../.dist',
30
30
  emptyOutDir: true,
31
31
  sourcemap: false,
32
32
  },
@@ -5,7 +5,7 @@ import { defineConfig } from 'vitest/config'
5
5
  * Vitest config for the self-contained client. A DOM env (`happy-dom`) backs
6
6
  * the React render + `window.postMessage`/`MessageChannel` used by the fake
7
7
  * shell-parent harness. Kept separate from `vite.config.ts` (which sets the
8
- * `/ui/` base + `../dist-client` build) so tests don't inherit the SPA build
8
+ * `/ui/` base + `../.dist` build) so tests don't inherit the SPA build
9
9
  * options.
10
10
  */
11
11
  export default defineConfig({