/** * Secret Commands * Manage repository and organization secrets from the CLI */ import chalk from "chalk"; import { Command } from "commander"; import inquirer from "inquirer"; import ora from "ora"; import { simpleGit } from "simple-git"; import { deleteWithAuth, getWithAuth, postWithAuth } from "../../lib/api.js"; import { getConfig } from "../../lib/config.js"; import { getRepoInfoFromGit } from "../../lib/git.js"; const git = simpleGit(); interface Secret { name: string; createdAt: string; updatedAt: string; } export const secretCommands = new Command("secret").description( "Manage secrets", ); // Get repo info from git remote async function getRepoInfo() { return getRepoInfoFromGit(git); } // Secret Set secretCommands .command("set ") .description("Set a secret") .option("-b, --body ", "Secret value (not recommended, use stdin)") .option("-o, --org ", "Set organization secret instead of repo secret") .option("-e, --env ", "Set environment-specific secret") .action(async (name: string, options) => { const config = getConfig(); if (!config.token) { console.error(chalk.hex("#ff5555")("Not logged in. Run 'och auth login' first.")); process.exit(1); } try { let value = options.body; if (!value) { // Check if stdin has data (piped input) if (!process.stdin.isTTY) { const chunks: Buffer[] = []; for await (const chunk of process.stdin) { chunks.push(chunk); } value = Buffer.concat(chunks).toString().trim(); } else { // Interactive prompt const answers = await inquirer.prompt([ { type: "password", name: "value", message: `Enter value for ${name}:`, mask: "*", }, ]); value = answers.value; } } if (!value) { console.error(chalk.hex("#ff5555")("Secret value cannot be empty")); process.exit(1); } const spinner = ora(`Setting secret ${name}...`).start(); let endpoint: string; if (options.org) { endpoint = `/api/orgs/${options.org}/actions/secrets/${name}`; } else { const repoInfo = await getRepoInfo(); if (!repoInfo) { spinner.fail("Could not determine repository"); process.exit(1); } const envPart = options.env ? `/environments/${options.env}` : ""; endpoint = `/api/repos/${repoInfo.owner}/${repoInfo.repo}${envPart}/actions/secrets/${name}`; } await postWithAuth(endpoint, { value }); spinner.succeed(`Set secret ${chalk.hex("#8be9fd")(name)}`); } catch (error) { console.error(chalk.hex("#ff5555")("Failed to set secret")); console.error( chalk.hex("#ff5555")(error instanceof Error ? error.message : "Unknown error"), ); process.exit(1); } }); // Secret List secretCommands .command("list") .alias("ls") .description("List secrets") .option("-o, --org ", "List organization secrets") .option("-e, --env ", "List environment secrets") .action(async (options) => { const config = getConfig(); if (!config.token) { console.error(chalk.hex("#ff5555")("Not logged in. Run 'och auth login' first.")); process.exit(1); } const spinner = ora("Fetching secrets...").start(); try { let endpoint: string; let context: string; if (options.org) { endpoint = `/api/orgs/${options.org}/actions/secrets`; context = `organization ${options.org}`; } else { const repoInfo = await getRepoInfo(); if (!repoInfo) { spinner.fail("Could not determine repository"); process.exit(1); } const envPart = options.env ? `/environments/${options.env}` : ""; endpoint = `/api/repos/${repoInfo.owner}/${repoInfo.repo}${envPart}/actions/secrets`; context = options.env ? `environment ${options.env}` : `repository ${repoInfo.owner}/${repoInfo.repo}`; } const result = await getWithAuth<{ data: Secret[] }>(endpoint); spinner.stop(); if (result.data.length === 0) { console.log(chalk.hex("#6272a4")(`No secrets found for ${context}`)); return; } console.log(chalk.bold(`\nšŸ” Secrets for ${context}\n`)); for (const secret of result.data) { console.log(` ${chalk.hex("#8be9fd")(secret.name)}`); console.log( chalk.hex("#6272a4")( ` Updated ${new Date(secret.updatedAt).toLocaleDateString()}`, ), ); } console.log(""); } catch (error) { spinner.fail("Failed to list secrets"); console.error( chalk.hex("#ff5555")(error instanceof Error ? error.message : "Unknown error"), ); process.exit(1); } }); // Secret Delete secretCommands .command("delete ") .alias("rm") .description("Delete a secret") .option("-o, --org ", "Delete organization secret") .option("-e, --env ", "Delete environment secret") .option("-y, --yes", "Skip confirmation") .action(async (name: string, options) => { const config = getConfig(); if (!config.token) { console.error(chalk.hex("#ff5555")("Not logged in. Run 'och auth login' first.")); process.exit(1); } try { if (!options.yes) { const { confirm } = await inquirer.prompt([ { type: "confirm", name: "confirm", message: `Delete secret ${name}?`, default: false, }, ]); if (!confirm) { console.log(chalk.hex("#6272a4")("Cancelled.")); return; } } const spinner = ora(`Deleting secret ${name}...`).start(); let endpoint: string; if (options.org) { endpoint = `/api/orgs/${options.org}/actions/secrets/${name}`; } else { const repoInfo = await getRepoInfo(); if (!repoInfo) { spinner.fail("Could not determine repository"); process.exit(1); } const envPart = options.env ? `/environments/${options.env}` : ""; endpoint = `/api/repos/${repoInfo.owner}/${repoInfo.repo}${envPart}/actions/secrets/${name}`; } await deleteWithAuth(endpoint); spinner.succeed(`Deleted secret ${chalk.hex("#8be9fd")(name)}`); } catch (error) { console.error(chalk.hex("#ff5555")("Failed to delete secret")); console.error( chalk.hex("#ff5555")(error instanceof Error ? error.message : "Unknown error"), ); process.exit(1); } }); export default secretCommands;