mirror of
https://github.com/bendtherules/opencode-multi-model.git
synced 2026-08-19 00:33:20 +00:00
build: color-code open and close outputs
This commit is contained in:
+1
-13
@@ -55,19 +55,7 @@ program
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log(`Closed session: ${result.sessionName}`);
|
console.log(result.instructions);
|
||||||
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 {
|
} else {
|
||||||
console.error(`Failed: ${result.error}`);
|
console.error(`Failed: ${result.error}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
+33
-18
@@ -1,6 +1,7 @@
|
|||||||
import * as readline from "node:readline";
|
import * as readline from "node:readline";
|
||||||
|
import chalk from 'chalk'; // removed console usage
|
||||||
import type { CloseSessionOptions, CloseSessionResult } from "../types";
|
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.
|
* Prompts the user for input on the terminal.
|
||||||
@@ -45,33 +46,42 @@ async function promptUser(question: string): Promise<string> {
|
|||||||
*/
|
*/
|
||||||
export async function closeMultiModel(options: CloseSessionOptions): Promise<CloseSessionResult> {
|
export async function closeMultiModel(options: CloseSessionOptions): Promise<CloseSessionResult> {
|
||||||
const worktreesRemoved: string[] = [];
|
const worktreesRemoved: string[] = [];
|
||||||
|
const instructionsArr: string[] = [];
|
||||||
const branchesDeleted: string[] = [];
|
const branchesDeleted: string[] = [];
|
||||||
const tagsCreated: string[] = [];
|
const tagsCreated: string[] = [];
|
||||||
const warnings: string[] = [];
|
|
||||||
|
|
||||||
|
|
||||||
|
const safeSessionName = sanitizeName(options.sessionName);
|
||||||
try {
|
try {
|
||||||
// Check if session exists
|
// 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
|
// Kill tmux session if it exists
|
||||||
if (sessionExists) {
|
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
|
// 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 cleanup requested and not forced, ask for confirmation
|
||||||
if (options.cleanupWorktrees && !options.force && worktrees.length > 0) {
|
if (options.cleanupWorktrees && !options.force && worktrees.length > 0) {
|
||||||
console.log(`\nThe following worktrees and branches will be removed:`);
|
instructionsArr.push(chalk.red('The following worktrees and branches will be removed:'));
|
||||||
worktrees.forEach((wt) => console.log(` - ${wt.path} (branch: ${wt.branch})`));
|
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") {
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
sessionName: options.sessionName,
|
sessionName: safeSessionName,
|
||||||
error: "Cleanup cancelled by user",
|
error: "Cleanup cancelled by user",
|
||||||
|
instructions: instructionsArr.join("\n"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -94,20 +104,19 @@ export async function closeMultiModel(options: CloseSessionOptions): Promise<Clo
|
|||||||
if (tagResult.ok) {
|
if (tagResult.ok) {
|
||||||
tagsCreated.push(archiveTag);
|
tagsCreated.push(archiveTag);
|
||||||
} else {
|
} else {
|
||||||
const warningMsg = `Failed to create tag ${archiveTag} for branch ${worktree.branch}.`;
|
const warningMsg = chalk.red(`Warning: Failed to create tag ${archiveTag} for branch ${worktree.branch}.`);
|
||||||
warnings.push(warningMsg);
|
instructionsArr.push(warningMsg);
|
||||||
console.warn(`Warning: ${warningMsg}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await runCommand(["git", "branch", "-D", worktree.branch]);
|
await runCommand(["git", "branch", "-D", worktree.branch]);
|
||||||
branchesDeleted.push(worktree.branch);
|
branchesDeleted.push(worktree.branch);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`Warning: Failed to cleanup worktree ${worktree.path}: ${err}`);
|
instructionsArr.push(chalk.red(`Failed to cleanup worktree ${worktree.path}: ${err}`));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also remove the base directory
|
// Also remove the base directory
|
||||||
const worktreeBase = getSessionPath(options.sessionName);
|
const worktreeBase = getSessionPath(safeSessionName);
|
||||||
try {
|
try {
|
||||||
await runCommand(["rm", "-rf", worktreeBase]);
|
await runCommand(["rm", "-rf", worktreeBase]);
|
||||||
} catch {
|
} catch {
|
||||||
@@ -115,26 +124,32 @@ export async function closeMultiModel(options: CloseSessionOptions): Promise<Clo
|
|||||||
}
|
}
|
||||||
|
|
||||||
cleanupPerformed = worktreesRemoved.length > 0;
|
cleanupPerformed = worktreesRemoved.length > 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 {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
sessionName: options.sessionName,
|
sessionName: safeSessionName,
|
||||||
cleanupPerformed,
|
cleanupPerformed,
|
||||||
worktreesRemoved,
|
worktreesRemoved,
|
||||||
branchesDeleted,
|
branchesDeleted,
|
||||||
tagsCreated,
|
tagsCreated,
|
||||||
warnings,
|
instructions: instructionsArr.join("\n"),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
sessionName: options.sessionName,
|
sessionName: safeSessionName,
|
||||||
error: String(error),
|
error: String(error),
|
||||||
worktreesRemoved,
|
worktreesRemoved,
|
||||||
branchesDeleted,
|
branchesDeleted,
|
||||||
tagsCreated,
|
tagsCreated,
|
||||||
warnings,
|
instructions: instructionsArr.join("\n"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-2
@@ -154,7 +154,9 @@ export async function openMultiModel(options: MultiModelOptions): Promise<MultiM
|
|||||||
if (!sessionCreateResult.ok) {
|
if (!sessionCreateResult.ok) {
|
||||||
const undoErrors = await undoWorktree(worktreePath, branchName);
|
const undoErrors = await undoWorktree(worktreePath, branchName);
|
||||||
let errorMsg = `Error: Failed to create tmux session '${sessionName}'.${sessionCreateResult.stderr ? ` ${sessionCreateResult.stderr}` : ""}`;
|
let errorMsg = `Error: Failed to create tmux session '${sessionName}'.${sessionCreateResult.stderr ? ` ${sessionCreateResult.stderr}` : ""}`;
|
||||||
if (undoErrors.length > 0) errorMsg += ` Cleanup errors: ${undoErrors.join(", ")}`;
|
if (undoErrors.length > 0) {
|
||||||
|
errorMsg += ` Cleanup errors: ${undoErrors.join(", ")}`;
|
||||||
|
}
|
||||||
return { success: false, sessionName, error: errorMsg };
|
return { success: false, sessionName, error: errorMsg };
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -173,7 +175,9 @@ export async function openMultiModel(options: MultiModelOptions): Promise<MultiM
|
|||||||
if (!windowCreateResult.ok) {
|
if (!windowCreateResult.ok) {
|
||||||
failedModels.push(`${plan.model} (${windowCreateResult.stderr || "failed to create window"})`);
|
failedModels.push(`${plan.model} (${windowCreateResult.stderr || "failed to create window"})`);
|
||||||
const undoErrors = await undoWorktree(worktreePath, branchName);
|
const undoErrors = await undoWorktree(worktreePath, branchName);
|
||||||
if (undoErrors.length > 0) failedModels.push(`cleanup failed for ${plan.model}: ${undoErrors.join(", ")}`);
|
if (undoErrors.length > 0) {
|
||||||
|
failedModels.push(`cleanup failed for ${plan.model}: ${undoErrors.join(", ")}`);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-12
@@ -25,17 +25,6 @@ export const closeTool = tool({
|
|||||||
return result.error!;
|
return result.error!;
|
||||||
}
|
}
|
||||||
|
|
||||||
let details = "";
|
return result.instructions;
|
||||||
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}`;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
+3
-2
@@ -44,6 +44,8 @@ export interface CloseSessionOptions {
|
|||||||
* Result returned after attempting to close a multi-model session.
|
* Result returned after attempting to close a multi-model session.
|
||||||
*/
|
*/
|
||||||
export interface CloseSessionResult {
|
export interface CloseSessionResult {
|
||||||
|
/** Console messages and instructions. */
|
||||||
|
instructions: string;
|
||||||
/** Whether the close succeeded. */
|
/** Whether the close succeeded. */
|
||||||
success: boolean;
|
success: boolean;
|
||||||
/** The session name that was closed. */
|
/** The session name that was closed. */
|
||||||
@@ -58,8 +60,7 @@ export interface CloseSessionResult {
|
|||||||
branchesDeleted?: string[];
|
branchesDeleted?: string[];
|
||||||
/** Archive tags that were created before branch deletion. */
|
/** Archive tags that were created before branch deletion. */
|
||||||
tagsCreated?: string[];
|
tagsCreated?: string[];
|
||||||
/** Warning messages. */
|
|
||||||
warnings?: string[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+4
-4
@@ -98,7 +98,7 @@ describe("closeMultiModel", () => {
|
|||||||
expect(result.worktreesRemoved).toEqual([worktreePath]);
|
expect(result.worktreesRemoved).toEqual([worktreePath]);
|
||||||
expect(result.branchesDeleted).toEqual(["opencode/test-session/model"]);
|
expect(result.branchesDeleted).toEqual(["opencode/test-session/model"]);
|
||||||
expect(result.tagsCreated?.length).toBe(1);
|
expect(result.tagsCreated?.length).toBe(1);
|
||||||
expect(result.warnings).toEqual([]);
|
|
||||||
// Ensure removal commands were executed
|
// Ensure removal commands were executed
|
||||||
expect(executedCommands).toContain(`git worktree remove -f ${worktreePath}`);
|
expect(executedCommands).toContain(`git worktree remove -f ${worktreePath}`);
|
||||||
expect(executedCommands).toContain(`git branch -D opencode/test-session/model`);
|
expect(executedCommands).toContain(`git branch -D opencode/test-session/model`);
|
||||||
@@ -153,7 +153,7 @@ describe("closeMultiModel", () => {
|
|||||||
expect(result.worktreesRemoved).toEqual([worktreePath]);
|
expect(result.worktreesRemoved).toEqual([worktreePath]);
|
||||||
expect(result.branchesDeleted).toEqual(["opencode/test-session/model"]);
|
expect(result.branchesDeleted).toEqual(["opencode/test-session/model"]);
|
||||||
expect(result.tagsCreated?.length).toBe(1);
|
expect(result.tagsCreated?.length).toBe(1);
|
||||||
expect(result.warnings).toEqual([]);
|
|
||||||
|
|
||||||
// Verify the expected git commands were executed
|
// Verify the expected git commands were executed
|
||||||
expect(executedCommands).toContain(`git worktree remove -f ${worktreePath}`);
|
expect(executedCommands).toContain(`git worktree remove -f ${worktreePath}`);
|
||||||
@@ -183,8 +183,8 @@ describe("closeMultiModel", () => {
|
|||||||
global.Date = OriginalDate;
|
global.Date = OriginalDate;
|
||||||
|
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
expect(result.warnings?.length).toBe(1);
|
// Expect the warning message to be captured in instructions
|
||||||
expect(result.warnings?.[0]).toContain("Failed to create tag");
|
expect(result.instructions?.some(msg => msg.includes('Failed to create tag'))).toBe(true);
|
||||||
expect(result.tagsCreated).toEqual([]);
|
expect(result.tagsCreated).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user