diff --git a/src/cli.ts b/src/cli.ts index d9fb2ac..407ef17 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -54,21 +54,9 @@ program force: options.force, }); - if (result.success) { - console.log(`Closed session: ${result.sessionName}`); - if (result.cleanupPerformed) { - console.log(` Removed ${result.worktreesRemoved?.length || 0} worktrees`); - if (result.tagsCreated && result.tagsCreated.length > 0) { - console.log(` Created backup tags: ${result.tagsCreated.join(", ")}`); - } - if (result.warnings && result.warnings.length > 0) { - console.log(` Warnings: ${result.warnings.join("; ")}`); - } - if (result.branchesDeleted && result.branchesDeleted.length > 0) { - console.log(` Deleted ${result.branchesDeleted.length} branches`); - } - } - } else { +if (result.success) { + console.log(result.instructions); + } else { console.error(`Failed: ${result.error}`); process.exit(1); } diff --git a/src/core/close.ts b/src/core/close.ts index fdcbd27..81cf080 100644 --- a/src/core/close.ts +++ b/src/core/close.ts @@ -1,6 +1,7 @@ import * as readline from "node:readline"; +import chalk from 'chalk'; // removed console usage import type { CloseSessionOptions, CloseSessionResult } from "../types"; -import { runCommand, getSessionPath, getWorktreesForSession } from "./utils"; +import { runCommand, getSessionPath, getWorktreesForSession, sanitizeName } from "./utils"; /** * Prompts the user for input on the terminal. @@ -45,33 +46,42 @@ async function promptUser(question: string): Promise { */ export async function closeMultiModel(options: CloseSessionOptions): Promise { const worktreesRemoved: string[] = []; + const instructionsArr: string[] = []; const branchesDeleted: string[] = []; const tagsCreated: string[] = []; - const warnings: string[] = []; + + const safeSessionName = sanitizeName(options.sessionName); try { // Check if session exists - const { ok: sessionExists } = await runCommand(["tmux", "has-session", "-t", options.sessionName]); + const { ok: sessionExists } = await runCommand(["tmux", "has-session", "-t", safeSessionName]); // Kill tmux session if it exists if (sessionExists) { - await runCommand(["tmux", "kill-session", "-t", options.sessionName]); + await runCommand(["tmux", "kill-session", "-t", safeSessionName]); + instructionsArr.push(chalk.green(`Closed session '${safeSessionName}'`)); + } else { + instructionsArr.push(chalk.yellow(`Session '${safeSessionName}' does not exist`)); } // Get list of worktrees for this session before killing tmux - const worktrees = await getWorktreesForSession(options.sessionName); + const worktrees = await getWorktreesForSession(safeSessionName); // If cleanup requested and not forced, ask for confirmation if (options.cleanupWorktrees && !options.force && worktrees.length > 0) { - console.log(`\nThe following worktrees and branches will be removed:`); - worktrees.forEach((wt) => console.log(` - ${wt.path} (branch: ${wt.branch})`)); + instructionsArr.push(chalk.red('The following worktrees and branches will be removed:')); + worktrees.forEach((wt) => { + const msg = ` - ${wt.path} (branch: ${wt.branch})`; + instructionsArr.push(msg.trim()); + }); - const answer = await promptUser("\nDo you want to proceed? (y/N): "); + const answer = await promptUser(chalk.bold("\nDo you want to proceed? (y/N): ")); if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") { return { success: false, - sessionName: options.sessionName, + sessionName: safeSessionName, error: "Cleanup cancelled by user", + instructions: instructionsArr.join("\n"), }; } } @@ -94,20 +104,19 @@ export async function closeMultiModel(options: CloseSessionOptions): Promise 0; + if (cleanupPerformed) { + instructionsArr.push(chalk.dim(`Removed ${worktreesRemoved.length} worktrees, alongwith their branches.`)); + if (tagsCreated.length > 0) { + instructionsArr.push(chalk.dim(`Created archive tags: ${tagsCreated.join(", ")}.`)); + } + } } - return { - success: true, - sessionName: options.sessionName, - cleanupPerformed, - worktreesRemoved, - branchesDeleted, - tagsCreated, - warnings, - }; +return { + success: true, + sessionName: safeSessionName, + cleanupPerformed, + worktreesRemoved, + branchesDeleted, + tagsCreated, + instructions: instructionsArr.join("\n"), + }; } catch (error) { - return { - success: false, - sessionName: options.sessionName, - error: String(error), - worktreesRemoved, - branchesDeleted, - tagsCreated, - warnings, - }; +return { + success: false, + sessionName: safeSessionName, + error: String(error), + worktreesRemoved, + branchesDeleted, + tagsCreated, + instructions: instructionsArr.join("\n"), + }; } } diff --git a/src/core/open.ts b/src/core/open.ts index 2ddc13d..b400530 100644 --- a/src/core/open.ts +++ b/src/core/open.ts @@ -154,7 +154,9 @@ export async function openMultiModel(options: MultiModelOptions): Promise 0) errorMsg += ` Cleanup errors: ${undoErrors.join(", ")}`; + if (undoErrors.length > 0) { + errorMsg += ` Cleanup errors: ${undoErrors.join(", ")}`; + } return { success: false, sessionName, error: errorMsg }; } } else { @@ -173,7 +175,9 @@ export async function openMultiModel(options: MultiModelOptions): Promise 0) failedModels.push(`cleanup failed for ${plan.model}: ${undoErrors.join(", ")}`); + if (undoErrors.length > 0) { + failedModels.push(`cleanup failed for ${plan.model}: ${undoErrors.join(", ")}`); + } continue; } } diff --git a/src/tools/close.ts b/src/tools/close.ts index 27ef355..0ed74b9 100644 --- a/src/tools/close.ts +++ b/src/tools/close.ts @@ -25,17 +25,6 @@ export const closeTool = tool({ return result.error!; } - let details = ""; - if (result.cleanupPerformed) { - details += ` Removed ${result.worktreesRemoved?.length || 0} worktrees.`; - if (result.tagsCreated && result.tagsCreated.length > 0) { - details += ` Created backup tags: ${result.tagsCreated.join(", ")}.`; - } - if (result.warnings && result.warnings.length > 0) { - details += ` Warnings: ${result.warnings.join("; ")}.`; - } - } - - return `Session "${result.sessionName}" has been closed.${details}`; + return result.instructions; }, }); diff --git a/src/types.ts b/src/types.ts index 2bb3451..eb1ed01 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,6 +44,8 @@ export interface CloseSessionOptions { * Result returned after attempting to close a multi-model session. */ export interface CloseSessionResult { + /** Console messages and instructions. */ + instructions: string; /** Whether the close succeeded. */ success: boolean; /** The session name that was closed. */ @@ -58,8 +60,7 @@ export interface CloseSessionResult { branchesDeleted?: string[]; /** Archive tags that were created before branch deletion. */ tagsCreated?: string[]; - /** Warning messages. */ - warnings?: string[]; + } /** diff --git a/tests/close.test.ts b/tests/close.test.ts index 282e54f..86e14e6 100644 --- a/tests/close.test.ts +++ b/tests/close.test.ts @@ -98,7 +98,7 @@ describe("closeMultiModel", () => { expect(result.worktreesRemoved).toEqual([worktreePath]); expect(result.branchesDeleted).toEqual(["opencode/test-session/model"]); expect(result.tagsCreated?.length).toBe(1); - expect(result.warnings).toEqual([]); + // Ensure removal commands were executed expect(executedCommands).toContain(`git worktree remove -f ${worktreePath}`); expect(executedCommands).toContain(`git branch -D opencode/test-session/model`); @@ -153,7 +153,7 @@ describe("closeMultiModel", () => { expect(result.worktreesRemoved).toEqual([worktreePath]); expect(result.branchesDeleted).toEqual(["opencode/test-session/model"]); expect(result.tagsCreated?.length).toBe(1); - expect(result.warnings).toEqual([]); + // Verify the expected git commands were executed expect(executedCommands).toContain(`git worktree remove -f ${worktreePath}`); @@ -183,8 +183,8 @@ describe("closeMultiModel", () => { global.Date = OriginalDate; expect(result.success).toBe(true); - expect(result.warnings?.length).toBe(1); - expect(result.warnings?.[0]).toContain("Failed to create tag"); + // Expect the warning message to be captured in instructions + expect(result.instructions?.some(msg => msg.includes('Failed to create tag'))).toBe(true); expect(result.tagsCreated).toEqual([]); });