natureco-cli 2.23.30 → 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.
- package/bin/natureco.js +166 -163
- package/package.json +1 -1
- package/src/commands/acp.js +39 -0
- package/src/commands/admin-rpc.js +83 -0
- package/src/commands/agent.js +214 -23
- package/src/commands/agents.js +114 -30
- package/src/commands/approvals.js +172 -11
- package/src/commands/browser.js +815 -0
- package/src/commands/capability.js +195 -22
- package/src/commands/channels.js +422 -267
- package/src/commands/chat.js +5 -8
- package/src/commands/clawbot.js +19 -0
- package/src/commands/code.js +3 -2
- package/src/commands/commitments.js +125 -9
- package/src/commands/completion.js +40 -32
- package/src/commands/config.js +219 -30
- package/src/commands/configure.js +84 -67
- package/src/commands/cron.js +239 -19
- package/src/commands/daemon.js +34 -4
- package/src/commands/dashboard.js +47 -374
- package/src/commands/devices.js +53 -26
- package/src/commands/directory.js +146 -14
- package/src/commands/dns.js +148 -10
- package/src/commands/docs.js +119 -26
- package/src/commands/doctor.js +143 -492
- package/src/commands/exec-policy.js +57 -48
- package/src/commands/gateway.js +492 -249
- package/src/commands/health.js +141 -11
- package/src/commands/help.js +24 -25
- package/src/commands/hooks.js +141 -87
- package/src/commands/infer.js +1442 -41
- package/src/commands/logs.js +122 -99
- package/src/commands/mcp.js +121 -309
- package/src/commands/memory.js +128 -0
- package/src/commands/message.js +720 -140
- package/src/commands/models.js +39 -1
- package/src/commands/node.js +77 -77
- package/src/commands/nodes.js +278 -22
- package/src/commands/onboard.js +115 -56
- package/src/commands/pairing.js +108 -107
- package/src/commands/path.js +206 -0
- package/src/commands/plugins.js +35 -1
- package/src/commands/proxy.js +159 -8
- package/src/commands/qr.js +55 -13
- package/src/commands/reset.js +101 -94
- package/src/commands/secrets.js +104 -21
- package/src/commands/sessions.js +110 -51
- package/src/commands/setup.js +102 -649
- package/src/commands/skills.js +67 -1
- package/src/commands/status.js +101 -127
- package/src/commands/tasks.js +208 -100
- package/src/commands/terminal.js +130 -12
- package/src/commands/transcripts.js +24 -1
- package/src/commands/tui.js +41 -0
- package/src/commands/uninstall.js +73 -92
- package/src/commands/update.js +146 -91
- package/src/commands/webhooks.js +58 -66
- package/src/commands/wiki.js +783 -0
- package/src/utils/agents-md.js +85 -0
- package/src/utils/api.js +39 -40
- package/src/utils/format.js +144 -0
- package/src/utils/headless.js +2 -1
- package/src/utils/parallel-tools.js +106 -0
- package/src/utils/sub-agent.js +148 -0
- package/src/utils/token-budget.js +304 -0
- package/src/utils/tool-runner.js +7 -5
- package/src/utils/web-fetch.js +107 -0
package/src/commands/message.js
CHANGED
|
@@ -1,140 +1,720 @@
|
|
|
1
|
-
const chalk = require('chalk');
|
|
2
|
-
const
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const {
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
console.log('');
|
|
122
|
-
|
|
123
|
-
process.exit(
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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;
|