pantheon-opencode 1.4.2 → 1.5.0-beta.1

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 (98) hide show
  1. package/.pantheon/code-mode/.gitkeep +0 -0
  2. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-1.json +8 -0
  3. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-10.json +8 -0
  4. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-11.json +8 -0
  5. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-2.json +8 -0
  6. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-3.json +8 -0
  7. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-4.json +8 -0
  8. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-5.json +8 -0
  9. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-6.json +8 -0
  10. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-7.json +8 -0
  11. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-8.json +8 -0
  12. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/checkpoint-9.json +8 -0
  13. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/heartbeat.json +8 -0
  14. package/.pantheon/code-mode/.pantheon/deepwork/initial-token-40k/session.json +11 -0
  15. package/.pantheon/code-mode/README.md +93 -0
  16. package/.pantheon/code-mode/checkpoint-session.py +7 -0
  17. package/.pantheon/code-mode/checkpoint-session.sh +11 -0
  18. package/.pantheon/code-mode/checkpoint_session.py +481 -0
  19. package/.pantheon/code-mode/compress-inline.py +450 -0
  20. package/.pantheon/code-mode/eval-llm-judge.py +255 -0
  21. package/.pantheon/code-mode/eval-monte-carlo.py +368 -0
  22. package/.pantheon/code-mode/eval-run.py +197 -0
  23. package/.pantheon/code-mode/eval-static.py +321 -0
  24. package/.pantheon/code-mode/example-sync.sh +5 -0
  25. package/.pantheon/code-mode/session-end-save.py +186 -0
  26. package/.pantheon/code-mode/session-end-save.sh +18 -0
  27. package/.pantheon/code-mode/test-allowed.sh +5 -0
  28. package/.pantheon/code-mode/test-auto-continue-checkpoint.sh +179 -0
  29. package/.pantheon/code-mode/test-env.sh +7 -0
  30. package/.pantheon/code-mode/test-prlimit.sh +9 -0
  31. package/.pantheon/code-mode/test-timeout.sh +6 -0
  32. package/README.md +158 -1189
  33. package/README.pt-BR.md +222 -0
  34. package/bin/pantheon-init.mjs +30 -11
  35. package/opencode.json +1 -0
  36. package/package.json +14 -3
  37. package/plugin.json +15 -0
  38. package/scripts/_pantheon_paths.py +48 -7
  39. package/scripts/ci-validate-yaml.py +7 -2
  40. package/scripts/code_mode_server.py +33 -2
  41. package/scripts/doctor.mjs +135 -127
  42. package/scripts/generate-preset-docs.mjs +127 -66
  43. package/scripts/generate-prompts.sh +2 -2
  44. package/scripts/install/cli-ui.mjs +8 -1
  45. package/scripts/install/config-migration.mjs +401 -0
  46. package/scripts/install/health-check.mjs +3 -3
  47. package/scripts/install/model-picker.mjs +72 -23
  48. package/scripts/install/opencode-version.mjs +47 -0
  49. package/scripts/install/opencode.mjs +261 -88
  50. package/scripts/install/plugin.mjs +112 -42
  51. package/scripts/install/shared.mjs +13 -158
  52. package/scripts/install-mcp.mjs +44 -232
  53. package/scripts/mcp_persistence_server.py +344 -15
  54. package/scripts/mcp_resources_server.py +144 -40
  55. package/scripts/memory_mcp_server.py +2 -2
  56. package/scripts/recover-zenodo-deposition.mjs +146 -0
  57. package/scripts/sync-tui.mjs +32 -7
  58. package/scripts/test-opencode-v1-v2-sandbox.sh +591 -0
  59. package/scripts/uninstall.mjs +298 -954
  60. package/scripts/validate-zenodo-release.mjs +154 -0
  61. package/scripts/zenodo-artifact.mjs +21 -0
  62. package/scripts/zenodo-release-state.mjs +53 -0
  63. package/src/mcp/_pantheon_paths.py +48 -7
  64. package/src/mcp/code_mode_server.py +33 -2
  65. package/src/mcp/eval_store.py +1 -1
  66. package/src/mcp/mcp_persistence_server.py +344 -15
  67. package/src/mcp/mcp_resources_server.py +144 -40
  68. package/src/mcp/pantheon_vision_server.py +1 -1
  69. package/src/mcp/toon_codec.py +491 -0
  70. package/src/pantheon/background-job-board.ts +67 -1
  71. package/src/pantheon/cost-command.ts +6 -2
  72. package/src/pantheon/delegate-manager.ts +384 -0
  73. package/src/pantheon/delegate-task-adapter.ts +444 -0
  74. package/src/pantheon/delegation-notify.ts +35 -20
  75. package/src/pantheon/model-command.ts +86 -32
  76. package/src/pantheon/token-opt.ts +555 -0
  77. package/src/pantheon/toon-codec.ts +481 -0
  78. package/src/pantheon/tui-version-check.ts +1 -3
  79. package/src/pantheon/v2-bridge.ts +129 -0
  80. package/src/pantheon/v2-events.ts +131 -0
  81. package/src/pantheon/v2-hooks.ts +287 -0
  82. package/src/pantheon/v2-tool-definitions.ts +331 -0
  83. package/src/plugin-v2/index.ts +21 -0
  84. package/src/plugin-v2.ts +836 -0
  85. package/src/plugin.ts +57 -10
  86. package/src/plugins/tui/dist/server.d.ts +1 -1
  87. package/src/plugins/tui/dist/server.d.ts.map +1 -1
  88. package/src/plugins/tui/dist/server.js +1 -1
  89. package/src/plugins/tui/dist/server.js.map +1 -1
  90. package/src/plugins/tui/dist/tui.d.ts +10 -7
  91. package/src/plugins/tui/dist/tui.d.ts.map +1 -1
  92. package/src/plugins/tui/dist/tui.js +11 -8
  93. package/src/plugins/tui/dist/tui.js.map +1 -1
  94. package/src/plugins/tui/dist/tui.tsx +22 -16
  95. package/src/plugins/tui/package-lock.json +51 -51
  96. package/src/plugins/tui/package.json +7 -7
  97. package/src/plugins/tui/src/index.tsx +22 -16
  98. package/src/skills/README.md +0 -2
@@ -4,13 +4,13 @@
4
4
 
5
5
  import { execSync } from 'node:child_process'
6
6
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync } from 'node:fs'
7
- import { join } from 'node:path'
7
+ import { isAbsolute, join, resolve } from 'node:path'
8
8
  import { warning } from './cli-ui.mjs'
9
- import { writeIfChanged } from './shared.mjs'
9
+ import { ROOT, writeIfChanged } from './shared.mjs'
10
10
 
11
11
  /**
12
12
  * Copy a plugin's runtime payload (src/index.tsx → index.tsx, dist/*,
13
- * package.json) from source to destination. Pure file copy — no npm install.
13
+ * package.json and package-lock.json) from source to destination.
14
14
  * @param {string} srcDir - Source plugin directory (e.g. ROOT/src/plugins/tui)
15
15
  * @param {string} dstDir - Destination plugin directory
16
16
  * @param {object} [options]
@@ -58,6 +58,19 @@ export function copyPluginFiles(srcDir, dstDir, { dryRun = false } = {}) {
58
58
  else result.skipped++
59
59
  }
60
60
 
61
+ // Copy the lockfile so dependency installation is reproducible. A missing
62
+ // lockfile is intentionally not synthesized by the installer.
63
+ const lockSrc = join(srcDir, 'package-lock.json')
64
+ if (existsSync(lockSrc)) {
65
+ const lockStatus = writeIfChanged(
66
+ join(dstDir, 'package-lock.json'),
67
+ readFileSync(lockSrc, 'utf8'),
68
+ dryRun,
69
+ )
70
+ if (lockStatus === 'created') result.created++
71
+ else result.skipped++
72
+ }
73
+
61
74
  return result
62
75
  }
63
76
 
@@ -81,13 +94,31 @@ export function installPlugin(srcDir, dstDir, { dryRun = false, clean = false }
81
94
  result.created += copyResult.created
82
95
  result.skipped += copyResult.skipped
83
96
 
84
- // Install plugin dependencies (@opentui/core, @opentui/solid, solid-js)
97
+ // Install plugin dependencies (@opentui/core, @opentui/solid, solid-js).
98
+ // npm ci is deterministic when the copied lockfile is present. The legacy
99
+ // fallback is opt-in because npm install may rewrite the dependency graph.
85
100
  if (!dryRun) {
86
101
  try {
87
- execSync('npm install --omit=dev --no-audit', { cwd: dstDir, stdio: 'pipe' })
102
+ execSync('npm ci --omit=dev --no-audit --no-fund', { cwd: dstDir, stdio: 'pipe' })
88
103
  } catch (e) {
89
- warning(`Failed to install TUI plugin dependencies: ${e.message}`)
90
- result.errors++
104
+ if (process.env.PANTHEON_ALLOW_NPM_INSTALL_FALLBACK === '1') {
105
+ warning(`npm ci failed; explicit fallback enabled: ${e.message}`)
106
+ try {
107
+ execSync('npm install --omit=dev --no-audit --no-fund --package-lock=false', {
108
+ cwd: dstDir,
109
+ stdio: 'pipe',
110
+ })
111
+ } catch (fallbackError) {
112
+ warning(`Failed to install TUI plugin dependencies: ${fallbackError.message}`)
113
+ result.errors++
114
+ }
115
+ } else {
116
+ warning(
117
+ `npm ci failed; dependencies were not changed. Set ` +
118
+ `PANTHEON_ALLOW_NPM_INSTALL_FALLBACK=1 to explicitly allow npm install: ${e.message}`,
119
+ )
120
+ result.errors++
121
+ }
91
122
  }
92
123
  }
93
124
 
@@ -103,9 +134,12 @@ export function installPlugin(srcDir, dstDir, { dryRun = false, clean = false }
103
134
  export function installPluginDependencies(pluginDir, { dryRun = false } = {}) {
104
135
  if (dryRun) return
105
136
  try {
106
- execSync('npm install --omit=dev --no-audit', { cwd: pluginDir, stdio: 'pipe' })
137
+ execSync('npm ci --omit=dev --no-audit --no-fund', { cwd: pluginDir, stdio: 'pipe' })
107
138
  } catch (e) {
108
- warning(`Failed to install plugin dependencies: ${e.message}`)
139
+ warning(
140
+ `npm ci failed; dependencies were not changed. Set ` +
141
+ `PANTHEON_ALLOW_NPM_INSTALL_FALLBACK=1 to explicitly allow npm install: ${e.message}`,
142
+ )
109
143
  }
110
144
  }
111
145
 
@@ -137,42 +171,70 @@ export function registerPlugin(tuiJsonPath, pluginId, { dryRun = false } = {}) {
137
171
  }
138
172
 
139
173
  /**
140
- * Suffix patterns that identify a Pantheon TUI registration as STALE. Every
141
- * reference shape ever written by previous installers must be covered:
142
- * - dist file refs (both the old in-package src/plugins/tui target and the
143
- * copied plugins/pantheon-tui target, tsx and js eras)
144
- * - the in-package source dir (src/plugins/tui) — a registration pointing
145
- * into the installed package breaks on reinstall/upgrade (the dir is
146
- * replaced under a live registration)
147
- * - bare copied dirs (plugins/pantheon-tui) from any OLD install target —
148
- * indistinguishable from the current copied dir without knowing the
149
- * install target, so they are flagged too; syncTuiRegistration keeps the
150
- * current ref via unregister-then-register
151
- * - node_modules copies (npx cache installs of the vendored plugin)
174
+ * Exact relative markers written by previous installers. These are intentionally
175
+ * complete references, not suffixes: a user-owned path may contain
176
+ * `pantheon-opencode` or end in the same filename without being ours.
152
177
  */
153
178
  export const TUI_STALE_SUFFIXES = [
154
- '/src/plugins/tui/dist/tui.tsx',
155
- '/src/plugins/tui/dist/tui.js',
156
- '/src/plugins/tui',
157
- '/plugins/pantheon-tui/dist/tui.tsx',
158
- '/plugins/pantheon-tui/dist/tui.js',
159
- '/plugins/pantheon-tui',
160
- '/node_modules/pantheon-tui',
179
+ 'plugins/pantheon-tui',
180
+ 'plugins/pantheon-tui/dist/tui.tsx',
181
+ 'plugins/pantheon-tui/dist/tui.js',
182
+ 'npx pantheon-tui',
183
+ 'npx -y pantheon-tui',
184
+ 'npx:pantheon-tui',
161
185
  ]
162
186
 
187
+ const PACKAGE_TUI_PATHS = [
188
+ join(ROOT, 'src', 'plugins', 'tui'),
189
+ join(ROOT, 'src', 'plugins', 'tui', 'dist', 'tui.tsx'),
190
+ join(ROOT, 'src', 'plugins', 'tui', 'dist', 'tui.js'),
191
+ ]
192
+
193
+ function normalizePluginPath(ref) {
194
+ return ref.replaceAll('\\', '/')
195
+ }
196
+
197
+ function exactManagedPaths(knownPantheonPaths) {
198
+ return new Set(
199
+ [...PACKAGE_TUI_PATHS, ...knownPantheonPaths]
200
+ .filter((path) => typeof path === 'string')
201
+ .map((path) => normalizePluginPath(path)),
202
+ )
203
+ }
204
+
205
+ function isExactNpxMarker(ref) {
206
+ return (
207
+ /^npx(?:\s+-y)?\s+pantheon-tui(?:@[A-Za-z0-9._-]+)?$/i.test(ref) ||
208
+ /^npx:pantheon-tui(?:@[A-Za-z0-9._-]+)?$/i.test(ref) ||
209
+ /(?:^|\/)\.npm\/_npx\/[^/]+\/node_modules\/pantheon-tui(?:\/|$)/i.test(ref)
210
+ )
211
+ }
212
+
163
213
  /**
164
- * Whether a single tui.json plugin entry is a stale Pantheon TUI reference.
214
+ * Return whether a registration is unambiguously one of our old TUI refs.
215
+ *
165
216
  * @param {unknown} ref - tui.json plugin entry
217
+ * @param {string[]} [knownPantheonPaths=[]] - paths owned by this installer
166
218
  * @returns {boolean}
167
219
  */
168
- export function isStaleTuiRef(ref) {
220
+ export function isPantheonTuiRef(ref, knownPantheonPaths = []) {
169
221
  if (typeof ref !== 'string') return false
170
- if (ref === 'plugins/pantheon-tui') return true
171
- const normalized = ref.replaceAll('\\', '/')
172
- // npx spec strings ("npx pantheon-tui", "npx -y pantheon-tui",
173
- // "npx:pantheon-tui") and npx cache paths end with /node_modules/pantheon-tui.
174
- if (/^npx[\s:]/i.test(normalized) && normalized.includes('pantheon-tui')) return true
175
- return TUI_STALE_SUFFIXES.some((suffix) => normalized.endsWith(suffix))
222
+ const normalized = normalizePluginPath(ref)
223
+ if (TUI_STALE_SUFFIXES.includes(normalized)) return true
224
+ if (isExactNpxMarker(normalized)) return true
225
+
226
+ const knownPaths = exactManagedPaths(knownPantheonPaths)
227
+ if (knownPaths.has(normalized)) return true
228
+ return isAbsolute(normalized) && knownPaths.has(normalizePluginPath(resolve(normalized)))
229
+ }
230
+
231
+ /**
232
+ * Whether a single tui.json plugin entry is a stale Pantheon TUI reference.
233
+ * @param {unknown} ref - tui.json plugin entry
234
+ * @returns {boolean}
235
+ */
236
+ export function isStaleTuiRef(ref, knownPantheonPaths = []) {
237
+ return isPantheonTuiRef(ref, knownPantheonPaths)
176
238
  }
177
239
 
178
240
  /**
@@ -181,8 +243,10 @@ export function isStaleTuiRef(ref) {
181
243
  * @param {unknown[]} pluginArray - tui.json plugin array
182
244
  * @returns {string[]}
183
245
  */
184
- export function staleTuiRefs(pluginArray) {
185
- return (Array.isArray(pluginArray) ? pluginArray : []).filter(isStaleTuiRef)
246
+ export function staleTuiRefs(pluginArray, knownPantheonPaths = []) {
247
+ return (Array.isArray(pluginArray) ? pluginArray : []).filter((ref) =>
248
+ isStaleTuiRef(ref, knownPantheonPaths),
249
+ )
186
250
  }
187
251
 
188
252
  /**
@@ -193,7 +257,11 @@ export function staleTuiRefs(pluginArray) {
193
257
  * @param {boolean} [options.dryRun=false]
194
258
  * @returns {'created'|'skipped'}
195
259
  */
196
- export function unregisterPlugin(tuiJsonPath, pluginId, { dryRun = false } = {}) {
260
+ export function unregisterPlugin(
261
+ tuiJsonPath,
262
+ pluginId,
263
+ { dryRun = false, knownPantheonPaths = [] } = {},
264
+ ) {
197
265
  if (!existsSync(tuiJsonPath)) {
198
266
  // Nothing to clean — and never create an empty tui.json in a location
199
267
  // OpenCode reads (the cross-location cleanup also touches global config
@@ -215,10 +283,12 @@ export function unregisterPlugin(tuiJsonPath, pluginId, { dryRun = false } = {})
215
283
  // bare copy dirs from previous install targets, and npx cache copies.
216
284
  // Without removing the absolute paths, each init run accumulates another
217
285
  // TUI plugin and OpenCode loads stale copies.
218
- const exactStale = [pluginId, `${pluginId}/dist/tui.tsx`, `${pluginId}/dist/tui.js`]
286
+ const exactStale = new Set(
287
+ [pluginId, `${pluginId}/dist/tui.tsx`, `${pluginId}/dist/tui.js`].map(normalizePluginPath),
288
+ )
219
289
  tuiConfig.plugin = tuiConfig.plugin.filter((ref) => {
220
- if (exactStale.includes(ref)) return false
221
- return !isStaleTuiRef(ref)
290
+ if (typeof ref === 'string' && exactStale.has(normalizePluginPath(ref))) return false
291
+ return !isPantheonTuiRef(ref, knownPantheonPaths)
222
292
  })
223
293
  const tuiContent = `${JSON.stringify(tuiConfig, null, 2)}\n`
224
294
  return writeIfChanged(tuiJsonPath, tuiContent, dryRun)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * shared.mjs — Shared utilities for Pantheon platform installers
2
+ * shared.mjs — Shared utilities for the OpenCode installer
3
3
  */
4
4
 
5
5
  import {
@@ -26,8 +26,6 @@ import yaml from 'js-yaml'
26
26
  export const __dirname = dirname(fileURLToPath(import.meta.url))
27
27
  export const ROOT = join(__dirname, '..', '..')
28
28
  export const AGENTS_DIR = join(ROOT, 'src', 'agents')
29
- // PLATFORM_DIR is deprecated — platform/ was removed in v1.0
30
- export const PLATFORM_DIR = join(ROOT, 'platform')
31
29
 
32
30
  // Auto-detect agent names from agents/ directory
33
31
  export function getAgentNames() {
@@ -43,33 +41,7 @@ export function getAgentNames() {
43
41
  // Cached constant for backward compatibility
44
42
  export const AGENT_NAMES = getAgentNames()
45
43
 
46
- // DEPRECATED: OpenCode-only since v1.0
47
- export const PLATFORM_DETECTORS = {
48
- opencode: (target) =>
49
- existsSync(join(target, 'opencode.json')) ||
50
- existsSync(join(target, 'platform', 'opencode', 'opencode.json')),
51
- claude: (target) => existsSync(join(target, '.claude')) || existsSync(join(target, 'CLAUDE.md')),
52
- cursor: (target) =>
53
- existsSync(join(target, '.cursor')) || existsSync(join(target, '.cursorrules')),
54
- windsurf: (target) =>
55
- existsSync(join(target, '.windsurf')) || existsSync(join(target, '.windsurfrules')),
56
- copilot: (target) =>
57
- existsSync(join(target, '.github', 'copilot-instructions.md')) ||
58
- existsSync(join(target, '.vscode')),
59
- continue: (target) =>
60
- existsSync(join(target, '.continue', 'config.yaml')) || existsSync(join(target, '.continue')),
61
- cline: (target) => existsSync(join(target, '.clinerules')),
62
- }
63
-
64
- export const summary = {
65
- opencode: { created: 0, skipped: 0, errors: 0 },
66
- claude: { created: 0, skipped: 0, errors: 0 },
67
- cursor: { created: 0, skipped: 0, errors: 0 },
68
- windsurf: { created: 0, skipped: 0, errors: 0 },
69
- copilot: { created: 0, skipped: 0, errors: 0 },
70
- continue: { created: 0, skipped: 0, errors: 0 },
71
- cline: { created: 0, skipped: 0, errors: 0 },
72
- }
44
+ export const summary = { opencode: { created: 0, skipped: 0, errors: 0 } }
73
45
 
74
46
  export function showHelp() {
75
47
  console.log(`
@@ -78,9 +50,7 @@ pantheon-init.mjs — Pantheon OpenCode installer
78
50
  Usage:
79
51
  npx pantheon-opencode init auto-detect, cwd
80
52
  npx pantheon-opencode init --project auto-detect, project
81
- npx pantheon-opencode init --project --platforms opencode,claude specific platforms
82
- npx pantheon-opencode init --project --platforms all all platforms
83
- npx pantheon-opencode init --detect detect platforms without installing
53
+ npx pantheon-opencode init --project install in a project
84
54
  npx pantheon-opencode init --project --dry-run preview without writing
85
55
  npx pantheon-opencode init --project --backup create timestamped backup before writing
86
56
  npx pantheon-opencode init --project --clean wipe + fresh install (all components)
@@ -101,19 +71,8 @@ Clean mode (--clean):
101
71
  re-installs fresh from source. Useful after removing/renaming agents or skills.
102
72
  OFF by default — without --clean only copies new/changed files (never deletes).
103
73
 
104
- Platforms:
74
+ Platform:
105
75
  opencode → .opencode/agents/ + opencode.json
106
- claude → .claude/agents/ + CLAUDE.md + settings.json
107
- cursor → .cursor/rules/ (renamed .mdc files)
108
- windsurf → .windsurf/agents/ + .windsurfrules
109
- copilot → .github/agents/ symlinks + .vscode/settings.json check
110
- continue → .continue/rules/ + .continue/config.yaml
111
- cline → .clinerules/ (no extension, plain markdown)
112
- all → install every platform
113
-
114
- When --platforms is omitted, the script auto-detects which platforms
115
- the target project already supports (based on config files present).
116
- If none are detected, ALL platforms are installed.
117
76
  `)
118
77
  }
119
78
 
@@ -206,63 +165,25 @@ export function createBackup(target) {
206
165
  }
207
166
 
208
167
  export function detectAndReport(target) {
209
- const PLATFORM_LABELS = {
210
- opencode: 'OpenCode',
211
- claude: 'Claude Code',
212
- cursor: 'Cursor',
213
- windsurf: 'Windsurf',
214
- copilot: 'VS Code / Copilot',
215
- continue: 'Continue.dev',
216
- cline: 'Cline',
217
- }
218
-
219
- const CONFIG_FILES = {
220
- opencode: () => 'opencode.json',
221
- claude: () => '.claude/ or CLAUDE.md',
222
- cursor: () => '.cursor/ or .cursorrules',
223
- windsurf: () => '.windsurf/ or .windsurfrules',
224
- copilot: () => '.github/copilot-instructions.md or .vscode/',
225
- continue: () => '.continue/config.yaml or .continue/',
226
- cline: () => '.clinerules',
227
- }
168
+ const found = detectPlatforms(target).length > 0
228
169
 
229
170
  console.log(`\n 🔍 Pantheon Platform Detection`)
230
171
  console.log(` Target: ${target}\n`)
231
172
 
232
- const results = []
233
- for (const [platform, detector] of Object.entries(PLATFORM_DETECTORS)) {
234
- const found = detector(target)
235
- results.push({ platform, found })
236
- }
237
-
238
- // Table header
239
173
  console.log(` ${'Platform'.padEnd(12)} ${'Detected?'.padEnd(12)} Config file`)
240
174
  console.log(` ${'─'.repeat(55)}`)
241
-
242
- const detected = results.filter((r) => r.found)
243
- for (const r of results) {
244
- const status = r.found ? '✅ YES' : '❌ no'
245
- const configFile = r.found ? CONFIG_FILES[r.platform]() : '(not found)'
246
- console.log(` ${PLATFORM_LABELS[r.platform].padEnd(12)} ${status.padEnd(12)} ${configFile}`)
175
+ const status = (found ? '✅ YES' : '❌ no').padEnd(12)
176
+ console.log(` ${'OpenCode'.padEnd(12)} ${status} ${found ? 'opencode.json' : '(not found)'}`)
177
+ console.log(`\n 📊 ${found ? 1 : 0} of 1 platforms detected.`)
178
+ if (found) {
179
+ console.log(' → Install with: npx pantheon-opencode init')
247
180
  }
248
181
 
249
- console.log(`\n 📊 ${detected.length} of ${results.length} platforms detected.`)
250
- if (detected.length > 0) {
251
- const names = detected.map((r) => r.platform).join(',')
252
- console.log(` → Install with: npx pantheon-opencode init --platforms ${names}`)
253
- }
254
-
255
- return detected.map((r) => r.platform)
182
+ return found ? ['opencode'] : []
256
183
  }
257
184
 
258
185
  export function detectPlatforms(target) {
259
- const detected = []
260
- for (const [platform, detector] of Object.entries(PLATFORM_DETECTORS)) {
261
- if (detector(target)) {
262
- detected.push(platform)
263
- }
264
- }
265
- return detected
186
+ return existsSync(join(target, 'opencode.json')) ? ['opencode'] : []
266
187
  }
267
188
 
268
189
  export function sourceDirValid(dir) {
@@ -569,16 +490,6 @@ export function validateMcpServers(mcpServers, _agentTools = []) {
569
490
  }
570
491
 
571
492
  export function printSummary(target, platforms) {
572
- const PLATFORM_LABELS = {
573
- opencode: 'OpenCode',
574
- claude: 'Claude Code',
575
- cursor: 'Cursor',
576
- windsurf: 'Windsurf',
577
- copilot: 'VS Code / Copilot',
578
- continue: 'Continue.dev',
579
- cline: 'Cline',
580
- }
581
-
582
493
  console.log('')
583
494
  console.log('='.repeat(60))
584
495
  console.log('📋 Installation Summary')
@@ -590,7 +501,7 @@ export function printSummary(target, platforms) {
590
501
  let totalErrors = 0
591
502
 
592
503
  for (const platform of platforms) {
593
- const label = PLATFORM_LABELS[platform] ?? platform
504
+ const label = platform === 'opencode' ? 'OpenCode' : platform
594
505
  const stats = summary[platform]
595
506
  totalCreated += stats.created
596
507
  totalSkipped += stats.skipped
@@ -624,62 +535,6 @@ export function printSummary(target, platforms) {
624
535
  console.log('')
625
536
  }
626
537
 
627
- if (platforms.includes('claude')) {
628
- console.log(' Claude Code:')
629
- console.log(` - Run \`claude\` in ${target}`)
630
- console.log(' - Agents in .claude/agents/')
631
- console.log(' - Skills in .claude/skills/')
632
- console.log(' - Settings in .claude/settings.json')
633
- console.log('')
634
- }
635
-
636
- if (platforms.includes('cursor')) {
637
- console.log(' Cursor:')
638
- console.log(` - Rules in .cursor/rules/ (.mdc format)`)
639
- console.log(' - Skills in .cursor/skills/')
640
- console.log(' - AGENTS.md in project root')
641
- console.log(' - Use @agent-name in Agent mode')
642
- console.log('')
643
- }
644
-
645
- if (platforms.includes('windsurf')) {
646
- console.log(' Windsurf (Cascade):')
647
- console.log(` - Rules in .windsurf/rules/`)
648
- console.log(' - Skills in .windsurf/skills/')
649
- console.log(' - Workflows in .windsurf/workflows/ (/orchestrate, /code-review)')
650
- console.log(' - AGENTS.md in project root')
651
- console.log('')
652
- }
653
-
654
- if (platforms.includes('copilot')) {
655
- console.log(' VS Code / Copilot:')
656
- console.log(` - Agents in .github/agents/`)
657
- console.log(' - Ensure .vscode/settings.json has plugin config')
658
- console.log(' - Use @agent-name in VS Code Copilot Chat')
659
- console.log(' - Plugin manifest in plugin.json')
660
- console.log('')
661
- }
662
-
663
- if (platforms.includes('continue')) {
664
- console.log(' Continue.dev:')
665
- console.log(` - Rules in .continue/rules/`)
666
- console.log(' - Config in .continue/config.yaml')
667
- console.log(' - Rules are injected into system prompts (no @name invocation)')
668
- console.log(' - Edit config.yaml to set API keys and models')
669
- console.log(' - Use /reload to apply config changes')
670
- console.log('')
671
- }
672
-
673
- if (platforms.includes('cline')) {
674
- console.log(' Cline:')
675
- console.log(` - Agent rules in .clinerules/ (no extension, plain markdown)`)
676
- console.log(' - Skills in .clinerules/skills/')
677
- console.log(' - Commands in .clinerules/commands/')
678
- console.log(' - AGENTS.md in project root')
679
- console.log(' - Invoke agents with @agent-name in Cline chat')
680
- console.log('')
681
- }
682
-
683
538
  console.log(' 📚 Full documentation: https://github.com/ils15/pantheon')
684
539
  console.log(' 🐛 Report issues: https://github.com/ils15/pantheon/issues')
685
540
  }