mirror of
https://github.com/bendtherules/opencode-multi-model.git
synced 2026-08-18 13:42:21 +00:00
build: add biome formatter and linter
This commit is contained in:
+76
-42
@@ -1,7 +1,12 @@
|
||||
import * as readline from "node:readline";
|
||||
import chalk from 'chalk'; // removed console usage
|
||||
import chalk from "chalk"; // removed console usage
|
||||
import type { CloseSessionOptions, CloseSessionResult } from "../types";
|
||||
import { runCommand, getSessionPath, getWorktreesForSession, sanitizeName } from "./utils";
|
||||
import {
|
||||
getSessionPath,
|
||||
getWorktreesForSession,
|
||||
runCommand,
|
||||
sanitizeName,
|
||||
} from "./utils";
|
||||
|
||||
/**
|
||||
* Prompts the user for input on the terminal.
|
||||
@@ -44,24 +49,34 @@ 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 instructionsArr: string[] = [];
|
||||
const branchesDeleted: string[] = [];
|
||||
const tagsCreated: 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",
|
||||
options.sessionName,
|
||||
]);
|
||||
|
||||
// Kill tmux session if it exists
|
||||
if (sessionExists) {
|
||||
await runCommand(["tmux", "kill-session", "-t", options.sessionName]);
|
||||
instructionsArr.push(chalk.green(`Closed session '${options.sessionName}'`));
|
||||
instructionsArr.push(
|
||||
chalk.green(`Closed session '${options.sessionName}'`),
|
||||
);
|
||||
} else {
|
||||
instructionsArr.push(chalk.yellow(`Session '${options.sessionName}' does not exist`));
|
||||
instructionsArr.push(
|
||||
chalk.yellow(`Session '${options.sessionName}' does not exist`),
|
||||
);
|
||||
}
|
||||
|
||||
// Get list of worktrees for this session before killing tmux
|
||||
@@ -69,13 +84,17 @@ export async function closeMultiModel(options: CloseSessionOptions): Promise<Clo
|
||||
|
||||
// If cleanup requested and not forced, ask for confirmation
|
||||
if (options.cleanupWorktrees && !options.force && worktrees.length > 0) {
|
||||
console.log(chalk.red('The following worktrees and branches will be removed:'));
|
||||
console.log(
|
||||
chalk.red("The following worktrees and branches will be removed:"),
|
||||
);
|
||||
worktrees.forEach((wt) => {
|
||||
const msg = ` - ${wt.path} (branch: ${wt.branch})`;
|
||||
console.log(msg.trim());
|
||||
});
|
||||
|
||||
const answer = await promptUser(chalk.bold("\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,
|
||||
@@ -96,24 +115,33 @@ export async function closeMultiModel(options: CloseSessionOptions): Promise<Clo
|
||||
await runCommand(["git", "worktree", "remove", "-f", worktree.path]);
|
||||
worktreesRemoved.push(worktree.path);
|
||||
|
||||
// Optionally create an archive tag before deleting the branch for safe recovery
|
||||
if (options.createArchiveTags !== false) {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const archiveTag = `archive/${worktree.branch}-${timestamp}`;
|
||||
const tagResult = await runCommand(["git", "tag", archiveTag, worktree.branch]);
|
||||
// Optionally create an archive tag before deleting the branch for safe recovery
|
||||
if (options.createArchiveTags !== false) {
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||
const archiveTag = `archive/${worktree.branch}-${timestamp}`;
|
||||
const tagResult = await runCommand([
|
||||
"git",
|
||||
"tag",
|
||||
archiveTag,
|
||||
worktree.branch,
|
||||
]);
|
||||
|
||||
if (tagResult.ok) {
|
||||
tagsCreated.push(archiveTag);
|
||||
} else {
|
||||
const warningMsg = chalk.red(`Warning: Failed to create tag ${archiveTag} for branch ${worktree.branch}.`);
|
||||
instructionsArr.push(warningMsg);
|
||||
}
|
||||
}
|
||||
if (tagResult.ok) {
|
||||
tagsCreated.push(archiveTag);
|
||||
} else {
|
||||
const warningMsg = chalk.red(
|
||||
`Warning: Failed to create tag ${archiveTag} for branch ${worktree.branch}.`,
|
||||
);
|
||||
instructionsArr.push(warningMsg);
|
||||
}
|
||||
}
|
||||
|
||||
await runCommand(["git", "branch", "-D", worktree.branch]);
|
||||
branchesDeleted.push(worktree.branch);
|
||||
} catch (err) {
|
||||
instructionsArr.push(chalk.red(`Failed to cleanup worktree ${worktree.path}: ${err}`));
|
||||
instructionsArr.push(
|
||||
chalk.red(`Failed to cleanup worktree ${worktree.path}: ${err}`),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,31 +155,37 @@ export async function closeMultiModel(options: CloseSessionOptions): Promise<Clo
|
||||
|
||||
cleanupPerformed = worktreesRemoved.length > 0;
|
||||
if (cleanupPerformed) {
|
||||
instructionsArr.push(chalk.dim(`Removed ${worktreesRemoved.length} worktrees, alongwith their branches.`));
|
||||
instructionsArr.push(
|
||||
chalk.dim(
|
||||
`Removed ${worktreesRemoved.length} worktrees, alongwith their branches.`,
|
||||
),
|
||||
);
|
||||
if (tagsCreated.length > 0) {
|
||||
instructionsArr.push(chalk.dim(`Created archive tags: ${tagsCreated.join(", ")}.`));
|
||||
instructionsArr.push(
|
||||
chalk.dim(`Created archive tags: ${tagsCreated.join(", ")}.`),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
sessionName: safeSessionName,
|
||||
cleanupPerformed,
|
||||
worktreesRemoved,
|
||||
branchesDeleted,
|
||||
tagsCreated,
|
||||
instructions: instructionsArr.join("\n"),
|
||||
};
|
||||
return {
|
||||
success: true,
|
||||
sessionName: safeSessionName,
|
||||
cleanupPerformed,
|
||||
worktreesRemoved,
|
||||
branchesDeleted,
|
||||
tagsCreated,
|
||||
instructions: instructionsArr.join("\n"),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
sessionName: safeSessionName,
|
||||
error: String(error),
|
||||
worktreesRemoved,
|
||||
branchesDeleted,
|
||||
tagsCreated,
|
||||
instructions: instructionsArr.join("\n"),
|
||||
};
|
||||
return {
|
||||
success: false,
|
||||
sessionName: safeSessionName,
|
||||
error: String(error),
|
||||
worktreesRemoved,
|
||||
branchesDeleted,
|
||||
tagsCreated,
|
||||
instructions: instructionsArr.join("\n"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
export { openMultiModel } from "./open";
|
||||
export { closeMultiModel } from "./close";
|
||||
export { openMultiModel } from "./open";
|
||||
export * from "./utils";
|
||||
|
||||
+121
-34
@@ -1,17 +1,17 @@
|
||||
import * as fs from "node:fs";
|
||||
import chalk from 'chalk';
|
||||
import chalk from "chalk";
|
||||
import type { MultiModelOptions, MultiModelResult } from "../types";
|
||||
import {
|
||||
runCommand,
|
||||
normalizeModels,
|
||||
findDuplicates,
|
||||
createWindowPlans,
|
||||
findDuplicates,
|
||||
formatInvalidModelError,
|
||||
sanitizeName,
|
||||
getWorktreePath,
|
||||
getSessionPath,
|
||||
undoWorktree,
|
||||
getWorktreePath,
|
||||
launchModelInWindow,
|
||||
normalizeModels,
|
||||
runCommand,
|
||||
sanitizeName,
|
||||
undoWorktree,
|
||||
} from "./utils";
|
||||
|
||||
/**
|
||||
@@ -35,7 +35,9 @@ import {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export async function openMultiModel(options: MultiModelOptions): Promise<MultiModelResult> {
|
||||
export async function openMultiModel(
|
||||
options: MultiModelOptions,
|
||||
): Promise<MultiModelResult> {
|
||||
const sessionName = options.sessionName.trim();
|
||||
const safeSessionName = sanitizeName(sessionName);
|
||||
const models = normalizeModels(options.models);
|
||||
@@ -43,11 +45,19 @@ export async function openMultiModel(options: MultiModelOptions): Promise<MultiM
|
||||
const mode = options.mode || "cli";
|
||||
|
||||
if (!sessionName) {
|
||||
return { success: false, sessionName: "", error: "Error: `sessionName` must not be empty." };
|
||||
return {
|
||||
success: false,
|
||||
sessionName: "",
|
||||
error: "Error: `sessionName` must not be empty.",
|
||||
};
|
||||
}
|
||||
|
||||
if (models.length === 0) {
|
||||
return { success: false, sessionName, error: "Error: Model names must not be empty." };
|
||||
return {
|
||||
success: false,
|
||||
sessionName,
|
||||
error: "Error: Model names must not be empty.",
|
||||
};
|
||||
}
|
||||
|
||||
const duplicateModels = findDuplicates(models);
|
||||
@@ -59,19 +69,35 @@ export async function openMultiModel(options: MultiModelOptions): Promise<MultiM
|
||||
};
|
||||
}
|
||||
|
||||
const gitRepoCheck = await runCommand(["git", "rev-parse", "--is-inside-work-tree"]);
|
||||
const gitRepoCheck = await runCommand([
|
||||
"git",
|
||||
"rev-parse",
|
||||
"--is-inside-work-tree",
|
||||
]);
|
||||
if (!gitRepoCheck.ok) {
|
||||
return { success: false, sessionName, error: "Error: `multi-model` requires a git repository." };
|
||||
return {
|
||||
success: false,
|
||||
sessionName,
|
||||
error: "Error: `multi-model` requires a git repository.",
|
||||
};
|
||||
}
|
||||
|
||||
const tmuxExists = await runCommand(["command", "-v", "tmux"]);
|
||||
if (!tmuxExists.ok) {
|
||||
return { success: false, sessionName, error: "Error: `tmux` is not installed or not on `PATH`." };
|
||||
return {
|
||||
success: false,
|
||||
sessionName,
|
||||
error: "Error: `tmux` is not installed or not on `PATH`.",
|
||||
};
|
||||
}
|
||||
|
||||
const binaryExists = await runCommand(["command", "-v", binaryName]);
|
||||
if (!binaryExists.ok) {
|
||||
return { success: false, sessionName, error: `Error: \`${binaryName}\` is not installed or not on \`PATH\`.` };
|
||||
return {
|
||||
success: false,
|
||||
sessionName,
|
||||
error: `Error: \`${binaryName}\` is not installed or not on \`PATH\`.`,
|
||||
};
|
||||
}
|
||||
|
||||
const modelListResult = await runCommand([binaryName, "models"]);
|
||||
@@ -90,12 +116,25 @@ export async function openMultiModel(options: MultiModelOptions): Promise<MultiM
|
||||
|
||||
const invalidModels = models.filter((model) => !allowlist.includes(model));
|
||||
if (invalidModels.length > 0) {
|
||||
return { success: false, sessionName, error: formatInvalidModelError(invalidModels, allowlist) };
|
||||
return {
|
||||
success: false,
|
||||
sessionName,
|
||||
error: formatInvalidModelError(invalidModels, allowlist),
|
||||
};
|
||||
}
|
||||
|
||||
const sessionExists = await runCommand(["tmux", "has-session", "-t", sessionName]);
|
||||
const sessionExists = await runCommand([
|
||||
"tmux",
|
||||
"has-session",
|
||||
"-t",
|
||||
sessionName,
|
||||
]);
|
||||
if (sessionExists.ok) {
|
||||
return { success: false, sessionName, error: "Error: Session already exists. Use a different `sessionName`." };
|
||||
return {
|
||||
success: false,
|
||||
sessionName,
|
||||
error: "Error: Session already exists. Use a different `sessionName`.",
|
||||
};
|
||||
}
|
||||
|
||||
const windowPlans = createWindowPlans(models);
|
||||
@@ -111,30 +150,61 @@ export async function openMultiModel(options: MultiModelOptions): Promise<MultiM
|
||||
|
||||
if (fs.existsSync(worktreePath)) {
|
||||
if (!isFirst) {
|
||||
failedModels.push(`${plan.model} (Worktree path already exists at ${worktreePath})`);
|
||||
failedModels.push(
|
||||
`${plan.model} (Worktree path already exists at ${worktreePath})`,
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
return { success: false, sessionName, error: `Error: Worktree path already exists at ${worktreePath}. Clean it up first.` };
|
||||
return {
|
||||
success: false,
|
||||
sessionName,
|
||||
error: `Error: Worktree path already exists at ${worktreePath}. Clean it up first.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const branchExists = await runCommand(["git", "show-ref", "--verify", "--quiet", `refs/heads/${branchName}`]);
|
||||
const branchExists = await runCommand([
|
||||
"git",
|
||||
"show-ref",
|
||||
"--verify",
|
||||
"--quiet",
|
||||
`refs/heads/${branchName}`,
|
||||
]);
|
||||
if (branchExists.ok) {
|
||||
if (!isFirst) {
|
||||
failedModels.push(`${plan.model} (Branch '${branchName}' already exists)`);
|
||||
failedModels.push(
|
||||
`${plan.model} (Branch '${branchName}' already exists)`,
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
return { success: false, sessionName, error: `Error: Branch '${branchName}' already exists.` };
|
||||
return {
|
||||
success: false,
|
||||
sessionName,
|
||||
error: `Error: Branch '${branchName}' already exists.`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const worktreeResult = await runCommand(["git", "worktree", "add", "-b", branchName, worktreePath]);
|
||||
const worktreeResult = await runCommand([
|
||||
"git",
|
||||
"worktree",
|
||||
"add",
|
||||
"-b",
|
||||
branchName,
|
||||
worktreePath,
|
||||
]);
|
||||
if (!worktreeResult.ok) {
|
||||
if (!isFirst) {
|
||||
failedModels.push(`${plan.model} (Failed to create worktree: ${worktreeResult.stderr})`);
|
||||
failedModels.push(
|
||||
`${plan.model} (Failed to create worktree: ${worktreeResult.stderr})`,
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
return { success: false, sessionName, error: `Error: Failed to create worktree for ${plan.model}: ${worktreeResult.stderr}` };
|
||||
return {
|
||||
success: false,
|
||||
sessionName,
|
||||
error: `Error: Failed to create worktree for ${plan.model}: ${worktreeResult.stderr}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,22 +243,35 @@ export async function openMultiModel(options: MultiModelOptions): Promise<MultiM
|
||||
]);
|
||||
|
||||
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);
|
||||
if (undoErrors.length > 0) {
|
||||
failedModels.push(`cleanup failed for ${plan.model}: ${undoErrors.join(", ")}`);
|
||||
failedModels.push(
|
||||
`cleanup failed for ${plan.model}: ${undoErrors.join(", ")}`,
|
||||
);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const launchResult = await launchModelInWindow(sessionName, plan, binaryName);
|
||||
const launchResult = await launchModelInWindow(
|
||||
sessionName,
|
||||
plan,
|
||||
binaryName,
|
||||
);
|
||||
if (launchResult.ok) {
|
||||
succeededModels.push(plan.model);
|
||||
} else {
|
||||
failedModels.push(`${plan.model} (${launchResult.stderr || "failed to send launch command"})`);
|
||||
failedModels.push(
|
||||
`${plan.model} (${launchResult.stderr || "failed to send launch command"})`,
|
||||
);
|
||||
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(", ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -206,9 +289,13 @@ export async function openMultiModel(options: MultiModelOptions): Promise<MultiM
|
||||
sessionName,
|
||||
windows: windowNames,
|
||||
instructions: [
|
||||
chalk.red(`Error: Created tmux session '${sessionName}', but some model launches failed.`),
|
||||
chalk.red(
|
||||
`Error: Created tmux session '${sessionName}', but some model launches failed.`,
|
||||
),
|
||||
chalk.red(`Failed: ${failedModels.join(", ")}.`),
|
||||
chalk.dim(`Succeeded: ${succeededModels.length > 0 ? succeededModels.join(", ") : "none"}.`),
|
||||
chalk.dim(
|
||||
`Succeeded: ${succeededModels.length > 0 ? succeededModels.join(", ") : "none"}.`,
|
||||
),
|
||||
"",
|
||||
chalk.bold(`Attach with \`${attachCommand}\` to inspect the session.`),
|
||||
"",
|
||||
@@ -216,7 +303,7 @@ export async function openMultiModel(options: MultiModelOptions): Promise<MultiM
|
||||
primaryCleanup,
|
||||
"",
|
||||
chalk.dim(`Alternate manual cleanup - `),
|
||||
chalk.dim(cleanupCommand)
|
||||
chalk.dim(cleanupCommand),
|
||||
].join("\n"),
|
||||
};
|
||||
}
|
||||
@@ -232,7 +319,7 @@ export async function openMultiModel(options: MultiModelOptions): Promise<MultiM
|
||||
primaryCleanup,
|
||||
"",
|
||||
chalk.dim(`Alternate manual cleanup - `),
|
||||
chalk.dim(cleanupCommand)
|
||||
chalk.dim(cleanupCommand),
|
||||
].join("\n"),
|
||||
};
|
||||
}
|
||||
|
||||
+67
-14
@@ -136,7 +136,10 @@ export function createWindowPlans(models: string[]): WindowLaunchPlan[] {
|
||||
}
|
||||
|
||||
const suffix = `-${nextCount}`;
|
||||
const trimmedBase = baseName.slice(0, Math.max(1, WINDOW_NAME_LIMIT - suffix.length));
|
||||
const trimmedBase = baseName.slice(
|
||||
0,
|
||||
Math.max(1, WINDOW_NAME_LIMIT - suffix.length),
|
||||
);
|
||||
|
||||
return {
|
||||
model,
|
||||
@@ -194,7 +197,10 @@ export function levenshtein(left: string, right: string): number {
|
||||
* // ["openai/gpt-5.4", "openai/gpt-5.4-pro"]
|
||||
* ```
|
||||
*/
|
||||
export function suggestModels(requested: string, allowlist: string[]): string[] {
|
||||
export function suggestModels(
|
||||
requested: string,
|
||||
allowlist: string[],
|
||||
): string[] {
|
||||
const normalizedRequested = requested.toLowerCase();
|
||||
|
||||
return allowlist
|
||||
@@ -212,7 +218,11 @@ export function suggestModels(requested: string, allowlist: string[]): string[]
|
||||
score: distance + containsBoost,
|
||||
};
|
||||
})
|
||||
.sort((left, right) => left.score - right.score || left.candidate.localeCompare(right.candidate))
|
||||
.sort(
|
||||
(left, right) =>
|
||||
left.score - right.score ||
|
||||
left.candidate.localeCompare(right.candidate),
|
||||
)
|
||||
.slice(0, MAX_SUGGESTIONS)
|
||||
.map(({ candidate }) => candidate);
|
||||
}
|
||||
@@ -229,7 +239,10 @@ export function suggestModels(requested: string, allowlist: string[]): string[]
|
||||
* const message = formatInvalidModelError(["openai/gpt5.4"], ["openai/gpt-5.4"]);
|
||||
* ```
|
||||
*/
|
||||
export function formatInvalidModelError(invalidModels: string[], allowlist: string[]): string {
|
||||
export function formatInvalidModelError(
|
||||
invalidModels: string[],
|
||||
allowlist: string[],
|
||||
): string {
|
||||
const firstInvalidModel = invalidModels[0]!;
|
||||
const suggestions = suggestModels(firstInvalidModel, allowlist);
|
||||
const suggestionText =
|
||||
@@ -264,7 +277,14 @@ export async function launchModelInWindow(
|
||||
): Promise<CommandResult> {
|
||||
const launchCommand = `${binaryName} --model ${shellQuote(plan.model)}`;
|
||||
|
||||
return runCommand(["tmux", "send-keys", "-t", `${sessionName}:${plan.windowName}`, launchCommand, "C-m"]);
|
||||
return runCommand([
|
||||
"tmux",
|
||||
"send-keys",
|
||||
"-t",
|
||||
`${sessionName}:${plan.windowName}`,
|
||||
launchCommand,
|
||||
"C-m",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -280,7 +300,10 @@ export async function launchModelInWindow(
|
||||
* ```
|
||||
*/
|
||||
export function sanitizeName(name: string): string {
|
||||
return name.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
||||
return name
|
||||
.replace(/[^a-zA-Z0-9_-]/g, "-")
|
||||
.replace(/-+/g, "-")
|
||||
.replace(/^-|-$/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -297,7 +320,14 @@ export function sanitizeName(name: string): string {
|
||||
*/
|
||||
export function getSessionPath(sessionName: string): string {
|
||||
const homedir = os.homedir();
|
||||
return path.join(homedir, ".local", "share", "opencode", "multi-model", sessionName);
|
||||
return path.join(
|
||||
homedir,
|
||||
".local",
|
||||
"share",
|
||||
"opencode",
|
||||
"multi-model",
|
||||
sessionName,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,7 +343,10 @@ export function getSessionPath(sessionName: string): string {
|
||||
* // "/home/user/.local/share/opencode/multi-model/my-session/gpt-5-2"
|
||||
* ```
|
||||
*/
|
||||
export function getWorktreePath(safeSession: string, windowName: string): string {
|
||||
export function getWorktreePath(
|
||||
safeSession: string,
|
||||
windowName: string,
|
||||
): string {
|
||||
return path.join(getSessionPath(safeSession), windowName);
|
||||
}
|
||||
|
||||
@@ -328,8 +361,15 @@ export function getWorktreePath(safeSession: string, windowName: string): string
|
||||
* const worktrees = await getWorktreesForSession("my-session");
|
||||
* ```
|
||||
*/
|
||||
export async function getWorktreesForSession(sessionName: string): Promise<WorktreeInfo[]> {
|
||||
const { stdout } = await runCommand(["git", "worktree", "list", "--porcelain"]);
|
||||
export async function getWorktreesForSession(
|
||||
sessionName: string,
|
||||
): Promise<WorktreeInfo[]> {
|
||||
const { stdout } = await runCommand([
|
||||
"git",
|
||||
"worktree",
|
||||
"list",
|
||||
"--porcelain",
|
||||
]);
|
||||
const worktrees: WorktreeInfo[] = [];
|
||||
|
||||
const sessionPath = getSessionPath(sanitizeName(sessionName));
|
||||
@@ -371,13 +411,26 @@ export async function getWorktreesForSession(sessionName: string): Promise<Workt
|
||||
* if (errors.length > 0) console.error(errors);
|
||||
* ```
|
||||
*/
|
||||
export async function undoWorktree(worktreePath: string, branchName: string): Promise<string[]> {
|
||||
export async function undoWorktree(
|
||||
worktreePath: string,
|
||||
branchName: string,
|
||||
): Promise<string[]> {
|
||||
const errors: string[] = [];
|
||||
const removeRes = await runCommand(["git", "worktree", "remove", "-f", worktreePath]);
|
||||
if (!removeRes.ok) errors.push(`Failed to remove worktree ${worktreePath}: ${removeRes.stderr}`);
|
||||
const removeRes = await runCommand([
|
||||
"git",
|
||||
"worktree",
|
||||
"remove",
|
||||
"-f",
|
||||
worktreePath,
|
||||
]);
|
||||
if (!removeRes.ok)
|
||||
errors.push(
|
||||
`Failed to remove worktree ${worktreePath}: ${removeRes.stderr}`,
|
||||
);
|
||||
|
||||
const branchRes = await runCommand(["git", "branch", "-D", branchName]);
|
||||
if (!branchRes.ok) errors.push(`Failed to delete branch ${branchName}: ${branchRes.stderr}`);
|
||||
if (!branchRes.ok)
|
||||
errors.push(`Failed to delete branch ${branchName}: ${branchRes.stderr}`);
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user