@latellu/atlas-agent-skills 0.1.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.
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "latellu",
3
+ "description": "Official Latellu plugins for Claude Code, including the Atlas CMS plugin (MCP server + agent skill).",
4
+ "owner": {
5
+ "name": "Latellu",
6
+ "url": "https://github.com/tenriajeng"
7
+ },
8
+ "plugins": [
9
+ {
10
+ "name": "atlas",
11
+ "source": {
12
+ "source": "npm",
13
+ "package": "@latellu/atlas-agent-skills"
14
+ },
15
+ "description": "Atlas CMS for Claude Code: the atlas-cms skill (SDK, CLI and MCP usage guidance) plus the bundled Atlas MCP server for reading and writing content from AI agents."
16
+ }
17
+ ]
18
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "atlas",
3
+ "version": "0.1.0",
4
+ "description": "Work with Atlas CMS from Claude Code: bundles the Atlas MCP server (read/write content via tools) and the atlas-cms skill that teaches agents when and how to use the MCP tools, the @latellu/atlas-sdk delivery/management clients, and the @latellu/atlas-cli type generator.",
5
+ "author": {
6
+ "name": "Latellu",
7
+ "url": "https://github.com/tenriajeng"
8
+ },
9
+ "homepage": "https://docs.atlas.latellu.com/docs/claude-code",
10
+ "repository": "https://github.com/tenriajeng/atlas-agent-skills",
11
+ "license": "MIT",
12
+ "keywords": ["atlas", "cms", "headless-cms", "mcp", "sdk"]
13
+ }
package/.mcp.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "mcpServers": {
3
+ "atlas": {
4
+ "command": "npx",
5
+ "args": ["-y", "@latellu/atlas-mcp"],
6
+ "env": {
7
+ "ATLAS_LIVE_API_KEY": "${ATLAS_LIVE_API_KEY}",
8
+ "ATLAS_MGMT_API_KEY": "${ATLAS_MGMT_API_KEY}",
9
+ "ATLAS_API_URL": "${ATLAS_API_URL:-https://api.atlas.latellu.com}",
10
+ "MCP_ALLOWED_UPLOAD_PATHS": "${MCP_ALLOWED_UPLOAD_PATHS:-}"
11
+ }
12
+ }
13
+ }
14
+ }
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Latellu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # Atlas Agent Skills
2
+
3
+ Agent skill + MCP configuration for **[Atlas CMS](https://docs.atlas.latellu.com)** — teaches AI coding agents when and how to use the Atlas MCP tools, the [`@latellu/atlas-sdk`](https://www.npmjs.com/package/@latellu/atlas-sdk) delivery/management clients, and the [`@latellu/atlas-cli`](https://www.npmjs.com/package/@latellu/atlas-cli) type generator.
4
+
5
+ The skill itself is plain markdown ([`skills/atlas-cms/SKILL.md`](skills/atlas-cms/SKILL.md) plus per-surface references), so it works with **any** AI coding agent. This repo also packages it as a Claude Code plugin with the Atlas MCP server pre-wired.
6
+
7
+ ## Claude Code
8
+
9
+ ```bash
10
+ claude plugin marketplace add tenriajeng/atlas-agent-skills
11
+ claude plugin install atlas@latellu
12
+ ```
13
+
14
+ Then set your credentials in the shell where you run `claude`:
15
+
16
+ ```bash
17
+ export ATLAS_LIVE_API_KEY="atlas_live_xxx..." # read tools
18
+ export ATLAS_MGMT_API_KEY="atlas_mgmt_xxx..." # write tools
19
+ ```
20
+
21
+ Create keys at [cms.atlas.latellu.com/dashboard/api-keys](https://cms.atlas.latellu.com/dashboard/api-keys). Full guide: [docs.atlas.latellu.com/docs/claude-code](https://docs.atlas.latellu.com/docs/claude-code).
22
+
23
+ ## Other agents (Cursor, Codex, opencode, Gemini CLI, ...)
24
+
25
+ ```bash
26
+ npm install -D @latellu/atlas-agent-skills
27
+ ```
28
+
29
+ Then point your agent at the skill (via `AGENTS.md`, Cursor rules, etc.) and register the MCP server — see [`adapters/other-agents.md`](adapters/other-agents.md).
30
+
31
+ ## What's inside
32
+
33
+ | Path | Purpose |
34
+ | --- | --- |
35
+ | `skills/atlas-cms/SKILL.md` | Router: shared Atlas concepts + decision table (MCP vs SDK vs CLI) |
36
+ | `skills/atlas-cms/references/` | Per-surface guides: MCP tools, SDK delivery, SDK management, CLI typegen |
37
+ | `.mcp.json` | Atlas MCP server config (used by the Claude Code plugin) |
38
+ | `.claude-plugin/` | Claude Code plugin + marketplace manifests |
39
+ | `adapters/other-agents.md` | Setup for non-Claude agents |
40
+
41
+ ## Releasing
42
+
43
+ Bump `version` in `package.json` (and `.claude-plugin/plugin.json`), tag `vX.Y.Z`, push the tag — CI publishes to npm. The Claude Code marketplace resolves the plugin from npm, so users pick up the new version on `claude plugin marketplace update latellu`.
44
+
45
+ ## License
46
+
47
+ [MIT](LICENSE)
@@ -0,0 +1,66 @@
1
+ # Using the Atlas skill outside Claude Code
2
+
3
+ The skill is plain markdown (`skills/atlas-cms/SKILL.md` + `references/`), so any AI
4
+ coding agent can consume it. Install the package into your project, then point your
5
+ agent at it.
6
+
7
+ ```bash
8
+ npm install -D @latellu/atlas-agent-skills
9
+ ```
10
+
11
+ ## AGENTS.md-based agents (Codex, opencode, Gemini CLI, Jules, Amp, ...)
12
+
13
+ Add to your project's `AGENTS.md`:
14
+
15
+ ```markdown
16
+ ## Atlas CMS
17
+
18
+ When a task involves Atlas CMS (entries, pages, media, @latellu/atlas-sdk,
19
+ @latellu/atlas-cli, atlas_live_/atlas_mgmt_ keys, or api.atlas.latellu.com), first read
20
+ node_modules/@latellu/atlas-agent-skills/skills/atlas-cms/SKILL.md and follow its
21
+ routing table — it links to detailed references per surface (MCP, SDK read, SDK write,
22
+ CLI type generation). Do not guess Atlas API shapes from memory.
23
+ ```
24
+
25
+ ## Cursor
26
+
27
+ Create `.cursor/rules/atlas-cms.mdc`:
28
+
29
+ ```markdown
30
+ ---
31
+ description: Atlas CMS integration guidance (SDK, CLI, MCP)
32
+ alwaysApply: false
33
+ ---
34
+
35
+ When working with Atlas CMS, read
36
+ node_modules/@latellu/atlas-agent-skills/skills/atlas-cms/SKILL.md and the reference
37
+ file it routes you to.
38
+ ```
39
+
40
+ ## MCP server (any MCP-capable agent)
41
+
42
+ The Atlas MCP server is a separate npm package (`@latellu/atlas-mcp`, stdio). Register it
43
+ with your agent's MCP configuration:
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "atlas": {
49
+ "command": "npx",
50
+ "args": ["-y", "@latellu/atlas-mcp"],
51
+ "env": {
52
+ "ATLAS_LIVE_API_KEY": "atlas_live_xxx...",
53
+ "ATLAS_MGMT_API_KEY": "atlas_mgmt_xxx..."
54
+ }
55
+ }
56
+ }
57
+ }
58
+ ```
59
+
60
+ - Cursor: `~/.cursor/mcp.json` or `.cursor/mcp.json`
61
+ - Codex CLI: `~/.codex/config.toml` (`[mcp_servers.atlas]`)
62
+ - opencode: `opencode.json` (`"mcp"` section)
63
+ - Others: consult your agent's MCP docs; the command/env above are the same everywhere.
64
+
65
+ See [docs.atlas.latellu.com/docs/mcp](https://docs.atlas.latellu.com/docs/mcp) for the
66
+ tool catalog and key setup.
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@latellu/atlas-agent-skills",
3
+ "version": "0.1.0",
4
+ "description": "Agent skill + MCP configuration for Atlas CMS. Works as a Claude Code plugin and as plain markdown guidance for any AI coding agent (Cursor, Codex, opencode, Gemini CLI, ...).",
5
+ "license": "MIT",
6
+ "author": "Latellu (https://github.com/tenriajeng)",
7
+ "homepage": "https://docs.atlas.latellu.com/docs/claude-code",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/tenriajeng/atlas-agent-skills.git"
11
+ },
12
+ "keywords": ["atlas", "cms", "headless-cms", "mcp", "agent", "skill", "claude-code"],
13
+ "files": [
14
+ ".claude-plugin",
15
+ ".mcp.json",
16
+ "skills",
17
+ "adapters",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ }
24
+ }
@@ -0,0 +1,55 @@
1
+ ---
2
+ name: atlas-cms
3
+ description: Use when integrating with or operating Atlas CMS - fetching entries, pages, blocks or media into a frontend, generating TypeScript types from a workspace schema, writing/publishing/scheduling content programmatically, uploading media, or operating content through the Atlas MCP tools (list_entries, create_entry, publish_page, etc.). Trigger on mentions of Atlas CMS, @latellu/atlas-sdk, @latellu/atlas-cli, atlas_live_/atlas_mgmt_ API keys, or api.atlas.latellu.com.
4
+ ---
5
+
6
+ # Atlas CMS
7
+
8
+ A router, not a knowledge dump. Read the shared concepts below (always), then load
9
+ **only** the reference file for the surface you are actually using.
10
+
11
+ ## Shared concepts
12
+
13
+ **Content model.** A *workspace* contains *content types* (schemas). A content type has
14
+ *entries* (structured records with a `slug`, a `status`, and a `data` field map). A
15
+ workspace also has *pages*: a page has SEO metadata and an ordered tree of *blocks*
16
+ (`{ id, type, position, data }`). Localized content is delivered as base-locale data plus
17
+ a `translations` array; readers merge a requested locale field-by-field with fallback to
18
+ the base locale.
19
+
20
+ **Two API key classes**, both sent as the `X-API-Key` header (never `Authorization`):
21
+
22
+ | Key prefix | Plane | Base path | Can do |
23
+ |---|---|---|---|
24
+ | `atlas_live_*` | Delivery (read) | `/api/v1/public` | Read published entries, pages, media, schema |
25
+ | `atlas_mgmt_*` | Management (write) | `/api/v1/manage` | Create/update/publish/delete content, upload media |
26
+
27
+ - Keys are minted in the dashboard: `cms.atlas.latellu.com/dashboard/api-keys`.
28
+ - Default base URL: `https://api.atlas.latellu.com` (override only for self-hosted).
29
+ - **Workspace scoping is implicit in the key.** There is no workspace parameter anywhere;
30
+ a key reaches exactly one workspace.
31
+ - Management keys act as a service actor bound to the RBAC permissions of the person who
32
+ created them, further limited by scopes: `content:write`, `content:publish`,
33
+ `media:write`. A missing scope returns 403 on that specific operation.
34
+ - Production `atlas_live_*` keys only ever see **published** content. Draft content is
35
+ not reachable through them, regardless of query parameters.
36
+
37
+ **Safety rule:** never ship an `atlas_mgmt_*` key or the management client in
38
+ browser-delivered code. Management usage is server-side only (API routes, scripts, CI).
39
+
40
+ ## Which surface do I use?
41
+
42
+ | You are... | Use | Read |
43
+ |---|---|---|
44
+ | Building a frontend that renders Atlas content | SDK delivery client | [references/sdk-delivery.md](references/sdk-delivery.md) |
45
+ | Wanting typed content interfaces for the SDK | CLI type generator | [references/cli-typegen.md](references/cli-typegen.md) |
46
+ | Writing content from your own code (scripts, migrations, backends) | SDK management client | [references/sdk-management.md](references/sdk-management.md) |
47
+ | Operating content via MCP tools in this session (`list_entries`, `create_entry`, ...) | Atlas MCP server | [references/mcp.md](references/mcp.md) |
48
+
49
+ Do **not** read references you don't need. If a task spans surfaces (e.g. "generate
50
+ types, then build the page"), route each sub-task to its own single reference.
51
+
52
+ ## Extending
53
+
54
+ Deep API detail lives at https://docs.atlas.latellu.com — link there rather than
55
+ duplicating endpoint documentation into this skill.
@@ -0,0 +1,51 @@
1
+ # CLI type generation
2
+
3
+ Generates TypeScript interfaces for a workspace from `/api/v1/public/schema`.
4
+
5
+ ```bash
6
+ npx @latellu/atlas-cli generate --api-key=atlas_live_xxx --output=./lib/atlas.types.ts
7
+ # or, installed globally, the binary is named atlas-cms (not atlas-cli):
8
+ atlas-cms generate --api-key=atlas_live_xxx
9
+ ```
10
+
11
+ | Flag | Env fallback | Default |
12
+ |---|---|---|
13
+ | `--api-key` | `ATLAS_API_KEY` | (required) |
14
+ | `--url` | `ATLAS_API_URL` | `https://api.atlas.latellu.com` |
15
+ | `--output` | `ATLAS_OUTPUT` | `./src/atlas.types.ts` |
16
+
17
+ Flags win over env vars. If `--output` doesn't end in `.ts` it is treated as a directory
18
+ and `atlas.types.ts` is appended. The workspace is derived server-side from the key.
19
+
20
+ ## Output anatomy
21
+
22
+ - One `export interface <PascalCaseSlug>` per content type, plus
23
+ `export type Locale = "en" | "ja" | ...` from the workspace locales.
24
+ - A registry mapping slugs to interfaces — feed it to the SDK for full typing:
25
+
26
+ ```ts
27
+ import { createClient } from "@latellu/atlas-sdk";
28
+ import type { AtlasContentTypes } from "./atlas.types";
29
+
30
+ const atlas = createClient<AtlasContentTypes>({ url, apiKey });
31
+ const article = await atlas.entries("news-article").get("hello"); // data is typed
32
+ ```
33
+
34
+ ## Type semantics (what the generated types do and don't promise)
35
+
36
+ - `select` fields with options → string-literal unions (`"News" | "Event"`).
37
+ - `number` → `number`, `boolean` → `boolean`; `text`/`textarea`/`richtext`/`date` → `string`.
38
+ - **`image`, `relation`, `content_type_reference` → plain `string`** — the raw media ID or
39
+ entry slug, never a resolved object. Resolve manually via `atlas.media.get(id)` /
40
+ `atlas.entries(type).get(slug)`.
41
+ - **Localizable fields are only marked with a `// localizable` comment**; the value is
42
+ typed as the base-locale scalar (no per-locale map in this release).
43
+ - Optional (`?`) mirrors non-required fields; non-identifier field names are quoted.
44
+ - A content type with zero fields renders as `{ [key: string]: unknown }`; unknown field
45
+ types degrade to `unknown` instead of failing codegen.
46
+
47
+ ## Rules
48
+
49
+ - The file is generated (`// Generated by @latellu/atlas-cli — DO NOT EDIT BY HAND.`) —
50
+ never hand-edit; **regenerate after every schema change** in the dashboard.
51
+ - A 401 means the API key is wrong or revoked (the error says so); check `--api-key`.
@@ -0,0 +1,63 @@
1
+ # Atlas MCP tools
2
+
3
+ The plugin bundles the `@latellu/atlas-mcp` server (stdio). Read tools need
4
+ `ATLAS_LIVE_API_KEY`; write tools need `ATLAS_MGMT_API_KEY`. With only one key set, the
5
+ other half of the tools fails client-side naming the missing variable. If neither is set
6
+ the server exits at startup. `ATLAS_API_URL` overrides the base URL for self-hosted.
7
+
8
+ ## Tool catalog
9
+
10
+ **Schema & discovery** (live key)
11
+ - `get_workspace_schema` — workspace meta (slug, locales, default locale) + all content types.
12
+ - `list_content_types` / `get_content_type(content_type)` — convenience views over the schema.
13
+
14
+ **Entries** (read: live key; write: mgmt key)
15
+ - `list_entries(content_type, page?, limit?, status?)`
16
+ - `get_entry(content_type, slug)`
17
+ - `create_entry(content_type, data, slug?)` — creates a **draft**; slug auto-generated if omitted.
18
+ - `update_entry(content_type, slug, data)`
19
+ - `publish_entry` / `unpublish_entry` / `archive_entry` / `duplicate_entry` / `delete_entry` — all `(content_type, slug)`.
20
+
21
+ **Pages** (read: live; write: mgmt)
22
+ - `list_pages(page?, limit?)` / `get_page(slug)`
23
+ - `create_page(title, slug?, blocks?, seo?)` — creates a draft. `seo` accepts `title`/`description` only.
24
+ - `update_page(slug, title?, blocks?, seo?)` / `publish_page(slug)` / `delete_page(slug)`
25
+
26
+ **Media**
27
+ - `get_media(id)` (live key)
28
+ - `upload_media(file_path, folder?, alt_text?)` (mgmt key) — reads a local file and uploads it.
29
+
30
+ ## Recommended workflow for writes
31
+
32
+ 1. Call `get_content_type` (or `get_workspace_schema`) first to learn the exact field
33
+ names and types. `data` is an unvalidated map — the backend rejects bad shapes with a
34
+ 400, and per-field validation detail is **not** surfaced through MCP (you only see
35
+ `Atlas API error: 400 - validation failed`).
36
+ 2. `create_entry` / `update_entry` with a `data` object matching the schema.
37
+ 3. `publish_entry` when ready (requires the `content:publish` scope on the key).
38
+
39
+ ## Gotchas
40
+
41
+ - **`update_entry` is a full replace** of `data`, not a patch. Read the entry first,
42
+ merge your change, then send the complete object (read-modify-write).
43
+ - **Page lifecycle is asymmetric**: there is no `unpublish_page`, `archive_page`, or
44
+ `duplicate_page` tool. There are also no bulk, schedule, or block-reorder tools, and no
45
+ `delete_media`/`update_media`. For those, use the dashboard or the SDK management
46
+ client (see `sdk-management.md`).
47
+ - **`get_page` returns each block's `data` as a JSON-encoded string**, not an object.
48
+ Parse it before reading fields — otherwise blocks look empty.
49
+ - **`get_entry` resolves by slug alone**; its `content_type` argument is accepted but not
50
+ sent to the backend. Don't rely on it to disambiguate slugs shared across types.
51
+ - **Drafts are invisible to live keys.** `list_entries(status="draft")` with a production
52
+ key returns nothing — that is key scoping, not an empty workspace.
53
+ - **Uploads are validated server-side only**: max 10 MB; MIME must be `image/*`,
54
+ `video/*`, or `application/pdf`. MIME is inferred from the file extension; unknown
55
+ extensions become `application/octet-stream` and are always rejected (e.g. `.svg`,
56
+ `.heic`). Check size and extension before calling `upload_media`. If
57
+ `MCP_ALLOWED_UPLOAD_PATHS` is set, `file_path` must live under one of those directories.
58
+ - **No client-side retry on 429.** On `Atlas API error: 429 - ...`, back off
59
+ (exponentially) and retry yourself.
60
+ - **`delete_entry` / `delete_page` return plain text** ("Entry 'x' deleted successfully"),
61
+ unlike every other tool, which returns JSON.
62
+ - Every write automatically sends a fresh `Idempotency-Key` header per call; identical
63
+ retries you issue as *new tool calls* are new operations, not deduplicated.
@@ -0,0 +1,82 @@
1
+ # SDK delivery client (read)
2
+
3
+ ```bash
4
+ npm install @latellu/atlas-sdk # Node 18+ (uses global fetch)
5
+ ```
6
+
7
+ ## Canonical setup (Next.js thin adapter)
8
+
9
+ Keep one server-only adapter, e.g. `lib/atlas.ts`:
10
+
11
+ ```ts
12
+ import "server-only";
13
+ import { createClient } from "@latellu/atlas-sdk";
14
+ import type { AtlasContentTypes } from "./atlas.types"; // generated — see cli-typegen.md
15
+
16
+ // The SDK sets NO cache directive. In Next.js you must decide caching yourself
17
+ // via a custom fetchImpl, or Next's defaults for un-annotated fetch apply.
18
+ const noStoreFetch: typeof fetch = (input, init) =>
19
+ fetch(input, { ...init, cache: "no-store" }); // or next: { revalidate: 60 }
20
+
21
+ export const atlas = createClient<AtlasContentTypes>({
22
+ url: process.env.ATLAS_BASE_URL ?? "", // https://api.atlas.latellu.com
23
+ apiKey: process.env.ATLAS_API_KEY ?? "", // atlas_live_...
24
+ fetchImpl: noStoreFetch,
25
+ });
26
+ ```
27
+
28
+ With `no-store`, pair route files with `export const dynamic = "force-dynamic"` so
29
+ dashboard edits appear immediately. The SDK reads no env vars itself — `url`/`apiKey`
30
+ must be passed explicitly. `createClient` throws synchronously if either is missing.
31
+
32
+ ## Surface
33
+
34
+ ```ts
35
+ const { items, total, page, pageSize } =
36
+ await atlas.entries("news-article").list({ locale: "en", page: 1, limit: 20, sort: "created_at:desc" });
37
+
38
+ const entry = await atlas.entries("news-article").get("hello-world", { locale: "en" }); // null on 404
39
+ // entry: { id, slug, status, published_at, data } — data is already parsed to an object
40
+
41
+ const pages = await atlas.pages.list({ locale: "en", page: 1, limit: 20 }); // summaries, no blocks
42
+ const pageDetail = await atlas.pages.get("home", { locale: "en" }); // null on 404
43
+ // pageDetail: { id, slug, status, seo, blocks: [{ id, type, position, data }] }
44
+
45
+ const asset = await atlas.media.get(mediaId); // null on 404
46
+ const raw = await atlas.raw.get<T>("/some/path"); // escape hatch for unwrapped /api/v1/public paths
47
+ ```
48
+
49
+ Semantics the SDK handles for you (don't re-implement):
50
+ - `entry.data`, page `seo`, and block `data` arrive from the API as JSON strings — the
51
+ SDK parses them.
52
+ - Locale merge is field-by-field: translated fields overlay base data; untranslated
53
+ fields fall back to the base locale.
54
+ - Page blocks are always returned sorted by `position`, regardless of API order.
55
+
56
+ Errors: non-404 failures throw `AtlasError` (`{ status?, code?, message }`). `get()`
57
+ methods return `null` only for 404.
58
+
59
+ ## Rendering blocks
60
+
61
+ Dispatch on `block.type` with an explicit fallback so new block types added in the
62
+ dashboard never crash the site:
63
+
64
+ ```tsx
65
+ export function BlockRenderer({ blocks }: { blocks: AtlasBlock[] }) {
66
+ return blocks.map((block) => {
67
+ switch (block.type) {
68
+ case "hero": return <HeroBlock key={block.id} data={block.data} />;
69
+ case "faq": return <FaqBlock key={block.id} data={block.data} />;
70
+ default: return null; // or an <UnknownBlock /> placeholder
71
+ }
72
+ });
73
+ }
74
+ ```
75
+
76
+ ## Limits
77
+
78
+ - Draft preview is not yet supported by the SDK — delivery keys see published content only.
79
+ - There is no `search` or `schema` namespace on the client; schema introspection is the
80
+ CLI's job (`cli-typegen.md`).
81
+ - `image` / `relation` / `content_type_reference` field values are raw string IDs/slugs —
82
+ resolve with `atlas.media.get(id)` or `atlas.entries(type).get(slug)`.
@@ -0,0 +1,60 @@
1
+ # SDK management client (write)
2
+
3
+ Server-side only. Never import this module or expose an `atlas_mgmt_*` key in
4
+ browser-bundled code.
5
+
6
+ ```ts
7
+ import { createManagementClient, AtlasError } from "@latellu/atlas-sdk/management";
8
+
9
+ const client = createManagementClient<AtlasContentTypes>({
10
+ url: process.env.ATLAS_BASE_URL ?? "", // https://api.atlas.latellu.com
11
+ token: process.env.ATLAS_MGMT_KEY ?? "", // MUST start with atlas_mgmt_
12
+ });
13
+ ```
14
+
15
+ `createManagementClient` throws `ManagementConfigError` **synchronously** if the token
16
+ doesn't start with `atlas_mgmt_` (e.g. you passed a live key) — wrap construction in
17
+ try/catch, not just the calls. Requests go to `/api/v1/manage` with `X-API-Key`.
18
+
19
+ ## Surface
20
+
21
+ ```ts
22
+ const w = client.entries("news-article");
23
+ await w.create({ slug: "hello", data: { title: "Hello" } });
24
+ await w.update("hello", { data: { title: "Hello v2" } });
25
+ await w.publish("hello"); // needs content:publish scope
26
+ await w.unpublish("hello");
27
+ await w.archive("hello");
28
+ await w.schedule("hello", "2026-08-01T09:00:00Z");
29
+ await w.duplicate("hello");
30
+ await w.delete("hello");
31
+ await w.bulk([{ op: "publish", id: "..." }, ...]);
32
+
33
+ await client.pages.create({ slug: "landing", /* ... */ });
34
+ await client.pages.update("landing", { /* ... */ });
35
+ await client.pages.publish("landing"); // also: unpublish, archive, schedule, delete
36
+ await client.pages.blocksReorder("landing", ["block-id-1", "block-id-2"]);
37
+
38
+ const asset = await client.media.upload(fileBlob, { alt: "Team photo" }); // multipart
39
+ await client.media.delete(asset.id);
40
+ ```
41
+
42
+ Every write accepts an optional trailing `{ idempotencyKey: "..." }` — pass a stable key
43
+ when a retry must not double-create/double-publish.
44
+
45
+ ## Built-ins
46
+
47
+ - **429s are retried automatically** (exponential backoff from 200 ms, up to 3 retries).
48
+ Not configurable.
49
+ - **Validation errors carry per-field detail** (unlike MCP): `AtlasError.errors` is
50
+ `{ field, message }[]` on 400s — surface it to users instead of the generic message.
51
+ - Scopes: `content:write` covers create/update/delete/duplicate; `content:publish` covers
52
+ publish/unpublish/archive/schedule; `media:write` covers uploads. Missing scope → 403.
53
+
54
+ ## Effect-native variant
55
+
56
+ `@latellu/atlas-sdk/management/effect` exports `makeManagementClient` with the same shape
57
+ where every method returns `Effect.Effect<T, AtlasError>`. The plain Promise client above
58
+ never bundles `effect`. If you run Effects yourself, note `Effect.runPromise` rejects
59
+ with a `FiberFailure` wrapper — recover the original `AtlasError` via
60
+ `Runtime.isFiberFailure` + `Cause.squash` (the Promise surface already does this for you).