@rubytech/create-maxy-code 0.1.400 → 0.1.402
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/package.json +1 -1
- package/payload/platform/config/brand.json +1 -1
- package/payload/platform/plugins/cloudflare/bin/__tests__/cf-token.test.sh +96 -1
- package/payload/platform/plugins/cloudflare/bin/cf-token.sh +24 -8
- package/payload/platform/plugins/cloudflare/references/api.md +3 -1
- package/payload/platform/plugins/web-designer/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/web-designer/PLUGIN.md +10 -1
- package/payload/platform/plugins/web-designer/commands/website.md +61 -0
package/package.json
CHANGED
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
|
|
55
55
|
"plugins": {
|
|
56
56
|
"core": ["admin", "memory", "docs", "cloudflare", "anthropic", "workflows", "work", "scheduling", "email", "contacts", "projects", "prompt-optimiser", "browser"],
|
|
57
|
-
"defaultEnabled": ["business-assistant", "sales"],
|
|
57
|
+
"defaultEnabled": ["business-assistant", "sales", "web-designer"],
|
|
58
58
|
"available": ["connector", "telegram", "deep-research", "whatsapp", "replicate", "linkedin-import", "notion-import", "obsidian-import", "x-import", "slides"],
|
|
59
59
|
"excluded": ["joblogic"]
|
|
60
60
|
},
|
|
@@ -48,7 +48,7 @@ echo "\$@" >> "\$SB/curl.log"
|
|
|
48
48
|
url=""; for a in "\$@"; do case "\$a" in https://*) url="\$a";; esac; done
|
|
49
49
|
case "\$url" in
|
|
50
50
|
*"/tokens/permission_groups"*)
|
|
51
|
-
echo '{"success":true,"result":[{"id":"pg_pages","name":"
|
|
51
|
+
echo '{"success":true,"result":[{"id":"pg_pages","name":"Pages Write"},{"id":"pg_d1","name":"D1 Write"},{"id":"pg_dns","name":"DNS Write"},{"id":"pg_acc1","name":"Access: Apps and Policies Write"},{"id":"pg_acc2","name":"Access: Apps and Policies Write"}]}'
|
|
52
52
|
;;
|
|
53
53
|
*"/tokens/verify"*) echo '{"success":true,"result":{"status":"active"}}';;
|
|
54
54
|
*"/tokens"*) echo '{"success":true,"result":{"id":"tok1","value":"'"$SECRET_VALUE"'"}}';;
|
|
@@ -191,5 +191,100 @@ echo "$ERR" | grep -q "code=1001" && ok "failure code surfaced" || bad "code not
|
|
|
191
191
|
no_leak "real failure"
|
|
192
192
|
teardown
|
|
193
193
|
|
|
194
|
+
# 9. pages exact-name: three custom-pages decoys ordered BEFORE the real group.
|
|
195
|
+
# The old fuzzy /pages.*(write|edit)/ + head -1 picks "Access: Custom Pages Write";
|
|
196
|
+
# exact-name must mint the real Pages Write + D1 Write ids and none of the decoys.
|
|
197
|
+
setup "cfat_master_abc"
|
|
198
|
+
cat > "$BIN/curl" <<'CURL'
|
|
199
|
+
#!/usr/bin/env bash
|
|
200
|
+
SB="$(cd "$(dirname "$0")/.." && pwd)"
|
|
201
|
+
echo "$@" >> "$SB/curl.log"
|
|
202
|
+
url=""; for a in "$@"; do case "$a" in https://*) url="$a";; esac; done
|
|
203
|
+
case "$url" in
|
|
204
|
+
*"/permission_groups"*) echo '{"success":true,"result":[{"id":"pg_x1","name":"Access: Custom Pages Write"},{"id":"pg_x2","name":"Account Custom Pages Write"},{"id":"pg_x3","name":"Custom Pages Write"},{"id":"pg_pages_real","name":"Pages Write"},{"id":"pg_d1_real","name":"D1 Write"}]}';;
|
|
205
|
+
*"/tokens/verify"*) echo '{"success":true,"result":{"status":"active"}}';;
|
|
206
|
+
*"/tokens"*) echo '{"success":true,"result":{"value":"cfat_minted_SECRET_do_not_leak_0123456789"}}';;
|
|
207
|
+
*) echo '{"success":false,"errors":[{"code":9999}]}';;
|
|
208
|
+
esac
|
|
209
|
+
CURL
|
|
210
|
+
chmod +x "$BIN/curl"
|
|
211
|
+
run "pages exact-name skips custom-pages decoys" pages 0
|
|
212
|
+
POSTLINE=$(grep -- "-X POST" "$SANDBOX/curl.log")
|
|
213
|
+
echo "$POSTLINE" | grep -q "pg_pages_real" && ok "minted real Pages Write id" || bad "did not mint real pages id: $POSTLINE"
|
|
214
|
+
echo "$POSTLINE" | grep -q "pg_d1_real" && ok "minted real D1 Write id" || bad "did not mint real d1 id"
|
|
215
|
+
echo "$POSTLINE" | grep -qE "pg_x1|pg_x2|pg_x3" && bad "minted a custom-pages decoy id: $POSTLINE" || ok "no custom-pages decoy id minted"
|
|
216
|
+
no_leak "pages exact-name"
|
|
217
|
+
teardown
|
|
218
|
+
|
|
219
|
+
# 10. dns exact-name on cfat_: skip the "Account DNS Settings Write" / "DNS Firewall
|
|
220
|
+
# Write" decoys, mint the real "DNS Write" id, AND nest the zone resource under
|
|
221
|
+
# the account resource (account-owned masters reject the flat zone.* with 1001).
|
|
222
|
+
setup "cfat_master_abc"
|
|
223
|
+
cat > "$BIN/curl" <<'CURL'
|
|
224
|
+
#!/usr/bin/env bash
|
|
225
|
+
SB="$(cd "$(dirname "$0")/.." && pwd)"
|
|
226
|
+
echo "$@" >> "$SB/curl.log"
|
|
227
|
+
url=""; for a in "$@"; do case "$a" in https://*) url="$a";; esac; done
|
|
228
|
+
case "$url" in
|
|
229
|
+
*"/permission_groups"*) echo '{"success":true,"result":[{"id":"pg_dnsx1","name":"Account DNS Settings Write"},{"id":"pg_dnsx2","name":"DNS Firewall Write"},{"id":"pg_dns_real","name":"DNS Write"}]}';;
|
|
230
|
+
*"/tokens/verify"*) echo '{"success":true,"result":{"status":"active"}}';;
|
|
231
|
+
*"/tokens"*) echo '{"success":true,"result":{"value":"cfat_minted_SECRET_do_not_leak_0123456789"}}';;
|
|
232
|
+
*) echo '{"success":false,"errors":[{"code":9999}]}';;
|
|
233
|
+
esac
|
|
234
|
+
CURL
|
|
235
|
+
chmod +x "$BIN/curl"
|
|
236
|
+
run "dns exact-name cfat nests zone under account" dns 0
|
|
237
|
+
POSTLINE=$(grep -- "-X POST" "$SANDBOX/curl.log")
|
|
238
|
+
echo "$POSTLINE" | grep -q "pg_dns_real" && ok "minted real DNS Write id" || bad "did not mint real dns id: $POSTLINE"
|
|
239
|
+
echo "$POSTLINE" | grep -qE "pg_dnsx1|pg_dnsx2" && bad "minted a dns decoy id: $POSTLINE" || ok "no dns decoy id minted"
|
|
240
|
+
echo "$POSTLINE" | grep -qF '"resources":{"com.cloudflare.api.account.acc123":{"com.cloudflare.api.account.zone.*":"*"}}' && ok "cfat nests zone under account" || bad "zone resource not nested for cfat: $POSTLINE"
|
|
241
|
+
no_leak "dns cfat nested"
|
|
242
|
+
teardown
|
|
243
|
+
|
|
244
|
+
# 11. dns exact-name on cfut_: the flat top-level zone resource is preserved, with
|
|
245
|
+
# no account nesting (user-owned masters accept the flat zone.* shape).
|
|
246
|
+
setup "cfut_master_xyz"
|
|
247
|
+
cat > "$BIN/curl" <<'CURL'
|
|
248
|
+
#!/usr/bin/env bash
|
|
249
|
+
SB="$(cd "$(dirname "$0")/.." && pwd)"
|
|
250
|
+
echo "$@" >> "$SB/curl.log"
|
|
251
|
+
url=""; for a in "$@"; do case "$a" in https://*) url="$a";; esac; done
|
|
252
|
+
case "$url" in
|
|
253
|
+
*"/permission_groups"*) echo '{"success":true,"result":[{"id":"pg_dns_real","name":"DNS Write"}]}';;
|
|
254
|
+
*"/tokens/verify"*) echo '{"success":true,"result":{"status":"active"}}';;
|
|
255
|
+
*"/tokens"*) echo '{"success":true,"result":{"value":"cfut_minted_SECRET_do_not_leak_0123456789"}}';;
|
|
256
|
+
*) echo '{"success":false,"errors":[{"code":9999}]}';;
|
|
257
|
+
esac
|
|
258
|
+
CURL
|
|
259
|
+
chmod +x "$BIN/curl"
|
|
260
|
+
run "dns cfut keeps flat zone resource" dns 0
|
|
261
|
+
POSTLINE=$(grep -- "-X POST" "$SANDBOX/curl.log")
|
|
262
|
+
echo "$POSTLINE" | grep -qF '"resources":{"com.cloudflare.api.account.zone.*":"*"}' && ok "cfut flat zone resource" || bad "cfut resource not flat: $POSTLINE"
|
|
263
|
+
echo "$POSTLINE" | grep -qF 'account.acc123":{' && bad "cfut wrongly nested under account: $POSTLINE" || ok "cfut not nested under account"
|
|
264
|
+
no_leak "dns cfut flat"
|
|
265
|
+
teardown
|
|
266
|
+
|
|
267
|
+
# 12. a Cloudflare group rename surfaces as a NAMED miss, not a silent wrong-group
|
|
268
|
+
# mint: with only decoys present and no exact "DNS Write", the helper dies 1 and
|
|
269
|
+
# names the exact group it looked for.
|
|
270
|
+
setup "cfat_master_abc"
|
|
271
|
+
cat > "$BIN/curl" <<'CURL'
|
|
272
|
+
#!/usr/bin/env bash
|
|
273
|
+
SB="$(cd "$(dirname "$0")/.." && pwd)"
|
|
274
|
+
echo "$@" >> "$SB/curl.log"
|
|
275
|
+
url=""; for a in "$@"; do case "$a" in https://*) url="$a";; esac; done
|
|
276
|
+
case "$url" in
|
|
277
|
+
*"/permission_groups"*) echo '{"success":true,"result":[{"id":"pg_dnsx1","name":"Account DNS Settings Write"},{"id":"pg_dnsx2","name":"DNS Firewall Write"}]}';;
|
|
278
|
+
*"/tokens/verify"*) echo '{"success":true,"result":{"status":"active"}}';;
|
|
279
|
+
*"/tokens"*) echo '{"success":true,"result":{"value":"cfat_minted_SECRET_do_not_leak_0123456789"}}';;
|
|
280
|
+
*) echo '{"success":false,"errors":[{"code":9999}]}';;
|
|
281
|
+
esac
|
|
282
|
+
CURL
|
|
283
|
+
chmod +x "$BIN/curl"
|
|
284
|
+
run "missing exact group fails closed" dns 1
|
|
285
|
+
grep -q -- "-X POST" "$SANDBOX/curl.log" && bad "minted despite no exact group match" || ok "no mint on missing exact group"
|
|
286
|
+
echo "$ERR" | grep -qF "no permission group named 'DNS Write'" && ok "die names the missing exact group" || bad "die message not named: $ERR"
|
|
287
|
+
teardown
|
|
288
|
+
|
|
194
289
|
echo "----"; echo "PASS=$PASS FAIL=$FAIL"
|
|
195
290
|
[ "$FAIL" -eq 0 ]
|
|
@@ -41,13 +41,17 @@ fi
|
|
|
41
41
|
[ -n "$brand" ] || die "cannot derive brand from path; set CF_TOKEN_BRAND"
|
|
42
42
|
|
|
43
43
|
# Scope table - resolved by case, never inferred.
|
|
44
|
-
|
|
44
|
+
# pages/d1/dns select their permission group by EXACT name (a broad substring once
|
|
45
|
+
# picked "Access: Custom Pages Write" over "Pages Write"); access keeps a regex
|
|
46
|
+
# because it legitimately enumerates duplicate-named candidates and functional-probes.
|
|
47
|
+
legacy=""; names=""; regexes=""; zone_scoped=""
|
|
45
48
|
case "$scope" in
|
|
46
49
|
pages|d1) suffix="pages-d1"; key="CF_PAGES_D1_TOKEN"; legacy="CF_PAGES_TOKEN"
|
|
47
|
-
#
|
|
48
|
-
|
|
50
|
+
# exact permission-group names separated by ';'
|
|
51
|
+
names="Pages Write;D1 Write"; res='{"com.cloudflare.api.account.'"$ACC"'":"*"}';;
|
|
49
52
|
dns) suffix="dns"; key="CF_DNS_TOKEN"
|
|
50
|
-
|
|
53
|
+
# zone-scoped: res is set after the master-prefix routing (nesting differs)
|
|
54
|
+
names="DNS Write"; zone_scoped=1;;
|
|
51
55
|
access) suffix="access"; key="CF_ACCESS_TOKEN"
|
|
52
56
|
regexes="access.*apps.*polic.*(write|edit)"; res='{"com.cloudflare.api.account.'"$ACC"'":"*"}';;
|
|
53
57
|
esac
|
|
@@ -74,9 +78,20 @@ case "$CLOUDFLARE_API_TOKEN" in
|
|
|
74
78
|
die "unrecognised master token prefix (not cfat_/cfut_); refusing to guess an endpoint";;
|
|
75
79
|
esac
|
|
76
80
|
|
|
81
|
+
# Zone-scoped resource shape branches by master type. An account-owned (cfat_) master
|
|
82
|
+
# rejects a flat zone.* resource with code 1001 "Must specify a zone for account owned
|
|
83
|
+
# tokens, or nest zone under specific account resource"; nest it under the account.
|
|
84
|
+
# A user-owned (cfut_) master accepts the flat zone.* shape.
|
|
85
|
+
if [ -n "$zone_scoped" ]; then
|
|
86
|
+
case "$mtag" in
|
|
87
|
+
cfat) res='{"com.cloudflare.api.account.'"$ACC"'":{"com.cloudflare.api.account.zone.*":"*"}}';;
|
|
88
|
+
cfut) res='{"com.cloudflare.api.account.zone.*":"*"}';;
|
|
89
|
+
esac
|
|
90
|
+
fi
|
|
91
|
+
|
|
77
92
|
api_get() { curl -sS -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" "$1"; }
|
|
78
93
|
|
|
79
|
-
# Resolve permission-group id(s) by
|
|
94
|
+
# Resolve permission-group id(s): access matches by regex, pages/d1/dns by exact name.
|
|
80
95
|
pg_json="$(api_get "${base}/tokens/permission_groups")"
|
|
81
96
|
echo "$pg_json" | jq -e '.success==true' >/dev/null 2>&1 || {
|
|
82
97
|
code=$(echo "$pg_json" | jq -r '.errors[0].code // "?"')
|
|
@@ -85,6 +100,7 @@ echo "$pg_json" | jq -e '.success==true' >/dev/null 2>&1 || {
|
|
|
85
100
|
}
|
|
86
101
|
|
|
87
102
|
collect_ids() { echo "$pg_json" | jq -r --arg re "$1" '.result[] | select(.name|test($re;"i")) | .id'; }
|
|
103
|
+
collect_exact() { echo "$pg_json" | jq -r --arg n "$1" '.result[] | select(.name==$n) | .id'; }
|
|
88
104
|
|
|
89
105
|
mint_with() { # $1 = json permission_groups array -> echoes minted token value or empty
|
|
90
106
|
local body
|
|
@@ -136,12 +152,12 @@ if [ "$scope" = "access" ]; then
|
|
|
136
152
|
else
|
|
137
153
|
pg_array='[]'
|
|
138
154
|
OLDIFS=$IFS; IFS=';'
|
|
139
|
-
for
|
|
140
|
-
id=$(
|
|
155
|
+
for nm in $names; do
|
|
156
|
+
id=$(collect_exact "$nm" | head -1)
|
|
141
157
|
[ -n "$id" ] || {
|
|
142
158
|
IFS=$OLDIFS
|
|
143
159
|
echo "[cf-token] op=resolve scope=${scope} master=${mtag} action=mint result=error code=0" >&2
|
|
144
|
-
die "no permission group
|
|
160
|
+
die "no permission group named '${nm}'"; }
|
|
145
161
|
pg_array=$(echo "$pg_array" | jq -c --arg id "$id" '. + [{id:$id}]')
|
|
146
162
|
done
|
|
147
163
|
IFS=$OLDIFS
|
|
@@ -112,6 +112,8 @@ The master an operator provisions is usually **account-scoped** (`cfat_…`, cre
|
|
|
112
112
|
|
|
113
113
|
Cross-type calls fail with a stable, recognizable signal: a `cfat_…` token at `/user/tokens/verify` returns `Invalid API Token`; a `cfut_…` token at any `/accounts/{id}/…` endpoint returns `9109`. The diagnostic is the verify call's JSON `errors[].code`.
|
|
114
114
|
|
|
115
|
+
The prefix also governs the **resource shape** of a zone-scoped mint body, not just the endpoint. A `cfut_…` master accepts a flat zone resource — `"resources": { "com.cloudflare.api.account.zone.*": "*" }`. A `cfat_…` (account-owned) master **rejects** that flat shape with code `1001` ("Must specify a zone for account owned tokens, or nest zone under specific account resource"); it requires the zone resource nested under the account resource — `"resources": { "com.cloudflare.api.account.${ACC}": { "com.cloudflare.api.account.zone.*": "*" } }`. Non-zone scopes (Pages/D1/Access) already scope to `com.cloudflare.api.account.${ACC}` and need no branch.
|
|
116
|
+
|
|
115
117
|
**Creating** a token stays an account-level, operator-owned concern — each account documents its own master token and its type (e.g. a per-account `secrets/cloudflare.README.md`). Where an account *can* mint an account-scoped token that is the cleaner default; but a skill that merely *consumes* a supplied token must detect the prefix and adapt, never assume it can mint at `/accounts/…`.
|
|
116
118
|
|
|
117
119
|
---
|
|
@@ -154,7 +156,7 @@ CLOUDFLARE_API_TOKEN="${PAGES_D1}" wrangler pages deploy ./dist --project-name <
|
|
|
154
156
|
|
|
155
157
|
No `expires_on`: the per-scope token is permanent and reused, so decay is the job of the **reconcile pass** (§ Reconcile), not a per-token TTL — the old short-TTL guidance was never honored in practice and produced only never-expiring strays. Look up `<pages-edit-permission-group-id>` (and the ids for DNS Edit, D1 Edit, etc.) once via `GET /accounts/{account_id}/tokens/permission_groups`; they are stable per account. Before reusing or before reconciling, `GET …/tokens` (§ Curated endpoint map) shows what already exists, so a scope is never minted twice under different names.
|
|
156
158
|
|
|
157
|
-
**Per-scope token resources.** Each reused per-scope token carries the resource that matches its operation class. The Pages-and-D1 token (`<brand>-pages-d1`) and the Access token (`<brand>-access`) are account-scoped: `"resources": { "com.cloudflare.api.account.${CLOUDFLARE_ACCOUNT_ID}": "*" }`. The DNS token (`<brand>-dns`) is zone-scoped,
|
|
159
|
+
**Per-scope token resources.** Each reused per-scope token carries the resource that matches its operation class. The Pages-and-D1 token (`<brand>-pages-d1`) and the Access token (`<brand>-access`) are account-scoped: `"resources": { "com.cloudflare.api.account.${CLOUDFLARE_ACCOUNT_ID}": "*" }`. The DNS token (`<brand>-dns`) is zone-scoped, and since it is a single stable token reused across every zone the install routes, its resource covers the account's zones — but the exact shape depends on the master's prefix (see § Token type — route endpoints by prefix): a `cfut_…` master takes the flat `"resources": { "com.cloudflare.api.account.zone.*": "*" }`, while a `cfat_…` master requires the zone nested under the account, `"resources": { "com.cloudflare.api.account.${CLOUDFLARE_ACCOUNT_ID}": { "com.cloudflare.api.account.zone.*": "*" } }` (the flat shape returns `1001` on an account-owned master). A per-zone DNS token would defeat the one-stable-token-per-scope model (one token, reused, not one per zone); the minted token is still strictly narrower than the master, which already carries Zone DNS Edit over the zones in scope.
|
|
158
160
|
|
|
159
161
|
**Redaction is binding.** Every example above pipes the token into a variable or an `Authorization` header and never to stdout. The agent surfaces the operation as verb + target — "minting a Pages-deploy token", "creating CNAME `chat.example.com`" — never the secret. A failure surfaces the API error body with any `Authorization` value masked.
|
|
160
162
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "web-designer",
|
|
3
|
-
"description": "Design and build a self-contained single-page website for any business and put it live on Cloudflare. Guided intake, brand extraction from an existing URL, a domain-neutral builder, social share tiles, and deploy with D1 form capture, gated access, and e-signing. Any install can enable it directly; it is not bundle-gated, not tied to a business category, and adds no background specialist worker, no graph write, and no memory write. Its only host coupling is one credential resolver that reads the Cloudflare token from the account secrets file inside maxy-code or from CLOUDFLARE_API_TOKEN as a standalone plugin. Trigger phrases include design me a website, build a landing page for this business, put this site live on its own domain.",
|
|
3
|
+
"description": "Design and build a self-contained single-page website for any business and put it live on Cloudflare. Guided intake, brand extraction from an existing URL, a domain-neutral builder, social share tiles, and deploy with D1 form capture, gated access, and e-signing. Ships one command, /website, that runs the pipeline end-to-end. Any install can enable it directly; it is not bundle-gated, not tied to a business category, and adds no background specialist worker, no graph write, and no memory write. Its only host coupling is one credential resolver that reads the Cloudflare token from the account secrets file inside maxy-code or from CLOUDFLARE_API_TOKEN as a standalone plugin. Trigger phrases include design me a website, build a landing page for this business, put this site live on its own domain.",
|
|
4
4
|
"version": "0.1.0",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Rubytech LLC"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: web-designer
|
|
3
|
-
description: "Design and build a self-contained single-page website for any business and put it live on Cloudflare. Guided intake, brand extraction from an existing URL, a domain-neutral builder, social share tiles, and deploy with D1 form capture, gated access, and e-signing. Any install can enable it directly; it is not bundle-gated, not tied to a business category, and adds no background specialist worker, no graph write, and no memory write. Its only host coupling is one credential resolver that reads the Cloudflare token from the account secrets file inside maxy-code or from CLOUDFLARE_API_TOKEN as a standalone plugin. Trigger phrases include design me a website, build a landing page for this business, put this site live on its own domain."
|
|
3
|
+
description: "Design and build a self-contained single-page website for any business and put it live on Cloudflare. Guided intake, brand extraction from an existing URL, a domain-neutral builder, social share tiles, and deploy with D1 form capture, gated access, and e-signing. Ships one command, /website, that runs the pipeline end-to-end. Any install can enable it directly; it is not bundle-gated, not tied to a business category, and adds no background specialist worker, no graph write, and no memory write. Its only host coupling is one credential resolver that reads the Cloudflare token from the account secrets file inside maxy-code or from CLOUDFLARE_API_TOKEN as a standalone plugin. Trigger phrases include design me a website, build a landing page for this business, put this site live on its own domain."
|
|
4
4
|
tools: []
|
|
5
5
|
skills:
|
|
6
6
|
- skills/web-intake/SKILL.md
|
|
@@ -24,6 +24,15 @@ Four skills, run in order or on their own:
|
|
|
24
24
|
3. **`web-design`** — builds the self-contained single-page site tree from the brand pack, the operator's copy, and their photos, by substituting a domain-neutral template. Its structured-data type defaults to `LocalBusiness` and is operator-supplied; no business category is baked in.
|
|
25
25
|
4. **`socials`** — renders Open Graph, square, and portrait share tiles plus per-platform captions for the built site, and wires the site's `og:image`.
|
|
26
26
|
|
|
27
|
+
## Command entry point
|
|
28
|
+
|
|
29
|
+
The plugin ships one slash command, `/website` (`commands/website.md`), a thin
|
|
30
|
+
launcher over `web-intake` for the standalone and marketplace editions, where a
|
|
31
|
+
`/`-invokable entry is the idiomatic surface. It carries no account assumptions and
|
|
32
|
+
defers token and root resolution to `bin/resolve-cf.sh`, so the same command works
|
|
33
|
+
in every edition. Inside maxy-code the skill triggers remain the primary surface and
|
|
34
|
+
the command is an equivalent entry point.
|
|
35
|
+
|
|
27
36
|
## The resolver seam
|
|
28
37
|
|
|
29
38
|
The plugin's only host coupling is `bin/resolve-cf.sh`. It resolves two things and prints them so the rest of the plugin is blind to which edition it runs in, and it never prints a token value:
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Design and build a self-contained single-page website for a business and put it live on Cloudflare. Runs the web-intake pipeline end-to-end (guided intake, brand extraction from a URL, a domain-neutral build, social share tiles, and deploy).
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /website
|
|
6
|
+
|
|
7
|
+
Turn photos, an existing website URL, and a few notes into a finished single-page
|
|
8
|
+
site plus share-ready social tiles, then put it live.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
- `/website`, then describe the business in the follow-up
|
|
13
|
+
- `/website "a landing page for Ridgeway Joinery, brand from ridgewayjoinery.co.uk, photos in ./photos"`
|
|
14
|
+
- `/website "one-page site and social tiles for this cafe, contact form, put it live on its own domain"`
|
|
15
|
+
|
|
16
|
+
## What the command does
|
|
17
|
+
|
|
18
|
+
Runs the `web-intake` skill end-to-end. `web-intake` is the single entry
|
|
19
|
+
orchestrator; this command hands it whatever the message and attachments already
|
|
20
|
+
provide and lets it ask for the rest.
|
|
21
|
+
|
|
22
|
+
1. Set the objective first: what the site is for, what a visitor should do, which
|
|
23
|
+
CTAs and forms, and whether anything needs gated access or signing.
|
|
24
|
+
2. Run `brand-extract` against the website URL for the palette, logo, icon, and the
|
|
25
|
+
company facts it can read (design tokens supplied directly are used verbatim).
|
|
26
|
+
3. Curate the supplied photos into a hero rotator and grids.
|
|
27
|
+
4. Fill every remaining gap by asking the operator. Never invent a fact.
|
|
28
|
+
5. Run `web-design` to build `site/index.html` + `site/llms.txt` with no
|
|
29
|
+
`{{ placeholder }}` left unfilled.
|
|
30
|
+
6. Run `socials` to render Open Graph, square, and portrait tiles into
|
|
31
|
+
`site/socials/` and wire the site's `og:image`.
|
|
32
|
+
7. Hand off to deploy when the operator wants the site live.
|
|
33
|
+
|
|
34
|
+
## The core rule: ask, never invent
|
|
35
|
+
|
|
36
|
+
Every placeholder is a fact about a real business. A fabricated phone number, a
|
|
37
|
+
guessed founding year, an invented testimonial, or a made-up company number is a
|
|
38
|
+
defect. Anything the sources do not supply is a question for the operator, not a
|
|
39
|
+
plausible value to synthesise.
|
|
40
|
+
|
|
41
|
+
## Editions
|
|
42
|
+
|
|
43
|
+
The command carries no account assumptions. Where the Cloudflare token and the
|
|
44
|
+
site-tree root come from is resolved by `bin/resolve-cf.sh`:
|
|
45
|
+
|
|
46
|
+
- **Standalone / marketplace:** the token is read from `CLOUDFLARE_API_TOKEN` in the
|
|
47
|
+
environment, and the site tree lands under `$WEB_DESIGNER_SITE_ROOT` (or `./pages`).
|
|
48
|
+
Deploy follows the generic `wrangler pages deploy` sequence. E-signing and
|
|
49
|
+
browser-rendered social tiles are host dependencies, not self-contained in this
|
|
50
|
+
edition.
|
|
51
|
+
- **Inside maxy-code:** the same command resolves the account's Cloudflare token and
|
|
52
|
+
account directory automatically and hands the built tree to `cloudflare:site-deploy`
|
|
53
|
+
unchanged. The skill triggers ("design me a website") remain the primary surface
|
|
54
|
+
there; this command is an equivalent entry point.
|
|
55
|
+
|
|
56
|
+
## What you get back
|
|
57
|
+
|
|
58
|
+
A self-contained `site/` directory: a scrollable single-page site, an `llms.txt`, a
|
|
59
|
+
`socials/` folder of tiles at exact dimensions (1200x628 / 1200x1200 / 1080x1350)
|
|
60
|
+
with a gallery and per-platform captions, and, when the operator asks, a live
|
|
61
|
+
Cloudflare deployment with any wired form buckets.
|