@latellu/atlas-agent-skills 0.5.1 → 0.5.3

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.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "atlas",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
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",
7
7
  "url": "https://github.com/tenriajeng"
8
8
  },
9
- "homepage": "https://docs.atlas.latellu.com/docs/claude-code",
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
12
  "keywords": ["atlas", "cms", "headless-cms", "mcp", "sdk"]
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
- Then set your credentials in the shell where you run `claude`:
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
- 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).
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
 
@@ -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
- The Atlas MCP server is a separate npm package (`@latellu/atlas-mcp`, stdio). Register it
43
- with your agent's MCP configuration:
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,10 +1,10 @@
1
1
  {
2
2
  "name": "@latellu/atlas-agent-skills",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
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)",
7
- "homepage": "https://docs.atlas.latellu.com/docs/claude-code",
7
+ "homepage": "https://docs.atlas.latellu.com/docs/agent-skill",
8
8
  "repository": {
9
9
  "type": "git",
10
10
  "url": "git+https://github.com/tenriajeng/atlas-agent-skills.git"
@@ -1,6 +1,6 @@
1
1
  ---
2
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.
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
@@ -84,6 +84,11 @@ types, then build the page"), route each sub-task to its own single reference.
84
84
  Choosing between MCP and the management SDK for writes: MCP is for operating content
85
85
  interactively in an agent session (one-off edits, content entry, audits); the SDK is for
86
86
  code that outlives the session (migrations, sync jobs, backends) and for structured per-field error objects.
87
+ This is about scale and recoverability, not run count: if the task touches more than a
88
+ handful of entries, needs to survive partial failure and be safely re-run, or needs
89
+ structured per-field error handling, that's the SDK management client — even if you'll
90
+ only run it once. A 200-entry backfill script is an SDK job; MCP's "one-off" framing
91
+ means a few manual edits typed out in conversation, not a bulk script.
87
92
 
88
93
  ## Hard capability boundaries (don't go looking for these)
89
94
 
@@ -5,18 +5,66 @@
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
- const client = createManagementClient<AtlasContentTypes>({
11
- url: process.env.ATLAS_BASE_URL ?? "", // https://api.atlas.latellu.com
12
- token: process.env.ATLAS_MGMT_KEY ?? "", // MUST start with atlas_mgmt_
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**: a non-`atlas_mgmt_` token (e.g. a
18
- live key) throws `ManagementConfigError` immediately — wrap
19
- `createManagementClient(...)` in try/catch, not just the awaited calls.
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.
@@ -35,15 +83,20 @@ await w.create({ slug: "hello", data: { title: "Hello", body: "..." } });
35
83
  // Field value formats (richtext = Tiptap HTML, relation/image = UUIDs, ...):
36
84
  // see authoring.md before composing `data`.
37
85
 
38
- await w.update("hello", { data: { title: "Hello v2", body: "..." } });
39
- // UpdateEntryInput = { slug?, data?, ... } send the FULL data object you want stored
40
-
41
- // Localized content: pass a `translations` map as an extra key (accepted by the API
42
- // on both create and update; required+localizable fields MUST live here, not in data):
86
+ // update() is a FULL REPLACE of `data` (and of each locale in `translations`) it does
87
+ // NOT merge. To change only one field, you MUST read the current entry first and spread
88
+ // its existing data/translations, or you will silently drop every other field:
89
+ const readClient = createDeliveryClient<AtlasContentTypes>({ url, token: deliveryKey });
90
+ const current = await readClient.entries("news-article").get("hello");
43
91
  await w.update("hello", {
44
- data: fullBaseData,
45
- translations: { de: { data: { title: "Hallo" } }, ja: { data: { title: "こんにちは" } } },
92
+ data: { ...current.data, category: "updated-category" },
93
+ translations: {
94
+ ...current.translations,
95
+ de: { data: { ...current.translations?.de?.data, title: "Hallo Welt" } },
96
+ },
46
97
  });
98
+ // UpdateEntryInput = { slug?, data?, translations?, ... } — required+localizable fields
99
+ // MUST live under translations[locale].data, not in the base `data` object.
47
100
 
48
101
  await w.publish("hello"); // PATCH .../publish
49
102
  await w.unpublish("hello"); // PATCH .../unpublish