@moxxy/cli 1.4.0 → 1.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moxxy/cli",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "CLI for the Moxxy agentic framework — manage agents, skills, plugins, channels, and vaults from the terminal",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,16 +49,12 @@ async function createChannel(client, args) {
49
49
  message: 'Channel type',
50
50
  options: [
51
51
  { value: 'telegram', label: 'Telegram', hint: 'BotFather bot token required' },
52
- { value: 'discord', label: 'Discord', hint: 'coming soon (scaffold)' },
52
+ { value: 'discord', label: 'Discord', hint: 'Discord bot token required' },
53
+ { value: 'whatsapp', label: 'WhatsApp', hint: 'WhatsApp Business API token required' },
53
54
  ],
54
55
  });
55
56
  handleCancel(channelType);
56
57
 
57
- if (channelType === 'discord') {
58
- p.log.info('Discord channel support is coming soon.');
59
- return;
60
- }
61
-
62
58
  // Step 1: Select agent to bind
63
59
  let agentId;
64
60
  try {
@@ -80,33 +76,84 @@ async function createChannel(client, args) {
80
76
  return;
81
77
  }
82
78
 
83
- // Step 2: Get bot token
84
- p.note(
85
- '1. Open Telegram and talk to @BotFather\n' +
86
- '2. Send /newbot and follow the prompts\n' +
87
- '3. Copy the bot token',
88
- 'Telegram Bot Setup'
89
- );
79
+ // Step 2: Get credentials based on channel type
80
+ let botToken, displayName, config;
90
81
 
91
- const botToken = await p.password({
92
- message: 'Paste your Telegram bot token',
93
- });
94
- handleCancel(botToken);
82
+ if (channelType === 'telegram') {
83
+ p.note(
84
+ '1. Open Telegram and talk to @BotFather\n' +
85
+ '2. Send /newbot and follow the prompts\n' +
86
+ '3. Copy the bot token',
87
+ 'Telegram Bot Setup'
88
+ );
89
+
90
+ botToken = await p.password({
91
+ message: 'Paste your Telegram bot token',
92
+ });
93
+ handleCancel(botToken);
94
+ } else if (channelType === 'discord') {
95
+ p.note(
96
+ '1. Go to https://discord.com/developers/applications\n' +
97
+ '2. Create a new application → Bot → copy the bot token\n' +
98
+ '3. Enable MESSAGE CONTENT intent under Bot → Privileged Intents\n' +
99
+ '4. Invite the bot to your server with the Messages scope',
100
+ 'Discord Bot Setup'
101
+ );
102
+
103
+ botToken = await p.password({
104
+ message: 'Paste your Discord bot token',
105
+ });
106
+ handleCancel(botToken);
107
+ } else if (channelType === 'whatsapp') {
108
+ p.note(
109
+ '1. Go to https://developers.facebook.com and create an app\n' +
110
+ '2. Add the WhatsApp product to your app\n' +
111
+ '3. Copy the permanent access token and Phone Number ID\n' +
112
+ '4. Configure the webhook URL to: <your-moxxy-url>/v1/channels/whatsapp/webhook',
113
+ 'WhatsApp Business API Setup'
114
+ );
115
+
116
+ botToken = await p.password({
117
+ message: 'Paste your WhatsApp access token',
118
+ });
119
+ handleCancel(botToken);
120
+
121
+ const phoneNumberId = await p.text({
122
+ message: 'Phone Number ID (from WhatsApp Business API)',
123
+ });
124
+ handleCancel(phoneNumberId);
125
+
126
+ const verifyToken = await p.text({
127
+ message: 'Webhook verify token (you choose this, used to verify the webhook)',
128
+ placeholder: 'my-verify-token',
129
+ });
130
+ handleCancel(verifyToken);
95
131
 
96
- const displayName = await p.text({
132
+ config = {
133
+ phone_number_id: phoneNumberId,
134
+ verify_token: verifyToken || undefined,
135
+ };
136
+ }
137
+
138
+ displayName = await p.text({
97
139
  message: 'Display name for this channel',
98
- placeholder: 'My Moxxy Bot',
140
+ placeholder: channelType === 'telegram' ? 'My Telegram Bot' :
141
+ channelType === 'discord' ? 'My Discord Bot' : 'My WhatsApp Bot',
99
142
  });
100
143
  handleCancel(displayName);
101
144
 
145
+ const defaultName = channelType === 'telegram' ? 'Telegram Bot' :
146
+ channelType === 'discord' ? 'Discord Bot' : 'WhatsApp Bot';
147
+
102
148
  // Step 3: Create channel
103
149
  let channel;
104
150
  try {
105
151
  channel = await withSpinner('Creating channel...', () =>
106
152
  client.request('/v1/channels', 'POST', {
107
153
  channel_type: channelType,
108
- display_name: displayName || 'Telegram Bot',
154
+ display_name: displayName || defaultName,
109
155
  bot_token: botToken,
156
+ ...(config ? { config } : {}),
110
157
  }), 'Channel created.');
111
158
 
112
159
  showResult('Channel Created', {
@@ -121,11 +168,27 @@ async function createChannel(client, args) {
121
168
  }
122
169
 
123
170
  // Step 4: Wait for pairing code
124
- p.note(
125
- '1. Open your Telegram bot and send /start\n' +
126
- '2. Copy the 6-digit pairing code',
127
- 'Pair your chat'
128
- );
171
+ if (channelType === 'telegram') {
172
+ p.note(
173
+ '1. Open your Telegram bot and send /start\n' +
174
+ '2. Copy the 6-digit pairing code',
175
+ 'Pair your chat'
176
+ );
177
+ } else if (channelType === 'discord') {
178
+ p.note(
179
+ '1. Send a message to your Discord bot or in a channel it can see\n' +
180
+ '2. The bot will respond with a pairing code if not yet paired\n' +
181
+ '3. Copy the 6-digit pairing code',
182
+ 'Pair your chat'
183
+ );
184
+ } else if (channelType === 'whatsapp') {
185
+ p.note(
186
+ '1. Send a message to your WhatsApp number\n' +
187
+ '2. The bot will respond with a pairing code\n' +
188
+ '3. Copy the 6-digit pairing code',
189
+ 'Pair your chat'
190
+ );
191
+ }
129
192
 
130
193
  const code = await p.text({
131
194
  message: 'Enter 6-digit pairing code',
@@ -650,12 +650,12 @@ export async function runInit(client, args) {
650
650
 
651
651
  // Step 6: Channel setup (optional)
652
652
  p.note(
653
- 'Channels enable agent communication via Telegram or Discord.\n' +
653
+ 'Channels enable agent communication via Telegram, Discord, or WhatsApp.\n' +
654
654
  'You can set up channels later with: moxxy channel create',
655
655
  'Channels'
656
656
  );
657
657
  const setupChannel = await p.confirm({
658
- message: 'Set up a messaging channel (Telegram/Discord)?',
658
+ message: 'Set up a messaging channel?',
659
659
  initialValue: false,
660
660
  });
661
661
  handleCancel(setupChannel);
@@ -665,11 +665,14 @@ export async function runInit(client, args) {
665
665
  message: 'Channel type',
666
666
  options: [
667
667
  { value: 'telegram', label: 'Telegram', hint: 'BotFather bot token required' },
668
- { value: 'discord', label: 'Discord', hint: 'coming soon (scaffold)' },
668
+ { value: 'discord', label: 'Discord', hint: 'Discord bot token required' },
669
+ { value: 'whatsapp', label: 'WhatsApp', hint: 'WhatsApp Business API token required' },
669
670
  ],
670
671
  });
671
672
  handleCancel(channelType);
672
673
 
674
+ let botToken, displayName, channelConfig;
675
+
673
676
  if (channelType === 'telegram') {
674
677
  p.note(
675
678
  '1. Open Telegram and talk to @BotFather\n' +
@@ -678,88 +681,148 @@ export async function runInit(client, args) {
678
681
  'Telegram Bot Setup'
679
682
  );
680
683
 
681
- const botToken = await p.password({
684
+ botToken = await p.password({
682
685
  message: 'Paste your Telegram bot token',
683
686
  });
684
687
  handleCancel(botToken);
688
+ } else if (channelType === 'discord') {
689
+ p.note(
690
+ '1. Go to https://discord.com/developers/applications\n' +
691
+ '2. Create a new application → Bot → copy the bot token\n' +
692
+ '3. Enable MESSAGE CONTENT intent under Bot → Privileged Intents\n' +
693
+ '4. Invite the bot to your server with the Messages scope',
694
+ 'Discord Bot Setup'
695
+ );
685
696
 
686
- const displayName = await p.text({
687
- message: 'Display name for this channel',
688
- placeholder: 'My Moxxy Bot',
697
+ botToken = await p.password({
698
+ message: 'Paste your Discord bot token',
689
699
  });
690
- handleCancel(displayName);
700
+ handleCancel(botToken);
701
+ } else if (channelType === 'whatsapp') {
702
+ p.note(
703
+ '1. Go to https://developers.facebook.com and create an app\n' +
704
+ '2. Add the WhatsApp product to your app\n' +
705
+ '3. Copy the permanent access token and Phone Number ID\n' +
706
+ '4. Configure the webhook URL to: <your-moxxy-url>/v1/channels/whatsapp/webhook',
707
+ 'WhatsApp Business API Setup'
708
+ );
691
709
 
692
- try {
693
- const result = await withSpinner('Registering Telegram channel...', () =>
694
- client.request('/v1/channels', 'POST', {
695
- channel_type: 'telegram',
696
- display_name: displayName || 'Telegram Bot',
697
- bot_token: botToken,
698
- }), 'Channel registered.');
710
+ botToken = await p.password({
711
+ message: 'Paste your WhatsApp access token',
712
+ });
713
+ handleCancel(botToken);
714
+
715
+ const phoneNumberId = await p.text({
716
+ message: 'Phone Number ID (from WhatsApp Business API)',
717
+ });
718
+ handleCancel(phoneNumberId);
699
719
 
700
- showResult('Telegram Channel', { ID: result.id, Status: result.status });
720
+ const verifyToken = await p.text({
721
+ message: 'Webhook verify token (you choose this, used to verify the webhook)',
722
+ placeholder: 'my-verify-token',
723
+ });
724
+ handleCancel(verifyToken);
725
+
726
+ channelConfig = {
727
+ phone_number_id: phoneNumberId,
728
+ verify_token: verifyToken || undefined,
729
+ };
730
+ }
701
731
 
702
- // Interactive pairing
732
+ const defaultName = channelType === 'telegram' ? 'Telegram Bot' :
733
+ channelType === 'discord' ? 'Discord Bot' : 'WhatsApp Bot';
734
+ const channelLabel = channelType === 'telegram' ? 'Telegram' :
735
+ channelType === 'discord' ? 'Discord' : 'WhatsApp';
736
+
737
+ displayName = await p.text({
738
+ message: 'Display name for this channel',
739
+ placeholder: `My ${channelLabel} Bot`,
740
+ });
741
+ handleCancel(displayName);
742
+
743
+ try {
744
+ const result = await withSpinner(`Registering ${channelLabel} channel...`, () =>
745
+ client.request('/v1/channels', 'POST', {
746
+ channel_type: channelType,
747
+ display_name: displayName || defaultName,
748
+ bot_token: botToken,
749
+ ...(channelConfig ? { config: channelConfig } : {}),
750
+ }), 'Channel registered.');
751
+
752
+ showResult(`${channelLabel} Channel`, { ID: result.id, Status: result.status });
753
+
754
+ // Interactive pairing
755
+ if (channelType === 'telegram') {
703
756
  p.note(
704
757
  '1. Open your Telegram bot and send /start\n' +
705
758
  '2. You will receive a 6-digit pairing code',
706
759
  'Pair your chat'
707
760
  );
761
+ } else if (channelType === 'discord') {
762
+ p.note(
763
+ '1. Send a message to your Discord bot or in a channel it can see\n' +
764
+ '2. Type /start to receive a 6-digit pairing code',
765
+ 'Pair your chat'
766
+ );
767
+ } else if (channelType === 'whatsapp') {
768
+ p.note(
769
+ '1. Send /start to your WhatsApp number\n' +
770
+ '2. You will receive a 6-digit pairing code',
771
+ 'Pair your chat'
772
+ );
773
+ }
708
774
 
709
- const pairCode = await p.text({
710
- message: 'Enter the 6-digit pairing code',
711
- placeholder: '123456',
712
- validate: (v) => {
713
- if (!v || v.trim().length === 0) return 'Code is required';
714
- },
715
- });
716
- handleCancel(pairCode);
775
+ const pairCode = await p.text({
776
+ message: 'Enter the 6-digit pairing code',
777
+ placeholder: '123456',
778
+ validate: (v) => {
779
+ if (!v || v.trim().length === 0) return 'Code is required';
780
+ },
781
+ });
782
+ handleCancel(pairCode);
717
783
 
718
- // Pick an agent to bind
719
- let agentId;
720
- try {
721
- const agents = await withSpinner('Fetching agents...', () =>
722
- client.listAgents(), 'Agents loaded.');
723
- if (!agents || agents.length === 0) {
724
- p.log.warn('No agents found. Create one first with: moxxy agent create');
725
- p.log.info(`Pair later with: moxxy channel pair --code ${pairCode} --agent <agent-id>`);
726
- } else {
727
- agentId = await p.select({
728
- message: 'Select agent to bind',
729
- options: agents.map(a => ({
730
- value: a.name,
731
- label: `${a.name} (${a.provider_id}/${a.model_id})`,
732
- })),
733
- });
734
- handleCancel(agentId);
735
- }
736
- } catch (err) {
737
- p.log.warn(`Could not list agents: ${err.message}`);
784
+ // Pick an agent to bind
785
+ let agentId;
786
+ try {
787
+ const agents = await withSpinner('Fetching agents...', () =>
788
+ client.listAgents(), 'Agents loaded.');
789
+ if (!agents || agents.length === 0) {
790
+ p.log.warn('No agents found. Create one first with: moxxy agent create');
738
791
  p.log.info(`Pair later with: moxxy channel pair --code ${pairCode} --agent <agent-id>`);
792
+ } else {
793
+ agentId = await p.select({
794
+ message: 'Select agent to bind',
795
+ options: agents.map(a => ({
796
+ value: a.name,
797
+ label: `${a.name} (${a.provider_id}/${a.model_id})`,
798
+ })),
799
+ });
800
+ handleCancel(agentId);
739
801
  }
802
+ } catch (err) {
803
+ p.log.warn(`Could not list agents: ${err.message}`);
804
+ p.log.info(`Pair later with: moxxy channel pair --code ${pairCode} --agent <agent-id>`);
805
+ }
740
806
 
741
- if (agentId) {
742
- try {
743
- const pairResult = await withSpinner('Pairing...', () =>
744
- client.request(`/v1/channels/${result.id}/pair`, 'POST', {
745
- code: pairCode,
746
- agent_id: agentId,
747
- }), 'Paired successfully.');
748
- showResult('Channel Paired', {
749
- 'Binding ID': pairResult.id,
750
- Agent: pairResult.agent_id,
751
- 'External Chat': pairResult.external_chat_id,
752
- });
753
- } catch (err) {
754
- p.log.error(`Failed to pair: ${err.message}`);
755
- p.log.info(`Try again with: moxxy channel pair --code ${pairCode} --agent ${agentId}`);
756
- }
807
+ if (agentId) {
808
+ try {
809
+ const pairResult = await withSpinner('Pairing...', () =>
810
+ client.request(`/v1/channels/${result.id}/pair`, 'POST', {
811
+ code: pairCode,
812
+ agent_id: agentId,
813
+ }), 'Paired successfully.');
814
+ showResult('Channel Paired', {
815
+ 'Binding ID': pairResult.id,
816
+ Agent: pairResult.agent_id,
817
+ 'External Chat': pairResult.external_chat_id,
818
+ });
819
+ } catch (err) {
820
+ p.log.error(`Failed to pair: ${err.message}`);
821
+ p.log.info(`Try again with: moxxy channel pair --code ${pairCode} --agent ${agentId}`);
757
822
  }
758
- } catch (err) {
759
- p.log.error(`Failed to register channel: ${err.message}`);
760
823
  }
761
- } else {
762
- p.log.info('Discord channel support is coming soon.');
824
+ } catch (err) {
825
+ p.log.error(`Failed to register channel: ${err.message}`);
763
826
  }
764
827
  }
765
828