natureco-cli 2.23.29 → 2.23.31

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.
Files changed (111) hide show
  1. package/README.md +94 -11
  2. package/bin/natureco.js +495 -94
  3. package/package.json +1 -1
  4. package/src/commands/acp.js +39 -0
  5. package/src/commands/admin-rpc.js +302 -0
  6. package/src/commands/agent.js +280 -0
  7. package/src/commands/agents.js +114 -30
  8. package/src/commands/approvals.js +214 -0
  9. package/src/commands/backup.js +124 -0
  10. package/src/commands/bonjour.js +167 -0
  11. package/src/commands/browser.js +815 -0
  12. package/src/commands/capability.js +237 -0
  13. package/src/commands/channels.js +422 -267
  14. package/src/commands/chat.js +5 -8
  15. package/src/commands/clawbot.js +19 -0
  16. package/src/commands/clickclack.js +130 -0
  17. package/src/commands/code.js +3 -2
  18. package/src/commands/commitments.js +148 -0
  19. package/src/commands/completion.js +84 -0
  20. package/src/commands/config.js +219 -30
  21. package/src/commands/configure.js +110 -0
  22. package/src/commands/crestodian.js +92 -0
  23. package/src/commands/cron.js +239 -19
  24. package/src/commands/daemon.js +90 -0
  25. package/src/commands/dashboard.js +47 -374
  26. package/src/commands/device-pair.js +248 -0
  27. package/src/commands/devices.js +137 -0
  28. package/src/commands/directory.js +179 -0
  29. package/src/commands/dns.js +196 -0
  30. package/src/commands/docs.js +136 -0
  31. package/src/commands/doctor.js +143 -492
  32. package/src/commands/exec-policy.js +80 -0
  33. package/src/commands/gateway-server.js +1155 -24
  34. package/src/commands/gateway.js +492 -249
  35. package/src/commands/health.js +148 -0
  36. package/src/commands/help.js +24 -25
  37. package/src/commands/hooks.js +141 -87
  38. package/src/commands/imessage.js +128 -14
  39. package/src/commands/infer.js +1474 -0
  40. package/src/commands/irc.js +64 -15
  41. package/src/commands/logs.js +122 -99
  42. package/src/commands/mattermost.js +114 -12
  43. package/src/commands/mcp.js +121 -309
  44. package/src/commands/memory-cmd.js +134 -1
  45. package/src/commands/memory.js +128 -0
  46. package/src/commands/message.js +720 -134
  47. package/src/commands/migrate.js +213 -2
  48. package/src/commands/models.js +39 -1
  49. package/src/commands/node.js +98 -0
  50. package/src/commands/nodes.js +362 -0
  51. package/src/commands/oc-path.js +200 -0
  52. package/src/commands/onboard.js +129 -0
  53. package/src/commands/open-prose.js +67 -0
  54. package/src/commands/pairing.js +108 -107
  55. package/src/commands/path.js +206 -0
  56. package/src/commands/plugins.js +35 -1
  57. package/src/commands/policy.js +176 -0
  58. package/src/commands/proxy.js +306 -0
  59. package/src/commands/qr.js +70 -0
  60. package/src/commands/reset.js +101 -94
  61. package/src/commands/sandbox.js +125 -0
  62. package/src/commands/secrets.js +201 -0
  63. package/src/commands/sessions.js +110 -51
  64. package/src/commands/setup.js +102 -543
  65. package/src/commands/signal.js +447 -18
  66. package/src/commands/skills.js +67 -1
  67. package/src/commands/sms.js +123 -19
  68. package/src/commands/status.js +101 -127
  69. package/src/commands/system.js +53 -0
  70. package/src/commands/tasks.js +208 -100
  71. package/src/commands/terminal.js +139 -0
  72. package/src/commands/thread-ownership.js +157 -0
  73. package/src/commands/transcripts.js +95 -0
  74. package/src/commands/tui.js +41 -0
  75. package/src/commands/uninstall.js +73 -92
  76. package/src/commands/update.js +146 -91
  77. package/src/commands/voice.js +82 -0
  78. package/src/commands/vydra.js +98 -0
  79. package/src/commands/webhooks.js +58 -66
  80. package/src/commands/wiki.js +783 -0
  81. package/src/commands/workboard.js +207 -0
  82. package/src/tools/audio_understanding.js +154 -0
  83. package/src/tools/browser.js +112 -0
  84. package/src/tools/canvas.js +104 -0
  85. package/src/tools/document_extract.js +84 -0
  86. package/src/tools/duckduckgo.js +54 -0
  87. package/src/tools/exa_search.js +66 -0
  88. package/src/tools/firecrawl.js +104 -0
  89. package/src/tools/image_generation.js +99 -0
  90. package/src/tools/llm_task.js +118 -0
  91. package/src/tools/media_understanding.js +128 -0
  92. package/src/tools/music_generation.js +113 -0
  93. package/src/tools/parallel_search.js +77 -0
  94. package/src/tools/phone_control.js +80 -0
  95. package/src/tools/phone_control_enhanced.js +184 -0
  96. package/src/tools/searxng.js +61 -0
  97. package/src/tools/speech_to_text.js +135 -0
  98. package/src/tools/text_to_speech.js +105 -0
  99. package/src/tools/thread_ownership.js +88 -0
  100. package/src/tools/video_generation.js +72 -0
  101. package/src/tools/web_readability.js +104 -0
  102. package/src/utils/agents-md.js +85 -0
  103. package/src/utils/api.js +39 -40
  104. package/src/utils/format.js +144 -0
  105. package/src/utils/headless.js +2 -1
  106. package/src/utils/memory.js +200 -0
  107. package/src/utils/parallel-tools.js +106 -0
  108. package/src/utils/sub-agent.js +148 -0
  109. package/src/utils/token-budget.js +304 -0
  110. package/src/utils/tool-runner.js +7 -5
  111. package/src/utils/web-fetch.js +107 -0
@@ -1,134 +1,720 @@
1
- const chalk = require('chalk');
2
- const fs = require('fs');
3
- const path = require('path');
4
- const os = require('os');
5
- const { getConfig } = require('../utils/config');
6
- const { ApiError } = require('../utils/errors');
7
-
8
- const PID_FILE = path.join(os.homedir(), '.natureco', 'gateway.pid');
9
- const GATEWAY_HTTP_URL = 'http://127.0.0.1:3847/send';
10
-
11
- async function message(args) {
12
- // Parse arguments
13
- const targetIndex = args.indexOf('--target');
14
- const messageIndex = args.indexOf('--message');
15
- const channelIndex = args.indexOf('--channel');
16
-
17
- if (targetIndex === -1 || messageIndex === -1) {
18
- console.log(chalk.red('\n❌ Eksik parametre\n'));
19
- console.log(chalk.gray('Kullanım:'));
20
- console.log(chalk.cyan(' natureco message send --target <target> --message <msg> --channel [whatsapp|telegram]'));
21
- console.log(chalk.gray('\nÖrnekler:'));
22
- console.log(chalk.cyan(' natureco message send --target +905422842631 --message "Test" --channel whatsapp'));
23
- console.log(chalk.cyan(' natureco message send --target 6139455189 --message "Test" --channel telegram\n'));
24
- process.exit(1);
25
- }
26
-
27
- const target = args[targetIndex + 1];
28
- const messageText = args[messageIndex + 1];
29
- const channel = channelIndex !== -1 ? args[channelIndex + 1] : 'whatsapp';
30
-
31
- if (!target || !messageText) {
32
- console.log(chalk.red('\n❌ Target veya message boş olamaz\n'));
33
- process.exit(1);
34
- }
35
-
36
- if (!['whatsapp', 'telegram', 'signal', 'irc', 'sms'].includes(channel)) {
37
- console.log(chalk.red('\n❌ Geçersiz kanal. Kullanılabilir: whatsapp, telegram, signal, irc, sms\n'));
38
- process.exit(1);
39
- }
40
-
41
- // Check if gateway is running
42
- if (!fs.existsSync(PID_FILE)) {
43
- console.log(chalk.red('\n❌ Gateway çalışmıyor\n'));
44
- console.log(chalk.yellow('Önce gateway\'i başlatın:'), chalk.cyan('natureco gateway start\n'));
45
- process.exit(1);
46
- }
47
-
48
- try {
49
- const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim());
50
- process.kill(pid, 0); // Check if process exists
51
- } catch {
52
- console.log(chalk.red('\n❌ Gateway çalışmıyor (stale PID)\n'));
53
- console.log(chalk.yellow('Önce gateway\'i başlatın:'), chalk.cyan('natureco gateway start\n'));
54
- process.exit(1);
55
- }
56
-
57
- const config = getConfig();
58
-
59
- // Validate channel configuration
60
- if (channel === 'whatsapp' && (!config.whatsappConnected || !config.whatsappBotId)) {
61
- console.log(chalk.red('\n❌ WhatsApp bağlı değil\n'));
62
- console.log(chalk.yellow('Önce WhatsApp\'ı bağlayın:'), chalk.cyan('natureco whatsapp connect\n'));
63
- process.exit(1);
64
- }
65
-
66
- if (channel === 'telegram' && (!config.telegramToken || !config.telegramBotId)) {
67
- console.log(chalk.red('\n❌ Telegram bağlı değil\n'));
68
- console.log(chalk.yellow('Önce Telegram\'ı bağlayın:'), chalk.cyan('natureco telegram connect\n'));
69
- process.exit(1);
70
- }
71
-
72
- if (channel === 'signal' && !config.signalBotId) {
73
- console.log(chalk.red('\n❌ Signal bağlı değil\n'));
74
- console.log(chalk.yellow('Önce Signal\'ı bağlayın:'), chalk.cyan('natureco signal connect\n'));
75
- process.exit(1);
76
- }
77
-
78
- if (channel === 'irc' && !config.ircBotId) {
79
- console.log(chalk.red('\n❌ IRC bağlı değil\n'));
80
- console.log(chalk.yellow('Önce IRC\'yi bağlayın:'), chalk.cyan('natureco irc connect\n'));
81
- process.exit(1);
82
- }
83
-
84
- if (channel === 'sms' && !config.smsBotId) {
85
- console.log(chalk.red('\n❌ SMS bağlı değil\n'));
86
- console.log(chalk.yellow('Önce SMS\'i bağlayın:'), chalk.cyan('natureco sms connect\n'));
87
- process.exit(1);
88
- }
89
-
90
- // Send message via HTTP endpoint
91
- await sendMessageViaHttp(channel, target, messageText);
92
- }
93
-
94
- async function sendMessageViaHttp(channel, target, messageText) {
95
- const channelNames = { whatsapp: 'WhatsApp', telegram: 'Telegram', signal: 'Signal', irc: 'IRC', sms: 'SMS' };
96
- console.log(chalk.yellow(`\n⏳ ${channelNames[channel] || channel} mesajı gönderiliyor...\n`));
97
-
98
- try {
99
- const response = await fetch(GATEWAY_HTTP_URL, {
100
- method: 'POST',
101
- headers: { 'Content-Type': 'application/json' },
102
- body: JSON.stringify({ channel, target, message: messageText })
103
- });
104
-
105
- const data = await response.json();
106
-
107
- if (!response.ok) {
108
- throw new ApiError(data.error || `HTTP ${response.status}`, response.status);
109
- }
110
-
111
- console.log(chalk.green('✅ Mesaj gönderildi!\n'));
112
- console.log(chalk.cyan('Kanal:'), chalk.white(channel));
113
- console.log(chalk.cyan('Hedef:'), chalk.white(target));
114
- console.log(chalk.cyan('Mesaj:'), chalk.white(messageText));
115
- console.log('');
116
-
117
- process.exit(0);
118
-
119
- } catch (err) {
120
- console.log(chalk.red(`\n❌ Hata: ${err.message}\n`));
121
-
122
- if (err.message.includes('ECONNREFUSED')) {
123
- console.log(chalk.yellow('⚠️ Gateway HTTP sunucusuna bağlanılamadı'));
124
- console.log(chalk.gray('Gateway\'i yeniden başlatın: natureco gateway stop && natureco gateway start\n'));
125
- } else if (err.message.includes('not connected')) {
126
- console.log(chalk.yellow(`⚠️ ${channel === 'whatsapp' ? 'WhatsApp' : 'Telegram'} bağlı değil`));
127
- console.log(chalk.gray(`Bağlantıyı kontrol edin: natureco ${channel} status\n`));
128
- }
129
-
130
- process.exit(1);
131
- }
132
- }
133
-
134
- module.exports = message;
1
+ const chalk = require('chalk');
2
+ const F = require('../utils/format');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const { getConfig } = require('../utils/config');
7
+
8
+ const HISTORY_FILE = path.join(os.homedir(), '.natureco', 'messages.jsonl');
9
+ const PID_FILE = path.join(os.homedir(), '.natureco', 'gateway.pid');
10
+ const GATEWAY_HTTP_URL = 'http://127.0.0.1:3847/send';
11
+
12
+ const VALID_CHANNELS = ['telegram', 'whatsapp', 'discord', 'slack', 'signal', 'irc', 'mattermost', 'imessage', 'sms'];
13
+
14
+ const CHANNEL_CONFIG_MAP = {
15
+ telegram: { keys: ['telegramToken', 'telegramBotId'], all: true },
16
+ whatsapp: { keys: ['whatsappConnected', 'whatsappBotId'], all: true },
17
+ discord: { keys: ['discordBotId'], all: false },
18
+ slack: { keys: ['slackToken', 'slackBotId'], all: true },
19
+ signal: { keys: ['signalBotId'], all: false },
20
+ irc: { keys: ['ircBotId'], all: false },
21
+ mattermost: { keys: ['mattermostBotId', 'mattermostToken'], all: true },
22
+ imessage: { keys: ['imessageBotId'], all: false },
23
+ sms: { keys: ['smsBotId'], all: false },
24
+ };
25
+
26
+ function parseFlags(args) {
27
+ const flags = {};
28
+ const flagNames = ['channel', 'target', 'message', 'media', 'question', 'options', 'message-id', 'emoji', 'query', 'limit', 'edit-id', 'delete-id', 'channel-all', 'thread-id', 'sticker-id', 'sticker', 'user', 'role', 'action', 'reason', 'pin-id', 'duration', 'name', 'time', 'path'];
29
+ for (let i = 0; i < args.length; i++) {
30
+ for (const name of flagNames) {
31
+ const long = '--' + name;
32
+ if (args[i] === long) {
33
+ const val = args[i + 1];
34
+ if (val !== undefined && !val.startsWith('--')) {
35
+ flags[name] = val;
36
+ i++;
37
+ } else {
38
+ flags[name] = true;
39
+ }
40
+ }
41
+ }
42
+ }
43
+ return flags;
44
+ }
45
+
46
+ function getAction(args) {
47
+ const action = args.find(a => !a.startsWith('--'));
48
+ return action || 'send';
49
+ }
50
+
51
+ function ensureHistoryDir() {
52
+ const dir = path.dirname(HISTORY_FILE);
53
+ if (!fs.existsSync(dir)) {
54
+ fs.mkdirSync(dir, { recursive: true });
55
+ }
56
+ }
57
+
58
+ function logHistory(entry) {
59
+ ensureHistoryDir();
60
+ try {
61
+ fs.appendFileSync(HISTORY_FILE, JSON.stringify({ ...entry, timestamp: new Date().toISOString() }) + '\n', 'utf8');
62
+ } catch (err) {
63
+ F.warning('Could not write message history: ' + err.message);
64
+ }
65
+ }
66
+
67
+ function checkGatewayRunning() {
68
+ if (!fs.existsSync(PID_FILE)) return false;
69
+ try {
70
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf-8').trim(), 10);
71
+ process.kill(pid, 0);
72
+ return true;
73
+ } catch {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ function channelDisplayName(ch) {
79
+ return ch.charAt(0).toUpperCase() + ch.slice(1);
80
+ }
81
+
82
+ async function trySendViaGateway(channel, target, messageText, mediaPath) {
83
+ if (!checkGatewayRunning()) return false;
84
+ try {
85
+ const body = { channel, target, message: messageText };
86
+ if (mediaPath) body.media = mediaPath;
87
+ const response = await fetch(GATEWAY_HTTP_URL, {
88
+ method: 'POST',
89
+ headers: { 'Content-Type': 'application/json' },
90
+ body: JSON.stringify(body),
91
+ });
92
+ const data = await response.json();
93
+ if (response.ok) {
94
+ F.success('Message sent via gateway.');
95
+ F.kv('Channel', channel);
96
+ F.kv('Target', target);
97
+ if (messageText) F.kv('Message', messageText);
98
+ return true;
99
+ }
100
+ F.warning('Gateway returned: ' + (data.error || 'unknown error'));
101
+ return false;
102
+ } catch {
103
+ return false;
104
+ }
105
+ }
106
+
107
+ function showPreview(channel, target, messageText, extra) {
108
+ F.kv('Channel', channel);
109
+ F.kv('Target', target);
110
+ if (messageText) F.kv('Message', messageText);
111
+ if (extra) {
112
+ for (const [k, v] of Object.entries(extra)) {
113
+ if (v) F.kv(k, v);
114
+ }
115
+ }
116
+ }
117
+
118
+ function checkChannelConfig(config, channel) {
119
+ const spec = CHANNEL_CONFIG_MAP[channel];
120
+ if (!spec) {
121
+ console.log(chalk.red('\nUnknown channel: ' + channel + '\n'));
122
+ console.log(chalk.gray('Supported channels: ' + VALID_CHANNELS.join(', ') + '\n'));
123
+ process.exit(1);
124
+ }
125
+ const present = spec.keys.map(k => config[k] !== undefined && config[k] !== null && config[k] !== '');
126
+ const missing = spec.keys.every(v => v === false);
127
+ if (missing) {
128
+ console.log(chalk.red('\nChannel ' + channelDisplayName(channel) + ' is not configured.\n'));
129
+ console.log(chalk.gray('Configure it first: natureco ' + channel + ' connect\n'));
130
+ process.exit(1);
131
+ }
132
+ if (spec.all && !present.every(Boolean)) {
133
+ console.log(chalk.red('\nChannel ' + channelDisplayName(channel) + ' is not fully configured.\n'));
134
+ console.log(chalk.gray('Run: natureco ' + channel + ' connect\n'));
135
+ process.exit(1);
136
+ }
137
+ }
138
+
139
+ async function message(args) {
140
+ const flags = parseFlags(args);
141
+ const action = getAction(args);
142
+ const config = getConfig();
143
+
144
+ const channel = flags['channel-all'] ? null : (flags.channel || 'whatsapp');
145
+ const target = flags.target;
146
+ const messageText = flags.message;
147
+ const mediaPath = flags.media;
148
+ const question = flags.question;
149
+ const optionsRaw = flags.options;
150
+ const messageId = flags['message-id'];
151
+ const emoji = flags.emoji;
152
+ const query = flags.query;
153
+ const limit = flags.limit || '20';
154
+ const editId = flags['edit-id'];
155
+ const deleteId = flags['delete-id'];
156
+ const nonFlagArgs = args.filter(a => !a.startsWith('--'));
157
+ const compoundAction = nonFlagArgs.length >= 2 ? nonFlagArgs[0] + ' ' + nonFlagArgs[1] : null;
158
+
159
+ if (action === 'send') {
160
+ if (!channel || !target || !messageText) {
161
+ console.log(chalk.red('\nUsage: message send --channel <ch> --target <dest> --message <text>\n'));
162
+ process.exit(1);
163
+ }
164
+ if (!VALID_CHANNELS.includes(channel)) {
165
+ console.log(chalk.red('\nUnsupported channel: ' + channel + '\n'));
166
+ process.exit(1);
167
+ }
168
+ checkChannelConfig(config, channel);
169
+ F.info('Preparing to send ' + channelDisplayName(channel) + ' message...');
170
+ showPreview(channel, target, messageText, mediaPath ? { Media: mediaPath } : null);
171
+ const sent = await trySendViaGateway(channel, target, messageText, mediaPath);
172
+ if (!sent) {
173
+ F.info('(Gateway not available -- message logged for later dispatch)');
174
+ }
175
+ logHistory({ action: 'send', channel, target, message: messageText, media: mediaPath, dispatched: sent });
176
+ if (!sent) process.exit(0);
177
+ return;
178
+ }
179
+
180
+ if (action === 'broadcast') {
181
+ const targetsRaw = flags.target || '';
182
+ const targets = targetsRaw.split(',').map(s => s.trim()).filter(Boolean);
183
+ if (targets.length === 0 || !messageText) {
184
+ console.log(chalk.red('\nUsage: message broadcast --targets <t1,t2,...> --message <text>\n'));
185
+ process.exit(1);
186
+ }
187
+ const channelsToUse = flags['channel-all'] ? VALID_CHANNELS : (channel ? [channel] : VALID_CHANNELS);
188
+ for (const ch of channelsToUse) {
189
+ if (!VALID_CHANNELS.includes(ch)) continue;
190
+ const cfgOk = (() => {
191
+ try { checkChannelConfig(config, ch); return true; } catch { return false; }
192
+ })();
193
+ if (!cfgOk) {
194
+ F.info('Skipping ' + ch + ' (not configured)');
195
+ continue;
196
+ }
197
+ for (const tgt of targets) {
198
+ F.info('Broadcasting to ' + ch + ' / ' + tgt + '...');
199
+ showPreview(ch, tgt, messageText, null);
200
+ const sent = await trySendViaGateway(ch, tgt, messageText, mediaPath);
201
+ if (!sent) {
202
+ F.info('(Gateway not available -- message logged)');
203
+ }
204
+ logHistory({ action: 'broadcast', channel: ch, target: tgt, message: messageText, media: mediaPath, dispatched: sent });
205
+ }
206
+ }
207
+ return;
208
+ }
209
+
210
+ if (action === 'poll') {
211
+ if (!channel || !target || !question || !optionsRaw) {
212
+ console.log(chalk.red('\nUsage: message poll --channel <ch> --target <dest> --question <q> --options <a,b,c>\n'));
213
+ process.exit(1);
214
+ }
215
+ if (!VALID_CHANNELS.includes(channel)) {
216
+ console.log(chalk.red('\nUnsupported channel: ' + channel + '\n'));
217
+ process.exit(1);
218
+ }
219
+ checkChannelConfig(config, channel);
220
+ const options = optionsRaw.split(',').map(s => s.trim()).filter(Boolean);
221
+ F.info('Preparing poll on ' + channelDisplayName(channel) + '...');
222
+ F.table(['Property', 'Value'], [
223
+ ['Channel', channel],
224
+ ['Target', target],
225
+ ['Question', question],
226
+ ['Options', options.join(' | ')],
227
+ ]);
228
+ const sent = await trySendViaGateway(channel, target, '[POLL] ' + question + ' (' + options.join(', ') + ')', mediaPath);
229
+ if (!sent) {
230
+ F.info('(Gateway not available -- poll logged for later dispatch)');
231
+ }
232
+ logHistory({ action: 'poll', channel, target, question, options, media: mediaPath, dispatched: sent });
233
+ if (!sent) process.exit(0);
234
+ return;
235
+ }
236
+
237
+ if (action === 'react') {
238
+ if (!channel || !target || !messageId || !emoji) {
239
+ console.log(chalk.red('\nUsage: message react --channel <ch> --target <dest> --message-id <id> --emoji <e>\n'));
240
+ process.exit(1);
241
+ }
242
+ if (!VALID_CHANNELS.includes(channel)) {
243
+ console.log(chalk.red('\nUnsupported channel: ' + channel + '\n'));
244
+ process.exit(1);
245
+ }
246
+ checkChannelConfig(config, channel);
247
+ F.info('Preparing reaction on ' + channelDisplayName(channel) + '...');
248
+ showPreview(channel, target, null, { 'Message ID': messageId, Emoji: emoji });
249
+ const sent = await trySendViaGateway(channel, target, '[REACT] ' + messageId + ' ' + emoji, null);
250
+ if (!sent) {
251
+ F.info('(Gateway not available -- reaction logged for later dispatch)');
252
+ }
253
+ logHistory({ action: 'react', channel, target, messageId, emoji, dispatched: sent });
254
+ if (!sent) process.exit(0);
255
+ return;
256
+ }
257
+
258
+ if (action === 'read') {
259
+ if (!channel || !target) {
260
+ console.log(chalk.red('\nUsage: message read --channel <ch> --target <dest> [--limit <n>]\n'));
261
+ process.exit(1);
262
+ }
263
+ if (!VALID_CHANNELS.includes(channel)) {
264
+ console.log(chalk.red('\nUnsupported channel: ' + channel + '\n'));
265
+ process.exit(1);
266
+ }
267
+ checkChannelConfig(config, channel);
268
+ const limitNum = parseInt(limit, 10) || 20;
269
+ F.info('Reading last ' + limitNum + ' messages from ' + channelDisplayName(channel) + ' / ' + target + '...');
270
+ if (!checkGatewayRunning()) {
271
+ F.info('Gateway not running. Showing local history...');
272
+ }
273
+ if (fs.existsSync(HISTORY_FILE)) {
274
+ const lines = fs.readFileSync(HISTORY_FILE, 'utf8').split('\n').filter(Boolean);
275
+ const relevant = lines
276
+ .map(l => { try { return JSON.parse(l); } catch { return null; } })
277
+ .filter(e => e && e.channel === channel && e.target === target)
278
+ .slice(-limitNum);
279
+ if (relevant.length === 0) {
280
+ F.info('No messages found in local history.');
281
+ } else {
282
+ for (const entry of relevant) {
283
+ const time = entry.timestamp ? new Date(entry.timestamp).toLocaleString() : '?';
284
+ F.kv('Time', time);
285
+ F.kv('Message', entry.message || entry.question || '(no text)');
286
+ if (entry.dispatched) F.success('sent');
287
+ }
288
+ }
289
+ } else {
290
+ F.info('No message history found.');
291
+ }
292
+ return;
293
+ }
294
+
295
+ if (action === 'edit') {
296
+ if (!channel || !target || !editId || !messageText) {
297
+ console.log(chalk.red('\nUsage: message edit --channel <ch> --target <dest> --edit-id <id> --message <text>\n'));
298
+ process.exit(1);
299
+ }
300
+ if (!VALID_CHANNELS.includes(channel)) {
301
+ console.log(chalk.red('\nUnsupported channel: ' + channel + '\n'));
302
+ process.exit(1);
303
+ }
304
+ checkChannelConfig(config, channel);
305
+ F.info('Preparing to edit message on ' + channelDisplayName(channel) + '...');
306
+ showPreview(channel, target, messageText, { 'Edit ID': editId });
307
+ const sent = await trySendViaGateway(channel, target, '[EDIT ' + editId + '] ' + messageText, null);
308
+ if (!sent) {
309
+ F.info('(Gateway not available -- edit logged for later dispatch)');
310
+ }
311
+ logHistory({ action: 'edit', channel, target, editId, message: messageText, dispatched: sent });
312
+ if (!sent) process.exit(0);
313
+ return;
314
+ }
315
+
316
+ if (action === 'delete') {
317
+ if (!channel || !target || !deleteId) {
318
+ console.log(chalk.red('\nUsage: message delete --channel <ch> --target <dest> --delete-id <id>\n'));
319
+ process.exit(1);
320
+ }
321
+ if (!VALID_CHANNELS.includes(channel)) {
322
+ console.log(chalk.red('\nUnsupported channel: ' + channel + '\n'));
323
+ process.exit(1);
324
+ }
325
+ checkChannelConfig(config, channel);
326
+ F.info('Preparing to delete message on ' + channelDisplayName(channel) + '...');
327
+ showPreview(channel, target, null, { 'Delete ID': deleteId });
328
+ const sent = await trySendViaGateway(channel, target, '[DELETE ' + deleteId + ']', null);
329
+ if (!sent) {
330
+ F.info('(Gateway not available -- deletion logged for later dispatch)');
331
+ }
332
+ logHistory({ action: 'delete', channel, target, deleteId, dispatched: sent });
333
+ if (!sent) process.exit(0);
334
+ return;
335
+ }
336
+
337
+ if (action === 'search') {
338
+ if (!channel || !query) {
339
+ console.log(chalk.red('\nUsage: message search --channel <ch> --query <q>\n'));
340
+ process.exit(1);
341
+ }
342
+ if (!VALID_CHANNELS.includes(channel)) {
343
+ console.log(chalk.red('\nUnsupported channel: ' + channel + '\n'));
344
+ process.exit(1);
345
+ }
346
+ checkChannelConfig(config, channel);
347
+ F.info('Searching ' + channelDisplayName(channel) + ' for: ' + query);
348
+ if (fs.existsSync(HISTORY_FILE)) {
349
+ const lines = fs.readFileSync(HISTORY_FILE, 'utf8').split('\n').filter(Boolean);
350
+ const q = query.toLowerCase();
351
+ const results = lines
352
+ .map(l => { try { return JSON.parse(l); } catch { return null; } })
353
+ .filter(e => e && e.channel === channel && (
354
+ (e.message && e.message.toLowerCase().includes(q)) ||
355
+ (e.question && e.question.toLowerCase().includes(q)) ||
356
+ (e.target && e.target.includes(q))
357
+ ));
358
+ if (results.length === 0) {
359
+ F.info('No matches found in local history.');
360
+ } else {
361
+ F.success('Found ' + results.length + ' result(s)');
362
+ const tableRows = results.map(entry => [
363
+ entry.timestamp ? new Date(entry.timestamp).toLocaleString() : '?',
364
+ entry.target,
365
+ entry.message || entry.question || '(no text)',
366
+ ]);
367
+ F.table(['Time', 'Target', 'Message'], tableRows);
368
+ }
369
+ } else {
370
+ F.info('No message history found.');
371
+ }
372
+ return;
373
+ }
374
+
375
+ if (action === 'pin') {
376
+ if (!channel || !target || !messageId) {
377
+ console.log(chalk.red('\nUsage: message pin --channel <ch> --target <dest> --message-id <id>\n'));
378
+ process.exit(1);
379
+ }
380
+ checkChannelConfig(config, channel);
381
+ F.info('Pinning message on ' + channelDisplayName(channel) + '...');
382
+ showPreview(channel, target, null, { 'Message ID': messageId });
383
+ const sent = await trySendViaGateway(channel, target, '[PIN] ' + messageId, null);
384
+ if (!sent) F.info('(Gateway not available -- pin logged)');
385
+ logHistory({ action: 'pin', channel, target, messageId, dispatched: sent });
386
+ if (!sent) process.exit(0);
387
+ return;
388
+ }
389
+
390
+ if (action === 'unpin') {
391
+ if (!channel || !target || !messageId) {
392
+ console.log(chalk.red('\nUsage: message unpin --channel <ch> --target <dest> --message-id <id>\n'));
393
+ process.exit(1);
394
+ }
395
+ checkChannelConfig(config, channel);
396
+ F.info('Unpinning message on ' + channelDisplayName(channel) + '...');
397
+ showPreview(channel, target, null, { 'Message ID': messageId });
398
+ const sent = await trySendViaGateway(channel, target, '[UNPIN] ' + messageId, null);
399
+ if (!sent) F.info('(Gateway not available -- unpin logged)');
400
+ logHistory({ action: 'unpin', channel, target, messageId, dispatched: sent });
401
+ if (!sent) process.exit(0);
402
+ return;
403
+ }
404
+
405
+ if (action === 'thread') {
406
+ const threadId = flags['thread-id'] || flags['message-id'];
407
+ if (!channel || !target || !threadId || !messageText) {
408
+ console.log(chalk.red('\nUsage: message thread --channel <ch> --target <dest> --thread-id <id> --message <text>\n'));
409
+ process.exit(1);
410
+ }
411
+ checkChannelConfig(config, channel);
412
+ F.info('Replying in thread on ' + channelDisplayName(channel) + '...');
413
+ showPreview(channel, target, messageText, { 'Thread ID': threadId });
414
+ const sent = await trySendViaGateway(channel, target, '[THREAD ' + threadId + '] ' + messageText, mediaPath);
415
+ if (!sent) F.info('(Gateway not available -- thread reply logged)');
416
+ logHistory({ action: 'thread', channel, target, threadId, message: messageText, media: mediaPath, dispatched: sent });
417
+ if (!sent) process.exit(0);
418
+ return;
419
+ }
420
+
421
+ if (action === 'sticker') {
422
+ const stickerId = flags['sticker-id'] || flags.sticker || messageText;
423
+ if (!channel || !target || !stickerId) {
424
+ console.log(chalk.red('\nUsage: message sticker --channel <ch> --target <dest> --sticker-id <id>\n'));
425
+ process.exit(1);
426
+ }
427
+ checkChannelConfig(config, channel);
428
+ F.info('Sending sticker on ' + channelDisplayName(channel) + '...');
429
+ showPreview(channel, target, null, { 'Sticker': stickerId });
430
+ const sent = await trySendViaGateway(channel, target, '[STICKER] ' + stickerId, mediaPath);
431
+ if (!sent) F.info('(Gateway not available -- sticker logged)');
432
+ logHistory({ action: 'sticker', channel, target, sticker: stickerId, dispatched: sent });
433
+ if (!sent) process.exit(0);
434
+ return;
435
+ }
436
+
437
+ if (action === 'role') {
438
+ const roleUser = flags.user;
439
+ const roleName = flags.role;
440
+ const roleAction = flags.action || 'set';
441
+ if (!channel || !target || !roleUser || !roleName) {
442
+ console.log(chalk.red('\nUsage: message role --channel <ch> --target <dest> --user <user> --role <role> [--action set|remove]\n'));
443
+ process.exit(1);
444
+ }
445
+ checkChannelConfig(config, channel);
446
+ F.info('Managing role on ' + channelDisplayName(channel) + '...');
447
+ showPreview(channel, target, null, { User: roleUser, Role: roleName, Action: roleAction });
448
+ const sent = await trySendViaGateway(channel, target, '[ROLE ' + roleAction + '] ' + roleUser + ' ' + roleName, null);
449
+ if (!sent) F.info('(Gateway not available -- role change logged)');
450
+ logHistory({ action: 'role', channel, target, user: roleUser, role: roleName, roleAction, dispatched: sent });
451
+ if (!sent) process.exit(0);
452
+ return;
453
+ }
454
+
455
+ if (action === 'moderation') {
456
+ const modAction = flags.action || 'warn';
457
+ const modReason = flags.reason || 'No reason specified';
458
+ const modMessageId = flags['message-id'];
459
+ if (!channel || !target || !modMessageId) {
460
+ console.log(chalk.red('\nUsage: message moderation --channel <ch> --target <dest> --message-id <id> [--action warn|mute|kick|ban] [--reason <text>]\n'));
461
+ process.exit(1);
462
+ }
463
+ checkChannelConfig(config, channel);
464
+ F.warning('Moderating message on ' + channelDisplayName(channel) + '...');
465
+ showPreview(channel, target, null, { 'Message ID': modMessageId, Action: modAction, Reason: modReason });
466
+ const sent = await trySendViaGateway(channel, target, '[MOD ' + modAction + '] ' + modMessageId + ': ' + modReason, null);
467
+ if (!sent) F.info('(Gateway not available -- moderation logged)');
468
+ logHistory({ action: 'moderation', channel, target, messageId: modMessageId, modAction, modReason, dispatched: sent });
469
+ if (!sent) process.exit(0);
470
+ return;
471
+ }
472
+
473
+ // ── reactions ─────────────────────────────────────────────────────
474
+ if (action === 'reactions') {
475
+ const ch = flags.channel || nonFlagArgs[1];
476
+ const msgId = flags['message-id'] || nonFlagArgs[2];
477
+ if (!ch || !msgId) { console.log(chalk.red('\nUsage: message reactions <channel> <messageId>\n')); process.exit(1); }
478
+ const file = path.join(os.homedir(), '.natureco', 'reactions.jsonl');
479
+ const dir = path.dirname(file);
480
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
481
+ try { fs.appendFileSync(file, JSON.stringify({ action: 'reactions', channel: ch, messageId: msgId, timestamp: new Date().toISOString() }) + '\n', 'utf8'); } catch (err) { F.warning(err.message); }
482
+ F.info('Reactions on ' + ch + ' for ' + msgId + ':');
483
+ if (fs.existsSync(file)) {
484
+ const entries = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
485
+ const relevant = entries.filter(e => e.channel === ch && e.messageId === msgId);
486
+ if (relevant.length === 0) F.info('No reactions found.');
487
+ else for (const r of relevant.slice(-10)) F.kv(new Date(r.timestamp).toLocaleString(), 'reaction');
488
+ }
489
+ return;
490
+ }
491
+
492
+ // ── pins ──────────────────────────────────────────────────────────
493
+ if (action === 'pins') {
494
+ const ch = flags.channel || nonFlagArgs[1];
495
+ if (!ch) { console.log(chalk.red('\nUsage: message pins <channel>\n')); process.exit(1); }
496
+ const file = path.join(os.homedir(), '.natureco', 'pins.jsonl');
497
+ const dir = path.dirname(file);
498
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
499
+ F.info('Pinned messages in ' + ch + ':');
500
+ if (fs.existsSync(file)) {
501
+ const entries = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
502
+ const pinned = entries.filter(e => e.channel === ch && e.action === 'pin');
503
+ if (pinned.length === 0) F.info('No pinned messages.');
504
+ else for (const p of pinned.slice(-20)) F.kv(new Date(p.timestamp).toLocaleString(), p.messageId || '?');
505
+ } else { F.info('No pinned messages found.'); }
506
+ return;
507
+ }
508
+
509
+ // ── permissions ──────────────────────────────────────────────────
510
+ if (action === 'permissions') {
511
+ const ch = flags.channel || nonFlagArgs[1];
512
+ const user = flags.user || nonFlagArgs[2];
513
+ if (!ch) { console.log(chalk.red('\nUsage: message permissions <channel> [user]\n')); process.exit(1); }
514
+ const file = path.join(os.homedir(), '.natureco', 'permissions.jsonl');
515
+ const dir = path.dirname(file);
516
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
517
+ const entry = { action: 'permissions', channel: ch, user: user || null, timestamp: new Date().toISOString() };
518
+ try { fs.appendFileSync(file, JSON.stringify(entry) + '\n', 'utf8'); } catch (err) { F.warning(err.message); }
519
+ F.info('Permissions for ' + ch + (user ? ' / ' + user : '') + ':');
520
+ F.info('(Full permission list not yet available)');
521
+ return;
522
+ }
523
+
524
+ // ── channel info ─────────────────────────────────────────────────
525
+ if (compoundAction === 'channel info') {
526
+ const ch = flags.channel || nonFlagArgs[2];
527
+ if (!ch) { console.log(chalk.red('\nUsage: message channel info <channel>\n')); process.exit(1); }
528
+ const file = path.join(os.homedir(), '.natureco', 'channels.jsonl');
529
+ const dir = path.dirname(file);
530
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
531
+ const entry = { action: 'channel info', channel: ch, timestamp: new Date().toISOString() };
532
+ try { fs.appendFileSync(file, JSON.stringify(entry) + '\n', 'utf8'); } catch (err) { F.warning(err.message); }
533
+ F.info('Channel info for ' + ch + ':');
534
+ F.kv('Name', channelDisplayName(ch));
535
+ F.kv('Key', ch);
536
+ F.info('(Channel info not yet fully implemented)');
537
+ return;
538
+ }
539
+
540
+ // ── channel list ─────────────────────────────────────────────────
541
+ if (compoundAction === 'channel list') {
542
+ const file = path.join(os.homedir(), '.natureco', 'channels.jsonl');
543
+ const dir = path.dirname(file);
544
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
545
+ F.info('Available channels:');
546
+ F.list(VALID_CHANNELS.map(ch => {
547
+ const configured = (() => { try { checkChannelConfig(config, ch); return true; } catch { return false; } })();
548
+ return { label: channelDisplayName(ch), value: configured ? 'configured' : 'not configured' };
549
+ }));
550
+ return;
551
+ }
552
+
553
+ // ── member info ──────────────────────────────────────────────────
554
+ if (compoundAction === 'member info') {
555
+ const ch = flags.channel || nonFlagArgs[2];
556
+ const user = flags.user || nonFlagArgs[3];
557
+ if (!ch || !user) { console.log(chalk.red('\nUsage: message member info <channel> <user>\n')); process.exit(1); }
558
+ const file = path.join(os.homedir(), '.natureco', 'members.jsonl');
559
+ const dir = path.dirname(file);
560
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
561
+ const entry = { action: 'member info', channel: ch, user, timestamp: new Date().toISOString() };
562
+ try { fs.appendFileSync(file, JSON.stringify(entry) + '\n', 'utf8'); } catch (err) { F.warning(err.message); }
563
+ F.info('Member info for ' + user + ' on ' + ch + ':');
564
+ F.kv('User', user);
565
+ F.kv('Channel', channelDisplayName(ch));
566
+ F.info('(Member info not yet fully implemented)');
567
+ return;
568
+ }
569
+
570
+ // ── voice status ─────────────────────────────────────────────────
571
+ if (compoundAction === 'voice status') {
572
+ const ch = flags.channel || nonFlagArgs[2];
573
+ const file = path.join(os.homedir(), '.natureco', 'voice.jsonl');
574
+ const dir = path.dirname(file);
575
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
576
+ const entry = { action: 'voice status', channel: ch || 'all', timestamp: new Date().toISOString() };
577
+ try { fs.appendFileSync(file, JSON.stringify(entry) + '\n', 'utf8'); } catch (err) { F.warning(err.message); }
578
+ F.info('Voice status' + (ch ? ' for ' + ch : ' (all channels)') + ':');
579
+ F.info('(Voice status not yet fully implemented)');
580
+ return;
581
+ }
582
+
583
+ // ── event list ──────────────────────────────────────────────────
584
+ if (compoundAction === 'event list') {
585
+ const ch = flags.channel || nonFlagArgs[2];
586
+ const file = path.join(os.homedir(), '.natureco', 'events.jsonl');
587
+ F.info('Events' + (ch ? ' in ' + ch : '') + ':');
588
+ if (fs.existsSync(file)) {
589
+ const entries = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
590
+ const filtered = ch ? entries.filter(e => e.channel === ch) : entries;
591
+ if (filtered.length === 0) F.info('No events found.');
592
+ else for (const e of filtered.slice(-20)) F.kv(new Date(e.timestamp).toLocaleString(), (e.name || 'Unnamed') + (e.time ? ' at ' + e.time : ''));
593
+ } else { F.info('No events found.'); }
594
+ return;
595
+ }
596
+
597
+ // ── event create ─────────────────────────────────────────────────
598
+ if (compoundAction === 'event create') {
599
+ const ch = flags.channel || nonFlagArgs[2];
600
+ const name = flags.name || nonFlagArgs[3];
601
+ const time = flags.time || nonFlagArgs[4];
602
+ if (!ch || !name) { console.log(chalk.red('\nUsage: message event create <channel> <name> [time]\n')); process.exit(1); }
603
+ const file = path.join(os.homedir(), '.natureco', 'events.jsonl');
604
+ const dir = path.dirname(file);
605
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
606
+ const entry = { action: 'event create', channel: ch, name, time: time || null, timestamp: new Date().toISOString() };
607
+ try { fs.appendFileSync(file, JSON.stringify(entry) + '\n', 'utf8'); F.success('Event "' + name + '" created on ' + ch); } catch (err) { console.log(chalk.red('Error: ' + err.message + '\n')); process.exit(1); }
608
+ return;
609
+ }
610
+
611
+ // ── thread create ─────────────────────────────────────────────────
612
+ if (compoundAction === 'thread create') {
613
+ const ch = flags.channel || nonFlagArgs[2];
614
+ const name = flags.name || nonFlagArgs[3];
615
+ if (!ch || !name) { console.log(chalk.red('\nUsage: message thread create <channel> <name>\n')); process.exit(1); }
616
+ F.info('Would create thread ' + name + ' in ' + ch);
617
+ return;
618
+ }
619
+
620
+ // ── thread list ─────────────────────────────────────────────────
621
+ if (compoundAction === 'thread list') {
622
+ const ch = flags.channel || nonFlagArgs[2];
623
+ if (!ch) { console.log(chalk.red('\nUsage: message thread list <channel>\n')); process.exit(1); }
624
+ F.info('Would list threads in ' + ch);
625
+ return;
626
+ }
627
+
628
+ // ── thread reply ────────────────────────────────────────────────
629
+ if (compoundAction === 'thread reply') {
630
+ const ch = flags.channel || nonFlagArgs[2];
631
+ const threadId = flags['thread-id'] || nonFlagArgs[3];
632
+ const text = flags.message || nonFlagArgs[4];
633
+ if (!ch || !threadId || !text) { console.log(chalk.red('\nUsage: message thread reply <channel> <threadId> <text>\n')); process.exit(1); }
634
+ F.info('Would reply to ' + threadId + ' in ' + ch + ': ' + text);
635
+ return;
636
+ }
637
+
638
+ // ── emoji list ──────────────────────────────────────────────────
639
+ if (compoundAction === 'emoji list') {
640
+ const ch = flags.channel || nonFlagArgs[2] || null;
641
+ F.info('Would list emoji for ' + (ch || 'all channels'));
642
+ return;
643
+ }
644
+
645
+ // ── emoji upload ────────────────────────────────────────────────
646
+ if (compoundAction === 'emoji upload') {
647
+ const ch = flags.channel || nonFlagArgs[2];
648
+ const emojiPath = flags.path || nonFlagArgs[3];
649
+ if (!ch || !emojiPath) { console.log(chalk.red('\nUsage: message emoji upload <channel> <path>\n')); process.exit(1); }
650
+ F.info('Would upload emoji from ' + emojiPath + ' to ' + ch);
651
+ return;
652
+ }
653
+
654
+ // ── sticker send ────────────────────────────────────────────────
655
+ if (compoundAction === 'sticker send') {
656
+ const ch = flags.channel || nonFlagArgs[2];
657
+ const stickerId = flags['sticker-id'] || flags.sticker || nonFlagArgs[3];
658
+ if (!ch || !stickerId) { console.log(chalk.red('\nUsage: message sticker send <channel> <stickerId>\n')); process.exit(1); }
659
+ F.info('Would send sticker ' + stickerId + ' to ' + ch);
660
+ return;
661
+ }
662
+
663
+ // ── sticker upload ──────────────────────────────────────────────
664
+ if (compoundAction === 'sticker upload') {
665
+ const ch = flags.channel || nonFlagArgs[2];
666
+ const stickerPath = flags.path || nonFlagArgs[3];
667
+ if (!ch || !stickerPath) { console.log(chalk.red('\nUsage: message sticker upload <channel> <path>\n')); process.exit(1); }
668
+ F.info('Would upload sticker from ' + stickerPath + ' to ' + ch);
669
+ return;
670
+ }
671
+
672
+ // ── timeout ──────────────────────────────────────────────────────
673
+ if (action === 'timeout') {
674
+ const ch = flags.channel || nonFlagArgs[1];
675
+ const user = flags.user || nonFlagArgs[2];
676
+ const duration = flags.duration || nonFlagArgs[3] || '5m';
677
+ if (!ch || !user) { console.log(chalk.red('\nUsage: message timeout <channel> <user> [duration]\n')); process.exit(1); }
678
+ const file = path.join(os.homedir(), '.natureco', 'moderation.jsonl');
679
+ const dir = path.dirname(file);
680
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
681
+ const entry = { action: 'timeout', channel: ch, user, duration, timestamp: new Date().toISOString() };
682
+ try { fs.appendFileSync(file, JSON.stringify(entry) + '\n', 'utf8'); F.warning(user + ' timed out on ' + ch + ' for ' + duration); } catch (err) { console.log(chalk.red('Error: ' + err.message + '\n')); process.exit(1); }
683
+ return;
684
+ }
685
+
686
+ // ── kick ─────────────────────────────────────────────────────────
687
+ if (action === 'kick') {
688
+ const ch = flags.channel || nonFlagArgs[1];
689
+ const user = flags.user || nonFlagArgs[2];
690
+ if (!ch || !user) { console.log(chalk.red('\nUsage: message kick <channel> <user>\n')); process.exit(1); }
691
+ const file = path.join(os.homedir(), '.natureco', 'moderation.jsonl');
692
+ const dir = path.dirname(file);
693
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
694
+ const entry = { action: 'kick', channel: ch, user, timestamp: new Date().toISOString() };
695
+ try { fs.appendFileSync(file, JSON.stringify(entry) + '\n', 'utf8'); F.warning(user + ' kicked from ' + ch); } catch (err) { console.log(chalk.red('Error: ' + err.message + '\n')); process.exit(1); }
696
+ return;
697
+ }
698
+
699
+ // ── ban ──────────────────────────────────────────────────────────
700
+ if (action === 'ban') {
701
+ const ch = flags.channel || nonFlagArgs[1];
702
+ const user = flags.user || nonFlagArgs[2];
703
+ const reason = flags.reason || nonFlagArgs[3] || 'No reason specified';
704
+ if (!ch || !user) { console.log(chalk.red('\nUsage: message ban <channel> <user> [reason]\n')); process.exit(1); }
705
+ const file = path.join(os.homedir(), '.natureco', 'moderation.jsonl');
706
+ const dir = path.dirname(file);
707
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
708
+ const entry = { action: 'ban', channel: ch, user, reason, timestamp: new Date().toISOString() };
709
+ try { fs.appendFileSync(file, JSON.stringify(entry) + '\n', 'utf8'); F.warning(user + ' banned from ' + ch + ': ' + reason); } catch (err) { console.log(chalk.red('Error: ' + err.message + '\n')); process.exit(1); }
710
+ return;
711
+ }
712
+
713
+ console.log(chalk.red('\nUnknown action: ' + (compoundAction || action) + '\n'));
714
+ console.log(chalk.gray('Available actions: send, broadcast, poll, react, read, edit, delete, search, pin, unpin, thread, thread create, thread list, thread reply, sticker, sticker send, sticker upload, emoji list, emoji upload, role, moderation, reactions, pins, permissions, channel info, channel list, member info, voice status, event list, event create, timeout, kick, ban\n'));
715
+ process.exit(1);
716
+ }
717
+
718
+ message.VALID_CHANNELS = VALID_CHANNELS;
719
+
720
+ module.exports = message;