50c 2.11.0 → 2.15.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/bin/50c.js CHANGED
@@ -464,6 +464,13 @@ async function main() {
464
464
  // MCP mode: --mcp flag or no args AND piped input
465
465
  const isMCPMode = args[0] === '--mcp' || (args.length === 0 && !process.stdin.isTTY);
466
466
 
467
+ // Version check (non-blocking, only warns)
468
+ if (!isMCPMode && args[0] !== 'help' && args[0] !== '--help') {
469
+ lib.checkVersion().then(v => {
470
+ if (v.message) console.error(`\n Warning: ${v.message}\n`);
471
+ }).catch(() => {});
472
+ }
473
+
467
474
  if (isMCPMode) {
468
475
  await runMCP();
469
476
  } else if (args.length === 0) {
package/lib/index.js CHANGED
@@ -25,9 +25,83 @@ const ux = require('./packs/ux');
25
25
  const promptEngine = require('./packs/prompt_engine');
26
26
  const grabr = require('./packs/grabr');
27
27
 
28
+ // ═══════════════════════════════════════════════════════════════
29
+ // MANIFEST - SINGLE SOURCE OF TRUTH
30
+ // ═══════════════════════════════════════════════════════════════
31
+ // genxis.one/v1/manifest is the canonical tool registry
32
+ // CLI bootstraps from it, caches locally, validates versions
33
+
34
+ const MANIFEST_URL = 'https://api.50c.ai/v1/manifest';
35
+ const MANIFEST_CACHE_TTL = 3600000; // 1 hour
36
+ let manifestCache = null;
37
+ let manifestCacheTime = 0;
38
+
39
+ async function fetchManifest(staging = false) {
40
+ const now = Date.now();
41
+ if (manifestCache && (now - manifestCacheTime) < MANIFEST_CACHE_TTL) {
42
+ return manifestCache;
43
+ }
44
+
45
+ try {
46
+ const https = require('https');
47
+ const headers = staging ? { 'X-GenXis-Env': 'staging' } : {};
48
+
49
+ const data = await new Promise((resolve, reject) => {
50
+ const req = https.get(MANIFEST_URL, { headers, timeout: 10000 }, (res) => {
51
+ let body = '';
52
+ res.on('data', chunk => body += chunk);
53
+ res.on('end', () => {
54
+ try {
55
+ resolve(JSON.parse(body));
56
+ } catch (e) {
57
+ reject(new Error('Invalid manifest JSON'));
58
+ }
59
+ });
60
+ });
61
+ req.on('error', reject);
62
+ req.on('timeout', () => { req.destroy(); reject(new Error('Manifest timeout')); });
63
+ });
64
+
65
+ manifestCache = data;
66
+ manifestCacheTime = now;
67
+ return data;
68
+ } catch (e) {
69
+ // Fallback to local tools if manifest unavailable
70
+ console.error('Manifest fetch failed, using local tools:', e.message);
71
+ return null;
72
+ }
73
+ }
74
+
75
+ async function checkVersion() {
76
+ try {
77
+ const manifest = await fetchManifest();
78
+ if (!manifest) return { upToDate: true };
79
+
80
+ const pkg = require('../package.json');
81
+ const minVersion = manifest.meta?.min_client_version || '0.0.0';
82
+ const currentVersion = pkg.version;
83
+
84
+ const [minMajor, minMinor] = minVersion.split('.').map(Number);
85
+ const [curMajor, curMinor] = currentVersion.split('.').map(Number);
86
+
87
+ const upToDate = curMajor > minMajor || (curMajor === minMajor && curMinor >= minMinor);
88
+
89
+ return {
90
+ upToDate,
91
+ current: currentVersion,
92
+ minimum: minVersion,
93
+ latest: manifest.meta?.api_version,
94
+ message: upToDate ? null : `Update available: npx 50c@latest`
95
+ };
96
+ } catch (e) {
97
+ return { upToDate: true, error: e.message };
98
+ }
99
+ }
100
+
28
101
  // Tool name mappings by pack
29
102
  const TOOL_PACKS = {
30
- beacon: ['hints', 'hints_plus', 'roast', 'quick_vibe', 'one_liner', 'name_it', 'price_it', 'compute', 'ide_conversation', 'learning_stats'],
103
+ beacon: ['hints', 'hints_plus', 'roast', 'quick_vibe', 'one_liner', 'name_it', 'price_it', 'compute', 'ide_conversation', 'learning_stats',
104
+ 'beacon_scan', 'beacon_compress', 'beacon_extract', 'beacon_focus', 'beacon_rank'],
31
105
  labs: ['genius', 'mind_opener', 'idea_fold', 'agent_autopsy', 'prompt_fortress', 'context_health', 'context_compress', 'context_extract', 'context_reposition'],
32
106
  labs_plus: ['bcalc', 'genius_plus', 'bcalc_why', 'discovery_collision', 'cvi_loop', 'cvi_verify', 'chaos_fingerprint', 'resonance', 'prime_residue', 'echo_sequence', 'conversation_diagnostic', 'handoff'],
33
107
  prompt_engine: ['prompt_extract', 'prompt_phases', 'prompt_refine', 'prompt_expand', 'prompt_categorize'],
@@ -195,5 +269,9 @@ module.exports = {
195
269
  labs,
196
270
  labsPlus,
197
271
  promptEngine,
198
- grabr
272
+ grabr,
273
+ // Manifest - Single Source of Truth
274
+ fetchManifest,
275
+ checkVersion,
276
+ MANIFEST_URL
199
277
  };
@@ -110,7 +110,7 @@ async function grabrScrape(url, depth = 1) {
110
110
 
111
111
  try {
112
112
  // Use 50c page_fetch via API
113
- const result = await apiRequest('page_fetch', { url });
113
+ const result = await apiRequest('POST', '/tools/page_fetch', { url });
114
114
  if (result.error) return { error: result.error };
115
115
 
116
116
  const html = result.content || result.text || '';
@@ -130,7 +130,7 @@ async function grabrScrape(url, depth = 1) {
130
130
  if (href) {
131
131
  try {
132
132
  const fullUrl = href.startsWith('http') ? href : new URL(href, url).href;
133
- const subResult = await apiRequest('page_fetch', { url: fullUrl });
133
+ const subResult = await apiRequest('POST', '/tools/page_fetch', { url: fullUrl });
134
134
  if (subResult.content) {
135
135
  contacts.emails.push(...extractEmails(subResult.content));
136
136
  contacts.phones.push(...extractPhones(subResult.content));
@@ -237,15 +237,15 @@ async function grabrSitemap(url) {
237
237
  }
238
238
 
239
239
  try {
240
- const result = await apiRequest('page_fetch', { url: sitemapUrl });
240
+ const result = await apiRequest('POST', '/tools/page_fetch', { url: sitemapUrl });
241
241
  if (result.error) {
242
242
  // Try robots.txt fallback
243
243
  const robotsUrl = url.replace(/\/$/, '') + '/robots.txt';
244
- const robotsResult = await apiRequest('page_fetch', { url: robotsUrl });
244
+ const robotsResult = await apiRequest('POST', '/tools/page_fetch', { url: robotsUrl });
245
245
  if (robotsResult.content) {
246
246
  const sitemapMatch = robotsResult.content.match(/Sitemap:\s*(\S+)/i);
247
247
  if (sitemapMatch) {
248
- const altResult = await apiRequest('page_fetch', { url: sitemapMatch[1] });
248
+ const altResult = await apiRequest('POST', '/tools/page_fetch', { url: sitemapMatch[1] });
249
249
  if (altResult.content) {
250
250
  result.content = altResult.content;
251
251
  }
package/lib/vault.js CHANGED
@@ -15,6 +15,9 @@ const SALT_LENGTH = 32;
15
15
  const IV_LENGTH = 16;
16
16
  const AUTH_TAG_LENGTH = 16;
17
17
 
18
+ // Security: Auto-lock after 5 minutes of inactivity (300 seconds)
19
+ const AUTO_LOCK_INACTIVITY_MS = 5 * 60 * 1000;
20
+
18
21
  // Local file paths
19
22
  const MASTER_KEY_FILE = path.join(VAULT_DIR, 'master.key.enc');
20
23
  const VAULT_DB_FILE = path.join(VAULT_DIR, 'vault.db');
@@ -54,10 +57,25 @@ function loadLocalSession() {
54
57
  try {
55
58
  if (fs.existsSync(SESSION_FILE)) {
56
59
  const session = JSON.parse(fs.readFileSync(SESSION_FILE, 'utf8'));
57
- if (session.expires_at > Date.now()) {
58
- return session;
60
+ const now = Date.now();
61
+
62
+ // Check TTL expiry
63
+ if (session.expires_at <= now) {
64
+ fs.unlinkSync(SESSION_FILE);
65
+ return null;
66
+ }
67
+
68
+ // Check inactivity auto-lock (5 minutes)
69
+ if (session.last_access && (now - session.last_access) > AUTO_LOCK_INACTIVITY_MS) {
70
+ fs.unlinkSync(SESSION_FILE);
71
+ return null;
59
72
  }
60
- fs.unlinkSync(SESSION_FILE);
73
+
74
+ // Update last_access timestamp
75
+ session.last_access = now;
76
+ fs.writeFileSync(SESSION_FILE, JSON.stringify(session), { mode: 0o600 });
77
+
78
+ return session;
61
79
  }
62
80
  } catch {}
63
81
  return null;
@@ -291,11 +309,26 @@ async function remove(name) {
291
309
  }
292
310
 
293
311
  async function status() {
312
+ const session = MODE === 'local' ? loadLocalSession() : null;
313
+ const now = Date.now();
314
+
315
+ let autoLockIn = null;
316
+ if (session && session.last_access) {
317
+ const remaining = AUTO_LOCK_INACTIVITY_MS - (now - session.last_access);
318
+ autoLockIn = remaining > 0 ? Math.ceil(remaining / 1000) : 0;
319
+ }
320
+
294
321
  return {
295
322
  mode: MODE,
296
323
  initialized: await isInitialized(),
297
324
  locked: !(await isUnlocked()),
298
- path: MODE === 'local' ? VAULT_DIR : 'cloud'
325
+ path: MODE === 'local' ? VAULT_DIR : 'cloud',
326
+ auto_lock_seconds: autoLockIn,
327
+ security: {
328
+ auto_lock_inactivity: '5 minutes',
329
+ encryption: 'AES-256-GCM',
330
+ pbkdf2_iterations: PBKDF2_ITERATIONS
331
+ }
299
332
  };
300
333
  }
301
334
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "50c",
3
- "version": "2.11.0",
4
- "description": "AI toolkit with Dewey quality gates. 137 tools, noise-free indexing.",
3
+ "version": "2.15.0",
4
+ "description": "AI toolkit. Added beacon_* tools to TOOL_PACKS routing.",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
7
7
  "50c": "./bin/50c.js"