prior-cli 1.6.2 → 1.6.3
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/tools.js +117 -0
- package/package.json +1 -1
package/lib/tools.js
CHANGED
|
@@ -416,6 +416,123 @@ const TOOLS = {
|
|
|
416
416
|
const data = await res.json();
|
|
417
417
|
return { output: data.output || JSON.stringify(data), summary: data.summary || `spider started for ${url}` };
|
|
418
418
|
},
|
|
419
|
+
|
|
420
|
+
async ip_lookup({ target }, {}) {
|
|
421
|
+
if (!target) throw new Error('"target" is required — provide an IP address or domain');
|
|
422
|
+
const encoded = encodeURIComponent(target.trim());
|
|
423
|
+
const res = await fetch(`https://ipinfo.io/${encoded}/json`, {
|
|
424
|
+
headers: { 'User-Agent': 'prior-cli/1.0', Accept: 'application/json' },
|
|
425
|
+
timeout: 10000,
|
|
426
|
+
});
|
|
427
|
+
if (!res.ok) throw new Error(`ipinfo.io error: HTTP ${res.status}`);
|
|
428
|
+
const d = await res.json();
|
|
429
|
+
if (d.error) throw new Error(d.error.message || 'Lookup failed');
|
|
430
|
+
const lines = [
|
|
431
|
+
`IP : ${d.ip || target}`,
|
|
432
|
+
d.hostname ? `Hostname : ${d.hostname}` : null,
|
|
433
|
+
d.org ? `Org / ASN : ${d.org}` : null,
|
|
434
|
+
d.city ? `Location : ${[d.city, d.region, d.country].filter(Boolean).join(', ')}` : null,
|
|
435
|
+
d.postal ? `Postal : ${d.postal}` : null,
|
|
436
|
+
d.timezone ? `Timezone : ${d.timezone}` : null,
|
|
437
|
+
d.loc ? `Coords : ${d.loc}` : null,
|
|
438
|
+
].filter(Boolean);
|
|
439
|
+
return {
|
|
440
|
+
output: lines.join('\n'),
|
|
441
|
+
summary: `${d.ip || target}${d.org ? ' · ' + d.org : ''}${d.city ? ' · ' + d.city : ''}`,
|
|
442
|
+
};
|
|
443
|
+
},
|
|
444
|
+
|
|
445
|
+
async dns_lookup({ domain, type = 'A' }, {}) {
|
|
446
|
+
if (!domain) throw new Error('"domain" is required');
|
|
447
|
+
const validTypes = ['A', 'AAAA', 'CNAME', 'MX', 'NS', 'TXT', 'SOA', 'PTR', 'SRV'];
|
|
448
|
+
const qtype = type.toUpperCase();
|
|
449
|
+
if (!validTypes.includes(qtype)) throw new Error(`Invalid type. Choose from: ${validTypes.join(', ')}`);
|
|
450
|
+
const res = await fetch(`https://dns.google/resolve?name=${encodeURIComponent(domain)}&type=${qtype}`, {
|
|
451
|
+
headers: { Accept: 'application/json' },
|
|
452
|
+
timeout: 10000,
|
|
453
|
+
});
|
|
454
|
+
if (!res.ok) throw new Error(`DNS query failed: HTTP ${res.status}`);
|
|
455
|
+
const data = await res.json();
|
|
456
|
+
if (data.Status !== 0) {
|
|
457
|
+
const STATUS = { 1: 'Format error', 2: 'Server failure', 3: 'NXDOMAIN (not found)', 5: 'Refused' };
|
|
458
|
+
throw new Error(STATUS[data.Status] || `DNS error code ${data.Status}`);
|
|
459
|
+
}
|
|
460
|
+
if (!data.Answer || data.Answer.length === 0) {
|
|
461
|
+
return { output: `No ${qtype} records found for ${domain}`, summary: `0 ${qtype} records` };
|
|
462
|
+
}
|
|
463
|
+
const lines = data.Answer.map(r => {
|
|
464
|
+
const ttl = `TTL ${r.TTL}s`;
|
|
465
|
+
return ` ${String(r.type).padEnd(6)} ${ttl.padEnd(12)} ${r.data}`;
|
|
466
|
+
});
|
|
467
|
+
const header = `${domain} ${qtype} records (${data.Answer.length})\n`;
|
|
468
|
+
return {
|
|
469
|
+
output: header + lines.join('\n'),
|
|
470
|
+
summary: `${data.Answer.length} ${qtype} record${data.Answer.length !== 1 ? 's' : ''} for ${domain}`,
|
|
471
|
+
};
|
|
472
|
+
},
|
|
473
|
+
|
|
474
|
+
async ssl_check({ domain }, {}) {
|
|
475
|
+
if (!domain) throw new Error('"domain" is required');
|
|
476
|
+
// Strip protocol if provided
|
|
477
|
+
const host = domain.replace(/^https?:\/\//, '').split('/')[0].split(':')[0];
|
|
478
|
+
// Use crt.sh to get cert info + check via HEAD for expiry details
|
|
479
|
+
const [certRes, headRes] = await Promise.allSettled([
|
|
480
|
+
fetch(`https://crt.sh/?q=${encodeURIComponent(host)}&output=json`, {
|
|
481
|
+
headers: { 'User-Agent': 'prior-cli/1.0' },
|
|
482
|
+
timeout: 12000,
|
|
483
|
+
}),
|
|
484
|
+
fetch(`https://${host}`, {
|
|
485
|
+
method: 'HEAD',
|
|
486
|
+
headers: { 'User-Agent': 'prior-cli/1.0' },
|
|
487
|
+
timeout: 8000,
|
|
488
|
+
}),
|
|
489
|
+
]);
|
|
490
|
+
|
|
491
|
+
const lines = [`Domain : ${host}`];
|
|
492
|
+
|
|
493
|
+
// Live cert check via HTTPS HEAD — Node's TLS gives us nothing in headers,
|
|
494
|
+
// but we can confirm TLS works and check for errors
|
|
495
|
+
if (headRes.status === 'fulfilled') {
|
|
496
|
+
lines.push(`HTTPS : ✓ reachable (HTTP ${headRes.value.status})`);
|
|
497
|
+
} else {
|
|
498
|
+
const msg = headRes.reason?.message || 'unreachable';
|
|
499
|
+
const expired = /certificate has expired|CERT_HAS_EXPIRED/i.test(msg);
|
|
500
|
+
lines.push(`HTTPS : ✗ ${expired ? 'CERTIFICATE EXPIRED' : msg}`);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// crt.sh — most recent issuances
|
|
504
|
+
if (certRes.status === 'fulfilled' && certRes.value.ok) {
|
|
505
|
+
try {
|
|
506
|
+
const certs = await certRes.value.json();
|
|
507
|
+
const recent = certs
|
|
508
|
+
.filter(c => c.name_value && !c.name_value.startsWith('*'))
|
|
509
|
+
.sort((a, b) => new Date(b.not_after) - new Date(a.not_after))
|
|
510
|
+
.slice(0, 3);
|
|
511
|
+
if (recent.length) {
|
|
512
|
+
lines.push('');
|
|
513
|
+
lines.push('Recent certificates (crt.sh):');
|
|
514
|
+
for (const c of recent) {
|
|
515
|
+
const expiry = new Date(c.not_after);
|
|
516
|
+
const issued = new Date(c.not_before);
|
|
517
|
+
const daysLeft = Math.ceil((expiry - Date.now()) / 86400000);
|
|
518
|
+
const status = daysLeft < 0 ? '✗ EXPIRED' : daysLeft < 14 ? `⚠ expires in ${daysLeft}d` : `✓ ${daysLeft}d left`;
|
|
519
|
+
lines.push(` Issuer : ${c.issuer_name?.replace(/^.*?CN=/, 'CN=') || '?'}`);
|
|
520
|
+
lines.push(` Issued : ${issued.toLocaleDateString()}`);
|
|
521
|
+
lines.push(` Expires : ${expiry.toLocaleDateString()} (${status})`);
|
|
522
|
+
lines.push(` Names : ${c.name_value.replace(/\n/g, ', ')}`);
|
|
523
|
+
lines.push(' ─────────────────────────────');
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
} catch { /* crt.sh parse failed, HTTPS check is enough */ }
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Summary line
|
|
530
|
+
const httpsOk = headRes.status === 'fulfilled';
|
|
531
|
+
return {
|
|
532
|
+
output: lines.join('\n'),
|
|
533
|
+
summary: `${host} · HTTPS ${httpsOk ? '✓' : '✗'}`,
|
|
534
|
+
};
|
|
535
|
+
},
|
|
419
536
|
};
|
|
420
537
|
|
|
421
538
|
async function executeTool(name, args, context) {
|