# @craftware/crafty-sdk 中文文档

规范地址：https://npmcn.com/package/%40craftware/crafty-sdk/

版本：0\.12\.0
npm 元数据更新时间：2026-10-31T20:45:38.351Z
中文译文更新时间：2026-10-31T21:40:58.317Z
软件包许可：未声明

本文由上游 README 机器翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40craftware/crafty-sdk
原文对照：https://npmcn.com/package/%40craftware/crafty-sdk/?lang=original
项目主页：https://github.com/twocare/crafty#readme
源码仓库：https://github.com/twocare/crafty

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：545 kB，147 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=20
- 运行依赖：0 个
- 首次发布：2026-02-23T20:24:26.287Z
- 最新版本发布：2026-10-31T20:45:38.182Z
- 已发布版本数：20（近 12 个月 20 个）

## 中文简介

官方JavaScript SDK用于Crafty CMS公共API

## 安装

```sh
npm install @craftware/crafty-sdk
```

也可以使用：`pnpm add @craftware/crafty-sdk`、`yarn add @craftware/crafty-sdk`、`bun add @craftware/crafty-sdk`

## README

# `@craftware/crafty-sdk`

官方JavaScript SDK用于Crafty公共API（当前本地实现）。

## AI代理技能

这个包裹运送一个代理 **技能** 它教Claude， Cursor和其他代理如何通过其MCP服务器配置Crafty站点，并使用此SDK构建它。它包含在已发布的包中 `skills/crafty-cms/`.

最快捷的安装方式，加上CLI的 `crafty-deploy` 技能,是 `npx @craftware/crafty-cli skills install` (到 `.claude/skills/`； `--global` 用于 `~/.claude/skills/`， `--dir=<path>` 其他代理商)。这些技能也可以作为压缩文件下载给克劳德。我和克劳德 [https://docs.crafty.do/skills/crafty-cms.zip](https://docs.crafty.do/skills/crafty-cms.zip).

或者在安装SDK后将其复制到项目的技能目录中：

```bash
cp -r node_modules/@craftware/crafty-sdk/skills/crafty-cms .claude/skills/
```

(使用等效技能目录为您的代理，例如： `.cursor/skills/`)。技能是自成一体的——它包括 `SKILL.md` 加上 `references/mcp-configuration.md` 和 `references/sdk-development.md`.

## 要求

-   节点 `>=20` (包引擎)
-   或者任何运行时 `fetch` 可用（浏览器、Bun、Deno等）

## 创建客户端

```ts
import { createCraftyClient } from "@craftware/crafty-sdk";

const crafty = createCraftyClient({
  baseUrl: "https://tenant.crafty.do",
  apiKey: "ENVIRONMENT_API_TOKEN",
  timeoutMs: 10_000
});
```

### 客户端配置

-   `baseUrl` （必选）：租户基础URL。附加SDK `/api/v1` 如果丢失自动。
-   `apiKey` （可选）：发送的环境API令牌 `X-API-Key`.
    -   当提供时，API从这个令牌解析活动站点。
    -   `pages.*` 请求自动使用此站点上下文（您未通过） `siteSlug`）。
-   `siteSlug` （可选）：用于公共表单提交的网站代码(`forms.submit()`）。
-   `fetch` （可选）：自定义获取实现。
-   `headers` （可选）：应用于所有请求的默认标头。
-   `timeoutMs` （可选）：所有请求的默认超时时间（ms）。仅当值大于时才应用超时 `0`.
-   `preview` （可选）：视觉编辑器预览令牌作为 `X-Crafty-Preview`． 在浏览器中忽略它（SDK读取） `?crafty_preview=` 把它藏在里面 `sessionStorage`）; 在服务器通道上 `previewFromRequest(req)`;通过 `false` 永远不发送。
-   `onPreviewInvalid` （可选）：当API应答时调用 `X-Crafty-Preview-Status: invalid` （过期或外来令牌；响应是公共内容）。
-   `locale` （可选）：为的默认区域设置 `entries.*` 和 `pages.*` (如。 `"en"`， `"pt-BR"`）. 看到 [本地化内容](https://npmcn.com/package/%40craftware/crafty-sdk/#readme-localized-content).

## 可用的资源

### `collections`

-   `crafty.collections.list(options?)`
-   `crafty.collections.get(slug, options?)`

### `entries`

-   `crafty.entries.list(collectionSlug, query?, options?)`
-   `crafty.entries.get(collectionSlug, id, options?)`

只返回已发布的条目(每个条目都带有 `status` 和 `published_at`； `sort_by: "published_at"` （按公布日期计算）。草稿只包含在预览模式中。

支持常见的 `query` 参数:

-   `sort_by` (任何可排序字段，包括集合字段，如 `title`）
-   `sort_order` （`asc` | `desc`）
-   `q` (在集合的 `string`， `text` 和 `longText` 字段;2至100个字符)
-   `per_page`
-   `page`
-   作为键/值对的附加过滤器（传递给API）

### `pages`

-   `crafty.pages.list(query?, options?)`
-   `crafty.pages.get(pageSlug, options?)`

`pages.*` 端点的作用域由解析的环境决定 `apiKey`.

`pages.get()` 返回一个包含块帮助器的页面响应：

-   `page.data` =>的raw `CraftyPage`
-   `page.blocks` => all blocks (`CraftyBlock[]`;一个非数组 `data.blocks` 被强迫 `[]`）
-   `page.block(type)` =>该类型的第一个块或 `null`
-   `page.blocksOf(type)` =>该类型的所有块(`[]` 如果没有一个)
-   `page.hasBlock(type)` = >布尔
-   `page.requireBlock(type)` =>第一个块，或抛出一个plain `Error` (不是 `CraftyError`）
-   `page.byType.<type>` =>该类型的块数组
-   `page.firstByType.<type>` =>该类型或的第一个块 `null`

### 本地化内容

当站点在Crafty中配置了区域设置时，可以在其中任何一个区域中请求条目和页面。通过 `locale` 给客户，或者每个电话 `options`：

```ts
const crafty = createCraftyClient({ baseUrl, apiKey, locale: "en" });

const posts = await crafty.entries.list("posts");                        // English
const home = await crafty.pages.get("home", { locale: "pt-BR" });        // overrides the client locale
```

每个字段都有回退：任何未翻译的内容都会返回到站点的默认区域设置中，因此部分翻译不会留下空字段。不支持的区域设置会退回到最近的区域设置(`en-US` → `en`)或默认值。反应进行 `meta.locale` （服务地点）， `meta.default_locale` 和 `meta.locales`； `get()` 反应增加 `meta.translations`项中包含有用内容的区域设置 `hreflang`）. 没有区域设置的站点会忽略 `locale` 不返回locale `meta`.

### `forms`

-   `crafty.forms.list(options?)` -列出所有表单（需要API密钥）
-   `crafty.forms.get(slug, options?)` -获得一个单一的形式（需要API密钥）
-   `crafty.forms.submit(formSlug, data, options?)` -提交一个公共表单到API密钥的环境

这个 `submit()` 方法使用不需要身份验证的公共端点。当客户端有 `apiKey`，它被发送，以便提交到达该键的环境（staging键写入staging）；没有一个，它就会投入生产。你必须提供 `siteSlug` 在提交到工作调用的客户端配置中 `submit()` 否则会抛出一个同步 `CraftyError` 使用代码 `BAD_REQUEST`：

```ts
const crafty = createCraftyClient({
  baseUrl: "https://tenant.crafty.do",
  apiKey: "ENVIRONMENT_API_TOKEN",
  siteSlug: "my-site",
});

// List forms (authenticated)
const forms = await crafty.forms.list();

// Get form details (authenticated)
const contactForm = await crafty.forms.get("contact");

// Submit a form into the API key's environment
const result = await crafty.forms.submit("contact", {
  name: "Jane Doe",
  email: "jane@example.com",
  message: "Hello!",
});
// result => { id, form_id, submitted_at }
```

### `mediaCollections`

-   `crafty.mediaCollections.list(query?, options?)`

支持 `query.include` 价值观:

-   `"stats"`
-   `"directories"`
-   支持数组形式，重复数据删除，并序列化为逗号分隔的值；原始字符串将不变地传递

### `mediaFiles`

-   `crafty.mediaFiles.list(query?, options?)`

支持常见的 `query` 参数:

-   `collection`
-   `directory_id`
-   `ttl_minutes`
-   `per_page`
-   `page`

每个文件都有 `url`， `is_public`， `expires_at` 和 `image_transform_base` (见 [图片](https://npmcn.com/package/%40craftware/crafty-sdk/#readme-images)）。

### `bookings`

网上预订（需要） `siteSlug`;网站的“Citas”标签必须打开)：

-   `crafty.bookings.config(options?)` -服务（包括时间和价格）、专业人员、工时、规则、时区、货币和付款方式； `enabled: false` 当预订取消时。
-   `crafty.bookings.availability({ service, professional?, from?, to? }, options?)` -本地每日免费时段： `{ days: [{ date, closed, slots: [{ start, time, professionals }] }] }`． 在浏览器中调用它，不要在构建时调用。
-   `crafty.checkout.start({ booking: { service, professional?, start } }, { returnUrl?, cancelUrl?, fields? })` -保持插槽并将访问者发送到托管结帐(`checkout.create` 返回会话而不重定向)。与此同时，一个插槽被占用 `SLOT_UNAVAILABLE`.
-   `crafty.bookings.get(number, token, options?)` -客户看到的预订（来自他们链接的令牌）。

导游: `skills/crafty-cms/references/online-bookings.md`.

## 图片

`crafty.image(media, options?)` 返回调整大小、重新编码的图像的URL； `crafty.srcset(media, widths, options?)` 返回一个 `srcset` 值，每个宽度一个URL。两者也都导出为 `imageUrl` 和 `imageSrcSet`. `media` 是一个 `CraftyMediaFile`,一个 `<key>_media` 对象从块或条目，或URL字符串。

```tsx
const photo = block.data.image_media;

<img
  src={crafty.image(photo, { width: 800, format: "auto" })}
  srcSet={crafty.srcset(photo, [400, 800, 1200, 1600], { format: "auto" })}
  sizes="(min-width: 768px) 50vw, 100vw"
  width={800}
  alt=""
/>
```

| 选项 | 值 |
| --- | --- |
| `width`， `height` | 整数像素，固定到1-4096 |
| `fit` | `"scale-down"`， `"contain"`， `"cover"`， `"crop"`， `"pad"` |
| `quality` | 1 - 100 |
| `format` | `"auto"` （浏览器接受时为AVIF/WebP）， `"webp"`， `"avif"`， `"jpeg"`， `"png"` |

未知的选项和值将被删除。url有这样的形式 `<image_transform_base>/cdn-cgi/image/width=800,format=auto,onerror=redirect/<source>` 并由Cloudflare Image transforms提供服务；Crafty本身从不调整图像的大小。

API返回 `image_transform_base` 只对 **公共** 栅格图像，并且仅当Crafty服务器启用图像转换时。否则（带有签名url的私有媒体、svg、pdf、关闭转换） `image()` 返回原始的 `url` 不变, `srcset()` 返回 `""`，因此浏览器退回到 `src`． 打电话总是安全的。

## llms.txt

狡猾的生成 `llms.txt` 和 `llms-full.txt` 从你发布的内容。标题、摘要、语言，以及哪些没有路由的集合被选择加入，这些都来自于admin的“llms.txt”设置屏幕 `configure_llms` MCP工具;url从可视化编辑器路由中解析。默认情况下，没有路由模式的集合将被排除在外——选择它in，让它的条目出现在索引和in中的“## Optional”下 `llms-full.txt`.

由Crafty托管的站点将这两个文件注入到站点的根目录 **下一个部署**;在开始使用此特性之前已经进行的部署不会进行追溯性更新。该网站自己的构建版本已经提供了一个文件 `llms.txt` / `llms-full.txt` 已经在部署输出中)总是获胜—Crafty只在文件丢失时添加文件。

从你自己的领域为他们服务：

```ts
// Next.js — app/llms.txt/route.ts
export const GET = crafty.llms.handler();

// Next.js — app/llms-full.txt/route.ts
export const GET = crafty.llms.handler({ full: true });

// Astro — src/pages/llms.txt.ts
export const GET = crafty.llms.handler();

// Nuxt — server/routes/llms.txt.ts
export default defineEventHandler(() => crafty.llms.handler()());
```

对于静态构建，将文件写入 `dist/` 自己在构建时，而不是依赖于部署时注入：

```ts
// e.g. a postbuild script, run after `vite build` / `astro build`
import { writeFile } from "node:fs/promises";

await writeFile("dist/llms.txt", await crafty.llms.index());
await writeFile("dist/llms-full.txt", await crafty.llms.full());
```

在网上做广告 `<head>` 与 `crafty.llms.linkTags()` （HTML字符串）或 `crafty.llms.linkTagAttrs()` (对象 `useHead` / `<Head>`）。

## 分析

谷歌标签管理器，GA4，元像素和TikTok像素id在“Analítica”管理屏幕上或与 `configure_analytics` MCP的工具。不要在站点中硬编码他们的代码片段。

-   **狡猾的托管:** 部署将加载器注入到每个HTML页面中。它还替换在构建时呈现的SDK的任何块，因此部署设置总是占上风。不需要其他任何东西。
-   **任何主机上的SSR：** 在服务器上渲染现成的HTML，以便爬虫看到它并保留第一个页面视图：

```ts
const analytics = await crafty.analytics.get();
// analytics.head  → before </head>
// analytics.body  → right after <body> (<noscript> fallbacks)
```

-   **任何主机上的客户端渲染站点：** 呼叫 `load()` 在浏览器中。它返回 `"already-loaded"` 当页面已经带有Crafty analytics (`window.__CRAFTY_ANALYTICS__` 是由部署注入的块或之前的加载设置的)，所以在Crafty Hosting上，部署获胜：

```ts
await crafty.analytics.load(); // "loaded" | "already-loaded" | "not-configured" | "unavailable"
```

客户端加载错过了不运行JavaScript的爬虫。在管理中所做的更改将显示在下一个请求中 `get()`/`load()`;在Crafty Hosting上，它们需要重新部署。

## 结构化数据（JSON-LD）

Crafty将内容转换为schema.org JSON-LD，因此谷歌可以显示丰富的结果（小时数，地址，价格，常见问题）和人工智能搜索引擎获得可靠的事实。不要手写JSON-LD：

-   网站背后的业务（类型，地址，工作时间，电话，社交资料）是在“Datos estructurados”管理屏幕上设置的 `configure_structured_data` MCP的工具。主页(`pages.get("home")`), `crafty.site.get()` 把它带进去 `structured_data`.
-   集合可以映射到 `Product`， `Article`， `BlogPosting`， `FAQPage`， `Event` 或者 `Service` (集合编辑或 `structured_data` 在 `update_collection`）. 然后，每个发布的条目都携带其JSON-LD `structured_data`.

`crafty.structuredData(...sources)` 返回 `<script type="application/ld+json">` 标签用于任何混合的页面、条目、列表响应、站点或JSON-LD对象(`""` 当没有人有时)。FAQPage对象被合并为一个，因为谷歌期望每个页面都有一个FAQPage：

```ts
const page = await crafty.pages.get("home");
const faqs = await crafty.entries.list("faq");

head += crafty.structuredData(page, faqs); // LocalBusiness + one FAQPage with every question
```

对于那些写在纸上的问题， `crafty.faqPage(block.data.items)` 构建FAQPage `{ question, answer }` 物品。 `crafty.jsonLd(...sources)` 返回对象而不是HTML `useHead` / `<Head>` 组件。

在Crafty Hosting上，部署还将业务JSON-LD添加到主页(`index.html`)，当页面还没有呈现它，所以一个网站得到LocalBusiness，即使没有帮助。

## 请求选项（每个呼叫）

每个方法都接受 `options?` 作为最后一个论点：

-   `signal`： `AbortSignal`
-   `headers`：特定于请求的报头（与客户端报头合并）
-   `timeoutMs`：覆盖该调用的客户端超时

```ts
const posts = await crafty.entries.list(
  "posts",
  { per_page: 15, page: 1, sort_by: "updated_at", sort_order: "desc" },
  { timeoutMs: 5_000 }
);
```

## 响应（当前形状）

-   `collections.list()` = > `{ data: CraftyCollection[] }`
-   `collections.get()` = > `CraftyCollection`
-   `entries.list()` = >分页 `{ data, links, meta }`
-   `entries.get()` = > `{ data: CraftyEntry }`
-   `pages.list()` = >分页 `{ data, links, meta }`
-   `pages.get()` = > `{ data: CraftyPage }` 加上上面列出的块帮助器
-   `mediaCollections.list()` = > `{ data, meta: { includes } }`
-   `mediaFiles.list()` = > `{ data, links, meta }` （`meta` 包括 `ttl_minutes`）
-   `forms.list()` = > `{ data: CraftyForm[] }`
-   `forms.get()` = > `{ data: CraftyForm }`
-   `forms.submit()` = > `{ id, form_id, submitted_at }`

## 错误

SDK抛出 `CraftyError` HTTP、超时、中止、网络和配置失败(例如no `fetch` 可在构建时实现)。

```ts
import { CraftyError } from "@craftware/crafty-sdk";

try {
  await crafty.pages.get("home");
} catch (error) {
  if (error instanceof CraftyError) {
    console.error(error.code, error.status, error.message);
    console.error(error.requestId); // if API sent x-request-id
  }
}
```

当前错误码（及其http状态映射）：

-   `NETWORK_ERROR` -网络故障，或否 `fetch` 施工时可用
-   `TIMEOUT` -请求超出 `timeoutMs`
-   `BAD_REQUEST` —HTTP 400或422
-   `UNAUTHORIZED` - HTTP 401
-   `FORBIDDEN` - HTTP 403
-   `NOT_FOUND` - HTTP 404
-   `RATE_LIMITED` - HTTP 429
-   `SLOT_UNAVAILABLE` -预订结帐时的HTTP 409：插槽同时被占用
-   `SERVER_ERROR` - HTTP 5xx
-   `UNKNOWN_ERROR` -任何其他/未映射状态

## 的端到端示例

```ts
import { createCraftyClient } from "@craftware/crafty-sdk";

const crafty = createCraftyClient({
  baseUrl: "https://tenant.crafty.do",
  apiKey: process.env.CRAFTY_API_KEY,
  siteSlug: "my-site",
  timeoutMs: 10_000
});

const collections = await crafty.collections.list();
const posts = await crafty.entries.list("posts", { per_page: 15 });
const mediaCollections = await crafty.mediaCollections.list({ include: ["stats", "directories"] });
const mediaFiles = await crafty.mediaFiles.list({ collection: "library", per_page: 24 });
const homePage = await crafty.pages.get("home");
const forms = await crafty.forms.list();
const submission = await crafty.forms.submit("contact", { name: "Jane", email: "jane@test.com" });
```

## 页面范围（通过API密钥的站点+环境）

`pages` 请求不再需要 `siteSlug` 参数。从服务器端推断出该站点 `X-API-Key` 报头（set from） `apiKey` 在 `createCraftyClient()`）。

```ts
const crafty = createCraftyClient({
  baseUrl: "https://tenant.crafty.do",
  apiKey: "ENVIRONMENT_API_TOKEN",
});

await crafty.pages.list({ per_page: 12 });

const landing = await crafty.pages.get("landing");
const processBlock = landing.block("process");
const allProcessBlocks = landing.blocksOf("process");
const processFirst = landing.firstByType.process;
const processList = landing.byType.process ?? [];
```

## 可视化编辑器

Crafty的可视化编辑器在iframe中加载您的网站，并允许编辑器单击文本或图像来更改它。三个部分使网站可编辑：

**1\. 标记可编辑元素** 与 `crafty.attrs(target, field, { label })` (也输出为 `attrs`）。 `target` 是来自API的块或条目(或 `{ kind: "block" | "entry", id }`); `field` 是字段键还是JSON路径 `json` 字段。

```tsx
<h1 {...crafty.attrs(block, "title")}>{block.data.title}</h1>
<h2 {...crafty.attrs(entry, `sections[${i}].title`, { label: "Título de la sección" })}>{section.title}</h2>
```

按钮和链接：通过 `hrefField` (可选 `hrefLabel`)，因此编辑器将目的地与文本一起编辑。单击链接上的任何位置（填充、图标、键盘激活）将选择其标记的标签，而不是跟随它。真正的 `<button>`， `<summary>` 和 `<label>` 元素保持它们自己的行为（菜单，手风琴），除非点击落在被标记的元素上：

```tsx
<a href={cta.href} className="btn">
  <span {...crafty.attrs(entry, "hero.cta.label", { label: "Texto del botón", hrefField: "hero.cta.href", hrefLabel: "Enlace del botón" })}>{cta.label}</span>
  <ArrowIcon />
</a>
```

一个链接，其唯一可编辑的值是它的目标(A) `url` 场地)被标记在 `<a>` 本身: `<a {...crafty.attrs(entry, "maps_url", { label: "Enlace del mapa" })} href={entry.maps_url}>Ver mapa</a>`． 链接目的地永远不会被实时写入页面；他们在保存草稿后显示。

通过 `hrefField` 只有当内容存储目的地时：如果网站在缺少它时退回到硬编码的href，则离开 `hrefField` 出（编辑器不能在属性中创建缺失的键） `json` 字段)。 `hrefField` 从与标签相同的目标中读取；当标签和URL位于不同的块或项中时，分别标记它们。硬编码链接文本永远不可编辑：标记 `<a>` 与 `url` 字段，或者先将文本移动到Crafty。

字符串模板的使用 `attrsHtml(target, field, options)`，返回转义后的 `data-crafty-*="…"` 对。属性总是被发出，所以静态构建会保留它们。

**2\. 携带预览令牌。** 在浏览器中什么都不需要。使用服务器渲染，每个请求读取它：

```ts
import { createCraftyClient, previewFromRequest } from "@craftware/crafty-sdk";

const crafty = createCraftyClient({ baseUrl, apiKey, preview: previewFromRequest(req) });
```

`previewFromRequest` 接受URL字符串， `URL`Web `Request`，节点请求(`req.url`)或特快专递要求(`req.query`）。

**3\. 启用网桥** 一次，在浏览器中：

```ts
import { enableVisualEditing } from "@craftware/crafty-sdk/visual-editing";

enableVisualEditing({ onRefresh: () => reloadContent() });
```

它不做任何事情，除非页面在Crafty管理iframe中带有预览令牌，所以它在生产中是安全的。即使如此，它仍然保持惰性，直到后端确认令牌（使用的响应） `X-Crafty-Preview-Status: active`) -通常是网站自己在页面加载期间的第一个Crafty请求。没有浏览器端请求携带令牌的站点（纯SSR，内容只在服务器上获取）必须通过 `client` 因此，桥梁可以通过一个轻量级请求(`pages.list({ per_page: 1 })`):

```ts
enableVisualEditing({ client: crafty });
```

如果API回答 `invalid` （过期或外来令牌），桥将保持惰性，并告诉管理员而不是激活。选项: `adminOrigins` (默认为 `https://*.crafty.do`)， `onRefresh` （重新获取而不是完全重新加载）， `page` / `entries` （服务器渲染页面显示的内容）， `touchHighlightMs`， `client` （验证令牌，而不是等待站点自己的请求）。

当桥处于活动状态时，在站点内按下的Escape将被转发给管理员（SDK 0.4.1+），因此即使焦点在iframe中，编辑器的底部表单和对话框也会关闭。网站自己处理的Escape（关闭自己的菜单或模式）必须调用 `event.preventDefault()`;然后桥就离开了。

**自定义获取层。** 如果一个站点用SDK客户端以外的东西获取Crafty(所以既不是站点自己的请求也不是 `client` 一直贯穿始终 `HttpClient`)，报告 `X-Crafty-Preview-Status` 响应头你自己，从 `@craftware/crafty-sdk/visual-editing`：

```ts
import { getPreviewStatus, notifyPreviewActive, notifyPreviewInvalid } from "@craftware/crafty-sdk/visual-editing";

const status = response.headers.get("X-Crafty-Preview-Status");
if (status === "active") notifyPreviewActive();
if (status === "invalid") notifyPreviewInvalid();
```

`getPreviewStatus()` 读取以这种方式（或SDK自己的客户端）报告的最后状态； `PREVIEW_ACTIVE_EVENT` / `PREVIEW_INVALID_EVENT` 挂起的网桥等待的事件名称。

没有捆绑者：

```html
<script src="https://cdn.jsdelivr.net/npm/@craftware/crafty-sdk@0.4/dist/crafty-visual-editing.global.js"></script>
<script>CraftyVisualEditing.enableVisualEditing();</script>
```

对于生产，固定一个确切的版本(`@0.4.1`)，并加上 `integrity="sha384-…" crossorigin="anonymous"` 使用jsdeliver为该文件显示的散列。

网站不托管在狡猾的托管必须允许管理员框架他们： `Content-Security-Policy: frame-ancestors 'self' https://<tenant>.crafty.do`.

## 笔记

-   SDK标准化 `baseUrl` 避免重复 `/api/v1`.
-   `Accept: application/json` 自动设置，除非被覆盖。
-   `src/types/generated.ts` 仍然是一个占位符，直到OpenAPI编码被连接。
-   公共 `blockTypes` 端点尚未公开；与块相关的类型目前是手工建模的。

## 常见问题

### @craftware/crafty-sdk 是什么？

官方JavaScript SDK用于Crafty CMS公共API

### 如何安装 @craftware/crafty-sdk？

在项目目录运行 npm install @craftware/crafty-sdk，或使用 pnpm add / yarn add / bun add。

### @craftware/crafty-sdk 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @craftware/crafty-sdk 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @craftware/crafty-sdk 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=20。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @craftware/crafty-sdk 可以商用吗？

@craftware/crafty-sdk 使用 未声明 许可。package\.json 没有声明许可证。没有许可证的代码默认保留全部权利，商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括，不构成法律意见，请以许可原文为准。

### @craftware/crafty-sdk 还在维护吗？

最新版本 0\.12\.0 发布于 2026/11/01（2 天前）；至今共发布 20 个版本，其中过去 12 个月发布了 20 个；首次发布于 2026/02/24。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

