prior-cli 1.6.1 → 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/bin/prior.js +59 -1
- package/lib/tools.js +117 -0
- package/package.json +1 -1
package/bin/prior.js
CHANGED
|
@@ -709,7 +709,7 @@ async function startChat(opts = {}) {
|
|
|
709
709
|
console.log(c.ok(' ◉') + c.muted(' Agent mode ') + c.dim('· file web shell image prior-network'));
|
|
710
710
|
|
|
711
711
|
console.log(DIVIDER);
|
|
712
|
-
console.log(c.muted(' /help /clear /compact /censored /uncensored /exit'));
|
|
712
|
+
console.log(c.muted(' /help /clear /compact /timer /censored /uncensored /exit'));
|
|
713
713
|
console.log(DIVIDER);
|
|
714
714
|
console.log('');
|
|
715
715
|
|
|
@@ -727,6 +727,7 @@ async function startChat(opts = {}) {
|
|
|
727
727
|
|
|
728
728
|
const SLASH_CMDS = [
|
|
729
729
|
{ cmd: '/compact', desc: 'Compact conversation to save context' },
|
|
730
|
+
{ cmd: '/timer', desc: 'Set a countdown timer e.g. /timer 30s' },
|
|
730
731
|
{ cmd: '/help', desc: 'Show help' },
|
|
731
732
|
{ cmd: '/clear', desc: 'Clear screen' },
|
|
732
733
|
{ cmd: '/censored', desc: 'Load standard model (qwen)' },
|
|
@@ -1164,10 +1165,67 @@ Be concise but thorough — this summary replaces the full history to save conte
|
|
|
1164
1165
|
return loop();
|
|
1165
1166
|
}
|
|
1166
1167
|
|
|
1168
|
+
case '/timer': {
|
|
1169
|
+
const timerArg = args.join(' ').trim();
|
|
1170
|
+
// Parse duration: e.g. 30, 30s, 5m, 1m30s, 1h
|
|
1171
|
+
const parseDuration = (str) => {
|
|
1172
|
+
if (!str) return null;
|
|
1173
|
+
let total = 0;
|
|
1174
|
+
const re = /(\d+(?:\.\d+)?)\s*([hms]?)/gi;
|
|
1175
|
+
let m, matched = false;
|
|
1176
|
+
while ((m = re.exec(str)) !== null) {
|
|
1177
|
+
const val = parseFloat(m[1]);
|
|
1178
|
+
const unit = (m[2] || 's').toLowerCase();
|
|
1179
|
+
if (unit === 'h') total += val * 3600;
|
|
1180
|
+
else if (unit === 'm') total += val * 60;
|
|
1181
|
+
else total += val;
|
|
1182
|
+
matched = true;
|
|
1183
|
+
}
|
|
1184
|
+
return matched ? Math.round(total) : null;
|
|
1185
|
+
};
|
|
1186
|
+
const fmtCountdown = (sec) => {
|
|
1187
|
+
const h = Math.floor(sec / 3600);
|
|
1188
|
+
const m = Math.floor((sec % 3600) / 60);
|
|
1189
|
+
const s = sec % 60;
|
|
1190
|
+
if (h > 0) return `${h}:${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`;
|
|
1191
|
+
return `${m > 0 ? m + ':' : ''}${m > 0 ? String(s).padStart(2,'0') : s + 's'}`;
|
|
1192
|
+
};
|
|
1193
|
+
const totalSec = parseDuration(timerArg);
|
|
1194
|
+
if (!totalSec || totalSec <= 0) {
|
|
1195
|
+
console.log(c.err(' Usage: /timer 30s or /timer 5m or /timer 1m30s\n'));
|
|
1196
|
+
return loop();
|
|
1197
|
+
}
|
|
1198
|
+
console.log(c.brand(` Timer set for ${timerArg} — starting now.\n`));
|
|
1199
|
+
let remaining = totalSec;
|
|
1200
|
+
const timerInterval = setInterval(() => {
|
|
1201
|
+
if (process.stdout.isTTY) {
|
|
1202
|
+
process.stdout.clearLine(0);
|
|
1203
|
+
process.stdout.cursorTo(0);
|
|
1204
|
+
}
|
|
1205
|
+
if (remaining <= 0) {
|
|
1206
|
+
clearInterval(timerInterval);
|
|
1207
|
+
if (process.stdout.isTTY) {
|
|
1208
|
+
process.stdout.clearLine(0);
|
|
1209
|
+
process.stdout.cursorTo(0);
|
|
1210
|
+
}
|
|
1211
|
+
// Bell + done message
|
|
1212
|
+
process.stdout.write('\x07');
|
|
1213
|
+
console.log(c.bold(` ⏰ Time's up! (${timerArg})\n`));
|
|
1214
|
+
rl.prompt(true);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
const bar = '█'.repeat(Math.ceil((remaining / totalSec) * 20)).padEnd(20, '░');
|
|
1218
|
+
process.stdout.write(` ${c.brand('⏱')} ${c.brand(fmtCountdown(remaining).padStart(6))} ${c.dim(bar)}`);
|
|
1219
|
+
remaining--;
|
|
1220
|
+
}, 1000);
|
|
1221
|
+
return loop();
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1167
1224
|
case '/help':
|
|
1168
1225
|
console.log('');
|
|
1169
1226
|
console.log(c.bold(' Commands'));
|
|
1170
1227
|
console.log(c.muted(' /compact ') + 'Compact conversation to save context');
|
|
1228
|
+
console.log(c.muted(' /timer <duration> ') + 'Countdown timer e.g. /timer 30s, /timer 5m');
|
|
1171
1229
|
console.log(c.muted(' /clear ') + 'Clear screen');
|
|
1172
1230
|
console.log(c.muted(' /censored ') + 'Load standard model (qwen)');
|
|
1173
1231
|
console.log(c.muted(' /uncensored ') + 'Load uncensored model (dolphin)');
|
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) {
|