squidclaw 2.2.0 โ†’ 2.3.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.
@@ -202,6 +202,18 @@ export class TelegramManager {
202
202
  /**
203
203
  * Send voice note
204
204
  */
205
+ async sendDocument(agentId, contactId, filePath, fileName, caption, metadata = {}) {
206
+ const botInfo = this.bots.get(agentId);
207
+ if (!botInfo?.bot) throw new Error('Bot not running');
208
+ const chatId = contactId.replace('tg_', '');
209
+ const { readFileSync } = await import('fs');
210
+ const buffer = readFileSync(filePath);
211
+ await botInfo.bot.api.sendDocument(chatId, new InputFile(buffer, fileName || 'file'), {
212
+ caption: caption || '',
213
+ });
214
+ logger.info('telegram', 'Sent document: ' + fileName);
215
+ }
216
+
205
217
  async sendVoiceNote(agentId, contactId, audioBuffer, metadata = {}) {
206
218
  const botInfo = this.bots.get(agentId);
207
219
  if (!botInfo?.bot) return;
@@ -39,27 +39,32 @@ export async function commandsMiddleware(ctx, next) {
39
39
 
40
40
  if (cmd === '/status' || textCmd === 'status') {
41
41
  const uptime = process.uptime();
42
- const h = Math.floor(uptime / 3600);
42
+ const d = Math.floor(uptime / 86400);
43
+ const h = Math.floor((uptime % 86400) / 3600);
43
44
  const m = Math.floor((uptime % 3600) / 60);
45
+ const uptimeStr = d > 0 ? d + 'd ' + h + 'h' : h + 'h ' + m + 'm';
44
46
  const usage = await ctx.storage.getUsage(ctx.agentId) || {};
45
47
  const tokens = (usage.input_tokens || 0) + (usage.output_tokens || 0);
46
- const fmtT = tokens >= 1e6 ? (tokens/1e6).toFixed(1)+'M' : tokens >= 1e3 ? (tokens/1e3).toFixed(1)+'K' : String(tokens);
48
+ const fmtT = (n) => n >= 1e6 ? (n/1e6).toFixed(1)+'M' : n >= 1e3 ? (n/1e3).toFixed(1)+'K' : String(n || 0);
47
49
  const waOn = Object.values(ctx.engine.whatsappManager?.getStatuses() || {}).some(s => s.connected);
50
+ const memCount = ctx.storage.db.prepare('SELECT COUNT(*) as c FROM memories WHERE agent_id = ?').get(ctx.agentId)?.c || 0;
51
+ const convCount = ctx.storage.db.prepare('SELECT COUNT(*) as c FROM conversations WHERE agent_id = ?').get(ctx.agentId)?.c || 0;
52
+ const reminderCount = ctx.storage.db.prepare("SELECT COUNT(*) as c FROM reminders WHERE agent_id = ? AND fired = 0").get(ctx.agentId)?.c || 0;
53
+ const ram = (process.memoryUsage().rss / 1024 / 1024).toFixed(0);
48
54
 
49
55
  await ctx.reply([
50
- `๐Ÿฆ‘ *${ctx.agent?.name || ctx.agentId} Status*`,
51
- 'โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€',
52
- `๐Ÿง  Model: ${ctx.agent?.model || 'unknown'}`,
53
- `โฑ๏ธ Uptime: ${h}h ${m}m`,
54
- `๐Ÿ’ฌ Messages: ${usage.messages || 0}`,
55
- `๐Ÿช™ Tokens: ${fmtT}`,
56
- `๐Ÿ’ฐ Cost: $${(usage.cost_usd || 0).toFixed(4)}`,
57
- 'โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€',
58
- `๐Ÿ“ฑ WhatsApp: ${waOn ? 'โœ…' : 'โŒ'}`,
59
- `โœˆ๏ธ Telegram: ${ctx.engine.telegramManager ? 'โœ…' : 'โŒ'}`,
60
- 'โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€',
61
- `โšก Skills: 20+`,
62
- `๐Ÿ—ฃ๏ธ Language: ${ctx.agent?.language || 'bilingual'}`,
56
+ '๐Ÿฆ‘ *Squidclaw v2.2.0*',
57
+ '*' + (ctx.agent?.name || 'Agent') + '* โ€” Status',
58
+ '๐Ÿง  Model: ' + (ctx.agent?.model || '?'),
59
+ 'โšก Pipeline: 14 middleware ยท 40+ skills',
60
+ '๐Ÿ—ฃ๏ธ Language: Bilingual (AR/EN)',
61
+ '๐Ÿ’ฌ Messages: ' + convCount,
62
+ '๐Ÿช™ Tokens: ' + fmtT(tokens) + ' (โ†‘' + fmtT(usage.input_tokens || 0) + ' โ†“' + fmtT(usage.output_tokens || 0) + ')',
63
+ '๐Ÿ’ฐ Cost: $' + (usage.cost_usd || 0).toFixed(4),
64
+ 'โœˆ๏ธ Telegram: ' + (ctx.engine.telegramManager ? '๐ŸŸข' : '๐Ÿ”ด') + ' ยท WhatsApp: ' + (waOn ? '๐ŸŸข' : '๐Ÿ”ด'),
65
+ '๐Ÿ’พ Memories: ' + memCount + ' ยท Reminders: ' + reminderCount,
66
+ 'โฑ๏ธ Uptime: ' + uptimeStr + ' ยท RAM: ' + ram + 'MB',
67
+ '๐Ÿ’š Heartbeat: active',
63
68
  ].join('\n'));
64
69
  return;
65
70
  }
@@ -132,6 +137,14 @@ export async function commandsMiddleware(ctx, next) {
132
137
  return;
133
138
  }
134
139
 
140
+ if (cmd === '/reset' || cmd === '/restart') {
141
+ await ctx.reply('๐Ÿ”„ Restarting Squidclaw...');
142
+ setTimeout(() => {
143
+ process.exit(0); // Process manager (pm2/systemd) will restart it
144
+ }, 1000);
145
+ return;
146
+ }
147
+
135
148
  if (cmd === '/allow') {
136
149
  const args = msg.slice(7).trim();
137
150
  if (!args) { await ctx.reply('Usage: /allow <user_id or phone>'); return; }
@@ -32,18 +32,9 @@ export async function responseSenderMiddleware(ctx, next) {
32
32
  // Send file attachment (pptx, etc)
33
33
  if (response.filePath) {
34
34
  try {
35
- const { readFileSync } = await import('fs');
36
- const { InputFile } = await import('grammy');
37
- const bot = tm.getBotForAgent?.(agentId) || tm.bots?.values()?.next()?.value;
38
- if (bot) {
39
- const buffer = readFileSync(response.filePath);
40
- await bot.api.sendDocument(contactId.replace('tg_', ''), new InputFile(buffer, response.fileName || 'file'), {
41
- caption: response.messages?.[0] || '',
42
- });
43
- // Skip normal message sending
44
- await next();
45
- return;
46
- }
35
+ await tm.sendDocument(agentId, contactId, response.filePath, response.fileName, response.messages?.[0] || '');
36
+ await next();
37
+ return;
47
38
  } catch (err) {
48
39
  logger.error('sender', 'File send failed: ' + err.message);
49
40
  // Fall through to text
package/lib/tools/pptx.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
- * ๐Ÿฆ‘ PowerPoint Generator
3
- * Create .pptx presentations from AI-generated content
2
+ * ๐Ÿฆ‘ PowerPoint Generator PRO
3
+ * Full-featured .pptx with charts, images, tables, layouts
4
4
  */
5
5
 
6
6
  import { logger } from '../core/logger.js';
@@ -13,111 +13,541 @@ export class PptxGenerator {
13
13
  mkdirSync(this.outputDir, { recursive: true });
14
14
  }
15
15
 
16
+ // โ”€โ”€ THEMES โ”€โ”€
17
+ static THEMES = {
18
+ dark: { bg: '0d1117', bg2: '161b22', title: 'e6edf3', text: 'b1bac4', accent: '58a6ff', accent2: '3fb950', accent3: 'bc8cff', border: '30363d' },
19
+ light: { bg: 'ffffff', bg2: 'f6f8fa', title: '1f2328', text: '656d76', accent: '0969da', accent2: '1a7f37', accent3: '8250df', border: 'd0d7de' },
20
+ blue: { bg: '0a1628', bg2: '0f2440', title: 'ffffff', text: 'a3c4e8', accent: '4da6ff', accent2: '00d4aa', accent3: 'ff6b9d', border: '1e3a5f' },
21
+ green: { bg: '0d1f0d', bg2: '132a13', title: 'ffffff', text: 'a7d7a7', accent: '4ade80', accent2: 'facc15', accent3: 'fb923c', border: '1a3d1a' },
22
+ corporate: { bg: 'ffffff', bg2: 'f1f5f9', title: '0f172a', text: '475569', accent: '2563eb', accent2: '059669', accent3: 'dc2626', border: 'e2e8f0' },
23
+ red: { bg: '1a0000', bg2: '2d0a0a', title: 'ffffff', text: 'fca5a5', accent: 'ef4444', accent2: 'f59e0b', accent3: '8b5cf6', border: '450a0a' },
24
+ gradient: { bg: '0c0c1d', bg2: '1a1a3e', title: 'ffffff', text: 'c4b5fd', accent: '818cf8', accent2: 'f472b6', accent3: '34d399', border: '312e81' },
25
+ minimal: { bg: 'fafafa', bg2: 'f0f0f0', title: '111111', text: '555555', accent: '111111', accent2: '888888', accent3: 'bbbbbb', border: 'dddddd' },
26
+ saudi: { bg: '003c1f', bg2: '004d28', title: 'ffffff', text: 'c8e6c9', accent: '4caf50', accent2: 'ffd54f', accent3: 'ffffff', border: '1b5e20' },
27
+ ocean: { bg: '0a192f', bg2: '112240', title: 'ccd6f6', text: '8892b0', accent: '64ffda', accent2: 'ffd700', accent3: 'ff6b6b', border: '233554' },
28
+ };
29
+
16
30
  /**
17
- * Create a presentation from structured content
18
- * @param {string} title - Presentation title
19
- * @param {Array} slides - [{title, content, bullets?, notes?}]
20
- * @param {object} options - {theme, author}
31
+ * Create a full presentation
21
32
  */
22
33
  async create(title, slides, options = {}) {
23
34
  const pptxgen = (await import('pptxgenjs')).default;
24
35
  const pres = new pptxgen();
25
36
 
26
- // Metadata
27
- pres.author = options.author || 'Squidclaw AI';
37
+ pres.author = options.author || 'Squidclaw AI ๐Ÿฆ‘';
28
38
  pres.title = title;
29
- pres.subject = title;
30
-
31
- // Theme colors
32
- const themes = {
33
- dark: { bg: '1a1a2e', title: 'e94560', text: 'eaeaea', accent: '0f3460' },
34
- light: { bg: 'ffffff', title: '2d3436', text: '636e72', accent: '0984e3' },
35
- blue: { bg: '0f3460', title: 'e94560', text: 'eaeaea', accent: '16213e' },
36
- green: { bg: '1b4332', title: 'b7e4c7', text: 'd8f3dc', accent: '2d6a4f' },
37
- corporate: { bg: 'ffffff', title: '2c3e50', text: '34495e', accent: '3498db' },
38
- red: { bg: '2d0000', title: 'ff6b6b', text: 'ffe0e0', accent: '4a0000' },
39
- };
40
- const theme = themes[options.theme] || themes.corporate;
39
+ pres.subject = options.subtitle || title;
40
+ pres.layout = 'LAYOUT_WIDE'; // 16:9
41
+
42
+ const theme = PptxGenerator.THEMES[options.theme] || PptxGenerator.THEMES.corporate;
43
+
44
+ // โ”€โ”€ TITLE SLIDE โ”€โ”€
45
+ this._addTitleSlide(pres, title, options, theme);
46
+
47
+ // โ”€โ”€ CONTENT SLIDES โ”€โ”€
48
+ for (let i = 0; i < slides.length; i++) {
49
+ const slide = slides[i];
50
+ const slideNum = i + 2;
51
+ const totalSlides = slides.length + 1;
52
+
53
+ switch (slide.type) {
54
+ case 'chart':
55
+ this._addChartSlide(pres, slide, theme, slideNum, totalSlides);
56
+ break;
57
+ case 'table':
58
+ this._addTableSlide(pres, slide, theme, slideNum, totalSlides);
59
+ break;
60
+ case 'comparison':
61
+ this._addComparisonSlide(pres, slide, theme, slideNum, totalSlides);
62
+ break;
63
+ case 'timeline':
64
+ this._addTimelineSlide(pres, slide, theme, slideNum, totalSlides);
65
+ break;
66
+ case 'stats':
67
+ this._addStatsSlide(pres, slide, theme, slideNum, totalSlides);
68
+ break;
69
+ case 'quote':
70
+ this._addQuoteSlide(pres, slide, theme, slideNum, totalSlides);
71
+ break;
72
+ case 'image':
73
+ this._addImageSlide(pres, slide, theme, slideNum, totalSlides);
74
+ break;
75
+ case 'section':
76
+ this._addSectionSlide(pres, slide, theme, slideNum, totalSlides);
77
+ break;
78
+ default:
79
+ this._addContentSlide(pres, slide, theme, slideNum, totalSlides);
80
+ }
81
+ }
82
+
83
+ // โ”€โ”€ THANK YOU SLIDE โ”€โ”€
84
+ if (options.thankYou !== false) {
85
+ this._addThankYouSlide(pres, options, theme);
86
+ }
87
+
88
+ // Save
89
+ const filename = title.replace(/[^a-zA-Z0-9\u0600-\u06FF ]/g, '').replace(/\s+/g, '_').slice(0, 50) + '.pptx';
90
+ const filepath = join(this.outputDir, filename);
91
+ const data = await pres.write({ outputType: 'nodebuffer' });
92
+ writeFileSync(filepath, data);
93
+
94
+ logger.info('pptx', `Created PRO: ${filepath} (${slides.length + 2} slides)`);
95
+ return { filepath, filename, slideCount: slides.length + 2 };
96
+ }
97
+
98
+ // โ”€โ”€ SLIDE BUILDERS โ”€โ”€
99
+
100
+ _addBase(pres, theme, slideNum, totalSlides) {
101
+ const s = pres.addSlide();
102
+ s.background = { color: theme.bg };
103
+
104
+ // Top accent bar
105
+ s.addShape(pres.ShapeType.rect, {
106
+ x: 0, y: 0, w: '100%', h: 0.06, fill: { color: theme.accent },
107
+ });
108
+
109
+ // Footer
110
+ if (slideNum && totalSlides) {
111
+ s.addText(slideNum + ' / ' + totalSlides, {
112
+ x: 11.5, y: 7.1, w: 1.5, h: 0.3,
113
+ fontSize: 9, color: theme.text, align: 'right', italic: true,
114
+ });
115
+ s.addText('Squidclaw AI ๐Ÿฆ‘', {
116
+ x: 0.3, y: 7.1, w: 2, h: 0.3,
117
+ fontSize: 9, color: theme.border,
118
+ });
119
+ }
120
+ return s;
121
+ }
41
122
 
42
- // Title slide
43
- const titleSlide = pres.addSlide();
44
- titleSlide.background = { color: theme.bg };
45
- titleSlide.addText(title, {
46
- x: 0.5, y: 1.5, w: 9, h: 1.5,
47
- fontSize: 36, bold: true, color: theme.title,
48
- align: 'center',
123
+ _addTitleSlide(pres, title, options, theme) {
124
+ const s = pres.addSlide();
125
+ s.background = { color: theme.bg };
126
+
127
+ // Large accent shape
128
+ s.addShape(pres.ShapeType.rect, {
129
+ x: 0, y: 0, w: '100%', h: 0.08, fill: { color: theme.accent },
130
+ });
131
+ s.addShape(pres.ShapeType.rect, {
132
+ x: 0.5, y: 5.5, w: 3, h: 0.05, fill: { color: theme.accent },
49
133
  });
134
+
135
+ // Title
136
+ s.addText(title, {
137
+ x: 0.5, y: 1.8, w: 12, h: 1.5,
138
+ fontSize: 42, bold: true, color: theme.title,
139
+ fontFace: 'Arial',
140
+ });
141
+
142
+ // Subtitle
50
143
  if (options.subtitle) {
51
- titleSlide.addText(options.subtitle, {
52
- x: 0.5, y: 3.2, w: 9, h: 0.8,
53
- fontSize: 18, color: theme.text, align: 'center',
144
+ s.addText(options.subtitle, {
145
+ x: 0.5, y: 3.4, w: 10, h: 0.8,
146
+ fontSize: 20, color: theme.text, fontFace: 'Arial',
54
147
  });
55
148
  }
56
- titleSlide.addText(new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }), {
57
- x: 0.5, y: 4.5, w: 9, h: 0.5,
58
- fontSize: 12, color: theme.text, align: 'center', italic: true,
149
+
150
+ // Date + author
151
+ const date = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
152
+ s.addText((options.author || '') + (options.author ? ' โ€ข ' : '') + date, {
153
+ x: 0.5, y: 5.8, w: 8, h: 0.5,
154
+ fontSize: 14, color: theme.accent, fontFace: 'Arial',
155
+ });
156
+ }
157
+
158
+ _addContentSlide(pres, slide, theme, slideNum, total) {
159
+ const s = this._addBase(pres, theme, slideNum, total);
160
+
161
+ // Title
162
+ s.addText(slide.title || '', {
163
+ x: 0.5, y: 0.3, w: 12, h: 0.8,
164
+ fontSize: 28, bold: true, color: theme.title, fontFace: 'Arial',
59
165
  });
60
166
 
61
- // Content slides
62
- for (const slide of slides) {
63
- const s = pres.addSlide();
64
- s.background = { color: theme.bg };
167
+ // Accent line under title
168
+ s.addShape(pres.ShapeType.rect, {
169
+ x: 0.5, y: 1.15, w: 2.5, h: 0.04, fill: { color: theme.accent },
170
+ });
65
171
 
66
- // Slide title
67
- s.addText(slide.title || '', {
68
- x: 0.5, y: 0.3, w: 9, h: 0.8,
69
- fontSize: 24, bold: true, color: theme.title,
172
+ if (slide.bullets && slide.bullets.length > 0) {
173
+ const rows = slide.bullets.map(b => ({
174
+ text: b,
175
+ options: {
176
+ fontSize: 18, color: theme.text, fontFace: 'Arial',
177
+ bullet: { type: 'bullet', style: 'โ—', color: theme.accent },
178
+ paraSpaceAfter: 10, paraSpaceBefore: 4,
179
+ indentLevel: 0,
180
+ },
181
+ }));
182
+
183
+ s.addText(rows, { x: 0.7, y: 1.5, w: 11.5, h: 5 });
184
+ }
185
+
186
+ if (slide.content) {
187
+ s.addText(slide.content, {
188
+ x: 0.5, y: 1.5, w: 12, h: 5,
189
+ fontSize: 18, color: theme.text, fontFace: 'Arial',
190
+ valign: 'top', wrap: true, lineSpacing: 28,
70
191
  });
192
+ }
193
+ }
71
194
 
72
- // Accent line
73
- s.addShape(pres.ShapeType.rect, {
74
- x: 0.5, y: 1.1, w: 2, h: 0.04, fill: { color: theme.title },
195
+ _addChartSlide(pres, slide, theme, slideNum, total) {
196
+ const s = this._addBase(pres, theme, slideNum, total);
197
+
198
+ s.addText(slide.title || 'Chart', {
199
+ x: 0.5, y: 0.3, w: 12, h: 0.7,
200
+ fontSize: 26, bold: true, color: theme.title, fontFace: 'Arial',
201
+ });
202
+ s.addShape(pres.ShapeType.rect, {
203
+ x: 0.5, y: 1.05, w: 2, h: 0.04, fill: { color: theme.accent },
204
+ });
205
+
206
+ const chartType = slide.chartType || 'bar';
207
+ const chartTypes = {
208
+ bar: pres.ChartType.bar,
209
+ line: pres.ChartType.line,
210
+ pie: pres.ChartType.pie,
211
+ doughnut: pres.ChartType.doughnut,
212
+ area: pres.ChartType.area,
213
+ bar3d: pres.ChartType.bar3D,
214
+ };
215
+
216
+ const type = chartTypes[chartType] || pres.ChartType.bar;
217
+ const colors = [theme.accent, theme.accent2, theme.accent3, theme.text, theme.border];
218
+
219
+ const chartData = slide.chartData || [
220
+ { name: 'Series 1', labels: ['A', 'B', 'C', 'D'], values: [10, 20, 30, 40] }
221
+ ];
222
+
223
+ s.addChart(type, chartData, {
224
+ x: 0.8, y: 1.4, w: 11.5, h: 5.2,
225
+ showTitle: false,
226
+ showValue: slide.showValues !== false,
227
+ catAxisLabelColor: theme.text,
228
+ valAxisLabelColor: theme.text,
229
+ chartColors: colors,
230
+ legendColor: theme.text,
231
+ showLegend: chartData.length > 1,
232
+ legendPos: 'b',
233
+ dataLabelColor: theme.title,
234
+ dataLabelFontSize: 10,
235
+ valGridLine: { color: theme.border, size: 0.5 },
236
+ });
237
+ }
238
+
239
+ _addTableSlide(pres, slide, theme, slideNum, total) {
240
+ const s = this._addBase(pres, theme, slideNum, total);
241
+
242
+ s.addText(slide.title || 'Table', {
243
+ x: 0.5, y: 0.3, w: 12, h: 0.7,
244
+ fontSize: 26, bold: true, color: theme.title, fontFace: 'Arial',
245
+ });
246
+ s.addShape(pres.ShapeType.rect, {
247
+ x: 0.5, y: 1.05, w: 2, h: 0.04, fill: { color: theme.accent },
248
+ });
249
+
250
+ const rows = slide.tableData || [['Header 1', 'Header 2'], ['Data 1', 'Data 2']];
251
+ const tableRows = rows.map((row, ri) =>
252
+ row.map(cell => ({
253
+ text: String(cell),
254
+ options: {
255
+ fontSize: ri === 0 ? 14 : 13,
256
+ bold: ri === 0,
257
+ color: ri === 0 ? 'ffffff' : theme.text,
258
+ fill: { color: ri === 0 ? theme.accent : (ri % 2 === 0 ? theme.bg2 : theme.bg) },
259
+ border: [{ color: theme.border, pt: 0.5 }],
260
+ align: 'left',
261
+ valign: 'middle',
262
+ fontFace: 'Arial',
263
+ },
264
+ }))
265
+ );
266
+
267
+ const colW = 11.5 / (rows[0]?.length || 2);
268
+ s.addTable(tableRows, {
269
+ x: 0.5, y: 1.4, w: 12, h: 0.5,
270
+ colW: Array(rows[0]?.length || 2).fill(colW),
271
+ rowH: rows.map((_, i) => i === 0 ? 0.5 : 0.45),
272
+ margin: [5, 8, 5, 8],
273
+ });
274
+ }
275
+
276
+ _addComparisonSlide(pres, slide, theme, slideNum, total) {
277
+ const s = this._addBase(pres, theme, slideNum, total);
278
+
279
+ s.addText(slide.title || 'Comparison', {
280
+ x: 0.5, y: 0.3, w: 12, h: 0.7,
281
+ fontSize: 26, bold: true, color: theme.title, fontFace: 'Arial',
282
+ });
283
+ s.addShape(pres.ShapeType.rect, {
284
+ x: 0.5, y: 1.05, w: 2, h: 0.04, fill: { color: theme.accent },
285
+ });
286
+
287
+ const left = slide.left || { title: 'Option A', points: ['Point 1'] };
288
+ const right = slide.right || { title: 'Option B', points: ['Point 1'] };
289
+
290
+ // Left column
291
+ s.addShape(pres.ShapeType.roundRect, {
292
+ x: 0.5, y: 1.4, w: 5.8, h: 5.2,
293
+ fill: { color: theme.bg2 }, rectRadius: 0.15,
294
+ line: { color: theme.accent, width: 1.5 },
295
+ });
296
+ s.addText(left.title, {
297
+ x: 0.8, y: 1.6, w: 5.2, h: 0.6,
298
+ fontSize: 20, bold: true, color: theme.accent, fontFace: 'Arial',
299
+ });
300
+ if (left.points) {
301
+ const lpts = left.points.map(p => ({
302
+ text: p,
303
+ options: { fontSize: 15, color: theme.text, bullet: { code: '2022', color: theme.accent }, paraSpaceAfter: 8 },
304
+ }));
305
+ s.addText(lpts, { x: 1, y: 2.3, w: 4.8, h: 4 });
306
+ }
307
+
308
+ // VS
309
+ s.addText('VS', {
310
+ x: 5.8, y: 3.5, w: 1.4, h: 0.6,
311
+ fontSize: 18, bold: true, color: theme.accent, align: 'center',
312
+ });
313
+
314
+ // Right column
315
+ s.addShape(pres.ShapeType.roundRect, {
316
+ x: 6.7, y: 1.4, w: 5.8, h: 5.2,
317
+ fill: { color: theme.bg2 }, rectRadius: 0.15,
318
+ line: { color: theme.accent2, width: 1.5 },
319
+ });
320
+ s.addText(right.title, {
321
+ x: 7, y: 1.6, w: 5.2, h: 0.6,
322
+ fontSize: 20, bold: true, color: theme.accent2, fontFace: 'Arial',
323
+ });
324
+ if (right.points) {
325
+ const rpts = right.points.map(p => ({
326
+ text: p,
327
+ options: { fontSize: 15, color: theme.text, bullet: { code: '2022', color: theme.accent2 }, paraSpaceAfter: 8 },
328
+ }));
329
+ s.addText(rpts, { x: 7.2, y: 2.3, w: 4.8, h: 4 });
330
+ }
331
+ }
332
+
333
+ _addTimelineSlide(pres, slide, theme, slideNum, total) {
334
+ const s = this._addBase(pres, theme, slideNum, total);
335
+
336
+ s.addText(slide.title || 'Timeline', {
337
+ x: 0.5, y: 0.3, w: 12, h: 0.7,
338
+ fontSize: 26, bold: true, color: theme.title, fontFace: 'Arial',
339
+ });
340
+ s.addShape(pres.ShapeType.rect, {
341
+ x: 0.5, y: 1.05, w: 2, h: 0.04, fill: { color: theme.accent },
342
+ });
343
+
344
+ const events = slide.events || [{ date: '2024', text: 'Event 1' }];
345
+ const count = Math.min(events.length, 6);
346
+ const totalW = 11.5;
347
+ const spacing = totalW / count;
348
+
349
+ // Timeline line
350
+ s.addShape(pres.ShapeType.rect, {
351
+ x: 0.5, y: 3.5, w: totalW, h: 0.04, fill: { color: theme.accent },
352
+ });
353
+
354
+ events.slice(0, 6).forEach((ev, i) => {
355
+ const x = 0.5 + (i * spacing) + (spacing / 2) - 0.8;
356
+ const above = i % 2 === 0;
357
+
358
+ // Circle
359
+ s.addShape(pres.ShapeType.ellipse, {
360
+ x: x + 0.55, y: 3.3, w: 0.4, h: 0.4,
361
+ fill: { color: theme.accent },
75
362
  });
76
363
 
77
- if (slide.bullets && slide.bullets.length > 0) {
78
- // Bullet points
79
- const bulletText = slide.bullets.map(b => ({
80
- text: b,
81
- options: { fontSize: 16, color: theme.text, bullet: { code: '2022' }, paraSpaceAfter: 8 },
82
- }));
83
- s.addText(bulletText, {
84
- x: 0.5, y: 1.4, w: 9, h: 3.5,
85
- });
86
- } else if (slide.content) {
87
- // Paragraph content
88
- s.addText(slide.content, {
89
- x: 0.5, y: 1.4, w: 9, h: 3.5,
90
- fontSize: 16, color: theme.text,
91
- valign: 'top', wrap: true,
364
+ // Date
365
+ s.addText(ev.date || '', {
366
+ x: x, y: above ? 2.2 : 3.9, w: 1.6, h: 0.4,
367
+ fontSize: 13, bold: true, color: theme.accent, align: 'center', fontFace: 'Arial',
368
+ });
369
+
370
+ // Text
371
+ s.addText(ev.text || '', {
372
+ x: x - 0.2, y: above ? 2.6 : 4.3, w: 2, h: 0.8,
373
+ fontSize: 11, color: theme.text, align: 'center', fontFace: 'Arial', wrap: true,
374
+ });
375
+ });
376
+ }
377
+
378
+ _addStatsSlide(pres, slide, theme, slideNum, total) {
379
+ const s = this._addBase(pres, theme, slideNum, total);
380
+
381
+ s.addText(slide.title || 'Key Metrics', {
382
+ x: 0.5, y: 0.3, w: 12, h: 0.7,
383
+ fontSize: 26, bold: true, color: theme.title, fontFace: 'Arial',
384
+ });
385
+ s.addShape(pres.ShapeType.rect, {
386
+ x: 0.5, y: 1.05, w: 2, h: 0.04, fill: { color: theme.accent },
387
+ });
388
+
389
+ const stats = slide.stats || [{ value: '100', label: 'Stat' }];
390
+ const count = Math.min(stats.length, 4);
391
+ const cardW = 2.5;
392
+ const gap = (12 - (count * cardW)) / (count + 1);
393
+ const colors = [theme.accent, theme.accent2, theme.accent3, theme.text];
394
+
395
+ stats.slice(0, 4).forEach((stat, i) => {
396
+ const x = gap + i * (cardW + gap);
397
+
398
+ // Card bg
399
+ s.addShape(pres.ShapeType.roundRect, {
400
+ x, y: 2, w: cardW, h: 3.5,
401
+ fill: { color: theme.bg2 }, rectRadius: 0.15,
402
+ line: { color: colors[i], width: 1.5 },
403
+ });
404
+
405
+ // Icon/emoji
406
+ if (stat.icon) {
407
+ s.addText(stat.icon, {
408
+ x, y: 2.3, w: cardW, h: 0.8,
409
+ fontSize: 32, align: 'center',
92
410
  });
93
411
  }
94
412
 
95
- // Slide notes
96
- if (slide.notes) {
97
- s.addNotes(slide.notes);
413
+ // Big number
414
+ s.addText(stat.value || '0', {
415
+ x, y: stat.icon ? 3 : 2.5, w: cardW, h: 1.2,
416
+ fontSize: 40, bold: true, color: colors[i], align: 'center', fontFace: 'Arial',
417
+ });
418
+
419
+ // Label
420
+ s.addText(stat.label || '', {
421
+ x, y: stat.icon ? 4.1 : 3.7, w: cardW, h: 0.8,
422
+ fontSize: 14, color: theme.text, align: 'center', fontFace: 'Arial', wrap: true,
423
+ });
424
+
425
+ // Sub text
426
+ if (stat.sub) {
427
+ s.addText(stat.sub, {
428
+ x, y: stat.icon ? 4.7 : 4.3, w: cardW, h: 0.5,
429
+ fontSize: 11, color: theme.border, align: 'center', fontFace: 'Arial', italic: true,
430
+ });
98
431
  }
432
+ });
433
+ }
434
+
435
+ _addQuoteSlide(pres, slide, theme, slideNum, total) {
436
+ const s = this._addBase(pres, theme, slideNum, total);
99
437
 
100
- // Slide number
101
- s.addText(String(slides.indexOf(slide) + 2), {
102
- x: 9, y: 5, w: 0.5, h: 0.3,
103
- fontSize: 10, color: theme.text, align: 'right',
438
+ // Large quote mark
439
+ s.addText('"', {
440
+ x: 1, y: 1.2, w: 2, h: 2,
441
+ fontSize: 120, color: theme.accent, fontFace: 'Georgia', bold: true,
442
+ });
443
+
444
+ // Quote text
445
+ s.addText(slide.quote || slide.content || '', {
446
+ x: 1.5, y: 2.5, w: 10, h: 2.5,
447
+ fontSize: 28, color: theme.title, fontFace: 'Georgia', italic: true,
448
+ lineSpacing: 36, wrap: true,
449
+ });
450
+
451
+ // Attribution
452
+ if (slide.author || slide.attribution) {
453
+ s.addShape(pres.ShapeType.rect, {
454
+ x: 1.5, y: 5.2, w: 1.5, h: 0.03, fill: { color: theme.accent },
455
+ });
456
+ s.addText('โ€” ' + (slide.author || slide.attribution), {
457
+ x: 1.5, y: 5.4, w: 8, h: 0.5,
458
+ fontSize: 16, color: theme.accent, fontFace: 'Arial',
104
459
  });
105
460
  }
461
+ }
106
462
 
107
- // Save
108
- const filename = title.replace(/[^a-zA-Z0-9\u0600-\u06FF ]/g, '').replace(/\s+/g, '_').slice(0, 50) + '.pptx';
109
- const filepath = join(this.outputDir, filename);
110
-
111
- const data = await pres.write({ outputType: 'nodebuffer' });
112
- writeFileSync(filepath, data);
463
+ _addImageSlide(pres, slide, theme, slideNum, total) {
464
+ const s = this._addBase(pres, theme, slideNum, total);
465
+
466
+ if (slide.title) {
467
+ s.addText(slide.title, {
468
+ x: 0.5, y: 0.3, w: 12, h: 0.7,
469
+ fontSize: 26, bold: true, color: theme.title, fontFace: 'Arial',
470
+ });
471
+ }
472
+
473
+ if (slide.imageUrl) {
474
+ try {
475
+ s.addImage({
476
+ path: slide.imageUrl,
477
+ x: 1, y: 1.5, w: 11, h: 5,
478
+ sizing: { type: 'contain', w: 11, h: 5 },
479
+ });
480
+ } catch {}
481
+ }
482
+
483
+ if (slide.caption) {
484
+ s.addText(slide.caption, {
485
+ x: 0.5, y: 6.5, w: 12, h: 0.5,
486
+ fontSize: 12, color: theme.text, align: 'center', italic: true,
487
+ });
488
+ }
489
+ }
490
+
491
+ _addSectionSlide(pres, slide, theme, slideNum, total) {
492
+ const s = pres.addSlide();
493
+ s.background = { color: theme.bg2 };
494
+
495
+ s.addShape(pres.ShapeType.rect, {
496
+ x: 0, y: 0, w: '100%', h: 0.08, fill: { color: theme.accent },
497
+ });
498
+
499
+ s.addText(slide.title || 'Section', {
500
+ x: 0.5, y: 2.5, w: 12, h: 1.5,
501
+ fontSize: 38, bold: true, color: theme.title, fontFace: 'Arial',
502
+ });
503
+
504
+ if (slide.subtitle) {
505
+ s.addText(slide.subtitle, {
506
+ x: 0.5, y: 4, w: 10, h: 0.8,
507
+ fontSize: 18, color: theme.text, fontFace: 'Arial',
508
+ });
509
+ }
510
+
511
+ s.addShape(pres.ShapeType.rect, {
512
+ x: 0.5, y: 4.9, w: 2.5, h: 0.04, fill: { color: theme.accent },
513
+ });
514
+ }
515
+
516
+ _addThankYouSlide(pres, options, theme) {
517
+ const s = pres.addSlide();
518
+ s.background = { color: theme.bg };
519
+
520
+ s.addShape(pres.ShapeType.rect, {
521
+ x: 0, y: 0, w: '100%', h: 0.08, fill: { color: theme.accent },
522
+ });
523
+
524
+ s.addText('Thank You', {
525
+ x: 0, y: 2, w: 13, h: 1.5,
526
+ fontSize: 48, bold: true, color: theme.title, align: 'center', fontFace: 'Arial',
527
+ });
528
+
529
+ s.addShape(pres.ShapeType.rect, {
530
+ x: 5.5, y: 3.6, w: 2, h: 0.04, fill: { color: theme.accent },
531
+ });
113
532
 
114
- logger.info('pptx', `Created: ${filepath} (${slides.length} slides)`);
115
- return { filepath, filename, slideCount: slides.length + 1 }; // +1 for title slide
533
+ if (options.contact) {
534
+ s.addText(options.contact, {
535
+ x: 0, y: 4, w: 13, h: 0.8,
536
+ fontSize: 16, color: theme.text, align: 'center', fontFace: 'Arial',
537
+ });
538
+ }
539
+
540
+ s.addText('Created with Squidclaw AI ๐Ÿฆ‘', {
541
+ x: 0, y: 6.5, w: 13, h: 0.5,
542
+ fontSize: 11, color: theme.border, align: 'center', italic: true,
543
+ });
116
544
  }
117
545
 
546
+ // โ”€โ”€ PARSER โ”€โ”€
547
+
118
548
  /**
119
- * Parse AI-generated content into slides
120
- * Format: "## Slide Title\n- bullet 1\n- bullet 2\n\n## Next Slide..."
549
+ * Parse AI content into structured slides
550
+ * Supports: ## Title, - bullets, [chart:bar], [table], [stats], [timeline], [quote], [compare], [section]
121
551
  */
122
552
  static parseContent(text) {
123
553
  const slides = [];
@@ -125,22 +555,190 @@ export class PptxGenerator {
125
555
 
126
556
  for (const section of sections) {
127
557
  const lines = section.trim().split('\n');
128
- const title = lines[0].trim();
129
- const bullets = [];
130
- let content = '';
558
+ const titleLine = lines[0].trim();
559
+
560
+ // Detect slide type from markers
561
+ let slide = { title: titleLine, type: 'content', bullets: [], content: '' };
562
+
563
+ // [chart:bar] or [chart:pie] etc
564
+ const chartMatch = titleLine.match(/\[chart:(\w+)\]/i) || lines.some(l => l.match(/\[chart:(\w+)\]/i));
565
+ if (chartMatch || lines.some(l => /\[chart/i.test(l))) {
566
+ slide.type = 'chart';
567
+ slide.title = titleLine.replace(/\[chart:\w+\]/i, '').trim();
568
+ const typeMatch = section.match(/\[chart:(\w+)\]/i);
569
+ slide.chartType = typeMatch ? typeMatch[1] : 'bar';
570
+ slide.chartData = this._parseChartData(lines.slice(1));
571
+ slides.push(slide);
572
+ continue;
573
+ }
574
+
575
+ // [table]
576
+ if (lines.some(l => /\[table\]|^\|/.test(l))) {
577
+ slide.type = 'table';
578
+ slide.title = titleLine.replace(/\[table\]/i, '').trim();
579
+ slide.tableData = this._parseTableData(lines.slice(1));
580
+ slides.push(slide);
581
+ continue;
582
+ }
583
+
584
+ // [stats]
585
+ if (lines.some(l => /\[stats?\]/i.test(l))) {
586
+ slide.type = 'stats';
587
+ slide.title = titleLine.replace(/\[stats?\]/i, '').trim();
588
+ slide.stats = this._parseStats(lines.slice(1));
589
+ slides.push(slide);
590
+ continue;
591
+ }
592
+
593
+ // [timeline]
594
+ if (lines.some(l => /\[timeline\]/i.test(l))) {
595
+ slide.type = 'timeline';
596
+ slide.title = titleLine.replace(/\[timeline\]/i, '').trim();
597
+ slide.events = this._parseTimeline(lines.slice(1));
598
+ slides.push(slide);
599
+ continue;
600
+ }
131
601
 
602
+ // [quote]
603
+ if (lines.some(l => /\[quote\]/i.test(l)) || titleLine.includes('[quote]')) {
604
+ slide.type = 'quote';
605
+ slide.title = titleLine.replace(/\[quote\]/i, '').trim();
606
+ const quoteLines = lines.slice(1).filter(l => !l.includes('[quote]'));
607
+ const authorLine = quoteLines.find(l => l.startsWith('โ€”') || l.startsWith('-โ€”') || l.startsWith('- Author:'));
608
+ slide.quote = quoteLines.filter(l => l !== authorLine).map(l => l.replace(/^[-โ€ข*]\s*/, '').trim()).filter(Boolean).join(' ');
609
+ slide.author = authorLine ? authorLine.replace(/^[-โ€”โ€ข*\s]*Author:\s*|^[-โ€”]\s*/i, '').trim() : '';
610
+ slides.push(slide);
611
+ continue;
612
+ }
613
+
614
+ // [compare] or [vs]
615
+ if (lines.some(l => /\[compare\]|\[vs\]/i.test(l))) {
616
+ slide.type = 'comparison';
617
+ slide.title = titleLine.replace(/\[compare\]|\[vs\]/i, '').trim();
618
+ const parsed = this._parseComparison(lines.slice(1));
619
+ slide.left = parsed.left;
620
+ slide.right = parsed.right;
621
+ slides.push(slide);
622
+ continue;
623
+ }
624
+
625
+ // [section]
626
+ if (titleLine.includes('[section]') || lines.some(l => /\[section\]/i.test(l))) {
627
+ slide.type = 'section';
628
+ slide.title = titleLine.replace(/\[section\]/i, '').trim();
629
+ slide.subtitle = lines.slice(1).filter(l => !l.includes('[section]')).map(l => l.trim()).filter(Boolean).join(' ');
630
+ slides.push(slide);
631
+ continue;
632
+ }
633
+
634
+ // Default: bullets
132
635
  for (let i = 1; i < lines.length; i++) {
133
636
  const line = lines[i].trim();
134
637
  if (line.startsWith('- ') || line.startsWith('โ€ข ') || line.startsWith('* ')) {
135
- bullets.push(line.replace(/^[-โ€ข*]\s*/, ''));
638
+ slide.bullets.push(line.replace(/^[-โ€ข*]\s*/, ''));
136
639
  } else if (line) {
137
- content += (content ? '\n' : '') + line;
640
+ slide.content += (slide.content ? '\n' : '') + line;
138
641
  }
139
642
  }
140
-
141
- slides.push({ title, bullets: bullets.length > 0 ? bullets : null, content: content || null });
643
+ if (!slide.bullets.length && slide.content) slide.bullets = null;
644
+ slides.push(slide);
142
645
  }
143
646
 
144
647
  return slides;
145
648
  }
649
+
650
+ static _parseChartData(lines) {
651
+ const data = [];
652
+ let current = null;
653
+ const labels = [];
654
+ const values = [];
655
+
656
+ for (const line of lines) {
657
+ const trimmed = line.trim();
658
+ if (!trimmed || trimmed.includes('[chart')) continue;
659
+
660
+ // Format: "Label: value" or "Label | value"
661
+ const match = trimmed.match(/^[-โ€ข*]?\s*(.+?)[\s:|\-โ†’]+(\d+[\d,.]*%?)\s*$/);
662
+ if (match) {
663
+ labels.push(match[1].trim());
664
+ values.push(parseFloat(match[2].replace(/[,%]/g, '')));
665
+ }
666
+ }
667
+
668
+ if (labels.length > 0) {
669
+ data.push({ name: 'Data', labels, values });
670
+ }
671
+ return data.length > 0 ? data : [{ name: 'Sample', labels: ['A', 'B', 'C'], values: [30, 50, 20] }];
672
+ }
673
+
674
+ static _parseTableData(lines) {
675
+ const rows = [];
676
+ for (const line of lines) {
677
+ const trimmed = line.trim();
678
+ if (!trimmed || trimmed.includes('[table]') || /^[-|:]+$/.test(trimmed)) continue;
679
+ if (trimmed.startsWith('|')) {
680
+ const cells = trimmed.split('|').filter(c => c.trim()).map(c => c.trim());
681
+ if (cells.length > 0) rows.push(cells);
682
+ } else if (trimmed.includes(',') || trimmed.includes('\t')) {
683
+ rows.push(trimmed.split(/[,\t]+/).map(c => c.trim()));
684
+ }
685
+ }
686
+ return rows.length > 0 ? rows : [['Col 1', 'Col 2'], ['Data', 'Data']];
687
+ }
688
+
689
+ static _parseStats(lines) {
690
+ const stats = [];
691
+ for (const line of lines) {
692
+ const trimmed = line.trim();
693
+ if (!trimmed || trimmed.includes('[stat')) continue;
694
+ // Format: "๐Ÿ“Š 100M+ โ€” Active users" or "100M: Active users"
695
+ const match = trimmed.match(/^[-โ€ข*]?\s*([^\w]*?)?\s*([\d,.]+[%KMBkm+]*)\s*[-:โ€”|]+\s*(.+)/);
696
+ if (match) {
697
+ stats.push({
698
+ icon: match[1]?.trim() || undefined,
699
+ value: match[2].trim(),
700
+ label: match[3].trim(),
701
+ });
702
+ }
703
+ }
704
+ return stats.length > 0 ? stats : [{ value: '?', label: 'No data' }];
705
+ }
706
+
707
+ static _parseTimeline(lines) {
708
+ const events = [];
709
+ for (const line of lines) {
710
+ const trimmed = line.trim();
711
+ if (!trimmed || trimmed.includes('[timeline]')) continue;
712
+ const match = trimmed.match(/^[-โ€ข*]?\s*(\d{4}[s]?|Phase \d+|Q\d|Step \d+)\s*[-:โ€”|]+\s*(.+)/i);
713
+ if (match) {
714
+ events.push({ date: match[1].trim(), text: match[2].trim() });
715
+ }
716
+ }
717
+ return events.length > 0 ? events : [{ date: '???', text: 'No events' }];
718
+ }
719
+
720
+ static _parseComparison(lines) {
721
+ const left = { title: 'Option A', points: [] };
722
+ const right = { title: 'Option B', points: [] };
723
+ let side = 'left';
724
+
725
+ for (const line of lines) {
726
+ const trimmed = line.trim();
727
+ if (!trimmed || trimmed.includes('[compare]') || trimmed.includes('[vs]')) continue;
728
+
729
+ if (trimmed.toLowerCase().includes('vs') || trimmed === '---') {
730
+ side = 'right';
731
+ continue;
732
+ }
733
+
734
+ const target = side === 'left' ? left : right;
735
+ if (trimmed.startsWith('###') || trimmed.startsWith('**')) {
736
+ target.title = trimmed.replace(/^[#*\s]+|[*]+$/g, '');
737
+ } else if (trimmed.startsWith('- ') || trimmed.startsWith('โ€ข ')) {
738
+ target.points.push(trimmed.replace(/^[-โ€ข]\s*/, ''));
739
+ }
740
+ }
741
+
742
+ return { left, right };
743
+ }
146
744
  }
@@ -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,23 @@ 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---');
75
81
 
76
82
  tools.push('', '### Allow User',
77
83
  '---TOOL:allow:user_id_or_phone---',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squidclaw",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "๐Ÿฆ‘ AI agent platform โ€” human-like agents for WhatsApp, Telegram & more",
5
5
  "main": "lib/engine.js",
6
6
  "bin": {