pantheon-opencode 1.4.3 → 1.5.0-beta.10
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/.pantheon/code-mode/README.md +93 -0
- package/.pantheon/code-mode/checkpoint-session.py +7 -0
- package/.pantheon/code-mode/checkpoint-session.sh +11 -0
- package/.pantheon/code-mode/checkpoint_session.py +481 -0
- package/.pantheon/code-mode/compress-inline.py +450 -0
- package/.pantheon/code-mode/eval-llm-judge.py +255 -0
- package/.pantheon/code-mode/eval-monte-carlo.py +368 -0
- package/.pantheon/code-mode/eval-run.py +197 -0
- package/.pantheon/code-mode/eval-static.py +321 -0
- package/.pantheon/code-mode/example-sync.sh +5 -0
- package/.pantheon/code-mode/manifest.json +21 -0
- package/.pantheon/code-mode/session-end-save.py +186 -0
- package/.pantheon/code-mode/session-end-save.sh +18 -0
- package/.pantheon/code-mode/test-allowed.sh +5 -0
- package/.pantheon/code-mode/test-auto-continue-checkpoint.sh +179 -0
- package/.pantheon/code-mode/test-env.sh +7 -0
- package/.pantheon/code-mode/test-prlimit.sh +9 -0
- package/.pantheon/code-mode/test-timeout.sh +6 -0
- package/CHANGELOG.md +1026 -0
- package/README.md +292 -1080
- package/README.pt-BR.md +308 -0
- package/bin/pantheon-init.mjs +207 -31
- package/opencode.json +1 -0
- package/package.json +34 -14
- package/plugin.json +15 -0
- package/pyproject.toml +65 -0
- package/scripts/_pantheon_paths.py +48 -7
- package/scripts/ci-validate-yaml.py +7 -2
- package/scripts/code_mode_server.py +451 -44
- package/scripts/cost.mjs +30 -14
- package/scripts/doctor.mjs +306 -129
- package/scripts/generate-preset-docs.mjs +127 -66
- package/scripts/generate-prompts.sh +2 -2
- package/scripts/install/cli-ui.mjs +23 -10
- package/scripts/install/config-migration.mjs +399 -0
- package/scripts/install/health-check.mjs +3 -3
- package/scripts/install/model-picker.mjs +72 -23
- package/scripts/install/opencode-version.mjs +47 -0
- package/scripts/install/opencode.mjs +396 -121
- package/scripts/install/plugin.mjs +89 -49
- package/scripts/install/shared.mjs +55 -159
- package/scripts/install/strings.mjs +129 -0
- package/scripts/install-mcp.mjs +45 -232
- package/scripts/manifest-inventory.mjs +410 -0
- package/scripts/mcp_persistence_server.py +798 -60
- package/scripts/mcp_resources_server.py +144 -40
- package/scripts/memory_mcp_server.py +2 -2
- package/scripts/package-evidence.mjs +242 -0
- package/scripts/probe-context-rehydrate.mjs +240 -0
- package/scripts/probe-pantheon-cost.mjs +313 -0
- package/scripts/recover-zenodo-deposition.mjs +146 -0
- package/scripts/release-beta-version.mjs +56 -60
- package/scripts/sync-artifacts.mjs +169 -0
- package/scripts/sync-tui.mjs +135 -42
- package/scripts/test-opencode-v1-v2-sandbox.sh +862 -0
- package/scripts/uninstall.mjs +298 -954
- package/scripts/validate-zenodo-release.mjs +154 -0
- package/scripts/validation-policy.mjs +7 -7
- package/scripts/version-check.mjs +113 -108
- package/scripts/versioning.mjs +230 -86
- package/scripts/zenodo-artifact.mjs +21 -0
- package/scripts/zenodo-release-state.mjs +53 -0
- package/src/agents/mnemosyne.md +1 -1
- package/src/agents/zeus.md +27 -16
- package/src/mcp/_pantheon_paths.py +48 -7
- package/src/mcp/code_mode_server.py +451 -44
- package/src/mcp/eval_store.py +1 -1
- package/src/mcp/mcp_persistence_server.py +798 -60
- package/src/mcp/mcp_resources_server.py +144 -40
- package/src/mcp/pantheon_vision_server.py +1 -1
- package/src/mcp/toon_codec.py +491 -0
- package/src/pantheon/auto-wake.ts +1 -5
- package/src/pantheon/background-job-board.ts +110 -2
- package/src/pantheon/compaction-handoff.ts +304 -0
- package/src/pantheon/cost-command.ts +356 -69
- package/src/pantheon/delegate-manager.ts +766 -0
- package/src/pantheon/delegation-classifier.ts +307 -0
- package/src/pantheon/delegation-enforce.ts +13 -12
- package/src/pantheon/delegation-finalize.ts +406 -0
- package/src/pantheon/delegation-notify.ts +212 -0
- package/src/pantheon/delegation-toast.ts +73 -0
- package/src/pantheon/delegation.ts +1337 -0
- package/src/pantheon/file-persistence-salvage.ts +64 -0
- package/src/pantheon/file-persistence.ts +194 -45
- package/src/pantheon/goal-loop.ts +2 -2
- package/src/pantheon/goal-store.ts +1 -1
- package/src/pantheon/hashline/tool.ts +2 -2
- package/src/pantheon/model-command.ts +2 -1
- package/src/pantheon/native-probe.ts +234 -0
- package/src/pantheon/native-task-status.ts +139 -0
- package/src/pantheon/permission-eval.ts +284 -0
- package/src/pantheon/provider-capabilities.ts +221 -0
- package/src/pantheon/provider-stream-tracker.ts +182 -0
- package/src/pantheon/shared-board.ts +37 -0
- package/src/pantheon/step-cap.ts +110 -0
- package/src/pantheon/task-result-guard.ts +107 -0
- package/src/pantheon/token-opt.ts +689 -0
- package/src/pantheon/tool-ceiling.ts +159 -0
- package/src/pantheon/toon-codec.ts +481 -0
- package/src/pantheon/tui-version-check.ts +1 -3
- package/src/pantheon/v2-bridge.ts +129 -0
- package/src/pantheon/v2-events.ts +148 -0
- package/src/pantheon/v2-hooks.ts +287 -0
- package/src/pantheon/v2-tool-definitions.ts +331 -0
- package/src/pantheon/zeus-delegate-with-retry.ts +406 -0
- package/src/plugin-v2/index.ts +21 -0
- package/src/plugin-v2.ts +824 -0
- package/src/plugin.ts +261 -51
- package/src/plugins/pantheon-hooks.ts +51 -0
- package/src/plugins/tui/dist/tui.d.ts +116 -24
- package/src/plugins/tui/dist/tui.d.ts.map +1 -1
- package/src/plugins/tui/dist/tui.js +416 -149
- package/src/plugins/tui/dist/tui.tsx +456 -87
- package/src/plugins/tui/package-lock.json +51 -51
- package/src/plugins/tui/package.json +7 -7
- package/src/plugins/tui/src/index.tsx +462 -95
- package/src/skills/README.md +0 -2
- package/src/pantheon/tool-context.ts +0 -41
|
@@ -4,13 +4,12 @@
|
|
|
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'
|
|
8
|
-
import {
|
|
9
|
-
import { writeIfChanged } from './shared.mjs'
|
|
7
|
+
import { isAbsolute, join, resolve } from 'node:path'
|
|
8
|
+
import { ROOT, writeIfChanged } from './shared.mjs'
|
|
10
9
|
|
|
11
10
|
/**
|
|
12
11
|
* Copy a plugin's runtime payload (src/index.tsx → index.tsx, dist/*,
|
|
13
|
-
* package.json) from source to destination.
|
|
12
|
+
* package.json and package-lock.json) from source to destination.
|
|
14
13
|
* @param {string} srcDir - Source plugin directory (e.g. ROOT/src/plugins/tui)
|
|
15
14
|
* @param {string} dstDir - Destination plugin directory
|
|
16
15
|
* @param {object} [options]
|
|
@@ -58,6 +57,19 @@ export function copyPluginFiles(srcDir, dstDir, { dryRun = false } = {}) {
|
|
|
58
57
|
else result.skipped++
|
|
59
58
|
}
|
|
60
59
|
|
|
60
|
+
// Copy the lockfile so dependency installation is reproducible. A missing
|
|
61
|
+
// lockfile is intentionally not synthesized by the installer.
|
|
62
|
+
const lockSrc = join(srcDir, 'package-lock.json')
|
|
63
|
+
if (existsSync(lockSrc)) {
|
|
64
|
+
const lockStatus = writeIfChanged(
|
|
65
|
+
join(dstDir, 'package-lock.json'),
|
|
66
|
+
readFileSync(lockSrc, 'utf8'),
|
|
67
|
+
dryRun,
|
|
68
|
+
)
|
|
69
|
+
if (lockStatus === 'created') result.created++
|
|
70
|
+
else result.skipped++
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
return result
|
|
62
74
|
}
|
|
63
75
|
|
|
@@ -81,14 +93,10 @@ export function installPlugin(srcDir, dstDir, { dryRun = false, clean = false }
|
|
|
81
93
|
result.created += copyResult.created
|
|
82
94
|
result.skipped += copyResult.skipped
|
|
83
95
|
|
|
84
|
-
// Install plugin dependencies (@opentui/core, @opentui/solid, solid-js)
|
|
96
|
+
// Install plugin dependencies (@opentui/core, @opentui/solid, solid-js).
|
|
97
|
+
// Lockfile installation is the only accepted dependency path.
|
|
85
98
|
if (!dryRun) {
|
|
86
|
-
|
|
87
|
-
execSync('npm install --omit=dev --no-audit', { cwd: dstDir, stdio: 'pipe' })
|
|
88
|
-
} catch (e) {
|
|
89
|
-
warning(`Failed to install TUI plugin dependencies: ${e.message}`)
|
|
90
|
-
result.errors++
|
|
91
|
-
}
|
|
99
|
+
execSync('npm ci --omit=dev --no-audit --no-fund', { cwd: dstDir, stdio: 'pipe' })
|
|
92
100
|
}
|
|
93
101
|
|
|
94
102
|
return result
|
|
@@ -102,11 +110,7 @@ export function installPlugin(srcDir, dstDir, { dryRun = false, clean = false }
|
|
|
102
110
|
*/
|
|
103
111
|
export function installPluginDependencies(pluginDir, { dryRun = false } = {}) {
|
|
104
112
|
if (dryRun) return
|
|
105
|
-
|
|
106
|
-
execSync('npm install --omit=dev --no-audit', { cwd: pluginDir, stdio: 'pipe' })
|
|
107
|
-
} catch (e) {
|
|
108
|
-
warning(`Failed to install plugin dependencies: ${e.message}`)
|
|
109
|
-
}
|
|
113
|
+
execSync('npm ci --omit=dev --no-audit --no-fund', { cwd: pluginDir, stdio: 'pipe' })
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
/**
|
|
@@ -137,42 +141,70 @@ export function registerPlugin(tuiJsonPath, pluginId, { dryRun = false } = {}) {
|
|
|
137
141
|
}
|
|
138
142
|
|
|
139
143
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
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)
|
|
144
|
+
* Exact relative markers written by previous installers. These are intentionally
|
|
145
|
+
* complete references, not suffixes: a user-owned path may contain
|
|
146
|
+
* `pantheon-opencode` or end in the same filename without being ours.
|
|
152
147
|
*/
|
|
153
148
|
export const TUI_STALE_SUFFIXES = [
|
|
154
|
-
'
|
|
155
|
-
'
|
|
156
|
-
'/
|
|
157
|
-
'
|
|
158
|
-
'
|
|
159
|
-
'
|
|
160
|
-
|
|
149
|
+
'plugins/pantheon-tui',
|
|
150
|
+
'plugins/pantheon-tui/dist/tui.tsx',
|
|
151
|
+
'plugins/pantheon-tui/dist/tui.js',
|
|
152
|
+
'npx pantheon-tui',
|
|
153
|
+
'npx -y pantheon-tui',
|
|
154
|
+
'npx:pantheon-tui',
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
const PACKAGE_TUI_PATHS = [
|
|
158
|
+
join(ROOT, 'src', 'plugins', 'tui'),
|
|
159
|
+
join(ROOT, 'src', 'plugins', 'tui', 'dist', 'tui.tsx'),
|
|
160
|
+
join(ROOT, 'src', 'plugins', 'tui', 'dist', 'tui.js'),
|
|
161
161
|
]
|
|
162
162
|
|
|
163
|
+
function normalizePluginPath(ref) {
|
|
164
|
+
return ref.replaceAll('\\', '/')
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function exactManagedPaths(knownPantheonPaths) {
|
|
168
|
+
return new Set(
|
|
169
|
+
[...PACKAGE_TUI_PATHS, ...knownPantheonPaths]
|
|
170
|
+
.filter((path) => typeof path === 'string')
|
|
171
|
+
.map((path) => normalizePluginPath(path)),
|
|
172
|
+
)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isExactNpxMarker(ref) {
|
|
176
|
+
return (
|
|
177
|
+
/^npx(?:\s+-y)?\s+pantheon-tui(?:@[A-Za-z0-9._-]+)?$/i.test(ref) ||
|
|
178
|
+
/^npx:pantheon-tui(?:@[A-Za-z0-9._-]+)?$/i.test(ref) ||
|
|
179
|
+
/(?:^|\/)\.npm\/_npx\/[^/]+\/node_modules\/pantheon-tui(?:\/|$)/i.test(ref)
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
163
183
|
/**
|
|
164
|
-
*
|
|
184
|
+
* Return whether a registration is unambiguously one of our old TUI refs.
|
|
185
|
+
*
|
|
165
186
|
* @param {unknown} ref - tui.json plugin entry
|
|
187
|
+
* @param {string[]} [knownPantheonPaths=[]] - paths owned by this installer
|
|
166
188
|
* @returns {boolean}
|
|
167
189
|
*/
|
|
168
|
-
export function
|
|
190
|
+
export function isPantheonTuiRef(ref, knownPantheonPaths = []) {
|
|
169
191
|
if (typeof ref !== 'string') return false
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
192
|
+
const normalized = normalizePluginPath(ref)
|
|
193
|
+
if (TUI_STALE_SUFFIXES.includes(normalized)) return true
|
|
194
|
+
if (isExactNpxMarker(normalized)) return true
|
|
195
|
+
|
|
196
|
+
const knownPaths = exactManagedPaths(knownPantheonPaths)
|
|
197
|
+
if (knownPaths.has(normalized)) return true
|
|
198
|
+
return isAbsolute(normalized) && knownPaths.has(normalizePluginPath(resolve(normalized)))
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Whether a single tui.json plugin entry is a stale Pantheon TUI reference.
|
|
203
|
+
* @param {unknown} ref - tui.json plugin entry
|
|
204
|
+
* @returns {boolean}
|
|
205
|
+
*/
|
|
206
|
+
export function isStaleTuiRef(ref, knownPantheonPaths = []) {
|
|
207
|
+
return isPantheonTuiRef(ref, knownPantheonPaths)
|
|
176
208
|
}
|
|
177
209
|
|
|
178
210
|
/**
|
|
@@ -181,8 +213,10 @@ export function isStaleTuiRef(ref) {
|
|
|
181
213
|
* @param {unknown[]} pluginArray - tui.json plugin array
|
|
182
214
|
* @returns {string[]}
|
|
183
215
|
*/
|
|
184
|
-
export function staleTuiRefs(pluginArray) {
|
|
185
|
-
return (Array.isArray(pluginArray) ? pluginArray : []).filter(
|
|
216
|
+
export function staleTuiRefs(pluginArray, knownPantheonPaths = []) {
|
|
217
|
+
return (Array.isArray(pluginArray) ? pluginArray : []).filter((ref) =>
|
|
218
|
+
isStaleTuiRef(ref, knownPantheonPaths),
|
|
219
|
+
)
|
|
186
220
|
}
|
|
187
221
|
|
|
188
222
|
/**
|
|
@@ -193,7 +227,11 @@ export function staleTuiRefs(pluginArray) {
|
|
|
193
227
|
* @param {boolean} [options.dryRun=false]
|
|
194
228
|
* @returns {'created'|'skipped'}
|
|
195
229
|
*/
|
|
196
|
-
export function unregisterPlugin(
|
|
230
|
+
export function unregisterPlugin(
|
|
231
|
+
tuiJsonPath,
|
|
232
|
+
pluginId,
|
|
233
|
+
{ dryRun = false, knownPantheonPaths = [] } = {},
|
|
234
|
+
) {
|
|
197
235
|
if (!existsSync(tuiJsonPath)) {
|
|
198
236
|
// Nothing to clean — and never create an empty tui.json in a location
|
|
199
237
|
// OpenCode reads (the cross-location cleanup also touches global config
|
|
@@ -215,10 +253,12 @@ export function unregisterPlugin(tuiJsonPath, pluginId, { dryRun = false } = {})
|
|
|
215
253
|
// bare copy dirs from previous install targets, and npx cache copies.
|
|
216
254
|
// Without removing the absolute paths, each init run accumulates another
|
|
217
255
|
// TUI plugin and OpenCode loads stale copies.
|
|
218
|
-
const exactStale =
|
|
256
|
+
const exactStale = new Set(
|
|
257
|
+
[pluginId, `${pluginId}/dist/tui.tsx`, `${pluginId}/dist/tui.js`].map(normalizePluginPath),
|
|
258
|
+
)
|
|
219
259
|
tuiConfig.plugin = tuiConfig.plugin.filter((ref) => {
|
|
220
|
-
if (exactStale.
|
|
221
|
-
return !
|
|
260
|
+
if (typeof ref === 'string' && exactStale.has(normalizePluginPath(ref))) return false
|
|
261
|
+
return !isPantheonTuiRef(ref, knownPantheonPaths)
|
|
222
262
|
})
|
|
223
263
|
const tuiContent = `${JSON.stringify(tuiConfig, null, 2)}\n`
|
|
224
264
|
return writeIfChanged(tuiJsonPath, tuiContent, dryRun)
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* shared.mjs — Shared utilities for
|
|
2
|
+
* shared.mjs — Shared utilities for the OpenCode installer
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { spawnSync } from 'node:child_process'
|
|
5
6
|
import {
|
|
7
|
+
copyFileSync,
|
|
6
8
|
cpSync,
|
|
7
9
|
existsSync,
|
|
8
10
|
mkdirSync,
|
|
9
11
|
readdirSync,
|
|
10
12
|
readFileSync,
|
|
13
|
+
renameSync,
|
|
11
14
|
rmSync,
|
|
12
15
|
statSync,
|
|
13
16
|
writeFileSync,
|
|
@@ -26,8 +29,6 @@ import yaml from 'js-yaml'
|
|
|
26
29
|
export const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
27
30
|
export const ROOT = join(__dirname, '..', '..')
|
|
28
31
|
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
32
|
|
|
32
33
|
// Auto-detect agent names from agents/ directory
|
|
33
34
|
export function getAgentNames() {
|
|
@@ -43,33 +44,7 @@ export function getAgentNames() {
|
|
|
43
44
|
// Cached constant for backward compatibility
|
|
44
45
|
export const AGENT_NAMES = getAgentNames()
|
|
45
46
|
|
|
46
|
-
|
|
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
|
-
}
|
|
47
|
+
export const summary = { opencode: { created: 0, skipped: 0, errors: 0, warnings: 0 } }
|
|
73
48
|
|
|
74
49
|
export function showHelp() {
|
|
75
50
|
console.log(`
|
|
@@ -78,9 +53,7 @@ pantheon-init.mjs — Pantheon OpenCode installer
|
|
|
78
53
|
Usage:
|
|
79
54
|
npx pantheon-opencode init auto-detect, cwd
|
|
80
55
|
npx pantheon-opencode init --project auto-detect, project
|
|
81
|
-
npx pantheon-opencode init --project
|
|
82
|
-
npx pantheon-opencode init --project --platforms all all platforms
|
|
83
|
-
npx pantheon-opencode init --detect detect platforms without installing
|
|
56
|
+
npx pantheon-opencode init --project install in a project
|
|
84
57
|
npx pantheon-opencode init --project --dry-run preview without writing
|
|
85
58
|
npx pantheon-opencode init --project --backup create timestamped backup before writing
|
|
86
59
|
npx pantheon-opencode init --project --clean wipe + fresh install (all components)
|
|
@@ -101,19 +74,8 @@ Clean mode (--clean):
|
|
|
101
74
|
re-installs fresh from source. Useful after removing/renaming agents or skills.
|
|
102
75
|
OFF by default — without --clean only copies new/changed files (never deletes).
|
|
103
76
|
|
|
104
|
-
|
|
77
|
+
Platform:
|
|
105
78
|
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
79
|
`)
|
|
118
80
|
}
|
|
119
81
|
|
|
@@ -206,63 +168,25 @@ export function createBackup(target) {
|
|
|
206
168
|
}
|
|
207
169
|
|
|
208
170
|
export function detectAndReport(target) {
|
|
209
|
-
const
|
|
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
|
-
}
|
|
171
|
+
const found = detectPlatforms(target).length > 0
|
|
228
172
|
|
|
229
173
|
console.log(`\n 🔍 Pantheon Platform Detection`)
|
|
230
174
|
console.log(` Target: ${target}\n`)
|
|
231
175
|
|
|
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
176
|
console.log(` ${'Platform'.padEnd(12)} ${'Detected?'.padEnd(12)} Config file`)
|
|
240
177
|
console.log(` ${'─'.repeat(55)}`)
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
console.log(` ${PLATFORM_LABELS[r.platform].padEnd(12)} ${status.padEnd(12)} ${configFile}`)
|
|
247
|
-
}
|
|
248
|
-
|
|
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}`)
|
|
178
|
+
const status = (found ? '✅ YES' : '❌ no').padEnd(12)
|
|
179
|
+
console.log(` ${'OpenCode'.padEnd(12)} ${status} ${found ? 'opencode.json' : '(not found)'}`)
|
|
180
|
+
console.log(`\n 📊 ${found ? 1 : 0} of 1 platforms detected.`)
|
|
181
|
+
if (found) {
|
|
182
|
+
console.log(' → Install with: npx pantheon-opencode init')
|
|
253
183
|
}
|
|
254
184
|
|
|
255
|
-
return
|
|
185
|
+
return found ? ['opencode'] : []
|
|
256
186
|
}
|
|
257
187
|
|
|
258
188
|
export function detectPlatforms(target) {
|
|
259
|
-
|
|
260
|
-
for (const [platform, detector] of Object.entries(PLATFORM_DETECTORS)) {
|
|
261
|
-
if (detector(target)) {
|
|
262
|
-
detected.push(platform)
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
return detected
|
|
189
|
+
return existsSync(join(target, 'opencode.json')) ? ['opencode'] : []
|
|
266
190
|
}
|
|
267
191
|
|
|
268
192
|
export function sourceDirValid(dir) {
|
|
@@ -339,11 +263,49 @@ export function writeIfChanged(filePath, content, dryRun) {
|
|
|
339
263
|
return 'skipped'
|
|
340
264
|
}
|
|
341
265
|
if (!dryRun) {
|
|
342
|
-
|
|
266
|
+
// Atomic write: write a temp sibling and rename over the target so a
|
|
267
|
+
// crash mid-write can never leave a truncated file behind (rename is
|
|
268
|
+
// atomic within the same directory).
|
|
269
|
+
const tmpPath = `${filePath}.tmp-${process.pid}`
|
|
270
|
+
writeFileSync(tmpPath, content, 'utf8')
|
|
271
|
+
renameSync(tmpPath, filePath)
|
|
343
272
|
}
|
|
344
273
|
return 'created'
|
|
345
274
|
}
|
|
346
275
|
|
|
276
|
+
/**
|
|
277
|
+
* beta.5: write the user's opencode.json with a same-directory backup of the
|
|
278
|
+
* previous content. The installer rewrites the whole config; a `.bak` gives
|
|
279
|
+
* the user a one-step recovery if a merge ever goes wrong.
|
|
280
|
+
*/
|
|
281
|
+
export function writeConfigWithBackup(filePath, content, dryRun) {
|
|
282
|
+
if (!dryRun && existsSync(filePath)) {
|
|
283
|
+
copyFileSync(filePath, `${filePath}.bak`)
|
|
284
|
+
}
|
|
285
|
+
return writeIfChanged(filePath, content, dryRun)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* beta.5 preflight: verify the toolchain pieces the runtime component needs
|
|
290
|
+
* BEFORE any files are written, so a missing python3/npm fails in seconds
|
|
291
|
+
* with a clear message instead of aborting a half-done install.
|
|
292
|
+
* Returns a list of human-readable problems (empty = all good).
|
|
293
|
+
*/
|
|
294
|
+
export function checkRuntimePrerequisites(env = process.env) {
|
|
295
|
+
const problems = []
|
|
296
|
+
const checks = [
|
|
297
|
+
{ cmd: 'python3', hint: 'install Python 3.10+ (https://www.python.org/downloads/)' },
|
|
298
|
+
{ cmd: 'npm', hint: 'install Node.js 22+ (https://nodejs.org) — npm ships with it' },
|
|
299
|
+
]
|
|
300
|
+
for (const { cmd, hint } of checks) {
|
|
301
|
+
const probe = spawnSync(cmd, ['--version'], { encoding: 'utf8', timeout: 10_000, env })
|
|
302
|
+
if (probe.error || probe.status !== 0) {
|
|
303
|
+
problems.push(`${cmd} not found or not runnable — ${hint}`)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return problems
|
|
307
|
+
}
|
|
308
|
+
|
|
347
309
|
export function collectSkillNames() {
|
|
348
310
|
const skillsDir = join(ROOT, 'src', 'skills')
|
|
349
311
|
if (!existsSync(skillsDir)) return []
|
|
@@ -569,16 +531,6 @@ export function validateMcpServers(mcpServers, _agentTools = []) {
|
|
|
569
531
|
}
|
|
570
532
|
|
|
571
533
|
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
534
|
console.log('')
|
|
583
535
|
console.log('='.repeat(60))
|
|
584
536
|
console.log('📋 Installation Summary')
|
|
@@ -590,7 +542,7 @@ export function printSummary(target, platforms) {
|
|
|
590
542
|
let totalErrors = 0
|
|
591
543
|
|
|
592
544
|
for (const platform of platforms) {
|
|
593
|
-
const label =
|
|
545
|
+
const label = platform === 'opencode' ? 'OpenCode' : platform
|
|
594
546
|
const stats = summary[platform]
|
|
595
547
|
totalCreated += stats.created
|
|
596
548
|
totalSkipped += stats.skipped
|
|
@@ -624,62 +576,6 @@ export function printSummary(target, platforms) {
|
|
|
624
576
|
console.log('')
|
|
625
577
|
}
|
|
626
578
|
|
|
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
579
|
console.log(' 📚 Full documentation: https://github.com/ils15/pantheon')
|
|
684
580
|
console.log(' 🐛 Report issues: https://github.com/ils15/pantheon/issues')
|
|
685
581
|
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* strings.mjs — installer strings with locale auto-detection.
|
|
3
|
+
*
|
|
4
|
+
* LANG/LC_ALL/LC_MESSAGES starting with "pt" selects the PT-BR table;
|
|
5
|
+
* everything else (and the absence of a locale) selects EN. A plain string
|
|
6
|
+
* table — deliberately no i18n framework (YAGNI).
|
|
7
|
+
*
|
|
8
|
+
* Scope: the user journey (banner → install steps → health → summary →
|
|
9
|
+
* next steps). Deep diagnostics (per-file copy detail, doctor output) and
|
|
10
|
+
* the model-picker wizard remain single-language by design.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const TABLE = {
|
|
14
|
+
en: {
|
|
15
|
+
installing: 'Installing...',
|
|
16
|
+
dryRun: 'DRY RUN',
|
|
17
|
+
canceled: 'Installation canceled — nothing was broken; run init again anytime.',
|
|
18
|
+
canceledShort: 'Installation canceled.',
|
|
19
|
+
installFailed: (msg) => `❌ Installation failed: ${msg}`,
|
|
20
|
+
failHintNoMcp:
|
|
21
|
+
' Run with --no-mcp to skip Python dependencies:\n npx pantheon-opencode init --no-mcp',
|
|
22
|
+
failHintForce:
|
|
23
|
+
' Or retry with --force to recreate the venv:\n npx pantheon-opencode init --force',
|
|
24
|
+
runningHealthCheck: 'Running health check...',
|
|
25
|
+
installedTitle: (v) => `✅ Pantheon OpenCode v${v} installed!`,
|
|
26
|
+
nextSteps: 'Next steps:',
|
|
27
|
+
nextVerify: '1. Verify installation:\n npx pantheon-opencode doctor',
|
|
28
|
+
nextLaunch: '2. Launch OpenCode',
|
|
29
|
+
nextAgents: '3. Invoke agents with @agent-name in chat',
|
|
30
|
+
nextProject: '4. For project-local install:\n npx pantheon-opencode init --project',
|
|
31
|
+
// install steps
|
|
32
|
+
globalLayout:
|
|
33
|
+
'Global config directory detected — using flat layout (agents/, skills/, commands/)',
|
|
34
|
+
installingAgents: 'Installing agents',
|
|
35
|
+
installingSkills: 'Installing skills',
|
|
36
|
+
installingInstructions: 'Installing instructions',
|
|
37
|
+
installingPrompts: 'Installing prompts',
|
|
38
|
+
installingCommands: 'Installing commands',
|
|
39
|
+
installingPlugins: 'Installing plugins',
|
|
40
|
+
settingUpRuntime: 'Setting up Python virtual environment',
|
|
41
|
+
healthSection: 'Health Check',
|
|
42
|
+
runningHealthChecks: 'Running health checks',
|
|
43
|
+
runtimeFailed: (msg) => `Python runtime setup failed: ${msg}`,
|
|
44
|
+
runtimeSkippedMcp:
|
|
45
|
+
'MCP servers were NOT configured because the Python runtime is unavailable. ' +
|
|
46
|
+
'Fix python3 (or free disk space) and re-run init to add them; everything else was installed.',
|
|
47
|
+
prereqMissing:
|
|
48
|
+
'runtime prerequisites missing — install the tools above, or re-run with --no-mcp to install without Python MCP servers',
|
|
49
|
+
v2Migrating: 'Migrating config to V2 native format...',
|
|
50
|
+
// summary (printSummary)
|
|
51
|
+
summaryInstalled: (target) => `OpenCode installed in ${target}`,
|
|
52
|
+
summaryWithErrors: (target) => `Installation finished WITH ERRORS in ${target}`,
|
|
53
|
+
summaryComponents: (created, skipped) =>
|
|
54
|
+
` Components:\t${created} installed, ${skipped} skipped`,
|
|
55
|
+
summaryErrors: (n) => ` ${n} error(s) found`,
|
|
56
|
+
summaryWarnings: (n) => ` ${n} warning(s) — run 'doctor' for details`,
|
|
57
|
+
summaryNext: ' Next steps:',
|
|
58
|
+
summaryNextAgents: ' • Configure your agents in opencode.json',
|
|
59
|
+
summaryNextMcp: ' • Add MCP servers in opencode.json (mcp section)',
|
|
60
|
+
summaryNextDoctor: " • Run 'opencode doctor' to verify the installation",
|
|
61
|
+
},
|
|
62
|
+
pt: {
|
|
63
|
+
installing: 'Instalando...',
|
|
64
|
+
dryRun: 'SIMULAÇÃO',
|
|
65
|
+
canceled: 'Instalação cancelada — nada foi quebrado; rode init de novo quando quiser.',
|
|
66
|
+
canceledShort: 'Instalação cancelada.',
|
|
67
|
+
installFailed: (msg) => `❌ Instalação falhou: ${msg}`,
|
|
68
|
+
failHintNoMcp:
|
|
69
|
+
' Rode com --no-mcp para pular dependências Python:\n npx pantheon-opencode init --no-mcp',
|
|
70
|
+
failHintForce:
|
|
71
|
+
' Ou tente de novo com --force para recriar a venv:\n npx pantheon-opencode init --force',
|
|
72
|
+
runningHealthCheck: 'Rodando verificação de saúde...',
|
|
73
|
+
installedTitle: (v) => `✅ Pantheon OpenCode v${v} instalado!`,
|
|
74
|
+
nextSteps: 'Próximos passos:',
|
|
75
|
+
nextVerify: '1. Verifique a instalação:\n npx pantheon-opencode doctor',
|
|
76
|
+
nextLaunch: '2. Abra o OpenCode',
|
|
77
|
+
nextAgents: '3. Invoc agentes com @nome-do-agente no chat',
|
|
78
|
+
nextProject: '4. Para instalar no projeto:\n npx pantheon-opencode init --project',
|
|
79
|
+
// install steps
|
|
80
|
+
globalLayout:
|
|
81
|
+
'Diretório de config global detectado — layout plano (agents/, skills/, commands/)',
|
|
82
|
+
installingAgents: 'Instalando agentes',
|
|
83
|
+
installingSkills: 'Instalando skills',
|
|
84
|
+
installingInstructions: 'Instalando instruções',
|
|
85
|
+
installingPrompts: 'Instalando prompts',
|
|
86
|
+
installingCommands: 'Instalando comandos',
|
|
87
|
+
installingPlugins: 'Instalando plugins',
|
|
88
|
+
settingUpRuntime: 'Configurando ambiente virtual Python',
|
|
89
|
+
healthSection: 'Verificação de saúde',
|
|
90
|
+
runningHealthChecks: 'Rodando verificações de saúde',
|
|
91
|
+
runtimeFailed: (msg) => `Falha ao configurar o runtime Python: ${msg}`,
|
|
92
|
+
runtimeSkippedMcp:
|
|
93
|
+
'Os servidores MCP NÃO foram configurados porque o runtime Python está indisponível. ' +
|
|
94
|
+
'Corrija o python3 (ou libere espaço em disco) e rode init de novo para adicioná-los; todo o resto foi instalado.',
|
|
95
|
+
prereqMissing:
|
|
96
|
+
'pré-requisitos do runtime ausentes — instale as ferramentas acima, ou rode de novo com --no-mcp para instalar sem os MCPs Python',
|
|
97
|
+
v2Migrating: 'Migrando config para o formato nativo V2...',
|
|
98
|
+
// summary (printSummary)
|
|
99
|
+
summaryInstalled: (target) => `OpenCode instalado em ${target}`,
|
|
100
|
+
summaryWithErrors: (target) => `Instalação concluída COM ERROS em ${target}`,
|
|
101
|
+
summaryComponents: (created, skipped) =>
|
|
102
|
+
` Componentes:\t${created} instalados, ${skipped} pulados`,
|
|
103
|
+
summaryErrors: (n) => ` ${n} erro(s) encontrados`,
|
|
104
|
+
summaryWarnings: (n) => ` ${n} aviso(s) — rode 'doctor' para detalhes`,
|
|
105
|
+
summaryNext: ' Próximos passos:',
|
|
106
|
+
summaryNextAgents: ' • Configure seus agentes em opencode.json',
|
|
107
|
+
summaryNextMcp: ' • Adicione MCP servers em opencode.json (seção mcp)',
|
|
108
|
+
summaryNextDoctor: " • Rode 'opencode doctor' para verificar a instalação",
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Pure locale resolution (exported for tests). */
|
|
113
|
+
export function detectLocale(env = process.env) {
|
|
114
|
+
const raw = env.LC_ALL ?? env.LC_MESSAGES ?? env.LANG ?? ''
|
|
115
|
+
return /^pt/i.test(raw) ? 'pt' : 'en'
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Pure table access (exported for tests and explicit-locale callers). */
|
|
119
|
+
export function stringsFor(locale) {
|
|
120
|
+
return TABLE[locale] ?? TABLE.en
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let cached = null
|
|
124
|
+
|
|
125
|
+
/** Locale-resolved string table for the current process. */
|
|
126
|
+
export function strings(env = process.env) {
|
|
127
|
+
if (cached === null) cached = stringsFor(detectLocale(env))
|
|
128
|
+
return cached
|
|
129
|
+
}
|