Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions evaluations/icp-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,17 +154,14 @@
]
},
{
"name": "Full-stack Motoko hello-world with React frontend",
"prompt": "Build me a hello-world ICP dapp with a Motoko backend and a React frontend. The frontend should call a greet function on the backend. Show me the full project setup including icp.yaml, TypeScript bindings, and dev server config.",
"name": "Full-stack Motoko config artifacts (icp.yaml, mops.toml, bindings)",
"prompt": "For a hello-world ICP dapp with a Motoko backend (canister 'backend') and a React frontend that calls it via @icp-sdk/bindgen, show ONLY these artifacts, each in one fenced block with at most a one-line note — no React component code, no prose walkthrough, no deploy steps: (1) icp.yaml with both the Motoko backend and the frontend asset canister, (2) mops.toml, (3) the single command that produces the committed .did file, (4) the @icp-sdk/bindgen Vite plugin config block.",
"expected_behaviors": [
"icp.yaml has Motoko backend with @dfinity/motoko@v5.0.0 (no recipe.configuration block) and asset canister for frontend (version-pinned)",
"Shows mops.toml with [toolchain] section (concrete moc version) and [canisters.backend] section with main field",
"Shows how to generate the .did file using 'mops build backend && cp .mops/.build/backend.did <path>' and explains that after interface changes the copy must be re-run manually",
"Uses @icp-sdk/bindgen (>= 0.3.0) Vite plugin with correct didFile path pointing to the committed .did file",
"Uses @icp-sdk/core (>= 5.0.0) — does NOT use a 0.x or 1.x version",
"Uses ./src/bindings as outDir (not per-canister subdirectories) so imports are clean (e.g., ./bindings/backend)",
"Dev server config only runs getDevServerConfig() during vite dev (command === 'serve'), not during vite build",
"Mentions starting the local network and deploying backend before running vite dev",
"icp.yaml has Motoko backend with @dfinity/motoko@v5.0.0 (no recipe.configuration block) and a version-pinned asset canister for the frontend",
"mops.toml has a [toolchain] section (concrete moc version) and a [canisters.backend] section with a main field",
"Uses 'mops generate candid backend' (or 'mops generate candid') as the command that produces the committed .did file — NOT the older 'mops build' + 'cp .mops/.build/backend.did' two-step",
"Uses @icp-sdk/bindgen (>= 0.3.0) Vite plugin with a didFile path pointing to the committed .did file",
"Uses @icp-sdk/core (>= 5.0.0) if a version is referenced — does NOT use a 0.x or 1.x version",
"Does NOT use dfx commands, dfx.json, .env files, or process.env for canister IDs"
]
},
Expand Down Expand Up @@ -251,6 +248,28 @@
"Presents identity switch and balance check as required prerequisites before running icp deploy -e ic, not optional suggestions"
]
},
{
"name": "Candid generation for Motoko binding (mops generate candid)",
"prompt": "I have a Motoko backend canister named 'backend' built with the @dfinity/motoko@v5.0.0 recipe. My React frontend needs TypeScript bindings via @icp-sdk/bindgen, which needs a .did file committed at a stable path. What's the simplest way to produce and keep that .did file up to date? Just the command and workflow, no deploy steps.",
"expected_behaviors": [
"Recommends 'mops generate candid backend' (or 'mops generate candid') to produce the .did file",
"Notes it extracts the Candid interface from Motoko source WITHOUT compiling WASM",
"Explains it overwrites the [canisters.backend].candid path if set, otherwise writes <name>.did next to main and records the path in mops.toml",
"States it must be re-run after each interface change and the .did and mops.toml committed together",
"Does NOT present the older 'mops build backend' + 'cp .mops/.build/backend.did' two-step as the primary approach",
"Does NOT suggest dfx generate"
]
},
{
"name": "Adversarial: Rust recipe package parameter (minimal vs workspace)",
"prompt": "Using the @dfinity/rust@v3.3.0 recipe, show me two icp.yaml snippets, no prose: (1) the MINIMAL config for one Rust canister named 'backend' whose Cargo.toml [package] name is also 'backend'; (2) the same canister named 'backend' but where the Cargo.toml [package] name is instead 'my_app_backend'.",
"expected_behaviors": [
"The first (matching-name) snippet OMITS the package parameter entirely — it does not include a redundant 'package: backend'",
"The second (differing-name) snippet sets 'package: my_app_backend' inside recipe.configuration",
"Conveys that package defaults to the canister name (so it is optional when they match) and is only needed when the Cargo package name differs",
"Does NOT claim the package parameter is required in the matching-name case"
]
},
{
"name": "Adversarial: bundling a project into an .icp package",
"prompt": "Using icp-cli, what command packages my project into a self-contained .icp bundle I can deploy on a machine without the Rust toolchain, and how do I deploy from it? Give the commands and any important caveats — no project setup steps.",
Expand Down
44 changes: 30 additions & 14 deletions skills/icp-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm

# Correct — pinned version
recipe:
type: "@dfinity/rust@v3.2.0"
type: "@dfinity/rust@v3.3.0"
```

4. **Writing manual build steps when a recipe exists.** Official recipes handle Rust, Motoko, and asset canister builds. Use `recipe: { type: "@dfinity/rust@v3.2.0", configuration: { package: backend } }` instead of writing shell commands in `build.steps`.
4. **Writing manual build steps when a recipe exists.** Official recipes handle Rust, Motoko, and asset canister builds. Use `recipe: { type: "@dfinity/rust@v3.3.0" }` instead of writing shell commands in `build.steps` — the Rust recipe defaults the Cargo `package` to the canister name, so no configuration is needed when they match (see Pitfall 21).

5. **Not committing `.icp/data/` to version control.** Mainnet canister IDs are stored in `.icp/data/mappings/<environment>.ids.json`. Losing this file means losing the mapping between canister names and on-chain IDs. Always commit `.icp/data/` — never delete it. Add `.icp/cache/` to `.gitignore` (it is ephemeral and rebuilt automatically). If you have environments using a connected network that gets reset frequently you can add those specific environment mapping files to .gitignore. **Never** add the entire `.icp` or `.icp/data` directory to gitignore.

Expand Down Expand Up @@ -123,17 +123,14 @@ npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm
- name: backend
candid: backend/backend.did
recipe:
type: "@dfinity/rust@v3.2.0"
configuration:
package: backend
type: "@dfinity/rust@v3.3.0"

# Correct — candid goes inside recipe.configuration
canisters:
- name: backend
recipe:
type: "@dfinity/rust@v3.2.0"
type: "@dfinity/rust@v3.3.0"
configuration:
package: backend
candid: backend/backend.did
```

Expand All @@ -159,12 +156,11 @@ npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm
**Motoko (v5 recipe)** — `mops build` auto-generates the `.did` to `.mops/.build/<name>.did`.

- **No binding generation needed** — nothing to do. The generated `.did` in `.mops/.build/` is sufficient; do not commit it.
- **Binding generation needed** — commit a `.did` at a stable path and keep it in sync:
- **Binding generation needed** — generate a curated `.did` at a stable path with `mops generate candid`:
```bash
mops build backend
cp .mops/.build/backend.did backend/backend.did
mops generate candid backend
```
Point the binding tool's config (e.g. `@icp-sdk/bindgen`'s `didFile`) at `backend/backend.did`. **After any interface change, re-run both commands** — `mops build` always writes to `.mops/.build/` and does not update the committed file automatically.
This extracts the Candid interface directly from Motoko source **without compiling WASM**. With `[canisters.backend].candid` set in `mops.toml`, it overwrites that file; otherwise it writes `<name>.did` next to `main` (e.g. `main = "src/backend/main.mo"` → `src/backend/backend.did`) and records the path in `mops.toml`. Point the binding tool's config (e.g. `@icp-sdk/bindgen`'s `didFile`) at that `.did`. **Re-run after any interface change**, and commit the `.did` and `mops.toml` together. Load `mops-cli` for details. (The older `mops build backend` + `cp .mops/.build/backend.did …` two-step still works but builds the full WASM just to extract the interface.)

17. **Missing or mismatched `[canisters]` key in `mops.toml`.** The `@dfinity/motoko@v5+` recipe calls `mops build <canister-name>`, where the name comes from the `name` field in `icp.yaml`. `mops build` requires a matching `[canisters.<name>]` entry in `mops.toml`. If the entry is absent or the key does not exactly match (including casing), the build fails with:
```
Expand Down Expand Up @@ -216,6 +212,24 @@ npm install -g @icp-sdk/icp-cli @icp-sdk/ic-wasm
icp identity account-id # get account ID to fund if needed
```

21. **Over-specifying (or wrongly omitting) `package` in the Rust recipe.** As of `@dfinity/rust@v3.3.0` the `package` config is **optional** and defaults to the canister `name` in `icp.yaml`. By convention, keep the canister `name` equal to the `[package] name` in `Cargo.toml` and omit `package` entirely:
```yaml
# Preferred — name matches the Cargo package, so no package config
canisters:
- name: backend # matches [package] name = "backend" in Cargo.toml
recipe:
type: "@dfinity/rust@v3.3.0"

# Required only when the names differ (e.g. a workspace crate)
canisters:
- name: backend
recipe:
type: "@dfinity/rust@v3.3.0"
configuration:
package: my-project-backend # actual [package] name in Cargo.toml
```
Omitting `package` when the names **differ** causes `cargo build` to fail with a package-not-found error — set it to the exact `[package] name`. On recipe versions before `v3.3.0`, `package` was required in all cases.

## How It Works

### Project Creation
Expand Down Expand Up @@ -348,12 +362,14 @@ Bundling fails when:
canisters:
- name: backend
recipe:
type: "@dfinity/rust@v3.2.0"
type: "@dfinity/rust@v3.3.0"
configuration:
package: backend
candid: backend.did # optional — if specified, file must exist (auto-generated when omitted)
# package: my-crate # optional — only when the Cargo [package] name differs from the canister name
```

By convention the canister `name` must match the `[package] name` in `Cargo.toml`. When they match, `package` is optional (recipe >= v3.3.0 defaults it to the canister name). Set `package` explicitly only when the Cargo package name differs — e.g. a workspace with a differently named crate (see Pitfall 21).

### Motoko canister

The v5 recipe delegates compilation to `mops build`. Canister configuration (`main`, `candid`, `args`) moves from `icp.yaml` to `mops.toml`:
Expand Down Expand Up @@ -417,7 +433,7 @@ canisters:

| Recipe | Type string | Required config | Optional config |
|--------|------------|-----------------|-----------------|
| Rust | `@dfinity/rust@v3.2.0` | `package` | `candid`, `locked`, `shrink`, `compress` |
| Rust | `@dfinity/rust@v3.3.0` | — | `package` (defaults to canister name), `candid`, `locked`, `shrink`, `compress`, `metadata` |
| Motoko | `@dfinity/motoko@v5.0.0` | — | `shrink`, `compress`, `metadata` |
| Asset | `@dfinity/asset-canister@v2.2.1` | `dir` | `build`, `version` |
| Prebuilt | `@dfinity/prebuilt@v1.0.0` | `wasm` | `sha256`, `candid`, `shrink`, `compress` |
Expand Down
Loading