/**
* βοΈ Config Commands
* Setup and verify configuration
*/
import inquirer from "inquirer";
import chalk from "chalk";
import { existsSync, writeFileSync, readFileSync } from "fs";
import { resolve, dirname } from "path";
import { fileURLToPath } from "url";
import { getGitHubService } from "../../services/github.js";
import { getConfig, isConfigured } from "../../utils/config.js";
import {
showBanner,
showSuccess,
showError,
showInfo,
showBox,
showConfigStatus,
createStyledSpinner,
} from "../ui/display.js";
import type { Personality } from "../../types/index.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const envPath = resolve(__dirname, "../../../.env");
/**
* Interactive setup wizard
*/
export async function setupInteractive(): Promise<void> {
showBanner();
showBox(
[
chalk.bold("Welcome to the Sean-MCP Setup Wizard! π§ββοΈ"),
"",
"Let's get you configured and ready to automate some PRs.",
"",
chalk.yellow("You'll need:"),
" β’ A GitHub Personal Access Token (PAT)",
" β’ Your GitHub username",
].join("\n"),
{ title: "π Setup", borderColor: "cyan" },
);
// Check if .env exists
const envExists = existsSync(envPath);
let existingEnv: Record<string, string> = {};
if (envExists) {
const { overwrite } = await inquirer.prompt<{ overwrite: boolean }>([
{
type: "confirm",
name: "overwrite",
message: ".env file already exists. Overwrite?",
default: false,
},
]);
if (!overwrite) {
showInfo("Setup cancelled. Your existing .env is preserved.");
return;
}
// Parse existing .env to use as defaults
try {
const content = readFileSync(envPath, "utf-8");
content.split("\n").forEach((line) => {
const [key, ...valueParts] = line.split("=");
if (key && !key.startsWith("#")) {
existingEnv[key.trim()] = valueParts.join("=").trim();
}
});
} catch {
// Ignore parse errors
}
}
// Prompt for configuration
const answers = await inquirer.prompt<{
githubToken: string;
githubUsername: string;
personality: Personality;
webhookPort: string;
baseBranch: string;
draftPRs: boolean;
}>([
{
type: "password",
name: "githubToken",
message: "π GitHub Personal Access Token:",
mask: "*",
validate: (input: string) => {
if (!input) return "Token is required";
if (!input.startsWith("ghp_") && !input.startsWith("github_pat_")) {
return "Token should start with ghp_ or github_pat_";
}
return true;
},
},
{
type: "input",
name: "githubUsername",
message: "π€ GitHub Username:",
default: existingEnv["GITHUB_USERNAME"] || "",
validate: (input: string) => input.length > 0 || "Username is required",
},
{
type: "list",
name: "personality",
message: "π CLI Personality:",
choices: [
{ name: "π₯ Chaotic - Fun, energetic, memes", value: "chaotic" },
{ name: "πΌ Professional - Clean, minimal", value: "professional" },
{ name: "π§ Zen - Calm, philosophical", value: "zen" },
],
default: existingEnv["CLI_PERSONALITY"] || "chaotic",
},
{
type: "input",
name: "webhookPort",
message: "π Webhook Server Port:",
default: existingEnv["WEBHOOK_PORT"] || "3847",
validate: (input: string) => {
const port = parseInt(input, 10);
if (isNaN(port) || port < 1 || port > 65535) {
return "Please enter a valid port number (1-65535)";
}
return true;
},
},
{
type: "input",
name: "baseBranch",
message: "πΏ Default base branch for PRs:",
default: existingEnv["DEFAULT_PR_BASE_BRANCH"] || "main",
},
{
type: "confirm",
name: "draftPRs",
message: "π Create PRs as drafts by default?",
default: existingEnv["DEFAULT_PR_DRAFT"] === "true" || false,
},
]);
// Generate .env content
const envContent = `# π GitHub Configuration
# Generated by Sean-MCP Setup Wizard
GITHUB_TOKEN=${answers.githubToken}
GITHUB_USERNAME=${answers.githubUsername}
# π Webhook Server Configuration
WEBHOOK_PORT=${answers.webhookPort}
WEBHOOK_SECRET=${generateSecret()}
# π¨ CLI Preferences
CLI_PERSONALITY=${answers.personality}
# π Default PR Settings
DEFAULT_PR_BASE_BRANCH=${answers.baseBranch}
DEFAULT_PR_DRAFT=${answers.draftPRs}
`;
// Write .env file
try {
writeFileSync(envPath, envContent, "utf-8");
showSuccess(".env file created successfully!");
} catch (error) {
showError(
"Failed to write .env file",
error instanceof Error ? error.message : "Unknown error",
);
return;
}
// Verify the configuration works
const spinner = createStyledSpinner("Verifying GitHub connection");
spinner.start();
try {
// Temporarily set env vars for verification
process.env["GITHUB_TOKEN"] = answers.githubToken;
process.env["GITHUB_USERNAME"] = answers.githubUsername;
const github = getGitHubService(answers.githubToken);
const connection = await github.verifyConnection();
if (connection.valid) {
spinner.success({
text: `Connected to GitHub as ${chalk.cyan(connection.username)}`,
});
showBox(
[
chalk.green("β Setup complete!"),
"",
"You can now use Sean-MCP to:",
` ${chalk.cyan("sean-mcp pr create")} - Create a new PR`,
` ${chalk.cyan("sean-mcp pr update")} - Update a PR description`,
` ${chalk.cyan("sean-mcp pr list")} - List PRs`,
` ${chalk.cyan("sean-mcp config status")} - Check config`,
"",
chalk.gray("Run sean-mcp --help for more commands"),
].join("\n"),
{ title: "π Ready to Rock!", borderColor: "green" },
);
} else {
spinner.error({ text: "GitHub connection failed" });
showError("Could not verify GitHub connection", connection.error);
showInfo("Your .env was saved. Please verify your token and try again.");
}
} catch (error) {
spinner.error({ text: "Verification failed" });
showError(
"Connection test failed",
error instanceof Error ? error.message : "Unknown error",
);
}
}
/**
* Show current config status
*/
export async function showStatus(): Promise<void> {
showBanner();
const config = getConfig();
const configured = isConfigured();
if (!configured) {
showConfigStatus(false);
return;
}
// Verify connection
const spinner = createStyledSpinner("Checking GitHub connection");
spinner.start();
try {
const github = getGitHubService();
const connection = await github.verifyConnection();
if (connection.valid) {
spinner.success({ text: "GitHub connected" });
showConfigStatus(true, connection.username);
// Show additional config info
showBox(
[
chalk.bold("Current Settings:"),
"",
`${chalk.gray("Personality:")} ${config.cli.personality}`,
`${chalk.gray("Default Base Branch:")} ${config.defaults.baseBranch}`,
`${chalk.gray("Draft PRs by Default:")} ${config.defaults.draft ? "Yes" : "No"}`,
`${chalk.gray("Webhook Port:")} ${config.webhook.port}`,
].join("\n"),
{ title: "βοΈ Settings", borderColor: "blue" },
);
} else {
spinner.error({ text: "Connection failed" });
showError("GitHub authentication failed", connection.error);
}
} catch (error) {
spinner.error({ text: "Check failed" });
showError(
"Failed to verify configuration",
error instanceof Error ? error.message : "Unknown error",
);
}
}
/**
* Generate a random secret for webhooks
*/
function generateSecret(): string {
const chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < 32; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
export default {
setupInteractive,
showStatus,
};