cargo-storage
Work with the data inside a Cargo workspace — models (Companies, Contacts, Deals…), datasets, columns, relationships, records, and SQL over workspace storage. Triggers: "what models do I have", "show me the schema", "add a column for", "how many contacts do I have", "SELECT … FRO
Install
npx skills add https://github.com/getcargohq/cargo-skills/tree/main/cargo-storage
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install getcargohq-cargo-skills@llmmart
git clone https://github.com/getcargohq/cargo-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole getcargohq/cargo-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Cargo CLI — Storage
Data layer management: inspecting and modifying models, datasets, columns, relationships, unification, and records, and running SQL queries against workspace storage.
See
references/response-shapes.mdfor full JSON response structures. Seereferences/troubleshooting.mdfor common errors and how to fix them. Seereferences/examples/models.mdfor model CRUD, DDL inspection, and schema discovery examples. Seereferences/examples/datasets.mdfor dataset listing and navigation examples. Seereferences/examples/columns.mdfor column creation and management examples. Seereferences/examples/queries.mdforstorage query execute/storage query downloadSQL examples (WHERE, aggregations, joins, pagination, exports). Seereferences/examples/ingest-webhook.mdfor ingest (webhook-fed) models — deriving the webhook URL and POSTing records.
Bootstrap
Already signed in (cargo-ai whoami returns a workspace)? Skip to the next section.
npm install -g @cargo-ai/cli # no global install? prefix every command with `npx @cargo-ai/cli`
cargo-ai login --email you@company.com # emailed code, no browser; creates the account on first use
# alternatives: --oauth (browser) · --token <api-token> (CI)
cargo-ai whoami # confirm the active workspace before any write
Every command prints JSON to stdout; failures exit non-zero with {"errorMessage": "..."}. Anything that creates a run or a batch is async — pass --wait-until-finished or poll the matching get. When the full skill bundle is installed, ../cargo/references/prerequisites.md adds the CLI version pin, token scopes, and the admin-only surface.
Discover resources first
Always list before inspecting or modifying.
cargo-ai storage dataset list # all datasets (uuid, slug)
cargo-ai storage model list # all models (uuid, name, slug, columns, datasetUuid)
# `model list` takes no flags — filter its output instead:
cargo-ai storage model list | jq '[.models[] | select(.datasetUuid == "<uuid>")]'
Retrieve in the UI: models live at app.getcargo.io/workspaces/<WORKSPACE_UUID>/models/<MODEL_UUID>. Get <WORKSPACE_UUID> from cargo-ai whoami under workspace.uuid.
Quick reference
cargo-ai storage model list
cargo-ai storage model get <model-uuid>
cargo-ai storage model get-ddl <model-uuid>
cargo-ai storage dataset list
cargo-ai storage column list --model-uuid <uuid>
cargo-ai storage relationship list
cargo-ai storage record list --model-uuid <uuid>
cargo-ai storage query execute "SELECT * FROM default.companies LIMIT 10"
cargo-ai storage query download --query "SELECT * FROM default.companies"
Models
Models are structured tables in your workspace (e.g. Companies, Contacts).
# List all models
cargo-ai storage model list
# List models in a dataset — every model carries `datasetUuid`, and
# `model list` has no flags of its own, so filter client-side
cargo-ai storage model list | jq '[.models[] | select(.datasetUuid == "<uuid>")]'
# Get a single model (includes columns)
cargo-ai storage model get <model-uuid>
# Get the DDL (full schema, table name and SQL dialect)
cargo-ai storage model get-ddl <model-uuid>
# → Useful for column discovery and SQL dialect (BigQuery vs Snowflake) before writing queries
# Create a model
cargo-ai storage model create \
--slug contacts \
--name "Contacts" \
--dataset-uuid <uuid> \
--extractor-slug <extractor-slug> \
--config '{}'
# Update a model
cargo-ai storage model update --uuid <model-uuid> --name "New Name"
# Remove a model
cargo-ai storage model remove <model-uuid>
Querying: Use cargo-ai storage query execute "<sql>" (or storage query download --query "<sql>" for full exports) to run SQL against storage. Tables are referenced as <datasetSlug>.<modelSlug> (e.g. default.companies) and rewritten to the underlying storage table under the hood. See Query with SQL below.
Ingest models (webhook-fed)
A model whose extractor has mode.kind === "ingest" — http.listenHook and
friends — is filled by pushing records to Cargo. The app shows a "Webhook URL"
on the model settings screen; no CLI command or API field returns it, but it's
assembled from values the CLI already exposes:
<baseUrl>/v1/models/<model-uuid>/records/ingest?token=<api-token>
MODEL_UUID=<model-uuid>
BASE=$(cargo-ai whoami | jq -r '.baseUrl')
TOKEN=$(cargo-ai workspaceManagement token list | jq -r '.tokens[0].token')
echo "$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN"
Check the extractor's mode first — when it reports "autoIngest": true (calendly,
smartlead, instantlyV2, heyReach, cargo signals) Cargo registers the
hook with the provider itself and the URL must not be handed out. Full flow,
payload shapes, and limits: references/examples/ingest-webhook.md.
Datasets
Datasets are logical groupings of models.
# List all datasets
cargo-ai storage dataset list
# Get a single dataset
cargo-ai storage dataset get <dataset-uuid>
Columns
Columns define the schema of a model.
# List columns for a model
cargo-ai storage column list --model-uuid <uuid>
# Create a column
cargo-ai storage column create \
--model-uuid <uuid> \
--column '{"slug":"my_column","type":"string","label":"My Column","kind":"custom"}'
# Update a column (pass the full column object — columns are identified by slug, not UUID)
cargo-ai storage column update \
--model-uuid <uuid> \
--column '{"slug":"my_column","type":"string","label":"Updated Label","kind":"custom"}'
# Remove a column
cargo-ai storage column remove --model-uuid <uuid> --column-slug <slug>
# Reorder a column (move to a specific index)
cargo-ai storage column reorder --model-uuid <uuid> --column-slug <slug> --to-index 2
Column types: string, number, boolean, date, object, array, vector, any.
Column kinds: custom (user-defined), computed (expression over other columns), metric (aggregated from a related model), lookup (single field pulled from a related model via a join).
Preview what you built
A column list doesn't tell the user whether the model is right — rows do. Two checkpoints (the pack-wide convention lives in ../cargo/references/interaction.md §4):
1. Right after model create / column create — show the schema, not rows. A new model is empty; a LIMIT 10 here returns nothing and reads as failure. Echo the columns as a compact table instead (column, type, what will fill it).
2. As soon as data lands — show the rows. After a batch, play, or import writes into the model, preview it:
cargo-ai storage query execute \
"SELECT * FROM <dataset-slug>.<model-slug> LIMIT 10"
Show ~10 rows and only the columns that carry meaning. Storage queries are free, so this costs nothing but a few lines of output — and it's the first moment the user can actually see what they built. When a play fills a new column, preview that column next to the record's identifying fields (name, domain) so filled vs. empty is obvious.
If the preview comes back empty or all-null when it shouldn't, that's a finding — surface it rather than reporting the write as a success. See cargo-diagnostics to trace why.
Relationships
Relationships link models together (e.g. Contacts belong to Companies). They are authored from the CLI, not just the UI.
relationship list takes no flags — it returns every relationship in the
workspace. Filter client-side on fromModelUuid / toModelUuid.
cargo-ai storage relationship list
relationship set replaces the dataset's whole relationship set. It takes a
dataset and the complete list that should exist within it: entries carrying a
uuid are updated, entries without one are created, and any existing
relationship whose uuid is absent from the payload is deleted. Sending one
relationship to a dataset that has five removes the other four. Always list
first, then send back the full array with your addition:
cargo-ai storage relationship set \
--dataset-uuid <dataset-uuid> \
--relationships '[
{"uuid":"<existing-uuid>","fromModelUuid":"<contacts-uuid>","fromColumnSlug":"account_id","toModelUuid":"<companies-uuid>","toColumnSlug":"id","relation":"manyToOne"},
{"fromModelUuid":"<deals-uuid>","fromColumnSlug":"company_id","toModelUuid":"<companies-uuid>","toColumnSlug":"id","relation":"manyToOne"}
]'
relation is oneToOne, manyToOne, or oneToMany. Both models must live in
the dataset you pass — relationships never span datasets, so fromDatasetUuid
and toDatasetUuid on the response always equal --dataset-uuid.
Failure reasons: datasetNotFound; invalidRelationships (a column slug or
model UUID that doesn't resolve, or a duplicate — including the same pair stated
in reverse); modelNotCompatible (see below).
Unify models refuse manual relationships. In the native dataset, a unify
model's relationships are generated during sync, so naming one as fromModelUuid
or toModelUuid returns modelNotCompatible. Those auto-generated rows are also
excluded from the replace above, so a set call cannot delete them.
Unification
Unification is what merges records from several source models into one canonical
account/contact — and it is configurable from the CLI, via --unification on
model update. Pass null to clear it.
# Connector-driven: the integration decides how records unify
cargo-ai storage model update --uuid <model-uuid> --unification '{"source":"integration"}'
# Custom: you name the type, the matching keys, and optionally a parent
cargo-ai storage model update --uuid <model-uuid> --unification '{
"source": "custom",
"type": "account",
"uniqueColumns": [{"slug":"domain","reference":"domain"}],
"selectedColumnSlugs": ["name","industry","employee_count"],
"parent": {"kind":"model","columnSlug":"account_id","parentModelUuid":"<accounts-uuid>"}
}'
| Field | Applies to | Meaning |
|---|---|---|
source |
both | integration (connector-defined) or custom |
type |
custom | account, contact, accountEvent, contactEvent |
uniqueColumns |
custom | Match keys — {slug, reference} per column. This is what decides which rows are the same entity |
selectedColumnSlugs |
custom | Columns carried into the unified model. Omit for all |
timeColumnSlug |
custom | Event timestamp — for the two *Event types |
parent |
custom | Links contacts/events to their account: {"kind":"model","columnSlug":…,"parentModelUuid":…} or {"kind":"reference","columnSlug":…,"reference":…} |
filter |
custom | Segmentation filter restricting which rows unify — same conjonction shape as segments |
Writing the config does not recompute anything. The unified rows are rebuilt by the model's sync run, so follow the update with a run and poll it:
cargo-ai storage run create --model-uuid <model-uuid>
cargo-ai storage run list --model-uuid <model-uuid>
Get the current config from storage model get <uuid> → unification (null
when the model doesn't unify). Once the run finishes, check the row count with
storage query execute before treating the change as done — a too-narrow
uniqueColumns under-merges and a too-broad one collapses distinct entities, and
both look like a successful run.
Records
# List records in a model
cargo-ai storage record list --model-uuid <uuid>
For advanced record queries (filtering, sorting, pagination), use segmentation segment fetch from the cargo-orchestration skill.
Query with SQL
Run SQL against workspace storage with storage query execute. Tables are referenced as <datasetSlug>.<modelSlug> (e.g. default.companies) and rewritten to the underlying storage table under the hood — no DDL lookup is needed for the table name.
cargo-ai storage query execute \
"SELECT name, domain FROM default.companies LIMIT 10"
# → { "rows": [...] } on success; non-zero exit with { "errorMessage": "..." } on error
For full exports, use storage query download — it returns a signed URL to a CSV (default) or Parquet file:
cargo-ai storage query download \
--query "SELECT name, domain, revenue FROM default.companies ORDER BY revenue DESC"
cargo-ai storage query download \
--query "SELECT * FROM default.companies" --format parquet
Get column slugs from storage column list --model-uuid <uuid> (or run storage model get-ddl <model-uuid> for the full schema and SQL dialect). Page through large result sets with LIMIT / OFFSET directly in the SQL.
See references/examples/queries.md for WHERE clauses, aggregations, joins, date queries, pagination, and the failure shapes returned on error.
Help
Every command supports --help:
cargo-ai storage model list --help
cargo-ai storage column create --help
cargo-ai storage relationship set --help
cargo-ai storage query execute --help
cargo-ai storage query download --help
Files (cargo-skills)
-
references
-
examples
-
columns.md 5.3 KB
# Column examples ## List columns for a model ```bash cargo-ai storage column list --model-uuid <uuid> ``` Response includes `uuid`, `slug`, `type`, `label`, and `position` for each column. ## Create a string column ```bash cargo-ai storage column create \ --model-uuid <uuid> \ --column '{"slug":"website_url","type":"string","label":"Website URL","kind":"custom"}' ``` ## Create a number column ```bash cargo-ai storage column create \ --model-uuid <uuid> \ --column '{"slug":"arr","type":"number","label":"Annual Recurring Revenue","kind":"custom"}' ``` ## Create a date column ```bash cargo-ai storage column create \ --model-uuid <uuid> \ --column '{"slug":"last_contacted_at","type":"date","label":"Last Contacted At","kind":"custom"}' ``` ## Create a boolean column ```bash cargo-ai storage column create \ --model-uuid <uuid> \ --column '{"slug":"is_customer","type":"boolean","label":"Is Customer","kind":"custom"}' ``` ## Create a computed column Computed columns derive their value from an expression over other columns. ```bash cargo-ai storage column create \ --model-uuid <uuid> \ --column '{"slug":"full_name","type":"string","label":"Full Name","kind":"computed","expression":{"kind":"jsExpression","expression":"{{record.first_name}} {{record.last_name}}","instructTo":"none","fromRecipe":false},"columnsUsed":["first_name","last_name"]}' ``` `columnsUsed` is optional but recommended for dependency tracking. ## Create a metric column Metric columns aggregate data from a related model via a relationship. ```bash cargo-ai storage column create \ --model-uuid <uuid> \ --column '{"slug":"total_deals","type":"number","label":"Total Deals","kind":"metric","relationshipUuid":"<relationship-uuid>","aggregation":{"function":"count","columnSlug":"uuid"}}' ``` With an optional filter: ```bash cargo-ai storage column create \ --model-uuid <uuid> \ --column '{"slug":"open_deals","type":"number","label":"Open Deals","kind":"metric","relationshipUuid":"<relationship-uuid>","aggregation":{"function":"count","columnSlug":"uuid"},"filter":{"conjonction":"and","groups":[{"conjonction":"and","conditions":[{"kind":"string","slug":"status","operator":"is","value":"open"}]}]}}' ``` ## Create a lookup column Lookup columns pull a field value from a related model via a join. ```bash cargo-ai storage column create \ --model-uuid <uuid> \ --column '{"slug":"company_name","type":"string","label":"Company Name","kind":"lookup","join":{"toModelUuid":"<company-model-uuid>","fromColumnSlug":"company_uuid","toColumnSlug":"uuid"},"extractColumnSlug":"name"}' ``` With an optional filter: ```bash cargo-ai storage column create \ --model-uuid <uuid> \ --column '{"slug":"primary_contact_email","type":"string","label":"Primary Contact Email","kind":"lookup","join":{"toModelUuid":"<contacts-model-uuid>","fromColumnSlug":"uuid","toColumnSlug":"company_uuid"},"extractColumnSlug":"email","filter":{"conjonction":"and","groups":[{"conjonction":"and","conditions":[{"kind":"boolean","slug":"is_primary","operator":"isTrue"}]}]}}' ``` ## Update a column Pass the full column object via `--column`. Columns are identified by `slug` (no UUID). ```bash cargo-ai storage column update \ --model-uuid <uuid> \ --column '{"slug":"website_url","type":"string","label":"Website","kind":"custom"}' ``` ## Remove a column ```bash cargo-ai storage column remove --model-uuid <uuid> --column-slug website_url ``` ## Reorder a column Move a column to a specific position index (0-based). ```bash cargo-ai storage column reorder --model-uuid <uuid> --column-slug website_url --to-index 2 ``` ## Column types reference | Type | Use for | | --------- | ------------------------ | | `string` | Text, names, URLs, slugs | | `number` | Counts, amounts, scores | | `boolean` | Flags, yes/no values | | `date` | Timestamps, dates | | `object` | Nested JSON objects | | `array` | Lists of values | | `vector` | Embedding vectors | | `any` | Untyped / mixed values | ## Column kinds reference | Kind | Use for | Required extra fields | | ---------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | `custom` | User-defined fields | — | | `computed` | Values derived from an expression over other columns | `expression`; optionally `columnsUsed` | | `metric` | Aggregated values from a related model | `relationshipUuid`, `aggregation.function`, `aggregation.columnSlug`; optionally `filter` | | `lookup` | A single field value pulled from a related model via a join | `join.toModelUuid`, `join.fromColumnSlug`, `join.toColumnSlug`, `extractColumnSlug`; optionally `filter` | Column `slug` values are used in filter conditions (see `cargo-orchestration` skill's `references/filter-syntax.md`) and in `storage query execute` SQL queries. -
datasets.md 1.2 KB
# Dataset examples ## List all datasets Datasets group related models together. ```bash cargo-ai storage dataset list ``` Response includes `uuid`, `name`, and `slug` for each dataset. ## Get a specific dataset ```bash cargo-ai storage dataset get <dataset-uuid> ``` ## List models in a dataset `storage model list` takes **no options** — it always returns every model in the workspace. Each one carries a `datasetUuid`, so narrow it client-side: ```bash cargo-ai storage model list | jq '[.models[] | select(.datasetUuid == "<dataset-uuid>")]' ``` ## Discover workspace data structure Full flow to understand how data is organized: ```bash # 1. List all datasets cargo-ai storage dataset list # → Note the dataset UUIDs and slugs # 2. Group the models by dataset (one call — `model list` has no filter flag) cargo-ai storage model list | jq 'group_by(.datasetUuid) | map({datasetUuid: .[0].datasetUuid, models: map(.slug)})' # → See which models (tables) belong to each dataset # 3. Inspect a model's columns cargo-ai storage model get <model-uuid> # → See column slugs and types for each model ``` The dataset `slug` appears in DDL table names (e.g. `datasets_default` for the dataset with slug `default`). -
ingest-webhook.md 6.7 KB
# Ingest models — get the webhook URL and POST records Some models are fed by **pushing** records to Cargo instead of Cargo pulling them. Their extractor has `mode.kind === "ingest"` — the canonical one is the `http` integration's `listenHook` ("Listen webhook"), but the same mechanism backs `storeleads.listenList`, `rb2b.listenProfiles`, `albacross.listenWebsiteVisits`, and others. The app shows a **Webhook URL** on the model's settings screen. There is **no CLI command and no API field that returns it** — the app builds the string client-side. You can build the exact same string from data the CLI already exposes. ## The URL ``` <baseUrl>/v1/models/<model-uuid>/records/ingest?token=<api-token> ``` - `<baseUrl>` — `cargo-ai whoami` → `.baseUrl` (e.g. `https://api.getcargo.io`). Note the path is `/v1/models/...`, **not** `/v1/storage/models/...`. - `<model-uuid>` — the ingest model's UUID. - `<api-token>` — any workspace API token. The token may carry **zero permissions**; this route is explicitly allowed for permission-less tokens so the URL can be handed to a third-party system safely. The app auto-creates one named `Quick access` for exactly this. `token` can also be sent as an `Authorization: Basic <token>` header instead of a query param — preferable when the receiving system supports custom headers, since a query param lands in logs. ## Derive it ```bash # 1. Find the model and confirm it is an ingest model cargo-ai storage model get <model-uuid> | jq '{uuid, slug, extractorSlug, kind, connectorUuid}' # 2. Confirm the extractor's mode is "ingest" (and NOT autoIngest — see below) cargo-ai connection integration get http | jq -c '.integration.extractors.listenHook.mode' # → {"kind":"ingest"} # 3. Pick or create a token (the raw value is on the list response) cargo-ai workspaceManagement token list | jq -r '.tokens[0].token' cargo-ai workspaceManagement token create --name "Webhook — <model-slug>" | jq -r '.token.token' ``` One-liner that assembles it: ```bash MODEL_UUID=<model-uuid> BASE=$(cargo-ai whoami | jq -r '.baseUrl') TOKEN=$(cargo-ai workspaceManagement token list | jq -r '.tokens[0].token') echo "$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN" ``` > Token values are secrets. Print the URL for the user to copy; don't write it > into a file, a commit, or a report. ## Skip models where Cargo owns the hook Some ingest extractors set `autoIngest: true` — Cargo registers the webhook with the provider itself during setup (calendly, smartlead, instantlyV2, heyReach, and cargo's own signal extractors). The app **hides** the URL for those, and handing it out is wrong: the provider is already pointed at it. Check before showing anything: ```bash cargo-ai connection integration get <integration-slug> \ | jq -c '.integration.extractors["<extractor-slug>"].mode' # {"kind":"ingest"} → manual: show the URL # {"kind":"ingest","autoIngest":true} → Cargo owns it: don't show the URL # anything else (fetch/…) → not an ingest model at all ``` List every ingest extractor an integration has: ```bash cargo-ai connection integration get <integration-slug> \ | jq -c '.integration.extractors | to_entries | map(select(.value.mode.kind=="ingest")) | map({(.key): .value.mode})' # calendly → [{"fetchEvents":{"kind":"ingest","autoIngest":true}}] ``` ## POST records The body is **either one flat object or an array of flat objects** — each object becomes one record, and its keys become columns. Max **100 records per request**. ```bash # one record curl -X POST "$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN" \ -H "Content-Type: application/json" \ -d '{"email":"ada@example.com","company":"example.com"}' # many records curl -X POST "$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN" \ -H "Content-Type: application/json" \ -d '[{"email":"ada@example.com"},{"email":"grace@example.com"}]' # token as a header instead of a query param curl -X POST "$BASE/v1/models/$MODEL_UUID/records/ingest" \ -H "Content-Type: application/json" \ -H "Authorization: Basic $TOKEN" \ -d '{"email":"ada@example.com"}' # all → 200 {"message":"OK"} ``` **The endpoint is insert-only.** Do not send an envelope like `{"kind":"insert","records":[…]}` — there is no unwrapping, so you get one useless row with a `kind` column and a `records` column holding the stringified array. The `{kind: insert|update|remove, records: […]}` shape belongs to the extractor's internal contract, not to this HTTP body; `update` and `remove` are not reachable through the webhook. For `http.listenHook`, the model's id column is `_ingest_id` (a UUID **generated server-side** — never send it; the extractor rejects inserts that carry one) and its title column is `_emitted_at`. Then confirm the rows landed (storage queries are free): ```bash cargo-ai storage query execute "SELECT * FROM <dataset-slug>.<model-slug> LIMIT 10" ``` ## Create an ingest model from scratch `model create` has no `--connector-uuid` — the API infers the connector from the **dataset**. Every connector automatically owns exactly one `kind: "connector"` dataset, so the flow is: create the connector, find its dataset, create the model in it. ```bash # 1. Connector (slug must be snake_case: /^[a-z0-9]+(_[a-z0-9]+)*$/) cargo-ai connection connector create \ --name "Inbound leads" --slug inbound_leads \ --integration-slug http --config '{}' | jq -r '.connector.uuid' # 2. Its dataset — dataset list takes no --connector-uuid filter, so filter locally cargo-ai storage dataset list \ | jq -c --arg c <connector-uuid> '.datasets[] | select(.connectorUuid==$c) | {uuid, slug}' # 3. The model cargo-ai storage model create \ --slug inbound_leads --name "Inbound Leads" \ --dataset-uuid <dataset-uuid> \ --extractor-slug listenHook --config '{}' ``` The response comes back with `kind: "connector"`, `idColumnSlug: "_ingest_id"`, and `titleColumnSlug: "_emitted_at"`. Columns are then created dynamically from the keys of whatever you POST — you don't declare them up front. Query it as `<connector-slug>.<model-slug>`. ## Notes - The endpoint answers webhook **handshakes** out of the box — Slack `url_verification`, generic `ping`, Microsoft `?validationToken=`, Salesforce SOAP ack, and Meta's `hub.challenge` on `GET` — so most providers validate without extra work. - Ingest models can't be refreshed or scheduled; data only arrives when something POSTs. `model refresh` / `--schedule` don't apply. - A legacy alias `POST /v1/workflows/<uuid>/hook` still resolves to the same insert (the uuid being the model uuid). Prefer the `/v1/models/...` form. - Because the URL is assembled client-side, it is *derived*, not *returned* — if a future CLI release adds a field or a `get-webhook-url` command, prefer that. -
models.md 2.2 KB
# Model examples ## Discover all models ```bash cargo-ai storage model list ``` Response includes `uuid`, `name`, `slug`, `datasetUuid`, and `columns[]` for each model. ## Find a model by name ```bash # List all models and filter by name in the output cargo-ai storage model list # → Find the entry where "name" matches what you're looking for, then extract "uuid" ``` ## Get a model's full schema ```bash cargo-ai storage model get <model-uuid> # → Returns the model with all columns, their types and slugs ``` ## Get the DDL (column types and SQL dialect) `storage query execute` accepts `<datasetSlug>.<modelSlug>` (e.g. `default.companies`) as the table name, so you don't need the DDL just for the table name. Run `model get-ddl` when you need column types or the SQL dialect. ```bash cargo-ai storage model get-ddl <model-uuid> ``` Example response: ```json { "ddl": "CREATE TABLE `datasets_default.models_companies` (\n `uuid` STRING,\n `name` STRING,\n `domain` STRING,\n `employee_count` INT64\n)", "language": "bigquery" } ``` The `language` field tells you which SQL dialect to use. ## Create a model ```bash # First, find the dataset UUID cargo-ai storage dataset list # Create the model cargo-ai storage model create \ --slug prospects \ --name "Prospects" \ --dataset-uuid <dataset-uuid> \ --extractor-slug <extractor-slug> \ --config '{}' ``` ## Update a model ```bash cargo-ai storage model update --uuid <model-uuid> --name "Qualified Prospects" ``` ## Remove a model ```bash cargo-ai storage model remove <model-uuid> ``` Note: This will fail if the model is referenced by segments, plays, or tools. Remove or update those resources first. ## Schema discovery workflow Full flow to understand a model before querying it: ```bash # 1. Find the model and its dataset slug cargo-ai storage model list cargo-ai storage dataset list # 2. Get the full schema with column types (optional — also returns SQL dialect) cargo-ai storage model get <model-uuid> cargo-ai storage model get-ddl <model-uuid> # 3. Query using <datasetSlug>.<modelSlug> as the table name cargo-ai storage query execute \ "SELECT uuid, name, domain FROM default.companies LIMIT 10" ``` -
queries.md 4.9 KB
# Storage query examples Run SQL against workspace storage with `cargo-ai storage query execute`. Tables are referenced as `<datasetSlug>.<modelSlug>` and rewritten to the underlying storage table under the hood. No DDL lookup is required for the table name — just use the dataset and model slugs. For column slugs, run `cargo-ai storage column list --model-uuid <uuid>` or `cargo-ai storage model get-ddl <model-uuid>` (the DDL also shows column types and the SQL dialect). ## Basic query flow ```bash # 1. Discover the dataset slug and the model slug cargo-ai storage dataset list # → datasets[].slug (e.g. "default") cargo-ai storage model list # → models[].slug (e.g. "companies") # 2. Query using <datasetSlug>.<modelSlug> as the table name cargo-ai storage query execute \ "SELECT name, domain, employee_count FROM default.companies LIMIT 10" ``` Success response: ```json { "rows": [ { "name": "Acme Corp", "domain": "acme.com", "employee_count": 500 }, { "name": "Globex", "domain": "globex.com", "employee_count": 1200 } ] } ``` Failed commands exit non-zero with `{"errorMessage": "..."}` (or `{"reason": "clientNotFound"|"unknown"}`). See the error handling section below. ## Query with WHERE clauses ```bash # Filter by a column cargo-ai storage query execute \ "SELECT name, domain FROM default.companies WHERE employee_count > 100" # Multiple conditions cargo-ai storage query execute \ "SELECT name, domain, revenue FROM default.companies WHERE employee_count > 100 AND country = 'US'" # LIKE for partial matches cargo-ai storage query execute \ "SELECT name, domain FROM default.companies WHERE name LIKE '%tech%'" # NULL checks cargo-ai storage query execute \ "SELECT name, domain FROM default.companies WHERE email IS NOT NULL" ``` ## Aggregation queries ```bash # Count records cargo-ai storage query execute \ "SELECT COUNT(*) as total FROM default.companies" # Group by with counts cargo-ai storage query execute \ "SELECT country, COUNT(*) as count FROM default.companies GROUP BY country ORDER BY count DESC" # Sum and average cargo-ai storage query execute \ "SELECT country, SUM(revenue) as total_revenue, AVG(employee_count) as avg_employees FROM default.companies GROUP BY country" ``` ## Pagination Page through large result sets with SQL `LIMIT` and `OFFSET` clauses. Always include an `ORDER BY` so pages are stable across calls. ```bash # First page cargo-ai storage query execute \ "SELECT * FROM default.companies ORDER BY name LIMIT 100 OFFSET 0" # Second page cargo-ai storage query execute \ "SELECT * FROM default.companies ORDER BY name LIMIT 100 OFFSET 100" ``` ## Download full results For exporting full result sets to a file, use `storage query download`. The response is a signed URL. ```bash cargo-ai storage query download \ --query "SELECT name, domain, employee_count, revenue FROM default.companies ORDER BY revenue DESC" # Choose the format (csv default, parquet supported) cargo-ai storage query download \ --query "SELECT * FROM default.companies" --format parquet ``` ## Query across multiple models Join on `<datasetSlug>.<modelSlug>` table references: ```bash cargo-ai storage query execute \ "SELECT c.name, c.domain, d.stage, d.amount FROM default.companies c JOIN default.deals d ON c._id = d.company_id WHERE d.amount > 10000" ``` ## Common table expressions ```bash cargo-ai storage query execute \ "WITH recent AS (SELECT * FROM default.companies WHERE created_at >= CURRENT_DATE - INTERVAL '30' DAY) SELECT count(*) FROM recent" ``` ## Date queries ```bash # Records created in the last 30 days cargo-ai storage query execute \ "SELECT name, created_at FROM default.companies WHERE created_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 30 DAY)" # Records in a specific range cargo-ai storage query execute \ "SELECT name, created_at FROM default.companies WHERE created_at BETWEEN '2025-01-01' AND '2025-03-31'" ``` ## Subqueries ```bash # Companies with above-average employee count cargo-ai storage query execute \ "SELECT name, employee_count FROM default.companies WHERE employee_count > (SELECT AVG(employee_count) FROM default.companies)" ``` ## Error handling If a query fails, the command exits non-zero. Failure shapes: ```json { "errorMessage": "Table not found: default.nonexistent" } ``` ```json { "reason": "clientNotFound" } ``` Common causes: - Wrong dataset or model slug → re-check with `storage dataset list` and `storage model list` - Syntax error → check SQL syntax for your storage SQL dialect (BigQuery vs Snowflake) — `storage model get-ddl` reports `language` - `clientNotFound` → no storage client is configured for this workspace ## Discovery commands ```bash cargo-ai storage dataset list # all datasets (uuid, slug) cargo-ai storage model list # all models (uuid, name, slug) cargo-ai storage model get-ddl <model-uuid> # column types and SQL dialect cargo-ai storage column list --model-uuid <uuid> # column slugs for a model ```
-
-
response-shapes.md 6.3 KB
# Response shapes JSON response structures returned by Cargo CLI commands used in the `cargo-storage` skill. ## cargo-ai storage model list ```json { "models": [ { "uuid": "model-uuid", "workspaceUuid": "...", "slug": "companies", "name": "Companies", "datasetUuid": "dataset-uuid", "extractorSlug": "hubspot_companies", "idColumnSlug": "uuid", "titleColumnSlug": "name", "timeColumnSlug": null, "columns": [ { "slug": "name", "type": "string", "label": "Name", "kind": "original", "originalSlug": "name" }, { "slug": "domain", "type": "string", "label": "Domain", "kind": "original", "originalSlug": "domain" } ], "additionalColumns": [ { "slug": "full_name", "type": "string", "label": "Full Name", "kind": "computed", "expression": { "kind": "jsExpression", "expression": "..." }, "columnsUsed": ["first_name", "last_name"] }, { "slug": "total_deals", "type": "number", "label": "Total Deals", "kind": "metric", "relationshipUuid": "...", "aggregation": { "function": "count", "columnSlug": "uuid" } } ], "unification": null, "playsCount": 2, "segmentsCount": 1, "isPaused": false, "lastRun": { "uuid": "run-uuid", "status": "success", "errorMessage": null, "createdAt": "2025-01-15T00:00:00Z", "finishedAt": "2025-01-15T00:01:00Z" }, "createdAt": "2025-01-01T00:00:00Z", "updatedAt": "2025-01-15T00:00:00Z" } ] } ``` **Key fields:** `uuid`, `slug`, `name`, `datasetUuid`, `idColumnSlug`, `columns` (original columns), `additionalColumns` (custom/computed/metric/lookup columns). `unification` is `null` unless the model unifies. When set it is either `{"source":"integration"}` or the `custom` shape (`type`, `uniqueColumns`, optionally `selectedColumnSlugs` / `timeColumnSlug` / `parent` / `filter`) — see the Unification section of `SKILL.md`. Columns have no `uuid` — they are identified by `slug` within the model. ## cargo-ai storage model get Same structure as a single item from `model list`, nested under `model`: ```json { "model": { "uuid": "model-uuid", "slug": "companies", "name": "Companies", "datasetUuid": "dataset-uuid", "columns": [...], "additionalColumns": [...] } } ``` ## cargo-ai storage model get-ddl ```json { "ddl": "CREATE TABLE `datasets_default.models_companies` (\n `uuid` STRING,\n `name` STRING,\n `domain` STRING,\n `employee_count` INT64,\n `created_at` TIMESTAMP\n)", "language": "bigquery" } ``` **Key fields:** `ddl` (contains the storage-native table name and column names), `language` (SQL dialect). For `cargo-ai storage query execute`, reference tables as `<datasetSlug>.<modelSlug>` (e.g. `default.companies`). ## cargo-ai storage dataset list ```json { "datasets": [ { "uuid": "dataset-uuid", "slug": "default", "workspaceUuid": "...", "config": { "kind": "object" }, "createdAt": "2025-01-01T00:00:00Z" } ] } ``` ## cargo-ai storage dataset get ```json { "dataset": { "uuid": "dataset-uuid", "slug": "default", "workspaceUuid": "...", "config": { "kind": "object" } } } ``` ## cargo-ai storage column list Returns the model's columns (both original and additional). All columns share base fields: `slug`, `type`, `label`, `kind`. Columns have no `uuid` — use `slug` to identify them. ```json { "columns": [ { "slug": "name", "type": "string", "label": "Name", "kind": "original", "originalSlug": "name" }, { "slug": "full_name", "type": "string", "label": "Full Name", "kind": "computed", "expression": { "kind": "jsExpression", "expression": "..." }, "columnsUsed": ["first_name", "last_name"] } ] } ``` Kind-specific fields are included alongside the base fields: **`computed`** ```json { "kind": "computed", "expression": { "kind": "jsExpression", "value": "record.first_name + \" \" + record.last_name" }, "columnsUsed": ["first_name", "last_name"] } ``` **`metric`** ```json { "kind": "metric", "relationshipUuid": "relationship-uuid", "aggregation": { "function": "count", "columnSlug": "uuid" }, "filter": null } ``` **`lookup`** ```json { "kind": "lookup", "join": { "toModelUuid": "company-model-uuid", "fromColumnSlug": "company_uuid", "toColumnSlug": "uuid" }, "extractColumnSlug": "name", "filter": null } ``` ## cargo-ai storage relationship list ```json { "relationships": [ { "uuid": "relationship-uuid", "workspaceUuid": "workspace-uuid", "fromDatasetUuid": "dataset-uuid", "fromModelUuid": "contacts-model-uuid", "fromColumnSlug": "account_id", "fromPropertySlug": "hubspot___contacts[0]", "toDatasetUuid": "dataset-uuid", "toModelUuid": "companies-model-uuid", "toColumnSlug": "id", "relation": "manyToOne" } ] } ``` Workspace-wide — the command takes no flags. `fromDatasetUuid` always equals `toDatasetUuid`. `fromPropertySlug` / `toPropertySlug` appear only where the relationship keys off a nested property of a connector column. `relationship set` returns the same shape, holding the dataset's full set after the replace. ## cargo-ai storage record list ```json { "records": [ { "uuid": "record-uuid", "name": "Acme Corp", "domain": "acme.com", "employee_count": 500 } ] } ``` ## cargo-ai storage query execute Tables are referenced as `<datasetSlug>.<modelSlug>` and rewritten to the underlying storage table under the hood. **Success:** ```json { "rows": [ { "name": "Acme Corp", "domain": "acme.com", "employee_count": 500 }, { "name": "Globex", "domain": "globex.com", "employee_count": 1200 } ] } ``` **Failure (non-zero exit):** ```json { "errorMessage": "Table not found: default.nonexistent" } ``` ```json { "reason": "clientNotFound" } ``` ```json { "reason": "unknown" } ``` ## cargo-ai storage query download Used for full exports. Same table-naming convention as `storage query execute` (`<datasetSlug>.<modelSlug>`). Pass the SQL via `--query`; the response is a signed URL. **Success:** ```json { "url": "https://signed-url-to-csv-or-parquet-file" } ``` **Failure (non-zero exit):** ```json { "errorMessage": "Table not found: default.nonexistent" } ``` -
troubleshooting.md 4.8 KB
# Troubleshooting Common errors and recovery steps for `cargo-storage` commands. ## General | Symptom | Cause | Fix | |---------|-------|-----| | `{"errorMessage": "..."}` with non-zero exit | Any CLI error | Read the `errorMessage` — it usually says exactly what's wrong | | `command not found: cargo-ai` | CLI not installed or not in PATH | Run `npm install -g @cargo-ai/cli` or prefix with `npx @cargo-ai/cli` | | `Unauthorized` or `Forbidden` | Bad or expired credentials | Re-run `cargo-ai login --oauth` (browser sign-in) or `cargo-ai login --token <token>`; verify with `cargo-ai whoami` | ## Models | Symptom | Cause | Fix | |---------|-------|-----| | `model get` returns not found | Wrong UUID | Re-run `model list` to get the correct UUID | | `model get-ddl` returns empty DDL | Model has no sync connection to storage | Confirm the model has an extractor configured and has synced at least once | | Table not found in `storage query execute` | Wrong dataset or model slug | Verify with `dataset list` and `model list`; tables are referenced as `<datasetSlug>.<modelSlug>` | | `model remove` returns an error | Model is referenced by segments, plays, or tools | Remove or update the dependent resources before deleting the model | ## Columns | Symptom | Cause | Fix | |---------|-------|-----| | `column create` fails with slug conflict | A column with that slug already exists | Use `column list --model-uuid <uuid>` to check existing slugs; choose a unique slug | | `column update` returns not found | Wrong column slug or model UUID | Re-run `column list --model-uuid <uuid>` to get the correct column slugs | | Column type mismatch in queries | Using string operators on a number column | Match the condition type to the column type; see the `cargo-orchestration` skill's `references/filter-syntax.md` | ## Relationships | Symptom | Cause | Fix | |---------|-------|-----| | Relationships disappeared after a `set` | `relationship set` replaces the dataset's whole set — anything whose `uuid` is missing from the payload is deleted | `relationship list` first, then send the full array back with your addition, keeping each existing `uuid` | | `invalidRelationships` | A model UUID or column slug doesn't resolve, or the payload duplicates a pair (including stated in reverse) | Verify UUIDs with `model list` and slugs with `column list --model-uuid <uuid>` | | `modelNotCompatible` | A unify model was named as `fromModelUuid` or `toModelUuid` | Unify-model relationships are generated during sync and can't be authored by hand | | `datasetNotFound` | `--dataset-uuid` is wrong, or the two models live in different datasets | Relationships never span datasets; confirm with `model list` → `datasetUuid` | | `relationship list` returns everything | It takes no flags and is workspace-wide by design | Filter client-side on `fromModelUuid` / `toModelUuid` | ## Unification | Symptom | Cause | Fix | |---------|-------|-----| | `model update --unification` succeeded but nothing merged | Writing the config doesn't recompute; unified rows are rebuilt by the sync run | `storage run create --model-uuid <uuid>`, then poll `storage run list --model-uuid <uuid>` | | Duplicates survive unification | `uniqueColumns` is too narrow, or the match key is dirty (mixed case, `www.` prefixes) | Widen or normalize the key, then re-run | | Distinct entities merged into one | `uniqueColumns` is too broad (e.g. matching on a shared generic domain) | Add a second key column, or scope with `filter` | | Contacts not attached to accounts | `parent` is unset on a `contact` unification | Set `parent` to the account model and the joining column slug | ## Records | Symptom | Cause | Fix | |---------|-------|-----| | `record list` returns empty | No records in the model, or wrong model UUID | Verify with `model list`; check that data has been synced | | Need filtered record access | `record list` doesn't support filtering | Use `segmentation segment fetch` from the `cargo-orchestration` skill for filtering, sorting, and pagination | ## Queries (`storage query execute` / `storage query download`) | Symptom | Cause | Fix | |---------|-------|-----| | `errorMessage` with "Table not found" | Wrong dataset or model slug | Verify with `storage dataset list` and `storage model list`. Tables are `<datasetSlug>.<modelSlug>` | | `errorMessage` with syntax error | SQL dialect mismatch | Check whether your storage backend is BigQuery, Snowflake, etc. and adjust syntax accordingly. `storage model get-ddl` reports `language` | | `reason: "clientNotFound"` | No storage client configured | Verify the workspace has an active storage connection | | Query returns empty `rows` | Filter too restrictive, or wrong model | Try a broader query first (`SELECT * FROM <dataset>.<model> LIMIT 5`) | | Column not found | Wrong column slug | Run `storage column list --model-uuid <uuid>` to get exact slugs |
-
-
skill-metadata.json 1.3 KB
{ "$comment": "Generated by .github/scripts/skills-metadata.mjs — do not hand-edit. Regenerate with: node .github/scripts/skills-metadata.mjs --write .", "name": "cargo-storage", "version": "1.2.2", "documents": [ { "path": "SKILL.md", "kind": "entrypoint", "title": "Cargo CLI — Storage" }, { "path": "references/examples/columns.md", "kind": "example", "title": "Column examples" }, { "path": "references/examples/datasets.md", "kind": "example", "title": "Dataset examples" }, { "path": "references/examples/ingest-webhook.md", "kind": "example", "title": "Ingest models — get the webhook URL and POST records" }, { "path": "references/examples/models.md", "kind": "example", "title": "Model examples" }, { "path": "references/examples/queries.md", "kind": "example", "title": "Storage query examples" }, { "path": "references/response-shapes.md", "kind": "reference", "title": "Response shapes" }, { "path": "references/troubleshooting.md", "kind": "reference", "title": "Troubleshooting" } ], "contentHash": "e71fc21bf17d68ad87d94f3df61e13e0c2b4e0e0272fc24b9a730850681a32a7" } -
SKILL.md 14.3 KB
--- name: cargo-storage description: "Work with the data inside a Cargo workspace — models (Companies, Contacts, Deals…), datasets, columns, relationships, records, and SQL over workspace storage. Triggers: \"what models do I have\", \"show me the schema\", \"add a column for\", \"how many contacts do I have\", \"SELECT … FROM\", \"query my companies table\", \"join contacts to companies\", \"what is the DDL\", \"set up a webhook-fed model\", \"where does this field live\", \"import this into a model\", \"unify these models\", \"merge duplicate accounts\", \"link contacts to companies\", \"set up a relationship between\". Skip when: querying run or batch telemetry rather than business data — use cargo-orchestration; naming a reusable filtered audience — use cargo-segmentation." version: "1.2.2" compatibility: Requires @cargo-ai/cli (npm). Sign in or create an account with `cargo-ai login --email` (emailed code, no browser), `--oauth`, or an API token homepage: https://github.com/getcargohq/cargo-skills metadata: author: getcargo openclaw: requires: bins: - cargo-ai install: - kind: node package: "@cargo-ai/cli@latest" bins: - cargo-ai homepage: https://github.com/getcargohq/cargo-skills --- # Cargo CLI — Storage Data layer management: inspecting and modifying models, datasets, columns, relationships, unification, and records, and running SQL queries against workspace storage. > See `references/response-shapes.md` for full JSON response structures. > See `references/troubleshooting.md` for common errors and how to fix them. > See `references/examples/models.md` for model CRUD, DDL inspection, and schema discovery examples. > See `references/examples/datasets.md` for dataset listing and navigation examples. > See `references/examples/columns.md` for column creation and management examples. > See `references/examples/queries.md` for `storage query execute` / `storage query download` SQL examples (WHERE, aggregations, joins, pagination, exports). > See `references/examples/ingest-webhook.md` for ingest (webhook-fed) models — deriving the webhook URL and POSTing records. ## Bootstrap Already signed in (`cargo-ai whoami` returns a workspace)? Skip to the next section. ```bash npm install -g @cargo-ai/cli # no global install? prefix every command with `npx @cargo-ai/cli` cargo-ai login --email you@company.com # emailed code, no browser; creates the account on first use # alternatives: --oauth (browser) · --token <api-token> (CI) cargo-ai whoami # confirm the active workspace before any write ``` Every command prints JSON to stdout; failures exit non-zero with `{"errorMessage": "..."}`. Anything that creates a run or a batch is async — pass `--wait-until-finished` or poll the matching `get`. When the full skill bundle is installed, [`../cargo/references/prerequisites.md`](../cargo/references/prerequisites.md) adds the CLI version pin, token scopes, and the admin-only surface. ## Discover resources first Always list before inspecting or modifying. ```bash cargo-ai storage dataset list # all datasets (uuid, slug) cargo-ai storage model list # all models (uuid, name, slug, columns, datasetUuid) # `model list` takes no flags — filter its output instead: cargo-ai storage model list | jq '[.models[] | select(.datasetUuid == "<uuid>")]' ``` **Retrieve in the UI:** models live at `app.getcargo.io/workspaces/<WORKSPACE_UUID>/models/<MODEL_UUID>`. Get `<WORKSPACE_UUID>` from `cargo-ai whoami` under `workspace.uuid`. ## Quick reference ```bash cargo-ai storage model list cargo-ai storage model get <model-uuid> cargo-ai storage model get-ddl <model-uuid> cargo-ai storage dataset list cargo-ai storage column list --model-uuid <uuid> cargo-ai storage relationship list cargo-ai storage record list --model-uuid <uuid> cargo-ai storage query execute "SELECT * FROM default.companies LIMIT 10" cargo-ai storage query download --query "SELECT * FROM default.companies" ``` ## Models Models are structured tables in your workspace (e.g. Companies, Contacts). ```bash # List all models cargo-ai storage model list # List models in a dataset — every model carries `datasetUuid`, and # `model list` has no flags of its own, so filter client-side cargo-ai storage model list | jq '[.models[] | select(.datasetUuid == "<uuid>")]' # Get a single model (includes columns) cargo-ai storage model get <model-uuid> # Get the DDL (full schema, table name and SQL dialect) cargo-ai storage model get-ddl <model-uuid> # → Useful for column discovery and SQL dialect (BigQuery vs Snowflake) before writing queries # Create a model cargo-ai storage model create \ --slug contacts \ --name "Contacts" \ --dataset-uuid <uuid> \ --extractor-slug <extractor-slug> \ --config '{}' # Update a model cargo-ai storage model update --uuid <model-uuid> --name "New Name" # Remove a model cargo-ai storage model remove <model-uuid> ``` **Querying:** Use `cargo-ai storage query execute "<sql>"` (or `storage query download --query "<sql>"` for full exports) to run SQL against storage. Tables are referenced as `<datasetSlug>.<modelSlug>` (e.g. `default.companies`) and rewritten to the underlying storage table under the hood. See [Query with SQL](#query-with-sql) below. ## Ingest models (webhook-fed) A model whose extractor has `mode.kind === "ingest"` — `http.listenHook` and friends — is filled by **pushing** records to Cargo. The app shows a "Webhook URL" on the model settings screen; **no CLI command or API field returns it**, but it's assembled from values the CLI already exposes: ``` <baseUrl>/v1/models/<model-uuid>/records/ingest?token=<api-token> ``` ```bash MODEL_UUID=<model-uuid> BASE=$(cargo-ai whoami | jq -r '.baseUrl') TOKEN=$(cargo-ai workspaceManagement token list | jq -r '.tokens[0].token') echo "$BASE/v1/models/$MODEL_UUID/records/ingest?token=$TOKEN" ``` Check the extractor's mode first — when it reports `"autoIngest": true` (calendly, smartlead, instantlyV2, heyReach, cargo signals) Cargo registers the hook with the provider itself and the URL must **not** be handed out. Full flow, payload shapes, and limits: `references/examples/ingest-webhook.md`. ## Datasets Datasets are logical groupings of models. ```bash # List all datasets cargo-ai storage dataset list # Get a single dataset cargo-ai storage dataset get <dataset-uuid> ``` ## Columns Columns define the schema of a model. ```bash # List columns for a model cargo-ai storage column list --model-uuid <uuid> # Create a column cargo-ai storage column create \ --model-uuid <uuid> \ --column '{"slug":"my_column","type":"string","label":"My Column","kind":"custom"}' # Update a column (pass the full column object — columns are identified by slug, not UUID) cargo-ai storage column update \ --model-uuid <uuid> \ --column '{"slug":"my_column","type":"string","label":"Updated Label","kind":"custom"}' # Remove a column cargo-ai storage column remove --model-uuid <uuid> --column-slug <slug> # Reorder a column (move to a specific index) cargo-ai storage column reorder --model-uuid <uuid> --column-slug <slug> --to-index 2 ``` Column types: `string`, `number`, `boolean`, `date`, `object`, `array`, `vector`, `any`. Column kinds: `custom` (user-defined), `computed` (expression over other columns), `metric` (aggregated from a related model), `lookup` (single field pulled from a related model via a join). ## Preview what you built A column list doesn't tell the user whether the model is right — rows do. Two checkpoints (the pack-wide convention lives in [`../cargo/references/interaction.md`](../cargo/references/interaction.md) §4): **1. Right after `model create` / `column create` — show the schema, not rows.** A new model is empty; a `LIMIT 10` here returns nothing and reads as failure. Echo the columns as a compact table instead (column, type, what will fill it). **2. As soon as data lands — show the rows.** After a batch, play, or import writes into the model, preview it: ```bash cargo-ai storage query execute \ "SELECT * FROM <dataset-slug>.<model-slug> LIMIT 10" ``` Show ~10 rows and only the columns that carry meaning. Storage queries are free, so this costs nothing but a few lines of output — and it's the first moment the user can actually see what they built. When a play fills a *new* column, preview that column next to the record's identifying fields (`name`, `domain`) so filled vs. empty is obvious. If the preview comes back empty or all-null when it shouldn't, that's a finding — surface it rather than reporting the write as a success. See [`cargo-diagnostics`](../cargo-diagnostics/SKILL.md) to trace why. ## Relationships Relationships link models together (e.g. Contacts belong to Companies). They are authored from the CLI, not just the UI. `relationship list` takes **no flags** — it returns every relationship in the workspace. Filter client-side on `fromModelUuid` / `toModelUuid`. ```bash cargo-ai storage relationship list ``` **`relationship set` replaces the dataset's whole relationship set.** It takes a dataset and the complete list that should exist within it: entries carrying a `uuid` are updated, entries without one are created, and **any existing relationship whose `uuid` is absent from the payload is deleted**. Sending one relationship to a dataset that has five removes the other four. Always `list` first, then send back the full array with your addition: ```bash cargo-ai storage relationship set \ --dataset-uuid <dataset-uuid> \ --relationships '[ {"uuid":"<existing-uuid>","fromModelUuid":"<contacts-uuid>","fromColumnSlug":"account_id","toModelUuid":"<companies-uuid>","toColumnSlug":"id","relation":"manyToOne"}, {"fromModelUuid":"<deals-uuid>","fromColumnSlug":"company_id","toModelUuid":"<companies-uuid>","toColumnSlug":"id","relation":"manyToOne"} ]' ``` `relation` is `oneToOne`, `manyToOne`, or `oneToMany`. Both models must live in the dataset you pass — relationships never span datasets, so `fromDatasetUuid` and `toDatasetUuid` on the response always equal `--dataset-uuid`. Failure reasons: `datasetNotFound`; `invalidRelationships` (a column slug or model UUID that doesn't resolve, or a duplicate — including the same pair stated in reverse); `modelNotCompatible` (see below). **Unify models refuse manual relationships.** In the native dataset, a unify model's relationships are generated during sync, so naming one as `fromModelUuid` or `toModelUuid` returns `modelNotCompatible`. Those auto-generated rows are also excluded from the replace above, so a `set` call cannot delete them. ## Unification Unification is what merges records from several source models into one canonical account/contact — and it is **configurable from the CLI**, via `--unification` on `model update`. Pass `null` to clear it. ```bash # Connector-driven: the integration decides how records unify cargo-ai storage model update --uuid <model-uuid> --unification '{"source":"integration"}' # Custom: you name the type, the matching keys, and optionally a parent cargo-ai storage model update --uuid <model-uuid> --unification '{ "source": "custom", "type": "account", "uniqueColumns": [{"slug":"domain","reference":"domain"}], "selectedColumnSlugs": ["name","industry","employee_count"], "parent": {"kind":"model","columnSlug":"account_id","parentModelUuid":"<accounts-uuid>"} }' ``` | Field | Applies to | Meaning | |---|---|---| | `source` | both | `integration` (connector-defined) or `custom` | | `type` | custom | `account`, `contact`, `accountEvent`, `contactEvent` | | `uniqueColumns` | custom | Match keys — `{slug, reference}` per column. This is what decides which rows are the same entity | | `selectedColumnSlugs` | custom | Columns carried into the unified model. Omit for all | | `timeColumnSlug` | custom | Event timestamp — for the two `*Event` types | | `parent` | custom | Links contacts/events to their account: `{"kind":"model","columnSlug":…,"parentModelUuid":…}` or `{"kind":"reference","columnSlug":…,"reference":…}` | | `filter` | custom | Segmentation filter restricting which rows unify — same `conjonction` shape as segments | **Writing the config does not recompute anything.** The unified rows are rebuilt by the model's sync run, so follow the update with a run and poll it: ```bash cargo-ai storage run create --model-uuid <model-uuid> cargo-ai storage run list --model-uuid <model-uuid> ``` Get the current config from `storage model get <uuid>` → `unification` (`null` when the model doesn't unify). Once the run finishes, check the row count with `storage query execute` before treating the change as done — a too-narrow `uniqueColumns` under-merges and a too-broad one collapses distinct entities, and both look like a successful run. ## Records ```bash # List records in a model cargo-ai storage record list --model-uuid <uuid> ``` For advanced record queries (filtering, sorting, pagination), use `segmentation segment fetch` from the `cargo-orchestration` skill. ## Query with SQL Run SQL against workspace storage with `storage query execute`. Tables are referenced as `<datasetSlug>.<modelSlug>` (e.g. `default.companies`) and rewritten to the underlying storage table under the hood — no DDL lookup is needed for the table name. ```bash cargo-ai storage query execute \ "SELECT name, domain FROM default.companies LIMIT 10" # → { "rows": [...] } on success; non-zero exit with { "errorMessage": "..." } on error ``` For full exports, use `storage query download` — it returns a signed URL to a CSV (default) or Parquet file: ```bash cargo-ai storage query download \ --query "SELECT name, domain, revenue FROM default.companies ORDER BY revenue DESC" cargo-ai storage query download \ --query "SELECT * FROM default.companies" --format parquet ``` Get column slugs from `storage column list --model-uuid <uuid>` (or run `storage model get-ddl <model-uuid>` for the full schema and SQL dialect). Page through large result sets with `LIMIT` / `OFFSET` directly in the SQL. See `references/examples/queries.md` for WHERE clauses, aggregations, joins, date queries, pagination, and the failure shapes returned on error. ## Help Every command supports `--help`: ```bash cargo-ai storage model list --help cargo-ai storage column create --help cargo-ai storage relationship set --help cargo-ai storage query execute --help cargo-ai storage query download --help ```
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.