mirror of
https://github.com/bendtherules/opencode-multi-model.git
synced 2026-08-18 13:42:21 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
114837cfae | ||
|
|
426def40f1 | ||
|
|
3ac3153aa9 | ||
|
|
233e211eb8 | ||
|
|
4f15a8f8d2 | ||
|
|
46d81b4162 | ||
|
|
c6d6c889d8 |
@@ -1,7 +1,8 @@
|
|||||||
// Re-export open and close tools for project-level use via relative paths.
|
// Re-export open and close tools for project-level use via relative paths.
|
||||||
// For published package usage, change imports to "@username/opencode-multi-model".
|
// For published package usage, change imports to "@username/opencode-multi-model".
|
||||||
|
|
||||||
|
import { cleanupTool } from "../../src/tools/cleanup";
|
||||||
import { closeTool } from "../../src/tools/close";
|
import { closeTool } from "../../src/tools/close";
|
||||||
import { openTool } from "../../src/tools/open";
|
import { openTool } from "../../src/tools/open";
|
||||||
|
|
||||||
export { closeTool as close, openTool as open };
|
export { cleanupTool as cleanup, closeTool as close, openTool as open };
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
opencode-multi-model is a Bun/TypeScript project that provides multi-model tmux session management for AI coding assistants. It allows running multiple AI models simultaneously in isolated tmux windows with separate git worktrees.
|
||||||
|
|
||||||
|
Available as three integration modes:
|
||||||
|
- **OpenCode Plugin**: Loaded via `opencode.json` plugin array
|
||||||
|
- **Standalone CLI**: Install globally and run `opencode-multi-model open <session> -m <models...>`
|
||||||
|
- **Project Tool**: Import core functions from `"opencode-multi-model/core"` for project-level commands (used during development).
|
||||||
|
|
||||||
|
Requires: tmux, git, opencode or kilo binary, Bun runtime.
|
||||||
|
|
||||||
|
## Build, Lint, and Test Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install dependencies
|
||||||
|
bun install
|
||||||
|
|
||||||
|
# Build for production
|
||||||
|
bun run build
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
bun test
|
||||||
|
|
||||||
|
# Run tests with coverage (HTML report in ./coverage)
|
||||||
|
bun run coverage
|
||||||
|
# Run a specific test by name
|
||||||
|
bun test --test-name-pattern "fails if session name is empty"
|
||||||
|
|
||||||
|
# TypeScript type checking
|
||||||
|
bun run typecheck
|
||||||
|
|
||||||
|
# Lint with Biome
|
||||||
|
bun run lint
|
||||||
|
|
||||||
|
# Auto-fix lint issues
|
||||||
|
bun run lint:fix
|
||||||
|
|
||||||
|
# Format code with Biome
|
||||||
|
bun run format
|
||||||
|
|
||||||
|
# Full quality check
|
||||||
|
bun run typecheck && bun run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Style Guidelines
|
||||||
|
|
||||||
|
### General
|
||||||
|
|
||||||
|
- Use **TypeScript** with strict mode
|
||||||
|
- Use **Bun** as the runtime and package manager
|
||||||
|
- Use ESM modules (`"type": "module"` in package.json)
|
||||||
|
- Use `node:` prefix for Node.js built-ins
|
||||||
|
|
||||||
|
### Formatting (Biome)
|
||||||
|
|
||||||
|
- **Indent**: 2 spaces (not tabs)
|
||||||
|
- **Quote style**: Double quotes for JS strings
|
||||||
|
- **Semicolons**: Required
|
||||||
|
- Run `bun run format` to format files
|
||||||
|
|
||||||
|
### TypeScript Conventions
|
||||||
|
|
||||||
|
- Use `interface` for object shapes; avoid `type` aliases unless needed
|
||||||
|
- Use explicit return types on exported functions
|
||||||
|
- Avoid `any` when possible
|
||||||
|
- Use `noUncheckedIndexedAccess: true` and `noImplicitOverride: true` in tsconfig
|
||||||
|
- All types/interfaces go in `src/types.ts`
|
||||||
|
|
||||||
|
### Naming Conventions
|
||||||
|
|
||||||
|
- **Files**: kebab-case (`multi-model-open.ts`)
|
||||||
|
- **Functions**: camelCase (`openMultiModel`)
|
||||||
|
- **Types/Interfaces**: PascalCase (`MultiModelOptions`)
|
||||||
|
- **Constants**: UPPER_SNAKE_CASE (e.g., `MAX_SUGGESTIONS`)
|
||||||
|
|
||||||
|
### Imports
|
||||||
|
|
||||||
|
- Use `import type` for type-only imports
|
||||||
|
- Group imports: external packages, internal modules, types
|
||||||
|
- Biome automatically organizes imports
|
||||||
|
|
||||||
|
### JSDoc Comments
|
||||||
|
|
||||||
|
- Use JSDoc for all exported functions
|
||||||
|
- Include `@example`, `@param`, and `@returns` descriptions
|
||||||
|
- Add inline comments for hard-to-understand code
|
||||||
|
|
||||||
|
### Error Handling
|
||||||
|
|
||||||
|
- Return result objects (e.g., `MultiModelResult`, `CloseSessionResult`, `CleanupResult`) with `success: boolean`
|
||||||
|
- Include descriptive error messages prefixed with `Error:`
|
||||||
|
- Provide actionable error messages (e.g., suggest valid models on invalid model name)
|
||||||
|
- Use `chalk` for colored terminal output (red for errors, dim for secondary info, bold for primary info)
|
||||||
|
|
||||||
|
### Shell Commands
|
||||||
|
|
||||||
|
- Use `Bun.$` template tag for shell commands
|
||||||
|
- Always use `quiet().nothrow()` to capture output without throwing
|
||||||
|
- Wrap command execution in `runCommand()` utility from `core/utils.ts`
|
||||||
|
- Use `shellQuote()` for user-provided values in shell commands
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
- Use `bun:test` framework
|
||||||
|
- Mock `Bun.$` using the `mock()` function from `bun:test`
|
||||||
|
- Use `spyOn()` for fs/os module mocking
|
||||||
|
- Group tests with `describe()` blocks
|
||||||
|
- Test file naming: `*.test.ts` in `tests/` directory
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── index.ts # Plugin entry point (default export)
|
||||||
|
├── cli.ts # CLI entry point (commander setup)
|
||||||
|
├── types.ts # All TypeScript interfaces/types
|
||||||
|
├── core/
|
||||||
|
│ ├── index.ts # Core exports
|
||||||
|
│ ├── open.ts # Session launch logic
|
||||||
|
│ ├── close.ts # Session close logic
|
||||||
|
│ ├── cleanup.ts # Archive tag cleanup logic
|
||||||
|
│ └── utils.ts # Shared utilities (runCommand, shellQuote, etc.)
|
||||||
|
└── tools/
|
||||||
|
├── open.ts # OpenCode tool definition
|
||||||
|
├── close.ts # CloseCode tool definition
|
||||||
|
└── cleanup.ts # Cleanup tool definition
|
||||||
|
tests/
|
||||||
|
├── open.test.ts # Open command tests
|
||||||
|
├── close.test.ts # Close command tests
|
||||||
|
├── cleanup.test.ts # Cleanup command tests
|
||||||
|
├── open_error.test.ts # Open error handling tests
|
||||||
|
├── close_error.test.ts # Close error handling tests
|
||||||
|
└── utils.test.ts # Utility function tests
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git Branch and Tag Naming
|
||||||
|
|
||||||
|
- Session branches: `opencode/<session-name>/<window-name>`
|
||||||
|
- Archive tags: `archive/<branch-name>-<timestamp>` (created by close command)
|
||||||
|
- Worktrees: `~/.local/share/opencode/multi-model/<session>/<window>/`
|
||||||
|
|
||||||
|
### Common Pitfalls
|
||||||
|
|
||||||
|
- Tmux session names use raw input; git branches/paths use sanitized names
|
||||||
|
- Window names must be unique; collisions get numeric suffixes (e.g., `gpt-5-2`, `gpt-5-2-2`)
|
||||||
|
- Window names are truncated to 24 characters to fit tmux limits
|
||||||
|
- Worktree paths are checked for existence before creation to avoid conflicts
|
||||||
|
- Archive tags preserve branch state before deletion for recovery
|
||||||
@@ -62,6 +62,11 @@ sessionName: my-session, models: ["openai/gpt-5.2", "anthropic/claude-3-5-sonnet
|
|||||||
call tool multi-model-close with sessionName: my-session
|
call tool multi-model-close with sessionName: my-session
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Cleanup archive tags (using tool):**
|
||||||
|
```
|
||||||
|
call tool multi-model-cleanup
|
||||||
|
```
|
||||||
|
|
||||||
**Close a session (using manual command):**
|
**Close a session (using manual command):**
|
||||||
```bash
|
```bash
|
||||||
tmux kill-session -t my-session && rm -rf ~/.local/share/opencode/multi-model/my-session && git worktree prune && git branch -D $(git branch --format='%(refname:short)' --list 'opencode/my-session/*')
|
tmux kill-session -t my-session && rm -rf ~/.local/share/opencode/multi-model/my-session && git worktree prune && git branch -D $(git branch --format='%(refname:short)' --list 'opencode/my-session/*')
|
||||||
@@ -88,6 +93,21 @@ opencode-multi-model open my-session -m openai/gpt-5.2
|
|||||||
opencode-multi-model close my-session
|
opencode-multi-model close my-session
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Cleanup archive tags (CLI command):**
|
||||||
|
```bash
|
||||||
|
# Delete all archive tags from local and remote repositories
|
||||||
|
opencode-multi-model cleanup
|
||||||
|
|
||||||
|
# Delete all archive tags without confirmation prompts
|
||||||
|
opencode-multi-model cleanup --force
|
||||||
|
|
||||||
|
# Delete only local archive tags, skip remote deletion
|
||||||
|
opencode-multi-model cleanup --no-remote
|
||||||
|
|
||||||
|
# Delete archive tags and push deletions to a specific remote
|
||||||
|
opencode-multi-model cleanup --remote origin
|
||||||
|
```
|
||||||
|
|
||||||
**Close a session (manual command):**
|
**Close a session (manual command):**
|
||||||
```bash
|
```bash
|
||||||
tmux kill-session -t my-session && rm -rf ~/.local/share/opencode/multi-model/my-session && git worktree prune && git branch -D $(git branch --format='%(refname:short)' --list 'opencode/my-session/*')
|
tmux kill-session -t my-session && rm -rf ~/.local/share/opencode/multi-model/my-session && git worktree prune && git branch -D $(git branch --format='%(refname:short)' --list 'opencode/my-session/*')
|
||||||
@@ -98,6 +118,7 @@ tmux kill-session -t my-session && rm -rf ~/.local/share/opencode/multi-model/my
|
|||||||
opencode-multi-model --help
|
opencode-multi-model --help
|
||||||
opencode-multi-model open --help
|
opencode-multi-model open --help
|
||||||
opencode-multi-model close --help
|
opencode-multi-model close --help
|
||||||
|
opencode-multi-model cleanup --help
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
@@ -151,6 +172,21 @@ opencode-multi-model close <session-name> [options]
|
|||||||
| `--no-tags` | Do not create archive tags before deleting branches |
|
| `--no-tags` | Do not create archive tags before deleting branches |
|
||||||
| `-f, --force` | Skip confirmation prompts |
|
| `-f, --force` | Skip confirmation prompts |
|
||||||
|
|
||||||
|
### `cleanup` - Delete all archive tags from local and remote repositories
|
||||||
|
|
||||||
|
Archive tags have the format: `archive/<branch-name>-<timestamp>`. These are created by the close command to preserve branch state before deletion.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
opencode-multi-model cleanup [options]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Options:**
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `-f, --force` | Skip confirmation prompts |
|
||||||
|
| `-r, --remote <remote>` | Remote name to push deletions to (default: first remote from git remote -v) |
|
||||||
|
| `--no-remote` | Only delete local tags, skip remote |
|
||||||
|
|
||||||
### Global Options
|
### Global Options
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
@@ -191,6 +227,20 @@ The `close` command performs the following steps:
|
|||||||
|
|
||||||
4. **Directory cleanup**: Removes the base session directory
|
4. **Directory cleanup**: Removes the base session directory
|
||||||
|
|
||||||
|
### `cleanup` command
|
||||||
|
|
||||||
|
The `cleanup` command performs the following steps:
|
||||||
|
|
||||||
|
1. **Archive tag discovery**: Finds all git tags matching the pattern `archive/*`
|
||||||
|
|
||||||
|
2. **Local tag deletion**: Deletes all archive tags from the local repository
|
||||||
|
|
||||||
|
3. **Remote tag deletion** (unless `--no-remote`):
|
||||||
|
- Determines which remote to use (user-specified, first available, or skip)
|
||||||
|
- Pushes tag deletions to the remote repository
|
||||||
|
|
||||||
|
4. **Summary**: Reports the number of tags deleted locally and remotely
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- tmux
|
- tmux
|
||||||
@@ -230,11 +280,13 @@ bun dist/cli.js close test-session
|
|||||||
│ ├── core/
|
│ ├── core/
|
||||||
│ │ ├── index.ts # Core exports
|
│ │ ├── index.ts # Core exports
|
||||||
│ │ ├── open.ts # Launch session logic
|
│ │ ├── open.ts # Launch session logic
|
||||||
│ │ ├── close.ts # Close session logic
|
│ │ ├── close.ts # Close session logic
|
||||||
|
│ │ ├── cleanup.ts # Cleanup archive tags logic
|
||||||
│ │ └── utils.ts # Helper functions
|
│ │ └── utils.ts # Helper functions
|
||||||
│ └── tools/
|
│ └── tools/
|
||||||
│ ├── open.ts # Open tool definition
|
│ ├── open.ts # Open tool definition
|
||||||
│ └── close.ts # Close tool definition
|
│ ├── close.ts # Close tool definition
|
||||||
|
│ └── cleanup.ts # Cleanup tool definition
|
||||||
├── tests/
|
├── tests/
|
||||||
│ └── multi-model.test.ts # Tests
|
│ └── multi-model.test.ts # Tests
|
||||||
├── package.json
|
├── package.json
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "opencode-multi-model",
|
"name": "opencode-multi-model",
|
||||||
"version": "0.0.11",
|
"version": "0.1.1",
|
||||||
"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",
|
||||||
|
|||||||
+12
-8
@@ -8,8 +8,11 @@ import { cleanupMultiModel } from "../core/cleanup";
|
|||||||
* Used to clean up recovery tags created by the close command.
|
* Used to clean up recovery tags created by the close command.
|
||||||
*/
|
*/
|
||||||
export const cleanupTool = tool({
|
export const cleanupTool = tool({
|
||||||
description:
|
description: `Delete all archive tags from the local repository and, optionally, from a remote.
|
||||||
"Delete all archive tags from local and optionally remote repository",
|
|
||||||
|
Arguments:
|
||||||
|
-\`remote\` (string, optional): Specify a remote name to which deletions should be pushed. If omitted, the first available remote is used.
|
||||||
|
- \`noRemote\` (boolean, optional): When true, only delete local tags and do not push deletions to any remote.`,
|
||||||
args: {
|
args: {
|
||||||
remote: tool.schema
|
remote: tool.schema
|
||||||
.union([
|
.union([
|
||||||
@@ -17,21 +20,22 @@ export const cleanupTool = tool({
|
|||||||
.string()
|
.string()
|
||||||
.describe(
|
.describe(
|
||||||
"Remote name to push deletions to. Error if doesn't exist.",
|
"Remote name to push deletions to. Error if doesn't exist.",
|
||||||
),
|
)
|
||||||
tool.schema
|
|
||||||
.literal(false)
|
|
||||||
.describe("Only delete local tags, don't push to remote."),
|
|
||||||
])
|
])
|
||||||
.optional()
|
.optional()
|
||||||
.describe(
|
.describe(
|
||||||
"Remote handling: string=specific remote, false=local-only, undefined=first available remote",
|
"Remote handling: string=specific remote, undefined=first available remote",
|
||||||
),
|
),
|
||||||
|
noRemote: tool.schema
|
||||||
|
.literal(true)
|
||||||
|
.describe("Only delete local tags, don't push to remote.")
|
||||||
|
.optional(),
|
||||||
},
|
},
|
||||||
async execute(args, _context) {
|
async execute(args, _context) {
|
||||||
// In plugin mode, we skip confirmation (force=true)
|
// In plugin mode, we skip confirmation (force=true)
|
||||||
const result = await cleanupMultiModel({
|
const result = await cleanupMultiModel({
|
||||||
force: true,
|
force: true,
|
||||||
remote: args.remote,
|
remote: args.noRemote ? false : args.remote,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
|
|||||||
+6
-2
@@ -8,8 +8,12 @@ import { closeMultiModel } from "../core/close";
|
|||||||
* creates archive tags, and deletes associated branches.
|
* creates archive tags, and deletes associated branches.
|
||||||
*/
|
*/
|
||||||
export const closeTool = tool({
|
export const closeTool = tool({
|
||||||
description:
|
description: `Close a multi-model tmux session and optionally clean up worktrees and branches.
|
||||||
"Close a multi-model tmux session and optionally cleanup worktrees and branches",
|
|
||||||
|
Arguments:
|
||||||
|
- \`sessionName\` (string, required): tmux session name to close.
|
||||||
|
- \`cleanupWorktrees\` (boolean, optional, default: true): Whether to remove worktrees and delete branches.
|
||||||
|
- \`createArchiveTags\` (boolean, optional, default: true): Whether to create archive tags before deleting branches.`,
|
||||||
args: {
|
args: {
|
||||||
sessionName: tool.schema
|
sessionName: tool.schema
|
||||||
.string()
|
.string()
|
||||||
|
|||||||
+5
-1
@@ -9,7 +9,11 @@ import { getBinaryName } from "../core/utils";
|
|||||||
* Returns instructions for attaching to and cleaning up the session.
|
* Returns instructions for attaching to and cleaning up the session.
|
||||||
*/
|
*/
|
||||||
export const openTool = tool({
|
export const openTool = tool({
|
||||||
description: "Launch multiple OpenCode models in tmux",
|
description: `Launch multiple OpenCode models in tmux.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- \`sessionName\` (string, required): tmux session name to create.
|
||||||
|
- \`models\` (array of strings, required): one or more OpenCode model IDs to launch.`,
|
||||||
args: {
|
args: {
|
||||||
sessionName: tool.schema
|
sessionName: tool.schema
|
||||||
.string()
|
.string()
|
||||||
|
|||||||
Reference in New Issue
Block a user