@palmyr/cli 1.6.0 → 1.8.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 +549 -6
- package/dist/cli.js.map +1 -1
- package/dist/pay-preflight.d.ts +54 -0
- package/dist/pay-preflight.js +202 -0
- package/dist/pay-preflight.js.map +1 -0
- package/dist/pay.js +79 -8
- 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/dist/cli.js
CHANGED
|
@@ -376,6 +376,336 @@ const WALLET_HELP = {
|
|
|
376
376
|
{ flag: '--src-decimals <n>', desc: 'Source token decimals', hint: 'default 18' },
|
|
377
377
|
{ flag: '--dst-decimals <n>', desc: 'Dest token decimals', hint: 'default 6 (USDC-like)' },
|
|
378
378
|
],
|
|
379
|
+
'pay-preflight': [
|
|
380
|
+
{ flag: '--chain <c>', desc: 'Override the default pay chain', hint: 'solana | base (default: config.defaultPayChain)' },
|
|
381
|
+
{ flag: '--wallet <ID>', desc: 'Override the wallet to check', hint: 'default: config.defaultPayWalletId / PALMYR_PAY_WALLET / auto-pick' },
|
|
382
|
+
{ flag: '--min-usdc <N>', desc: 'Required USDC balance to pass (default 0 — just check the wallet exists)' },
|
|
383
|
+
{ flag: '--passphrase <p>', desc: 'Wallet passphrase if no OS-keychain session secret', hint: 'or PALMYR_WALLET_PASSPHRASE env' },
|
|
384
|
+
{ flag: '(price)', desc: 'Free — one RPC call to read USDC balance' },
|
|
385
|
+
{ flag: '(example)', desc: 'palmyr wallet pay-preflight --chain base --min-usdc 3 --json' },
|
|
386
|
+
{ flag: '(note)', desc: 'Local-only version runs automatically before every paid command (set PALMYR_NO_PREFLIGHT=1 to disable)' },
|
|
387
|
+
],
|
|
388
|
+
};
|
|
389
|
+
const PHONE_HELP = {
|
|
390
|
+
search: [
|
|
391
|
+
{ flag: '--country <ISO>', desc: 'Country code', hint: 'default US (e.g. US, GB, AE)' },
|
|
392
|
+
{ flag: '--limit <N>', desc: 'Max results to return' },
|
|
393
|
+
{ flag: '(price)', desc: 'Free — no payment required' },
|
|
394
|
+
{ flag: '(example)', desc: 'palmyr phone search --country US --json' },
|
|
395
|
+
],
|
|
396
|
+
buy: [
|
|
397
|
+
{ flag: '--country <ISO>', desc: 'Country code (required)', hint: 'e.g. US, GB' },
|
|
398
|
+
{ flag: '--area <code>', desc: 'Preferred area code (optional, US only)' },
|
|
399
|
+
{ flag: '(price)', desc: '$3.00 per number provisioned' },
|
|
400
|
+
{ flag: '(example)', desc: 'palmyr phone buy --country US' },
|
|
401
|
+
],
|
|
402
|
+
sms: [
|
|
403
|
+
{ flag: '--id <PHONE_ID>', desc: 'Source phone number id (required)' },
|
|
404
|
+
{ flag: '--to <+E.164>', desc: 'Destination phone number (required)', hint: 'e.g. +15551234567' },
|
|
405
|
+
{ flag: '--body <text>', desc: 'Message body (required)' },
|
|
406
|
+
{ flag: '(price)', desc: '$0.05 per SMS sent' },
|
|
407
|
+
{ flag: '(example)', desc: 'palmyr phone sms --id PN_abc --to +15551234567 --body "hi"' },
|
|
408
|
+
],
|
|
409
|
+
call: [
|
|
410
|
+
{ flag: '--id <PHONE_ID>', desc: 'Source phone number id (required)' },
|
|
411
|
+
{ flag: '--to <+E.164>', desc: 'Destination phone number (required)' },
|
|
412
|
+
{ flag: '--tts <text>', desc: 'Text-to-speech to play on answer (optional)' },
|
|
413
|
+
{ flag: '(price)', desc: '$0.10 per call placed' },
|
|
414
|
+
{ flag: '(example)', desc: 'palmyr phone call --id PN_abc --to +15551234567 --tts "hello"' },
|
|
415
|
+
],
|
|
416
|
+
list: [
|
|
417
|
+
{ flag: '(no args)', desc: 'List phone numbers owned by your wallet' },
|
|
418
|
+
{ flag: '(price)', desc: '$0.01 per call' },
|
|
419
|
+
{ flag: '(example)', desc: 'palmyr phone list --json' },
|
|
420
|
+
],
|
|
421
|
+
messages: [
|
|
422
|
+
{ flag: '--id <PHONE_ID>', desc: 'Phone number id to read SMS for (required; positional also accepted)' },
|
|
423
|
+
{ flag: '(price)', desc: '$0.02 per call' },
|
|
424
|
+
{ flag: '(example)', desc: 'palmyr phone messages --id PN_abc' },
|
|
425
|
+
],
|
|
426
|
+
calls: [
|
|
427
|
+
{ flag: '--id <PHONE_ID>', desc: 'Phone number id to list calls for (required; positional also accepted)' },
|
|
428
|
+
{ flag: '(price)', desc: '$0.02 per call' },
|
|
429
|
+
{ flag: '(example)', desc: 'palmyr phone calls --id PN_abc' },
|
|
430
|
+
],
|
|
431
|
+
release: [
|
|
432
|
+
{ flag: '--id <PHONE_ID>', desc: 'Phone number id to release (required; positional also accepted)' },
|
|
433
|
+
{ flag: '(price)', desc: '$0.01 per release (stops monthly Telnyx billing)' },
|
|
434
|
+
{ flag: '(example)', desc: 'palmyr phone release --id PN_abc' },
|
|
435
|
+
],
|
|
436
|
+
'call-info': [
|
|
437
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id (required; --id and positional also accepted)' },
|
|
438
|
+
{ flag: '(price)', desc: '$0.02 per lookup' },
|
|
439
|
+
{ flag: '(example)', desc: 'palmyr phone call-info --call CC_abc' },
|
|
440
|
+
],
|
|
441
|
+
speak: [
|
|
442
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
443
|
+
{ flag: '--text <text>', desc: 'TTS text to speak (required; --tts alias accepted)' },
|
|
444
|
+
{ flag: '--voice <name>', desc: 'TTS voice (optional, provider default otherwise)' },
|
|
445
|
+
{ flag: '--language <code>', desc: 'TTS language code (optional, e.g. en-US)' },
|
|
446
|
+
{ flag: '(price)', desc: '$0.08 per speak action' },
|
|
447
|
+
{ flag: '(example)', desc: 'palmyr phone speak --call CC_abc --text "please hold"' },
|
|
448
|
+
],
|
|
449
|
+
play: [
|
|
450
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
451
|
+
{ flag: '--url <audio_url>', desc: 'Public audio URL to play (required; --audio-url alias accepted)' },
|
|
452
|
+
{ flag: '(price)', desc: '$0.08 per playback' },
|
|
453
|
+
{ flag: '(example)', desc: 'palmyr phone play --call CC_abc --url https://example.com/hold.mp3' },
|
|
454
|
+
],
|
|
455
|
+
dtmf: [
|
|
456
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
457
|
+
{ flag: '--digits <seq>', desc: 'DTMF digit sequence (required; positional also accepted)', hint: 'e.g. "1234#"' },
|
|
458
|
+
{ flag: '(price)', desc: '$0.02 per DTMF send' },
|
|
459
|
+
{ flag: '(example)', desc: 'palmyr phone dtmf --call CC_abc --digits "1234#"' },
|
|
460
|
+
],
|
|
461
|
+
gather: [
|
|
462
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
463
|
+
{ flag: '--min-digits <N>', desc: 'Minimum digits to collect (optional)' },
|
|
464
|
+
{ flag: '--max-digits <N>', desc: 'Maximum digits to collect (optional)' },
|
|
465
|
+
{ flag: '--timeout <ms>', desc: 'Per-input timeout in milliseconds (optional)' },
|
|
466
|
+
{ flag: '--terminating-digit <d>', desc: 'Digit that ends collection (optional, e.g. "#")' },
|
|
467
|
+
{ flag: '--prompt <text>', desc: 'Optional TTS prompt to play before gathering' },
|
|
468
|
+
{ flag: '--prompt-voice <name>', desc: 'TTS voice for the prompt (optional)' },
|
|
469
|
+
{ flag: '(price)', desc: '$0.08 per gather action' },
|
|
470
|
+
{ flag: '(example)', desc: 'palmyr phone gather --call CC_abc --max-digits 4 --terminating-digit "#" --prompt "Enter PIN"' },
|
|
471
|
+
],
|
|
472
|
+
record: [
|
|
473
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
474
|
+
{ flag: '--format <fmt>', desc: 'Recording format (optional, provider default otherwise)' },
|
|
475
|
+
{ flag: '(price)', desc: '$0.10 per record start' },
|
|
476
|
+
{ flag: '(example)', desc: 'palmyr phone record --call CC_abc --format mp3' },
|
|
477
|
+
],
|
|
478
|
+
'record-stop': [
|
|
479
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
480
|
+
{ flag: '(price)', desc: '$0.02 per stop' },
|
|
481
|
+
{ flag: '(example)', desc: 'palmyr phone record-stop --call CC_abc' },
|
|
482
|
+
],
|
|
483
|
+
hangup: [
|
|
484
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
485
|
+
{ flag: '(price)', desc: '$0.02 per hangup' },
|
|
486
|
+
{ flag: '(example)', desc: 'palmyr phone hangup --call CC_abc' },
|
|
487
|
+
],
|
|
488
|
+
answer: [
|
|
489
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of an inbound call (required)' },
|
|
490
|
+
{ flag: '(price)', desc: '$0.02 per answer' },
|
|
491
|
+
{ flag: '(example)', desc: 'palmyr phone answer --call CC_abc' },
|
|
492
|
+
],
|
|
493
|
+
transfer: [
|
|
494
|
+
{ flag: '--call <CALL_ID>', desc: 'Call control id of a live call (required)' },
|
|
495
|
+
{ flag: '--to <+E.164>', desc: 'Destination phone number to bridge into (required)' },
|
|
496
|
+
{ flag: '(price)', desc: '$0.10 per transfer' },
|
|
497
|
+
{ flag: '(example)', desc: 'palmyr phone transfer --call CC_abc --to +15557654321' },
|
|
498
|
+
],
|
|
499
|
+
};
|
|
500
|
+
const EMAIL_HELP = {
|
|
501
|
+
create: [
|
|
502
|
+
{ flag: '--name <name>', desc: 'Inbox name (required)' },
|
|
503
|
+
{ flag: '--domain <domain>', desc: 'Wallet-owned domain to host the inbox on (optional)', hint: 'default: Palmyr-hosted domain' },
|
|
504
|
+
{ flag: '--wallet <id|name>', desc: 'Wallet to own the inbox (optional)' },
|
|
505
|
+
{ flag: '(price)', desc: '$2.00 per inbox provisioned' },
|
|
506
|
+
{ flag: '(example)', desc: 'palmyr email create --name agent --domain example.com' },
|
|
507
|
+
],
|
|
508
|
+
list: [
|
|
509
|
+
{ flag: '(no args)', desc: 'List inboxes owned by your wallet' },
|
|
510
|
+
{ flag: '(price)', desc: '$0.01 per call' },
|
|
511
|
+
{ flag: '(example)', desc: 'palmyr email list --json' },
|
|
512
|
+
],
|
|
513
|
+
status: [
|
|
514
|
+
{ flag: '<domain>', desc: 'Domain to check (positional or --domain)', hint: 'e.g. example.com' },
|
|
515
|
+
{ flag: '(price)', desc: '$0.01 per call' },
|
|
516
|
+
{ flag: '(example)', desc: 'palmyr email status example.com' },
|
|
517
|
+
],
|
|
518
|
+
register: [
|
|
519
|
+
{ flag: '<domain>', desc: 'Wallet-owned domain to (re-)register with Mailgun (positional or --domain)' },
|
|
520
|
+
{ flag: '(price)', desc: '$0.05 per registration' },
|
|
521
|
+
{ flag: '(example)', desc: 'palmyr email register example.com' },
|
|
522
|
+
],
|
|
523
|
+
read: [
|
|
524
|
+
{ flag: '--id <INBOX_ID>', desc: 'Inbox id (required; positional also accepted)' },
|
|
525
|
+
{ flag: '(price)', desc: '$0.02 per call' },
|
|
526
|
+
{ flag: '(example)', desc: 'palmyr email read --id INB_abc123' },
|
|
527
|
+
],
|
|
528
|
+
send: [
|
|
529
|
+
{ flag: '--id <INBOX_ID>', desc: 'Source inbox id (required)' },
|
|
530
|
+
{ flag: '--to <addr>', desc: 'Destination email (required)' },
|
|
531
|
+
{ flag: '--subject <text>', desc: 'Subject line (required)' },
|
|
532
|
+
{ flag: '--body <text>', desc: 'Message body (required)' },
|
|
533
|
+
{ flag: '(price)', desc: '$0.08 per email sent' },
|
|
534
|
+
{ flag: '(example)', desc: 'palmyr email send --id INB_abc --to user@x.com --subject Hi --body "..."' },
|
|
535
|
+
],
|
|
536
|
+
threads: [
|
|
537
|
+
{ flag: '--id <INBOX_ID>', desc: 'Inbox id (required; positional also accepted)' },
|
|
538
|
+
{ flag: '(price)', desc: '$0.02 per call' },
|
|
539
|
+
{ flag: '(example)', desc: 'palmyr email threads --id INB_abc123' },
|
|
540
|
+
],
|
|
541
|
+
};
|
|
542
|
+
const COMPUTE_HELP = {
|
|
543
|
+
plans: [
|
|
544
|
+
{ flag: '--location <loc>', desc: 'Filter to types deployable in this datacenter (optional)', hint: 'e.g. fsn1, nbg1, hel1, ash, hil' },
|
|
545
|
+
{ flag: '(price)', desc: 'Free — live discovery from Hetzner' },
|
|
546
|
+
{ flag: '(example)', desc: 'palmyr compute plans --location fsn1 --json' },
|
|
547
|
+
],
|
|
548
|
+
locations: [
|
|
549
|
+
{ flag: '(no args)', desc: 'List Hetzner datacenters + per-location server-type availability' },
|
|
550
|
+
{ flag: '(price)', desc: 'Free' },
|
|
551
|
+
],
|
|
552
|
+
'install-recipes': [
|
|
553
|
+
{ flag: '(no args)', desc: 'List available agent install recipes (hermes, openclaw, …)' },
|
|
554
|
+
{ flag: '(price)', desc: 'Free' },
|
|
555
|
+
],
|
|
556
|
+
'ssh-key': [
|
|
557
|
+
{ flag: 'add <pubkey-file>', desc: 'Upload a key to Hetzner', hint: '[--name "label"]' },
|
|
558
|
+
{ flag: 'list', desc: 'List uploaded Hetzner SSH keys' },
|
|
559
|
+
{ flag: 'delete <id>', desc: 'Remove a Hetzner SSH key' },
|
|
560
|
+
{ flag: '(price)', desc: 'add $0.10 · list $0.01 · delete $0.01' },
|
|
561
|
+
],
|
|
562
|
+
deploy: [
|
|
563
|
+
{ flag: '--type <name>', desc: 'Hetzner server type', hint: 'default cx23' },
|
|
564
|
+
{ flag: '--name <name>', desc: 'Server name', hint: 'default agent-<timestamp>' },
|
|
565
|
+
{ flag: '--location <loc>', desc: 'Hetzner datacenter (optional)', hint: 'e.g. fsn1, nbg1' },
|
|
566
|
+
{ flag: '--install <recipes>', desc: 'Comma-separated install recipes', hint: 'e.g. hermes,openclaw' },
|
|
567
|
+
{ flag: '--no-install', desc: 'Skip cloud-init entirely (vanilla Ubuntu)' },
|
|
568
|
+
{ flag: '--generate-ssh-key', desc: 'GOLDEN PATH (default): generate a fresh keypair locally' },
|
|
569
|
+
{ flag: '--pubkey-file <path>', desc: 'Use an existing public key from disk' },
|
|
570
|
+
{ flag: '--pubkey "ssh-..."', desc: 'Use an inline public key string' },
|
|
571
|
+
{ flag: '--ssh-key <id>', desc: 'Numeric Hetzner key id (already uploaded)' },
|
|
572
|
+
{ flag: '--no-wait', desc: 'Return immediately instead of waiting for SSH-ready' },
|
|
573
|
+
{ flag: '--wait-timeout <s>', desc: 'Override readiness timeout (30–900s)' },
|
|
574
|
+
{ flag: '(price)', desc: '$6.00 per deploy (Hetzner billing flows through)' },
|
|
575
|
+
{ flag: '(example)', desc: 'palmyr compute deploy --type cx23 --install hermes' },
|
|
576
|
+
],
|
|
577
|
+
wait: [
|
|
578
|
+
{ flag: '<name|id>', desc: 'Server (positional, name or numeric id)' },
|
|
579
|
+
{ flag: '--install <recipes>', desc: 'Also gate on the install marker file (e.g. hermes)' },
|
|
580
|
+
{ flag: '--key <path>', desc: 'Path to the private key for SSH verification' },
|
|
581
|
+
{ flag: '--wait-timeout <s>', desc: 'Override readiness timeout (30–900s)' },
|
|
582
|
+
{ flag: '(price)', desc: '$0.01 per readiness poll (server status check)' },
|
|
583
|
+
{ flag: '(example)', desc: 'palmyr compute wait my-vps --install hermes' },
|
|
584
|
+
],
|
|
585
|
+
ssh: [
|
|
586
|
+
{ flag: '<name|id>', desc: 'Server (positional)' },
|
|
587
|
+
{ flag: '(price)', desc: 'Free — local cache lookup only' },
|
|
588
|
+
{ flag: '(example)', desc: 'palmyr compute ssh my-vps' },
|
|
589
|
+
],
|
|
590
|
+
exec: [
|
|
591
|
+
{ flag: '<name|id> -- <cmd> [args...]', desc: 'Run a one-shot command via Palmyr SSH bridge' },
|
|
592
|
+
{ flag: '--timeout <s>', desc: 'Command timeout (1–120s)' },
|
|
593
|
+
{ flag: '(price)', desc: '$0.05 per command' },
|
|
594
|
+
{ flag: '(example)', desc: 'palmyr compute exec my-vps -- systemctl status openclaw' },
|
|
595
|
+
],
|
|
596
|
+
rename: [
|
|
597
|
+
{ flag: '<name|id> <new-name>', desc: 'Rename server (metadata-only, no reboot)' },
|
|
598
|
+
{ flag: '(price)', desc: '$0.01 per rename' },
|
|
599
|
+
],
|
|
600
|
+
'reset-password': [
|
|
601
|
+
{ flag: '<name|id>', desc: 'Rotate the root password (Hetzner-side)' },
|
|
602
|
+
{ flag: '(price)', desc: '$0.10 per action' },
|
|
603
|
+
],
|
|
604
|
+
console: [
|
|
605
|
+
{ flag: '<name|id>', desc: 'Get a noVNC console URL (break-glass)' },
|
|
606
|
+
{ flag: '(price)', desc: '$0.10 per action' },
|
|
607
|
+
],
|
|
608
|
+
reboot: [
|
|
609
|
+
{ flag: '<name|id>', desc: 'Reboot the server' },
|
|
610
|
+
{ flag: '(price)', desc: '$0.10 per action' },
|
|
611
|
+
],
|
|
612
|
+
'setup-ssh': [
|
|
613
|
+
{ flag: '--id <SERVER_ID>', desc: 'Server id (required; positional also accepted)' },
|
|
614
|
+
{ flag: '--pubkey-file <path>', desc: 'Public key file to inject' },
|
|
615
|
+
{ flag: '--pubkey "ssh-..."', desc: 'Inline public key string' },
|
|
616
|
+
{ flag: '(price)', desc: '$0.01 per call' },
|
|
617
|
+
{ flag: '(example)', desc: 'palmyr compute setup-ssh --id 12345 --pubkey-file ~/.ssh/id_ed25519.pub' },
|
|
618
|
+
],
|
|
619
|
+
list: [
|
|
620
|
+
{ flag: '(no args)', desc: 'List your deployed servers' },
|
|
621
|
+
{ flag: '(price)', desc: '$0.01 per call' },
|
|
622
|
+
],
|
|
623
|
+
delete: [
|
|
624
|
+
{ flag: '--id <SERVER_ID>', desc: 'Server id (required; positional also accepted)' },
|
|
625
|
+
{ flag: '(price)', desc: '$0.10 per deletion (Hetzner billing stops on confirm)' },
|
|
626
|
+
],
|
|
627
|
+
};
|
|
628
|
+
const DOMAIN_HELP = {
|
|
629
|
+
check: [
|
|
630
|
+
{ flag: '--name <domain>', desc: 'Domain or root name (positional also accepted)' },
|
|
631
|
+
{ flag: '(price)', desc: 'Free — availability lookup only' },
|
|
632
|
+
{ flag: '(example)', desc: 'palmyr domain check example.dev' },
|
|
633
|
+
],
|
|
634
|
+
pricing: [
|
|
635
|
+
{ flag: '--name <root>', desc: 'Root name to price across TLDs (positional also accepted)' },
|
|
636
|
+
{ flag: '(price)', desc: 'Free' },
|
|
637
|
+
{ flag: '(example)', desc: 'palmyr domain pricing example' },
|
|
638
|
+
],
|
|
639
|
+
buy: [
|
|
640
|
+
{ flag: '--name <domain>', desc: 'Fully-qualified domain (required, e.g. example.dev)' },
|
|
641
|
+
{ flag: '(price)', desc: 'Dynamic — registrar cost × 1.25 markup (charged via x402)' },
|
|
642
|
+
{ flag: '(example)', desc: 'palmyr domain buy --name example.dev' },
|
|
643
|
+
],
|
|
644
|
+
list: [
|
|
645
|
+
{ flag: '(no args)', desc: 'List domains owned or shared with your wallet' },
|
|
646
|
+
{ flag: '(price)', desc: '$0.0001 ownership-proof micro-payment' },
|
|
647
|
+
],
|
|
648
|
+
dns: [
|
|
649
|
+
{ flag: '--name <domain>', desc: 'Domain to read DNS for (positional also accepted)' },
|
|
650
|
+
{ flag: '(price)', desc: '$0.0001 ownership-proof micro-payment' },
|
|
651
|
+
],
|
|
652
|
+
'transfer-ownership': [
|
|
653
|
+
{ flag: '--name <domain>', desc: 'Domain to transfer (required)' },
|
|
654
|
+
{ flag: '--to <wallet>', desc: 'Recipient wallet address (required)' },
|
|
655
|
+
{ flag: '(price)', desc: '$0.0001 ownership-proof micro-payment' },
|
|
656
|
+
],
|
|
657
|
+
share: [
|
|
658
|
+
{ flag: '--name <domain>', desc: 'Domain to share (required)' },
|
|
659
|
+
{ flag: '--with <wallet>', desc: 'Wallet to grant shared access (required)' },
|
|
660
|
+
{ flag: '(price)', desc: '$0.0001 ownership-proof micro-payment' },
|
|
661
|
+
],
|
|
662
|
+
unshare: [
|
|
663
|
+
{ flag: '--name <domain>', desc: 'Domain to revoke from (required)' },
|
|
664
|
+
{ flag: '--from <wallet>', desc: 'Wallet to revoke (required)' },
|
|
665
|
+
{ flag: '(price)', desc: '$0.0001 ownership-proof micro-payment' },
|
|
666
|
+
],
|
|
667
|
+
};
|
|
668
|
+
const CHAT_HELP = {
|
|
669
|
+
run: [
|
|
670
|
+
{ flag: '"<intent>"', desc: 'Plain-string intent (positional or --intent)' },
|
|
671
|
+
{ flag: '--budget <USDC>', desc: 'Max spend cap (required, positive USDC)' },
|
|
672
|
+
{ flag: '--quality <q>', desc: 'Quality tier', hint: 'fast | cheap | best (default best)' },
|
|
673
|
+
{ flag: '--execute', desc: 'Auto-execute the plan once generated' },
|
|
674
|
+
{ flag: '--auto-approve-under <USDC>', desc: 'Skip approval prompts for steps cheaper than this' },
|
|
675
|
+
{ flag: '(price)', desc: '$0.10 orchestration fee + sum of per-step costs (capped by --budget)' },
|
|
676
|
+
{ flag: '(example)', desc: 'palmyr chat run "launch a sneaker brand" --budget 50' },
|
|
677
|
+
],
|
|
678
|
+
resume: [
|
|
679
|
+
{ flag: '<session_id>', desc: 'Existing session id (positional)' },
|
|
680
|
+
{ flag: '"<follow-up>"', desc: 'Follow-up intent (positional remainder or --intent)' },
|
|
681
|
+
{ flag: '--approve', desc: 'Approve a previously-generated plan' },
|
|
682
|
+
{ flag: '--plan-id <id>', desc: 'Plan id to approve (pair with --approve)' },
|
|
683
|
+
{ flag: '--budget <USDC>', desc: 'Override session budget (default $20)' },
|
|
684
|
+
{ flag: '--execute', desc: 'Auto-execute the new plan' },
|
|
685
|
+
{ flag: '(price)', desc: '$0.10 orchestration fee per new plan + per-step costs' },
|
|
686
|
+
{ flag: '(example)', desc: 'palmyr chat resume sess_abc "now post 3 videos"' },
|
|
687
|
+
],
|
|
688
|
+
status: [
|
|
689
|
+
{ flag: '<session_id>', desc: 'Session id (positional)' },
|
|
690
|
+
{ flag: '(price)', desc: 'Free — session inspection' },
|
|
691
|
+
],
|
|
692
|
+
cancel: [
|
|
693
|
+
{ flag: '<session_id>', desc: 'Session id (positional)' },
|
|
694
|
+
{ flag: '(price)', desc: 'Free — halts execution and refunds remaining escrow' },
|
|
695
|
+
],
|
|
696
|
+
sessions: [
|
|
697
|
+
{ flag: '(no args)', desc: 'List your active i402 sessions' },
|
|
698
|
+
{ flag: '(price)', desc: 'Free' },
|
|
699
|
+
],
|
|
700
|
+
capabilities: [
|
|
701
|
+
{ flag: '(no args)', desc: 'List canonical capability classes (e.g. web_search, mint_nft)' },
|
|
702
|
+
{ flag: '(price)', desc: 'Free' },
|
|
703
|
+
],
|
|
704
|
+
providers: [
|
|
705
|
+
{ flag: '--capability <name>', desc: 'Filter providers by capability (optional)' },
|
|
706
|
+
{ flag: '(price)', desc: 'Free' },
|
|
707
|
+
{ flag: '(example)', desc: 'palmyr chat providers --capability web_search' },
|
|
708
|
+
],
|
|
379
709
|
};
|
|
380
710
|
/**
|
|
381
711
|
* Render a per-command menu (no subcommand given). On a TTY → Ink MenuScreen
|
|
@@ -673,7 +1003,7 @@ async function main() {
|
|
|
673
1003
|
break;
|
|
674
1004
|
}
|
|
675
1005
|
case 'phone': {
|
|
676
|
-
if (!subcommand || flags.help) {
|
|
1006
|
+
if (!subcommand || (flags.help && !PHONE_HELP[subcommand])) {
|
|
677
1007
|
showMenu({
|
|
678
1008
|
command: 'phone',
|
|
679
1009
|
title: 'phone',
|
|
@@ -682,17 +1012,42 @@ async function main() {
|
|
|
682
1012
|
commands: [
|
|
683
1013
|
{ name: 'search', description: 'Search available numbers', hint: '--country US' },
|
|
684
1014
|
{ name: 'buy', description: 'Buy a phone number', hint: '--country US' },
|
|
1015
|
+
{ name: 'list', description: 'List numbers owned by your wallet' },
|
|
1016
|
+
{ name: 'release', description: 'Release a phone number', hint: '--id PHONE_ID' },
|
|
685
1017
|
{ name: 'sms', description: 'Send an SMS', hint: '--id ID --to +1... --body "hi"' },
|
|
1018
|
+
{ name: 'messages', description: 'Read SMS messages received on a number', hint: '--id PHONE_ID' },
|
|
686
1019
|
{ name: 'call', description: 'Place a voice call', hint: '--id ID --to +1... --tts "hello"' },
|
|
1020
|
+
{ name: 'calls', description: 'List calls placed/received on a number', hint: '--id PHONE_ID' },
|
|
1021
|
+
{ name: 'call-info', description: 'Get details on a single call', hint: '--call CALL_CONTROL_ID' },
|
|
1022
|
+
{ name: 'speak', description: 'TTS into a live call', hint: '--call ID --text "..." [--voice V]' },
|
|
1023
|
+
{ name: 'play', description: 'Play an audio URL into a live call', hint: '--call ID --url https://...' },
|
|
1024
|
+
{ name: 'dtmf', description: 'Send DTMF tones to a live call', hint: '--call ID --digits "1234#"' },
|
|
1025
|
+
{ name: 'gather', description: 'Collect DTMF input from caller', hint: '--call ID [--min-digits N --max-digits N --timeout MS --prompt "..."]' },
|
|
1026
|
+
{ name: 'record', description: 'Start recording a live call', hint: '--call ID [--format mp3]' },
|
|
1027
|
+
{ name: 'record-stop', description: 'Stop recording a live call', hint: '--call ID' },
|
|
1028
|
+
{ name: 'hangup', description: 'End a live call', hint: '--call ID' },
|
|
1029
|
+
{ name: 'answer', description: 'Answer an inbound call', hint: '--call ID' },
|
|
1030
|
+
{ name: 'transfer', description: 'Transfer a live call to another number', hint: '--call ID --to +1...' },
|
|
687
1031
|
],
|
|
688
1032
|
fromHome,
|
|
689
1033
|
});
|
|
690
1034
|
break;
|
|
691
1035
|
}
|
|
1036
|
+
if (flags.help && subcommand && PHONE_HELP[subcommand]) {
|
|
1037
|
+
subcommandHelp('phone', subcommand, PHONE_HELP[subcommand]);
|
|
1038
|
+
break;
|
|
1039
|
+
}
|
|
692
1040
|
switch (subcommand) {
|
|
693
1041
|
case 'search': {
|
|
694
1042
|
const country = flags.country || 'US';
|
|
695
1043
|
const data = await ao.phoneSearch(country, flags.limit ? parseInt(flags.limit) : undefined);
|
|
1044
|
+
// Empty result is a valid response but `{numbers: []}` alone made it
|
|
1045
|
+
// ambiguous whether the API failed or the country has no inventory.
|
|
1046
|
+
// Add a non-breaking `note` field — agents that already key off
|
|
1047
|
+
// `.numbers.length` keep working; new readers get a clear signal.
|
|
1048
|
+
if (data && Array.isArray(data.numbers) && data.numbers.length === 0 && !data.note) {
|
|
1049
|
+
data.note = `No numbers available for ${country}. Try a different country code (US, GB, CA, DE, etc.).`;
|
|
1050
|
+
}
|
|
696
1051
|
return print(data);
|
|
697
1052
|
render(React.createElement(RecordsScreen, {
|
|
698
1053
|
version: VERSION,
|
|
@@ -757,12 +1112,130 @@ async function main() {
|
|
|
757
1112
|
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
1113
|
break;
|
|
759
1114
|
}
|
|
760
|
-
|
|
1115
|
+
case 'list': {
|
|
1116
|
+
const data = await ao.phoneListNumbers();
|
|
1117
|
+
return print(data);
|
|
1118
|
+
}
|
|
1119
|
+
case 'messages': {
|
|
1120
|
+
const id = flags.id || positional[0];
|
|
1121
|
+
if (!id)
|
|
1122
|
+
err('--id PHONE_ID required');
|
|
1123
|
+
const data = await ao.phoneMessages(id);
|
|
1124
|
+
return print(data);
|
|
1125
|
+
}
|
|
1126
|
+
case 'calls': {
|
|
1127
|
+
const id = flags.id || positional[0];
|
|
1128
|
+
if (!id)
|
|
1129
|
+
err('--id PHONE_ID required');
|
|
1130
|
+
const data = await ao.phoneCalls(id);
|
|
1131
|
+
return print(data);
|
|
1132
|
+
}
|
|
1133
|
+
case 'release': {
|
|
1134
|
+
const id = flags.id || positional[0];
|
|
1135
|
+
if (!id)
|
|
1136
|
+
err('--id PHONE_ID required');
|
|
1137
|
+
const data = await ao.phoneRelease(id);
|
|
1138
|
+
return print(data);
|
|
1139
|
+
}
|
|
1140
|
+
case 'call-info': {
|
|
1141
|
+
const callId = flags.call || flags.id || positional[0];
|
|
1142
|
+
if (!callId)
|
|
1143
|
+
err('--call CALL_CONTROL_ID required');
|
|
1144
|
+
const data = await ao.phoneCallInfo(callId);
|
|
1145
|
+
return print(data);
|
|
1146
|
+
}
|
|
1147
|
+
case 'speak': {
|
|
1148
|
+
const callId = flags.call || flags.id;
|
|
1149
|
+
const text = flags.text || flags.tts;
|
|
1150
|
+
if (!callId || !text)
|
|
1151
|
+
err('--call <id>, --text <text> required');
|
|
1152
|
+
const voice = flags.voice;
|
|
1153
|
+
const language = flags.language;
|
|
1154
|
+
const data = await ao.phoneSpeak(callId, text, { ...(voice ? { voice } : {}), ...(language ? { language } : {}) });
|
|
1155
|
+
return print(data);
|
|
1156
|
+
}
|
|
1157
|
+
case 'play': {
|
|
1158
|
+
const callId = flags.call || flags.id;
|
|
1159
|
+
const audioUrl = flags.url || flags['audio-url'];
|
|
1160
|
+
if (!callId || !audioUrl)
|
|
1161
|
+
err('--call <id>, --url <https://...> required');
|
|
1162
|
+
const data = await ao.phonePlay(callId, audioUrl);
|
|
1163
|
+
return print(data);
|
|
1164
|
+
}
|
|
1165
|
+
case 'dtmf': {
|
|
1166
|
+
const callId = flags.call || flags.id;
|
|
1167
|
+
const digits = flags.digits || positional[0];
|
|
1168
|
+
if (!callId || !digits)
|
|
1169
|
+
err('--call <id>, --digits "1234#" required');
|
|
1170
|
+
const data = await ao.phoneDtmf(callId, digits);
|
|
1171
|
+
return print(data);
|
|
1172
|
+
}
|
|
1173
|
+
case 'gather': {
|
|
1174
|
+
const callId = flags.call || flags.id;
|
|
1175
|
+
if (!callId)
|
|
1176
|
+
err('--call <id> required');
|
|
1177
|
+
const parseInt0 = (v) => (typeof v === 'string' ? parseInt(v, 10) : undefined);
|
|
1178
|
+
const opts = {};
|
|
1179
|
+
const minDigits = parseInt0(flags['min-digits']);
|
|
1180
|
+
if (minDigits !== undefined && Number.isFinite(minDigits))
|
|
1181
|
+
opts.minDigits = minDigits;
|
|
1182
|
+
const maxDigits = parseInt0(flags['max-digits']);
|
|
1183
|
+
if (maxDigits !== undefined && Number.isFinite(maxDigits))
|
|
1184
|
+
opts.maxDigits = maxDigits;
|
|
1185
|
+
const timeoutMillis = parseInt0(flags.timeout);
|
|
1186
|
+
if (timeoutMillis !== undefined && Number.isFinite(timeoutMillis))
|
|
1187
|
+
opts.timeoutMillis = timeoutMillis;
|
|
1188
|
+
if (flags['terminating-digit'])
|
|
1189
|
+
opts.terminatingDigit = flags['terminating-digit'];
|
|
1190
|
+
if (flags.prompt)
|
|
1191
|
+
opts.prompt = flags.prompt;
|
|
1192
|
+
if (flags['prompt-voice'])
|
|
1193
|
+
opts.promptVoice = flags['prompt-voice'];
|
|
1194
|
+
const data = await ao.phoneGather(callId, opts);
|
|
1195
|
+
return print(data);
|
|
1196
|
+
}
|
|
1197
|
+
case 'record': {
|
|
1198
|
+
const callId = flags.call || flags.id;
|
|
1199
|
+
if (!callId)
|
|
1200
|
+
err('--call <id> required');
|
|
1201
|
+
const data = await ao.phoneRecord(callId, flags.format);
|
|
1202
|
+
return print(data);
|
|
1203
|
+
}
|
|
1204
|
+
case 'record-stop': {
|
|
1205
|
+
const callId = flags.call || flags.id;
|
|
1206
|
+
if (!callId)
|
|
1207
|
+
err('--call <id> required');
|
|
1208
|
+
const data = await ao.phoneRecordStop(callId);
|
|
1209
|
+
return print(data);
|
|
1210
|
+
}
|
|
1211
|
+
case 'hangup': {
|
|
1212
|
+
const callId = flags.call || flags.id;
|
|
1213
|
+
if (!callId)
|
|
1214
|
+
err('--call <id> required');
|
|
1215
|
+
const data = await ao.phoneHangup(callId);
|
|
1216
|
+
return print(data);
|
|
1217
|
+
}
|
|
1218
|
+
case 'answer': {
|
|
1219
|
+
const callId = flags.call || flags.id;
|
|
1220
|
+
if (!callId)
|
|
1221
|
+
err('--call <id> required');
|
|
1222
|
+
const data = await ao.phoneAnswer(callId);
|
|
1223
|
+
return print(data);
|
|
1224
|
+
}
|
|
1225
|
+
case 'transfer': {
|
|
1226
|
+
const callId = flags.call || flags.id;
|
|
1227
|
+
const to = flags.to;
|
|
1228
|
+
if (!callId || !to)
|
|
1229
|
+
err('--call <id>, --to <+E.164> required');
|
|
1230
|
+
const data = await ao.phoneTransfer(callId, to);
|
|
1231
|
+
return print(data);
|
|
1232
|
+
}
|
|
1233
|
+
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
1234
|
}
|
|
762
1235
|
break;
|
|
763
1236
|
}
|
|
764
1237
|
case 'email': {
|
|
765
|
-
if (!subcommand || flags.help) {
|
|
1238
|
+
if (!subcommand || (flags.help && !EMAIL_HELP[subcommand])) {
|
|
766
1239
|
showMenu({
|
|
767
1240
|
command: 'email',
|
|
768
1241
|
title: 'email',
|
|
@@ -781,6 +1254,10 @@ async function main() {
|
|
|
781
1254
|
});
|
|
782
1255
|
break;
|
|
783
1256
|
}
|
|
1257
|
+
if (flags.help && subcommand && EMAIL_HELP[subcommand]) {
|
|
1258
|
+
subcommandHelp('email', subcommand, EMAIL_HELP[subcommand]);
|
|
1259
|
+
break;
|
|
1260
|
+
}
|
|
784
1261
|
switch (subcommand) {
|
|
785
1262
|
case 'create': {
|
|
786
1263
|
const name = flags.name || positional[0];
|
|
@@ -872,7 +1349,7 @@ async function main() {
|
|
|
872
1349
|
break;
|
|
873
1350
|
}
|
|
874
1351
|
case 'compute': {
|
|
875
|
-
if (!subcommand || flags.help) {
|
|
1352
|
+
if (!subcommand || (flags.help && !COMPUTE_HELP[subcommand])) {
|
|
876
1353
|
showMenu({
|
|
877
1354
|
command: 'compute',
|
|
878
1355
|
title: 'compute',
|
|
@@ -899,6 +1376,10 @@ async function main() {
|
|
|
899
1376
|
});
|
|
900
1377
|
break;
|
|
901
1378
|
}
|
|
1379
|
+
if (flags.help && subcommand && COMPUTE_HELP[subcommand]) {
|
|
1380
|
+
subcommandHelp('compute', subcommand, COMPUTE_HELP[subcommand]);
|
|
1381
|
+
break;
|
|
1382
|
+
}
|
|
902
1383
|
switch (subcommand) {
|
|
903
1384
|
case 'plans': {
|
|
904
1385
|
// --location filters to types deployable in that location. Each
|
|
@@ -1474,7 +1955,7 @@ async function main() {
|
|
|
1474
1955
|
break;
|
|
1475
1956
|
}
|
|
1476
1957
|
case 'domain': {
|
|
1477
|
-
if (!subcommand || flags.help) {
|
|
1958
|
+
if (!subcommand || (flags.help && !DOMAIN_HELP[subcommand])) {
|
|
1478
1959
|
showMenu({
|
|
1479
1960
|
command: 'domain',
|
|
1480
1961
|
title: 'domain',
|
|
@@ -1494,6 +1975,10 @@ async function main() {
|
|
|
1494
1975
|
});
|
|
1495
1976
|
break;
|
|
1496
1977
|
}
|
|
1978
|
+
if (flags.help && subcommand && DOMAIN_HELP[subcommand]) {
|
|
1979
|
+
subcommandHelp('domain', subcommand, DOMAIN_HELP[subcommand]);
|
|
1980
|
+
break;
|
|
1981
|
+
}
|
|
1497
1982
|
switch (subcommand) {
|
|
1498
1983
|
case 'check': {
|
|
1499
1984
|
const name = flags.name || positional[0];
|
|
@@ -1684,6 +2169,7 @@ async function main() {
|
|
|
1684
2169
|
{ name: 'watch', description: 'Maintain a watchlist of CAs to monitor', hint: 'add <CA> --trigger "..." | list' },
|
|
1685
2170
|
{ name: 'brief', description: 'Show thesis + PnL brief for a position', hint: '<CA>' },
|
|
1686
2171
|
{ name: 'doctor', description: 'Health check for the wallet-trading subsystem', hint: '[--wallet <ref>]' },
|
|
2172
|
+
{ name: 'pay-preflight', description: 'Check the x402 pay flow is ready (chain, wallet, signing, USDC balance)', hint: '[--chain solana|base] [--min-usdc N]' },
|
|
1687
2173
|
{ name: 'smoke-test', description: 'End-to-end validation of wallet trading on Solana + Base', hint: '--wallet <ref> [--chain solana|base|all]' },
|
|
1688
2174
|
{ name: 'readiness', description: 'Go/no-go autonomous-trading readiness — sign, gas, quotes, daemon, open positions', hint: '--wallet <ref>' },
|
|
1689
2175
|
{ name: 'live-test', description: 'Execute tiny real round trips on Solana + Base, verify no leftover positions', hint: '--wallet <ref> --budget Nusdc [--chain ...]' },
|
|
@@ -3337,6 +3823,59 @@ async function main() {
|
|
|
3337
3823
|
process.exit(EXIT.GENERAL);
|
|
3338
3824
|
break;
|
|
3339
3825
|
}
|
|
3826
|
+
case 'pay-preflight': {
|
|
3827
|
+
// Five-question readiness check for the x402 pay flow. Local-only
|
|
3828
|
+
// siblings of this same module run automatically before every paid
|
|
3829
|
+
// command (see paidRequest/paidStreamRequest); this command is the
|
|
3830
|
+
// explicit, full-fat version with the RPC USDC balance check.
|
|
3831
|
+
const ppChain = flags.chain?.toLowerCase();
|
|
3832
|
+
if (ppChain && ppChain !== 'solana' && ppChain !== 'base') {
|
|
3833
|
+
err(`--chain must be solana or base (got ${ppChain})`, EXIT.BAD_INPUT);
|
|
3834
|
+
}
|
|
3835
|
+
const ppWallet = flags.wallet || undefined;
|
|
3836
|
+
const ppMinUsdcRaw = flags['min-usdc'];
|
|
3837
|
+
const ppMinUsdc = typeof ppMinUsdcRaw === 'string' ? Number(ppMinUsdcRaw) : undefined;
|
|
3838
|
+
if (ppMinUsdc !== undefined && (!Number.isFinite(ppMinUsdc) || ppMinUsdc < 0)) {
|
|
3839
|
+
err(`--min-usdc must be a non-negative number (got ${String(ppMinUsdcRaw)})`, EXIT.BAD_INPUT);
|
|
3840
|
+
}
|
|
3841
|
+
const { fullPreflight } = await import('./pay-preflight.js');
|
|
3842
|
+
const report = await fullPreflight({
|
|
3843
|
+
...(ppChain ? { chain: ppChain } : {}),
|
|
3844
|
+
...(ppWallet ? { walletRef: ppWallet } : {}),
|
|
3845
|
+
...(ppMinUsdc !== undefined ? { minUsdc: ppMinUsdc } : {}),
|
|
3846
|
+
...(passphrase ? { passphrase } : {}),
|
|
3847
|
+
});
|
|
3848
|
+
if (AGENT_MODE) {
|
|
3849
|
+
print(report);
|
|
3850
|
+
if (!report.ok)
|
|
3851
|
+
process.exit(EXIT.GENERAL);
|
|
3852
|
+
break;
|
|
3853
|
+
}
|
|
3854
|
+
// TTY rendering — mirrors the doctor command's layout.
|
|
3855
|
+
console.log();
|
|
3856
|
+
section('Pay preflight');
|
|
3857
|
+
kv('Verdict', report.ok ? `${t.success}ready${t.reset}` : `${t.error}not ready${t.reset}`);
|
|
3858
|
+
kv('Pay chain', report.payChain);
|
|
3859
|
+
kv('Wallet ID', report.walletId || `${t.muted}(none)${t.reset}`);
|
|
3860
|
+
kv('Address', report.walletAddress || `${t.muted}(unknown)${t.reset}`);
|
|
3861
|
+
kv('Can sign', report.canSign ? `${t.success}yes${t.reset}` : `${t.error}no${t.reset}`);
|
|
3862
|
+
if (report.usdc) {
|
|
3863
|
+
const bal = report.usdc.balance;
|
|
3864
|
+
kv('USDC balance', bal === null ? `${t.muted}(unknown)${t.reset}` : `${bal.toFixed(6)} USDC`);
|
|
3865
|
+
if (report.usdc.requiredMin > 0)
|
|
3866
|
+
kv('Required min', `${report.usdc.requiredMin.toFixed(6)} USDC`);
|
|
3867
|
+
if (report.usdc.ataStatus)
|
|
3868
|
+
kv('Solana ATA', report.usdc.ataStatus);
|
|
3869
|
+
}
|
|
3870
|
+
if (report.fix) {
|
|
3871
|
+
console.log();
|
|
3872
|
+
console.log(` ${t.warn}Fix:${t.reset} ${report.fix}`);
|
|
3873
|
+
}
|
|
3874
|
+
console.log();
|
|
3875
|
+
if (!report.ok)
|
|
3876
|
+
process.exit(EXIT.GENERAL);
|
|
3877
|
+
break;
|
|
3878
|
+
}
|
|
3340
3879
|
case 'smoke-test': {
|
|
3341
3880
|
const smokeWalletRef = flags.wallet || undefined;
|
|
3342
3881
|
if (!smokeWalletRef)
|
|
@@ -3917,7 +4456,7 @@ async function main() {
|
|
|
3917
4456
|
break;
|
|
3918
4457
|
}
|
|
3919
4458
|
case 'chat': {
|
|
3920
|
-
if (!subcommand || flags.help) {
|
|
4459
|
+
if (!subcommand || (flags.help && !CHAT_HELP[subcommand])) {
|
|
3921
4460
|
showMenu({
|
|
3922
4461
|
command: 'chat',
|
|
3923
4462
|
title: 'chat',
|
|
@@ -3936,6 +4475,10 @@ async function main() {
|
|
|
3936
4475
|
});
|
|
3937
4476
|
break;
|
|
3938
4477
|
}
|
|
4478
|
+
if (flags.help && subcommand && CHAT_HELP[subcommand]) {
|
|
4479
|
+
subcommandHelp('chat', subcommand, CHAT_HELP[subcommand]);
|
|
4480
|
+
break;
|
|
4481
|
+
}
|
|
3939
4482
|
switch (subcommand) {
|
|
3940
4483
|
case 'run': {
|
|
3941
4484
|
const intent = (positional.join(' ') || flags.intent || '').trim();
|