opencode
This commit is contained in:
committed by
Cameron Palmer
parent
38adfa4d8b
commit
98e154b18e
@@ -19,6 +19,7 @@ Complete setup guide for Gas Town multi-agent orchestrator.
|
|||||||
| **tmux** | 3.0+ | `tmux -V` | See below |
|
| **tmux** | 3.0+ | `tmux -V` | See below |
|
||||||
| **Claude Code** (default) | latest | `claude --version` | See [claude.ai/claude-code](https://claude.ai/claude-code) |
|
| **Claude Code** (default) | latest | `claude --version` | See [claude.ai/claude-code](https://claude.ai/claude-code) |
|
||||||
| **Codex CLI** (optional) | latest | `codex --version` | See [developers.openai.com/codex/cli](https://developers.openai.com/codex/cli) |
|
| **Codex CLI** (optional) | latest | `codex --version` | See [developers.openai.com/codex/cli](https://developers.openai.com/codex/cli) |
|
||||||
|
| **OpenCode CLI** (optional) | latest | `opencode --version` | See [opencode.ai](https://opencode.ai) |
|
||||||
|
|
||||||
## Installing Prerequisites
|
## Installing Prerequisites
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/steveyegge/gastown/internal/config"
|
"github.com/steveyegge/gastown/internal/config"
|
||||||
"github.com/steveyegge/gastown/internal/constants"
|
"github.com/steveyegge/gastown/internal/constants"
|
||||||
"github.com/steveyegge/gastown/internal/crew"
|
"github.com/steveyegge/gastown/internal/crew"
|
||||||
|
"github.com/steveyegge/gastown/internal/runtime"
|
||||||
"github.com/steveyegge/gastown/internal/style"
|
"github.com/steveyegge/gastown/internal/style"
|
||||||
"github.com/steveyegge/gastown/internal/tmux"
|
"github.com/steveyegge/gastown/internal/tmux"
|
||||||
"github.com/steveyegge/gastown/internal/workspace"
|
"github.com/steveyegge/gastown/internal/workspace"
|
||||||
@@ -88,6 +89,7 @@ func runCrewAt(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runtimeConfig := config.LoadRuntimeConfig(r.Path)
|
runtimeConfig := config.LoadRuntimeConfig(r.Path)
|
||||||
|
_ = runtime.EnsureSettingsForRole(worker.ClonePath, "crew", runtimeConfig)
|
||||||
|
|
||||||
// Check if session exists
|
// Check if session exists
|
||||||
t := tmux.NewTmux()
|
t := tmux.NewTmux()
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ type RuntimeSessionConfig struct {
|
|||||||
|
|
||||||
// RuntimeHooksConfig configures runtime hook installation.
|
// RuntimeHooksConfig configures runtime hook installation.
|
||||||
type RuntimeHooksConfig struct {
|
type RuntimeHooksConfig struct {
|
||||||
// Provider controls which hook templates to install: "claude" or "none".
|
// Provider controls which hook templates to install: "claude", "opencode", or "none".
|
||||||
Provider string `json:"provider,omitempty"`
|
Provider string `json:"provider,omitempty"`
|
||||||
|
|
||||||
// Dir is the settings directory (e.g., ".claude").
|
// Dir is the settings directory (e.g., ".claude").
|
||||||
@@ -435,6 +435,8 @@ func defaultRuntimeCommand(provider string) string {
|
|||||||
switch provider {
|
switch provider {
|
||||||
case "codex":
|
case "codex":
|
||||||
return "codex"
|
return "codex"
|
||||||
|
case "opencode":
|
||||||
|
return "opencode"
|
||||||
case "generic":
|
case "generic":
|
||||||
return ""
|
return ""
|
||||||
default:
|
default:
|
||||||
@@ -455,6 +457,8 @@ func defaultPromptMode(provider string) string {
|
|||||||
switch provider {
|
switch provider {
|
||||||
case "codex":
|
case "codex":
|
||||||
return "none"
|
return "none"
|
||||||
|
case "opencode":
|
||||||
|
return "none"
|
||||||
default:
|
default:
|
||||||
return "arg"
|
return "arg"
|
||||||
}
|
}
|
||||||
@@ -475,24 +479,36 @@ func defaultConfigDirEnv(provider string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultHooksProvider(provider string) string {
|
func defaultHooksProvider(provider string) string {
|
||||||
if provider == "claude" {
|
switch provider {
|
||||||
|
case "claude":
|
||||||
return "claude"
|
return "claude"
|
||||||
|
case "opencode":
|
||||||
|
return "opencode"
|
||||||
|
default:
|
||||||
|
return "none"
|
||||||
}
|
}
|
||||||
return "none"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultHooksDir(provider string) string {
|
func defaultHooksDir(provider string) string {
|
||||||
if provider == "claude" {
|
switch provider {
|
||||||
|
case "claude":
|
||||||
return ".claude"
|
return ".claude"
|
||||||
|
case "opencode":
|
||||||
|
return ".opencode/plugin"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultHooksFile(provider string) string {
|
func defaultHooksFile(provider string) string {
|
||||||
if provider == "claude" {
|
switch provider {
|
||||||
|
case "claude":
|
||||||
return "settings.json"
|
return "settings.json"
|
||||||
|
case "opencode":
|
||||||
|
return "gastown.js"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
return ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func defaultProcessNames(provider, command string) []string {
|
func defaultProcessNames(provider, command string) []string {
|
||||||
@@ -526,6 +542,9 @@ func defaultInstructionsFile(provider string) string {
|
|||||||
if provider == "codex" {
|
if provider == "codex" {
|
||||||
return "AGENTS.md"
|
return "AGENTS.md"
|
||||||
}
|
}
|
||||||
|
if provider == "opencode" {
|
||||||
|
return "AGENTS.md"
|
||||||
|
}
|
||||||
return "CLAUDE.md"
|
return "CLAUDE.md"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
40
internal/opencode/plugin.go
Normal file
40
internal/opencode/plugin.go
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
// Package opencode provides OpenCode plugin management.
|
||||||
|
package opencode
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed plugin/gastown.js
|
||||||
|
var pluginFS embed.FS
|
||||||
|
|
||||||
|
// EnsurePluginAt ensures the Gas Town OpenCode plugin exists.
|
||||||
|
// If the file already exists, it's left unchanged.
|
||||||
|
func EnsurePluginAt(workDir, pluginDir, pluginFile string) error {
|
||||||
|
if pluginDir == "" || pluginFile == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginPath := filepath.Join(workDir, pluginDir, pluginFile)
|
||||||
|
if _, err := os.Stat(pluginPath); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(pluginPath), 0755); err != nil {
|
||||||
|
return fmt.Errorf("creating plugin directory: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := pluginFS.ReadFile("plugin/gastown.js")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading plugin template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(pluginPath, content, 0644); err != nil {
|
||||||
|
return fmt.Errorf("writing plugin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
32
internal/opencode/plugin/gastown.js
Normal file
32
internal/opencode/plugin/gastown.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// Gas Town OpenCode plugin: hooks SessionStart/Compaction via events.
|
||||||
|
export const GasTown = async ({ $, directory }) => {
|
||||||
|
const role = (process.env.GT_ROLE || "").toLowerCase();
|
||||||
|
const autonomousRoles = new Set(["polecat", "witness", "refinery", "deacon"]);
|
||||||
|
let didInit = false;
|
||||||
|
|
||||||
|
const run = async (cmd) => {
|
||||||
|
try {
|
||||||
|
await $`/bin/sh -lc ${cmd}`.cwd(directory);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[gastown] ${cmd} failed`, err?.message || err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSessionCreated = async () => {
|
||||||
|
if (didInit) return;
|
||||||
|
didInit = true;
|
||||||
|
await run("gt prime");
|
||||||
|
if (autonomousRoles.has(role)) {
|
||||||
|
await run("gt mail check --inject");
|
||||||
|
}
|
||||||
|
await run("gt nudge deacon session-started");
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
event: async ({ event }) => {
|
||||||
|
if (event?.type === "session.created") {
|
||||||
|
await onSessionCreated();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/steveyegge/gastown/internal/claude"
|
"github.com/steveyegge/gastown/internal/claude"
|
||||||
"github.com/steveyegge/gastown/internal/config"
|
"github.com/steveyegge/gastown/internal/config"
|
||||||
|
"github.com/steveyegge/gastown/internal/opencode"
|
||||||
"github.com/steveyegge/gastown/internal/tmux"
|
"github.com/steveyegge/gastown/internal/tmux"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -24,6 +25,8 @@ func EnsureSettingsForRole(workDir, role string, rc *config.RuntimeConfig) error
|
|||||||
switch rc.Hooks.Provider {
|
switch rc.Hooks.Provider {
|
||||||
case "claude":
|
case "claude":
|
||||||
return claude.EnsureSettingsForRoleAt(workDir, role, rc.Hooks.Dir, rc.Hooks.SettingsFile)
|
return claude.EnsureSettingsForRoleAt(workDir, role, rc.Hooks.Dir, rc.Hooks.SettingsFile)
|
||||||
|
case "opencode":
|
||||||
|
return opencode.EnsurePluginAt(workDir, rc.Hooks.Dir, rc.Hooks.SettingsFile)
|
||||||
default:
|
default:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -56,7 +59,7 @@ func StartupFallbackCommands(role string, rc *config.RuntimeConfig) []string {
|
|||||||
if rc == nil {
|
if rc == nil {
|
||||||
rc = config.DefaultRuntimeConfig()
|
rc = config.DefaultRuntimeConfig()
|
||||||
}
|
}
|
||||||
if rc.Hooks != nil && rc.Hooks.Provider == "claude" {
|
if rc.Hooks != nil && rc.Hooks.Provider != "" && rc.Hooks.Provider != "none" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user