Compare commits

..
4 Commits
Author SHA1 Message Date
bendtherules fc7feb2095 chore(release): 0.0.6 2026-03-21 01:28:15 +05:30
bendtherules 7180569b87 feat: add option to disable archive tags 2026-03-21 01:27:43 +05:30
bendtherules f0cc07f513 chore(release): 0.0.5 2026-03-21 01:11:36 +05:30
bendtherules 6dd4ee4b4f fix: correct path for cli.js in package.json 2026-03-21 01:11:30 +05:30
6 changed files with 42 additions and 13 deletions
+2 -2
View File
@@ -1,11 +1,11 @@
{
"name": "opencode-multi-model",
"version": "0.0.4",
"version": "0.0.6",
"description": "Launch multiple AI models in tmux sessions - OpenCode Plugin, CLI, and Project Tool",
"type": "module",
"main": "./dist/index.js",
"bin": {
"opencode-multi-model": "./dist/cli.js"
"opencode-multi-model": "dist/cli.js"
},
"exports": {
".": "./dist/index.js",
+3 -1
View File
@@ -45,13 +45,15 @@ program
.description("Close a multi-model tmux session")
.argument("<session-name>", "Name of the tmux session to close")
.option("-c, --cleanup-worktrees", "Remove worktrees and delete branches", true)
.option("-t, --add-tags", "Create archive tags before deleting branches", true)
.option("-f, --force", "Skip confirmation prompts", false)
.action(async (sessionName: string, options: { cleanupWorktrees: boolean; force: boolean }) => {
.action(async (sessionName: string, options: { cleanupWorktrees: boolean; force: boolean; addTags: boolean }) => {
try {
const result = await closeMultiModel({
sessionName,
cleanupWorktrees: options.cleanupWorktrees,
force: options.force,
createArchiveTags: options.addTags,
});
if (result.success) {
+4 -2
View File
@@ -96,7 +96,8 @@ export async function closeMultiModel(options: CloseSessionOptions): Promise<Clo
await runCommand(["git", "worktree", "remove", "-f", worktree.path]);
worktreesRemoved.push(worktree.path);
// Create an archive tag before deleting the branch for safe recovery
// 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]);
@@ -104,9 +105,10 @@ export async function closeMultiModel(options: CloseSessionOptions): Promise<Clo
if (tagResult.ok) {
tagsCreated.push(archiveTag);
} else {
const warningMsg = chalk.red(`Warning: Failed to create tag ${archiveTag} for branch ${worktree.branch}.`);
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);
+2
View File
@@ -12,12 +12,14 @@ export const closeTool = tool({
args: {
sessionName: tool.schema.string().min(1).describe("tmux session name to close"),
cleanupWorktrees: tool.schema.boolean().default(true).describe("whether to remove worktrees and delete branches (default: true)"),
createArchiveTags: tool.schema.boolean().default(true).describe("whether to create archive tags before deleting branches (default: true)"),
},
async execute(args, context) {
// In plugin mode, we skip confirmation (force=true) since there's no interactive terminal
const result = await closeMultiModel({
sessionName: args.sessionName,
cleanupWorktrees: args.cleanupWorktrees,
createArchiveTags: args.createArchiveTags,
force: true,
});
+2
View File
@@ -32,6 +32,8 @@ export interface MultiModelResult {
* Options for closing a multi-model tmux session.
*/
export interface CloseSessionOptions {
/** Whether to create archive tags before deleting branches. */
createArchiveTags?: boolean;
/** Name of the tmux session to close. */
sessionName: string;
/** Whether to remove worktrees and delete branches. */
+21
View File
@@ -220,4 +220,25 @@ describe("closeMultiModel", () => {
expect(executedCommands).toContain(`git worktree remove -f ${worktreePath}`);
expect(executedCommands).toContain(`git branch -D opencode/${sanitized}/model`);
});
test("skip archive tags when flag disabled", async () => {
mockCommandResponses["tmux has-session -t test-session"] = { ok: false, stdout: "", stderr: "" };
const worktreePath = "/home/user/.local/share/opencode/multi-model/test-session/model";
const worktreeListOutput = `worktree ${worktreePath}\nbranch refs/heads/opencode/test-session/model`;
mockCommandResponses["git worktree list --porcelain"] = { ok: true, stdout: worktreeListOutput, stderr: "" };
mockCommandResponses[`git worktree remove -f ${worktreePath}`] = { ok: true, stdout: "", stderr: "" };
mockCommandResponses["git branch -D opencode/test-session/model"] = { ok: true, stdout: "", stderr: "" };
mockCommandResponses["rm -rf /home/user/.local/share/opencode/multi-model/test-session"] = { ok: true, stdout: "", stderr: "" };
const result = await closeMultiModel({ sessionName: "test-session", cleanupWorktrees: true, force: true, createArchiveTags: false });
expect(result.success).toBe(true);
expect(result.cleanupPerformed).toBe(true);
expect(result.worktreesRemoved).toEqual([worktreePath]);
expect(result.branchesDeleted).toEqual(["opencode/test-session/model"]);
expect(result.tagsCreated?.length ?? 0).toBe(0);
// Ensure no tag command was executed
expect(executedCommands.some(cmd => cmd.startsWith("git tag"))).toBe(false);
});
});