@cargo-ai/cdk 0.1.1
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/README.md +187 -0
- package/build/src/core.d.ts +83 -0
- package/build/src/core.d.ts.map +1 -0
- package/build/src/core.js +205 -0
- package/build/src/deploy/apply.d.ts +55 -0
- package/build/src/deploy/apply.d.ts.map +1 -0
- package/build/src/deploy/apply.js +219 -0
- package/build/src/deploy/audit.d.ts +14 -0
- package/build/src/deploy/audit.d.ts.map +1 -0
- package/build/src/deploy/audit.js +25 -0
- package/build/src/deploy/compile.d.ts +36 -0
- package/build/src/deploy/compile.d.ts.map +1 -0
- package/build/src/deploy/compile.js +99 -0
- package/build/src/deploy/destroy.d.ts +20 -0
- package/build/src/deploy/destroy.d.ts.map +1 -0
- package/build/src/deploy/destroy.js +153 -0
- package/build/src/deploy/executors.live.d.ts +4 -0
- package/build/src/deploy/executors.live.d.ts.map +1 -0
- package/build/src/deploy/executors.live.js +623 -0
- package/build/src/deploy/import.d.ts +13 -0
- package/build/src/deploy/import.d.ts.map +1 -0
- package/build/src/deploy/import.js +87 -0
- package/build/src/deploy/index.d.ts +39 -0
- package/build/src/deploy/index.d.ts.map +1 -0
- package/build/src/deploy/index.js +51 -0
- package/build/src/deploy/load.d.ts +7 -0
- package/build/src/deploy/load.d.ts.map +1 -0
- package/build/src/deploy/load.js +58 -0
- package/build/src/deploy/lock.d.ts +10 -0
- package/build/src/deploy/lock.d.ts.map +1 -0
- package/build/src/deploy/lock.js +101 -0
- package/build/src/deploy/plan.d.ts +3 -0
- package/build/src/deploy/plan.d.ts.map +1 -0
- package/build/src/deploy/plan.js +39 -0
- package/build/src/deploy/readers.live.d.ts +4 -0
- package/build/src/deploy/readers.live.d.ts.map +1 -0
- package/build/src/deploy/readers.live.js +273 -0
- package/build/src/deploy/refresh.d.ts +32 -0
- package/build/src/deploy/refresh.d.ts.map +1 -0
- package/build/src/deploy/refresh.js +76 -0
- package/build/src/deploy/state.d.ts +23 -0
- package/build/src/deploy/state.d.ts.map +1 -0
- package/build/src/deploy/state.js +107 -0
- package/build/src/index.d.ts +30 -0
- package/build/src/index.d.ts.map +1 -0
- package/build/src/index.js +30 -0
- package/build/src/refs.d.ts +28 -0
- package/build/src/refs.d.ts.map +1 -0
- package/build/src/refs.js +34 -0
- package/build/src/resources/actions.d.ts +31 -0
- package/build/src/resources/actions.d.ts.map +1 -0
- package/build/src/resources/actions.js +44 -0
- package/build/src/resources/agent.d.ts +71 -0
- package/build/src/resources/agent.d.ts.map +1 -0
- package/build/src/resources/agent.js +96 -0
- package/build/src/resources/app.d.ts +18 -0
- package/build/src/resources/app.d.ts.map +1 -0
- package/build/src/resources/app.js +33 -0
- package/build/src/resources/bundle.d.ts +15 -0
- package/build/src/resources/bundle.d.ts.map +1 -0
- package/build/src/resources/bundle.js +61 -0
- package/build/src/resources/connector.d.ts +30 -0
- package/build/src/resources/connector.d.ts.map +1 -0
- package/build/src/resources/connector.js +41 -0
- package/build/src/resources/context.d.ts +15 -0
- package/build/src/resources/context.d.ts.map +1 -0
- package/build/src/resources/context.js +58 -0
- package/build/src/resources/file.d.ts +15 -0
- package/build/src/resources/file.d.ts.map +1 -0
- package/build/src/resources/file.js +35 -0
- package/build/src/resources/folder.d.ts +20 -0
- package/build/src/resources/folder.d.ts.map +1 -0
- package/build/src/resources/folder.js +28 -0
- package/build/src/resources/mcpServer.d.ts +25 -0
- package/build/src/resources/mcpServer.d.ts.map +1 -0
- package/build/src/resources/mcpServer.js +34 -0
- package/build/src/resources/model.d.ts +34 -0
- package/build/src/resources/model.d.ts.map +1 -0
- package/build/src/resources/model.js +43 -0
- package/build/src/resources/play.d.ts +35 -0
- package/build/src/resources/play.d.ts.map +1 -0
- package/build/src/resources/play.js +49 -0
- package/build/src/resources/tool.d.ts +40 -0
- package/build/src/resources/tool.d.ts.map +1 -0
- package/build/src/resources/tool.js +36 -0
- package/build/src/resources/worker.d.ts +18 -0
- package/build/src/resources/worker.d.ts.map +1 -0
- package/build/src/resources/worker.js +40 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/package.json +71 -0
- package/templates/blank/README.md +24 -0
- package/templates/blank/cargo.ts +44 -0
- package/templates/blank/package.json +17 -0
- package/templates/blank/tsconfig.json +14 -0
- package/templates/full/README.md +69 -0
- package/templates/full/agents/enricher.ts +13 -0
- package/templates/full/agents/sdr.ts +28 -0
- package/templates/full/apps/dashboard/index.html +12 -0
- package/templates/full/apps/dashboard/package-lock.json +1080 -0
- package/templates/full/apps/dashboard/package.json +15 -0
- package/templates/full/apps/dashboard/src/main.tsx +12 -0
- package/templates/full/apps/dashboard/tsconfig.json +13 -0
- package/templates/full/apps/dashboard.ts +9 -0
- package/templates/full/connectors/hubspot.ts +12 -0
- package/templates/full/connectors/openai.ts +9 -0
- package/templates/full/context/context.ts +7 -0
- package/templates/full/context/icp.md +22 -0
- package/templates/full/context/personas/head-of-growth.md +26 -0
- package/templates/full/files/playbook.md +19 -0
- package/templates/full/files/playbook.ts +8 -0
- package/templates/full/folders/crm.ts +14 -0
- package/templates/full/mcp/crm.ts +15 -0
- package/templates/full/models/contacts.ts +15 -0
- package/templates/full/package.json +17 -0
- package/templates/full/plays/onboarding.ts +41 -0
- package/templates/full/tools/enrich.ts +34 -0
- package/templates/full/tsconfig.json +17 -0
- package/templates/full/workers/webhook/index.js +12 -0
- package/templates/full/workers/webhook/manifest.json +1 -0
- package/templates/full/workers/webhook/package-lock.json +12 -0
- package/templates/full/workers/webhook/package.json +6 -0
- package/templates/full/workers/webhook.ts +13 -0
package/README.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Cargo CDK
|
|
2
|
+
|
|
3
|
+
Define your entire Cargo workspace in code — connectors, models, plays, tools,
|
|
4
|
+
agents, MCP servers, context, folders, files, workers and apps — and deploy it
|
|
5
|
+
declaratively, the same way you'd manage cloud infrastructure with Pulumi or the
|
|
6
|
+
AWS CDK. For a complete overview, see the [Cargo CDK docs](https://docs.getcargo.ai/cdk/overview).
|
|
7
|
+
|
|
8
|
+
## Requirements
|
|
9
|
+
|
|
10
|
+
- Node.js 22.x
|
|
11
|
+
- A Cargo API token (create in your workspace under **Settings → API**)
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Install the CDK in your project (for the `define*` builders you import in your
|
|
16
|
+
resource files):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @cargo-ai/cdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The `cargo-ai cdk` commands ship with the Cargo CLI — install it globally:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g @cargo-ai/cli
|
|
26
|
+
cargo-ai login --token <your-api-token>
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick start
|
|
30
|
+
|
|
31
|
+
Scaffold a starter project from a template:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
cargo-ai cdk init my-workspace # minimal 'blank' template (default)
|
|
35
|
+
cargo-ai cdk init my-workspace --template full # every resource type, wired up
|
|
36
|
+
cargo-ai cdk init --list-templates # see all templates
|
|
37
|
+
cd my-workspace && npm install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Or add a resource file yourself — importing a file **is** registration, there's
|
|
41
|
+
no manifest to maintain:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
// my-workspace/contacts.ts
|
|
45
|
+
import { defineConnector, defineModel, secret } from "@cargo-ai/cdk";
|
|
46
|
+
|
|
47
|
+
// `secret()` reads the value at deploy time and keeps it out of state and the
|
|
48
|
+
// content hash — use it for credentials. The config shape is per-integration
|
|
49
|
+
// (HubSpot takes a Private App token or an OAuth refresh token); run
|
|
50
|
+
// `cargo-ai cdk types` to have it type-checked (see Typed config below).
|
|
51
|
+
export const hubspot = defineConnector("hubspot", {
|
|
52
|
+
integration: "hubspot",
|
|
53
|
+
config: { method: "privateApp", accessToken: secret("HUBSPOT_API_KEY") },
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Pass the connector handle as `dataset` — the model depends on it, so the CDK
|
|
57
|
+
// creates the connector first and injects its dataset uuid.
|
|
58
|
+
export const contacts = defineModel("contacts", {
|
|
59
|
+
dataset: hubspot,
|
|
60
|
+
extractSlug: "fetchRecords",
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
export HUBSPOT_API_KEY=... # secret() reads this at deploy time
|
|
66
|
+
cargo-ai cdk plan --dir my-workspace # offline diff — no API calls
|
|
67
|
+
cargo-ai cdk deploy --dir my-workspace # create everything, write cargo.state.json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Re-running `deploy` only changes what changed; an unchanged workspace is a no-op.
|
|
71
|
+
|
|
72
|
+
## Typed config
|
|
73
|
+
|
|
74
|
+
`defineConnector` and `defineModel` config are typed against your workspace's
|
|
75
|
+
actual integration schemas. Generate the types once:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
cargo-ai cdk types --dir my-workspace
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
They land in `.cargo-ai/`. Add it to your tsconfig `include` with an explicit
|
|
82
|
+
glob — a bare `.cargo-ai` (a dot-dir) is ignored by TypeScript:
|
|
83
|
+
|
|
84
|
+
```jsonc
|
|
85
|
+
// tsconfig.json
|
|
86
|
+
"include": ["*.ts", ".cargo-ai/**/*.d.ts"]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
After that, `config` is checked against the integration's schema — e.g. HubSpot
|
|
90
|
+
is a discriminated union, so the editor completes `method` and requires the
|
|
91
|
+
matching credential, and `secret()` is accepted wherever a credential is
|
|
92
|
+
expected. Re-run `cdk types` after adding or changing workspace integrations.
|
|
93
|
+
|
|
94
|
+
Typing is a bonus, never a gate: an integration you haven't synced (or a custom
|
|
95
|
+
one) falls back to a loose `Record<string, unknown>`, so `deploy` still works
|
|
96
|
+
without ever running `cdk types`.
|
|
97
|
+
|
|
98
|
+
## Defining resources
|
|
99
|
+
|
|
100
|
+
Every Cargo resource has a `define*` builder that returns a **handle**. Wire
|
|
101
|
+
resources together by passing one handle to another — the dependency graph is
|
|
102
|
+
just your variable graph. Pass the **handle** directly (or `xxRef("uuid")` for a
|
|
103
|
+
resource you didn't define in code); no `.uuid` at the call site. Where a
|
|
104
|
+
reference takes per-call options, wrap it as `{ ref, …options }`.
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { defineAgent, defineTool, defineWorkflow } from "@cargo-ai/cdk";
|
|
108
|
+
|
|
109
|
+
const flow = defineWorkflow("enrich", { input, output }, ({ input, ai }) => …);
|
|
110
|
+
export const enrich = defineTool("enrich", { workflow: flow, emojiSlug: "mag" });
|
|
111
|
+
|
|
112
|
+
export const sdr = defineAgent("sdr", {
|
|
113
|
+
connector: openai, // handle (or connectorRef("…"))
|
|
114
|
+
languageModel: "gpt-4o",
|
|
115
|
+
systemPrompt: "Qualify inbound leads.",
|
|
116
|
+
models: [{ ref: contacts, readOnly: true }], // handle + options
|
|
117
|
+
tools: [enrich], // bare handle
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
See [Defining Resources](https://docs.getcargo.ai/cdk/resources) for every
|
|
122
|
+
builder (connectors, models, plays, tools, agents, MCP servers, context, folders,
|
|
123
|
+
files, workers, apps) and the slug rules.
|
|
124
|
+
|
|
125
|
+
### Context
|
|
126
|
+
|
|
127
|
+
`defineContext` manages the workspace's context repository — its git-backed GTM
|
|
128
|
+
knowledge base of markdown/MDX — as code. Point it at a local folder and/or pass
|
|
129
|
+
inline files:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { defineContext } from "@cargo-ai/cdk";
|
|
133
|
+
|
|
134
|
+
export const context = defineContext({
|
|
135
|
+
dir: "./context", // sync a local folder of .md/.mdx
|
|
136
|
+
files: { "generated/icp.md": buildIcp() }, // + inline additions/overrides
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
There is one context repo per workspace, so this is a singleton. On deploy it
|
|
141
|
+
writes each changed file into the repo via the runtime — **additive**: files
|
|
142
|
+
added elsewhere (the UI, other tooling) are left in place.
|
|
143
|
+
|
|
144
|
+
## Commands
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
cargo-ai cdk init <directory> # scaffold from a template (--template, --list-templates)
|
|
148
|
+
cargo-ai cdk types # generate per-workspace types for typed config
|
|
149
|
+
cargo-ai cdk plan # offline: diff the code against cargo.state.json
|
|
150
|
+
cargo-ai cdk deploy # create/update resources, write cargo.state.json
|
|
151
|
+
cargo-ai cdk deploy --prune # also delete resources removed from code
|
|
152
|
+
cargo-ai cdk deploy --refresh # re-read live resources, re-apply out-of-band changes
|
|
153
|
+
cargo-ai cdk refresh # read-only: report resources that drifted from code
|
|
154
|
+
cargo-ai cdk import <id> <uuid> # bind an existing live resource into state
|
|
155
|
+
cargo-ai cdk rollback # restore cargo.state.json from the pre-deploy snapshot
|
|
156
|
+
cargo-ai cdk destroy --target <id> # tear down one resource
|
|
157
|
+
cargo-ai cdk destroy --all # tear down everything in state
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Common flags: `--dir <path>`, `--yes` (skip the prompt — required in CI),
|
|
161
|
+
`--json`, and `--force` (steal a stale state lock). See
|
|
162
|
+
[Deploying](https://docs.getcargo.ai/cdk/deploying).
|
|
163
|
+
|
|
164
|
+
## State & drift
|
|
165
|
+
|
|
166
|
+
`cargo-ai cdk deploy` writes `cargo.state.json` — the link from your code to the
|
|
167
|
+
resources Cargo created. **Commit it** (losing it orphans resources; recover a
|
|
168
|
+
link with `cdk import`). It records only uuids, hashes and outputs — never
|
|
169
|
+
secret values.
|
|
170
|
+
|
|
171
|
+
Git-ignore the generated types and the working files the CDK writes alongside it
|
|
172
|
+
(but commit `cargo.state.json`):
|
|
173
|
+
|
|
174
|
+
```gitignore
|
|
175
|
+
.cargo-ai/
|
|
176
|
+
cargo.state.lock
|
|
177
|
+
cargo.state.bak.json
|
|
178
|
+
cargo.state.audit.jsonl
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`cargo-ai cdk refresh` reports resources changed or deleted outside the CDK
|
|
182
|
+
(e.g. edited in the Cargo UI); `cargo-ai cdk deploy --refresh` re-applies your
|
|
183
|
+
code over them. See [State & Drift](https://docs.getcargo.ai/cdk/state-and-drift).
|
|
184
|
+
|
|
185
|
+
## Documentation
|
|
186
|
+
|
|
187
|
+
Full documentation: [docs.getcargo.ai/cdk](https://docs.getcargo.ai/cdk/overview).
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export type Token = {
|
|
2
|
+
readonly __token: true;
|
|
3
|
+
readonly resourceId: string;
|
|
4
|
+
readonly field: string;
|
|
5
|
+
};
|
|
6
|
+
export declare function token(resourceId: string, field: string): Token;
|
|
7
|
+
export declare function isToken(value: unknown): value is Token;
|
|
8
|
+
export type ResourceKind = "connector" | "model" | "agent" | "play" | "tool" | "mcpServer" | "folder" | "file" | "worker" | "app" | "context";
|
|
9
|
+
export type ResourceNode = {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly kind: ResourceKind;
|
|
12
|
+
readonly slug: string;
|
|
13
|
+
readonly spec: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Register a resource. Re-registering the SAME id with identical content is a
|
|
17
|
+
* no-op (the loader may evaluate a file more than once when it's imported both
|
|
18
|
+
* directly and transitively); re-registering an id with DIFFERENT content is a
|
|
19
|
+
* genuine duplicate slug and throws.
|
|
20
|
+
*/
|
|
21
|
+
export declare function register(node: ResourceNode): void;
|
|
22
|
+
/**
|
|
23
|
+
* Deterministic JSON serialization for content hashing. Object keys are sorted
|
|
24
|
+
* recursively and `undefined`-valued keys are dropped, so the same logical spec
|
|
25
|
+
* always hashes the same regardless of how the author ordered keys or whether
|
|
26
|
+
* an optional field was passed as `undefined` vs omitted. Without this, the
|
|
27
|
+
* drift hash (and the register-dedup check) are sensitive to insertion order
|
|
28
|
+
* and produce spurious "update" diffs on every deploy. Arrays keep their order
|
|
29
|
+
* (it's semantically significant); Dates serialize to their ISO string.
|
|
30
|
+
*/
|
|
31
|
+
export declare function stableStringify(value: unknown): string;
|
|
32
|
+
/** Every resource declared so far. */
|
|
33
|
+
export declare function resources(): ResourceNode[];
|
|
34
|
+
/** Clear the registry — the loader resets between runs; tests between cases. */
|
|
35
|
+
export declare function resetRegistry(): void;
|
|
36
|
+
/** Compose a resource's stable id from its kind + slug, validating the slug. */
|
|
37
|
+
export declare function resourceId(kind: ResourceKind, slug: string): string;
|
|
38
|
+
/** Collect the resource ids a spec depends on (every token reachable in it). */
|
|
39
|
+
export declare function dependenciesOf(value: unknown, acc?: Set<string>): Set<string>;
|
|
40
|
+
/** Deep-replace every token in a spec with the real value its producer returned. */
|
|
41
|
+
export declare function resolveTokens(value: unknown, outputs: Map<string, Record<string, string>>): unknown;
|
|
42
|
+
/**
|
|
43
|
+
* Read a config value from the environment at load time. Its value enters the
|
|
44
|
+
* spec — and therefore the content hash — so changing it shows as drift. Use for
|
|
45
|
+
* non-secret config you WANT to track; use `secret()` for rotating secrets.
|
|
46
|
+
* Returns a visible `${NAME}` placeholder when unset so it surfaces in a plan.
|
|
47
|
+
*/
|
|
48
|
+
export declare function env(name: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* A deferred reference to an environment secret, resolved at APPLY time and
|
|
51
|
+
* excluded from the content hash (the spec stores the reference, not the value).
|
|
52
|
+
* Rotating the secret therefore doesn't read as code drift — but a plain deploy
|
|
53
|
+
* won't push the new value either, since nothing in the hash changed; re-apply
|
|
54
|
+
* the resource (any other edit, or `--refresh`) to roll a rotated secret.
|
|
55
|
+
*/
|
|
56
|
+
export type SecretRef = {
|
|
57
|
+
readonly __secret: true;
|
|
58
|
+
readonly name: string;
|
|
59
|
+
};
|
|
60
|
+
export declare function secret(name: string): SecretRef;
|
|
61
|
+
/**
|
|
62
|
+
* An encryption-typed connector/model config field. Server-side these fields are
|
|
63
|
+
* the `{ type: "encryption", isEncrypted, value }` envelope; in code you fill one
|
|
64
|
+
* with `secret("ENV")` (resolved to that envelope at deploy) — or, rarely, the
|
|
65
|
+
* raw envelope. `cargo-ai cdk types` prints `EncryptionRef` at exactly the schema
|
|
66
|
+
* positions typed as `encryption`, so `secret()` is accepted there and ONLY there.
|
|
67
|
+
*/
|
|
68
|
+
export type EncryptionRef = SecretRef | {
|
|
69
|
+
type: "encryption";
|
|
70
|
+
isEncrypted: boolean;
|
|
71
|
+
value: string;
|
|
72
|
+
};
|
|
73
|
+
export declare function isSecret(value: unknown): value is SecretRef;
|
|
74
|
+
/**
|
|
75
|
+
* Deep-replace every `secret()` reference with its environment value, wrapped in
|
|
76
|
+
* Cargo's encryption envelope (`{ type: "encryption", isEncrypted: false, value }`).
|
|
77
|
+
* Every secret connector-config field (API keys, tokens, refresh tokens) is typed
|
|
78
|
+
* as this envelope server-side — the same shape the UI submits — so a bare string
|
|
79
|
+
* would be rejected as invalid config. The backend encrypts it at rest. Run at
|
|
80
|
+
* apply time (after token resolution). Throws if a referenced secret is unset.
|
|
81
|
+
*/
|
|
82
|
+
export declare function resolveSecrets(value: unknown): unknown;
|
|
83
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/core.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,KAAK,GAAG;IAClB,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,wBAAgB,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,KAAK,CAE9D;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,CAMtD;AAGD,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,OAAO,GACP,OAAO,GACP,MAAM,GACN,MAAM,GACN,WAAW,GACX,QAAQ,GACR,MAAM,GACN,QAAQ,GACR,KAAK,GACL,SAAS,CAAC;AAEd,MAAM,MAAM,YAAY,GAAG;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC,CAAC;AAoBF;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAUjD;AAMD;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEtD;AAgBD,sCAAsC;AACtC,wBAAgB,SAAS,IAAI,YAAY,EAAE,CAE1C;AAED,gFAAgF;AAChF,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAgCD,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMnE;AAGD,gFAAgF;AAChF,wBAAgB,cAAc,CAC5B,KAAK,EAAE,OAAO,EACd,GAAG,GAAE,GAAG,CAAC,MAAM,CAAa,GAC3B,GAAG,CAAC,MAAM,CAAC,CAcb;AAED,oFAAoF;AACpF,wBAAgB,aAAa,CAC3B,KAAK,EAAE,OAAO,EACd,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAC3C,OAAO,CAoBT;AAGD;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGxC;AAED;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAE9C;AAED;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GACrB,SAAS,GACT;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,WAAW,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,SAAS,CAM3D;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAmBtD"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// @cargo-ai/cdk — core
|
|
2
|
+
//
|
|
3
|
+
// The runtime for Cargo's infrastructure-as-code graph (Pulumi/CDK-style).
|
|
4
|
+
// `define*` builders REGISTER a resource and return a HANDLE whose outputs
|
|
5
|
+
// (uuid, datasetUuid, …) are deferred TOKENS. You wire resources by passing one
|
|
6
|
+
// resource's token into another's spec (`defineModel("c", { datasetUuid:
|
|
7
|
+
// connector.datasetUuid })`) — the dependency graph is the variable graph. The
|
|
8
|
+
// deploy engine topologically creates resources and resolves each token to the
|
|
9
|
+
// real value its producer returned. No central manifest, no string slugs.
|
|
10
|
+
import { createHash } from "node:crypto";
|
|
11
|
+
export function token(resourceId, field) {
|
|
12
|
+
return { __token: true, resourceId, field };
|
|
13
|
+
}
|
|
14
|
+
export function isToken(value) {
|
|
15
|
+
return (typeof value === "object" &&
|
|
16
|
+
value !== null &&
|
|
17
|
+
value.__token === true);
|
|
18
|
+
}
|
|
19
|
+
// The registry is stored on globalThis under a global symbol, so that all copies
|
|
20
|
+
// of this module — including the separate instance the tsx loader creates when it
|
|
21
|
+
// transpiles a user's resource files — share ONE registry. Without this, define*
|
|
22
|
+
// (running in the loader's module graph) would populate a different Map than the
|
|
23
|
+
// host reads (the module-duplication / dual-package hazard).
|
|
24
|
+
const REGISTRY_KEY = Symbol.for("@cargo-ai/cdk/registry");
|
|
25
|
+
function registry() {
|
|
26
|
+
const holder = globalThis;
|
|
27
|
+
const existing = holder[REGISTRY_KEY];
|
|
28
|
+
if (existing !== undefined)
|
|
29
|
+
return existing;
|
|
30
|
+
const created = new Map();
|
|
31
|
+
holder[REGISTRY_KEY] = created;
|
|
32
|
+
return created;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Register a resource. Re-registering the SAME id with identical content is a
|
|
36
|
+
* no-op (the loader may evaluate a file more than once when it's imported both
|
|
37
|
+
* directly and transitively); re-registering an id with DIFFERENT content is a
|
|
38
|
+
* genuine duplicate slug and throws.
|
|
39
|
+
*/
|
|
40
|
+
export function register(node) {
|
|
41
|
+
const store = registry();
|
|
42
|
+
const existing = store.get(node.id);
|
|
43
|
+
if (existing !== undefined) {
|
|
44
|
+
if (specHash(existing.spec) === specHash(node.spec))
|
|
45
|
+
return;
|
|
46
|
+
throw new Error(`Duplicate resource "${node.id}" — slugs must be unique per kind.`);
|
|
47
|
+
}
|
|
48
|
+
store.set(node.id, node);
|
|
49
|
+
}
|
|
50
|
+
function specHash(spec) {
|
|
51
|
+
return createHash("sha256").update(stableStringify(spec)).digest("hex");
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Deterministic JSON serialization for content hashing. Object keys are sorted
|
|
55
|
+
* recursively and `undefined`-valued keys are dropped, so the same logical spec
|
|
56
|
+
* always hashes the same regardless of how the author ordered keys or whether
|
|
57
|
+
* an optional field was passed as `undefined` vs omitted. Without this, the
|
|
58
|
+
* drift hash (and the register-dedup check) are sensitive to insertion order
|
|
59
|
+
* and produce spurious "update" diffs on every deploy. Arrays keep their order
|
|
60
|
+
* (it's semantically significant); Dates serialize to their ISO string.
|
|
61
|
+
*/
|
|
62
|
+
export function stableStringify(value) {
|
|
63
|
+
return JSON.stringify(canonicalize(value));
|
|
64
|
+
}
|
|
65
|
+
function canonicalize(value) {
|
|
66
|
+
if (value === null || typeof value !== "object")
|
|
67
|
+
return value;
|
|
68
|
+
if (Array.isArray(value))
|
|
69
|
+
return value.map(canonicalize);
|
|
70
|
+
if (value instanceof Date)
|
|
71
|
+
return value.toISOString();
|
|
72
|
+
const source = value;
|
|
73
|
+
const out = {};
|
|
74
|
+
for (const key of Object.keys(source).sort()) {
|
|
75
|
+
const v = source[key];
|
|
76
|
+
if (v === undefined)
|
|
77
|
+
continue;
|
|
78
|
+
out[key] = canonicalize(v);
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
/** Every resource declared so far. */
|
|
83
|
+
export function resources() {
|
|
84
|
+
return Array.from(registry().values());
|
|
85
|
+
}
|
|
86
|
+
/** Clear the registry — the loader resets between runs; tests between cases. */
|
|
87
|
+
export function resetRegistry() {
|
|
88
|
+
globalThis[REGISTRY_KEY] = new Map();
|
|
89
|
+
}
|
|
90
|
+
// Slug rules, matched to what each Cargo create endpoint actually enforces so a
|
|
91
|
+
// bad slug fails at define time (in `plan`) instead of mid-deploy. connector +
|
|
92
|
+
// model are snake_case; worker + app use the hosting kebab format; the rest have
|
|
93
|
+
// no API-side slug (uuid- or name-addressed), so they only get a sane base rule.
|
|
94
|
+
const SNAKE_CASE = /^[a-z0-9]+(_[a-z0-9]+)*$/;
|
|
95
|
+
const HOSTING_SLUG = /^[a-z][a-z0-9-]{2,30}$/;
|
|
96
|
+
const BASE_SLUG = /^[a-z0-9][a-z0-9_-]*$/;
|
|
97
|
+
function slugRule(kind) {
|
|
98
|
+
switch (kind) {
|
|
99
|
+
case "connector":
|
|
100
|
+
case "model":
|
|
101
|
+
return {
|
|
102
|
+
pattern: SNAKE_CASE,
|
|
103
|
+
help: 'snake_case — lowercase letters and digits joined by single underscores (e.g. "my_source")',
|
|
104
|
+
};
|
|
105
|
+
case "worker":
|
|
106
|
+
case "app":
|
|
107
|
+
return {
|
|
108
|
+
pattern: HOSTING_SLUG,
|
|
109
|
+
help: 'kebab-case — start with a letter, then 2-30 lowercase letters, digits or hyphens (e.g. "my-worker")',
|
|
110
|
+
};
|
|
111
|
+
default:
|
|
112
|
+
return {
|
|
113
|
+
pattern: BASE_SLUG,
|
|
114
|
+
help: 'lowercase letters, digits, hyphens and underscores, starting with a letter or digit (e.g. "my-thing")',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/** Compose a resource's stable id from its kind + slug, validating the slug. */
|
|
119
|
+
export function resourceId(kind, slug) {
|
|
120
|
+
const { pattern, help } = slugRule(kind);
|
|
121
|
+
if (!pattern.test(slug)) {
|
|
122
|
+
throw new Error(`invalid ${kind} slug "${slug}" — must be ${help}.`);
|
|
123
|
+
}
|
|
124
|
+
return `${kind}:${slug}`;
|
|
125
|
+
}
|
|
126
|
+
// ── graph helpers used by the deploy engine ──────────────────────────────────
|
|
127
|
+
/** Collect the resource ids a spec depends on (every token reachable in it). */
|
|
128
|
+
export function dependenciesOf(value, acc = new Set()) {
|
|
129
|
+
if (isToken(value)) {
|
|
130
|
+
acc.add(value.resourceId);
|
|
131
|
+
return acc;
|
|
132
|
+
}
|
|
133
|
+
if (Array.isArray(value)) {
|
|
134
|
+
for (const item of value)
|
|
135
|
+
dependenciesOf(item, acc);
|
|
136
|
+
return acc;
|
|
137
|
+
}
|
|
138
|
+
if (value !== null && typeof value === "object") {
|
|
139
|
+
for (const item of Object.values(value))
|
|
140
|
+
dependenciesOf(item, acc);
|
|
141
|
+
return acc;
|
|
142
|
+
}
|
|
143
|
+
return acc;
|
|
144
|
+
}
|
|
145
|
+
/** Deep-replace every token in a spec with the real value its producer returned. */
|
|
146
|
+
export function resolveTokens(value, outputs) {
|
|
147
|
+
if (isToken(value)) {
|
|
148
|
+
const produced = outputs.get(value.resourceId);
|
|
149
|
+
const resolved = produced === undefined ? undefined : produced[value.field];
|
|
150
|
+
if (resolved === undefined) {
|
|
151
|
+
throw new Error(`Unresolved token ${value.resourceId}.${value.field} — producer not applied yet.`);
|
|
152
|
+
}
|
|
153
|
+
return resolved;
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(value)) {
|
|
156
|
+
return value.map((item) => resolveTokens(item, outputs));
|
|
157
|
+
}
|
|
158
|
+
if (value !== null && typeof value === "object") {
|
|
159
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, resolveTokens(v, outputs)]));
|
|
160
|
+
}
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
// ── secrets ──────────────────────────────────────────────────────────────────
|
|
164
|
+
/**
|
|
165
|
+
* Read a config value from the environment at load time. Its value enters the
|
|
166
|
+
* spec — and therefore the content hash — so changing it shows as drift. Use for
|
|
167
|
+
* non-secret config you WANT to track; use `secret()` for rotating secrets.
|
|
168
|
+
* Returns a visible `${NAME}` placeholder when unset so it surfaces in a plan.
|
|
169
|
+
*/
|
|
170
|
+
export function env(name) {
|
|
171
|
+
const value = process.env[name];
|
|
172
|
+
return value === undefined ? `\${${name}}` : value;
|
|
173
|
+
}
|
|
174
|
+
export function secret(name) {
|
|
175
|
+
return { __secret: true, name };
|
|
176
|
+
}
|
|
177
|
+
export function isSecret(value) {
|
|
178
|
+
return (typeof value === "object" &&
|
|
179
|
+
value !== null &&
|
|
180
|
+
value.__secret === true);
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Deep-replace every `secret()` reference with its environment value, wrapped in
|
|
184
|
+
* Cargo's encryption envelope (`{ type: "encryption", isEncrypted: false, value }`).
|
|
185
|
+
* Every secret connector-config field (API keys, tokens, refresh tokens) is typed
|
|
186
|
+
* as this envelope server-side — the same shape the UI submits — so a bare string
|
|
187
|
+
* would be rejected as invalid config. The backend encrypts it at rest. Run at
|
|
188
|
+
* apply time (after token resolution). Throws if a referenced secret is unset.
|
|
189
|
+
*/
|
|
190
|
+
export function resolveSecrets(value) {
|
|
191
|
+
if (isSecret(value)) {
|
|
192
|
+
const resolved = process.env[value.name];
|
|
193
|
+
if (resolved === undefined) {
|
|
194
|
+
throw new Error(`secret "${value.name}" is not set — export ${value.name} before deploying.`);
|
|
195
|
+
}
|
|
196
|
+
return { type: "encryption", isEncrypted: false, value: resolved };
|
|
197
|
+
}
|
|
198
|
+
if (Array.isArray(value)) {
|
|
199
|
+
return value.map((item) => resolveSecrets(item));
|
|
200
|
+
}
|
|
201
|
+
if (value !== null && typeof value === "object") {
|
|
202
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, resolveSecrets(v)]));
|
|
203
|
+
}
|
|
204
|
+
return value;
|
|
205
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { type CompileInput, type PlanEntry, type State, type StateEntry } from "./compile.js";
|
|
2
|
+
import type { LiveReaders } from "./refresh.js";
|
|
3
|
+
export type Outputs = Record<string, string>;
|
|
4
|
+
export type Executors = {
|
|
5
|
+
connector(spec: Record<string, unknown>, prior: StateEntry | undefined): Promise<Outputs & {
|
|
6
|
+
uuid: string;
|
|
7
|
+
datasetUuid: string;
|
|
8
|
+
}>;
|
|
9
|
+
model(spec: Record<string, unknown>, prior: StateEntry | undefined): Promise<Outputs & {
|
|
10
|
+
uuid: string;
|
|
11
|
+
}>;
|
|
12
|
+
agent(spec: Record<string, unknown>, prior: StateEntry | undefined): Promise<Outputs & {
|
|
13
|
+
uuid: string;
|
|
14
|
+
}>;
|
|
15
|
+
play(spec: Record<string, unknown>, prior: StateEntry | undefined): Promise<Outputs & {
|
|
16
|
+
uuid: string;
|
|
17
|
+
}>;
|
|
18
|
+
tool(spec: Record<string, unknown>, prior: StateEntry | undefined): Promise<Outputs & {
|
|
19
|
+
uuid: string;
|
|
20
|
+
}>;
|
|
21
|
+
mcpServer(spec: Record<string, unknown>, prior: StateEntry | undefined): Promise<Outputs & {
|
|
22
|
+
uuid: string;
|
|
23
|
+
}>;
|
|
24
|
+
folder(spec: Record<string, unknown>, prior: StateEntry | undefined): Promise<Outputs & {
|
|
25
|
+
uuid: string;
|
|
26
|
+
}>;
|
|
27
|
+
file(spec: Record<string, unknown>, prior: StateEntry | undefined): Promise<Outputs & {
|
|
28
|
+
uuid: string;
|
|
29
|
+
}>;
|
|
30
|
+
worker(spec: Record<string, unknown>, prior: StateEntry | undefined): Promise<Outputs & {
|
|
31
|
+
uuid: string;
|
|
32
|
+
url: string;
|
|
33
|
+
}>;
|
|
34
|
+
app(spec: Record<string, unknown>, prior: StateEntry | undefined): Promise<Outputs & {
|
|
35
|
+
uuid: string;
|
|
36
|
+
url: string;
|
|
37
|
+
}>;
|
|
38
|
+
context(spec: Record<string, unknown>, prior: StateEntry | undefined): Promise<Outputs & {
|
|
39
|
+
uuid: string;
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
42
|
+
export type ApplyResult = {
|
|
43
|
+
state: State;
|
|
44
|
+
applied: PlanEntry[];
|
|
45
|
+
skipped: PlanEntry[];
|
|
46
|
+
};
|
|
47
|
+
export type ApplyProgress = {
|
|
48
|
+
phase: "start" | "done";
|
|
49
|
+
id: string;
|
|
50
|
+
change: "create" | "update";
|
|
51
|
+
index: number;
|
|
52
|
+
total: number;
|
|
53
|
+
};
|
|
54
|
+
export declare function apply(input: CompileInput, executors: Executors, persist?: (state: State) => void, readers?: LiveReaders, onProgress?: (event: ApplyProgress) => void): Promise<ApplyResult>;
|
|
55
|
+
//# sourceMappingURL=apply.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../src/deploy/apply.ts"],"names":[],"mappings":"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"}
|