50c 2.5.2 → 2.6.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/packs/beacon.js +4 -4
- package/lib/packs/librarian.js +299 -1
- package/package.json +2 -2
package/lib/packs/beacon.js
CHANGED
|
@@ -47,19 +47,19 @@ async function learningStats() {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
async function roadmapAdd(idea, priority = 'medium', tags = []) {
|
|
50
|
-
return apiRequest('POST', '/tools/
|
|
50
|
+
return apiRequest('POST', '/tools/roadmap_add', { idea, priority, tags });
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
async function roadmapList(filter = null) {
|
|
54
|
-
return apiRequest('POST', '/tools/
|
|
54
|
+
return apiRequest('POST', '/tools/roadmap_list', { filter });
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
async function roadmapUpdate(id, updates) {
|
|
58
|
-
return apiRequest('POST', '/tools/
|
|
58
|
+
return apiRequest('POST', '/tools/roadmap_update', { id, ...updates });
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
async function roadmapDelete(id) {
|
|
62
|
-
return apiRequest('POST', '/tools/
|
|
62
|
+
return apiRequest('POST', '/tools/roadmap_delete', { id });
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// Tool definitions - STARTER tier ($29/mo)
|
package/lib/packs/librarian.js
CHANGED
|
@@ -96,6 +96,210 @@ async function folderPlan(fileList, instruction) {
|
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
// ═══════════════════════════════════════════════════════════════
|
|
100
|
+
// BROWSER UTILITIES (LOCAL)
|
|
101
|
+
// ═══════════════════════════════════════════════════════════════
|
|
102
|
+
|
|
103
|
+
const fs = require('fs');
|
|
104
|
+
const path = require('path');
|
|
105
|
+
const https = require('https');
|
|
106
|
+
const http = require('http');
|
|
107
|
+
|
|
108
|
+
function getChromeDataPath(profile = 'Default') {
|
|
109
|
+
const platform = process.platform;
|
|
110
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
111
|
+
|
|
112
|
+
if (platform === 'win32') {
|
|
113
|
+
return path.join(process.env.LOCALAPPDATA, 'Google', 'Chrome', 'User Data', profile);
|
|
114
|
+
} else if (platform === 'darwin') {
|
|
115
|
+
return path.join(home, 'Library', 'Application Support', 'Google', 'Chrome', profile);
|
|
116
|
+
} else {
|
|
117
|
+
return path.join(home, '.config', 'google-chrome', profile);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseBookmarksFile(profile = 'Default') {
|
|
122
|
+
const bookmarksPath = path.join(getChromeDataPath(profile), 'Bookmarks');
|
|
123
|
+
if (!fs.existsSync(bookmarksPath)) {
|
|
124
|
+
throw new Error(`Chrome bookmarks not found at ${bookmarksPath}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const data = JSON.parse(fs.readFileSync(bookmarksPath, 'utf8'));
|
|
128
|
+
const urls = [];
|
|
129
|
+
|
|
130
|
+
function extractUrls(node, folder = '') {
|
|
131
|
+
if (node.url) {
|
|
132
|
+
urls.push({ url: node.url, title: node.name, folder });
|
|
133
|
+
}
|
|
134
|
+
if (node.children) {
|
|
135
|
+
const newFolder = node.name || folder;
|
|
136
|
+
node.children.forEach(child => extractUrls(child, newFolder));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Object.values(data.roots).forEach(root => extractUrls(root));
|
|
141
|
+
return urls;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function checkUrl(url, timeout = 10000) {
|
|
145
|
+
return new Promise((resolve) => {
|
|
146
|
+
const protocol = url.startsWith('https') ? https : http;
|
|
147
|
+
const req = protocol.get(url, {
|
|
148
|
+
timeout,
|
|
149
|
+
headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0' }
|
|
150
|
+
}, (res) => {
|
|
151
|
+
resolve({ url, status: res.statusCode, ok: res.statusCode >= 200 && res.statusCode < 400 });
|
|
152
|
+
});
|
|
153
|
+
req.on('error', (e) => resolve({ url, status: 0, ok: false, error: e.message }));
|
|
154
|
+
req.on('timeout', () => {
|
|
155
|
+
req.destroy();
|
|
156
|
+
resolve({ url, status: 0, ok: false, error: 'timeout' });
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async function chromeBookmarksCheck(limit = 100, timeout = 10, profile = 'Default') {
|
|
162
|
+
const bookmarks = parseBookmarksFile(profile);
|
|
163
|
+
const toCheck = bookmarks.filter(b =>
|
|
164
|
+
b.url.startsWith('http') && !b.url.includes('localhost') && !b.url.includes('127.0.0.1')
|
|
165
|
+
).slice(0, limit);
|
|
166
|
+
|
|
167
|
+
const results = { ok: [], dead: [], errors: [], skipped: bookmarks.length - toCheck.length };
|
|
168
|
+
|
|
169
|
+
// Check in batches of 10
|
|
170
|
+
for (let i = 0; i < toCheck.length; i += 10) {
|
|
171
|
+
const batch = toCheck.slice(i, i + 10);
|
|
172
|
+
const checks = await Promise.all(batch.map(b => checkUrl(b.url, timeout * 1000)));
|
|
173
|
+
|
|
174
|
+
checks.forEach((result, idx) => {
|
|
175
|
+
const bookmark = batch[idx];
|
|
176
|
+
if (result.ok) {
|
|
177
|
+
results.ok.push({ ...bookmark, status: result.status });
|
|
178
|
+
} else if (result.status === 404) {
|
|
179
|
+
results.dead.push({ ...bookmark, status: 404 });
|
|
180
|
+
} else {
|
|
181
|
+
results.errors.push({ ...bookmark, status: result.status, error: result.error });
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
total: bookmarks.length,
|
|
188
|
+
checked: toCheck.length,
|
|
189
|
+
ok: results.ok.length,
|
|
190
|
+
dead: results.dead,
|
|
191
|
+
errors: results.errors.filter(e => e.status !== 403), // 403 = login required, not dead
|
|
192
|
+
login_required: results.errors.filter(e => e.status === 403),
|
|
193
|
+
skipped: results.skipped
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getExtensionsInfo(profile = 'Default') {
|
|
198
|
+
const extPath = path.join(getChromeDataPath(profile), 'Extensions');
|
|
199
|
+
if (!fs.existsSync(extPath)) {
|
|
200
|
+
return { error: 'Extensions folder not found' };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const extensions = [];
|
|
204
|
+
const extDirs = fs.readdirSync(extPath);
|
|
205
|
+
|
|
206
|
+
extDirs.forEach(extId => {
|
|
207
|
+
const extDir = path.join(extPath, extId);
|
|
208
|
+
if (fs.statSync(extDir).isDirectory()) {
|
|
209
|
+
const versions = fs.readdirSync(extDir).filter(v => !v.startsWith('.'));
|
|
210
|
+
if (versions.length > 0) {
|
|
211
|
+
const latestVersion = versions.sort().pop();
|
|
212
|
+
const manifestPath = path.join(extDir, latestVersion, 'manifest.json');
|
|
213
|
+
|
|
214
|
+
if (fs.existsSync(manifestPath)) {
|
|
215
|
+
try {
|
|
216
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
|
217
|
+
extensions.push({
|
|
218
|
+
id: extId,
|
|
219
|
+
name: manifest.name || 'Unknown',
|
|
220
|
+
version: manifest.version,
|
|
221
|
+
description: manifest.description?.substring(0, 100),
|
|
222
|
+
permissions: manifest.permissions || [],
|
|
223
|
+
hostPermissions: manifest.host_permissions || manifest.optional_host_permissions || []
|
|
224
|
+
});
|
|
225
|
+
} catch (e) {
|
|
226
|
+
extensions.push({ id: extId, name: 'Unknown', error: 'Invalid manifest' });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// Flag risky extensions
|
|
234
|
+
const risky = extensions.filter(ext => {
|
|
235
|
+
const perms = [...(ext.permissions || []), ...(ext.hostPermissions || [])];
|
|
236
|
+
return perms.some(p =>
|
|
237
|
+
p.includes('<all_urls>') ||
|
|
238
|
+
p.includes('*://*/*') ||
|
|
239
|
+
p === 'webRequest' ||
|
|
240
|
+
p === 'webRequestBlocking'
|
|
241
|
+
);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
total: extensions.length,
|
|
246
|
+
extensions,
|
|
247
|
+
risky,
|
|
248
|
+
recommendations: risky.length > 0 ?
|
|
249
|
+
`Found ${risky.length} extensions with broad permissions. Review: ${risky.map(e => e.name).join(', ')}` :
|
|
250
|
+
'No obviously risky extensions found'
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function passwordsCheck(exportFile, checkBreaches = false) {
|
|
255
|
+
if (!fs.existsSync(exportFile)) {
|
|
256
|
+
return { error: `File not found: ${exportFile}` };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const csv = fs.readFileSync(exportFile, 'utf8');
|
|
260
|
+
const lines = csv.split('\n').slice(1); // Skip header
|
|
261
|
+
|
|
262
|
+
const passwords = lines.filter(l => l.trim()).map(line => {
|
|
263
|
+
const parts = line.split(',');
|
|
264
|
+
return {
|
|
265
|
+
name: parts[0],
|
|
266
|
+
url: parts[1],
|
|
267
|
+
username: parts[2],
|
|
268
|
+
password: parts[3]
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// Check for weak passwords
|
|
273
|
+
const weak = passwords.filter(p => {
|
|
274
|
+
const pwd = p.password || '';
|
|
275
|
+
return pwd.length < 8 ||
|
|
276
|
+
pwd === pwd.toLowerCase() ||
|
|
277
|
+
/^[0-9]+$/.test(pwd) ||
|
|
278
|
+
['password', '123456', 'qwerty'].includes(pwd.toLowerCase());
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Check for reused passwords
|
|
282
|
+
const pwdCounts = {};
|
|
283
|
+
passwords.forEach(p => {
|
|
284
|
+
const pwd = p.password;
|
|
285
|
+
pwdCounts[pwd] = (pwdCounts[pwd] || 0) + 1;
|
|
286
|
+
});
|
|
287
|
+
const reused = Object.entries(pwdCounts).filter(([_, count]) => count > 1);
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
total: passwords.length,
|
|
291
|
+
weak: weak.map(p => ({ name: p.name, url: p.url, reason: 'Weak password pattern' })),
|
|
292
|
+
reused: reused.map(([pwd, count]) => ({
|
|
293
|
+
count,
|
|
294
|
+
sites: passwords.filter(p => p.password === pwd).map(p => p.name).slice(0, 3)
|
|
295
|
+
})),
|
|
296
|
+
recommendations: [
|
|
297
|
+
weak.length > 0 ? `${weak.length} weak passwords need updating` : null,
|
|
298
|
+
reused.length > 0 ? `${reused.length} passwords are reused across sites` : null
|
|
299
|
+
].filter(Boolean)
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
99
303
|
// ═══════════════════════════════════════════════════════════════
|
|
100
304
|
// TOOL DEFINITIONS
|
|
101
305
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -439,6 +643,76 @@ const LIBRARIAN_TOOLS = [
|
|
|
439
643
|
},
|
|
440
644
|
cost: 0.08,
|
|
441
645
|
tier: 'pro'
|
|
646
|
+
},
|
|
647
|
+
|
|
648
|
+
// ═══════════════════════════════════════════════════════════════
|
|
649
|
+
// BROWSER MANAGEMENT
|
|
650
|
+
// ═══════════════════════════════════════════════════════════════
|
|
651
|
+
{
|
|
652
|
+
name: 'chrome_bookmarks_check',
|
|
653
|
+
description: 'Check Chrome bookmarks for dead links (404, timeouts). Local scan. FREE.',
|
|
654
|
+
inputSchema: {
|
|
655
|
+
type: 'object',
|
|
656
|
+
properties: {
|
|
657
|
+
limit: { type: 'number', default: 100, description: 'Max URLs to check' },
|
|
658
|
+
timeout: { type: 'number', default: 10, description: 'Timeout per URL (seconds)' },
|
|
659
|
+
profile: { type: 'string', default: 'Default', description: 'Chrome profile name' }
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
cost: 0,
|
|
663
|
+
tier: 'pro'
|
|
664
|
+
},
|
|
665
|
+
{
|
|
666
|
+
name: 'chrome_bookmarks_report',
|
|
667
|
+
description: 'Full bookmark health report with categorization. $0.05',
|
|
668
|
+
inputSchema: {
|
|
669
|
+
type: 'object',
|
|
670
|
+
properties: {
|
|
671
|
+
include_recommendations: { type: 'boolean', default: true },
|
|
672
|
+
profile: { type: 'string', default: 'Default' }
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
cost: 0.05,
|
|
676
|
+
tier: 'pro'
|
|
677
|
+
},
|
|
678
|
+
{
|
|
679
|
+
name: 'chrome_extensions_audit',
|
|
680
|
+
description: 'Audit installed Chrome extensions for security/bloat. FREE.',
|
|
681
|
+
inputSchema: {
|
|
682
|
+
type: 'object',
|
|
683
|
+
properties: {
|
|
684
|
+
profile: { type: 'string', default: 'Default' }
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
cost: 0,
|
|
688
|
+
tier: 'pro'
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
name: 'browser_passwords_check',
|
|
692
|
+
description: 'Check for weak/reused passwords (via Puppeteer). $0.10',
|
|
693
|
+
inputSchema: {
|
|
694
|
+
type: 'object',
|
|
695
|
+
properties: {
|
|
696
|
+
export_file: { type: 'string', description: 'Path to exported passwords CSV' },
|
|
697
|
+
check_breaches: { type: 'boolean', default: false, description: 'Check haveibeenpwned (requires API key)' }
|
|
698
|
+
},
|
|
699
|
+
required: ['export_file']
|
|
700
|
+
},
|
|
701
|
+
cost: 0.10,
|
|
702
|
+
tier: 'pro'
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
name: 'browser_history_analyze',
|
|
706
|
+
description: 'Analyze browsing patterns for productivity insights. $0.08',
|
|
707
|
+
inputSchema: {
|
|
708
|
+
type: 'object',
|
|
709
|
+
properties: {
|
|
710
|
+
days: { type: 'number', default: 30, description: 'Days of history to analyze' },
|
|
711
|
+
profile: { type: 'string', default: 'Default' }
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
cost: 0.08,
|
|
715
|
+
tier: 'pro'
|
|
442
716
|
}
|
|
443
717
|
];
|
|
444
718
|
|
|
@@ -471,6 +745,26 @@ async function handleTool(name, args) {
|
|
|
471
745
|
return await folderDedup(args.files, args);
|
|
472
746
|
case 'folder_plan':
|
|
473
747
|
return await folderPlan(args.files, args.instruction);
|
|
748
|
+
|
|
749
|
+
// Browser tools (local execution)
|
|
750
|
+
case 'chrome_bookmarks_check':
|
|
751
|
+
return await chromeBookmarksCheck(args.limit || 100, args.timeout || 10, args.profile || 'Default');
|
|
752
|
+
case 'chrome_bookmarks_report':
|
|
753
|
+
const checkResult = await chromeBookmarksCheck(500, 8, args.profile || 'Default');
|
|
754
|
+
return apiRequest('POST', '/tools/librarian/bookmarks_report', {
|
|
755
|
+
check_result: checkResult,
|
|
756
|
+
include_recommendations: args.include_recommendations !== false
|
|
757
|
+
});
|
|
758
|
+
case 'chrome_extensions_audit':
|
|
759
|
+
return getExtensionsInfo(args.profile || 'Default');
|
|
760
|
+
case 'browser_passwords_check':
|
|
761
|
+
return await passwordsCheck(args.export_file, args.check_breaches);
|
|
762
|
+
case 'browser_history_analyze':
|
|
763
|
+
return apiRequest('POST', '/tools/librarian/history_analyze', {
|
|
764
|
+
days: args.days || 30,
|
|
765
|
+
profile: args.profile || 'Default'
|
|
766
|
+
});
|
|
767
|
+
|
|
474
768
|
default:
|
|
475
769
|
return { error: `Unknown librarian tool: ${name}` };
|
|
476
770
|
}
|
|
@@ -492,5 +786,9 @@ module.exports = {
|
|
|
492
786
|
folderAnalyze,
|
|
493
787
|
folderOrganize,
|
|
494
788
|
folderDedup,
|
|
495
|
-
folderPlan
|
|
789
|
+
folderPlan,
|
|
790
|
+
// Browser tools
|
|
791
|
+
chromeBookmarksCheck,
|
|
792
|
+
getExtensionsInfo,
|
|
793
|
+
passwordsCheck
|
|
496
794
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "50c",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "AI toolkit with
|
|
3
|
+
"version": "2.6.0",
|
|
4
|
+
"description": "AI toolkit with browser tools. One install, 100+ tools.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"50c": "./bin/50c.js"
|