squidclaw 2.2.0 β†’ 2.4.0

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.
@@ -23,7 +23,9 @@ export class ToolRouter {
23
23
  getToolDescriptions() {
24
24
  const tools = [
25
25
  '## Available Tools',
26
- 'You can use tools by including special tags in your response:',
26
+ 'You can use tools by including special tags in your response.',
27
+ 'IMPORTANT: You CAN send files, images, voice notes, and documents in this chat. The system handles delivery automatically.',
28
+ 'When asked to create files (PowerPoint, etc), USE THE TOOL β€” do NOT say you cannot send files.',
27
29
  '',
28
30
  '### Web Search',
29
31
  '---TOOL:search:your search query---',
@@ -59,19 +61,54 @@ export class ToolRouter {
59
61
  'Send an email.');
60
62
  }
61
63
 
62
- tools.push('', '### Create PowerPoint',
63
- '---TOOL:pptx:Title of Presentation---',
64
- 'Create a .pptx PowerPoint file. After calling this, generate slides using markdown format:',
65
- '## Slide Title',
66
- '- Bullet point 1',
67
- '- Bullet point 2',
64
+ tools.push('', '### Create PowerPoint (SENDS AS FILE IN CHAT!)',
65
+ '---TOOL:pptx_slides:Title|theme|slides content---',
66
+ 'Creates a .pptx file and SENDS IT directly. You MUST use this when asked for PPT/presentation.',
67
+ 'Format: title|theme|slides. Themes: dark, light, blue, green, corporate, red, gradient, minimal, saudi, ocean.',
68
68
  '',
69
- '## Another Slide',
70
- '- More content',
69
+ 'SLIDE TYPES (use tags in ## title):',
70
+ '- Normal: ## Title then - bullet points',
71
+ '- Chart: ## Revenue [chart:bar] then - Label: value (types: bar, pie, line, doughnut, area)',
72
+ '- Table: ## Data [table] then | Col1 | Col2 | rows',
73
+ '- Stats: ## Metrics [stats] then - πŸ“Š 100M β€” Active users',
74
+ '- Timeline: ## History [timeline] then - 2020: Event happened',
75
+ '- Quote: ## [quote] then quote text then β€” Author Name',
76
+ '- Compare: ## Comparison [compare] then left items, ---, right items',
77
+ '- Section: ## New Section [section]',
71
78
  '',
72
- 'Use ---TOOL:pptx_slides:## Slide 1\n- Point 1\n- Point 2\n\n## Slide 2\n- Point 3--- to generate the actual file.',
73
- 'Theme options after pipe: dark, light, blue, green, corporate, red',
74
- 'Example: ---TOOL:pptx_slides:title|theme|## Slide 1\n- point---');
79
+ 'Example:',
80
+ '---TOOL:pptx_slides:AI Report|dark|## Introduction\n- AI is transforming industries\n- Revenue growing 40% YoY\n\n## Growth [chart:bar]\n- 2020: 50\n- 2021: 80\n- 2022: 120\n- 2023: 200\n\n## Key Stats [stats]\n- 🌍 195 β€” Countries using AI\n- πŸ’° $500B β€” Market size\n- πŸš€ 40% β€” Annual growth---');
81
+
82
+ tools.push('', '### Create Excel Spreadsheet (SENDS AS FILE!)',
83
+ '---TOOL:excel:Title|theme|sheet content---',
84
+ 'Creates .xlsx file and sends it. Theme: blue, green, dark, red, saudi, corporate.',
85
+ 'Content: use | for columns. First row = headers. Separate sheets with ### SheetName.',
86
+ 'Example: ---TOOL:excel:Sales Report|blue|Product|Revenue|Growth\nWidget A|50000|12%\nWidget B|80000|25%---',
87
+ '', '### Create PDF Document (SENDS AS FILE!)',
88
+ '---TOOL:pdf:Title|content in markdown---',
89
+ 'Creates .pdf file and sends it. Use ## for headings, - for bullets, > for quotes, --- for dividers.',
90
+ 'Example: ---TOOL:pdf:Meeting Notes|## Summary\n- Discussed Q1 results\n- Action items assigned---',
91
+ '', '### Create HTML Page (SENDS AS FILE!)',
92
+ '---TOOL:html:Title|theme|content in markdown---',
93
+ 'Creates .html file and sends it. Themes: light, dark, ocean.',
94
+ 'Example: ---TOOL:html:Report|dark|## Overview\n- Key findings\n- Recommendations---');
95
+
96
+ tools.push('', '### Schedule Cron Job',
97
+ '---TOOL:cron_add:name|schedule|action|data---',
98
+ 'Create a recurring scheduled job. Schedule formats:',
99
+ '- every:5m / every:1h / every:24h (interval)',
100
+ '- daily:09:00 (every day at time UTC)',
101
+ '- weekly:fri:09:00 (every week on day)',
102
+ '- monthly:1:09:00 (every month on date)',
103
+ 'Actions: message (send text), briefing (daily brief), remind (reminder), ai (run AI prompt)',
104
+ 'Example: ---TOOL:cron_add:Morning Briefing|daily:06:00|briefing|---',
105
+ 'Example: ---TOOL:cron_add:Weekly Report Reminder|weekly:fri:09:00|remind|Submit the weekly report!---',
106
+ '', '### List Cron Jobs',
107
+ '---TOOL:cron_list:all---',
108
+ 'Show all scheduled jobs.',
109
+ '', '### Remove Cron Job',
110
+ '---TOOL:cron_remove:job_id---',
111
+ 'Remove a scheduled job by ID.');
75
112
 
76
113
  tools.push('', '### Allow User',
77
114
  '---TOOL:allow:user_id_or_phone---',
@@ -181,9 +218,16 @@ export class ToolRouter {
181
218
  'You already have vision β€” use it to read text from screenshots, documents, signs, etc.');
182
219
 
183
220
  tools.push('', '### Set Reminder',
184
- '---TOOL:remind:YYYY-MM-DDTHH:MM|Your reminder message---',
185
- 'Set a reminder to message the user at a specific time. Time must be in UTC.',
186
- 'Example: ---TOOL:remind:2026-03-03T08:30|Wake up! Time to start the day!---',
221
+ '---TOOL:remind:TIME|Your reminder message---',
222
+ 'Set a reminder. TIME can be:',
223
+ '- Relative: 5m, 30m, 1h, 2h, 1d (minutes, hours, days from now)',
224
+ '- Absolute: 2026-03-03T08:30 (UTC)',
225
+ '- Natural: tomorrow, tonight',
226
+ 'Examples:',
227
+ '- ---TOOL:remind:30m|Check the oven!---',
228
+ '- ---TOOL:remind:2h|Call Ahmed---',
229
+ '- ---TOOL:remind:2026-03-03T15:00|Meeting time!---',
230
+ 'ALWAYS use this tool when user says "remind me". Do NOT just acknowledge β€” actually set it.',
187
231
  'The user will receive a proactive message at that time even if they are not chatting.');
188
232
 
189
233
  tools.push('', '**Important:** Use tools when needed. The tool result will be injected into the conversation automatically. Only use one tool per response.');
@@ -202,7 +246,7 @@ export class ToolRouter {
202
246
  }
203
247
 
204
248
  async processResponse(response, agentId) {
205
- const toolMatch = response.match(/---TOOL:(\w+):(.+?)---/);
249
+ const toolMatch = response.match(/---TOOL:(\w+):([\s\S]+?)---/);
206
250
  if (!toolMatch) return { toolUsed: false, toolResult: null, cleanResponse: response };
207
251
 
208
252
  const [fullMatch, toolName, toolArg] = toolMatch;
@@ -223,12 +267,31 @@ export class ToolRouter {
223
267
  try {
224
268
  const pipeIdx = toolArg.indexOf('|');
225
269
  if (pipeIdx === -1) {
226
- toolResult = 'Invalid reminder format. Use: YYYY-MM-DDTHH:MM|message';
270
+ toolResult = 'Format: time|message. Example: 30m|Check oven';
227
271
  break;
228
272
  }
229
- const timeStr = toolArg.slice(0, pipeIdx).trim();
273
+ let timeStr = toolArg.slice(0, pipeIdx).trim();
230
274
  const msg = toolArg.slice(pipeIdx + 1).trim();
231
- // Store reminder request in result for engine to pick up
275
+
276
+ // Parse relative times
277
+ const relMatch = timeStr.match(/^(\d+)\s*(m|min|minutes?|h|hr|hours?|d|days?)$/i);
278
+ if (relMatch) {
279
+ const num = parseInt(relMatch[1]);
280
+ const unit = relMatch[2][0].toLowerCase();
281
+ const ms = unit === 'm' ? num * 60000 : unit === 'h' ? num * 3600000 : num * 86400000;
282
+ const fireDate = new Date(Date.now() + ms);
283
+ timeStr = fireDate.toISOString().slice(0, 16);
284
+ } else if (timeStr.toLowerCase() === 'tomorrow') {
285
+ const d = new Date(Date.now() + 86400000);
286
+ d.setUTCHours(9, 0, 0, 0);
287
+ timeStr = d.toISOString().slice(0, 16);
288
+ } else if (timeStr.toLowerCase() === 'tonight') {
289
+ const d = new Date();
290
+ d.setUTCHours(18, 0, 0, 0);
291
+ if (d < new Date()) d.setDate(d.getDate() + 1);
292
+ timeStr = d.toISOString().slice(0, 16);
293
+ }
294
+
232
295
  return { toolUsed: true, toolName: 'remind', reminderTime: timeStr, reminderMessage: msg, cleanResponse };
233
296
  } catch (err) {
234
297
  toolResult = 'Failed to set reminder: ' + err.message;
@@ -285,6 +348,86 @@ export class ToolRouter {
285
348
  }
286
349
  break;
287
350
  }
351
+ case 'excel':
352
+ case 'xlsx':
353
+ case 'spreadsheet': {
354
+ try {
355
+ const { ExcelGenerator } = await import('./excel.js');
356
+ const gen = new ExcelGenerator();
357
+ const parts = toolArg.split('|');
358
+ let title, themeName, sheetContent;
359
+ if (parts.length >= 3) {
360
+ title = parts[0].trim();
361
+ themeName = parts[1].trim();
362
+ sheetContent = parts.slice(2).join('|');
363
+ } else {
364
+ title = parts[0]?.trim() || 'Spreadsheet';
365
+ sheetContent = parts.slice(1).join('|');
366
+ themeName = 'corporate';
367
+ }
368
+ const sheets = ExcelGenerator.parseContent(sheetContent);
369
+ const result = await gen.create(title, sheets, { theme: themeName });
370
+ return { toolUsed: true, toolName: 'excel', toolResult: 'Excel created: ' + result.filename, filePath: result.filepath, fileName: result.filename, cleanResponse };
371
+ } catch (err) { toolResult = 'Excel failed: ' + err.message; }
372
+ break;
373
+ }
374
+ case 'pdf':
375
+ case 'document': {
376
+ try {
377
+ const { PdfGenerator } = await import('./pdf.js');
378
+ const gen = new PdfGenerator();
379
+ const pipeIdx = toolArg.indexOf('|');
380
+ const title = pipeIdx > -1 ? toolArg.slice(0, pipeIdx).trim() : 'Document';
381
+ const content = pipeIdx > -1 ? toolArg.slice(pipeIdx + 1) : toolArg;
382
+ const result = await gen.create(title, content);
383
+ return { toolUsed: true, toolName: 'pdf', toolResult: 'PDF created: ' + result.filename, filePath: result.filepath, fileName: result.filename, cleanResponse };
384
+ } catch (err) { toolResult = 'PDF failed: ' + err.message; }
385
+ break;
386
+ }
387
+ case 'html':
388
+ case 'webpage': {
389
+ try {
390
+ const { HtmlGenerator } = await import('./html.js');
391
+ const gen = new HtmlGenerator();
392
+ const parts = toolArg.split('|');
393
+ const title = parts[0]?.trim() || 'Page';
394
+ const themeName = parts.length >= 3 ? parts[1].trim() : 'light';
395
+ const content = parts.length >= 3 ? parts.slice(2).join('|') : parts.slice(1).join('|');
396
+ const result = gen.create(title, content, { theme: themeName });
397
+ return { toolUsed: true, toolName: 'html', toolResult: 'HTML created: ' + result.filename, filePath: result.filepath, fileName: result.filename, cleanResponse };
398
+ } catch (err) { toolResult = 'HTML failed: ' + err.message; }
399
+ break;
400
+ }
401
+ case 'cron_add': {
402
+ try {
403
+ if (!this._engine?.cron) { toolResult = 'Cron not available'; break; }
404
+ const parts = toolArg.split('|').map(p => p.trim());
405
+ const [name, schedule, action, ...dataParts] = parts;
406
+ const data = dataParts.join('|');
407
+ const result = this._engine.cron.add(agentId, this._currentContactId, name, schedule, action || 'message', data, this._currentPlatform);
408
+ toolResult = 'Cron job created! βœ…\nπŸ“‹ ' + result.name + '\n⏰ Schedule: ' + result.schedule + '\nπŸ”œ Next run: ' + result.nextRun;
409
+ } catch (err) { toolResult = 'Failed: ' + err.message; }
410
+ break;
411
+ }
412
+ case 'cron_list': {
413
+ try {
414
+ if (!this._engine?.cron) { toolResult = 'Cron not available'; break; }
415
+ const jobs = this._engine.cron.listAll(agentId);
416
+ if (jobs.length === 0) { toolResult = 'No scheduled jobs'; break; }
417
+ toolResult = jobs.map(j =>
418
+ (j.enabled ? 'βœ…' : '⏸️') + ' ' + j.name + ' (' + j.schedule + ')\n ID: ' + j.id + '\n Next: ' + (j.next_run || 'N/A')
419
+ ).join('\n\n');
420
+ } catch (err) { toolResult = 'Failed: ' + err.message; }
421
+ break;
422
+ }
423
+ case 'cron_remove': {
424
+ try {
425
+ if (!this._engine?.cron) { toolResult = 'Cron not available'; break; }
426
+ this._engine.cron.remove(toolArg.trim());
427
+ toolResult = 'Cron job removed βœ…';
428
+ } catch (err) { toolResult = 'Failed: ' + err.message; }
429
+ break;
430
+ }
288
431
  case 'allow': {
289
432
  try {
290
433
  const { AllowlistManager } = await import('../features/allowlist-manager.js');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squidclaw",
3
- "version": "2.2.0",
3
+ "version": "2.4.0",
4
4
  "description": "πŸ¦‘ AI agent platform β€” human-like agents for WhatsApp, Telegram & more",
5
5
  "main": "lib/engine.js",
6
6
  "bin": {
@@ -42,12 +42,14 @@
42
42
  "commander": "^14.0.3",
43
43
  "croner": "^10.0.1",
44
44
  "dotenv": "^17.3.1",
45
+ "exceljs": "^4.4.0",
45
46
  "express": "^5.2.1",
46
47
  "file-type": "^21.3.0",
47
48
  "grammy": "^1.40.1",
48
49
  "linkedom": "^0.18.12",
49
50
  "node-edge-tts": "^1.2.10",
50
51
  "pdfjs-dist": "^5.4.624",
52
+ "pdfkit": "^0.17.2",
51
53
  "pino": "^10.3.1",
52
54
  "pptxgenjs": "^4.0.1",
53
55
  "puppeteer-core": "^24.37.5",