@aotter/mantle 0.0.11-alpha.35 → 0.0.11-alpha.37

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
@@ -38,7 +38,41 @@ deployable Mantle site, and connects the first Cloudflare deploy. Then
38
38
  point your coding agent (Claude Code / Cursor / Codex) at the provisioned
39
39
  repo to continue.
40
40
 
41
- Starter source and provision bundles live in [`aotter/mantle-starters`](https://github.com/aotter/mantle-starters); launch starts from the blank base and the agent applies the selected type as a post-launch overlay. See `skills/install` in the [Mantle repo](https://github.com/aotter/mantle/tree/develop/skills/install) for how the agent continues a provisioned repo.
41
+ Starter source and provision bundles live in [`aotter/mantle-starters`](https://github.com/aotter/mantle-starters); launch starts from the blank base and the agent applies the selected type as a post-launch overlay. Generated repos vendor Core-owned `mantle:*` skills from this package for repo-local use. See `skills/install` in the [Mantle repo](https://github.com/aotter/mantle/tree/develop/skills/install) for how the agent continues a provisioned repo.
42
+
43
+ ## Agent marketplace install
44
+
45
+ Install the Mantle Core skill bundle before working on generated repos:
46
+
47
+ ```bash
48
+ # Claude Code
49
+ /plugin marketplace add aotter/mantle
50
+ /plugin install mantle
51
+
52
+ # Codex
53
+ codex plugin marketplace add aotter/mantle --ref develop
54
+ codex plugin add mantle@mantle
55
+ ```
56
+
57
+ Cursor and VS Code Copilot can auto-discover the GitHub repo through
58
+ `.cursor-plugin/plugin.json` and `.copilot-plugin/plugin.json` after the repo
59
+ is cloned or opened.
60
+
61
+ ## Marketplace capability installs
62
+
63
+ In a generated repo, tell your coding agent:
64
+
65
+ ```txt
66
+ Use repo-local mantle:plugin to install <plugin slug or recipe URL> in this repo.
67
+ Use repo-local mantle:plugin to update <plugin id> in this repo.
68
+ Use repo-local mantle:plugin to remove <plugin id> from this repo.
69
+ ```
70
+
71
+ Mantle marketplace entries are agent-installable recipes. They declare the
72
+ plugin source, Mantle version range, files/atoms/routes/tools, adapter
73
+ requirements, secrets, and checks. The agent applies the recipe through the
74
+ Core-owned `mantle:plugin` skill and records it in `.mantle/plugins.json` plus
75
+ `.mantle/plugins.lock.json`. There is no `mantle plugin add` CLI yet.
42
76
 
43
77
  ## Adapter targets
44
78
 
@@ -54,7 +88,12 @@ The `mantle-runtime` package never imports Cloudflare-specific types — adapter
54
88
  - Embedded docs and agent skills ship inside this npm package for
55
89
  generated-site agents:
56
90
  - `node_modules/@aotter/mantle/docs/design-atoms.md`
91
+ - `node_modules/@aotter/mantle/docs/media-uploads.md` (Cloudflare R2 adapter recipe)
57
92
  - `node_modules/@aotter/mantle/docs/adr/`
93
+ - `node_modules/@aotter/mantle/skills/develop/SKILL.md`
94
+ - `node_modules/@aotter/mantle/skills/plugin/SKILL.md`
95
+ - `node_modules/@aotter/mantle/skills/theme/SKILL.md`
96
+ - `node_modules/@aotter/mantle/skills/update/SKILL.md`
58
97
  - `node_modules/@aotter/mantle/skills/install/SKILL.md`
59
98
  - `node_modules/@aotter/mantle/skills/provision/SKILL.md`
60
99
  - [Repo](https://github.com/aotter/mantle)
@@ -2,7 +2,8 @@
2
2
 
3
3
  ## Status
4
4
 
5
- Accepted (new)
5
+ Superseded for first launch by provisioning v2 (2026-06-27). Historical
6
+ record only.
6
7
 
7
8
  ## Date
8
9
 
@@ -10,6 +11,12 @@ Accepted (new)
10
11
 
11
12
  ## Context
12
13
 
14
+ > **Current direction:** Mantle landing creates the GitHub repo from
15
+ > `mantle-starters` `provision-bundles/<type>.json`, commits the blank
16
+ > or type bundle, and connects Cloudflare Workers CI when possible.
17
+ > Do not extend the old `create-mantle launch --session` or
18
+ > `provision:up` path.
19
+
13
20
  mantle is not optimized for a human developer reading a long
14
21
  installation guide and hand-editing boilerplate. The intended v0.1.0
15
22
  entry path is:
@@ -419,3 +419,42 @@ The original ADR-0014 §"Auth as contract, Better Auth as default" framing stays
419
419
  - A `@cloudflare/vitest-pool-workers`-based integration test covering the full OAuth flow (DCR → consent → token → MCP RPC). Node-vitest can't load `@cloudflare/workers-oauth-provider` because it imports from `cloudflare:workers`.
420
420
  - Starters (`aotter/mantle-starters`) migration to the same top-level OAuthProvider shape. All 8 archetypes currently use the pre-carve-out `mountMcp` API and need updating before the next starter tag.
421
421
  - Track whether Anthropic relaxes (1) the `/mcp` resource-path-prefix requirement and (2) the no-colon-in-scope requirement. Both are de-facto MCP client behaviors, not RFC requirements; if upstream relaxes them, the SDK can re-introduce `mcp:read` / `mcp:staff` scopes for finer-grained delegation.
422
+
423
+ ## Amendment — 2026-06-30: Hosted-auth boundary and first-party cookie fields
424
+
425
+ Mantle Platform introduces a first-party hosted-auth use case:
426
+ `platform.mantle.tools` owns site-owner account, billing, hosted
427
+ identity, and platform email, while generated sites still own local
428
+ grants, member records, content, commerce, forms, and legal copy. The
429
+ product boundary is documented in
430
+ [`docs/auth-hosting-model.md`](../auth-hosting-model.md).
431
+
432
+ This does not change ADR-0014's core rule: Mantle does not expose an
433
+ un-curated `betterAuthOptions` or `advanced` passthrough. Missing Better
434
+ Auth knobs become first-class SDK fields only when a real Mantle use
435
+ case needs them.
436
+
437
+ The first-party SSO use case needs exactly three Better Auth fields:
438
+
439
+ - `trustedOrigins` — app-owned origins that Better Auth should trust for
440
+ auth flows. SDK-managed provider origins, such as Apple, are still
441
+ injected automatically.
442
+ - `crossSubDomainCookies` — Better Auth's same-parent-domain cookie
443
+ support for a trusted first-party app family.
444
+ - `cookiePrefix` — required when two Better Auth apps can write cookies
445
+ under the same parent domain, so Platform cookies do not collide with
446
+ Landing or generated-site cookies.
447
+
448
+ These fields are additive. Existing consumers that do not pass them keep
449
+ the previous cookie/session behavior.
450
+
451
+ The boundary is deliberately narrower than "hosted auth everywhere":
452
+
453
+ - Free users can still self-host every login method Mantle exposes
454
+ through `createAuth()`.
455
+ - Same-parent-domain SSO can use `crossSubDomainCookies` when the same
456
+ party controls all participating subdomains.
457
+ - Customer-owned domains cannot receive `mantle.tools` cookies. Paid
458
+ hosted auth for `customer.com` must use an OAuth/OIDC broker flow:
459
+ Platform authenticates and returns identity; the customer site creates
460
+ its own local session and maps identity into local grants.
@@ -3,7 +3,8 @@
3
3
  ## Status
4
4
 
5
5
  Accepted (slimmed 2026-05-12 per Epic #116; `mantle/site.md` letter
6
- surface suspended 2026-06-19 and removed from first-run scaffolds).
6
+ surface suspended 2026-06-19 and removed from first-run scaffolds;
7
+ amended 2026-06-27 for landing provision bundles).
7
8
 
8
9
  ## Decision
9
10
 
@@ -27,7 +28,8 @@ from deterministic scaffold state.
27
28
 
28
29
  ## Placeholder macros
29
30
 
30
- `create-mantle` substitutes these across `_common/*.template` files in a single pass:
31
+ Mantle landing substitutes these across provision-bundle `*.template`
32
+ files in a single pass:
31
33
 
32
34
  | Macro | Source | Example |
33
35
  |---|---|---|
@@ -41,7 +43,8 @@ from deterministic scaffold state.
41
43
  | `{{INSTALL_TIMESTAMP}}` | ISO 8601 of install run | `2026-05-12T14:03:00Z` |
42
44
  | `{{INSTALL_SUMMARY}}` | CLI flag | `bootstrapped publication site for Lab Cafe in zh-TW/en` |
43
45
 
44
- New macros must be added here, to `_common/*.template`, and to the substitution pass in `create-mantle`.
46
+ New macros must be added here, to `mantle-starters` bundle templates,
47
+ and to the landing substitution pass.
45
48
 
46
49
  ## Update rules
47
50
 
@@ -58,8 +61,9 @@ New macros must be added here, to `_common/*.template`, and to the substitution
58
61
 
59
62
  ## Implementation
60
63
 
61
- - Templates: `mantle-starters/_common/AGENTS.md.template`.
62
- - Substitution: `packages/create-mantle/src/placeholder.ts`.
63
- - Install handoff: `skills/install/SKILL.md` describes deterministic scaffold validation and the short post-substitution notes fill.
64
+ - Templates: `mantle-starters/blank/AGENTS.md.template` and
65
+ generated `.mantle/*.template` bundle files.
66
+ - Substitution: Mantle landing provision-bundle substitution.
67
+ - Install handoff: `skills/install/SKILL.md` describes how agents continue from the landing-provisioned repo.
64
68
  - Provision update: `skills/provision/SKILL.md` describes the `AGENTS.md` public-site rewrite after deploy.
65
- - Theme overlay merge (Epic #116): `themes/<theme-key>/` overlay applies after the archetype starter and may touch `src/theme/` — never these two files.
69
+ - Type overlays are applied while building `provision-bundles/<type>.json`; generated repos should not need a second overlay step.
@@ -14,7 +14,7 @@ Records of *why* mantle ended up shaped this way. The numbering preserves POC AD
14
14
  | [0010](0010-locale-and-translates.md) | Locale 3-layer (manifest / D1 site_config / data field) + translates pattern. Boot decoupled from `site_config` (issue #60 fix). | Accepted (refreshed) |
15
15
  | [0011](0011-adapter-port-spec.md) | Adapter port spec. Required runtime ports plus optional feature ports. CF impl + Netlify stub. | Accepted (new) |
16
16
  | [0012](0012-views-as-public-rest.md) | Views auto-expose `GET /api/views/<name>` as the public REST read surface. Schemas never get a public REST endpoint. Filter comparison values accept `{ $param: <name> }`; `?page=&show=` reserved for pagination. | Accepted (new) |
17
- | [0013](0013-agent-provisioned-consumer-projects.md) | Agent-provisioned consumer projects: website prompt Skill npm packages → starter setup → first-run provision/seed → owner/MCP handoff. | Accepted (new) |
17
+ | [0013](0013-agent-provisioned-consumer-projects.md) | Historical agent-provisioned consumer projects path. Superseded for first launch by landing provision bundles. | Superseded |
18
18
  | [0014](0014-auth-better-auth-and-multi-tenant-mcp.md) | Better Auth for staff sign-in (D1 session); the MCP OAuth surface carves out to `@cloudflare/workers-oauth-provider` (KV grant store) at top level. The two meet at `/oauth/authorize` where the consent handler reads the Better Auth session. MCP splits into `/mcp/staff` (write, admin-role) and `/mcp` (read, any signed-in). Scope advertised as `["mcp"]` (single non-colon) because claude.ai rejects colon-shaped scopes. Auth port disappears; runtime takes Better Auth instance directly. See § "Amendment 2026-05-15". | Accepted + amended |
19
19
  | [0016](0016-site-semantic-layer.md) | Site semantic layer: `AGENTS.md` (cross-tool entry) + `.mantle/launch-state.json` (deterministic install context). The older `mantle/site.md` letter surface is suspended from first-run scaffolds. | Accepted (slimmed) |
20
20
 
@@ -27,7 +27,7 @@ If you're new to the codebase:
27
27
  3. **0007** — what running the SDK feels like as an AI author (and as the operator agent).
28
28
  4. **0011** — the boundary between the runtime and the adapter (most load-bearing for the rebuild).
29
29
  5. **0010** — how locale flows through the system.
30
- 6. **0013** — how the website prompt, Skills, npm packages, starters, seed, provision, and handoff fit together.
30
+ 6. **0013** — historical install-session context; current first launch is landing provision bundles plus repo-local handoff.
31
31
  7. **0002, 0008** — the two ADRs that touch every diagnostic and every binding.
32
32
 
33
33
  ## What's NOT here (and why)
@@ -0,0 +1,139 @@
1
+ # Auth Hosting Model
2
+
3
+ Mantle treats authentication as a runtime capability, not a paywall on
4
+ basic login. The split is:
5
+
6
+ - **Mantle SDK** gives every generated site the primitives needed to
7
+ run its own auth.
8
+ - **Mantle starters** decide whether a generated site is wired for
9
+ self-hosted auth or as a client of Mantle Platform hosted auth.
10
+ - **Mantle Platform** can sell hosted identity, provider setup, email,
11
+ and billing convenience for site owners who do not want to operate
12
+ those pieces.
13
+
14
+ This document is the source of truth for the product and SDK boundary.
15
+ Do not duplicate this model in starter handoff copy or landing prose;
16
+ link here and keep downstream docs short.
17
+
18
+ ## Free Self-Hosted Auth
19
+
20
+ A free Mantle site can run every login method the SDK exposes through
21
+ `createAuth()`:
22
+
23
+ - social OAuth providers supported by Better Auth;
24
+ - generic provider-specific options through the `social.extras` shape;
25
+ - email OTP;
26
+ - magic link;
27
+ - passkey or additional Better Auth method support when the SDK adds a
28
+ curated first-class field for it;
29
+ - first-party same-domain or same-parent-domain SSO when the site owner
30
+ controls every participating subdomain.
31
+
32
+ The site owner supplies their own provider client ids/secrets,
33
+ transactional email sender, DNS, cookie policy, and operational
34
+ maintenance. Mantle must not block self-hosted login behind a paid
35
+ feature flag.
36
+
37
+ ## Hosted Platform Auth
38
+
39
+ Paid hosted auth is a convenience and operations product. The value is
40
+ that Mantle Platform can:
41
+
42
+ - hold OAuth provider configuration;
43
+ - hold email provider credentials;
44
+ - send OTP and magic-link email;
45
+ - manage deliverability and provider rotation;
46
+ - register generated sites as clients;
47
+ - check billing/license entitlements before hosted capabilities are
48
+ enabled;
49
+ - expose a stable hosted identity broker for generated sites.
50
+
51
+ The generated site still owns local grants, member records, content,
52
+ orders, forms, and legal copy. Platform returns identity. The site maps
53
+ that identity into `ctx.user`, `ctx.staff`, and its own grant model.
54
+
55
+ ## Same Parent Domain SSO
56
+
57
+ When the apps share a parent domain that the same party controls, SSO
58
+ can use browser cookies:
59
+
60
+ ```ts
61
+ createAuth({
62
+ // ...
63
+ trustedOrigins: ["https://mantle.tools", "https://www.mantle.tools"],
64
+ cookiePrefix: "mantle-platform",
65
+ crossSubDomainCookies: {
66
+ enabled: true,
67
+ domain: "mantle.tools",
68
+ },
69
+ });
70
+ ```
71
+
72
+ Use this only for a trusted first-party app family, for example
73
+ `platform.mantle.tools` and `mantle.tools`.
74
+
75
+ `cookiePrefix` is required whenever more than one Better Auth app can
76
+ write cookies under the same parent domain. Without it, Platform and
77
+ Landing can overwrite each other's default Better Auth cookie names.
78
+
79
+ `trustedOrigins` is a Better Auth auth-flow trust list. It is not a
80
+ general API CORS policy and must not be documented as one.
81
+
82
+ ## Customer Domain SSO
83
+
84
+ Shared cookies do not work across different registrable domains:
85
+
86
+ ```text
87
+ platform.mantle.tools -> customer.com
88
+ ```
89
+
90
+ Browsers will not send a `mantle.tools` cookie to `customer.com`. For a
91
+ customer-owned domain, hosted auth must use OAuth/OIDC:
92
+
93
+ ```text
94
+ customer.com/login
95
+ -> platform.mantle.tools/oauth/authorize
96
+ -> user signs in with Platform-supported methods
97
+ -> customer.com/api/auth/callback/mantle
98
+ -> customer.com verifies the authorization response
99
+ -> customer.com creates its own local session
100
+ ```
101
+
102
+ That local session is the customer's site session. Platform remains the
103
+ identity broker and entitlement authority; it does not become the
104
+ customer site's member database or authorization system.
105
+
106
+ ## Landing Provisioning Split
107
+
108
+ Landing should branch before GitHub or Cloudflare side effects:
109
+
110
+ - **Continue free**: commit a self-hosted auth-ready site with docs,
111
+ env placeholders, and the user's own provider setup path.
112
+ - **Use hosted auth**: check Platform entitlement, register the site as
113
+ a Platform auth client, and commit the generated site's Platform auth
114
+ client config.
115
+
116
+ Landing can probe Platform staff/session state, but provisioning's
117
+ GitHub OAuth token is still Landing-owned unless a separate token
118
+ handoff design is introduced.
119
+
120
+ The generated site's hosted-auth client code belongs in the starter or
121
+ a starter overlay. Core owns only the auth contract, manifest/runtime
122
+ identity vocabulary (`ctx.user`, `ctx.staff`), and curated Better Auth
123
+ server primitives.
124
+
125
+ ## SDK Surface Rule
126
+
127
+ Mantle should expose Better Auth knobs only as curated first-class
128
+ fields when there is a real Mantle use case. Do not add a generic
129
+ `betterAuthOptions` or `advanced` passthrough.
130
+
131
+ The current first-party SSO use case justifies these optional fields on
132
+ `CreateAuthConfig`:
133
+
134
+ - `trustedOrigins`
135
+ - `crossSubDomainCookies`
136
+ - `cookiePrefix`
137
+
138
+ All three are additive. Existing generated sites that do not pass them
139
+ keep their previous cookie and session behavior.
@@ -0,0 +1,179 @@
1
+ # Cloudflare R2 Media Uploads
2
+
3
+ Mantle supports staff media uploads through Staff MCP tools:
4
+ `create_media_upload` and `commit_media_upload`. This is an optional
5
+ post-launch feature for sites that use the Cloudflare adapter and need
6
+ staff-managed images or files.
7
+
8
+ Do not make R2 part of the Day 1 launch path. Cloudflare R2 setup may
9
+ require billing or a credit card, so landing and the blank starter do not
10
+ provision it by default.
11
+
12
+ ## When To Enable
13
+
14
+ Enable this after the site already has:
15
+
16
+ - a working Cloudflare Worker deploy;
17
+ - staff auth configured;
18
+ - a real need for staff or an agent to upload images/files.
19
+
20
+ Use Claude Code, Codex, Cursor, or another local/non-sandboxed coding
21
+ agent for media maintenance. Claude Cowork often cannot complete this
22
+ flow because the final upload is a direct HTTP PUT to
23
+ `*.r2.cloudflarestorage.com`.
24
+
25
+ ## How It Works
26
+
27
+ The upload flow is deliberately split:
28
+
29
+ 1. The agent calls Staff MCP `create_media_upload` with purpose, variant
30
+ metadata, byte sizes, and mime types.
31
+ 2. Mantle returns signed upload URLs and required headers.
32
+ 3. The agent reads the local/chat attachment bytes and PUTs them directly
33
+ to R2.
34
+ 4. The agent calls `commit_media_upload`.
35
+
36
+ Do not pass image bytes or base64 payloads through MCP tool arguments.
37
+ The Worker validates policy; the agent runtime performs file processing
38
+ and upload.
39
+
40
+ ## Cloudflare Setup
41
+
42
+ Create a bucket and public read URL:
43
+
44
+ ```bash
45
+ wrangler r2 bucket create <project>-media
46
+ wrangler r2 bucket dev-url enable <project>-media
47
+ ```
48
+
49
+ In the Cloudflare dashboard, create an R2 S3 API token:
50
+
51
+ 1. Open **R2**.
52
+ 2. Open **Manage R2 API Tokens**.
53
+ 3. Create an Object Read & Write token.
54
+ 4. Copy the Access Key ID and Secret Access Key.
55
+
56
+ The R2 binding alone cannot issue presigned PUT URLs; Mantle also needs
57
+ these S3-compatible credentials.
58
+
59
+ ## `wrangler.toml`
60
+
61
+ ```toml
62
+ [vars]
63
+ R2_ACCOUNT_ID = "<account-id>"
64
+ MEDIA_PUBLIC_URL_BASE = "https://pub-<hash>.r2.dev"
65
+
66
+ [[r2_buckets]]
67
+ binding = "MEDIA_BUCKET"
68
+ bucket_name = "<project>-media"
69
+ ```
70
+
71
+ Set secrets:
72
+
73
+ ```bash
74
+ wrangler secret put R2_ACCESS_KEY_ID
75
+ wrangler secret put R2_SECRET_ACCESS_KEY
76
+ ```
77
+
78
+ ## `src/mantleConfig.ts`
79
+
80
+ ```ts
81
+ import { R2MediaStorage, type CmsConfig } from "@aotter/mantle/cloudflare";
82
+ import { AwsClient } from "aws4fetch";
83
+
84
+ export interface Env {
85
+ readonly MEDIA_BUCKET?: R2Bucket;
86
+ readonly R2_ACCOUNT_ID?: string;
87
+ readonly R2_ACCESS_KEY_ID?: string;
88
+ readonly R2_SECRET_ACCESS_KEY?: string;
89
+ readonly MEDIA_PUBLIC_URL_BASE?: string;
90
+ }
91
+
92
+ function buildMediaStorage(env: Env): CmsConfig["bindings"]["mediaStorage"] {
93
+ if (
94
+ !env.MEDIA_BUCKET ||
95
+ !env.R2_ACCOUNT_ID ||
96
+ !env.R2_ACCESS_KEY_ID ||
97
+ !env.R2_SECRET_ACCESS_KEY ||
98
+ !env.MEDIA_PUBLIC_URL_BASE
99
+ ) {
100
+ return undefined;
101
+ }
102
+
103
+ const s3 = new AwsClient({
104
+ accessKeyId: env.R2_ACCESS_KEY_ID,
105
+ secretAccessKey: env.R2_SECRET_ACCESS_KEY,
106
+ region: "auto",
107
+ service: "s3",
108
+ });
109
+
110
+ return new R2MediaStorage(
111
+ env.MEDIA_BUCKET,
112
+ s3,
113
+ `https://<project>-media.${env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
114
+ env.MEDIA_PUBLIC_URL_BASE,
115
+ );
116
+ }
117
+ ```
118
+
119
+ Then wire it into the CMS config:
120
+
121
+ ```ts
122
+ siteDefaults: {
123
+ media: {
124
+ purposes: [
125
+ {
126
+ name: "page-image",
127
+ required: ["image/jpeg,image/png", "image/webp", "image/avif"],
128
+ maxBytes: {
129
+ "image/jpeg": 5_000_000,
130
+ "image/png": 5_000_000,
131
+ "image/webp": 3_000_000,
132
+ "image/avif": 2_000_000,
133
+ },
134
+ },
135
+ ],
136
+ },
137
+ },
138
+ bindings: {
139
+ mediaStorage: buildMediaStorage(env),
140
+ },
141
+ ```
142
+
143
+ `required` is slot-based. The example above declares three slots:
144
+
145
+ - slot 0: choose exactly one fallback mime, preserving the source
146
+ semantics (`image/jpeg` for opaque photos, `image/png` when alpha must
147
+ be preserved);
148
+ - slot 1: include a WebP variant;
149
+ - slot 2: include an AVIF variant.
150
+
151
+ This models the ADR-0017 multi-variant `<picture>` shape. If a site wants
152
+ a simpler single-variant upload for a narrow purpose, it may declare one
153
+ slot such as `["image/jpeg,image/png,image/webp,image/gif"]`; in that
154
+ case, the agent chooses exactly one mime from the comma-separated list and
155
+ must not upload one variant per listed mime.
156
+
157
+ ## Tool Visibility
158
+
159
+ The Staff MCP media tools are registered only when both are true:
160
+
161
+ - `bindings.mediaStorage` is set;
162
+ - `siteDefaults.media.purposes` contains at least one purpose.
163
+
164
+ If either side is missing, `create_media_upload` and
165
+ `commit_media_upload` will not appear in `tools/list`.
166
+
167
+ ## Agent Guidance
168
+
169
+ For image maintenance, prefer Claude Code or another coding agent that can
170
+ read local files, process images, and make outbound PUT requests to R2.
171
+ Avoid Claude Cowork for this workflow; use a non-sandboxed agent instead.
172
+
173
+ Preserve source semantics:
174
+
175
+ - photos may use JPEG/WebP variants;
176
+ - transparent logos must keep alpha;
177
+ - animated GIFs must stay animated;
178
+ - do not silently flatten, resize, or recompress user assets without
179
+ asking.
@@ -139,7 +139,7 @@ The audit takes ~30 seconds and rules out the most common fanout failure. Do it
139
139
  `bump-from-sdk.yml` / `bump-from-starters.yml` failed at the validate / typecheck gate because the SDK release introduced a code-shape break? Re-firing the workflow won't help — it'll fail the same gate on the same source. The fix-forward path:
140
140
 
141
141
  1. Branch off `develop` (starters) or `main` (landing): `release/vX.Y.Z`.
142
- 2. Replicate what the bump workflow would have done — bump every `@aotter/mantle*` dep + own `version` in package.json files, refresh lockfile via `pnpm install --no-frozen-lockfile`, update `sources.json.version` (starters only).
142
+ 2. Replicate what the bump workflow would have done — bump every `@aotter/mantle*` dep + own `version` in package.json files, refresh lockfiles via `pnpm install --no-frozen-lockfile`, and rebuild starter provision bundles when starters source changed.
143
143
  3. Add whatever source-code fixes satisfy the new SDK shape.
144
144
  4. Commit subject MUST be `release: bump @aotter/mantle* to vX.Y.Z` (starters) or `release: bump @aotter/mantle to vX.Y.Z` (landing) — `tag-and-dispatch-landing.yml` filters on this in starters; landing has no equivalent filter but the convention keeps history consistent.
145
145
  5. Open PR base=`main` (starters and landing), CI passes now that lockfile + source are in sync, rebase-merge.
@@ -148,11 +148,11 @@ The audit takes ~30 seconds and rules out the most common fanout failure. Do it
148
148
 
149
149
  ### Re-spin release for a downstream-content-only fix
150
150
 
151
- Sometimes the SDK npm artifact is fine but the GitHub release tarball used by `create-mantle` to scaffold starters — is broken (e.g. starter content didn't include a freshly-required field at release time). Per `§ Rollback / yanking policy`, the right path is to publish the next alpha as a no-op SDK bump that re-spins the fanout:
151
+ Sometimes the SDK npm artifact is fine but the generated starter provision bundle is broken (e.g. starter content didn't include a freshly-required field at release time). Per `§ Rollback / yanking policy`, the right path is to publish the next alpha as a no-op SDK bump that re-spins the fanout:
152
152
 
153
153
  1. Cut alpha.N+1 in `mantle/` with empty SDK diff (versions + CHANGELOG only).
154
154
  2. CHANGELOG entry MUST say explicitly: `No SDK code changes. alpha.N+1 re-spins the release fanout to ship starter content that should have been part of alpha.N (see #XXX).`
155
- 3. Tag + push full fanout produces fresh `mantle-starters` tag + GitHub release tarball with the corrected content.
155
+ 3. Tag + push -> full fanout produces fresh `mantle-starters` provision bundles with the corrected content.
156
156
 
157
157
  Don't force-retag the broken alpha. Don't introduce a starter-only sub-tag like `vX.Y.Z-starter.N`. Either breaks the convention that starter version === SDK version.
158
158
 
@@ -173,7 +173,7 @@ release.yml: pnpm install → build → test (gate) → verify package.json
173
173
 
174
174
 
175
175
  mantle-starters/bump-from-sdk.yml: bump @aotter/mantle* deps
176
- + own version + sources.json.version → pnpm install →
176
+ + own version → pnpm install + rebuild bundles
177
177
  validate × 5 starters (gate) → typecheck × 5 (gate) →
178
178
  PR onto main → auto-approve + auto-merge
179
179
 
@@ -271,22 +271,20 @@ stay individually installable for tooling / alt-adapter authors.
271
271
 
272
272
  Do **not** publish starter packages during alpha unless a separate PR
273
273
  explicitly prepares their package allowlists and verifies the tarballs.
274
- Current starter install flow downloads starter source tarballs from
275
- GitHub/template refs, extracts them without preserving the template
276
- repo remote, and uses npm as the runtime dependency source.
274
+ Current starter launch flow is landing-driven: landing fetches generated
275
+ `provision-bundles/<type>.json` artifacts from `aotter/mantle-starters`,
276
+ commits the user's GitHub repo, and uses npm as the runtime dependency
277
+ source.
277
278
 
278
279
  Do **not** publish `@aotter/mantle-netlify` while it is a stub.
279
280
 
280
- `create-mantle` lives in `aotter/mantle-starters`, not here. The
281
- scaffolder couples to starter content (sources.json, merge layout,
282
- placeholder macros) and has zero coupling to SDK runtime, so it ships
283
- from the starters repo as a GitHub release tarball. Releases on this SDK
284
- repo do not attach a create-mantle tarball and must not publish
285
- `@aotter/create-mantle`.
281
+ The retired `create-mantle` scaffolder is not shipped by this SDK repo.
282
+ Releases on this SDK repo must not attach a create-mantle tarball and
283
+ must not publish `@aotter/create-mantle`.
286
284
 
287
- `skills/install/SKILL.md` consumes the command composed by the landing
288
- page. Human-facing direct usage belongs in the `mantle-starters` README,
289
- not this SDK repo.
285
+ `skills/install/SKILL.md` consumes the repo and handoff created by the
286
+ landing page. Human-facing starter bundle details belong in the
287
+ `mantle-starters` README, not this SDK repo.
290
288
 
291
289
  ### Pre-publish checks
292
290
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aotter/mantle",
3
- "version": "0.0.11-alpha.35",
3
+ "version": "0.0.11-alpha.37",
4
4
  "description": "Umbrella entry for @aotter/mantle. Adopters install this one package and import from subpaths: /spec, /runtime, /cloudflare, /admin-ui. Sub-packages remain individually installable on npm for tooling / alt-adapter authors. The Netlify adapter ships as a private workspace stub in v0.1 — its subpath will be added when the impl lands in v0.2.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://mantle.tools/",
@@ -47,15 +47,15 @@
47
47
  "README.md"
48
48
  ],
49
49
  "dependencies": {
50
- "@aotter/mantle-cloudflare": "0.0.11-alpha.35",
51
- "@aotter/mantle-runtime": "0.0.11-alpha.35",
52
- "@aotter/mantle-spec": "0.0.11-alpha.35",
53
- "@aotter/mantle-admin-ui": "0.0.11-alpha.35"
50
+ "@aotter/mantle-admin-ui": "0.0.11-alpha.37",
51
+ "@aotter/mantle-runtime": "0.0.11-alpha.37",
52
+ "@aotter/mantle-spec": "0.0.11-alpha.37",
53
+ "@aotter/mantle-cloudflare": "0.0.11-alpha.37"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@cloudflare/workers-oauth-provider": "^0.7.0",
57
57
  "aws4fetch": "^1.0.20",
58
- "better-auth": "^1.6.0",
58
+ "better-auth": "^1.6.23",
59
59
  "hono": "^4.12.0",
60
60
  "zod": "^4.0.0"
61
61
  },
@@ -63,7 +63,7 @@
63
63
  "@cloudflare/workers-oauth-provider": "^0.7.0",
64
64
  "@types/node": "^25",
65
65
  "aws4fetch": "^1.0.20",
66
- "better-auth": "^1.6.14",
66
+ "better-auth": "^1.6.23",
67
67
  "hono": "^4.12.23",
68
68
  "typescript": "^6.0.3",
69
69
  "zod": "^4.4.2"
package/skills/README.md CHANGED
@@ -4,13 +4,33 @@ Agent-readable skill briefs for consumers of `@aotter/mantle-*`. Discoverable by
4
4
 
5
5
  | Skill | When to invoke |
6
6
  |---|---|
7
+ | [`develop`](develop/SKILL.md) | `mantle:develop`: Core-owned workflow for manifest, runtime, handler, adapter, validation, and MCP work in any Mantle project. |
8
+ | [`plugin`](plugin/SKILL.md) | `mantle:plugin`: Core-owned marketplace workflow for plan-first capability installs across starters and adapters. |
9
+ | [`theme`](theme/SKILL.md) | `mantle:theme`: Core-owned visual workflow. Reads project context but does not depend on starter-owned skill semantics. |
10
+ | [`update`](update/SKILL.md) | `mantle:update`: Core-owned drift check workflow for SDK, starter snapshots, and plugin lockfiles. |
7
11
  | [`install`](install/SKILL.md) | User wants to start or continue a Mantle site. Sites launch on [Mantle landing](https://mantle.tools), which provisions the GitHub repo and first Cloudflare deploy; this brief orients the agent to take over and continue the provisioned repo. |
8
- | [`customize-design`](customize-design/SKILL.md) | User wants to rebrand or restyle a publication starter project. Walks the L1–L4 theme stack (tokens / extraCss+icons+i18n / Header+Footer / whole-template). |
9
- | [`extend`](extend/SKILL.md) | User has an existing project and wants to add a Schema / View / Procedure / Trigger or wire a feature (contact form, search, newsletter signup). |
10
- | [`provision`](provision/SKILL.md) | User wants production fully usable after a landing launch. Verify the Cloudflare deploy, wire per-site staff auth (GitHub OAuth App + Worker secrets), smoke test, and hand off the operator setup URL. |
12
+ | [`customize-design`](customize-design/SKILL.md) | Legacy publication-specific design guide. Prefer `mantle:theme` for generated repos. |
13
+ | [`extend`](extend/SKILL.md) | Legacy atom-authoring guide. Prefer `mantle:develop` or `mantle:plugin` depending on whether the work is one-off or installable. |
14
+ | [`provision`](provision/SKILL.md) | User wants production fully usable after a landing launch. Verify the current landing deploy, wire per-site staff auth, smoke test, and hand off the operator setup URL. |
11
15
 
12
16
  The skills target `mantle@v0.1.0`. Each one names its assumed grammar version in the front-matter `applies_to:` field; future versions add a sibling SKILL.md or update the existing one.
13
17
 
18
+ ## Skill authority
19
+
20
+ The `mantle:*` namespace is owned by `@aotter/mantle`. Starter template
21
+ repos may vendor exact copies for offline/repo-local use, but they must not
22
+ fork the meaning of a `mantle:*` skill. Starter launch files and plugin recipes
23
+ are context that Core skills read, not competing skill contracts.
24
+
25
+ ## Marketplace install
26
+
27
+ The repo is also an agent plugin bundle:
28
+
29
+ - Claude Code: `.claude-plugin/plugin.json` plus `.claude-plugin/marketplace.json`.
30
+ - Codex: `.codex-plugin/plugin.json` plus `.agents/plugins/marketplace.json`.
31
+ - Cursor: `.cursor-plugin/plugin.json`.
32
+ - VS Code + GitHub Copilot: `.copilot-plugin/plugin.json`.
33
+
14
34
  ## Audience
15
35
 
16
36
  These are written for **AI agents acting on behalf of consumers of mantle**, not for agents maintaining the mantle SDK itself. SDK-internal guidance lives in [`/CLAUDE.md`](../CLAUDE.md). Two audiences, two artifacts.
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: mantle:develop
3
+ description: Work on any Mantle project using the Core SDK contract. Use for manifest, runtime, content model, handler, adapter, validation, and MCP work after a project already exists.
4
+ source: "@aotter/mantle"
5
+ sourcePath: skills/develop/SKILL.md
6
+ when_to_invoke: |
7
+ Working dir contains a Mantle project: package.json depends on @aotter/mantle or @aotter/mantle-*, and manifests/ or src/mantleConfig.ts exists.
8
+ applies_to: mantle@v0.1.0
9
+ ---
10
+
11
+ # Mantle Develop
12
+
13
+ This is the Core SDK skill for working inside an existing Mantle project.
14
+ Starter files, launch handoffs, and plugin recipes are context; this skill
15
+ owns the workflow vocabulary.
16
+
17
+ ## First Read
18
+
19
+ 1. `package.json` for the installed `@aotter/mantle*` versions.
20
+ 2. `manifests/` and `src/mantleConfig.ts` for the active atoms and adapter wiring.
21
+ 3. Optional local context: `.mantle/launch-state.json`, `.mantle/handoff.md`,
22
+ `.mantle/plugins.json`, `.mantle/plugins.lock.json`, and `.mantle/recipes/`.
23
+ 4. Installed Core docs in `node_modules/@aotter/mantle/docs/`.
24
+
25
+ If `node_modules/` is missing, run `pnpm install --frozen-lockfile` before
26
+ falling back to remote docs.
27
+
28
+ ## Core Model
29
+
30
+ Mantle exposes exactly four declarative atoms:
31
+
32
+ | Atom | Purpose |
33
+ |---|---|
34
+ | `Schema` | Stored entity/table shape. |
35
+ | `View` | Read/query surface. |
36
+ | `Procedure` | Typed mutation or operation. |
37
+ | `Trigger` | HTTP/lifecycle/MCP invocation binding. |
38
+
39
+ Do not invent manifest kinds such as `Form`, `Feature`, `Workflow`, or
40
+ `Membership`. Compose those from the four atoms plus TypeScript only where
41
+ the atoms cannot express the behavior.
42
+
43
+ ## Adapter Boundary
44
+
45
+ The runtime is adapter-neutral. Required runtime ports are `DatabaseDriver`,
46
+ `KvCache`, and `AssetServer`. Optional feature ports, such as `MediaStorage`
47
+ or `DeferredHookDispatcher`, are enabled only when the current adapter wires
48
+ them.
49
+
50
+ Do not assume Cloudflare unless the project imports `@aotter/mantle/cloudflare`
51
+ or its adapter config is visible. A future Netlify adapter should satisfy the
52
+ same Core workflow through its own ports and provider setup.
53
+
54
+ ## Loop
55
+
56
+ ```bash
57
+ pnpm install --frozen-lockfile
58
+ pnpm validate
59
+ pnpm typecheck
60
+ pnpm check
61
+ ```
62
+
63
+ Use `pnpm dev` for local preview when the project provides it.
64
+
65
+ ## Rules
66
+
67
+ - Prefer manifest YAML for content model changes.
68
+ - Add TypeScript only for handlers, rendering, adapter wiring, or real behavior.
69
+ - Do not write directly to D1, KV, Postgres, or object storage for content
70
+ authoring. Use runtime use cases, admin APIs, or Staff MCP.
71
+ - Do not commit provider secrets.
72
+ - If the work is an installable capability, switch to `mantle:plugin`.
@@ -42,13 +42,13 @@ A landing-provisioned repo carries its launch context and ground truth in:
42
42
  | Path | Contents |
43
43
  |---|---|
44
44
  | `.mantle/launch-state.json` | Non-secret launch choices: type, purpose, locales, repo, owner, suggested overlay |
45
- | `.mantle/features.json` | Selected feature overlays and applied state |
45
+ | `.mantle/features.json` | Starter launch context and applied starter state |
46
46
  | `.mantle/handoff.md` | The launch handoff note written for you |
47
47
  | `manifests/*.yaml` | Schemas / Views / Procedures / Triggers |
48
48
  | `src/mantleConfig.ts` | Site defaults, handler registration, runtime bindings |
49
49
  | `src/handlers/` | Handler implementations |
50
50
  | `AGENTS.md` | Cross-tool agent entry |
51
- | `.agent/skills/` + `.claude/skills/` | Repo-local skills: `mantle:develop`, `mantle:overlay`, `mantle:theme`, `mantle:update` |
51
+ | `.agent/skills/` + `.claude/skills/` | Vendored Core skills from `@aotter/mantle`: `mantle:develop`, `mantle:plugin`, `mantle:theme`, `mantle:update` |
52
52
 
53
53
  ## Take Over a Provisioned Repo
54
54
 
@@ -73,23 +73,37 @@ Surface both verbatim when validation fails.
73
73
 
74
74
  ## Continue With Repo-Local Skills
75
75
 
76
- The generated repo ships its own skills under `.agent/skills/` and
77
- `.claude/skills/`. Hand off to the one that fits the work:
76
+ The generated repo vendors Core-owned skills under `.agent/skills/` and
77
+ `.claude/skills/`. The starter carries local copies for offline/pinned use; it
78
+ does not own a separate skill contract.
78
79
 
79
- - **`mantle:overlay`** — apply the selected launch type as a small
80
- post-launch overlay (first useful pages plus tiny seed data in the
81
- user's language). This is the usual first step on a fresh blank launch.
82
80
  - **`mantle:develop`** — manifest, runtime, content-model, layout,
83
81
  bug-fix, and validation work on the site.
82
+ - **`mantle:plugin`** — install repeatable marketplace capabilities using
83
+ a plan-first Core workflow.
84
84
  - **`mantle:theme`** — brand and visual direction via Mantle / Kiwa tokens
85
85
  and source. There is no premade theme picker; visual direction is agent
86
86
  work after launch.
87
87
  - **`mantle:update`** — compare the repo against its upstream provision
88
- bundle.
88
+ bundle or installed plugin locks.
89
89
 
90
90
  Remove the placeholder blank homepage once the selected type ships a real
91
91
  homepage.
92
92
 
93
+ ## Optional Media Uploads
94
+
95
+ Media uploads are a post-launch opt-in, not part of Day 1 provisioning. Do
96
+ not block the first deploy or first useful page on storage setup.
97
+
98
+ When the current repo uses the Cloudflare adapter and really needs
99
+ staff-managed images or files, follow the Cloudflare R2 recipe:
100
+ <https://raw.githubusercontent.com/aotter/mantle/develop/docs/media-uploads.md>.
101
+
102
+ For media maintenance, prefer Claude Code, Codex, Cursor, or another
103
+ local/non-sandboxed coding agent that can read files and make outbound PUT
104
+ requests. Do not rely on Claude Cowork for R2 uploads; use a
105
+ non-sandboxed agent instead.
106
+
93
107
  ## Local Preview
94
108
 
95
109
  To see the site locally before pushing:
@@ -122,6 +136,8 @@ skill:
122
136
  - Don't scaffold a project from scratch locally; launch happens on landing.
123
137
  - Don't block the first useful page on polishing prose or writing a site letter.
124
138
  - Don't ask for a Cloudflare API token in the base flow.
139
+ - Don't require R2 media setup in the base flow; it is optional
140
+ post-launch work.
125
141
  - Don't commit provider secrets.
126
142
  - Don't use `127.0.0.1` in OAuth callback examples; use `localhost` locally
127
143
  and the real Worker URL in production.
@@ -0,0 +1,120 @@
1
+ ---
2
+ name: mantle:plugin
3
+ description: Discover, plan, apply, and verify Mantle marketplace plugins through the Core SDK contract. Use when the user wants to add an installable capability without hand-planning provisioning steps.
4
+ source: "@aotter/mantle"
5
+ sourcePath: skills/plugin/SKILL.md
6
+ when_to_invoke: |
7
+ The user asks for a marketplace/plugin/capability install, or wants a domain feature that should be repeatable across starters and adapters.
8
+ applies_to: mantle@v0.1.0
9
+ ---
10
+
11
+ # Mantle Plugin
12
+
13
+ Mantle plugins are Core SDK capability packages. They are not starter overlays
14
+ and they are not provider provisioning scripts.
15
+
16
+ A plugin may contribute:
17
+
18
+ - manifests: `Schema`, `View`, `Procedure`, `Trigger`;
19
+ - handler source or handler registration notes;
20
+ - site defaults or media policy additions;
21
+ - expected HTTP, admin, and MCP surfaces;
22
+ - adapter capability requirements and provider setup notes.
23
+
24
+ ## User Install Entry
25
+
26
+ The user-facing install path is:
27
+
28
+ ```txt
29
+ Use repo-local mantle:plugin to install <plugin slug or recipe URL> in this repo.
30
+ Use repo-local mantle:plugin to update <plugin id> in this repo.
31
+ Use repo-local mantle:plugin to remove <plugin id> from this repo.
32
+ ```
33
+
34
+ There is no `mantle plugin add` CLI yet. Do not invent one. Install from a
35
+ marketplace entry, plugin package, or recipe URL that declares enough data for
36
+ an agent to apply the capability deterministically.
37
+
38
+ A valid marketplace entry must include:
39
+
40
+ - plugin id, title, source, and version;
41
+ - supported Mantle version range;
42
+ - files, manifests, handlers, routes, MCP tools, and admin surfaces it adds;
43
+ - adapter capabilities and provider resources it requires;
44
+ - required env vars and secrets, without secret values;
45
+ - verification commands and expected surfaces.
46
+
47
+ If the marketplace page is only marketing copy or lacks an install recipe,
48
+ stop and ask for the recipe instead of guessing.
49
+
50
+ ## First Read
51
+
52
+ 1. `package.json` for Mantle version and adapter package.
53
+ 2. `manifests/` for current atom names and route/tool collisions.
54
+ 3. `src/mantleConfig.ts` for registered handlers, templates, and optional ports.
55
+ 4. `.mantle/plugins.json` and `.mantle/plugins.lock.json` if present.
56
+ 5. `.mantle/launch-state.json` only as context, not as plugin authority.
57
+
58
+ ## Plan First
59
+
60
+ Before applying any plugin, produce a plan:
61
+
62
+ - files to add or change;
63
+ - atoms to add and their names;
64
+ - HTTP routes and MCP tools that will appear;
65
+ - required runtime ports;
66
+ - adapter-specific resources, env vars, and secrets;
67
+ - checks to run.
68
+
69
+ If the plugin needs a capability the current adapter does not expose, stop
70
+ with the missing capability instead of inventing provider steps.
71
+
72
+ ## Apply
73
+
74
+ Apply the smallest deterministic diff. Do not run arbitrary install scripts
75
+ from a plugin package. Copy declared files, wire declared handlers, update the
76
+ plugin ledger, then validate.
77
+
78
+ Suggested ledger paths:
79
+
80
+ ```txt
81
+ .mantle/plugins.json
82
+ .mantle/plugins.lock.json
83
+ ```
84
+
85
+ Keep starter launch state separate from plugin state. `.mantle/features.json`
86
+ is launch/starter context, not the Core plugin ledger.
87
+
88
+ ## Update
89
+
90
+ Compare the installed lock entry against the marketplace entry or recipe URL.
91
+ Apply only the declared version diff, update `.mantle/plugins.lock.json`, then
92
+ run the same verification checks.
93
+
94
+ ## Remove
95
+
96
+ Use the lock entry as the removal manifest. Delete only files and atoms owned
97
+ by that plugin, unwind handler registrations it added, remove its ledger entry,
98
+ then validate. If another plugin or local code depends on a removed atom, stop
99
+ and report the dependency instead of deleting through it.
100
+
101
+ ## Verify
102
+
103
+ ```bash
104
+ pnpm validate
105
+ pnpm typecheck
106
+ ```
107
+
108
+ Then verify the plugin's declared surfaces:
109
+
110
+ - `GET /api/views/<name>` for View reads;
111
+ - HTTP Trigger path for public writes;
112
+ - Staff/Public MCP `tools/list` for MCP Trigger or Schema-derived tools;
113
+ - adapter resource presence when the plugin requires optional ports.
114
+
115
+ ## Don't
116
+
117
+ - Don't treat a starter archetype as a plugin.
118
+ - Don't assume Cloudflare; inspect the active adapter and capability ports.
119
+ - Don't create a second skill namespace for starter-specific plugins.
120
+ - Don't commit secrets. Provider secrets stay in the platform secret store.
@@ -39,6 +39,15 @@ with v2.
39
39
  Provision does not seed production content. First real content is created
40
40
  after owner sign-in through Staff MCP / admin authoring.
41
41
 
42
+ Media uploads are optional post-launch work. Do not require storage or media
43
+ upload credentials to finish first production provisioning. If the current
44
+ repo uses the Cloudflare adapter and the owner asks for staff image/file
45
+ uploads later, follow the Cloudflare R2 recipe:
46
+ <https://raw.githubusercontent.com/aotter/mantle/develop/docs/media-uploads.md>
47
+ and use Claude Code or another local/non-sandboxed coding agent for the
48
+ upload workflow. Do not use Claude Cowork for R2 uploads; use a
49
+ non-sandboxed agent instead.
50
+
42
51
  ## Principles
43
52
 
44
53
  1. Use the user's accounts. The repo and Worker belong to the user's
@@ -149,10 +158,13 @@ repo-local `.agent/skills/` directory (`mantle:develop`, `mantle:overlay`,
149
158
  | GitHub OAuth callback mismatch | OAuth App callback URL is wrong | Set it exactly to `<worker-url>/api/auth/callback/github`. |
150
159
  | Owner signs in but admin / MCP returns 403 | `ADMIN_GITHUB_LOGIN` does not match the signed-in GitHub login | Fix the `ADMIN_GITHUB_LOGIN` value and redeploy. |
151
160
  | Worker boots but sessions fail after a rerun | `BETTER_AUTH_SECRET` changed or was deleted | Restore the old secret if available; otherwise users must sign in again. |
161
+ | `create_media_upload` is missing from Staff MCP | Optional R2 media is not configured, or `media.purposes` is empty | Only fix this if the owner explicitly wants media uploads; follow `docs/media-uploads.md`. |
162
+ | Upload session works but the PUT to R2 fails from Claude Cowork | Cowork sandbox egress blocks direct R2 uploads | Retry from Claude Code / another non-sandboxed agent. |
152
163
 
153
164
  ## Don't
154
165
 
155
166
  - Don't ask for a Cloudflare API token in the base first-run path.
167
+ - Don't require R2 media setup in the base first-run path.
156
168
  - Don't re-create the repo or re-run the first deploy from the agent;
157
169
  landing owns first provisioning.
158
170
  - Don't resurrect `provision:up` / `provision:plan` as a second
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: mantle:theme
3
+ description: Apply brand and visual direction in a Mantle project without depending on starter-owned skill semantics.
4
+ source: "@aotter/mantle"
5
+ sourcePath: skills/theme/SKILL.md
6
+ when_to_invoke: |
7
+ User wants visual, brand, layout, copy, or UI polish in an existing Mantle project.
8
+ applies_to: mantle@v0.1.0
9
+ ---
10
+
11
+ # Mantle Theme
12
+
13
+ Theme work is project-owned source editing. Starters may ship Kiwa files,
14
+ tokens, or recipes, but the skill contract is Core-owned.
15
+
16
+ ## First Read
17
+
18
+ 1. `.mantle/handoff.md` and `.mantle/recipes/` if present.
19
+ 2. `styles/`, `components/`, `src/home.tsx`, `src/theme*`, and `kiwa-ui.json`
20
+ if present.
21
+ 3. `manifests/` to understand which content shape drives the public UI.
22
+
23
+ ## Work
24
+
25
+ - Edit source the repo owns.
26
+ - Use existing tokens, CSS, components, and installed dependencies first.
27
+ - Keep accessibility basics: semantic HTML, focus states, contrast, and
28
+ keyboard reachability.
29
+ - Do not require registry access for a project that already vendors UI source.
30
+ - Add UI dependencies only when existing source cannot cover the requested
31
+ change.
32
+
33
+ ## Check
34
+
35
+ ```bash
36
+ pnpm validate
37
+ pnpm typecheck
38
+ pnpm dev
39
+ ```
40
+
41
+ Visually verify UI changes before calling them done.
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: mantle:update
3
+ description: Check a Mantle project for drift against its Core SDK, starter source, or installed plugin lockfiles.
4
+ source: "@aotter/mantle"
5
+ sourcePath: skills/update/SKILL.md
6
+ when_to_invoke: |
7
+ User asks to update Mantle, compare generated files, refresh starter/vendor output, or inspect marketplace plugin drift.
8
+ applies_to: mantle@v0.1.0
9
+ ---
10
+
11
+ # Mantle Update
12
+
13
+ Use this for drift checks. Do not blindly overwrite user-owned code.
14
+
15
+ ## First Read
16
+
17
+ 1. `package.json` and lockfile for installed `@aotter/mantle*` versions.
18
+ 2. `.mantle/launch-state.json` and `.mantle/features.json` when the project
19
+ came from Mantle landing.
20
+ 3. `.mantle/plugins.json` and `.mantle/plugins.lock.json` when plugins are
21
+ installed.
22
+ 4. Existing project scripts such as `mantle:update`, `validate`, and
23
+ `typecheck`.
24
+
25
+ ## Workflow
26
+
27
+ 1. Start from a clean git worktree.
28
+ 2. Run the project's existing update or compare script if one exists.
29
+ 3. Read the generated report before editing.
30
+ 4. Apply useful differences manually.
31
+ 5. Re-run:
32
+
33
+ ```bash
34
+ pnpm validate
35
+ pnpm typecheck
36
+ ```
37
+
38
+ ## Boundary
39
+
40
+ Starter bundles and plugin packages can provide source snapshots, but Core owns
41
+ the update vocabulary. A stale starter recipe or plugin note is context; it does
42
+ not override the installed Core SDK contract.