@latellu/atlas-agent-skills 0.5.2 → 0.6.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/plugin.json +8 -2
- package/README.md +19 -2
- package/adapters/other-agents.md +8 -2
- package/package.json +10 -2
- package/skills/atlas-cms/SKILL.md +25 -8
- package/skills/atlas-cms/references/authoring.md +3 -1
- package/skills/atlas-cms/references/mcp.md +39 -10
- package/skills/atlas-cms/references/sdk-management.md +71 -15
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "atlas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
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
5
|
"author": {
|
|
6
6
|
"name": "Latellu",
|
|
@@ -9,5 +9,11 @@
|
|
|
9
9
|
"homepage": "https://docs.atlas.latellu.com/docs/agent-skill",
|
|
10
10
|
"repository": "https://github.com/tenriajeng/atlas-agent-skills",
|
|
11
11
|
"license": "MIT",
|
|
12
|
-
"keywords": [
|
|
12
|
+
"keywords": [
|
|
13
|
+
"atlas",
|
|
14
|
+
"cms",
|
|
15
|
+
"headless-cms",
|
|
16
|
+
"mcp",
|
|
17
|
+
"sdk"
|
|
18
|
+
]
|
|
13
19
|
}
|
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Agent skill + MCP configuration for **[Atlas CMS](https://docs.atlas.latellu.com
|
|
|
4
4
|
|
|
5
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
6
|
|
|
7
|
+
**New to MCP?** MCP (Model Context Protocol) is just the plug-in standard that lets your AI agent call a tool directly — installing "the Atlas MCP server" gives your agent tools like "list entries" or "publish page" it can call on its own, instead of you copy-pasting API calls for it.
|
|
8
|
+
|
|
7
9
|
## Claude Code
|
|
8
10
|
|
|
9
11
|
```bash
|
|
@@ -11,14 +13,29 @@ claude plugin marketplace add tenriajeng/atlas-agent-skills
|
|
|
11
13
|
claude plugin install atlas@latellu
|
|
12
14
|
```
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
### Get your API keys
|
|
17
|
+
|
|
18
|
+
Atlas uses two separate keys so a read-only integration can never accidentally write:
|
|
19
|
+
|
|
20
|
+
- **`atlas_live_...`** — read-only. Fetches published content (blog posts, pages, media). Use this if your agent only needs to *display* content.
|
|
21
|
+
- **`atlas_mgmt_...`** — read/write. Lets your agent create, edit, and publish content. Only create this if you want your agent to make changes for you.
|
|
22
|
+
|
|
23
|
+
To create them: go to [cms.atlas.latellu.com/dashboard/api-keys](https://cms.atlas.latellu.com/dashboard/api-keys), log in to (or create) your workspace, click **New API key**, pick the key type above, and copy the value shown — it's only shown once.
|
|
24
|
+
|
|
25
|
+
Then set them in the shell where you run `claude`:
|
|
15
26
|
|
|
16
27
|
```bash
|
|
17
28
|
export ATLAS_LIVE_API_KEY="atlas_live_xxx..." # read tools
|
|
18
29
|
export ATLAS_MGMT_API_KEY="atlas_mgmt_xxx..." # write tools
|
|
19
30
|
```
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
Add these two `export` lines to your shell profile (`~/.zshrc` or `~/.bashrc`) if you want them available in every new terminal, not just the current session.
|
|
33
|
+
|
|
34
|
+
### Verify it worked
|
|
35
|
+
|
|
36
|
+
Start `claude` in your project and ask it something like *"list the content types in my Atlas workspace"*. If it calls an Atlas tool and returns a real answer, setup is complete. If it says it has no Atlas tools available, double check the plugin installed (`claude plugin install atlas@latellu` again) and that both env vars are set in that same shell (`echo $ATLAS_LIVE_API_KEY`).
|
|
37
|
+
|
|
38
|
+
Full guide: [docs.atlas.latellu.com/docs/agent-skill](https://docs.atlas.latellu.com/docs/agent-skill).
|
|
22
39
|
|
|
23
40
|
## Other agents (Cursor, Codex, opencode, Gemini CLI, ...)
|
|
24
41
|
|
package/adapters/other-agents.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Using the Atlas skill outside Claude Code
|
|
2
2
|
|
|
3
|
+
> New to Atlas or MCP? Read the [README](../README.md) first — it explains what MCP is
|
|
4
|
+
> and the difference between `atlas_live_` (read-only) and `atlas_mgmt_` (read/write)
|
|
5
|
+
> keys before you get here.
|
|
6
|
+
|
|
3
7
|
The skill is plain markdown (`skills/atlas-cms/SKILL.md` + `references/`), so any AI
|
|
4
8
|
coding agent can consume it. Install the package into your project, then point your
|
|
5
9
|
agent at it.
|
|
@@ -39,8 +43,10 @@ file it routes you to.
|
|
|
39
43
|
|
|
40
44
|
## MCP server (any MCP-capable agent)
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
"Registering an MCP server" means telling your agent to launch this process and use the
|
|
47
|
+
tools it exposes (list entries, publish page, etc.) — it's the same two API keys as
|
|
48
|
+
above, just read by a different process. The Atlas MCP server is a separate npm package
|
|
49
|
+
(`@latellu/atlas-mcp`, stdio). Register it with your agent's MCP configuration:
|
|
44
50
|
|
|
45
51
|
```json
|
|
46
52
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@latellu/atlas-agent-skills",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
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
5
|
"license": "MIT",
|
|
6
6
|
"author": "Latellu (https://github.com/tenriajeng)",
|
|
@@ -9,7 +9,15 @@
|
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "git+https://github.com/tenriajeng/atlas-agent-skills.git"
|
|
11
11
|
},
|
|
12
|
-
"keywords": [
|
|
12
|
+
"keywords": [
|
|
13
|
+
"atlas",
|
|
14
|
+
"cms",
|
|
15
|
+
"headless-cms",
|
|
16
|
+
"mcp",
|
|
17
|
+
"agent",
|
|
18
|
+
"skill",
|
|
19
|
+
"claude-code"
|
|
20
|
+
],
|
|
13
21
|
"files": [
|
|
14
22
|
".claude-plugin",
|
|
15
23
|
".mcp.json",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: atlas-cms
|
|
3
|
-
description: Use
|
|
3
|
+
description: Use for any task that touches Atlas CMS, however casually phrased - fetching entries, pages, blocks or media into a frontend, generating/gen-ing TypeScript types from a workspace schema, writing/publishing/scheduling/pushing content programmatically, uploading media, or operating content through the Atlas MCP tools (list_entries, create_entry, publish_page, etc.). Always trigger on any mention of Atlas CMS, @latellu/atlas-sdk, @latellu/atlas-cli, atlas_live_/atlas_mgmt_ API keys, or api.atlas.latellu.com - including quick/informal asks like "gen the types for our repo" or "push some content into atlas real quick" - rather than answering from general knowledge, which risks hallucinating wrong package names or endpoints.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Atlas CMS
|
|
@@ -43,8 +43,10 @@ Two key classes, both sent as the `X-API-Key` header (never `Authorization: Bear
|
|
|
43
43
|
drafts.
|
|
44
44
|
- Management keys act as a service actor bound to the RBAC permissions of whoever
|
|
45
45
|
created them, further limited by scopes: `content:write` (create/update/delete),
|
|
46
|
-
`content:publish` (publish/unpublish/archive/schedule), `media:write` (uploads)
|
|
47
|
-
A missing scope → 403 on that operation
|
|
46
|
+
`content:publish` (publish/unpublish/archive/schedule), `media:write` (uploads),
|
|
47
|
+
`schema:write` (content-type/field authoring). A missing scope → 403 on that operation
|
|
48
|
+
only. Scopes don't imply one another, and keys minted before a scope existed don't
|
|
49
|
+
gain it — if schema tools 403, the key needs `schema:write` added in the dashboard.
|
|
48
50
|
|
|
49
51
|
**Safety rule:** never ship an `atlas_mgmt_*` key or the management client in
|
|
50
52
|
browser-delivered code. Management usage is server-side only (API routes, scripts, CI).
|
|
@@ -84,16 +86,31 @@ types, then build the page"), route each sub-task to its own single reference.
|
|
|
84
86
|
Choosing between MCP and the management SDK for writes: MCP is for operating content
|
|
85
87
|
interactively in an agent session (one-off edits, content entry, audits); the SDK is for
|
|
86
88
|
code that outlives the session (migrations, sync jobs, backends) and for structured per-field error objects.
|
|
89
|
+
This is about scale and recoverability, not run count: if the task touches more than a
|
|
90
|
+
handful of entries, needs to survive partial failure and be safely re-run, or needs
|
|
91
|
+
structured per-field error handling, that's the SDK management client — even if you'll
|
|
92
|
+
only run it once. A 200-entry backfill script is an SDK job; MCP's "one-off" framing
|
|
93
|
+
means a few manual edits typed out in conversation, not a bulk script.
|
|
87
94
|
|
|
88
95
|
## Hard capability boundaries (don't go looking for these)
|
|
89
96
|
|
|
90
|
-
- **Schema
|
|
91
|
-
|
|
92
|
-
|
|
97
|
+
- **Schema authoring stops at the shape of stored data.** With a `schema:write` key you
|
|
98
|
+
CAN create content types, add fields, edit a field's label/filterable/sortable, reorder
|
|
99
|
+
fields, and delete a content type that still has zero entries. You CANNOT rename a
|
|
100
|
+
field, change its `field_type`, toggle `localizable`/`is_unique`/`required`, edit
|
|
101
|
+
`validation`, delete a field, or toggle `is_block` — those rewrite or invalidate data
|
|
102
|
+
already in entries and are dashboard-only. Don't hunt for a workaround (deleting and
|
|
103
|
+
re-adding a field is NOT one — it drops the data): call `plan_field_change`, then give
|
|
104
|
+
the user its numbers and dashboard link. Available in MCP only; the SDK has no
|
|
105
|
+
schema methods.
|
|
93
106
|
- **No field-value queries.** The public list endpoints filter by `type`/`locale` only —
|
|
94
107
|
"all articles where category = X" means paging through and filtering client-side.
|
|
95
|
-
- **The management plane
|
|
96
|
-
|
|
108
|
+
- **The management plane has no entry/page/media reads.** Its only GETs are the
|
|
109
|
+
content-type/field ones (`/manage/content-types...`, which exist because the public
|
|
110
|
+
schema omits field ids). Reading content still needs a live key, so a
|
|
111
|
+
write-then-verify flow needs both keys.
|
|
112
|
+
- **A live key never sees drafts** and there is no `status` parameter to change that —
|
|
113
|
+
visibility follows the key's environment (`production` vs `preview`).
|
|
97
114
|
|
|
98
115
|
Deep API detail lives at https://docs.atlas.latellu.com — link there rather than
|
|
99
116
|
duplicating endpoint documentation into this skill.
|
|
@@ -109,7 +109,9 @@ The real write shape (`POST/PUT /pages`) differs from what the reading API shows
|
|
|
109
109
|
`get_workspace_schema` tool) returns each content type's `id`, `is_block`, and field
|
|
110
110
|
list. Use the `id` of an `is_block: true` type as `block_type_id`, and compose the
|
|
111
111
|
block's `data` against its fields exactly like an entry. (Older backends omit
|
|
112
|
-
`id`/`is_block` from the schema — there, block-type UUIDs are dashboard-only.)
|
|
112
|
+
`id`/`is_block` from the schema — there, block-type UUIDs are dashboard-only.) A new
|
|
113
|
+
block type can be created with MCP `create_content_type` (`is_block: true`) given a
|
|
114
|
+
`schema:write` key; only flipping `is_block` on an *existing* type is dashboard-only.
|
|
113
115
|
- MCP `create_page`/`update_page` caveats (≥ 1.1.0): `title` is an alias mapped to
|
|
114
116
|
`seo.title` (on update, `seo` is replaced wholesale — title alone drops the other seo
|
|
115
117
|
fields), and `seo_translations`/per-block `translations` are regular arguments.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Atlas MCP tools
|
|
2
2
|
|
|
3
3
|
The `@latellu/atlas-mcp` server (stdio) exposes Atlas content operations as agent tools.
|
|
4
|
-
This file describes `@latellu/atlas-mcp` **≥ 1.
|
|
5
|
-
`npx -y`, so it always runs the latest).
|
|
4
|
+
This file describes `@latellu/atlas-mcp` **≥ 1.5.0** (the bundled `.mcp.json` uses
|
|
5
|
+
`npx -y`, so it always runs the latest). Servers below 1.5.0 lack the schema-authoring
|
|
6
|
+
tools and the `locale` parameter on reads; below 1.2.0 they also lack `translations`,
|
|
6
7
|
`seo_translations`, per-field error detail, the schema `id`/`is_block` fields, page/entry lifecycle tools (unpublish/archive/duplicate/schedule), bulk operations, reorder tools, media alt-text editing, list meta+cursor pagination, automatic 429 retry, and pre-parsed block data in get_page.
|
|
7
8
|
|
|
8
9
|
**Environment** — read tools need `ATLAS_LIVE_API_KEY`, write tools need
|
|
@@ -23,14 +24,27 @@ errors).
|
|
|
23
24
|
|---|---|---|
|
|
24
25
|
| `get_workspace_schema` | — | `{ workspace: { slug, name, locales, default_locale }, content_types: [...] }` — each content type carries `id` and `is_block`; types with `is_block: true` are the valid `block_type_id` values for page blocks |
|
|
25
26
|
| `list_content_types` | — | `content_types` array (client-side view of the schema) |
|
|
26
|
-
| `get_content_type` | `content_type` | one content type with its full field list; error text if the slug doesn't exist |
|
|
27
|
+
| `get_content_type` | `content_type` | one content type with its full field list; error text if the slug doesn't exist. **No field ids** — use `list_fields` for those |
|
|
28
|
+
| `list_fields` | `content_type` | field definitions **with `id`** (mgmt key). The ids are what `update_field` and `reorder_fields` address |
|
|
29
|
+
|
|
30
|
+
### Schema authoring — mgmt key with `schema:write`
|
|
31
|
+
|
|
32
|
+
| Tool | Params | Notes |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `create_content_type` | `name`, `slug?`, `is_block?`, `orderable?` | slug auto-generated from name when omitted; `is_block: true` makes it usable as a page block |
|
|
35
|
+
| `update_content_type` | `content_type`, `name?`, `description?`, `icon?`, `display_field?`, `orderable?` | metadata only; slug is immutable and `is_block` is not accepted |
|
|
36
|
+
| `delete_content_type` | `content_type` | **409 while any entry exists** — so it only undoes an empty type you just created |
|
|
37
|
+
| `add_field` | `content_type`, `name`, `label`, `field_type`, `required?`, `is_unique?`, `localizable?`, `filterable?`, `sortable?`, `options?`, `validation?` | `options` is a plain array (encoded for you). Pick `field_type` carefully — it cannot be changed later via API |
|
|
38
|
+
| `update_field` | `content_type`, `field_name` \| `field_id`, `label?`, `filterable?`, `sortable?` | presentation only; `field_name` is resolved to an id for you |
|
|
39
|
+
| `reorder_fields` | `content_type`, `order` | complete ordered list of field **ids** (from `list_fields`), not a patch |
|
|
40
|
+
| `plan_field_change` | `content_type`, `field_name` \| `field_id` | read-only impact report + `dashboard_url`; use for any change the API refuses |
|
|
27
41
|
|
|
28
42
|
### Entries — read: live key · write: mgmt key
|
|
29
43
|
|
|
30
44
|
| Tool | Params | Notes |
|
|
31
45
|
|---|---|---|
|
|
32
|
-
| `list_entries` | `content_type`, `page?`=1, `limit?`=20, `
|
|
33
|
-
| `get_entry` | `content_type`, `slug` | resolves by `slug` alone — `content_type` is accepted but errors clearly if the slug belongs to a different content type |
|
|
46
|
+
| `list_entries` | `content_type`, `page?`=1, `limit?`=20, `cursor?`, `fields?`, `locale?`, `sort?`, `search?` | **No `status` param** — visibility follows the key's environment (`production` = published only, `preview` = also draft/scheduled). Returns `{ data, meta }` where `meta` has `total_data`, `total_pages` (offset mode) or `next_cursor` (cursor mode). Use `cursor: meta.next_cursor` to page past the 1000-page limit. |
|
|
47
|
+
| `get_entry` | `content_type`, `slug`, `fields?`, `locale?` | resolves by `slug` alone — `content_type` is accepted but errors clearly if the slug belongs to a different content type |
|
|
34
48
|
| `create_entry` | `content_type`, `data`, `slug?`, `translations?` | creates a **draft**; slug auto-generated when omitted; `translations` = `{locale: {data: {...}}}` (required+localizable fields go HERE) |
|
|
35
49
|
| `update_entry` | `content_type`, `slug`, `data`, `translations?` | **full replace** of `data` (and per-locale `translations`), not a patch |
|
|
36
50
|
| `publish_entry` / `unpublish_entry` / `archive_entry` | `content_type`, `slug` | need the `content:publish` scope |
|
|
@@ -90,17 +104,32 @@ The MCP manage plane is now fully covered. These gaps remain:
|
|
|
90
104
|
must read-modify-write yourself: `get_entry` → merge your changes → `update_entry`.
|
|
91
105
|
(The management SDK's `update` is a full replace too — this is API semantics, not an
|
|
92
106
|
MCP quirk.)
|
|
93
|
-
- **Schema
|
|
94
|
-
|
|
95
|
-
|
|
107
|
+
- **Schema changes that touch stored data are dashboard-only.** Creating types and fields
|
|
108
|
+
IS available (see "Schema authoring" above, needs `schema:write`). What is not: renaming
|
|
109
|
+
a field, changing its `field_type`, toggling `localizable`/`is_unique`/`required`,
|
|
110
|
+
editing `validation`, deleting a field, deleting a type that has entries, toggling
|
|
111
|
+
`is_block`. For those, call `plan_field_change` and hand the user its affected-entry
|
|
112
|
+
counts plus `dashboard_url`. **Delete-and-re-add is not a workaround** — it discards the
|
|
113
|
+
data the rename was meant to preserve.
|
|
114
|
+
- **Schema authoring is MCP-only.** `@latellu/atlas-sdk`'s management client has no
|
|
115
|
+
content-type methods; don't look for them there.
|
|
96
116
|
|
|
97
117
|
Before composing any `data` payload, read `authoring.md` — it defines the exact value
|
|
98
118
|
format per field type (richtext = Tiptap HTML, relation/image = UUIDs, etc.).
|
|
99
119
|
|
|
100
120
|
## Gotchas
|
|
101
121
|
|
|
102
|
-
- **Drafts are invisible to live keys** — `list_entries
|
|
103
|
-
empty
|
|
122
|
+
- **Drafts are invisible to live keys** — an empty `list_entries` means key scoping, not
|
|
123
|
+
an empty workspace. There is no `status` parameter to override it; you need a `preview`
|
|
124
|
+
environment key.
|
|
125
|
+
- **Reads are base-locale unless you pass `locale`** — write a translation, then
|
|
126
|
+
`get_entry` without `locale`, and it looks like the write silently failed. Pass the
|
|
127
|
+
locale to verify your own writes.
|
|
128
|
+
- **Field ids are not in the schema** — `get_workspace_schema` / `get_content_type` omit
|
|
129
|
+
them by design. `update_field` accepts `field_name` so you rarely need them, but
|
|
130
|
+
`reorder_fields` needs real ids from `list_fields`.
|
|
131
|
+
- **A freshly created content type appears immediately**, but only because schema writes
|
|
132
|
+
bust the delivery response cache server-side; don't add sleeps or retries around it.
|
|
104
133
|
- **`get_entry` looks up by slug alone** (slug-global, `content_type` is not sent to the
|
|
105
134
|
API) — but if the entry found belongs to a different content type than you requested,
|
|
106
135
|
the tool returns a clear mismatch error instead of the wrong entry.
|
|
@@ -5,22 +5,73 @@
|
|
|
5
5
|
browser-bundled code.
|
|
6
6
|
|
|
7
7
|
```ts
|
|
8
|
-
import { createManagementClient, AtlasError } from "@latellu/atlas-sdk/management";
|
|
8
|
+
import { createManagementClient, AtlasError, ManagementConfigError } from "@latellu/atlas-sdk/management";
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
// ✅ CORRECT: Wrap construction in try/catch for config errors
|
|
11
|
+
let client: ManagementClient<AtlasContentTypes> | null = null;
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
client = createManagementClient<AtlasContentTypes>({
|
|
15
|
+
url: process.env.ATLAS_BASE_URL ?? "https://api.atlas.latellu.com",
|
|
16
|
+
token: process.env.ATLAS_MGMT_KEY ?? "", // MUST start with atlas_mgmt_
|
|
17
|
+
});
|
|
18
|
+
} catch (e) {
|
|
19
|
+
if (e instanceof ManagementConfigError) {
|
|
20
|
+
// Configuration error — fail fast at startup
|
|
21
|
+
console.error("Failed to initialize Atlas:", e.message);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
throw e;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!client) {
|
|
28
|
+
throw new Error("Atlas client not initialized");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Now client is safely initialized and ready to use
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Common error scenario:**
|
|
35
|
+
If someone accidentally uses an `atlas_live_` delivery key instead of `atlas_mgmt_`, they'll see:
|
|
36
|
+
```
|
|
37
|
+
ManagementConfigError: management client received an atlas_live_ delivery key, which is read-only.
|
|
38
|
+
You need an atlas_mgmt_ management key for writes.
|
|
39
|
+
Create one at cms.atlas.latellu.com/dashboard/api-keys
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Validate config without creating the client:**
|
|
43
|
+
```ts
|
|
44
|
+
import { validateManagementConfig } from "@latellu/atlas-sdk/management";
|
|
45
|
+
|
|
46
|
+
const validation = validateManagementConfig({
|
|
47
|
+
url: process.env.ATLAS_BASE_URL ?? "",
|
|
48
|
+
token: process.env.ATLAS_MGMT_KEY ?? "",
|
|
13
49
|
});
|
|
50
|
+
|
|
51
|
+
if (!validation.valid) {
|
|
52
|
+
console.error("Invalid Atlas config:", validation.error);
|
|
53
|
+
if (validation.keyType === 'atlas_live_') {
|
|
54
|
+
console.error("→ Use an atlas_mgmt_ key (management key) instead");
|
|
55
|
+
}
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
14
58
|
```
|
|
15
59
|
|
|
16
60
|
Construction rules:
|
|
17
|
-
- The token prefix is validated **synchronously
|
|
18
|
-
|
|
19
|
-
|
|
61
|
+
- The token prefix is validated **synchronously at creation time**, not during API calls.
|
|
62
|
+
A non-`atlas_mgmt_` token (e.g., `atlas_live_` or malformed) throws `ManagementConfigError`
|
|
63
|
+
immediately with a descriptive message identifying what went wrong.
|
|
64
|
+
- **Critical:** Wrap `createManagementClient(...)` in a try/catch block, **not just the
|
|
65
|
+
awaited calls**. Config errors are synchronous exceptions; request errors are rejected promises.
|
|
66
|
+
- If you accidentally use an `atlas_live_` delivery key, the error message will explicitly
|
|
67
|
+
state that read keys cannot be used for management operations.
|
|
20
68
|
- Requests carry `X-API-Key` and go to `${url}/api/v1/manage/*`.
|
|
21
69
|
- The key's scopes gate operations: `content:write` → create/update/delete/duplicate,
|
|
22
70
|
`content:publish` → publish/unpublish/archive/schedule, `media:write` → uploads.
|
|
23
|
-
Missing scope = 403 on that call only.
|
|
71
|
+
Missing scope = 403 on that call only.
|
|
72
|
+
- **No schema methods.** Content-type and field authoring (the `schema:write` scope) is
|
|
73
|
+
exposed through the MCP server only — this SDK has no equivalent. Reach for MCP, or call
|
|
74
|
+
`/api/v1/manage/content-types` directly, when you need to build schema from code. Every write is audit-attributed to the key's
|
|
24
75
|
creator and bounded by their RBAC — a key can never do more than its creator could in
|
|
25
76
|
the dashboard.
|
|
26
77
|
|
|
@@ -35,15 +86,20 @@ await w.create({ slug: "hello", data: { title: "Hello", body: "..." } });
|
|
|
35
86
|
// Field value formats (richtext = Tiptap HTML, relation/image = UUIDs, ...):
|
|
36
87
|
// see authoring.md before composing `data`.
|
|
37
88
|
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
89
|
+
// update() is a FULL REPLACE of `data` (and of each locale in `translations`) — it does
|
|
90
|
+
// NOT merge. To change only one field, you MUST read the current entry first and spread
|
|
91
|
+
// its existing data/translations, or you will silently drop every other field:
|
|
92
|
+
const readClient = createDeliveryClient<AtlasContentTypes>({ url, token: deliveryKey });
|
|
93
|
+
const current = await readClient.entries("news-article").get("hello");
|
|
43
94
|
await w.update("hello", {
|
|
44
|
-
data:
|
|
45
|
-
translations: {
|
|
95
|
+
data: { ...current.data, category: "updated-category" },
|
|
96
|
+
translations: {
|
|
97
|
+
...current.translations,
|
|
98
|
+
de: { data: { ...current.translations?.de?.data, title: "Hallo Welt" } },
|
|
99
|
+
},
|
|
46
100
|
});
|
|
101
|
+
// UpdateEntryInput = { slug?, data?, translations?, ... } — required+localizable fields
|
|
102
|
+
// MUST live under translations[locale].data, not in the base `data` object.
|
|
47
103
|
|
|
48
104
|
await w.publish("hello"); // PATCH .../publish
|
|
49
105
|
await w.unpublish("hello"); // PATCH .../unpublish
|