@palmyr/cli 1.6.0 → 1.7.0
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 +39 -4
- package/dist/cli.js +479 -6
- package/dist/cli.js.map +1 -1
- package/dist/pay.js +36 -6
- package/dist/pay.js.map +1 -1
- package/dist/sdk.d.ts +30 -1
- package/dist/sdk.js +46 -2
- package/dist/sdk.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -118,14 +118,46 @@ palmyr wallet create --name agent-prod
|
|
|
118
118
|
|
|
119
119
|
# Managed: prints a setup link to send to a human
|
|
120
120
|
palmyr wallet create --name treasury --managed
|
|
121
|
+
|
|
122
|
+
# Single-chain: skip the other chain's account
|
|
123
|
+
palmyr wallet create --name sol-only --solana
|
|
124
|
+
palmyr wallet create --name base-only --base
|
|
121
125
|
```
|
|
122
126
|
|
|
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.
|
|
128
|
+
|
|
123
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.
|
|
124
130
|
|
|
131
|
+
### Bulk creation with tags
|
|
132
|
+
|
|
133
|
+
Group many wallets under one folder-like tag, then cascade-delete them together when done — ideal for spinning up demo, agent-cohort, or test wallets:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Create 100 unmanaged wallets sharing one tag (~7s on Windows, batched DPAPI seal)
|
|
137
|
+
palmyr wallet create --tag palmyr-demo --count 100
|
|
138
|
+
|
|
139
|
+
# Custom name prefix; defaults to the tag if omitted → wallets named bot-001..bot-050
|
|
140
|
+
palmyr wallet create --tag agents --count 50 --name-prefix bot
|
|
141
|
+
|
|
142
|
+
# List, retag, untag
|
|
143
|
+
palmyr wallet list --tag palmyr-demo
|
|
144
|
+
palmyr wallet tag <WALLET_ID> palmyr-demo # assign / change
|
|
145
|
+
palmyr wallet tag <WALLET_ID> --clear # remove
|
|
146
|
+
palmyr wallet tags # all tags + counts + chains
|
|
147
|
+
|
|
148
|
+
# Cascade-delete every wallet under a tag (vault file + OS-credential-store secret)
|
|
149
|
+
palmyr wallet tag-delete palmyr-demo --confirm
|
|
150
|
+
```
|
|
151
|
+
|
|
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.
|
|
153
|
+
|
|
125
154
|
### Importing an existing seed
|
|
126
155
|
|
|
127
156
|
```bash
|
|
128
157
|
palmyr wallet import --mnemonic "twelve word seed phrase ..." --name imported
|
|
158
|
+
|
|
159
|
+
# Same chain / tag flags as create
|
|
160
|
+
palmyr wallet import --mnemonic "..." --name from-backup --tag restored --solana
|
|
129
161
|
```
|
|
130
162
|
|
|
131
163
|
### Exporting a seed
|
|
@@ -377,10 +409,13 @@ All wallet operations except `addresses`, `api-key`, `config`, and `request-appr
|
|
|
377
409
|
|
|
378
410
|
| Command | Network | Notes |
|
|
379
411
|
|---|---|---|
|
|
380
|
-
| `palmyr wallet create [--name N] [--managed]` | local *(server only if `--managed`)* | New wallet. Stores session secret in OS credential store. |
|
|
381
|
-
| `palmyr wallet import --mnemonic "..." [--name N] [--managed]` | local | Restore from BIP-39. |
|
|
382
|
-
| `palmyr wallet list` | local | Lists wallets in the local vault. |
|
|
383
|
-
| `palmyr wallet info <ID>` | local | Show one wallet (id, name, addresses, mode). |
|
|
412
|
+
| `palmyr wallet create [--name N] [--managed] [--solana\|--base] [--tag T] [--count N] [--name-prefix 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. |
|
|
413
|
+
| `palmyr wallet import --mnemonic "..." [--name N] [--managed] [--solana\|--base] [--tag T]` | local | Restore from BIP-39. Same chain / tag flags as `create`. |
|
|
414
|
+
| `palmyr wallet list [--tag T]` | local | Lists wallets in the local vault. `--tag` filters to one folder. |
|
|
415
|
+
| `palmyr wallet info <ID>` | local | Show one wallet (id, name, addresses, mode, tag). |
|
|
416
|
+
| `palmyr wallet tags` | local | List all tags with wallet count, chains, and date range. |
|
|
417
|
+
| `palmyr wallet tag <ID> <TAG>` / `palmyr wallet tag <ID> --clear` | local | Assign, change, or clear a wallet's tag. |
|
|
418
|
+
| `palmyr wallet tag-delete <TAG> --confirm` | local | Cascade-delete every wallet under the tag (vault file + OS-credential-store secret). Requires explicit `--confirm`. |
|
|
384
419
|
| `palmyr wallet addresses <ID>` | API | Server-side derived addresses (multi-chain). |
|
|
385
420
|
| `palmyr wallet sign-message <ID> --chain solana\|evm --msg "..."` | local | Sign an arbitrary message offline. |
|
|
386
421
|
| `palmyr wallet api-key <ID> [--name N]` | API | Mint an agent API key bound to the wallet. |
|
package/dist/cli.js
CHANGED
|
@@ -377,6 +377,327 @@ const WALLET_HELP = {
|
|
|
377
377
|
{ flag: '--dst-decimals <n>', desc: 'Dest token decimals', hint: 'default 6 (USDC-like)' },
|
|
378
378
|
],
|
|
379
379
|
};
|
|
380
|
+
const PHONE_HELP = {
|
|
381
|
+
search: [
|
|
382
|
+
{ flag: '--country <ISO>', desc: 'Country code', hint: 'default US (e.g. US, GB, AE)' },
|
|
383
|
+
{ flag: '--limit <N>', desc: 'Max results to return' },
|
|
384
|
+
{ flag: '(price)', desc: 'Free — no payment required' },
|
|
385
|
+
{ flag: '(example)', desc: 'palmyr phone search --country US --json' },
|
|
386
|
+
],
|
|
387
|
+
buy: [
|
|
388
|
+
{ flag: '--country <ISO>', desc: 'Country code (required)', hint: 'e.g. US, GB' },
|
|
389
|
+
{ flag: '--area <code>', desc: 'Preferred area code (optional, US only)' },
|
|
390
|
+
{ flag: '(price)', desc: '$3.00 per number provisioned' },
|
|
391
|
+
{ flag: '(example)', desc: 'palmyr phone buy --country US' },
|
|
392
|
+
],
|
|
393
|
+
sms: [
|
|
394
|
+
{ flag: '--id <PHONE_ID>', desc: 'Source phone number id (required)' },
|
|
395
|
+
{ flag: '--to <+E.164>', desc: 'Destination phone number (required)', hint: 'e.g. +15551234567' },
|
|
396
|
+
{ flag: '--body <text>', desc: 'Message body (required)' },
|
|
397
|
+
{ flag: '(price)', desc: '$0.05 per SMS sent' },
|
|
398
|
+
{ flag: '(example)', desc: 'palmyr phone sms --id PN_abc --to +15551234567 --body "hi"' },
|
|
399
|
+
],
|
|
400
|
+
call: [
|
|
401
|
+
{ flag: '--id <PHONE_ID>', desc: 'Source phone number id (required)' },
|
|
402
|
+
{ flag: '--to <+E.164>', desc: 'Destination phone number (required)' },
|
|
403
|
+
{ flag: '--tts <text>', desc: 'Text-to-speech to play on answer (optional)' },
|
|
404
|
+
{ flag: '(price)', desc: '$0.10 per call placed' },
|
|
405
|
+
{ flag: '(example)', desc: 'palmyr phone call --id PN_abc --to +15551234567 --tts "hello"' },
|
|
406
|
+
],
|
|
407
|
+
list: [
|
|
408
|
+
{ flag: '(no args)', desc: 'List phone numbers owned by your wallet' },
|
|
409
|
+
{ flag: '(price)', desc: '$0.01 per call' },
|
|
410
|
+
{ flag: '(example)', desc: 'palmyr phone list --json' },
|
|
411
|
+
],
|
|
412
|
+
messages: [
|
|
413
|
+
{ flag: '--id <PHONE_ID>', desc: 'Phone number id to read SMS for (required; positional also accepted)' },
|
|
414
|
+
{ flag: '(price)', desc: '$0.02 per call' },
|
|
415
|
+
{ flag: '(example)', desc: 'palmyr phone messages --id PN_abc' },
|
|
416
|
+
],
|
|
417
|
+
calls: [
|
|
418
|
+
{ flag: '--id <PHONE_ID>', desc: 'Phone number id to list calls for (required; positional also accepted)' },
|
|
419
|
+
{ flag: '(price)', desc: '$0.02 per call' },
|
|
420
|
+
{ flag: '(example)', desc: 'palmyr phone calls --id PN_abc' },
|
|
421
|
+
],
|
|
422
|
+
release: [
|
|
423
|
+
{ flag: '--id <PHONE_ID>', desc: 'Phone number id to release (required; positional also accepted)' },
|
|
424
|
+
{ flag: '(price)', desc: '$0.01 per release (stops monthly Telnyx billing)' },
|
|
425
|
+
{ flag: '(example)', desc: 'palmyr phone release --id PN_abc' },
|
|
426
|
+
],
|
|
427
|
+
'call-info': [
|
|
428
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id (required; --id and positional also accepted)' },
|
|
429
|
+
{ flag: '(price)', desc: '$0.02 per lookup' },
|
|
430
|
+
{ flag: '(example)', desc: 'palmyr phone call-info --call CC_abc' },
|
|
431
|
+
],
|
|
432
|
+
speak: [
|
|
433
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
434
|
+
{ flag: '--text <text>', desc: 'TTS text to speak (required; --tts alias accepted)' },
|
|
435
|
+
{ flag: '--voice <name>', desc: 'TTS voice (optional, provider default otherwise)' },
|
|
436
|
+
{ flag: '--language <code>', desc: 'TTS language code (optional, e.g. en-US)' },
|
|
437
|
+
{ flag: '(price)', desc: '$0.08 per speak action' },
|
|
438
|
+
{ flag: '(example)', desc: 'palmyr phone speak --call CC_abc --text "please hold"' },
|
|
439
|
+
],
|
|
440
|
+
play: [
|
|
441
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
442
|
+
{ flag: '--url <audio_url>', desc: 'Public audio URL to play (required; --audio-url alias accepted)' },
|
|
443
|
+
{ flag: '(price)', desc: '$0.08 per playback' },
|
|
444
|
+
{ flag: '(example)', desc: 'palmyr phone play --call CC_abc --url https://example.com/hold.mp3' },
|
|
445
|
+
],
|
|
446
|
+
dtmf: [
|
|
447
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
448
|
+
{ flag: '--digits <seq>', desc: 'DTMF digit sequence (required; positional also accepted)', hint: 'e.g. "1234#"' },
|
|
449
|
+
{ flag: '(price)', desc: '$0.02 per DTMF send' },
|
|
450
|
+
{ flag: '(example)', desc: 'palmyr phone dtmf --call CC_abc --digits "1234#"' },
|
|
451
|
+
],
|
|
452
|
+
gather: [
|
|
453
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
454
|
+
{ flag: '--min-digits <N>', desc: 'Minimum digits to collect (optional)' },
|
|
455
|
+
{ flag: '--max-digits <N>', desc: 'Maximum digits to collect (optional)' },
|
|
456
|
+
{ flag: '--timeout <ms>', desc: 'Per-input timeout in milliseconds (optional)' },
|
|
457
|
+
{ flag: '--terminating-digit <d>', desc: 'Digit that ends collection (optional, e.g. "#")' },
|
|
458
|
+
{ flag: '--prompt <text>', desc: 'Optional TTS prompt to play before gathering' },
|
|
459
|
+
{ flag: '--prompt-voice <name>', desc: 'TTS voice for the prompt (optional)' },
|
|
460
|
+
{ flag: '(price)', desc: '$0.08 per gather action' },
|
|
461
|
+
{ flag: '(example)', desc: 'palmyr phone gather --call CC_abc --max-digits 4 --terminating-digit "#" --prompt "Enter PIN"' },
|
|
462
|
+
],
|
|
463
|
+
record: [
|
|
464
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
465
|
+
{ flag: '--format <fmt>', desc: 'Recording format (optional, provider default otherwise)' },
|
|
466
|
+
{ flag: '(price)', desc: '$0.10 per record start' },
|
|
467
|
+
{ flag: '(example)', desc: 'palmyr phone record --call CC_abc --format mp3' },
|
|
468
|
+
],
|
|
469
|
+
'record-stop': [
|
|
470
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
471
|
+
{ flag: '(price)', desc: '$0.02 per stop' },
|
|
472
|
+
{ flag: '(example)', desc: 'palmyr phone record-stop --call CC_abc' },
|
|
473
|
+
],
|
|
474
|
+
hangup: [
|
|
475
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
476
|
+
{ flag: '(price)', desc: '$0.02 per hangup' },
|
|
477
|
+
{ flag: '(example)', desc: 'palmyr phone hangup --call CC_abc' },
|
|
478
|
+
],
|
|
479
|
+
answer: [
|
|
480
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of an inbound call (required)' },
|
|
481
|
+
{ flag: '(price)', desc: '$0.02 per answer' },
|
|
482
|
+
{ flag: '(example)', desc: 'palmyr phone answer --call CC_abc' },
|
|
483
|
+
],
|
|
484
|
+
transfer: [
|
|
485
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
486
|
+
{ flag: '--to <+E.164>', desc: 'Destination phone number to bridge into (required)' },
|
|
487
|
+
{ flag: '(price)', desc: '$0.10 per transfer' },
|
|
488
|
+
{ flag: '(example)', desc: 'palmyr phone transfer --call CC_abc --to +15557654321' },
|
|
489
|
+
],
|
|
490
|
+
};
|
|
491
|
+
const EMAIL_HELP = {
|
|
492
|
+
create: [
|
|
493
|
+
{ flag: '--name <name>', desc: 'Inbox name (required)' },
|
|
494
|
+
{ flag: '--domain <domain>', desc: 'Wallet-owned domain to host the inbox on (optional)', hint: 'default: Palmyr-hosted domain' },
|
|
495
|
+
{ flag: '--wallet <id|name>', desc: 'Wallet to own the inbox (optional)' },
|
|
496
|
+
{ flag: '(price)', desc: '$2.00 per inbox provisioned' },
|
|
497
|
+
{ flag: '(example)', desc: 'palmyr email create --name agent --domain example.com' },
|
|
498
|
+
],
|
|
499
|
+
list: [
|
|
500
|
+
{ flag: '(no args)', desc: 'List inboxes owned by your wallet' },
|
|
501
|
+
{ flag: '(price)', desc: '$0.01 per call' },
|
|
502
|
+
{ flag: '(example)', desc: 'palmyr email list --json' },
|
|
503
|
+
],
|
|
504
|
+
status: [
|
|
505
|
+
{ flag: '<domain>', desc: 'Domain to check (positional or --domain)', hint: 'e.g. example.com' },
|
|
506
|
+
{ flag: '(price)', desc: '$0.01 per call' },
|
|
507
|
+
{ flag: '(example)', desc: 'palmyr email status example.com' },
|
|
508
|
+
],
|
|
509
|
+
register: [
|
|
510
|
+
{ flag: '<domain>', desc: 'Wallet-owned domain to (re-)register with Mailgun (positional or --domain)' },
|
|
511
|
+
{ flag: '(price)', desc: '$0.05 per registration' },
|
|
512
|
+
{ flag: '(example)', desc: 'palmyr email register example.com' },
|
|
513
|
+
],
|
|
514
|
+
read: [
|
|
515
|
+
{ flag: '--id <INBOX_ID>', desc: 'Inbox id (required; positional also accepted)' },
|
|
516
|
+
{ flag: '(price)', desc: '$0.02 per call' },
|
|
517
|
+
{ flag: '(example)', desc: 'palmyr email read --id INB_abc123' },
|
|
518
|
+
],
|
|
519
|
+
send: [
|
|
520
|
+
{ flag: '--id <INBOX_ID>', desc: 'Source inbox id (required)' },
|
|
521
|
+
{ flag: '--to <addr>', desc: 'Destination email (required)' },
|
|
522
|
+
{ flag: '--subject <text>', desc: 'Subject line (required)' },
|
|
523
|
+
{ flag: '--body <text>', desc: 'Message body (required)' },
|
|
524
|
+
{ flag: '(price)', desc: '$0.08 per email sent' },
|
|
525
|
+
{ flag: '(example)', desc: 'palmyr email send --id INB_abc --to user@x.com --subject Hi --body "..."' },
|
|
526
|
+
],
|
|
527
|
+
threads: [
|
|
528
|
+
{ flag: '--id <INBOX_ID>', desc: 'Inbox id (required; positional also accepted)' },
|
|
529
|
+
{ flag: '(price)', desc: '$0.02 per call' },
|
|
530
|
+
{ flag: '(example)', desc: 'palmyr email threads --id INB_abc123' },
|
|
531
|
+
],
|
|
532
|
+
};
|
|
533
|
+
const COMPUTE_HELP = {
|
|
534
|
+
plans: [
|
|
535
|
+
{ flag: '--location <loc>', desc: 'Filter to types deployable in this datacenter (optional)', hint: 'e.g. fsn1, nbg1, hel1, ash, hil' },
|
|
536
|
+
{ flag: '(price)', desc: 'Free — live discovery from Hetzner' },
|
|
537
|
+
{ flag: '(example)', desc: 'palmyr compute plans --location fsn1 --json' },
|
|
538
|
+
],
|
|
539
|
+
locations: [
|
|
540
|
+
{ flag: '(no args)', desc: 'List Hetzner datacenters + per-location server-type availability' },
|
|
541
|
+
{ flag: '(price)', desc: 'Free' },
|
|
542
|
+
],
|
|
543
|
+
'install-recipes': [
|
|
544
|
+
{ flag: '(no args)', desc: 'List available agent install recipes (hermes, openclaw, …)' },
|
|
545
|
+
{ flag: '(price)', desc: 'Free' },
|
|
546
|
+
],
|
|
547
|
+
'ssh-key': [
|
|
548
|
+
{ flag: 'add <pubkey-file>', desc: 'Upload a key to Hetzner', hint: '[--name "label"]' },
|
|
549
|
+
{ flag: 'list', desc: 'List uploaded Hetzner SSH keys' },
|
|
550
|
+
{ flag: 'delete <id>', desc: 'Remove a Hetzner SSH key' },
|
|
551
|
+
{ flag: '(price)', desc: 'add $0.10 · list $0.01 · delete $0.01' },
|
|
552
|
+
],
|
|
553
|
+
deploy: [
|
|
554
|
+
{ flag: '--type <name>', desc: 'Hetzner server type', hint: 'default cx23' },
|
|
555
|
+
{ flag: '--name <name>', desc: 'Server name', hint: 'default agent-<timestamp>' },
|
|
556
|
+
{ flag: '--location <loc>', desc: 'Hetzner datacenter (optional)', hint: 'e.g. fsn1, nbg1' },
|
|
557
|
+
{ flag: '--install <recipes>', desc: 'Comma-separated install recipes', hint: 'e.g. hermes,openclaw' },
|
|
558
|
+
{ flag: '--no-install', desc: 'Skip cloud-init entirely (vanilla Ubuntu)' },
|
|
559
|
+
{ flag: '--generate-ssh-key', desc: 'GOLDEN PATH (default): generate a fresh keypair locally' },
|
|
560
|
+
{ flag: '--pubkey-file <path>', desc: 'Use an existing public key from disk' },
|
|
561
|
+
{ flag: '--pubkey "ssh-..."', desc: 'Use an inline public key string' },
|
|
562
|
+
{ flag: '--ssh-key <id>', desc: 'Numeric Hetzner key id (already uploaded)' },
|
|
563
|
+
{ flag: '--no-wait', desc: 'Return immediately instead of waiting for SSH-ready' },
|
|
564
|
+
{ flag: '--wait-timeout <s>', desc: 'Override readiness timeout (30–900s)' },
|
|
565
|
+
{ flag: '(price)', desc: '$6.00 per deploy (Hetzner billing flows through)' },
|
|
566
|
+
{ flag: '(example)', desc: 'palmyr compute deploy --type cx23 --install hermes' },
|
|
567
|
+
],
|
|
568
|
+
wait: [
|
|
569
|
+
{ flag: '<name|id>', desc: 'Server (positional, name or numeric id)' },
|
|
570
|
+
{ flag: '--install <recipes>', desc: 'Also gate on the install marker file (e.g. hermes)' },
|
|
571
|
+
{ flag: '--key <path>', desc: 'Path to the private key for SSH verification' },
|
|
572
|
+
{ flag: '--wait-timeout <s>', desc: 'Override readiness timeout (30–900s)' },
|
|
573
|
+
{ flag: '(price)', desc: '$0.01 per readiness poll (server status check)' },
|
|
574
|
+
{ flag: '(example)', desc: 'palmyr compute wait my-vps --install hermes' },
|
|
575
|
+
],
|
|
576
|
+
ssh: [
|
|
577
|
+
{ flag: '<name|id>', desc: 'Server (positional)' },
|
|
578
|
+
{ flag: '(price)', desc: 'Free — local cache lookup only' },
|
|
579
|
+
{ flag: '(example)', desc: 'palmyr compute ssh my-vps' },
|
|
580
|
+
],
|
|
581
|
+
exec: [
|
|
582
|
+
{ flag: '<name|id> -- <cmd> [args...]', desc: 'Run a one-shot command via Palmyr SSH bridge' },
|
|
583
|
+
{ flag: '--timeout <s>', desc: 'Command timeout (1–120s)' },
|
|
584
|
+
{ flag: '(price)', desc: '$0.05 per command' },
|
|
585
|
+
{ flag: '(example)', desc: 'palmyr compute exec my-vps -- systemctl status openclaw' },
|
|
586
|
+
],
|
|
587
|
+
rename: [
|
|
588
|
+
{ flag: '<name|id> <new-name>', desc: 'Rename server (metadata-only, no reboot)' },
|
|
589
|
+
{ flag: '(price)', desc: '$0.01 per rename' },
|
|
590
|
+
],
|
|
591
|
+
'reset-password': [
|
|
592
|
+
{ flag: '<name|id>', desc: 'Rotate the root password (Hetzner-side)' },
|
|
593
|
+
{ flag: '(price)', desc: '$0.10 per action' },
|
|
594
|
+
],
|
|
595
|
+
console: [
|
|
596
|
+
{ flag: '<name|id>', desc: 'Get a noVNC console URL (break-glass)' },
|
|
597
|
+
{ flag: '(price)', desc: '$0.10 per action' },
|
|
598
|
+
],
|
|
599
|
+
reboot: [
|
|
600
|
+
{ flag: '<name|id>', desc: 'Reboot the server' },
|
|
601
|
+
{ flag: '(price)', desc: '$0.10 per action' },
|
|
602
|
+
],
|
|
603
|
+
'setup-ssh': [
|
|
604
|
+
{ flag: '--id <SERVER_ID>', desc: 'Server id (required; positional also accepted)' },
|
|
605
|
+
{ flag: '--pubkey-file <path>', desc: 'Public key file to inject' },
|
|
606
|
+
{ flag: '--pubkey "ssh-..."', desc: 'Inline public key string' },
|
|
607
|
+
{ flag: '(price)', desc: '$0.01 per call' },
|
|
608
|
+
{ flag: '(example)', desc: 'palmyr compute setup-ssh --id 12345 --pubkey-file ~/.ssh/id_ed25519.pub' },
|
|
609
|
+
],
|
|
610
|
+
list: [
|
|
611
|
+
{ flag: '(no args)', desc: 'List your deployed servers' },
|
|
612
|
+
{ flag: '(price)', desc: '$0.01 per call' },
|
|
613
|
+
],
|
|
614
|
+
delete: [
|
|
615
|
+
{ flag: '--id <SERVER_ID>', desc: 'Server id (required; positional also accepted)' },
|
|
616
|
+
{ flag: '(price)', desc: '$0.10 per deletion (Hetzner billing stops on confirm)' },
|
|
617
|
+
],
|
|
618
|
+
};
|
|
619
|
+
const DOMAIN_HELP = {
|
|
620
|
+
check: [
|
|
621
|
+
{ flag: '--name <domain>', desc: 'Domain or root name (positional also accepted)' },
|
|
622
|
+
{ flag: '(price)', desc: 'Free — availability lookup only' },
|
|
623
|
+
{ flag: '(example)', desc: 'palmyr domain check example.dev' },
|
|
624
|
+
],
|
|
625
|
+
pricing: [
|
|
626
|
+
{ flag: '--name <root>', desc: 'Root name to price across TLDs (positional also accepted)' },
|
|
627
|
+
{ flag: '(price)', desc: 'Free' },
|
|
628
|
+
{ flag: '(example)', desc: 'palmyr domain pricing example' },
|
|
629
|
+
],
|
|
630
|
+
buy: [
|
|
631
|
+
{ flag: '--name <domain>', desc: 'Fully-qualified domain (required, e.g. example.dev)' },
|
|
632
|
+
{ flag: '(price)', desc: 'Dynamic — registrar cost × 1.25 markup (charged via x402)' },
|
|
633
|
+
{ flag: '(example)', desc: 'palmyr domain buy --name example.dev' },
|
|
634
|
+
],
|
|
635
|
+
list: [
|
|
636
|
+
{ flag: '(no args)', desc: 'List domains owned or shared with your wallet' },
|
|
637
|
+
{ flag: '(price)', desc: '$0.0001 ownership-proof micro-payment' },
|
|
638
|
+
],
|
|
639
|
+
dns: [
|
|
640
|
+
{ flag: '--name <domain>', desc: 'Domain to read DNS for (positional also accepted)' },
|
|
641
|
+
{ flag: '(price)', desc: '$0.0001 ownership-proof micro-payment' },
|
|
642
|
+
],
|
|
643
|
+
'transfer-ownership': [
|
|
644
|
+
{ flag: '--name <domain>', desc: 'Domain to transfer (required)' },
|
|
645
|
+
{ flag: '--to <wallet>', desc: 'Recipient wallet address (required)' },
|
|
646
|
+
{ flag: '(price)', desc: '$0.0001 ownership-proof micro-payment' },
|
|
647
|
+
],
|
|
648
|
+
share: [
|
|
649
|
+
{ flag: '--name <domain>', desc: 'Domain to share (required)' },
|
|
650
|
+
{ flag: '--with <wallet>', desc: 'Wallet to grant shared access (required)' },
|
|
651
|
+
{ flag: '(price)', desc: '$0.0001 ownership-proof micro-payment' },
|
|
652
|
+
],
|
|
653
|
+
unshare: [
|
|
654
|
+
{ flag: '--name <domain>', desc: 'Domain to revoke from (required)' },
|
|
655
|
+
{ flag: '--from <wallet>', desc: 'Wallet to revoke (required)' },
|
|
656
|
+
{ flag: '(price)', desc: '$0.0001 ownership-proof micro-payment' },
|
|
657
|
+
],
|
|
658
|
+
};
|
|
659
|
+
const CHAT_HELP = {
|
|
660
|
+
run: [
|
|
661
|
+
{ flag: '"<intent>"', desc: 'Plain-string intent (positional or --intent)' },
|
|
662
|
+
{ flag: '--budget <USDC>', desc: 'Max spend cap (required, positive USDC)' },
|
|
663
|
+
{ flag: '--quality <q>', desc: 'Quality tier', hint: 'fast | cheap | best (default best)' },
|
|
664
|
+
{ flag: '--execute', desc: 'Auto-execute the plan once generated' },
|
|
665
|
+
{ flag: '--auto-approve-under <USDC>', desc: 'Skip approval prompts for steps cheaper than this' },
|
|
666
|
+
{ flag: '(price)', desc: '$0.10 orchestration fee + sum of per-step costs (capped by --budget)' },
|
|
667
|
+
{ flag: '(example)', desc: 'palmyr chat run "launch a sneaker brand" --budget 50' },
|
|
668
|
+
],
|
|
669
|
+
resume: [
|
|
670
|
+
{ flag: '<session_id>', desc: 'Existing session id (positional)' },
|
|
671
|
+
{ flag: '"<follow-up>"', desc: 'Follow-up intent (positional remainder or --intent)' },
|
|
672
|
+
{ flag: '--approve', desc: 'Approve a previously-generated plan' },
|
|
673
|
+
{ flag: '--plan-id <id>', desc: 'Plan id to approve (pair with --approve)' },
|
|
674
|
+
{ flag: '--budget <USDC>', desc: 'Override session budget (default $20)' },
|
|
675
|
+
{ flag: '--execute', desc: 'Auto-execute the new plan' },
|
|
676
|
+
{ flag: '(price)', desc: '$0.10 orchestration fee per new plan + per-step costs' },
|
|
677
|
+
{ flag: '(example)', desc: 'palmyr chat resume sess_abc "now post 3 videos"' },
|
|
678
|
+
],
|
|
679
|
+
status: [
|
|
680
|
+
{ flag: '<session_id>', desc: 'Session id (positional)' },
|
|
681
|
+
{ flag: '(price)', desc: 'Free — session inspection' },
|
|
682
|
+
],
|
|
683
|
+
cancel: [
|
|
684
|
+
{ flag: '<session_id>', desc: 'Session id (positional)' },
|
|
685
|
+
{ flag: '(price)', desc: 'Free — halts execution and refunds remaining escrow' },
|
|
686
|
+
],
|
|
687
|
+
sessions: [
|
|
688
|
+
{ flag: '(no args)', desc: 'List your active i402 sessions' },
|
|
689
|
+
{ flag: '(price)', desc: 'Free' },
|
|
690
|
+
],
|
|
691
|
+
capabilities: [
|
|
692
|
+
{ flag: '(no args)', desc: 'List canonical capability classes (e.g. web_search, mint_nft)' },
|
|
693
|
+
{ flag: '(price)', desc: 'Free' },
|
|
694
|
+
],
|
|
695
|
+
providers: [
|
|
696
|
+
{ flag: '--capability <name>', desc: 'Filter providers by capability (optional)' },
|
|
697
|
+
{ flag: '(price)', desc: 'Free' },
|
|
698
|
+
{ flag: '(example)', desc: 'palmyr chat providers --capability web_search' },
|
|
699
|
+
],
|
|
700
|
+
};
|
|
380
701
|
/**
|
|
381
702
|
* Render a per-command menu (no subcommand given). On a TTY → Ink MenuScreen
|
|
382
703
|
* with the Palmyr aesthetic. In agent mode → flat JSON listing the available
|
|
@@ -673,7 +994,7 @@ async function main() {
|
|
|
673
994
|
break;
|
|
674
995
|
}
|
|
675
996
|
case 'phone': {
|
|
676
|
-
if (!subcommand || flags.help) {
|
|
997
|
+
if (!subcommand || (flags.help && !PHONE_HELP[subcommand])) {
|
|
677
998
|
showMenu({
|
|
678
999
|
command: 'phone',
|
|
679
1000
|
title: 'phone',
|
|
@@ -682,13 +1003,31 @@ async function main() {
|
|
|
682
1003
|
commands: [
|
|
683
1004
|
{ name: 'search', description: 'Search available numbers', hint: '--country US' },
|
|
684
1005
|
{ name: 'buy', description: 'Buy a phone number', hint: '--country US' },
|
|
1006
|
+
{ name: 'list', description: 'List numbers owned by your wallet' },
|
|
1007
|
+
{ name: 'release', description: 'Release a phone number', hint: '--id PHONE_ID' },
|
|
685
1008
|
{ name: 'sms', description: 'Send an SMS', hint: '--id ID --to +1... --body "hi"' },
|
|
1009
|
+
{ name: 'messages', description: 'Read SMS messages received on a number', hint: '--id PHONE_ID' },
|
|
686
1010
|
{ name: 'call', description: 'Place a voice call', hint: '--id ID --to +1... --tts "hello"' },
|
|
1011
|
+
{ name: 'calls', description: 'List calls placed/received on a number', hint: '--id PHONE_ID' },
|
|
1012
|
+
{ name: 'call-info', description: 'Get details on a single call', hint: '--call CALL_CONTROL_ID' },
|
|
1013
|
+
{ name: 'speak', description: 'TTS into a live call', hint: '--call ID --text "..." [--voice V]' },
|
|
1014
|
+
{ name: 'play', description: 'Play an audio URL into a live call', hint: '--call ID --url https://...' },
|
|
1015
|
+
{ name: 'dtmf', description: 'Send DTMF tones to a live call', hint: '--call ID --digits "1234#"' },
|
|
1016
|
+
{ name: 'gather', description: 'Collect DTMF input from caller', hint: '--call ID [--min-digits N --max-digits N --timeout MS --prompt "..."]' },
|
|
1017
|
+
{ name: 'record', description: 'Start recording a live call', hint: '--call ID [--format mp3]' },
|
|
1018
|
+
{ name: 'record-stop', description: 'Stop recording a live call', hint: '--call ID' },
|
|
1019
|
+
{ name: 'hangup', description: 'End a live call', hint: '--call ID' },
|
|
1020
|
+
{ name: 'answer', description: 'Answer an inbound call', hint: '--call ID' },
|
|
1021
|
+
{ name: 'transfer', description: 'Transfer a live call to another number', hint: '--call ID --to +1...' },
|
|
687
1022
|
],
|
|
688
1023
|
fromHome,
|
|
689
1024
|
});
|
|
690
1025
|
break;
|
|
691
1026
|
}
|
|
1027
|
+
if (flags.help && subcommand && PHONE_HELP[subcommand]) {
|
|
1028
|
+
subcommandHelp('phone', subcommand, PHONE_HELP[subcommand]);
|
|
1029
|
+
break;
|
|
1030
|
+
}
|
|
692
1031
|
switch (subcommand) {
|
|
693
1032
|
case 'search': {
|
|
694
1033
|
const country = flags.country || 'US';
|
|
@@ -757,12 +1096,130 @@ async function main() {
|
|
|
757
1096
|
render(React.createElement(SuccessScreen, { version: VERSION, title: 'calling', subtitle: to, details: [{ label: 'To', value: to }, { label: 'Call ID', value: data.callControlId || data.id || '' }], footerLeft: 'Call initiated' }));
|
|
758
1097
|
break;
|
|
759
1098
|
}
|
|
760
|
-
|
|
1099
|
+
case 'list': {
|
|
1100
|
+
const data = await ao.phoneListNumbers();
|
|
1101
|
+
return print(data);
|
|
1102
|
+
}
|
|
1103
|
+
case 'messages': {
|
|
1104
|
+
const id = flags.id || positional[0];
|
|
1105
|
+
if (!id)
|
|
1106
|
+
err('--id PHONE_ID required');
|
|
1107
|
+
const data = await ao.phoneMessages(id);
|
|
1108
|
+
return print(data);
|
|
1109
|
+
}
|
|
1110
|
+
case 'calls': {
|
|
1111
|
+
const id = flags.id || positional[0];
|
|
1112
|
+
if (!id)
|
|
1113
|
+
err('--id PHONE_ID required');
|
|
1114
|
+
const data = await ao.phoneCalls(id);
|
|
1115
|
+
return print(data);
|
|
1116
|
+
}
|
|
1117
|
+
case 'release': {
|
|
1118
|
+
const id = flags.id || positional[0];
|
|
1119
|
+
if (!id)
|
|
1120
|
+
err('--id PHONE_ID required');
|
|
1121
|
+
const data = await ao.phoneRelease(id);
|
|
1122
|
+
return print(data);
|
|
1123
|
+
}
|
|
1124
|
+
case 'call-info': {
|
|
1125
|
+
const callId = flags.call || flags.id || positional[0];
|
|
1126
|
+
if (!callId)
|
|
1127
|
+
err('--call CALL_CONTROL_ID required');
|
|
1128
|
+
const data = await ao.phoneCallInfo(callId);
|
|
1129
|
+
return print(data);
|
|
1130
|
+
}
|
|
1131
|
+
case 'speak': {
|
|
1132
|
+
const callId = flags.call || flags.id;
|
|
1133
|
+
const text = flags.text || flags.tts;
|
|
1134
|
+
if (!callId || !text)
|
|
1135
|
+
err('--call <id>, --text <text> required');
|
|
1136
|
+
const voice = flags.voice;
|
|
1137
|
+
const language = flags.language;
|
|
1138
|
+
const data = await ao.phoneSpeak(callId, text, { ...(voice ? { voice } : {}), ...(language ? { language } : {}) });
|
|
1139
|
+
return print(data);
|
|
1140
|
+
}
|
|
1141
|
+
case 'play': {
|
|
1142
|
+
const callId = flags.call || flags.id;
|
|
1143
|
+
const audioUrl = flags.url || flags['audio-url'];
|
|
1144
|
+
if (!callId || !audioUrl)
|
|
1145
|
+
err('--call <id>, --url <https://...> required');
|
|
1146
|
+
const data = await ao.phonePlay(callId, audioUrl);
|
|
1147
|
+
return print(data);
|
|
1148
|
+
}
|
|
1149
|
+
case 'dtmf': {
|
|
1150
|
+
const callId = flags.call || flags.id;
|
|
1151
|
+
const digits = flags.digits || positional[0];
|
|
1152
|
+
if (!callId || !digits)
|
|
1153
|
+
err('--call <id>, --digits "1234#" required');
|
|
1154
|
+
const data = await ao.phoneDtmf(callId, digits);
|
|
1155
|
+
return print(data);
|
|
1156
|
+
}
|
|
1157
|
+
case 'gather': {
|
|
1158
|
+
const callId = flags.call || flags.id;
|
|
1159
|
+
if (!callId)
|
|
1160
|
+
err('--call <id> required');
|
|
1161
|
+
const parseInt0 = (v) => (typeof v === 'string' ? parseInt(v, 10) : undefined);
|
|
1162
|
+
const opts = {};
|
|
1163
|
+
const minDigits = parseInt0(flags['min-digits']);
|
|
1164
|
+
if (minDigits !== undefined && Number.isFinite(minDigits))
|
|
1165
|
+
opts.minDigits = minDigits;
|
|
1166
|
+
const maxDigits = parseInt0(flags['max-digits']);
|
|
1167
|
+
if (maxDigits !== undefined && Number.isFinite(maxDigits))
|
|
1168
|
+
opts.maxDigits = maxDigits;
|
|
1169
|
+
const timeoutMillis = parseInt0(flags.timeout);
|
|
1170
|
+
if (timeoutMillis !== undefined && Number.isFinite(timeoutMillis))
|
|
1171
|
+
opts.timeoutMillis = timeoutMillis;
|
|
1172
|
+
if (flags['terminating-digit'])
|
|
1173
|
+
opts.terminatingDigit = flags['terminating-digit'];
|
|
1174
|
+
if (flags.prompt)
|
|
1175
|
+
opts.prompt = flags.prompt;
|
|
1176
|
+
if (flags['prompt-voice'])
|
|
1177
|
+
opts.promptVoice = flags['prompt-voice'];
|
|
1178
|
+
const data = await ao.phoneGather(callId, opts);
|
|
1179
|
+
return print(data);
|
|
1180
|
+
}
|
|
1181
|
+
case 'record': {
|
|
1182
|
+
const callId = flags.call || flags.id;
|
|
1183
|
+
if (!callId)
|
|
1184
|
+
err('--call <id> required');
|
|
1185
|
+
const data = await ao.phoneRecord(callId, flags.format);
|
|
1186
|
+
return print(data);
|
|
1187
|
+
}
|
|
1188
|
+
case 'record-stop': {
|
|
1189
|
+
const callId = flags.call || flags.id;
|
|
1190
|
+
if (!callId)
|
|
1191
|
+
err('--call <id> required');
|
|
1192
|
+
const data = await ao.phoneRecordStop(callId);
|
|
1193
|
+
return print(data);
|
|
1194
|
+
}
|
|
1195
|
+
case 'hangup': {
|
|
1196
|
+
const callId = flags.call || flags.id;
|
|
1197
|
+
if (!callId)
|
|
1198
|
+
err('--call <id> required');
|
|
1199
|
+
const data = await ao.phoneHangup(callId);
|
|
1200
|
+
return print(data);
|
|
1201
|
+
}
|
|
1202
|
+
case 'answer': {
|
|
1203
|
+
const callId = flags.call || flags.id;
|
|
1204
|
+
if (!callId)
|
|
1205
|
+
err('--call <id> required');
|
|
1206
|
+
const data = await ao.phoneAnswer(callId);
|
|
1207
|
+
return print(data);
|
|
1208
|
+
}
|
|
1209
|
+
case 'transfer': {
|
|
1210
|
+
const callId = flags.call || flags.id;
|
|
1211
|
+
const to = flags.to;
|
|
1212
|
+
if (!callId || !to)
|
|
1213
|
+
err('--call <id>, --to <+E.164> required');
|
|
1214
|
+
const data = await ao.phoneTransfer(callId, to);
|
|
1215
|
+
return print(data);
|
|
1216
|
+
}
|
|
1217
|
+
default: err(`Unknown phone command: ${subcommand}. Try: search, buy, list, release, sms, messages, call, calls, call-info, speak, play, dtmf, gather, record, record-stop, hangup, answer, transfer`);
|
|
761
1218
|
}
|
|
762
1219
|
break;
|
|
763
1220
|
}
|
|
764
1221
|
case 'email': {
|
|
765
|
-
if (!subcommand || flags.help) {
|
|
1222
|
+
if (!subcommand || (flags.help && !EMAIL_HELP[subcommand])) {
|
|
766
1223
|
showMenu({
|
|
767
1224
|
command: 'email',
|
|
768
1225
|
title: 'email',
|
|
@@ -781,6 +1238,10 @@ async function main() {
|
|
|
781
1238
|
});
|
|
782
1239
|
break;
|
|
783
1240
|
}
|
|
1241
|
+
if (flags.help && subcommand && EMAIL_HELP[subcommand]) {
|
|
1242
|
+
subcommandHelp('email', subcommand, EMAIL_HELP[subcommand]);
|
|
1243
|
+
break;
|
|
1244
|
+
}
|
|
784
1245
|
switch (subcommand) {
|
|
785
1246
|
case 'create': {
|
|
786
1247
|
const name = flags.name || positional[0];
|
|
@@ -872,7 +1333,7 @@ async function main() {
|
|
|
872
1333
|
break;
|
|
873
1334
|
}
|
|
874
1335
|
case 'compute': {
|
|
875
|
-
if (!subcommand || flags.help) {
|
|
1336
|
+
if (!subcommand || (flags.help && !COMPUTE_HELP[subcommand])) {
|
|
876
1337
|
showMenu({
|
|
877
1338
|
command: 'compute',
|
|
878
1339
|
title: 'compute',
|
|
@@ -899,6 +1360,10 @@ async function main() {
|
|
|
899
1360
|
});
|
|
900
1361
|
break;
|
|
901
1362
|
}
|
|
1363
|
+
if (flags.help && subcommand && COMPUTE_HELP[subcommand]) {
|
|
1364
|
+
subcommandHelp('compute', subcommand, COMPUTE_HELP[subcommand]);
|
|
1365
|
+
break;
|
|
1366
|
+
}
|
|
902
1367
|
switch (subcommand) {
|
|
903
1368
|
case 'plans': {
|
|
904
1369
|
// --location filters to types deployable in that location. Each
|
|
@@ -1474,7 +1939,7 @@ async function main() {
|
|
|
1474
1939
|
break;
|
|
1475
1940
|
}
|
|
1476
1941
|
case 'domain': {
|
|
1477
|
-
if (!subcommand || flags.help) {
|
|
1942
|
+
if (!subcommand || (flags.help && !DOMAIN_HELP[subcommand])) {
|
|
1478
1943
|
showMenu({
|
|
1479
1944
|
command: 'domain',
|
|
1480
1945
|
title: 'domain',
|
|
@@ -1494,6 +1959,10 @@ async function main() {
|
|
|
1494
1959
|
});
|
|
1495
1960
|
break;
|
|
1496
1961
|
}
|
|
1962
|
+
if (flags.help && subcommand && DOMAIN_HELP[subcommand]) {
|
|
1963
|
+
subcommandHelp('domain', subcommand, DOMAIN_HELP[subcommand]);
|
|
1964
|
+
break;
|
|
1965
|
+
}
|
|
1497
1966
|
switch (subcommand) {
|
|
1498
1967
|
case 'check': {
|
|
1499
1968
|
const name = flags.name || positional[0];
|
|
@@ -3917,7 +4386,7 @@ async function main() {
|
|
|
3917
4386
|
break;
|
|
3918
4387
|
}
|
|
3919
4388
|
case 'chat': {
|
|
3920
|
-
if (!subcommand || flags.help) {
|
|
4389
|
+
if (!subcommand || (flags.help && !CHAT_HELP[subcommand])) {
|
|
3921
4390
|
showMenu({
|
|
3922
4391
|
command: 'chat',
|
|
3923
4392
|
title: 'chat',
|
|
@@ -3936,6 +4405,10 @@ async function main() {
|
|
|
3936
4405
|
});
|
|
3937
4406
|
break;
|
|
3938
4407
|
}
|
|
4408
|
+
if (flags.help && subcommand && CHAT_HELP[subcommand]) {
|
|
4409
|
+
subcommandHelp('chat', subcommand, CHAT_HELP[subcommand]);
|
|
4410
|
+
break;
|
|
4411
|
+
}
|
|
3939
4412
|
switch (subcommand) {
|
|
3940
4413
|
case 'run': {
|
|
3941
4414
|
const intent = (positional.join(' ') || flags.intent || '').trim();
|