twinclaw 1.2.6 → 1.2.8

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.
@@ -109,8 +109,18 @@ function detectActiveFeatures() {
109
109
  * Individual model conditions only generate issues when no model key is available at all.
110
110
  */
111
111
  function hasAnyModelKey() {
112
- const modelKeys = ['MODAL_API_KEY', 'OPENROUTER_API_KEY', 'GEMINI_API_KEY', 'GITHUB_TOKEN'];
113
- return modelKeys.some((key) => getSecretVaultService().readSecret(key) !== null);
112
+ const modelKeys = ['MODAL_API_KEY', 'OPENROUTER_API_KEY', 'GEMINI_API_KEY', 'GITHUB_TOKEN', 'GROQ_API_KEY'];
113
+ // Check secret vault first
114
+ const vaultKeys = modelKeys.some((key) => getSecretVaultService().readSecret(key) !== null);
115
+ if (vaultKeys)
116
+ return true;
117
+ // Also check config file via getConfigValue (sync)
118
+ const modalKey = getConfigValue('MODAL_API_KEY');
119
+ const openRouterKey = getConfigValue('OPENROUTER_API_KEY');
120
+ const geminiKey = getConfigValue('GEMINI_API_KEY');
121
+ const githubKey = getConfigValue('GITHUB_TOKEN');
122
+ const groqKey = getConfigValue('GROQ_API_KEY');
123
+ return !!(modalKey || openRouterKey || geminiKey || githubKey || groqKey);
114
124
  }
115
125
  // ── Public API ───────────────────────────────────────────────────────────────
116
126
  /**
@@ -9,6 +9,16 @@ import { logThought } from '../utils/logger.js';
9
9
  import { getConfigValue } from '../config/json-config.js';
10
10
  const { Client, LocalAuth, MessageMedia } = WAWebJS;
11
11
  const RATE_LIMIT_MS = 1500;
12
+ const WHATSAPP_COMMANDS = [
13
+ { cmd: '/start', desc: 'Show welcome message and menu' },
14
+ { cmd: '/help', desc: 'Show all commands' },
15
+ { cmd: '/menu', desc: 'Show menu' },
16
+ { cmd: '/status', desc: 'Show gateway status' },
17
+ { cmd: '/models', desc: 'Show configured models' },
18
+ { cmd: '/keys', desc: 'Show API keys status' },
19
+ { cmd: '/channel', desc: 'Show channel status' },
20
+ { cmd: '/clear', desc: 'Clear conversation' },
21
+ ];
12
22
  /**
13
23
  * Create MessageMedia from URL
14
24
  */
@@ -59,6 +69,73 @@ export class WhatsAppHandler {
59
69
  this.#maxReconnectDelayMs = options.maxReconnectDelayMs ?? 60000;
60
70
  this.#registerListeners();
61
71
  }
72
+ // ── Command Handlers ─────────────────────────────────────────────────────────
73
+ async handleCommand(chatId, command) {
74
+ const cmd = command.toLowerCase().trim();
75
+ switch (cmd) {
76
+ case '/start':
77
+ case '/menu':
78
+ const menuText = `🎯 *TwinClaw Menu*
79
+
80
+ ${WHATSAPP_COMMANDS.map(c => `${c.cmd} - ${c.desc}`).join('\n')}
81
+
82
+ _How can I help you today?_
83
+ `;
84
+ await this.sendText(chatId, menuText);
85
+ break;
86
+ case '/help':
87
+ await this.sendText(chatId, `📋 *Available Commands:*
88
+
89
+ ${WHATSAPP_COMMANDS.map(c => `${c.cmd} - ${c.desc}`).join('\n')}
90
+
91
+ _Just send me a message and I'll respond!_
92
+ `);
93
+ break;
94
+ case '/status':
95
+ await this.sendText(chatId, `✅ *Gateway Status:*
96
+
97
+ • Status: Running
98
+ • Platform: WhatsApp
99
+ • Mode: AI Assistant
100
+
101
+ _All systems operational_
102
+ `);
103
+ break;
104
+ case '/models':
105
+ case '/model':
106
+ await this.sendText(chatId, `📦 *Models*
107
+
108
+ Use *twinclaw config* to manage models.
109
+
110
+ Or run: *twinclaw config model* in terminal
111
+ `);
112
+ break;
113
+ case '/keys':
114
+ case '/key':
115
+ await this.sendText(chatId, `🔑 *API Keys*
116
+
117
+ Use *twinclaw config* to manage API keys.
118
+
119
+ Or run: *twinclaw config* in terminal
120
+ `);
121
+ break;
122
+ case '/channel':
123
+ case '/channels':
124
+ await this.sendText(chatId, `📱 *Channels*
125
+
126
+ WhatsApp: ✅ Connected
127
+ Telegram: Use /channels to check
128
+
129
+ Run: *twinclaw channels* in terminal
130
+ `);
131
+ break;
132
+ case '/clear':
133
+ await this.sendText(chatId, '🗑️ Conversation cleared! Starting fresh.');
134
+ break;
135
+ default:
136
+ await this.sendText(chatId, `Unknown command: ${command}\nType /menu for available commands.`);
137
+ }
138
+ }
62
139
  onMessage;
63
140
  async #applyRateLimit() {
64
141
  const elapsed = Date.now() - this.#lastMessageAt;
@@ -166,6 +243,13 @@ export class WhatsAppHandler {
166
243
  }
167
244
  // Exclude empty bodies unless they were intercepted voice notes
168
245
  if (msg.body) {
246
+ const text = msg.body;
247
+ // Handle commands
248
+ if (text.trim().startsWith('/')) {
249
+ const command = text.trim().split(' ')[0];
250
+ await this.handleCommand(msg.from, command);
251
+ return;
252
+ }
169
253
  await this.onMessage?.(base);
170
254
  }
171
255
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "twinclaw",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "description": "Eagle-eyed agentic AI gateway with multi-modal hooks and proactive memory.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {