apexbot 1.1.1 → 1.1.2
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/dist/cli/index.js +101 -0
- package/dist/skills/calendar.js +215 -17
- package/package.json +2 -2
package/dist/cli/index.js
CHANGED
|
@@ -581,6 +581,107 @@ async function startGatewayServer(config, options = {}) {
|
|
|
581
581
|
});
|
|
582
582
|
}
|
|
583
583
|
// ─────────────────────────────────────────────────────────────────
|
|
584
|
+
// GOOGLE-AUTH Command - OAuth2 authorization for Google Calendar
|
|
585
|
+
// ─────────────────────────────────────────────────────────────────
|
|
586
|
+
program
|
|
587
|
+
.command('google-auth')
|
|
588
|
+
.description('Authorize ApexBot to access your Google Calendar')
|
|
589
|
+
.action(async () => {
|
|
590
|
+
showBanner();
|
|
591
|
+
console.log(chalk.cyan('\n📅 Google Calendar Authorization\n'));
|
|
592
|
+
console.log(chalk.gray('This will allow ApexBot to create events in your Google Calendar.\n'));
|
|
593
|
+
// Check for credentials
|
|
594
|
+
const clientId = process.env.GOOGLE_CLIENT_ID;
|
|
595
|
+
const clientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
|
596
|
+
if (!clientId || !clientSecret) {
|
|
597
|
+
console.log(chalk.yellow('⚠️ Google OAuth2 credentials not found.\n'));
|
|
598
|
+
console.log('To set up Google Calendar integration:\n');
|
|
599
|
+
console.log('1. Go to https://console.cloud.google.com/apis/credentials');
|
|
600
|
+
console.log('2. Create OAuth 2.0 Client ID (type: Desktop app)');
|
|
601
|
+
console.log('3. Enable Google Calendar API');
|
|
602
|
+
console.log('4. Set environment variables:\n');
|
|
603
|
+
console.log(chalk.cyan(' GOOGLE_CLIENT_ID=your-client-id'));
|
|
604
|
+
console.log(chalk.cyan(' GOOGLE_CLIENT_SECRET=your-client-secret\n'));
|
|
605
|
+
console.log('5. Run this command again\n');
|
|
606
|
+
process.exit(1);
|
|
607
|
+
}
|
|
608
|
+
// Build OAuth URL
|
|
609
|
+
const redirectUri = 'urn:ietf:wg:oauth:2.0:oob'; // Manual copy/paste flow
|
|
610
|
+
const scope = 'https://www.googleapis.com/auth/calendar';
|
|
611
|
+
const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
|
|
612
|
+
`client_id=${encodeURIComponent(clientId)}` +
|
|
613
|
+
`&redirect_uri=${encodeURIComponent(redirectUri)}` +
|
|
614
|
+
`&response_type=code` +
|
|
615
|
+
`&scope=${encodeURIComponent(scope)}` +
|
|
616
|
+
`&access_type=offline` +
|
|
617
|
+
`&prompt=consent`;
|
|
618
|
+
console.log(chalk.green('✓ Credentials found!\n'));
|
|
619
|
+
console.log('Open this URL in your browser to authorize:\n');
|
|
620
|
+
console.log(chalk.cyan(authUrl) + '\n');
|
|
621
|
+
// Try to open browser automatically using native OS command
|
|
622
|
+
const { exec } = require('child_process');
|
|
623
|
+
const platform = process.platform;
|
|
624
|
+
const openCmd = platform === 'win32' ? 'start ""' :
|
|
625
|
+
platform === 'darwin' ? 'open' : 'xdg-open';
|
|
626
|
+
exec(`${openCmd} "${authUrl}"`, (err) => {
|
|
627
|
+
if (!err) {
|
|
628
|
+
console.log(chalk.gray('(Browser should open automatically)\n'));
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
// Get authorization code from user
|
|
632
|
+
const { code } = await inquirer.prompt([
|
|
633
|
+
{
|
|
634
|
+
type: 'input',
|
|
635
|
+
name: 'code',
|
|
636
|
+
message: 'Paste the authorization code here:',
|
|
637
|
+
validate: (input) => input.length > 10 || 'Please enter the full authorization code',
|
|
638
|
+
},
|
|
639
|
+
]);
|
|
640
|
+
console.log(chalk.gray('\nExchanging code for tokens...'));
|
|
641
|
+
// Exchange code for tokens
|
|
642
|
+
try {
|
|
643
|
+
const response = await fetch('https://oauth2.googleapis.com/token', {
|
|
644
|
+
method: 'POST',
|
|
645
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
646
|
+
body: new URLSearchParams({
|
|
647
|
+
client_id: clientId,
|
|
648
|
+
client_secret: clientSecret,
|
|
649
|
+
code: code.trim(),
|
|
650
|
+
grant_type: 'authorization_code',
|
|
651
|
+
redirect_uri: redirectUri,
|
|
652
|
+
}),
|
|
653
|
+
});
|
|
654
|
+
if (!response.ok) {
|
|
655
|
+
const error = await response.json();
|
|
656
|
+
throw new Error(error.error_description || error.error || 'Token exchange failed');
|
|
657
|
+
}
|
|
658
|
+
const tokens = await response.json();
|
|
659
|
+
// Save tokens
|
|
660
|
+
const tokenPath = path.join(CONFIG_DIR, 'google_tokens.json');
|
|
661
|
+
fs.writeFileSync(tokenPath, JSON.stringify({
|
|
662
|
+
access_token: tokens.access_token,
|
|
663
|
+
refresh_token: tokens.refresh_token,
|
|
664
|
+
expiry: Date.now() + (tokens.expires_in * 1000),
|
|
665
|
+
}, null, 2));
|
|
666
|
+
console.log(chalk.green('\n✅ Google Calendar authorized successfully!\n'));
|
|
667
|
+
console.log('ApexBot can now create events in your Google Calendar.');
|
|
668
|
+
console.log(`Tokens saved to: ${chalk.gray(tokenPath)}\n`);
|
|
669
|
+
// Test the connection
|
|
670
|
+
console.log(chalk.gray('Testing connection...'));
|
|
671
|
+
const testResponse = await fetch('https://www.googleapis.com/calendar/v3/calendars/primary', {
|
|
672
|
+
headers: { 'Authorization': `Bearer ${tokens.access_token}` },
|
|
673
|
+
});
|
|
674
|
+
if (testResponse.ok) {
|
|
675
|
+
const calendar = await testResponse.json();
|
|
676
|
+
console.log(chalk.green(`✓ Connected to: ${calendar.summary || 'Primary Calendar'}\n`));
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
catch (error) {
|
|
680
|
+
console.log(chalk.red(`\n❌ Authorization failed: ${error.message}\n`));
|
|
681
|
+
process.exit(1);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
// ─────────────────────────────────────────────────────────────────
|
|
584
685
|
// GATEWAY Command
|
|
585
686
|
// ─────────────────────────────────────────────────────────────────
|
|
586
687
|
program
|
package/dist/skills/calendar.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Calendar Skill
|
|
4
4
|
*
|
|
5
5
|
* Integration with Google Calendar and local calendar storage.
|
|
6
|
-
*
|
|
6
|
+
* Supports OAuth2 for full read/write access to Google Calendar.
|
|
7
7
|
*/
|
|
8
8
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
9
|
if (k2 === undefined) k2 = k;
|
|
@@ -94,6 +94,82 @@ let dataPath = '';
|
|
|
94
94
|
let events = [];
|
|
95
95
|
let googleApiKey = '';
|
|
96
96
|
let googleCalendarId = 'primary';
|
|
97
|
+
// Google OAuth2 credentials
|
|
98
|
+
let googleClientId = '';
|
|
99
|
+
let googleClientSecret = '';
|
|
100
|
+
let googleRefreshToken = '';
|
|
101
|
+
let googleAccessToken = '';
|
|
102
|
+
let googleTokenExpiry = 0;
|
|
103
|
+
let configDir = '';
|
|
104
|
+
// ─────────────────────────────────────────────────────────────────
|
|
105
|
+
// Google OAuth2 Token Management
|
|
106
|
+
// ─────────────────────────────────────────────────────────────────
|
|
107
|
+
async function loadGoogleTokens() {
|
|
108
|
+
try {
|
|
109
|
+
const tokenPath = path.join(configDir, 'google_tokens.json');
|
|
110
|
+
const data = await fs.readFile(tokenPath, 'utf-8');
|
|
111
|
+
const tokens = JSON.parse(data);
|
|
112
|
+
googleAccessToken = tokens.access_token || '';
|
|
113
|
+
googleRefreshToken = tokens.refresh_token || '';
|
|
114
|
+
googleTokenExpiry = tokens.expiry || 0;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// No tokens saved yet
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function saveGoogleTokens() {
|
|
121
|
+
const tokenPath = path.join(configDir, 'google_tokens.json');
|
|
122
|
+
await fs.writeFile(tokenPath, JSON.stringify({
|
|
123
|
+
access_token: googleAccessToken,
|
|
124
|
+
refresh_token: googleRefreshToken,
|
|
125
|
+
expiry: googleTokenExpiry,
|
|
126
|
+
}, null, 2));
|
|
127
|
+
}
|
|
128
|
+
async function refreshGoogleToken() {
|
|
129
|
+
if (!googleClientId || !googleClientSecret || !googleRefreshToken) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const response = await fetch('https://oauth2.googleapis.com/token', {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
136
|
+
body: new URLSearchParams({
|
|
137
|
+
client_id: googleClientId,
|
|
138
|
+
client_secret: googleClientSecret,
|
|
139
|
+
refresh_token: googleRefreshToken,
|
|
140
|
+
grant_type: 'refresh_token',
|
|
141
|
+
}),
|
|
142
|
+
});
|
|
143
|
+
if (!response.ok) {
|
|
144
|
+
console.error('[Calendar] Failed to refresh Google token');
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const data = await response.json();
|
|
148
|
+
googleAccessToken = data.access_token;
|
|
149
|
+
googleTokenExpiry = Date.now() + (data.expires_in * 1000) - 60000; // 1 min buffer
|
|
150
|
+
await saveGoogleTokens();
|
|
151
|
+
console.log('[Calendar] Google token refreshed');
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
console.error('[Calendar] Token refresh error:', error.message);
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
async function getValidGoogleToken() {
|
|
160
|
+
// Check if we have a valid token
|
|
161
|
+
if (googleAccessToken && Date.now() < googleTokenExpiry) {
|
|
162
|
+
return googleAccessToken;
|
|
163
|
+
}
|
|
164
|
+
// Try to refresh
|
|
165
|
+
if (await refreshGoogleToken()) {
|
|
166
|
+
return googleAccessToken;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function isGoogleCalendarConfigured() {
|
|
171
|
+
return !!(googleClientId && googleClientSecret && googleRefreshToken);
|
|
172
|
+
}
|
|
97
173
|
// ─────────────────────────────────────────────────────────────────
|
|
98
174
|
// Local Calendar Storage
|
|
99
175
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -168,6 +244,19 @@ function parseDate(input) {
|
|
|
168
244
|
return null;
|
|
169
245
|
}
|
|
170
246
|
function parseDuration(input) {
|
|
247
|
+
// If already a number (minutes), return it
|
|
248
|
+
if (typeof input === 'number') {
|
|
249
|
+
return input;
|
|
250
|
+
}
|
|
251
|
+
// If not a string, default to 60 minutes
|
|
252
|
+
if (typeof input !== 'string') {
|
|
253
|
+
return 60;
|
|
254
|
+
}
|
|
255
|
+
// Check if it's just a number string
|
|
256
|
+
const numOnly = parseInt(input);
|
|
257
|
+
if (!isNaN(numOnly) && input.trim() === String(numOnly)) {
|
|
258
|
+
return numOnly; // Assume minutes
|
|
259
|
+
}
|
|
171
260
|
// Duration in minutes
|
|
172
261
|
const match = input.match(/^(\d+)\s*(minute|min|hour|hr|h|m)s?$/i);
|
|
173
262
|
if (match) {
|
|
@@ -183,11 +272,31 @@ function parseDuration(input) {
|
|
|
183
272
|
// ─────────────────────────────────────────────────────────────────
|
|
184
273
|
// Google Calendar Integration
|
|
185
274
|
// ─────────────────────────────────────────────────────────────────
|
|
275
|
+
// Google Calendar API (supports both API key read-only and OAuth2 read/write)
|
|
186
276
|
async function googleCalendarFetch(endpoint, options = {}) {
|
|
277
|
+
const baseUrl = 'https://www.googleapis.com/calendar/v3';
|
|
278
|
+
// Try OAuth2 first for write operations
|
|
279
|
+
const token = await getValidGoogleToken();
|
|
280
|
+
if (token) {
|
|
281
|
+
const url = `${baseUrl}${endpoint}`;
|
|
282
|
+
const response = await fetch(url, {
|
|
283
|
+
...options,
|
|
284
|
+
headers: {
|
|
285
|
+
'Content-Type': 'application/json',
|
|
286
|
+
'Authorization': `Bearer ${token}`,
|
|
287
|
+
...options.headers,
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
if (!response.ok) {
|
|
291
|
+
const error = await response.json().catch(() => ({}));
|
|
292
|
+
throw new Error(error.error?.message || `Google Calendar API error: ${response.status}`);
|
|
293
|
+
}
|
|
294
|
+
return response.json();
|
|
295
|
+
}
|
|
296
|
+
// Fallback to API key (read-only)
|
|
187
297
|
if (!googleApiKey) {
|
|
188
|
-
throw new Error('Google Calendar
|
|
298
|
+
throw new Error('Google Calendar nie skonfigurowany. Ustaw GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET i uruchom "apexbot google-auth"');
|
|
189
299
|
}
|
|
190
|
-
const baseUrl = 'https://www.googleapis.com/calendar/v3';
|
|
191
300
|
const url = `${baseUrl}${endpoint}${endpoint.includes('?') ? '&' : '?'}key=${googleApiKey}`;
|
|
192
301
|
const response = await fetch(url, {
|
|
193
302
|
...options,
|
|
@@ -202,6 +311,34 @@ async function googleCalendarFetch(endpoint, options = {}) {
|
|
|
202
311
|
}
|
|
203
312
|
return response.json();
|
|
204
313
|
}
|
|
314
|
+
// Create event in Google Calendar
|
|
315
|
+
async function createGoogleCalendarEvent(event) {
|
|
316
|
+
if (!isGoogleCalendarConfigured()) {
|
|
317
|
+
throw new Error('Google Calendar OAuth nie skonfigurowany. Uruchom "apexbot google-auth" aby połączyć konto.');
|
|
318
|
+
}
|
|
319
|
+
const calendarId = googleCalendarId || 'primary';
|
|
320
|
+
const googleEvent = {
|
|
321
|
+
summary: event.title,
|
|
322
|
+
description: event.description,
|
|
323
|
+
location: event.location,
|
|
324
|
+
start: {
|
|
325
|
+
dateTime: event.start,
|
|
326
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
327
|
+
},
|
|
328
|
+
end: {
|
|
329
|
+
dateTime: event.end,
|
|
330
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
331
|
+
},
|
|
332
|
+
reminders: event.reminders ? {
|
|
333
|
+
useDefault: false,
|
|
334
|
+
overrides: event.reminders.map(mins => ({ method: 'popup', minutes: mins })),
|
|
335
|
+
} : { useDefault: true },
|
|
336
|
+
};
|
|
337
|
+
return googleCalendarFetch(`/calendars/${encodeURIComponent(calendarId)}/events`, {
|
|
338
|
+
method: 'POST',
|
|
339
|
+
body: JSON.stringify(googleEvent),
|
|
340
|
+
});
|
|
341
|
+
}
|
|
205
342
|
// ─────────────────────────────────────────────────────────────────
|
|
206
343
|
// Calendar Tools
|
|
207
344
|
// ─────────────────────────────────────────────────────────────────
|
|
@@ -267,6 +404,59 @@ const createEventTool = {
|
|
|
267
404
|
}
|
|
268
405
|
const durationMinutes = allDay ? 24 * 60 : parseDuration(duration);
|
|
269
406
|
const endDate = new Date(startDate.getTime() + durationMinutes * 60000);
|
|
407
|
+
// Format date for display
|
|
408
|
+
const dateStr = startDate.toLocaleDateString('pl-PL', {
|
|
409
|
+
weekday: 'long',
|
|
410
|
+
year: 'numeric',
|
|
411
|
+
month: 'long',
|
|
412
|
+
day: 'numeric',
|
|
413
|
+
hour: '2-digit',
|
|
414
|
+
minute: '2-digit'
|
|
415
|
+
});
|
|
416
|
+
// Try Google Calendar first if configured
|
|
417
|
+
if (isGoogleCalendarConfigured()) {
|
|
418
|
+
try {
|
|
419
|
+
const googleEvent = await createGoogleCalendarEvent({
|
|
420
|
+
title,
|
|
421
|
+
description,
|
|
422
|
+
location,
|
|
423
|
+
start: startDate.toISOString(),
|
|
424
|
+
end: endDate.toISOString(),
|
|
425
|
+
reminders: [15], // 15 min before
|
|
426
|
+
});
|
|
427
|
+
// Also save locally for notifications
|
|
428
|
+
const event = {
|
|
429
|
+
id: googleEvent.id || generateId(),
|
|
430
|
+
title,
|
|
431
|
+
description,
|
|
432
|
+
start: startDate.toISOString(),
|
|
433
|
+
end: endDate.toISOString(),
|
|
434
|
+
location,
|
|
435
|
+
allDay,
|
|
436
|
+
calendar: 'google',
|
|
437
|
+
};
|
|
438
|
+
events.push(event);
|
|
439
|
+
await saveEvents();
|
|
440
|
+
return {
|
|
441
|
+
success: true,
|
|
442
|
+
data: {
|
|
443
|
+
id: googleEvent.id,
|
|
444
|
+
title,
|
|
445
|
+
start: startDate.toISOString(),
|
|
446
|
+
end: endDate.toISOString(),
|
|
447
|
+
location,
|
|
448
|
+
calendar: 'Google Calendar',
|
|
449
|
+
htmlLink: googleEvent.htmlLink,
|
|
450
|
+
message: `✅ Wydarzenie "${title}" utworzone w Google Calendar na ${dateStr}. Link: ${googleEvent.htmlLink}`,
|
|
451
|
+
},
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
console.error('[Calendar] Google Calendar error:', error.message);
|
|
456
|
+
// Fall through to local storage
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Local storage fallback
|
|
270
460
|
const event = {
|
|
271
461
|
id: generateId(),
|
|
272
462
|
title,
|
|
@@ -279,15 +469,10 @@ const createEventTool = {
|
|
|
279
469
|
};
|
|
280
470
|
events.push(event);
|
|
281
471
|
await saveEvents();
|
|
282
|
-
//
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
month: 'long',
|
|
287
|
-
day: 'numeric',
|
|
288
|
-
hour: '2-digit',
|
|
289
|
-
minute: '2-digit'
|
|
290
|
-
});
|
|
472
|
+
// Check if Google Calendar is configured but failed
|
|
473
|
+
const googleNote = isGoogleCalendarConfigured()
|
|
474
|
+
? '⚠️ Nie udało się zapisać w Google Calendar, zapisano lokalnie.'
|
|
475
|
+
: 'ℹ️ Aby zapisywać wydarzenia w Google Calendar, uruchom "apexbot google-auth".';
|
|
291
476
|
return {
|
|
292
477
|
success: true,
|
|
293
478
|
data: {
|
|
@@ -297,8 +482,7 @@ const createEventTool = {
|
|
|
297
482
|
end: event.end,
|
|
298
483
|
location: event.location,
|
|
299
484
|
calendar: 'ApexBot (lokalny)',
|
|
300
|
-
message: `📅 Wydarzenie "${title}" zapisane w
|
|
301
|
-
note: 'Aby zsynchronizować z Google Calendar, skonfiguruj GOOGLE_CALENDAR_API_KEY.',
|
|
485
|
+
message: `📅 Wydarzenie "${title}" zapisane w kalendarzu ApexBot na ${dateStr}. Dostaniesz powiadomienie 15 minut przed. ${googleNote}`,
|
|
302
486
|
},
|
|
303
487
|
};
|
|
304
488
|
},
|
|
@@ -616,11 +800,25 @@ const calendarSkill = {
|
|
|
616
800
|
manifest,
|
|
617
801
|
tools: [createEventTool, listEventsTool, deleteEventTool, checkAvailabilityTool, findTimeTool],
|
|
618
802
|
async initialize(config) {
|
|
619
|
-
|
|
803
|
+
configDir = config.configDir || path.join(process.env.HOME || process.env.USERPROFILE || '', '.apexbot');
|
|
804
|
+
dataPath = config.dataPath || path.join(configDir, 'calendar.json');
|
|
805
|
+
// Google Calendar OAuth2 credentials
|
|
806
|
+
googleClientId = config.googleClientId || process.env.GOOGLE_CLIENT_ID || '';
|
|
807
|
+
googleClientSecret = config.googleClientSecret || process.env.GOOGLE_CLIENT_SECRET || '';
|
|
620
808
|
googleApiKey = config.googleApiKey || process.env.GOOGLE_CALENDAR_API_KEY || '';
|
|
621
|
-
googleCalendarId = config.defaultCalendar || 'primary';
|
|
809
|
+
googleCalendarId = config.defaultCalendar || process.env.GOOGLE_CALENDAR_ID || 'primary';
|
|
622
810
|
await loadEvents();
|
|
623
|
-
|
|
811
|
+
await loadGoogleTokens();
|
|
812
|
+
if (isGoogleCalendarConfigured()) {
|
|
813
|
+
console.log(`[Calendar] Initialized with Google Calendar integration`);
|
|
814
|
+
}
|
|
815
|
+
else if (googleApiKey) {
|
|
816
|
+
console.log(`[Calendar] Initialized with Google Calendar (read-only)`);
|
|
817
|
+
}
|
|
818
|
+
else {
|
|
819
|
+
console.log(`[Calendar] Initialized with local storage only`);
|
|
820
|
+
}
|
|
821
|
+
console.log(`[Calendar] ${events.length} events loaded`);
|
|
624
822
|
},
|
|
625
823
|
async shutdown() {
|
|
626
824
|
await saveEvents();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apexbot",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "ApexBot - Your free, private AI assistant. 100% free with Ollama (local AI). Multi-channel: Telegram, Discord, WebChat. Tools & Skills system with Spotify, Obsidian, and more!",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -84,4 +84,4 @@
|
|
|
84
84
|
"ts-node": "^10.9.1",
|
|
85
85
|
"typescript": "^5.2.2"
|
|
86
86
|
}
|
|
87
|
-
}
|
|
87
|
+
}
|