@mapled/cli 0.1.0 → 0.3.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 +104 -11
- package/dist/api.d.ts +4 -0
- package/dist/api.js +22 -5
- package/dist/commands.d.ts +10 -3
- package/dist/commands.js +375 -5
- package/dist/doctor.d.ts +14 -0
- package/dist/doctor.js +48 -0
- package/dist/index.js +34 -2
- package/dist/manifest.d.ts +95 -0
- package/dist/manifest.js +374 -0
- package/dist/md.d.ts +59 -0
- package/dist/md.js +110 -0
- package/dist/pin.d.ts +41 -0
- package/dist/pin.js +297 -0
- package/dist/scan.d.ts +73 -0
- package/dist/scan.js +1295 -0
- package/dist/schema.d.ts +3 -0
- package/dist/types.js +2 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# @mapled/cli
|
|
2
2
|
|
|
3
|
-
The `mapled` command line for [Mapled](https://mapled.io) — a hosted headless CMS built for sites created with AI. Sign in to a project, link the repository to it, generate TypeScript types for the content your site reads, and check the whole integration in one go.
|
|
3
|
+
The `mapled` command line for [Mapled](https://mapled.io) — a hosted headless CMS built for sites created with AI. Sign in to a project, link the repository to it, generate TypeScript types for the content your site reads, keep a pin of the schema and a manifest of where the code reads it, and check the whole integration in one go.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npx @mapled/cli auth login
|
|
7
7
|
npx @mapled/cli project link
|
|
8
8
|
npx @mapled/cli types generate
|
|
9
|
+
npx @mapled/cli scan --write
|
|
10
|
+
npx @mapled/cli bindings push
|
|
11
|
+
npx @mapled/cli md pull
|
|
9
12
|
npx @mapled/cli doctor
|
|
10
13
|
```
|
|
11
14
|
|
|
@@ -45,18 +48,92 @@ Only public values live here — the project id, where the generated types go, t
|
|
|
45
48
|
npx @mapled/cli types generate
|
|
46
49
|
```
|
|
47
50
|
|
|
48
|
-
Reads the project's schema and writes `mapled-types.ts` (the path from `mapled.json`, or `--out <file>`): one interface per collection and single, keyed maps, and doc comments with the field types. Sensitive fields never reach the site and are left out.
|
|
51
|
+
Reads the project's schema and writes `mapled-types.ts` (the path from `mapled.json`, or `--out <file>`): one interface per collection and single, keyed maps, a `MapledSchema` that ties them together, and doc comments with the field types. Sensitive fields never reach the site and are left out.
|
|
49
52
|
|
|
50
53
|
```ts
|
|
51
|
-
import type {
|
|
54
|
+
import type { MapledSchema } from "./mapled-types";
|
|
52
55
|
import { createClient } from "@mapled/next";
|
|
53
56
|
|
|
54
|
-
const mapled = createClient({ key: process.env.MAPLED_KEY! });
|
|
55
|
-
const { records } = await mapled.getRecords
|
|
56
|
-
const home = await mapled.getSingle
|
|
57
|
+
const mapled = createClient<MapledSchema>({ key: process.env.MAPLED_KEY! });
|
|
58
|
+
const { records } = await mapled.getRecords("articles"); // records[0].data is an Article
|
|
59
|
+
const home = await mapled.getSingle("homepage"); // Homepage | null
|
|
57
60
|
```
|
|
58
61
|
|
|
59
|
-
The file names the schema it came from; `mapled doctor` tells you when it is out of date. Run `types generate` again after the schema changes.
|
|
62
|
+
The file names the schema it came from; `mapled doctor` tells you when it is out of date. Run `types generate` again after the schema changes — it refreshes `mapled/schema.json` too when you keep one.
|
|
63
|
+
|
|
64
|
+
## Pin the schema and see what changed
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx @mapled/cli schema pull
|
|
68
|
+
npx @mapled/cli schema diff
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
`schema pull` writes `mapled/schema.json` — the schema this site is built against, without ids or timestamps, so it diffs cleanly in git. Commit it. When editors or agents change the structure in Mapled, `schema diff` compares the pin with the live schema and judges every change **for a site that reads the content**:
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
Schema changes since mapled/schema.json (3f9a1c2b4d5e → 8c1d2e3f4a5b):
|
|
75
|
+
|
|
76
|
+
Articles (articles)
|
|
77
|
+
+ subtitle added — short text, optional safe
|
|
78
|
+
- legacy-id removed — number breaking
|
|
79
|
+
~ author short text → relation to Authors breaking
|
|
80
|
+
~ title required → optional (may be empty now) breaking
|
|
81
|
+
~ tags option “Legacy” removed breaking
|
|
82
|
+
+ Authors (authors) new collection — 3 fields safe
|
|
83
|
+
|
|
84
|
+
4 breaking changes, 2 safe changes, judged for a site that reads the content. Update the site where needed, then run `mapled types generate` (it refreshes mapled/schema.json as well).
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Breaking: a collection, field or option the site may rely on disappears, a type changes, a required field becomes optional, a field becomes sensitive (it stops being delivered), a relation points elsewhere. Safe: additions, renamed labels, help texts, validation rules. `--json` for scripts and agents; `--exit-code` exits 1 when anything changed, for CI.
|
|
88
|
+
|
|
89
|
+
## Scan the code into a manifest
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx @mapled/cli scan
|
|
93
|
+
npx @mapled/cli scan --write
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The scanner reads the site's source and writes down what it reads from Mapled: the pages (from the framework's file layout — Next.js App and Pages Router, Astro, SvelteKit, Nuxt, Remix) and one binding per place a collection or field is used.
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
Scanned 42 files with TypeScript — 3 pages, 14 bindings.
|
|
100
|
+
|
|
101
|
+
/ app/page.tsx
|
|
102
|
+
homepage headline, cover, alt
|
|
103
|
+
articles list • title, date, slug
|
|
104
|
+
/blog/[slug] app/blog/[slug]/page.tsx
|
|
105
|
+
articles by slug • title, body
|
|
106
|
+
|
|
107
|
+
No mapled/manifest.json yet — run `mapled scan --write` to create it, then `mapled bindings push`.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
It follows `getRecords` / `getRecord` / `getRecordBySlug` / `getSingle` calls through variables, destructuring, `.map()` callbacks and into the components a record is passed to as a prop, and through helpers in `lib/` that return a read. The parser is your repository's own TypeScript; without it the scanner still finds the collections, but not the fields. Source code never leaves your machine — only the manifest does, when you push it.
|
|
111
|
+
|
|
112
|
+
`--write` creates or updates `mapled/manifest.json`. Bindings the scan can't see (written by your AI agent, or by hand) are kept and listed; `--prune` drops them. Commit the file.
|
|
113
|
+
|
|
114
|
+
## Validate and push the manifest
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
npx @mapled/cli manifest validate
|
|
118
|
+
npx @mapled/cli bindings push
|
|
119
|
+
npx @mapled/cli bindings pull
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`manifest validate` checks `mapled/manifest.json` the way Mapled will: the shape and limits, duplicate keys, and — against the schema pin or the live schema — collections and fields that don't exist and targets that don't fit the field type, plus files the repository doesn't have. Problems exit 1; warnings only tell you what Mapled would grade down.
|
|
123
|
+
|
|
124
|
+
`bindings push` sends the manifest to Mapled (the same call your AI agent's `push_site_manifest` makes). Mapled grades every binding — healthy, type mismatch, outdated, missing on site — and the result shows up in **Structure → Bindings**. `--dry-run` validates and stops. Pushing bindings changes structure, so it needs the builder plan.
|
|
125
|
+
|
|
126
|
+
`bindings pull` writes the pushed manifest into `mapled/manifest.json` — say, after your agent pushed one from a chat and you want the repository's copy. It refuses to overwrite a local file that differs unless you pass `--force`.
|
|
127
|
+
|
|
128
|
+
## Leave a guide for the next agent
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
npx @mapled/cli md pull
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Writes `MAPLED.md` at the repository root — the guide Mapled renders from the project for whoever works on the site next, person or AI agent: the project and how the site reads it, the content model with every field's type, rules and help text, where each page renders which field (from the pushed manifest), the last setup run with its verification, the working rules, and the commands that verify the integration. It never contains a secret.
|
|
135
|
+
|
|
136
|
+
The first line is a stamp with the schema hash and the manifest version. Everything below the `<!-- mapled:notes -->` line is yours — conventions, what stays hardcoded on purpose, things to avoid — and survives every `md pull`. A `MAPLED.md` Mapled didn't write is left alone unless you pass `--force`, which moves its content below the notes line. `mapled doctor` tells you when the file no longer matches the project; your AI agent gets the same document from the `get_mapled_md` tool.
|
|
60
137
|
|
|
61
138
|
## Check the integration
|
|
62
139
|
|
|
@@ -70,21 +147,24 @@ Mapled doctor — Dolphin Landing
|
|
|
70
147
|
✓ Project link mapled.json → Dolphin Landing
|
|
71
148
|
✓ Signed in Mapled CLI • Dolphin Landing
|
|
72
149
|
✓ Generated types mapled-types.ts matches the schema (3f9a1c2b4d5e)
|
|
150
|
+
✓ Schema pin mapled/schema.json matches the schema (3f9a1c2b4d5e)
|
|
151
|
+
✓ Site manifest mapled/manifest.json is pushed as manifest v3 (14 bindings on 3 pages)
|
|
152
|
+
✓ MAPLED.md MAPLED.md matches the schema (3f9a1c2b4d5e) and manifest v3
|
|
73
153
|
✓ Environment MAPLED_KEY and MAPLED_WEBHOOK_SECRET in .env.local
|
|
74
154
|
✓ Secrets in git No env files or Mapled secrets are tracked.
|
|
75
|
-
✓ @mapled/next 0.
|
|
76
|
-
✓ mapled CLI 0.
|
|
155
|
+
✓ @mapled/next 0.6.0 (current)
|
|
156
|
+
✓ mapled CLI 0.2.0 (current)
|
|
77
157
|
✓ Revalidation route app/api/mapled/revalidate/route.ts
|
|
78
158
|
⚠ Preview route app/api/mapled/preview/route.ts is missing — mount createPreviewHandler from "@mapled/next/server" so Preview from Mapled works.
|
|
79
159
|
✓ Site reads content Last read 3m ago
|
|
80
160
|
✓ Publish webhook Delivered 2h ago to dolphin-landing.example
|
|
81
161
|
✓ Preview on the site Responds at https://dolphin-landing.example/api/mapled/preview
|
|
82
|
-
✓ Bindings
|
|
162
|
+
✓ Bindings 14 healthy • synced 2d ago from Mapled CLI
|
|
83
163
|
|
|
84
164
|
1 warning.
|
|
85
165
|
```
|
|
86
166
|
|
|
87
|
-
The repository side: the link, the sign-in, the generated types, `MAPLED_KEY` and `MAPLED_WEBHOOK_SECRET` in your env files (names only — values are never read out), env files or Mapled secrets tracked by git, the installed `@mapled/next` and CLI versions, the revalidation and preview routes of a Next.js site. The Mapled side, from the same status the AI agent's `check_integration` tool reads: whether the site has read content with the delivery key, the publish webhook and its last delivery, the preview route on the deployed site, the bindings health.
|
|
167
|
+
The repository side: the link, the sign-in, the generated types, the schema pin, the site manifest (and whether it matches what was pushed), `MAPLED.md` (and whether it is what Mapled would render now), `MAPLED_KEY` and `MAPLED_WEBHOOK_SECRET` in your env files (names only — values are never read out), env files or Mapled secrets tracked by git, the installed `@mapled/next` and CLI versions, the revalidation and preview routes of a Next.js site. The Mapled side, from the same status the AI agent's `check_integration` tool reads: whether the site has read content with the delivery key, the publish webhook and its last delivery, the preview route on the deployed site, the bindings health.
|
|
88
168
|
|
|
89
169
|
Exit code 1 when something is marked ✗; `--json` prints the checks for scripts and agents.
|
|
90
170
|
|
|
@@ -92,4 +172,17 @@ Exit code 1 when something is marked ✗; `--json` prints the checks for scripts
|
|
|
92
172
|
|
|
93
173
|
- `--api <origin>` — a Mapled API other than `https://api.mapled.io` (or set `MAPLED_API_URL`); `project link` remembers it in `mapled.json`
|
|
94
174
|
- `--no-browser` — print the sign-in link instead of opening a browser
|
|
175
|
+
- `--json` — machine-readable output (`doctor`, `schema diff`, `scan`, `manifest validate`)
|
|
95
176
|
- `MAPLED_CONFIG_DIR` — where credentials live (default: `$XDG_CONFIG_HOME/mapled` or `~/.config/mapled`)
|
|
177
|
+
|
|
178
|
+
## Files in the repository
|
|
179
|
+
|
|
180
|
+
| File | Written by | Purpose |
|
|
181
|
+
|---|---|---|
|
|
182
|
+
| `mapled.json` | `project link` | The project id, the types path, the framework — public values only |
|
|
183
|
+
| `mapled-types.ts` | `types generate` | TypeScript types of the published content, with `MapledSchema` |
|
|
184
|
+
| `mapled/schema.json` | `schema pull`, `types generate` | The schema this site is built against — `schema diff` starts from it |
|
|
185
|
+
| `mapled/manifest.json` | `scan --write`, `bindings pull`, your agent | Pages and bindings — where the code reads each collection and field |
|
|
186
|
+
| `MAPLED.md` | `md pull`, your agent | The guide for the next agent — project, content model, bindings, working rules; your notes below the marker |
|
|
187
|
+
|
|
188
|
+
Commit all five. Credentials never live in the repository.
|
package/dist/api.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export declare class ApiError extends Error {
|
|
|
6
6
|
code: string | null;
|
|
7
7
|
constructor(status: number, code: string | null, message: string);
|
|
8
8
|
}
|
|
9
|
+
export type Method = "GET" | "POST" | "PATCH" | "DELETE";
|
|
10
|
+
export declare function apiRequest<T>(api: string, method: Method, path: string, token: string, body: unknown, f: Fetch): Promise<T>;
|
|
9
11
|
export declare function apiGet<T>(api: string, path: string, token: string, f: Fetch): Promise<T>;
|
|
10
12
|
/** A signed-in connection with its store: reads refresh the access
|
|
11
13
|
token when it is about to expire or when the API says it did, and
|
|
@@ -17,5 +19,7 @@ export declare class Session {
|
|
|
17
19
|
private readonly f;
|
|
18
20
|
constructor(store: CredentialsFile, file: string, conn: Connection, f: Fetch);
|
|
19
21
|
get<T>(path: string): Promise<T>;
|
|
22
|
+
send<T>(method: Method, path: string, body: unknown): Promise<T>;
|
|
23
|
+
private request;
|
|
20
24
|
private refresh;
|
|
21
25
|
}
|
package/dist/api.js
CHANGED
|
@@ -12,10 +12,18 @@ export class ApiError extends Error {
|
|
|
12
12
|
this.code = code;
|
|
13
13
|
}
|
|
14
14
|
}
|
|
15
|
-
export async function
|
|
15
|
+
export async function apiRequest(api, method, path, token, body, f) {
|
|
16
16
|
let res;
|
|
17
17
|
try {
|
|
18
|
-
res = await f(`${api}${path}`, {
|
|
18
|
+
res = await f(`${api}${path}`, {
|
|
19
|
+
method,
|
|
20
|
+
headers: {
|
|
21
|
+
authorization: `Bearer ${token}`,
|
|
22
|
+
accept: "application/json",
|
|
23
|
+
...(body !== undefined ? { "content-type": "application/json" } : {}),
|
|
24
|
+
},
|
|
25
|
+
...(body !== undefined ? { body: JSON.stringify(body) } : {}),
|
|
26
|
+
});
|
|
19
27
|
}
|
|
20
28
|
catch {
|
|
21
29
|
throw new CliError(`Couldn't reach ${api}. Check your connection and try again.`);
|
|
@@ -26,6 +34,9 @@ export async function apiGet(api, path, token, f) {
|
|
|
26
34
|
}
|
|
27
35
|
return data;
|
|
28
36
|
}
|
|
37
|
+
export function apiGet(api, path, token, f) {
|
|
38
|
+
return apiRequest(api, "GET", path, token, undefined, f);
|
|
39
|
+
}
|
|
29
40
|
/** A signed-in connection with its store: reads refresh the access
|
|
30
41
|
token when it is about to expire or when the API says it did, and
|
|
31
42
|
the rotated pair is written back before the call returns. */
|
|
@@ -40,17 +51,23 @@ export class Session {
|
|
|
40
51
|
this.conn = conn;
|
|
41
52
|
this.f = f;
|
|
42
53
|
}
|
|
43
|
-
|
|
54
|
+
get(path) {
|
|
55
|
+
return this.request("GET", path);
|
|
56
|
+
}
|
|
57
|
+
send(method, path, body) {
|
|
58
|
+
return this.request(method, path, body);
|
|
59
|
+
}
|
|
60
|
+
async request(method, path, body) {
|
|
44
61
|
if (new Date(this.conn.expiresAt).getTime() - Date.now() < 60_000)
|
|
45
62
|
await this.refresh();
|
|
46
63
|
try {
|
|
47
|
-
return await
|
|
64
|
+
return await apiRequest(this.conn.api, method, path, this.conn.accessToken, body, this.f);
|
|
48
65
|
}
|
|
49
66
|
catch (err) {
|
|
50
67
|
if (err instanceof ApiError && err.status === 401) {
|
|
51
68
|
if (err.code === "TOKEN_EXPIRED") {
|
|
52
69
|
await this.refresh();
|
|
53
|
-
return
|
|
70
|
+
return apiRequest(this.conn.api, method, path, this.conn.accessToken, body, this.f);
|
|
54
71
|
}
|
|
55
72
|
throw new CliError(`The connection to ${this.conn.projectName} was revoked. Run \`mapled auth login\` to sign in again.`);
|
|
56
73
|
}
|
package/dist/commands.d.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { type ParsedArgs } from "./args.js";
|
|
|
2
2
|
import { type FoundConfig } from "./config.js";
|
|
3
3
|
import { type Connection } from "./credentials.js";
|
|
4
4
|
import { type Fetch } from "./oauth.js";
|
|
5
|
-
/** The
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/** The commands of waves 1 and 2 (§31) and MAPLED.md (§21.3). Everything that talks to the
|
|
6
|
+
world comes in through `Ctx`, so the commands run in tests against a
|
|
7
|
+
fake API and a temp directory. */
|
|
8
8
|
export type Ctx = {
|
|
9
9
|
cwd: string;
|
|
10
10
|
env: NodeJS.ProcessEnv;
|
|
@@ -23,5 +23,12 @@ export declare function login(ctx: Ctx, flags: Flags): Promise<Connection>;
|
|
|
23
23
|
export declare function logout(ctx: Ctx, flags: Flags): Promise<void>;
|
|
24
24
|
export declare function link(ctx: Ctx, flags: Flags): Promise<FoundConfig>;
|
|
25
25
|
export declare function generate(ctx: Ctx, flags: Flags): Promise<void>;
|
|
26
|
+
export declare function schemaPull(ctx: Ctx, flags: Flags): Promise<void>;
|
|
27
|
+
export declare function schemaDiff(ctx: Ctx, flags: Flags): Promise<number>;
|
|
28
|
+
export declare function manifestValidate(ctx: Ctx, flags: Flags): Promise<number>;
|
|
29
|
+
export declare function bindingsPush(ctx: Ctx, flags: Flags): Promise<void>;
|
|
30
|
+
export declare function bindingsPull(ctx: Ctx, flags: Flags): Promise<void>;
|
|
31
|
+
export declare function mdPull(ctx: Ctx, flags: Flags): Promise<void>;
|
|
32
|
+
export declare function scan(ctx: Ctx, flags: Flags): Promise<void>;
|
|
26
33
|
export declare function doctor(ctx: Ctx, flags: Flags): Promise<number>;
|
|
27
34
|
export {};
|