mirror of
https://github.com/bendtherules/opencode-multi-model.git
synced 2026-08-18 13:42:21 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc1efdbd63 | ||
|
|
77e5470d04 | ||
|
|
ac31d69e3a | ||
|
|
651c2ae9a9 | ||
|
|
0a509d2bc3 | ||
|
|
d8a2e11d29 | ||
|
|
2cc01f5faa |
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "opencode-multi-model",
|
"name": "opencode-multi-model",
|
||||||
"version": "0.2.1",
|
"version": "0.2.2",
|
||||||
"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",
|
||||||
|
|||||||
+5
-3
@@ -1,11 +1,11 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
import chalk from "chalk";
|
||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import pkg from "../package.json";
|
import pkg from "../package.json";
|
||||||
import { cleanupMultiModel } from "./core/cleanup";
|
import { cleanupMultiModel } from "./core/cleanup";
|
||||||
import { closeMultiModel } from "./core/close";
|
import { closeMultiModel } from "./core/close";
|
||||||
import { openMultiModel } from "./core/open";
|
import { openMultiModel } from "./core/open";
|
||||||
import { getBinaryName, loadSettings, promptUser } from "./core/utils";
|
import { getBinaryName, loadSettings, promptUser } from "./core/utils";
|
||||||
import chalk from "chalk";
|
|
||||||
|
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
@@ -46,9 +46,11 @@ program
|
|||||||
const answer = await promptUser(
|
const answer = await promptUser(
|
||||||
`Continue with last used models?\n${modelList}\n(Y/n): `,
|
`Continue with last used models?\n${modelList}\n(Y/n): `,
|
||||||
);
|
);
|
||||||
if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
|
if (
|
||||||
|
answer.toLowerCase() === "y" ||
|
||||||
|
answer.toLowerCase() === "yes"
|
||||||
|
) {
|
||||||
models = settings.lastModels;
|
models = settings.lastModels;
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
models = settings.lastModels;
|
models = settings.lastModels;
|
||||||
|
|||||||
+34
-9
@@ -5,16 +5,32 @@ import { promptUser, runCommand } from "./utils";
|
|||||||
/**
|
/**
|
||||||
* Lists all archive tags matching the pattern archive/*.
|
* Lists all archive tags matching the pattern archive/*.
|
||||||
*/
|
*/
|
||||||
export async function listArchiveTags(): Promise<string[]> {
|
export async function listArchiveTags(
|
||||||
const { stdout } = await runCommand(["git", "tag", "-l", "archive/*"]);
|
mode?: "cli" | "tool",
|
||||||
|
abortSignal?: AbortSignal,
|
||||||
|
): Promise<string[]> {
|
||||||
|
const { stdout } = await runCommand(["git", "tag", "-l", "archive/*"], {
|
||||||
|
mode,
|
||||||
|
abortSignal,
|
||||||
|
});
|
||||||
return stdout.split("\n").filter(Boolean);
|
return stdout.split("\n").filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the list of available git remotes.
|
* Gets the list of available git remotes.
|
||||||
*/
|
*/
|
||||||
export async function getAvailableRemotes(): Promise<string[]> {
|
/**
|
||||||
const { stdout } = await runCommand(["git", "remote"]);
|
* Retrieves the list of git remotes.
|
||||||
|
* Accepts optional mode and abort signal for cancellation support.
|
||||||
|
*/
|
||||||
|
export async function getAvailableRemotes(
|
||||||
|
mode?: "cli" | "tool",
|
||||||
|
abortSignal?: AbortSignal,
|
||||||
|
): Promise<string[]> {
|
||||||
|
const { stdout } = await runCommand(["git", "remote"], {
|
||||||
|
mode,
|
||||||
|
abortSignal,
|
||||||
|
});
|
||||||
return stdout.split("\n").filter(Boolean);
|
return stdout.split("\n").filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -24,10 +40,12 @@ export async function getAvailableRemotes(): Promise<string[]> {
|
|||||||
async function deleteLocalTag(
|
async function deleteLocalTag(
|
||||||
tagName: string,
|
tagName: string,
|
||||||
mode?: "cli" | "tool",
|
mode?: "cli" | "tool",
|
||||||
|
abortSignal?: AbortSignal,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const result = await runCommand(["git", "tag", "-d", tagName], {
|
const result = await runCommand(["git", "tag", "-d", tagName], {
|
||||||
mode,
|
mode,
|
||||||
spinnerMsg: `Deleting local tag ${tagName}...`,
|
spinnerMsg: `Deleting local tag ${tagName}...`,
|
||||||
|
abortSignal,
|
||||||
});
|
});
|
||||||
return result.ok;
|
return result.ok;
|
||||||
}
|
}
|
||||||
@@ -39,12 +57,14 @@ async function deleteRemoteTag(
|
|||||||
tagName: string,
|
tagName: string,
|
||||||
remoteName: string,
|
remoteName: string,
|
||||||
mode?: "cli" | "tool",
|
mode?: "cli" | "tool",
|
||||||
|
abortSignal?: AbortSignal,
|
||||||
): Promise<{ ok: boolean; error: string }> {
|
): Promise<{ ok: boolean; error: string }> {
|
||||||
const result = await runCommand(
|
const result = await runCommand(
|
||||||
["git", "push", remoteName, "--delete", tagName],
|
["git", "push", remoteName, "--delete", tagName],
|
||||||
{
|
{
|
||||||
mode,
|
mode,
|
||||||
spinnerMsg: `Deleting remote tag ${tagName} from ${remoteName}...`,
|
spinnerMsg: `Deleting remote tag ${tagName} from ${remoteName}...`,
|
||||||
|
abortSignal,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
@@ -65,11 +85,11 @@ async function deleteRemoteTag(
|
|||||||
export async function cleanupMultiModel(
|
export async function cleanupMultiModel(
|
||||||
options: CleanupOptions,
|
options: CleanupOptions,
|
||||||
): Promise<CleanupResult> {
|
): Promise<CleanupResult> {
|
||||||
const { force = false, remote, mode } = options;
|
const { force = false, remote, mode, abortSignal } = options;
|
||||||
|
|
||||||
// Get available remotes and archive tags first
|
// Get available remotes and archive tags first
|
||||||
const availableRemotes = await getAvailableRemotes();
|
const availableRemotes = await getAvailableRemotes(mode, abortSignal);
|
||||||
const archiveTags = await listArchiveTags();
|
const archiveTags = await listArchiveTags(mode, abortSignal);
|
||||||
|
|
||||||
// Determine whether to push to remote and which remote to use:
|
// Determine whether to push to remote and which remote to use:
|
||||||
// - remote === false: --no-remote flag, local-only mode
|
// - remote === false: --no-remote flag, local-only mode
|
||||||
@@ -155,7 +175,7 @@ export async function cleanupMultiModel(
|
|||||||
|
|
||||||
// Delete local tags
|
// Delete local tags
|
||||||
for (const tag of archiveTags) {
|
for (const tag of archiveTags) {
|
||||||
const deleted = await deleteLocalTag(tag, mode);
|
const deleted = await deleteLocalTag(tag, mode, abortSignal);
|
||||||
if (deleted) {
|
if (deleted) {
|
||||||
localTagNames.push(tag);
|
localTagNames.push(tag);
|
||||||
}
|
}
|
||||||
@@ -164,7 +184,12 @@ export async function cleanupMultiModel(
|
|||||||
// Delete from remote (if remote is specified and exists)
|
// Delete from remote (if remote is specified and exists)
|
||||||
if (shouldPushToRemote && remoteToDeleteFrom) {
|
if (shouldPushToRemote && remoteToDeleteFrom) {
|
||||||
for (const tag of localTagNames) {
|
for (const tag of localTagNames) {
|
||||||
const result = await deleteRemoteTag(tag, remoteToDeleteFrom, mode);
|
const result = await deleteRemoteTag(
|
||||||
|
tag,
|
||||||
|
remoteToDeleteFrom,
|
||||||
|
mode,
|
||||||
|
abortSignal,
|
||||||
|
);
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
remoteTagNames.push(tag);
|
remoteTagNames.push(tag);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+21
-9
@@ -36,21 +36,27 @@ export async function closeMultiModel(
|
|||||||
const instructionsArr: string[] = [];
|
const instructionsArr: string[] = [];
|
||||||
const branchesDeleted: string[] = [];
|
const branchesDeleted: string[] = [];
|
||||||
const tagsCreated: string[] = [];
|
const tagsCreated: string[] = [];
|
||||||
const mode = options.mode;
|
const { mode, abortSignal } = options;
|
||||||
|
|
||||||
const safeSessionName = sanitizeName(options.sessionName);
|
const safeSessionName = sanitizeName(options.sessionName);
|
||||||
try {
|
try {
|
||||||
// Check if session exists
|
// Check if session exists
|
||||||
const { ok: sessionExists } = await runCommand([
|
const { ok: sessionExists } = await runCommand(
|
||||||
"tmux",
|
["tmux", "has-session", "-t", options.sessionName],
|
||||||
"has-session",
|
{
|
||||||
"-t",
|
mode,
|
||||||
options.sessionName,
|
spinnerMsg: `Checking if session '${options.sessionName}' exists...`,
|
||||||
]);
|
abortSignal,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
// 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", options.sessionName], {
|
||||||
|
mode,
|
||||||
|
spinnerMsg: `Killing session '${options.sessionName}'...`,
|
||||||
|
abortSignal,
|
||||||
|
});
|
||||||
instructionsArr.push(
|
instructionsArr.push(
|
||||||
chalk.green(`Closed session '${options.sessionName}'`),
|
chalk.green(`Closed session '${options.sessionName}'`),
|
||||||
);
|
);
|
||||||
@@ -96,6 +102,7 @@ export async function closeMultiModel(
|
|||||||
await runCommand(["git", "worktree", "remove", "-f", worktree.path], {
|
await runCommand(["git", "worktree", "remove", "-f", worktree.path], {
|
||||||
mode,
|
mode,
|
||||||
spinnerMsg: `Removing worktree ${worktree.path}...`,
|
spinnerMsg: `Removing worktree ${worktree.path}...`,
|
||||||
|
abortSignal,
|
||||||
});
|
});
|
||||||
worktreesRemoved.push(worktree.path);
|
worktreesRemoved.push(worktree.path);
|
||||||
|
|
||||||
@@ -121,6 +128,7 @@ export async function closeMultiModel(
|
|||||||
await runCommand(["git", "branch", "-D", worktree.branch], {
|
await runCommand(["git", "branch", "-D", worktree.branch], {
|
||||||
mode,
|
mode,
|
||||||
spinnerMsg: `Deleting branch ${worktree.branch}...`,
|
spinnerMsg: `Deleting branch ${worktree.branch}...`,
|
||||||
|
abortSignal,
|
||||||
});
|
});
|
||||||
branchesDeleted.push(worktree.branch);
|
branchesDeleted.push(worktree.branch);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -133,7 +141,11 @@ export async function closeMultiModel(
|
|||||||
// Also remove the base directory
|
// Also remove the base directory
|
||||||
const worktreeBase = getSessionPath(safeSessionName);
|
const worktreeBase = getSessionPath(safeSessionName);
|
||||||
try {
|
try {
|
||||||
await runCommand(["rm", "-rf", worktreeBase]);
|
await runCommand(["rm", "-rf", worktreeBase], {
|
||||||
|
mode,
|
||||||
|
spinnerMsg: `Removing worktree base directory: ${worktreeBase}...`,
|
||||||
|
abortSignal,
|
||||||
|
});
|
||||||
} catch {
|
} catch {
|
||||||
// Ignore errors
|
// Ignore errors
|
||||||
}
|
}
|
||||||
|
|||||||
+54
-27
@@ -1,6 +1,6 @@
|
|||||||
import * as fs from "node:fs";
|
import * as fs from "node:fs";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import type { MultiModelOptions, MultiModelResult } from "../types";
|
import type { OpenSessionOptions, OpenSessionResult } from "../types";
|
||||||
import {
|
import {
|
||||||
createWindowPlans,
|
createWindowPlans,
|
||||||
findDuplicates,
|
findDuplicates,
|
||||||
@@ -36,13 +36,13 @@ import {
|
|||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
export async function openMultiModel(
|
export async function openMultiModel(
|
||||||
options: MultiModelOptions,
|
options: OpenSessionOptions,
|
||||||
): Promise<MultiModelResult> {
|
): Promise<OpenSessionResult> {
|
||||||
const sessionName = options.sessionName.trim();
|
const sessionName = options.sessionName.trim();
|
||||||
const safeSessionName = sanitizeName(sessionName);
|
const safeSessionName = sanitizeName(sessionName);
|
||||||
const models = normalizeModels(options.models);
|
const models = normalizeModels(options.models);
|
||||||
const binaryName = options.binaryName || "opencode";
|
const binaryName = options.binaryName || "opencode";
|
||||||
const mode = options.mode;
|
const { mode, abortSignal } = options;
|
||||||
|
|
||||||
if (!sessionName) {
|
if (!sessionName) {
|
||||||
return {
|
return {
|
||||||
@@ -69,11 +69,14 @@ export async function openMultiModel(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const gitRepoCheck = await runCommand([
|
const gitRepoCheck = await runCommand(
|
||||||
"git",
|
["git", "rev-parse", "--is-inside-work-tree"],
|
||||||
"rev-parse",
|
{
|
||||||
"--is-inside-work-tree",
|
mode,
|
||||||
]);
|
spinnerMsg: "Checking if inside git repository...",
|
||||||
|
abortSignal,
|
||||||
|
},
|
||||||
|
);
|
||||||
if (!gitRepoCheck.ok) {
|
if (!gitRepoCheck.ok) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -82,7 +85,11 @@ export async function openMultiModel(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const tmuxExists = await runCommand(["command", "-v", "tmux"]);
|
const tmuxExists = await runCommand(["command", "-v", "tmux"], {
|
||||||
|
mode,
|
||||||
|
spinnerMsg: "Checking if tmux is installed...",
|
||||||
|
abortSignal,
|
||||||
|
});
|
||||||
if (!tmuxExists.ok) {
|
if (!tmuxExists.ok) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -91,7 +98,11 @@ export async function openMultiModel(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const binaryExists = await runCommand(["command", "-v", binaryName]);
|
const binaryExists = await runCommand(["command", "-v", binaryName], {
|
||||||
|
mode,
|
||||||
|
spinnerMsg: `Checking if ${binaryName} is installed...`,
|
||||||
|
abortSignal,
|
||||||
|
});
|
||||||
if (!binaryExists.ok) {
|
if (!binaryExists.ok) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -103,6 +114,7 @@ export async function openMultiModel(
|
|||||||
const modelListResult = await runCommand([binaryName, "models"], {
|
const modelListResult = await runCommand([binaryName, "models"], {
|
||||||
mode,
|
mode,
|
||||||
spinnerMsg: `Fetching available models using \`${binaryName} models\`...`,
|
spinnerMsg: `Fetching available models using \`${binaryName} models\`...`,
|
||||||
|
abortSignal,
|
||||||
});
|
});
|
||||||
if (!modelListResult.ok) {
|
if (!modelListResult.ok) {
|
||||||
return {
|
return {
|
||||||
@@ -126,12 +138,14 @@ export async function openMultiModel(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const sessionExists = await runCommand([
|
const sessionExists = await runCommand(
|
||||||
"tmux",
|
["tmux", "has-session", "-t", sessionName],
|
||||||
"has-session",
|
{
|
||||||
"-t",
|
mode,
|
||||||
sessionName,
|
spinnerMsg: `Checking if tmux session '${sessionName}' already exists...`,
|
||||||
]);
|
abortSignal,
|
||||||
|
},
|
||||||
|
);
|
||||||
if (sessionExists.ok) {
|
if (sessionExists.ok) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -166,13 +180,14 @@ export async function openMultiModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const branchExists = await runCommand([
|
const branchExists = await runCommand(
|
||||||
"git",
|
["git", "show-ref", "--verify", "--quiet", `refs/heads/${branchName}`],
|
||||||
"show-ref",
|
{
|
||||||
"--verify",
|
mode,
|
||||||
"--quiet",
|
spinnerMsg: `Checking if branch '${branchName}' already exists...`,
|
||||||
`refs/heads/${branchName}`,
|
abortSignal,
|
||||||
]);
|
},
|
||||||
|
);
|
||||||
if (branchExists.ok) {
|
if (branchExists.ok) {
|
||||||
if (!isFirst) {
|
if (!isFirst) {
|
||||||
failedModels.push(
|
failedModels.push(
|
||||||
@@ -190,7 +205,11 @@ export async function openMultiModel(
|
|||||||
|
|
||||||
const worktreeResult = await runCommand(
|
const worktreeResult = await runCommand(
|
||||||
["git", "worktree", "add", "-b", branchName, worktreePath],
|
["git", "worktree", "add", "-b", branchName, worktreePath],
|
||||||
{ mode, spinnerMsg: `Creating worktree for ${plan.model}...` },
|
{
|
||||||
|
mode,
|
||||||
|
spinnerMsg: `Creating worktree for ${plan.model}...`,
|
||||||
|
abortSignal,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (!worktreeResult.ok) {
|
if (!worktreeResult.ok) {
|
||||||
if (!isFirst) {
|
if (!isFirst) {
|
||||||
@@ -220,7 +239,11 @@ export async function openMultiModel(
|
|||||||
"-c",
|
"-c",
|
||||||
worktreePath,
|
worktreePath,
|
||||||
],
|
],
|
||||||
{ mode, spinnerMsg: `Starting tmux session '${sessionName}'...` },
|
{
|
||||||
|
mode,
|
||||||
|
spinnerMsg: `Starting tmux session '${sessionName}'...`,
|
||||||
|
abortSignal,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!sessionCreateResult.ok) {
|
if (!sessionCreateResult.ok) {
|
||||||
@@ -244,7 +267,11 @@ export async function openMultiModel(
|
|||||||
"-c",
|
"-c",
|
||||||
worktreePath,
|
worktreePath,
|
||||||
],
|
],
|
||||||
{ mode, spinnerMsg: `Creating tmux window '${plan.windowName}'...` },
|
{
|
||||||
|
mode,
|
||||||
|
spinnerMsg: `Creating tmux window '${plan.windowName}'...`,
|
||||||
|
abortSignal,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!windowCreateResult.ok) {
|
if (!windowCreateResult.ok) {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ export async function runCommand(
|
|||||||
spinner = sharedSpinner;
|
spinner = sharedSpinner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
options?.abortSignal?.throwIfAborted();
|
||||||
const result = await Bun.$`${parts}`.quiet().nothrow();
|
const result = await Bun.$`${parts}`.quiet().nothrow();
|
||||||
|
|
||||||
if (spinner) {
|
if (spinner) {
|
||||||
|
|||||||
+1
-8
@@ -32,14 +32,7 @@ Arguments:
|
|||||||
models: args.models,
|
models: args.models,
|
||||||
binaryName,
|
binaryName,
|
||||||
mode: "tool",
|
mode: "tool",
|
||||||
});
|
abortSignal: context.abort,
|
||||||
|
|
||||||
context.metadata({
|
|
||||||
title: `multi-model ${args.sessionName}`,
|
|
||||||
metadata: {
|
|
||||||
safeSessionName: result.sessionName,
|
|
||||||
modelCount: args.models.length,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
|
|||||||
+10
-2
@@ -8,7 +8,7 @@ export interface MultiModelSettings {
|
|||||||
/**
|
/**
|
||||||
* Options for launching a multi-model tmux session.
|
* Options for launching a multi-model tmux session.
|
||||||
*/
|
*/
|
||||||
export interface MultiModelOptions {
|
export interface OpenSessionOptions {
|
||||||
/** Name for the tmux session. */
|
/** Name for the tmux session. */
|
||||||
sessionName: string;
|
sessionName: string;
|
||||||
/** List of model ids to launch. */
|
/** List of model ids to launch. */
|
||||||
@@ -19,12 +19,14 @@ export interface MultiModelOptions {
|
|||||||
mode: "cli" | "tool";
|
mode: "cli" | "tool";
|
||||||
/** Save models to settings file on successful launch (CLI only). */
|
/** Save models to settings file on successful launch (CLI only). */
|
||||||
saveToSettings?: boolean;
|
saveToSettings?: boolean;
|
||||||
|
/** Signal to abort the operation. */
|
||||||
|
abortSignal?: AbortSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Result returned after attempting to launch a multi-model session.
|
* Result returned after attempting to launch a multi-model session.
|
||||||
*/
|
*/
|
||||||
export interface MultiModelResult {
|
export interface OpenSessionResult {
|
||||||
/** Whether the launch succeeded. */
|
/** Whether the launch succeeded. */
|
||||||
success: boolean;
|
success: boolean;
|
||||||
/** The session name used. */
|
/** The session name used. */
|
||||||
@@ -51,6 +53,8 @@ export interface CloseSessionOptions {
|
|||||||
force?: boolean;
|
force?: boolean;
|
||||||
/** Caller context: 'cli' shows shell commands, 'tool' shows tool instructions. */
|
/** Caller context: 'cli' shows shell commands, 'tool' shows tool instructions. */
|
||||||
mode: "cli" | "tool";
|
mode: "cli" | "tool";
|
||||||
|
/** Signal to abort the operation. */
|
||||||
|
abortSignal?: AbortSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,6 +104,8 @@ export interface CleanupOptions {
|
|||||||
remote?: string | false;
|
remote?: string | false;
|
||||||
/** Caller context: 'cli' shows shell commands, 'tool' shows tool instructions. */
|
/** Caller context: 'cli' shows shell commands, 'tool' shows tool instructions. */
|
||||||
mode: "cli" | "tool";
|
mode: "cli" | "tool";
|
||||||
|
/** Optional abort signal to cancel the operation. */
|
||||||
|
abortSignal?: AbortSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -146,6 +152,8 @@ export interface RunCommandOptions {
|
|||||||
mode?: "cli" | "tool";
|
mode?: "cli" | "tool";
|
||||||
/** Message to display in the spinner while the command runs. */
|
/** Message to display in the spinner while the command runs. */
|
||||||
spinnerMsg?: string;
|
spinnerMsg?: string;
|
||||||
|
/** Signal to abort the operation. Check it before running the command. */
|
||||||
|
abortSignal?: AbortSignal;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user