@palmyr/cli 1.8.3 → 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 +10 -18
- package/dist/cli.js +400 -13
- package/dist/cli.js.map +1 -1
- package/dist/sdk.d.ts +1 -0
- package/dist/sdk.js +14 -1
- package/dist/sdk.js.map +1 -1
- package/dist/vault.d.ts +5 -4
- package/dist/vault.js +5 -4
- package/dist/vault.js.map +1 -1
- package/dist/wallet-live-test.js +23 -8
- package/dist/wallet-live-test.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -229,11 +229,10 @@ function emitSessionOnlyWarning(write) {
|
|
|
229
229
|
const WALLET_HELP = {
|
|
230
230
|
create: [
|
|
231
231
|
{ flag: '--name <name>', desc: 'Wallet name', hint: 'default: "My Wallet"' },
|
|
232
|
-
{ flag: '--managed', desc: 'Create managed wallet with human oversight via passkey (single-create only)' },
|
|
233
232
|
{ flag: '--solana', desc: 'Materialize the Solana account only', hint: 'default: both chains' },
|
|
234
233
|
{ flag: '--base', desc: 'Materialize the Base/EVM account only', hint: 'pair with --solana for both (default)' },
|
|
235
234
|
{ flag: '--tag <name>', desc: 'Folder-like grouping tag', hint: 'e.g. palmyr-demo — required with --count' },
|
|
236
|
-
{ flag: '--count <N>', desc: 'Bulk-create N wallets in one call (1-500)', hint: '
|
|
235
|
+
{ flag: '--count <N>', desc: 'Bulk-create N wallets in one call (1-500)', hint: 'requires --tag' },
|
|
237
236
|
{ flag: '--name-prefix <p>', desc: 'Bulk name prefix; suffixed `-001..-N`', hint: 'default: same as --tag' },
|
|
238
237
|
{ flag: '--passphrase <p>', desc: 'Seal the mnemonic with this passphrase (≥8 chars) for durable recovery across reboot / OS-keychain loss / host migration', hint: 'or PALMYR_WALLET_PASSPHRASE env (env preferred — keeps phrase out of shell history). Interactive prompt on TTY when neither set.' },
|
|
239
238
|
{ flag: '--session-only', desc: 'OPT OUT of the passphrase fallback. Wallet is bound to this machine\'s OS keychain — dies on reboot/keyring loss/migration.', hint: 'use only for ephemeral / throwaway wallets where loss is acceptable' },
|
|
@@ -241,7 +240,6 @@ const WALLET_HELP = {
|
|
|
241
240
|
import: [
|
|
242
241
|
{ flag: '--mnemonic <words>', desc: 'BIP-39 mnemonic phrase (required)' },
|
|
243
242
|
{ flag: '--name <name>', desc: 'Wallet name', hint: 'default: "Imported Wallet"' },
|
|
244
|
-
{ flag: '--managed', desc: 'Import as managed wallet' },
|
|
245
243
|
{ flag: '--solana', desc: 'Materialize the Solana account only' },
|
|
246
244
|
{ flag: '--base', desc: 'Materialize the Base/EVM account only' },
|
|
247
245
|
{ flag: '--tag <name>', desc: 'Assign a tag at import time' },
|
|
@@ -449,6 +447,15 @@ const PHONE_HELP = {
|
|
|
449
447
|
{ flag: '(price)', desc: '$0.02 per call' },
|
|
450
448
|
{ flag: '(example)', desc: 'palmyr phone messages --id PN_abc' },
|
|
451
449
|
],
|
|
450
|
+
message: [
|
|
451
|
+
{ flag: '--id <MESSAGE_ID>', desc: 'SMS message id (Telnyx-supplied; positional also accepted)' },
|
|
452
|
+
{ flag: '(price)', desc: '$0.005 per readback — cheap so agents can poll until delivery_status is terminal' },
|
|
453
|
+
{ flag: '(example)', desc: 'palmyr phone message <message-id-from-sms-response>' },
|
|
454
|
+
],
|
|
455
|
+
'sms-status': [
|
|
456
|
+
{ flag: '<message-id>', desc: 'Alias for `palmyr phone message <id>` — readback by id' },
|
|
457
|
+
{ flag: '(price)', desc: '$0.005 per readback' },
|
|
458
|
+
],
|
|
452
459
|
calls: [
|
|
453
460
|
{ flag: '--id <PHONE_ID>', desc: 'Phone number id to list calls for (required; positional also accepted)' },
|
|
454
461
|
{ flag: '(price)', desc: '$0.02 per call' },
|
|
@@ -527,7 +534,7 @@ const EMAIL_HELP = {
|
|
|
527
534
|
create: [
|
|
528
535
|
{ flag: '--name <name>', desc: 'Inbox name (required)' },
|
|
529
536
|
{ flag: '--domain <domain>', desc: 'Wallet-owned domain to host the inbox on (optional)', hint: 'default: Palmyr-hosted domain' },
|
|
530
|
-
{ flag: '--wallet <id|name>', desc: '
|
|
537
|
+
{ flag: '--wallet <id|name|sol_pubkey>', desc: 'Inbox owner — vault id/name (resolves to its Solana address) or a raw Solana pubkey. Omit to use the paying wallet.', hint: 'E2E encryption is Ed25519, so the owner must always be a Solana address — Base addresses cannot own an inbox' },
|
|
531
538
|
{ flag: '(price)', desc: '$2.00 per inbox provisioned' },
|
|
532
539
|
{ flag: '(example)', desc: 'palmyr email create --name agent --domain example.com' },
|
|
533
540
|
],
|
|
@@ -733,6 +740,283 @@ const CHAT_HELP = {
|
|
|
733
740
|
{ flag: '(example)', desc: 'palmyr chat providers --capability web_search' },
|
|
734
741
|
],
|
|
735
742
|
};
|
|
743
|
+
// Help tables for the two social subsystems. Their presence is what gates
|
|
744
|
+
// `--help` from dispatching paid actions — see the `case 'twitter'` and
|
|
745
|
+
// `case 'tiktok'` blocks below. 1.8.3 had no entries here and the
|
|
746
|
+
// `case 'buy'` arm immediately called the $5 paid endpoint when a user
|
|
747
|
+
// (reasonably) ran `palmyr twitter buy --help`. Every entry below MUST
|
|
748
|
+
// flag the price for paid subcommands so future readers can scan it.
|
|
749
|
+
const TWITTER_HELP = {
|
|
750
|
+
import: [
|
|
751
|
+
{ flag: '<username>', desc: 'Twitter handle to import' },
|
|
752
|
+
{ flag: '--credentials-line "..."', desc: 'login:password:email:email_pw:totp_seed:ct0:auth_token format' },
|
|
753
|
+
{ flag: '--username --password ...', desc: 'Alternative: individual flags for each field' },
|
|
754
|
+
{ flag: '(price)', desc: 'Free — local vault only' },
|
|
755
|
+
],
|
|
756
|
+
list: [
|
|
757
|
+
{ flag: '--local', desc: 'Skip server check; show only locally-vaulted accounts' },
|
|
758
|
+
{ flag: '(price)', desc: 'Free local listing + paid lookups when --local is omitted (~$0.001 to enumerate server-side access)' },
|
|
759
|
+
],
|
|
760
|
+
info: [
|
|
761
|
+
{ flag: '<username>', desc: 'Account to inspect' },
|
|
762
|
+
{ flag: '(price)', desc: 'Free — local vault read' },
|
|
763
|
+
],
|
|
764
|
+
rename: [
|
|
765
|
+
{ flag: '<old>', desc: 'Current local handle' },
|
|
766
|
+
{ flag: '--to <new>', desc: 'New handle (after a real-server rename)' },
|
|
767
|
+
{ flag: '(price)', desc: 'Free — local-only metadata update' },
|
|
768
|
+
],
|
|
769
|
+
remove: [
|
|
770
|
+
{ flag: '<username>', desc: 'Account to remove' },
|
|
771
|
+
{ flag: '--confirm', desc: 'Required — local delete is irreversible' },
|
|
772
|
+
{ flag: '(price)', desc: 'Free — local vault only' },
|
|
773
|
+
],
|
|
774
|
+
totp: [
|
|
775
|
+
{ flag: '<username>', desc: 'Account whose current TOTP code to print' },
|
|
776
|
+
{ flag: '(price)', desc: 'Free — local TOTP generation' },
|
|
777
|
+
],
|
|
778
|
+
buy: [
|
|
779
|
+
{ flag: '(no args)', desc: 'Purchase the oldest ready X account from the supplier pool' },
|
|
780
|
+
{ flag: '(price)', desc: '$5 USDC — paid via x402. Account auto-imported into the local vault and session primed.' },
|
|
781
|
+
{ flag: '(example)', desc: 'palmyr twitter buy' },
|
|
782
|
+
],
|
|
783
|
+
login: [
|
|
784
|
+
{ flag: '<username>', desc: 'Force a fresh server-side session (browser runtime)' },
|
|
785
|
+
{ flag: '(price)', desc: '$0.005 USDC' },
|
|
786
|
+
],
|
|
787
|
+
'manual-login': [
|
|
788
|
+
{ flag: '<username>', desc: 'Open a remote browser session you sign in to manually' },
|
|
789
|
+
{ flag: '(price)', desc: 'Variable — server-side browser session cost' },
|
|
790
|
+
],
|
|
791
|
+
session: [
|
|
792
|
+
{ flag: '<username>', desc: 'Inspect cached server-side session status' },
|
|
793
|
+
{ flag: '(price)', desc: 'Free' },
|
|
794
|
+
],
|
|
795
|
+
post: [
|
|
796
|
+
{ flag: '<username>', desc: 'Account to post from' },
|
|
797
|
+
{ flag: '--body "..."', desc: 'Tweet body (required)' },
|
|
798
|
+
{ flag: '(price)', desc: '$0.001 USDC per post' },
|
|
799
|
+
],
|
|
800
|
+
reply: [
|
|
801
|
+
{ flag: '<username>', desc: 'Account to reply from' },
|
|
802
|
+
{ flag: '--to <url>', desc: 'Tweet URL to reply to' },
|
|
803
|
+
{ flag: '--body "..."', desc: 'Reply body' },
|
|
804
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
805
|
+
],
|
|
806
|
+
like: [
|
|
807
|
+
{ flag: '<username>', desc: 'Account doing the like' },
|
|
808
|
+
{ flag: '--tweet <url>', desc: 'Tweet to like' },
|
|
809
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
810
|
+
],
|
|
811
|
+
retweet: [
|
|
812
|
+
{ flag: '<username>', desc: 'Account doing the retweet' },
|
|
813
|
+
{ flag: '--tweet <url>', desc: 'Tweet to retweet' },
|
|
814
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
815
|
+
],
|
|
816
|
+
follow: [
|
|
817
|
+
{ flag: '<username>', desc: 'Account doing the follow' },
|
|
818
|
+
{ flag: '--user @handle', desc: 'User to follow' },
|
|
819
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
820
|
+
],
|
|
821
|
+
unfollow: [
|
|
822
|
+
{ flag: '<username>', desc: 'Account doing the unfollow' },
|
|
823
|
+
{ flag: '--user @handle', desc: 'User to unfollow' },
|
|
824
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
825
|
+
],
|
|
826
|
+
delete: [
|
|
827
|
+
{ flag: '<username>', desc: 'Account that posted the tweet' },
|
|
828
|
+
{ flag: '--tweet <url>', desc: 'Tweet to delete' },
|
|
829
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
830
|
+
],
|
|
831
|
+
'list-tweets': [
|
|
832
|
+
{ flag: '<username>', desc: 'Account whose timeline to fetch' },
|
|
833
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
834
|
+
],
|
|
835
|
+
bio: [
|
|
836
|
+
{ flag: '<username>', desc: 'Account whose bio to update' },
|
|
837
|
+
{ flag: '--text "..."', desc: 'New bio (<=160 chars)' },
|
|
838
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
839
|
+
],
|
|
840
|
+
name: [
|
|
841
|
+
{ flag: '<username>', desc: 'Account whose display name to update' },
|
|
842
|
+
{ flag: '--display "..."', desc: 'New display name' },
|
|
843
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
844
|
+
],
|
|
845
|
+
location: [
|
|
846
|
+
{ flag: '<username>', desc: 'Account whose location to update' },
|
|
847
|
+
{ flag: '--location "..."', desc: 'New location string' },
|
|
848
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
849
|
+
],
|
|
850
|
+
website: [
|
|
851
|
+
{ flag: '<username>', desc: 'Account whose profile website to update' },
|
|
852
|
+
{ flag: '--url https://...', desc: 'New website URL' },
|
|
853
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
854
|
+
],
|
|
855
|
+
pfp: [
|
|
856
|
+
{ flag: '<username>', desc: 'Account whose avatar to update' },
|
|
857
|
+
{ flag: '--file pic.png', desc: 'Image file (jpeg / png)' },
|
|
858
|
+
{ flag: '(price)', desc: '$0.005 USDC' },
|
|
859
|
+
],
|
|
860
|
+
banner: [
|
|
861
|
+
{ flag: '<username>', desc: 'Account whose banner to update' },
|
|
862
|
+
{ flag: '--file banner.png', desc: 'Image file' },
|
|
863
|
+
{ flag: '(price)', desc: '$0.005 USDC' },
|
|
864
|
+
],
|
|
865
|
+
username: [
|
|
866
|
+
{ flag: '<username>', desc: 'Current account handle' },
|
|
867
|
+
{ flag: '--to <new>', desc: 'New handle' },
|
|
868
|
+
{ flag: '(price)', desc: '$0.005 USDC' },
|
|
869
|
+
],
|
|
870
|
+
transfer: [
|
|
871
|
+
{ flag: '<username>', desc: 'Account to transfer' },
|
|
872
|
+
{ flag: '--to <wallet>', desc: 'Destination wallet address' },
|
|
873
|
+
{ flag: '--confirm', desc: 'Required — rotates password, revokes other sessions' },
|
|
874
|
+
{ flag: '(price)', desc: 'Free for vaulted accounts; ~$0.01 USDC to auto-register an imported-only account first' },
|
|
875
|
+
],
|
|
876
|
+
share: [
|
|
877
|
+
{ flag: '<username>', desc: 'Account to share' },
|
|
878
|
+
{ flag: '--with <wallet>', desc: 'Wallet to grant access to' },
|
|
879
|
+
{ flag: '(price)', desc: 'Free for shared access (no password rotation)' },
|
|
880
|
+
],
|
|
881
|
+
unshare: [
|
|
882
|
+
{ flag: '<username>', desc: 'Account to revoke share on' },
|
|
883
|
+
{ flag: '--from <wallet>', desc: 'Wallet to revoke' },
|
|
884
|
+
{ flag: '--rotate', desc: 'Also rotate password so cached cookies stop working' },
|
|
885
|
+
{ flag: '(price)', desc: 'Free; --rotate runs async like transfer (~30-90s)' },
|
|
886
|
+
],
|
|
887
|
+
claim: [
|
|
888
|
+
{ flag: '(no args)', desc: 'Pull every server-side X account the wallet can access into the local vault' },
|
|
889
|
+
{ flag: '(price)', desc: '~$0.001 USDC per account claimed (creds-decryption fee)' },
|
|
890
|
+
],
|
|
891
|
+
// Server-backed account registration. Without these entries, `palmyr twitter
|
|
892
|
+
// register --help` (and friends) fell through to the top-level menu instead
|
|
893
|
+
// of explaining their flags. Subcommands listed in the parent switch must
|
|
894
|
+
// always have an entry here so the help guard fires before the case body.
|
|
895
|
+
thread: [
|
|
896
|
+
{ flag: '<username>', desc: 'Account to post the thread from' },
|
|
897
|
+
{ flag: '--texts \'["...","..."]\'', desc: 'JSON array of tweets (in order)' },
|
|
898
|
+
{ flag: '--file path.json', desc: 'Alternative: read the JSON array from a file' },
|
|
899
|
+
{ flag: '(price)', desc: '$0.005 USDC' },
|
|
900
|
+
],
|
|
901
|
+
register: [
|
|
902
|
+
{ flag: '<username>', desc: 'Account handle' },
|
|
903
|
+
{ flag: '--password <pw>', desc: 'Required if the account is not already in the local vault' },
|
|
904
|
+
{ flag: '--login --email --totp-seed --auth-token --ct0', desc: 'Optional; auto-pulled from local vault when not passed' },
|
|
905
|
+
{ flag: '--country <CC>', desc: 'Optional residency hint stored alongside the encrypted credentials' },
|
|
906
|
+
{ flag: '(price)', desc: 'Free — server tests login + encrypts creds at rest. Enables scheduling.' },
|
|
907
|
+
],
|
|
908
|
+
unregister: [
|
|
909
|
+
{ flag: '<username-or-id>', desc: 'Handle or 32-char hex account id' },
|
|
910
|
+
{ flag: '(price)', desc: 'Free — wipes server-side credentials, account no longer schedulable' },
|
|
911
|
+
],
|
|
912
|
+
registered: [
|
|
913
|
+
{ flag: '(no args)', desc: 'List every server-registered X account this wallet owns' },
|
|
914
|
+
{ flag: '(price)', desc: 'Free' },
|
|
915
|
+
],
|
|
916
|
+
schedule: [
|
|
917
|
+
{ flag: '<username>', desc: 'Account to post from (must be `register`-ed)' },
|
|
918
|
+
{ flag: '--at "ISO8601"', desc: 'When to fire (e.g. --at "2026-05-15T14:00:00Z")' },
|
|
919
|
+
{ flag: '--body "..."', desc: 'Text-only post' },
|
|
920
|
+
{ flag: '--texts \'["..."]\' / --file path.json', desc: 'Thread' },
|
|
921
|
+
{ flag: '--image / --video / --media-json', desc: 'Media attachments' },
|
|
922
|
+
{ flag: '--community <id>', desc: 'Post into an X Community' },
|
|
923
|
+
{ flag: '(price)', desc: '$0.001 USDC (text) or $0.005 USDC (thread / media) — paid up front' },
|
|
924
|
+
],
|
|
925
|
+
queue: [
|
|
926
|
+
{ flag: '--status pending|in_progress|completed|failed|cancelled', desc: 'Filter by status' },
|
|
927
|
+
{ flag: '--from / --to "ISO8601"', desc: 'Filter by post-at window' },
|
|
928
|
+
{ flag: '--account-id <id>', desc: 'Filter to one account' },
|
|
929
|
+
{ flag: '--limit <n>', desc: 'Cap result count' },
|
|
930
|
+
{ flag: '(price)', desc: 'Free' },
|
|
931
|
+
],
|
|
932
|
+
cancel: [
|
|
933
|
+
{ flag: '<schedule-id>', desc: 'Id from `palmyr twitter queue`' },
|
|
934
|
+
{ flag: '(price)', desc: 'Free — only cancels pending posts; in-flight ones are already settled' },
|
|
935
|
+
],
|
|
936
|
+
status: [
|
|
937
|
+
{ flag: '<username>', desc: 'Account to inspect' },
|
|
938
|
+
{ flag: '(price)', desc: 'Server-side liveness/shadow-ban check (not yet wired — see `palmyr twitter session` for cached login state)' },
|
|
939
|
+
],
|
|
940
|
+
'pool-add': [
|
|
941
|
+
{ flag: '--credentials-line "..."', desc: 'Single account creds (login:pw:email:email_pw[:2fa[:ct0:auth_token]])' },
|
|
942
|
+
{ flag: '--file path.txt', desc: 'Bulk: one credentials-line per row (# = comment)' },
|
|
943
|
+
{ flag: '--price <USDC>', desc: 'Required — what `twitter buy` will charge per account' },
|
|
944
|
+
{ flag: '--country <CC>', desc: 'Optional metadata' },
|
|
945
|
+
{ flag: '--age 1y|2y|3y|...', desc: 'Optional age category metadata' },
|
|
946
|
+
{ flag: '(auth)', desc: 'Admin-signed call — requires PALMYR_ADMIN_KEY' },
|
|
947
|
+
{ flag: '(price)', desc: 'Free — server-side seeding by pool operator' },
|
|
948
|
+
],
|
|
949
|
+
'pool-status': [
|
|
950
|
+
{ flag: '(no args)', desc: 'Available / sold / reserved counts in the X account pool' },
|
|
951
|
+
{ flag: '(auth)', desc: 'Admin-signed call — requires PALMYR_ADMIN_KEY' },
|
|
952
|
+
{ flag: '(price)', desc: 'Free' },
|
|
953
|
+
],
|
|
954
|
+
};
|
|
955
|
+
const TIKTOK_HELP = {
|
|
956
|
+
import: [
|
|
957
|
+
{ flag: '<username>', desc: 'TikTok handle to import' },
|
|
958
|
+
{ flag: '--sessionid <s> --csrf <c> --webid <w>', desc: 'Cookies from a logged-in TikTok browser' },
|
|
959
|
+
{ flag: '--credentials-line "..."', desc: 'Marketplace login:pw:email:email_pw format' },
|
|
960
|
+
{ flag: '(price)', desc: 'Free — local vault only' },
|
|
961
|
+
],
|
|
962
|
+
list: [
|
|
963
|
+
{ flag: '(no args)', desc: 'List all local TikTok accounts' },
|
|
964
|
+
{ flag: '(price)', desc: 'Free' },
|
|
965
|
+
],
|
|
966
|
+
info: [{ flag: '<username>', desc: 'Show one account' }, { flag: '(price)', desc: 'Free' }],
|
|
967
|
+
rename: [
|
|
968
|
+
{ flag: '<old>', desc: 'Current local handle' },
|
|
969
|
+
{ flag: '--to <new>', desc: 'New handle' },
|
|
970
|
+
{ flag: '(price)', desc: 'Free — local-only metadata update' },
|
|
971
|
+
],
|
|
972
|
+
remove: [
|
|
973
|
+
{ flag: '<username>', desc: 'Account to delete from local vault' },
|
|
974
|
+
{ flag: '--confirm', desc: 'Required' },
|
|
975
|
+
{ flag: '(price)', desc: 'Free' },
|
|
976
|
+
],
|
|
977
|
+
totp: [{ flag: '<username>', desc: 'Print current TOTP code' }, { flag: '(price)', desc: 'Free' }],
|
|
978
|
+
login: [
|
|
979
|
+
{ flag: '<username>', desc: 'Validate cookies and cache the session' },
|
|
980
|
+
{ flag: '(price)', desc: '$0.005 USDC' },
|
|
981
|
+
],
|
|
982
|
+
session: [{ flag: '<username>', desc: 'Check cached session' }, { flag: '(price)', desc: 'Free' }],
|
|
983
|
+
post: [
|
|
984
|
+
{ flag: '<username>', desc: 'Account to post from' },
|
|
985
|
+
{ flag: '--file video.mp4', desc: 'Video file' },
|
|
986
|
+
{ flag: '--caption "..."', desc: 'Caption' },
|
|
987
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
988
|
+
],
|
|
989
|
+
follow: [
|
|
990
|
+
{ flag: '<username>', desc: 'Account doing the follow' },
|
|
991
|
+
{ flag: '--user @handle', desc: 'User to follow' },
|
|
992
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
993
|
+
],
|
|
994
|
+
like: [
|
|
995
|
+
{ flag: '<username>', desc: 'Account doing the like' },
|
|
996
|
+
{ flag: '--video <url>', desc: 'Video URL to like' },
|
|
997
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
998
|
+
],
|
|
999
|
+
delete: [
|
|
1000
|
+
{ flag: '<username>', desc: 'Account that posted the video' },
|
|
1001
|
+
{ flag: '--video <url>', desc: 'Video to delete' },
|
|
1002
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
1003
|
+
],
|
|
1004
|
+
bio: [
|
|
1005
|
+
{ flag: '<username>', desc: 'Account whose bio to update' },
|
|
1006
|
+
{ flag: '--text "..."', desc: 'New bio (<=80 chars)' },
|
|
1007
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
1008
|
+
],
|
|
1009
|
+
name: [
|
|
1010
|
+
{ flag: '<username>', desc: 'Account whose display name to update' },
|
|
1011
|
+
{ flag: '--display "..."', desc: 'New display name (<=30 chars)' },
|
|
1012
|
+
{ flag: '(price)', desc: '$0.001 USDC' },
|
|
1013
|
+
],
|
|
1014
|
+
pfp: [
|
|
1015
|
+
{ flag: '<username>', desc: 'Account whose avatar to update' },
|
|
1016
|
+
{ flag: '--file pic.png', desc: 'Image file' },
|
|
1017
|
+
{ flag: '(price)', desc: '$0.005 USDC' },
|
|
1018
|
+
],
|
|
1019
|
+
};
|
|
736
1020
|
/**
|
|
737
1021
|
* Render a per-command menu (no subcommand given). On a TTY → Ink MenuScreen
|
|
738
1022
|
* with the Palmyr aesthetic. In agent mode → flat JSON listing the available
|
|
@@ -1042,6 +1326,7 @@ async function main() {
|
|
|
1042
1326
|
{ name: 'release', description: 'Release a phone number', hint: '--id PHONE_ID' },
|
|
1043
1327
|
{ name: 'sms', description: 'Send an SMS', hint: '--id ID --to +1... --body "hi"' },
|
|
1044
1328
|
{ name: 'messages', description: 'Read SMS messages received on a number', hint: '--id PHONE_ID' },
|
|
1329
|
+
{ name: 'message', description: 'Get one SMS message by id (incl. delivery status)', hint: '--id MESSAGE_ID' },
|
|
1045
1330
|
{ name: 'call', description: 'Place a voice call', hint: '--id ID --to +1... --tts "hello"' },
|
|
1046
1331
|
{ name: 'calls', description: 'List calls placed/received on a number', hint: '--id PHONE_ID' },
|
|
1047
1332
|
{ name: 'call-info', description: 'Get details on a single call', hint: '--call CALL_CONTROL_ID' },
|
|
@@ -1149,6 +1434,18 @@ async function main() {
|
|
|
1149
1434
|
const data = await ao.phoneMessages(id);
|
|
1150
1435
|
return print(data);
|
|
1151
1436
|
}
|
|
1437
|
+
case 'message':
|
|
1438
|
+
case 'sms-status': {
|
|
1439
|
+
// Per-message readback. Mirrors `phone call-info` for SMS:
|
|
1440
|
+
// the Telnyx webhook updates delivery_status on the row, and this
|
|
1441
|
+
// endpoint serves it back. Useful when the immediate sms response
|
|
1442
|
+
// is 'queued' and the caller wants to confirm delivery.
|
|
1443
|
+
const messageId = flags.id || flags.message || positional[0];
|
|
1444
|
+
if (!messageId)
|
|
1445
|
+
err('Usage: palmyr phone message <message-id>');
|
|
1446
|
+
const data = await ao.phoneMessage(messageId);
|
|
1447
|
+
return print(data);
|
|
1448
|
+
}
|
|
1152
1449
|
case 'calls': {
|
|
1153
1450
|
const id = flags.id || positional[0];
|
|
1154
1451
|
if (!id)
|
|
@@ -1287,13 +1584,58 @@ async function main() {
|
|
|
1287
1584
|
switch (subcommand) {
|
|
1288
1585
|
case 'create': {
|
|
1289
1586
|
const name = flags.name || positional[0];
|
|
1290
|
-
const
|
|
1587
|
+
const walletInput = flags.wallet;
|
|
1291
1588
|
const domain = flags.domain;
|
|
1292
1589
|
if (!name)
|
|
1293
1590
|
err('--name required (e.g. palmyr email create --name hello [--domain example.com])');
|
|
1591
|
+
// --wallet accepts three forms: vault id, vault name, or a raw
|
|
1592
|
+
// Solana base58 pubkey. The server only accepts a Solana pubkey
|
|
1593
|
+
// (E2E encryption is Ed25519→X25519), so resolve id/name to a
|
|
1594
|
+
// pubkey here before the request. Raw pubkeys pass through.
|
|
1595
|
+
// Doing this resolution client-side also means a Base-paying user
|
|
1596
|
+
// doesn't need a Solana wallet — their vault already has one
|
|
1597
|
+
// (single mnemonic, both chains), and we can find it without
|
|
1598
|
+
// making the server reach back for client-side keys.
|
|
1599
|
+
let walletAddress;
|
|
1600
|
+
if (walletInput) {
|
|
1601
|
+
const looksLikeSolPubkey = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(walletInput);
|
|
1602
|
+
if (looksLikeSolPubkey) {
|
|
1603
|
+
walletAddress = walletInput;
|
|
1604
|
+
}
|
|
1605
|
+
else {
|
|
1606
|
+
const { listVaultWallets } = await import('./vault.js');
|
|
1607
|
+
const wallets = listVaultWallets();
|
|
1608
|
+
const match = wallets.find(w => w.id === walletInput || w.name === walletInput);
|
|
1609
|
+
if (!match)
|
|
1610
|
+
err(`--wallet "${walletInput}" did not match any vault id, name, or look like a Solana pubkey`);
|
|
1611
|
+
if (!match.solanaAddress)
|
|
1612
|
+
err(`Wallet "${walletInput}" has no Solana address — email inboxes require one (E2E uses Ed25519). Re-create with: palmyr wallet create`);
|
|
1613
|
+
walletAddress = match.solanaAddress;
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
else {
|
|
1617
|
+
// No --wallet: the server would normally default the inbox owner
|
|
1618
|
+
// to the x402 payer. That works for Solana-paid calls but
|
|
1619
|
+
// 400s on Base because the payer is an EVM address. Auto-fill
|
|
1620
|
+
// the *paying wallet's* Solana address here so a single
|
|
1621
|
+
// mnemonic-derived vault wallet works on either pay chain.
|
|
1622
|
+
const cfg = loadConfig();
|
|
1623
|
+
const payChain = (cfg.defaultPayChain || 'solana');
|
|
1624
|
+
if (payChain === 'base') {
|
|
1625
|
+
const { listVaultWallets } = await import('./vault.js');
|
|
1626
|
+
const wallets = listVaultWallets();
|
|
1627
|
+
const targetId = cfg.defaultPayWalletId || process.env.PALMYR_PAY_WALLET;
|
|
1628
|
+
const paying = (targetId && wallets.find(w => w.id === targetId)) || wallets.find(w => w.evmAddress && w.solanaAddress);
|
|
1629
|
+
if (paying?.solanaAddress)
|
|
1630
|
+
walletAddress = paying.solanaAddress;
|
|
1631
|
+
// If no Solana address is reachable, fall through and let the
|
|
1632
|
+
// server return its actionable 400 — silent failure would be
|
|
1633
|
+
// worse than a clear error.
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1294
1636
|
const spin = new Spinner();
|
|
1295
1637
|
spin.start('Creating inbox...');
|
|
1296
|
-
const data = await ao.emailCreate(name,
|
|
1638
|
+
const data = await ao.emailCreate(name, walletAddress, domain);
|
|
1297
1639
|
spin.stop('Inbox created', true);
|
|
1298
1640
|
return print(data);
|
|
1299
1641
|
}
|
|
@@ -1589,6 +1931,26 @@ async function main() {
|
|
|
1589
1931
|
const localKeyPath = generatedKeyMeta?.privateKeyPath
|
|
1590
1932
|
|| (pubkeyFile ? pubkeyFile.replace(/\.pub$/, '').replace('~', homedir()) : undefined)
|
|
1591
1933
|
|| (explicitKeyPath ? explicitKeyPath.replace('~', homedir()) : undefined);
|
|
1934
|
+
// --ssh-key <id> uploads a server-side key but doesn't tell us where
|
|
1935
|
+
// the matching private key lives on this machine. Without
|
|
1936
|
+
// --key-path, the SSH readiness gate later silently skips and the
|
|
1937
|
+
// deploy reports `ssh: skipped` while still marking the server as
|
|
1938
|
+
// "ready" — a real foot-gun (dogfood report 2026-05-25 hit this
|
|
1939
|
+
// exact path). Be loud about it now, before anyone pays.
|
|
1940
|
+
if (sshKeyIds && !localKeyPath) {
|
|
1941
|
+
const msg = 'Warning: --ssh-key <id> uploaded the public key server-side, ' +
|
|
1942
|
+
'but no matching private key was passed to this CLI. ' +
|
|
1943
|
+
'SSH readiness cannot be verified locally — `compute wait` and the ' +
|
|
1944
|
+
'inline --wait check will skip the SSH gate. Pass ' +
|
|
1945
|
+
'`--key-path /path/to/private_key` (or `--private-key`) to ' +
|
|
1946
|
+
'enable the verification.';
|
|
1947
|
+
if (AGENT_MODE) {
|
|
1948
|
+
process.stderr.write(JSON.stringify({ event: 'warning', code: 'ssh_key_no_local_key', message: msg }) + '\n');
|
|
1949
|
+
}
|
|
1950
|
+
else {
|
|
1951
|
+
process.stderr.write(`\n${msg}\n\n`);
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1592
1954
|
// Progress events to stderr — default ON in agent mode so a
|
|
1593
1955
|
// long deploy isn't a 10-minute silence. Pass --no-progress to
|
|
1594
1956
|
// opt out. Stdout still gets one final JSON object, so jq
|
|
@@ -2169,7 +2531,7 @@ async function main() {
|
|
|
2169
2531
|
subtitle: 'Non-custodial HD wallet',
|
|
2170
2532
|
footerLeft: 'Solana + Base wallet operations',
|
|
2171
2533
|
commands: [
|
|
2172
|
-
{ name: 'create', description: 'Create one or many wallets', hint: '[--tag X --count 100] [--solana|--base]
|
|
2534
|
+
{ name: 'create', description: 'Create one or many wallets', hint: '[--tag X --count 100] [--solana|--base]' },
|
|
2173
2535
|
{ name: 'import', description: 'Import from mnemonic', hint: '--mnemonic "..." [--tag X]' },
|
|
2174
2536
|
{ name: 'list', description: 'List all wallets', hint: '[--tag <name>]' },
|
|
2175
2537
|
{ name: 'info', description: 'Wallet details', hint: 'WALLET_ID' },
|
|
@@ -2183,7 +2545,6 @@ async function main() {
|
|
|
2183
2545
|
{ name: 'api-key', description: 'Create agent API key', hint: 'WALLET_ID --name my-agent' },
|
|
2184
2546
|
{ name: 'config', description: 'Get agent config', hint: 'WALLET_ID' },
|
|
2185
2547
|
{ name: 'use', description: 'Set default pay wallet', hint: 'WALLET_ID' },
|
|
2186
|
-
{ name: 'request-approval', description: 'Request human approval (managed)', hint: 'WALLET_ID --action limits --daily 100' },
|
|
2187
2548
|
{ name: 'buy', description: 'Open a trading position', hint: 'solana <CA> --amount 0.5sol --thesis "..."' },
|
|
2188
2549
|
{ name: 'cohort', description: 'Split a buy across N derived wallets with jitter (Phase 4c)', hint: 'buy <CHAIN> <CA> --total ... --split N' },
|
|
2189
2550
|
{ name: 'template', description: 'Manage YAML strategy templates', hint: 'list | show <name> | path <name> | delete <name>' },
|
|
@@ -4978,7 +5339,12 @@ async function main() {
|
|
|
4978
5339
|
log(`auto-imported @${username} from server (${sourceLabel} → local vault)`);
|
|
4979
5340
|
return summary;
|
|
4980
5341
|
};
|
|
4981
|
-
|
|
5342
|
+
// Help guard. `palmyr twitter buy --help` MUST never dispatch to the
|
|
5343
|
+
// paid `case 'buy'` below — 1.8.3 had no guard here and a real user
|
|
5344
|
+
// got charged $5 for a help command. Falls back to the top-level menu
|
|
5345
|
+
// when the subcommand has no per-subcommand help entry, so even an
|
|
5346
|
+
// unrecognized `palmyr twitter <whatever> --help` is safe to run.
|
|
5347
|
+
if (!subcommand || (flags.help && !TWITTER_HELP[subcommand])) {
|
|
4982
5348
|
showMenu({
|
|
4983
5349
|
command: 'twitter',
|
|
4984
5350
|
title: 'twitter',
|
|
@@ -4994,7 +5360,10 @@ async function main() {
|
|
|
4994
5360
|
{ name: 'buy', description: 'Purchase an aged account (requires server supplier config)', hint: '--age 1y --country US' },
|
|
4995
5361
|
{ name: 'login', description: 'Force a fresh server-side session (requires browser runtime)', hint: '<username>' },
|
|
4996
5362
|
{ name: 'post', description: 'Post a tweet (requires server browser runtime)', hint: '<username> --body "..."' },
|
|
4997
|
-
|
|
5363
|
+
// `status` is not wired yet (Phase 3). Hidden from this menu so
|
|
5364
|
+
// users don't try a command that will only error; `session`
|
|
5365
|
+
// covers the most useful subset (cached server-side login state).
|
|
5366
|
+
{ name: 'session', description: 'Inspect cached server-side session for an account', hint: '<username>' },
|
|
4998
5367
|
{ name: 'transfer', description: 'Hand an account to another wallet (rotates password; auto-registers if needed)', hint: '<username> --to <wallet> --confirm' },
|
|
4999
5368
|
{ name: 'share', description: 'Grant another wallet shared access', hint: '<username> --with <wallet>' },
|
|
5000
5369
|
{ name: 'unshare', description: 'Revoke a wallet’s shared access', hint: '<username> --from <wallet> [--rotate]' },
|
|
@@ -5004,6 +5373,10 @@ async function main() {
|
|
|
5004
5373
|
});
|
|
5005
5374
|
return;
|
|
5006
5375
|
}
|
|
5376
|
+
if (flags.help && subcommand && TWITTER_HELP[subcommand]) {
|
|
5377
|
+
subcommandHelp('twitter', subcommand, TWITTER_HELP[subcommand]);
|
|
5378
|
+
return;
|
|
5379
|
+
}
|
|
5007
5380
|
switch (subcommand) {
|
|
5008
5381
|
case 'import': {
|
|
5009
5382
|
// Option 1: --credentials-line "login:password:email:email_pw:2fa:ct0:auth_token"
|
|
@@ -5941,14 +6314,22 @@ async function main() {
|
|
|
5941
6314
|
return print(data);
|
|
5942
6315
|
}
|
|
5943
6316
|
case 'status': {
|
|
5944
|
-
|
|
6317
|
+
// `twitter status` was meant to check live shadow-ban / suspension
|
|
6318
|
+
// state via a server-side probe. That's a Phase 3 build (needs a
|
|
6319
|
+
// browser runtime on the server to render the profile). Until
|
|
6320
|
+
// then, point users at `session` for the cached login state and
|
|
6321
|
+
// `info` for vault metadata — together they cover the practical
|
|
6322
|
+
// "is this account still usable from the agent's side" question.
|
|
6323
|
+
err(`twitter status: not wired yet (Phase 3). Closest equivalents available today: ` +
|
|
6324
|
+
`\`palmyr twitter session <username>\` (cached server-side login validity) and ` +
|
|
6325
|
+
`\`palmyr twitter info <username>\` (local vault record).`, EXIT.GENERAL);
|
|
5945
6326
|
}
|
|
5946
6327
|
case 'transfer': {
|
|
5947
6328
|
// Hand the X account to another wallet. End-to-end one-command:
|
|
5948
6329
|
// 1. If the account is only in the local vault, auto-register it
|
|
5949
6330
|
// with the server (uploads encrypted creds; $0.01 USDC).
|
|
5950
6331
|
// 2. Server rotates the password and revokes other sessions
|
|
5951
|
-
// ($0.
|
|
6332
|
+
// ($0.01 USDC ownership proof).
|
|
5952
6333
|
// 3. Atomically flips ownership in the DB.
|
|
5953
6334
|
// Receiver picks up the rotated credentials via `palmyr twitter
|
|
5954
6335
|
// list` (which now surfaces server-side accounts) and/or `claim`.
|
|
@@ -6320,7 +6701,9 @@ async function main() {
|
|
|
6320
6701
|
case 'tiktok': {
|
|
6321
6702
|
const sv = await import('./social-vault.js');
|
|
6322
6703
|
const platform = 'tiktok';
|
|
6323
|
-
|
|
6704
|
+
// Same help guard as `twitter` — prevents `--help` from dispatching
|
|
6705
|
+
// a paid subcommand. Same bug class lived here too in 1.8.3.
|
|
6706
|
+
if (!subcommand || (flags.help && !TIKTOK_HELP[subcommand])) {
|
|
6324
6707
|
showMenu({
|
|
6325
6708
|
command: 'tiktok',
|
|
6326
6709
|
title: 'tiktok',
|
|
@@ -6347,6 +6730,10 @@ async function main() {
|
|
|
6347
6730
|
});
|
|
6348
6731
|
return;
|
|
6349
6732
|
}
|
|
6733
|
+
if (flags.help && subcommand && TIKTOK_HELP[subcommand]) {
|
|
6734
|
+
subcommandHelp('tiktok', subcommand, TIKTOK_HELP[subcommand]);
|
|
6735
|
+
return;
|
|
6736
|
+
}
|
|
6350
6737
|
switch (subcommand) {
|
|
6351
6738
|
case 'import': {
|
|
6352
6739
|
// Two formats:
|