@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.
- package/.claude-plugin/marketplace.json +18 -0
- package/.claude-plugin/plugin.json +13 -0
- package/.mcp.json +14 -0
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/adapters/other-agents.md +66 -0
- package/package.json +24 -0
- package/skills/atlas-cms/SKILL.md +55 -0
- package/skills/atlas-cms/references/cli-typegen.md +51 -0
- package/skills/atlas-cms/references/mcp.md +63 -0
- package/skills/atlas-cms/references/sdk-delivery.md +82 -0
- package/skills/atlas-cms/references/sdk-management.md +60 -0
|
@@ -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).
|