pantheon-opencode 1.6.0-beta.11 → 1.6.0-beta.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.global.md +3 -3
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +39 -0
- package/README.md +85 -31
- package/README.pt-BR.md +7 -4
- package/bin/pantheon-init.mjs +278 -102
- package/package.json +5 -3
- package/plugin.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/build-agents-md.mjs +77 -13
- package/scripts/check-plugin-v2-coverage.mjs +7 -7
- package/scripts/doctor.mjs +242 -19
- package/scripts/install/backup.mjs +191 -18
- package/scripts/install/cli-ui.mjs +73 -0
- package/scripts/install/file-inventory.mjs +555 -0
- package/scripts/install/interactive.mjs +69 -391
- package/scripts/install/migrate.mjs +45 -38
- package/scripts/install/model-picker.mjs +63 -22
- package/scripts/install/opencode-version.mjs +9 -1
- package/scripts/install/opencode.mjs +279 -79
- package/scripts/install/plan-artifact.mjs +517 -0
- package/scripts/install/plan.mjs +165 -3
- package/scripts/install/plugin.mjs +118 -14
- package/scripts/install/prompt-result.mjs +2 -0
- package/scripts/install/shared.mjs +188 -65
- package/scripts/install/state.mjs +63 -8
- package/scripts/install/venv.mjs +15 -6
- package/scripts/install/verify.mjs +128 -0
- package/scripts/install/vision-prompt.mjs +2 -0
- package/scripts/install-mcp.mjs +1 -1
- package/scripts/manifest-inventory.mjs +49 -3
- package/scripts/node-engine-policy.mjs +85 -0
- package/scripts/package-evidence.mjs +6 -0
- package/scripts/postinstall.mjs +26 -26
- package/scripts/reconcile.mjs +325 -0
- package/scripts/sync-tui.mjs +5 -1
- package/scripts/uninstall.mjs +258 -34
- package/scripts/update.mjs +164 -0
- package/scripts/validate-node-engine-policy.mjs +13 -0
- package/scripts/version-check.mjs +34 -1
- package/scripts/versioning.mjs +9 -1
- package/skills-lock.json +3 -3
- package/src/agents/mnemosyne.md +7 -2
- package/src/instructions/memory-protocol.instructions.md +2 -2
- package/src/instructions/zeus-memory-operations.instructions.md +4 -3
- package/src/pantheon/v2-unsupported.mjs +2 -3
- package/src/plugins/tui/dist/tui-v2.js +2 -2
- package/src/plugins/tui/dist/tui.js +1 -1
- package/src/plugins/tui/package-lock.json +3 -3
- package/src/plugins/tui/package.json +2 -2
- package/src/skills/artifact-management/SKILL.md +5 -1
- package/src/skills/memory-bank/SKILL.md +5 -1
- package/src/skills/worktrees/SKILL.md +30 -11
package/bin/pantheon-init.mjs
CHANGED
|
@@ -20,16 +20,24 @@ import fs from 'node:fs'
|
|
|
20
20
|
import os from 'node:os'
|
|
21
21
|
import path from 'node:path'
|
|
22
22
|
import { fileURLToPath } from 'node:url'
|
|
23
|
-
|
|
24
23
|
import { resolveInstallExitCode } from '../scripts/install/exit-codes.mjs'
|
|
24
|
+
import {
|
|
25
|
+
createPlanArtifact,
|
|
26
|
+
loadPlanArtifact,
|
|
27
|
+
savePlanArtifact,
|
|
28
|
+
} from '../scripts/install/plan-artifact.mjs'
|
|
29
|
+
import { PROMPT_CANCELED } from '../scripts/install/prompt-result.mjs'
|
|
30
|
+
import {
|
|
31
|
+
isSupportedNodeVersion,
|
|
32
|
+
unsupportedNodeVersionMessage,
|
|
33
|
+
} from '../scripts/node-engine-policy.mjs'
|
|
34
|
+
import { runUpdate } from '../scripts/update.mjs'
|
|
25
35
|
|
|
26
36
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
27
37
|
const ROOT = path.resolve(__dirname, '..')
|
|
28
38
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
console.error(`❌ Node.js >= ${REQUIRED_NODE_MAJOR} required (current: ${process.versions.node})`)
|
|
32
|
-
console.error(' Install a newer Node via nvm: https://github.com/nvm-sh/nvm')
|
|
39
|
+
if (!isSupportedNodeVersion(process.versions.node)) {
|
|
40
|
+
console.error(unsupportedNodeVersionMessage(process.versions.node))
|
|
33
41
|
process.exit(1)
|
|
34
42
|
}
|
|
35
43
|
|
|
@@ -49,6 +57,9 @@ function printUsage() {
|
|
|
49
57
|
console.log(' npx pantheon-opencode init # Install globally')
|
|
50
58
|
console.log(' npx pantheon-opencode init --project # Install in project')
|
|
51
59
|
console.log(' npx pantheon-opencode init --dry-run # Preview only')
|
|
60
|
+
console.log(' npx pantheon-opencode init --dry-run --save-plan <file> # Save reviewed plan')
|
|
61
|
+
console.log(' npx pantheon-opencode init --apply-plan <file> [--yes] # Apply reviewed plan')
|
|
62
|
+
console.log(' npx pantheon-opencode init --dry-run --json # Versioned JSON preview')
|
|
52
63
|
console.log(' npx pantheon-opencode init --no-mcp # Skip MCP + venv')
|
|
53
64
|
console.log(' npx pantheon-opencode init --force # Overwrite + recreate venv')
|
|
54
65
|
console.log(' npx pantheon-opencode init --doctor # Run health check after install')
|
|
@@ -76,6 +87,7 @@ function printUsage() {
|
|
|
76
87
|
)
|
|
77
88
|
console.log(' npx pantheon-opencode uninstall --project # Remove project artifacts safely')
|
|
78
89
|
console.log(' npx pantheon-opencode uninstall --global # Remove global artifacts safely')
|
|
90
|
+
console.log(' npx pantheon-opencode reconcile --project # Review stale owned installer files')
|
|
79
91
|
console.log(' npx pantheon-opencode --version, -v # Print version and exit')
|
|
80
92
|
console.log(' npx pantheon-opencode --help # Show this help')
|
|
81
93
|
console.log('')
|
|
@@ -91,6 +103,16 @@ function printUsage() {
|
|
|
91
103
|
console.log(' 130 canceled')
|
|
92
104
|
}
|
|
93
105
|
|
|
106
|
+
async function authorizeReviewedPlan(plan) {
|
|
107
|
+
const operations = plan.operations.filter((operation) => operation.action !== 'unchanged')
|
|
108
|
+
console.log(`Reviewed plan: ${plan.target.path} (${operations.length} change(s))`)
|
|
109
|
+
for (const operation of operations) {
|
|
110
|
+
console.log(` ${operation.action.padEnd(9)} ${operation.path}`)
|
|
111
|
+
}
|
|
112
|
+
const { promptReview } = await import('../scripts/install/interactive.mjs')
|
|
113
|
+
return promptReview('Apply this reviewed plan?', { default: false })
|
|
114
|
+
}
|
|
115
|
+
|
|
94
116
|
async function main() {
|
|
95
117
|
const args = process.argv.slice(2)
|
|
96
118
|
const command = args[0]
|
|
@@ -125,6 +147,16 @@ async function main() {
|
|
|
125
147
|
process.exit(result.status ?? 1)
|
|
126
148
|
}
|
|
127
149
|
|
|
150
|
+
if (command === 'reconcile') {
|
|
151
|
+
const { spawnSync } = await import('node:child_process')
|
|
152
|
+
const reconcileScript = path.join(ROOT, 'scripts', 'reconcile.mjs')
|
|
153
|
+
const result = spawnSync(process.execPath, [reconcileScript, ...args.slice(1)], {
|
|
154
|
+
stdio: 'inherit',
|
|
155
|
+
cwd: process.cwd(),
|
|
156
|
+
})
|
|
157
|
+
process.exit(result.status ?? 1)
|
|
158
|
+
}
|
|
159
|
+
|
|
128
160
|
// restore delegates to scripts/restore.mjs (same spawnSync pattern as prune).
|
|
129
161
|
if (command === 'restore') {
|
|
130
162
|
const { spawnSync } = await import('node:child_process')
|
|
@@ -152,72 +184,12 @@ async function main() {
|
|
|
152
184
|
// beta.5: one-command update — check npm dist-tag, install, re-init.
|
|
153
185
|
// Default channel is `beta` (the prerelease line); `--stable` uses `latest`.
|
|
154
186
|
if (command === 'update') {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
console.log(`Pantheon OpenCode v${current} — checking the "${channel}" channel...`)
|
|
161
|
-
const view = spawnSync('npm', ['view', `pantheon-opencode@${channel}`, 'version'], {
|
|
162
|
-
encoding: 'utf8',
|
|
163
|
-
timeout: 30_000,
|
|
164
|
-
})
|
|
165
|
-
const latest = (view.stdout || '').trim().split('\n').filter(Boolean).pop()
|
|
166
|
-
if (view.status !== 0 || !latest) {
|
|
167
|
-
console.error(
|
|
168
|
-
`❌ Could not reach the npm registry: ${(view.stderr || view.stdout || '').trim() || 'no version returned'}`,
|
|
169
|
-
)
|
|
170
|
-
process.exit(1)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Prerelease-aware compare: core (major.minor.patch) first, then the
|
|
174
|
-
// beta counter; a stable release outranks any beta of the same core.
|
|
175
|
-
const parsePantheonVersion = (v) => {
|
|
176
|
-
const m = String(v)
|
|
177
|
-
.trim()
|
|
178
|
-
.match(/^(\d+)\.(\d+)\.(\d+)(?:-beta\.(\d+))?$/)
|
|
179
|
-
if (!m) return null
|
|
180
|
-
return {
|
|
181
|
-
core: [+m[1], +m[2], +m[3]],
|
|
182
|
-
beta: m[4] === undefined ? Number.POSITIVE_INFINITY : +m[4],
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
const cmp = (a, b) => {
|
|
186
|
-
const pa = parsePantheonVersion(a)
|
|
187
|
-
const pb = parsePantheonVersion(b)
|
|
188
|
-
if (!pa || !pb) return 0
|
|
189
|
-
for (let i = 0; i < 3; i++) {
|
|
190
|
-
if (pa.core[i] !== pb.core[i]) return pa.core[i] - pb.core[i]
|
|
191
|
-
}
|
|
192
|
-
return pa.beta - pb.beta
|
|
193
|
-
}
|
|
194
|
-
if (cmp(latest, current) <= 0) {
|
|
195
|
-
console.log(`✅ Already up to date (installed v${current}; ${channel} channel: v${latest}).`)
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
console.log(`Updating v${current} → v${latest} (channel: ${channel})...`)
|
|
200
|
-
const install = spawnSync('npm', ['install', '-g', `pantheon-opencode@${channel}`], {
|
|
201
|
-
stdio: 'inherit',
|
|
187
|
+
const status = runUpdate(args, {
|
|
188
|
+
root: ROOT,
|
|
189
|
+
currentVersion: readVersion(),
|
|
190
|
+
runningScript: process.argv[1],
|
|
202
191
|
})
|
|
203
|
-
if (
|
|
204
|
-
console.error('❌ npm install failed — the previous installation is untouched.')
|
|
205
|
-
process.exit(install.status ?? 1)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Refresh config/venv/MCP entries with the NEW package (resolved via
|
|
209
|
-
// PATH — the freshly installed global bin).
|
|
210
|
-
const init = spawnSync('pantheon-opencode', ['init', '--yes', '--headless'], {
|
|
211
|
-
stdio: 'inherit',
|
|
212
|
-
shell: process.platform === 'win32',
|
|
213
|
-
})
|
|
214
|
-
if (init.status !== 0 || init.error) {
|
|
215
|
-
console.error(
|
|
216
|
-
'⚠️ Updated, but the config refresh did not complete — run `npx pantheon-opencode init` manually.',
|
|
217
|
-
)
|
|
218
|
-
process.exit(init.status ?? 1)
|
|
219
|
-
}
|
|
220
|
-
console.log(`✅ Updated to v${latest}.`)
|
|
192
|
+
if (status !== 0) process.exit(status)
|
|
221
193
|
return
|
|
222
194
|
}
|
|
223
195
|
|
|
@@ -301,15 +273,89 @@ async function main() {
|
|
|
301
273
|
}
|
|
302
274
|
|
|
303
275
|
if (command === 'init' || !command) {
|
|
304
|
-
const
|
|
305
|
-
const
|
|
276
|
+
const applyPlanIndex = args.indexOf('--apply-plan')
|
|
277
|
+
const inlineApplyPlan = args.find((arg) => arg.startsWith('--apply-plan='))
|
|
278
|
+
const applyPlanPath =
|
|
279
|
+
applyPlanIndex >= 0
|
|
280
|
+
? (args[applyPlanIndex + 1] ?? null)
|
|
281
|
+
: (inlineApplyPlan?.split('=', 2)[1] ?? null)
|
|
282
|
+
if (applyPlanIndex >= 0 && !applyPlanPath) {
|
|
283
|
+
console.error('❌ --apply-plan requires a plan file path')
|
|
284
|
+
process.exit(1)
|
|
285
|
+
}
|
|
286
|
+
let reviewedPlan = null
|
|
287
|
+
if (applyPlanPath !== null) {
|
|
288
|
+
const allowed = new Set(['init', '--apply-plan', applyPlanPath, '--yes', '-y', '--headless'])
|
|
289
|
+
const normalized = args.map((value) =>
|
|
290
|
+
value.startsWith('--apply-plan=') ? '--apply-plan' : value,
|
|
291
|
+
)
|
|
292
|
+
if (normalized.some((value) => !allowed.has(value))) {
|
|
293
|
+
console.error('❌ --apply-plan accepts only the plan path and fresh authorization flags')
|
|
294
|
+
process.exit(1)
|
|
295
|
+
}
|
|
296
|
+
reviewedPlan = loadPlanArtifact(applyPlanPath)
|
|
297
|
+
if (
|
|
298
|
+
reviewedPlan.artifact_path &&
|
|
299
|
+
path.resolve(applyPlanPath) !== reviewedPlan.artifact_path
|
|
300
|
+
) {
|
|
301
|
+
console.error('❌ The reviewed plan artifact was moved; use its original path')
|
|
302
|
+
process.exit(1)
|
|
303
|
+
}
|
|
304
|
+
const options = reviewedPlan.options
|
|
305
|
+
if (
|
|
306
|
+
!Array.isArray(options.components) ||
|
|
307
|
+
options.components.some(
|
|
308
|
+
(component) =>
|
|
309
|
+
!['agents', 'skills', 'instructions', 'commands', 'plugins', 'runtime'].includes(
|
|
310
|
+
component,
|
|
311
|
+
),
|
|
312
|
+
) ||
|
|
313
|
+
options.clean !== false ||
|
|
314
|
+
!['v1', 'v2', 'auto'].includes(options.version) ||
|
|
315
|
+
typeof options.isProject !== 'boolean'
|
|
316
|
+
) {
|
|
317
|
+
console.error('❌ Install-plan options are invalid or unsupported')
|
|
318
|
+
process.exit(1)
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const isProject = reviewedPlan ? reviewedPlan.options.isProject : args.includes('--project')
|
|
322
|
+
const isDryRun = reviewedPlan ? false : args.includes('--dry-run')
|
|
306
323
|
const skipMCP = args.includes('--no-mcp')
|
|
307
324
|
// --clean is an alias of --force (both wipe component dirs + recreate venv).
|
|
308
|
-
const forceReinstall =
|
|
309
|
-
|
|
310
|
-
|
|
325
|
+
const forceReinstall = reviewedPlan
|
|
326
|
+
? reviewedPlan.options.clean
|
|
327
|
+
: args.includes('--force') || args.includes('--clean')
|
|
328
|
+
const runDoctor = !reviewedPlan && args.includes('--doctor')
|
|
329
|
+
const forceInteractive = !reviewedPlan && args.includes('--interactive')
|
|
311
330
|
const forceHeadless = args.includes('--headless')
|
|
312
|
-
|
|
331
|
+
let autoYes = args.includes('--yes') || args.includes('-y')
|
|
332
|
+
const isJson = args.includes('--json')
|
|
333
|
+
const savePlanIndex = args.indexOf('--save-plan')
|
|
334
|
+
const inlineSavePlan = args.find((arg) => arg.startsWith('--save-plan='))
|
|
335
|
+
const savePlanPath =
|
|
336
|
+
savePlanIndex >= 0
|
|
337
|
+
? (args[savePlanIndex + 1] ?? null)
|
|
338
|
+
: (inlineSavePlan?.split('=', 2)[1] ?? null)
|
|
339
|
+
if (savePlanIndex >= 0 && !savePlanPath) {
|
|
340
|
+
console.error('❌ --save-plan requires a plan file path')
|
|
341
|
+
process.exit(1)
|
|
342
|
+
}
|
|
343
|
+
if (savePlanPath !== null && (!isDryRun || reviewedPlan)) {
|
|
344
|
+
console.error('❌ --save-plan is available only with init --dry-run')
|
|
345
|
+
process.exit(1)
|
|
346
|
+
}
|
|
347
|
+
if (savePlanPath !== null && forceReinstall) {
|
|
348
|
+
console.error('❌ Saving a reviewed plan with --force/--clean is not supported safely')
|
|
349
|
+
process.exit(1)
|
|
350
|
+
}
|
|
351
|
+
if (isJson && (!isDryRun || reviewedPlan)) {
|
|
352
|
+
console.error('❌ --json is supported only with init --dry-run')
|
|
353
|
+
process.exit(1)
|
|
354
|
+
}
|
|
355
|
+
if (isJson && forceReinstall) {
|
|
356
|
+
console.error('❌ --force/--clean cleanup is not supported with --json')
|
|
357
|
+
process.exit(1)
|
|
358
|
+
}
|
|
313
359
|
|
|
314
360
|
// beta.5: warn on unrecognized flags instead of silently ignoring them.
|
|
315
361
|
const VALUE_FLAGS = new Set([
|
|
@@ -320,6 +366,8 @@ async function main() {
|
|
|
320
366
|
'--opencode-version',
|
|
321
367
|
'--components',
|
|
322
368
|
'--merge-dev-config',
|
|
369
|
+
'--apply-plan',
|
|
370
|
+
'--save-plan',
|
|
323
371
|
])
|
|
324
372
|
for (let i = 1; i < args.length; i++) {
|
|
325
373
|
const arg = args[i]
|
|
@@ -342,6 +390,7 @@ async function main() {
|
|
|
342
390
|
'--headless',
|
|
343
391
|
'--yes',
|
|
344
392
|
'-y',
|
|
393
|
+
'--json',
|
|
345
394
|
].includes(bare)
|
|
346
395
|
) {
|
|
347
396
|
console.error(`⚠️ Unknown option ignored: ${arg}`)
|
|
@@ -349,20 +398,33 @@ async function main() {
|
|
|
349
398
|
}
|
|
350
399
|
|
|
351
400
|
const presetIndex = args.indexOf('--preset')
|
|
352
|
-
const presetOpt =
|
|
401
|
+
const presetOpt = reviewedPlan
|
|
402
|
+
? (reviewedPlan.options.preset ?? null)
|
|
403
|
+
: presetIndex >= 0
|
|
404
|
+
? (args[presetIndex + 1] ?? null)
|
|
405
|
+
: null
|
|
353
406
|
// Explicit top-level model overrides for the generated config.
|
|
354
407
|
const modelIndex = args.indexOf('--model')
|
|
355
|
-
const modelOpt =
|
|
408
|
+
const modelOpt = reviewedPlan
|
|
409
|
+
? (reviewedPlan.options.model ?? null)
|
|
410
|
+
: modelIndex >= 0
|
|
411
|
+
? (args[modelIndex + 1] ?? null)
|
|
412
|
+
: null
|
|
356
413
|
const smallModelIndex = args.indexOf('--small-model')
|
|
357
|
-
const smallModelOpt =
|
|
414
|
+
const smallModelOpt = reviewedPlan
|
|
415
|
+
? (reviewedPlan.options.smallModel ?? null)
|
|
416
|
+
: smallModelIndex >= 0
|
|
417
|
+
? (args[smallModelIndex + 1] ?? null)
|
|
418
|
+
: null
|
|
358
419
|
// --version v1|v2|auto labels the install target. `auto` resolves the
|
|
359
420
|
// generation from the opencode binary at install time (see
|
|
360
421
|
// scripts/install/opencode-version.mjs). Invalid values fail fast.
|
|
361
422
|
const versionIndex = args.indexOf('--version')
|
|
362
423
|
const legacyVersionIndex = args.indexOf('--opencode-version')
|
|
363
424
|
const inlineVersion = args.find((arg) => arg.startsWith('--opencode-version='))
|
|
364
|
-
const versionOpt =
|
|
365
|
-
|
|
425
|
+
const versionOpt = reviewedPlan
|
|
426
|
+
? reviewedPlan.options.version
|
|
427
|
+
: versionIndex >= 0
|
|
366
428
|
? (args[versionIndex + 1] ?? null)
|
|
367
429
|
: legacyVersionIndex >= 0
|
|
368
430
|
? (args[legacyVersionIndex + 1] ?? null)
|
|
@@ -386,8 +448,8 @@ async function main() {
|
|
|
386
448
|
componentsIndex >= 0
|
|
387
449
|
? (args[componentsIndex + 1] ?? null)
|
|
388
450
|
: (inlineComponents?.split('=', 2)[1] ?? null)
|
|
389
|
-
let requestedComponents = null
|
|
390
|
-
if (componentsOpt !== null) {
|
|
451
|
+
let requestedComponents = reviewedPlan ? [...reviewedPlan.options.components] : null
|
|
452
|
+
if (!reviewedPlan && componentsOpt !== null) {
|
|
391
453
|
requestedComponents = componentsOpt
|
|
392
454
|
.split(',')
|
|
393
455
|
.map((c) => c.trim())
|
|
@@ -408,8 +470,9 @@ async function main() {
|
|
|
408
470
|
// from the installer code, never from a dev checkout).
|
|
409
471
|
const mergeDevConfigIndex = args.indexOf('--merge-dev-config')
|
|
410
472
|
const inlineMergeDevConfig = args.find((arg) => arg.startsWith('--merge-dev-config='))
|
|
411
|
-
const mergeDevConfigOpt =
|
|
412
|
-
|
|
473
|
+
const mergeDevConfigOpt = reviewedPlan
|
|
474
|
+
? (reviewedPlan.options.mergeDevConfig ?? null)
|
|
475
|
+
: mergeDevConfigIndex >= 0
|
|
413
476
|
? (args[mergeDevConfigIndex + 1] ?? null)
|
|
414
477
|
: (inlineMergeDevConfig?.split('=', 2)[1] ?? null)
|
|
415
478
|
if (mergeDevConfigIndex >= 0 && mergeDevConfigOpt === null) {
|
|
@@ -417,15 +480,30 @@ async function main() {
|
|
|
417
480
|
process.exit(1)
|
|
418
481
|
}
|
|
419
482
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
483
|
+
if (reviewedPlan && !autoYes) {
|
|
484
|
+
if (!forceHeadless && process.stdin.isTTY && process.stdout.isTTY) {
|
|
485
|
+
const authorization = await authorizeReviewedPlan(reviewedPlan)
|
|
486
|
+
if (authorization === PROMPT_CANCELED) {
|
|
487
|
+
console.log('❌ Install canceled; no files were written.')
|
|
488
|
+
process.exitCode = 130
|
|
489
|
+
return
|
|
490
|
+
}
|
|
491
|
+
if (!authorization) {
|
|
492
|
+
console.log('❌ Install declined; no files were written.')
|
|
493
|
+
return
|
|
494
|
+
}
|
|
495
|
+
} else {
|
|
496
|
+
console.error('❌ Applying a reviewed plan requires --yes or an interactive TTY prompt.')
|
|
497
|
+
process.exit(2)
|
|
498
|
+
}
|
|
499
|
+
autoYes = true
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
const components = reviewedPlan
|
|
503
|
+
? requestedComponents
|
|
504
|
+
: (requestedComponents ?? ['agents', 'skills', 'instructions', 'commands', 'plugins'])
|
|
505
|
+
if (!reviewedPlan && !skipMCP && !components.includes('runtime')) components.push('runtime')
|
|
506
|
+
if (!reviewedPlan && skipMCP) {
|
|
429
507
|
const runtimeIndex = components.indexOf('runtime')
|
|
430
508
|
if (runtimeIndex >= 0) components.splice(runtimeIndex, 1)
|
|
431
509
|
}
|
|
@@ -435,33 +513,131 @@ async function main() {
|
|
|
435
513
|
const { strings } = await import('../scripts/install/strings.mjs')
|
|
436
514
|
const S = strings()
|
|
437
515
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
516
|
+
const planTimestamp = reviewedPlan?.planned_at ?? new Date().toISOString()
|
|
517
|
+
const stdoutWrite = process.stdout.write
|
|
518
|
+
if (isJson) process.stdout.write = () => true
|
|
519
|
+
if (!isJson) {
|
|
441
520
|
console.log('')
|
|
521
|
+
if (!forceInteractive || isDryRun) {
|
|
522
|
+
console.log(`Pantheon OpenCode v${version} — ${isDryRun ? S.dryRun : S.installing}`)
|
|
523
|
+
console.log('')
|
|
524
|
+
}
|
|
442
525
|
}
|
|
443
526
|
|
|
444
527
|
let result
|
|
528
|
+
let installError = null
|
|
445
529
|
try {
|
|
446
530
|
const { installOpenCode } = await import('../scripts/install/opencode.mjs')
|
|
447
|
-
const target = isProject ? process.cwd() : undefined
|
|
531
|
+
const target = reviewedPlan ? reviewedPlan.target.path : isProject ? process.cwd() : undefined
|
|
448
532
|
result = await installOpenCode(target, isDryRun, forceReinstall, components, {
|
|
449
533
|
interactive: forceInteractive,
|
|
450
|
-
headless: forceHeadless,
|
|
534
|
+
headless: reviewedPlan ? true : forceHeadless,
|
|
451
535
|
yes: autoYes,
|
|
452
536
|
model: modelOpt,
|
|
453
537
|
smallModel: smallModelOpt,
|
|
454
538
|
preset: presetOpt,
|
|
455
539
|
version: versionOpt ?? 'auto',
|
|
456
540
|
mergeDevConfig: mergeDevConfigOpt,
|
|
541
|
+
expectedPlan: reviewedPlan,
|
|
542
|
+
planTimestamp,
|
|
543
|
+
quiet: isJson,
|
|
457
544
|
})
|
|
458
545
|
} catch (err) {
|
|
459
|
-
|
|
546
|
+
installError = err
|
|
547
|
+
} finally {
|
|
548
|
+
if (isJson) process.stdout.write = stdoutWrite
|
|
549
|
+
}
|
|
550
|
+
if (installError) {
|
|
551
|
+
if (isJson) {
|
|
552
|
+
process.stdout.write(
|
|
553
|
+
`${JSON.stringify({
|
|
554
|
+
schema_version: 1,
|
|
555
|
+
command: 'init',
|
|
556
|
+
mode: 'dry-run',
|
|
557
|
+
status: 'error',
|
|
558
|
+
error: installError.message,
|
|
559
|
+
})}\n`,
|
|
560
|
+
)
|
|
561
|
+
console.error(`❌ Init dry-run failed: ${installError.message}`)
|
|
562
|
+
} else if (installError?.message === 'Canceled') {
|
|
460
563
|
console.error(S.canceled)
|
|
461
564
|
process.exit(130)
|
|
565
|
+
} else {
|
|
566
|
+
console.error(
|
|
567
|
+
`${S.installFailed(installError.message)}\n${S.failHintNoMcp}\n${S.failHintForce}`,
|
|
568
|
+
)
|
|
462
569
|
}
|
|
463
|
-
|
|
464
|
-
|
|
570
|
+
process.exitCode = 1
|
|
571
|
+
return
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
if (savePlanPath !== null && result?.reason === 'plan') {
|
|
575
|
+
try {
|
|
576
|
+
const artifact = createPlanArtifact({
|
|
577
|
+
target: result.target,
|
|
578
|
+
packageRoot: ROOT,
|
|
579
|
+
packageVersion: version,
|
|
580
|
+
plannedAt: planTimestamp,
|
|
581
|
+
artifactPath: savePlanPath,
|
|
582
|
+
options: {
|
|
583
|
+
clean: forceReinstall,
|
|
584
|
+
components,
|
|
585
|
+
version: result.openCodeVersion,
|
|
586
|
+
isProject,
|
|
587
|
+
model: modelOpt,
|
|
588
|
+
smallModel: smallModelOpt,
|
|
589
|
+
preset: presetOpt,
|
|
590
|
+
mergeDevConfig: mergeDevConfigOpt,
|
|
591
|
+
},
|
|
592
|
+
files: result.files,
|
|
593
|
+
snapshots: result.snapshots,
|
|
594
|
+
})
|
|
595
|
+
savePlanArtifact(savePlanPath, artifact)
|
|
596
|
+
} catch (error) {
|
|
597
|
+
if (!isJson) throw error
|
|
598
|
+
process.stdout.write(
|
|
599
|
+
`${JSON.stringify({
|
|
600
|
+
schema_version: 1,
|
|
601
|
+
command: 'init',
|
|
602
|
+
mode: 'dry-run',
|
|
603
|
+
status: 'error',
|
|
604
|
+
error: error.message,
|
|
605
|
+
})}\n`,
|
|
606
|
+
)
|
|
607
|
+
console.error(`❌ Could not save reviewed install plan: ${error.message}`)
|
|
608
|
+
process.exitCode = 1
|
|
609
|
+
return
|
|
610
|
+
}
|
|
611
|
+
if (!isJson) console.log(`Reviewed plan saved to ${path.resolve(savePlanPath)} (mode 0600).`)
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (isJson) {
|
|
615
|
+
const files = result.files ?? []
|
|
616
|
+
const totals = { create: 0, update: 0, remove: 0, unchanged: 0, conflict: 0 }
|
|
617
|
+
for (const file of files) {
|
|
618
|
+
if (Object.hasOwn(totals, file.action)) totals[file.action] += 1
|
|
619
|
+
}
|
|
620
|
+
process.stdout.write(
|
|
621
|
+
`${JSON.stringify({
|
|
622
|
+
schema_version: 1,
|
|
623
|
+
command: 'init',
|
|
624
|
+
mode: 'dry-run',
|
|
625
|
+
status: 'planned',
|
|
626
|
+
target: result.target,
|
|
627
|
+
open_code_version: result.openCodeVersion,
|
|
628
|
+
advisories: result.versionAssumed
|
|
629
|
+
? ['OpenCode host probe skipped to keep dry-run write-free; V1 was assumed.']
|
|
630
|
+
: [],
|
|
631
|
+
totals,
|
|
632
|
+
files: files.map(({ path: filePath, action, bytes }) => ({
|
|
633
|
+
path: path.relative(result.target, filePath) || '.',
|
|
634
|
+
action,
|
|
635
|
+
bytes,
|
|
636
|
+
})),
|
|
637
|
+
saved_plan: savePlanPath ? path.resolve(savePlanPath) : null,
|
|
638
|
+
})}\n`,
|
|
639
|
+
)
|
|
640
|
+
return
|
|
465
641
|
}
|
|
466
642
|
|
|
467
643
|
// Beta 11 Setup phases 3-4: a run that did NOT apply (declined /
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-opencode",
|
|
3
|
-
"version": "1.6.0-beta.
|
|
3
|
+
"version": "1.6.0-beta.13",
|
|
4
4
|
"description": "Multi-agent orchestration for OpenCode — 14 specialized agents, TDD, Themis review gates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"workspaces": [
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"version:check": "node scripts/version-check.mjs",
|
|
34
34
|
"release:dry-run": "node scripts/versioning.mjs status && node scripts/release-notes.mjs && node scripts/package-evidence.mjs",
|
|
35
35
|
"package:evidence": "node scripts/package-evidence.mjs",
|
|
36
|
-
"
|
|
36
|
+
"engine:check": "node scripts/validate-node-engine-policy.mjs",
|
|
37
|
+
"package:validate": "node scripts/validate-package.mjs && npm run engine:check",
|
|
37
38
|
"lint": "biome ci .",
|
|
38
39
|
"typecheck": "tsc --noEmit",
|
|
39
40
|
"audit": "npm audit --audit-level=high",
|
|
@@ -98,6 +99,7 @@
|
|
|
98
99
|
"!**/*.pyc"
|
|
99
100
|
],
|
|
100
101
|
"dependencies": {
|
|
102
|
+
"@clack/prompts": "1.8.1",
|
|
101
103
|
"@opencode-ai/plugin": "1.18.33",
|
|
102
104
|
"js-yaml": "^5.4.2",
|
|
103
105
|
"zod": "4.1.8",
|
|
@@ -138,7 +140,7 @@
|
|
|
138
140
|
"author": "ils15",
|
|
139
141
|
"license": "MIT",
|
|
140
142
|
"engines": {
|
|
141
|
-
"node": "
|
|
143
|
+
"node": ">=26.4.0"
|
|
142
144
|
},
|
|
143
145
|
"preferGlobal": true
|
|
144
146
|
}
|
package/plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon",
|
|
3
3
|
"publisher": "ils15",
|
|
4
|
-
"version": "1.6.0-beta.
|
|
4
|
+
"version": "1.6.0-beta.13",
|
|
5
5
|
"displayName": "Pantheon",
|
|
6
6
|
"description": "Conductor-Delegate orchestration platform with 14 specialized agents, 20 skills, and OpenCode support.",
|
|
7
7
|
"agents": "./src/agents",
|
package/pyproject.toml
CHANGED