[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6GPT1VFF907GGBS05PSJBXC":3,"skill-same:01M6GPT1VFF907GGBS05PSJBXC":95},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":16,"sourceHtmlUrl":17,"categories":18,"keywords":27,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":58,"commitSha":59,"skillDirectory":60,"forksCount":61,"repositoryHtmlUrl":62,"repositoryHomepageUrl":6,"ownerHtmlUrl":63,"ownerBlogUrl":64,"ownerDescription":65,"updatedAt":66,"install":67,"resources":80},"01M6GPT1VFF907GGBS05PSJBXC","deploy-to-cloud-engine",null,"Deploys a built Internet Computer project to a cloud engine (OpenCloud). From a sandboxed chat or cloud session with the ICP MCP connector (ChatGPT, Claude, https:\u002F\u002Fmcp.internetcomputer.org\u002Fmcp): install the wasm through the engine-operator's `proxy`. Otherwise with the icp CLI: `icp identity link web` (defaults to https:\u002F\u002Fopencloud.org), `icp deploy` on the engine's subnet. Also: `__META_*` for a named console app, version metadata (service:git:sha), and the proxy canister for Bitcoin\u002FEthereum signing, VetKeys or exchange rates (paid from the engine's balance, or `icp new --subfolder proxy`). Use when shipping to a cloud engine; on mention of OpenCloud, an engine subnet id, ICP MCP deploys, or linking the icp CLI; when sign-in never completes from a sandbox; when naming, versioning or giving an icon to a console app; when funding a proxy or picking which balance to top up (failing proxied calls: cloud-engine-canisters). Do NOT use for a mainnet deploy with no engine (icp-cli) or canister logic.","jiayaoqijia\u002Fcryptoskill","jiayaoqijia","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F1453879?v=4","01M6GMYTX7Q7S504VC6E756ENR","YQ","01M6GMYV1QKJ1CC75MTTGT9PR0",81,"Apache-2.0","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-deploy-to-cloud-engine\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-deploy-to-cloud-engine\u002FSKILL.md",[19,24],{"categoryNo":20,"categoryName":21,"taxonomyType":22,"taxonomyTypeDesc":23},"01JBSEEDCAT000000000000011","运维与部署","SCENARIO","使用场景",{"categoryNo":25,"categoryName":26,"taxonomyType":22,"taxonomyTypeDesc":23},"01JBSEEDCAT000000000000023","Skill与智能体开发",[28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"deploy","deploy to cloud engine","cloud","engine","failing","for","verified","exchange rates","do","identity","git","bitcoin","versioning","MCP","internet","icp","Meta","actions","project","which","app","2026-11-10 10:12:30",false,"---\nname: deploy-to-cloud-engine\ndescription: \"Deploys a built Internet Computer project to a cloud engine (OpenCloud). From a sandboxed chat or cloud session with the ICP MCP connector (ChatGPT, Claude, https:\u002F\u002Fmcp.internetcomputer.org\u002Fmcp): install the wasm through the engine-operator's `proxy`. Otherwise with the icp CLI: `icp identity link web` (defaults to https:\u002F\u002Fopencloud.org), `icp deploy` on the engine's subnet. Also: `__META_*` for a named console app, version metadata (service:git:sha), and the proxy canister for Bitcoin\u002FEthereum signing, VetKeys or exchange rates (paid from the engine's balance, or `icp new --subfolder proxy`). Use when shipping to a cloud engine; on mention of OpenCloud, an engine subnet id, ICP MCP deploys, or linking the icp CLI; when sign-in never completes from a sandbox; when naming, versioning or giving an icon to a console app; when funding a proxy or picking which balance to top up (failing proxied calls: cloud-engine-canisters). Do NOT use for a mainnet deploy with no engine (icp-cli) or canister logic.\"\nlicense: Apache-2.0\ncompatibility: \"ICP MCP route: the ICP MCP connector (native in ChatGPT and Claude, or https:\u002F\u002Fmcp.internetcomputer.org\u002Fmcp) with Actions & questions access, on an engine whose engine-operator has `proxy`. CLI route: icp-cli >= 0.3.0 (verified against 0.3.0, 1.0.2, 1.3.0; delegation handoff needs 1.0.x; the `proxy` template needs `icp new --subfolder`) and a browser for the Internet Identity sign-in. Both: a cloud engine console account; for a console proxy, the engine owner to deploy it\"\nmetadata:\n  title: Deploy to Cloud Engine\n  category: CloudEngine\n---\n\n# Deploy to Cloud Engine\n\n## What This Is\n\nA **cloud engine** is a user-owned slice of Internet Computer capacity, administered from a web console (by default `https:\u002F\u002Fopencloud.org`). Each engine runs on a single **subnet**. This skill takes a project that already builds and gets it deployed onto that engine, from a coding agent or an AI chat app.\n\nThis skill only covers the cloud-engine-specific steps: choosing how to deploy, signing in as an identity the engine accepts, and putting the canisters on the engine. For everything else about the CLI (`icp.yaml`, recipes, environments, bindings, identities), load the **`icp-cli`** skill.\n\n## Choose the Deploy Route First\n\nThere are two ways onto an engine. Pick one **before you write any code**: the route decides how the deploy signs in and what you can ship.\n\n- **ICP MCP connector.** DFINITY's official MCP server for the Internet Computer. It is available natively in ChatGPT (the plugin **Internet Computer (ICP)** by DFINITY Stiftung) and in Claude, and any MCP-capable app can add it as a custom connector at `https:\u002F\u002Fmcp.internetcomputer.org\u002Fmcp` (setup steps: https:\u002F\u002Fmcp.internetcomputer.org\u002F). It signs as the user with the Internet Identity session they authorized in the chat app, so nothing needs a local CLI, a browser on your machine, or a loopback. You deploy a wasm through the engine's engine-operator canister. See **\"ICP MCP Route\"**.\n- **icp CLI.** `icp identity link web`, then `icp deploy --subnet`. The full toolchain (recipes, asset canisters, `icp.yaml`), but the sign-in completes only when `icp` runs on the same machine as the user's browser. See **Steps 1 to 4**.\n\nDecide like this:\n\n1. **You run in a sandbox and the ICP MCP tools are available to you** (`open_app`, `get_app_principal`, `canister_update_call`, …) → **use the ICP MCP route.** A sandbox is a web or mobile chat (ChatGPT, Claude on the web or a phone) or a cloud coding session. Do not ask the user to move to a local session, and do not attempt `icp identity link web` or the delegation handoff there.\n2. **Otherwise → the CLI route: link the CLI** (Step 1).\n   - **Local session** (a coding agent on the user's own computer: Claude Code, Codex CLI, Cursor, or the local environment of the Claude desktop app): link the CLI and deploy with it, even when the ICP MCP tools are also available. The normal link flow works there, and the CLI can ship what the connector cannot, such as the files of an asset canister (see \"What the connector cannot do\").\n   - **Sandbox without the ICP MCP tools**: the CLI sign-in cannot finish there (see Step 1.0). Tell the user before you build anything, and ask them to start a local session instead. If their chat app can add MCP connectors, adding the ICP MCP connector is the other way. A cloud session cannot move to their computer later, so ask before you build, not after, and wait for the answer. Continue in the sandbox with the CLI only when the user has neither option. Then do the delegation handoff in Step 1.0 **first**, and build only after the sandbox CLI signs as the user: do not leave the sign-in for deploy time.\n\nBefore running any `icp` command you are unsure of, run `icp \u003Csubcommand> --help` (e.g. `icp identity link --help`, `icp deploy --help`) to confirm the command and flags exist. Do not infer flags. Authoritative reference: https:\u002F\u002Fcli.internetcomputer.org\u002Fllms.txt\n\n## What You Need\n\nTwo values, and a third for some apps. Look for them first in `icp.yaml` or earlier in the conversation. One has a default; the others you must ask for:\n\n1. **Console origin** — the URL the user signs in to their cloud engine console with. **Defaults to `https:\u002F\u002Fopencloud.org`** (the main OpenCloud console). Its host is the `--app` value in Step 1, so the linked CLI identity derives the **same principal that administers the engine**. Use the default, but say so and give the user a chance to override before linking:\n   - Say: \"I'll link the CLI against `https:\u002F\u002Fopencloud.org`, the default console. If you sign in to your engine console at a different URL, tell me now.\"\n   - Only use a different origin when the user names one — never substitute another URL on your own; the `--app` domain determines the derived principal (see Pitfall 2).\n   - On the ICP MCP route there is no `--app`; the console origin is where you look the engine up and where the owner confirms access (M1, M3).\n2. **Subnet id** (CLI route): the subnet the engine deploys to, required by `icp deploy --subnet`. There is **no default**; never guess it. The user finds it on the engine's **Settings** page in the console (under the engine's identifiers), or copies it from the console's command palette. If absent, **ask and do not proceed without it**:\n   - Ask: \"What is your engine's subnet id? It is on your engine's Settings page in the console.\"\n   - On the ICP MCP route you need the **engine id** instead (M1). The operator creates canisters on its own engine, so there is no subnet to pass.\n3. **Proxy canister id**: only if the app calls threshold ECDSA\u002FSchnorr, vetKD, the exchange-rate canister, the Bitcoin API, or the EVM\u002FSOL RPC canisters (look for a `PROXY_CANISTER_ID` variable, or a call to a `proxy` method). There is **no default**, you cannot create the console proxy yourself, and a self-deployed one is no substitute (Step 5). If absent, **ask before deploying**:\n   - Ask: \"This app needs your engine's proxy canister. If the engine has one, send me its id (engine → Integrations → Proxy canisters). If not, the engine owner can deploy one there with Deploy a proxy: it is paid from the engine's balance, no card. Then send me its id.\"\n\nRecord them so you do not re-ask within the session.\n\n## Prerequisites\n\n- CLI route: `icp` on `$PATH` — see the **`icp-cli`** skill to install. Verify with `icp --version` (this skill's commands are verified against 0.3.0 and 1.0.2). If the installed version differs, confirm the flag set with `icp \u003Ccmd> --help` before running — flags have changed across major versions.\n- A project that already builds. If it does not build or package yet, set that up first (see the `icp-cli` skill), then return here.\n- **No project yet, and no description of one?** Ask the user what to build before you write any code or create any files: \"What app do you want to build?\" Do **not** pick a sample app (a guestbook, a counter, a to-do list) on your own. A prompt that only says \"build and deploy an app\" is a request to ask, not a request to choose.\n- CLI route, macOS: `icp` stores its data under `~\u002FLibrary\u002FApplication Support\u002Forg.dfinity.icp-cli\u002F`. If the shell cannot write there (`Operation not permitted` from macOS TCC, e.g. when commands run through a bridge), redirect the data to an unprotected path for the session — `HOME=\u002Ftmp\u002Ficp-home icp …` — and keep that same `HOME` on **every** subsequent `icp` command, or the later commands will not see the linked identity.\n- ICP MCP route: the connector's tools in your tool list, connected with **Actions & questions** access (M0).\n\n## ICP MCP Route\n\nFor a sandboxed agent that has the ICP MCP tools (see \"Choose the Deploy Route First\"). No `icp` CLI, no loopback, no delegation handoff. Steps 1 and 3 are the CLI route and do not apply. Step 2 (naming the app) and Step 5 (the proxy canister for chain-key services) still do: M6 shows how to set their variables without `icp.yaml`.\n\nThe engine-operator canister's own API guide is the authoritative, current walkthrough of the calls below: read it in M2 before you call anything.\n\n### M0. The connector and its access level\n\nIf the tools are missing and the user's app can add MCP connectors, ask them to add it: in ChatGPT the plugin **Internet Computer (ICP)** by DFINITY Stiftung, in Claude its connector, and in any other MCP-capable app a custom connector at `https:\u002F\u002Fmcp.internetcomputer.org\u002Fmcp`. The steps are at https:\u002F\u002Fmcp.internetcomputer.org\u002F.\n\nOn connecting, the user signs in with Internet Identity, picks how long the session lasts, and picks an access level. A deploy needs **Actions & questions**. With **Questions only**, reads work, but the network rejects every state-changing call, so `create_canister` and `install_code` fail. If an update call is rejected that way, ask the user to reconnect with Actions & questions. Sessions expire by design: reconnecting later is normal.\n\n### M1. Find the engine\n\nYou need three values: the engine's **id**, its **engine-operator canister id** (`engine_operator_id`), and its **platform domain** (`platform_domain`, like `\u003Cengine-name>--\u003Cslug>.opencloud.org`).\n\n- Ask the user for the engine id. It is in the console URL (`\u002Fconsole\u002Fengines\u002F\u003Cengine-id>\u002F…`) and on the engine's **Settings** page.\n- `open_app` on the console origin (`https:\u002F\u002Fopencloud.org`) lists the engine registry canister. Read its public `getEngineViews` query with `(vec { \"\u003Cengine-id>\" })`; the record carries `engine_operator_id` and `platform_domain`. Signed as the user, its `listVisibleEngines` query returns the engines they own, with the same fields.\n\nA new or renamed engine's platform domain takes about ten minutes to register. Do not open or fetch it before then: a lookup that finds no record can be cached as missing for up to 30 minutes, and the domain keeps failing for you after it is live. Reach the engine through its operator's `https:\u002F\u002F\u003Cengine_operator_id>.icp.net` host instead, which works as soon as `engine_operator_id` is set. `GET https:\u002F\u002Ficp.net\u002Fcustom-domains\u002Fv1\u002F\u003Cplatform_domain>` answers `registration_status` `registered` once the platform domain serves: give the user that address from then on.\n\n### M2. Open the engine and read the operator's guide\n\n1. `open_app` on `https:\u002F\u002F\u003Cengine_operator_id>.icp.net`. The engine-operator publishes the engine's `\u002F.well-known\u002Fic-architecture` manifest, so it comes back as `[declared]`; that is what lets the connector call it. The connector refuses calls to any canister an app does not declare. Check that the `derivation_origin` it returns is `https:\u002F\u002F\u003Cplatform_domain>`. If it is the icp.net origin, the operator does not hold the engine's domain yet: wait a minute and `open_app` again before M3, or the principal you get there changes later.\n2. `get_canister_api_doc` on the operator, and read its section **\"Creating a canister and installing a wasm\"**.\n3. `get_canister_candid` on the operator, and check that its service has a `proxy` method. If it has none, the engine still runs an engine-operator from before the ICP MCP route existed: tell the user, and use the CLI route.\n\n### M3. Get the connector's principal added to the engine\n\nInternet Identity derives a principal per origin. At the engine, the connector signs as the principal derived for the engine's **platform domain** (the origin in its `\u002F.well-known\u002Fii-derivation-origin`). That is **not** the principal the user signs in to the console with, nor a CLI identity linked against the console origin. So the engine does not know it yet, even when the user owns the engine.\n\n1. `get_app_principal` with the `derivation_origin` that `open_app` returned in M2 gives you that principal, `P`. Its reply also says whether the session is query-only: if it is, stop and have the user reconnect with Actions & questions (M0). If the user has several accounts at the engine, pick one with `account` and keep passing the same one, since each account is a different principal.\n2. Check whether it is a member: the operator's public `getRole` query with `(record { principal_id = opt principal \"\u003CP>\" })`. Any role is enough, because every role has the `CreateCanister` permission that `proxy` needs.\n3. If the role is null, you cannot add `P` yourself. Give the user (the engine owner, or an engine admin) this link. It opens the engine's invite dialog with the grant filled in, so they only sign in and confirm:\n\n   ```\n   https:\u002F\u002Fopencloud.org\u002Fconsole\u002Fengines\u002F\u003Cengine-id>\u002Fteam?modal=invite-member&principal=\u003CP>&role=developer\n   ```\n\n   Use their console origin instead of `https:\u002F\u002Fopencloud.org` if it differs. `developer` is enough to deploy: do not ask for `admin`. Wait for them to confirm, then repeat step 2.\n\nRenaming the engine changes its platform domain, and with it `P`: the new principal needs the invite again.\n\n### M4. Build a wasm the connector can ship\n\nThe connector installs a **wasm module** and nothing else.\n\n- **Build it where you can.** In a code sandbox with the toolchain, `icp build` needs no sign-in (see `icp-cli`), and its recipe bakes in Step 2's version metadata as usual. Or use a prebuilt `.wasm` \u002F `.wasm.gz` the user attaches. Canister code follows the engine's call rules: load `cloud-engine-canisters`.\n- **Keep it small.** The module travels inside the tool call as a Candid blob, so build for release and gzip it: the operator accepts a gzipped module. Above about 2 MB, it has to go up in chunks (M5, step 4).\n\n#### What the connector cannot do\n\nThe connector calls only canisters that an app declares, and the engine's manifest declares only its operator. So your new canister is reachable only through the operator's relayed management calls (M5): the connector **cannot call the new canister's own methods**. That rules out anything that needs calls after the install, such as uploading the files of an asset canister (`@dfinity\u002Fstatic-site`, `@dfinity\u002Fasset-canister`). Pass configuration in the init argument (`arg`), or as environment variables (M6). If the app needs an asset upload, it needs the CLI route: tell the user.\n\n### M5. Create the canister and install the wasm through the operator's `proxy`\n\nThe connector may not call the IC management canister (`aaaaa-aa`) directly. The engine-operator's **`proxy` method** calls it for you, as the caller you sign as. Do not confuse it with the **proxy canister** of Step 5: that one relays chain-key calls for your app's code, and has nothing to do with deploying.\n\nEach step is one `canister_update_call` with `canister_id` the engine-operator, `method = \"proxy\"`, `app_url` the app URL and `derivation_origin` the derivation origin that `open_app` returned in M2 (plus the same `account` as in M3, if you chose one). **Always pass `derivation_origin`**: without it the call runs anonymously, and `proxy` answers `UnauthorizedUser`. The `args` are:\n\n```candid\n(record {\n  canister_id = principal \"aaaaa-aa\";\n  method = \"\u003Cmanagement method name>\";\n  args = blob \"\u003Cthe method's argument, Candid-encoded>\";\n  cycles = 0;\n})\n```\n\n`args` holds the **binary** Candid encoding (bytes starting with `DIDL`) of the management method's argument, written as a `blob \"…\"` literal with `\\xx` escapes. It is not textual Candid: encode it with a Candid library in your code sandbox. A success is `variant { Ok = record { result = blob \"…\" } }`, where `result` is the method's reply in binary Candid. The argument and reply types are in https:\u002F\u002Fdocs.internetcomputer.org\u002Freferences\u002Fic.did.\n\n1. **Create.** `method = \"create_canister\"`, and `args` encodes:\n\n   ```candid\n   (record { settings = opt record { controllers = opt vec { principal \"\u003Cengine-operator-id>\" } } })\n   ```\n\n   List the engine-operator as a controller. You (`P`) are added as a controller automatically, but without the operator `proxy` can never reach the canister again: no install, no settings, and no `deleteCanisters` for the owner either. If the code reads an environment variable when it initializes (such as `PROXY_CANISTER_ID`), add `environment_variables` to these `settings` now: it must be in place before step 2 (see M6). `result` decodes to `(record { canister_id : principal })`.\n2. **Install.** `method = \"install_code\"`, and `args` encodes:\n\n   ```candid\n   (record {\n     mode = variant { install };\n     canister_id = principal \"\u003Ccanister id from step 1>\";\n     wasm_module = blob \"\u003Cwasm bytes, gzipped or not>\";\n     arg = blob \"DIDL\\00\\00\";\n     sender_canister_version = null;\n   })\n   ```\n\n   `arg` is the binary-Candid init argument; `DIDL\\00\\00` passes none. `result` decodes to `()`.\n3. **Replace the code later.** The same call with `mode = variant { upgrade = null }` keeps the stable memory; `variant { reinstall }` wipes all state. A canister built with enhanced orthogonal persistence (current Motoko) rejects `upgrade = null`: upgrade it with `variant { upgrade = opt record { wasm_memory_persistence = opt variant { keep } } }`. Use `keep` **only** for such a build: the IC rejects it when the new module lacks the `enhanced-orthogonal-persistence` custom section (a Rust canister, or classical-persistence Motoko), so those keep `upgrade = null`. (The operator's guide says other canisters ignore `keep`; the IC interface spec says otherwise, and the spec wins.)\n4. **A module over about 2 MB** (the single-message limit) goes up with `upload_chunk`, then installs with `install_chunked_code`.\n\nThe operator relays only `create_canister`, `canister_status`, `update_settings`, `install_code`, `upload_chunk`, `clear_chunk_store`, `install_chunked_code`, `start_canister` and `stop_canister`, and only on canisters that list you as a controller. Any other method, the operator itself, and protected canisters come back as `Err` `CallFailed` with a `reason`, as does any `proxy` call on a canister while an `update_settings` on it is in flight: retry once it completes. `cycles` is ignored, since the engine charges nothing. `Err` `UnauthorizedUser` means the call was not signed by a member: check that you passed `derivation_origin`, then go back to M3.\n\n### M6. Name the app, and pass configuration\n\nStep 2's `__META_*` variables, and `PROXY_CANISTER_ID` from Step 5, are canister environment variables. With no `icp.yaml` to carry them, you set them yourself, and **when** matters:\n\n- **A variable the code reads at init or upgrade** (`PROXY_CANISTER_ID`, a key name, any configuration) must be set **before** the `install_code` that reads it. Otherwise init sees it missing, and traps or bakes in a fallback that a later upgrade does not undo. Put it in `create_canister`'s `settings` (M5, step 1), or set it with `update_settings` between steps 1 and 2. Before an upgrade or reinstall that should see a new value, set that value first.\n- **Display-only metadata** (`__META_*`) is read by the console, not by your code, so it can follow at any time. `__META_BASE_URL` needs the canister id, so it comes after step 1 anyway.\n\nEither way it is `update_settings` through `proxy`, with `args` encoding:\n\n```candid\n(record {\n  canister_id = principal \"\u003Ccanister id>\";\n  settings = record {\n    environment_variables = opt vec {\n      record { name = \"__META_PROJECT\"; value = \"My App\" };\n      record { name = \"__META_NAME\"; value = \"App\" };\n      record { name = \"__META_MAIN_CANISTER\"; value = \"true\" };\n    };\n  };\n})\n```\n\nThe list **replaces** the canister's whole set of variables, so send all of them every time. The rules for their values are in Step 2.\n\n### M7. Verify\n\n- `canister_status` through `proxy`, with `args` encoding `(record { canister_id = principal \"\u003Ccanister id>\" })`, shows `status = variant { running }` and the installed `module_hash`.\n- A canister that serves HTTP is reachable at `https:\u002F\u002F\u003Ccanister-id>.icp.net`. On an all-in-one engine it is under the engine's own domain instead: the operator's guide names which.\n- The canister appears on the engine's **Your apps and services** page in the console.\n\nReport the canister id(s), and the URL if any, back to the user.\n\n## Step 1 — Link the CLI to your engine identity (CLI route, once per machine)\n\nThe CLI must sign as the **same identity that administers the engine** — that is the principal you log in to the console with.\n\n### Step 1.0 — First determine WHERE the CLI runs relative to the browser\n\n`icp identity link web` completes the sign-in via a redirect to `http:\u002F\u002F127.0.0.1:\u003Cport>` **on the machine where the CLI runs**. The browser in which the user completes the Internet Identity sign-in must be able to reach that loopback address. Determine the environment before linking:\n\n- **CLI and browser on the same machine, with network** (local development, or a terminal-integrated agent on the user's own machine) → use the normal link flow below.\n- **CLI in a remote or isolated sandbox, with network** (the agent runs in the cloud; the user's browser is on a different machine) → the normal flow **cannot** work: the sign-in URL carries a `callback=http:\u002F\u002F127.0.0.1:\u003Cport>` bound to the sandbox, the user's browser resolves `127.0.0.1` to its *own* machine, and the delegation never reaches the CLI. Later commands then fail with authorization errors. If you have the ICP MCP tools, stop here and use the ICP MCP route. Otherwise ask the user to switch to a local session, or to add the ICP MCP connector (see \"Choose the Deploy Route First\"). Use the **delegation handoff** below only if they can do neither.\n- **CLI shell with no network at all** (e.g. a sandboxed device bridge on the user's own machine: DNS blocked, HTTPS requests fail, writes limited to `\u002Ftmp`) → **no** `icp` network command can run there — not the link, and not `icp deploy` either. The delegation handoff does not help: it only moves signing authority, not network access. Do not retry, tunnel, or proxy. Prepare the project and hand the user **one script** to run in their real terminal — see \"No-network CLI host\" below.\n\nWhen in doubt, probe before linking: `curl -sI https:\u002F\u002F\u003Cconsole-origin>` failing (or DNS not resolving) from the CLI shell means the no-network case.\n\n**Never** present a sandbox `127.0.0.1` URL to the user as something to open in their browser — it is unreachable from their machine. And do not invent a headless flag on `icp identity link web`: as of icp-cli 1.0.2 it has none (confirm with `icp identity link web --help`).\n\n### Delegation handoff (CLI host and browser on different machines)\n\n`icp identity delegation` transfers signing authority from an identity linked on the **user's** machine to a session key on the **CLI host**, without the browser ever reaching the CLI host. It requires `icp` installed on the user's machine too, and exists in icp-cli 1.0.x — verify with `icp identity delegation --help` on both machines. If the user's machine has `icp` and you can run commands there, the simplest path is to run this whole skill there instead; otherwise:\n\n1. **On the CLI host (sandbox)** — create a pending identity with a fresh session key:\n\n   ```bash\n   icp identity delegation request \u003Cyour-identity-name>\n   ```\n\n   It prints the session **public** key as a PEM to stdout. Give that PEM block to the user. (If the default `--storage keyring` fails in a headless sandbox, retry with `--storage plaintext`.)\n\n2. **On the user's machine** — the user links there if not already linked (`icp identity link web \u003Clocal-name> --app \u003Cconsole-domain>` — the normal flow works locally), saves the PEM to a file, and signs a delegation to it:\n\n   ```bash\n   icp identity delegation sign --identity \u003Clocal-name> --key-pem session-key.pem --duration 8h > delegation.json\n   ```\n\n   `--duration` takes e.g. `30m`, `8h`, `1d`; optionally `--canisters \u003Cids>` restricts the delegation. The user sends `delegation.json` back. If `sign` fails with `delegation for identity \u003Cname> has expired or will expire within 5 minutes`, the local web session has lapsed — run `icp identity reauth \u003Clocal-name>` (the normal browser flow, which works locally) and retry.\n\n3. **On the CLI host** — complete the pending identity and activate it:\n\n   ```bash\n   icp identity delegation use \u003Cyour-identity-name> --from-json delegation.json\n   icp identity default \u003Cyour-identity-name>\n   icp identity principal   # must print the user's console principal\n   ```\n\nTreat `delegation.json` as a **time-limited credential**: whoever holds the sandbox's session key can sign as the user's console principal until it expires. Keep the duration short and re-run the handoff when it lapses.\n\n### No-network CLI host — hand the user one script\n\nIf the CLI shell cannot reach the network, the user's real terminal is the deploy machine — and because their terminal and browser share a loopback there, the **normal** link flow works; no delegation handoff is needed. Your job shifts to preparation:\n\n1. Get the built project into a directory the user's terminal can reach.\n2. Write one script at the project root that does the whole network-bound sequence — adapt this template (fill in the real identity name, console domain, and subnet id; do not leave placeholders):\n\n   ```bash\n   #!\u002Fusr\u002Fbin\u002Fenv bash\n   set -euo pipefail\n   icp --version\n   icp identity list | grep -E '^\\*? *\u003Cyour-identity-name> ' >\u002Fdev\u002Fnull || icp identity link web \u003Cyour-identity-name> --app \u003Cconsole-domain>\n   icp identity default \u003Cyour-identity-name>\n   icp deploy -e ic --subnet \u003Csubnet-id>\n   ```\n\n3. Ask the user to run it in their terminal, complete the Internet Identity sign-in when the browser opens, and paste the output back.\n4. Verify from the pasted output (canister ids, frontend URL) and continue — a re-deploy for the Step 2 `__META_*` variables goes through the same script.\n\n### Normal link flow (CLI and browser share a loopback)\n\nFirst check what already exists:\n\n```bash\nicp identity list      # names + principals; * marks the active identity\n```\n\nThe list does **not** show which console (if any) an identity was linked against — that cannot be determined from the CLI. Decide like this:\n\n- **Only `anonymous` (or plain local identities) listed** — no web-linked identity exists; run the link command below.\n- **An identity the user recognizes as their engine identity** (by name or principal) — set it active (below) and skip to Step 2.\n- **Unsure** — ask the user, or simply relink under a new name; linking again is cheap and safe.\n\nTo link, run this, substituting a name the user picks — `\u003Cyour-identity-name>` is any local label, not a fixed value (do not hardcode something like `my-engine-admin`); reuse the **same** name in every command below:\n\n```bash\nicp identity link web \u003Cyour-identity-name> --app \u003Cconsole-domain>\n```\n\n- `\u003Cconsole-domain>` is the host of the console origin, with no scheme, port or path: `opencloud.org` for the default console, `beta.opencloud.org` for `https:\u002F\u002Fbeta.opencloud.org`. The console derives its principal from the host it serves, so this is the domain to name. Do not take it from `\u002F.well-known\u002Fii-derivation-origin`: that file serves the ICP MCP route and can name another host.\n- Never omit `--app`. Without it, Internet Identity derives the principal for `cli.id.ai`, which is **not** your console, and the engine rejects every deploy as unauthorized.\n- Do not use `--auth \u003Cconsole-origin>` for the OpenCloud console. It reaches the same principal, but through the console's own `\u002Fcli` page, so the user signs in on two screens instead of one. Use `--auth \u003Cconsole-origin>` (no `--app`) only when the user says their console signs in with its own Internet Identity rather than `id.ai`, as a test deployment does.\n- The command first waits at a **\"Press Enter to log in\"** prompt before anything happens. Run it interactively when you can; in a non-interactive shell (e.g. a background process) pipe a real newline:\n\n  ```bash\n  printf '\\n' | icp identity link web \u003Cyour-identity-name> --app \u003Cconsole-domain>\n  ```\n\n  Do **not** redirect stdin from `\u002Fdev\u002Fnull` — the bare EOF does not satisfy the prompt, and the command sits on \"Press Enter to log in\" indefinitely with no browser ever opening.\n- After Enter it opens a **browser tab**. The **user** completes the Internet Identity sign-in there. Wait for them to confirm before continuing — you cannot complete the sign-in for them.\n- On first use, id.ai can stop at a **\"CLI access disabled\"** screen. That is the expected first run, not an error. Tell the user to turn on **CLI access** for their identity in the Internet Identity settings, then run the link command again from the start: the old sign-in URL is single-use. See the `agent-web-identity` skill, Pitfall 1.\n- `--app` must be the **exact** console host, e.g. `opencloud.org`. Another domain derives a *different* principal, and the engine will reject the deploy as unauthorized.\n- This is a **one-time, per-machine** step.\n\nThen make it the active identity and verify:\n\n```bash\nicp identity default \u003Cyour-identity-name>\nicp identity default     # prints the active identity name\nicp identity principal   # prints the principal the deploy will sign as\n```\n\n## Step 2 — Name the app (and give it an icon) in the console (recommended)\n\nBy default, CLI-deployed canisters appear on the engine console's **Your apps and services** page as bare rows labelled only by their principal id. A set of **canister environment variables** makes the console group them into a single named application with readable per-canister labels, an \"Open\" button, and an icon. Set them once in your project config:\n\n- `__META_PROJECT` — the application name. Canisters that share the **same** value are grouped into one named app, so set an identical value on every canister of the app.\n- `__META_NAME` — the per-canister display label (e.g. `Backend`, `Frontend`).\n- `__META_MAIN_CANISTER` — the literal string `\"true\"` on exactly one canister (the entry point, usually the frontend\u002Fasset canister). This marks the app's **main canister**: the console reads `__META_BASE_URL` and `__META_ICON_PATH` only from it, and the \"Open\" button targets it.\n- `__META_BASE_URL` — an **absolute `https:\u002F\u002F` URL**, set on the main canister (e.g. the frontend canister's URL `https:\u002F\u002F\u003Cfrontend-canister-id>.icp.net`, or a custom domain). When present and valid, it is the URL the \"Open\" button opens; when absent or not `https`, the \"Open\" button falls back to the main canister's gateway URL. It is also the base that `__META_ICON_PATH` resolves against.\n- `__META_ICON_PATH` — the path to the app icon, resolved against `__META_BASE_URL` to form the icon the console renders (e.g. `\u002Ffavicon.svg` → `https:\u002F\u002F\u003Cbase>\u002Ffavicon.svg`). Set it on the **main** canister, alongside `__META_BASE_URL`.\n\nThe icon and \"Open\" link are read **only from the main canister** (the one marked `__META_MAIN_CANISTER: \"true\"`) — `__META_BASE_URL` \u002F `__META_ICON_PATH` on any other canister are ignored.\n\nOn the ICP MCP route there is no project config: set the same variables with `update_settings` (M6). On the CLI route, set them under each canister's `settings.environment_variables` — this is valid alongside a recipe. With per-canister `canister.yaml` files:\n\n```yaml\n# frontend\u002Fcanister.yaml\nname: frontend\nrecipe:\n  type: \"@dfinity\u002Fstatic-site@v0.4.3\"\n  configuration:\n    build:\n      - npm install\n      - npm run build\n    dir: dist\nsettings:\n  environment_variables:\n    __META_PROJECT: \"My App\"\n    __META_NAME: \"Frontend\"\n    __META_MAIN_CANISTER: \"true\"\n    __META_BASE_URL: \"https:\u002F\u002F\u003Cfrontend-canister-id>.icp.net\"\n    __META_ICON_PATH: \"\u002Ffavicon.svg\"\n```\n\n```yaml\n# backend\u002Fcanister.yaml\nname: backend\nrecipe:\n  type: \"@dfinity\u002Fmotoko@v5.0.0\"   # v5 reads main\u002Fcandid from mops.toml ([canisters.backend]) — see the icp-cli skill\nsettings:\n  environment_variables:\n    __META_PROJECT: \"My App\"\n    __META_NAME: \"Backend\"\n```\n\nFor a single inline `icp.yaml` (canisters defined there directly), put the same `settings.environment_variables` block under each canister entry. Note the inline form: `canisters` is an **array** of `{name, recipe, settings}` items, not a map keyed by canister name:\n\n```yaml\n# icp.yaml — canisters defined inline\ncanisters:\n  - name: frontend\n    recipe: # … as in the canister.yaml example above\n    settings:\n      environment_variables:\n        __META_PROJECT: \"My App\"\n        __META_NAME: \"Frontend\"\n        __META_MAIN_CANISTER: \"true\"\n        __META_BASE_URL: \"https:\u002F\u002F\u003Cfrontend-canister-id>.icp.net\"\n        __META_ICON_PATH: \"\u002Ffavicon.svg\"\n  - name: backend\n    recipe: # … as in the canister.yaml example above\n    settings:\n      environment_variables:\n        __META_PROJECT: \"My App\"\n        __META_NAME: \"Backend\"\n```\n\nNotes:\n- icp-cli **merges** these with the `PUBLIC_CANISTER_ID:\u003Cname>` variables it injects automatically at deploy time — the asset canister keeps serving and the app keeps working. (Verified against icp-cli 0.3.0.)\n- All values are strings; `__META_MAIN_CANISTER` must be the exact string `\"true\"`.\n- They are applied during `icp deploy` (the \"Setting environment variables\" step). After deploy, confirm with `icp canister settings show \u003Cname> -e ic`.\n\nIcon specifics (the console builds the icon as `__META_BASE_URL` + `__META_ICON_PATH`):\n- **Both** must be present and **on the main canister** for an icon to appear — there is no fallback. `__META_ICON_PATH` alone does nothing.\n- `__META_BASE_URL` must parse as an absolute **`https:\u002F\u002F`** URL. A bare host, an `http:\u002F\u002F` URL, or a `data:` \u002F `javascript:` value is rejected: the icon then does not render, and the \"Open\" button falls back to the main canister's gateway URL (it does not disappear). (The console validates the scheme before using it.)\n- `__META_ICON_PATH` is a **path to an asset your frontend actually serves** (e.g. `\u002Ffavicon.svg`), not an inline image. The resolved URL is rendered as an `\u003Cimg>` `src`, so it must return an image. Do **not** put a `data:` URI here: engine env values are length-capped (≤128 chars observed), so it would not fit, and the field is a path by design.\n- The frontend canister's id is only known **after** the first deploy. The usual flow is: deploy once, read the frontend canister id from the output, set `__META_BASE_URL` to `https:\u002F\u002F\u003Cthat-id>.icp.net` (and `__META_ICON_PATH`), then re-deploy to apply. If you control a custom domain for the app, you can set it up front instead.\n\n### Pin the Internet Identity derivation origin (if the app signs users in)\n\nInternet Identity derives a principal **per origin**. An app reached at both its canister address and a custom domain signs the same person in as two different users.\n\nThe engine console names the app's **canister address** as the origin to derive from. It cannot be renamed or removed, so it survives adding, changing, or dropping a custom domain. It is the address of the canister that **serves your frontend** (the asset\u002Fstatic-site canister the browser loads the app from), built from that canister's id:\n\n```\nhttps:\u002F\u002F\u003Cfrontend-canister-id>.icp.net\n```\n\nThat is usually the canister you marked `__META_MAIN_CANISTER: \"true\"`, but that flag is a console display setting, not a guarantee. If you marked a backend canister, still derive from the frontend canister. A backend canister cannot be a derivation origin: the browser is never on it, and it cannot serve the `.well-known\u002Fii-alternative-origins` file Internet Identity fetches from that origin to validate the claim.\n\nBuild the value from the canister id, **not** from `__META_BASE_URL`. The two match only while `__META_BASE_URL` still points at the canister address. Pointing it at a custom domain is normal and supported (see the icon notes above), and the two then differ. A custom domain is the one value that must never become the derivation origin.\n\n**Do this before the app has users, not after.** A domain that has already collected sign-ins cannot be repointed at a derivation origin without orphaning every account created under it. If the app might ever get a custom domain, set `derivationOrigin` on the first deploy even while the canister address is the only origin — it costs nothing then and cannot be applied retroactively without losing accounts.\n\nFor the mechanics — the `derivationOrigin` option and the `.well-known\u002Fii-alternative-origins` file, including the `_headers` entry the `@dfinity\u002Fstatic-site` recipe needs — load the **`user-sign-in`** skill.\n\nCaffeine apps are the exception: Caffeine owns their address, so it is not the app's to derive from. The console withholds the canister-address row for them.\n\n### Version metadata (recommended)\n\nEmbed build provenance into each canister's wasm so tooling and the console can show what version is running (console support is rolling out; the metadata is already readable — see the verify note below). Add this **by default on every deploy** — do not wait for the user to ask. All official recipes (motoko, rust, static-site, asset-canister, prebuilt) accept a `metadata` list under the recipe `configuration`; each entry is baked into the wasm as a custom section. Values are interpolated into a shell command at build time, so `$(…)` command substitution works. (Verified by live builds against `@dfinity\u002Fmotoko` v4.1.0 and v5.0.0 on icp-cli 1.0.2.)\n\n**Git project** (check first: `git rev-parse HEAD` succeeds):\n\n```yaml\nrecipe:\n  type: \"@dfinity\u002Fmotoko@v5.0.0\"\n  configuration:\n    metadata:\n      - name: service:git:sha\n        value: $(git rev-parse HEAD)$(git diff --quiet HEAD 2>\u002Fdev\u002Fnull || echo +dirty)\n      - name: service:git:origin\n        value: $(git remote get-url origin)\n      - name: service:git:updated_at\n        value: $(git log -1 --format=%cI)\n      - name: service:version\n        value: $(node -p \"require('.\u002Fpackage.json').version\" 2>\u002Fdev\u002Fnull || echo 1.0.0)\n```\n\n**Non-git project** — the git substitutions above do **not** fail the build; they silently bake garbage (`service:git:sha` becomes the literal `+dirty`, `service:git:origin` comes out empty). Use only an explicit version, and bump it on each subsequent deploy:\n\n```yaml\nmetadata:\n  - name: service:version\n    value: \"1.0.0\"\n```\n\nNotes:\n\n- Use these exact names (`service:git:sha`, `service:git:origin`, `service:git:updated_at`, `service:version`) — they are the agreed convention the console will read; differently named entries will not be picked up.\n- Prefer command results over literals where possible: `+dirty` on the sha flags uncommitted changes, so a deploy is traceable to its exact source state.\n- Values must be **deterministic** for a given source tree: sha, origin, the last commit's date, a version string. Never embed a build time (`$(date)`) or other build-time-varying values. The commit date (`%cI`) is allowed because it is a property of the commit, not of the build — but note it means \"code last changed\", not \"last deployed\": deploying an old commit shows the old date. The deploy time itself comes from the canister history the network records automatically.\n- If the project has no `package.json` (or no version field), the fallback `1.0.0` applies; replace it with the project's real versioning scheme when one exists.\n- The sections are injected as `icp:private`, readable by the canister's controllers (you, and the console acting as you) via read_state — no canister call needed. Verify after deploy, with the linked identity active: `icp canister metadata \u003Ccanister-name> service:git:sha -e ic`.\n\n## Step 3 — Deploy to the engine's subnet (CLI route)\n\nFrom the project root:\n\n```bash\nicp deploy -e ic --subnet \u003Csubnet-id>\n```\n\n- `-e ic` targets mainnet (the engine runs on an IC subnet); `--subnet \u003Csubnet-id>` pins the deploy to **your engine's** subnet. Confirm the exact flags with `icp deploy --help` before running if unsure.\n- Deploying consumes capacity on the engine; make sure the engine has room.\n\n**Alternative: packaged upload.** If the project is distributed as a built `.icp` package and a direct `icp deploy` is not available, the user can upload the bundle instead: engine → **Your apps and services** → **Upload a bundle**, which walks through uploading the `.icp` bundle. The page's other button, **Build with agents**, gathers the other ways in: a coding agent, Caffeine, the CLI instructions this skill automates, and OpenSaaS for ready-made apps.\n\n## Step 4 — Verify\n\n- The `icp deploy` output reports the deployed canister ids.\n- The canisters appear on the engine's **Your apps and services** page in the console; each canister's detail view offers an \"Open in browser\" link.\n- If you set the metadata in Step 2, the canisters are grouped under your `__META_PROJECT` name with their `__META_NAME` labels, and the main canister shows an \"Open\" button — instead of bare principal rows. With `__META_BASE_URL` + `__META_ICON_PATH` set, the app also shows its icon (allow for a short console cache delay).\n- A frontend (asset) canister is served at `https:\u002F\u002F\u003Cfrontend-canister-id>.icp.net`.\n\nReport the deployed canister ids (and the frontend URL, if any) back to the user.\n\n## Step 5: Deploy a proxy canister (only if the app needs chain-key services)\n\nSkip this step unless the app calls **threshold ECDSA or Schnorr** (Bitcoin, Ethereum), **vetKD \u002F VetKeys**, the **exchange-rate canister** (XRC), the **Bitcoin API**, or the **EVM \u002F SOL RPC** canisters: a console proxy relays nothing else for the engine's canisters (the exact list is in `cloud-engine-canisters`). Everything else (the app's own execution, storage, messaging, and HTTPS outcalls) is free on an engine and needs no proxy at all.\n\nThose services live on other subnets and charge cycles per call, and an engine canister can neither hold cycles nor send a cycle-bearing message across the engine boundary. A **proxy canister** on a normal Application subnet makes the call on its behalf and pays from its own balance.\n\nIf you are writing or deploying such an app and do not have the proxy id yet, ask for it now (What You Need, item 3) rather than deploying with a placeholder: the first proxied call would fail.\n\n### Which proxy\n\nTwo different canisters can play this role, and picking the wrong one produces a working-looking setup that fails at the first call:\n\n| | **Console proxy** | **Self-deployed proxy** |\n|---|---|---|\n| Deployed by | The engine's **owner**, from the console | You, with `icp` |\n| Authorized callers | Every canister on the engine's **subnet** (checked with the NNS registry), plus its controller | **Controllers only** |\n| Your engine canisters may call it | Yes, automatically | Only after you add each one as a controller |\n| What it relays for them | Only chain-key, Bitcoin and exchange-rate \u002F RPC services (list in `cloud-engine-canisters`) | Anything |\n| Your CLI identity may call it | **No** (rejected as `UnauthorizedUser`) | Yes: you are a controller |\n| Threshold-key derivation | Isolated per calling canister | No isolation |\n| Funded with | The engine's balance, automatically (no card) | Cycles you hold (`icp cycles mint`) |\n\n- **App code calling chain-key services → the console proxy.** It is the one that admits your engine's canisters without per-canister configuration.\n- **You calling something from the CLI** (`icp canister call --proxy`, `icp deploy --proxy`, canister-only management methods like `canister_info` or `raw_rand`) **→ your own proxy.** The console proxy cannot serve this: it rejects ingress from any non-controller principal, and your CLI identity is not one.\n\nThey are not substitutes, and they derive **different keys**: see the derivation warning in `cloud-engine-canisters` before switching an app from one to the other.\n\n### Console proxy: the engine owner deploys it (a console action, not a CLI one)\n\nOnly the engine's **owner** can deploy one, and only from the console. There is no `icp` command for it, and nothing an agent can call creates one: the console endpoints behind the button authenticate with a browser session cookie minted by the Internet Identity login. Hand the user these steps and wait for the proxy canister id:\n\n1. Open the engine in the console → **Integrations** in the engine's sidebar → the **Proxy canisters** card.\n2. **Deploy a proxy**, then **Deploy proxy** in the dialog. There is no amount to choose and no card is charged: the proxy is created on a normal Application subnet out of the engine's balance, starting with a few dollars' worth of cycles.\n3. Copy the **proxy canister id** from the card's table (it opens after a deploy; otherwise expand it with the **N deployed** toggle). That id is what the app calls.\n\nThe deploy is refused while the engine is not serving, or when the engine's balance has too little free to cover the proxy's first cycles: the owner adds funds to the engine, then retries.\n\n**How it stays funded.** Every few minutes while its balance is below a low threshold, the proxy asks for a top-up, which is sent out of the engine's balance: capped per hour, and never out of what the engine owes for its nodes. There is no **Top up** button and no automatic top-up setting for it; its row shows the balance and the cycles it has drawn so far. If it runs dry anyway, fund the engine (see \"Paying for an engine\" below).\n\nAlso on that table: **Refresh balance** (a live read, since the displayed figure is cached) and **Delete proxy** (stops and deletes the canister; its unspent cycles, less the small reserve a canister keeps to run, go back to the engine's balance).\n\nAn engine may have **more than one** proxy (\"Deploy another proxy\"). Each has its own balance (all refilled from the same engine balance) and, for a signing app, its own key namespace.\n\n### Wire the proxy id into the app\n\nThe app needs the id at runtime. Pass it as a canister environment variable rather than hardcoding it in source, so the same code can run against a local mock and against the engine:\n\n```yaml\nsettings:\n  environment_variables:\n    PROXY_CANISTER_ID: \"\u003Cproxy-canister-id>\"\n```\n\nOn the ICP MCP route, set it with `update_settings` (M6). Read it in the canister the same way as any other environment variable, and treat it as **permanent configuration**: for an app that derives threshold keys, changing this value changes every address the app owns.\n\n### Self-deployed proxy: the CLI path\n\nFor proxying *your own* calls. This one is the upstream `dfinity\u002Fproxy-canister`, deployed from a template:\n\n```bash\nicp new my-proxy --subfolder proxy       # pre-built wasm, no init args\ncd my-proxy\nicp deploy -e ic                         # NOT --subnet \u003Cengine-subnet>: see below\nexport PROXY_ID=$(icp canister status -e ic --id-only proxy)\n```\n\nFund it, and check on it, with the cycles ledger:\n\n```bash\nicp cycles balance -e ic                 # what you hold\nicp cycles mint --icp 1 -e ic            # ICP -> cycles, if you need more\nicp canister top-up \"$PROXY_ID\" --amount 5t -e ic\nicp canister status \"$PROXY_ID\" -e ic    # the proxy's own balance\n```\n\nThen pass it to any command that needs cycles or a canister-only management method:\n\n```bash\nicp canister call my-canister charge_me '()' -e ic --proxy \"$PROXY_ID\" --cycles 1_000_000_000_000\nicp deploy -e ic --proxy \"$PROXY_ID\"\n```\n\nThree things to get right:\n\n- **Do not deploy it onto the engine's subnet.** A proxy on a `CloudEngine` subnet is useless: it holds 0 cycles and cannot send a cycle-bearing message either. Omit `--subnet` (Step 3's engine-subnet flag is for your app, not for this) so it lands on a normal Application subnet.\n- **It authorizes controllers only.** For an *engine canister* to call it, that canister's principal must be added as a controller (`icp canister settings update \"$PROXY_ID\" --add-controller \u003Ccanister-id> -e ic`), one call per canister, repeated whenever a canister is added. This is the maintenance burden the console proxy exists to remove.\n- **It does not isolate key derivation.** Any authorized caller can request any derivation path, and the keys it produces differ from the console proxy's. Do not point a signing app at one casually.\n\n## Paying for an engine: which balance\n\nTwo balances exist, they are funded differently, and the app's own canisters have neither. Do not \"top up the canister\" on an engine without first establishing which of these is meant:\n\n| Balance | What it pays for | How it is funded | What happens when it empties |\n|---|---|---|---|\n| **Engine balance** | The engine itself (its nodes, day by day), and the top-ups of its console proxies | **Add funds** on the engine's **Billing** page (charged to the card on file), and automatic top-ups that charge the card about a week before the balance would run out | The engine is **deleted**, with everything on it: not reversible, so treat a low-runway warning as urgent. An engine whose automatic top-up keeps failing is **halted**, and runs again once a payment lands |\n| **Proxy cycle balance** | Only the calls the proxy relays (signing, vetKD, XRC, Bitcoin API, EVM\u002FSOL RPC) | A console proxy refills itself from the engine balance (Step 5); a self-deployed one takes `icp canister top-up` | Relayed calls fail with `InsufficientCycles`; the proxy freezes rather than being deleted, and recovers on a later top-up |\n\nA console proxy's top-ups come out of the engine balance, so a busy proxy shortens the engine's runway, though never by what the engine owes for its nodes.\n\nThe app's own canisters have **none** of these: they hold 0 cycles by design and cannot be topped up. A \"0 cycles\" reading on an engine canister is normal (see `cloud-engine-canisters`, pitfall 5).\n\n## Code that runs on the engine\n\nA cloud engine runs on a **`CloudEngine` subnet** with protocol-level call rules that differ from normal Application subnets: engine canisters hold 0 cycles and must **never attach cycles** to any call, **cross-subnet calls must be bounded-wait**, HTTPS outcalls are made directly and free, and cycle-bearing cross-subnet targets (exchange-rate XRC, threshold ECDSA\u002FSchnorr, vetKD) go through the engine's funded **console proxy canister**. Before writing or debugging any canister code that will run on the engine, load the **`cloud-engine-canisters`** skill — it has the rules, the proxy interface, and the pitfalls.\n\n## Common Pitfalls\n\n1. **Sign-in not completed.** Running `icp identity link web …` but not finishing the Internet Identity sign-in in the browser leaves the CLI unlinked; later commands fail with authorization errors. Re-run and wait for the user to confirm the browser flow finished. If no browser ever opened, the command is stalled at the \"Press Enter to log in\" prompt — relaunch with a piped newline, `printf '\\n' | icp identity link web …`, never `\u003C \u002Fdev\u002Fnull` (see Step 1). If the CLI runs in a remote sandbox, re-running can never complete — see Pitfall 10 and the delegation handoff in Step 1.0.\n2. **Wrong or missing `--app` domain.** Any domain other than the console host, or no `--app` at all, derives a different principal, and the engine rejects the deploy as not authorized. Relink with the exact domain (Step 1). If the deploy is rejected as unauthorized after linking with the default `--app opencloud.org`, ask the user for the exact URL they sign in with and relink with its host.\n3. **Guessing the subnet id.** Never invent it: the deploy fails or targets the wrong subnet. It is on the engine's **Settings** page in the console; ask the user.\n4. **Deploying with the anonymous identity.** The default local identity is anonymous and is not the engine admin. You must link and `icp identity default \u003Cyour-identity-name>` first.\n5. **Using `dfx`.** This ecosystem uses `icp`, never `dfx`. The correct sequence is `icp identity link web \u003Cname> --app \u003Cconsole-domain>` (Step 1), then `icp deploy -e ic --subnet \u003Csubnet-id>` (Step 3). See the `icp-cli` skill.\n6. **Skipping the app metadata.** Without `__META_PROJECT` (Step 2), the canisters still deploy and work but render as bare, unnamed principal rows in the console. Setting `__META_*` is what produces a named app with labelled canisters and an \"Open\" button.\n7. **Wrong `__META_MAIN_CANISTER` value.** It is matched as the exact string `\"true\"`. A boolean, `\"True\"`, or marking more than one canister means no (or the wrong) \"Open\" button. Mark exactly one entry-point canister.\n8. **Inventing an icon variable.** The icon variable is `__META_ICON_PATH` (a path resolved against `__META_BASE_URL`). Do not guess `__META_ICON`, `__META_LOGO`, or `__META_ICON_LINK` — they are ignored, so the icon silently never appears.\n9. **Icon set on the wrong canister, or without a base URL.** The icon is read only from the **main** canister and needs **both** `__META_BASE_URL` (a valid absolute `https:\u002F\u002F` URL) and `__META_ICON_PATH`. Setting the icon path on a side canister, omitting the base URL, or giving a non-https \u002F `data:` base means no icon renders. (The \"Open\" button still works — it falls back to the main canister's gateway URL — so a bad base URL costs the icon and the custom Open URL, not the button.)\n10. **Assuming the browser and CLI share localhost.** `icp identity link web` returns the delegation to `127.0.0.1:\u003Cport>` on the CLI host. If the CLI runs in a remote sandbox while the user's browser is on a different machine, the sign-in never completes and later commands fail with authorization errors — no amount of re-running the link fixes it. With the ICP MCP tools, use the ICP MCP route instead. Without them, see Step 1.0: prefer a local session, where the browser and CLI share a loopback, or the ICP MCP connector, and use the delegation handoff (`icp identity delegation request` \u002F `sign` \u002F `use`) only when the user has neither option. Ask before you build in the sandbox, not after.\n11. **Expecting the delegation handoff to fix a missing network.** The handoff moves signing authority, not connectivity — `icp deploy` still needs the network from the shell that runs it. If the CLI shell has no network at all (a sandboxed device bridge: DNS blocked, HTTPS fails), no `icp` network command can run there, link or deploy. Do not offer to \"do the rest\" from that shell and do not tunnel — hand the user one script for their real terminal (see \"No-network CLI host\" in Step 1.0), where the normal link flow works because terminal and browser share a loopback.\n12. **Build timestamps in wasm metadata.** Metadata is baked into the wasm and must be deterministic — a build time (`$(date)`) changes every build even with identical source, breaking reproducibility and changing the module hash on every deploy. The deterministic alternative is the last commit's date, `service:git:updated_at` = `$(git log -1 --format=%cI)` — a property of the source tree, not the build. The deploy time itself comes from the canister history recorded by the network, never from metadata.\n13. **Git metadata substitutions in a non-git project.** Outside a git repository, `$(git rev-parse HEAD)` does not fail the build — it silently bakes garbage: `service:git:sha` becomes the literal `+dirty` and `service:git:origin` comes out empty. Check for a git repo first (`git rev-parse HEAD` succeeds); if there is none, set only `service:version` with an explicit value (or `git init` and commit before deploying, if version control is wanted anyway).\n14. **Letting an engine app collect sign-ins before pinning a derivation origin.** Internet Identity principals are per-origin, so adding a custom domain later turns every existing user into a stranger at the new address — and the fix cannot be applied retroactively without orphaning the accounts already made under the old origin. On the first deploy of any app that uses II, set `derivationOrigin` to the address of the canister that serves the frontend, built from its canister id (`https:\u002F\u002F\u003Cfrontend-canister-id>.icp.net`), even when that is currently the app's only origin. Do **not** copy `__META_BASE_URL`: it is allowed to point at a custom domain, and a custom domain is exactly what must not become the derivation origin. See the `user-sign-in` skill for the `.well-known\u002Fii-alternative-origins` half.\n15. **Trying to deploy or fund the console proxy from the CLI or an API.** There is no `icp` command for it, and the console endpoints behind the buttons authenticate with a browser-session cookie from the Internet Identity login (no token auth), so an agent cannot drive them. Only the engine's owner can deploy one. Hand the user the console steps (engine → **Integrations** → **Proxy canisters** → **Deploy a proxy**) and wait for the proxy canister id. It needs no funding step: it is paid from the engine's balance. An agent *can* do the whole self-deployed-proxy path unattended, but that is a different canister with different authorization (Step 5).\n16. **Deploying a self-deployed proxy onto the engine's subnet.** Reusing Step 3's `--subnet \u003Cengine-subnet-id>` puts the proxy on the `CloudEngine` subnet, where it holds 0 cycles and may not send cycle-bearing messages either, so it cannot do the one thing a proxy is for. Omit `--subnet` so it lands on a normal Application subnet.\n17. **Expecting a self-deployed proxy to accept calls from engine canisters.** It authorizes **controllers only**. An engine canister calling it gets `UnauthorizedUser` until that canister's principal is added with `icp canister settings update \u003Cproxy> --add-controller \u003Ccanister-id> -e ic`, and again for every canister added later. The console proxy authorizes every canister on the engine's subnet instead, which is why it is the right one for app code.\n18. **Deleting a proxy that a signing app derives keys from.** The delete button sends the unspent cycles back to the engine's balance, which makes it look like a tidy-up. It is not: threshold keys are derived from the proxy's own principal, so deleting it (or repointing the app at another proxy) changes every Bitcoin\u002FEthereum address the app owns and strands any funds at the old ones. See `cloud-engine-canisters` for the derivation rules before touching a proxy that an app already signs with.\n19. **Confusing the balances.** \"The canister is out of cycles\" means something different for each: an engine canister holds 0 cycles by design and cannot be topped up, the *engine's* balance running low is fixed with **Add funds** on its Billing page, and `InsufficientCycles` from a relayed call is the *proxy's* balance (for a console proxy, which refills from the engine's balance, that again means funding the engine). Establish which one before topping anything up, and note that the engine's balance running out deletes the engine.\n20. **Deploying a proxy-dependent app without the proxy id.** An app that signs, derives vetKeys, reads exchange rates or calls the Bitcoin API or EVM\u002FSOL RPC deploys fine with a placeholder, a guessed id or an id from another engine, then fails on its first proxied call (`UnauthorizedUser`, or keys and addresses that belong to someone else's proxy). Ask for the id before deploying (What You Need, item 3). If the engine has none, the owner deploys one from the console (Step 5); never stand in a self-deployed proxy for it.\n21. **Picking the wrong route.** In a sandbox (a web or mobile chat, or a cloud coding session) that has the ICP MCP tools, asking the user to switch to a local session, or trying `icp identity link web` or the delegation handoff, wastes a round trip the connector makes unnecessary: use the ICP MCP route. The reverse holds too: in a local session, link the CLI and deploy with it even when the connector is also available, because the CLI can upload an asset canister's files and the connector cannot.\n22. **Assuming the connector signs as the console principal.** Internet Identity principals are per-origin, and at the engine the connector signs as the principal derived for the engine's platform domain. It is not the user's console principal and not a CLI identity, so a `proxy` call answers `UnauthorizedUser` until that principal is an engine member, even for the engine's owner. Read it with `get_app_principal` and hand the user the invite link (M3); you cannot add it yourself. A `canister_update_call` without `derivation_origin` gets the same answer for a different reason: it runs anonymously, so pass the derivation origin `open_app` returned on every call.\n23. **A Questions-only connector session.** With the **Questions only** access level, reads work and every state-changing call is rejected by the network, `create_canister` and `install_code` included. This is not an engine permission problem, so the invite link does not fix it. Ask the user to reconnect with **Actions & questions** (M0).\n24. **Calling the management canister directly, or confusing the two proxies.** The connector refuses calls to `aaaaa-aa`; deploys go through the engine-operator's `proxy` method (M5). That method is not the chain-key proxy canister of Step 5, and the console proxy canister cannot create or install canisters: it relays only chain-key, Bitcoin and exchange-rate \u002F RPC services.\n25. **Creating a canister without listing the engine-operator as a controller.** `create_canister` through `proxy` always adds you, but the operator only when you list it in `settings.controllers`. Without it, `proxy` can no longer reach the canister (no install, no settings), and the owner cannot remove it with `deleteCanisters` either. List the operator in the create call (M5, step 1).\n26. **Textual Candid in `proxy`'s `args`.** `args` is a `blob` holding the **binary** Candid encoding of the management method's argument (bytes starting with `DIDL`), and `result` comes back the same way. Passing the textual `(record { … })` as `args` makes the management canister fail to decode the argument. Encode it with a Candid library first (M5).\n27. **Expecting the connector to finish a deploy that needs calls into the new canister.** The connector calls only canisters an app declares, and the engine's manifest declares only its operator. It can install your wasm through `proxy`, but it cannot call the canister you installed, so it cannot upload an asset canister's files. Ship configuration in the init argument or environment variables, or use the CLI route for such an app.\n28. **Serving a custom domain from certified-assets older than 0.4.3.** For each app canister with a custom domain, the engine writes `\u002F.well-known\u002Fic-domains` (the HTTP gateway needs it to serve the domain) and `\u002F.well-known\u002Fii-alternative-origins` (Internet Identity needs it to accept the domain). It writes them with certified-assets' `upload_asset`, which arrived in 0.4.3. On an older canister the console warns that the engine can't write these files, and the domain does not work. Pin `@dfinity\u002Fstatic-site@v0.4.3` or newer. To fix an app that is already deployed, bump the recipe and run `icp deploy -e ic`. If the sync then stops with `assets canister version mismatch`, run `icp canister install \u003Cfrontend> --mode upgrade -e ic` (v0.4.x to v0.4.3 is a patch, so the state stays), then `icp deploy -e ic` again. From v0.3.x the bump is breaking: see the `static-site` skill, pitfall 11. The engine replaces whatever the app ships at these two paths, so do not build them into `dir`. List both in a `_preserve` file at the root of `dir`: without it, every deploy deletes them until the engine writes them again (within 10 minutes).\n\n## Additional References\n\n- Load `cloud-engine-canisters` for the call rules for code that runs on the engine: never attach cycles, bounded-wait cross-subnet calls, direct HTTPS outcalls, and the console proxy for cycle-bearing cross-subnet targets (XRC, threshold signing, vetKD). Load it before writing or debugging engine canister code.\n- Load `icp-cli` for general icp CLI usage (`icp.yaml`, recipes, environments, bindings, identities). Load it for anything beyond this cloud-engine deploy flow — in particular when the project does not build or package yet.\n- Load `user-sign-in` for details of the Internet Identity sign-in that Step 1 triggers in the browser.\n- Load `service-discoverability` for the `\u002F.well-known\u002Fic-architecture` manifest that decides which canisters the ICP MCP connector may call, including how an app you deployed can declare its own canisters, and the operator terms that publishing it accepts.\n- ICP MCP connector setup (ChatGPT plugin, Claude, custom connector URL): https:\u002F\u002Fmcp.internetcomputer.org\u002F.\n- Load `vetkeys` for what vetKD is and how VetKeys are used, once a proxy is in place: its call code targets a normal subnet, so see `cloud-engine-canisters` for the proxied form an engine needs.\n- Load `cycles-management` for cycles on ordinary (non-engine) canisters: balances, freezing thresholds, and ICP-to-cycles conversion via the CMC.\n","# Deploy to Cloud Engine\n\n## What This Is\n\nA **cloud engine** is a user-owned slice of Internet Computer capacity, administered from a web console (by default `https:\u002F\u002Fopencloud.org`). Each engine runs on a single **subnet**. This skill takes a project that already builds and gets it deployed onto that engine, from a coding agent or an AI chat app.\n\nThis skill only covers the cloud-engine-specific steps: choosing how to deploy, signing in as an identity the engine accepts, and putting the canisters on the engine. For everything else about the CLI (`icp.yaml`, recipes, environments, bindings, identities), load the **`icp-cli`** skill.\n\n## Choose the Deploy Route First\n\nThere are two ways onto an engine. Pick one **before you write any code**: the route decides how the deploy signs in and what you can ship.\n\n- **ICP MCP connector.** DFINITY's official MCP server for the Internet Computer. It is available natively in ChatGPT (the plugin **Internet Computer (ICP)** by DFINITY Stiftung) and in Claude, and any MCP-capable app can add it as a custom connector at `https:\u002F\u002Fmcp.internetcomputer.org\u002Fmcp` (setup steps: https:\u002F\u002Fmcp.internetcomputer.org\u002F). It signs as the user with the Internet Identity session they authorized in the chat app, so nothing needs a local CLI, a browser on your machine, or a loopback. You deploy a wasm through the engine's engine-operator canister. See **\"ICP MCP Route\"**.\n- **icp CLI.** `icp identity link web`, then `icp deploy --subnet`. The full toolchain (recipes, asset canisters, `icp.yaml`), but the sign-in completes only when `icp` runs on the same machine as the user's browser. See **Steps 1 to 4**.\n\nDecide like this:\n\n1. **You run in a sandbox and the ICP MCP tools are available to you** (`open_app`, `get_app_principal`, `canister_update_call`, …) → **use the ICP MCP route.** A sandbox is a web or mobile chat (ChatGPT, Claude on the web or a phone) or a cloud coding session. Do not ask the user to move to a local session, and do not attempt `icp identity link web` or the delegation handoff there.\n2. **Otherwise → the CLI route: link the CLI** (Step 1).\n   - **Local session** (a coding agent on the user's own computer: Claude Code, Codex CLI, Cursor, or the local environment of the Claude desktop app): link the CLI and deploy with it, even when the ICP MCP tools are also available. The normal link flow works there, and the CLI can ship what the connector cannot, such as the files of an asset canister (see \"What the connector cannot do\").\n   - **Sandbox without the ICP MCP tools**: the CLI sign-in cannot finish there (see Step 1.0). Tell the user before you build anything, and ask them to start a local session instead. If their chat app can add MCP connectors, adding the ICP MCP connector is the other way. A cloud session cannot move to their computer later, so ask before you build, not after, and wait for the answer. Continue in the sandbox with the CLI only when the user has neither option. Then do the delegation handoff in Step 1.0 **first**, and build only after the sandbox CLI signs as the user: do not leave the sign-in for deploy time.\n\nBefore running any `icp` command you are unsure of, run `icp \u003Csubcommand> --help` (e.g. `icp identity link --help`, `icp deploy --help`) to confirm the command and flags exist. Do not infer flags. Authoritative reference: https:\u002F\u002Fcli.internetcomputer.org\u002Fllms.txt\n\n## What You Need\n\nTwo values, and a third for some apps. Look for them first in `icp.yaml` or earlier in the conversation. One has a default; the others you must ask for:\n\n1. **Console origin** — the URL the user signs in to their cloud engine console with. **Defaults to `https:\u002F\u002Fopencloud.org`** (the main OpenCloud console). Its host is the `--app` value in Step 1, so the linked CLI identity derives the **same principal that administers the engine**. Use the default, but say so and give the user a chance to override before linking:\n   - Say: \"I'll link the CLI against `https:\u002F\u002Fopencloud.org`, the default console. If you sign in to your engine console at a different URL, tell me now.\"\n   - Only use a different origin when the user names one — never substitute another URL on your own; the `--app` domain determines the derived principal (see Pitfall 2).\n   - On the ICP MCP route there is no `--app`; the console origin is where you look the engine up and where the owner confirms access (M1, M3).\n2. **Subnet id** (CLI route): the subnet the engine deploys to, required by `icp deploy --subnet`. There is **no default**; never guess it. The user finds it on the engine's **Settings** page in the console (under the engine's identifiers), or copies it from the console's command palette. If absent, **ask and do not proceed without it**:\n   - Ask: \"What is your engine's subnet id? It is on your engine's Settings page in the console.\"\n   - On the ICP MCP route you need the **engine id** instead (M1). The operator creates canisters on its own engine, so there is no subnet to pass.\n3. **Proxy canister id**: only if the app calls threshold ECDSA\u002FSchnorr, vetKD, the exchange-rate canister, the Bitcoin API, or the EVM\u002FSOL RPC canisters (look for a `PROXY_CANISTER_ID` variable, or a call to a `proxy` method). There is **no default**, you cannot create the console proxy yourself, and a self-deployed one is no substitute (Step 5). If absent, **ask before deploying**:\n   - Ask: \"This app needs your engine's proxy canister. If the engine has one, send me its id (engine → Integrations → Proxy canisters). If not, the engine owner can deploy one there with Deploy a proxy: it is paid from the engine's balance, no card. Then send me its id.\"\n\nRecord them so you do not re-ask within the session.\n\n## Prerequisites\n\n- CLI route: `icp` on `$PATH` — see the **`icp-cli`** skill to install. Verify with `icp --version` (this skill's commands are verified against 0.3.0 and 1.0.2). If the installed version differs, confirm the flag set with `icp \u003Ccmd> --help` before running — flags have changed across major versions.\n- A project that already builds. If it does not build or package yet, set that up first (see the `icp-cli` skill), then return here.\n- **No project yet, and no description of one?** Ask the user what to build before you write any code or create any files: \"What app do you want to build?\" Do **not** pick a sample app (a guestbook, a counter, a to-do list) on your own. A prompt that only says \"build and deploy an app\" is a request to ask, not a request to choose.\n- CLI route, macOS: `icp` stores its data under `~\u002FLibrary\u002FApplication Support\u002Forg.dfinity.icp-cli\u002F`. If the shell cannot write there (`Operation not permitted` from macOS TCC, e.g. when commands run through a bridge), redirect the data to an unprotected path for the session — `HOME=\u002Ftmp\u002Ficp-home icp …` — and keep that same `HOME` on **every** subsequent `icp` command, or the later commands will not see the linked identity.\n- ICP MCP route: the connector's tools in your tool list, connected with **Actions & questions** access (M0).\n\n## ICP MCP Route\n\nFor a sandboxed agent that has the ICP MCP tools (see \"Choose the Deploy Route First\"). No `icp` CLI, no loopback, no delegation handoff. Steps 1 and 3 are the CLI route and do not apply. Step 2 (naming the app) and Step 5 (the proxy canister for chain-key services) still do: M6 shows how to set their variables without `icp.yaml`.\n\nThe engine-operator canister's own API guide is the authoritative, current walkthrough of the calls below: read it in M2 before you call anything.\n\n### M0. The connector and its access level\n\nIf the tools are missing and the user's app can add MCP connectors, ask them to add it: in ChatGPT the plugin **Internet Computer (ICP)** by DFINITY Stiftung, in Claude its connector, and in any other MCP-capable app a custom connector at `https:\u002F\u002Fmcp.internetcomputer.org\u002Fmcp`. The steps are at https:\u002F\u002Fmcp.internetcomputer.org\u002F.\n\nOn connecting, the user signs in with Internet Identity, picks how long the session lasts, and picks an access level. A deploy needs **Actions & questions**. With **Questions only**, reads work, but the network rejects every state-changing call, so `create_canister` and `install_code` fail. If an update call is rejected that way, ask the user to reconnect with Actions & questions. Sessions expire by design: reconnecting later is normal.\n\n### M1. Find the engine\n\nYou need three values: the engine's **id**, its **engine-operator canister id** (`engine_operator_id`), and its **platform domain** (`platform_domain`, like `\u003Cengine-name>--\u003Cslug>.opencloud.org`).\n\n- Ask the user for the engine id. It is in the console URL (`\u002Fconsole\u002Fengines\u002F\u003Cengine-id>\u002F…`) and on the engine's **Settings** page.\n- `open_app` on the console origin (`https:\u002F\u002Fopencloud.org`) lists the engine registry canister. Read its public `getEngineViews` query with `(vec { \"\u003Cengine-id>\" })`; the record carries `engine_operator_id` and `platform_domain`. Signed as the user, its `listVisibleEngines` query returns the engines they own, with the same fields.\n\nA new or renamed engine's platform domain takes about ten minutes to register. Do not open or fetch it before then: a lookup that finds no record can be cached as missing for up to 30 minutes, and the domain keeps failing for you after it is live. Reach the engine through its operator's `https:\u002F\u002F\u003Cengine_operator_id>.icp.net` host instead, which works as soon as `engine_operator_id` is set. `GET https:\u002F\u002Ficp.net\u002Fcustom-domains\u002Fv1\u002F\u003Cplatform_domain>` answers `registration_status` `registered` once the platform domain serves: give the user that address from then on.\n\n### M2. Open the engine and read the operator's guide\n\n1. `open_app` on `https:\u002F\u002F\u003Cengine_operator_id>.icp.net`. The engine-operator publishes the engine's `\u002F.well-known\u002Fic-architecture` manifest, so it comes back as `[declared]`; that is what lets the connector call it. The connector refuses calls to any canister an app does not declare. Check that the `derivation_origin` it returns is `https:\u002F\u002F\u003Cplatform_domain>`. If it is the icp.net origin, the operator does not hold the engine's domain yet: wait a minute and `open_app` again before M3, or the principal you get there changes later.\n2. `get_canister_api_doc` on the operator, and read its section **\"Creating a canister and installing a wasm\"**.\n3. `get_canister_candid` on the operator, and check that its service has a `proxy` method. If it has none, the engine still runs an engine-operator from before the ICP MCP route existed: tell the user, and use the CLI route.\n\n### M3. Get the connector's principal added to the engine\n\nInternet Identity derives a principal per origin. At the engine, the connector signs as the principal derived for the engine's **platform domain** (the origin in its `\u002F.well-known\u002Fii-derivation-origin`). That is **not** the principal the user signs in to the console with, nor a CLI identity linked against the console origin. So the engine does not know it yet, even when the user owns the engine.\n\n1. `get_app_principal` with the `derivation_origin` that `open_app` returned in M2 gives you that principal, `P`. Its reply also says whether the session is query-only: if it is, stop and have the user reconnect with Actions & questions (M0). If the user has several accounts at the engine, pick one with `account` and keep passing the same one, since each account is a different principal.\n2. Check whether it is a member: the operator's public `getRole` query with `(record { principal_id = opt principal \"\u003CP>\" })`. Any role is enough, because every role has the `CreateCanister` permission that `proxy` needs.\n3. If the role is null, you cannot add `P` yourself. Give the user (the engine owner, or an engine admin) this link. It opens the engine's invite dialog with the grant filled in, so they only sign in and confirm:\n\n   ```\n   https:\u002F\u002Fopencloud.org\u002Fconsole\u002Fengines\u002F\u003Cengine-id>\u002Fteam?modal=invite-member&principal=\u003CP>&role=developer\n   ```\n\n   Use their console origin instead of `https:\u002F\u002Fopencloud.org` if it differs. `developer` is enough to deploy: do not ask for `admin`. Wait for them to confirm, then repeat step 2.\n\nRenaming the engine changes its platform domain, and with it `P`: the new principal needs the invite again.\n\n### M4. Build a wasm the connector can ship\n\nThe connector installs a **wasm module** and nothing else.\n\n- **Build it where you can.** In a code sandbox with the toolchain, `icp build` needs no sign-in (see `icp-cli`), and its recipe bakes in Step 2's version metadata as usual. Or use a prebuilt `.wasm` \u002F `.wasm.gz` the user attaches. Canister code follows the engine's call rules: load `cloud-engine-canisters`.\n- **Keep it small.** The module travels inside the tool call as a Candid blob, so build for release and gzip it: the operator accepts a gzipped module. Above about 2 MB, it has to go up in chunks (M5, step 4).\n\n#### What the connector cannot do\n\nThe connector calls only canisters that an app declares, and the engine's manifest declares only its operator. So your new canister is reachable only through the operator's relayed management calls (M5): the connector **cannot call the new canister's own methods**. That rules out anything that needs calls after the install, such as uploading the files of an asset canister (`@dfinity\u002Fstatic-site`, `@dfinity\u002Fasset-canister`). Pass configuration in the init argument (`arg`), or as environment variables (M6). If the app needs an asset upload, it needs the CLI route: tell the user.\n\n### M5. Create the canister and install the wasm through the operator's `proxy`\n\nThe connector may not call the IC management canister (`aaaaa-aa`) directly. The engine-operator's **`proxy` method** calls it for you, as the caller you sign as. Do not confuse it with the **proxy canister** of Step 5: that one relays chain-key calls for your app's code, and has nothing to do with deploying.\n\nEach step is one `canister_update_call` with `canister_id` the engine-operator, `method = \"proxy\"`, `app_url` the app URL and `derivation_origin` the derivation origin that `open_app` returned in M2 (plus the same `account` as in M3, if you chose one). **Always pass `derivation_origin`**: without it the call runs anonymously, and `proxy` answers `UnauthorizedUser`. The `args` are:\n\n```candid\n(record {\n  canister_id = principal \"aaaaa-aa\";\n  method = \"\u003Cmanagement method name>\";\n  args = blob \"\u003Cthe method's argument, Candid-encoded>\";\n  cycles = 0;\n})\n```\n\n`args` holds the **binary** Candid encoding (bytes starting with `DIDL`) of the management method's argument, written as a `blob \"…\"` literal with `\\xx` escapes. It is not textual Candid: encode it with a Candid library in your code sandbox. A success is `variant { Ok = record { result = blob \"…\" } }`, where `result` is the method's reply in binary Candid. The argument and reply types are in https:\u002F\u002Fdocs.internetcomputer.org\u002Freferences\u002Fic.did.\n\n1. **Create.** `method = \"create_canister\"`, and `args` encodes:\n\n   ```candid\n   (record { settings = opt record { controllers = opt vec { principal \"\u003Cengine-operator-id>\" } } })\n   ```\n\n   List the engine-operator as a controller. You (`P`) are added as a controller automatically, but without the operator `proxy` can never reach the canister again: no install, no settings, and no `deleteCanisters` for the owner either. If the code reads an environment variable when it initializes (such as `PROXY_CANISTER_ID`), add `environment_variables` to these `settings` now: it must be in place before step 2 (see M6). `result` decodes to `(record { canister_id : principal })`.\n2. **Install.** `method = \"install_code\"`, and `args` encodes:\n\n   ```candid\n   (record {\n     mode = variant { install };\n     canister_id = principal \"\u003Ccanister id from step 1>\";\n     wasm_module = blob \"\u003Cwasm bytes, gzipped or not>\";\n     arg = blob \"DIDL\\00\\00\";\n     sender_canister_version = null;\n   })\n   ```\n\n   `arg` is the binary-Candid init argument; `DIDL\\00\\00` passes none. `result` decodes to `()`.\n3. **Replace the code later.** The same call with `mode = variant { upgrade = null }` keeps the stable memory; `variant { reinstall }` wipes all state. A canister built with enhanced orthogonal persistence (current Motoko) rejects `upgrade = null`: upgrade it with `variant { upgrade = opt record { wasm_memory_persistence = opt variant { keep } } }`. Use `keep` **only** for such a build: the IC rejects it when the new module lacks the `enhanced-orthogonal-persistence` custom section (a Rust canister, or classical-persistence Motoko), so those keep `upgrade = null`. (The operator's guide says other canisters ignore `keep`; the IC interface spec says otherwise, and the spec wins.)\n4. **A module over about 2 MB** (the single-message limit) goes up with `upload_chunk`, then installs with `install_chunked_code`.\n\nThe operator relays only `create_canister`, `canister_status`, `update_settings`, `install_code`, `upload_chunk`, `clear_chunk_store`, `install_chunked_code`, `start_canister` and `stop_canister`, and only on canisters that list you as a controller. Any other method, the operator itself, and protected canisters come back as `Err` `CallFailed` with a `reason`, as does any `proxy` call on a canister while an `update_settings` on it is in flight: retry once it completes. `cycles` is ignored, since the engine charges nothing. `Err` `UnauthorizedUser` means the call was not signed by a member: check that you passed `derivation_origin`, then go back to M3.\n\n### M6. Name the app, and pass configuration\n\nStep 2's `__META_*` variables, and `PROXY_CANISTER_ID` from Step 5, are canister environment variables. With no `icp.yaml` to carry them, you set them yourself, and **when** matters:\n\n- **A variable the code reads at init or upgrade** (`PROXY_CANISTER_ID`, a key name, any configuration) must be set **before** the `install_code` that reads it. Otherwise init sees it missing, and traps or bakes in a fallback that a later upgrade does not undo. Put it in `create_canister`'s `settings` (M5, step 1), or set it with `update_settings` between steps 1 and 2. Before an upgrade or reinstall that should see a new value, set that value first.\n- **Display-only metadata** (`__META_*`) is read by the console, not by your code, so it can follow at any time. `__META_BASE_URL` needs the canister id, so it comes after step 1 anyway.\n\nEither way it is `update_settings` through `proxy`, with `args` encoding:\n\n```candid\n(record {\n  canister_id = principal \"\u003Ccanister id>\";\n  settings = record {\n    environment_variables = opt vec {\n      record { name = \"__META_PROJECT\"; value = \"My App\" };\n      record { name = \"__META_NAME\"; value = \"App\" };\n      record { name = \"__META_MAIN_CANISTER\"; value = \"true\" };\n    };\n  };\n})\n```\n\nThe list **replaces** the canister's whole set of variables, so send all of them every time. The rules for their values are in Step 2.\n\n### M7. Verify\n\n- `canister_status` through `proxy`, with `args` encoding `(record { canister_id = principal \"\u003Ccanister id>\" })`, shows `status = variant { running }` and the installed `module_hash`.\n- A canister that serves HTTP is reachable at `https:\u002F\u002F\u003Ccanister-id>.icp.net`. On an all-in-one engine it is under the engine's own domain instead: the operator's guide names which.\n- The canister appears on the engine's **Your apps and services** page in the console.\n\nReport the canister id(s), and the URL if any, back to the user.\n\n## Step 1 — Link the CLI to your engine identity (CLI route, once per machine)\n\nThe CLI must sign as the **same identity that administers the engine** — that is the principal you log in to the console with.\n\n### Step 1.0 — First determine WHERE the CLI runs relative to the browser\n\n`icp identity link web` completes the sign-in via a redirect to `http:\u002F\u002F127.0.0.1:\u003Cport>` **on the machine where the CLI runs**. The browser in which the user completes the Internet Identity sign-in must be able to reach that loopback address. Determine the environment before linking:\n\n- **CLI and browser on the same machine, with network** (local development, or a terminal-integrated agent on the user's own machine) → use the normal link flow below.\n- **CLI in a remote or isolated sandbox, with network** (the agent runs in the cloud; the user's browser is on a different machine) → the normal flow **cannot** work: the sign-in URL carries a `callback=http:\u002F\u002F127.0.0.1:\u003Cport>` bound to the sandbox, the user's browser resolves `127.0.0.1` to its *own* machine, and the delegation never reaches the CLI. Later commands then fail with authorization errors. If you have the ICP MCP tools, stop here and use the ICP MCP route. Otherwise ask the user to switch to a local session, or to add the ICP MCP connector (see \"Choose the Deploy Route First\"). Use the **delegation handoff** below only if they can do neither.\n- **CLI shell with no network at all** (e.g. a sandboxed device bridge on the user's own machine: DNS blocked, HTTPS requests fail, writes limited to `\u002Ftmp`) → **no** `icp` network command can run there — not the link, and not `icp deploy` either. The delegation handoff does not help: it only moves signing authority, not network access. Do not retry, tunnel, or proxy. Prepare the project and hand the user **one script** to run in their real terminal — see \"No-network CLI host\" below.\n\nWhen in doubt, probe before linking: `curl -sI https:\u002F\u002F\u003Cconsole-origin>` failing (or DNS not resolving) from the CLI shell means the no-network case.\n\n**Never** present a sandbox `127.0.0.1` URL to the user as something to open in their browser — it is unreachable from their machine. And do not invent a headless flag on `icp identity link web`: as of icp-cli 1.0.2 it has none (confirm with `icp identity link web --help`).\n\n### Delegation handoff (CLI host and browser on different machines)\n\n`icp identity delegation` transfers signing authority from an identity linked on the **user's** machine to a session key on the **CLI host**, without the browser ever reaching the CLI host. It requires `icp` installed on the user's machine too, and exists in icp-cli 1.0.x — verify with `icp identity delegation --help` on both machines. If the user's machine has `icp` and you can run commands there, the simplest path is to run this whole skill there instead; otherwise:\n\n1. **On the CLI host (sandbox)** — create a pending identity with a fresh session key:\n\n   ```bash\n   icp identity delegation request \u003Cyour-identity-name>\n   ```\n\n   It prints the session **public** key as a PEM to stdout. Give that PEM block to the user. (If the default `--storage keyring` fails in a headless sandbox, retry with `--storage plaintext`.)\n\n2. **On the user's machine** — the user links there if not already linked (`icp identity link web \u003Clocal-name> --app \u003Cconsole-domain>` — the normal flow works locally), saves the PEM to a file, and signs a delegation to it:\n\n   ```bash\n   icp identity delegation sign --identity \u003Clocal-name> --key-pem session-key.pem --duration 8h > delegation.json\n   ```\n\n   `--duration` takes e.g. `30m`, `8h`, `1d`; optionally `--canisters \u003Cids>` restricts the delegation. The user sends `delegation.json` back. If `sign` fails with `delegation for identity \u003Cname> has expired or will expire within 5 minutes`, the local web session has lapsed — run `icp identity reauth \u003Clocal-name>` (the normal browser flow, which works locally) and retry.\n\n3. **On the CLI host** — complete the pending identity and activate it:\n\n   ```bash\n   icp identity delegation use \u003Cyour-identity-name> --from-json delegation.json\n   icp identity default \u003Cyour-identity-name>\n   icp identity principal   # must print the user's console principal\n   ```\n\nTreat `delegation.json` as a **time-limited credential**: whoever holds the sandbox's session key can sign as the user's console principal until it expires. Keep the duration short and re-run the handoff when it lapses.\n\n### No-network CLI host — hand the user one script\n\nIf the CLI shell cannot reach the network, the user's real terminal is the deploy machine — and because their terminal and browser share a loopback there, the **normal** link flow works; no delegation handoff is needed. Your job shifts to preparation:\n\n1. Get the built project into a directory the user's terminal can reach.\n2. Write one script at the project root that does the whole network-bound sequence — adapt this template (fill in the real identity name, console domain, and subnet id; do not leave placeholders):\n\n   ```bash\n   #!\u002Fusr\u002Fbin\u002Fenv bash\n   set -euo pipefail\n   icp --version\n   icp identity list | grep -E '^\\*? *\u003Cyour-identity-name> ' >\u002Fdev\u002Fnull || icp identity link web \u003Cyour-identity-name> --app \u003Cconsole-domain>\n   icp identity default \u003Cyour-identity-name>\n   icp deploy -e ic --subnet \u003Csubnet-id>\n   ```\n\n3. Ask the user to run it in their terminal, complete the Internet Identity sign-in when the browser opens, and paste the output back.\n4. Verify from the pasted output (canister ids, frontend URL) and continue — a re-deploy for the Step 2 `__META_*` variables goes through the same script.\n\n### Normal link flow (CLI and browser share a loopback)\n\nFirst check what already exists:\n\n```bash\nicp identity list      # names + principals; * marks the active identity\n```\n\nThe list does **not** show which console (if any) an identity was linked against — that cannot be determined from the CLI. Decide like this:\n\n- **Only `anonymous` (or plain local identities) listed** — no web-linked identity exists; run the link command below.\n- **An identity the user recognizes as their engine identity** (by name or principal) — set it active (below) and skip to Step 2.\n- **Unsure** — ask the user, or simply relink under a new name; linking again is cheap and safe.\n\nTo link, run this, substituting a name the user picks — `\u003Cyour-identity-name>` is any local label, not a fixed value (do not hardcode something like `my-engine-admin`); reuse the **same** name in every command below:\n\n```bash\nicp identity link web \u003Cyour-identity-name> --app \u003Cconsole-domain>\n```\n\n- `\u003Cconsole-domain>` is the host of the console origin, with no scheme, port or path: `opencloud.org` for the default console, `beta.opencloud.org` for `https:\u002F\u002Fbeta.opencloud.org`. The console derives its principal from the host it serves, so this is the domain to name. Do not take it from `\u002F.well-known\u002Fii-derivation-origin`: that file serves the ICP MCP route and can name another host.\n- Never omit `--app`. Without it, Internet Identity derives the principal for `cli.id.ai`, which is **not** your console, and the engine rejects every deploy as unauthorized.\n- Do not use `--auth \u003Cconsole-origin>` for the OpenCloud console. It reaches the same principal, but through the console's own `\u002Fcli` page, so the user signs in on two screens instead of one. Use `--auth \u003Cconsole-origin>` (no `--app`) only when the user says their console signs in with its own Internet Identity rather than `id.ai`, as a test deployment does.\n- The command first waits at a **\"Press Enter to log in\"** prompt before anything happens. Run it interactively when you can; in a non-interactive shell (e.g. a background process) pipe a real newline:\n\n  ```bash\n  printf '\\n' | icp identity link web \u003Cyour-identity-name> --app \u003Cconsole-domain>\n  ```\n\n  Do **not** redirect stdin from `\u002Fdev\u002Fnull` — the bare EOF does not satisfy the prompt, and the command sits on \"Press Enter to log in\" indefinitely with no browser ever opening.\n- After Enter it opens a **browser tab**. The **user** completes the Internet Identity sign-in there. Wait for them to confirm before continuing — you cannot complete the sign-in for them.\n- On first use, id.ai can stop at a **\"CLI access disabled\"** screen. That is the expected first run, not an error. Tell the user to turn on **CLI access** for their identity in the Internet Identity settings, then run the link command again from the start: the old sign-in URL is single-use. See the `agent-web-identity` skill, Pitfall 1.\n- `--app` must be the **exact** console host, e.g. `opencloud.org`. Another domain derives a *different* principal, and the engine will reject the deploy as unauthorized.\n- This is a **one-time, per-machine** step.\n\nThen make it the active identity and verify:\n\n```bash\nicp identity default \u003Cyour-identity-name>\nicp identity default     # prints the active identity name\nicp identity principal   # prints the principal the deploy will sign as\n```\n\n## Step 2 — Name the app (and give it an icon) in the console (recommended)\n\nBy default, CLI-deployed canisters appear on the engine console's **Your apps and services** page as bare rows labelled only by their principal id. A set of **canister environment variables** makes the console group them into a single named application with readable per-canister labels, an \"Open\" button, and an icon. Set them once in your project config:\n\n- `__META_PROJECT` — the application name. Canisters that share the **same** value are grouped into one named app, so set an identical value on every canister of the app.\n- `__META_NAME` — the per-canister display label (e.g. `Backend`, `Frontend`).\n- `__META_MAIN_CANISTER` — the literal string `\"true\"` on exactly one canister (the entry point, usually the frontend\u002Fasset canister). This marks the app's **main canister**: the console reads `__META_BASE_URL` and `__META_ICON_PATH` only from it, and the \"Open\" button targets it.\n- `__META_BASE_URL` — an **absolute `https:\u002F\u002F` URL**, set on the main canister (e.g. the frontend canister's URL `https:\u002F\u002F\u003Cfrontend-canister-id>.icp.net`, or a custom domain). When present and valid, it is the URL the \"Open\" button opens; when absent or not `https`, the \"Open\" button falls back to the main canister's gateway URL. It is also the base that `__META_ICON_PATH` resolves against.\n- `__META_ICON_PATH` — the path to the app icon, resolved against `__META_BASE_URL` to form the icon the console renders (e.g. `\u002Ffavicon.svg` → `https:\u002F\u002F\u003Cbase>\u002Ffavicon.svg`). Set it on the **main** canister, alongside `__META_BASE_URL`.\n\nThe icon and \"Open\" link are read **only from the main canister** (the one marked `__META_MAIN_CANISTER: \"true\"`) — `__META_BASE_URL` \u002F `__META_ICON_PATH` on any other canister are ignored.\n\nOn the ICP MCP route there is no project config: set the same variables with `update_settings` (M6). On the CLI route, set them under each canister's `settings.environment_variables` — this is valid alongside a recipe. With per-canister `canister.yaml` files:\n\n```yaml\n# frontend\u002Fcanister.yaml\nname: frontend\nrecipe:\n  type: \"@dfinity\u002Fstatic-site@v0.4.3\"\n  configuration:\n    build:\n      - npm install\n      - npm run build\n    dir: dist\nsettings:\n  environment_variables:\n    __META_PROJECT: \"My App\"\n    __META_NAME: \"Frontend\"\n    __META_MAIN_CANISTER: \"true\"\n    __META_BASE_URL: \"https:\u002F\u002F\u003Cfrontend-canister-id>.icp.net\"\n    __META_ICON_PATH: \"\u002Ffavicon.svg\"\n```\n\n```yaml\n# backend\u002Fcanister.yaml\nname: backend\nrecipe:\n  type: \"@dfinity\u002Fmotoko@v5.0.0\"   # v5 reads main\u002Fcandid from mops.toml ([canisters.backend]) — see the icp-cli skill\nsettings:\n  environment_variables:\n    __META_PROJECT: \"My App\"\n    __META_NAME: \"Backend\"\n```\n\nFor a single inline `icp.yaml` (canisters defined there directly), put the same `settings.environment_variables` block under each canister entry. Note the inline form: `canisters` is an **array** of `{name, recipe, settings}` items, not a map keyed by canister name:\n\n```yaml\n# icp.yaml — canisters defined inline\ncanisters:\n  - name: frontend\n    recipe: # … as in the canister.yaml example above\n    settings:\n      environment_variables:\n        __META_PROJECT: \"My App\"\n        __META_NAME: \"Frontend\"\n        __META_MAIN_CANISTER: \"true\"\n        __META_BASE_URL: \"https:\u002F\u002F\u003Cfrontend-canister-id>.icp.net\"\n        __META_ICON_PATH: \"\u002Ffavicon.svg\"\n  - name: backend\n    recipe: # … as in the canister.yaml example above\n    settings:\n      environment_variables:\n        __META_PROJECT: \"My App\"\n        __META_NAME: \"Backend\"\n```\n\nNotes:\n- icp-cli **merges** these with the `PUBLIC_CANISTER_ID:\u003Cname>` variables it injects automatically at deploy time — the asset canister keeps serving and the app keeps working. (Verified against icp-cli 0.3.0.)\n- All values are strings; `__META_MAIN_CANISTER` must be the exact string `\"true\"`.\n- They are applied during `icp deploy` (the \"Setting environment variables\" step). After deploy, confirm with `icp canister settings show \u003Cname> -e ic`.\n\nIcon specifics (the console builds the icon as `__META_BASE_URL` + `__META_ICON_PATH`):\n- **Both** must be present and **on the main canister** for an icon to appear — there is no fallback. `__META_ICON_PATH` alone does nothing.\n- `__META_BASE_URL` must parse as an absolute **`https:\u002F\u002F`** URL. A bare host, an `http:\u002F\u002F` URL, or a `data:` \u002F `javascript:` value is rejected: the icon then does not render, and the \"Open\" button falls back to the main canister's gateway URL (it does not disappear). (The console validates the scheme before using it.)\n- `__META_ICON_PATH` is a **path to an asset your frontend actually serves** (e.g. `\u002Ffavicon.svg`), not an inline image. The resolved URL is rendered as an `\u003Cimg>` `src`, so it must return an image. Do **not** put a `data:` URI here: engine env values are length-capped (≤128 chars observed), so it would not fit, and the field is a path by design.\n- The frontend canister's id is only known **after** the first deploy. The usual flow is: deploy once, read the frontend canister id from the output, set `__META_BASE_URL` to `https:\u002F\u002F\u003Cthat-id>.icp.net` (and `__META_ICON_PATH`), then re-deploy to apply. If you control a custom domain for the app, you can set it up front instead.\n\n### Pin the Internet Identity derivation origin (if the app signs users in)\n\nInternet Identity derives a principal **per origin**. An app reached at both its canister address and a custom domain signs the same person in as two different users.\n\nThe engine console names the app's **canister address** as the origin to derive from. It cannot be renamed or removed, so it survives adding, changing, or dropping a custom domain. It is the address of the canister that **serves your frontend** (the asset\u002Fstatic-site canister the browser loads the app from), built from that canister's id:\n\n```\nhttps:\u002F\u002F\u003Cfrontend-canister-id>.icp.net\n```\n\nThat is usually the canister you marked `__META_MAIN_CANISTER: \"true\"`, but that flag is a console display setting, not a guarantee. If you marked a backend canister, still derive from the frontend canister. A backend canister cannot be a derivation origin: the browser is never on it, and it cannot serve the `.well-known\u002Fii-alternative-origins` file Internet Identity fetches from that origin to validate the claim.\n\nBuild the value from the canister id, **not** from `__META_BASE_URL`. The two match only while `__META_BASE_URL` still points at the canister address. Pointing it at a custom domain is normal and supported (see the icon notes above), and the two then differ. A custom domain is the one value that must never become the derivation origin.\n\n**Do this before the app has users, not after.** A domain that has already collected sign-ins cannot be repointed at a derivation origin without orphaning every account created under it. If the app might ever get a custom domain, set `derivationOrigin` on the first deploy even while the canister address is the only origin — it costs nothing then and cannot be applied retroactively without losing accounts.\n\nFor the mechanics — the `derivationOrigin` option and the `.well-known\u002Fii-alternative-origins` file, including the `_headers` entry the `@dfinity\u002Fstatic-site` recipe needs — load the **`user-sign-in`** skill.\n\nCaffeine apps are the exception: Caffeine owns their address, so it is not the app's to derive from. The console withholds the canister-address row for them.\n\n### Version metadata (recommended)\n\nEmbed build provenance into each canister's wasm so tooling and the console can show what version is running (console support is rolling out; the metadata is already readable — see the verify note below). Add this **by default on every deploy** — do not wait for the user to ask. All official recipes (motoko, rust, static-site, asset-canister, prebuilt) accept a `metadata` list under the recipe `configuration`; each entry is baked into the wasm as a custom section. Values are interpolated into a shell command at build time, so `$(…)` command substitution works. (Verified by live builds against `@dfinity\u002Fmotoko` v4.1.0 and v5.0.0 on icp-cli 1.0.2.)\n\n**Git project** (check first: `git rev-parse HEAD` succeeds):\n\n```yaml\nrecipe:\n  type: \"@dfinity\u002Fmotoko@v5.0.0\"\n  configuration:\n    metadata:\n      - name: service:git:sha\n        value: $(git rev-parse HEAD)$(git diff --quiet HEAD 2>\u002Fdev\u002Fnull || echo +dirty)\n      - name: service:git:origin\n        value: $(git remote get-url origin)\n      - name: service:git:updated_at\n        value: $(git log -1 --format=%cI)\n      - name: service:version\n        value: $(node -p \"require('.\u002Fpackage.json').version\" 2>\u002Fdev\u002Fnull || echo 1.0.0)\n```\n\n**Non-git project** — the git substitutions above do **not** fail the build; they silently bake garbage (`service:git:sha` becomes the literal `+dirty`, `service:git:origin` comes out empty). Use only an explicit version, and bump it on each subsequent deploy:\n\n```yaml\nmetadata:\n  - name: service:version\n    value: \"1.0.0\"\n```\n\nNotes:\n\n- Use these exact names (`service:git:sha`, `service:git:origin`, `service:git:updated_at`, `service:version`) — they are the agreed convention the console will read; differently named entries will not be picked up.\n- Prefer command results over literals where possible: `+dirty` on the sha flags uncommitted changes, so a deploy is traceable to its exact source state.\n- Values must be **deterministic** for a given source tree: sha, origin, the last commit's date, a version string. Never embed a build time (`$(date)`) or other build-time-varying values. The commit date (`%cI`) is allowed because it is a property of the commit, not of the build — but note it means \"code last changed\", not \"last deployed\": deploying an old commit shows the old date. The deploy time itself comes from the canister history the network records automatically.\n- If the project has no `package.json` (or no version field), the fallback `1.0.0` applies; replace it with the project's real versioning scheme when one exists.\n- The sections are injected as `icp:private`, readable by the canister's controllers (you, and the console acting as you) via read_state — no canister call needed. Verify after deploy, with the linked identity active: `icp canister metadata \u003Ccanister-name> service:git:sha -e ic`.\n\n## Step 3 — Deploy to the engine's subnet (CLI route)\n\nFrom the project root:\n\n```bash\nicp deploy -e ic --subnet \u003Csubnet-id>\n```\n\n- `-e ic` targets mainnet (the engine runs on an IC subnet); `--subnet \u003Csubnet-id>` pins the deploy to **your engine's** subnet. Confirm the exact flags with `icp deploy --help` before running if unsure.\n- Deploying consumes capacity on the engine; make sure the engine has room.\n\n**Alternative: packaged upload.** If the project is distributed as a built `.icp` package and a direct `icp deploy` is not available, the user can upload the bundle instead: engine → **Your apps and services** → **Upload a bundle**, which walks through uploading the `.icp` bundle. The page's other button, **Build with agents**, gathers the other ways in: a coding agent, Caffeine, the CLI instructions this skill automates, and OpenSaaS for ready-made apps.\n\n## Step 4 — Verify\n\n- The `icp deploy` output reports the deployed canister ids.\n- The canisters appear on the engine's **Your apps and services** page in the console; each canister's detail view offers an \"Open in browser\" link.\n- If you set the metadata in Step 2, the canisters are grouped under your `__META_PROJECT` name with their `__META_NAME` labels, and the main canister shows an \"Open\" button — instead of bare principal rows. With `__META_BASE_URL` + `__META_ICON_PATH` set, the app also shows its icon (allow for a short console cache delay).\n- A frontend (asset) canister is served at `https:\u002F\u002F\u003Cfrontend-canister-id>.icp.net`.\n\nReport the deployed canister ids (and the frontend URL, if any) back to the user.\n\n## Step 5: Deploy a proxy canister (only if the app needs chain-key services)\n\nSkip this step unless the app calls **threshold ECDSA or Schnorr** (Bitcoin, Ethereum), **vetKD \u002F VetKeys**, the **exchange-rate canister** (XRC), the **Bitcoin API**, or the **EVM \u002F SOL RPC** canisters: a console proxy relays nothing else for the engine's canisters (the exact list is in `cloud-engine-canisters`). Everything else (the app's own execution, storage, messaging, and HTTPS outcalls) is free on an engine and needs no proxy at all.\n\nThose services live on other subnets and charge cycles per call, and an engine canister can neither hold cycles nor send a cycle-bearing message across the engine boundary. A **proxy canister** on a normal Application subnet makes the call on its behalf and pays from its own balance.\n\nIf you are writing or deploying such an app and do not have the proxy id yet, ask for it now (What You Need, item 3) rather than deploying with a placeholder: the first proxied call would fail.\n\n### Which proxy\n\nTwo different canisters can play this role, and picking the wrong one produces a working-looking setup that fails at the first call:\n\n| | **Console proxy** | **Self-deployed proxy** |\n|---|---|---|\n| Deployed by | The engine's **owner**, from the console | You, with `icp` |\n| Authorized callers | Every canister on the engine's **subnet** (checked with the NNS registry), plus its controller | **Controllers only** |\n| Your engine canisters may call it | Yes, automatically | Only after you add each one as a controller |\n| What it relays for them | Only chain-key, Bitcoin and exchange-rate \u002F RPC services (list in `cloud-engine-canisters`) | Anything |\n| Your CLI identity may call it | **No** (rejected as `UnauthorizedUser`) | Yes: you are a controller |\n| Threshold-key derivation | Isolated per calling canister | No isolation |\n| Funded with | The engine's balance, automatically (no card) | Cycles you hold (`icp cycles mint`) |\n\n- **App code calling chain-key services → the console proxy.** It is the one that admits your engine's canisters without per-canister configuration.\n- **You calling something from the CLI** (`icp canister call --proxy`, `icp deploy --proxy`, canister-only management methods like `canister_info` or `raw_rand`) **→ your own proxy.** The console proxy cannot serve this: it rejects ingress from any non-controller principal, and your CLI identity is not one.\n\nThey are not substitutes, and they derive **different keys**: see the derivation warning in `cloud-engine-canisters` before switching an app from one to the other.\n\n### Console proxy: the engine owner deploys it (a console action, not a CLI one)\n\nOnly the engine's **owner** can deploy one, and only from the console. There is no `icp` command for it, and nothing an agent can call creates one: the console endpoints behind the button authenticate with a browser session cookie minted by the Internet Identity login. Hand the user these steps and wait for the proxy canister id:\n\n1. Open the engine in the console → **Integrations** in the engine's sidebar → the **Proxy canisters** card.\n2. **Deploy a proxy**, then **Deploy proxy** in the dialog. There is no amount to choose and no card is charged: the proxy is created on a normal Application subnet out of the engine's balance, starting with a few dollars' worth of cycles.\n3. Copy the **proxy canister id** from the card's table (it opens after a deploy; otherwise expand it with the **N deployed** toggle). That id is what the app calls.\n\nThe deploy is refused while the engine is not serving, or when the engine's balance has too little free to cover the proxy's first cycles: the owner adds funds to the engine, then retries.\n\n**How it stays funded.** Every few minutes while its balance is below a low threshold, the proxy asks for a top-up, which is sent out of the engine's balance: capped per hour, and never out of what the engine owes for its nodes. There is no **Top up** button and no automatic top-up setting for it; its row shows the balance and the cycles it has drawn so far. If it runs dry anyway, fund the engine (see \"Paying for an engine\" below).\n\nAlso on that table: **Refresh balance** (a live read, since the displayed figure is cached) and **Delete proxy** (stops and deletes the canister; its unspent cycles, less the small reserve a canister keeps to run, go back to the engine's balance).\n\nAn engine may have **more than one** proxy (\"Deploy another proxy\"). Each has its own balance (all refilled from the same engine balance) and, for a signing app, its own key namespace.\n\n### Wire the proxy id into the app\n\nThe app needs the id at runtime. Pass it as a canister environment variable rather than hardcoding it in source, so the same code can run against a local mock and against the engine:\n\n```yaml\nsettings:\n  environment_variables:\n    PROXY_CANISTER_ID: \"\u003Cproxy-canister-id>\"\n```\n\nOn the ICP MCP route, set it with `update_settings` (M6). Read it in the canister the same way as any other environment variable, and treat it as **permanent configuration**: for an app that derives threshold keys, changing this value changes every address the app owns.\n\n### Self-deployed proxy: the CLI path\n\nFor proxying *your own* calls. This one is the upstream `dfinity\u002Fproxy-canister`, deployed from a template:\n\n```bash\nicp new my-proxy --subfolder proxy       # pre-built wasm, no init args\ncd my-proxy\nicp deploy -e ic                         # NOT --subnet \u003Cengine-subnet>: see below\nexport PROXY_ID=$(icp canister status -e ic --id-only proxy)\n```\n\nFund it, and check on it, with the cycles ledger:\n\n```bash\nicp cycles balance -e ic                 # what you hold\nicp cycles mint --icp 1 -e ic            # ICP -> cycles, if you need more\nicp canister top-up \"$PROXY_ID\" --amount 5t -e ic\nicp canister status \"$PROXY_ID\" -e ic    # the proxy's own balance\n```\n\nThen pass it to any command that needs cycles or a canister-only management method:\n\n```bash\nicp canister call my-canister charge_me '()' -e ic --proxy \"$PROXY_ID\" --cycles 1_000_000_000_000\nicp deploy -e ic --proxy \"$PROXY_ID\"\n```\n\nThree things to get right:\n\n- **Do not deploy it onto the engine's subnet.** A proxy on a `CloudEngine` subnet is useless: it holds 0 cycles and cannot send a cycle-bearing message either. Omit `--subnet` (Step 3's engine-subnet flag is for your app, not for this) so it lands on a normal Application subnet.\n- **It authorizes controllers only.** For an *engine canister* to call it, that canister's principal must be added as a controller (`icp canister settings update \"$PROXY_ID\" --add-controller \u003Ccanister-id> -e ic`), one call per canister, repeated whenever a canister is added. This is the maintenance burden the console proxy exists to remove.\n- **It does not isolate key derivation.** Any authorized caller can request any derivation path, and the keys it produces differ from the console proxy's. Do not point a signing app at one casually.\n\n## Paying for an engine: which balance\n\nTwo balances exist, they are funded differently, and the app's own canisters have neither. Do not \"top up the canister\" on an engine without first establishing which of these is meant:\n\n| Balance | What it pays for | How it is funded | What happens when it empties |\n|---|---|---|---|\n| **Engine balance** | The engine itself (its nodes, day by day), and the top-ups of its console proxies | **Add funds** on the engine's **Billing** page (charged to the card on file), and automatic top-ups that charge the card about a week before the balance would run out | The engine is **deleted**, with everything on it: not reversible, so treat a low-runway warning as urgent. An engine whose automatic top-up keeps failing is **halted**, and runs again once a payment lands |\n| **Proxy cycle balance** | Only the calls the proxy relays (signing, vetKD, XRC, Bitcoin API, EVM\u002FSOL RPC) | A console proxy refills itself from the engine balance (Step 5); a self-deployed one takes `icp canister top-up` | Relayed calls fail with `InsufficientCycles`; the proxy freezes rather than being deleted, and recovers on a later top-up |\n\nA console proxy's top-ups come out of the engine balance, so a busy proxy shortens the engine's runway, though never by what the engine owes for its nodes.\n\nThe app's own canisters have **none** of these: they hold 0 cycles by design and cannot be topped up. A \"0 cycles\" reading on an engine canister is normal (see `cloud-engine-canisters`, pitfall 5).\n\n## Code that runs on the engine\n\nA cloud engine runs on a **`CloudEngine` subnet** with protocol-level call rules that differ from normal Application subnets: engine canisters hold 0 cycles and must **never attach cycles** to any call, **cross-subnet calls must be bounded-wait**, HTTPS outcalls are made directly and free, and cycle-bearing cross-subnet targets (exchange-rate XRC, threshold ECDSA\u002FSchnorr, vetKD) go through the engine's funded **console proxy canister**. Before writing or debugging any canister code that will run on the engine, load the **`cloud-engine-canisters`** skill — it has the rules, the proxy interface, and the pitfalls.\n\n## Common Pitfalls\n\n1. **Sign-in not completed.** Running `icp identity link web …` but not finishing the Internet Identity sign-in in the browser leaves the CLI unlinked; later commands fail with authorization errors. Re-run and wait for the user to confirm the browser flow finished. If no browser ever opened, the command is stalled at the \"Press Enter to log in\" prompt — relaunch with a piped newline, `printf '\\n' | icp identity link web …`, never `\u003C \u002Fdev\u002Fnull` (see Step 1). If the CLI runs in a remote sandbox, re-running can never complete — see Pitfall 10 and the delegation handoff in Step 1.0.\n2. **Wrong or missing `--app` domain.** Any domain other than the console host, or no `--app` at all, derives a different principal, and the engine rejects the deploy as not authorized. Relink with the exact domain (Step 1). If the deploy is rejected as unauthorized after linking with the default `--app opencloud.org`, ask the user for the exact URL they sign in with and relink with its host.\n3. **Guessing the subnet id.** Never invent it: the deploy fails or targets the wrong subnet. It is on the engine's **Settings** page in the console; ask the user.\n4. **Deploying with the anonymous identity.** The default local identity is anonymous and is not the engine admin. You must link and `icp identity default \u003Cyour-identity-name>` first.\n5. **Using `dfx`.** This ecosystem uses `icp`, never `dfx`. The correct sequence is `icp identity link web \u003Cname> --app \u003Cconsole-domain>` (Step 1), then `icp deploy -e ic --subnet \u003Csubnet-id>` (Step 3). See the `icp-cli` skill.\n6. **Skipping the app metadata.** Without `__META_PROJECT` (Step 2), the canisters still deploy and work but render as bare, unnamed principal rows in the console. Setting `__META_*` is what produces a named app with labelled canisters and an \"Open\" button.\n7. **Wrong `__META_MAIN_CANISTER` value.** It is matched as the exact string `\"true\"`. A boolean, `\"True\"`, or marking more than one canister means no (or the wrong) \"Open\" button. Mark exactly one entry-point canister.\n8. **Inventing an icon variable.** The icon variable is `__META_ICON_PATH` (a path resolved against `__META_BASE_URL`). Do not guess `__META_ICON`, `__META_LOGO`, or `__META_ICON_LINK` — they are ignored, so the icon silently never appears.\n9. **Icon set on the wrong canister, or without a base URL.** The icon is read only from the **main** canister and needs **both** `__META_BASE_URL` (a valid absolute `https:\u002F\u002F` URL) and `__META_ICON_PATH`. Setting the icon path on a side canister, omitting the base URL, or giving a non-https \u002F `data:` base means no icon renders. (The \"Open\" button still works — it falls back to the main canister's gateway URL — so a bad base URL costs the icon and the custom Open URL, not the button.)\n10. **Assuming the browser and CLI share localhost.** `icp identity link web` returns the delegation to `127.0.0.1:\u003Cport>` on the CLI host. If the CLI runs in a remote sandbox while the user's browser is on a different machine, the sign-in never completes and later commands fail with authorization errors — no amount of re-running the link fixes it. With the ICP MCP tools, use the ICP MCP route instead. Without them, see Step 1.0: prefer a local session, where the browser and CLI share a loopback, or the ICP MCP connector, and use the delegation handoff (`icp identity delegation request` \u002F `sign` \u002F `use`) only when the user has neither option. Ask before you build in the sandbox, not after.\n11. **Expecting the delegation handoff to fix a missing network.** The handoff moves signing authority, not connectivity — `icp deploy` still needs the network from the shell that runs it. If the CLI shell has no network at all (a sandboxed device bridge: DNS blocked, HTTPS fails), no `icp` network command can run there, link or deploy. Do not offer to \"do the rest\" from that shell and do not tunnel — hand the user one script for their real terminal (see \"No-network CLI host\" in Step 1.0), where the normal link flow works because terminal and browser share a loopback.\n12. **Build timestamps in wasm metadata.** Metadata is baked into the wasm and must be deterministic — a build time (`$(date)`) changes every build even with identical source, breaking reproducibility and changing the module hash on every deploy. The deterministic alternative is the last commit's date, `service:git:updated_at` = `$(git log -1 --format=%cI)` — a property of the source tree, not the build. The deploy time itself comes from the canister history recorded by the network, never from metadata.\n13. **Git metadata substitutions in a non-git project.** Outside a git repository, `$(git rev-parse HEAD)` does not fail the build — it silently bakes garbage: `service:git:sha` becomes the literal `+dirty` and `service:git:origin` comes out empty. Check for a git repo first (`git rev-parse HEAD` succeeds); if there is none, set only `service:version` with an explicit value (or `git init` and commit before deploying, if version control is wanted anyway).\n14. **Letting an engine app collect sign-ins before pinning a derivation origin.** Internet Identity principals are per-origin, so adding a custom domain later turns every existing user into a stranger at the new address — and the fix cannot be applied retroactively without orphaning the accounts already made under the old origin. On the first deploy of any app that uses II, set `derivationOrigin` to the address of the canister that serves the frontend, built from its canister id (`https:\u002F\u002F\u003Cfrontend-canister-id>.icp.net`), even when that is currently the app's only origin. Do **not** copy `__META_BASE_URL`: it is allowed to point at a custom domain, and a custom domain is exactly what must not become the derivation origin. See the `user-sign-in` skill for the `.well-known\u002Fii-alternative-origins` half.\n15. **Trying to deploy or fund the console proxy from the CLI or an API.** There is no `icp` command for it, and the console endpoints behind the buttons authenticate with a browser-session cookie from the Internet Identity login (no token auth), so an agent cannot drive them. Only the engine's owner can deploy one. Hand the user the console steps (engine → **Integrations** → **Proxy canisters** → **Deploy a proxy**) and wait for the proxy canister id. It needs no funding step: it is paid from the engine's balance. An agent *can* do the whole self-deployed-proxy path unattended, but that is a different canister with different authorization (Step 5).\n16. **Deploying a self-deployed proxy onto the engine's subnet.** Reusing Step 3's `--subnet \u003Cengine-subnet-id>` puts the proxy on the `CloudEngine` subnet, where it holds 0 cycles and may not send cycle-bearing messages either, so it cannot do the one thing a proxy is for. Omit `--subnet` so it lands on a normal Application subnet.\n17. **Expecting a self-deployed proxy to accept calls from engine canisters.** It authorizes **controllers only**. An engine canister calling it gets `UnauthorizedUser` until that canister's principal is added with `icp canister settings update \u003Cproxy> --add-controller \u003Ccanister-id> -e ic`, and again for every canister added later. The console proxy authorizes every canister on the engine's subnet instead, which is why it is the right one for app code.\n18. **Deleting a proxy that a signing app derives keys from.** The delete button sends the unspent cycles back to the engine's balance, which makes it look like a tidy-up. It is not: threshold keys are derived from the proxy's own principal, so deleting it (or repointing the app at another proxy) changes every Bitcoin\u002FEthereum address the app owns and strands any funds at the old ones. See `cloud-engine-canisters` for the derivation rules before touching a proxy that an app already signs with.\n19. **Confusing the balances.** \"The canister is out of cycles\" means something different for each: an engine canister holds 0 cycles by design and cannot be topped up, the *engine's* balance running low is fixed with **Add funds** on its Billing page, and `InsufficientCycles` from a relayed call is the *proxy's* balance (for a console proxy, which refills from the engine's balance, that again means funding the engine). Establish which one before topping anything up, and note that the engine's balance running out deletes the engine.\n20. **Deploying a proxy-dependent app without the proxy id.** An app that signs, derives vetKeys, reads exchange rates or calls the Bitcoin API or EVM\u002FSOL RPC deploys fine with a placeholder, a guessed id or an id from another engine, then fails on its first proxied call (`UnauthorizedUser`, or keys and addresses that belong to someone else's proxy). Ask for the id before deploying (What You Need, item 3). If the engine has none, the owner deploys one from the console (Step 5); never stand in a self-deployed proxy for it.\n21. **Picking the wrong route.** In a sandbox (a web or mobile chat, or a cloud coding session) that has the ICP MCP tools, asking the user to switch to a local session, or trying `icp identity link web` or the delegation handoff, wastes a round trip the connector makes unnecessary: use the ICP MCP route. The reverse holds too: in a local session, link the CLI and deploy with it even when the connector is also available, because the CLI can upload an asset canister's files and the connector cannot.\n22. **Assuming the connector signs as the console principal.** Internet Identity principals are per-origin, and at the engine the connector signs as the principal derived for the engine's platform domain. It is not the user's console principal and not a CLI identity, so a `proxy` call answers `UnauthorizedUser` until that principal is an engine member, even for the engine's owner. Read it with `get_app_principal` and hand the user the invite link (M3); you cannot add it yourself. A `canister_update_call` without `derivation_origin` gets the same answer for a different reason: it runs anonymously, so pass the derivation origin `open_app` returned on every call.\n23. **A Questions-only connector session.** With the **Questions only** access level, reads work and every state-changing call is rejected by the network, `create_canister` and `install_code` included. This is not an engine permission problem, so the invite link does not fix it. Ask the user to reconnect with **Actions & questions** (M0).\n24. **Calling the management canister directly, or confusing the two proxies.** The connector refuses calls to `aaaaa-aa`; deploys go through the engine-operator's `proxy` method (M5). That method is not the chain-key proxy canister of Step 5, and the console proxy canister cannot create or install canisters: it relays only chain-key, Bitcoin and exchange-rate \u002F RPC services.\n25. **Creating a canister without listing the engine-operator as a controller.** `create_canister` through `proxy` always adds you, but the operator only when you list it in `settings.controllers`. Without it, `proxy` can no longer reach the canister (no install, no settings), and the owner cannot remove it with `deleteCanisters` either. List the operator in the create call (M5, step 1).\n26. **Textual Candid in `proxy`'s `args`.** `args` is a `blob` holding the **binary** Candid encoding of the management method's argument (bytes starting with `DIDL`), and `result` comes back the same way. Passing the textual `(record { … })` as `args` makes the management canister fail to decode the argument. Encode it with a Candid library first (M5).\n27. **Expecting the connector to finish a deploy that needs calls into the new canister.** The connector calls only canisters an app declares, and the engine's manifest declares only its operator. It can install your wasm through `proxy`, but it cannot call the canister you installed, so it cannot upload an asset canister's files. Ship configuration in the init argument or environment variables, or use the CLI route for such an app.\n28. **Serving a custom domain from certified-assets older than 0.4.3.** For each app canister with a custom domain, the engine writes `\u002F.well-known\u002Fic-domains` (the HTTP gateway needs it to serve the domain) and `\u002F.well-known\u002Fii-alternative-origins` (Internet Identity needs it to accept the domain). It writes them with certified-assets' `upload_asset`, which arrived in 0.4.3. On an older canister the console warns that the engine can't write these files, and the domain does not work. Pin `@dfinity\u002Fstatic-site@v0.4.3` or newer. To fix an app that is already deployed, bump the recipe and run `icp deploy -e ic`. If the sync then stops with `assets canister version mismatch`, run `icp canister install \u003Cfrontend> --mode upgrade -e ic` (v0.4.x to v0.4.3 is a patch, so the state stays), then `icp deploy -e ic` again. From v0.3.x the bump is breaking: see the `static-site` skill, pitfall 11. The engine replaces whatever the app ships at these two paths, so do not build them into `dir`. List both in a `_preserve` file at the root of `dir`: without it, every deploy deletes them until the engine writes them again (within 10 minutes).\n\n## Additional References\n\n- Load `cloud-engine-canisters` for the call rules for code that runs on the engine: never attach cycles, bounded-wait cross-subnet calls, direct HTTPS outcalls, and the console proxy for cycle-bearing cross-subnet targets (XRC, threshold signing, vetKD). Load it before writing or debugging engine canister code.\n- Load `icp-cli` for general icp CLI usage (`icp.yaml`, recipes, environments, bindings, identities). Load it for anything beyond this cloud-engine deploy flow — in particular when the project does not build or package yet.\n- Load `user-sign-in` for details of the Internet Identity sign-in that Step 1 triggers in the browser.\n- Load `service-discoverability` for the `\u002F.well-known\u002Fic-architecture` manifest that decides which canisters the ICP MCP connector may call, including how an app you deployed can declare its own canisters, and the operator terms that publishing it accepts.\n- ICP MCP connector setup (ChatGPT plugin, Claude, custom connector URL): https:\u002F\u002Fmcp.internetcomputer.org\u002F.\n- Load `vetkeys` for what vetKD is and how VetKeys are used, once a proxy is in place: its call code targets a normal subnet, so see `cloud-engine-canisters` for the proxied form an engine needs.\n- Load `cycles-management` for cycles on ordinary (non-engine) canisters: balances, freezing thresholds, and ICP-to-cycles conversion via the CMC.\n",{"name":5,"license":15,"metadata":54,"description":7,"compatibility":57},{"title":55,"category":56},"Deploy to Cloud Engine","CloudEngine","ICP MCP route: the ICP MCP connector (native in ChatGPT and Claude, or https:\u002F\u002Fmcp.internetcomputer.org\u002Fmcp) with Actions & questions access, on an engine whose engine-operator has `proxy`. CLI route: icp-cli >= 0.3.0 (verified against 0.3.0, 1.0.2, 1.3.0; delegation handoff needs 1.0.x; the `proxy` template needs `icp new --subfolder`) and a browser for the Internet Identity sign-in. Both: a cloud engine console account; for a console proxy, the engine owner to deploy it","main","4aff87e77e63c1886a978b82902814d015f55a4f","skills\u002Fchains\u002Fdfinity-official-deploy-to-cloud-engine",17,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia","https:\u002F\u002Feth2030.com","Hardcore infra. Building @alt-research and applied cryptography.","2026-11-14 02:27:51",{"source":8,"skillName":5,"commands":68,"prompt":78,"zipAvailable":79},[69,72,75],{"tool":70,"command":71},"npx","npx skills add jiayaoqijia\u002Fcryptoskill --skill deploy-to-cloud-engine",{"tool":73,"command":74},"bunx","bunx skills add jiayaoqijia\u002Fcryptoskill --skill deploy-to-cloud-engine",{"tool":76,"command":77},"pnpm","pnpm dlx skills add jiayaoqijia\u002Fcryptoskill --skill deploy-to-cloud-engine","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-deploy-to-cloud-engine\u002FSKILL.md",true,[81,85,91],{"path":82,"resourceType":82,"resourceTypeDesc":83,"sizeBytes":84,"sourceHtmlUrl":16},"LICENSE","许可证",10770,{"path":86,"resourceType":87,"resourceTypeDesc":88,"sizeBytes":89,"sourceHtmlUrl":90},"SOURCE.md","REFERENCE","参考文档",340,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-deploy-to-cloud-engine\u002FSOURCE.md",{"path":92,"resourceType":87,"resourceTypeDesc":88,"sizeBytes":93,"sourceHtmlUrl":94},"TRUST.auto.yaml",2207,"https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fchains\u002Fdfinity-official-deploy-to-cloud-engine\u002FTRUST.auto.yaml",[96,124,145,162,175,185],{"skillNo":97,"name":98,"nameZh":6,"description":99,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":100,"licenseFileUrl":101,"sourceHtmlUrl":102,"categories":103,"keywords":104,"publishedAt":49},"01M712C436EQYE43342Q4E7GG1","recipe-performance","Diagnose a specific MetaMask user flow's real-app performance and Sentry critical-user-flow telemetry with deterministic mm-harness recipes. Use for CUF boundaries, cold\u002Fwarm\u002Fbackground-resume behavior, latency regressions, lifecycle tags, duplicate or blocking network requests, long tasks, layout\u002Fpaint cost, screen redraws, React rerenders, or browser trace analysis where tests alone cannot prove the real app.","MIT","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fwallets\u002Fmetamask-official-recipe-performance\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fwallets\u002Fmetamask-official-recipe-performance\u002FSKILL.md",[],[105,106,107,108,109,110,48,111,112,113,114,115,116,117,118,119,120,121,122,123],"performance","recipe performance","recipe","mm","sentry","harness","deterministic","recipes","flow","cold","critical","analysis","tasks","react","browser","layout","screen","network","metamask",{"skillNo":125,"name":126,"nameZh":6,"description":127,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":128,"sourceHtmlUrl":129,"categories":130,"keywords":131,"publishedAt":49},"01M70XT2F8A5CQZ81JBP98KSW3","mexc-spot-websocket","MEXC Spot WebSocket workflows for public streams, protobuf handling, depth maintenance, listen keys, and private streams without exposing API keys.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-websocket\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-websocket\u002FSKILL.md",[],[132,133,134,135,136,137,138,139,140,141,33,142,143,144],"mexc spot websocket","spot","websocket","api","workflows","public","listen","streams","depth","keys","api keys","maintenance","protobuf",{"skillNo":146,"name":147,"nameZh":6,"description":148,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":149,"sourceHtmlUrl":150,"categories":151,"keywords":152,"publishedAt":49},"01M70XT0MBXN3WZY1BKN0CPNKR","mexc-spot-rest","MEXC Spot REST workflows for public data, signed account requests, orders, wallet\u002Fadmin actions, and secure signing without exposing API keys.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-rest\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-spot-rest\u002FSKILL.md",[],[153,133,154,155,156,157,158,142,33,141,45,159,160,137,161,136,135],"mexc spot rest","REST","admin","signing","Wallet","requests","secure","data","orders",{"skillNo":163,"name":164,"nameZh":6,"description":165,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":166,"sourceHtmlUrl":167,"categories":168,"keywords":169,"publishedAt":49},"01M70XSYSVZ2N0N91XCAR2CAK0","mexc-futures-websocket","MEXC Futures WebSocket workflows for public streams, private account\u002Forder\u002Fposition streams, ping\u002Fpong, subscriptions, and depth maintenance.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-futures-websocket\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fexchanges\u002Fmncrftfrcnm-mexc-futures-websocket\u002FSKILL.md",[],[170,171,134,172,173,143,33,174,140,139,137,136],"mexc futures websocket","futures","position","ping","subscriptions",{"skillNo":176,"name":177,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":100,"licenseFileUrl":178,"sourceHtmlUrl":179,"categories":180,"keywords":181,"publishedAt":49},"01M6H0ZWHVE5W04WG2DKQE2SQC","0xklen-version-and-refresh-sanctions-lists","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-version-and-refresh-sanctions-lists\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-version-and-refresh-sanctions-lists\u002FSKILL.md",[],[182,183,184],"refresh","0xklen version and refresh sanctions lists","version",{"skillNo":186,"name":187,"nameZh":6,"description":188,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":100,"licenseFileUrl":189,"sourceHtmlUrl":190,"categories":191,"keywords":192,"publishedAt":49},"01M6H0Z28Q3R8J0S799GJQWXM3","verify-tests-cover-changed-lines","Use when a PR adds code that the existing suite may not exercise. Measures patch coverage — the share of newly changed lines hit by tests — before approving.","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-verify-tests-cover-changed-lines\u002FLICENSE","https:\u002F\u002Fgithub.com\u002Fjiayaoqijia\u002Fcryptoskill\u002Fblob\u002Fmain\u002Fskills\u002Fpayments\u002F0xklen-verify-tests-cover-changed-lines\u002FSKILL.md",[],[193,194,195,196,197,198,199,200,201,202,203],"tests","cover","verify tests cover changed lines","verify","coverage","pr","exercise","use","patch","code","share"]