mirror of
https://github.com/bendtherules/opencode-multi-model.git
synced 2026-08-18 13:42:21 +00:00
plan: Multi-format multi-model tool
This commit is contained in:
@@ -1,246 +0,0 @@
|
|||||||
---
|
|
||||||
model: minimax-m2.5-free
|
|
||||||
---
|
|
||||||
|
|
||||||
# Plan: Package multi-model tool as npm OpenCode Plugin
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Convert the existing `.opencode/tools/multi-model.ts` into a sharable npm package that users can install and configure. The tool will be packaged as an **OpenCode Plugin** that exposes the multi-model tool functionality.
|
|
||||||
|
|
||||||
## Key Requirements
|
|
||||||
|
|
||||||
1. **Package as OpenCode Plugin** - Users install via `"plugin"` field in config
|
|
||||||
2. **Custom npm scope** - Package name will be user-defined (e.g., `@username/opencode-multi-model`)
|
|
||||||
3. **Configurable binary** - Support both `opencode` and `kilo` binaries via environment variable/config
|
|
||||||
|
|
||||||
## Architecture
|
|
||||||
|
|
||||||
### Current State
|
|
||||||
- Tool lives in `.opencode/tools/multi-model.ts`
|
|
||||||
- Uses `@opencode-ai/plugin` to define custom tool
|
|
||||||
- Already has comprehensive tests in `.opencode/multi-model.test.ts`
|
|
||||||
|
|
||||||
### Target State
|
|
||||||
```
|
|
||||||
├── src/
|
|
||||||
│ ├── index.ts # Plugin entry point
|
|
||||||
│ └── multi-model.ts # Tool definition (refactored)
|
|
||||||
├── tests/
|
|
||||||
│ └── multi-model.test.ts # Tests (refactored from existing)
|
|
||||||
├── package.json # NPM package config
|
|
||||||
├── tsconfig.json # TypeScript config
|
|
||||||
├── README.md # Documentation
|
|
||||||
└── LICENSE # License
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation Steps
|
|
||||||
|
|
||||||
### 1. Create Plugin Structure
|
|
||||||
|
|
||||||
Create new package with proper TypeScript configuration:
|
|
||||||
|
|
||||||
```json
|
|
||||||
// package.json
|
|
||||||
{
|
|
||||||
"name": "@username/opencode-multi-model",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"type": "module",
|
|
||||||
"main": "./dist/index.js",
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"import": "./dist/index.js",
|
|
||||||
"types": "./dist/index.d.ts"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@opencode-ai/plugin": "^1.2.26",
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"bun-types": "^1.0.0",
|
|
||||||
"typescript": "^5.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Refactor Tool for Plugin Export
|
|
||||||
|
|
||||||
The tool definition stays similar but needs env-based binary configuration:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// src/multi-model.ts
|
|
||||||
import { tool, type ToolContext } from "@opencode-ai/plugin"
|
|
||||||
|
|
||||||
// Make binary name configurable via environment variable
|
|
||||||
function getBinaryName(context: ToolContext): string {
|
|
||||||
// Users set OPENCODE_MULTI_MODEL_BINARY=kilo or OPENCODE_MULTI_MODEL_BINARY=opencode
|
|
||||||
return process.env.OPENCODE_MULTI_MODEL_BINARY || "opencode"
|
|
||||||
}
|
|
||||||
|
|
||||||
export const multiModelTool = tool({
|
|
||||||
description: "Launch multiple OpenCode models in tmux",
|
|
||||||
args: {
|
|
||||||
sessionName: tool.schema.string().min(1).describe("tmux session name to create"),
|
|
||||||
models: tool.schema
|
|
||||||
.array(tool.schema.string().min(1))
|
|
||||||
.min(1)
|
|
||||||
.describe("one or more OpenCode model ids to launch"),
|
|
||||||
},
|
|
||||||
async execute(args, context) {
|
|
||||||
const binaryName = getBinaryName(context)
|
|
||||||
// ... use binaryName instead of hardcoded "opencode"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Create Plugin Entry Point
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// src/index.ts
|
|
||||||
import type { Plugin } from "@opencode-ai/plugin"
|
|
||||||
import { multiModelTool } from "./multi-model"
|
|
||||||
|
|
||||||
export const OpenCodeMultiModelPlugin: Plugin = async (ctx) => {
|
|
||||||
return {
|
|
||||||
tool: {
|
|
||||||
"multi-model": multiModelTool,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default OpenCodeMultiModelPlugin
|
|
||||||
export { multiModelTool }
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Add Build Configuration
|
|
||||||
|
|
||||||
```json
|
|
||||||
// tsconfig.json
|
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2022",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"strict": true,
|
|
||||||
"declaration": true,
|
|
||||||
"outDir": "./dist",
|
|
||||||
"rootDir": "./src",
|
|
||||||
"skipLibCheck": true
|
|
||||||
},
|
|
||||||
"include": ["src/**/*"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5. Add Build Script and Tests
|
|
||||||
|
|
||||||
```json
|
|
||||||
// package.json scripts
|
|
||||||
{
|
|
||||||
"scripts": {
|
|
||||||
"build": "bun build src/index.ts --outdir dist --target bun --format esm",
|
|
||||||
"test": "bun test",
|
|
||||||
"prepublishOnly": "bun run build"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Tests**: Copy `.opencode/multi-model.test.ts` to `tests/multi-model.test.ts` and update imports:
|
|
||||||
- Change `import multiModelTool, { __testing_helpers } from "./tools/multi-model"`
|
|
||||||
- To `import multiModelTool, { __testing_helpers } from "../src/multi-model"`
|
|
||||||
|
|
||||||
The existing tests should work with minimal changes since the tool API remains the same.
|
|
||||||
|
|
||||||
### 6. Create README with Usage Instructions
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# opencode-multi-model
|
|
||||||
|
|
||||||
Plugin for OpenCode that enables launching multiple AI models in tmux sessions.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Add to your OpenCode config (`opencode.json`):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"plugin": ["@username/opencode-multi-model"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```
|
|
||||||
Use multi-model tool to launch multiple models:
|
|
||||||
sessionName: my-session, models: ["openai/gpt-4o", "anthropic/claude-3-5-sonnet"]
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
### Custom Binary
|
|
||||||
|
|
||||||
By default uses `opencode`. To use `kilo` instead, set environment variable:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"plugin": ["@username/opencode-multi-model"],
|
|
||||||
"env": {
|
|
||||||
"OPENCODE_MULTI_MODEL_BINARY": "kilo"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- tmux
|
|
||||||
- git
|
|
||||||
- opencode or kilo binary
|
|
||||||
|
|
||||||
|
|
||||||
## Files to Create/Modify
|
|
||||||
|
|
||||||
### New Files
|
|
||||||
- `package.json` - NPM package configuration
|
|
||||||
- `tsconfig.json` - TypeScript configuration
|
|
||||||
- `src/index.ts` - Plugin entry point
|
|
||||||
- `src/multi-model.ts` - Refactored tool with configurable binary
|
|
||||||
- `tests/multi-model.test.ts` - Tests (copy from `.opencode/multi-model.test.ts`)
|
|
||||||
- `README.md` - Installation and usage docs
|
|
||||||
- `LICENSE` - License file
|
|
||||||
|
|
||||||
### Delete
|
|
||||||
- Delete `.opencode/tools/multi-model.ts` after refactoring to new package
|
|
||||||
- Delete `.opencode/multi-model.test.ts` after copying to new package
|
|
||||||
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
1. **Build**: Run `bun run build` to compile TypeScript
|
|
||||||
2. **Test**: Run `bun test` in the new package directory
|
|
||||||
3. **Local Test**:
|
|
||||||
- Link package locally with `bun link`
|
|
||||||
- Add to test project config
|
|
||||||
- Verify tool works
|
|
||||||
4. **Publish**: Run `npm publish --access public` (or `bun publish`)
|
|
||||||
|
|
||||||
## Usage for End Users
|
|
||||||
|
|
||||||
After publishing, users will:
|
|
||||||
|
|
||||||
1. Install package:
|
|
||||||
```bash
|
|
||||||
npm install -D @username/opencode-multi-model
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Add to config:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"plugin": ["@username/opencode-multi-model"]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Use the tool:
|
|
||||||
```
|
|
||||||
Use tool multi-model with sessionName "testing" with models opencode/mimo-v2-flash-free and opencode/minimax-m2.5-free
|
|
||||||
```
|
|
||||||
@@ -0,0 +1,888 @@
|
|||||||
|
---
|
||||||
|
model: ollama/kimi-k2.5:cloud
|
||||||
|
---
|
||||||
|
|
||||||
|
# Plan: Multi-format multi-model tool - OpenCode Plugin + CLI + Project Tool
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Convert the existing `.opencode/tools/multi-model.ts` into a sharable npm package that users can install and configure. The tool will support **three usage formats**:
|
||||||
|
1. **OpenCode Plugin** - Installed and loaded via plugin system
|
||||||
|
2. **Standalone CLI** - Direct command-line usage without OpenCode
|
||||||
|
3. **Project Tool** - Re-exported in `.opencode/tools/` for local testing
|
||||||
|
|
||||||
|
## Key Requirements
|
||||||
|
|
||||||
|
1. **Package as OpenCode Plugin** - Users install via `"plugin"` field in config
|
||||||
|
2. **Custom npm scope** - Package name will be user-defined (e.g., `@username/opencode-multi-model`)
|
||||||
|
3. **Configurable binary** - Support both `opencode` and `kilo` binaries via environment variable/config
|
||||||
|
4. **Standalone CLI** - Can be used directly via `npx @username/opencode-multi-model ...`
|
||||||
|
5. **Project-level tool** - Re-export tool in `.opencode/tools/multi-model.ts` for local development
|
||||||
|
6. **Shared core logic** - Single implementation across all three formats to avoid duplication
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Current State
|
||||||
|
- Tool lives in `.opencode/tools/multi-model.ts`
|
||||||
|
- Uses `@opencode-ai/plugin` to define custom tool
|
||||||
|
- Already has comprehensive tests in `.opencode/multi-model.test.ts`
|
||||||
|
|
||||||
|
### Target State
|
||||||
|
```
|
||||||
|
├── src/
|
||||||
|
│ ├── index.ts # Plugin entry point - exports open and close tools
|
||||||
|
│ ├── cli.ts # CLI entry point with open/close subcommands
|
||||||
|
│ ├── core/
|
||||||
|
│ │ ├── index.ts # Core exports (launchMultiModel, closeMultiModel)
|
||||||
|
│ │ ├── launch.ts # Launch session logic
|
||||||
|
│ │ ├── close.ts # Close session logic
|
||||||
|
│ │ └── utils.ts # Helper functions (shellQuote, levenshtein, etc.)
|
||||||
|
│ ├── tools/
|
||||||
|
│ │ ├── open.ts # Open tool definition
|
||||||
|
│ │ └── close.ts # Close tool definition
|
||||||
|
│ └── types.ts # Shared TypeScript types
|
||||||
|
├── tests/
|
||||||
|
│ └── multi-model.test.ts # Tests (refactored from existing)
|
||||||
|
├── package.json # NPM package config
|
||||||
|
├── tsconfig.json # TypeScript config
|
||||||
|
├── README.md # Documentation
|
||||||
|
└── LICENSE # License
|
||||||
|
```
|
||||||
|
|
||||||
|
## Implementation Steps
|
||||||
|
|
||||||
|
### 1. Create Plugin Structure
|
||||||
|
|
||||||
|
Create new package with proper TypeScript configuration:
|
||||||
|
|
||||||
|
```json
|
||||||
|
// package.json
|
||||||
|
{
|
||||||
|
"name": "@username/opencode-multi-model",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"bin": {
|
||||||
|
"opencode-multi-model": "./dist/cli.js"
|
||||||
|
},
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
},
|
||||||
|
"./core": {
|
||||||
|
"import": "./dist/core/index.js",
|
||||||
|
"types": "./dist/core/index.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@opencode-ai/plugin": "^1.2.26",
|
||||||
|
"commander": "^12.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"bun-types": "^1.0.0",
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create Shared Types
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/types.ts
|
||||||
|
export interface MultiModelOptions {
|
||||||
|
sessionName: string;
|
||||||
|
models: string[];
|
||||||
|
binaryName?: string; // 'opencode' or 'kilo'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MultiModelResult {
|
||||||
|
success: boolean;
|
||||||
|
sessionName: string;
|
||||||
|
windows?: string[];
|
||||||
|
error?: string;
|
||||||
|
instructions?: string; // Connection and cleanup instructions
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CloseSessionOptions {
|
||||||
|
sessionName: string;
|
||||||
|
cleanupWorktrees?: boolean; // Remove worktrees and branches
|
||||||
|
force?: boolean; // Skip confirmation prompts
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CloseSessionResult {
|
||||||
|
success: boolean;
|
||||||
|
sessionName: string;
|
||||||
|
error?: string;
|
||||||
|
cleanupPerformed?: boolean;
|
||||||
|
worktreesRemoved?: string[];
|
||||||
|
branchesDeleted?: string[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Refactor Utils to Separate File
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/core/utils.ts
|
||||||
|
// All helper functions extracted from current multi-model.ts
|
||||||
|
|
||||||
|
export function runCommand(command: string): Promise<{ stdout: string; stderr: string; exitCode: number }> {
|
||||||
|
// Implementation using Bun.$
|
||||||
|
}
|
||||||
|
|
||||||
|
export function shellQuote(str: string): string {
|
||||||
|
// POSIX-safe shell escaping
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeModels(models: unknown): string[] {
|
||||||
|
// Input sanitization
|
||||||
|
}
|
||||||
|
|
||||||
|
export function findDuplicates<T>(arr: T[]): T[] {
|
||||||
|
// Detect duplicates
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWindowBaseName(model: string): string {
|
||||||
|
// Generate tmux-safe window names
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createWindowPlans(models: string[]): Array<{ model: string; windowName: string }> {
|
||||||
|
// Create unique window plans with collision handling
|
||||||
|
}
|
||||||
|
|
||||||
|
export function levenshtein(a: string, b: string): number {
|
||||||
|
// Edit distance calculation for fuzzy matching
|
||||||
|
}
|
||||||
|
|
||||||
|
export function suggestModels(input: string, validModels: string[], maxSuggestions?: number): string[] {
|
||||||
|
// Fuzzy matching and suggestion limits
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatInvalidModelError(invalidModels: string[], suggestions: Record<string, string[]>): string {
|
||||||
|
// Error message formatting
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sanitizeName(name: string): string {
|
||||||
|
// Session name sanitization
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWorktreePath(sessionName: string, windowName: string): string {
|
||||||
|
// Worktree path generation
|
||||||
|
const homedir = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
||||||
|
return `${homedir}/.local/share/opencode/multi-model/${sessionName}/${windowName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorktreeInfo {
|
||||||
|
path: string;
|
||||||
|
branch: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWorktreesForSession(sessionName: string): Promise<WorktreeInfo[]> {
|
||||||
|
// Get all worktrees and filter for this session
|
||||||
|
const { stdout } = await runCommand("git worktree list --porcelain");
|
||||||
|
const worktrees: WorktreeInfo[] = [];
|
||||||
|
|
||||||
|
const homedir = process.env.HOME || process.env.USERPROFILE || "/tmp";
|
||||||
|
const sessionPath = `${homedir}/.local/share/opencode/multi-model/${sessionName}`;
|
||||||
|
|
||||||
|
let currentWorktree: Partial<WorktreeInfo> = {};
|
||||||
|
|
||||||
|
for (const line of stdout.split("\n")) {
|
||||||
|
if (line.startsWith("worktree ")) {
|
||||||
|
if (currentWorktree.path && currentWorktree.branch) {
|
||||||
|
worktrees.push(currentWorktree as WorktreeInfo);
|
||||||
|
}
|
||||||
|
currentWorktree = {
|
||||||
|
path: line.slice(9)
|
||||||
|
};
|
||||||
|
} else if (line.startsWith("branch ")) {
|
||||||
|
currentWorktree.branch = line.slice(7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add last worktree
|
||||||
|
if (currentWorktree.path && currentWorktree.branch) {
|
||||||
|
worktrees.push(currentWorktree as WorktreeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter for session-specific worktrees
|
||||||
|
return worktrees.filter(wt => wt.path.startsWith(sessionPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function undoWorktree(worktreePath: string): Promise<void> {
|
||||||
|
// Cleanup helper for failed operations
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBinaryName(context?: { config?: { multiModelBinary?: string } }): string {
|
||||||
|
// Priority: context config > env var > default
|
||||||
|
return context?.config?.multiModelBinary ||
|
||||||
|
process.env.OPENCODE_MULTI_MODEL_BINARY ||
|
||||||
|
"opencode";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Create Core Launch Logic
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/core/launch.ts
|
||||||
|
import type { MultiModelOptions, MultiModelResult } from "../types.js";
|
||||||
|
import {
|
||||||
|
runCommand, shellQuote, normalizeModels, findDuplicates,
|
||||||
|
createWindowPlans, suggestModels, formatInvalidModelError,
|
||||||
|
sanitizeName, getWorktreePath, undoWorktree, getBinaryName
|
||||||
|
} from "./utils.js";
|
||||||
|
|
||||||
|
export async function launchMultiModel(options: MultiModelOptions): Promise<MultiModelResult> {
|
||||||
|
// Core implementation from current multi-model.ts
|
||||||
|
// All the validation, tmux setup, worktree creation, etc.
|
||||||
|
|
||||||
|
// Returns result with instructions for connecting and cleanup
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
sessionName: options.sessionName,
|
||||||
|
windows: [], // populated with actual window names
|
||||||
|
instructions: `
|
||||||
|
Session "${options.sessionName}" created successfully!
|
||||||
|
|
||||||
|
To attach to the session:
|
||||||
|
tmux attach -t ${options.sessionName}
|
||||||
|
|
||||||
|
To list all windows:
|
||||||
|
tmux list-windows -t ${options.sessionName}
|
||||||
|
|
||||||
|
To close the session and cleanup:
|
||||||
|
opencode-multi-model close ${options.sessionName}
|
||||||
|
|
||||||
|
Or manually:
|
||||||
|
tmux kill-session -t ${options.sessionName}
|
||||||
|
rm -rf ~/.local/share/opencode/multi-model/${safeSessionName} && git worktree prune
|
||||||
|
git branch -D $(git branch --format='%(refname:short)' --list 'opencode/${safeSessionName}/*')
|
||||||
|
`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Create Core Close Logic
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/core/close.ts
|
||||||
|
import type { CloseSessionOptions, CloseSessionResult } from "../types.ts";
|
||||||
|
import { runCommand, getWorktreePath, getWorktreesForSession, sanitizeName } from "./utils.ts";
|
||||||
|
import * as readline from "readline";
|
||||||
|
|
||||||
|
async function promptUser(question: string): Promise<string> {
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
rl.question(question, (answer) => {
|
||||||
|
rl.close();
|
||||||
|
resolve(answer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function closeMultiModel(options: CloseSessionOptions): Promise<CloseSessionResult> {
|
||||||
|
const worktreesRemoved: string[] = [];
|
||||||
|
const branchesDeleted: string[] = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if session exists
|
||||||
|
const { exitCode } = await runCommand(`tmux has-session -t ${options.sessionName} 2>/dev/null`);
|
||||||
|
const sessionExists = exitCode === 0;
|
||||||
|
|
||||||
|
// Get list of worktrees for this session before killing tmux
|
||||||
|
const worktrees = await getWorktreesForSession(options.sessionName);
|
||||||
|
|
||||||
|
// If cleanup requested and not forced, ask for confirmation
|
||||||
|
if (options.cleanupWorktrees && !options.force && worktrees.length > 0) {
|
||||||
|
console.log(`\nThe following worktrees and branches will be removed:`);
|
||||||
|
worktrees.forEach(wt => console.log(` - ${wt.path} (branch: ${wt.branch})`));
|
||||||
|
|
||||||
|
const answer = await promptUser("\nDo you want to proceed? (y/N): ");
|
||||||
|
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
sessionName: options.sessionName,
|
||||||
|
error: "Cleanup cancelled by user"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill tmux session if it exists
|
||||||
|
if (sessionExists) {
|
||||||
|
await runCommand(`tmux kill-session -t ${options.sessionName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let cleanupPerformed = false;
|
||||||
|
|
||||||
|
// Cleanup worktrees and optionally branches
|
||||||
|
if (options.cleanup) {
|
||||||
|
for (const worktree of worktrees) {
|
||||||
|
try {
|
||||||
|
// Remove worktree
|
||||||
|
await runCommand(`git worktree remove -f ${worktree.path}`);
|
||||||
|
worktreesRemoved.push(worktree.path);
|
||||||
|
|
||||||
|
await runCommand(`git branch -D ${worktree.branch}`);
|
||||||
|
branchesDeleted.push(worktree.branch);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`Warning: Failed to cleanup worktree ${worktree.path}: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also remove the base directory if empty
|
||||||
|
const worktreeBase = `~/.local/share/opencode/multi-model/${options.sessionName}`;
|
||||||
|
try {
|
||||||
|
await runCommand(`rmdir ${worktreeBase} 2>/dev/null || true`);
|
||||||
|
} catch {
|
||||||
|
// Ignore errors if directory not empty
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupPerformed = worktreesRemoved.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
sessionName: options.sessionName,
|
||||||
|
cleanupPerformed,
|
||||||
|
worktreesRemoved,
|
||||||
|
branchesDeleted
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
sessionName: options.sessionName,
|
||||||
|
error: String(error),
|
||||||
|
worktreesRemoved,
|
||||||
|
branchesDeleted
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Create Core Index
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/core/index.ts
|
||||||
|
export { launchMultiModel } from "./launch.ts";
|
||||||
|
export { closeMultiModel } from "./close.ts";
|
||||||
|
export * from "./utils.ts";
|
||||||
|
export * from "../types.ts";
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. Create Open Tool Definition
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tools/open.ts
|
||||||
|
import { tool, type ToolContext } from "@opencode-ai/plugin";
|
||||||
|
import { launchMultiModel } from "../core/launch.ts";
|
||||||
|
import { getBinaryName } from "../core/utils.ts";
|
||||||
|
|
||||||
|
export const openTool = tool({
|
||||||
|
name: "multi-model-open",
|
||||||
|
description: "Launch multiple OpenCode models in tmux session",
|
||||||
|
args: {
|
||||||
|
sessionName: tool.schema.string().min(1).describe("tmux session name to create"),
|
||||||
|
models: tool.schema
|
||||||
|
.array(tool.schema.string().min(1))
|
||||||
|
.min(1)
|
||||||
|
.describe("one or more OpenCode model ids to launch"),
|
||||||
|
},
|
||||||
|
async execute(args, context: ToolContext) {
|
||||||
|
const binaryName = getBinaryName(context);
|
||||||
|
const result = await launchMultiModel({
|
||||||
|
sessionName: args.sessionName,
|
||||||
|
models: args.models,
|
||||||
|
binaryName
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: result.instructions || `Created session "${result.sessionName}" with ${result.windows?.length || 0} windows`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default openTool;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. Create Close Tool Definition
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/tools/close.ts
|
||||||
|
import { tool, type ToolContext } from "@opencode-ai/plugin";
|
||||||
|
import { closeMultiModel } from "../core/close.ts";
|
||||||
|
|
||||||
|
export const closeTool = tool({
|
||||||
|
name: "multi-model-close",
|
||||||
|
description: "Close a multi-model tmux session and optionally cleanup worktrees and branches",
|
||||||
|
args: {
|
||||||
|
sessionName: tool.schema.string().min(1).describe("tmux session name to close"),
|
||||||
|
cleanupWorktrees: tool.schema.boolean().optional().describe("whether to remove worktrees and delete branches (default: true)"),(default: true)"),
|
||||||
|
},
|
||||||
|
async execute(args, context: ToolContext) {
|
||||||
|
// In plugin mode, we skip confirmation (force=true) since there's no interactive terminal
|
||||||
|
const result = await closeMultiModel({
|
||||||
|
sessionName: args.sessionName,
|
||||||
|
cleanupWorktrees: args.cleanupWorktrees,
|
||||||
|
force: true, // Skip confirmation in plugin mode
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
let details = "";
|
||||||
|
if (result.cleanupPerformed) {
|
||||||
|
details += ` Removed ${result.worktreesRemoved?.length || 0} worktrees.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: `Session "${result.sessionName}" has been closed.${details}`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default closeTool;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9. Create Plugin Entry Point
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/index.ts
|
||||||
|
import type { Plugin } from "@opencode-ai/plugin";
|
||||||
|
import { openTool } from "./tools/open.ts";
|
||||||
|
import { closeTool } from "./tools/close.ts";
|
||||||
|
|
||||||
|
export const OpenCodeMultiModelPlugin: Plugin = async (ctx) => {
|
||||||
|
return {
|
||||||
|
tool: {
|
||||||
|
"multi-model-open": openTool,
|
||||||
|
"multi-model-close": closeTool,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default OpenCodeMultiModelPlugin;
|
||||||
|
export { openTool, closeTool };
|
||||||
|
export * from "./core/index.ts";
|
||||||
|
```
|
||||||
|
|
||||||
|
### 10. Create CLI Entry Point (with env support)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/cli.ts
|
||||||
|
#!/usr/bin/env node
|
||||||
|
import { Command } from "commander";
|
||||||
|
import { launchMultiModel } from "./core/launch.js";
|
||||||
|
import { closeMultiModel } from "./core/close.js";
|
||||||
|
import { getBinaryName } from "./core/utils.js";
|
||||||
|
|
||||||
|
const program = new Command();
|
||||||
|
|
||||||
|
program
|
||||||
|
.name("opencode-multi-model")
|
||||||
|
.description("Launch multiple OpenCode models in tmux sessions")
|
||||||
|
.version("1.0.0");
|
||||||
|
|
||||||
|
// Open subcommand
|
||||||
|
program
|
||||||
|
.command("open")
|
||||||
|
.description("Create a new multi-model tmux session")
|
||||||
|
.argument("<session-name>", "Name for the tmux session")
|
||||||
|
.option("-m, --models <models...>", "Model IDs to launch (space-separated)", [])
|
||||||
|
.option("-b, --binary <binary>", "Binary to use (opencode or kilo). Defaults to env var OPENCODE_MULTI_MODEL_BINARY or 'opencode'")
|
||||||
|
.action(async (sessionName, options) => {
|
||||||
|
try {
|
||||||
|
// CLI flag takes precedence, then env var via getBinaryName()
|
||||||
|
const binaryName = options.binary || getBinaryName();
|
||||||
|
|
||||||
|
const result = await launchMultiModel({
|
||||||
|
sessionName,
|
||||||
|
models: options.models,
|
||||||
|
binaryName
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
console.log(result.instructions || `✓ Created session: ${result.sessionName}`);
|
||||||
|
} else {
|
||||||
|
console.error(`✗ Failed: ${result.error}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`✗ Error: ${error}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close subcommand
|
||||||
|
program
|
||||||
|
.command("close")
|
||||||
|
.description("Close a multi-model tmux session")
|
||||||
|
.argument("<session-name>", "Name of the tmux session to close")
|
||||||
|
.option("-c, --cleanup", "Remove worktrees and delete branches", false)
|
||||||
|
.option("-f, --force", "Skip confirmation prompts", false)
|
||||||
|
.option("-k, --keep-branches", "Keep git branches (only remove worktrees)", false)
|
||||||
|
.action(async (sessionName, options) => {
|
||||||
|
try {
|
||||||
|
const result = await closeMultiModel({
|
||||||
|
sessionName,
|
||||||
|
cleanup: options.cleanup,
|
||||||
|
force: options.force,
|
||||||
|
keepBranches: options.keepBranches
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
console.log(`✓ Closed session: ${result.sessionName}`);
|
||||||
|
if (result.cleanupPerformed) {
|
||||||
|
console.log(` Removed ${result.worktreesRemoved?.length || 0} worktrees`);
|
||||||
|
if (result.branchesDeleted && result.branchesDeleted.length > 0) {
|
||||||
|
console.log(` Deleted ${result.branchesDeleted.length} branches`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error(`✗ Failed: ${result.error}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`✗ Error: ${error}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Default to open command for backwards compatibility
|
||||||
|
program
|
||||||
|
.argument("[session-name]", "Name for the tmux session (deprecated, use 'open' command)")
|
||||||
|
.option("-m, --models <models...>", "Model IDs to launch", [])
|
||||||
|
.option("-b, --binary <binary>", "Binary to use")
|
||||||
|
.action(async (sessionName, options) => {
|
||||||
|
if (!sessionName) {
|
||||||
|
program.help();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.warn("Warning: Direct session name is deprecated. Use 'opencode-multi-model open <session-name>' instead.");
|
||||||
|
|
||||||
|
const binaryName = options.binary || getBinaryName();
|
||||||
|
|
||||||
|
const result = await launchMultiModel({
|
||||||
|
sessionName,
|
||||||
|
models: options.models,
|
||||||
|
binaryName
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
console.log(result.instructions);
|
||||||
|
} else {
|
||||||
|
console.error(`✗ Failed: ${result.error}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
program.parse();
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11. Create Project-Level Tool Re-export
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// .opencode/tools/multi-model.ts
|
||||||
|
// Re-export both open and close tools for project-level use
|
||||||
|
|
||||||
|
import { openTool, closeTool } from "../../src/tools/open.js";
|
||||||
|
|
||||||
|
export { openTool, closeTool };
|
||||||
|
export default openTool; // Default to open for backwards compatibility
|
||||||
|
```
|
||||||
|
|
||||||
|
### 12. Add Build Configuration
|
||||||
|
|
||||||
|
```json
|
||||||
|
// tsconfig.json
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"strict": true,
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 13. Add Build Script and Tests
|
||||||
|
|
||||||
|
```json
|
||||||
|
// package.json scripts
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"build": "bun build src/index.ts src/cli.ts --outdir dist --target bun --format esm",
|
||||||
|
"test": "bun test",
|
||||||
|
"prepublishOnly": "bun run build"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests**: Copy `.opencode/multi-model.test.ts` to `tests/multi-model.test.ts` and update imports:
|
||||||
|
- Change `import multiModelTool, { __testing_helpers } from "./tools/multi-model"`
|
||||||
|
- To `import { launchMultiModel, closeMultiModel, runCommand, ... } from "../src/core"`
|
||||||
|
|
||||||
|
The existing tests should work with minimal changes since the core logic remains the same.
|
||||||
|
|
||||||
|
### 14. Create README with Usage Instructions
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# opencode-multi-model
|
||||||
|
|
||||||
|
Launch multiple AI models in tmux sessions. Available as:
|
||||||
|
- OpenCode Plugin
|
||||||
|
- Standalone CLI
|
||||||
|
- Project-level tool
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### As OpenCode Plugin
|
||||||
|
|
||||||
|
Add to your OpenCode config (`opencode.json`):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugin": ["@username/opencode-multi-model"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### As Standalone CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Using npx (no install)
|
||||||
|
npx @username/opencode-multi-model open my-session -m openai/gpt-4o anthropic/claude-3-5-sonnet
|
||||||
|
|
||||||
|
# Or install globally
|
||||||
|
npm install -g @username/opencode-multi-model
|
||||||
|
opencode-multi-model open my-session -m openai/gpt-4o
|
||||||
|
```
|
||||||
|
|
||||||
|
### As Project Tool (for development)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// .opencode/tools/multi-model.ts
|
||||||
|
import { openTool, closeTool } from "@username/opencode-multi-model"
|
||||||
|
export { openTool, closeTool }
|
||||||
|
export default openTool
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Plugin Usage
|
||||||
|
|
||||||
|
**Open a session:**
|
||||||
|
```
|
||||||
|
Use multi-model-open tool:
|
||||||
|
sessionName: my-session, models: ["openai/gpt-4o", "anthropic/claude-3-5-sonnet"]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Close a session:**
|
||||||
|
```
|
||||||
|
Use multi-model-close tool:
|
||||||
|
sessionName: my-session, cleanup: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### CLI Usage
|
||||||
|
|
||||||
|
**Open a session:**
|
||||||
|
```bash
|
||||||
|
# Basic usage
|
||||||
|
opencode-multi-model open my-session -m openai/gpt-4o anthropic/claude-3-5-sonnet
|
||||||
|
|
||||||
|
# With custom binary (via flag)
|
||||||
|
opencode-multi-model open my-session -m openai/gpt-4o -b kilo
|
||||||
|
|
||||||
|
# With custom binary (via env)
|
||||||
|
export OPENCODE_MULTI_MODEL_BINARY=kilo
|
||||||
|
opencode-multi-model open my-session -m openai/gpt-4o
|
||||||
|
```
|
||||||
|
|
||||||
|
**Close a session:**
|
||||||
|
```bash
|
||||||
|
# Just close the tmux session
|
||||||
|
opencode-multi-model close my-session
|
||||||
|
|
||||||
|
# Close and cleanup worktrees
|
||||||
|
opencode-multi-model close my-session --cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
**Help:**
|
||||||
|
```bash
|
||||||
|
opencode-multi-model --help
|
||||||
|
opencode-multi-model open --help
|
||||||
|
opencode-multi-model close --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Custom Binary
|
||||||
|
|
||||||
|
By default uses `opencode`. To use `kilo` instead:
|
||||||
|
|
||||||
|
**Via environment variable:**
|
||||||
|
```bash
|
||||||
|
export OPENCODE_MULTI_MODEL_BINARY=kilo
|
||||||
|
```
|
||||||
|
|
||||||
|
**Via OpenCode config:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugin": ["@username/opencode-multi-model"],
|
||||||
|
"multiModelBinary": "kilo"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Via CLI flag:**
|
||||||
|
```bash
|
||||||
|
opencode-multi-model open my-session -m openai/gpt-4o -b kilo
|
||||||
|
```
|
||||||
|
|
||||||
|
Priority: CLI flag > OpenCode config > Environment variable > Default ("opencode")
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- tmux
|
||||||
|
- git
|
||||||
|
- opencode or kilo binary
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
For local development and testing without publishing:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// .opencode/tools/multi-model.ts
|
||||||
|
import { openTool, closeTool } from "../../src/tools/open.js"
|
||||||
|
export { openTool, closeTool }
|
||||||
|
export default openTool
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows testing the tool without publishing to npm.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files to Create/Modify
|
||||||
|
|
||||||
|
### New Files
|
||||||
|
- `package.json` - NPM package configuration with CLI bin entry
|
||||||
|
- `tsconfig.json` - TypeScript configuration
|
||||||
|
- `src/types.ts` - Shared TypeScript types
|
||||||
|
- `src/index.ts` - Plugin entry point (exports open and close tools)
|
||||||
|
- `src/cli.ts` - CLI entry point with open/close subcommands
|
||||||
|
- `src/core/index.ts` - Core exports
|
||||||
|
- `src/core/utils.ts` - Helper functions
|
||||||
|
- `src/core/launch.ts` - Launch session logic
|
||||||
|
- `src/core/close.ts` - Close session logic
|
||||||
|
- `src/tools/open.ts` - Open tool definition
|
||||||
|
- `src/tools/close.ts` - Close tool definition
|
||||||
|
- `tests/multi-model.test.ts` - Tests (copy from `.opencode/multi-model.test.ts`)
|
||||||
|
- `README.md` - Installation and usage docs
|
||||||
|
- `LICENSE` - License file
|
||||||
|
|
||||||
|
### Modified Files
|
||||||
|
- `.opencode/tools/multi-model.ts` - Re-export from package (for project-level use)
|
||||||
|
|
||||||
|
### Delete (after migration)
|
||||||
|
- `.opencode/multi-model.test.ts` - After copying to new package
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
1. **Build**: Run `bun run build` to compile TypeScript
|
||||||
|
2. **Test**: Run `bun test` in the new package directory
|
||||||
|
3. **CLI Test**:
|
||||||
|
```bash
|
||||||
|
# Test open
|
||||||
|
node dist/cli.js open test-session -m openai/gpt-4o
|
||||||
|
|
||||||
|
# Test close
|
||||||
|
node dist/cli.js close test-session
|
||||||
|
|
||||||
|
# Test env var
|
||||||
|
OPENCODE_MULTI_MODEL_BINARY=kilo node dist/cli.js open test-session -m openai/gpt-4o
|
||||||
|
```
|
||||||
|
4. **Plugin Test**:
|
||||||
|
- Link package locally with `bun link`
|
||||||
|
- Add to test project config
|
||||||
|
- Verify both open and close tools work
|
||||||
|
5. **Project Tool Test**:
|
||||||
|
- Update `.opencode/tools/multi-model.ts` to re-export
|
||||||
|
- Run from project directory
|
||||||
|
6. **Publish**: Run `npm publish --access public` (or `bun publish`)
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### End User - Plugin
|
||||||
|
|
||||||
|
```json
|
||||||
|
// opencode.json
|
||||||
|
{
|
||||||
|
"plugin": ["@username/opencode-multi-model"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then use via OpenCode interface with `multi-model-open` and `multi-model-close` tools.
|
||||||
|
|
||||||
|
### End User - CLI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install globally
|
||||||
|
npm install -g @username/opencode-multi-model
|
||||||
|
|
||||||
|
# Open session
|
||||||
|
opencode-multi-model open compare-session -m openai/gpt-4o anthropic/claude-3-opus google/gemini-pro
|
||||||
|
|
||||||
|
# Close session with cleanup
|
||||||
|
opencode-multi-model close compare-session --cleanup
|
||||||
|
```
|
||||||
|
|
||||||
|
### Developer - Project Tool
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// .opencode/tools/multi-model.ts
|
||||||
|
// For testing local changes before publishing
|
||||||
|
import { openTool, closeTool } from "../../src/tools/open.js"
|
||||||
|
export { openTool, closeTool }
|
||||||
|
export default openTool
|
||||||
|
```
|
||||||
|
|
||||||
|
This allows testing the tool without publishing to npm.
|
||||||
|
|
||||||
|
## Key Design Decisions
|
||||||
|
|
||||||
|
### 1. Utils in Separate File
|
||||||
|
All helper functions (shellQuote, levenshtein, etc.) are in `src/core/utils.ts` for better organization and testability.
|
||||||
|
|
||||||
|
### 2. CLI Honors Environment Variable
|
||||||
|
The CLI now properly checks `OPENCODE_MULTI_MODEL_BINARY` environment variable via the `getBinaryName()` function. Priority order:
|
||||||
|
1. CLI flag (`-b, --binary`)
|
||||||
|
2. OpenCode config (`multiModelBinary`)
|
||||||
|
3. Environment variable (`OPENCODE_MULTI_MODEL_BINARY`)
|
||||||
|
4. Default (`"opencode"`)
|
||||||
|
|
||||||
|
### 3. Detailed Return Instructions
|
||||||
|
Both the tool and CLI return detailed instructions for:
|
||||||
|
- How to attach to the tmux session
|
||||||
|
- How to list windows
|
||||||
|
- How to close the session
|
||||||
|
- How to cleanup worktrees
|
||||||
|
|
||||||
|
### 4. Open/Close Command Structure
|
||||||
|
The tool is split into two commands:
|
||||||
|
- `multi-model-open` (or `open` in CLI) - Creates sessions
|
||||||
|
- `multi-model-close` (or `close` in CLI) - Closes sessions
|
||||||
|
|
||||||
|
This provides a cleaner API and makes it easier for users to manage sessions.
|
||||||
Reference in New Issue
Block a user