@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 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 { Article, MapledCollections } from "./mapled-types";
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<Article>("articles");
56
- const home = await mapled.getSingle<MapledCollections["homepage"]>("homepage");
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.3.0 (current)
76
- ✓ mapled CLI 0.1.0 (current)
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 12 healthy • synced 2d ago from Claude Code
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 apiGet(api, path, token, f) {
15
+ export async function apiRequest(api, method, path, token, body, f) {
16
16
  let res;
17
17
  try {
18
- res = await f(`${api}${path}`, { headers: { authorization: `Bearer ${token}`, accept: "application/json" } });
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
- async get(path) {
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 apiGet(this.conn.api, path, this.conn.accessToken, this.f);
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 apiGet(this.conn.api, path, this.conn.accessToken, this.f);
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
  }
@@ -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 four wave-1 commands (§31) plus sign-out. Everything that talks
6
- to the world comes in through `Ctx`, so the commands run in tests
7
- against a fake API and a temp directory. */
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 {};