New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@mapled/cli

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@mapled/cli

Mapled CLI — sign in, link a project, generate TypeScript types for its content, create forms, preview drafts and check the site's integration.

npmnpm
Version
0.16.0
Version published
Weekly downloads
265
-6.69%
Maintainers
1
Weekly downloads
 
Created
Source

@mapled/cli

The mapled command line for Mapled — 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.

npx @mapled/cli auth login
npx @mapled/cli project link
npx @mapled/cli types generate
npx @mapled/cli scan --write
npx @mapled/cli bindings push
npx @mapled/cli md pull
npx @mapled/cli doctor

Node 18 or newer. No dependencies. Prefer a global install? npm install -g @mapled/cli puts mapled on your PATH, and every command below works as mapled ….

Connecting a site without an AI agent? Integrate by hand in the @mapled/next README walks through the whole integration — modeling the content, reading it, the publish webhook, preview, forms — and uses these commands along the way.

For AI agents

Mapled is a hosted headless CMS for sites built with AI: the owner edits content in Mapled, the site reads published content through a small SDK, and you connect the two. Everything below runs from the shell of the session you are already in — no MCP server to register, no restart.

npx @mapled/cli connect --project <id>

The browser opens Mapled on that project and the person clicks Authorize; nothing is copied or pasted. The command writes mapled.json and prints Mapled's guide for agents (also at https://api.mapled.io/agents.md). Run again in a connected repository — or in a subdirectory of one — it opens no browser. The guide is read from Mapled, or from the origin you name with --api on that run; an origin that only mapled.json names never serves it, and a first sign-in there asks for --api too. Without --project the person picks the project in the browser.

npx @mapled/cli tools
npx @mapled/cli tool get_schema
npx @mapled/cli tool propose_setup_plan --input plan.json
npx @mapled/cli tool get_setup_run '{"runId":"…"}' --wait

tools lists every tool with its input (--json for the schemas). tool <name> calls one — the same tools the MCP server offers, through the same connection: the input is a JSON object, inline or in a file with --input; the answer is printed as Mapled worded it, and a refusal exits 1. tool get_setup_run '{"runId":"…"}' --wait waits while the plan is still waiting for approval and prints the run once the person has decided on the Mapled screen — the agent carries on without being asked; it waits 90 seconds by default (--wait=<seconds>, up to 600) and then prints the run as it stands, so the agent runs the same command again.

Do the work yourself: the person approves on Mapled screens (the connection, the plan, a destructive change) and is never sent to a terminal or to GitHub. Never ask for a token or a key in the conversation.

Sign in

npx @mapled/cli auth login

Your browser opens Mapled; you pick the project and authorize. The CLI is an ordinary OAuth client of Mapled, so the connection shows up in the project's Integrations and can be revoked there at any time. Tokens are stored in your user config directory (~/.config/mapled/credentials.json, readable only by you) — never in the repository.

npx @mapled/cli auth logout revokes the connection of the linked project (--all for every project on this machine).

npx @mapled/cli project link

Writes mapled.json next to your package.json:

{
  "project": "bae66c47-…",
  "types": "mapled-types.ts",
  "framework": "nextjs"
}

Only public values live here — the project id, where the generated types go, the framework. Commit it. With more than one signed-in project, pass --project <id> or pick from the list.

Generate types

npx @mapled/cli types generate

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. A group field is typed by an interface of its items; the project's component types become one interface each (HeroComponent, with _type: "hero" and the item's _key), a components field a list of the union of the types it allows, and MapledComponent the union of them all — switch on _type to render an item.

import type { MapledSchema } from "./mapled-types";
import { createClient } from "@mapled/next";

const mapled = createClient<MapledSchema>({ key: process.env.MAPLED_KEY! });
const { records } = await mapled.getRecords("articles"); // records[0].data is an Article
const home = await mapled.getSingle("homepage");          // Homepage | null

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.

Pin the schema and see what changed

npx @mapled/cli schema pull
npx @mapled/cli schema diff

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:

Schema changes since mapled/schema.json (3f9a1c2b4d5e → 8c1d2e3f4a5b):

Articles (articles)
  + subtitle   added — short text, optional                     safe
  - legacy-id  removed — number                                 breaking
  ~ author     short text → relation to Authors                 breaking
  ~ title      required → optional (may be empty now)           breaking
  ~ tags       option “Legacy” removed                          breaking
+ Authors (authors)  new collection — 3 fields                  safe

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).

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 — and a renamed key while its old one is still an alias (> price renamed to “amount” — “price” still works as an alias): the site keeps reading it, the rest of the field is compared under the new key, and types generate types the old key @deprecated. Once the alias is removed in Mapled the old key is a removed field. --json for scripts and agents; --exit-code exits 1 when anything changed, for CI.

The pin keeps formulas of computed fields and previous keys. (A pin written by 0.5.0 lost the formulas, so its hash never matched a project that has computed fields — run schema pull once after upgrading.)

Scan the code into a manifest

npx @mapled/cli scan
npx @mapled/cli scan --write

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 — or a plain HTML site's .html files) and one binding per place a collection or field is used.

Scanned 42 files with TypeScript — 3 pages, 14 bindings.

/              app/page.tsx
  homepage     headline, cover, alt
  articles     list • title, date, slug
/blog/[slug]   app/blog/[slug]/page.tsx
  articles     by slug • title, body

No mapled/manifest.json yet — run `mapled scan --write` to create it, then `mapled bindings push`.

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.

A plain HTML site (framework is plain-html in mapled.json — a folder with an index.html and no framework in package.json) has no reads in code: the scanner reads the data-mapled-* attributes of its .html files the way the @mapled/vanilla script does, in the tree a browser builds from the file (the CLI carries the standard HTML parser, parse5, inside — nothing to install). Each file is a page where a static host serves it (index.html → /, blog/index.html → /blog/, post.html → /post.html, public/ from the root); a record read by data-mapled-slug-param has its own address with that parameter in the query, so Preview in the record's editor opens the right page — and the scan says when Preview can't open an address. The target of a binding is what the attribute renders — data-mapled-format="markdown" rich text, an <img> an image, data-mapled-bind-href a link — and each different way a field is read is a binding of its own, so a field bound where it can't render shows up as a mismatch even when another element reads it well. With the schema at hand it also names the paths nothing in Mapled answers to: on the page, a misspelled field and an empty one look the same.

Scanned 2 HTML files — 2 pages, 11 bindings.

/                       index.html
  home   headline, intro, tagline
  posts  list • date, slug, title
/post.html?slug=[slug]  post.html
  home   headline
  posts  by slug • body, title

⚠ This path names nothing in the schema: home.tagline (index.html). A misspelled field empties its place on the page, a misspelled single leaves the page's own HTML.

For a Next.js site the scan also records its live updates: capabilities.realtime is true, with the route in releaseRoute, when <MapledLive /> or watchRelease asks — at the default path or one written in the code — a route whose exported GET answers with createReleaseHandler, or, on the Pages Router, an API route whose default export answers with createPagesReleaseHandler (with <MapledLive /> from @mapled/next/live/pages in pages/_app), and false otherwise — MAPLED.md then tells the next agent whether open tabs follow Publish. Capabilities aren't part of the integration hash; doctor's Site manifest line says when the pushed ones differ.

On a site with more than one language the scan records how the routes carry it: locales is { "routing": "prefix", "param": "locale" } when the routes start with a segment named for the language (/[locale]/blog/[slug]) — Mapled then builds each language's address of a record from it (the editor's routes, Preview), a translated slug being the address in that language. Write { "routing": "prefix-except-default" } by hand when the default language lives at the root and the others under /ru/…, /de/… (without param Mapled puts the language before the page); leave locales out when every language shares one address. It is part of the integration hash.

--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.

Validate and push the manifest

npx @mapled/cli manifest validate
npx @mapled/cli bindings push
npx @mapled/cli bindings pull

manifest validate checks mapled/manifest.json the way Mapled will: the shape and limits, duplicate keys, capabilities and locales (routing and param only, as Mapled's schema takes them — and whether a page route has the param segment), 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.

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. The push also records the integration hash: one marker over the schema and these bindings, what the site is synced with from now on. --dry-run validates and stops. Pushing bindings changes structure, so it needs the builder plan.

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.

Create a form

npx @mapled/cli forms create "Contact"
npx @mapled/cli forms list

forms create makes a form the site can post submissions to — the same call your AI agent's create_form makes, so an integration done by hand needs no agent for it:

Created the form “Contact” (contact) in Dolphin Landing.
The site posts submissions to https://api.mapled.io/v1/delivery/forms/contact — flat JSON with the delivery key in X-Mapled-Key, an empty hidden _gotcha field, and the consent label as _consent when the form has a consent checkbox.
Submissions show up in Mapled under Forms.

The key comes from the name; --key <key> chooses it (lowercase letters, digits and dashes), --no-spam-protection turns the honeypot off. Creating a form changes structure, so it needs the builder plan. forms list shows every form with the URL the site posts to (--json for scripts). The component that posts to it is in the @mapled/next README.

Leave a guide for the next agent

npx @mapled/cli md pull

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.

The first line is a stamp with the schema hash, the manifest version and the integration hash. 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.

Check the integration

npx @mapled/cli doctor
Mapled doctor — Dolphin Landing

✓ Project link         mapled.json → Dolphin Landing
✓ Signed in            Mapled CLI • Dolphin Landing
✓ Generated types      mapled-types.ts matches the schema (3f9a1c2b4d5e)
✓ Schema pin           mapled/schema.json matches the schema (3f9a1c2b4d5e)
✓ Site manifest        mapled/manifest.json is pushed as manifest v3 (14 bindings on 3 pages)
✓ Integration          In sync with Mapled (7c1d9e0f2a3b)
✓ MAPLED.md            MAPLED.md matches the schema (3f9a1c2b4d5e) and manifest v3
✓ Environment          MAPLED_KEY and MAPLED_WEBHOOK_SECRET in .env.local
✓ Secrets in git       No env files or Mapled secrets are tracked.
✓ @mapled/next         0.8.0 (current)
✓ mapled CLI           0.9.0 (current)
✓ Revalidation route   app/api/mapled/revalidate/route.ts
⚠ Preview route        app/api/mapled/preview/route.ts is missing — mount createPreviewHandler from "@mapled/next/server" so Preview from Mapled works.
✓ Live updates         app/api/mapled/release/route.ts — open tabs follow Publish (<MapledLive /> in app/layout.tsx)
✓ Site reads content   Last read 3m ago
✓ Publish webhook      Delivered 2h ago to dolphin-landing.example
✓ Preview on the site  Responds at https://dolphin-landing.example/api/mapled/preview
✓ Bindings             14 healthy • synced 2d ago from Mapled CLI

1 warning.

The repository side: the link, the sign-in, the generated types, the schema pin, the site manifest (and whether it matches what was pushed), the integration hash — one line saying whether the pin and the manifest here describe the same integration Mapled holds, and if not, what moved (the schema, the bindings, or edits made in Mapled) and which command catches up — 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, and its live updates — ○ «Not used» while no page mounts <MapledLive /> or calls watchRelease, ✓ when the route they ask answers its GET with createReleaseHandler (GET is the handler, or always returns its call: every return is that call, the last statement is one, and nothing throws), ⚠ when it is missing (the tabs would poll a 404) with the file to add, when the route's GET isn't seen to answer with the handler (exported as POST they would get a 405; a GET that only mentions it, calls it and returns something else, or answers something else on another branch, answers without the release), or when no route can be checked against the endpoint — it is relative to the page, a full address (the tab asks it as written, and doctor can't tell it's the site's own), computed, or comes from spread props; a path from the site's root is checked against the route the App Router picks for it, dynamic and catch-all folders included, among route files with the site's page extensions (pageExtensions in next.config; tsx, ts, jsx, js by default) and under its basePath; ⚠ too when a page of either router, more specific than the route, takes that path first, when the dynamic route that answers sets dynamicParams, when the route may set dynamic to anything but "auto" or "force-dynamic" (a copy Next builds without the tab's request), sets "force-dynamic" while createReleaseHandler there isn't seen to keep a revalidate above 0 (its default does; with false or 0 Next reads the release past the cache tag) or fetchCache to anything but "auto" (the release read past the cache tag, before the webhook refreshes the pages), when next.config sets output: "export" (no server runs the route), or when next.config may set pageExtensions, basePath or output where doctor can't read them — it reads them only as literals, once, in the object the file exports, and only while the file is declarative: type imports, declarations of data, one export of the config object itself, and functions only as Next's hooks (webpack, headers …) in it. A module the file loads (a plugin such as withMDX, a path helper, an env check), a config exported as a function, any other code, a spread or a getter leaves the config unread — mapled doctor --load-config then has the site's own installed Next load the file (see below). With a basePath, a listener that gives no endpoint asks under it from @mapled/next 0.10.0 — doctor reads the version the site has installed, and it's a ⚠ when that is older or missing. A middleware file (proxy in Next 16) next to the folder Next reads is a ⚠ unless its matcher, read the way Next's build reads export const config, surely leaves the route's path alone: it has no matcher, doctor can't read it, a source may take the path, or next.config has locales (i18n). src/app or src/pages counts only when the root has no app or pages, as Next reads them. On the Pages Router the route is an API route — a file of the pages folder at /api or under it — whose default export is createPagesReleaseHandler or always returns its call, and the listener is <MapledLive /> from @mapled/next/live/pages in pages/_app: a listener without it gets the API route to add (pages/api/mapled/release.ts by default; an endpoint outside /api no API route can answer). ⚠ there too when the API route may run on the edge runtime — runtime or config.runtime other than "nodejs", or written where doctor can't read it (Next reads both only as literals) — where the handler has no Node request and response; when a listener the App Router may run asks it — a file of app/, or on a site with both folders one either router may render: the API route answers Mapled's live release, which moves before the webhook refreshes what App Router pages read; and when <MapledLive /> from @mapled/next/live/pages sits in the App Router's folder, where it can't mount. doctor reads the files, not how Next serves them at run time: rewrites and what a middleware does on the path aren't checked, and neither is whether a page renders the component that listens; revalidate on the route is no ⚠ — the handler's read names its own. 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 (for a project with a staging channel, through which key: production's or staging's — the key decides which releases a deployment shows), the publish webhook and its last delivery, the preview route on the deployed site, the bindings health.

A site rendered in the visitor's browser — a React single-page app (Vite and the like), plain HTML — is checked for what it has: the package line is @mapled/react, Environment wants VITE_MAPLED_KEY and no webhook secret, and the routes and the webhook read ○ «Not needed» — such a site shows a publish on its next load and trades Mapled's preview link on the page itself; Live updates reads ○ as well: live on MapledProvider or data-mapled-live asks Mapled directly, with no route of the site's. Plain HTML installs nothing: the line is @mapled/vanilla, read from the script tag in the pages (or from a copy of the file next to them) — ⚠ when the address has no version or an older one, and before that when a page's tag has an integrity a browser won't pass: from another origin without crossorigin (a browser checks only a file that came with CORS), crossorigin="use-credentials" on jsDelivr or unpkg (their * answer fails a request with credentials, integrity or not), or a digest that isn't the file's — for a copy next to the pages, the copy's own; for a jsDelivr or unpkg address, the released file's of the version in it (another host may serve a file of its own; an address with a query isn't checked — ?meta answers with something else), which doctor knows without the network for the releases up to its own, or from node_modules/@mapled/vanilla of that version — and Generated types and Environment read ○: the fields are named in attributes, the delivery key sits in the page. doctor takes the framework from mapled.json, or works it out from package.json (no package.json and an index.html is plain HTML). Whatever the framework, a server key or a signing secret under a public prefix (VITE_…, NEXT_PUBLIC_…) is a ✗: it would ship to every visitor.

Exit code 1 when something is marked ✗; --json prints the checks for scripts and agents.

npx @mapled/cli doctor --load-config

--load-config is for a next.config doctor can't read — one that calls a plugin (withMDX(nextConfig)), imports an env check (import "./src/env.js"), sets a value in a statement or exports a function. Doctor then loads it the way next build does: the site's installed Next (loadConfig) in a process of its own, in the site's folder, and takes pageExtensions, basePath, output and whether i18n is set from the result — nothing else of the config leaves that process. This runs the site's code: next.config and every module it loads, with your rights, as next build would. Without the flag doctor runs nothing of the site, and no agent tool sets it. The process gets 20 seconds and your environment without the variables named MAPLED_* — the site's .env files still load, as Next loads them, so keep MAPLED_KEY there if the config checks for it. A config doctor reads on its own is never loaded. When the loading fails — Next (or, for next.config.ts, TypeScript) isn't installed, the config throws, it doesn't finish in time — the line stays a ⚠ with the reason; doctor never guesses the settings. Of an error the config throws doctor prints only its class, and only one of JavaScript's own (TypeError …) — the message is the site's and may quote a value of its environment; next build shows it in full.

Read the setup report

npx @mapled/cli integration report

When an AI agent set the site up, Mapled keeps the run: the plan a person approved, what was applied, the agent's closing report, and the Verification checklist the server runs — the one on the run's page in Mapled. integration report prints the last applied run:

Integration report — Dolphin Landing

Setup run  Claude Code • checks failed • applied 2h ago • checked 1h ago
Plan       “Blog with authors, a homepage and a contact form”

Verification
✓ Site reads content from Mapled        Last read 3m ago
✗ Publish webhook works                 No deliveries since the plan was applied. Send a test ping from Integrations.
✓ Preview route responds                Responds at /api/mapled/preview
⚠ Field names and help texts filled in  2 of 14 fields have no help text. Editors see it under the field.
✓ Site builds successfully              Reported by the agent
✓ No secrets in the repository          Reported by the agent
✓ MAPLED.md written                     Reported by the agent

Collections created
  Articles (articles)  collection • 6 fields • 12 records imported
  Homepage (homepage)  single • 4 fields • 1 record imported

Files changed
  app/page.tsx
  app/blog/[slug]/page.tsx

Still hardcoded
  footer copyright

1 problem, 1 warning. Fix what the checks name, then `mapled integration report --verify` runs them again.

--verify runs the server checks again first, as Run checks again does in Mapled. A newer plan that isn't applied yet is named on its own line. The report never shows a key or a token: whatever an agent wrote into its report, anything shaped like a Mapled key is masked, and a reported preview address keeps only its page. Exit code 1 when a check is marked ✗; --json for scripts. A site connected without an agent has no run — doctor checks it from the repository.

Preview the drafts

npx @mapled/cli preview
npx @mapled/cli preview articles hello-world
npx @mapled/cli preview articles hello-world --locale ru

Prints a Preview link — the one the Preview button in Mapled opens: the site in draft mode, with every saved change that isn't published yet. Name a collection and a record's slug (or id) and the link lands on the record's page, the one the pushed site manifest says shows it; a collection alone opens the page that lists it, a single its page, nothing at all the home page. Mapled makes the link after checking that the site answers and mounts its preview route; it works once, for five minutes. A plain HTML site whose pages are built with the content in them has no page for a record it wasn't built with: for such a record the command says so instead of printing a link — its page appears once the site is built with it. --locale <code> opens the page in that language — the address Mapled builds from the manifest's locales (a translated slug is the address in its language); a language the project doesn't have is refused. --open opens it in your browser instead of printing it; --json prints the link with the page, the language when one was asked, and when it expires.

Options

  • --api <origin> — a Mapled API other than https://api.mapled.io (or set MAPLED_API_URL); project link remembers it in mapled.json
  • --no-browser — print the sign-in link instead of opening a browser
  • --project <id> — the project connect and auth login open the consent on, and the one project link links
  • --input <file> — tool only: the tool's input as a JSON file
  • --wait[=<seconds>] — tool get_setup_run only: wait while the plan awaits approval (90 seconds by default, up to 600)
  • --json — machine-readable output (tools, doctor, integration report, schema diff, scan, manifest validate, forms list, preview)
  • --load-config — doctor only: let the site's installed Next load a next.config doctor can't read; runs the site's code, as next build does
  • MAPLED_CONFIG_DIR — where credentials live (default: $XDG_CONFIG_HOME/mapled or ~/.config/mapled)

Files in the repository

FileWritten byPurpose
mapled.jsonproject linkThe project id, the types path, the framework — public values only
mapled-types.tstypes generateTypeScript types of the published content, with MapledSchema
mapled/schema.jsonschema pull, types generateThe schema this site is built against — schema diff starts from it
mapled/manifest.jsonscan --write, bindings pull, your agentPages and bindings — where the code reads each collection and field
MAPLED.mdmd pull, your agentThe guide for the next agent — project, content model, bindings, working rules; your notes below the marker

Commit all five. Credentials never live in the repository.

Keywords

mapled

FAQs

Package last updated on 13 Oct 2026

Related posts