@candledottv/cli 0.10.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +117 -6
  2. package/dist/index.js +44749 -37901
  3. package/package.json +3 -1
package/README.md CHANGED
@@ -7,7 +7,7 @@ health from the terminal. Zero runtime dependencies; the whole thing is one self
7
7
  ## Quick start
8
8
 
9
9
  ```
10
- Install the Candle CLI (macOS or Linux):
10
+ Install the Candle CLI (macOS 13 or later, or Linux):
11
11
 
12
12
  curl -fsSL https://candle.tv/install.sh | bash
13
13
 
@@ -24,6 +24,8 @@ login` on its own does just the authorization step.
24
24
 
25
25
  The npm package `@candledottv/cli` stays published for CI, programmatic use, and Windows until
26
26
  `install.ps1` ships; `npx -y @candledottv/cli@latest <command>` runs it once without installing.
27
+ It is also the way in on macOS 12 or earlier: the release binaries need macOS 13, and `install.sh`
28
+ and Homebrew refuse an older Mac rather than install a binary not built for it.
27
29
 
28
30
  ### No-npm fallback
29
31
 
@@ -57,7 +59,18 @@ node packages/cli/dist/index.js auth login
57
59
  | `candle keys create [--scopes <a,b,c>] [--label <name>] [--expires-in <days>] [--tx-limit <usd> [--reset daily\|weekly\|monthly\|never]]` | Creates a new API key and prints the plaintext exactly once, with the same optional name, expiration, and USD transaction limit the portal's create form takes. Stored locally only if the CLI does not already hold a working key. |
58
60
  | `candle keys revoke <prefix>` | Revokes an API key by prefix. Revoking the CLI's own stored key also clears it locally. |
59
61
  | `candle wallets` | Shows the account's embedded (launch) wallets and any linked wallets, using the API key, with a `Signer` column saying whether this machine holds each linked wallet's signing key. |
62
+ | `candle vault init\|status\|new-key\|phrase show\|restore` | The encrypted local vault, `vault.enc`. `init` creates it with a passphrase factor and a 24-word recovery phrase, `new-key` derives a key inside it, `phrase show` displays the phrase on a terminal (never in `--json`, a log or a pipe), and `restore --phrase` rebuilds the derived keys on another machine (`--count`, `--tee-count`, `--external-count`, or a gap scan with `--rpc-url`). No key ever leaves the vault to reach Candle. See [The vault](https://docs.candle.tv/developers/cli#the-vault). |
63
+ | `candle vault factor list\|add passphrase\|add security-key\|add touch-id\|add passkey\|remove`, `vault backup --to <path>`, `vault verify-backup <path>` | What can open the vault, and proving a copy of it works: `backup` verifies the copy in full before reporting (a copy is sealed to the passphrase envelope unless it goes to a recognised local disk or removable drive, so a cloud folder and any path the CLI cannot place are both sealed unless `--accept-shared-domain` is passed), and `verify-backup` re-checks an existing one against this vault's key and address set. `add security-key` enrolls a FIDO2 security key (CTAP2 `hmac-secret`, user-verified with the key's PIN or biometric) through the bundled `candle-fido2` helper; one key is not a recoverable factor, two keys are a pair, and the passphrase stays the recovery floor. Every vault command then takes `--factor <id\|passphrase\|security-key>` to say which envelope opens it and `--device <id>` to name the key when several are attached. |
64
+ | `candle vault transfer\|promote\|fund\|demote\|export-key` | Moving value and authority. `transfer` and `fund` sign locally from a vault key after showing the decoded transaction (`fund` reaches a TEE wallet from its pinned vault key, or an external wallet, and confirms the destination's last six characters with no `--yes`); `promote` turns a vault key into a delegated TEE wallet (fresh, or the key's own address after a typed warning) and `demote` sweeps it back; `export-key` is the one ceremony that writes a single private key to a file you name. |
65
+ | `candle external new\|list\|sweep` | External wallets for outside tools: keys on the vault's third branch (`m/44'/501'/n'/2'`), never delegated to Privy and never registered with Candle. `new` allocates one (and moves the vault to format version 3; refused in a restored vault), `list` shows them, and `sweep <external> --to <vault>` sends everything back to a named vault key, signed locally, confirming the vault key's last six characters. See [Bring your own services](https://docs.candle.tv/developers/cli#bring-your-own-services). |
66
+ | `candle sign [--file <path>] --wallet <external>... [--broadcast] [--yes]`, `candle sign message --wallet <external> [--file <path>] [--yes]` | The generic signer: a base64 transaction any tool built, legacy or v0, decoded (lookup tables resolved) and simulated over your own RPC before it is displayed and signed with an external wallet only. A vault key, a TEE wallet or an unnamed signer is refused; a failing simulation is refused with no override; `--yes` skips the confirmation prompt and nothing else. `sign message` signs the exact bytes of a file or of stdin. |
67
+ | `candle secrets set\|list\|remove <name>` | Your own third-party API keys, in a keychain namespace separate from Candle's credentials (`tv.candle.cli.secrets`, or `secrets.enc` on the encrypted-file fallback). Typed on a hidden prompt, never sent to Candle, never printed after `set`; `list` shows names only. A plug-in receives one as `CANDLE_SECRET_<NAME>` only when named with `--secret`. |
68
+ | `candle plugins`, `candle <name> [--secret <name>]... [--wallet <label>]... [args]` | Git-style plug-ins: an executable `candle-<name>` on your `PATH` runs as `candle <name>` with an allowlist environment built from empty (`PATH`, `HOME`, `TMPDIR`, `TERM`, `TZ`, `LANG`, `LC_*`, the proxy variables, `CANDLE_PLUGIN_NETWORK`, `CANDLE_PLUGIN_RPC_URL`, the named wallets as `CANDLE_PLUGIN_WALLET_<LABEL>` and the named secrets as `CANDLE_SECRET_<NAME>`). No parent `CANDLE_*` variable, no Candle credential, no passphrase and no private key ever reach it; `--secret` and `--wallet` are stripped from its argv and everything else passes through verbatim. `plugins` lists what is on `PATH`. |
69
+ | `candle vault import-legacy --tee`, `candle vault retire-legacy` | Moves an existing `tee-wallets.enc` into the vault without deleting it, then retires the old file once the vault holds everything and a backup has been verified. |
60
70
  | `candle tee new\|enable\|fund\|status\|disable\|sweep` | A dedicated, capped TEE wallet for one agent: the CLI generates the key and seals it locally in `tee-wallets.enc`, `enable` delegates it to this profile's API key with a pinned sweep vault, `fund` prints what your vault signs, and `disable` then `sweep` stop the agent and move everything back to the vault, signed locally. Solana only. See [TEE wallets](https://docs.candle.tv/developers/cli#tee-wallets). |
71
+ | `candle swap <from> <to> --amount <n>\|--percent <n> --wallet <tee>` | Quote, confirm and swap on Solana through the TEE wallet's bound key; first buy after a launch is this command. |
72
+ | `candle swap status <id> [--kind trade\|swap\|launch]` | Read an operation without resending it. |
73
+ | `candle launch --name <name> --symbol <symbol> --image-url <url> --wallet <tee>` | Create a Solana token with no first buy; needs `launch:write` and operator-enabled `allowLaunch`. |
61
74
  | `candle profile list` | Lists profiles on this machine, with cached accounts. |
62
75
  | `candle profile add <name> --api-url <url>` | Creates a profile before authenticating it. |
63
76
  | `candle profile use <name>` | Makes a profile the active one. |
@@ -67,6 +80,8 @@ node packages/cli/dist/index.js auth login
67
80
  | `candle doctor` | Runs a full health check (runtime, backend, credentials, API reachability, credential validity, wallet delegation) as a PASS/FAIL/SKIP table. Exits nonzero on any FAIL. |
68
81
  | `candle verify <file> --bundle <path> [--identity <uri>] [--issuer <url>]` | Verifies a release asset's Sigstore bundle against the trusted root compiled into this binary. No network, no credentials, and nothing else installed: the bundle carries the certificate and the transparency-log entry. `--identity` defaults to the release identity for the version in a `latest.json` sitting beside the bundle; `--issuer` defaults to GitHub Actions'. Prints `verified: <identity>` and exits 0, or the reason on stderr and exits 1. |
69
82
  | `candle update [--check] [--to <tag>]` | Replaces this binary with the latest signed release. The download is renamed over the running binary only after its SHA-256 matches both SHA256SUMS and `latest.json` AND its Sigstore bundle verifies in process against that exact version's release workflow. `--check` reports what is available and installs nothing; `--to <tag>` pins a release (an older one installs, with a warning). A Homebrew or npm install is left alone, with the command that owns it printed instead. |
83
+ | `candle completion <zsh\|bash\|fish>` | Prints a shell completion script to stdout, generated from the same data `candle help` prints, so it offers exactly the commands, subcommands and flags the help documents. Install it by redirecting: `candle completion zsh > "${fpath[1]}/_candle"`, `candle completion bash > ~/.local/share/bash-completion/completions/candle`, `candle completion fish > ~/.config/fish/completions/candle.fish`. Static: profile names, key labels and factor ids are not completed, because that would mean reading config or opening the vault at tab time. Regenerate after every `candle update`. |
84
+ | `candle help [<command>]` | The top-level screen (six groups of command words, the global flags, and every environment variable the CLI reads), or one command's own: its subcommands, the flags they share, examples, and the environment it reads. `candle <command> --help` is the same screen. Reads no config and makes no request, so it answers before any profile is selected. |
70
85
 
71
86
  Every command accepts these global options:
72
87
 
@@ -123,11 +138,48 @@ guarantee is unconfirmed: a wallet stop whose remote enforcement is still pendin
123
138
  enabled without verified signing authority, or a sweep that left a residual). Treat `3` as not
124
139
  done: follow the printed next step, usually re-running the same command.
125
140
 
141
+ A failure envelope is `{ ok: false, code, message }`, with an optional one-line `suggestion` and,
142
+ where a refusal carries facts worth acting on, an optional nested `details`. A vault that is not
143
+ where the command looked answers with `details: { path, pathSource: "flag" | "env" | "default" }`,
144
+ so an agent can retry against the right file without parsing the message. Keys are added, never
145
+ removed, and `code` values never change; `message` and `suggestion` are prose and may.
146
+
126
147
  A generated vault passphrase is shown once on the terminal and never inside a JSON value, so
127
148
  `vault init` and `vault factor add passphrase` under `--json` take `--own-passphrase` (typed at a
128
149
  hidden prompt, nothing shown) and refuse the generated form with exit `2`. Prompts themselves are
129
150
  rendered on stderr, so a command that unlocks a vault on a terminal still leaves stdout as one
130
- JSON value.
151
+ JSON value. A security key's PIN is read the same way, by the CLI on its own hidden prompt, and
152
+ reaches the `candle-fido2` helper only inside a request piped to it; the helper never reads the
153
+ terminal and the PIN never appears in any output.
154
+
155
+ The security key factor needs the `candle-fido2` helper beside the `candle` binary, which the
156
+ installer script and Homebrew put there (Homebrew also installs `libfido2`, which the helper loads
157
+ at run time; the installer names the package to add), and it needs libfido2's udev rule on Linux.
158
+ The npm package ships no native helper: there the factor is refused with `VAULT_HELPER_MISSING`
159
+ until a release build's `candle-fido2` is installed and `CANDLE_FIDO2_HELPER` points at it. No
160
+ other factor is ever substituted for one that is refused.
161
+
162
+ The Touch ID factor (`candle vault factor add touch-id`; not yet released) needs
163
+ the signed `candle-enclave.app` helper, which the darwin tarball, the installer script and Homebrew
164
+ place beside `candle` once a release ships it; a release cut before Apple's approval ships no
165
+ helper and the CLI refuses the factor with a typed code that says so. The CLI verifies the
166
+ helper's code signature with `/usr/bin/codesign` before trusting it, and `CANDLE_ENCLAVE_HELPER`
167
+ points at a bundle elsewhere. The factor opens the vault on one Mac only and is never a recovery
168
+ factor; the passphrase remains the recovery floor.
169
+
170
+ The synced passkey factor (`candle vault factor add passkey`; not yet released)
171
+ runs through the same signed helper on macOS 15 or later, and needs the helper's
172
+ associated-domains entitlement for `webcredentials:cli.candle.tv` with an embedded provisioning
173
+ profile, plus an `apple-app-site-association` served at
174
+ `https://cli.candle.tv/.well-known/apple-app-site-association` (status 200, no redirect,
175
+ `Content-Type: application/json`, `{"webcredentials":{"apps":["<TEAM ID>.tv.candle.cli.enclave"]}}`).
176
+ Each missing piece is a typed refusal before any passkey sheet, and the refusal names what must be
177
+ served. The passkey follows the Apple account, so it is a recoverable factor; two are one factor,
178
+ and a copy of the vault is sealed to the passphrase envelope by default unless it goes to a
179
+ recognised local disk or removable drive, so that one account never holds
180
+ both the blob and a factor that opens it. iCloud Drive and other cloud folders are sealed for that
181
+ reason, and so is any path the CLI cannot place, since it cannot tell whether that path syncs to an
182
+ account; `--accept-shared-domain` writes the full copy and the acceptance is recorded.
131
183
 
132
184
  The failure envelope is stable:
133
185
 
@@ -235,10 +287,17 @@ both and every command works without ever touching a keychain or the encrypted f
235
287
 
236
288
  ## What this CLI deliberately does not do
237
289
 
238
- **Launch, trade, and order commands.** Executing trades and launches belongs to the SDK and MCP
239
- server, not this CLI. This CLI's whole job is credential management plus a handful of read-only or
240
- administrative operations; anything that moves an agent's actual workload stays with the packages
241
- built to run one.
290
+ **A built-in integration with any third-party exchange or privacy service.** Candle is never a
291
+ party to a transaction you make elsewhere: no Candle API key for the service, no Candle server in
292
+ the request path, no fee, and no `--private` flag. Bring your own keys (`candle secrets`), your own
293
+ tools (`candle <plugin>`), and sign with an external wallet (`candle sign`); the request goes from
294
+ your machine to the service with your key, and nothing goes to Candle.
295
+
296
+ **Launch, trade, and order commands.** `candle swap`, `candle launch`, and `candle swap status`
297
+ execute same-chain Solana swaps and token launches for a TEE wallet. They never open a vault or
298
+ locally sign with a TEE private key; Candle builds the transaction and Privy signs it through the
299
+ relay. See TEE swaps and launches below. Order-book commands stay with the SDK and MCP, not this
300
+ CLI.
242
301
 
243
302
  **`keys limits`.** There is no command for setting per-key spend limits, because the API route
244
303
  that sets them (`PUT /keys/:prefix/limits`) structurally rejects a device token. It only accepts
@@ -253,3 +312,55 @@ a sibling device. A device token cannot list or revoke devices, including itself
253
312
  device token. Sibling device prefixes are not themselves secret: they appear in `keys list`'s
254
313
  "minted by" column, which is attribution and grants no capability. Device management is the
255
314
  portal's job, not this CLI's.
315
+
316
+ ### TEE swaps and launches (Phase 3)
317
+
318
+ Use the profile holding the TEE wallet's bound API key and the machine that holds its
319
+ relay authorization key. `--wallet` accepts its id, address or unique label. These commands
320
+ never open or locally sign with a vault or TEE private key. Candle builds the transaction,
321
+ and Privy signs it through Candle's relay. The server's scopes, raw/USD caps and atomic
322
+ budget reservation apply to the human and the agent alike. Wide routes require the server's
323
+ `TEE_WIDE_ROUTES_ENABLED=1`; it remains off by default.
324
+
325
+ ```sh
326
+ candle swap SOL USDC --amount 0.1 --wallet trading --client-trade-id lunch-1
327
+ candle swap <mint> SOL --percent 25 --wallet trading --rpc-url https://your-solana-rpc --yes --json
328
+ candle launch --name Example --symbol EX --image-url https://example.com/token.png \
329
+ --wallet trading --rpc-url https://your-solana-rpc --client-trade-id example-launch
330
+ candle swap status lunch-1
331
+ candle swap status example-launch --kind launch --json
332
+ ```
333
+
334
+ A same-chain SOL/USDC/CNDL pair uses the base-swap rail. A Solana mint paired with one
335
+ of those assets uses the token rail, whether on Candle's curve or graduated to Jupiter/DFlow.
336
+ Cross-chain and EVM pairs are refused. Token-to-token pairs without a supported quote asset
337
+ are not available. Mint precision and percentage balances use `--rpc-url` or
338
+ `CANDLE_SOLANA_RPC_URL`; balances include both classic SPL and Token-2022 accounts.
339
+ Amounts use exact decimal arithmetic. `--slippage-bps` defaults to 50.
340
+
341
+ Before signing, the command displays venue, price impact, tier fee, minimum received and
342
+ all returned token warnings. Missing price impact is explicitly **unavailable**, never zero.
343
+ `--yes` skips the ordinary prompt but still prints warnings. With `--json`, stdout is one
344
+ JSON result containing the quote; the preview and operation id go to stderr. Launch creation
345
+ has no price impact or token receipt, shows its maximum SOL debit, and makes no first buy.
346
+ Use a separate swap for that buy.
347
+
348
+ Swaps require `swap:write`. Launch requires `launch:write` **and** operator-enabled
349
+ `allowLaunch` on the wallet. The operator sets or clears it through the shipped
350
+ `PUT /api/v1/agent/wallets/<id>/capabilities` endpoint with device/session authentication and
351
+ `{"capability":"allowLaunch","enabled":true}`. An agent key cannot grant itself this permission.
352
+
353
+ For scripts, always supply a stable `--client-trade-id` for each intention. Otherwise the CLI
354
+ generates and prints one. Reusing an existing id reads its status instead of rebuilding.
355
+ The local operation marker survives restarts and prevents concurrent CLI invocations from
356
+ starting the same id twice. Base swaps additionally claim build and submission atomically in
357
+ Convex and keep the payer signature before broadcast, surviving an API restart. A timeout
358
+ or an unconfirmed signature is **not** permission to create a replacement transaction.
359
+ `swap status` never resends writes. Use `--kind trade|swap|launch` when an id exists on
360
+ multiple rails or when querying from another machine with limited scopes.
361
+
362
+ For a launch whose broadcast/confirm response was lost, rerunning the same launch id on the
363
+ original machine retries **confirmation only** using its saved signature. It never rebuilds
364
+ or rebroadcasts. A failed or abandoned build needs a new id after checking the old operation.
365
+ Keep the CLI's `operations` directory with its configuration; deleting it removes the local
366
+ record of an attempt whose request may not have reached the server.