@palmyr/cli 1.8.2 → 1.8.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -112,21 +112,26 @@ from a single 12-word mnemonic. Wallets are created locally; the seed never leav
112
112
 
113
113
  ### Creating a wallet
114
114
 
115
+ Wallet creation **requires** a recoverable passphrase fallback (the env var keeps the phrase out of shell history) — or an explicit `--session-only` opt-out for ephemeral wallets you're OK losing on reboot / keyring change / host migration.
116
+
115
117
  ```bash
116
- # Unmanaged: one command, ready to sign
117
- palmyr wallet create --name agent-prod
118
+ # Recommended env-var passphrase fallback (durable across reboots and machines)
119
+ PALMYR_WALLET_PASSPHRASE="your-passphrase" palmyr wallet create --name agent-prod
118
120
 
119
- # Managed: prints a setup link to send to a human
120
- palmyr wallet create --name treasury --managed
121
+ # Equivalent flag form (less safe, ends up in shell history)
122
+ palmyr wallet create --name agent-prod --passphrase "your-passphrase"
121
123
 
122
124
  # Single-chain: skip the other chain's account
123
- palmyr wallet create --name sol-only --solana
124
- palmyr wallet create --name base-only --base
125
+ PALMYR_WALLET_PASSPHRASE="..." palmyr wallet create --name sol-only --solana
126
+ PALMYR_WALLET_PASSPHRASE="..." palmyr wallet create --name base-only --base
127
+
128
+ # OPT OUT — bound to this machine's OS keychain, NOT recoverable from the JSON file alone
129
+ palmyr wallet create --name throwaway --session-only
125
130
  ```
126
131
 
127
- By default a wallet derives both Solana and Base/EVM accounts. Pass `--solana` or `--base` (not both) to materialize only one side. The mnemonic always derives both `--solana` / `--base` controls *which addresses are surfaced and stored*, not which keys exist cryptographically.
132
+ On a TTY without env or flag, the CLI prompts twice with confirmation; non-TTY callers (CI, agents) must provide one of the three knobs above or get a clear error.
128
133
 
129
- The managed flow returns a one-time URL. The recipient opens it in a browser, registers a WebAuthn passkey, and sets spending limits. From that point on, transactions inside the limit sign instantly; transactions over the limit emit an `approvalUrl` that the human visits to authenticate and approve.
134
+ By default a wallet derives both Solana and Base/EVM accounts. Pass `--solana` or `--base` (not both) to materialize only one side. The mnemonic always derives both `--solana` / `--base` controls *which addresses are surfaced and stored*, not which keys exist cryptographically.
130
135
 
131
136
  ### Bulk creation with tags
132
137
 
@@ -149,7 +154,7 @@ palmyr wallet tags # all tags + counts + chains
149
154
  palmyr wallet tag-delete palmyr-demo --confirm
150
155
  ```
151
156
 
152
- Bulk-create is unmanaged-only (managed wallets need per-wallet passkey setup) and capped at 500 per call. Names auto-suffix `-001..-N` with zero-padding based on count width.
157
+ Bulk-create caps at 500 per call. Names auto-suffix `-001..-N` with zero-padding based on count width.
153
158
 
154
159
  ### Importing an existing seed
155
160
 
@@ -162,7 +167,9 @@ palmyr wallet import --mnemonic "..." --name from-backup --tag restored --solana
162
167
 
163
168
  ### Durable recovery — passphrase fallback
164
169
 
165
- By default a wallet is **session-only**: the mnemonic is encrypted with a random session secret stored in your OS credential store (DPAPI / Keychain / `secret-tool`). That secret does not survive a different OS user, a fresh install, or a headless box without an unlocked keyring. Two ways to add a durable scrypt-derived passphrase that decrypts on any machine:
170
+ Wallets created with a passphrase store **two** decryption blobs: one keyed by the OS-keychain session secret (fast, local), one keyed by scrypt(passphrase, salt) (durable). Decryption tries the keychain first, then falls back to `PALMYR_WALLET_PASSPHRASE`. That second blob is what lets the wallet survive a reboot, OS-keychain password change, fresh OS install, or copy to another machine.
171
+
172
+ Session-only wallets (`--session-only`) **only** have the keychain blob and are NOT recoverable from the JSON file alone. If you have legacy session-only wallets and still have access to the original machine, add a fallback retroactively:
166
173
 
167
174
  ```bash
168
175
  # At create time — env var preferred (keeps the phrase out of shell history)
@@ -414,11 +421,11 @@ palmyr compute exec my-vps -- bash -c 'cloud-init clean && cloud-init init --all
414
421
  | `palmyr domain check --name example.dev` | free | Availability check. |
415
422
  | `palmyr domain pricing --name example.dev` | free | TLD pricing. |
416
423
  | `palmyr domain buy --name example.dev` | $20.00 | One-year registration. Renewals are charged annually. |
417
- | `palmyr domain list` | $0.0001 *(ownership proof)* | List domains your wallet owns plus any shared with you. Each row tagged `access: owner | shared`. |
418
- | `palmyr domain dns --name example.dev` | $0.0001 *(ownership proof)* | View DNS records. Owners and shared wallets allowed. |
419
- | `palmyr domain transfer-ownership --name example.dev --to <wallet>` | $0.0001 *(ownership proof)* | Hand the domain to another wallet. Clears `shared_with` — the prior owner's collaborators don't travel with the domain. |
420
- | `palmyr domain share --name example.dev --with <wallet>` | $0.0001 *(ownership proof)* | Grant another wallet shared access (visible in `domain list`, can edit DNS). Owner-only. |
421
- | `palmyr domain unshare --name example.dev --from <wallet>` | $0.0001 *(ownership proof)* | Revoke a shared wallet's access. Owner-only. |
424
+ | `palmyr domain list` | $0.01 *(ownership proof)* | List domains your wallet owns plus any shared with you. Each row tagged `access: owner | shared`. |
425
+ | `palmyr domain dns --name example.dev` | $0.01 *(ownership proof)* | View DNS records. Owners and shared wallets allowed. |
426
+ | `palmyr domain transfer-ownership --name example.dev --to <wallet>` | $0.01 *(ownership proof)* | Hand the domain to another wallet. Clears `shared_with` — the prior owner's collaborators don't travel with the domain. |
427
+ | `palmyr domain share --name example.dev --with <wallet>` | $0.01 *(ownership proof)* | Grant another wallet shared access (visible in `domain list`, can edit DNS). Owner-only. |
428
+ | `palmyr domain unshare --name example.dev --from <wallet>` | $0.01 *(ownership proof)* | Revoke a shared wallet's access. Owner-only. |
422
429
 
423
430
  ### Wallet
424
431
 
@@ -426,8 +433,8 @@ All wallet operations except `addresses`, `api-key`, `config`, and `request-appr
426
433
 
427
434
  | Command | Network | Notes |
428
435
  |---|---|---|
429
- | `palmyr wallet create [--name N] [--managed] [--solana\|--base] [--tag T] [--count N] [--name-prefix P] [--passphrase P]` | local *(server only if `--managed`)* | New wallet. Stores session secret in OS credential store. `--count > 1` bulk-creates N unmanaged wallets under a required `--tag` (max 500/call, batched DPAPI seal on Windows). `--solana` / `--base` materializes only one chain. `--passphrase` (or `PALMYR_WALLET_PASSPHRASE` env) also seals the mnemonic with scrypt so the wallet survives OS-keychain loss. |
430
- | `palmyr wallet import --mnemonic "..." [--name N] [--managed] [--solana\|--base] [--tag T] [--passphrase P]` | local | Restore from BIP-39. Same chain / tag / passphrase flags as `create`. |
436
+ | `palmyr wallet create [--name N] [--solana\|--base] [--tag T] [--count N] [--name-prefix P] (--passphrase P \| PALMYR_WALLET_PASSPHRASE env \| --session-only)` | local | New wallet. **Requires** either a passphrase (recoverable across reboot / OS-keychain loss / host migration) or explicit `--session-only` opt-out. On TTY without env/flag, prompts twice. Stores keychain secret + (with passphrase) a scrypt-sealed `owner_crypto` blob. `--count > 1` bulk-creates N wallets under a required `--tag` (max 500/call, batched DPAPI seal on Windows). |
437
+ | `palmyr wallet import --mnemonic "..." [--name N] [--solana\|--base] [--tag T] (--passphrase P \| PALMYR_WALLET_PASSPHRASE env \| --session-only)` | local | Restore from BIP-39. Same passphrase rules as `create` — re-importing on a new machine to recover from keychain loss should always set a passphrase so you don't get trapped again. |
431
438
  | `palmyr wallet rekey <ID> --passphrase P` | local | Add (or rotate) the scrypt passphrase fallback on an existing wallet. Wallet must be decryptable right now (session secret still works, or `--current-passphrase` provided). Run on the original machine, then `PALMYR_WALLET_PASSPHRASE` decrypts the wallet anywhere. |
432
439
  | `palmyr wallet list [--tag T]` | local | Lists wallets in the local vault. `--tag` filters to one folder. |
433
440
  | `palmyr wallet info <ID>` | local | Show one wallet (id, name, addresses, mode, tag). |
@@ -439,7 +446,6 @@ All wallet operations except `addresses`, `api-key`, `config`, and `request-appr
439
446
  | `palmyr wallet api-key <ID> [--name N]` | API | Mint an agent API key bound to the wallet. |
440
447
  | `palmyr wallet config <ID>` | API | Pull the agent's runtime config. |
441
448
  | `palmyr wallet use <ID> [--chain solana\|base]` | local | Set default payer and payment chain. |
442
- | `palmyr wallet request-approval <ID> [--action limits] [--daily N] [--per-tx N]` | API | Managed wallets only — generate an approval URL for a human. |
443
449
  | `palmyr wallet export <ID> --confirm` | local | Print mnemonic. Requires explicit `--confirm`. |
444
450
 
445
451
  **Trading.** Thesis-tracked positions on Solana + Base persist at `~/.palmyr/trading/` (overridable via `PALMYR_TRADING_PATH`). Swaps route through Jupiter v6 on Solana and ParaSwap on Base. Positions, sells, journal, and watchlist live as JSON / JSONL.
@@ -576,7 +582,7 @@ Local credentials are encrypted with AES-256-GCM (per-account session secret in
576
582
  | `palmyr twitter banner <username> --file path.png` *(or `--url ...`)* | $0.005 | |
577
583
  | `palmyr twitter username <username> --to <new-handle>` | $0.005 | Pre-flight validates handle (4–15 chars, `[A-Za-z0-9_]`) before payment. May trigger X's password re-auth modal — handled automatically. |
578
584
  | `palmyr twitter transfer <username> --to <wallet> --confirm` | $0.0001 ownership proof + $0.0011 lookup; **adds $0.01 if auto-register runs**; **plus ~$0.001 in poll fees** ($0.0001 × ~10 polls) | Atomically hand the X account to another wallet. End-to-end one-command: looks up the account in both server tables (`x_accounts` + `social_registered_accounts`), auto-registers if only in local vault, then kicks off an async rotation job. Server returns `{ transfer_id }` immediately and the CLI polls `/transfers/:id` every 5s until `completed` or `failed` (rotation takes 30-90s in the background — async so it survives Cloudflare's HTTP timeout). Password is rotated and other sessions revoked before ownership flips, so the local copy of credentials becomes useless. Requires `--confirm`. Local vault entry is removed on success — receiver picks up fresh creds with `palmyr twitter list` (which shows server-only accounts) and/or `palmyr twitter claim`. |
579
- | `palmyr twitter share <username> --with <wallet>` | $0.0001 *(ownership proof)* | Grant another wallet shared access — same login, no credential rotation. Both wallets see the account via `palmyr twitter claim`. Owner-only. Same pool / registered dispatch as transfer. |
585
+ | `palmyr twitter share <username> --with <wallet>` | $0.01 *(ownership proof)* | Grant another wallet shared access — same login, no credential rotation. Both wallets see the account via `palmyr twitter claim`. Owner-only. Same pool / registered dispatch as transfer. |
580
586
  | `palmyr twitter unshare <username> --from <wallet> [--rotate]` | $0.0001 ownership proof + $0.0011 lookup; **with `--rotate`: kicks off async rotation, ~$0.001 in poll fees plus another lookup to sync local vault** | Revoke a wallet's shared access. Without `--rotate`, the wallet is removed from `shared_with` immediately but their previously exported cookies / password remain valid until X-side expiry. With `--rotate`, the unshare is immediate AND the server kicks off an async password rotation (same machinery as transfer — Playwright + polling so it survives Cloudflare's HTTP timeout). On completion, the CLI fetches the new credentials from the appropriate `/mine` endpoint and updates the local vault in place. Owner-only. Same pool / registered dispatch. |
581
587
  | `palmyr twitter claim` | $0.0011 *(ownership proof on both `/x/accounts/mine` and `/social/twitter/registered/mine`)* | Pull every X account on the server bound to your wallet (owner or shared) into the local vault, with session cookies pre-warmed. Queries both server tables in parallel. The fast path for a wallet that just received a transferred account. |
582
588
 
@@ -803,10 +809,11 @@ Config is stored in `~/.palmyr/config.json`. Environment variables override file
803
809
 
804
810
  The payment chain is resolved in this order:
805
811
 
806
- 1. The `--chain` flag passed to `wallet use`, persisted as `defaultPayChain`.
807
- 2. `PALMYR_PAY_CHAIN` environment variable.
808
- 3. `defaultChain` in config.
809
- 4. `solana` as the final default.
812
+ 1. `--chain` flag on the command itself (e.g. `wallet pay-preflight --chain base`).
813
+ 2. `defaultPayChain` in `~/.palmyr/config.json` — set by `palmyr wallet use <ID> --chain <chain>`.
814
+ 3. `solana` as the final default.
815
+
816
+ (Earlier versions of this doc listed `PALMYR_PAY_CHAIN` and `defaultChain` as fallbacks — neither is read by the current pay path. `defaultChain` is legacy keyfile-flow state and `saveConfig` strips it from vault-only configs.)
810
817
 
811
818
  If the server doesn't offer the chosen chain for an endpoint, the CLI errors loudly. There is no silent fallback — the assumption is that an agent should know which chain it pays from.
812
819
 
@@ -820,7 +827,6 @@ If the server doesn't offer the chosen chain for an endpoint, the CLI errors lou
820
827
  ├── wallet/
821
828
  │ ├── wallets/ # Encrypted wallet files (AES-256-GCM)
822
829
  │ ├── keys/ # API key metadata
823
- │ ├── policies/ # Spending policies for managed wallets
824
830
  │ └── spends/ # Per-day spend ledgers
825
831
  ├── secrets/ # Windows DPAPI fallback (macOS/Linux use OS keychain)
826
832
  ├── data/ # Local cache of phones, inboxes, servers, domains
@@ -853,10 +859,10 @@ The CLI uses distinct exit codes so scripts and agents can branch on failure mod
853
859
 
854
860
  ## Security Model
855
861
 
856
- - **Keys never leave the machine.** `wallet create` and `wallet import` never transmit seed material to the server. Even managed wallets register only the wallet ID and the derived public addresses with the server.
862
+ - **Keys never leave the machine.** `wallet create` and `wallet import` never transmit seed material to the server.
857
863
  - **Encryption at rest.** The wallet file is AES-256-GCM with the session secret as the key. The session secret is generated on wallet creation and never stored on disk in plaintext.
858
864
  - **OS credential store.** Session secrets live in DPAPI (Windows), Keychain (macOS), or `secret-tool` (libsecret on Linux). If none is available the CLI errors rather than falling back to plaintext.
859
- - **Optional passphrase fallback.** Create with `--passphrase` (or `PALMYR_WALLET_PASSPHRASE` env), or migrate with `wallet rekey`, to add a second AES-256-GCM blob keyed by scrypt(passphrase, random salt). Lets the wallet decrypt on a different machine / user / headless box where the OS keychain isn't reachable. Decryption tries the OS session secret first, then falls back to the env/flag passphrase.
865
+ - **Recoverable by default.** `wallet create` and `wallet import` require a scrypt passphrase fallback (via `--passphrase` or `PALMYR_WALLET_PASSPHRASE`) or an explicit `--session-only` opt-out. The passphrase blob is a second AES-256-GCM ciphertext keyed by scrypt(passphrase, random salt) and lets the wallet decrypt on a different machine / user / headless box where the OS keychain isn't reachable. Decryption tries the OS session secret first, then falls back to the env/flag passphrase. Session-only wallets are bound to this machine's OS keychain — explicit opt-in for ephemeral use only.
860
866
  - **Bidirectional vault integrity.** On every load, the CLI checks that every account stored in the wallet file is still derivable from the seed, **and** that every account derivable from the seed is still in the file. Either direction failing returns exit code `7`.
861
867
  - **Pre-flight validation.** Endpoints that are easy to fail (bad pubkey for an inbox, malformed E.164 for SMS, unsupported destination country) are validated **before** the x402 paywall, so you don't pay for requests that were never going to succeed.
862
868
  - **No `--no-verify`.** Hooks, signatures, and webhooks are always verified.
@@ -876,7 +882,6 @@ Common issues:
876
882
  - **`SECURITY` exit code on any wallet command.** The vault file or its derived accounts have drifted. Restore the wallet file from backup, or re-import from the mnemonic.
877
883
  - **`Server did not offer <chain> as option`.** Either the endpoint only supports the other chain, or your `defaultPayChain` is misconfigured. Use `palmyr wallet use <ID> --chain <other>` and retry.
878
884
  - **`No session secret found`.** The wallet was created on a different machine, or the OS credential store is unavailable. Re-import the wallet and let the CLI store the secret again.
879
- - **Managed wallet, `REQUIRES_APPROVAL`.** The transaction exceeded a per-tx or daily limit. Run `palmyr wallet request-approval <ID>` and forward the URL to the human reviewer.
880
885
 
881
886
  ---
882
887