too-many-claw 1.0.4 → 1.0.6
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/dist/cli.js +574 -30
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +139 -8
- package/dist/index.js +334 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1209,22 +1209,134 @@ var ConfigManager = class {
|
|
|
1209
1209
|
*/
|
|
1210
1210
|
hasOpenClawDiscordConfig() {
|
|
1211
1211
|
const config = this.readOpenClawConfig();
|
|
1212
|
-
return !!config?.gateway?.discord?.token;
|
|
1212
|
+
return !!(config?.gateway?.discord?.token || config?.discord?.token || config?.channels?.discord?.token);
|
|
1213
1213
|
}
|
|
1214
1214
|
/**
|
|
1215
|
-
* Get Discord settings from OpenClaw config
|
|
1215
|
+
* Get raw Discord settings from OpenClaw config
|
|
1216
1216
|
*/
|
|
1217
1217
|
getOpenClawDiscordConfig() {
|
|
1218
1218
|
const config = this.readOpenClawConfig();
|
|
1219
|
-
return config?.gateway?.discord ?? null;
|
|
1219
|
+
return config?.gateway?.discord ?? config?.channels?.discord ?? config?.discord ?? null;
|
|
1220
|
+
}
|
|
1221
|
+
/**
|
|
1222
|
+
* Parse a channel string that may be in "guildId/channelId" format
|
|
1223
|
+
* Returns { guildId, channelId } or just { channelId } if no slash
|
|
1224
|
+
*/
|
|
1225
|
+
parseChannelString(channelStr) {
|
|
1226
|
+
if (channelStr.includes("/")) {
|
|
1227
|
+
const [guildId, channelId] = channelStr.split("/");
|
|
1228
|
+
return { guildId, channelId };
|
|
1229
|
+
}
|
|
1230
|
+
return { channelId: channelStr };
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Get the full OpenClaw config for debugging
|
|
1234
|
+
*/
|
|
1235
|
+
getOpenClawRawConfig() {
|
|
1236
|
+
return this.readOpenClawConfig();
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Extract and normalize Discord settings from OpenClaw config
|
|
1240
|
+
* Returns a clean, normalized structure regardless of how OpenClaw stores it
|
|
1241
|
+
*/
|
|
1242
|
+
extractOpenClawDiscordSettings() {
|
|
1243
|
+
const config = this.readOpenClawConfig();
|
|
1244
|
+
if (!config) return null;
|
|
1245
|
+
const discord = this.getOpenClawDiscordConfig();
|
|
1246
|
+
const channelsDiscord = config?.channels?.discord;
|
|
1247
|
+
if (!discord && !channelsDiscord) return null;
|
|
1248
|
+
const settings = {};
|
|
1249
|
+
if (discord?.token) {
|
|
1250
|
+
settings.token = discord.token;
|
|
1251
|
+
}
|
|
1252
|
+
if (discord?.guildId) {
|
|
1253
|
+
settings.guildId = discord.guildId;
|
|
1254
|
+
} else if (discord?.serverId) {
|
|
1255
|
+
settings.guildId = discord.serverId;
|
|
1256
|
+
} else if (discord?.allowlist?.guilds && discord.allowlist.guilds.length > 0) {
|
|
1257
|
+
settings.guildId = discord.allowlist.guilds[0];
|
|
1258
|
+
}
|
|
1259
|
+
if (channelsDiscord?.channel) {
|
|
1260
|
+
const parsed = this.parseChannelString(channelsDiscord.channel);
|
|
1261
|
+
if (parsed.guildId && !settings.guildId) {
|
|
1262
|
+
settings.guildId = parsed.guildId;
|
|
1263
|
+
}
|
|
1264
|
+
if (!settings.chatChannelId) {
|
|
1265
|
+
settings.chatChannelId = parsed.channelId;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
if (channelsDiscord?.channels && channelsDiscord.channels.length > 0) {
|
|
1269
|
+
const parsedChannels = [];
|
|
1270
|
+
for (const ch of channelsDiscord.channels) {
|
|
1271
|
+
parsedChannels.push(this.parseChannelString(ch));
|
|
1272
|
+
}
|
|
1273
|
+
if (!settings.guildId && parsedChannels[0]?.guildId) {
|
|
1274
|
+
settings.guildId = parsedChannels[0].guildId;
|
|
1275
|
+
}
|
|
1276
|
+
if (!settings.chatChannelId && parsedChannels[0]) {
|
|
1277
|
+
settings.chatChannelId = parsedChannels[0].channelId;
|
|
1278
|
+
}
|
|
1279
|
+
if (!settings.statusChannelId && parsedChannels[1]) {
|
|
1280
|
+
settings.statusChannelId = parsedChannels[1].channelId;
|
|
1281
|
+
}
|
|
1282
|
+
settings.allowedChannels = parsedChannels.map((p) => p.channelId);
|
|
1283
|
+
}
|
|
1284
|
+
if (discord?.channels?.chat) {
|
|
1285
|
+
const parsed = this.parseChannelString(discord.channels.chat);
|
|
1286
|
+
if (parsed.guildId && !settings.guildId) {
|
|
1287
|
+
settings.guildId = parsed.guildId;
|
|
1288
|
+
}
|
|
1289
|
+
if (!settings.chatChannelId) {
|
|
1290
|
+
settings.chatChannelId = parsed.channelId;
|
|
1291
|
+
}
|
|
1292
|
+
} else if (discord?.channels?.default) {
|
|
1293
|
+
const parsed = this.parseChannelString(discord.channels.default);
|
|
1294
|
+
if (parsed.guildId && !settings.guildId) {
|
|
1295
|
+
settings.guildId = parsed.guildId;
|
|
1296
|
+
}
|
|
1297
|
+
if (!settings.chatChannelId) {
|
|
1298
|
+
settings.chatChannelId = parsed.channelId;
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
if (discord?.allowlist?.channels && discord.allowlist.channels.length > 0) {
|
|
1302
|
+
const parsedChannels = [];
|
|
1303
|
+
for (const ch of discord.allowlist.channels) {
|
|
1304
|
+
parsedChannels.push(this.parseChannelString(ch));
|
|
1305
|
+
}
|
|
1306
|
+
if (!settings.guildId && parsedChannels[0]?.guildId) {
|
|
1307
|
+
settings.guildId = parsedChannels[0].guildId;
|
|
1308
|
+
}
|
|
1309
|
+
if (!settings.chatChannelId && parsedChannels[0]) {
|
|
1310
|
+
settings.chatChannelId = parsedChannels[0].channelId;
|
|
1311
|
+
}
|
|
1312
|
+
if (!settings.statusChannelId && parsedChannels[1]) {
|
|
1313
|
+
settings.statusChannelId = parsedChannels[1].channelId;
|
|
1314
|
+
}
|
|
1315
|
+
if (!settings.allowedChannels) {
|
|
1316
|
+
settings.allowedChannels = parsedChannels.map((p) => p.channelId);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
if (discord?.channels?.status) {
|
|
1320
|
+
const parsed = this.parseChannelString(discord.channels.status);
|
|
1321
|
+
if (!settings.statusChannelId) {
|
|
1322
|
+
settings.statusChannelId = parsed.channelId;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
if (discord?.allowlist?.users) {
|
|
1326
|
+
settings.allowedUsers = [...discord.allowlist.users];
|
|
1327
|
+
}
|
|
1328
|
+
if (Object.keys(settings).length === 0) {
|
|
1329
|
+
return null;
|
|
1330
|
+
}
|
|
1331
|
+
return settings;
|
|
1220
1332
|
}
|
|
1221
1333
|
/**
|
|
1222
1334
|
* Import Discord settings from OpenClaw configuration
|
|
1223
1335
|
* Returns true if successful, false if no OpenClaw Discord config found
|
|
1224
1336
|
*/
|
|
1225
1337
|
importFromOpenClaw() {
|
|
1226
|
-
const
|
|
1227
|
-
if (!
|
|
1338
|
+
const extracted = this.extractOpenClawDiscordSettings();
|
|
1339
|
+
if (!extracted) {
|
|
1228
1340
|
return {
|
|
1229
1341
|
success: false,
|
|
1230
1342
|
imported: {},
|
|
@@ -1232,14 +1344,17 @@ var ConfigManager = class {
|
|
|
1232
1344
|
};
|
|
1233
1345
|
}
|
|
1234
1346
|
const imported = {};
|
|
1235
|
-
if (
|
|
1236
|
-
imported.token =
|
|
1347
|
+
if (extracted.token) {
|
|
1348
|
+
imported.token = extracted.token;
|
|
1237
1349
|
}
|
|
1238
|
-
if (
|
|
1239
|
-
imported.
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1350
|
+
if (extracted.guildId) {
|
|
1351
|
+
imported.guildId = extracted.guildId;
|
|
1352
|
+
}
|
|
1353
|
+
if (extracted.chatChannelId) {
|
|
1354
|
+
imported.chatChannelId = extracted.chatChannelId;
|
|
1355
|
+
}
|
|
1356
|
+
if (extracted.statusChannelId) {
|
|
1357
|
+
imported.statusChannelId = extracted.statusChannelId;
|
|
1243
1358
|
}
|
|
1244
1359
|
if (Object.keys(imported).length === 0) {
|
|
1245
1360
|
return {
|
|
@@ -1261,6 +1376,46 @@ var ConfigManager = class {
|
|
|
1261
1376
|
message: `Imported ${Object.keys(imported).length} setting(s) from OpenClaw`
|
|
1262
1377
|
};
|
|
1263
1378
|
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Update guildId after bot connects and detects it
|
|
1381
|
+
* This is called when the bot auto-detects the guild from connection
|
|
1382
|
+
*/
|
|
1383
|
+
updateGuildId(guildId) {
|
|
1384
|
+
const currentDiscord = this.getDiscordConfig();
|
|
1385
|
+
if (!currentDiscord.guildId) {
|
|
1386
|
+
this.setDiscordConfig({
|
|
1387
|
+
...currentDiscord,
|
|
1388
|
+
guildId
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
/**
|
|
1393
|
+
* Get summary of what can be imported from OpenClaw
|
|
1394
|
+
* Useful for displaying to user before import
|
|
1395
|
+
*/
|
|
1396
|
+
getOpenClawImportSummary() {
|
|
1397
|
+
const extracted = this.extractOpenClawDiscordSettings();
|
|
1398
|
+
if (!extracted) {
|
|
1399
|
+
return {
|
|
1400
|
+
hasConfig: false,
|
|
1401
|
+
availableSettings: [],
|
|
1402
|
+
extracted: null
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
const availableSettings = [];
|
|
1406
|
+
if (extracted.token) availableSettings.push("Bot Token");
|
|
1407
|
+
if (extracted.guildId) availableSettings.push("Server (Guild) ID");
|
|
1408
|
+
if (extracted.chatChannelId) availableSettings.push("Chat Channel");
|
|
1409
|
+
if (extracted.statusChannelId) availableSettings.push("Status Channel");
|
|
1410
|
+
if (extracted.allowedChannels && extracted.allowedChannels.length > 0) {
|
|
1411
|
+
availableSettings.push(`${extracted.allowedChannels.length} Allowed Channel(s)`);
|
|
1412
|
+
}
|
|
1413
|
+
return {
|
|
1414
|
+
hasConfig: true,
|
|
1415
|
+
availableSettings,
|
|
1416
|
+
extracted
|
|
1417
|
+
};
|
|
1418
|
+
}
|
|
1264
1419
|
/**
|
|
1265
1420
|
* Get OpenClaw config file path
|
|
1266
1421
|
*/
|
|
@@ -1274,7 +1429,8 @@ import {
|
|
|
1274
1429
|
Client,
|
|
1275
1430
|
GatewayIntentBits,
|
|
1276
1431
|
TextChannel,
|
|
1277
|
-
Partials
|
|
1432
|
+
Partials,
|
|
1433
|
+
PermissionFlagsBits
|
|
1278
1434
|
} from "discord.js";
|
|
1279
1435
|
var Bot = class {
|
|
1280
1436
|
client;
|
|
@@ -1409,6 +1565,126 @@ var Bot = class {
|
|
|
1409
1565
|
get isConnected() {
|
|
1410
1566
|
return this.client.isReady();
|
|
1411
1567
|
}
|
|
1568
|
+
/**
|
|
1569
|
+
* Detect guild ID from connected guilds
|
|
1570
|
+
* Returns the first guild the bot is connected to, or the configured guildId
|
|
1571
|
+
*/
|
|
1572
|
+
async detectGuildId() {
|
|
1573
|
+
if (!this.client.isReady()) {
|
|
1574
|
+
return this.config.guildId || null;
|
|
1575
|
+
}
|
|
1576
|
+
if (this.config.guildId) {
|
|
1577
|
+
const guild = this.client.guilds.cache.get(this.config.guildId);
|
|
1578
|
+
if (guild) {
|
|
1579
|
+
return this.config.guildId;
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
const firstGuild = this.client.guilds.cache.first();
|
|
1583
|
+
return firstGuild?.id || null;
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Get all connected guilds
|
|
1587
|
+
*/
|
|
1588
|
+
getConnectedGuilds() {
|
|
1589
|
+
return this.client.guilds.cache;
|
|
1590
|
+
}
|
|
1591
|
+
/**
|
|
1592
|
+
* Get existing webhooks in a channel
|
|
1593
|
+
*/
|
|
1594
|
+
async getExistingWebhooks(channelId) {
|
|
1595
|
+
const channel = await this.client.channels.fetch(channelId);
|
|
1596
|
+
if (!(channel instanceof TextChannel)) {
|
|
1597
|
+
throw new Error("Channel is not a text channel");
|
|
1598
|
+
}
|
|
1599
|
+
const permissions = channel.permissionsFor(this.client.user);
|
|
1600
|
+
if (!permissions?.has(PermissionFlagsBits.ManageWebhooks)) {
|
|
1601
|
+
throw new Error("Bot does not have MANAGE_WEBHOOKS permission in this channel");
|
|
1602
|
+
}
|
|
1603
|
+
const webhooks = await channel.fetchWebhooks();
|
|
1604
|
+
return Array.from(webhooks.values());
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* Check if bot has webhook management permission in a channel
|
|
1608
|
+
*/
|
|
1609
|
+
async hasWebhookPermission(channelId) {
|
|
1610
|
+
try {
|
|
1611
|
+
const channel = await this.client.channels.fetch(channelId);
|
|
1612
|
+
if (!(channel instanceof TextChannel)) {
|
|
1613
|
+
return false;
|
|
1614
|
+
}
|
|
1615
|
+
const permissions = channel.permissionsFor(this.client.user);
|
|
1616
|
+
return permissions?.has(PermissionFlagsBits.ManageWebhooks) ?? false;
|
|
1617
|
+
} catch {
|
|
1618
|
+
return false;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Auto-create webhooks for agents in a channel
|
|
1623
|
+
* @param channelId - The channel to create webhooks in
|
|
1624
|
+
* @param agents - Array of agent definitions to create webhooks for
|
|
1625
|
+
* @param onProgress - Optional callback for progress updates
|
|
1626
|
+
* @returns Record of agentId -> webhook URL
|
|
1627
|
+
*/
|
|
1628
|
+
async autoCreateWebhooks(channelId, agents, onProgress) {
|
|
1629
|
+
const channel = await this.client.channels.fetch(channelId);
|
|
1630
|
+
if (!(channel instanceof TextChannel)) {
|
|
1631
|
+
throw new Error("Channel is not a text channel");
|
|
1632
|
+
}
|
|
1633
|
+
const permissions = channel.permissionsFor(this.client.user);
|
|
1634
|
+
if (!permissions?.has(PermissionFlagsBits.ManageWebhooks)) {
|
|
1635
|
+
throw new Error("Bot does not have MANAGE_WEBHOOKS permission in this channel");
|
|
1636
|
+
}
|
|
1637
|
+
const existingWebhooks = await channel.fetchWebhooks();
|
|
1638
|
+
const webhooksByName = /* @__PURE__ */ new Map();
|
|
1639
|
+
for (const webhook of existingWebhooks.values()) {
|
|
1640
|
+
if (webhook.name) {
|
|
1641
|
+
webhooksByName.set(webhook.name, webhook);
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
const result = {};
|
|
1645
|
+
let current = 0;
|
|
1646
|
+
const total = agents.length;
|
|
1647
|
+
for (const agent of agents) {
|
|
1648
|
+
current++;
|
|
1649
|
+
const webhookName = `${agent.emoji} ${agent.name}`;
|
|
1650
|
+
if (onProgress) {
|
|
1651
|
+
onProgress(current, total, agent.name);
|
|
1652
|
+
}
|
|
1653
|
+
const existing = webhooksByName.get(webhookName);
|
|
1654
|
+
if (existing) {
|
|
1655
|
+
result[agent.id] = existing.url;
|
|
1656
|
+
continue;
|
|
1657
|
+
}
|
|
1658
|
+
try {
|
|
1659
|
+
const webhook = await channel.createWebhook({
|
|
1660
|
+
name: webhookName,
|
|
1661
|
+
reason: `Too Many Claw - Auto-created webhook for agent: ${agent.id}`
|
|
1662
|
+
});
|
|
1663
|
+
result[agent.id] = webhook.url;
|
|
1664
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1665
|
+
} catch (error) {
|
|
1666
|
+
console.error(`Failed to create webhook for ${agent.name}:`, error);
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
return result;
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Delete a webhook by URL
|
|
1673
|
+
*/
|
|
1674
|
+
async deleteWebhook(webhookUrl) {
|
|
1675
|
+
try {
|
|
1676
|
+
const match = webhookUrl.match(/webhooks\/(\d+)\/([\w-]+)/);
|
|
1677
|
+
if (!match) {
|
|
1678
|
+
return false;
|
|
1679
|
+
}
|
|
1680
|
+
const [, id, token] = match;
|
|
1681
|
+
const webhook = await this.client.fetchWebhook(id, token);
|
|
1682
|
+
await webhook.delete("Too Many Claw - Webhook cleanup");
|
|
1683
|
+
return true;
|
|
1684
|
+
} catch {
|
|
1685
|
+
return false;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1412
1688
|
};
|
|
1413
1689
|
|
|
1414
1690
|
// src/discord/WebhookManager.ts
|
|
@@ -1574,6 +1850,51 @@ var DiscordAdapter = class {
|
|
|
1574
1850
|
get isConnected() {
|
|
1575
1851
|
return this.bot.isConnected;
|
|
1576
1852
|
}
|
|
1853
|
+
/**
|
|
1854
|
+
* Detect guild ID from connected bot
|
|
1855
|
+
*/
|
|
1856
|
+
async detectGuildId() {
|
|
1857
|
+
return this.bot.detectGuildId();
|
|
1858
|
+
}
|
|
1859
|
+
/**
|
|
1860
|
+
* Get all connected guilds
|
|
1861
|
+
*/
|
|
1862
|
+
getConnectedGuilds() {
|
|
1863
|
+
return this.bot.getConnectedGuilds();
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Check if bot has webhook permission in channel
|
|
1867
|
+
*/
|
|
1868
|
+
async hasWebhookPermission(channelId) {
|
|
1869
|
+
return this.bot.hasWebhookPermission(channelId);
|
|
1870
|
+
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Get existing webhooks in a channel
|
|
1873
|
+
*/
|
|
1874
|
+
async getExistingWebhooks(channelId) {
|
|
1875
|
+
return this.bot.getExistingWebhooks(channelId);
|
|
1876
|
+
}
|
|
1877
|
+
/**
|
|
1878
|
+
* Auto-create webhooks for all agents
|
|
1879
|
+
* @param channelId - Channel to create webhooks in
|
|
1880
|
+
* @param onProgress - Progress callback
|
|
1881
|
+
* @returns Record of agentId -> webhook URL
|
|
1882
|
+
*/
|
|
1883
|
+
async autoCreateWebhooks(channelId, onProgress) {
|
|
1884
|
+
return this.bot.autoCreateWebhooks(channelId, AGENT_DEFINITIONS, onProgress);
|
|
1885
|
+
}
|
|
1886
|
+
/**
|
|
1887
|
+
* Auto-create webhooks for specific agents
|
|
1888
|
+
*/
|
|
1889
|
+
async autoCreateWebhooksForAgents(channelId, agents, onProgress) {
|
|
1890
|
+
return this.bot.autoCreateWebhooks(channelId, agents, onProgress);
|
|
1891
|
+
}
|
|
1892
|
+
/**
|
|
1893
|
+
* Delete a webhook
|
|
1894
|
+
*/
|
|
1895
|
+
async deleteWebhook(webhookUrl) {
|
|
1896
|
+
return this.bot.deleteWebhook(webhookUrl);
|
|
1897
|
+
}
|
|
1577
1898
|
};
|
|
1578
1899
|
|
|
1579
1900
|
// src/cli.ts
|
|
@@ -1641,16 +1962,26 @@ async function checkAndImportOpenClaw(config) {
|
|
|
1641
1962
|
console.log(chalk3.green("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
1642
1963
|
const openclawDiscord = config.getOpenClawDiscordConfig();
|
|
1643
1964
|
if (!openclawDiscord) return false;
|
|
1965
|
+
const summary = config.getOpenClawImportSummary();
|
|
1966
|
+
const extracted = summary.extracted;
|
|
1644
1967
|
console.log(chalk3.white("\nThe following settings can be imported:\n"));
|
|
1645
|
-
if (
|
|
1646
|
-
const maskedToken =
|
|
1968
|
+
if (extracted?.token) {
|
|
1969
|
+
const maskedToken = extracted.token.substring(0, 10) + "..." + extracted.token.slice(-4);
|
|
1647
1970
|
console.log(chalk3.gray(` \u2022 Bot Token: ${maskedToken}`));
|
|
1648
1971
|
}
|
|
1649
|
-
if (
|
|
1650
|
-
console.log(chalk3.gray(` \u2022
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1972
|
+
if (extracted?.guildId) {
|
|
1973
|
+
console.log(chalk3.gray(` \u2022 Server (Guild) ID: ${extracted.guildId}`));
|
|
1974
|
+
}
|
|
1975
|
+
if (extracted?.chatChannelId) {
|
|
1976
|
+
console.log(chalk3.gray(` \u2022 Chat Channel: ${extracted.chatChannelId}`));
|
|
1977
|
+
}
|
|
1978
|
+
if (extracted?.statusChannelId) {
|
|
1979
|
+
console.log(chalk3.gray(` \u2022 Status Channel: ${extracted.statusChannelId}`));
|
|
1980
|
+
}
|
|
1981
|
+
if (extracted?.allowedChannels && extracted.allowedChannels.length > 0) {
|
|
1982
|
+
console.log(chalk3.gray(` \u2022 All Allowed Channels: ${extracted.allowedChannels.length} channel(s)`));
|
|
1983
|
+
extracted.allowedChannels.forEach((ch) => {
|
|
1984
|
+
console.log(chalk3.gray(` - ${ch}`));
|
|
1654
1985
|
});
|
|
1655
1986
|
}
|
|
1656
1987
|
console.log();
|
|
@@ -1673,6 +2004,9 @@ async function checkAndImportOpenClaw(config) {
|
|
|
1673
2004
|
if (result.imported.token) {
|
|
1674
2005
|
console.log(chalk3.green(" \u2713 Bot Token"));
|
|
1675
2006
|
}
|
|
2007
|
+
if (result.imported.guildId) {
|
|
2008
|
+
console.log(chalk3.green(` \u2713 Server (Guild) ID: ${result.imported.guildId}`));
|
|
2009
|
+
}
|
|
1676
2010
|
if (result.imported.chatChannelId) {
|
|
1677
2011
|
console.log(chalk3.green(` \u2713 Chat Channel: ${result.imported.chatChannelId}`));
|
|
1678
2012
|
}
|
|
@@ -1838,26 +2172,40 @@ async function runDiscordSetup(config) {
|
|
|
1838
2172
|
console.log(chalk3.green("\n\u2713 Discord settings saved successfully!"));
|
|
1839
2173
|
}
|
|
1840
2174
|
async function runWebhookSetup(config) {
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
2175
|
+
const discordConfig = config.getDiscordConfig();
|
|
2176
|
+
const hasDiscordConfig = !!(discordConfig.token && discordConfig.chatChannelId);
|
|
2177
|
+
const choices = [
|
|
2178
|
+
...hasDiscordConfig ? [{ name: "\u{1F916} Auto-create webhooks (requires bot connection)", value: "auto" }] : [],
|
|
2179
|
+
{ name: "\u{1F4DD} Use a single webhook for all agents", value: "single" },
|
|
2180
|
+
{ name: "\u{1F4C1} Configure webhooks per category", value: "category" },
|
|
2181
|
+
{ name: "\u{1F527} Configure webhooks per agent", value: "individual" },
|
|
2182
|
+
{ name: "\u23ED\uFE0F Skip webhook configuration", value: "skip" }
|
|
2183
|
+
];
|
|
2184
|
+
if (!hasDiscordConfig) {
|
|
2185
|
+
console.log(chalk3.yellow("\n\u26A0 Auto-create webhooks requires Discord to be configured first."));
|
|
2186
|
+
console.log(chalk3.gray(" Run Discord setup to enable this feature.\n"));
|
|
2187
|
+
}
|
|
2188
|
+
console.log(chalk3.gray("\nWebhooks allow each agent to have a unique name and avatar in Discord."));
|
|
2189
|
+
console.log(chalk3.gray("You can create them automatically or manually.\n"));
|
|
1845
2190
|
const { webhookMode } = await inquirer.prompt([
|
|
1846
2191
|
{
|
|
1847
2192
|
type: "list",
|
|
1848
2193
|
name: "webhookMode",
|
|
1849
2194
|
message: "How would you like to configure webhooks?",
|
|
1850
|
-
choices
|
|
1851
|
-
{ name: "Use a single webhook for all agents", value: "single" },
|
|
1852
|
-
{ name: "Configure webhooks per category", value: "category" },
|
|
1853
|
-
{ name: "Configure webhooks per agent", value: "individual" },
|
|
1854
|
-
{ name: "Skip webhook configuration", value: "skip" }
|
|
1855
|
-
]
|
|
2195
|
+
choices
|
|
1856
2196
|
}
|
|
1857
2197
|
]);
|
|
1858
2198
|
if (webhookMode === "skip") {
|
|
1859
2199
|
return;
|
|
1860
2200
|
}
|
|
2201
|
+
if (webhookMode === "auto") {
|
|
2202
|
+
await runAutoWebhookSetup(config);
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
console.log(chalk3.gray("\nTo create a webhook manually:"));
|
|
2206
|
+
console.log(chalk3.gray(" 1. Go to your Discord channel settings"));
|
|
2207
|
+
console.log(chalk3.gray(' 2. Click "Integrations" \u2192 "Webhooks"'));
|
|
2208
|
+
console.log(chalk3.gray(" 3. Create a new webhook and copy its URL\n"));
|
|
1861
2209
|
if (webhookMode === "single") {
|
|
1862
2210
|
const { webhookUrl } = await inquirer.prompt([
|
|
1863
2211
|
{
|
|
@@ -1931,6 +2279,114 @@ async function runWebhookSetup(config) {
|
|
|
1931
2279
|
}
|
|
1932
2280
|
}
|
|
1933
2281
|
}
|
|
2282
|
+
async function runAutoWebhookSetup(config) {
|
|
2283
|
+
console.log(chalk3.cyan("\n\u{1F916} Auto-Create Webhooks\n"));
|
|
2284
|
+
console.log(chalk3.gray("This will connect to Discord and automatically create webhooks for all 35 agents."));
|
|
2285
|
+
console.log(chalk3.gray("The bot needs MANAGE_WEBHOOKS permission in the target channel.\n"));
|
|
2286
|
+
const discordConfig = config.getDiscordConfig();
|
|
2287
|
+
if (!discordConfig.token) {
|
|
2288
|
+
console.log(chalk3.red("\u274C Discord bot token not configured."));
|
|
2289
|
+
console.log(chalk3.gray("Run `tmc setup` to configure Discord settings first.\n"));
|
|
2290
|
+
return;
|
|
2291
|
+
}
|
|
2292
|
+
if (!discordConfig.chatChannelId) {
|
|
2293
|
+
console.log(chalk3.red("\u274C Chat channel not configured."));
|
|
2294
|
+
console.log(chalk3.gray("Run `tmc setup` to configure Discord settings first.\n"));
|
|
2295
|
+
return;
|
|
2296
|
+
}
|
|
2297
|
+
console.log(chalk3.yellow("\u26A0 Important: Discord limits webhooks to 15 per channel."));
|
|
2298
|
+
console.log(chalk3.gray(` You have ${AGENT_DEFINITIONS.length} agents, so some will share webhooks or fail to create.`));
|
|
2299
|
+
console.log(chalk3.gray(" Consider using multiple channels or a single shared webhook if this is an issue.\n"));
|
|
2300
|
+
const { confirm } = await inquirer.prompt([
|
|
2301
|
+
{
|
|
2302
|
+
type: "confirm",
|
|
2303
|
+
name: "confirm",
|
|
2304
|
+
message: `Create webhooks for all ${AGENT_DEFINITIONS.length} agents in channel ${discordConfig.chatChannelId}?`,
|
|
2305
|
+
default: true
|
|
2306
|
+
}
|
|
2307
|
+
]);
|
|
2308
|
+
if (!confirm) {
|
|
2309
|
+
console.log(chalk3.yellow("\nWebhook creation cancelled.\n"));
|
|
2310
|
+
return;
|
|
2311
|
+
}
|
|
2312
|
+
const connectSpinner = ora("Connecting to Discord...").start();
|
|
2313
|
+
let adapter;
|
|
2314
|
+
try {
|
|
2315
|
+
adapter = new DiscordAdapter({
|
|
2316
|
+
token: discordConfig.token,
|
|
2317
|
+
guildId: discordConfig.guildId || "0",
|
|
2318
|
+
// Placeholder, will be auto-detected
|
|
2319
|
+
chatChannelId: discordConfig.chatChannelId,
|
|
2320
|
+
statusChannelId: discordConfig.statusChannelId
|
|
2321
|
+
});
|
|
2322
|
+
await adapter.connect();
|
|
2323
|
+
connectSpinner.succeed("Connected to Discord");
|
|
2324
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
2325
|
+
} catch (error) {
|
|
2326
|
+
connectSpinner.fail("Failed to connect to Discord");
|
|
2327
|
+
console.log(chalk3.red(`
|
|
2328
|
+
Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
2329
|
+
console.log(chalk3.gray("\nMake sure your bot token is valid and the bot is invited to your server.\n"));
|
|
2330
|
+
return;
|
|
2331
|
+
}
|
|
2332
|
+
try {
|
|
2333
|
+
if (!discordConfig.guildId) {
|
|
2334
|
+
const detectSpinner = ora("Detecting server (guild) ID...").start();
|
|
2335
|
+
const detectedGuildId = await adapter.detectGuildId();
|
|
2336
|
+
if (detectedGuildId) {
|
|
2337
|
+
config.updateGuildId(detectedGuildId);
|
|
2338
|
+
detectSpinner.succeed(`Detected server ID: ${detectedGuildId}`);
|
|
2339
|
+
} else {
|
|
2340
|
+
detectSpinner.warn("Could not auto-detect server ID (bot may not be in any servers)");
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
const permSpinner = ora("Checking webhook permissions...").start();
|
|
2344
|
+
const hasPermission = await adapter.hasWebhookPermission(discordConfig.chatChannelId);
|
|
2345
|
+
if (!hasPermission) {
|
|
2346
|
+
permSpinner.fail("Bot lacks MANAGE_WEBHOOKS permission");
|
|
2347
|
+
console.log(chalk3.red("\n\u274C The bot does not have permission to manage webhooks in this channel."));
|
|
2348
|
+
console.log(chalk3.gray("\nTo fix this:"));
|
|
2349
|
+
console.log(chalk3.gray(" 1. Go to your Discord server settings"));
|
|
2350
|
+
console.log(chalk3.gray(" 2. Navigate to Roles or Channel Permissions"));
|
|
2351
|
+
console.log(chalk3.gray(' 3. Grant the bot "Manage Webhooks" permission'));
|
|
2352
|
+
console.log(chalk3.gray(" 4. Try again\n"));
|
|
2353
|
+
await adapter.disconnect();
|
|
2354
|
+
return;
|
|
2355
|
+
}
|
|
2356
|
+
permSpinner.succeed("Bot has webhook permissions");
|
|
2357
|
+
console.log(chalk3.cyan(`
|
|
2358
|
+
Creating webhooks for ${AGENT_DEFINITIONS.length} agents...
|
|
2359
|
+
`));
|
|
2360
|
+
const progressSpinner = ora("Starting webhook creation...").start();
|
|
2361
|
+
const webhooks = await adapter.autoCreateWebhooks(
|
|
2362
|
+
discordConfig.chatChannelId,
|
|
2363
|
+
(current, total, agentName) => {
|
|
2364
|
+
progressSpinner.text = `Creating webhooks... (${current}/${total}) ${agentName}`;
|
|
2365
|
+
}
|
|
2366
|
+
);
|
|
2367
|
+
progressSpinner.succeed(`Created webhooks for ${Object.keys(webhooks).length} agents`);
|
|
2368
|
+
const saveSpinner = ora("Saving webhook configuration...").start();
|
|
2369
|
+
const existingWebhooks = config.getAllWebhooks();
|
|
2370
|
+
config.setAllWebhooks({ ...existingWebhooks, ...webhooks });
|
|
2371
|
+
saveSpinner.succeed("Webhook configuration saved");
|
|
2372
|
+
console.log(chalk3.green(`
|
|
2373
|
+
\u2705 Successfully configured ${Object.keys(webhooks).length} webhooks!
|
|
2374
|
+
`));
|
|
2375
|
+
const missingCount = AGENT_DEFINITIONS.length - Object.keys(webhooks).length;
|
|
2376
|
+
if (missingCount > 0) {
|
|
2377
|
+
console.log(chalk3.yellow(`\u26A0 ${missingCount} webhook(s) could not be created.`));
|
|
2378
|
+
console.log(chalk3.gray(" Possible reasons:"));
|
|
2379
|
+
console.log(chalk3.gray(" \u2022 Discord's 15 webhook per channel limit reached"));
|
|
2380
|
+
console.log(chalk3.gray(" \u2022 Rate limiting from Discord API"));
|
|
2381
|
+
console.log(chalk3.gray(" \u2022 Webhook already exists with the same name"));
|
|
2382
|
+
console.log(chalk3.gray("\n Consider using multiple channels or a shared webhook for remaining agents.\n"));
|
|
2383
|
+
}
|
|
2384
|
+
} finally {
|
|
2385
|
+
const disconnectSpinner = ora("Disconnecting from Discord...").start();
|
|
2386
|
+
await adapter.disconnect();
|
|
2387
|
+
disconnectSpinner.succeed("Disconnected from Discord");
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
1934
2390
|
function viewConfiguration(config) {
|
|
1935
2391
|
console.log(chalk3.cyan("\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
1936
2392
|
console.log(chalk3.cyan("\u2502 \u{1F4CA} Current Configuration \u2502"));
|
|
@@ -2081,6 +2537,94 @@ program.command("uninstall").description("Remove Too Many Claw configuration").a
|
|
|
2081
2537
|
console.error(chalk3.red(error));
|
|
2082
2538
|
}
|
|
2083
2539
|
});
|
|
2540
|
+
program.command("debug").description("Debug OpenClaw configuration detection").option("--raw", "Show raw config (WARNING: may expose sensitive data)").action((options) => {
|
|
2541
|
+
console.log(chalk3.cyan("\n\u{1F50D} Too Many Claw - Debug Information\n"));
|
|
2542
|
+
const config = new ConfigManager();
|
|
2543
|
+
console.log(chalk3.yellow("\u2501\u2501\u2501 OpenClaw Configuration \u2501\u2501\u2501\n"));
|
|
2544
|
+
const openclawPath = config.getOpenClawConfigPath();
|
|
2545
|
+
console.log(chalk3.white("Config file path:"));
|
|
2546
|
+
console.log(chalk3.gray(` ${openclawPath}`));
|
|
2547
|
+
console.log(chalk3.white("File exists:"), config.hasOpenClawConfig() ? chalk3.green("Yes") : chalk3.red("No"));
|
|
2548
|
+
if (config.hasOpenClawConfig()) {
|
|
2549
|
+
const rawConfig = config.getOpenClawRawConfig();
|
|
2550
|
+
if (rawConfig) {
|
|
2551
|
+
console.log(chalk3.white("\nTop-level keys:"));
|
|
2552
|
+
const topKeys = Object.keys(rawConfig);
|
|
2553
|
+
topKeys.forEach((key) => {
|
|
2554
|
+
const value = rawConfig[key];
|
|
2555
|
+
const valueType = Array.isArray(value) ? "array" : typeof value;
|
|
2556
|
+
const subKeys = valueType === "object" && value ? Object.keys(value) : [];
|
|
2557
|
+
console.log(chalk3.gray(` \u2022 ${key} (${valueType})${subKeys.length > 0 ? ": " + subKeys.join(", ") : ""}`));
|
|
2558
|
+
});
|
|
2559
|
+
console.log(chalk3.white("\nDiscord config paths checked:"));
|
|
2560
|
+
const paths = [
|
|
2561
|
+
{ path: "gateway.discord", value: rawConfig?.gateway?.discord },
|
|
2562
|
+
{ path: "channels.discord", value: rawConfig?.channels?.discord },
|
|
2563
|
+
{ path: "discord", value: rawConfig?.discord },
|
|
2564
|
+
{ path: "providers.discord", value: rawConfig?.providers?.discord }
|
|
2565
|
+
];
|
|
2566
|
+
paths.forEach(({ path: path2, value }) => {
|
|
2567
|
+
if (value) {
|
|
2568
|
+
const keys = typeof value === "object" ? Object.keys(value) : [];
|
|
2569
|
+
console.log(chalk3.green(` \u2713 ${path2}`) + chalk3.gray(` (keys: ${keys.join(", ") || "none"})`));
|
|
2570
|
+
} else {
|
|
2571
|
+
console.log(chalk3.gray(` \u25CB ${path2} - not found`));
|
|
2572
|
+
}
|
|
2573
|
+
});
|
|
2574
|
+
if (options.raw) {
|
|
2575
|
+
console.log(chalk3.yellow("\n\u26A0 Raw OpenClaw config (may contain sensitive data):"));
|
|
2576
|
+
console.log(chalk3.gray(JSON.stringify(rawConfig, null, 2)));
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
console.log(chalk3.yellow("\n\u2501\u2501\u2501 TMC OpenClaw Detection \u2501\u2501\u2501\n"));
|
|
2581
|
+
console.log(chalk3.white("hasOpenClawConfig():"), config.hasOpenClawConfig() ? chalk3.green("true") : chalk3.red("false"));
|
|
2582
|
+
console.log(chalk3.white("hasOpenClawDiscordConfig():"), config.hasOpenClawDiscordConfig() ? chalk3.green("true") : chalk3.red("false"));
|
|
2583
|
+
const extracted = config.extractOpenClawDiscordSettings();
|
|
2584
|
+
if (extracted) {
|
|
2585
|
+
console.log(chalk3.white("\nExtracted Discord settings:"));
|
|
2586
|
+
if (extracted.token) {
|
|
2587
|
+
const masked = extracted.token.substring(0, 10) + "..." + extracted.token.slice(-4);
|
|
2588
|
+
console.log(chalk3.green(` \u2713 token: ${masked}`));
|
|
2589
|
+
} else {
|
|
2590
|
+
console.log(chalk3.gray(" \u25CB token: not found"));
|
|
2591
|
+
}
|
|
2592
|
+
console.log(extracted.guildId ? chalk3.green(` \u2713 guildId: ${extracted.guildId}`) : chalk3.gray(" \u25CB guildId: not found"));
|
|
2593
|
+
console.log(extracted.chatChannelId ? chalk3.green(` \u2713 chatChannelId: ${extracted.chatChannelId}`) : chalk3.gray(" \u25CB chatChannelId: not found"));
|
|
2594
|
+
console.log(extracted.statusChannelId ? chalk3.green(` \u2713 statusChannelId: ${extracted.statusChannelId}`) : chalk3.gray(" \u25CB statusChannelId: not found"));
|
|
2595
|
+
if (extracted.allowedChannels && extracted.allowedChannels.length > 0) {
|
|
2596
|
+
console.log(chalk3.green(` \u2713 allowedChannels: ${extracted.allowedChannels.join(", ")}`));
|
|
2597
|
+
}
|
|
2598
|
+
} else {
|
|
2599
|
+
console.log(chalk3.gray("\nNo Discord settings could be extracted from OpenClaw config."));
|
|
2600
|
+
}
|
|
2601
|
+
console.log(chalk3.yellow("\n\u2501\u2501\u2501 Current TMC Configuration \u2501\u2501\u2501\n"));
|
|
2602
|
+
const tmcConfig = config.getDiscordConfig();
|
|
2603
|
+
const webhooks = config.getAllWebhooks();
|
|
2604
|
+
console.log(chalk3.white("Discord settings:"));
|
|
2605
|
+
if (tmcConfig.token) {
|
|
2606
|
+
const masked = tmcConfig.token.substring(0, 10) + "..." + tmcConfig.token.slice(-4);
|
|
2607
|
+
console.log(chalk3.gray(` \u2022 token: ${masked}`));
|
|
2608
|
+
} else {
|
|
2609
|
+
console.log(chalk3.gray(" \u2022 token: not set"));
|
|
2610
|
+
}
|
|
2611
|
+
console.log(chalk3.gray(` \u2022 guildId: ${tmcConfig.guildId || "not set"}`));
|
|
2612
|
+
console.log(chalk3.gray(` \u2022 chatChannelId: ${tmcConfig.chatChannelId || "not set"}`));
|
|
2613
|
+
console.log(chalk3.gray(` \u2022 statusChannelId: ${tmcConfig.statusChannelId || "not set"}`));
|
|
2614
|
+
console.log(chalk3.white("\nWebhooks configured:"), Object.keys(webhooks).length);
|
|
2615
|
+
console.log(chalk3.white("Discord fully configured:"), config.isDiscordConfigured() ? chalk3.green("Yes") : chalk3.yellow("No"));
|
|
2616
|
+
console.log(chalk3.cyan("\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\n"));
|
|
2617
|
+
if (!config.hasOpenClawConfig()) {
|
|
2618
|
+
console.log(chalk3.yellow("\u{1F4A1} Tip: OpenClaw config not found. Make sure OpenClaw is installed and run `openclaw onboard` first.\n"));
|
|
2619
|
+
} else if (!config.hasOpenClawDiscordConfig()) {
|
|
2620
|
+
console.log(chalk3.yellow("\u{1F4A1} Tip: OpenClaw config exists but no Discord token found."));
|
|
2621
|
+
console.log(chalk3.gray(" The config structure may be different than expected."));
|
|
2622
|
+
console.log(chalk3.gray(" Please share the output above (without --raw) to help diagnose.\n"));
|
|
2623
|
+
} else if (!extracted?.token) {
|
|
2624
|
+
console.log(chalk3.yellow("\u{1F4A1} Tip: Discord config detected but token not extracted."));
|
|
2625
|
+
console.log(chalk3.gray(" Check the paths above to see where Discord settings are stored.\n"));
|
|
2626
|
+
}
|
|
2627
|
+
});
|
|
2084
2628
|
program.command("agents").description("List all available agents").option("-c, --category <category>", "Filter by category").action((options) => {
|
|
2085
2629
|
console.log(chalk3.cyan("\n\u{1F980} Too Many Claw - Agent Directory\n"));
|
|
2086
2630
|
let agents = AGENT_DEFINITIONS;
|