50c 2.2.0 → 2.5.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.
@@ -46,6 +46,22 @@ async function learningStats() {
46
46
  return apiRequest('GET', '/tools/learning_stats');
47
47
  }
48
48
 
49
+ async function roadmapAdd(idea, priority = 'medium', tags = []) {
50
+ return apiRequest('POST', '/tools/roadmap', { action: 'add', idea, priority, tags });
51
+ }
52
+
53
+ async function roadmapList(filter = null) {
54
+ return apiRequest('POST', '/tools/roadmap', { action: 'list', filter });
55
+ }
56
+
57
+ async function roadmapUpdate(id, updates) {
58
+ return apiRequest('POST', '/tools/roadmap', { action: 'update', id, ...updates });
59
+ }
60
+
61
+ async function roadmapDelete(id) {
62
+ return apiRequest('POST', '/tools/roadmap', { action: 'delete', id });
63
+ }
64
+
49
65
  // Tool definitions - STARTER tier ($29/mo)
50
66
  const BEACON_TOOLS = [
51
67
  {
@@ -173,6 +189,62 @@ const BEACON_TOOLS = [
173
189
  inputSchema: { type: 'object', properties: {} },
174
190
  cost: 0,
175
191
  tier: 'free'
192
+ },
193
+ {
194
+ name: 'roadmap_add',
195
+ description: 'Add idea to dev roadmap. FREE.',
196
+ inputSchema: {
197
+ type: 'object',
198
+ properties: {
199
+ idea: { type: 'string', description: 'The idea or feature to add' },
200
+ priority: { type: 'string', enum: ['low', 'medium', 'high', 'critical'], default: 'medium' },
201
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags for categorization' }
202
+ },
203
+ required: ['idea']
204
+ },
205
+ cost: 0,
206
+ tier: 'free'
207
+ },
208
+ {
209
+ name: 'roadmap_list',
210
+ description: 'List roadmap ideas. FREE.',
211
+ inputSchema: {
212
+ type: 'object',
213
+ properties: {
214
+ filter: { type: 'string', description: 'Filter by priority or tag' }
215
+ }
216
+ },
217
+ cost: 0,
218
+ tier: 'free'
219
+ },
220
+ {
221
+ name: 'roadmap_update',
222
+ description: 'Update roadmap item status. FREE.',
223
+ inputSchema: {
224
+ type: 'object',
225
+ properties: {
226
+ id: { type: 'string', description: 'Roadmap item ID' },
227
+ status: { type: 'string', enum: ['idea', 'planned', 'in_progress', 'done', 'archived'] },
228
+ priority: { type: 'string', enum: ['low', 'medium', 'high', 'critical'] },
229
+ notes: { type: 'string', description: 'Additional notes' }
230
+ },
231
+ required: ['id']
232
+ },
233
+ cost: 0,
234
+ tier: 'free'
235
+ },
236
+ {
237
+ name: 'roadmap_delete',
238
+ description: 'Delete roadmap item. FREE.',
239
+ inputSchema: {
240
+ type: 'object',
241
+ properties: {
242
+ id: { type: 'string', description: 'Roadmap item ID to delete' }
243
+ },
244
+ required: ['id']
245
+ },
246
+ cost: 0,
247
+ tier: 'free'
176
248
  }
177
249
  ];
178
250
 
@@ -200,6 +272,14 @@ async function handleTool(name, args) {
200
272
  return await ideConversation(args.session_id, args.message, args.mode);
201
273
  case 'learning_stats':
202
274
  return await learningStats();
275
+ case 'roadmap_add':
276
+ return await roadmapAdd(args.idea, args.priority, args.tags);
277
+ case 'roadmap_list':
278
+ return await roadmapList(args.filter);
279
+ case 'roadmap_update':
280
+ return await roadmapUpdate(args.id, args);
281
+ case 'roadmap_delete':
282
+ return await roadmapDelete(args.id);
203
283
  default:
204
284
  return { error: `Unknown beacon tool: ${name}` };
205
285
  }
@@ -220,5 +300,9 @@ module.exports = {
220
300
  priceIt,
221
301
  compute,
222
302
  ideConversation,
223
- learningStats
303
+ learningStats,
304
+ roadmapAdd,
305
+ roadmapList,
306
+ roadmapUpdate,
307
+ roadmapDelete
224
308
  };
@@ -0,0 +1,496 @@
1
+ /**
2
+ * 50c Librarian Pack - Digital Organization Tools (PRO tier)
3
+ * Bookmark cleanup, CSV organization, folder structure, file dedup
4
+ */
5
+
6
+ const { apiRequest } = require('../config');
7
+
8
+ // ═══════════════════════════════════════════════════════════════
9
+ // BOOKMARK MANAGEMENT
10
+ // ═══════════════════════════════════════════════════════════════
11
+
12
+ async function bookmarksPrune(bookmarks, options = {}) {
13
+ return apiRequest('POST', '/tools/librarian/bookmarks_prune', {
14
+ bookmarks,
15
+ check_dead: options.checkDead !== false,
16
+ remove_duplicates: options.removeDuplicates !== false
17
+ });
18
+ }
19
+
20
+ async function bookmarksOrganize(bookmarks, instruction) {
21
+ return apiRequest('POST', '/tools/librarian/bookmarks_organize', {
22
+ bookmarks,
23
+ instruction
24
+ });
25
+ }
26
+
27
+ async function bookmarksParse(filePath) {
28
+ return apiRequest('POST', '/tools/librarian/bookmarks_parse', { file_path: filePath });
29
+ }
30
+
31
+ // ═══════════════════════════════════════════════════════════════
32
+ // CSV MANAGEMENT
33
+ // ═══════════════════════════════════════════════════════════════
34
+
35
+ async function csvClean(csvContent, options = {}) {
36
+ return apiRequest('POST', '/tools/librarian/csv_clean', {
37
+ csv: csvContent,
38
+ remove_empty: options.removeEmpty !== false,
39
+ trim_whitespace: options.trimWhitespace !== false,
40
+ remove_duplicates: options.removeDuplicates || false,
41
+ normalize_headers: options.normalizeHeaders || false
42
+ });
43
+ }
44
+
45
+ async function csvOrganize(csvContent, instruction) {
46
+ return apiRequest('POST', '/tools/librarian/csv_organize', {
47
+ csv: csvContent,
48
+ instruction
49
+ });
50
+ }
51
+
52
+ async function csvMerge(csvFiles, options = {}) {
53
+ return apiRequest('POST', '/tools/librarian/csv_merge', {
54
+ files: csvFiles,
55
+ deduplicate: options.deduplicate || false,
56
+ key_column: options.keyColumn
57
+ });
58
+ }
59
+
60
+ async function csvSplit(csvContent, options) {
61
+ return apiRequest('POST', '/tools/librarian/csv_split', {
62
+ csv: csvContent,
63
+ split_by: options.splitBy,
64
+ max_rows: options.maxRows
65
+ });
66
+ }
67
+
68
+ // ═══════════════════════════════════════════════════════════════
69
+ // FOLDER MANAGEMENT
70
+ // ═══════════════════════════════════════════════════════════════
71
+
72
+ async function folderAnalyze(fileList) {
73
+ return apiRequest('POST', '/tools/librarian/folder_analyze', { files: fileList });
74
+ }
75
+
76
+ async function folderOrganize(fileList, instruction) {
77
+ return apiRequest('POST', '/tools/librarian/folder_organize', {
78
+ files: fileList,
79
+ instruction
80
+ });
81
+ }
82
+
83
+ async function folderDedup(fileList, options = {}) {
84
+ return apiRequest('POST', '/tools/librarian/folder_dedup', {
85
+ files: fileList,
86
+ by_name: options.byName || false,
87
+ by_hash: options.byHash || false,
88
+ by_size: options.bySize || true
89
+ });
90
+ }
91
+
92
+ async function folderPlan(fileList, instruction) {
93
+ return apiRequest('POST', '/tools/librarian/folder_plan', {
94
+ files: fileList,
95
+ instruction
96
+ });
97
+ }
98
+
99
+ // ═══════════════════════════════════════════════════════════════
100
+ // TOOL DEFINITIONS
101
+ // ═══════════════════════════════════════════════════════════════
102
+
103
+ const LIBRARIAN_TOOLS = [
104
+ {
105
+ name: 'bookmarks_prune',
106
+ description: 'Check bookmarks for dead links (404s) and duplicates. $0.05',
107
+ inputSchema: {
108
+ type: 'object',
109
+ properties: {
110
+ bookmarks: { type: 'array', items: { type: 'object' }, description: 'Array of {url, title, folder?}' },
111
+ check_dead: { type: 'boolean', default: true, description: 'Check for 404s' },
112
+ remove_duplicates: { type: 'boolean', default: true }
113
+ },
114
+ required: ['bookmarks']
115
+ },
116
+ cost: 0.05,
117
+ tier: 'pro'
118
+ },
119
+ {
120
+ name: 'bookmarks_organize',
121
+ description: 'Reorganize bookmarks into folders via LLM instruction. $0.10',
122
+ inputSchema: {
123
+ type: 'object',
124
+ properties: {
125
+ bookmarks: { type: 'array', items: { type: 'object' }, description: 'Array of bookmarks' },
126
+ instruction: { type: 'string', description: 'How to organize (e.g., "by topic", "by project")' }
127
+ },
128
+ required: ['bookmarks', 'instruction']
129
+ },
130
+ cost: 0.10,
131
+ tier: 'pro'
132
+ },
133
+ {
134
+ name: 'bookmarks_parse',
135
+ description: 'Parse browser bookmark export file. $0.02',
136
+ inputSchema: {
137
+ type: 'object',
138
+ properties: {
139
+ file_path: { type: 'string', description: 'Path to bookmarks HTML file' }
140
+ },
141
+ required: ['file_path']
142
+ },
143
+ cost: 0.02,
144
+ tier: 'pro'
145
+ },
146
+ {
147
+ name: 'csv_clean',
148
+ description: 'Clean CSV: remove empty rows, trim whitespace, normalize. $0.03',
149
+ inputSchema: {
150
+ type: 'object',
151
+ properties: {
152
+ csv: { type: 'string', description: 'CSV content' },
153
+ remove_empty: { type: 'boolean', default: true },
154
+ trim_whitespace: { type: 'boolean', default: true },
155
+ remove_duplicates: { type: 'boolean', default: false },
156
+ normalize_headers: { type: 'boolean', default: false }
157
+ },
158
+ required: ['csv']
159
+ },
160
+ cost: 0.03,
161
+ tier: 'pro'
162
+ },
163
+ {
164
+ name: 'csv_organize',
165
+ description: 'Reorganize CSV columns/rows via LLM instruction. $0.08',
166
+ inputSchema: {
167
+ type: 'object',
168
+ properties: {
169
+ csv: { type: 'string', description: 'CSV content' },
170
+ instruction: { type: 'string', description: 'How to reorganize' }
171
+ },
172
+ required: ['csv', 'instruction']
173
+ },
174
+ cost: 0.08,
175
+ tier: 'pro'
176
+ },
177
+ {
178
+ name: 'csv_merge',
179
+ description: 'Merge multiple CSVs intelligently. $0.05',
180
+ inputSchema: {
181
+ type: 'object',
182
+ properties: {
183
+ files: { type: 'array', items: { type: 'string' }, description: 'Array of CSV contents' },
184
+ deduplicate: { type: 'boolean', default: false },
185
+ key_column: { type: 'string', description: 'Column to use as key for dedup' }
186
+ },
187
+ required: ['files']
188
+ },
189
+ cost: 0.05,
190
+ tier: 'pro'
191
+ },
192
+ {
193
+ name: 'csv_split',
194
+ description: 'Split CSV by column value or row count. $0.03',
195
+ inputSchema: {
196
+ type: 'object',
197
+ properties: {
198
+ csv: { type: 'string', description: 'CSV content' },
199
+ split_by: { type: 'string', description: 'Column name to split by' },
200
+ max_rows: { type: 'number', description: 'Max rows per file' }
201
+ },
202
+ required: ['csv']
203
+ },
204
+ cost: 0.03,
205
+ tier: 'pro'
206
+ },
207
+ {
208
+ name: 'folder_analyze',
209
+ description: 'Analyze folder structure: size, types, duplicates. $0.03',
210
+ inputSchema: {
211
+ type: 'object',
212
+ properties: {
213
+ files: { type: 'array', items: { type: 'object' }, description: 'Array of {path, size, modified}' }
214
+ },
215
+ required: ['files']
216
+ },
217
+ cost: 0.03,
218
+ tier: 'pro'
219
+ },
220
+ {
221
+ name: 'folder_organize',
222
+ description: 'Generate folder reorganization plan via LLM. $0.10',
223
+ inputSchema: {
224
+ type: 'object',
225
+ properties: {
226
+ files: { type: 'array', items: { type: 'object' }, description: 'File list' },
227
+ instruction: { type: 'string', description: 'Organization goal' }
228
+ },
229
+ required: ['files', 'instruction']
230
+ },
231
+ cost: 0.10,
232
+ tier: 'pro'
233
+ },
234
+ {
235
+ name: 'folder_dedup',
236
+ description: 'Find duplicate files by name/hash/size. $0.05',
237
+ inputSchema: {
238
+ type: 'object',
239
+ properties: {
240
+ files: { type: 'array', items: { type: 'object' }, description: 'Array of {path, size, hash?}' },
241
+ by_name: { type: 'boolean', default: false },
242
+ by_hash: { type: 'boolean', default: false },
243
+ by_size: { type: 'boolean', default: true }
244
+ },
245
+ required: ['files']
246
+ },
247
+ cost: 0.05,
248
+ tier: 'pro'
249
+ },
250
+ {
251
+ name: 'folder_plan',
252
+ description: 'Create actionable file organization plan. $0.08',
253
+ inputSchema: {
254
+ type: 'object',
255
+ properties: {
256
+ files: { type: 'array', items: { type: 'object' }, description: 'File list' },
257
+ instruction: { type: 'string', description: 'What to achieve' }
258
+ },
259
+ required: ['files', 'instruction']
260
+ },
261
+ cost: 0.08,
262
+ tier: 'pro'
263
+ },
264
+
265
+ // ═══════════════════════════════════════════════════════════════
266
+ // DOMAIN MANAGEMENT
267
+ // ═══════════════════════════════════════════════════════════════
268
+ {
269
+ name: 'domains_add',
270
+ description: 'Add domain to your portfolio with registrar data. FREE.',
271
+ inputSchema: {
272
+ type: 'object',
273
+ properties: {
274
+ domain: { type: 'string', description: 'Domain name' },
275
+ registrar: { type: 'string', description: 'Registrar name' },
276
+ expiry: { type: 'string', description: 'Expiry date (YYYY-MM-DD)' },
277
+ auto_renew: { type: 'boolean', default: true },
278
+ purchase_price: { type: 'number' },
279
+ category: { type: 'string', description: 'Category/project' },
280
+ notes: { type: 'string' }
281
+ },
282
+ required: ['domain']
283
+ },
284
+ cost: 0,
285
+ tier: 'pro'
286
+ },
287
+ {
288
+ name: 'domains_list',
289
+ description: 'List your domain portfolio. FREE.',
290
+ inputSchema: {
291
+ type: 'object',
292
+ properties: {
293
+ expiring_within: { type: 'number', description: 'Days until expiry filter' },
294
+ category: { type: 'string' },
295
+ registrar: { type: 'string' }
296
+ }
297
+ },
298
+ cost: 0,
299
+ tier: 'pro'
300
+ },
301
+ {
302
+ name: 'domains_expiring',
303
+ description: 'Get domains expiring soon. FREE.',
304
+ inputSchema: {
305
+ type: 'object',
306
+ properties: {
307
+ days: { type: 'number', default: 30, description: 'Within X days' }
308
+ }
309
+ },
310
+ cost: 0,
311
+ tier: 'pro'
312
+ },
313
+ {
314
+ name: 'domains_suggest',
315
+ description: 'Suggest domains matching your portfolio style. $0.05',
316
+ inputSchema: {
317
+ type: 'object',
318
+ properties: {
319
+ concept: { type: 'string', description: 'What the domain is for' },
320
+ style: { type: 'string', enum: ['short', 'brandable', 'keyword', 'match_portfolio'], default: 'match_portfolio' }
321
+ },
322
+ required: ['concept']
323
+ },
324
+ cost: 0.05,
325
+ tier: 'pro'
326
+ },
327
+ {
328
+ name: 'domains_value',
329
+ description: 'Estimate domain portfolio value. $0.05',
330
+ inputSchema: {
331
+ type: 'object',
332
+ properties: {
333
+ domains: { type: 'array', items: { type: 'string' }, description: 'Domains to value (or omit for all)' }
334
+ }
335
+ },
336
+ cost: 0.05,
337
+ tier: 'pro'
338
+ },
339
+
340
+ // ═══════════════════════════════════════════════════════════════
341
+ // EMAIL/WRITING STYLE
342
+ // ═══════════════════════════════════════════════════════════════
343
+ {
344
+ name: 'writing_sample_add',
345
+ description: 'Store a writing sample to learn your style. FREE.',
346
+ inputSchema: {
347
+ type: 'object',
348
+ properties: {
349
+ content: { type: 'string', description: 'Your writing sample' },
350
+ type: { type: 'string', enum: ['email', 'blog', 'social', 'formal', 'casual', 'sales'], default: 'email' },
351
+ audience: { type: 'string', description: 'Who this was written for' },
352
+ context: { type: 'string', description: 'What was the situation' }
353
+ },
354
+ required: ['content']
355
+ },
356
+ cost: 0,
357
+ tier: 'pro'
358
+ },
359
+ {
360
+ name: 'writing_style_analyze',
361
+ description: 'Analyze your writing style from samples. $0.08',
362
+ inputSchema: {
363
+ type: 'object',
364
+ properties: {
365
+ type: { type: 'string', description: 'Filter by type' }
366
+ }
367
+ },
368
+ cost: 0.08,
369
+ tier: 'pro'
370
+ },
371
+ {
372
+ name: 'writing_draft',
373
+ description: 'Draft content in YOUR style. $0.10',
374
+ inputSchema: {
375
+ type: 'object',
376
+ properties: {
377
+ purpose: { type: 'string', description: 'What to write' },
378
+ audience: { type: 'string', description: 'Who is this for' },
379
+ type: { type: 'string', enum: ['email', 'blog', 'social', 'formal', 'casual', 'sales'], default: 'email' },
380
+ tone: { type: 'string', description: 'Override detected tone' }
381
+ },
382
+ required: ['purpose']
383
+ },
384
+ cost: 0.10,
385
+ tier: 'pro'
386
+ },
387
+ {
388
+ name: 'writing_improve',
389
+ description: 'Improve text while keeping your voice. $0.08',
390
+ inputSchema: {
391
+ type: 'object',
392
+ properties: {
393
+ content: { type: 'string', description: 'Text to improve' },
394
+ goal: { type: 'string', enum: ['clarity', 'persuasion', 'brevity', 'warmth', 'authority'], default: 'clarity' }
395
+ },
396
+ required: ['content']
397
+ },
398
+ cost: 0.08,
399
+ tier: 'pro'
400
+ },
401
+ {
402
+ name: 'writing_roast',
403
+ description: 'Brutally honest feedback on your writing. $0.05',
404
+ inputSchema: {
405
+ type: 'object',
406
+ properties: {
407
+ content: { type: 'string', description: 'Text to roast' },
408
+ context: { type: 'string', description: 'What this is for' }
409
+ },
410
+ required: ['content']
411
+ },
412
+ cost: 0.05,
413
+ tier: 'pro'
414
+ },
415
+ {
416
+ name: 'email_templates_list',
417
+ description: 'List your email templates by situation. FREE.',
418
+ inputSchema: {
419
+ type: 'object',
420
+ properties: {
421
+ situation: { type: 'string', description: 'Filter by situation type' }
422
+ }
423
+ },
424
+ cost: 0,
425
+ tier: 'pro'
426
+ },
427
+ {
428
+ name: 'email_generate',
429
+ description: 'Generate email from your templates + style. $0.08',
430
+ inputSchema: {
431
+ type: 'object',
432
+ properties: {
433
+ to: { type: 'string', description: 'Recipient description' },
434
+ purpose: { type: 'string', description: 'What the email should accomplish' },
435
+ context: { type: 'string', description: 'Background info' },
436
+ tone: { type: 'string', enum: ['professional', 'friendly', 'urgent', 'apologetic', 'persuasive'] }
437
+ },
438
+ required: ['purpose']
439
+ },
440
+ cost: 0.08,
441
+ tier: 'pro'
442
+ }
443
+ ];
444
+
445
+ // ═══════════════════════════════════════════════════════════════
446
+ // TOOL HANDLER
447
+ // ═══════════════════════════════════════════════════════════════
448
+
449
+ async function handleTool(name, args) {
450
+ try {
451
+ switch (name) {
452
+ case 'bookmarks_prune':
453
+ return await bookmarksPrune(args.bookmarks, args);
454
+ case 'bookmarks_organize':
455
+ return await bookmarksOrganize(args.bookmarks, args.instruction);
456
+ case 'bookmarks_parse':
457
+ return await bookmarksParse(args.file_path);
458
+ case 'csv_clean':
459
+ return await csvClean(args.csv, args);
460
+ case 'csv_organize':
461
+ return await csvOrganize(args.csv, args.instruction);
462
+ case 'csv_merge':
463
+ return await csvMerge(args.files, args);
464
+ case 'csv_split':
465
+ return await csvSplit(args.csv, args);
466
+ case 'folder_analyze':
467
+ return await folderAnalyze(args.files);
468
+ case 'folder_organize':
469
+ return await folderOrganize(args.files, args.instruction);
470
+ case 'folder_dedup':
471
+ return await folderDedup(args.files, args);
472
+ case 'folder_plan':
473
+ return await folderPlan(args.files, args.instruction);
474
+ default:
475
+ return { error: `Unknown librarian tool: ${name}` };
476
+ }
477
+ } catch (e) {
478
+ return { error: e.message };
479
+ }
480
+ }
481
+
482
+ module.exports = {
483
+ LIBRARIAN_TOOLS,
484
+ handleTool,
485
+ bookmarksPrune,
486
+ bookmarksOrganize,
487
+ bookmarksParse,
488
+ csvClean,
489
+ csvOrganize,
490
+ csvMerge,
491
+ csvSplit,
492
+ folderAnalyze,
493
+ folderOrganize,
494
+ folderDedup,
495
+ folderPlan
496
+ };
package/lib/packs/whm.js CHANGED
@@ -15,9 +15,10 @@ async function getWHMCreds() {
15
15
  try {
16
16
  if (await vault.isUnlocked()) {
17
17
  const creds = await vault.get('whm/default');
18
+ const host = await vault.get('whm/host');
18
19
  if (creds) {
19
20
  const [user, token] = creds.split(':');
20
- return { host: process.env.WHM_HOST, user, token };
21
+ return { host: host || process.env.WHM_HOST, user, token };
21
22
  }
22
23
  }
23
24
  } catch {}
package/lib/packs.js CHANGED
@@ -26,11 +26,11 @@ const PACKS = {
26
26
  },
27
27
  vault: {
28
28
  name: 'vault',
29
- description: 'Secure credential storage',
30
- tools: 9,
29
+ description: 'Secure storage + Dewey index card catalog',
30
+ tools: 19,
31
31
  tier: 'free',
32
32
  always_on: true,
33
- highlights: ['vault_add', 'vault_get', 'vault_yolo']
33
+ highlights: ['vault_yolo', 'dewey_add', 'dewey_search', 'dewey_stats']
34
34
  },
35
35
 
36
36
  // === STARTER TIER ($29/mo) ===
@@ -73,6 +73,13 @@ const PACKS = {
73
73
  tier: 'pro',
74
74
  highlights: ['ux_list_blocks', 'ux_ab_compare', 'ux_a11y_check']
75
75
  },
76
+ librarian: {
77
+ name: 'librarian',
78
+ description: 'Digital asset manager - domains, bookmarks, CSV, folders, writing style',
79
+ tools: 24,
80
+ tier: 'pro',
81
+ highlights: ['domains_expiring', 'writing_draft', 'bookmarks_prune', 'csv_clean']
82
+ },
76
83
 
77
84
  // === ENTERPRISE TIER ($499/mo) ===
78
85
  labs_plus: {
@@ -110,6 +117,17 @@ const TOOL_TIERS = {
110
117
  vault_get: 'free',
111
118
  vault_list: 'free',
112
119
  vault_delete: 'free',
120
+ vault_add_proxies: 'free',
121
+ vault_get_proxy: 'free',
122
+ vault_list_proxies: 'free',
123
+ // Dewey Index (free - local vault)
124
+ dewey_add: 'free',
125
+ dewey_get: 'free',
126
+ dewey_search: 'free',
127
+ dewey_list: 'free',
128
+ dewey_update: 'free',
129
+ dewey_delete: 'free',
130
+ dewey_stats: 'free',
113
131
 
114
132
  // STARTER ($29/mo)
115
133
  hints: 'starter',
@@ -166,6 +184,37 @@ const TOOL_TIERS = {
166
184
  ux_a11y_check: 'pro',
167
185
  ux_color_palette: 'pro',
168
186
  ux_contrast_check: 'pro',
187
+ // Librarian tools
188
+ bookmarks_prune: 'pro',
189
+ bookmarks_organize: 'pro',
190
+ bookmarks_parse: 'pro',
191
+ csv_clean: 'pro',
192
+ csv_organize: 'pro',
193
+ csv_merge: 'pro',
194
+ csv_split: 'pro',
195
+ folder_analyze: 'pro',
196
+ folder_organize: 'pro',
197
+ folder_dedup: 'pro',
198
+ folder_plan: 'pro',
199
+ // Domain portfolio
200
+ domains_add: 'pro',
201
+ domains_list: 'pro',
202
+ domains_expiring: 'pro',
203
+ domains_suggest: 'pro',
204
+ domains_value: 'pro',
205
+ // Writing style
206
+ writing_sample_add: 'pro',
207
+ writing_style_analyze: 'pro',
208
+ writing_draft: 'pro',
209
+ writing_improve: 'pro',
210
+ writing_roast: 'pro',
211
+ email_templates_list: 'pro',
212
+ email_generate: 'pro',
213
+ // Roadmap (free - per user)
214
+ roadmap_add: 'free',
215
+ roadmap_list: 'free',
216
+ roadmap_update: 'free',
217
+ roadmap_delete: 'free',
169
218
 
170
219
  // ENTERPRISE ($499/mo)
171
220
  genius_plus: 'enterprise',
@@ -346,7 +395,7 @@ const PACK_TOOLS = [
346
395
  inputSchema: {
347
396
  type: 'object',
348
397
  properties: {
349
- pack: { type: 'string', description: 'Pack name', enum: ['beacon', 'labs', 'labs_plus', 'whm', 'cf', 'wp', 'ux'] }
398
+ pack: { type: 'string', description: 'Pack name', enum: ['beacon', 'labs', 'labs_plus', 'whm', 'cf', 'wp', 'ux', 'librarian'] }
350
399
  },
351
400
  required: ['pack']
352
401
  }
package/lib/vault.js CHANGED
@@ -304,6 +304,298 @@ async function yolo(passphrase) {
304
304
  return unlock(passphrase, 365 * 24 * 3600);
305
305
  }
306
306
 
307
+ // Proxy list management
308
+ async function addProxies(name, proxyList) {
309
+ const masterKey = await getMasterKey();
310
+ if (!masterKey) throw new Error('Vault locked. Unlock first.');
311
+
312
+ const proxies = typeof proxyList === 'string'
313
+ ? proxyList.split('\n').map(p => p.trim()).filter(p => p && p.includes(':'))
314
+ : proxyList;
315
+
316
+ const data = JSON.stringify({ proxies, added_at: Date.now(), count: proxies.length });
317
+ const encrypted = encrypt(data, masterKey);
318
+
319
+ if (MODE === 'local') {
320
+ const db = loadLocalDB();
321
+ db.credentials[`proxies/${name}`] = { encrypted, updated_at: Date.now() };
322
+ saveLocalDB(db);
323
+ } else {
324
+ await apiRequest('POST', '/vault/credentials', { name: `proxies/${name}`, encrypted });
325
+ }
326
+
327
+ return { success: true, name, count: proxies.length };
328
+ }
329
+
330
+ async function getProxies(name, random = false) {
331
+ const masterKey = await getMasterKey();
332
+ if (!masterKey) throw new Error('Vault locked. Unlock first.');
333
+
334
+ let encrypted;
335
+ const fullName = `proxies/${name}`;
336
+
337
+ if (MODE === 'local') {
338
+ const db = loadLocalDB();
339
+ if (!db.credentials[fullName]) throw new Error(`Proxy list not found: ${name}`);
340
+ encrypted = db.credentials[fullName].encrypted;
341
+ } else {
342
+ const response = await apiRequest('GET', `/vault/credentials/${encodeURIComponent(fullName)}`);
343
+ if (!response?.encrypted) throw new Error(`Proxy list not found: ${name}`);
344
+ encrypted = response.encrypted;
345
+ }
346
+
347
+ const data = JSON.parse(decrypt(encrypted, masterKey));
348
+
349
+ if (random) {
350
+ const idx = Math.floor(Math.random() * data.proxies.length);
351
+ return { proxy: data.proxies[idx], total: data.count };
352
+ }
353
+
354
+ return data;
355
+ }
356
+
357
+ async function listProxies() {
358
+ const all = await list('proxies');
359
+ return all.map(n => n.replace('proxies/', ''));
360
+ }
361
+
362
+ // ═══════════════════════════════════════════════════════════════
363
+ // DEWEY INDEX - Digital Card Catalog
364
+ // Index cards for files, articles, books, anything
365
+ // ═══════════════════════════════════════════════════════════════
366
+
367
+ async function deweyAdd(card) {
368
+ const masterKey = await getMasterKey();
369
+ if (!masterKey) throw new Error('Vault locked. Unlock first.');
370
+
371
+ const id = card.id || `dew_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
372
+ const now = Date.now();
373
+
374
+ const indexCard = {
375
+ id,
376
+ type: card.type || 'file', // file, article, book, idea, snippet, email, url
377
+ title: card.title || 'Untitled',
378
+ location: card.location || null, // file path, URL, or null
379
+ category: card.category || 'uncategorized',
380
+ tags: card.tags || [],
381
+ summary: card.summary || null, // LLM-generated or manual
382
+ content_preview: card.content_preview || null, // first 500 chars
383
+ metadata: {
384
+ author: card.author || null,
385
+ date_created: card.date_created || null,
386
+ date_read: card.date_read || null,
387
+ rating: card.rating || null, // 1-5
388
+ notes: card.notes || null, // personal notes
389
+ source: card.source || null, // goodreads, audible, local, web
390
+ ...card.metadata
391
+ },
392
+ indexed_at: now,
393
+ updated_at: now
394
+ };
395
+
396
+ const encrypted = encrypt(JSON.stringify(indexCard), masterKey);
397
+
398
+ if (MODE === 'local') {
399
+ const db = loadLocalDB();
400
+ db.credentials[`dewey/${id}`] = { encrypted, updated_at: now };
401
+ saveLocalDB(db);
402
+ } else {
403
+ await apiRequest('POST', '/vault/credentials', { name: `dewey/${id}`, encrypted });
404
+ }
405
+
406
+ return { id, title: indexCard.title, type: indexCard.type, indexed: true };
407
+ }
408
+
409
+ async function deweyGet(id) {
410
+ const masterKey = await getMasterKey();
411
+ if (!masterKey) throw new Error('Vault locked. Unlock first.');
412
+
413
+ let encrypted;
414
+ const fullName = `dewey/${id}`;
415
+
416
+ if (MODE === 'local') {
417
+ const db = loadLocalDB();
418
+ if (!db.credentials[fullName]) throw new Error(`Card not found: ${id}`);
419
+ encrypted = db.credentials[fullName].encrypted;
420
+ } else {
421
+ const response = await apiRequest('GET', `/vault/credentials/${encodeURIComponent(fullName)}`);
422
+ if (!response?.encrypted) throw new Error(`Card not found: ${id}`);
423
+ encrypted = response.encrypted;
424
+ }
425
+
426
+ return JSON.parse(decrypt(encrypted, masterKey));
427
+ }
428
+
429
+ async function deweySearch(query) {
430
+ const masterKey = await getMasterKey();
431
+ if (!masterKey) throw new Error('Vault locked. Unlock first.');
432
+
433
+ const allCards = await list('dewey');
434
+ const results = [];
435
+ const q = query.toLowerCase();
436
+
437
+ for (const name of allCards) {
438
+ try {
439
+ let encrypted;
440
+ if (MODE === 'local') {
441
+ const db = loadLocalDB();
442
+ encrypted = db.credentials[name]?.encrypted;
443
+ } else {
444
+ const response = await apiRequest('GET', `/vault/credentials/${encodeURIComponent(name)}`);
445
+ encrypted = response?.encrypted;
446
+ }
447
+
448
+ if (encrypted) {
449
+ const card = JSON.parse(decrypt(encrypted, masterKey));
450
+ // Search in title, tags, category, summary, notes
451
+ const searchable = [
452
+ card.title,
453
+ card.category,
454
+ card.summary,
455
+ card.metadata?.notes,
456
+ card.metadata?.author,
457
+ ...(card.tags || [])
458
+ ].filter(Boolean).join(' ').toLowerCase();
459
+
460
+ if (searchable.includes(q)) {
461
+ results.push({
462
+ id: card.id,
463
+ type: card.type,
464
+ title: card.title,
465
+ category: card.category,
466
+ tags: card.tags,
467
+ rating: card.metadata?.rating,
468
+ date_read: card.metadata?.date_read
469
+ });
470
+ }
471
+ }
472
+ } catch {}
473
+ }
474
+
475
+ return { query, results, count: results.length };
476
+ }
477
+
478
+ async function deweyList(options = {}) {
479
+ const masterKey = await getMasterKey();
480
+ if (!masterKey) throw new Error('Vault locked. Unlock first.');
481
+
482
+ const allCards = await list('dewey');
483
+ const results = [];
484
+
485
+ for (const name of allCards) {
486
+ try {
487
+ let encrypted;
488
+ if (MODE === 'local') {
489
+ const db = loadLocalDB();
490
+ encrypted = db.credentials[name]?.encrypted;
491
+ } else {
492
+ const response = await apiRequest('GET', `/vault/credentials/${encodeURIComponent(name)}`);
493
+ encrypted = response?.encrypted;
494
+ }
495
+
496
+ if (encrypted) {
497
+ const card = JSON.parse(decrypt(encrypted, masterKey));
498
+
499
+ // Filter by type
500
+ if (options.type && card.type !== options.type) continue;
501
+ // Filter by category
502
+ if (options.category && card.category !== options.category) continue;
503
+ // Filter by tag
504
+ if (options.tag && !card.tags?.includes(options.tag)) continue;
505
+ // Filter by rating
506
+ if (options.min_rating && (card.metadata?.rating || 0) < options.min_rating) continue;
507
+
508
+ results.push({
509
+ id: card.id,
510
+ type: card.type,
511
+ title: card.title,
512
+ category: card.category,
513
+ tags: card.tags,
514
+ rating: card.metadata?.rating,
515
+ location: card.location,
516
+ indexed_at: card.indexed_at
517
+ });
518
+ }
519
+ } catch {}
520
+ }
521
+
522
+ // Sort by indexed_at desc
523
+ results.sort((a, b) => (b.indexed_at || 0) - (a.indexed_at || 0));
524
+
525
+ return {
526
+ cards: results.slice(0, options.limit || 50),
527
+ total: results.length,
528
+ filters: { type: options.type, category: options.category, tag: options.tag }
529
+ };
530
+ }
531
+
532
+ async function deweyUpdate(id, updates) {
533
+ const masterKey = await getMasterKey();
534
+ if (!masterKey) throw new Error('Vault locked. Unlock first.');
535
+
536
+ const card = await deweyGet(id);
537
+
538
+ // Merge updates
539
+ if (updates.title) card.title = updates.title;
540
+ if (updates.category) card.category = updates.category;
541
+ if (updates.tags) card.tags = updates.tags;
542
+ if (updates.summary) card.summary = updates.summary;
543
+ if (updates.rating !== undefined) card.metadata.rating = updates.rating;
544
+ if (updates.notes) card.metadata.notes = updates.notes;
545
+ if (updates.date_read) card.metadata.date_read = updates.date_read;
546
+ card.updated_at = Date.now();
547
+
548
+ const encrypted = encrypt(JSON.stringify(card), masterKey);
549
+
550
+ if (MODE === 'local') {
551
+ const db = loadLocalDB();
552
+ db.credentials[`dewey/${id}`] = { encrypted, updated_at: card.updated_at };
553
+ saveLocalDB(db);
554
+ } else {
555
+ await apiRequest('POST', '/vault/credentials', { name: `dewey/${id}`, encrypted });
556
+ }
557
+
558
+ return { id, updated: true };
559
+ }
560
+
561
+ async function deweyDelete(id) {
562
+ return remove(`dewey/${id}`);
563
+ }
564
+
565
+ async function deweyStats() {
566
+ const masterKey = await getMasterKey();
567
+ if (!masterKey) throw new Error('Vault locked. Unlock first.');
568
+
569
+ const allCards = await list('dewey');
570
+ const stats = {
571
+ total: allCards.length,
572
+ by_type: {},
573
+ by_category: {},
574
+ rated: 0,
575
+ unread: 0
576
+ };
577
+
578
+ for (const name of allCards) {
579
+ try {
580
+ let encrypted;
581
+ if (MODE === 'local') {
582
+ const db = loadLocalDB();
583
+ encrypted = db.credentials[name]?.encrypted;
584
+ }
585
+
586
+ if (encrypted) {
587
+ const card = JSON.parse(decrypt(encrypted, masterKey));
588
+ stats.by_type[card.type] = (stats.by_type[card.type] || 0) + 1;
589
+ stats.by_category[card.category] = (stats.by_category[card.category] || 0) + 1;
590
+ if (card.metadata?.rating) stats.rated++;
591
+ if (card.type === 'book' && !card.metadata?.date_read) stats.unread++;
592
+ }
593
+ } catch {}
594
+ }
595
+
596
+ return stats;
597
+ }
598
+
307
599
  // Tool definitions for MCP
308
600
  const VAULT_TOOLS = [
309
601
  { name: 'vault_status', description: 'Check vault status. FREE.', inputSchema: { type: 'object', properties: {} } },
@@ -314,7 +606,44 @@ const VAULT_TOOLS = [
314
606
  { name: 'vault_add', description: 'Add credential to vault. FREE.', inputSchema: { type: 'object', properties: { name: { type: 'string' }, value: { type: 'string' } }, required: ['name', 'value'] } },
315
607
  { name: 'vault_get', description: 'Get credential from vault. FREE.', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } },
316
608
  { name: 'vault_list', description: 'List credentials in vault. FREE.', inputSchema: { type: 'object', properties: { namespace: { type: 'string' } } } },
317
- { name: 'vault_delete', description: 'Delete credential from vault. FREE.', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } }
609
+ { name: 'vault_delete', description: 'Delete credential from vault. FREE.', inputSchema: { type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } },
610
+ { name: 'vault_add_proxies', description: 'Store proxy list in vault. FREE.', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'List name (e.g., residential, datacenter)' }, proxy_list: { type: 'string', description: 'Newline-separated IP:PORT:USER:PASS' } }, required: ['name', 'proxy_list'] } },
611
+ { name: 'vault_get_proxy', description: 'Get proxy from stored list. FREE.', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'List name' }, random: { type: 'boolean', default: true, description: 'Return random proxy' } }, required: ['name'] } },
612
+ { name: 'vault_list_proxies', description: 'List stored proxy lists. FREE.', inputSchema: { type: 'object', properties: {} } },
613
+ // Dewey Index - Digital Card Catalog
614
+ { name: 'dewey_add', description: 'Add item to Dewey index (file, article, book, idea). FREE.', inputSchema: { type: 'object', properties: {
615
+ type: { type: 'string', enum: ['file', 'article', 'book', 'idea', 'snippet', 'email', 'url'], default: 'file' },
616
+ title: { type: 'string', description: 'Title of the item' },
617
+ location: { type: 'string', description: 'File path or URL' },
618
+ category: { type: 'string', description: 'Category/topic' },
619
+ tags: { type: 'array', items: { type: 'string' } },
620
+ summary: { type: 'string', description: 'Brief summary' },
621
+ author: { type: 'string' },
622
+ rating: { type: 'number', minimum: 1, maximum: 5 },
623
+ notes: { type: 'string', description: 'Personal notes' },
624
+ source: { type: 'string', description: 'Where from (goodreads, audible, local, web)' }
625
+ }, required: ['title'] } },
626
+ { name: 'dewey_get', description: 'Get full details of indexed item. FREE.', inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },
627
+ { name: 'dewey_search', description: 'Search your Dewey index. FREE.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search in titles, tags, notes, authors' } }, required: ['query'] } },
628
+ { name: 'dewey_list', description: 'List items in Dewey index with filters. FREE.', inputSchema: { type: 'object', properties: {
629
+ type: { type: 'string', description: 'Filter by type' },
630
+ category: { type: 'string', description: 'Filter by category' },
631
+ tag: { type: 'string', description: 'Filter by tag' },
632
+ min_rating: { type: 'number', description: 'Minimum rating filter' },
633
+ limit: { type: 'number', default: 50 }
634
+ } } },
635
+ { name: 'dewey_update', description: 'Update indexed item. FREE.', inputSchema: { type: 'object', properties: {
636
+ id: { type: 'string' },
637
+ title: { type: 'string' },
638
+ category: { type: 'string' },
639
+ tags: { type: 'array', items: { type: 'string' } },
640
+ summary: { type: 'string' },
641
+ rating: { type: 'number' },
642
+ notes: { type: 'string' },
643
+ date_read: { type: 'string', description: 'When you read/consumed it' }
644
+ }, required: ['id'] } },
645
+ { name: 'dewey_delete', description: 'Remove item from Dewey index. FREE.', inputSchema: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },
646
+ { name: 'dewey_stats', description: 'Get stats on your Dewey index. FREE.', inputSchema: { type: 'object', properties: {} } }
318
647
  ];
319
648
 
320
649
  // Handle vault tool calls
@@ -330,6 +659,17 @@ async function handleTool(name, args) {
330
659
  case 'vault_get': return { value: await get(args.name) };
331
660
  case 'vault_list': return { credentials: await list(args.namespace) };
332
661
  case 'vault_delete': return await remove(args.name);
662
+ case 'vault_add_proxies': return await addProxies(args.name, args.proxy_list);
663
+ case 'vault_get_proxy': return await getProxies(args.name, args.random !== false);
664
+ case 'vault_list_proxies': return { lists: await listProxies() };
665
+ // Dewey Index
666
+ case 'dewey_add': return await deweyAdd(args);
667
+ case 'dewey_get': return await deweyGet(args.id);
668
+ case 'dewey_search': return await deweySearch(args.query);
669
+ case 'dewey_list': return await deweyList(args);
670
+ case 'dewey_update': return await deweyUpdate(args.id, args);
671
+ case 'dewey_delete': return await deweyDelete(args.id);
672
+ case 'dewey_stats': return await deweyStats();
333
673
  default: return { error: `Unknown vault tool: ${name}` };
334
674
  }
335
675
  } catch (e) {
@@ -350,5 +690,15 @@ module.exports = {
350
690
  list,
351
691
  remove,
352
692
  isUnlocked,
353
- getMasterKey
693
+ getMasterKey,
694
+ addProxies,
695
+ getProxies,
696
+ listProxies,
697
+ deweyAdd,
698
+ deweyGet,
699
+ deweySearch,
700
+ deweyList,
701
+ deweyUpdate,
702
+ deweyDelete,
703
+ deweyStats
354
704
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "50c",
3
- "version": "2.2.0",
4
- "description": "AI toolkit. One install, 100+ tools. Works everywhere.",
3
+ "version": "2.5.0",
4
+ "description": "AI toolkit with Dewey index. One install, 100+ tools.",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
7
7
  "50c": "./bin/50c.js"
@@ -19,7 +19,10 @@
19
19
  "cloudflare",
20
20
  "whm",
21
21
  "cpanel",
22
- "wordpress"
22
+ "wordpress",
23
+ "librarian",
24
+ "bookmarks",
25
+ "csv"
23
26
  ],
24
27
  "author": "genxis.com",
25
28
  "license": "MIT",