pantheon-opencode 1.1.3-beta.0 → 1.2.0-beta.9.7bef03e

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 (38) hide show
  1. package/README.md +74 -1
  2. package/bin/pantheon-init.mjs +79 -0
  3. package/commands/pantheon-vision.md +67 -0
  4. package/opencode.json +12 -41
  5. package/package.json +5 -1
  6. package/scripts/doctor.mjs +4 -2
  7. package/scripts/install/health-check.mjs +101 -1
  8. package/scripts/install/model-picker.mjs +118 -0
  9. package/scripts/install/opencode.mjs +98 -17
  10. package/scripts/install-mcp.mjs +98 -13
  11. package/scripts/mcp_persistence_server.py +331 -1
  12. package/scripts/secret-scan.mjs +76 -0
  13. package/scripts/validate-routing.mjs +15 -0
  14. package/src/agents/aphrodite.md +7 -0
  15. package/src/agents/demeter.md +7 -0
  16. package/src/agents/hephaestus.md +7 -0
  17. package/src/agents/hermes.md +7 -0
  18. package/src/agents/zeus.md +31 -18
  19. package/src/instructions/agent-return-format.instructions.md +1 -0
  20. package/src/instructions/zeus-anti-stall.instructions.md +43 -5
  21. package/src/instructions/zeus-timeout-retry.instructions.md +47 -10
  22. package/src/mcp/install-mcp.mjs +6 -913
  23. package/src/mcp/mcp_persistence_server.py +332 -0
  24. package/src/mcp/pantheon_vision_server.py +495 -0
  25. package/src/mcp/requirements-vision.txt +3 -0
  26. package/src/pantheon/auto-wake.ts +91 -0
  27. package/src/pantheon/background-job-board.ts +568 -0
  28. package/src/pantheon/file-persistence.ts +95 -0
  29. package/src/pantheon/presets.d.mts +99 -0
  30. package/src/pantheon/presets.mjs +491 -0
  31. package/src/pantheon/vision.ts +1340 -0
  32. package/src/plugin.ts +40 -25
  33. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  34. package/src/plugins/tui/dist/tui.js +806 -222
  35. package/src/plugins/tui/dist/tui.tsx +866 -121
  36. package/src/plugins/tui/package.json +2 -2
  37. package/src/plugins/tui/src/index.tsx +866 -121
  38. package/src/routing.yml +179 -0
@@ -3,12 +3,33 @@
3
3
  * opencode.mjs — OpenCode platform installer
4
4
  */
5
5
 
6
- import { chmodSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
6
+ import {
7
+ chmodSync,
8
+ existsSync,
9
+ mkdirSync,
10
+ readdirSync,
11
+ readFileSync,
12
+ rmSync,
13
+ writeFileSync,
14
+ } from 'node:fs'
7
15
  import { homedir } from 'node:os'
8
16
  import { join, resolve } from 'node:path'
17
+ import {
18
+ bullet,
19
+ colors,
20
+ configure,
21
+ error,
22
+ info,
23
+ printSummary,
24
+ section,
25
+ spinner,
26
+ step,
27
+ success,
28
+ warning,
29
+ } from './cli-ui.mjs'
9
30
  import { healthCheck } from './health-check.mjs'
10
31
  import { detectVersion, runMigrations } from './migrate.mjs'
11
- import { colors, configure, section, step, success, warning, error, info, bullet, spinner, printSummary } from './cli-ui.mjs'
32
+ import { installPlugin, registerPlugin, unregisterPlugin } from './plugin.mjs'
12
33
  import {
13
34
  collectSkillNames,
14
35
  copyFiles,
@@ -21,10 +42,16 @@ import {
21
42
  writeIfChanged,
22
43
  } from './shared.mjs'
23
44
  import { setupVenv } from './venv.mjs'
24
- import { installPlugin, registerPlugin, unregisterPlugin } from './plugin.mjs'
25
- import { resolveComponents } from './component.mjs'
26
45
 
27
- const COMPONENT_NAMES = ['agents', 'skills', 'instructions', 'prompts', 'commands', 'plugins', 'runtime']
46
+ const COMPONENT_NAMES = [
47
+ 'agents',
48
+ 'skills',
49
+ 'instructions',
50
+ 'prompts',
51
+ 'commands',
52
+ 'plugins',
53
+ 'runtime',
54
+ ]
28
55
 
29
56
  function deepClone(value) {
30
57
  return JSON.parse(JSON.stringify(value))
@@ -63,7 +90,7 @@ function mergeMissing(target, source) {
63
90
  * Global installs use a flat layout: agents/ skills/ commands/ plugins/
64
91
  * Project installs use the .opencode/ sub-directory layout.
65
92
  */
66
- function isGlobalConfigDir(target) {
93
+ export function isGlobalConfigDir(target) {
67
94
  // Primary: ~/.opencode (actual OpenCode installation)
68
95
  const homeDir = resolve(join(homedir(), '.opencode'))
69
96
  if (resolve(target) === homeDir) return true
@@ -261,9 +288,12 @@ export async function installOpenCode(
261
288
  try {
262
289
  const cfg = JSON.parse(readFileSync(cfgPath, 'utf8'))
263
290
  let changed = false
264
- if (cfg.subagent_depth === undefined) { cfg.subagent_depth = 2; changed = true }
291
+ if (cfg.subagent_depth === undefined) {
292
+ cfg.subagent_depth = 2
293
+ changed = true
294
+ }
265
295
  if (changed && !dryRun) {
266
- writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n')
296
+ writeFileSync(cfgPath, `${JSON.stringify(cfg, null, 2)}\n`)
267
297
  success('subagent_depth: 2 added to opencode.json')
268
298
  }
269
299
  } catch (e) {
@@ -314,12 +344,16 @@ export async function installOpenCode(
314
344
  'scrub-secrets.py',
315
345
  '_pantheon_paths.py',
316
346
  'mcp_persistence_server.py',
347
+ 'pantheon_vision_server.py',
317
348
  ]
318
349
  const srcScriptsDir = join(ROOT, 'scripts')
350
+ const canonicalMcpScripts = {
351
+ 'pantheon_vision_server.py': join(ROOT, 'src', 'mcp', 'pantheon_vision_server.py'),
352
+ }
319
353
  const dstScriptsDir = join(runtimeTarget, 'scripts')
320
354
  if (!dryRun) mkdirSync(dstScriptsDir, { recursive: true })
321
355
  for (const script of mcpScripts) {
322
- const src = join(srcScriptsDir, script)
356
+ const src = canonicalMcpScripts[script] || join(srcScriptsDir, script)
323
357
  if (!existsSync(src)) {
324
358
  warning(`Script not found: ${src}`)
325
359
  stats.errors++
@@ -341,6 +375,18 @@ export async function installOpenCode(
341
375
  } else stats.skipped++
342
376
  }
343
377
 
378
+ // Keep Vision's dependency contract available to the installed runtime.
379
+ const visionRequirements = join(ROOT, 'src', 'mcp', 'requirements-vision.txt')
380
+ if (existsSync(visionRequirements)) {
381
+ const destination = join(runtimeTarget, 'requirements-vision.txt')
382
+ const status = writeIfChanged(destination, readFileSync(visionRequirements, 'utf8'), dryRun)
383
+ if (status === 'created') stats.created++
384
+ else stats.skipped++
385
+ } else {
386
+ warning(`Requirements file not found: ${visionRequirements}`)
387
+ stats.errors++
388
+ }
389
+
344
390
  // ── Code-mode scripts ──
345
391
  const srcCodeModeDir = join(ROOT, '.pantheon', 'code-mode')
346
392
  const dstCodeModeDir = join(runtimeTarget, '.pantheon', 'code-mode')
@@ -605,9 +651,13 @@ export async function installOpenCode(
605
651
  if (componentSet.has('runtime')) {
606
652
  config.mcp = config.mcp || {}
607
653
  const runtimeTarget = isGlobal ? target : join(target, '.opencode')
608
- const pythonPath = process.platform === 'win32' ? 'python' : 'python3'
609
- const venvPython = join(runtimeTarget, '.venv', 'bin', 'python3')
610
- const memoryPython = existsSync(venvPython) ? venvPython : pythonPath
654
+ const venvPython =
655
+ process.platform === 'win32'
656
+ ? join(runtimeTarget, '.venv', 'Scripts', 'python.exe')
657
+ : join(runtimeTarget, '.venv', 'bin', 'python3')
658
+ // Point the config at the venv even on a first install; setupVenv runs
659
+ // later in this function and creates this path before OpenCode starts.
660
+ const memoryPython = venvPython
611
661
 
612
662
  // Only add if not already configured by user
613
663
  if (!config.mcp['pantheon-resources']) {
@@ -642,6 +692,14 @@ export async function installOpenCode(
642
692
  enabled: true,
643
693
  }
644
694
  }
695
+ if (!config.mcp['pantheon-vision']) {
696
+ config.mcp['pantheon-vision'] = {
697
+ type: 'local',
698
+ cwd: runtimeTarget,
699
+ command: [memoryPython, 'scripts/pantheon_vision_server.py'],
700
+ enabled: true,
701
+ }
702
+ }
645
703
 
646
704
  // Default MCP permissions
647
705
  config.permission = config.permission || {}
@@ -658,6 +716,9 @@ export async function installOpenCode(
658
716
  if (!config.permission.mcp['pantheon-persistence']) {
659
717
  config.permission.mcp['pantheon-persistence'] = 'allow'
660
718
  }
719
+ if (!config.permission.mcp['pantheon-vision']) {
720
+ config.permission.mcp['pantheon-vision'] = 'ask'
721
+ }
661
722
  }
662
723
 
663
724
  const configContent = `${JSON.stringify(config, null, 2)}\n`
@@ -707,7 +768,7 @@ export async function installOpenCode(
707
768
  }
708
769
  } catch (err) {
709
770
  error(`Setup failed: ${err.message}`)
710
- throw err // Fatal — abort installation
771
+ throw err // Fatal — abort installation
711
772
  }
712
773
  }
713
774
 
@@ -718,18 +779,38 @@ export async function installOpenCode(
718
779
 
719
780
  process.stdout.write('\n')
720
781
  process.stdout.write(` ${colors.bold(colors.green('Installation complete'))}\n`)
721
- process.stdout.write(` ${colors.dim('\u2500'.repeat(Math.min(process.stdout.columns || 60, 60)))}\n`)
782
+ process.stdout.write(
783
+ ` ${colors.dim('\u2500'.repeat(Math.min(process.stdout.columns || 60, 60)))}\n`,
784
+ )
722
785
  process.stdout.write(` ${colors.green('\u2713')} ${created} component(s) installed\n`)
723
- if (skipped > 0) process.stdout.write(` ${colors.dim('\u2014')} ${skipped} already up-to-date\n`)
724
- if (errors > 0) process.stdout.write(` ${colors.yellow('\u26a0')} ${errors} error(s) encountered\n`)
786
+ if (skipped > 0)
787
+ process.stdout.write(` ${colors.dim('\u2014')} ${skipped} already up-to-date\n`)
788
+ if (errors > 0)
789
+ process.stdout.write(` ${colors.yellow('\u26a0')} ${errors} error(s) encountered\n`)
725
790
  process.stdout.write('\n')
726
791
  process.stdout.write(` ${colors.bold('Next steps:')}\n`)
727
792
  process.stdout.write(` ${colors.dim('\u2022')} Configure agents in opencode.json\n`)
728
793
  process.stdout.write(` ${colors.dim('\u2022')} Add MCP servers in mcp.json\n`)
729
794
  process.stdout.write(` ${colors.dim('\u2022')} Run 'opencode doctor' to verify\n`)
730
- process.stdout.write(` ${colors.dim('\u2022')} Run 'opencode' to start using Pantheon agents\n`)
795
+ process.stdout.write(
796
+ ` ${colors.dim('\u2022')} Run 'opencode' to start using Pantheon agents\n`,
797
+ )
731
798
  process.stdout.write('\n')
732
799
  } else {
733
800
  printSummary(target, ['opencode'], stats)
734
801
  }
802
+
803
+ // -----------------------------------------------------------------------
804
+ // 5. Model preset selection (--components agents). Interactive picker runs
805
+ // unless autoYes (use defaults) or an explicit --preset was given.
806
+ // -----------------------------------------------------------------------
807
+ if (interactive && !dryRun && !autoYes && !opts.preset) {
808
+ const { runModelPicker } = await import('./model-picker.mjs')
809
+ await runModelPicker({ presetDir: target, logger: console })
810
+ }
811
+
812
+ if (opts.preset && !dryRun) {
813
+ const { writeActivePreset } = await import('./model-picker.mjs')
814
+ writeActivePreset(target, opts.preset, { source: 'cli' })
815
+ }
735
816
  }
@@ -23,7 +23,7 @@
23
23
  import { execSync } from 'node:child_process'
24
24
  import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
25
25
  import { homedir } from 'node:os'
26
- import { dirname, join } from 'node:path'
26
+ import { dirname, join, resolve } from 'node:path'
27
27
  import { createInterface } from 'node:readline'
28
28
  import { fileURLToPath } from 'node:url'
29
29
 
@@ -171,11 +171,31 @@ const MCPS = {
171
171
  name: 'Pantheon Resources',
172
172
  description: 'Pantheon framework resources — agents, skills, routing, deepwork, memory bank',
173
173
  platforms: {
174
- opencode: { type: 'local', command: join(ROOT, '.venv/bin/python3'), args: [join(ROOT, 'scripts/mcp_resources_server.py')] },
175
- vscode: { type: 'stdio', command: join(ROOT, '.venv/bin/python3'), args: [join(ROOT, 'scripts/mcp_resources_server.py')] },
176
- cursor: { type: 'local', command: join(ROOT, '.venv/bin/python3'), args: [join(ROOT, 'scripts/mcp_resources_server.py')] },
177
- claude: { type: 'local', command: join(ROOT, '.venv/bin/python3'), args: [join(ROOT, 'scripts/mcp_resources_server.py')] },
178
- windsurf: { type: 'local', command: join(ROOT, '.venv/bin/python3'), args: [join(ROOT, 'scripts/mcp_resources_server.py')] },
174
+ opencode: {
175
+ type: 'local',
176
+ command: join(ROOT, '.venv/bin/python3'),
177
+ args: [join(ROOT, 'scripts/mcp_resources_server.py')],
178
+ },
179
+ vscode: {
180
+ type: 'stdio',
181
+ command: join(ROOT, '.venv/bin/python3'),
182
+ args: [join(ROOT, 'scripts/mcp_resources_server.py')],
183
+ },
184
+ cursor: {
185
+ type: 'local',
186
+ command: join(ROOT, '.venv/bin/python3'),
187
+ args: [join(ROOT, 'scripts/mcp_resources_server.py')],
188
+ },
189
+ claude: {
190
+ type: 'local',
191
+ command: join(ROOT, '.venv/bin/python3'),
192
+ args: [join(ROOT, 'scripts/mcp_resources_server.py')],
193
+ },
194
+ windsurf: {
195
+ type: 'local',
196
+ command: join(ROOT, '.venv/bin/python3'),
197
+ args: [join(ROOT, 'scripts/mcp_resources_server.py')],
198
+ },
179
199
  },
180
200
  env: [],
181
201
  validate: async () => {
@@ -191,11 +211,31 @@ const MCPS = {
191
211
  name: 'Pantheon Code Mode',
192
212
  description: 'Execute orchestration scripts from .pantheon/code-mode/ directory',
193
213
  platforms: {
194
- opencode: { type: 'local', command: join(ROOT, '.venv/bin/python3'), args: [join(ROOT, 'scripts/code_mode_server.py')] },
195
- vscode: { type: 'stdio', command: join(ROOT, '.venv/bin/python3'), args: [join(ROOT, 'scripts/code_mode_server.py')] },
196
- cursor: { type: 'local', command: join(ROOT, '.venv/bin/python3'), args: [join(ROOT, 'scripts/code_mode_server.py')] },
197
- claude: { type: 'local', command: join(ROOT, '.venv/bin/python3'), args: [join(ROOT, 'scripts/code_mode_server.py')] },
198
- windsurf: { type: 'local', command: join(ROOT, '.venv/bin/python3'), args: [join(ROOT, 'scripts/code_mode_server.py')] },
214
+ opencode: {
215
+ type: 'local',
216
+ command: join(ROOT, '.venv/bin/python3'),
217
+ args: [join(ROOT, 'scripts/code_mode_server.py')],
218
+ },
219
+ vscode: {
220
+ type: 'stdio',
221
+ command: join(ROOT, '.venv/bin/python3'),
222
+ args: [join(ROOT, 'scripts/code_mode_server.py')],
223
+ },
224
+ cursor: {
225
+ type: 'local',
226
+ command: join(ROOT, '.venv/bin/python3'),
227
+ args: [join(ROOT, 'scripts/code_mode_server.py')],
228
+ },
229
+ claude: {
230
+ type: 'local',
231
+ command: join(ROOT, '.venv/bin/python3'),
232
+ args: [join(ROOT, 'scripts/code_mode_server.py')],
233
+ },
234
+ windsurf: {
235
+ type: 'local',
236
+ command: join(ROOT, '.venv/bin/python3'),
237
+ args: [join(ROOT, 'scripts/code_mode_server.py')],
238
+ },
199
239
  },
200
240
  env: [],
201
241
  validate: async () => {
@@ -278,6 +318,49 @@ const MCPS = {
278
318
  return { ok: false, message: 'scripts/mcp_persistence_server.py not found' }
279
319
  },
280
320
  },
321
+ 'pantheon-vision': {
322
+ tier: 1,
323
+ name: 'Pantheon Vision',
324
+ description:
325
+ 'Image description, OCR, and structured analysis through the OpenCode vision gateway',
326
+ requirements: join(ROOT, 'src', 'mcp', 'requirements-vision.txt'),
327
+ platforms: {
328
+ opencode: {
329
+ type: 'local',
330
+ command: join(ROOT, '.venv/bin/python3'),
331
+ args: [join(ROOT, 'src', 'mcp', 'pantheon_vision_server.py')],
332
+ },
333
+ vscode: {
334
+ type: 'stdio',
335
+ command: join(ROOT, '.venv/bin/python3'),
336
+ args: [join(ROOT, 'src', 'mcp', 'pantheon_vision_server.py')],
337
+ },
338
+ claude: {
339
+ type: 'local',
340
+ command: join(ROOT, '.venv/bin/python3'),
341
+ args: [join(ROOT, 'src', 'mcp', 'pantheon_vision_server.py')],
342
+ },
343
+ cursor: {
344
+ type: 'local',
345
+ command: join(ROOT, '.venv/bin/python3'),
346
+ args: [join(ROOT, 'src', 'mcp', 'pantheon_vision_server.py')],
347
+ },
348
+ windsurf: {
349
+ type: 'local',
350
+ command: join(ROOT, '.venv/bin/python3'),
351
+ args: [join(ROOT, 'src', 'mcp', 'pantheon_vision_server.py')],
352
+ },
353
+ },
354
+ env: [],
355
+ validate: async () => {
356
+ const sourcePath = join(ROOT, 'src', 'mcp', 'pantheon_vision_server.py')
357
+ const requirementsPath = join(ROOT, 'src', 'mcp', 'requirements-vision.txt')
358
+ if (existsSync(sourcePath) && existsSync(requirementsPath)) {
359
+ return { ok: true, message: 'canonical server source and isolated requirements present' }
360
+ }
361
+ return { ok: false, message: 'canonical vision source or requirements not found' }
362
+ },
363
+ },
281
364
  docker: {
282
365
  tier: 2,
283
366
  name: 'Docker MCP',
@@ -790,7 +873,7 @@ function logValidation(mcpKey, result) {
790
873
  // Main
791
874
  // ---------------------------------------------------------------------------
792
875
 
793
- async function main() {
876
+ export async function main() {
794
877
  const args = parseArgs(process.argv)
795
878
 
796
879
  if (args.help) {
@@ -918,4 +1001,6 @@ async function main() {
918
1001
  process.exit(progress.errors.length > 0 ? 1 : 0)
919
1002
  }
920
1003
 
921
- main()
1004
+ if (process.argv[1] && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
1005
+ main()
1006
+ }