@awesomate/hosting-mcp 0.62.0 → 0.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -28
- package/dist/index.js +126 -84
- package/package.json +1 -1
- package/skill/CHANGELOG.json +12 -0
- package/skill/awesomate-app-builder/SKILL.md +15 -0
- package/skill/awesomate-app-builder/references/design-system.md +69 -0
- package/skill/awesomate-email/SKILL.md +5 -0
- package/skill/awesomate-hosting/SKILL.md +15 -0
- package/skill/awesomate-portals/SKILL.md +15 -0
package/README.md
CHANGED
|
@@ -3,46 +3,33 @@
|
|
|
3
3
|
The MCP server that connects an Awesomate customer's own Claude Code to their
|
|
4
4
|
Awesomate account: WordPress hosting, n8n automations, apps, and Knowledge
|
|
5
5
|
Base. It authenticates to the Awesomate hub with a scoped Personal Access
|
|
6
|
-
Token and ships the
|
|
6
|
+
Token and ships the `awesomate-*` skills that teach Claude how to use it.
|
|
7
|
+
|
|
8
|
+
**Docs: https://hub.awesomate.ai/docs/mcp/** (a quickstart, how it works, a guide
|
|
9
|
+
per area, and a reference generated from this server's own `tools/list`). Its
|
|
10
|
+
sister site documents the SDK: https://hub.awesomate.ai/docs/sdk/.
|
|
7
11
|
|
|
8
12
|
## Install (customers)
|
|
9
13
|
|
|
10
14
|
Don't install this by hand. Open **hub.awesomate.ai/claude** (or the Sites
|
|
11
|
-
page) and click **Connect Claude Code
|
|
15
|
+
page) and click **Connect Claude Code**. It gives you a one-time command that:
|
|
12
16
|
|
|
13
17
|
1. redeems a short-lived setup code for a scoped access token, born on your
|
|
14
|
-
machine (`~/.awesomate/credentials.json`, chmod 600
|
|
18
|
+
machine (`~/.awesomate/credentials.json`, chmod 600; it never traverses
|
|
15
19
|
the clipboard),
|
|
16
20
|
2. installs the `awesomate-*` skills into `~/.claude/skills`,
|
|
17
21
|
3. registers this server in Claude Code (user scope, no token in the
|
|
18
|
-
registration
|
|
22
|
+
registration: see below).
|
|
19
23
|
|
|
20
24
|
Then restart Claude Code and run `awesomate_whoami`.
|
|
21
25
|
|
|
22
26
|
## What Claude can do with it
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- **Hosting & WordPress** — sites, domains, DNS checks, uptime, plan limits,
|
|
29
|
-
snapshots/rollback, `.dev` staging, allowlisted WP-CLI, structured post/page
|
|
30
|
-
writes (drafts first) and media import. Writes need Support Plus+.
|
|
31
|
-
- **n8n automations** — full read surface on every plan (workflows, executions
|
|
32
|
-
with structural error decode, instance-wide error clusters, KPIs, storage
|
|
33
|
-
footprint, node docs); validate → draft → test → promote build loop on
|
|
34
|
-
Support Plus+; AI error-analyzer findings on Pro/Embedded.
|
|
35
|
-
- **Apps** — scaffold, create, deploy-via-git, env vars, health, app Postgres.
|
|
36
|
-
- **Knowledge Base** (every plan; media/OCR Pro+, public widget Support Plus+) — provision, ingest sources, faceted search,
|
|
37
|
-
cited answers, agents, people/entities, business-data queries.
|
|
38
|
-
- **Account** — context + attention digest (notifications, erroring workflows,
|
|
39
|
-
token expiry), plan features, upgrade nudges, support tickets, done-for-you
|
|
40
|
-
build requests (two-step credit confirmation; never spends without an
|
|
41
|
-
explicit yes).
|
|
42
|
-
|
|
28
|
+
Every tool, with its inputs and whether it reads, changes or can delete, is in
|
|
29
|
+
the generated reference: https://hub.awesomate.ai/docs/mcp/reference/account/.
|
|
30
|
+
The docs build fails if a tool is missing from it, so it cannot fall behind.
|
|
43
31
|
Every response is stamped `account: <slug>` so a session talking to the wrong
|
|
44
|
-
account is impossible to miss.
|
|
45
|
-
ones carry `destructiveHint` and confirm-before-acting instructions.
|
|
32
|
+
account is impossible to miss.
|
|
46
33
|
|
|
47
34
|
## Security posture
|
|
48
35
|
|
|
@@ -60,13 +47,13 @@ ones carry `destructiveHint` and confirm-before-acting instructions.
|
|
|
60
47
|
|
|
61
48
|
The server self-updates: it's registered as unpinned `npx -y`, so every Claude
|
|
62
49
|
Code restart runs the latest published version. The skill files update via the
|
|
63
|
-
`awesomate_skill_update` tool
|
|
50
|
+
`awesomate_skill_update` tool. Claude notices the drift itself (it's stamped
|
|
64
51
|
on every response) and offers the update with what's-new lines from
|
|
65
52
|
`skill/CHANGELOG.json`, then hands over the exact restart step.
|
|
66
53
|
|
|
67
54
|
## Manual registration (developers)
|
|
68
55
|
|
|
69
|
-
Keep `--package=` with the
|
|
56
|
+
Keep `--package=` with the `=`: the claude CLI's global-argv pre-scan chokes
|
|
70
57
|
on a bare `-p`, and the package must be explicit because this package ships
|
|
71
58
|
two bins:
|
|
72
59
|
|
|
@@ -78,8 +65,13 @@ claude mcp add awesomate-hosting -- npx -y --package=@awesomate/hosting-mcp awes
|
|
|
78
65
|
|
|
79
66
|
```bash
|
|
80
67
|
npm run typecheck
|
|
81
|
-
npm test
|
|
68
|
+
npm test # builds, then boot-smoke + resolution + skills + knowledge suites
|
|
69
|
+
node scripts/build-docs.mjs --check # the docs checks (also in CI and the hub's web build)
|
|
82
70
|
```
|
|
83
71
|
|
|
72
|
+
A change to `src/` or `skill/` needs a new version and a `skill/CHANGELOG.json`
|
|
73
|
+
entry at the top, and a new tool needs a place in `docs/reference.json` and,
|
|
74
|
+
where it changes what someone can ask for, a line in the right guide under `docs/`.
|
|
75
|
+
|
|
84
76
|
Publishing is CI-only: any `mcp/**` change merged to main with a bumped
|
|
85
77
|
version publishes via `publish-mcp.yml`. Never `npm publish` by hand.
|
package/dist/index.js
CHANGED
|
@@ -2987,7 +2987,7 @@ var require_compile = __commonJS({
|
|
|
2987
2987
|
const schOrFunc = root.refs[ref];
|
|
2988
2988
|
if (schOrFunc)
|
|
2989
2989
|
return schOrFunc;
|
|
2990
|
-
let _sch =
|
|
2990
|
+
let _sch = resolve3.call(this, root, ref);
|
|
2991
2991
|
if (_sch === void 0) {
|
|
2992
2992
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
2993
2993
|
const { schemaId } = this.opts;
|
|
@@ -3014,7 +3014,7 @@ var require_compile = __commonJS({
|
|
|
3014
3014
|
function sameSchemaEnv(s1, s2) {
|
|
3015
3015
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3016
3016
|
}
|
|
3017
|
-
function
|
|
3017
|
+
function resolve3(root, ref) {
|
|
3018
3018
|
let sch;
|
|
3019
3019
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3020
3020
|
ref = sch;
|
|
@@ -3645,55 +3645,55 @@ var require_fast_uri = __commonJS({
|
|
|
3645
3645
|
}
|
|
3646
3646
|
return uri;
|
|
3647
3647
|
}
|
|
3648
|
-
function
|
|
3648
|
+
function resolve3(baseURI, relativeURI, options) {
|
|
3649
3649
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3650
3650
|
const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
3651
3651
|
schemelessOptions.skipEscape = true;
|
|
3652
3652
|
return serialize(resolved, schemelessOptions);
|
|
3653
3653
|
}
|
|
3654
|
-
function resolveComponent(base,
|
|
3654
|
+
function resolveComponent(base, relative3, options, skipNormalization) {
|
|
3655
3655
|
const target = {};
|
|
3656
3656
|
if (!skipNormalization) {
|
|
3657
3657
|
base = parse3(serialize(base, options), options);
|
|
3658
|
-
|
|
3658
|
+
relative3 = parse3(serialize(relative3, options), options);
|
|
3659
3659
|
}
|
|
3660
3660
|
options = options || {};
|
|
3661
|
-
if (!options.tolerant &&
|
|
3662
|
-
target.scheme =
|
|
3663
|
-
target.userinfo =
|
|
3664
|
-
target.host =
|
|
3665
|
-
target.port =
|
|
3666
|
-
target.path = removeDotSegments(
|
|
3667
|
-
target.query =
|
|
3661
|
+
if (!options.tolerant && relative3.scheme) {
|
|
3662
|
+
target.scheme = relative3.scheme;
|
|
3663
|
+
target.userinfo = relative3.userinfo;
|
|
3664
|
+
target.host = relative3.host;
|
|
3665
|
+
target.port = relative3.port;
|
|
3666
|
+
target.path = removeDotSegments(relative3.path || "");
|
|
3667
|
+
target.query = relative3.query;
|
|
3668
3668
|
} else {
|
|
3669
|
-
if (
|
|
3670
|
-
target.userinfo =
|
|
3671
|
-
target.host =
|
|
3672
|
-
target.port =
|
|
3673
|
-
target.path = removeDotSegments(
|
|
3674
|
-
target.query =
|
|
3669
|
+
if (relative3.userinfo !== void 0 || relative3.host !== void 0 || relative3.port !== void 0) {
|
|
3670
|
+
target.userinfo = relative3.userinfo;
|
|
3671
|
+
target.host = relative3.host;
|
|
3672
|
+
target.port = relative3.port;
|
|
3673
|
+
target.path = removeDotSegments(relative3.path || "");
|
|
3674
|
+
target.query = relative3.query;
|
|
3675
3675
|
} else {
|
|
3676
|
-
if (!
|
|
3676
|
+
if (!relative3.path) {
|
|
3677
3677
|
target.path = base.path;
|
|
3678
|
-
if (
|
|
3679
|
-
target.query =
|
|
3678
|
+
if (relative3.query !== void 0) {
|
|
3679
|
+
target.query = relative3.query;
|
|
3680
3680
|
} else {
|
|
3681
3681
|
target.query = base.query;
|
|
3682
3682
|
}
|
|
3683
3683
|
} else {
|
|
3684
|
-
if (
|
|
3685
|
-
target.path = removeDotSegments(
|
|
3684
|
+
if (relative3.path[0] === "/") {
|
|
3685
|
+
target.path = removeDotSegments(relative3.path);
|
|
3686
3686
|
} else {
|
|
3687
3687
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
3688
|
-
target.path = "/" +
|
|
3688
|
+
target.path = "/" + relative3.path;
|
|
3689
3689
|
} else if (!base.path) {
|
|
3690
|
-
target.path =
|
|
3690
|
+
target.path = relative3.path;
|
|
3691
3691
|
} else {
|
|
3692
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
3692
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative3.path;
|
|
3693
3693
|
}
|
|
3694
3694
|
target.path = removeDotSegments(target.path);
|
|
3695
3695
|
}
|
|
3696
|
-
target.query =
|
|
3696
|
+
target.query = relative3.query;
|
|
3697
3697
|
}
|
|
3698
3698
|
target.userinfo = base.userinfo;
|
|
3699
3699
|
target.host = base.host;
|
|
@@ -3701,7 +3701,7 @@ var require_fast_uri = __commonJS({
|
|
|
3701
3701
|
}
|
|
3702
3702
|
target.scheme = base.scheme;
|
|
3703
3703
|
}
|
|
3704
|
-
target.fragment =
|
|
3704
|
+
target.fragment = relative3.fragment;
|
|
3705
3705
|
return target;
|
|
3706
3706
|
}
|
|
3707
3707
|
function equal(uriA, uriB, options) {
|
|
@@ -3909,7 +3909,7 @@ var require_fast_uri = __commonJS({
|
|
|
3909
3909
|
var fastUri = {
|
|
3910
3910
|
SCHEMES,
|
|
3911
3911
|
normalize,
|
|
3912
|
-
resolve:
|
|
3912
|
+
resolve: resolve3,
|
|
3913
3913
|
resolveComponent,
|
|
3914
3914
|
equal,
|
|
3915
3915
|
serialize,
|
|
@@ -8722,9 +8722,9 @@ var require_dispatcher_base = __commonJS({
|
|
|
8722
8722
|
}
|
|
8723
8723
|
close(callback) {
|
|
8724
8724
|
if (callback === void 0) {
|
|
8725
|
-
return new Promise((
|
|
8725
|
+
return new Promise((resolve3, reject) => {
|
|
8726
8726
|
this.close((err, data) => {
|
|
8727
|
-
return err ? reject(err) :
|
|
8727
|
+
return err ? reject(err) : resolve3(data);
|
|
8728
8728
|
});
|
|
8729
8729
|
});
|
|
8730
8730
|
}
|
|
@@ -8762,12 +8762,12 @@ var require_dispatcher_base = __commonJS({
|
|
|
8762
8762
|
err = null;
|
|
8763
8763
|
}
|
|
8764
8764
|
if (callback === void 0) {
|
|
8765
|
-
return new Promise((
|
|
8765
|
+
return new Promise((resolve3, reject) => {
|
|
8766
8766
|
this.destroy(err, (err2, data) => {
|
|
8767
8767
|
return err2 ? (
|
|
8768
8768
|
/* istanbul ignore next: should never error */
|
|
8769
8769
|
reject(err2)
|
|
8770
|
-
) :
|
|
8770
|
+
) : resolve3(data);
|
|
8771
8771
|
});
|
|
8772
8772
|
});
|
|
8773
8773
|
}
|
|
@@ -11034,8 +11034,8 @@ var require_util3 = __commonJS({
|
|
|
11034
11034
|
function createDeferredPromise() {
|
|
11035
11035
|
let res;
|
|
11036
11036
|
let rej;
|
|
11037
|
-
const promise = new Promise((
|
|
11038
|
-
res =
|
|
11037
|
+
const promise = new Promise((resolve3, reject) => {
|
|
11038
|
+
res = resolve3;
|
|
11039
11039
|
rej = reject;
|
|
11040
11040
|
});
|
|
11041
11041
|
return { promise, resolve: res, reject: rej };
|
|
@@ -13281,12 +13281,12 @@ upgrade: ${upgrade}\r
|
|
|
13281
13281
|
cb();
|
|
13282
13282
|
}
|
|
13283
13283
|
}
|
|
13284
|
-
const waitForDrain = () => new Promise((
|
|
13284
|
+
const waitForDrain = () => new Promise((resolve3, reject) => {
|
|
13285
13285
|
assert2(callback === null);
|
|
13286
13286
|
if (socket[kError]) {
|
|
13287
13287
|
reject(socket[kError]);
|
|
13288
13288
|
} else {
|
|
13289
|
-
callback =
|
|
13289
|
+
callback = resolve3;
|
|
13290
13290
|
}
|
|
13291
13291
|
});
|
|
13292
13292
|
socket.on("close", onDrain).on("drain", onDrain);
|
|
@@ -13923,12 +13923,12 @@ var require_client_h2 = __commonJS({
|
|
|
13923
13923
|
cb();
|
|
13924
13924
|
}
|
|
13925
13925
|
}
|
|
13926
|
-
const waitForDrain = () => new Promise((
|
|
13926
|
+
const waitForDrain = () => new Promise((resolve3, reject) => {
|
|
13927
13927
|
assert2(callback === null);
|
|
13928
13928
|
if (socket[kError]) {
|
|
13929
13929
|
reject(socket[kError]);
|
|
13930
13930
|
} else {
|
|
13931
|
-
callback =
|
|
13931
|
+
callback = resolve3;
|
|
13932
13932
|
}
|
|
13933
13933
|
});
|
|
13934
13934
|
h2stream.on("close", onDrain).on("drain", onDrain);
|
|
@@ -14406,16 +14406,16 @@ var require_client = __commonJS({
|
|
|
14406
14406
|
return this[kNeedDrain] < 2;
|
|
14407
14407
|
}
|
|
14408
14408
|
async [kClose]() {
|
|
14409
|
-
return new Promise((
|
|
14409
|
+
return new Promise((resolve3) => {
|
|
14410
14410
|
if (this[kSize]) {
|
|
14411
|
-
this[kClosedResolve] =
|
|
14411
|
+
this[kClosedResolve] = resolve3;
|
|
14412
14412
|
} else {
|
|
14413
|
-
|
|
14413
|
+
resolve3(null);
|
|
14414
14414
|
}
|
|
14415
14415
|
});
|
|
14416
14416
|
}
|
|
14417
14417
|
async [kDestroy](err) {
|
|
14418
|
-
return new Promise((
|
|
14418
|
+
return new Promise((resolve3) => {
|
|
14419
14419
|
const requests = this[kQueue].splice(this[kPendingIdx]);
|
|
14420
14420
|
for (let i = 0; i < requests.length; i++) {
|
|
14421
14421
|
const request = requests[i];
|
|
@@ -14426,7 +14426,7 @@ var require_client = __commonJS({
|
|
|
14426
14426
|
this[kClosedResolve]();
|
|
14427
14427
|
this[kClosedResolve] = null;
|
|
14428
14428
|
}
|
|
14429
|
-
|
|
14429
|
+
resolve3(null);
|
|
14430
14430
|
};
|
|
14431
14431
|
if (this[kHTTPContext]) {
|
|
14432
14432
|
this[kHTTPContext].destroy(err, callback);
|
|
@@ -14477,7 +14477,7 @@ var require_client = __commonJS({
|
|
|
14477
14477
|
});
|
|
14478
14478
|
}
|
|
14479
14479
|
try {
|
|
14480
|
-
const socket = await new Promise((
|
|
14480
|
+
const socket = await new Promise((resolve3, reject) => {
|
|
14481
14481
|
client[kConnector]({
|
|
14482
14482
|
host,
|
|
14483
14483
|
hostname: hostname2,
|
|
@@ -14489,7 +14489,7 @@ var require_client = __commonJS({
|
|
|
14489
14489
|
if (err) {
|
|
14490
14490
|
reject(err);
|
|
14491
14491
|
} else {
|
|
14492
|
-
|
|
14492
|
+
resolve3(socket2);
|
|
14493
14493
|
}
|
|
14494
14494
|
});
|
|
14495
14495
|
});
|
|
@@ -14825,8 +14825,8 @@ var require_pool_base = __commonJS({
|
|
|
14825
14825
|
if (this[kQueue].isEmpty()) {
|
|
14826
14826
|
await Promise.all(this[kClients].map((c) => c.close()));
|
|
14827
14827
|
} else {
|
|
14828
|
-
await new Promise((
|
|
14829
|
-
this[kClosedResolve] =
|
|
14828
|
+
await new Promise((resolve3) => {
|
|
14829
|
+
this[kClosedResolve] = resolve3;
|
|
14830
14830
|
});
|
|
14831
14831
|
}
|
|
14832
14832
|
}
|
|
@@ -16069,7 +16069,7 @@ var require_readable = __commonJS({
|
|
|
16069
16069
|
if (this._readableState.closeEmitted) {
|
|
16070
16070
|
return null;
|
|
16071
16071
|
}
|
|
16072
|
-
return await new Promise((
|
|
16072
|
+
return await new Promise((resolve3, reject) => {
|
|
16073
16073
|
if (this[kContentLength] > limit) {
|
|
16074
16074
|
this.destroy(new AbortError());
|
|
16075
16075
|
}
|
|
@@ -16082,7 +16082,7 @@ var require_readable = __commonJS({
|
|
|
16082
16082
|
if (signal?.aborted) {
|
|
16083
16083
|
reject(signal.reason ?? new AbortError());
|
|
16084
16084
|
} else {
|
|
16085
|
-
|
|
16085
|
+
resolve3(null);
|
|
16086
16086
|
}
|
|
16087
16087
|
}).on("error", noop).on("data", function(chunk) {
|
|
16088
16088
|
limit -= chunk.length;
|
|
@@ -16101,7 +16101,7 @@ var require_readable = __commonJS({
|
|
|
16101
16101
|
}
|
|
16102
16102
|
async function consume(stream, type) {
|
|
16103
16103
|
assert2(!stream[kConsume]);
|
|
16104
|
-
return new Promise((
|
|
16104
|
+
return new Promise((resolve3, reject) => {
|
|
16105
16105
|
if (isUnusable(stream)) {
|
|
16106
16106
|
const rState = stream._readableState;
|
|
16107
16107
|
if (rState.destroyed && rState.closeEmitted === false) {
|
|
@@ -16118,7 +16118,7 @@ var require_readable = __commonJS({
|
|
|
16118
16118
|
stream[kConsume] = {
|
|
16119
16119
|
type,
|
|
16120
16120
|
stream,
|
|
16121
|
-
resolve:
|
|
16121
|
+
resolve: resolve3,
|
|
16122
16122
|
reject,
|
|
16123
16123
|
length: 0,
|
|
16124
16124
|
body: []
|
|
@@ -16188,18 +16188,18 @@ var require_readable = __commonJS({
|
|
|
16188
16188
|
return buffer;
|
|
16189
16189
|
}
|
|
16190
16190
|
function consumeEnd(consume2) {
|
|
16191
|
-
const { type, body, resolve:
|
|
16191
|
+
const { type, body, resolve: resolve3, stream, length } = consume2;
|
|
16192
16192
|
try {
|
|
16193
16193
|
if (type === "text") {
|
|
16194
|
-
|
|
16194
|
+
resolve3(chunksDecode(body, length));
|
|
16195
16195
|
} else if (type === "json") {
|
|
16196
|
-
|
|
16196
|
+
resolve3(JSON.parse(chunksDecode(body, length)));
|
|
16197
16197
|
} else if (type === "arrayBuffer") {
|
|
16198
|
-
|
|
16198
|
+
resolve3(chunksConcat(body, length).buffer);
|
|
16199
16199
|
} else if (type === "blob") {
|
|
16200
|
-
|
|
16200
|
+
resolve3(new Blob(body, { type: stream[kContentType] }));
|
|
16201
16201
|
} else if (type === "bytes") {
|
|
16202
|
-
|
|
16202
|
+
resolve3(chunksConcat(body, length));
|
|
16203
16203
|
}
|
|
16204
16204
|
consumeFinish(consume2);
|
|
16205
16205
|
} catch (err) {
|
|
@@ -16456,9 +16456,9 @@ var require_api_request = __commonJS({
|
|
|
16456
16456
|
};
|
|
16457
16457
|
function request(opts, callback) {
|
|
16458
16458
|
if (callback === void 0) {
|
|
16459
|
-
return new Promise((
|
|
16459
|
+
return new Promise((resolve3, reject) => {
|
|
16460
16460
|
request.call(this, opts, (err, data) => {
|
|
16461
|
-
return err ? reject(err) :
|
|
16461
|
+
return err ? reject(err) : resolve3(data);
|
|
16462
16462
|
});
|
|
16463
16463
|
});
|
|
16464
16464
|
}
|
|
@@ -16681,9 +16681,9 @@ var require_api_stream = __commonJS({
|
|
|
16681
16681
|
};
|
|
16682
16682
|
function stream(opts, factory, callback) {
|
|
16683
16683
|
if (callback === void 0) {
|
|
16684
|
-
return new Promise((
|
|
16684
|
+
return new Promise((resolve3, reject) => {
|
|
16685
16685
|
stream.call(this, opts, factory, (err, data) => {
|
|
16686
|
-
return err ? reject(err) :
|
|
16686
|
+
return err ? reject(err) : resolve3(data);
|
|
16687
16687
|
});
|
|
16688
16688
|
});
|
|
16689
16689
|
}
|
|
@@ -16968,9 +16968,9 @@ var require_api_upgrade = __commonJS({
|
|
|
16968
16968
|
};
|
|
16969
16969
|
function upgrade(opts, callback) {
|
|
16970
16970
|
if (callback === void 0) {
|
|
16971
|
-
return new Promise((
|
|
16971
|
+
return new Promise((resolve3, reject) => {
|
|
16972
16972
|
upgrade.call(this, opts, (err, data) => {
|
|
16973
|
-
return err ? reject(err) :
|
|
16973
|
+
return err ? reject(err) : resolve3(data);
|
|
16974
16974
|
});
|
|
16975
16975
|
});
|
|
16976
16976
|
}
|
|
@@ -17062,9 +17062,9 @@ var require_api_connect = __commonJS({
|
|
|
17062
17062
|
};
|
|
17063
17063
|
function connect(opts, callback) {
|
|
17064
17064
|
if (callback === void 0) {
|
|
17065
|
-
return new Promise((
|
|
17065
|
+
return new Promise((resolve3, reject) => {
|
|
17066
17066
|
connect.call(this, opts, (err, data) => {
|
|
17067
|
-
return err ? reject(err) :
|
|
17067
|
+
return err ? reject(err) : resolve3(data);
|
|
17068
17068
|
});
|
|
17069
17069
|
});
|
|
17070
17070
|
}
|
|
@@ -20926,7 +20926,7 @@ var require_fetch = __commonJS({
|
|
|
20926
20926
|
function dispatch({ body }) {
|
|
20927
20927
|
const url = requestCurrentURL(request);
|
|
20928
20928
|
const agent = fetchParams.controller.dispatcher;
|
|
20929
|
-
return new Promise((
|
|
20929
|
+
return new Promise((resolve3, reject) => agent.dispatch(
|
|
20930
20930
|
{
|
|
20931
20931
|
path: url.pathname + url.search,
|
|
20932
20932
|
origin: url.origin,
|
|
@@ -21002,7 +21002,7 @@ var require_fetch = __commonJS({
|
|
|
21002
21002
|
}
|
|
21003
21003
|
}
|
|
21004
21004
|
const onError = this.onError.bind(this);
|
|
21005
|
-
|
|
21005
|
+
resolve3({
|
|
21006
21006
|
status,
|
|
21007
21007
|
statusText,
|
|
21008
21008
|
headersList,
|
|
@@ -21048,7 +21048,7 @@ var require_fetch = __commonJS({
|
|
|
21048
21048
|
for (let i = 0; i < rawHeaders.length; i += 2) {
|
|
21049
21049
|
headersList.append(bufferToLowerCasedHeaderName(rawHeaders[i]), rawHeaders[i + 1].toString("latin1"), true);
|
|
21050
21050
|
}
|
|
21051
|
-
|
|
21051
|
+
resolve3({
|
|
21052
21052
|
status,
|
|
21053
21053
|
statusText: STATUS_CODES[status],
|
|
21054
21054
|
headersList,
|
|
@@ -24778,8 +24778,8 @@ var require_util9 = __commonJS({
|
|
|
24778
24778
|
return true;
|
|
24779
24779
|
}
|
|
24780
24780
|
function delay(ms) {
|
|
24781
|
-
return new Promise((
|
|
24782
|
-
setTimeout(
|
|
24781
|
+
return new Promise((resolve3) => {
|
|
24782
|
+
setTimeout(resolve3, ms).unref();
|
|
24783
24783
|
});
|
|
24784
24784
|
}
|
|
24785
24785
|
module.exports = {
|
|
@@ -37553,7 +37553,7 @@ var Protocol = class {
|
|
|
37553
37553
|
return;
|
|
37554
37554
|
}
|
|
37555
37555
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
37556
|
-
await new Promise((
|
|
37556
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
|
|
37557
37557
|
options?.signal?.throwIfAborted();
|
|
37558
37558
|
}
|
|
37559
37559
|
} catch (error2) {
|
|
@@ -37570,7 +37570,7 @@ var Protocol = class {
|
|
|
37570
37570
|
*/
|
|
37571
37571
|
request(request, resultSchema, options) {
|
|
37572
37572
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
37573
|
-
return new Promise((
|
|
37573
|
+
return new Promise((resolve3, reject) => {
|
|
37574
37574
|
const earlyReject = (error2) => {
|
|
37575
37575
|
reject(error2);
|
|
37576
37576
|
};
|
|
@@ -37648,7 +37648,7 @@ var Protocol = class {
|
|
|
37648
37648
|
if (!parseResult.success) {
|
|
37649
37649
|
reject(parseResult.error);
|
|
37650
37650
|
} else {
|
|
37651
|
-
|
|
37651
|
+
resolve3(parseResult.data);
|
|
37652
37652
|
}
|
|
37653
37653
|
} catch (error2) {
|
|
37654
37654
|
reject(error2);
|
|
@@ -37909,12 +37909,12 @@ var Protocol = class {
|
|
|
37909
37909
|
}
|
|
37910
37910
|
} catch {
|
|
37911
37911
|
}
|
|
37912
|
-
return new Promise((
|
|
37912
|
+
return new Promise((resolve3, reject) => {
|
|
37913
37913
|
if (signal.aborted) {
|
|
37914
37914
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
37915
37915
|
return;
|
|
37916
37916
|
}
|
|
37917
|
-
const timeoutId = setTimeout(
|
|
37917
|
+
const timeoutId = setTimeout(resolve3, interval);
|
|
37918
37918
|
signal.addEventListener("abort", () => {
|
|
37919
37919
|
clearTimeout(timeoutId);
|
|
37920
37920
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -39014,7 +39014,7 @@ var McpServer = class {
|
|
|
39014
39014
|
let task = createTaskResult.task;
|
|
39015
39015
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
39016
39016
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
39017
|
-
await new Promise((
|
|
39017
|
+
await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
|
|
39018
39018
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
39019
39019
|
if (!updatedTask) {
|
|
39020
39020
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -39663,21 +39663,21 @@ var StdioServerTransport = class {
|
|
|
39663
39663
|
this.onclose?.();
|
|
39664
39664
|
}
|
|
39665
39665
|
send(message) {
|
|
39666
|
-
return new Promise((
|
|
39666
|
+
return new Promise((resolve3) => {
|
|
39667
39667
|
const json = serializeMessage(message);
|
|
39668
39668
|
if (this._stdout.write(json)) {
|
|
39669
|
-
|
|
39669
|
+
resolve3();
|
|
39670
39670
|
} else {
|
|
39671
|
-
this._stdout.once("drain",
|
|
39671
|
+
this._stdout.once("drain", resolve3);
|
|
39672
39672
|
}
|
|
39673
39673
|
});
|
|
39674
39674
|
}
|
|
39675
39675
|
};
|
|
39676
39676
|
|
|
39677
39677
|
// src/index.ts
|
|
39678
|
-
import { readFileSync as readFileSync3 } from "node:fs";
|
|
39678
|
+
import { mkdirSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "node:fs";
|
|
39679
39679
|
import { homedir as homedir3 } from "node:os";
|
|
39680
|
-
import { join as join3, dirname as dirname3 } from "node:path";
|
|
39680
|
+
import { join as join3, dirname as dirname3, resolve as resolve2, relative as relative2, isAbsolute as isAbsolute2 } from "node:path";
|
|
39681
39681
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
39682
39682
|
|
|
39683
39683
|
// src/config.ts
|
|
@@ -41267,6 +41267,7 @@ var TITLES = {
|
|
|
41267
41267
|
awesomate_dashboard_metrics: "Get dashboard metrics",
|
|
41268
41268
|
awesomate_account_report: "Get account report",
|
|
41269
41269
|
awesomate_business: "Read business details",
|
|
41270
|
+
awesomate_design_system_package: "Make a starter brand design system",
|
|
41270
41271
|
awesomate_privacy_settings: "Get privacy settings",
|
|
41271
41272
|
awesomate_app_list: "List apps",
|
|
41272
41273
|
awesomate_app_context: "Get apps context",
|
|
@@ -43423,6 +43424,47 @@ server.registerTool(
|
|
|
43423
43424
|
}
|
|
43424
43425
|
}
|
|
43425
43426
|
);
|
|
43427
|
+
server.registerTool(
|
|
43428
|
+
"awesomate_design_system_package",
|
|
43429
|
+
{
|
|
43430
|
+
annotations: annotate("awesomate_design_system_package", MUTATING),
|
|
43431
|
+
description: "Make a starter brand design system from this business's CONFIRMED details (colours, font, logo, name, voice) and write it into a folder in the current project, ready to put in Claude Design. Use it when the business has no design system yet and the owner says yes to making one (see 'Brand design system first' in the build skills). It writes tokens.css, preview cards (each starting with its @dsCard line), the fonts and the logo inside the folder (Claude Design allows no external requests), and a README saying what is still to decide. It returns only the file list, never the file contents. Next: tell the owner to run /design-sync (and /design-login once if asked) to create a design system in Claude Design from that folder, then save its name and link on Your business in the hub.",
|
|
43432
|
+
inputSchema: {
|
|
43433
|
+
dir: external_exports.string().max(200).optional().describe("Folder inside the current project to write to (default 'brand-design-system')")
|
|
43434
|
+
}
|
|
43435
|
+
},
|
|
43436
|
+
async ({ dir }) => {
|
|
43437
|
+
try {
|
|
43438
|
+
const root = process.cwd();
|
|
43439
|
+
const target = resolve2(root, dir || "brand-design-system");
|
|
43440
|
+
const rel = relative2(root, target);
|
|
43441
|
+
if (!rel || rel.startsWith("..") || isAbsolute2(rel)) {
|
|
43442
|
+
return errorResult(new Error("Pick a folder inside the current project, for example brand-design-system."));
|
|
43443
|
+
}
|
|
43444
|
+
const pkg = await hubGet(requireConfig(), "/api/my-business/v1/design-system/package");
|
|
43445
|
+
const written = [];
|
|
43446
|
+
for (const f of pkg.files) {
|
|
43447
|
+
const out = resolve2(target, f.path);
|
|
43448
|
+
const inside = relative2(target, out);
|
|
43449
|
+
if (!inside || inside.startsWith("..") || isAbsolute2(inside)) continue;
|
|
43450
|
+
mkdirSync(dirname3(out), { recursive: true });
|
|
43451
|
+
writeFileSync2(out, f.encoding === "base64" ? Buffer.from(f.content, "base64") : f.content);
|
|
43452
|
+
written.push(f.path);
|
|
43453
|
+
}
|
|
43454
|
+
const toDecide = pkg.plan.checklist.filter((c) => c.status === "to_decide").map((c) => c.point);
|
|
43455
|
+
return textResult({
|
|
43456
|
+
folder: rel,
|
|
43457
|
+
files: written,
|
|
43458
|
+
embedded: pkg.embedded,
|
|
43459
|
+
still_to_decide: toDecide,
|
|
43460
|
+
accent_too_faint_for_text: pkg.plan.accentTooFaintForText,
|
|
43461
|
+
next: `Ask the owner to run /design-sync and create a new design system in Claude Design from ./${rel}. Then they save its name and link on Your business in the hub (Knowledge, Your business, Your brand).`
|
|
43462
|
+
});
|
|
43463
|
+
} catch (err) {
|
|
43464
|
+
return errorResult(err);
|
|
43465
|
+
}
|
|
43466
|
+
}
|
|
43467
|
+
);
|
|
43426
43468
|
server.registerResource(
|
|
43427
43469
|
"business",
|
|
43428
43470
|
"awesomate://business",
|
|
@@ -43635,9 +43677,9 @@ server.registerTool(
|
|
|
43635
43677
|
}
|
|
43636
43678
|
if (!rawPath) return errorResult(new Error("Give either path (a local file) or fromFilesPath (a File Manager path)."));
|
|
43637
43679
|
const { statSync, existsSync: existsSync3 } = await import("node:fs");
|
|
43638
|
-
const { resolve:
|
|
43680
|
+
const { resolve: resolve3, basename, extname } = await import("node:path");
|
|
43639
43681
|
const expanded = rawPath.startsWith("~") ? join3(homedir3(), rawPath.slice(1).replace(/^[/\\]/, "")) : rawPath;
|
|
43640
|
-
const abs =
|
|
43682
|
+
const abs = resolve3(expanded);
|
|
43641
43683
|
if (!existsSync3(abs)) {
|
|
43642
43684
|
return errorResult(
|
|
43643
43685
|
new Error(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awesomate/hosting-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.64.0",
|
|
4
4
|
"description": "Awesomate MCP server — lets Claude manage your Awesomate WordPress hosting, plan, limits, n8n automations, and build Node/static apps + databases",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
package/skill/CHANGELOG.json
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"versions": [
|
|
3
|
+
{
|
|
4
|
+
"version": "0.64.0",
|
|
5
|
+
"highlights": [
|
|
6
|
+
"No brand design system yet? Claude can make you a starter one from your confirmed business details (colours, font, logo, voice) and help you put it in Claude Design, so everything built for you looks like you from day one"
|
|
7
|
+
]
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
"version": "0.63.0",
|
|
11
|
+
"highlights": [
|
|
12
|
+
"Before building anything people will see, Claude asks about your brand design system: it uses yours in Claude Design, or offers to make one from your business details, so your apps, pages and emails all look like you. You can save its name and link on Your business"
|
|
13
|
+
]
|
|
14
|
+
},
|
|
3
15
|
{
|
|
4
16
|
"version": "0.62.0",
|
|
5
17
|
"highlights": [
|
|
@@ -37,6 +37,21 @@ own hosting) but you should always confirm the **stack + name** before creating.
|
|
|
37
37
|
voice, logo, colours and contact details. Use them rather than inventing
|
|
38
38
|
placeholders; confirm anything marked *suggestion* with the owner first.
|
|
39
39
|
|
|
40
|
+
### Brand design system first (strongly recommended)
|
|
41
|
+
|
|
42
|
+
Before you build anything people will see, find the business's brand design system:
|
|
43
|
+
|
|
44
|
+
1. `awesomate_business` shows **Design system** and **Design system link** when the owner has
|
|
45
|
+
saved them.
|
|
46
|
+
2. If it is in Claude Design, connect to it with `/design-sync` (the owner may need `/design-login`
|
|
47
|
+
once) and build with its tokens and components.
|
|
48
|
+
3. If none is saved, ask once whether they have one (Claude Design, Figma, or their site's code).
|
|
49
|
+
4. If they don't, offer to make one: `awesomate_design_system_package` writes a starter design
|
|
50
|
+
system from their confirmed details into the project, and `/design-sync` puts it in Claude Design.
|
|
51
|
+
5. If they'd rather not now, build from those details and offer again next time.
|
|
52
|
+
|
|
53
|
+
What makes a good one, and the tokens to start from: [references/design-system.md](references/design-system.md).
|
|
54
|
+
|
|
40
55
|
## 1. Decide the stack (do this before building)
|
|
41
56
|
|
|
42
57
|
Read [references/stack-decision.md](references/stack-decision.md) — it's the
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# The brand design system: find it, connect it, or make one
|
|
2
|
+
|
|
3
|
+
A brand design system keeps a business's colours, type, components and layout rules in one place,
|
|
4
|
+
so everything built for it looks like the same business. Claude Design holds them (claude.ai/design,
|
|
5
|
+
Settings, Design systems). It checks its own work against the design system, and `/design-sync` in
|
|
6
|
+
Claude Code keeps a project's code and the design system in step. Asking for the design system
|
|
7
|
+
before building is strongly recommended. It is never a reason to refuse to build.
|
|
8
|
+
|
|
9
|
+
## Before you build anything people will see
|
|
10
|
+
|
|
11
|
+
1. **Look it up.** `awesomate_business` shows **Design system** and **Design system link** when the
|
|
12
|
+
owner has saved them on Your business in the hub.
|
|
13
|
+
2. **It's in Claude Design.** Connect this project to it with `/design-sync`. The owner may need to
|
|
14
|
+
run `/design-login` once first. Build with its tokens and components, not with new ones.
|
|
15
|
+
3. **Nothing is saved.** Ask once: "Do you have a brand design system, in Claude Design, Figma, or
|
|
16
|
+
your website's code?" If they do, connect it, and suggest they save its name and link on Your
|
|
17
|
+
business so the next build finds it straight away.
|
|
18
|
+
4. **They have none.** Offer to make one. `awesomate_design_system_package` writes a starter design
|
|
19
|
+
system into the project from their confirmed details: `tokens.css`, preview cards, the fonts and
|
|
20
|
+
the logo inside the folder, and a README of what is still to decide. Then `/design-sync` puts it
|
|
21
|
+
in Claude Design as their design system. Tell them to save its name and link on Your business,
|
|
22
|
+
and to work through the README's "still to decide" list with you or in Claude Design.
|
|
23
|
+
5. **They'd rather not now.** Build from the business's colours, fonts and logo, and offer again
|
|
24
|
+
next time. Never make the build wait for a design system.
|
|
25
|
+
|
|
26
|
+
Follow Anthropic's `/design-sync` for how the sync itself works. Don't describe it from memory.
|
|
27
|
+
|
|
28
|
+
## What a good brand design system has
|
|
29
|
+
|
|
30
|
+
Check the system you connect or create against these eight points, and say in one line what is
|
|
31
|
+
missing:
|
|
32
|
+
|
|
33
|
+
1. **One idea**, rooted in the business's place or story: its town, its building, its trade, its people.
|
|
34
|
+
2. **Type first.** One expressive face for headings and one neutral face for text. Generic system
|
|
35
|
+
fonts are not a display face.
|
|
36
|
+
3. **Colour with a rule.** A tight trio (a near-black, one brand colour, a near-white), or small
|
|
37
|
+
palettes that never mix. Large areas use off-black and off-white, not pure black and white.
|
|
38
|
+
4. **The name at hero scale.** The wordmark large, over an image or a flat colour.
|
|
39
|
+
5. **A system the owner can run:**
|
|
40
|
+
- a type scale for phone and desktop;
|
|
41
|
+
- spacing;
|
|
42
|
+
- corner radii, with pills for buttons and tags;
|
|
43
|
+
- which colours go together;
|
|
44
|
+
- card and list patterns.
|
|
45
|
+
6. **One piece of motion** that expresses the idea, not scattered effects.
|
|
46
|
+
7. **Voice.** Two or three lines on how it sounds, and real copy, never placeholder text.
|
|
47
|
+
8. **Accessible.** AA contrast for text, and buttons and links at least 44 px to tap.
|
|
48
|
+
|
|
49
|
+
## Tokens to start from
|
|
50
|
+
|
|
51
|
+
When you make one from the business's details, name the variables so a person can read them:
|
|
52
|
+
|
|
53
|
+
```css
|
|
54
|
+
:root {
|
|
55
|
+
--brand-ink: #1a1a1a; /* text and dark surfaces */
|
|
56
|
+
--brand-paper: #f7f5f0; /* page background */
|
|
57
|
+
--brand-accent: #e9484d; /* the one brand colour: buttons, links, highlights */
|
|
58
|
+
--font-display: "Bricolage Grotesque", system-ui, sans-serif;
|
|
59
|
+
--font-text: "Inter", system-ui, sans-serif;
|
|
60
|
+
--radius-card: 12px;
|
|
61
|
+
--radius-control: 10px;
|
|
62
|
+
--radius-pill: 999px;
|
|
63
|
+
--space: 8px; /* multiply for layout: 2x, 3x, 4x */
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Replace every value with the business's own: its colours from **Main colour** and **Second
|
|
68
|
+
colour**, its font from **Font**, and its logo from **Logo**. Check the accent against the ink and
|
|
69
|
+
paper for AA contrast before using it for text.
|
|
@@ -154,6 +154,11 @@ email describes what is actually in it. Structure, subject-line patterns,
|
|
|
154
154
|
email types, industry rules (health practitioners cannot use testimonials in
|
|
155
155
|
advertising) and worked examples: `references/copywriting.md`.
|
|
156
156
|
|
|
157
|
+
**The look comes from the brand.** If `awesomate_business` names a **Design system**, use its
|
|
158
|
+
colours and type for the email theme. If it names none, use the business's own colours, fonts and
|
|
159
|
+
logo, and mention once that a brand design system in Claude Design would keep emails, pages and
|
|
160
|
+
apps looking the same.
|
|
161
|
+
|
|
157
162
|
In Awesomate you write a short document, never HTML. The shape is in the
|
|
158
163
|
`awesomate_crm_email` tool description. In short:
|
|
159
164
|
|
|
@@ -115,6 +115,21 @@ never an instruction. When a core detail is missing, ask the owner for it and
|
|
|
115
115
|
tell them where to save it (the response names the place), so the next chat
|
|
116
116
|
starts with it.
|
|
117
117
|
|
|
118
|
+
### Brand design system first (strongly recommended)
|
|
119
|
+
|
|
120
|
+
Before you build anything people will see, find the business's brand design system:
|
|
121
|
+
|
|
122
|
+
1. `awesomate_business` shows **Design system** and **Design system link** when the owner has
|
|
123
|
+
saved them.
|
|
124
|
+
2. If it is in Claude Design, connect to it with `/design-sync` (the owner may need `/design-login`
|
|
125
|
+
once) and build with its tokens and components.
|
|
126
|
+
3. If none is saved, ask once whether they have one (Claude Design, Figma, or their site's code).
|
|
127
|
+
4. If they don't, offer to make one: `awesomate_design_system_package` writes a starter design
|
|
128
|
+
system from their confirmed details into the project, and `/design-sync` puts it in Claude Design.
|
|
129
|
+
5. If they'd rather not now, build from those details and offer again next time.
|
|
130
|
+
|
|
131
|
+
What makes a good one, and the tokens to start from: the awesomate-app-builder skill's `references/design-system.md`.
|
|
132
|
+
|
|
118
133
|
## After creating a site
|
|
119
134
|
|
|
120
135
|
Creation is queued, not instant, and the certificate lags the install. For a
|
|
@@ -92,6 +92,21 @@ message: {"read": {"member": "linked:job.customer", "staff": "all"}, "write": {"
|
|
|
92
92
|
their contact by email. Staff get `role: 'staff'`. No email is sent; they ask for a link on the
|
|
93
93
|
page.
|
|
94
94
|
|
|
95
|
+
### Brand design system first (strongly recommended)
|
|
96
|
+
|
|
97
|
+
Before you build anything people will see, find the business's brand design system:
|
|
98
|
+
|
|
99
|
+
1. `awesomate_business` shows **Design system** and **Design system link** when the owner has
|
|
100
|
+
saved them.
|
|
101
|
+
2. If it is in Claude Design, connect to it with `/design-sync` (the owner may need `/design-login`
|
|
102
|
+
once) and build with its tokens and components.
|
|
103
|
+
3. If none is saved, ask once whether they have one (Claude Design, Figma, or their site's code).
|
|
104
|
+
4. If they don't, offer to make one: `awesomate_design_system_package` writes a starter design
|
|
105
|
+
system from their confirmed details into the project, and `/design-sync` puts it in Claude Design.
|
|
106
|
+
5. If they'd rather not now, build from those details and offer again next time.
|
|
107
|
+
|
|
108
|
+
What makes a good one, and the tokens to start from: the awesomate-app-builder skill's `references/design-system.md`.
|
|
109
|
+
|
|
95
110
|
## 5. Build the page
|
|
96
111
|
|
|
97
112
|
Any static page works (host it with the awesomate-app-builder skill, `kind: 'static'`). The SDK
|