mirror of
https://github.com/bendtherules/opencode-multi-model.git
synced 2026-08-18 21:52:11 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc7feb2095 | ||
|
|
7180569b87 |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "opencode-multi-model",
|
"name": "opencode-multi-model",
|
||||||
"version": "0.0.5",
|
"version": "0.0.6",
|
||||||
"description": "Launch multiple AI models in tmux sessions - OpenCode Plugin, CLI, and Project Tool",
|
"description": "Launch multiple AI models in tmux sessions - OpenCode Plugin, CLI, and Project Tool",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|||||||
+3
-1
@@ -45,13 +45,15 @@ program
|
|||||||
.description("Close a multi-model tmux session")
|
.description("Close a multi-model tmux session")
|
||||||
.argument("<session-name>", "Name of the tmux session to close")
|
.argument("<session-name>", "Name of the tmux session to close")
|
||||||
.option("-c, --cleanup-worktrees", "Remove worktrees and delete branches", true)
|
.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)
|
.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 {
|
try {
|
||||||
const result = await closeMultiModel({
|
const result = await closeMultiModel({
|
||||||
sessionName,
|
sessionName,
|
||||||
cleanupWorktrees: options.cleanupWorktrees,
|
cleanupWorktrees: options.cleanupWorktrees,
|
||||||
force: options.force,
|
force: options.force,
|
||||||
|
createArchiveTags: options.addTags,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
|
|||||||
+4
-2
@@ -96,7 +96,8 @@ export async function closeMultiModel(options: CloseSessionOptions): Promise<Clo
|
|||||||
await runCommand(["git", "worktree", "remove", "-f", worktree.path]);
|
await runCommand(["git", "worktree", "remove", "-f", worktree.path]);
|
||||||
worktreesRemoved.push(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 timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
||||||
const archiveTag = `archive/${worktree.branch}-${timestamp}`;
|
const archiveTag = `archive/${worktree.branch}-${timestamp}`;
|
||||||
const tagResult = await runCommand(["git", "tag", archiveTag, worktree.branch]);
|
const tagResult = await runCommand(["git", "tag", archiveTag, worktree.branch]);
|
||||||
@@ -104,9 +105,10 @@ export async function closeMultiModel(options: CloseSessionOptions): Promise<Clo
|
|||||||
if (tagResult.ok) {
|
if (tagResult.ok) {
|
||||||
tagsCreated.push(archiveTag);
|
tagsCreated.push(archiveTag);
|
||||||
} else {
|
} 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);
|
instructionsArr.push(warningMsg);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await runCommand(["git", "branch", "-D", worktree.branch]);
|
await runCommand(["git", "branch", "-D", worktree.branch]);
|
||||||
branchesDeleted.push(worktree.branch);
|
branchesDeleted.push(worktree.branch);
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ export const closeTool = tool({
|
|||||||
args: {
|
args: {
|
||||||
sessionName: tool.schema.string().min(1).describe("tmux session name to close"),
|
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)"),
|
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) {
|
async execute(args, context) {
|
||||||
// In plugin mode, we skip confirmation (force=true) since there's no interactive terminal
|
// In plugin mode, we skip confirmation (force=true) since there's no interactive terminal
|
||||||
const result = await closeMultiModel({
|
const result = await closeMultiModel({
|
||||||
sessionName: args.sessionName,
|
sessionName: args.sessionName,
|
||||||
cleanupWorktrees: args.cleanupWorktrees,
|
cleanupWorktrees: args.cleanupWorktrees,
|
||||||
|
createArchiveTags: args.createArchiveTags,
|
||||||
force: true,
|
force: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,8 @@ export interface MultiModelResult {
|
|||||||
* Options for closing a multi-model tmux session.
|
* Options for closing a multi-model tmux session.
|
||||||
*/
|
*/
|
||||||
export interface CloseSessionOptions {
|
export interface CloseSessionOptions {
|
||||||
|
/** Whether to create archive tags before deleting branches. */
|
||||||
|
createArchiveTags?: boolean;
|
||||||
/** Name of the tmux session to close. */
|
/** Name of the tmux session to close. */
|
||||||
sessionName: string;
|
sessionName: string;
|
||||||
/** Whether to remove worktrees and delete branches. */
|
/** Whether to remove worktrees and delete branches. */
|
||||||
|
|||||||
@@ -220,4 +220,25 @@ describe("closeMultiModel", () => {
|
|||||||
expect(executedCommands).toContain(`git worktree remove -f ${worktreePath}`);
|
expect(executedCommands).toContain(`git worktree remove -f ${worktreePath}`);
|
||||||
expect(executedCommands).toContain(`git branch -D opencode/${sanitized}/model`);
|
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);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user