@shaferllc/keel 0.83.4 → 0.83.6

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/AGENTS.md CHANGED
@@ -5,10 +5,11 @@ house framework for Node.js — or an app built on it. Humans: this is also a fa
5
5
  orientation. For prose guides see [`docs/`](./docs); for a machine-readable
6
6
  surface, run the [MCP server](#mcp-server-recommended).
7
7
 
8
- > **The fastest path:** connect the Keel MCP server (below) and call
9
- > `keel_overview` first. It returns conventions, the folder map, every doc
10
- > topic, and the generators. Then `keel_search_docs` / `keel_read_doc` for
11
- > depth, `keel_search_api` to find exports, `keel_scaffold` to generate code.
8
+ > **The fastest path:** follow
9
+ > [`docs/from-install-to-deploy.md`](./docs/from-install-to-deploy.md)
10
+ > (create-keeljs local Cloudflare or optional Keel Cloud). For deep
11
+ > lookups, connect the MCP server and call `keel_overview`, then
12
+ > `keel_search_docs` / `keel_search_api`.
12
13
 
13
14
  ---
14
15
 
package/README.md CHANGED
@@ -61,19 +61,29 @@ export class HomeController {
61
61
  - **Thin and legible.** The whole framework is a few hundred lines in
62
62
  `src/core/`. No magic you can't read.
63
63
 
64
- ## Two repos: the framework and your app
64
+ ## Two pieces: the framework and your app
65
65
 
66
- Keel is distributed the same way most frameworks are a **library** you install,
67
- plus an **app** that depends on it:
66
+ Keel is a **library** you install. New apps come from the generator (templates
67
+ ship inside the package so they cannot lag the framework):
68
+
69
+ ```bash
70
+ npm create keeljs@latest my-app
71
+ cd my-app && npm install && npm run dev
72
+ ```
73
+
74
+ End-to-end (local → Cloudflare or optional Keel Cloud):
75
+ [From install to deploy](./docs/from-install-to-deploy.md).
68
76
 
69
77
  | Repo | Role |
70
78
  |------|------|
71
79
  | [`shaferllc/keel`](https://github.com/shaferllc/keel) (this repo) | The framework. Published as `@shaferllc/keel`. |
72
- | [`shaferllc/keel-app`](https://github.com/shaferllc/keel-app) | The starter appclone it to build something. Gets core updates via `npm update`. |
80
+ | Your app (via `create-keeljs`) | Routes, models, viewsdepends on `@shaferllc/keel`. |
73
81
 
74
82
  ## Install in your app
75
83
 
76
84
  ```bash
85
+ npm create keeljs@latest my-app
86
+ # or, into an existing project:
77
87
  npm install @shaferllc/keel
78
88
  ```
79
89
 
@@ -81,24 +91,20 @@ npm install @shaferllc/keel
81
91
  import { Application, Router, config } from "@shaferllc/keel/core";
82
92
  ```
83
93
 
84
- Or clone the [starter app](https://github.com/shaferllc/keel-app) and start
85
- from a working skeleton.
86
-
87
94
  ## Hack on the framework itself
88
95
 
89
96
  ```bash
90
97
  git clone https://github.com/shaferllc/keel.git
91
98
  cd keel
92
99
  npm install
93
- npm test # 740 tests
94
- npm run typecheck # src + tests
95
- npm run build # compile the package to dist/
96
- npm run verify:release # build from what's committed — what a consumer's install runs
100
+ npm test
101
+ npm run typecheck
102
+ npm run build
103
+ npm run verify:release
97
104
  ```
98
105
 
99
- This repo is **the framework, and only the framework** — there is no app in it. To
100
- run one, clone the [starter app](https://github.com/shaferllc/keel-app) and point it
101
- at your checkout (`"@shaferllc/keel": "file:../keel"`).
106
+ Point a generated kit at your checkout with
107
+ `"@shaferllc/keel": "file:../keel"` while developing.
102
108
 
103
109
  ## The console
104
110
 
@@ -192,7 +198,9 @@ See [docs/architecture.md](./docs/architecture.md) for the full picture.
192
198
 
193
199
  | Guide | What it covers |
194
200
  |-------|----------------|
195
- | [Getting Started](./docs/getting-started.md) | Install, run, first route and controller |
201
+ | [Getting Started](./docs/getting-started.md) | First route, controller, view, config |
202
+ | [From install to deploy](./docs/from-install-to-deploy.md) | create-keeljs → local → Cloudflare or Keel Cloud |
203
+ | [Starter kits](./docs/starter-kits.md) | Presets (`minimal` / `api` / `app` / `saas`) |
196
204
  | [The Service Container](./docs/container.md) | Binding and resolving services, DI |
197
205
  | [Service Providers](./docs/providers.md) | Plugin system: register/boot lifecycle, options |
198
206
  | [Configuration](./docs/configuration.md) | `config/*.ts`, dot-notation, `env()` |
@@ -15,7 +15,17 @@ import { defaultConfig, resolveConfig } from "./config.js";
15
15
  import { accountsMigration } from "./migration.js";
16
16
  import { registerAccountsRoutes } from "./routes.js";
17
17
  import { setAccountStore, tableStore } from "./store.js";
18
- const here = dirname(fileURLToPath(import.meta.url));
18
+ function packageDir() {
19
+ try {
20
+ const url = import.meta.url;
21
+ if (!url)
22
+ return ".";
23
+ return dirname(fileURLToPath(url));
24
+ }
25
+ catch {
26
+ return ".";
27
+ }
28
+ }
19
29
  export class AccountsServiceProvider extends PackageProvider {
20
30
  name = "accounts";
21
31
  config;
@@ -24,7 +34,7 @@ export class AccountsServiceProvider extends PackageProvider {
24
34
  this.config = resolveConfig();
25
35
  setAccountStore(tableStore(this.config.userTable));
26
36
  this.migrations([accountsMigration(this.config.userTable)]);
27
- this.publishes({ [join(here, "accounts.config.stub")]: "config/accounts.ts" }, "accounts-config");
37
+ this.publishes({ [join(packageDir(), "accounts.config.stub")]: "config/accounts.ts" }, "accounts-config");
28
38
  }
29
39
  boot() {
30
40
  if (!this.config.routes.enabled)
@@ -17,7 +17,17 @@ import { BillingManager, setBilling } from "./manager.js";
17
17
  import { registerDefaultGateways } from "./drivers/index.js";
18
18
  import { billingMigration } from "./migration.js";
19
19
  import { registerBillingRoutes } from "./routes.js";
20
- const here = dirname(fileURLToPath(import.meta.url));
20
+ function packageDir() {
21
+ try {
22
+ const url = import.meta.url;
23
+ if (!url)
24
+ return ".";
25
+ return dirname(fileURLToPath(url));
26
+ }
27
+ catch {
28
+ return ".";
29
+ }
30
+ }
21
31
  export class BillingServiceProvider extends PackageProvider {
22
32
  name = "billing";
23
33
  config;
@@ -31,7 +41,7 @@ export class BillingServiceProvider extends PackageProvider {
31
41
  this.app.instance(BillingManager, this.manager);
32
42
  // Default billable table is `users`.
33
43
  this.migrations([billingMigration("users")]);
34
- this.publishes({ [join(here, "billing.config.stub")]: "config/billing.ts" }, "billing-config");
44
+ this.publishes({ [join(packageDir(), "billing.config.stub")]: "config/billing.ts" }, "billing-config");
35
45
  }
36
46
  boot() {
37
47
  this.routes((r) => registerBillingRoutes(r, this.config.webhook.path));
@@ -2,7 +2,8 @@
2
2
  * Keel Cloud MCP tools — registered only when KEEL_CLOUD_TOKEN is set.
3
3
  *
4
4
  * Talks to a Keel Cloud control plane over HTTP (Bearer token). Agents use these
5
- * to create sites, preview/publish, and manage secrets without leaving the IDE.
5
+ * to create sites, preview/publish, manage secrets/domains/billing, and export
6
+ * without leaving the IDE.
6
7
  */
7
8
  import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
9
  /** Register Cloud tools on an existing MCP server when a Cloud token is present. */
package/dist/mcp/cloud.js CHANGED
@@ -2,7 +2,8 @@
2
2
  * Keel Cloud MCP tools — registered only when KEEL_CLOUD_TOKEN is set.
3
3
  *
4
4
  * Talks to a Keel Cloud control plane over HTTP (Bearer token). Agents use these
5
- * to create sites, preview/publish, and manage secrets without leaving the IDE.
5
+ * to create sites, preview/publish, manage secrets/domains/billing, and export
6
+ * without leaving the IDE.
6
7
  */
7
8
  import { z } from "zod";
8
9
  const PRESETS = ["minimal", "api", "app", "saas"];
@@ -42,18 +43,49 @@ function jsonText(data, isError = false) {
42
43
  ...(isError ? { isError: true } : {}),
43
44
  };
44
45
  }
46
+ function requireConfirm(confirm, action) {
47
+ if (confirm)
48
+ return null;
49
+ return jsonText({
50
+ error: `${action} requires confirm=true. Ask the user to confirm before calling again.`,
51
+ }, true);
52
+ }
45
53
  /** Register Cloud tools on an existing MCP server when a Cloud token is present. */
46
54
  export function registerCloudTools(server) {
47
55
  if (!cloudConfig())
48
56
  return false;
49
57
  server.registerTool("keel_cloud_me", {
50
58
  title: "Keel Cloud — who am I",
51
- description: "Return the Keel Cloud user authenticated by KEEL_CLOUD_TOKEN.",
59
+ description: "Return the Keel Cloud user authenticated by KEEL_CLOUD_TOKEN (plan, site_limit, team_id). Token binds to the user's first team.",
52
60
  inputSchema: {},
53
61
  }, async () => {
54
62
  const res = await cloudFetch("/api/v1/me");
55
63
  return jsonText(res.body, !res.ok);
56
64
  });
65
+ server.registerTool("keel_cloud_billing", {
66
+ title: "Keel Cloud — billing status",
67
+ description: "Current team plan (free|pro), site limits, custom-domain eligibility, and whether the caller is the owner.",
68
+ inputSchema: {},
69
+ }, async () => {
70
+ const res = await cloudFetch("/api/v1/billing");
71
+ return jsonText(res.body, !res.ok);
72
+ });
73
+ server.registerTool("keel_cloud_billing_checkout", {
74
+ title: "Keel Cloud — Stripe checkout URL",
75
+ description: "Create a Stripe Checkout session for Keel Cloud Pro (owner only). Return the url for the user to open — do not scrape cards.",
76
+ inputSchema: {},
77
+ }, async () => {
78
+ const res = await cloudFetch("/api/v1/billing/checkout", { method: "POST", body: "{}" });
79
+ return jsonText(res.body, !res.ok);
80
+ });
81
+ server.registerTool("keel_cloud_billing_portal", {
82
+ title: "Keel Cloud — Stripe customer portal URL",
83
+ description: "Open the Stripe customer portal URL for the team (owner only) to update card / cancel. Return the url for the user.",
84
+ inputSchema: {},
85
+ }, async () => {
86
+ const res = await cloudFetch("/api/v1/billing/portal", { method: "POST", body: "{}" });
87
+ return jsonText(res.body, !res.ok);
88
+ });
57
89
  server.registerTool("keel_cloud_list_sites", {
58
90
  title: "Keel Cloud — list sites",
59
91
  description: "List sites on the current Keel Cloud team. Includes storage_path for local editing and hostnames.",
@@ -64,7 +96,7 @@ export function registerCloudTools(server) {
64
96
  });
65
97
  server.registerTool("keel_cloud_get_site", {
66
98
  title: "Keel Cloud — get site",
67
- description: "Fetch one Keel Cloud site by id (paths, hostnames, status).",
99
+ description: "Fetch one Keel Cloud site by id (paths, hostnames, status, git).",
68
100
  inputSchema: {
69
101
  site_id: z.number().int().describe("Site id from keel_cloud_list_sites"),
70
102
  },
@@ -86,6 +118,36 @@ export function registerCloudTools(server) {
86
118
  });
87
119
  return jsonText(res.body, !res.ok);
88
120
  });
121
+ server.registerTool("keel_cloud_delete_site", {
122
+ title: "Keel Cloud — soft-delete site",
123
+ description: "Soft-delete a site (detaches hostnames; recoverable within retention). Requires confirm=true.",
124
+ inputSchema: {
125
+ site_id: z.number().int().describe("Site id"),
126
+ confirm: z.boolean().describe("Must be true — ask the user before setting this"),
127
+ },
128
+ }, async ({ site_id, confirm }) => {
129
+ const blocked = requireConfirm(confirm, "Delete");
130
+ if (blocked)
131
+ return blocked;
132
+ const res = await cloudFetch(`/api/v1/sites/${site_id}/delete`, {
133
+ method: "POST",
134
+ body: JSON.stringify({ confirm: true }),
135
+ });
136
+ return jsonText(res.body, !res.ok);
137
+ });
138
+ server.registerTool("keel_cloud_restore_site", {
139
+ title: "Keel Cloud — restore soft-deleted site",
140
+ description: "Restore a soft-deleted site within the retention window. Redeploy to reattach hostnames.",
141
+ inputSchema: {
142
+ site_id: z.number().int().describe("Site id"),
143
+ },
144
+ }, async ({ site_id }) => {
145
+ const res = await cloudFetch(`/api/v1/sites/${site_id}/restore`, {
146
+ method: "POST",
147
+ body: "{}",
148
+ });
149
+ return jsonText(res.body, !res.ok);
150
+ });
89
151
  server.registerTool("keel_cloud_preview", {
90
152
  title: "Keel Cloud — deploy preview",
91
153
  description: "Deploy the site's preview Worker (and attach preview hostname when Cloudflare is configured). Safe to call freely while iterating.",
@@ -106,11 +168,9 @@ export function registerCloudTools(server) {
106
168
  .describe("Must be true to publish — ask the user before setting this"),
107
169
  },
108
170
  }, async ({ site_id, confirm }) => {
109
- if (!confirm) {
110
- return jsonText({
111
- error: "Publish requires confirm=true. Ask the user to confirm before calling again.",
112
- }, true);
113
- }
171
+ const blocked = requireConfirm(confirm, "Publish");
172
+ if (blocked)
173
+ return blocked;
114
174
  const res = await cloudFetch(`/api/v1/sites/${site_id}/publish`, {
115
175
  method: "POST",
116
176
  body: JSON.stringify({ confirm: true }),
@@ -127,6 +187,16 @@ export function registerCloudTools(server) {
127
187
  const res = await cloudFetch(`/api/v1/sites/${site_id}/deploys`);
128
188
  return jsonText(res.body, !res.ok);
129
189
  });
190
+ server.registerTool("keel_cloud_list_secrets", {
191
+ title: "Keel Cloud — list secret keys",
192
+ description: "List vault key names for a site (values are never returned).",
193
+ inputSchema: {
194
+ site_id: z.number().int().describe("Site id"),
195
+ },
196
+ }, async ({ site_id }) => {
197
+ const res = await cloudFetch(`/api/v1/sites/${site_id}/secrets`);
198
+ return jsonText(res.body, !res.ok);
199
+ });
130
200
  server.registerTool("keel_cloud_set_secret", {
131
201
  title: "Keel Cloud — set secret",
132
202
  description: "Store a secret in the site vault (never in git). Injected into the Worker on the next preview/publish.",
@@ -142,6 +212,45 @@ export function registerCloudTools(server) {
142
212
  });
143
213
  return jsonText(res.body, !res.ok);
144
214
  });
215
+ server.registerTool("keel_cloud_delete_secret", {
216
+ title: "Keel Cloud — delete secret",
217
+ description: "Remove a secret key from the site vault.",
218
+ inputSchema: {
219
+ site_id: z.number().int().describe("Site id"),
220
+ key: z.string().min(1).describe("Secret key to remove"),
221
+ },
222
+ }, async ({ site_id, key }) => {
223
+ const res = await cloudFetch(`/api/v1/sites/${site_id}/secrets?key=${encodeURIComponent(key)}`, { method: "DELETE" });
224
+ return jsonText(res.body, !res.ok);
225
+ });
226
+ server.registerTool("keel_cloud_set_custom_domain", {
227
+ title: "Keel Cloud — set custom domain",
228
+ description: "Set a Pro custom hostname (CNAME instructions returned). Optionally attach=true to verify DNS and attach to the production Worker.",
229
+ inputSchema: {
230
+ site_id: z.number().int().describe("Site id"),
231
+ hostname: z.string().min(1).describe("Customer hostname, e.g. app.example.com"),
232
+ attach: z
233
+ .boolean()
234
+ .optional()
235
+ .describe("If true, verify DNS and attach Workers Custom Domain now"),
236
+ },
237
+ }, async ({ site_id, hostname, attach }) => {
238
+ const res = await cloudFetch(`/api/v1/sites/${site_id}/custom-domain`, {
239
+ method: "PUT",
240
+ body: JSON.stringify({ hostname, attach: Boolean(attach) }),
241
+ });
242
+ return jsonText(res.body, !res.ok);
243
+ });
244
+ server.registerTool("keel_cloud_clear_custom_domain", {
245
+ title: "Keel Cloud — clear custom domain",
246
+ description: "Detach and clear the site's custom hostname (Pro).",
247
+ inputSchema: {
248
+ site_id: z.number().int().describe("Site id"),
249
+ },
250
+ }, async ({ site_id }) => {
251
+ const res = await cloudFetch(`/api/v1/sites/${site_id}/custom-domain`, { method: "DELETE" });
252
+ return jsonText(res.body, !res.ok);
253
+ });
145
254
  server.registerTool("keel_cloud_export", {
146
255
  title: "Keel Cloud — export manifest",
147
256
  description: "Return the site's export manifest (paths, hostnames, Keel version, SQL dump URLs). Use storage_path / git_url for code; use keel_cloud_export_sql for data.",
@@ -190,12 +190,14 @@ export async function createServer() {
190
190
  ? [
191
191
  "",
192
192
  "## Keel Cloud (token detected)",
193
- "Cloud tools are enabled. Typical loop:",
193
+ "Cloud tools are enabled. Token binds to the user's first team.",
194
+ "Typical loop:",
194
195
  "1. keel_cloud_create_site { name, preset }",
195
196
  "2. Edit files at the returned storage_path (real Keel app)",
196
197
  "3. keel_cloud_preview { site_id }",
197
198
  "4. keel_cloud_publish { site_id, confirm: true } after user approval",
198
- "Also: keel_cloud_list_sites, keel_cloud_get_site, keel_cloud_set_secret, keel_cloud_deploys, keel_cloud_export.",
199
+ "Also: me, billing(+checkout/portal), list/get/delete/restore sites,",
200
+ "secrets (list/set/delete), custom domain (set/clear), deploys, export(+sql).",
199
201
  ]
200
202
  : []),
201
203
  ].join("\n");
@@ -14,7 +14,17 @@ import { dirname, join } from "node:path";
14
14
  import { PackageProvider } from "../core/package.js";
15
15
  import { defaultConfig, resolveConfig } from "./config.js";
16
16
  import { teamsMigration } from "./migration.js";
17
- const here = dirname(fileURLToPath(import.meta.url));
17
+ function packageDir() {
18
+ try {
19
+ const url = import.meta.url;
20
+ if (!url)
21
+ return ".";
22
+ return dirname(fileURLToPath(url));
23
+ }
24
+ catch {
25
+ return ".";
26
+ }
27
+ }
18
28
  export class TeamsServiceProvider extends PackageProvider {
19
29
  name = "teams";
20
30
  config;
@@ -22,6 +32,6 @@ export class TeamsServiceProvider extends PackageProvider {
22
32
  this.mergeConfig("teams", defaultConfig);
23
33
  this.config = resolveConfig();
24
34
  this.migrations([teamsMigration(this.config.userTable)]);
25
- this.publishes({ [join(here, "teams.config.stub")]: "config/teams.ts" }, "teams-config");
35
+ this.publishes({ [join(packageDir(), "teams.config.stub")]: "config/teams.ts" }, "teams-config");
26
36
  }
27
37
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shaferllc/keel",
3
- "version": "0.83.4",
3
+ "version": "0.83.6",
4
4
  "description": "The house framework for Node.js — a service container, providers, routing, JSX views, and a code-generating console.",
5
5
  "repo": "https://github.com/shaferllc/keel",
6
6
  "generated": "run `npm run build:ai` to regenerate",
@@ -159,6 +159,13 @@
159
159
  "path": "docs/factories.md",
160
160
  "example": "docs/examples/factories.ts"
161
161
  },
162
+ {
163
+ "slug": "from-install-to-deploy",
164
+ "title": "From install to deploy",
165
+ "summary": "One path from zero to a live Keel app — locally, on Cloudflare yourself, or on Keel Cloud with an AI agent.",
166
+ "path": "docs/from-install-to-deploy.md",
167
+ "example": null
168
+ },
162
169
  {
163
170
  "slug": "gates",
164
171
  "title": "Gates",
package/docs/ai.md CHANGED
@@ -5,8 +5,9 @@ AI-facing surface: an MCP server, machine-readable docs (`llms.txt` /
5
5
  `llms-full.txt`), an agent playbook (`AGENTS.md`), and code generators an agent
6
6
  can drive directly.
7
7
 
8
- If you only read one thing: point your agent at the [MCP server](#the-mcp-server)
9
- and have it call `keel_overview` first.
8
+ If you only read one thing: follow
9
+ [From install to deploy](./from-install-to-deploy.md), then point your agent at
10
+ the [MCP server](#the-mcp-server) and have it call `keel_overview` first.
10
11
 
11
12
  ## Why this exists
12
13
 
@@ -62,18 +63,23 @@ package, so it always matches your installed version.
62
63
  | `keel_scaffold` | Generate a controller/provider/middleware/factory/seeder/job/notification/transformer stub. Returns code + target path — it does **not** write to disk. |
63
64
 
64
65
  When `KEEL_CLOUD_TOKEN` (and optional `KEEL_CLOUD_URL`) is set, Cloud tools are
65
- also registered:
66
+ also registered. Create a token at `/tokens` on the control plane — the plaintext
67
+ looks like `keel_<selector>.<verifier>`.
66
68
 
67
69
  | Tool | What it does |
68
70
  |------|--------------|
69
- | `keel_cloud_me` | Authenticated Cloud user |
70
- | `keel_cloud_list_sites` / `keel_cloud_get_site` | List or fetch a site (includes `storage_path`) |
71
+ | `keel_cloud_me` | Authenticated Cloud user (plan, site limit, team) |
72
+ | `keel_cloud_billing` | Team plan / limits / owner flag |
73
+ | `keel_cloud_billing_checkout` / `_portal` | Stripe Checkout or Customer Portal URL (owner) |
74
+ | `keel_cloud_list_sites` / `keel_cloud_get_site` | List or fetch a site (`storage_path`, hostnames, git) |
71
75
  | `keel_cloud_create_site` | Create from preset (`minimal` \| `api` \| `app` \| `saas`) |
76
+ | `keel_cloud_delete_site` / `keel_cloud_restore_site` | Soft-delete (confirm) / restore |
72
77
  | `keel_cloud_preview` | Deploy preview Worker |
73
78
  | `keel_cloud_publish` | Publish production — requires `confirm: true` |
74
- | `keel_cloud_set_secret` | Vault a secret (injected on next deploy) |
75
- | `keel_cloud_deploys` / `keel_cloud_export` | Deploy history and export manifest |
76
- | `keel_cloud_export_sql` | Portable `.sql` dump (`local` \| `preview` \| `production`) |
79
+ | `keel_cloud_deploys` | Deploy history + logs |
80
+ | `keel_cloud_list_secrets` / `keel_cloud_set_secret` / `keel_cloud_delete_secret` | Vault keys (values never returned) |
81
+ | `keel_cloud_set_custom_domain` / `keel_cloud_clear_custom_domain` | Pro custom hostname (+ optional attach) |
82
+ | `keel_cloud_export` / `keel_cloud_export_sql` | Export manifest / portable `.sql` dump |
77
83
 
78
84
  ```json
79
85
  {
@@ -82,7 +88,7 @@ also registered:
82
88
  "command": "npx",
83
89
  "args": ["-y", "keel-mcp"],
84
90
  "env": {
85
- "KEEL_CLOUD_TOKEN": "kc_…",
91
+ "KEEL_CLOUD_TOKEN": "keel_….…",
86
92
  "KEEL_CLOUD_URL": "https://app.keeljs.cloud"
87
93
  }
88
94
  }
@@ -90,6 +96,8 @@ also registered:
90
96
  }
91
97
  ```
92
98
 
99
+ The token binds to the user's **first team**. Switch teams in the dashboard before
100
+ minting a token if you need a different team context.
93
101
  ### Resources
94
102
 
95
103
  - `keel://overview` — the same orientation text as `keel_overview`
@@ -108,8 +116,11 @@ also registered:
108
116
 
109
117
  1. `keel_cloud_create_site { name: "Acme", preset: "app" }`
110
118
  2. Edit the returned `storage_path` (real Keel app + git)
111
- 3. `keel_cloud_preview { site_id }`
112
- 4. `keel_cloud_publish { site_id, confirm: true }` after the user approves
119
+ 3. `keel_cloud_set_secret` for env the Worker needs
120
+ 4. `keel_cloud_preview { site_id }`
121
+ 5. `keel_cloud_publish { site_id, confirm: true }` after the user approves
122
+ 6. Optional Pro: `keel_cloud_set_custom_domain { hostname, attach: true }`
123
+ 7. Escape hatch anytime: `keel_cloud_export` + `keel_cloud_export_sql`
113
124
 
114
125
  ## `llms.txt` and `llms-full.txt`
115
126
 
package/docs/changelog.md CHANGED
@@ -4,6 +4,27 @@ All notable changes to Keel are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project aims to
5
5
  adhere to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.83.6] — 2026-07-13
8
+
9
+ ### Documentation
10
+
11
+ - **[From install to deploy](./docs/from-install-to-deploy.md)** — end-to-end
12
+ guide: `create-keeljs` → local dev → Cloudflare self-deploy → optional Keel
13
+ Cloud + MCP. Getting Started and the README now lead with the generator.
14
+
15
+ [0.83.6]: https://github.com/shaferllc/keel/releases/tag/v0.83.6
16
+
17
+ ## [0.83.5] — 2026-07-13
18
+
19
+ ### Added
20
+
21
+ - **Full Keel Cloud MCP surface** — billing status/checkout/portal, soft-delete
22
+ and restore sites, list/delete secrets, set/clear custom domains. Documented in
23
+ [Building with AI](./docs/ai.md). Pairs with expanded `/api/v1` routes on Keel
24
+ Cloud.
25
+
26
+ [0.83.5]: https://github.com/shaferllc/keel/releases/tag/v0.83.5
27
+
7
28
  ## [0.83.4] — 2026-07-13
8
29
 
9
30
  ### Added