ralph-cli-sandboxed 0.4.1 → 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 (69) hide show
  1. package/README.md +30 -0
  2. package/dist/commands/action.js +9 -9
  3. package/dist/commands/chat.js +13 -12
  4. package/dist/commands/config.js +2 -1
  5. package/dist/commands/daemon.js +4 -3
  6. package/dist/commands/docker.js +102 -66
  7. package/dist/commands/fix-config.js +2 -1
  8. package/dist/commands/fix-prd.js +2 -2
  9. package/dist/commands/init.js +78 -17
  10. package/dist/commands/listen.js +3 -1
  11. package/dist/commands/notify.js +1 -1
  12. package/dist/commands/once.js +17 -9
  13. package/dist/commands/prd.js +4 -1
  14. package/dist/commands/run.js +40 -25
  15. package/dist/commands/slack.js +2 -2
  16. package/dist/config/responder-presets.json +69 -0
  17. package/dist/index.js +1 -1
  18. package/dist/providers/discord.d.ts +28 -0
  19. package/dist/providers/discord.js +227 -14
  20. package/dist/providers/slack.d.ts +41 -1
  21. package/dist/providers/slack.js +389 -8
  22. package/dist/providers/telegram.d.ts +30 -0
  23. package/dist/providers/telegram.js +185 -5
  24. package/dist/responders/claude-code-responder.d.ts +48 -0
  25. package/dist/responders/claude-code-responder.js +203 -0
  26. package/dist/responders/cli-responder.d.ts +62 -0
  27. package/dist/responders/cli-responder.js +298 -0
  28. package/dist/responders/llm-responder.d.ts +135 -0
  29. package/dist/responders/llm-responder.js +582 -0
  30. package/dist/templates/macos-scripts.js +2 -4
  31. package/dist/templates/prompts.js +4 -2
  32. package/dist/tui/ConfigEditor.js +19 -5
  33. package/dist/tui/components/ArrayEditor.js +1 -1
  34. package/dist/tui/components/EditorPanel.js +10 -6
  35. package/dist/tui/components/HelpPanel.d.ts +1 -1
  36. package/dist/tui/components/HelpPanel.js +1 -1
  37. package/dist/tui/components/JsonSnippetEditor.js +8 -5
  38. package/dist/tui/components/KeyValueEditor.js +54 -9
  39. package/dist/tui/components/LLMProvidersEditor.d.ts +22 -0
  40. package/dist/tui/components/LLMProvidersEditor.js +357 -0
  41. package/dist/tui/components/ObjectEditor.js +1 -1
  42. package/dist/tui/components/Preview.js +1 -1
  43. package/dist/tui/components/RespondersEditor.d.ts +22 -0
  44. package/dist/tui/components/RespondersEditor.js +437 -0
  45. package/dist/tui/components/SectionNav.js +27 -3
  46. package/dist/utils/chat-client.d.ts +4 -0
  47. package/dist/utils/chat-client.js +12 -5
  48. package/dist/utils/config.d.ts +84 -0
  49. package/dist/utils/config.js +78 -1
  50. package/dist/utils/daemon-client.d.ts +21 -0
  51. package/dist/utils/daemon-client.js +28 -1
  52. package/dist/utils/llm-client.d.ts +82 -0
  53. package/dist/utils/llm-client.js +185 -0
  54. package/dist/utils/message-queue.js +6 -6
  55. package/dist/utils/notification.d.ts +6 -1
  56. package/dist/utils/notification.js +103 -2
  57. package/dist/utils/prd-validator.js +60 -19
  58. package/dist/utils/prompt.js +22 -12
  59. package/dist/utils/responder-logger.d.ts +47 -0
  60. package/dist/utils/responder-logger.js +129 -0
  61. package/dist/utils/responder-presets.d.ts +92 -0
  62. package/dist/utils/responder-presets.js +156 -0
  63. package/dist/utils/responder.d.ts +88 -0
  64. package/dist/utils/responder.js +207 -0
  65. package/dist/utils/stream-json.js +6 -6
  66. package/docs/CHAT-RESPONDERS.md +785 -0
  67. package/docs/DEVELOPMENT.md +25 -0
  68. package/docs/chat-architecture.md +251 -0
  69. package/package.json +11 -1
@@ -4,6 +4,11 @@
4
4
  */
5
5
  import https from "https";
6
6
  import { parseCommand, escapeHtml, } from "../utils/chat-client.js";
7
+ import { ResponderMatcher } from "../utils/responder.js";
8
+ import { loadConfig } from "../utils/config.js";
9
+ import { executeLLMResponder } from "../responders/llm-responder.js";
10
+ import { executeClaudeCodeResponder } from "../responders/claude-code-responder.js";
11
+ import { executeCLIResponder } from "../responders/cli-responder.js";
7
12
  export class TelegramChatClient {
8
13
  provider = "telegram";
9
14
  settings;
@@ -12,9 +17,134 @@ export class TelegramChatClient {
12
17
  lastUpdateId = 0;
13
18
  pollingTimeout = null;
14
19
  debug;
20
+ responderMatcher = null;
21
+ respondersConfig = null;
22
+ botUserId = null;
23
+ botUsername = null;
15
24
  constructor(settings, debug = false) {
16
25
  this.settings = settings;
17
26
  this.debug = debug;
27
+ // Initialize responders from config if available
28
+ this.initializeResponders();
29
+ }
30
+ /**
31
+ * Initialize responder matching from config.
32
+ */
33
+ initializeResponders() {
34
+ try {
35
+ const config = loadConfig();
36
+ if (config.chat?.responders) {
37
+ this.respondersConfig = config.chat.responders;
38
+ this.responderMatcher = new ResponderMatcher(config.chat.responders);
39
+ if (this.debug) {
40
+ console.log(`[telegram] Initialized ${Object.keys(config.chat.responders).length} responders`);
41
+ }
42
+ }
43
+ }
44
+ catch {
45
+ // Config not available or responders not configured
46
+ if (this.debug) {
47
+ console.log("[telegram] No responders configured");
48
+ }
49
+ }
50
+ }
51
+ /**
52
+ * Execute a responder and return the result.
53
+ */
54
+ async executeResponder(match, message) {
55
+ const { responder } = match;
56
+ switch (responder.type) {
57
+ case "llm":
58
+ return executeLLMResponder(message, responder);
59
+ case "claude-code":
60
+ return executeClaudeCodeResponder(message, responder);
61
+ case "cli":
62
+ return executeCLIResponder(message, responder);
63
+ default:
64
+ return {
65
+ success: false,
66
+ response: "",
67
+ error: `Unknown responder type: ${responder.type}`,
68
+ };
69
+ }
70
+ }
71
+ /**
72
+ * Handle a message that might match a responder.
73
+ * Returns true if a responder was matched and executed.
74
+ */
75
+ async handleResponderMessage(message, messageId) {
76
+ if (!this.responderMatcher) {
77
+ return false;
78
+ }
79
+ const match = this.responderMatcher.matchResponder(message.text);
80
+ if (!match) {
81
+ return false;
82
+ }
83
+ if (this.debug) {
84
+ console.log(`[telegram] Matched responder: ${match.name} (type: ${match.responder.type})`);
85
+ }
86
+ // Execute the responder
87
+ const result = await this.executeResponder(match, match.args || message.text);
88
+ // Send the response (reply to the original message for context)
89
+ if (result.success) {
90
+ await this.sendMessage(message.chatId, result.response, {
91
+ replyToMessageId: messageId,
92
+ });
93
+ }
94
+ else {
95
+ const errorMsg = result.error
96
+ ? `Error: ${result.error}`
97
+ : "An error occurred while processing your message.";
98
+ await this.sendMessage(message.chatId, errorMsg, {
99
+ replyToMessageId: messageId,
100
+ });
101
+ }
102
+ return true;
103
+ }
104
+ /**
105
+ * Check if the bot is mentioned in a message.
106
+ * Handles both @username mentions and direct replies to the bot.
107
+ */
108
+ isBotMentioned(update) {
109
+ const msg = update.message;
110
+ if (!msg)
111
+ return false;
112
+ // Check if this is a reply to the bot's message
113
+ if (msg.reply_to_message?.from?.id === this.botUserId) {
114
+ return true;
115
+ }
116
+ // Check for @mention in message entities
117
+ if (msg.entities && this.botUsername) {
118
+ for (const entity of msg.entities) {
119
+ if (entity.type === "mention" && msg.text) {
120
+ const mention = msg.text.substring(entity.offset, entity.offset + entity.length);
121
+ if (mention.toLowerCase() === `@${this.botUsername.toLowerCase()}`) {
122
+ return true;
123
+ }
124
+ }
125
+ if (entity.type === "text_mention" && entity.user?.id === this.botUserId) {
126
+ return true;
127
+ }
128
+ }
129
+ }
130
+ return false;
131
+ }
132
+ /**
133
+ * Remove bot mention from message text.
134
+ */
135
+ removeBotMention(text) {
136
+ if (!this.botUsername) {
137
+ return text;
138
+ }
139
+ // Remove @username and any surrounding whitespace
140
+ const regex = new RegExp(`@${this.botUsername}\\s*`, "gi");
141
+ return text.replace(regex, "").trim();
142
+ }
143
+ /**
144
+ * Check if a chat is a group chat (group or supergroup).
145
+ */
146
+ isGroupChat(chatType) {
147
+ return chatType === "group" || chatType === "supergroup";
18
148
  }
19
149
  /**
20
150
  * Make a request to the Telegram Bot API.
@@ -128,6 +258,8 @@ export class TelegramChatClient {
128
258
  }
129
259
  if (update.message?.text) {
130
260
  const chatId = String(update.message.chat.id);
261
+ const messageId = update.message.message_id;
262
+ const chatType = update.message.chat.type;
131
263
  // Check if chat is allowed
132
264
  if (!this.isChatAllowed(chatId)) {
133
265
  if (this.debug) {
@@ -135,12 +267,22 @@ export class TelegramChatClient {
135
267
  }
136
268
  continue;
137
269
  }
270
+ // In group chats, only process messages that mention the bot or are replies to the bot
271
+ const isGroup = this.isGroupChat(chatType);
272
+ const isMention = this.isBotMentioned(update);
273
+ // Clean the message text (remove bot mention if present)
274
+ let messageText = update.message.text;
275
+ if (isMention) {
276
+ messageText = this.removeBotMention(messageText);
277
+ }
138
278
  const message = {
139
- text: update.message.text,
279
+ text: messageText,
140
280
  chatId,
141
281
  senderId: update.message.from ? String(update.message.from.id) : undefined,
142
282
  senderName: update.message.from
143
- ? [update.message.from.first_name, update.message.from.last_name].filter(Boolean).join(" ")
283
+ ? [update.message.from.first_name, update.message.from.last_name]
284
+ .filter(Boolean)
285
+ .join(" ")
144
286
  : undefined,
145
287
  timestamp: new Date(update.message.date * 1000),
146
288
  raw: update,
@@ -156,7 +298,7 @@ export class TelegramChatClient {
156
298
  }
157
299
  }
158
300
  }
159
- // Try to parse as a command
301
+ // Try to parse as a command first
160
302
  const command = parseCommand(message.text, message);
161
303
  if (command) {
162
304
  try {
@@ -167,7 +309,39 @@ export class TelegramChatClient {
167
309
  console.error(`[telegram] Command handler error: ${err}`);
168
310
  }
169
311
  // Send error message to chat
170
- await this.sendMessage(chatId, `Error executing command: ${err instanceof Error ? err.message : "Unknown error"}`);
312
+ await this.sendMessage(chatId, `Error executing command: ${err instanceof Error ? err.message : "Unknown error"}`, {
313
+ replyToMessageId: messageId,
314
+ });
315
+ }
316
+ continue; // Command handled, don't process as responder message
317
+ }
318
+ // For non-command messages, route through responders
319
+ // In group chats: only respond if bot is mentioned or message is a reply to bot
320
+ // In private chats: always respond if responders are configured
321
+ const shouldProcessAsResponder = !isGroup || isMention;
322
+ if (shouldProcessAsResponder && this.responderMatcher) {
323
+ // Check if there's a matching responder or a default responder
324
+ const hasDefaultResponder = this.responderMatcher.hasDefaultResponder();
325
+ const match = this.responderMatcher.matchResponder(message.text);
326
+ if (match) {
327
+ try {
328
+ const handled = await this.handleResponderMessage(message, messageId);
329
+ if (handled) {
330
+ continue;
331
+ }
332
+ }
333
+ catch (err) {
334
+ if (this.debug) {
335
+ console.error(`[telegram] Responder error: ${err}`);
336
+ }
337
+ await this.sendMessage(chatId, `Error processing message: ${err instanceof Error ? err.message : "Unknown error"}`, { replyToMessageId: messageId });
338
+ continue;
339
+ }
340
+ }
341
+ else if (isMention && !hasDefaultResponder) {
342
+ // Bot was mentioned but no responder matched and no default responder
343
+ // Send a helpful message
344
+ await this.sendMessage(chatId, "I received your message, but no responders are configured. Use /help for available commands.", { replyToMessageId: messageId });
171
345
  }
172
346
  }
173
347
  }
@@ -189,9 +363,11 @@ export class TelegramChatClient {
189
363
  if (this.connected) {
190
364
  throw new Error("Already connected");
191
365
  }
192
- // Verify bot token by calling getMe
366
+ // Verify bot token by calling getMe and store bot info
193
367
  try {
194
368
  const me = await this.apiRequest("getMe");
369
+ this.botUserId = me.id;
370
+ this.botUsername = me.username || null;
195
371
  if (this.debug) {
196
372
  console.log(`[telegram] Connected as @${me.username || me.first_name} (ID: ${me.id})`);
197
373
  }
@@ -216,6 +392,10 @@ export class TelegramChatClient {
216
392
  text: escapedText,
217
393
  parse_mode: "HTML",
218
394
  };
395
+ // Add reply_to_message_id for context in group chats
396
+ if (options?.replyToMessageId) {
397
+ body.reply_to_message_id = options.replyToMessageId;
398
+ }
219
399
  // Convert generic InlineButton format to Telegram's InlineKeyboardMarkup
220
400
  if (options?.inlineKeyboard && options.inlineKeyboard.length > 0) {
221
401
  const inlineKeyboard = options.inlineKeyboard.map((row) => row.map((button) => ({
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Claude Code Responder - Spawns Claude Code CLI with user prompts.
3
+ * Executes Claude Code in --dangerously-skip-permissions mode to run autonomously.
4
+ */
5
+ import { ResponderConfig } from "../utils/config.js";
6
+ import { ResponderResult } from "./llm-responder.js";
7
+ /**
8
+ * Options for executing a Claude Code responder.
9
+ */
10
+ export interface ClaudeCodeResponderOptions {
11
+ /** Callback for progress updates during execution */
12
+ onProgress?: (output: string) => void;
13
+ /** Override default timeout in milliseconds */
14
+ timeout?: number;
15
+ /** Maximum response length in characters */
16
+ maxLength?: number;
17
+ /** Working directory for Claude Code execution */
18
+ cwd?: string;
19
+ }
20
+ /**
21
+ * Executes Claude Code with the given prompt.
22
+ *
23
+ * Spawns the claude CLI with:
24
+ * - -p flag for non-interactive prompt mode
25
+ * - --dangerously-skip-permissions to skip all permission prompts
26
+ * - --print to get clean output
27
+ *
28
+ * @param prompt The user prompt to send to Claude Code
29
+ * @param responderConfig The responder configuration
30
+ * @param options Optional execution options
31
+ * @returns The responder result with response or error
32
+ */
33
+ export declare function executeClaudeCodeResponder(prompt: string, responderConfig: ResponderConfig, options?: ClaudeCodeResponderOptions): Promise<ResponderResult>;
34
+ /**
35
+ * Creates a reusable Claude Code responder function.
36
+ * This is useful for handling multiple messages with the same configuration.
37
+ *
38
+ * @param responderConfig The responder configuration
39
+ * @returns A function that executes the responder with a prompt
40
+ */
41
+ export declare function createClaudeCodeResponder(responderConfig: ResponderConfig): (prompt: string, options?: ClaudeCodeResponderOptions) => Promise<ResponderResult>;
42
+ /**
43
+ * Validates that a responder configuration is valid for Claude Code execution.
44
+ *
45
+ * @param responderConfig The responder configuration to validate
46
+ * @returns An error message if invalid, or null if valid
47
+ */
48
+ export declare function validateClaudeCodeResponder(responderConfig: ResponderConfig): string | null;
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Claude Code Responder - Spawns Claude Code CLI with user prompts.
3
+ * Executes Claude Code in --dangerously-skip-permissions mode to run autonomously.
4
+ */
5
+ import { spawn } from "child_process";
6
+ import { truncateResponse } from "./llm-responder.js";
7
+ /**
8
+ * Default timeout for Claude Code execution (5 minutes).
9
+ */
10
+ const DEFAULT_TIMEOUT = 300000;
11
+ /**
12
+ * Default max length for chat responses (characters).
13
+ */
14
+ const DEFAULT_MAX_LENGTH = 2000;
15
+ /**
16
+ * Interval for sending progress updates (milliseconds).
17
+ */
18
+ const PROGRESS_INTERVAL = 5000;
19
+ /**
20
+ * Executes Claude Code with the given prompt.
21
+ *
22
+ * Spawns the claude CLI with:
23
+ * - -p flag for non-interactive prompt mode
24
+ * - --dangerously-skip-permissions to skip all permission prompts
25
+ * - --print to get clean output
26
+ *
27
+ * @param prompt The user prompt to send to Claude Code
28
+ * @param responderConfig The responder configuration
29
+ * @param options Optional execution options
30
+ * @returns The responder result with response or error
31
+ */
32
+ export async function executeClaudeCodeResponder(prompt, responderConfig, options) {
33
+ const timeout = options?.timeout ?? responderConfig.timeout ?? DEFAULT_TIMEOUT;
34
+ const maxLength = options?.maxLength ?? responderConfig.maxLength ?? DEFAULT_MAX_LENGTH;
35
+ const cwd = options?.cwd ?? process.cwd();
36
+ const onProgress = options?.onProgress;
37
+ return new Promise((resolve) => {
38
+ let stdout = "";
39
+ let stderr = "";
40
+ let killed = false;
41
+ let lastProgressSent = 0;
42
+ let progressTimer = null;
43
+ // Build the command arguments
44
+ const args = ["-p", prompt, "--dangerously-skip-permissions", "--print"];
45
+ // Spawn claude process
46
+ let proc;
47
+ try {
48
+ proc = spawn("claude", args, {
49
+ cwd,
50
+ stdio: ["ignore", "pipe", "pipe"],
51
+ env: { ...process.env },
52
+ });
53
+ }
54
+ catch (err) {
55
+ const error = err instanceof Error ? err.message : String(err);
56
+ resolve({
57
+ success: false,
58
+ response: "",
59
+ error: `Failed to spawn claude: ${error}`,
60
+ });
61
+ return;
62
+ }
63
+ // Handle timeout
64
+ const timeoutTimer = setTimeout(() => {
65
+ killed = true;
66
+ proc.kill("SIGTERM");
67
+ // Give it a moment to terminate gracefully, then force kill
68
+ setTimeout(() => {
69
+ try {
70
+ proc.kill("SIGKILL");
71
+ }
72
+ catch {
73
+ // Already dead
74
+ }
75
+ }, 2000);
76
+ if (progressTimer) {
77
+ clearInterval(progressTimer);
78
+ }
79
+ resolve({
80
+ success: false,
81
+ response: stdout,
82
+ error: `Claude Code timed out after ${Math.round(timeout / 1000)} seconds`,
83
+ });
84
+ }, timeout);
85
+ // Set up progress updates
86
+ if (onProgress) {
87
+ progressTimer = setInterval(() => {
88
+ const now = Date.now();
89
+ if (now - lastProgressSent >= PROGRESS_INTERVAL && stdout.length > 0) {
90
+ // Send a progress indicator
91
+ const lines = stdout.split("\n");
92
+ const lastLine = lines[lines.length - 1] || lines[lines.length - 2] || "";
93
+ const truncatedLine = lastLine.length > 100 ? lastLine.substring(0, 100) + "..." : lastLine;
94
+ onProgress(`⏳ Working... ${truncatedLine}`);
95
+ lastProgressSent = now;
96
+ }
97
+ }, PROGRESS_INTERVAL);
98
+ }
99
+ // Capture stdout
100
+ proc.stdout?.on("data", (data) => {
101
+ stdout += data.toString();
102
+ });
103
+ // Capture stderr
104
+ proc.stderr?.on("data", (data) => {
105
+ stderr += data.toString();
106
+ });
107
+ // Handle process completion
108
+ proc.on("close", (code) => {
109
+ if (killed)
110
+ return;
111
+ clearTimeout(timeoutTimer);
112
+ if (progressTimer) {
113
+ clearInterval(progressTimer);
114
+ }
115
+ if (code === 0 || code === null) {
116
+ // Success - format and truncate output
117
+ const output = formatClaudeCodeOutput(stdout);
118
+ const { text, truncated, originalLength } = truncateResponse(output, maxLength);
119
+ resolve({
120
+ success: true,
121
+ response: text,
122
+ truncated,
123
+ originalLength: truncated ? originalLength : undefined,
124
+ });
125
+ }
126
+ else {
127
+ // Failure
128
+ const errorMsg = stderr.trim() || `Claude Code exited with code ${code}`;
129
+ resolve({
130
+ success: false,
131
+ response: stdout,
132
+ error: errorMsg,
133
+ });
134
+ }
135
+ });
136
+ // Handle spawn errors
137
+ proc.on("error", (err) => {
138
+ if (killed)
139
+ return;
140
+ clearTimeout(timeoutTimer);
141
+ if (progressTimer) {
142
+ clearInterval(progressTimer);
143
+ }
144
+ resolve({
145
+ success: false,
146
+ response: "",
147
+ error: `Claude Code error: ${err.message}`,
148
+ });
149
+ });
150
+ });
151
+ }
152
+ /**
153
+ * Formats Claude Code output for chat display.
154
+ * Cleans up ANSI codes, excessive whitespace, and formats for readability.
155
+ */
156
+ function formatClaudeCodeOutput(output) {
157
+ // Remove ANSI escape codes
158
+ let cleaned = output.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
159
+ // Remove carriage returns (used for progress overwriting)
160
+ cleaned = cleaned.replace(/\r/g, "");
161
+ // Collapse multiple blank lines into one
162
+ cleaned = cleaned.replace(/\n{3,}/g, "\n\n");
163
+ // Trim leading/trailing whitespace
164
+ cleaned = cleaned.trim();
165
+ // If output is empty, provide a default message
166
+ if (!cleaned) {
167
+ return "(Claude Code completed with no output)";
168
+ }
169
+ return cleaned;
170
+ }
171
+ /**
172
+ * Creates a reusable Claude Code responder function.
173
+ * This is useful for handling multiple messages with the same configuration.
174
+ *
175
+ * @param responderConfig The responder configuration
176
+ * @returns A function that executes the responder with a prompt
177
+ */
178
+ export function createClaudeCodeResponder(responderConfig) {
179
+ return async (prompt, options) => {
180
+ return executeClaudeCodeResponder(prompt, responderConfig, options);
181
+ };
182
+ }
183
+ /**
184
+ * Validates that a responder configuration is valid for Claude Code execution.
185
+ *
186
+ * @param responderConfig The responder configuration to validate
187
+ * @returns An error message if invalid, or null if valid
188
+ */
189
+ export function validateClaudeCodeResponder(responderConfig) {
190
+ if (responderConfig.type !== "claude-code") {
191
+ return `Responder type is "${responderConfig.type}", expected "claude-code"`;
192
+ }
193
+ // Check that timeout is reasonable if specified
194
+ if (responderConfig.timeout !== undefined) {
195
+ if (responderConfig.timeout < 1000) {
196
+ return `Timeout ${responderConfig.timeout}ms is too short (minimum: 1000ms)`;
197
+ }
198
+ if (responderConfig.timeout > 600000) {
199
+ return `Timeout ${responderConfig.timeout}ms is too long (maximum: 600000ms / 10 minutes)`;
200
+ }
201
+ }
202
+ return null;
203
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * CLI Responder - Executes configured CLI commands with user messages.
3
+ * Useful for integrating with aider, custom scripts, or other AI CLIs.
4
+ */
5
+ import { ResponderConfig } from "../utils/config.js";
6
+ import { ResponderResult } from "./llm-responder.js";
7
+ /**
8
+ * Options for executing a CLI responder.
9
+ */
10
+ export interface CLIResponderOptions {
11
+ /** Callback for progress updates during execution */
12
+ onProgress?: (output: string) => void;
13
+ /** Override default timeout in milliseconds */
14
+ timeout?: number;
15
+ /** Maximum response length in characters */
16
+ maxLength?: number;
17
+ /** Working directory for command execution */
18
+ cwd?: string;
19
+ /** Additional environment variables */
20
+ env?: Record<string, string>;
21
+ }
22
+ /**
23
+ * Replaces {{message}} placeholder in command string with the actual message.
24
+ * Escapes the message to prevent shell injection.
25
+ */
26
+ export declare function replaceMessagePlaceholder(command: string, message: string): string;
27
+ /**
28
+ * Parses a command string into command and arguments.
29
+ * Handles quoted strings and basic shell syntax.
30
+ */
31
+ export declare function parseCommand(commandString: string): {
32
+ command: string;
33
+ args: string[];
34
+ };
35
+ /**
36
+ * Executes a CLI command with the given message.
37
+ *
38
+ * The command string can include {{message}} placeholder which will be replaced
39
+ * with the user's message. If no placeholder is present, the message is appended
40
+ * as an argument.
41
+ *
42
+ * @param message The user message to include in the command
43
+ * @param responderConfig The responder configuration
44
+ * @param options Optional execution options
45
+ * @returns The responder result with response or error
46
+ */
47
+ export declare function executeCLIResponder(message: string, responderConfig: ResponderConfig, options?: CLIResponderOptions): Promise<ResponderResult>;
48
+ /**
49
+ * Creates a reusable CLI responder function.
50
+ * This is useful for handling multiple messages with the same configuration.
51
+ *
52
+ * @param responderConfig The responder configuration
53
+ * @returns A function that executes the responder with a message
54
+ */
55
+ export declare function createCLIResponder(responderConfig: ResponderConfig): (message: string, options?: CLIResponderOptions) => Promise<ResponderResult>;
56
+ /**
57
+ * Validates that a responder configuration is valid for CLI execution.
58
+ *
59
+ * @param responderConfig The responder configuration to validate
60
+ * @returns An error message if invalid, or null if valid
61
+ */
62
+ export declare function validateCLIResponder(responderConfig: ResponderConfig): string | null;