ralph-cli-sandboxed 0.4.0 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/README.md +30 -0
  2. package/dist/commands/action.js +47 -20
  3. package/dist/commands/chat.d.ts +1 -1
  4. package/dist/commands/chat.js +325 -62
  5. package/dist/commands/config.js +2 -1
  6. package/dist/commands/daemon.d.ts +2 -5
  7. package/dist/commands/daemon.js +118 -49
  8. package/dist/commands/docker.js +110 -73
  9. package/dist/commands/fix-config.js +2 -1
  10. package/dist/commands/fix-prd.js +2 -2
  11. package/dist/commands/help.js +19 -3
  12. package/dist/commands/init.js +78 -17
  13. package/dist/commands/listen.js +116 -5
  14. package/dist/commands/logo.d.ts +5 -0
  15. package/dist/commands/logo.js +41 -0
  16. package/dist/commands/notify.js +1 -1
  17. package/dist/commands/once.js +19 -9
  18. package/dist/commands/prd.js +20 -2
  19. package/dist/commands/run.js +111 -27
  20. package/dist/commands/slack.d.ts +10 -0
  21. package/dist/commands/slack.js +333 -0
  22. package/dist/config/responder-presets.json +69 -0
  23. package/dist/index.js +6 -1
  24. package/dist/providers/discord.d.ts +82 -0
  25. package/dist/providers/discord.js +697 -0
  26. package/dist/providers/slack.d.ts +79 -0
  27. package/dist/providers/slack.js +715 -0
  28. package/dist/providers/telegram.d.ts +30 -0
  29. package/dist/providers/telegram.js +190 -7
  30. package/dist/responders/claude-code-responder.d.ts +48 -0
  31. package/dist/responders/claude-code-responder.js +203 -0
  32. package/dist/responders/cli-responder.d.ts +62 -0
  33. package/dist/responders/cli-responder.js +298 -0
  34. package/dist/responders/llm-responder.d.ts +135 -0
  35. package/dist/responders/llm-responder.js +582 -0
  36. package/dist/templates/macos-scripts.js +2 -4
  37. package/dist/templates/prompts.js +4 -2
  38. package/dist/tui/ConfigEditor.js +42 -5
  39. package/dist/tui/components/ArrayEditor.js +1 -1
  40. package/dist/tui/components/EditorPanel.js +10 -6
  41. package/dist/tui/components/HelpPanel.d.ts +1 -1
  42. package/dist/tui/components/HelpPanel.js +1 -1
  43. package/dist/tui/components/JsonSnippetEditor.js +8 -5
  44. package/dist/tui/components/KeyValueEditor.js +69 -5
  45. package/dist/tui/components/LLMProvidersEditor.d.ts +22 -0
  46. package/dist/tui/components/LLMProvidersEditor.js +357 -0
  47. package/dist/tui/components/ObjectEditor.js +1 -1
  48. package/dist/tui/components/Preview.js +1 -1
  49. package/dist/tui/components/RespondersEditor.d.ts +22 -0
  50. package/dist/tui/components/RespondersEditor.js +437 -0
  51. package/dist/tui/components/SectionNav.js +27 -3
  52. package/dist/tui/utils/presets.js +15 -2
  53. package/dist/utils/chat-client.d.ts +33 -4
  54. package/dist/utils/chat-client.js +20 -1
  55. package/dist/utils/config.d.ts +100 -1
  56. package/dist/utils/config.js +78 -1
  57. package/dist/utils/daemon-actions.d.ts +19 -0
  58. package/dist/utils/daemon-actions.js +111 -0
  59. package/dist/utils/daemon-client.d.ts +21 -0
  60. package/dist/utils/daemon-client.js +28 -1
  61. package/dist/utils/llm-client.d.ts +82 -0
  62. package/dist/utils/llm-client.js +185 -0
  63. package/dist/utils/message-queue.js +6 -6
  64. package/dist/utils/notification.d.ts +10 -2
  65. package/dist/utils/notification.js +111 -4
  66. package/dist/utils/prd-validator.js +60 -19
  67. package/dist/utils/prompt.js +22 -12
  68. package/dist/utils/responder-logger.d.ts +47 -0
  69. package/dist/utils/responder-logger.js +129 -0
  70. package/dist/utils/responder-presets.d.ts +92 -0
  71. package/dist/utils/responder-presets.js +156 -0
  72. package/dist/utils/responder.d.ts +88 -0
  73. package/dist/utils/responder.js +207 -0
  74. package/dist/utils/stream-json.js +6 -6
  75. package/docs/CHAT-CLIENTS.md +520 -0
  76. package/docs/CHAT-RESPONDERS.md +785 -0
  77. package/docs/DEVELOPMENT.md +25 -0
  78. package/docs/USEFUL_ACTIONS.md +815 -0
  79. package/docs/chat-architecture.md +251 -0
  80. package/package.json +14 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Chat command for managing Telegram (and other) chat integrations.
2
+ * Chat command for managing Telegram, Slack, and other chat integrations.
3
3
  * Allows ralph to receive commands and send notifications via chat services.
4
4
  */
5
5
  import { existsSync, readFileSync, writeFileSync } from "fs";
@@ -7,8 +7,10 @@ import { join, basename } from "path";
7
7
  import { spawn } from "child_process";
8
8
  import { loadConfig, getRalphDir, isRunningInContainer } from "../utils/config.js";
9
9
  import { createTelegramClient } from "../providers/telegram.js";
10
+ import { createSlackClient } from "../providers/slack.js";
11
+ import { createDiscordClient } from "../providers/discord.js";
10
12
  import { generateProjectId, formatStatusMessage, formatStatusForChat, } from "../utils/chat-client.js";
11
- import { getMessagesPath, sendMessage, waitForResponse, } from "../utils/message-queue.js";
13
+ import { getMessagesPath, sendMessage, waitForResponse } from "../utils/message-queue.js";
12
14
  const CHAT_STATE_FILE = "chat-state.json";
13
15
  /**
14
16
  * Load chat state from .ralph/chat-state.json
@@ -220,6 +222,23 @@ async function handleCommand(command, client, config, state, debug) {
220
222
  saveChatState(state);
221
223
  break;
222
224
  }
225
+ case "stop": {
226
+ // Stop a running ralph run process in the sandbox
227
+ await client.sendMessage(chatId, `${state.projectName}: Stopping ralph run...`);
228
+ const response = await sendToSandbox("stop", [], debug, 10000);
229
+ if (response) {
230
+ if (response.success) {
231
+ await client.sendMessage(chatId, `${state.projectName}: ${response.output}`);
232
+ }
233
+ else {
234
+ await client.sendMessage(chatId, `${state.projectName}: ${response.error}`);
235
+ }
236
+ }
237
+ else {
238
+ await client.sendMessage(chatId, `${state.projectName}: No response from sandbox. Is 'ralph listen' running?`);
239
+ }
240
+ break;
241
+ }
223
242
  case "status": {
224
243
  // Try sandbox first, fall back to host
225
244
  const response = await sendToSandbox("status", [], debug, 5000);
@@ -253,7 +272,8 @@ async function handleCommand(command, client, config, state, debug) {
253
272
  }
254
273
  case "add": {
255
274
  if (args.length === 0) {
256
- await client.sendMessage(chatId, `${state.projectName}: Usage: /add [task description]`);
275
+ const usage = client.provider === "slack" ? "/ralph add [task description]" : "/add [task description]";
276
+ await client.sendMessage(chatId, `${state.projectName}: Usage: ${usage}`);
257
277
  return;
258
278
  }
259
279
  const description = args.join(" ");
@@ -268,7 +288,8 @@ async function handleCommand(command, client, config, state, debug) {
268
288
  }
269
289
  case "exec": {
270
290
  if (args.length === 0) {
271
- await client.sendMessage(chatId, `${state.projectName}: Usage: /exec [command]`);
291
+ const usage = client.provider === "slack" ? "/ralph exec [command]" : "/exec [command]";
292
+ await client.sendMessage(chatId, `${state.projectName}: Usage: ${usage}`);
272
293
  return;
273
294
  }
274
295
  // Send exec command to sandbox
@@ -286,22 +307,19 @@ async function handleCommand(command, client, config, state, debug) {
286
307
  }
287
308
  break;
288
309
  }
289
- case "stop": {
290
- await client.sendMessage(chatId, `${state.projectName}: Stop command received (not implemented yet)`);
291
- break;
292
- }
293
310
  case "action": {
294
311
  // Reload config to pick up new actions
295
312
  const freshConfig = loadConfig();
296
313
  const actions = freshConfig.daemon?.actions || {};
297
- const actionNames = Object.keys(actions).filter(name => name !== "notify" && name !== "telegram_notify");
314
+ const actionNames = Object.keys(actions).filter((name) => name !== "notify" && name !== "telegram_notify");
298
315
  if (args.length === 0) {
299
316
  // List available actions
317
+ const usage = client.provider === "slack" ? "/ralph action <name>" : "/action <name>";
300
318
  if (actionNames.length === 0) {
301
319
  await client.sendMessage(chatId, `${state.projectName}: No actions configured. Add actions to daemon.actions in config.json`);
302
320
  }
303
321
  else {
304
- await client.sendMessage(chatId, `${state.projectName}: Available actions: ${actionNames.join(", ")}\nUsage: /action <name>`);
322
+ await client.sendMessage(chatId, `${state.projectName}: Available actions: ${actionNames.join(", ")}\nUsage: ${usage}`);
305
323
  }
306
324
  return;
307
325
  }
@@ -346,7 +364,8 @@ async function handleCommand(command, client, config, state, debug) {
346
364
  }
347
365
  case "claude": {
348
366
  if (args.length === 0) {
349
- await client.sendMessage(chatId, `${state.projectName}: Usage: /claude [prompt]`);
367
+ const usage = client.provider === "slack" ? "/ralph <prompt>" : "/claude [prompt]";
368
+ await client.sendMessage(chatId, `${state.projectName}: Usage: ${usage}`);
350
369
  return;
351
370
  }
352
371
  const prompt = args.join(" ");
@@ -380,15 +399,24 @@ async function handleCommand(command, client, config, state, debug) {
380
399
  break;
381
400
  }
382
401
  case "help": {
383
- const helpText = `
384
- /run - Start automation
402
+ const isSlack = client.provider === "slack";
403
+ const helpText = isSlack
404
+ ? `/ralph help - This help
405
+ /ralph status - PRD progress
406
+ /ralph run [category] - Start automation
407
+ /ralph stop - Stop automation
408
+ /ralph add [desc] - Add task
409
+ /ralph exec [cmd] - Shell command
410
+ /ralph action [name] - Run action
411
+ /ralph <prompt> - Run Claude Code`
412
+ : `/help - This help
385
413
  /status - PRD progress
414
+ /run - Start automation
415
+ /stop - Stop automation
386
416
  /add [desc] - Add task
387
417
  /exec [cmd] - Shell command
388
418
  /action [name] - Run action
389
- /claude [prompt] - Run Claude Code
390
- /help - This help
391
- `.trim();
419
+ /claude [prompt] - Run Claude Code`;
392
420
  await client.sendMessage(chatId, helpText);
393
421
  break;
394
422
  }
@@ -397,21 +425,91 @@ async function handleCommand(command, client, config, state, debug) {
397
425
  }
398
426
  }
399
427
  /**
400
- * Start the chat daemon (listens for messages and handles commands).
428
+ * Create a chat client based on the provider configuration.
401
429
  */
402
- async function startChat(config, debug) {
403
- // Check that Telegram is configured
430
+ function createChatClient(config, debug) {
431
+ const provider = config.chat?.provider || "telegram";
432
+ if (provider === "slack") {
433
+ // Check that Slack is configured
434
+ if (!config.chat?.slack?.botToken) {
435
+ console.error("Error: Slack bot token not configured");
436
+ console.error("Set chat.slack.botToken in .ralph/config.json");
437
+ console.error("Get a token from your Slack app settings: https://api.slack.com/apps");
438
+ process.exit(1);
439
+ }
440
+ if (!config.chat?.slack?.appToken) {
441
+ console.error("Error: Slack app token not configured");
442
+ console.error("Set chat.slack.appToken in .ralph/config.json");
443
+ console.error("Enable Socket Mode in your Slack app and generate an app token");
444
+ process.exit(1);
445
+ }
446
+ if (!config.chat?.slack?.signingSecret) {
447
+ console.error("Error: Slack signing secret not configured");
448
+ console.error("Set chat.slack.signingSecret in .ralph/config.json");
449
+ console.error("Find your signing secret in Slack app Basic Information");
450
+ process.exit(1);
451
+ }
452
+ if (config.chat.slack.enabled === false) {
453
+ console.error("Error: Slack is disabled in config (slack.enabled = false)");
454
+ process.exit(1);
455
+ }
456
+ return {
457
+ client: createSlackClient({
458
+ botToken: config.chat.slack.botToken,
459
+ appToken: config.chat.slack.appToken,
460
+ signingSecret: config.chat.slack.signingSecret,
461
+ allowedChannelIds: config.chat.slack.allowedChannelIds,
462
+ }, debug),
463
+ provider: "slack",
464
+ allowedChatIds: config.chat.slack.allowedChannelIds,
465
+ };
466
+ }
467
+ if (provider === "discord") {
468
+ // Check that Discord is configured
469
+ if (!config.chat?.discord?.botToken) {
470
+ console.error("Error: Discord bot token not configured");
471
+ console.error("Set chat.discord.botToken in .ralph/config.json");
472
+ console.error("Get a token from the Discord Developer Portal: https://discord.com/developers/applications");
473
+ process.exit(1);
474
+ }
475
+ if (config.chat.discord.enabled === false) {
476
+ console.error("Error: Discord is disabled in config (discord.enabled = false)");
477
+ process.exit(1);
478
+ }
479
+ return {
480
+ client: createDiscordClient({
481
+ botToken: config.chat.discord.botToken,
482
+ allowedGuildIds: config.chat.discord.allowedGuildIds,
483
+ allowedChannelIds: config.chat.discord.allowedChannelIds,
484
+ }, debug),
485
+ provider: "discord",
486
+ allowedChatIds: config.chat.discord.allowedChannelIds,
487
+ };
488
+ }
489
+ // Default to Telegram
404
490
  if (!config.chat?.telegram?.botToken) {
405
491
  console.error("Error: Telegram bot token not configured");
406
492
  console.error("Set chat.telegram.botToken in .ralph/config.json");
407
493
  console.error("Get a token from @BotFather on Telegram");
408
494
  process.exit(1);
409
495
  }
410
- // Check if Telegram is explicitly disabled
411
496
  if (config.chat.telegram.enabled === false) {
412
497
  console.error("Error: Telegram is disabled in config (telegram.enabled = false)");
413
498
  process.exit(1);
414
499
  }
500
+ return {
501
+ client: createTelegramClient({
502
+ botToken: config.chat.telegram.botToken,
503
+ allowedChatIds: config.chat.telegram.allowedChatIds,
504
+ }, debug),
505
+ provider: "telegram",
506
+ allowedChatIds: config.chat.telegram.allowedChatIds,
507
+ };
508
+ }
509
+ /**
510
+ * Start the chat daemon (listens for messages and handles commands).
511
+ */
512
+ async function startChat(config, debug) {
415
513
  // Create or load chat state
416
514
  let state = loadChatState();
417
515
  const projectId = getOrCreateProjectId();
@@ -424,15 +522,12 @@ async function startChat(config, debug) {
424
522
  };
425
523
  saveChatState(state);
426
524
  }
427
- // Create Telegram client
428
- const client = createTelegramClient({
429
- botToken: config.chat.telegram.botToken,
430
- allowedChatIds: config.chat.telegram.allowedChatIds,
431
- }, debug);
525
+ // Create chat client based on provider
526
+ const { client, provider, allowedChatIds } = createChatClient(config, debug);
432
527
  console.log("Ralph Chat Daemon");
433
528
  console.log("-".repeat(40));
434
529
  console.log(`Project: ${projectName}`);
435
- console.log(`Provider: ${config.chat.provider}`);
530
+ console.log(`Provider: ${provider}`);
436
531
  console.log("");
437
532
  // Connect and start listening
438
533
  try {
@@ -442,21 +537,34 @@ async function startChat(config, debug) {
442
537
  return Promise.resolve();
443
538
  }
444
539
  : undefined);
445
- console.log("Connected to Telegram!");
540
+ const providerName = provider === "slack" ? "Slack" : provider === "discord" ? "Discord" : "Telegram";
541
+ console.log(`Connected to ${providerName}!`);
446
542
  console.log("");
447
- console.log("Commands (send in Telegram):");
448
- console.log(" /run - Start ralph automation");
449
- console.log(" /status - Show PRD progress");
450
- console.log(" /add ... - Add new task to PRD");
451
- console.log(" /exec ... - Execute shell command");
452
- console.log(" /action ... - Run daemon action");
453
- console.log(" /claude ... - Run Claude Code with prompt (YOLO mode)");
454
- console.log(" /help - Show help");
543
+ console.log(`Commands (send in ${providerName}):`);
544
+ if (provider === "slack") {
545
+ console.log(" /ralph help - Show help");
546
+ console.log(" /ralph status - Show PRD progress");
547
+ console.log(" /ralph run - Start ralph automation");
548
+ console.log(" /ralph stop - Stop running automation");
549
+ console.log(" /ralph add ... - Add new task to PRD");
550
+ console.log(" /ralph exec ... - Execute shell command");
551
+ console.log(" /ralph action ... - Run daemon action");
552
+ console.log(" /ralph <prompt> - Run Claude Code with prompt");
553
+ }
554
+ else {
555
+ console.log(" /run - Start ralph automation");
556
+ console.log(" /status - Show PRD progress");
557
+ console.log(" /add ... - Add new task to PRD");
558
+ console.log(" /exec ... - Execute shell command");
559
+ console.log(" /action ... - Run daemon action");
560
+ console.log(" /claude ... - Run Claude Code with prompt (YOLO mode)");
561
+ console.log(" /help - Show help");
562
+ }
455
563
  console.log("");
456
564
  console.log("Press Ctrl+C to stop the daemon.");
457
565
  // Send connected message to all allowed chats
458
- if (config.chat.telegram.allowedChatIds && config.chat.telegram.allowedChatIds.length > 0) {
459
- for (const chatId of config.chat.telegram.allowedChatIds) {
566
+ if (allowedChatIds && allowedChatIds.length > 0) {
567
+ for (const chatId of allowedChatIds) {
460
568
  try {
461
569
  await client.sendMessage(chatId, `${projectName} connected`);
462
570
  }
@@ -476,8 +584,8 @@ async function startChat(config, debug) {
476
584
  const shutdown = async () => {
477
585
  console.log("\nShutting down chat daemon...");
478
586
  // Send disconnected message to all allowed chats
479
- if (config.chat?.telegram?.allowedChatIds) {
480
- for (const chatId of config.chat.telegram.allowedChatIds) {
587
+ if (allowedChatIds && allowedChatIds.length > 0) {
588
+ for (const chatId of allowedChatIds) {
481
589
  try {
482
590
  await client.sendMessage(chatId, `${projectName} disconnected`);
483
591
  }
@@ -518,7 +626,51 @@ function showStatus(config) {
518
626
  console.log("State: not initialized (run 'ralph chat start' to initialize)");
519
627
  }
520
628
  console.log("");
521
- if (config.chat.provider === "telegram") {
629
+ if (config.chat.provider === "slack") {
630
+ if (config.chat.slack?.botToken &&
631
+ config.chat.slack?.appToken &&
632
+ config.chat.slack?.signingSecret) {
633
+ console.log("Slack: configured");
634
+ if (config.chat.slack.allowedChannelIds && config.chat.slack.allowedChannelIds.length > 0) {
635
+ console.log(`Allowed channels: ${config.chat.slack.allowedChannelIds.join(", ")}`);
636
+ }
637
+ else {
638
+ console.log("Allowed channels: all (no restrictions)");
639
+ }
640
+ }
641
+ else {
642
+ const missing = [];
643
+ if (!config.chat.slack?.botToken)
644
+ missing.push("botToken");
645
+ if (!config.chat.slack?.appToken)
646
+ missing.push("appToken");
647
+ if (!config.chat.slack?.signingSecret)
648
+ missing.push("signingSecret");
649
+ console.log(`Slack: not configured (missing: ${missing.join(", ")})`);
650
+ }
651
+ }
652
+ else if (config.chat.provider === "discord") {
653
+ if (config.chat.discord?.botToken) {
654
+ console.log("Discord: configured");
655
+ if (config.chat.discord.allowedGuildIds && config.chat.discord.allowedGuildIds.length > 0) {
656
+ console.log(`Allowed guilds: ${config.chat.discord.allowedGuildIds.join(", ")}`);
657
+ }
658
+ else {
659
+ console.log("Allowed guilds: all (no restrictions)");
660
+ }
661
+ if (config.chat.discord.allowedChannelIds &&
662
+ config.chat.discord.allowedChannelIds.length > 0) {
663
+ console.log(`Allowed channels: ${config.chat.discord.allowedChannelIds.join(", ")}`);
664
+ }
665
+ else {
666
+ console.log("Allowed channels: all (no restrictions)");
667
+ }
668
+ }
669
+ else {
670
+ console.log("Discord: not configured (missing botToken)");
671
+ }
672
+ }
673
+ else if (config.chat.provider === "telegram") {
522
674
  if (config.chat.telegram?.botToken) {
523
675
  console.log("Telegram: configured");
524
676
  if (config.chat.telegram.allowedChatIds && config.chat.telegram.allowedChatIds.length > 0) {
@@ -541,23 +693,68 @@ async function testChat(config, chatId) {
541
693
  console.error("Error: Chat is not enabled in config.json");
542
694
  process.exit(1);
543
695
  }
544
- if (!config.chat.telegram?.botToken) {
545
- console.error("Error: Telegram bot token not configured");
546
- process.exit(1);
696
+ const provider = config.chat.provider || "telegram";
697
+ let client;
698
+ let targetChatId;
699
+ if (provider === "slack") {
700
+ if (!config.chat.slack?.botToken ||
701
+ !config.chat.slack?.appToken ||
702
+ !config.chat.slack?.signingSecret) {
703
+ console.error("Error: Slack configuration incomplete");
704
+ console.error("Required: botToken, appToken, signingSecret");
705
+ process.exit(1);
706
+ }
707
+ targetChatId = chatId || config.chat.slack.allowedChannelIds?.[0];
708
+ if (!targetChatId) {
709
+ console.error("Error: No channel ID specified and no allowed channel IDs configured");
710
+ console.error("Usage: ralph chat test <channel_id>");
711
+ console.error("Or add channel IDs to chat.slack.allowedChannelIds in config.json");
712
+ process.exit(1);
713
+ }
714
+ client = createSlackClient({
715
+ botToken: config.chat.slack.botToken,
716
+ appToken: config.chat.slack.appToken,
717
+ signingSecret: config.chat.slack.signingSecret,
718
+ allowedChannelIds: config.chat.slack.allowedChannelIds,
719
+ });
547
720
  }
548
- // If no chat ID provided, use the first allowed chat ID
549
- const targetChatId = chatId || (config.chat.telegram.allowedChatIds?.[0]);
550
- if (!targetChatId) {
551
- console.error("Error: No chat ID specified and no allowed chat IDs configured");
552
- console.error("Usage: ralph chat test <chat_id>");
553
- console.error("Or add chat IDs to chat.telegram.allowedChatIds in config.json");
554
- process.exit(1);
721
+ else if (provider === "discord") {
722
+ if (!config.chat.discord?.botToken) {
723
+ console.error("Error: Discord bot token not configured");
724
+ process.exit(1);
725
+ }
726
+ targetChatId = chatId || config.chat.discord.allowedChannelIds?.[0];
727
+ if (!targetChatId) {
728
+ console.error("Error: No channel ID specified and no allowed channel IDs configured");
729
+ console.error("Usage: ralph chat test <channel_id>");
730
+ console.error("Or add channel IDs to chat.discord.allowedChannelIds in config.json");
731
+ process.exit(1);
732
+ }
733
+ client = createDiscordClient({
734
+ botToken: config.chat.discord.botToken,
735
+ allowedGuildIds: config.chat.discord.allowedGuildIds,
736
+ allowedChannelIds: config.chat.discord.allowedChannelIds,
737
+ });
555
738
  }
556
- const client = createTelegramClient({
557
- botToken: config.chat.telegram.botToken,
558
- allowedChatIds: config.chat.telegram.allowedChatIds,
559
- });
560
- console.log(`Testing connection to chat ${targetChatId}...`);
739
+ else {
740
+ // Telegram
741
+ if (!config.chat.telegram?.botToken) {
742
+ console.error("Error: Telegram bot token not configured");
743
+ process.exit(1);
744
+ }
745
+ targetChatId = chatId || config.chat.telegram.allowedChatIds?.[0];
746
+ if (!targetChatId) {
747
+ console.error("Error: No chat ID specified and no allowed chat IDs configured");
748
+ console.error("Usage: ralph chat test <chat_id>");
749
+ console.error("Or add chat IDs to chat.telegram.allowedChatIds in config.json");
750
+ process.exit(1);
751
+ }
752
+ client = createTelegramClient({
753
+ botToken: config.chat.telegram.botToken,
754
+ allowedChatIds: config.chat.telegram.allowedChatIds,
755
+ });
756
+ }
757
+ console.log(`Testing connection to ${provider} chat ${targetChatId}...`);
561
758
  try {
562
759
  // Just connect to verify credentials
563
760
  await client.connect(() => Promise.resolve());
@@ -584,7 +781,7 @@ export async function chat(args) {
584
781
  // Show help
585
782
  if (subcommand === "help" || subcommand === "--help" || subcommand === "-h" || !subcommand) {
586
783
  console.log(`
587
- ralph chat - Chat client integration (Telegram, etc.)
784
+ ralph chat - Chat client integration (Telegram, Slack, Discord)
588
785
 
589
786
  USAGE:
590
787
  ralph chat start [--debug] Start the chat daemon
@@ -595,6 +792,7 @@ USAGE:
595
792
  CONFIGURATION:
596
793
  Configure chat in .ralph/config.json:
597
794
 
795
+ Telegram:
598
796
  {
599
797
  "chat": {
600
798
  "enabled": true,
@@ -606,6 +804,33 @@ CONFIGURATION:
606
804
  }
607
805
  }
608
806
 
807
+ Slack:
808
+ {
809
+ "chat": {
810
+ "enabled": true,
811
+ "provider": "slack",
812
+ "slack": {
813
+ "botToken": "xoxb-YOUR-BOT-TOKEN",
814
+ "appToken": "xapp-YOUR-APP-TOKEN",
815
+ "signingSecret": "YOUR_SIGNING_SECRET",
816
+ "allowedChannelIds": ["C01234567"]
817
+ }
818
+ }
819
+ }
820
+
821
+ Discord:
822
+ {
823
+ "chat": {
824
+ "enabled": true,
825
+ "provider": "discord",
826
+ "discord": {
827
+ "botToken": "YOUR_BOT_TOKEN",
828
+ "allowedGuildIds": ["123456789"],
829
+ "allowedChannelIds": ["987654321"]
830
+ }
831
+ }
832
+ }
833
+
609
834
  TELEGRAM SETUP:
610
835
  1. Create a bot with @BotFather on Telegram
611
836
  2. Copy the bot token to chat.telegram.botToken
@@ -616,8 +841,40 @@ TELEGRAM SETUP:
616
841
  Example: https://api.telegram.org/bot123456:ABC-xyz/getUpdates
617
842
  5. Add the chat ID to chat.telegram.allowedChatIds (optional security)
618
843
 
844
+ SLACK SETUP:
845
+ 1. Create a Slack app at https://api.slack.com/apps
846
+ 2. Enable Socket Mode in the app settings
847
+ 3. Generate an App-Level Token with connections:write scope (xapp-...)
848
+ 4. Under OAuth & Permissions, add these Bot Token Scopes:
849
+ - chat:write (send messages)
850
+ - channels:history (read public channel messages)
851
+ - groups:history (read private channel messages)
852
+ - im:history (read direct messages)
853
+ - commands (for slash commands, optional)
854
+ 5. Install the app to your workspace
855
+ 6. Copy the Bot User OAuth Token (xoxb-...) to chat.slack.botToken
856
+ 7. Copy the App Token (xapp-...) to chat.slack.appToken
857
+ 8. Copy the Signing Secret to chat.slack.signingSecret
858
+ 9. Invite the bot to channels: /invite @your-bot-name
859
+ 10. Add channel IDs to chat.slack.allowedChannelIds (optional security)
860
+
861
+ DISCORD SETUP:
862
+ 1. Create an application at https://discord.com/developers/applications
863
+ 2. Go to "Bot" section and click "Add Bot"
864
+ 3. Enable these Privileged Gateway Intents:
865
+ - MESSAGE CONTENT INTENT (to read message content)
866
+ 4. Copy the bot token to chat.discord.botToken
867
+ 5. Go to "OAuth2" > "URL Generator":
868
+ - Select scopes: bot, applications.commands
869
+ - Select permissions: Send Messages, Read Message History, Use Slash Commands
870
+ 6. Use the generated URL to invite the bot to your server
871
+ 7. Get your guild (server) ID: Enable Developer Mode in Discord settings,
872
+ then right-click your server and "Copy Server ID"
873
+ 8. Get channel IDs: Right-click a channel and "Copy Channel ID"
874
+ 9. Add IDs to allowedGuildIds and allowedChannelIds (optional security)
875
+
619
876
  CHAT COMMANDS:
620
- Once connected, send commands to your Telegram bot:
877
+ Once connected, send commands to your bot:
621
878
 
622
879
  /run - Start ralph automation
623
880
  /status - Show PRD progress
@@ -629,8 +886,8 @@ CHAT COMMANDS:
629
886
  /help - Show help
630
887
 
631
888
  SECURITY:
632
- - Use allowedChatIds to restrict which chats can control ralph
633
- - Never share your bot token
889
+ - Use allowedChatIds/allowedChannelIds/allowedGuildIds to restrict access
890
+ - Never share your bot tokens
634
891
  - The daemon should run on the host, not in the container
635
892
 
636
893
  DAEMON ACTIONS:
@@ -651,16 +908,22 @@ DAEMON ACTIONS:
651
908
  }
652
909
  }
653
910
 
654
- Then trigger them via Telegram: /action build or /action deploy
911
+ Then trigger them via chat: /action build or /action deploy
655
912
 
656
913
  EXAMPLES:
657
914
  # Start the chat daemon
658
915
  ralph chat start
659
916
 
660
- # Test the connection
917
+ # Test the connection (Telegram)
661
918
  ralph chat test 123456789
662
919
 
663
- # In Telegram:
920
+ # Test the connection (Slack)
921
+ ralph chat test C01234567
922
+
923
+ # Test the connection (Discord)
924
+ ralph chat test 123456789012345678
925
+
926
+ # In Telegram/Slack/Discord:
664
927
  /run # Start ralph automation
665
928
  /status # Show task progress
666
929
  /add Fix login # Add new task
@@ -44,8 +44,9 @@ SECTIONS:
44
44
  Docker Ports, volumes, environment, packages
45
45
  Daemon Actions, socket path
46
46
  Claude MCP servers, skills
47
- Chat Telegram integration
47
+ Chat Telegram, Slack, Discord integration
48
48
  Notify Notification settings
49
+ LLM LLM provider configuration (Anthropic, OpenAI, Ollama)
49
50
  `;
50
51
  console.log(helpText.trim());
51
52
  }
@@ -1,8 +1,5 @@
1
- export interface DaemonAction {
2
- command: string;
3
- description?: string;
4
- ntfyUrl?: string;
5
- }
1
+ import { DaemonAction } from "../utils/daemon-actions.js";
2
+ export type { DaemonAction };
6
3
  export interface DaemonConfig {
7
4
  enabled?: boolean;
8
5
  actions?: Record<string, DaemonAction>;