@cargo-ai/cdk

中文文档
1.0.140发布于 2026/11/09UNLICENSEDTypeScriptES Module近 30 天下载 10.1万

Cargo projects — define every Cargo resource (connectors, models, plays, tools, agents, MCP servers, folders, files, workers, apps, sending domains, mailboxes) in code and deploy it declaratively: plan, apply, refresh, import and destroy over a repo of de

原始使用文档
文档目录13 节
  1. Requirements
  2. Installation
  3. Project layout
  4. Quick start
  5. Typed config
  6. Defining resources
  7. Referencing handles inside a workflow body
  8. Commands
  9. Cookbooks
  10. Handing off to a coding agent
  11. Running from a coding agent
  12. State & drift
  13. Documentation

@cargo-ai/cdk — Cargo projects

Define your entire Cargo workspace in code — connectors, models, plays, tools, agents, MCP servers, context, segments, capacities, territories, folders, files, workers, apps, sending domains and mailboxes — and deploy it declaratively, the same way you'd manage cloud infrastructure with Pulumi or the AWS CDK. A project is the folder that declares it. For a complete overview, see the project docs.

Requirements

  • Node.js 22.x
  • A Cargo API token (create in your workspace under Settings → API)

Installation

@cargo-ai/cdk is the library behind Cargo projects: the define* builders you import in your resource files, and the engine that deploys them. Drive it with the Cargo CLI:

npm install -g @cargo-ai/cli          # the Cargo CLI — `cargo-ai login` + `cargo-ai project`
cargo-ai login --oauth                # browser sign-in
# …or, for CI / non-interactive:
export CARGO_API_TOKEN=<your-api-token>

Login writes ~/.config/cargo-ai/credentials.json, which every project command reads (along with the CARGO_API_TOKEN / CARGO_WORKSPACE_UUID env vars). Run the commands as cargo-ai project <command>, or as npm run <script> for the everyday ones (a scaffolded project wires up types / info / plan / deploy).

The package also ships a project-local cargo-cdk bin (npx @cargo-ai/cdk <command>), and cargo-ai cdk is kept as an alias of cargo-ai project, so older scripts keep running. Don't install the package globally: a global cargo-cdk on your PATH would clash with the AWS CDK.

Project layout

A project is the folder holding cargo.json, the way package.json marks a Node package:

my-workspace/
  cargo.json          { "resources": "infra" }
  cargo.state.json    the pointer to this project's deploy state — commit it
  infra/              the resource files; every .ts in it is loaded
  outputs/            executions (runs and batches), when the project has any

resources names the folder holding the .ts resource files, relative to cargo.json; an optional executions array replaces the default outputs/, and a * in it matches any folder name ("outputs/*/executions"). cargo.state.json and outputs/ sit beside cargo.json. Every command walks up from where it runs to the nearest cargo.json and works from there — and stops, naming the line that adds one, when there is none, rather than guessing the project from what the folders hold. A repository can hold several projects, one cargo.json each.

Quick start

Scaffold a project:

cargo-ai project init my-workspace
# pick a cookbook in the wizard, or afterwards:
#   cargo-ai project add cookbook/<name>
cd my-workspace && npm install

init scaffolds the Cargo template (getcargohq/cargo-manifest): the context, cadence and evals layers, a cargo.json at the root, and the resource files in infra/. Run commands from anywhere inside the project — they find cargo.json and load infra/ from it.

Or add a resource file yourself — importing a file is registration, there's no manifest to maintain:

// my-workspace/infra/contacts.ts
import { defineConnector, defineModel, secret } from "@cargo-ai/cdk";

// `secret()` reads the value at deploy time and keeps it out of state and the
// content hash — use it for credentials. The config shape is per-integration
// (HubSpot takes a Private App token or an OAuth refresh token); run
// `cargo-ai project types` to have it type-checked (see Typed config below).
export const hubspot = defineConnector("hubspot", {
  integration: "hubspot",
  config: { method: "privateApp", accessToken: secret("HUBSPOT_API_KEY") },
});

// Pass the connector handle as `connector` — the model depends on it, so the
// deploy creates the connector first and injects its dataset uuid.
export const contacts = defineModel("contacts", {
  connector: hubspot,
  extractSlug: "fetchRecords",
});
export HUBSPOT_API_KEY=...        # secret() reads this at deploy time
npm run info                      # what the project declares — no API calls
npm run plan                      # diff the code against the deploy state
npm run deploy                    # create everything, record it in the deploy state

(The scaffolded package.json wires npm run types / info / plan / deploy. Or run any command directly with cargo-ai project <command>.)

Re-running deploy only changes what changed; an unchanged workspace is a no-op.

Typed config

defineConnector and defineModel config are typed against your workspace's actual integration schemas. Generate the types once:

npm run types          # or: cargo-ai project types

They land in .cargo-ai/. Add it to your tsconfig include with an explicit glob — a bare .cargo-ai (a dot-dir) is ignored by TypeScript:

// tsconfig.json
"include": ["*.ts", ".cargo-ai/**/*.d.ts"]

After that, config is checked against the integration's schema — e.g. HubSpot is a discriminated union, so the editor completes method and requires the matching credential, and secret() is accepted wherever a credential is expected. A uuid-reference config field (e.g. an extractor's modelUuid / relatedModelUuid) is typed as In<string>, so you can pass a resource handle's uuid directly (modelUuid: accounts.uuid) to wire the dependency — no cast — or a literal uuid string. defineAgent's languageModel is typed the same way: pass a defineConnector handle and it is the union of models that integration's connectors expose. Re-run npm run types after adding or changing workspace integrations, or when a connector's models change.

Typing is a bonus, never a gate: an integration you haven't synced (or a custom one) falls back to a loose record or a plain string, and a connectorRef carries no integration to type against. The API rejects a slug the connector does not expose when the release is written, the same way it rejects a connector config that does not match the integration.

Defining resources

Every Cargo resource has a define* builder that returns a handle. Wire resources together by passing one handle to another — the dependency graph is just your variable graph. Pass the handle directly (or xxRef("uuid") for a resource you didn't define in code); no .uuid at the call site. Where a reference takes per-call options, wrap it as { ref, …options }.

import { defineAgent, defineTool, defineWorkflow, sendEmail } from "@cargo-ai/cdk";

const flow = defineWorkflow("enrich", { input, output }, ({ input, ai }) => …);
export const enrich = defineTool("enrich", { workflow: flow, emojiSlug: "mag" });

export const sdr = defineAgent("sdr", {
  connector: openai,                              // the LLM connector (or connectorRef("…"))
  languageModel: "gpt-4o",
  systemPrompt: "Qualify inbound leads.",
  // everything the agent can call or read — one array, kind inferred per handle
  uses: [
    { ref: contacts, readOnly: true },            // a data model (handle + options)
    enrich,                                        // a tool (bare handle)
    hunter.actions.findEmail,                      // an action off a connector handle
    sendEmail,                                     // a native action (same binding a workflow body calls)
  ],
});

Referencing handles inside a workflow body

A defineWorkflow body is parsed from its source and never executed, so it can't read closure values at runtime. The loader closes that gap: it scans each body's free variables and threads the ones bound in the file (imports, module consts) through the SDK's imports option automatically. So a build-time handle can be used bare inside a body — no boilerplate:

const accounts = defineModel("accounts", { … });

const flow = defineWorkflow(
  "tam",
  { input, output },
  ({ input, model }) =>
    // `accounts` is a module-level handle — the loader injects it for you.
    model.upsert({ modelUuid: accounts.uuid, matchingColumnSlug: "domain", matchingValue: input.domain }),
);

The handle's uuid is a deferred token, so the model is ordered first on deploy and its real uuid is substituted in. Constants work the same way (inlined as values). Two things to know: a handle's token can't be used inside a computed expression (only as a direct config value — it isn't a string until deploy), and a workflow defined in a file that's reached only via a transitive import (not scanned as a top-level resource file) bypasses this pass — the SDK then throws an error telling you to pass its explicit imports option in that file.

See Defining Resources for every builder (connectors, models, plays, tools, agents, MCP servers, context, segments, capacities, territories, folders, files, workers, apps, domains) and the slug rules.

Native object and unified models (kind: "native") and the context repository (defineContext) have their own guides: Object models, Unification, and Context.

Commands

In a scaffolded project, the everyday commands are npm run scripts:

npm run types     # generate per-workspace types for typed config
npm run info      # offline: what the project declares, and any diagnostics
npm run plan      # diff the code against this project's deploy state
npm run deploy    # create/update resources, record them in the state

Two of those answer different questions and it is worth knowing which is which. plan answers what will deploy do — its unit is the change, and it exits non-zero so CI can gate on it. info answers what is here — its unit is the resource, including the file that declared it, and it never fails. Reach for plan before deploying and info when a resource is not behaving the way its file reads.

Run with no command, the project-local bin (npx @cargo-ai/cdk) does whichever of those makes sense: info inside a project, init outside one.

deploy is the only command that spends money: a + create domain:… line registers a domain against workspace credits and isn't refundable (and destroy cancels it). Read the plan before confirming.

Run any command — including the ones without a script — with cargo-ai project:

cargo-ai project init                  # ask what to scaffold (a directory, and the repo or a cookbook)
cargo-ai project init <directory>      # scaffold the Cargo template
cargo-ai project info                  # the resolved project: every resource, its file, its state, diagnostics
cargo-ai project graph                 # the live workspace's resource graph, as a Mermaid flowchart (--json for CargoGraph)
cargo-ai project graph --project --plan # offline: this project's graph, coloured by what deploy would change
cargo-ai project deploy --prune        # also delete resources removed from code
cargo-ai project deploy --refresh      # re-read live resources, re-apply out-of-band changes
cargo-ai project refresh               # read-only: report resources that drifted from code
cargo-ai project import <id> <uuid>    # bind an existing live resource into state
cargo-ai project rollback              # restore the deploy state from the pre-deploy snapshot
cargo-ai project destroy --target <id> # tear down one resource
cargo-ai project destroy --all         # tear down everything in state

Common flags: --dir <path>, --yes (skip the prompt — required in CI), --json, and --force (steal a stale state lock). See Deploying.

Cookbooks

Cookbooks are worked project examples — a TAM pipeline, account scoring, CRM enrichment — published as folders in getcargohq/gtm-skills. add installs one into a project you already have, as a sibling of what is there:

cargo-ai project cookbook list             # every cookbook
cargo-ai project cookbook search scoring   # by name, job, or the resources it declares
cargo-ai project cookbook view tam-building # what it deploys, what it will ask you for, how to adapt it
cargo-ai project add cookbook/tam-building # copy it in

Copying is all add does. What follows — reconciling the example with what the project already declares, adapting it to the real CRM and fields, the inputs it wants looked up before any of them are asked — is the cookbook's own procedure, and add prints it as a checklist (or hands it to a coding agent) rather than ending on deploy.

A cookbook's infra/ lands under infra/<name>/, its scripts/ under scripts/<name>/, and everything else — its SKILL.md, references/, evals/ — under .claude/skills/<name>/, beside the skills the repo already ships, and again under .agents/skills/<name>/:

infra/crm-enrichment/index.ts
scripts/crm-enrichment/seed.ts
.claude/skills/crm-enrichment/SKILL.md
.claude/skills/crm-enrichment/references/run.md
.claude/skills/crm-enrichment/evals/acceptance.md
.agents/skills/crm-enrichment/SKILL.md
.agents/skills/crm-enrichment/references/run.md
.agents/skills/crm-enrichment/evals/acceptance.md

The skill half is written twice because no one directory is read by every agent: Claude Code discovers project skills only under .claude/skills/, while Cursor, Codex and Gemini read .agents/skills/. They are copies rather than symlinks, which git does not carry reliably across a Windows checkout — so if you edit a cookbook's procedure, edit both. The infra/ and scripts/ halves are written once: resources are loaded by path rather than discovered, and a second copy would register every resource twice; scripts live at the repo root so the loader never imports them.

Each piece stays namespaced by cookbook, so two cookbooks that each bring a CRM connector stay out of each other's way, and the references stay next to the procedure that cites them. The loader scans every .ts under the resources folder, so infra/<name>/ is discovered like any other directory.

add never touches your package.json or tsconfig.json, reports every file that already exists and skips it (--overwrite replaces instead), runs check afterwards so a duplicate slug surfaces immediately, and never deploys. Use it rather than init --force, which replaces the project shell while the deploy state survives — leaving the next plan diffing a live workspace against code nobody wrote.

Handing off to a coding agent

When a person runs init or add and a coding agent (Claude Code, Cursor, Codex, Gemini CLI) is on PATH, the last question is whether to open it on the new project:

How would you like to continue?
❯ Open Claude Code   build it with the cargo-project skill loaded
  Exit               print the next steps

It opens in the project directory with a brief describing what was just created: which commands this project has, that the cargo-project skill is the authority on the define* builders, and that deploy spends credits and must not be run unasked. After add, the brief points at the cookbook's own .claude/skills/<name>/SKILL.md and orders the work left over it — start at Adapt, not at the copy step that just ran; look these inputs up, ask for those — rather than restating a procedure the cookbook already ships.

Running from a coding agent

Every command that could ask a question refuses to when there is no one to answer: no TTY, CI=true, or a coding-agent harness (Claude Code, Cursor, Codex, …). Instead of hanging on a prompt it exits 2 and prints the command to re-run with the missing argument supplied. Passing the argument — as every scripted invocation already does — is always the non-interactive path.

State & drift

A project's deploy state is the link from your code to the resources Cargo created. It lives in your workspace, and the repo commits a pointer to it:

{ "stateUuid": "8f2c…" }

init creates the state and writes that cargo.state.json, so the pointer is in the scaffold commit. Commit it — without the uuid a fresh checkout cannot find its state, and a deploy will not quietly make a replacement (that would orphan everything the old one tracks). The state records only uuids, hashes and outputs — never secret values.

One state per project, at most 10 live states per workspace:

cargo-ai project state list            # every state here, and which one this project is on
cargo-ai project state create          # create one for this project and write the pointer
cargo-ai project state bind <uuid>     # repoint at an existing one
cargo-ai project state remove <uuid>   # drop a state (does not delete live resources)
cargo-ai project state connect         # plan PRs and deploy pushes from its GitHub repository
cargo-ai project state disconnect      # stop deploying from GitHub (the state stays)

A project scaffolded before states moved to the workspace has the resource map itself in cargo.state.json, and keeps deploying against that file for as long as you leave it there. state create is what moves it: the map goes onto a new state, the file becomes a pointer, and you commit it. Nothing migrates a project behind your back — a teammate who deploys before pulling that pointer would otherwise be deploying from a state that no longer exists.

Git-ignore the generated types and the working files a deploy writes beside it (but commit cargo.state.json):

.cargo-ai/
cargo.state.lock
cargo.state.bak.json
cargo.state.cache.json
cargo.state.audit.jsonl

cargo-ai project refresh reports resources changed or deleted outside the project (e.g. edited in the Cargo UI); cargo-ai project deploy --refresh re-applies your code over them. See State & Drift.

Documentation

Full documentation: docs.getcargo.ai.

版本与发布记录

@cargo-ai/cdk 共发布 129 个版本,首次发布于 2026/07/03;过去 12 个月发布了 129 个版本。

版本发布日期距今
1.0.140latest2026/11/09今天
1.0.1392026/11/063 天前
1.0.1382026/11/054 天前
1.0.1372026/11/054 天前
1.0.1362026/11/054 天前
1.0.1352026/11/036 天前

关于 @cargo-ai/cdk 的常见问题

@cargo-ai/cdk 是什么?

Cargo projects — define every Cargo resource (connectors, models, plays, tools, agents, MCP servers, folders, files, workers, apps, sending domains, mailboxes) in code and deploy it declaratively: plan, apply, refresh, import and destroy over a repo of de

如何安装 @cargo-ai/cdk?

在项目目录运行 npm install @cargo-ai/cdk,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:zod(^4.4.3)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。它还提供命令行工具 cargo-cdk,可以用 npx cargo-cdk 直接运行。

@cargo-ai/cdk 支持 TypeScript 吗?

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

@cargo-ai/cdk 支持 ESM 还是 CommonJS?

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

@cargo-ai/cdk 需要什么版本的 Node.js?

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

@cargo-ai/cdk 可以商用吗?

@cargo-ai/cdk 使用 UNLICENSED 许可。作者声明为 UNLICENSED,即不授予公开使用许可(与公共领域的 Unlicense 不同)。使用前需获得作者授权。以上为概括,不构成法律意见,请以许可原文为准。

@cargo-ai/cdk 的下载量有多少?

根据 npm 官方下载统计,@cargo-ai/cdk 在 2026/10/09 – 2026/11/07 这 30 天内被下载 101,309 次,日均约 3,377 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cargo-ai/cdk 还在维护吗?

最新版本 1.0.140 发布于 2026/11/09(今天);至今共发布 129 个版本,其中过去 12 个月发布了 129 个;首次发布于 2026/07/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。