squidclaw 2.0.0 → 2.2.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.
- package/lib/core/agent-tools-mixin.js +10 -0
- package/lib/features/allowlist-manager.js +105 -0
- package/lib/middleware/ai-process.js +2 -0
- package/lib/middleware/commands.js +65 -1
- package/lib/middleware/response-sender.js +21 -0
- package/lib/tools/pptx.js +146 -0
- package/lib/tools/router.js +104 -0
- package/package.json +2 -1
|
@@ -58,6 +58,8 @@ export function addToolSupport(agent, toolRouter, knowledgeBase) {
|
|
|
58
58
|
const fullResponse = result.messages.join('\n');
|
|
59
59
|
toolRouter._currentContactId = contactId;
|
|
60
60
|
toolRouter.storage = agent.storage;
|
|
61
|
+
toolRouter._currentContactId = contactId;
|
|
62
|
+
toolRouter._currentPlatform = metadata?.platform;
|
|
61
63
|
const toolResult = await toolRouter.processResponse(fullResponse, agent.id);
|
|
62
64
|
|
|
63
65
|
if (toolResult.toolUsed && toolResult.toolName === 'remind' && toolResult.reminderTime) {
|
|
@@ -71,6 +73,14 @@ export function addToolSupport(agent, toolRouter, knowledgeBase) {
|
|
|
71
73
|
return result;
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
// File attachment (pptx, etc)
|
|
77
|
+
if (toolResult.toolUsed && toolResult.filePath) {
|
|
78
|
+
result.filePath = toolResult.filePath;
|
|
79
|
+
result.fileName = toolResult.fileName;
|
|
80
|
+
result.messages = [toolResult.toolResult || 'Here\'s your file! 📎'];
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
74
84
|
if (toolResult.toolUsed && (toolResult.imageBase64 || toolResult.imageUrl)) {
|
|
75
85
|
// Image generated — pass through directly
|
|
76
86
|
result.image = { base64: toolResult.imageBase64, url: toolResult.imageUrl, mimeType: toolResult.mimeType };
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🦑 Allowlist Manager
|
|
3
|
+
* Manage who can talk to the agent via chat commands or AI tool
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { loadConfig, saveConfig, getHome } from '../core/config.js';
|
|
7
|
+
import { logger } from '../core/logger.js';
|
|
8
|
+
|
|
9
|
+
export class AllowlistManager {
|
|
10
|
+
constructor(engine) {
|
|
11
|
+
this.engine = engine;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get current allowlist for a platform
|
|
16
|
+
*/
|
|
17
|
+
list(platform = 'telegram') {
|
|
18
|
+
const config = loadConfig();
|
|
19
|
+
const allowFrom = config.channels?.[platform]?.allowFrom;
|
|
20
|
+
if (!allowFrom || allowFrom === '*') return { mode: 'open', list: [] };
|
|
21
|
+
const list = Array.isArray(allowFrom) ? allowFrom : [String(allowFrom)];
|
|
22
|
+
return { mode: 'restricted', list };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Add someone to the allowlist
|
|
27
|
+
*/
|
|
28
|
+
add(platform, id) {
|
|
29
|
+
const config = loadConfig();
|
|
30
|
+
if (!config.channels) config.channels = {};
|
|
31
|
+
if (!config.channels[platform]) config.channels[platform] = {};
|
|
32
|
+
|
|
33
|
+
let allowFrom = config.channels[platform].allowFrom;
|
|
34
|
+
|
|
35
|
+
if (!allowFrom || allowFrom === '*') {
|
|
36
|
+
// First time — also include current entries
|
|
37
|
+
config.channels[platform].allowFrom = [String(id)];
|
|
38
|
+
} else if (Array.isArray(allowFrom)) {
|
|
39
|
+
if (!allowFrom.includes(String(id))) {
|
|
40
|
+
allowFrom.push(String(id));
|
|
41
|
+
}
|
|
42
|
+
} else {
|
|
43
|
+
// Single value → convert to array
|
|
44
|
+
config.channels[platform].allowFrom = [String(allowFrom), String(id)];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
saveConfig(config);
|
|
48
|
+
this.engine.config = config;
|
|
49
|
+
logger.info('allowlist', `Added ${id} to ${platform} allowlist`);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Remove someone from the allowlist
|
|
55
|
+
*/
|
|
56
|
+
remove(platform, id) {
|
|
57
|
+
const config = loadConfig();
|
|
58
|
+
const allowFrom = config.channels?.[platform]?.allowFrom;
|
|
59
|
+
if (!allowFrom || allowFrom === '*') return false;
|
|
60
|
+
|
|
61
|
+
if (Array.isArray(allowFrom)) {
|
|
62
|
+
config.channels[platform].allowFrom = allowFrom.filter(a => a !== String(id));
|
|
63
|
+
if (config.channels[platform].allowFrom.length === 0) {
|
|
64
|
+
config.channels[platform].allowFrom = '*'; // No one left = open
|
|
65
|
+
}
|
|
66
|
+
} else if (String(allowFrom) === String(id)) {
|
|
67
|
+
config.channels[platform].allowFrom = '*';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
saveConfig(config);
|
|
71
|
+
this.engine.config = config;
|
|
72
|
+
logger.info('allowlist', `Removed ${id} from ${platform} allowlist`);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Open to everyone
|
|
78
|
+
*/
|
|
79
|
+
openAll(platform) {
|
|
80
|
+
const config = loadConfig();
|
|
81
|
+
if (config.channels?.[platform]) {
|
|
82
|
+
config.channels[platform].allowFrom = '*';
|
|
83
|
+
}
|
|
84
|
+
saveConfig(config);
|
|
85
|
+
this.engine.config = config;
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Lock down — only allowlist can message
|
|
91
|
+
*/
|
|
92
|
+
lockDown(platform) {
|
|
93
|
+
// Just make sure allowFrom is an array (not '*')
|
|
94
|
+
const config = loadConfig();
|
|
95
|
+
if (config.channels?.[platform]) {
|
|
96
|
+
const cur = config.channels[platform].allowFrom;
|
|
97
|
+
if (!cur || cur === '*') {
|
|
98
|
+
config.channels[platform].allowFrom = [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
saveConfig(config);
|
|
102
|
+
this.engine.config = config;
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -12,6 +12,8 @@ export async function aiProcessMiddleware(ctx, next) {
|
|
|
12
12
|
messages: result.messages || [],
|
|
13
13
|
reaction: result.reaction || null,
|
|
14
14
|
image: result.image || null,
|
|
15
|
+
filePath: result.filePath || null,
|
|
16
|
+
fileName: result.fileName || null,
|
|
15
17
|
_reminder: result._reminder || null,
|
|
16
18
|
};
|
|
17
19
|
ctx.metadata.originalType = ctx.metadata.originalType || null;
|
|
@@ -16,7 +16,12 @@ export async function commandsMiddleware(ctx, next) {
|
|
|
16
16
|
'/memories — what I remember about you',
|
|
17
17
|
'/tasks — your todo list',
|
|
18
18
|
'/usage — spending report',
|
|
19
|
-
'/
|
|
19
|
+
'/allow <id> — allow someone to text me',
|
|
20
|
+
'/block <id> — block someone',
|
|
21
|
+
'/allowlist — show allowed users',
|
|
22
|
+
'/lockdown — restrict to allowlist only',
|
|
23
|
+
'/open — allow everyone',
|
|
24
|
+
'/notes — notes',
|
|
20
25
|
'/contacts — contact book',
|
|
21
26
|
'/briefing — daily briefing',
|
|
22
27
|
'/summary — summarize chat',
|
|
@@ -127,6 +132,65 @@ export async function commandsMiddleware(ctx, next) {
|
|
|
127
132
|
return;
|
|
128
133
|
}
|
|
129
134
|
|
|
135
|
+
if (cmd === '/allow') {
|
|
136
|
+
const args = msg.slice(7).trim();
|
|
137
|
+
if (!args) { await ctx.reply('Usage: /allow <user_id or phone>'); return; }
|
|
138
|
+
try {
|
|
139
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
140
|
+
const am = new AllowlistManager(ctx.engine);
|
|
141
|
+
am.add(ctx.platform, args);
|
|
142
|
+
await ctx.reply('✅ Allowed *' + args + '* to message me');
|
|
143
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (cmd === '/block') {
|
|
148
|
+
const args = msg.slice(7).trim();
|
|
149
|
+
if (!args) { await ctx.reply('Usage: /block <user_id or phone>'); return; }
|
|
150
|
+
try {
|
|
151
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
152
|
+
const am = new AllowlistManager(ctx.engine);
|
|
153
|
+
am.remove(ctx.platform, args);
|
|
154
|
+
await ctx.reply('🚫 Blocked *' + args + '* from messaging me');
|
|
155
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (cmd === '/allowlist') {
|
|
160
|
+
try {
|
|
161
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
162
|
+
const am = new AllowlistManager(ctx.engine);
|
|
163
|
+
const { mode, list } = am.list(ctx.platform);
|
|
164
|
+
if (mode === 'open') {
|
|
165
|
+
await ctx.reply('🔓 *Open Mode* — anyone can message me\n\nUse /allow <id> to start restricting');
|
|
166
|
+
} else {
|
|
167
|
+
const lines = list.map(id => '• ' + id).join('\n');
|
|
168
|
+
await ctx.reply('🔒 *Restricted Mode*\n\nAllowed:\n' + lines + '\n\nUse /allow <id> or /block <id>');
|
|
169
|
+
}
|
|
170
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (cmd === '/open') {
|
|
175
|
+
try {
|
|
176
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
177
|
+
const am = new AllowlistManager(ctx.engine);
|
|
178
|
+
am.openAll(ctx.platform);
|
|
179
|
+
await ctx.reply('🔓 Open mode — anyone can message me now');
|
|
180
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (cmd === '/lockdown') {
|
|
185
|
+
try {
|
|
186
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
187
|
+
const am = new AllowlistManager(ctx.engine);
|
|
188
|
+
am.lockDown(ctx.platform);
|
|
189
|
+
await ctx.reply('🔒 Locked down — only allowlisted users can message me');
|
|
190
|
+
} catch (err) { await ctx.reply('❌ ' + err.message); }
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
|
|
130
194
|
if (cmd === '/notes') {
|
|
131
195
|
try {
|
|
132
196
|
const { NotesManager } = await import('../tools/notes.js');
|
|
@@ -29,6 +29,27 @@ export async function responseSenderMiddleware(ctx, next) {
|
|
|
29
29
|
|
|
30
30
|
// Send via appropriate channel
|
|
31
31
|
if (ctx.platform === 'telegram' && tm) {
|
|
32
|
+
// Send file attachment (pptx, etc)
|
|
33
|
+
if (response.filePath) {
|
|
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
|
+
}
|
|
47
|
+
} catch (err) {
|
|
48
|
+
logger.error('sender', 'File send failed: ' + err.message);
|
|
49
|
+
// Fall through to text
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
32
53
|
if (response.image) {
|
|
33
54
|
const photoData = response.image.url ? { url: response.image.url } : { base64: response.image.base64 };
|
|
34
55
|
await tm.sendPhoto(agentId, contactId, photoData, response.messages?.[0] || '', metadata);
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🦑 PowerPoint Generator
|
|
3
|
+
* Create .pptx presentations from AI-generated content
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { logger } from '../core/logger.js';
|
|
7
|
+
import { writeFileSync, mkdirSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
|
|
10
|
+
export class PptxGenerator {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.outputDir = '/tmp/squidclaw-pptx';
|
|
13
|
+
mkdirSync(this.outputDir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
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}
|
|
21
|
+
*/
|
|
22
|
+
async create(title, slides, options = {}) {
|
|
23
|
+
const pptxgen = (await import('pptxgenjs')).default;
|
|
24
|
+
const pres = new pptxgen();
|
|
25
|
+
|
|
26
|
+
// Metadata
|
|
27
|
+
pres.author = options.author || 'Squidclaw AI';
|
|
28
|
+
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;
|
|
41
|
+
|
|
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',
|
|
49
|
+
});
|
|
50
|
+
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',
|
|
54
|
+
});
|
|
55
|
+
}
|
|
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,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Content slides
|
|
62
|
+
for (const slide of slides) {
|
|
63
|
+
const s = pres.addSlide();
|
|
64
|
+
s.background = { color: theme.bg };
|
|
65
|
+
|
|
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,
|
|
70
|
+
});
|
|
71
|
+
|
|
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 },
|
|
75
|
+
});
|
|
76
|
+
|
|
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,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Slide notes
|
|
96
|
+
if (slide.notes) {
|
|
97
|
+
s.addNotes(slide.notes);
|
|
98
|
+
}
|
|
99
|
+
|
|
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',
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
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);
|
|
113
|
+
|
|
114
|
+
logger.info('pptx', `Created: ${filepath} (${slides.length} slides)`);
|
|
115
|
+
return { filepath, filename, slideCount: slides.length + 1 }; // +1 for title slide
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parse AI-generated content into slides
|
|
120
|
+
* Format: "## Slide Title\n- bullet 1\n- bullet 2\n\n## Next Slide..."
|
|
121
|
+
*/
|
|
122
|
+
static parseContent(text) {
|
|
123
|
+
const slides = [];
|
|
124
|
+
const sections = text.split(/^## /gm).filter(s => s.trim());
|
|
125
|
+
|
|
126
|
+
for (const section of sections) {
|
|
127
|
+
const lines = section.trim().split('\n');
|
|
128
|
+
const title = lines[0].trim();
|
|
129
|
+
const bullets = [];
|
|
130
|
+
let content = '';
|
|
131
|
+
|
|
132
|
+
for (let i = 1; i < lines.length; i++) {
|
|
133
|
+
const line = lines[i].trim();
|
|
134
|
+
if (line.startsWith('- ') || line.startsWith('• ') || line.startsWith('* ')) {
|
|
135
|
+
bullets.push(line.replace(/^[-•*]\s*/, ''));
|
|
136
|
+
} else if (line) {
|
|
137
|
+
content += (content ? '\n' : '') + line;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
slides.push({ title, bullets: bullets.length > 0 ? bullets : null, content: content || null });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return slides;
|
|
145
|
+
}
|
|
146
|
+
}
|
package/lib/tools/router.js
CHANGED
|
@@ -59,6 +59,30 @@ export class ToolRouter {
|
|
|
59
59
|
'Send an email.');
|
|
60
60
|
}
|
|
61
61
|
|
|
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',
|
|
68
|
+
'',
|
|
69
|
+
'## Another Slide',
|
|
70
|
+
'- More content',
|
|
71
|
+
'',
|
|
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---');
|
|
75
|
+
|
|
76
|
+
tools.push('', '### Allow User',
|
|
77
|
+
'---TOOL:allow:user_id_or_phone---',
|
|
78
|
+
'Add someone to the allowlist so they can message you.',
|
|
79
|
+
'', '### Block User',
|
|
80
|
+
'---TOOL:block:user_id_or_phone---',
|
|
81
|
+
'Remove someone from the allowlist.',
|
|
82
|
+
'', '### Show Allowlist',
|
|
83
|
+
'---TOOL:allowlist:show---',
|
|
84
|
+
'Show who is currently allowed to message.');
|
|
85
|
+
|
|
62
86
|
tools.push('', '### Save Note',
|
|
63
87
|
'---TOOL:note:content of the note---',
|
|
64
88
|
'Save a personal note for the user.',
|
|
@@ -211,6 +235,86 @@ export class ToolRouter {
|
|
|
211
235
|
}
|
|
212
236
|
break;
|
|
213
237
|
}
|
|
238
|
+
case 'pptx':
|
|
239
|
+
case 'pptx_slides':
|
|
240
|
+
case 'powerpoint':
|
|
241
|
+
case 'presentation': {
|
|
242
|
+
try {
|
|
243
|
+
const { PptxGenerator } = await import('./pptx.js');
|
|
244
|
+
const gen = new PptxGenerator();
|
|
245
|
+
|
|
246
|
+
// Parse: title|theme|content or just content
|
|
247
|
+
const parts = toolArg.split('|');
|
|
248
|
+
let title, theme, slideContent;
|
|
249
|
+
|
|
250
|
+
if (parts.length >= 3) {
|
|
251
|
+
title = parts[0].trim();
|
|
252
|
+
theme = parts[1].trim();
|
|
253
|
+
slideContent = parts.slice(2).join('|');
|
|
254
|
+
} else if (parts.length === 2) {
|
|
255
|
+
title = parts[0].trim();
|
|
256
|
+
slideContent = parts[1];
|
|
257
|
+
theme = 'corporate';
|
|
258
|
+
} else {
|
|
259
|
+
// Try to extract title from first ## heading
|
|
260
|
+
const firstH2 = toolArg.match(/^##\s+(.+)/m);
|
|
261
|
+
title = firstH2 ? firstH2[1] : 'Presentation';
|
|
262
|
+
slideContent = toolArg;
|
|
263
|
+
theme = 'corporate';
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const slides = PptxGenerator.parseContent(slideContent);
|
|
267
|
+
if (slides.length === 0) {
|
|
268
|
+
toolResult = 'No slides found. Use ## headings and - bullet points.';
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const result = await gen.create(title, slides, { theme });
|
|
273
|
+
|
|
274
|
+
// Return file path for sending
|
|
275
|
+
return {
|
|
276
|
+
toolUsed: true,
|
|
277
|
+
toolName: 'pptx',
|
|
278
|
+
toolResult: 'PowerPoint created: ' + result.filename + ' (' + result.slideCount + ' slides)',
|
|
279
|
+
filePath: result.filepath,
|
|
280
|
+
fileName: result.filename,
|
|
281
|
+
cleanResponse
|
|
282
|
+
};
|
|
283
|
+
} catch (err) {
|
|
284
|
+
toolResult = 'PowerPoint failed: ' + err.message;
|
|
285
|
+
}
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
case 'allow': {
|
|
289
|
+
try {
|
|
290
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
291
|
+
const am = new AllowlistManager(this._engine);
|
|
292
|
+
const platform = this._currentPlatform || 'telegram';
|
|
293
|
+
am.add(platform, toolArg);
|
|
294
|
+
toolResult = 'Allowed ' + toolArg + ' to message you ✅';
|
|
295
|
+
} catch (err) { toolResult = 'Failed: ' + err.message; }
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
case 'block': {
|
|
299
|
+
try {
|
|
300
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
301
|
+
const am = new AllowlistManager(this._engine);
|
|
302
|
+
const platform = this._currentPlatform || 'telegram';
|
|
303
|
+
am.remove(platform, toolArg);
|
|
304
|
+
toolResult = 'Blocked ' + toolArg + ' ✅';
|
|
305
|
+
} catch (err) { toolResult = 'Failed: ' + err.message; }
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
case 'allowlist': {
|
|
309
|
+
try {
|
|
310
|
+
const { AllowlistManager } = await import('../features/allowlist-manager.js');
|
|
311
|
+
const am = new AllowlistManager(this._engine);
|
|
312
|
+
const platform = this._currentPlatform || 'telegram';
|
|
313
|
+
const { mode, list } = am.list(platform);
|
|
314
|
+
toolResult = mode === 'open' ? 'Open — anyone can message' : 'Restricted to: ' + list.join(', ');
|
|
315
|
+
} catch (err) { toolResult = 'Failed: ' + err.message; }
|
|
316
|
+
break;
|
|
317
|
+
}
|
|
214
318
|
case 'note': {
|
|
215
319
|
const { NotesManager } = await import('./notes.js');
|
|
216
320
|
const nm = new NotesManager(this.storage);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squidclaw",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "🦑 AI agent platform — human-like agents for WhatsApp, Telegram & more",
|
|
5
5
|
"main": "lib/engine.js",
|
|
6
6
|
"bin": {
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"node-edge-tts": "^1.2.10",
|
|
50
50
|
"pdfjs-dist": "^5.4.624",
|
|
51
51
|
"pino": "^10.3.1",
|
|
52
|
+
"pptxgenjs": "^4.0.1",
|
|
52
53
|
"puppeteer-core": "^24.37.5",
|
|
53
54
|
"qrcode-terminal": "^0.12.0",
|
|
54
55
|
"sharp": "^0.34.5",
|