pantheon-opencode 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -0
- package/README.md +112 -15
- package/bin/pantheon-init.mjs +93 -0
- package/commands/pantheon-vision.md +67 -0
- package/opencode.json +14 -10
- package/package.json +20 -4
- package/scripts/_pantheon_paths.py +0 -2
- package/scripts/changelog-extract.mjs +112 -0
- package/scripts/doctor.mjs +307 -15
- package/scripts/hooks/audit-imports.sh +43 -0
- package/scripts/hooks/format-multi-language.sh +96 -0
- package/scripts/hooks/log-session-start.sh +24 -0
- package/scripts/hooks/on-subagent-delegation-start.sh +91 -0
- package/scripts/hooks/on-subagent-delegation-stop.sh +37 -0
- package/scripts/hooks/run-type-check.sh +21 -0
- package/scripts/hooks/scan-secrets.sh +55 -0
- package/scripts/hooks/validate-post-conditions.sh +21 -0
- package/scripts/hooks/validate-talos-scope.sh +43 -0
- package/scripts/hooks/validate-tool-safety.sh +46 -0
- package/scripts/install/health-check.mjs +101 -1
- package/scripts/install/model-picker.mjs +118 -0
- package/scripts/install/opencode.mjs +155 -43
- package/scripts/install/plugin.mjs +4 -2
- package/scripts/install/venv.mjs +25 -2
- package/scripts/install-mcp.mjs +170 -37
- package/scripts/mcp_persistence_server.py +2 -1
- package/scripts/prune.mjs +359 -0
- package/scripts/secret-scan.mjs +88 -0
- package/scripts/uninstall.mjs +3 -3
- package/scripts/validate-routing.mjs +15 -0
- package/scripts/version-check.mjs +146 -0
- package/scripts/versioning.mjs +13 -14
- package/src/agents/hermes.md +2 -2
- package/src/agents/mnemosyne.md +1 -1
- package/src/agents/zeus.md +1 -1
- package/src/mcp/_pantheon_paths.py +0 -2
- package/src/mcp/install-mcp.mjs +6 -913
- package/src/mcp/mcp_persistence_server.py +2 -1
- package/src/mcp/pantheon_vision_server.py +495 -0
- package/src/mcp/requirements-mcp.txt +8 -5
- package/src/mcp/requirements-vision.txt +3 -0
- package/src/pantheon/auto-wake.ts +91 -0
- package/src/pantheon/background-job-board.ts +568 -0
- package/src/pantheon/file-persistence.ts +95 -0
- package/src/pantheon/presets.d.mts +108 -0
- package/src/pantheon/presets.mjs +554 -0
- package/src/pantheon/vision.ts +1437 -0
- package/src/plugin.ts +69 -25
- package/src/plugins/hook-runner.ts +186 -0
- package/src/plugins/pantheon-hooks.ts +153 -0
- package/src/plugins/tui/dist/tui.d.ts +1 -1
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +812 -223
- package/src/plugins/tui/dist/tui.js.map +1 -1
- package/src/plugins/tui/dist/tui.tsx +881 -121
- package/src/plugins/tui/package.json +3 -3
- package/src/plugins/tui/src/index.tsx +881 -121
- package/src/routing.yml +148 -0
- package/src/skills/context-compression/SKILL.md +1 -1
- package/src/skills/loop-engineering/SKILL.md +1 -1
- package/src/skills/orchestration-workflow/SKILL.md +11 -11
- package/scripts/scrub_secrets.py +0 -281
- package/src/mcp/requirements-mcp-core.txt +0 -5
package/AGENTS.md
CHANGED
|
@@ -29,6 +29,15 @@ See [INSTALLATION.md](docs/INSTALLATION.md) for setup instructions.
|
|
|
29
29
|
- Test: `npm test`
|
|
30
30
|
- Lint: `npm run lint`
|
|
31
31
|
|
|
32
|
+
## Teste de Instalação Global (sandbox)
|
|
33
|
+
|
|
34
|
+
Para validar a instalação global do pacote pantheon-opencode COMO UM USUÁRIO REAL, use o sandbox isolado em `~/pantheon-sandbox/` (fora do repo, HOME + prefix npm + venv próprios). O ambiente de dev mistura 3 instalações + config global + venv — NÃO serve para testar instalação/empacotamento.
|
|
35
|
+
|
|
36
|
+
- Rodar: `bash ~/pantheon-sandbox/run-test.sh` (opencode mcp list 5/5 connected + doctor 0 erros + abre TUI isolado)
|
|
37
|
+
- Regra para agentes: ao validar instalação global (npm pack, `init`, MCPs, hooks), usar o sandbox — NUNCA testar no ambiente de dev
|
|
38
|
+
- Descarte: `rm -rf ~/pantheon-sandbox`
|
|
39
|
+
- Detalhes: ver `~/pantheon-sandbox/README.md`
|
|
40
|
+
|
|
32
41
|
## Conventions
|
|
33
42
|
|
|
34
43
|
- TDD: Write failing test first, then implement
|
package/README.md
CHANGED
|
@@ -172,6 +172,9 @@ npx pantheon-opencode init
|
|
|
172
172
|
# Headless mode — for CI and automation
|
|
173
173
|
npx pantheon-opencode init --headless
|
|
174
174
|
|
|
175
|
+
# (Optional) Install + activate a model routing preset
|
|
176
|
+
npx pantheon-opencode init --preset go-fast
|
|
177
|
+
|
|
175
178
|
# (Optional) Install MCP servers + skills + TUI plugin
|
|
176
179
|
npm run setup
|
|
177
180
|
|
|
@@ -182,7 +185,7 @@ opencode
|
|
|
182
185
|
|
|
183
186
|
> **Modes:** Interactive TUI with checkbox selection (default TTY), or `--headless` for scripts/CI.
|
|
184
187
|
> **Minimal:** `--headless --no-mcp` installs only agents (~2s).
|
|
185
|
-
> **Full:** `--headless` also creates Python venv + MCP servers (memory, persistence, KV).
|
|
188
|
+
> **Full:** `--headless` also creates Python venv + MCP servers (memory, persistence, KV, vision).
|
|
186
189
|
|
|
187
190
|
---
|
|
188
191
|
|
|
@@ -418,6 +421,76 @@ subscription (OpenCode Go, Copilot Pro, Claude Pro, etc.).
|
|
|
418
421
|
|
|
419
422
|
---
|
|
420
423
|
|
|
424
|
+
## Model Routing Presets
|
|
425
|
+
|
|
426
|
+
The abstract tiers above are pinned to concrete `provider/model` IDs by **6 built-in model presets**. Each preset maps all 14 agents to a specific model plus a reasoning effort per role (planners/reviewers, implementers, scouts). **Zero-mutation default**: without an active preset, agents run on OpenCode's default model — behavior is unchanged.
|
|
427
|
+
|
|
428
|
+
### Presets
|
|
429
|
+
|
|
430
|
+
| Preset | Provider | Planners/Reviewers | Implementers | Scouts |
|
|
431
|
+
|--------|----------|--------------------|--------------|--------|
|
|
432
|
+
| `go-deepseek` | opencode (Zen) | `opencode/deepseek-v4-pro` | `opencode/deepseek-v4-flash` | `opencode/deepseek-v4-flash-free` |
|
|
433
|
+
| `go-fast` | opencode-go | `opencode-go/deepseek-v4-flash` — all 14 agents, effort `low` | | |
|
|
434
|
+
| `go-claude` | anthropic | `anthropic/claude-opus-4-8` | `anthropic/claude-sonnet-5` | `anthropic/claude-haiku-4-5` |
|
|
435
|
+
| `go-openai` | openai | `openai/gpt-5.6-sol` | `openai/gpt-5.6-terra` | `openai/gpt-5.6-luna-fast` |
|
|
436
|
+
| `go-premium` | opencode-go | GLM-5.1 (zeus), DeepSeek V4 Pro (athena), Qwen3.7 Max (themis) | MiniMax M2.7 (hermes, hephaestus), Kimi K2.6 (aphrodite), Qwen3.7 Plus (demeter), GLM-5.2 (prometheus) | DeepSeek V4 Flash (scouts) |
|
|
437
|
+
| `go-free` | opencode (Zen free, $0) | Big Pickle (zeus), Nemotron 3 Ultra Free (athena) | DeepSeek V4 Flash Free | North Mini Code Free (scouts) |
|
|
438
|
+
|
|
439
|
+
The exact per-agent mapping (model + reasoning effort + fallbacks) lives in the `presets:` block of [src/routing.yml](src/routing.yml).
|
|
440
|
+
|
|
441
|
+
### Requirements
|
|
442
|
+
|
|
443
|
+
- **Env vars** — checked fail-fast (CLI and plugin) when the selected preset uses the provider:
|
|
444
|
+
- `PANTHEON_OPENCODE_API_KEY` — `opencode` + `opencode-go` providers (`go-deepseek`, `go-fast`, `go-premium`, `go-free`)
|
|
445
|
+
- `PANTHEON_ANTHROPIC_API_KEY` — `anthropic` provider (`go-claude`)
|
|
446
|
+
- `PANTHEON_OPENAI_API_KEY` — `openai` provider (`go-openai`)
|
|
447
|
+
- **OpenCode Go subscription** required for `go-fast` / `go-premium`.
|
|
448
|
+
- **OpenCode Zen free tier** for `go-free` — note `nemotron-3-ultra-free` has known intermittent failures ([opencode#38028](https://github.com/opencode/opencode/issues/38028)); fall back to `go-fast` if flaky.
|
|
449
|
+
|
|
450
|
+
### Commands
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
# Install + activate a preset
|
|
454
|
+
npx pantheon-opencode init --preset go-fast
|
|
455
|
+
|
|
456
|
+
# Switch preset (global config; --project for project-local; --dry-run to preview)
|
|
457
|
+
pantheon-opencode set-tier go-deepseek
|
|
458
|
+
pantheon-opencode set-tier go-fast --project
|
|
459
|
+
pantheon-opencode set-tier go-fast --dry-run
|
|
460
|
+
|
|
461
|
+
# Clear preset — back to the zero-mutation default
|
|
462
|
+
pantheon-opencode set-tier none
|
|
463
|
+
|
|
464
|
+
# No name → lists available presets (none, go-deepseek, go-fast, go-claude, go-openai, go-premium, go-free)
|
|
465
|
+
pantheon-opencode set-tier
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
**Env override (CI/headless)** — wins over the file:
|
|
469
|
+
|
|
470
|
+
```bash
|
|
471
|
+
export PANTHEON_MODEL_PRESET=go-deepseek
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### How it works
|
|
475
|
+
|
|
476
|
+
- The active selection is persisted to `.pantheon/active-preset.json` — `{version, preset, source, updated_at}` — written atomically (tmp file + rename) with a `.bak` backup of the previous file. It applies on the next OpenCode startup (no hot-swap); the plugin's `hooks.config` injects it.
|
|
477
|
+
- **Resolution precedence**: `PANTHEON_MODEL_PRESET` env → first existing `.pantheon/active-preset.json` (project → `~/.config/opencode` → `~/.opencode`) → **none** (defaults).
|
|
478
|
+
- **Partial semantics**: a preset overrides only the agents it lists; unlisted agents inherit the invoking primary agent's model.
|
|
479
|
+
- Interactive picker during `init`: one prompt listing the 6 presets (select by number or name, blank to skip). It never touches your `opencode.json` — the choice is injected at startup instead. The file format also supports an optional `overrides` block (per-agent `model`/`variant`, per-provider `baseURL`/`apiKeyEnv`) merged over the preset definition.
|
|
480
|
+
- **Capability normalization**: requested reasoning effort is clamped to each model family's ceiling (`deepseek-v4-flash` max `medium`; claude models strip the variant entirely — Anthropic uses thinking).
|
|
481
|
+
- **fallback_models**: per-agent ordered fallback list where configured (e.g. `go-deepseek` sets `[opencode/mimo-v2.5]` for every agent).
|
|
482
|
+
|
|
483
|
+
### Quick smoke test
|
|
484
|
+
|
|
485
|
+
```bash
|
|
486
|
+
export PANTHEON_OPENCODE_API_KEY=...
|
|
487
|
+
pantheon-opencode set-tier go-fast --project
|
|
488
|
+
opencode run "hello" 2>&1 | grep "Pantheon"
|
|
489
|
+
# expect: [Pantheon Plugin] Model preset active: go-fast (source: file)
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
---
|
|
493
|
+
|
|
421
494
|
## Quick Start
|
|
422
495
|
|
|
423
496
|
### 1. Install Pantheon
|
|
@@ -664,19 +737,43 @@ Edit README.md line 11: agents-17 → agents-18
|
|
|
664
737
|
- Hardcoded secret detection
|
|
665
738
|
- Minimum 80% test coverage (hard block)
|
|
666
739
|
|
|
667
|
-
**Agent hooks enforce at runtime (`scripts/hooks/` +
|
|
668
|
-
- `scan-secrets.sh` — detects hardcoded secrets and credentials (
|
|
669
|
-
- `validate-tool-safety.sh` — blocks destructive operations (
|
|
670
|
-
- `validate-talos-scope.sh` — restricts Talos hotfix scope (
|
|
671
|
-
- `on-subagent-delegation-start.sh` — tracks delegation start (
|
|
672
|
-
- `format-multi-language.sh` — auto-formats modified files (
|
|
673
|
-
- `log-session-start.sh` — audit trail of sessions (
|
|
674
|
-
- `on-subagent-delegation-stop.sh` — delegation cleanup (
|
|
675
|
-
- `validate-post-conditions.sh` — post-condition validation (event)
|
|
676
|
-
- `audit-imports.sh`
|
|
677
|
-
|
|
740
|
+
**Agent hooks enforce at runtime (`scripts/hooks/` + `src/plugins/pantheon-hooks.ts`):**
|
|
741
|
+
- `scan-secrets.sh` — detects hardcoded secrets and credentials (tool.execute.before)
|
|
742
|
+
- `validate-tool-safety.sh` — blocks destructive operations (tool.execute.before)
|
|
743
|
+
- `validate-talos-scope.sh` — restricts Talos hotfix scope (tool.execute.before)
|
|
744
|
+
- `on-subagent-delegation-start.sh` — tracks delegation start (tool.execute.before, delegation tools only)
|
|
745
|
+
- `format-multi-language.sh` — auto-formats modified files (tool.execute.after)
|
|
746
|
+
- `log-session-start.sh` — audit trail of sessions (tool.execute.after + event session.created)
|
|
747
|
+
- `on-subagent-delegation-stop.sh` — delegation cleanup (tool.execute.after, delegation tools only)
|
|
748
|
+
- `validate-post-conditions.sh` — post-condition validation (event session.created)
|
|
749
|
+
- `audit-imports.sh` and `run-type-check.sh` also live in `scripts/hooks/` but are not wired into the plugin.
|
|
750
|
+
|
|
751
|
+
The `src/plugins/pantheon-hooks.ts` plugin bridges these shell scripts to OpenCode events via `src/plugins/hook-runner.ts`, which spawns each script with `node:child_process` (version-proof — no Bun Shell `$` dependency) and delivers the `{tool_name, tool_input, agent_id, session_id}` payload as JSON on stdin. Register it explicitly in `opencode.json`: `"plugin": ["<path>/src/plugins/pantheon-hooks.ts"]` (absolute path). It is **not** auto-discovered from `.opencode/plugins/`.
|
|
752
|
+
|
|
753
|
+
### Pre-commit hooks (local secret gate)
|
|
754
|
+
|
|
755
|
+
Pre-commit blocks **secrets and hygiene issues locally**, before anything reaches CI — the same fail-closed policy as the `Security / security-scan` CI gate, one layer earlier.
|
|
756
|
+
|
|
757
|
+
**Install (one-time, per clone):**
|
|
758
|
+
|
|
759
|
+
```bash
|
|
760
|
+
pip install pre-commit
|
|
761
|
+
npm run hooks:install # -> pre-commit install
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
**What the hooks verify (`.pre-commit-config.yaml`):**
|
|
765
|
+
|
|
766
|
+
| Hook | Checks |
|
|
767
|
+
|---|---|
|
|
768
|
+
| `gitleaks` (v8.24.3) | Hardcoded secrets in the staged diff — Bifrost credential values (`sk-bf-*`), private keys, GitHub/npm/AWS/Google/Slack tokens, `sk-*` keys. Uses `.gitleaks.toml`, redacted output, exit code 2 |
|
|
769
|
+
| `secret-scan` (local, `scripts/secret-scan.mjs`) | Bifrost MCP credential header + value patterns + literal API key / `Authorization: Bearer` values in versionable files |
|
|
770
|
+
| `trailing-whitespace`, `end-of-file-fixer` | Formatting hygiene (auto-fixed) |
|
|
771
|
+
| `check-json`, `check-yaml`, `check-toml` | Config file syntax |
|
|
772
|
+
| `check-merge-conflict`, `check-added-large-files` (max 5 MB), `forbid-new-submodules` | Repo hygiene |
|
|
773
|
+
|
|
774
|
+
**Security policy:** any secret found **blocks the commit** (fail-fast, no `--no-verify` exceptions). If a secret is flagged, **rotate it immediately** — do not "fix" the scan, do not force the commit.
|
|
678
775
|
|
|
679
|
-
|
|
776
|
+
**Update hooks:** `npm run hooks:update` (runs `pre-commit autoupdate`).
|
|
680
777
|
|
|
681
778
|
---
|
|
682
779
|
|
|
@@ -745,6 +842,6 @@ Pantheon draws from the broader multi-agent landscape while diverging in key way
|
|
|
745
842
|
|
|
746
843
|
---
|
|
747
844
|
|
|
748
|
-
**License:** MIT
|
|
749
|
-
**Architecture Pattern:** Conductor-Delegate
|
|
845
|
+
**License:** MIT
|
|
846
|
+
**Architecture Pattern:** Conductor-Delegate
|
|
750
847
|
**Mythology:** Greek (Zeus, Athena, Apollo, Hermes, Aphrodite, Talos, Themis, Mnemosyne, Gaia, Hephaestus, Nyx, Prometheus, Demeter, Iris)
|
package/bin/pantheon-init.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* installOpenCode() from scripts/install/opencode.mjs.
|
|
9
9
|
*/
|
|
10
10
|
import fs from 'fs';
|
|
11
|
+
import os from 'os';
|
|
11
12
|
import path from 'path';
|
|
12
13
|
import { fileURLToPath } from 'url';
|
|
13
14
|
|
|
@@ -33,6 +34,11 @@ function printUsage() {
|
|
|
33
34
|
console.log(' npx pantheon-opencode init --interactive # Force interactive TUI mode');
|
|
34
35
|
console.log(' npx pantheon-opencode init --headless # Force non-interactive mode');
|
|
35
36
|
console.log(' npx pantheon-opencode init -y # Skip confirmations, use defaults');
|
|
37
|
+
console.log(' npx pantheon-opencode init --preset <name> # Install and activate model preset');
|
|
38
|
+
console.log(' npx pantheon-opencode set-tier <name> # Set active model preset (global)');
|
|
39
|
+
console.log(' npx pantheon-opencode set-tier <name> --project # Set active model preset (project)');
|
|
40
|
+
console.log(' npx pantheon-opencode prune # List legacy artifacts (dry-run)');
|
|
41
|
+
console.log(' npx pantheon-opencode prune --apply # Remove stale backups');
|
|
36
42
|
console.log(' npx pantheon-opencode --help # Show this help');
|
|
37
43
|
}
|
|
38
44
|
|
|
@@ -40,8 +46,92 @@ async function main() {
|
|
|
40
46
|
const args = process.argv.slice(2);
|
|
41
47
|
const command = args[0];
|
|
42
48
|
|
|
49
|
+
// prune handles its own --help (delegated to scripts/prune.mjs), so its
|
|
50
|
+
// dispatch must run BEFORE the generic --help check.
|
|
51
|
+
if (command === 'prune') {
|
|
52
|
+
const { spawnSync } = await import('child_process');
|
|
53
|
+
const pruneScript = path.join(ROOT, 'scripts', 'prune.mjs');
|
|
54
|
+
const result = spawnSync(process.execPath, [pruneScript, ...args.slice(1)], {
|
|
55
|
+
stdio: 'inherit',
|
|
56
|
+
cwd: ROOT,
|
|
57
|
+
});
|
|
58
|
+
process.exit(result.status ?? 1);
|
|
59
|
+
}
|
|
60
|
+
|
|
43
61
|
if (args.includes('--help')) { printUsage(); process.exit(0); }
|
|
44
62
|
|
|
63
|
+
if (command === 'set-tier') {
|
|
64
|
+
const isProject = args.includes('--project');
|
|
65
|
+
const isDryRun = args.includes('--dry-run');
|
|
66
|
+
const name = args[1];
|
|
67
|
+
|
|
68
|
+
const { loadPresetDefs } = await import('../src/pantheon/presets.mjs');
|
|
69
|
+
const { writeActivePreset } = await import('../scripts/install/model-picker.mjs');
|
|
70
|
+
const presets = loadPresetDefs();
|
|
71
|
+
const presetList = `none, ${Object.keys(presets).join(', ')}`;
|
|
72
|
+
|
|
73
|
+
if (!name) {
|
|
74
|
+
console.error('❌ set-tier requires a preset name');
|
|
75
|
+
console.error(` Available: ${presetList}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
if (name !== 'none' && !presets[name]) {
|
|
79
|
+
console.error(`❌ Unknown preset "${name}"`);
|
|
80
|
+
console.error(` Available: ${presetList}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Fail-fast: every provider's API key env var must be set before writing
|
|
85
|
+
if (name !== 'none') {
|
|
86
|
+
for (const [pid, provider] of Object.entries(presets[name].providers || {})) {
|
|
87
|
+
if (!process.env[provider.apiKeyEnv]) {
|
|
88
|
+
console.error(`❌ Missing required API key configuration for provider "${pid}"`);
|
|
89
|
+
console.error(' Set the provider API key environment variable and retry.');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const presetDir = isProject
|
|
96
|
+
? process.cwd()
|
|
97
|
+
: path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'opencode');
|
|
98
|
+
const filePath = path.join(presetDir, '.pantheon', 'active-preset.json');
|
|
99
|
+
const bakPath = path.join(presetDir, '.pantheon', 'active-preset.json.bak');
|
|
100
|
+
|
|
101
|
+
let prevName = '(none)';
|
|
102
|
+
if (fs.existsSync(filePath)) {
|
|
103
|
+
try {
|
|
104
|
+
prevName = JSON.parse(fs.readFileSync(filePath, 'utf8')).preset || '(unknown)';
|
|
105
|
+
} catch { prevName = '(unknown)'; }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (name === 'none') {
|
|
109
|
+
if (isDryRun) {
|
|
110
|
+
console.log('[dry-run] Would clear model preset');
|
|
111
|
+
} else {
|
|
112
|
+
if (fs.existsSync(filePath)) {
|
|
113
|
+
fs.mkdirSync(path.dirname(bakPath), { recursive: true });
|
|
114
|
+
fs.copyFileSync(filePath, bakPath);
|
|
115
|
+
fs.rmSync(filePath, { force: true });
|
|
116
|
+
}
|
|
117
|
+
console.log('Model preset cleared. Using opencode defaults.');
|
|
118
|
+
console.log(` Backup: ${bakPath}`);
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const result = writeActivePreset(presetDir, name, { dryRun: isDryRun, source: 'cli' });
|
|
124
|
+
|
|
125
|
+
console.log(`Model preset set: ${prevName} → ${name}`);
|
|
126
|
+
for (const [agent, spec] of Object.entries(presets[name].agents || {})) {
|
|
127
|
+
console.log(` ${agent}: ${spec.model} [${spec.reasoning_effort}]`);
|
|
128
|
+
}
|
|
129
|
+
const providerNames = Object.keys(presets[name].providers || {});
|
|
130
|
+
console.log(` Providers: ${providerNames.join(', ') || 'none'}`);
|
|
131
|
+
if (result.backupPath) console.log(` Backup: ${result.backupPath}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
45
135
|
if (command === 'init' || !command) {
|
|
46
136
|
const isProject = args.includes('--project');
|
|
47
137
|
const isDryRun = args.includes('--dry-run');
|
|
@@ -51,6 +141,8 @@ async function main() {
|
|
|
51
141
|
const forceInteractive = args.includes('--interactive');
|
|
52
142
|
const forceHeadless = args.includes('--headless');
|
|
53
143
|
const autoYes = args.includes('--yes') || args.includes('-y');
|
|
144
|
+
const presetIndex = args.indexOf('--preset');
|
|
145
|
+
const presetOpt = presetIndex >= 0 ? (args[presetIndex + 1] ?? null) : null;
|
|
54
146
|
|
|
55
147
|
const components = ['agents', 'skills', 'instructions', 'commands', 'plugins'];
|
|
56
148
|
if (!skipMCP) components.push('runtime');
|
|
@@ -75,6 +167,7 @@ async function main() {
|
|
|
75
167
|
interactive: forceInteractive,
|
|
76
168
|
headless: forceHeadless,
|
|
77
169
|
yes: autoYes,
|
|
170
|
+
preset: presetOpt,
|
|
78
171
|
});
|
|
79
172
|
} catch (err) {
|
|
80
173
|
console.error(`❌ Installation failed: ${err.message}\n Run with --no-mcp to skip Python dependencies:\n npx pantheon-opencode init --no-mcp\n Or retry with --force to recreate the venv:\n npx pantheon-opencode init --force`);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Configure Pantheon vision interactively: auth check, mode (native|tool|auto), vision model, tool fallback. Saves to opencode-vision.json. Usage: /pantheon-vision"
|
|
3
|
+
agent: hermes
|
|
4
|
+
---
|
|
5
|
+
# /pantheon-vision — Configure Vision
|
|
6
|
+
|
|
7
|
+
**What:** Walks the user through configuring Pantheon's image-vision plugin
|
|
8
|
+
(native multimodal model via the opencode Zen endpoint, or MCP tool fallback).
|
|
9
|
+
**Usage:** `/pantheon-vision [--global]`
|
|
10
|
+
**When:** First-time setup, changing the vision model, troubleshooting images
|
|
11
|
+
not being described, switching between native and tool mode.
|
|
12
|
+
|
|
13
|
+
## Steps
|
|
14
|
+
|
|
15
|
+
1. **Check connectivity & key source**
|
|
16
|
+
- Read the opencode auth store: `<data dir>/auth.json`
|
|
17
|
+
(default `~/.local/share/opencode/auth.json`, provider entries
|
|
18
|
+
`opencode-go` then `opencode`) via `readOpencodeAuthToken`.
|
|
19
|
+
- Report whether the user is connected (`opencode auth login`) or has an
|
|
20
|
+
env key (`PANTHEON_OPENCODE_API_KEY` / `OPENCODE_API_KEY`).
|
|
21
|
+
- **Never log or echo the token itself** — only connected/not connected.
|
|
22
|
+
|
|
23
|
+
2. **Show current status**
|
|
24
|
+
- Effective mode precedence: env `PANTHEON_VISION_MODE` > config file
|
|
25
|
+
`mode` > default `auto`.
|
|
26
|
+
- Read the current config file if present (project `.opencode/opencode-vision.json`
|
|
27
|
+
then user `~/.config/opencode/opencode-vision.json`).
|
|
28
|
+
|
|
29
|
+
3. **Ask what to set (interactive)**
|
|
30
|
+
- **Mode:** `native` | `tool` | `auto`
|
|
31
|
+
- `native` — describe images via the multimodal model directly (needs key)
|
|
32
|
+
- `tool` — always use the MCP tool fallback (`imageAnalysisTool`)
|
|
33
|
+
- `auto` — try native first, fall back to the tool (default)
|
|
34
|
+
- **Vision model:** e.g. `opencode-go/mimo-v2.5`, `opencode-go/minimax-m3`,
|
|
35
|
+
or an explicit override (precedence: env `PANTHEON_VISION_MODEL` > config
|
|
36
|
+
`visionModel` > active preset `vision.model` > default `opencode-go/mimo-v2.5`).
|
|
37
|
+
- **Tool fallback:** `imageAnalysisTool` (default
|
|
38
|
+
`pantheon_vision_vision_describe`). Bifrost is opt-in only when
|
|
39
|
+
explicitly selected here or through `PANTHEON_VISION_TOOL`.
|
|
40
|
+
|
|
41
|
+
4. **Save the config**
|
|
42
|
+
- Default: project config `.opencode/opencode-vision.json`
|
|
43
|
+
(merged with the user config `~/.config/opencode/opencode-vision.json`;
|
|
44
|
+
project wins on conflicts).
|
|
45
|
+
- `--global` → write to `~/.config/opencode/opencode-vision.json`.
|
|
46
|
+
- Shape:
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mode": "auto",
|
|
50
|
+
"visionModel": "opencode-go/mimo-v2.5",
|
|
51
|
+
"imageAnalysisTool": "pantheon_vision_vision_describe"
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
- Optional fields also supported: `models` (model wildcard patterns),
|
|
55
|
+
`promptTemplate` (with `{imageList}`/`{imageCount}`/`{toolName}`/`{userText}`).
|
|
56
|
+
|
|
57
|
+
5. **Wrap up**
|
|
58
|
+
- Tell the user to restart opencode (config is read at startup / per turn)
|
|
59
|
+
and test by pasting an image with a question.
|
|
60
|
+
- If no key and no auth store entry exist, recommend `opencode auth login`
|
|
61
|
+
(native mode) or leaving the tool fallback enabled.
|
|
62
|
+
|
|
63
|
+
## Rules
|
|
64
|
+
|
|
65
|
+
- Do NOT print tokens or auth.json contents.
|
|
66
|
+
- Do NOT overwrite existing config keys not being changed — merge.
|
|
67
|
+
- If the config file is corrupted JSON, warn and ask before overwriting.
|
package/opencode.json
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"provider": {
|
|
4
|
+
"opencode-go": {
|
|
5
|
+
"models": {
|
|
6
|
+
"mimo-v2.5": {
|
|
7
|
+
"attachment": true,
|
|
8
|
+
"modalities": { "input": ["text", "image"] }
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
},
|
|
3
13
|
"default_agent": "zeus",
|
|
4
14
|
"subagent_depth": 2,
|
|
5
15
|
"model": "opencode-go/deepseek-v4-flash",
|
|
@@ -18,19 +28,13 @@
|
|
|
18
28
|
"pip *": "allow"
|
|
19
29
|
}
|
|
20
30
|
},
|
|
21
|
-
"instructions": [
|
|
22
|
-
|
|
23
|
-
"src/instructions/*.instructions.md"
|
|
24
|
-
],
|
|
25
|
-
"plugin": [],
|
|
31
|
+
"instructions": ["AGENTS.md", "src/instructions/*.instructions.md"],
|
|
32
|
+
"plugin": ["/home/ils15/pantheon/src/plugins/pantheon-hooks.ts"],
|
|
26
33
|
"mcp": {
|
|
27
34
|
"bifrost": {
|
|
28
35
|
"type": "remote",
|
|
29
36
|
"url": "https://llm.ofertachina.cloud/mcp",
|
|
30
|
-
"enabled": true
|
|
31
|
-
"headers": {
|
|
32
|
-
"x-bf-vk": "sk-bf-a5a9b5b0-45fc-414d-8e4d-f9beb8c49a6d"
|
|
33
|
-
}
|
|
37
|
+
"enabled": true
|
|
34
38
|
}
|
|
35
39
|
},
|
|
36
40
|
"agent": {
|
|
@@ -44,4 +48,4 @@
|
|
|
44
48
|
"model": "opencode-go/deepseek-v4-pro"
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
|
-
}
|
|
51
|
+
}
|
package/package.json
CHANGED
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-opencode",
|
|
3
|
-
"version": "1.2.
|
|
4
|
-
"description": "Multi-agent orchestration for OpenCode
|
|
3
|
+
"version": "1.2.2",
|
|
4
|
+
"description": "Multi-agent orchestration for OpenCode — 14 specialized agents, TDD, Themis review gates.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
7
7
|
"registry": "https://registry.npmjs.org/"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"postinstall": "node scripts/postinstall.mjs",
|
|
11
|
+
"prepare": "husky",
|
|
11
12
|
"setup": "node bin/pantheon-init.mjs init",
|
|
12
13
|
"setup:lite": "node bin/pantheon-init.mjs init --clean --components agents,skills,instructions",
|
|
13
14
|
"doctor": "node scripts/doctor.mjs",
|
|
15
|
+
"prune": "node scripts/prune.mjs",
|
|
14
16
|
"init": "node bin/pantheon-init.mjs init",
|
|
15
17
|
"opencode": "OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode",
|
|
16
18
|
"start": "OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode",
|
|
17
19
|
"prepublishOnly": "node scripts/validate-routing.mjs",
|
|
18
|
-
"postpublish": "node -e \"console.log('
|
|
20
|
+
"postpublish": "node -e \"console.log('✅ Published pantheon-opencode v' + require('./package.json').version + ' to npm')\"",
|
|
19
21
|
"version:status": "node scripts/versioning.mjs status",
|
|
20
22
|
"version:apply": "node scripts/versioning.mjs apply",
|
|
23
|
+
"version:check": "node scripts/version-check.mjs",
|
|
21
24
|
"release:dry-run": "node scripts/versioning.mjs status && npm pack --dry-run",
|
|
22
25
|
"publish:next": "npm publish --tag next --access public",
|
|
23
26
|
"lint": "npx biome ci .",
|
|
24
27
|
"typecheck": "npx tsc --noEmit",
|
|
25
28
|
"audit": "npm audit --audit-level=high",
|
|
29
|
+
"secret-scan": "node scripts/secret-scan.mjs",
|
|
30
|
+
"hooks:install": "pre-commit install",
|
|
31
|
+
"hooks:update": "pre-commit autoupdate",
|
|
32
|
+
"test:security": "npm run secret-scan && node tests/test_install_mcp_vision.mjs && node tests/test_doctor_layers.mjs",
|
|
33
|
+
"test:node": "node --test tests/*.mjs",
|
|
26
34
|
"test": "python3 -m pytest tests/ -q 2>&1 || echo \"No tests found\"",
|
|
27
35
|
"test:ci": "python3 -m pytest tests/ -q --tb=short 2>&1 || true"
|
|
28
36
|
},
|
|
@@ -50,6 +58,9 @@
|
|
|
50
58
|
"scripts/*.mjs",
|
|
51
59
|
"src/mcp/*.sh",
|
|
52
60
|
"src/routing.yml",
|
|
61
|
+
"src/pantheon/**",
|
|
62
|
+
"src/plugins/**",
|
|
63
|
+
"scripts/hooks/**",
|
|
53
64
|
"src/mcp/requirements*",
|
|
54
65
|
"src/plugins/tui/**",
|
|
55
66
|
"src/skills/**"
|
|
@@ -60,12 +71,17 @@
|
|
|
60
71
|
"devDependencies": {
|
|
61
72
|
"@commitlint/cli": "^19.8.0",
|
|
62
73
|
"@commitlint/config-conventional": "^19.8.0",
|
|
74
|
+
"@opencode-ai/plugin": "1.18.11",
|
|
63
75
|
"husky": "^9.1.7"
|
|
64
76
|
},
|
|
65
77
|
"repository": {
|
|
66
78
|
"type": "git",
|
|
67
79
|
"url": "https://github.com/ils15/pantheon-opencode.git"
|
|
68
80
|
},
|
|
81
|
+
"homepage": "https://github.com/ils15/pantheon-opencode",
|
|
82
|
+
"bugs": {
|
|
83
|
+
"url": "https://github.com/ils15/pantheon-opencode/issues"
|
|
84
|
+
},
|
|
69
85
|
"keywords": [
|
|
70
86
|
"github",
|
|
71
87
|
"copilot",
|
|
@@ -80,4 +96,4 @@
|
|
|
80
96
|
"node": ">=18"
|
|
81
97
|
},
|
|
82
98
|
"preferGlobal": true
|
|
83
|
-
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* changelog-extract.mjs — print a version section from CHANGELOG.md
|
|
4
|
+
*
|
|
5
|
+
* The release workflow needs the CHANGELOG entry for the version it is about
|
|
6
|
+
* to tag. This script extracts the body of `## [<version>] - YYYY-MM-DD`
|
|
7
|
+
* (leading `v` tolerated, date optional) and prints it to stdout. When the
|
|
8
|
+
* section is missing it exits 1, so the workflow fails loudly: "add the
|
|
9
|
+
* section in the version-bump PR".
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* node scripts/changelog-extract.mjs 1.2.1
|
|
13
|
+
* node scripts/changelog-extract.mjs v1.2.1 --changelog /path/to/CHANGELOG.md
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { readFileSync } from 'node:fs'
|
|
17
|
+
import { dirname, join } from 'node:path'
|
|
18
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
19
|
+
|
|
20
|
+
const DEFAULT_CHANGELOG = join(dirname(fileURLToPath(import.meta.url)), '..', 'CHANGELOG.md')
|
|
21
|
+
|
|
22
|
+
/** Escape regex metacharacters so a version like 1.0.0 matches literally. */
|
|
23
|
+
function escapeRegExp(text) {
|
|
24
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Extract the body of a version section from changelog text.
|
|
29
|
+
*
|
|
30
|
+
* Finds the heading `## [<version>] - YYYY-MM-DD` (leading `v` and the date
|
|
31
|
+
* are optional) and returns everything after the heading line up to the next
|
|
32
|
+
* `## [` heading (or end of text). The `## [Unreleased]` heading is never
|
|
33
|
+
* matched, even when asked for explicitly.
|
|
34
|
+
*
|
|
35
|
+
* @param {string} changelogText - full CHANGELOG.md content
|
|
36
|
+
* @param {string} version - e.g. "1.2.1" or "v1.2.1"
|
|
37
|
+
* @returns {{found: boolean, body: string|null}} body is null when not found
|
|
38
|
+
*/
|
|
39
|
+
export function extractSection(changelogText, version) {
|
|
40
|
+
const normalized = String(version).trim().replace(/^v/i, '')
|
|
41
|
+
if (!normalized || /^unreleased$/i.test(normalized)) {
|
|
42
|
+
return { found: false, body: null }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const escaped = escapeRegExp(normalized)
|
|
46
|
+
// Single-line heading: `## [v?1.2.1]` optionally followed by ` - YYYY-MM-DD`.
|
|
47
|
+
// [ \t] (not \s) keeps the match from crossing newlines; the lookahead
|
|
48
|
+
// requires end-of-line or whitespace so `[1.0.0-beta]` can never match `1.0.0`.
|
|
49
|
+
const headingRe = new RegExp(
|
|
50
|
+
`^##[ \\t]+\\[v?${escaped}\\](?:[ \\t]*-[ \\t]*\\d{4}-\\d{2}-\\d{2})?(?=[ \\t]|$)`,
|
|
51
|
+
'm',
|
|
52
|
+
)
|
|
53
|
+
const match = headingRe.exec(changelogText)
|
|
54
|
+
if (!match) return { found: false, body: null }
|
|
55
|
+
|
|
56
|
+
// Advance past the heading line (match.index may point mid-line).
|
|
57
|
+
const newline = changelogText.indexOf('\n', match.index)
|
|
58
|
+
const start = newline === -1 ? changelogText.length : newline + 1
|
|
59
|
+
|
|
60
|
+
// Body ends at the next `## [` heading or end of text.
|
|
61
|
+
const nextHeading = /^##[ \t]+\[/m.exec(changelogText.slice(start))
|
|
62
|
+
const end = nextHeading ? start + nextHeading.index : changelogText.length
|
|
63
|
+
|
|
64
|
+
return { found: true, body: changelogText.slice(start, end).trim() }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const isDirectRun = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href
|
|
68
|
+
|
|
69
|
+
if (isDirectRun) {
|
|
70
|
+
const args = process.argv.slice(2)
|
|
71
|
+
let changelogPath = DEFAULT_CHANGELOG
|
|
72
|
+
let version = null
|
|
73
|
+
|
|
74
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
75
|
+
const arg = args[i]
|
|
76
|
+
if (arg === '--changelog') {
|
|
77
|
+
changelogPath = args[i + 1]
|
|
78
|
+
i += 1
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
if (arg.startsWith('--changelog=')) {
|
|
82
|
+
changelogPath = arg.slice('--changelog='.length)
|
|
83
|
+
continue
|
|
84
|
+
}
|
|
85
|
+
version = arg
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!version) {
|
|
89
|
+
console.error('Usage: node scripts/changelog-extract.mjs <version> [--changelog <path>]')
|
|
90
|
+
process.exitCode = 2
|
|
91
|
+
} else {
|
|
92
|
+
let text
|
|
93
|
+
try {
|
|
94
|
+
text = readFileSync(changelogPath, 'utf-8')
|
|
95
|
+
} catch {
|
|
96
|
+
console.error(`changelog-extract: cannot read ${changelogPath}`)
|
|
97
|
+
process.exitCode = 1
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (text !== undefined) {
|
|
101
|
+
const { found, body } = extractSection(text, version)
|
|
102
|
+
if (!found) {
|
|
103
|
+
console.error(`changelog-extract: section [${version}] not found in ${changelogPath}`)
|
|
104
|
+
console.error('Add the section in the version-bump PR — the release workflow requires it.')
|
|
105
|
+
process.exitCode = 1
|
|
106
|
+
} else {
|
|
107
|
+
process.stdout.write(`${body}\n`)
|
|
108
|
+
process.exitCode = 0
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|