pantheon-opencode 1.5.0-beta.3 → 1.5.0-beta.5

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.
Files changed (44) hide show
  1. package/.pantheon/code-mode/manifest.json +21 -0
  2. package/CHANGELOG.md +871 -0
  3. package/README.md +101 -1
  4. package/README.pt-BR.md +55 -1
  5. package/bin/pantheon-init.mjs +178 -21
  6. package/package.json +14 -10
  7. package/plugin.json +1 -1
  8. package/pyproject.toml +1 -1
  9. package/scripts/code_mode_server.py +339 -28
  10. package/scripts/doctor.mjs +178 -9
  11. package/scripts/install/cli-ui.mjs +17 -11
  12. package/scripts/install/config-migration.mjs +4 -6
  13. package/scripts/install/opencode.mjs +116 -35
  14. package/scripts/install/shared.mjs +43 -2
  15. package/scripts/install/strings.mjs +129 -0
  16. package/scripts/install-mcp.mjs +1 -0
  17. package/scripts/probe-pantheon-cost.mjs +10 -1
  18. package/scripts/release-beta-version.mjs +2 -7
  19. package/scripts/sync-artifacts.mjs +169 -0
  20. package/scripts/sync-tui.mjs +124 -34
  21. package/src/agents/zeus.md +2 -2
  22. package/src/mcp/code_mode_server.py +339 -28
  23. package/src/pantheon/auto-wake.ts +1 -5
  24. package/src/pantheon/background-job-board.ts +14 -0
  25. package/src/pantheon/cost-command.ts +62 -42
  26. package/src/pantheon/delegate-manager.ts +109 -12
  27. package/src/pantheon/native-probe.ts +234 -0
  28. package/src/pantheon/native-task-status.ts +139 -0
  29. package/src/pantheon/shared-board.ts +37 -0
  30. package/src/pantheon/task-result-guard.ts +107 -0
  31. package/src/pantheon/token-opt.ts +18 -110
  32. package/src/pantheon/tool-ceiling.ts +159 -0
  33. package/src/pantheon/v2-events.ts +10 -1
  34. package/src/plugin.ts +25 -9
  35. package/src/plugins/pantheon-hooks.ts +51 -0
  36. package/src/plugins/tui/dist/server.d.ts +1 -1
  37. package/src/plugins/tui/dist/server.d.ts.map +1 -1
  38. package/src/plugins/tui/dist/server.js +1 -1
  39. package/src/plugins/tui/dist/server.js.map +1 -1
  40. package/src/plugins/tui/dist/tui.d.ts +1 -1
  41. package/src/plugins/tui/dist/tui.js +1 -1
  42. package/src/plugins/tui/dist/tui.js.map +1 -1
  43. package/src/plugins/tui/package-lock.json +2 -2
  44. package/src/plugins/tui/package.json +1 -1
package/README.md CHANGED
@@ -30,7 +30,7 @@ way to plan work, make progress, check results, and keep useful project context.
30
30
 
31
31
  ## Start in 2 minutes
32
32
 
33
- Requirements: [OpenCode 1.18.4+](https://opencode.ai/docs/) and Node.js 18+.
33
+ Requirements: [OpenCode 1.18.4+](https://opencode.ai/docs/) and Node.js 22+.
34
34
 
35
35
  From the project where you want to use Pantheon:
36
36
 
@@ -76,6 +76,65 @@ availability and configuration of OpenCode and any optional services you choose
76
76
  to use. Check the [releases](https://github.com/ils15/pantheon-opencode/releases)
77
77
  and [changelog](CHANGELOG.md) for the latest published changes.
78
78
 
79
+ ## Native delegation mode
80
+
81
+ Set `PANTHEON_DELEGATE_MODE=native` to use OpenCode's native child-session
82
+ delegation. Native mode is strict: it does not use foreground fallback, model
83
+ failover, or retry; it performs a lazy capability probe on the first dispatch,
84
+ and includes the resulting status in the delegation receipt.
85
+
86
+ ## Cost tool backend
87
+
88
+ `pantheon_cost` has one backend: read-only `node:sqlite` against the selected
89
+ `opencode.db`. On Node versions without `node:sqlite` (Node < 22.5), the tool
90
+ returns `status: UNSUPPORTED`; it has no CLI fallback. Database failures return
91
+ a contract status such as `UNAVAILABLE` or
92
+ `CORRUPT_DATA` and include diagnostic detail, including captured stderr when
93
+ available.
94
+
95
+
96
+ ## Code-mode execution (explicit opt-in)
97
+
98
+ Scripts run through the `pantheon-code-mode` MCP server (`execute_code_script`)
99
+ only when they are explicitly approved. Approval is recorded in
100
+ `.pantheon/code-mode/manifest.json` with the SHA-256 of each script:
101
+
102
+ ```json
103
+ {
104
+ "version": 1,
105
+ "scripts": {
106
+ "example-sync.sh": "<sha256>"
107
+ }
108
+ }
109
+ ```
110
+
111
+ - **No manifest → nothing executes.** A missing manifest returns
112
+ `INVALID_STATE`; scripts are never run implicitly.
113
+ - **Not listed → `CONFLICT`.** The script must be approved first.
114
+ - **Hash mismatch → `CORRUPT_DATA`.** The SHA-256 of the file on disk must
115
+ match the manifest entry, so edits after approval are detected.
116
+
117
+ Approve or re-approve a script with the `approve_code_script` MCP tool:
118
+
119
+ ```
120
+ approve_code_script("checkpoint-session.sh")
121
+ ```
122
+
123
+ The installer seeds the manifest with the SHA-256 of every bundled script, so
124
+ shipped scripts stay approved across installs. Re-run `approve_code_script`
125
+ after intentionally editing a script. Status codes follow the shared nine-code
126
+ contract (`OK`, `UNSUPPORTED`, `UNAVAILABLE`, `INVALID_INPUT`, `INVALID_STATE`,
127
+ `CONFLICT`, `CORRUPT_DATA`, `TIMEOUT`, `ESCALATE`); with `json_output=true`,
128
+ results carry a `status` field.
129
+
130
+ Code-mode resolves project-first: `.opencode/.pantheon/code-mode` is preferred,
131
+ then `.pantheon/code-mode`. `PANTHEON_PROJECT` overrides the working directory,
132
+ and the installed MCP uses `cwd: "."` so OpenCode supplies the workspace. A
133
+ global directory is used only when no usable project directory is available;
134
+ once a project directory is selected, a missing or corrupt manifest fails
135
+ closed instead of falling back. `doctor` validates the manifest and every
136
+ script's SHA-256 without regenerating it.
137
+
79
138
 
80
139
  ## What's new in 1.5.0-beta.2
81
140
 
@@ -154,6 +213,36 @@ The installer still writes the compatibility settings required by the selected
154
213
  OpenCode host, such as `experimental.subagent_depth`; this does not convert a
155
214
  V1 plugin into V2 or provide V2 with V1 hooks.
156
215
 
216
+ ## Updating between releases (beta.5+)
217
+
218
+ One command keeps an existing installation current:
219
+
220
+ ```bash
221
+ npx pantheon-opencode update # npm beta channel + config refresh
222
+ npx pantheon-opencode update --stable # stable channel instead
223
+ ```
224
+
225
+ `update` compares your installed version with the npm dist-tag, runs
226
+ `npm install -g pantheon-opencode@<channel>`, then re-runs `init --yes
227
+ --headless` so config merges, the venv and MCP entries match the new package.
228
+ Two freshness guarantees back it up:
229
+
230
+ - **Postinstall artifact sync** — after any `npm install`, copy-only artifacts
231
+ (agents, skills, AGENTS.md, commands, MCP scripts, code-mode payload) are
232
+ refreshed into the existing config dir automatically; you never have to
233
+ re-run `init` just for file copies.
234
+ - **Drift detection** — the installer stamps the installed version in
235
+ `.pantheon/install-state.json` and `doctor` warns when the package is newer
236
+ than the last sync, pointing at `update`.
237
+
238
+ `init` also gained `--components agents,skills,...` (narrow install),
239
+ `--clean` (alias of `--force`), `--opencode-version auto`, atomic config
240
+ writes with an `opencode.json.bak` backup, preflight checks for python3/npm
241
+ before any file is written, and a non-fatal Python runtime: if the venv fails,
242
+ the install completes but MCP entries are omitted (with a warning) instead of
243
+ pointing at a broken interpreter. Installer messages auto-detect pt-BR via
244
+ `LANG`/`LC_ALL`.
245
+
157
246
  ## Releases
158
247
 
159
248
  Publication is authorized **only** by an explicit `workflow_dispatch` of the
@@ -218,6 +307,17 @@ optional codemap schema and `code_index`, `code_query`, and `code_neighbors`.
218
307
  The other shared MCP copies remain identical. Do not overwrite one memory copy
219
308
  with the other.
220
309
 
310
+ ### Task-result guard
311
+
312
+ A `task-result-guard` intercepts calls to the native `task()` tool where the
313
+ child session returns an empty or missing result. Instead of surfacing a silent
314
+ `completed` with no payload (which confuses the orchestrator), the guard now
315
+ raises an explicit error. This catches the common free-tier failure mode where a
316
+ child session exceeds the uncached-prefill token budget (`BackendAdmissionRejected`)
317
+ and returns nothing. For large payloads, prefer `pantheon_delegate` over native
318
+ `task()` — the delegation layer has better timeout and error handling.
319
+
320
+
221
321
  ## Documentation
222
322
 
223
323
  - [Installation](docs/INSTALLATION.md) · [Quick start](docs/QUICKSTART.md)
package/README.pt-BR.md CHANGED
@@ -78,6 +78,31 @@ escolher. Veja as [releases](https://github.com/ils15/pantheon-opencode/releases
78
78
  e o [changelog](CHANGELOG.md) para acompanhar as mudanças publicadas.
79
79
 
80
80
 
81
+ ## Execução code-mode (opt-in explícito)
82
+
83
+ Scripts executados pelo MCP server `pantheon-code-mode` (`execute_code_script`)
84
+ só rodam quando aprovados explicitamente. A aprovação fica em
85
+ `.pantheon/code-mode/manifest.json` com o SHA-256 de cada script.
86
+
87
+ - **Sem manifest → nada executa** (`INVALID_STATE`); nunca há execução implícita.
88
+ - **Script fora do manifest → `CONFLICT`**; é preciso aprovar antes.
89
+ - **Hash divergente → `CORRUPT_DATA`**; edições após a aprovação são detectadas.
90
+
91
+ Aprove (ou reaprove) um script com a tool `approve_code_script`. O instalador
92
+ semeia o manifest com o hash de todos os scripts empacotados. Os resultados
93
+ seguem o contrato de nove códigos (`OK`, `UNSUPPORTED`, `UNAVAILABLE`,
94
+ `INVALID_INPUT`, `INVALID_STATE`, `CONFLICT`, `CORRUPT_DATA`, `TIMEOUT`,
95
+ `ESCALATE`) e, com `json_output=true`, incluem o campo `status`.
96
+
97
+ O code-mode resolve primeiro o projeto: `.opencode/.pantheon/code-mode` vem
98
+ antes de `.pantheon/code-mode`. `PANTHEON_PROJECT` sobrescreve o diretório de
99
+ trabalho, e o MCP instalado usa `cwd: "."` para receber o workspace do
100
+ OpenCode. O diretório global só é usado sem um diretório de projeto utilizável;
101
+ depois de selecionar um diretório de projeto, manifest ausente ou corrompido
102
+ falha fechado, sem fallback silencioso. O `doctor` valida o manifest e o
103
+ SHA-256 de cada script sem regenerá-lo.
104
+
105
+
81
106
  ## Novidades da 1.5.0-beta.2
82
107
 
83
108
  - Instalador exclusivo para OpenCode: guias de plataformas consolidados em um
@@ -142,7 +167,7 @@ instalador remove referências Pantheon das duas formas de config antes de
142
167
  gravar apenas o registro Pantheon selecionado. Entradas de terceiros não são
143
168
  convertidas nem reivindicadas por essa regra.
144
169
 
145
- O relatório `pantheon_cost` (apenas V1) pode selecionar o banco com
170
+ O relatório `pantheon_cost`, disponível nos plugins V1 e V2, pode selecionar o banco com
146
171
  `PANTHEON_OPENCODE_VERSION=v1` ou `v2` (`opencode.db` ou `opencode-v2.db`).
147
172
  `PANTHEON_COST_DB=/caminho/absoluto/para/opencode.db` tem precedência sobre o
148
173
  seletor de versão, e um `dbPath` explícito fornecido pelo chamador da
@@ -154,6 +179,35 @@ O instalador continua gravando as configurações de compatibilidade exigidas
154
179
  pelo host OpenCode selecionado, como `experimental.subagent_depth`; isso não
155
180
  converte um plugin V1 em V2 nem dá hooks V1 ao V2.
156
181
 
182
+ ## Atualizando entre releases (beta.5+)
183
+
184
+ Um comando mantém uma instalação existente em dia:
185
+
186
+ ```bash
187
+ npx pantheon-opencode update # canal beta do npm + refresh do config
188
+ npx pantheon-opencode update --stable # canal estável
189
+ ```
190
+
191
+ O `update` compara a versão instalada com o dist-tag do npm, roda
192
+ `npm install -g pantheon-opencode@<canal>` e re-executa `init --yes
193
+ --headless` para alinhar merge de config, venv e entradas de MCP com o novo
194
+ pacote. Duas garantias de freshness sustentam isso:
195
+
196
+ - **Sync de artifacts no postinstall** — depois de qualquer `npm install`, os
197
+ artefatos de cópia (agents, skills, AGENTS.md, commands, scripts MCP,
198
+ payload code-mode) são atualizados automaticamente no config dir existente;
199
+ você nunca precisa re-rodar `init` só para atualizar arquivos.
200
+ - **Detecção de drift** — o instalador grava a versão instalada em
201
+ `.pantheon/install-state.json` e o `doctor` avisa quando o pacote é mais
202
+ novo que o último sync, apontando para o `update`.
203
+
204
+ O `init` também ganhou `--components agents,skills,...` (instalação enxuta),
205
+ `--clean` (alias de `--force`), `--opencode-version auto`, escrita atômica do
206
+ config com backup em `opencode.json.bak`, pré-checagens de python3/npm antes
207
+ de escrever qualquer arquivo e runtime Python não-fatal: se a venv falha, a
208
+ instalação completa mas as entradas MCP ficam de fora (com aviso) em vez de
209
+ apontar para um interpretador quebrado.
210
+
157
211
  ## Releases
158
212
 
159
213
  A publicação é autorizada **somente** por um `workflow_dispatch` explícito do
@@ -22,9 +22,10 @@ import { fileURLToPath } from 'node:url'
22
22
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
23
23
  const ROOT = path.resolve(__dirname, '..')
24
24
 
25
- const REQUIRED_NODE_MAJOR = 18
25
+ const REQUIRED_NODE_MAJOR = 22
26
26
  if (parseInt(process.versions.node.split('.')[0], 10) < REQUIRED_NODE_MAJOR) {
27
27
  console.error(`❌ Node.js >= ${REQUIRED_NODE_MAJOR} required (current: ${process.versions.node})`)
28
+ console.error(' Install a newer Node via nvm: https://github.com/nvm-sh/nvm')
28
29
  process.exit(1)
29
30
  }
30
31
 
@@ -50,9 +51,13 @@ function printUsage() {
50
51
  console.log(' npx pantheon-opencode init --interactive # Force interactive TUI mode')
51
52
  console.log(' npx pantheon-opencode init --headless # Force non-interactive mode')
52
53
  console.log(' npx pantheon-opencode init -y # Skip confirmations, use defaults')
54
+ console.log(' npx pantheon-opencode init --components agents,skills,instructions')
53
55
  console.log(' npx pantheon-opencode init --opencode-version v1|v2|auto')
54
56
  console.log(' npx pantheon-opencode init --preset <name> # Install and activate model preset')
55
57
  console.log(' npx pantheon-opencode set-tier <name> # Set active model preset (global)')
58
+ console.log(
59
+ ' npx pantheon-opencode update [--stable] # Update package + re-run init (default: beta channel)',
60
+ )
56
61
  console.log(
57
62
  ' npx pantheon-opencode set-tier <name> --project # Set active model preset (project)',
58
63
  )
@@ -112,6 +117,78 @@ async function main() {
112
117
  process.exit(result.status ?? 1)
113
118
  }
114
119
 
120
+ // beta.5: one-command update — check npm dist-tag, install, re-init.
121
+ // Default channel is `beta` (the prerelease line); `--stable` uses `latest`.
122
+ if (command === 'update') {
123
+ const channel = args.includes('--stable') ? 'latest' : 'beta'
124
+ const current = readVersion()
125
+ const { spawnSync } = await import('node:child_process')
126
+ const _S = (await import('../scripts/install/strings.mjs')).strings()
127
+
128
+ console.log(`Pantheon OpenCode v${current} — checking the "${channel}" channel...`)
129
+ const view = spawnSync('npm', ['view', `pantheon-opencode@${channel}`, 'version'], {
130
+ encoding: 'utf8',
131
+ timeout: 30_000,
132
+ })
133
+ const latest = (view.stdout || '').trim().split('\n').filter(Boolean).pop()
134
+ if (view.status !== 0 || !latest) {
135
+ console.error(
136
+ `❌ Could not reach the npm registry: ${(view.stderr || view.stdout || '').trim() || 'no version returned'}`,
137
+ )
138
+ process.exit(1)
139
+ }
140
+
141
+ // Prerelease-aware compare: core (major.minor.patch) first, then the
142
+ // beta counter; a stable release outranks any beta of the same core.
143
+ const parsePantheonVersion = (v) => {
144
+ const m = String(v)
145
+ .trim()
146
+ .match(/^(\d+)\.(\d+)\.(\d+)(?:-beta\.(\d+))?$/)
147
+ if (!m) return null
148
+ return {
149
+ core: [+m[1], +m[2], +m[3]],
150
+ beta: m[4] === undefined ? Number.POSITIVE_INFINITY : +m[4],
151
+ }
152
+ }
153
+ const cmp = (a, b) => {
154
+ const pa = parsePantheonVersion(a)
155
+ const pb = parsePantheonVersion(b)
156
+ if (!pa || !pb) return 0
157
+ for (let i = 0; i < 3; i++) {
158
+ if (pa.core[i] !== pb.core[i]) return pa.core[i] - pb.core[i]
159
+ }
160
+ return pa.beta - pb.beta
161
+ }
162
+ if (cmp(latest, current) <= 0) {
163
+ console.log(`✅ Already up to date (installed v${current}; ${channel} channel: v${latest}).`)
164
+ return
165
+ }
166
+
167
+ console.log(`Updating v${current} → v${latest} (channel: ${channel})...`)
168
+ const install = spawnSync('npm', ['install', '-g', `pantheon-opencode@${channel}`], {
169
+ stdio: 'inherit',
170
+ })
171
+ if (install.status !== 0) {
172
+ console.error('❌ npm install failed — the previous installation is untouched.')
173
+ process.exit(install.status ?? 1)
174
+ }
175
+
176
+ // Refresh config/venv/MCP entries with the NEW package (resolved via
177
+ // PATH — the freshly installed global bin).
178
+ const init = spawnSync('pantheon-opencode', ['init', '--yes', '--headless'], {
179
+ stdio: 'inherit',
180
+ shell: process.platform === 'win32',
181
+ })
182
+ if (init.status !== 0 || init.error) {
183
+ console.error(
184
+ '⚠️ Updated, but the config refresh did not complete — run `npx pantheon-opencode init` manually.',
185
+ )
186
+ process.exit(init.status ?? 1)
187
+ }
188
+ console.log(`✅ Updated to v${latest}.`)
189
+ return
190
+ }
191
+
115
192
  if (args.includes('--help')) {
116
193
  printUsage()
117
194
  process.exit(0)
@@ -195,11 +272,49 @@ async function main() {
195
272
  const isProject = args.includes('--project')
196
273
  const isDryRun = args.includes('--dry-run')
197
274
  const skipMCP = args.includes('--no-mcp')
198
- const forceReinstall = args.includes('--force')
275
+ // --clean is an alias of --force (both wipe component dirs + recreate venv).
276
+ const forceReinstall = args.includes('--force') || args.includes('--clean')
199
277
  const runDoctor = args.includes('--doctor')
200
278
  const forceInteractive = args.includes('--interactive')
201
279
  const forceHeadless = args.includes('--headless')
202
280
  const autoYes = args.includes('--yes') || args.includes('-y')
281
+
282
+ // beta.5: warn on unrecognized flags instead of silently ignoring them.
283
+ const VALUE_FLAGS = new Set([
284
+ '--preset',
285
+ '--model',
286
+ '--small-model',
287
+ '--version',
288
+ '--opencode-version',
289
+ '--components',
290
+ ])
291
+ for (let i = 1; i < args.length; i++) {
292
+ const arg = args[i]
293
+ if (!arg.startsWith('--') || arg === '--help') continue
294
+ if (VALUE_FLAGS.has(arg)) {
295
+ i++ // skip the flag's value
296
+ continue
297
+ }
298
+ const bare = arg.split('=', 1)[0]
299
+ if (
300
+ !VALUE_FLAGS.has(bare) &&
301
+ ![
302
+ '--project',
303
+ '--dry-run',
304
+ '--no-mcp',
305
+ '--force',
306
+ '--clean',
307
+ '--doctor',
308
+ '--interactive',
309
+ '--headless',
310
+ '--yes',
311
+ '-y',
312
+ ].includes(bare)
313
+ ) {
314
+ console.error(`⚠️ Unknown option ignored: ${arg}`)
315
+ }
316
+ }
317
+
203
318
  const presetIndex = args.indexOf('--preset')
204
319
  const presetOpt = presetIndex >= 0 ? (args[presetIndex + 1] ?? null) : null
205
320
  // Explicit top-level model overrides for the generated config.
@@ -207,8 +322,9 @@ async function main() {
207
322
  const modelOpt = modelIndex >= 0 ? (args[modelIndex + 1] ?? null) : null
208
323
  const smallModelIndex = args.indexOf('--small-model')
209
324
  const smallModelOpt = smallModelIndex >= 0 ? (args[smallModelIndex + 1] ?? null) : null
210
- // --version v1|v2 labels the install target (informational; the config is
211
- // shared and V1-shaped under both versions). Invalid values fail fast.
325
+ // --version v1|v2|auto labels the install target. `auto` resolves the
326
+ // generation from the opencode binary at install time (see
327
+ // scripts/install/opencode-version.mjs). Invalid values fail fast.
212
328
  const versionIndex = args.indexOf('--version')
213
329
  const legacyVersionIndex = args.indexOf('--opencode-version')
214
330
  const inlineVersion = args.find((arg) => arg.startsWith('--opencode-version='))
@@ -218,20 +334,61 @@ async function main() {
218
334
  : legacyVersionIndex >= 0
219
335
  ? (args[legacyVersionIndex + 1] ?? null)
220
336
  : (inlineVersion?.split('=', 2)[1] ?? null)
221
- if (versionOpt !== null && versionOpt !== 'v1' && versionOpt !== 'v2') {
222
- console.error(`❌ Invalid --version "${versionOpt}" — expected v1 or v2`)
337
+ if (
338
+ versionOpt !== null &&
339
+ versionOpt !== 'v1' &&
340
+ versionOpt !== 'v2' &&
341
+ versionOpt !== 'auto'
342
+ ) {
343
+ console.error(`❌ Invalid --version "${versionOpt}" — expected v1, v2 or auto`)
223
344
  process.exit(1)
224
345
  }
225
346
 
226
- const components = ['agents', 'skills', 'instructions', 'commands', 'plugins']
227
- if (!skipMCP) components.push('runtime')
347
+ // --components agents,skills,... narrows the install; --no-mcp drops the
348
+ // runtime component. Unknown component names fail fast (typo protection).
349
+ const KNOWN_COMPONENTS = ['agents', 'skills', 'instructions', 'commands', 'plugins', 'runtime']
350
+ const componentsIndex = args.indexOf('--components')
351
+ const inlineComponents = args.find((arg) => arg.startsWith('--components='))
352
+ const componentsOpt =
353
+ componentsIndex >= 0
354
+ ? (args[componentsIndex + 1] ?? null)
355
+ : (inlineComponents?.split('=', 2)[1] ?? null)
356
+ let requestedComponents = null
357
+ if (componentsOpt !== null) {
358
+ requestedComponents = componentsOpt
359
+ .split(',')
360
+ .map((c) => c.trim())
361
+ .filter(Boolean)
362
+ const unknown = requestedComponents.filter((c) => !KNOWN_COMPONENTS.includes(c))
363
+ if (requestedComponents.length === 0 || unknown.length > 0) {
364
+ console.error(
365
+ `❌ Invalid --components "${componentsOpt}" — expected comma-separated names from: ${KNOWN_COMPONENTS.join(', ')}`,
366
+ )
367
+ process.exit(1)
368
+ }
369
+ }
370
+
371
+ const components = requestedComponents ?? [
372
+ 'agents',
373
+ 'skills',
374
+ 'instructions',
375
+ 'commands',
376
+ 'plugins',
377
+ ]
378
+ if (!skipMCP && !components.includes('runtime')) components.push('runtime')
379
+ if (skipMCP) {
380
+ const runtimeIndex = components.indexOf('runtime')
381
+ if (runtimeIndex >= 0) components.splice(runtimeIndex, 1)
382
+ }
228
383
 
229
384
  // Version info
230
385
  const version = readVersion()
386
+ const { strings } = await import('../scripts/install/strings.mjs')
387
+ const S = strings()
231
388
 
232
389
  console.log('')
233
390
  if (!forceInteractive || isDryRun) {
234
- console.log(`Pantheon OpenCode v${version} — ${isDryRun ? 'DRY RUN' : 'Installing...'}`)
391
+ console.log(`Pantheon OpenCode v${version} — ${isDryRun ? S.dryRun : S.installing}`)
235
392
  console.log('')
236
393
  }
237
394
 
@@ -248,15 +405,17 @@ async function main() {
248
405
  version: versionOpt ?? 'v1',
249
406
  })
250
407
  } catch (err) {
251
- console.error(
252
- `❌ 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`,
253
- )
408
+ if (err?.message === 'Canceled') {
409
+ console.error(S.canceled)
410
+ process.exit(130)
411
+ }
412
+ console.error(`${S.installFailed(err.message)}\n${S.failHintNoMcp}\n${S.failHintForce}`)
254
413
  process.exit(1)
255
414
  }
256
415
 
257
416
  if (runDoctor && !isDryRun) {
258
417
  console.log('')
259
- console.log(' Running health check...')
418
+ console.log(` ${S.runningHealthCheck}`)
260
419
  try {
261
420
  const { spawnSync } = await import('node:child_process')
262
421
  const doctorScript = path.join(ROOT, 'scripts', 'doctor.mjs')
@@ -268,16 +427,14 @@ async function main() {
268
427
 
269
428
  console.log('')
270
429
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
271
- console.log(` ✅ Pantheon OpenCode v${version} installed!`)
430
+ console.log(` ${S.installedTitle(version)}`)
272
431
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
273
432
  console.log('')
274
- console.log(' Next steps:')
275
- console.log(' 1. Verify installation:')
276
- console.log(' npx pantheon-opencode doctor')
277
- console.log(' 2. Launch OpenCode')
278
- console.log(' 3. Invoke agents with @agent-name in chat')
279
- console.log(' 4. For project-local install:')
280
- console.log(' npx pantheon-opencode init --project')
433
+ console.log(` ${S.nextSteps}`)
434
+ console.log(` ${S.nextVerify}`)
435
+ console.log(` ${S.nextLaunch}`)
436
+ console.log(` ${S.nextAgents}`)
437
+ console.log(` ${S.nextProject}`)
281
438
  console.log('')
282
439
 
283
440
  return
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pantheon-opencode",
3
- "version": "1.5.0-beta.3",
3
+ "version": "1.5.0-beta.5",
4
4
  "description": "Multi-agent orchestration for OpenCode — 14 specialized agents, TDD, Themis review gates.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -32,15 +32,15 @@
32
32
  "package:evidence": "node scripts/package-evidence.mjs",
33
33
  "package:validate": "node scripts/validate-package.mjs",
34
34
  "publish:next": "npm publish --tag next --access public",
35
- "lint": "npx biome ci .",
36
- "typecheck": "npx tsc --noEmit",
35
+ "lint": "biome ci .",
36
+ "typecheck": "tsc --noEmit",
37
37
  "audit": "npm audit --audit-level=high",
38
38
  "secret-scan": "node scripts/secret-scan.mjs",
39
39
  "hooks:install": "pre-commit install",
40
40
  "hooks:update": "pre-commit autoupdate",
41
- "test:security": "npm run secret-scan && node tests/test_install_mcp_vision.mjs && node tests/test_doctor_layers.mjs",
41
+ "test:security": "npm run secret-scan",
42
42
  "test:node": "node --test tests/*.mjs tests/pantheon/*.mjs",
43
- "test:ts": "for f in tests/pantheon/*.test.ts; do if [ \"$f\" = tests/pantheon/plugin-v2-contract.test.ts ]; then NODE_OPTIONS=--conditions=import npx tsx \"$f\"; else npx tsx \"$f\"; fi || exit 1; done",
43
+ "test:ts": "for f in tests/pantheon/*.test.ts; do if [ \"$f\" = tests/pantheon/plugin-v2-contract.test.ts ]; then NODE_OPTIONS=--conditions=import tsx \"$f\"; else tsx \"$f\"; fi || exit 1; done",
44
44
  "test": "python3 -m pytest tests/ -q 2>&1; status=$?; if [ \"$status\" -eq 5 ]; then echo \"No tests found\"; fi; exit \"$status\"",
45
45
  "test:ci": "python3 -m pytest tests/ -q --tb=short 2>&1"
46
46
  },
@@ -68,6 +68,7 @@
68
68
  "!src/agents/README.md",
69
69
  "bin/*.mjs",
70
70
  "AGENTS.md",
71
+ "CHANGELOG.md",
71
72
  "opencode.json",
72
73
  "src/mcp/*.py",
73
74
  "src/instructions/*.instructions.md",
@@ -87,14 +88,17 @@
87
88
  "!**/*.pyc"
88
89
  ],
89
90
  "dependencies": {
90
- "@opencode-ai/plugin": "1.18.18",
91
+ "@opencode-ai/plugin": "1.18.30",
91
92
  "js-yaml": "^4.3.2",
92
93
  "zod": "4.1.8"
93
94
  },
94
95
  "devDependencies": {
95
- "@commitlint/cli": "^19.8.0",
96
- "@commitlint/config-conventional": "^19.8.0",
97
- "husky": "^9.1.7"
96
+ "@biomejs/biome": "2.5.13",
97
+ "@commitlint/cli": "21.2.2",
98
+ "@commitlint/config-conventional": "21.2.2",
99
+ "husky": "^9.1.7",
100
+ "tsx": "4.23.13",
101
+ "typescript": "6.0.3"
98
102
  },
99
103
  "repository": {
100
104
  "type": "git",
@@ -115,7 +119,7 @@
115
119
  "author": "ils15",
116
120
  "license": "MIT",
117
121
  "engines": {
118
- "node": ">=18"
122
+ "node": ">=22"
119
123
  },
120
124
  "preferGlobal": true
121
125
  }
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pantheon",
3
3
  "publisher": "ils15",
4
- "version": "1.5.0-beta.3",
4
+ "version": "1.5.0-beta.5",
5
5
  "displayName": "Pantheon",
6
6
  "description": "Conductor-Delegate orchestration platform with 14 specialized agents, 21 skills, and OpenCode support.",
7
7
  "agents": "./src/agents",
package/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.backends._legacy:_Backend"
4
4
 
5
5
  [project]
6
6
  name = "pantheon"
7
- version = "1.5.0-beta.3"
7
+ version = "1.5.0-beta.5"
8
8
  description = "Multi-agent orchestration framework"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = []