fabiana 1.5.0 → 1.7.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.
@@ -29,6 +29,38 @@ export class TelegramAdapter {
29
29
  });
30
30
  console.log(`šŸ“Ø [telegram] Message queued: "${text.slice(0, 50)}"`);
31
31
  });
32
+ this.bot.on('photo', async (ctx) => {
33
+ if (ctx.chat.id !== this.chatId)
34
+ return;
35
+ // Pick the largest available size (last in array)
36
+ const photo = ctx.message.photo[ctx.message.photo.length - 1];
37
+ const caption = ctx.message.caption ?? '';
38
+ try {
39
+ const fileLink = await this.bot.telegram.getFileLink(photo.file_id);
40
+ const response = await fetch(fileLink.href);
41
+ if (!response.ok)
42
+ throw new Error(`HTTP ${response.status}`);
43
+ const buffer = Buffer.from(await response.arrayBuffer());
44
+ await fs.mkdir(paths.imagesDir, { recursive: true });
45
+ const filename = `photo-${Date.now()}.jpg`;
46
+ const imagePath = paths.images(filename);
47
+ await fs.writeFile(imagePath, buffer);
48
+ const text = caption || '[sent a photo]';
49
+ this.queue.push({
50
+ text,
51
+ senderId: String(ctx.from.id),
52
+ channelId: String(ctx.chat.id),
53
+ threadId: String(ctx.message.message_id),
54
+ timestamp: new Date(ctx.message.date * 1000),
55
+ source: 'telegram',
56
+ imagePaths: [imagePath],
57
+ });
58
+ console.log(`šŸ“Ø [telegram] Photo queued → ${imagePath}`);
59
+ }
60
+ catch (err) {
61
+ console.error('āŒ Failed to download Telegram photo:', err.message);
62
+ }
63
+ });
32
64
  this.bot.catch((err) => {
33
65
  console.error('Telegram error:', err);
34
66
  });
package/dist/cli.js CHANGED
@@ -16,6 +16,7 @@ import { runBackup, runRestore } from './backup.js';
16
16
  import { pluginsAdd, pluginsList } from './plugins-cmd.js';
17
17
  import { skillsAdd, skillsList, skillsRemove, skillsEnable, skillsDisable } from './skills-cmd.js';
18
18
  import { runSetup } from './setup/index.js';
19
+ import { runSync } from './sync-cmd.js';
19
20
  import { providerStatus, providerUse, providerAdd } from './provider-cmd.js';
20
21
  import { modelStatus, modelUse, modelTest } from './model-cmd.js';
21
22
  const C = '\x1b[96m'; // cyan — name
@@ -49,6 +50,10 @@ program
49
50
  .command('init')
50
51
  .description('First time? Let\'s get acquainted')
51
52
  .action(runSetup);
53
+ program
54
+ .command('sync')
55
+ .description('Sync bundled plugins and skills to ~/.fabiana (run after upgrades)')
56
+ .action(runSync);
52
57
  program
53
58
  .command('start')
54
59
  .description('Wake her up — she\'ll take it from there')
@@ -158,6 +158,24 @@ export async function runPiSession(mode, incomingMessage, channel, incomingMsg,
158
158
  await updateLastSolitude();
159
159
  }
160
160
  };
161
+ // Lightweight status sender — chat mode only, no logging, no interaction tracking
162
+ const sendStatus = async (text) => {
163
+ if (mode === 'chat' && channel) {
164
+ await channel.send(text, incomingMsg?.channelId, incomingMsg?.threadId);
165
+ }
166
+ };
167
+ const pick = (arr) => arr[Math.floor(Math.random() * arr.length)];
168
+ const TOOL_STATUS = {
169
+ safe_read: ['having a look...', 'let me check that.', 'nosing through your files...', 'one sec, reading.'],
170
+ safe_write: ['writing that down...', 'putting that somewhere safe.', 'noted, filing it away.'],
171
+ safe_edit: ['making a small edit...', 'adjusting things a bit.', 'tweaking that.'],
172
+ fetch_url: ['heading out to the web...', 'brb, checking the internet.', 'let me go look that up.'],
173
+ brave_search: ['diving into search...', 'looking that up for you.', 'let me see what I can find.'],
174
+ hackernews: ['peeking at hacker news...', 'seeing what the nerds are saying.', 'checking HN real quick.'],
175
+ calendar: ['checking your schedule...', 'having a look at your calendar.', 'let me see what\'s coming up.'],
176
+ manage_todo: ['peeking at your todos...', 'checking what\'s on your list.'],
177
+ bash: ['running something for you...', 'brb, executing.', 'let me run that.'],
178
+ };
161
179
  console.log('[6/8] Creating tools...');
162
180
  const fabianaTools = createFabianaTools(permissions, sendMessage, {
163
181
  toolset,
@@ -199,6 +217,11 @@ export async function runPiSession(mode, incomingMessage, channel, incomingMsg,
199
217
  if (event.toolName === 'send_message') {
200
218
  sendMessageCalled = true;
201
219
  }
220
+ else {
221
+ const variants = TOOL_STATUS[event.toolName];
222
+ if (variants)
223
+ await sendStatus(pick(variants));
224
+ }
202
225
  lastActivityWasThinking = false;
203
226
  }
204
227
  if (event.type === 'tool_execution_end') {
@@ -221,7 +244,21 @@ export async function runPiSession(mode, incomingMessage, channel, incomingMsg,
221
244
  const prompt = buildPrompt(context);
222
245
  console.log(` Context loaded: ${prompt.length} chars`);
223
246
  console.log('\nšŸ’­ Sending prompt to agent...');
224
- await session.prompt(prompt);
247
+ if (mode === 'chat') {
248
+ await sendStatus(pick(['on it.', 'give me a sec.', 'let me think.', 'sure, one moment.', 'on it!']));
249
+ }
250
+ // Load any attached images as base64 for the Pi SDK
251
+ let promptImages;
252
+ if (incomingMsg?.imagePaths?.length) {
253
+ promptImages = await Promise.all(incomingMsg.imagePaths.map(async (p) => {
254
+ const data = await fs.readFile(p);
255
+ const ext = p.split('.').pop()?.toLowerCase() ?? 'jpg';
256
+ const mimeType = ext === 'png' ? 'image/png' : ext === 'gif' ? 'image/gif' : ext === 'webp' ? 'image/webp' : 'image/jpeg';
257
+ return { type: 'image', data: data.toString('base64'), mimeType };
258
+ }));
259
+ console.log(` šŸ–¼ļø Attaching ${promptImages.length} image(s) to prompt`);
260
+ }
261
+ await session.prompt(prompt, promptImages ? { images: promptImages } : undefined);
225
262
  console.log('\nā³ Waiting for agent to complete...');
226
263
  await session.agent.waitForIdle();
227
264
  if (lastActivityWasThinking) {
package/dist/paths.js CHANGED
@@ -18,6 +18,8 @@ export const paths = {
18
18
  logs: (filename) => join(DATA_DIR, 'logs', filename),
19
19
  sessions: join(DATA_DIR, 'sessions'),
20
20
  agentTodo: join(DATA_DIR, 'agent-todo'),
21
+ imagesDir: join(DATA_DIR, 'images'),
22
+ images: (filename) => join(DATA_DIR, 'images', filename),
21
23
  conversations: join(DATA_DIR, 'conversations'),
22
24
  moodMd: join(DATA_DIR, 'memory', 'mood.md'),
23
25
  memoryDb: join(DATA_DIR, 'memory.db'),