@agentcommunity/aid-doctor
Version:
CLI tool for Agent Identity & Discovery (AID) - validate and check AID records
672 lines (667 loc) • 28.2 kB
JavaScript
import { Command } from 'commander';
import { writeFile } from 'fs/promises';
import { realpathSync, readFileSync, promises } from 'fs';
import path2 from 'path';
import os from 'os';
import { fileURLToPath } from 'url';
import { AidError, PROTOCOL_TOKENS, AUTH_TOKENS } from '@agentcommunity/aid';
import { runCheck, verifyPka, buildTxtRecordVariant, validateTxtRecord, generateEd25519KeyPair, classifySecurityChange as classifySecurityChange$1, shouldRejectForFailPolicy as shouldRejectForFailPolicy$1, derivePkaKeyid as derivePkaKeyid$1, ERROR_MESSAGES } from '@agentcommunity/aid-engine';
import chalk2 from 'chalk';
import ora from 'ora';
import inquirer from 'inquirer';
import clipboardy from 'clipboardy';
var CACHE_SCHEMA_VERSION = 3;
function cachePath() {
return path2.join(os.homedir(), ".aid", "cache.json");
}
var derivePkaKeyid = derivePkaKeyid$1;
function isCacheEntry(value) {
if (!value || typeof value !== "object") return false;
const entry = value;
return typeof entry.lastSeen === "string" && (typeof entry.pka === "string" || entry.pka === null) && (typeof entry.kid === "string" || entry.kid === null);
}
function migrateEntry(entry) {
const keyMaterial = derivePkaKeyid(entry.pka);
return {
...entry,
version: entry.version ?? (entry.pka?.startsWith("z") ? "aid1" : entry.pka ? "aid2" : null),
trustSource: entry.trustSource ?? "dns",
keyid: entry.keyid ?? keyMaterial?.keyid ?? null,
jwkX: entry.jwkX ?? keyMaterial?.jwkX ?? null,
domainBound: entry.domainBound ?? null
};
}
function migrateCacheFile(raw) {
if (raw && typeof raw === "object" && raw.schemaVersion === CACHE_SCHEMA_VERSION && raw.entries && typeof raw.entries === "object") {
return raw;
}
const entries = {};
if (!raw || typeof raw !== "object") {
return { schemaVersion: CACHE_SCHEMA_VERSION, entries };
}
const wrapped = raw;
const source = wrapped.entries && typeof wrapped.entries === "object" ? wrapped.entries : raw;
for (const [domain, entry] of Object.entries(source)) {
if (domain === "schemaVersion" || domain === "entries") continue;
if (isCacheEntry(entry)) {
entries[domain] = migrateEntry(entry);
}
}
return { schemaVersion: CACHE_SCHEMA_VERSION, entries };
}
function buildCacheEntryFromReport(report, now = /* @__PURE__ */ new Date()) {
const record = report.record.parsed;
const keyMaterial = derivePkaKeyid(record?.pka ?? null);
return {
lastSeen: now.toISOString(),
version: record?.v ?? null,
trustSource: report.queried.wellKnown.used ? "well-known-tls" : "dns",
pka: record?.pka ?? null,
kid: record?.v === "aid1" ? record.kid ?? null : null,
keyid: keyMaterial?.keyid ?? null,
jwkX: keyMaterial?.jwkX ?? null,
domainBound: report.pka.domainBound ?? null,
hash: report.cacheEntry?.hash ?? null
};
}
var classifySecurityChange = classifySecurityChange$1;
var shouldRejectForFailPolicy = shouldRejectForFailPolicy$1;
async function loadCache() {
try {
const p = cachePath();
const data = await promises.readFile(p, "utf8");
return migrateCacheFile(JSON.parse(data));
} catch {
return { schemaVersion: CACHE_SCHEMA_VERSION, entries: {} };
}
}
async function ensureDir(filePath) {
try {
await promises.mkdir(path2.dirname(filePath), { recursive: true });
} catch {
}
}
async function saveCache(cache) {
const p = cachePath();
await ensureDir(p);
const tmp = p + ".tmp";
const content = JSON.stringify(migrateCacheFile(cache), null, 2);
await promises.writeFile(tmp, content, { mode: 384 });
await promises.rename(tmp, p);
}
// src/output.ts
function generateActionableSuggestions(report) {
const suggestions = [];
const { record, dnssec, tls, pka } = report;
const icon = chalk2.blue("\u{1F4A1}");
if (record.raw) {
const byteLen = new TextEncoder().encode(record.raw).length;
if (byteLen > 255) {
suggestions.push(
`${icon} ${chalk2.bold("Reduce record size:")} ${ERROR_MESSAGES.BYTE_LIMIT_EXCEEDED}`
);
}
}
if (record.warnings.some((warning) => warning.code === "LONG_KEY_COMPAT")) {
suggestions.push(
`${icon} ${chalk2.bold("Canonicalize TXT keys:")} ${ERROR_MESSAGES.USE_ALIASES}`
);
}
if (!dnssec.present) {
suggestions.push(`${icon} ${chalk2.bold("Enable DNSSEC:")} ${ERROR_MESSAGES.ENABLE_DNSSEC}`);
}
if (!pka.present) {
suggestions.push(`${icon} ${chalk2.bold("Add endpoint proof:")} ${ERROR_MESSAGES.ADD_PKA}`);
}
if (record.parsed?.v === "aid2" && pka.present && pka.verified && pka.domainBound === false) {
suggestions.push(
`${icon} ${chalk2.bold("Enable domain binding:")} Your endpoint proof is valid but not domain-bound. Configure your endpoint to cover the AID-Domain header in its HTTP Message Signature so the endpoint attests that it serves the queried domain.`
);
}
if (tls.checked && tls.valid && tls.daysRemaining !== null && tls.daysRemaining < 21) {
suggestions.push(`${icon} ${chalk2.bold("Renew TLS certificate:")} ${ERROR_MESSAGES.RENEW_TLS}`);
}
if (record.errors.some((e) => e.code === "ERR_UNSUPPORTED_PROTO")) {
suggestions.push(
`${icon} ${chalk2.bold("Check protocol support:")} The protocol in your record is not supported by this client. See the official registry for valid tokens.`
);
}
return suggestions;
}
function formatCheckResult(report) {
const { domain, record, queried, tls, dnssec, pka, downgrade } = report;
const lines = [];
const icons = {
ok: chalk2.green("\u2705"),
error: chalk2.red("\u274C"),
warn: chalk2.yellow("\u26A0\uFE0F"),
info: chalk2.blue("\u{1F4A1}")
};
const finalQueryName = queried.attempts[queried.attempts.length - 1]?.name ?? `_agent.${domain}`;
const dnsAttempt = queried.attempts.find((a) => a.result === "NOERROR");
if (dnsAttempt) {
const ttl = dnsAttempt.ttl ?? "N/A";
const bytes = new TextEncoder().encode(record.raw ?? "").length;
const source = queried.wellKnown.used ? "(.well-known)" : "(DNS)";
lines.push(
`[1/6] DNS TXT ${chalk2.cyan(finalQueryName)} ... ${icons.ok} Found ${source} (TTL ${ttl}, ${bytes} bytes)`
);
} else {
lines.push(`[1/6] DNS TXT ${chalk2.cyan(finalQueryName)} ... ${icons.error} Not Found`);
}
if (record.valid) {
lines.push(`[2/6] Record validation ... ${icons.ok} Valid`);
} else {
const msg = record.errors[0]?.message ?? "Invalid structure";
lines.push(`[2/6] Record validation ... ${icons.error} ${msg}`);
}
if (dnssec.present) {
lines.push(`[3/6] DNSSEC (RRSIG) ... ${icons.ok} Detected`);
} else {
lines.push(`[3/6] DNSSEC (RRSIG) ... ${icons.info} Not detected`);
}
if (tls.checked) {
if (tls.valid) {
const expiry = tls.daysRemaining;
lines.push(
`[4/6] TLS ${chalk2.cyan(tls.host ?? "")} ... ${icons.ok} Valid (SAN matches, expires in ${expiry} days)`
);
} else {
const reason = record.errors.find((e) => e.code.startsWith("ERR_SECURITY"))?.message ?? "TLS validation failed";
lines.push(`[4/6] TLS ... ${icons.error} ${reason}`);
}
} else {
lines.push(`[4/6] TLS ... ${chalk2.gray("Skipped (local protocol)")}`);
}
if (pka.present) {
if (pka.verified) {
const keyLabel = record.parsed?.v === "aid2" ? `keyid=${derivePkaKeyid(record.parsed.pka ?? null)?.keyid ?? "unknown"}` : `legacy kid=${pka.kid ?? record.parsed?.kid ?? "unknown"}`;
const isAid2 = record.parsed?.v === "aid2";
const bindingLabel = pka.domainBound === true ? ", domain-bound" : isAid2 && pka.domainBound === false ? ", endpoint-proof only" : "";
lines.push(
`[5/6] PKA handshake ... ${icons.ok} Verified (alg=${pka.alg}, ${keyLabel}${bindingLabel})`
);
} else {
const reason = record.errors.find((e) => e.message.includes("PKA"))?.message ?? "Verification failed";
lines.push(`[5/6] PKA handshake ... ${icons.error} ${reason}`);
}
} else {
lines.push(`[5/6] PKA handshake ... ${icons.info} Not present`);
}
if (downgrade.checked) {
const status = downgrade.status;
if (status === "downgrade" || status === "pka_removed") {
lines.push(`[6/6] Downgrade check ... ${icons.warn} PKA removed`);
} else if (status === "key_rotation" || status === "key_replaced") {
lines.push(`[6/6] Downgrade check ... ${icons.warn} PKA key replaced`);
} else if (status === "pka_added") {
lines.push(`[6/6] Downgrade check ... ${icons.ok} PKA added`);
} else if (status === "version_downgrade") {
lines.push(`[6/6] Downgrade check ... ${icons.warn} AID version downgrade aid2->aid1`);
} else if (status === "binding_loss") {
lines.push(
`[6/6] Downgrade check ... ${icons.warn} Domain-binding lost (endpoint-proof only)`
);
} else if (status === "fallback_well_known_tls") {
lines.push(
`[6/6] Downgrade check ... ${icons.warn} DNS failed; using TLS-hosted fallback metadata (trustSource=well-known-tls)`
);
} else {
lines.push(
`[6/6] Downgrade check ... ${icons.ok} ${status === "first_seen" ? "First seen" : "No change"}`
);
}
} else {
lines.push(`[6/6] Downgrade check ... ${chalk2.gray("Skipped")}`);
}
lines.push("\n--- Summary ---");
if (report.exitCode === 0) {
const warnings = record.warnings.filter((w) => w.code !== "PROTOCOL_SUBDOMAIN_EXISTS");
if (warnings.length > 0) {
lines.push(`${icons.warn} Record is valid but has ${warnings.length} warning(s).`);
for (const warn of warnings) {
lines.push(` - ${warn.message}`);
}
} else {
lines.push(`${icons.ok} Record is valid and secure.`);
}
} else {
lines.push(
`${icons.error} Found ${report.record.errors.length} error(s) and ${record.warnings.length} warning(s).`
);
}
const suggestions = generateActionableSuggestions(report);
if (suggestions.length > 0) {
lines.push("\n--- Recommendations ---");
lines.push(...suggestions);
}
return lines.join("\n");
}
// src/security-state.ts
function applySecurityState(report, previousCacheEntry, downgradePolicy) {
if (!report.record.parsed) return { shouldPersist: false };
if (report.exitCode !== 0 || report.pka.verified === false) {
report.cacheEntry = null;
return { shouldPersist: false };
}
const currentEntry = buildCacheEntryFromReport(report);
const status = classifySecurityChange(previousCacheEntry, currentEntry);
report.downgrade.checked = true;
report.downgrade.previous = previousCacheEntry ? {
pka: previousCacheEntry.pka,
kid: previousCacheEntry.kid,
keyid: previousCacheEntry.keyid ?? null,
version: previousCacheEntry.version ?? null,
trustSource: previousCacheEntry.trustSource ?? "dns"
} : null;
report.downgrade.status = status;
const warningByStatus = {
pka_removed: {
code: "PKA_REMOVED",
message: "Previously present PKA was removed."
},
key_replaced: {
code: "KEY_REPLACED",
message: "Previously observed PKA key was replaced."
},
version_downgrade: {
code: "VERSION_DOWNGRADE",
message: "Previously observed aid2 record is now aid1."
},
fallback_well_known_tls: {
code: "FALLBACK_WELL_KNOWN_TLS",
message: "DNS failed and .well-known supplied TLS-hosted metadata."
},
pka_added: {
code: "PKA_ADDED",
message: "PKA endpoint proof was added since the previous check."
},
binding_loss: {
code: "BINDING_LOSS",
message: "Domain-binding proof was present in the previous check but is now absent (endpoint-proof only)."
}
};
const warning = warningByStatus[status];
if (warning) {
report.record.warnings.push(warning);
}
if (downgradePolicy === "fail" && shouldRejectForFailPolicy(status, previousCacheEntry)) {
report.exitCode = 1003;
report.cacheEntry = null;
return { shouldPersist: false };
}
report.cacheEntry = currentEntry;
return { shouldPersist: true };
}
// src/cli.ts
function readPackageVersion() {
try {
const pkgUrl = new URL("../package.json", import.meta.url);
const fileContents = readFileSync(pkgUrl, "utf8");
const pkg = JSON.parse(fileContents);
return pkg.version ?? "0.0.0";
} catch {
return "0.0.0";
}
}
async function generateEd25519(label, outDir) {
const { publicKey, privateKeyPem } = await generateEd25519KeyPair();
const dir = outDir || path2.join(os.homedir(), ".aid", "keys");
await promises.mkdir(dir, { recursive: true });
const name = (label || "key") + "-ed25519.key";
const privatePath = path2.join(dir, name);
await promises.writeFile(privatePath, privateKeyPem, { mode: 384 });
return { publicKey, privatePath };
}
function createCliProgram() {
const program = new Command();
program.name("aid-doctor").description("CLI tool for Agent Identity & Discovery (AID)").version(readPackageVersion());
function formatError(error, domain) {
if (error instanceof AidError) {
const codeColor = error.code >= 1003 ? chalk2.red : chalk2.yellow;
return [
chalk2.red(`\u274C AID Discovery Failed for ${domain}`),
"",
` Error Code: ${codeColor(error.code)} (${error.errorCode})`,
` Message: ${error.message}`
].join("\n");
}
if (error instanceof Error) {
return [chalk2.red(`\u274C Unexpected Error for ${domain}`), "", ` ${error.message}`].join("\n");
}
return [chalk2.red(`\u274C Unexpected Error for ${domain}`), "", ` ${String(error)}`].join("\n");
}
program.command("check <domain>").description("Check a domain for AID records and display a human-readable report").option("-p, --protocol <protocol>", "Diagnostics hint only; base-first remains canonical").option(
"--probe-proto-subdomain",
"If base TXT is missing and --protocol set, probe _agent._<proto>.<domain>",
false
).option(
"--probe-proto-even-if-base",
"Probe proto subdomain even when base exists (diagnostics only)",
false
).option("-t, --timeout <ms>", "DNS query timeout in milliseconds", "5000").option("--no-fallback", "Disable .well-known fallback on DNS miss").option("--fallback-timeout <ms>", "Timeout for .well-known fetch (ms)", "2000").option("--security-mode <mode>", "Security preset: balanced | strict").option("--dnssec <policy>", "DNSSEC policy: off | prefer | require").option("--pka-policy <policy>", "PKA policy: if-present | require").option("--downgrade-policy <policy>", "Downgrade policy: off | warn | fail").option("--well-known-policy <policy>", "Well-known policy: auto | disable").option("--domain-binding <policy>", "Domain binding policy: off | prefer | require").option("--show-details", "Show TLS/DNSSEC/PKA short details", false).option("--dump-well-known [path]", "On fallback failure, print or save body snippet", false).option(
"--check-downgrade",
"Consult cache and warn when pka has been removed or changed",
false
).option("--no-color", "Disable ANSI color output").option("--code", "Exit with the specific error code on failure (for scripting)").action(
async (domain, options) => {
if (options.noColor) {
chalk2.level = 0;
}
const spinner = ora(`Checking AID record for ${domain}...`).start();
try {
const cache = options.checkDowngrade ? await loadCache() : null;
const previousCacheEntry = cache ? cache.entries[domain] : void 0;
const report = await runCheck(domain, {
protocol: options.protocol,
timeoutMs: Number.parseInt(options.timeout),
allowFallback: options.fallback !== false,
wellKnownTimeoutMs: Number.parseInt(options.fallbackTimeout || "2000"),
securityMode: options.securityMode,
dnssecPolicy: options.dnssec,
pkaPolicy: options.pkaPolicy,
downgradePolicy: options.downgradePolicy,
wellKnownPolicy: options.wellKnownPolicy,
...options.domainBinding ? { domainBindingPolicy: options.domainBinding } : {},
showDetails: options.showDetails,
probeProtoSubdomain: options.probeProtoSubdomain,
probeProtoEvenIfBase: options.probeProtoEvenIfBase,
dumpWellKnownPath: typeof options.dumpWellKnown === "string" ? options.dumpWellKnown : null,
checkDowngrade: false
});
const securityState = cache ? applySecurityState(report, previousCacheEntry, options.downgradePolicy) : null;
if (cache && securityState?.shouldPersist && report.cacheEntry) {
cache.entries[domain] = report.cacheEntry;
await saveCache(cache);
}
spinner.stop();
console.log(formatCheckResult(report));
process.exit(options.code ? report.exitCode : report.exitCode === 0 ? 0 : 1);
} catch (error) {
spinner.stop();
console.log(formatError(error, domain));
if (options.code && error instanceof AidError) {
process.exit(error.code);
} else {
process.exit(1);
}
}
}
);
program.command("json <domain>").description("Check a domain for AID records and output machine-readable JSON").option("-p, --protocol <protocol>", "Diagnostics hint only; base-first remains canonical").option("-t, --timeout <ms>", "DNS query timeout in milliseconds", "5000").option("--no-fallback", "Disable .well-known fallback on DNS miss").option("--fallback-timeout <ms>", "Timeout for .well-known fetch (ms)", "2000").option("--security-mode <mode>", "Security preset: balanced | strict").option("--dnssec <policy>", "DNSSEC policy: off | prefer | require").option("--pka-policy <policy>", "PKA policy: if-present | require").option("--downgrade-policy <policy>", "Downgrade policy: off | warn | fail").option("--well-known-policy <policy>", "Well-known policy: auto | disable").option("--domain-binding <policy>", "Domain binding policy: off | prefer | require").option("--show-details", "Show TLS/DNSSEC/PKA short details", false).option("--dump-well-known [path]", "On fallback failure, print or save body snippet", false).option(
"--check-downgrade",
"Consult cache and warn when pka has been removed or changed",
false
).option("--no-color", "Disable ANSI color output").option("--code", "Exit with the specific error code on failure (for scripting)").action(
async (domain, options) => {
try {
const cache = options.checkDowngrade ? await loadCache() : null;
const previousCacheEntry = cache ? cache.entries[domain] : void 0;
const report = await runCheck(domain, {
protocol: options.protocol,
timeoutMs: Number.parseInt(options.timeout),
allowFallback: options.fallback !== false,
wellKnownTimeoutMs: Number.parseInt(options.fallbackTimeout || "2000"),
securityMode: options.securityMode,
dnssecPolicy: options.dnssec,
pkaPolicy: options.pkaPolicy,
downgradePolicy: options.downgradePolicy,
wellKnownPolicy: options.wellKnownPolicy,
...options.domainBinding ? { domainBindingPolicy: options.domainBinding } : {},
showDetails: options.showDetails || false,
probeProtoSubdomain: false,
// JSON command doesn't support proto probing for now
probeProtoEvenIfBase: false,
dumpWellKnownPath: typeof options.dumpWellKnown === "string" ? options.dumpWellKnown : null,
checkDowngrade: false
});
const securityState = cache ? applySecurityState(report, previousCacheEntry, options.downgradePolicy) : null;
if (cache && securityState?.shouldPersist && report.cacheEntry) {
cache.entries[domain] = report.cacheEntry;
await saveCache(cache);
}
console.log(JSON.stringify(report, null, 2));
process.exit(options.code ? report.exitCode : report.exitCode === 0 ? 0 : 1);
} catch (error) {
if (error instanceof AidError) {
console.log(
JSON.stringify(
{
success: false,
domain,
error: {
code: error.code,
errorCode: error.errorCode,
message: error.message
},
timestamp: (/* @__PURE__ */ new Date()).toISOString()
},
null,
2
)
);
process.exit(options.code ? error.code : 1);
} else if (error instanceof Error) {
console.log(
JSON.stringify(
{
success: false,
domain,
error: {
code: 1,
errorCode: "UNKNOWN_ERROR",
message: error.message
},
timestamp: (/* @__PURE__ */ new Date()).toISOString()
},
null,
2
)
);
process.exit(1);
} else {
console.log(
JSON.stringify(
{
success: false,
domain,
error: {
code: 1,
errorCode: "UNKNOWN_ERROR",
message: String(error)
},
timestamp: (/* @__PURE__ */ new Date()).toISOString()
},
null,
2
)
);
process.exit(1);
}
}
}
);
program.command("pka").description("PKA key helpers").addCommand(
new Command("generate").description(
"Generate a new Ed25519 keypair and print the AID v2 public key (base64url JWK x); private key saved to ~/.aid/keys"
).option("--label <name>", "Key label (filename prefix)").option("--out <dir>", "Output directory for private key").option("--print-private", "Also print private key PEM to stdout (not recommended)", false).action(async (opts) => {
const { publicKey, privatePath } = await generateEd25519(opts.label, opts.out);
console.log(publicKey);
console.error(`Saved private key to ${privatePath}`);
if (opts.printPrivate) {
console.error("Printing private key was requested; handle with care.");
}
})
).addCommand(
new Command("verify").description("Verify an AID v2 PKA public key string").requiredOption("--key <pka>", "base64url JWK x for a raw Ed25519 public key").action((opts) => {
const res = verifyPka(opts.key);
if (res.valid) console.log("\u2705 valid");
else {
console.log("\u274C invalid" + (res.reason ? `: ${res.reason}` : ""));
process.exitCode = 1;
}
})
);
program.command("generate").description("Run an interactive prompt to generate a new AID record").option("--save-draft <path>", "Save the generated record to a file").action(async (opts) => {
console.log(chalk2.bold.cyan("AID Record Generator"));
console.log(
chalk2.gray("This tool will guide you through creating a valid DNS TXT record.\n")
);
const answers = await inquirer.prompt([
{
type: "input",
name: "domain",
message: "What is the primary domain for your agent?",
validate: (input) => input ? true : "Domain cannot be empty."
},
{
type: "input",
name: "uri",
message: "What is the full URI of your agent's endpoint?",
validate: (input) => {
try {
new URL(input);
return true;
} catch {
return "Please enter a valid URI (e.g., https://api.example.com/agent).";
}
}
},
{
type: "list",
name: "proto",
message: "Select the communication protocol:",
choices: Object.keys(PROTOCOL_TOKENS),
default: "mcp"
},
{
type: "list",
name: "auth",
message: "Select the authentication method (if any):",
choices: ["none", ...Object.keys(AUTH_TOKENS)],
filter: (val) => val === "none" ? "" : val
},
{
type: "input",
name: "desc",
message: "Enter a short description (optional):",
validate: (input) => {
if (!input) return true;
const byteLength = new TextEncoder().encode(input).length;
return byteLength <= 60 || `Description is ${byteLength} bytes (max 60).`;
}
},
{
type: "input",
name: "docs",
message: "Docs URL (https, optional):",
validate: (input) => {
if (!input) return true;
try {
const u = new URL(input);
return u.protocol === "https:" || "Docs must be https:// URL";
} catch {
return "Docs must be https:// URL";
}
}
},
{
type: "input",
name: "dep",
message: "Deprecation date (ISO 8601 UTC, e.g., 2026-01-01T00:00:00Z, optional):",
validate: (input) => {
if (!input) return true;
return /Z$/.test(input) && !Number.isNaN(Date.parse(input)) || "Must be ISO 8601 UTC with Z";
}
},
{
type: "confirm",
name: "addPka",
message: "Add PKA endpoint proof now?",
default: false
},
{
type: "input",
name: "pka",
message: "PKA public key (base64url JWK x for Ed25519, AID v2):",
when: (a) => Boolean(a.addPka),
validate: (input) => {
if (!input) return "PKA key required when adding PKA";
const r = verifyPka(input);
return r.valid || r.reason || "Invalid";
}
}
]);
const formData = answers;
const txtRecord = buildTxtRecordVariant(formData, true);
const validation = validateTxtRecord(txtRecord);
console.log(chalk2.green("\n--- Generation Complete ---\n"));
console.log(chalk2.bold("Host:"));
console.log(chalk2.yellow(`_agent.${formData.domain}`));
console.log("");
console.log(chalk2.bold("Type:"));
console.log(chalk2.yellow("TXT"));
console.log("");
console.log(chalk2.bold("Value:"));
console.log(chalk2.yellow(txtRecord));
console.log("");
if (validation.isValid) {
try {
await clipboardy.write(txtRecord);
console.log(
chalk2.bold.green("\u2705 Success! The TXT record value has been copied to your clipboard.")
);
} catch {
console.log(chalk2.red("Could not copy to clipboard. Please copy the value manually."));
}
if (opts.saveDraft) {
try {
await writeFile(opts.saveDraft, txtRecord, "utf8");
console.log(chalk2.green(`\u{1F4BE} Draft saved to ${opts.saveDraft}`));
} catch (error) {
if (error instanceof Error) {
console.log(chalk2.red(`Could not save draft: ${error.message}`));
} else {
console.log(chalk2.red(`Could not save draft: ${String(error)}`));
}
}
}
} else {
console.log(chalk2.bold.red(`\u{1F525} Validation Failed: ${validation.error}`));
}
});
program.command("version").description("Show version information").action(async () => {
const version = readPackageVersion();
console.log(
[
chalk2.bold("aid-doctor") + " - Agent Identity & Discovery CLI",
`Version: ${chalk2.green(version)}`,
`Node: ${chalk2.gray(process.version)}`,
`Platform: ${chalk2.gray(process.platform)}`,
"",
"For more information, visit:",
chalk2.blue("https://aid.agentcommunity.org")
].join("\n")
);
});
return program;
}
function isDirectCliInvocation(argvPath, modulePath = fileURLToPath(import.meta.url)) {
if (!argvPath) {
return false;
}
const resolvedArgvPath = path2.resolve(argvPath);
try {
return realpathSync(modulePath) === realpathSync(resolvedArgvPath);
} catch {
return modulePath === resolvedArgvPath;
}
}
var isDirectCliRun = isDirectCliInvocation(process.argv[1]);
if (isDirectCliRun) {
createCliProgram().parse();
}
export { createCliProgram, isDirectCliInvocation };
//# sourceMappingURL=cli.js.map
//# sourceMappingURL=cli.js.map