@kya-os/cli
Version:
CLI for MCP-I setup and management
167 lines ⢠8.76 kB
JavaScript
import chalk from "chalk";
import ora from "ora";
import inquirer from "inquirer";
import { showSuccess, showError, showInfo } from "../utils/prompts.js";
import { hasDelegation, isKTAAvailable } from "../utils/kta-api.js";
import { loadCurrentIdentity, archiveOldKey, generateNewIdentity, saveIdentityToDev, generateAuditLine, } from "../utils/identity-manager.js";
import contractsCli from "@kya-os/contracts/cli";
const { CLI_EXIT_CODES } = contractsCli;
/**
* Rotate identity keys with dev/prod mode handling
* Requirements: 2.3, 2.4, 2.5, 2.6
*/
export async function rotate(options = {}) {
console.log(chalk.cyan("\nš Rotating Identity Keys\n"));
const { force = false, reason, newDid = false, yes = false } = options;
try {
// Determine environment
const isDev = !process.env.AGENT_PRIVATE_KEY;
const mode = isDev ? "dev" : "prod";
console.log(`${chalk.bold("Environment:")} ${chalk.gray(mode)}`);
if (reason) {
console.log(`${chalk.bold("Reason:")} ${chalk.gray(reason)}`);
}
// Load current identity
const currentIdentity = loadCurrentIdentity();
if (!currentIdentity) {
showError("No current identity found. Run 'mcpi init' first.");
process.exit(CLI_EXIT_CODES.NOIDENTITY);
}
console.log(`\n${chalk.bold("Current Identity:")}`);
console.log(` DID: ${chalk.gray(currentIdentity.did)}`);
console.log(` Key ID: ${chalk.gray(currentIdentity.kid)}`);
// Production mode validation
if (!isDev) {
console.log(`\n${chalk.bold("š Production Mode Detected")}`);
if (!force) {
// Check delegation via KTA lookup by DID/KID (Requirement 2.4)
const spinner = ora("Checking delegation status via KTA...").start();
try {
const ktaAvailable = await isKTAAvailable();
if (!ktaAvailable) {
spinner.fail("KTA is not reachable");
showError("Cannot verify delegation status - KTA is unavailable");
console.log(`\n${chalk.gray("To proceed:")}`);
console.log(`${chalk.gray("⢠Check your internet connection")}`);
console.log(`${chalk.gray("⢠Verify KTA service status")}`);
console.log(`${chalk.gray("⢠Or use --force with --yes for emergency rotation")}`);
process.exit(CLI_EXIT_CODES.CONFIG);
}
const delegationExists = await hasDelegation(currentIdentity.did, currentIdentity.kid);
if (delegationExists) {
spinner.succeed("Delegation verified via KTA");
}
else {
spinner.fail("No delegation found for current key");
showError("Key rotation requires delegation to new key ID or --force flag");
console.log(`\n${chalk.gray("To proceed:")}`);
console.log(`${chalk.gray("⢠Set up delegation via KTA for the new key")}`);
console.log(`${chalk.gray("⢠Or use --force with --yes for emergency rotation")}`);
process.exit(CLI_EXIT_CODES.CONFIG);
}
}
catch (error) {
spinner.fail("Failed to check delegation");
showError(`Delegation check failed: ${error}`);
console.log(`\n${chalk.gray("To proceed:")}`);
console.log(`${chalk.gray("⢠Check your KYA_VOUCHED_API_KEY environment variable")}`);
console.log(`${chalk.gray("⢠Verify your network connection")}`);
console.log(`${chalk.gray("⢠Or use --force with --yes for emergency rotation")}`);
process.exit(CLI_EXIT_CODES.CONFIG);
}
}
else {
// Force mode requires confirmation (Requirement 2.5)
if (!yes) {
console.log(`\n${chalk.red("ā ļø FORCE ROTATION WARNING")}`);
console.log(`${chalk.gray("This will rotate keys without delegation check.")}`);
console.log(`${chalk.gray("Current DID:")} ${chalk.yellow(currentIdentity.did)}`);
console.log(`${chalk.gray("Current Key ID:")} ${chalk.yellow(currentIdentity.kid)}`);
const { confirmed } = await inquirer.prompt([
{
type: "confirm",
name: "confirmed",
message: "Are you sure you want to force rotate keys?",
default: false,
},
]);
if (!confirmed) {
console.log(chalk.yellow("Key rotation cancelled."));
return;
}
}
}
}
// Development mode - archive old key (Requirement 2.3)
let archiveFile;
if (isDev) {
const spinner = ora("Archiving old key...").start();
try {
archiveFile = archiveOldKey(currentIdentity, reason);
spinner.succeed(`Old key archived to: ${archiveFile}`);
}
catch (error) {
spinner.fail("Failed to archive old key");
throw error;
}
}
// Generate new keys
const spinner = ora("Generating new keys...").start();
try {
// Generate new identity (keeping same DID unless --new-did)
const preserveDID = newDid ? undefined : currentIdentity.did;
const newIdentity = generateNewIdentity(preserveDID);
// Save new identity in development mode
if (isDev) {
saveIdentityToDev(newIdentity);
}
spinner.succeed("New keys generated successfully!");
console.log(`\n${chalk.bold("New Identity:")}`);
console.log(` DID: ${chalk.gray(newIdentity.did)}`);
console.log(` Key ID: ${chalk.gray(newIdentity.kid)}`);
// Emit audit line (Requirement 2.6)
const delegated = !isDev && !force;
const auditLine = generateAuditLine(currentIdentity.kid, newIdentity.kid, newIdentity.did, mode, delegated, force);
console.log(`\n${chalk.bold("š Audit Log:")}`);
console.log(` ${chalk.gray(auditLine)}`);
showSuccess("Key rotation completed successfully!");
console.log(`\n${chalk.bold("š Next Steps:")}`);
if (isDev) {
console.log(` ${chalk.gray("⢠Restart your development server")}`);
console.log(` ${chalk.gray("⢠Test with:")} ${chalk.cyan("mcpi dev")}`);
if (archiveFile) {
console.log(` ${chalk.gray("⢠Old key archived at:")} ${chalk.cyan(archiveFile)}`);
}
}
else {
console.log(` ${chalk.gray("⢠Update production environment variables:")}`);
console.log(` ${chalk.gray("AGENT_PRIVATE_KEY=")}${chalk.yellow(newIdentity.privateKey)}`);
console.log(` ${chalk.gray("AGENT_KEY_ID=")}${chalk.yellow(newIdentity.kid)}`);
if (newDid) {
console.log(` ${chalk.gray("AGENT_DID=")}${chalk.yellow(newIdentity.did)}`);
}
console.log(` ${chalk.gray("⢠Deploy updated configuration")}`);
console.log(` ${chalk.gray("⢠Verify with:")} ${chalk.cyan("mcpi status")}`);
}
if (!newDid) {
showInfo("DID remained unchanged. Use --new-did to generate a new DID.");
}
if (newDid && isDev) {
showInfo("New DID generated. You may need to re-register with KTA.");
}
}
catch (error) {
spinner.fail("Key rotation failed");
throw error;
}
}
catch (error) {
showError(`Key rotation failed: ${error.message}`);
console.log(`\n${chalk.gray("You can:")}`);
console.log(`${chalk.gray("⢠Check your current identity with:")} ${chalk.cyan("mcpi status")}`);
console.log(`${chalk.gray("⢠Try with --force flag (production only)")}`);
console.log(`${chalk.gray("⢠Check file permissions and network connectivity")}`);
process.exit(CLI_EXIT_CODES.GENERAL_ERROR);
}
}
//# sourceMappingURL=rotate.js.map