@nordbyte/nordrelay 0.7.0 → 0.8.1
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/.env.example +35 -0
- package/README.md +118 -49
- package/dist/activity-events.js +2 -2
- package/dist/adapter-conformance.js +61 -0
- package/dist/bot.js +18 -31
- package/dist/channel-adapter.js +33 -6
- package/dist/channel-command-catalog.js +6 -0
- package/dist/channel-command-core.js +60 -0
- package/dist/channel-command-service.js +20 -4
- package/dist/channel-mirror-registry.js +9 -2
- package/dist/channel-prompt-engine.js +177 -0
- package/dist/channel-turn-lifecycle.js +73 -0
- package/dist/config-metadata.js +67 -8
- package/dist/config.js +48 -1
- package/dist/context-key.js +32 -0
- package/dist/discord-bot.js +99 -327
- package/dist/index.js +9 -0
- package/dist/metrics.js +2 -0
- package/dist/peer-client.js +90 -2
- package/dist/peer-readiness.js +77 -0
- package/dist/peer-runtime-service.js +22 -0
- package/dist/peer-server.js +20 -4
- package/dist/peer-store.js +17 -2
- package/dist/relay-runtime-helpers.js +3 -1
- package/dist/relay-runtime.js +7 -0
- package/dist/settings-wizard-test.js +216 -0
- package/dist/slack-artifacts.js +165 -0
- package/dist/slack-bot.js +1461 -0
- package/dist/slack-channel-runtime.js +147 -0
- package/dist/slack-command-surface.js +46 -0
- package/dist/slack-diagnostics.js +116 -0
- package/dist/slack-rate-limit.js +139 -0
- package/dist/user-management-crypto.js +38 -0
- package/dist/user-management-normalize.js +188 -0
- package/dist/user-management-types.js +1 -0
- package/dist/user-management.js +193 -196
- package/dist/web-api-contract.js +8 -0
- package/dist/web-dashboard-access-routes.js +62 -0
- package/dist/web-dashboard-assets.js +1 -0
- package/dist/web-dashboard-pages.js +14 -4
- package/dist/web-dashboard-peer-routes.js +32 -11
- package/dist/web-dashboard.js +34 -0
- package/dist/web-state.js +2 -2
- package/dist/webui-assets/dashboard.css +193 -0
- package/dist/webui-assets/dashboard.js +546 -145
- package/package.json +3 -1
- package/plugins/nordrelay/scripts/nordrelay.mjs +105 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nordbyte/nordrelay",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Remote control plane for coding agents across messaging channels.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "Ricardo",
|
|
@@ -64,6 +64,8 @@
|
|
|
64
64
|
"@anthropic-ai/claude-agent-sdk": "^0.2.141",
|
|
65
65
|
"@grammyjs/auto-retry": "^2.0.2",
|
|
66
66
|
"@openai/codex-sdk": "^0.130.0",
|
|
67
|
+
"@slack/bolt": "^4.7.2",
|
|
68
|
+
"@slack/web-api": "^7.16.0",
|
|
67
69
|
"discord.js": "^14.26.4",
|
|
68
70
|
"grammy": "^1.41.1",
|
|
69
71
|
"selfsigned": "^3.0.1",
|
|
@@ -76,10 +76,16 @@ function parseArgs(argv) {
|
|
|
76
76
|
else if (arg === "--enable-discord") options.enableDiscord = true;
|
|
77
77
|
else if (arg === "--discord-token") options.discordBotToken = requireValue(copy, ++i, arg);
|
|
78
78
|
else if (arg === "--discord-client-id") options.discordClientId = requireValue(copy, ++i, arg);
|
|
79
|
+
else if (arg === "--enable-slack") options.enableSlack = true;
|
|
80
|
+
else if (arg === "--slack-bot-token") options.slackBotToken = requireValue(copy, ++i, arg);
|
|
81
|
+
else if (arg === "--slack-app-token") options.slackAppToken = requireValue(copy, ++i, arg);
|
|
82
|
+
else if (arg === "--slack-signing-secret") options.slackSigningSecret = requireValue(copy, ++i, arg);
|
|
79
83
|
else if (arg === "--admin-email") options.adminEmail = requireValue(copy, ++i, arg);
|
|
80
84
|
else if (arg === "--admin-name") options.adminName = requireValue(copy, ++i, arg);
|
|
81
85
|
else if (arg === "--admin-password") options.adminPassword = requireValue(copy, ++i, arg);
|
|
82
86
|
else if (arg === "--telegram-user-id") options.telegramUserId = requireValue(copy, ++i, arg);
|
|
87
|
+
else if (arg === "--slack-user-id") options.slackUserId = requireValue(copy, ++i, arg);
|
|
88
|
+
else if (arg === "--slack-team-id") options.slackTeamId = requireValue(copy, ++i, arg);
|
|
83
89
|
else if (arg === "--state-backend") options.stateBackend = requireValue(copy, ++i, arg);
|
|
84
90
|
else if (arg === "--enable-pi") options.enablePi = true;
|
|
85
91
|
else if (arg === "--enable-hermes") options.enableHermes = true;
|
|
@@ -684,10 +690,22 @@ async function commandInit(options) {
|
|
|
684
690
|
const discordClientId = enableDiscord === "true"
|
|
685
691
|
? options.discordClientId || process.env.DISCORD_CLIENT_ID || await ask(null, "Discord client ID", "")
|
|
686
692
|
: "";
|
|
693
|
+
const enableSlack = options.enableSlack ? "true" : await askChoice(null, "Enable Slack", "false");
|
|
694
|
+
const slackBotToken = enableSlack === "true"
|
|
695
|
+
? options.slackBotToken || process.env.SLACK_BOT_TOKEN || await ask(null, "Slack bot token", "")
|
|
696
|
+
: "";
|
|
697
|
+
const slackAppToken = enableSlack === "true"
|
|
698
|
+
? options.slackAppToken || process.env.SLACK_APP_TOKEN || await ask(null, "Slack app-level token for Socket Mode", "")
|
|
699
|
+
: "";
|
|
700
|
+
const slackSigningSecret = enableSlack === "true"
|
|
701
|
+
? options.slackSigningSecret || process.env.SLACK_SIGNING_SECRET || await ask(null, "Slack signing secret (optional for Socket Mode)", "")
|
|
702
|
+
: "";
|
|
687
703
|
const adminEmail = options.adminEmail || await ask(null, "Admin email", "");
|
|
688
704
|
const adminName = options.adminName || await ask(null, "Admin name", "Admin");
|
|
689
705
|
const adminPassword = options.adminPassword || await askSecret(null, "Admin password", "");
|
|
690
706
|
const telegramUserId = options.telegramUserId || await ask(null, "Optional Telegram user id to link", "");
|
|
707
|
+
const slackUserId = options.slackUserId || await ask(null, "Optional Slack user id to link", "");
|
|
708
|
+
const slackTeamId = slackUserId ? (options.slackTeamId || await ask(null, "Optional Slack team id for linked user", "")) : "";
|
|
691
709
|
const enableCodex = options.disableCodex ? "false" : await askChoice(null, "Enable Codex", "true");
|
|
692
710
|
const enablePi = options.enablePi ? "true" : await askChoice(null, "Enable Pi", "false");
|
|
693
711
|
const enableHermes = options.enableHermes ? "true" : await askChoice(null, "Enable Hermes", "false");
|
|
@@ -697,7 +715,9 @@ async function commandInit(options) {
|
|
|
697
715
|
|
|
698
716
|
if (enableTelegram === "true" && !telegramBotToken) throw new Error("Telegram bot token is required when Telegram is enabled.");
|
|
699
717
|
if (enableDiscord === "true" && !discordBotToken) throw new Error("Discord bot token is required when Discord is enabled.");
|
|
700
|
-
if (
|
|
718
|
+
if (enableSlack === "true" && !slackBotToken) throw new Error("Slack bot token is required when Slack is enabled.");
|
|
719
|
+
if (enableSlack === "true" && !slackAppToken) throw new Error("Slack app-level token is required for default Socket Mode.");
|
|
720
|
+
if (enableTelegram !== "true" && enableDiscord !== "true" && enableSlack !== "true") throw new Error("At least one chat adapter must be enabled.");
|
|
701
721
|
if (!adminEmail) throw new Error("Admin email is required.");
|
|
702
722
|
if (!adminPassword) throw new Error("Admin password is required.");
|
|
703
723
|
if (enableCodex !== "true" && enablePi !== "true" && enableHermes !== "true" && enableOpenClaw !== "true" && enableClaudeCode !== "true") throw new Error("At least one agent must be enabled.");
|
|
@@ -722,6 +742,13 @@ async function commandInit(options) {
|
|
|
722
742
|
"DISCORD_COMMAND_MODE=both",
|
|
723
743
|
"DISCORD_MESSAGE_CONTENT_ENABLED=true",
|
|
724
744
|
"DISCORD_AUTO_REGISTER_COMMANDS=true",
|
|
745
|
+
`SLACK_ENABLED=${enableSlack}`,
|
|
746
|
+
`SLACK_BOT_TOKEN=${slackBotToken}`,
|
|
747
|
+
`SLACK_APP_TOKEN=${slackAppToken}`,
|
|
748
|
+
`SLACK_SIGNING_SECRET=${slackSigningSecret}`,
|
|
749
|
+
"SLACK_SOCKET_MODE=true",
|
|
750
|
+
"SLACK_MESSAGE_CONTENT_ENABLED=true",
|
|
751
|
+
"SLACK_AUTO_SEND_ARTIFACTS=false",
|
|
725
752
|
`NORDRELAY_CODEX_ENABLED=${enableCodex}`,
|
|
726
753
|
`NORDRELAY_PI_ENABLED=${enablePi}`,
|
|
727
754
|
`NORDRELAY_HERMES_ENABLED=${enableHermes}`,
|
|
@@ -754,6 +781,8 @@ async function commandInit(options) {
|
|
|
754
781
|
displayName: adminName || adminEmail,
|
|
755
782
|
password: adminPassword,
|
|
756
783
|
telegramUserId: telegramUserId ? Number(telegramUserId) : undefined,
|
|
784
|
+
slackUserId: slackUserId || undefined,
|
|
785
|
+
slackTeamId: slackTeamId || undefined,
|
|
757
786
|
});
|
|
758
787
|
console.log(`Wrote ${envPath}`);
|
|
759
788
|
console.log(`Created admin user ${adminEmail}.`);
|
|
@@ -789,7 +818,7 @@ function parsePeerFlags(argv) {
|
|
|
789
818
|
const copy = [...argv];
|
|
790
819
|
const subcommand = copy[0] && !copy[0].startsWith("-") ? copy.shift() : "list";
|
|
791
820
|
const flags = { subcommand, url: undefined };
|
|
792
|
-
if (["add", "test", "revoke"].includes(subcommand) && copy[0] && !copy[0].startsWith("-")) {
|
|
821
|
+
if (["add", "test", "check", "revoke"].includes(subcommand) && copy[0] && !copy[0].startsWith("-")) {
|
|
793
822
|
flags.url = copy.shift();
|
|
794
823
|
flags.id = flags.url;
|
|
795
824
|
}
|
|
@@ -797,6 +826,7 @@ function parsePeerFlags(argv) {
|
|
|
797
826
|
const arg = copy[i];
|
|
798
827
|
if (arg === "--name") flags.name = requireValue(copy, ++i, arg);
|
|
799
828
|
else if (arg === "--code") flags.code = requireValue(copy, ++i, arg);
|
|
829
|
+
else if (arg === "--expect-fingerprint") flags.expectFingerprint = requireValue(copy, ++i, arg);
|
|
800
830
|
else if (arg === "--public-url") flags.publicUrl = requireValue(copy, ++i, arg);
|
|
801
831
|
else if (arg === "--expires" || arg === "--expires-minutes") flags.expiresMinutes = Number.parseInt(requireValue(copy, ++i, arg), 10);
|
|
802
832
|
else if (arg === "--scopes") flags.scopes = requireValue(copy, ++i, arg);
|
|
@@ -858,6 +888,15 @@ async function commandPeer(options) {
|
|
|
858
888
|
|
|
859
889
|
if (flags.subcommand === "invite") {
|
|
860
890
|
const url = process.env.NORDRELAY_PEER_PUBLIC_URL || `${process.env.NORDRELAY_PEER_TLS_ENABLED === "false" ? "http" : "https"}://${process.env.NORDRELAY_PEER_HOST || "127.0.0.1"}:${process.env.NORDRELAY_PEER_PORT || "31979"}`;
|
|
891
|
+
const peerEnabled = process.env.NORDRELAY_PEER_ENABLED === "true";
|
|
892
|
+
if (!peerEnabled) {
|
|
893
|
+
console.log("Warning: peer server is disabled. The invite can be created, but pairing will fail until NORDRELAY_PEER_ENABLED=true and NordRelay is restarted.");
|
|
894
|
+
} else {
|
|
895
|
+
const probe = await clientMod.checkPeerEndpoint(url, { timeoutMs: 2500 });
|
|
896
|
+
if (!probe.ok) {
|
|
897
|
+
console.log(`Warning: peer endpoint is not reachable from this machine: ${probe.detail}`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
861
900
|
const created = store.createInvitation({
|
|
862
901
|
name: flags.name,
|
|
863
902
|
expiresInMs: Number.isFinite(flags.expiresMinutes) ? flags.expiresMinutes * 60 * 1000 : undefined,
|
|
@@ -876,16 +915,19 @@ async function commandPeer(options) {
|
|
|
876
915
|
if (flags.subcommand === "add") {
|
|
877
916
|
const url = flags.url || await ask(null, "Peer URL", "");
|
|
878
917
|
const code = flags.code || await ask(null, "Pairing code", "");
|
|
918
|
+
const configuredPublicUrl = process.env.NORDRELAY_PEER_ENABLED === "true" ? process.env.NORDRELAY_PEER_PUBLIC_URL : undefined;
|
|
919
|
+
const publicUrl = flags.publicUrl || configuredPublicUrl;
|
|
879
920
|
const result = await clientMod.pairPeer({
|
|
880
921
|
url,
|
|
881
922
|
code,
|
|
882
923
|
name: flags.name,
|
|
883
|
-
publicUrl
|
|
924
|
+
publicUrl,
|
|
884
925
|
}, identity, store);
|
|
885
926
|
console.log(`Added peer ${result.peer.name} (${result.peer.id}).`);
|
|
886
927
|
console.log(`Node: ${result.peer.nodeId}`);
|
|
887
928
|
console.log(`Fingerprint: ${result.peer.fingerprint}`);
|
|
888
929
|
if (result.tlsFingerprint) console.log(`TLS fingerprint: ${result.tlsFingerprint}`);
|
|
930
|
+
if (publicUrl) console.log(`Shared public URL: ${publicUrl}`);
|
|
889
931
|
return;
|
|
890
932
|
}
|
|
891
933
|
|
|
@@ -896,13 +938,26 @@ async function commandPeer(options) {
|
|
|
896
938
|
return;
|
|
897
939
|
}
|
|
898
940
|
|
|
941
|
+
if (flags.subcommand === "check") {
|
|
942
|
+
const url = flags.url || await ask(null, "Peer URL", "");
|
|
943
|
+
const probe = await clientMod.checkPeerEndpoint(url, { expectedTlsFingerprint: flags.expectFingerprint });
|
|
944
|
+
console.log(`Peer endpoint: ${probe.url}`);
|
|
945
|
+
console.log(`Status: ${probe.ok ? "reachable" : "unreachable"}`);
|
|
946
|
+
if (probe.latencyMs !== undefined) console.log(`Latency: ${probe.latencyMs}ms`);
|
|
947
|
+
if (probe.statusCode !== undefined) console.log(`HTTP status: ${probe.statusCode}`);
|
|
948
|
+
if (probe.tlsFingerprint) console.log(`TLS fingerprint: ${probe.tlsFingerprint}`);
|
|
949
|
+
console.log(`Detail: ${probe.detail}`);
|
|
950
|
+
if (!probe.ok) process.exitCode = 1;
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
|
|
899
954
|
if (flags.subcommand === "revoke") {
|
|
900
955
|
const id = flags.id || await ask(null, "Peer id", "");
|
|
901
956
|
console.log(store.revokePeer(id) ? `Revoked peer ${id}.` : `Peer not found: ${id}`);
|
|
902
957
|
return;
|
|
903
958
|
}
|
|
904
959
|
|
|
905
|
-
throw new Error("Usage: nordrelay peer [identity|list|invite|add|test|revoke]");
|
|
960
|
+
throw new Error("Usage: nordrelay peer [identity|list|invite|add|test|check|revoke]");
|
|
906
961
|
}
|
|
907
962
|
|
|
908
963
|
function parseUserFlags(argv) {
|
|
@@ -917,6 +972,8 @@ function parseUserFlags(argv) {
|
|
|
917
972
|
else if (arg === "--group" || arg === "--groups") flags.groups = requireValue(copy, ++i, arg);
|
|
918
973
|
else if (arg === "--telegram-user-id") flags.telegramUserId = Number.parseInt(requireValue(copy, ++i, arg), 10);
|
|
919
974
|
else if (arg === "--discord-user-id") flags.discordUserId = requireValue(copy, ++i, arg);
|
|
975
|
+
else if (arg === "--slack-user-id") flags.slackUserId = requireValue(copy, ++i, arg);
|
|
976
|
+
else if (arg === "--slack-team-id") flags.slackTeamId = requireValue(copy, ++i, arg);
|
|
920
977
|
else if (arg === "--user-id") flags.userId = requireValue(copy, ++i, arg);
|
|
921
978
|
}
|
|
922
979
|
return flags;
|
|
@@ -948,8 +1005,8 @@ async function commandUser(options) {
|
|
|
948
1005
|
? ["admin"]
|
|
949
1006
|
: (flags.groups ? flags.groups.split(",").map((item) => item.trim()).filter(Boolean) : ["user"]);
|
|
950
1007
|
const created = flags.subcommand === "create-admin"
|
|
951
|
-
? store.createAdmin({ email, displayName: name, password, telegramUserId: flags.telegramUserId, discordUserId: flags.discordUserId })
|
|
952
|
-
: store.createUser({ email, displayName: name, password, groupIds, telegramUserId: flags.telegramUserId, discordUserId: flags.discordUserId });
|
|
1008
|
+
? store.createAdmin({ email, displayName: name, password, telegramUserId: flags.telegramUserId, discordUserId: flags.discordUserId, slackUserId: flags.slackUserId, slackTeamId: flags.slackTeamId })
|
|
1009
|
+
: store.createUser({ email, displayName: name, password, groupIds, telegramUserId: flags.telegramUserId, discordUserId: flags.discordUserId, slackUserId: flags.slackUserId, slackTeamId: flags.slackTeamId });
|
|
953
1010
|
console.log(`Created user ${created.user.email} (${created.groups.map((group) => group.name).join(", ")}).`);
|
|
954
1011
|
return;
|
|
955
1012
|
}
|
|
@@ -984,6 +1041,17 @@ async function commandUser(options) {
|
|
|
984
1041
|
return;
|
|
985
1042
|
}
|
|
986
1043
|
|
|
1044
|
+
if (flags.subcommand === "link-slack") {
|
|
1045
|
+
const email = flags.email || await ask(null, "Email", "");
|
|
1046
|
+
const slackUserId = flags.slackUserId || await ask(null, "Slack user id", "");
|
|
1047
|
+
const teamId = flags.slackTeamId || await ask(null, "Slack team id (optional)", "");
|
|
1048
|
+
const user = store.getUserByEmail(email);
|
|
1049
|
+
if (!user) throw new Error(`User not found: ${email}`);
|
|
1050
|
+
store.linkSlackUser(user.user.id, { slackUserId, teamId: teamId || undefined });
|
|
1051
|
+
console.log(`Linked Slack user ${slackUserId} to ${user.user.email}.`);
|
|
1052
|
+
return;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
987
1055
|
if (flags.subcommand === "link-code" || flags.subcommand === "telegram-link-code") {
|
|
988
1056
|
const email = flags.email || await ask(null, "Email", "");
|
|
989
1057
|
const user = store.getUserByEmail(email);
|
|
@@ -1004,7 +1072,17 @@ async function commandUser(options) {
|
|
|
1004
1072
|
return;
|
|
1005
1073
|
}
|
|
1006
1074
|
|
|
1007
|
-
|
|
1075
|
+
if (flags.subcommand === "slack-link-code") {
|
|
1076
|
+
const email = flags.email || await ask(null, "Email", "");
|
|
1077
|
+
const user = store.getUserByEmail(email);
|
|
1078
|
+
if (!user) throw new Error(`User not found: ${email}`);
|
|
1079
|
+
const code = store.createSlackLinkCode(user.user.id);
|
|
1080
|
+
console.log(`Slack link code for ${user.user.email}: ${code.code}`);
|
|
1081
|
+
console.log(`Expires: ${code.expiresAt}`);
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
throw new Error("Usage: nordrelay user [list|create-admin|create|reset-password|link-telegram|link-discord|link-slack|link-code|telegram-link-code|discord-link-code|slack-link-code]");
|
|
1008
1086
|
}
|
|
1009
1087
|
|
|
1010
1088
|
async function commandDoctor(options) {
|
|
@@ -1016,24 +1094,39 @@ async function commandDoctor(options) {
|
|
|
1016
1094
|
checks.push(check("Node.js >= 22", Number.parseInt(process.versions.node.split(".")[0], 10) >= 22, process.version));
|
|
1017
1095
|
const telegramRequested = process.env.TELEGRAM_ENABLED !== "false";
|
|
1018
1096
|
const discordRequested = process.env.DISCORD_ENABLED === "true";
|
|
1097
|
+
const slackRequested = process.env.SLACK_ENABLED === "true";
|
|
1098
|
+
const slackSocketMode = process.env.SLACK_SOCKET_MODE !== "false";
|
|
1019
1099
|
const telegramUsable = telegramRequested && Boolean(process.env.TELEGRAM_BOT_TOKEN);
|
|
1020
1100
|
const discordUsable = discordRequested && Boolean(process.env.DISCORD_BOT_TOKEN);
|
|
1101
|
+
const slackUsable = slackRequested && Boolean(process.env.SLACK_BOT_TOKEN) && (slackSocketMode ? Boolean(process.env.SLACK_APP_TOKEN) : Boolean(process.env.SLACK_SIGNING_SECRET));
|
|
1021
1102
|
checks.push(check(
|
|
1022
1103
|
"Telegram bot token",
|
|
1023
1104
|
!telegramRequested || telegramUsable,
|
|
1024
1105
|
telegramRequested ? (telegramUsable ? "configured" : "missing; Telegram adapter will be disabled") : "disabled",
|
|
1025
|
-
telegramRequested && !discordUsable ? "fail" : "warn",
|
|
1106
|
+
telegramRequested && !discordUsable && !slackUsable ? "fail" : "warn",
|
|
1026
1107
|
));
|
|
1027
1108
|
checks.push(check(
|
|
1028
1109
|
"Discord bot token",
|
|
1029
1110
|
!discordRequested || discordUsable,
|
|
1030
1111
|
discordRequested ? (discordUsable ? "configured" : "missing; Discord adapter will be disabled") : "disabled",
|
|
1031
|
-
discordRequested && !telegramUsable ? "fail" : "warn",
|
|
1112
|
+
discordRequested && !telegramUsable && !slackUsable ? "fail" : "warn",
|
|
1113
|
+
));
|
|
1114
|
+
checks.push(check(
|
|
1115
|
+
"Slack bot token",
|
|
1116
|
+
!slackRequested || Boolean(process.env.SLACK_BOT_TOKEN),
|
|
1117
|
+
slackRequested ? (process.env.SLACK_BOT_TOKEN ? "configured" : "missing; Slack adapter will be disabled") : "disabled",
|
|
1118
|
+
slackRequested && !telegramUsable && !discordUsable ? "fail" : "warn",
|
|
1119
|
+
));
|
|
1120
|
+
checks.push(check(
|
|
1121
|
+
slackSocketMode ? "Slack app token" : "Slack signing secret",
|
|
1122
|
+
!slackRequested || slackUsable,
|
|
1123
|
+
slackRequested ? (slackUsable ? "configured" : `missing; ${slackSocketMode ? "Socket Mode requires SLACK_APP_TOKEN" : "HTTP mode requires SLACK_SIGNING_SECRET"}`) : "disabled",
|
|
1124
|
+
slackRequested && !telegramUsable && !discordUsable ? "fail" : "warn",
|
|
1032
1125
|
));
|
|
1033
1126
|
checks.push(check(
|
|
1034
1127
|
"Usable chat adapter",
|
|
1035
|
-
telegramUsable || discordUsable,
|
|
1036
|
-
telegramUsable
|
|
1128
|
+
telegramUsable || discordUsable || slackUsable,
|
|
1129
|
+
[telegramUsable ? "Telegram" : "", discordUsable ? "Discord" : "", slackUsable ? "Slack" : ""].filter(Boolean).join(" and ") || "none",
|
|
1037
1130
|
"fail",
|
|
1038
1131
|
));
|
|
1039
1132
|
checks.push(check("Discord client ID", !discordUsable || Boolean(process.env.DISCORD_CLIENT_ID), discordUsable ? (process.env.DISCORD_CLIENT_ID ? "configured" : "missing; slash command auto-registration disabled") : "disabled", "warn"));
|
|
@@ -1042,6 +1135,7 @@ async function commandDoctor(options) {
|
|
|
1042
1135
|
checks.push(check("WebUI login", true, "required for every dashboard request"));
|
|
1043
1136
|
checks.push(check("Telegram access", true, "requires linked active users and enabled group chats"));
|
|
1044
1137
|
checks.push(check("Discord access", true, "requires linked active users and enabled channels"));
|
|
1138
|
+
checks.push(check("Slack access", true, "requires linked active users and enabled channels"));
|
|
1045
1139
|
const peerEnabled = process.env.NORDRELAY_PEER_ENABLED === "true";
|
|
1046
1140
|
const peerTlsEnabled = process.env.NORDRELAY_PEER_TLS_ENABLED !== "false";
|
|
1047
1141
|
const peerHost = process.env.NORDRELAY_PEER_HOST || "127.0.0.1";
|