pantheon-opencode 1.6.0-beta.11 → 1.6.0-beta.12
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 +22 -0
- package/README.md +69 -30
- package/README.pt-BR.md +5 -4
- package/bin/pantheon-init.mjs +279 -101
- package/package.json +4 -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/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 +265 -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/shared.mjs +186 -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 +74 -0
- package/scripts/package-evidence.mjs +6 -0
- package/scripts/postinstall.mjs +3 -4
- package/scripts/reconcile.mjs +325 -0
- 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,15 +20,25 @@ 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 {
|
|
30
|
+
isSupportedNodeVersion,
|
|
31
|
+
MIN_SUPPORTED_NODE_VERSION,
|
|
32
|
+
} from '../scripts/node-engine-policy.mjs'
|
|
33
|
+
import { runUpdate } from '../scripts/update.mjs'
|
|
25
34
|
|
|
26
35
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
27
36
|
const ROOT = path.resolve(__dirname, '..')
|
|
28
37
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
38
|
+
if (!isSupportedNodeVersion(process.versions.node)) {
|
|
39
|
+
console.error(
|
|
40
|
+
`❌ Pantheon requires Node.js >= ${MIN_SUPPORTED_NODE_VERSION} (current: ${process.versions.node})`,
|
|
41
|
+
)
|
|
32
42
|
console.error(' Install a newer Node via nvm: https://github.com/nvm-sh/nvm')
|
|
33
43
|
process.exit(1)
|
|
34
44
|
}
|
|
@@ -49,6 +59,9 @@ function printUsage() {
|
|
|
49
59
|
console.log(' npx pantheon-opencode init # Install globally')
|
|
50
60
|
console.log(' npx pantheon-opencode init --project # Install in project')
|
|
51
61
|
console.log(' npx pantheon-opencode init --dry-run # Preview only')
|
|
62
|
+
console.log(' npx pantheon-opencode init --dry-run --save-plan <file> # Save reviewed plan')
|
|
63
|
+
console.log(' npx pantheon-opencode init --apply-plan <file> [--yes] # Apply reviewed plan')
|
|
64
|
+
console.log(' npx pantheon-opencode init --dry-run --json # Versioned JSON preview')
|
|
52
65
|
console.log(' npx pantheon-opencode init --no-mcp # Skip MCP + venv')
|
|
53
66
|
console.log(' npx pantheon-opencode init --force # Overwrite + recreate venv')
|
|
54
67
|
console.log(' npx pantheon-opencode init --doctor # Run health check after install')
|
|
@@ -76,6 +89,7 @@ function printUsage() {
|
|
|
76
89
|
)
|
|
77
90
|
console.log(' npx pantheon-opencode uninstall --project # Remove project artifacts safely')
|
|
78
91
|
console.log(' npx pantheon-opencode uninstall --global # Remove global artifacts safely')
|
|
92
|
+
console.log(' npx pantheon-opencode reconcile --project # Review stale owned installer files')
|
|
79
93
|
console.log(' npx pantheon-opencode --version, -v # Print version and exit')
|
|
80
94
|
console.log(' npx pantheon-opencode --help # Show this help')
|
|
81
95
|
console.log('')
|
|
@@ -91,6 +105,22 @@ function printUsage() {
|
|
|
91
105
|
console.log(' 130 canceled')
|
|
92
106
|
}
|
|
93
107
|
|
|
108
|
+
async function authorizeReviewedPlan(plan) {
|
|
109
|
+
const operations = plan.operations.filter((operation) => operation.action !== 'unchanged')
|
|
110
|
+
console.log(`Reviewed plan: ${plan.target.path} (${operations.length} change(s))`)
|
|
111
|
+
for (const operation of operations) {
|
|
112
|
+
console.log(` ${operation.action.padEnd(9)} ${operation.path}`)
|
|
113
|
+
}
|
|
114
|
+
const readline = await import('node:readline/promises')
|
|
115
|
+
const prompt = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
116
|
+
try {
|
|
117
|
+
const answer = await prompt.question('Apply this reviewed plan? [y/N] ')
|
|
118
|
+
return ['y', 'yes'].includes(answer.trim().toLowerCase())
|
|
119
|
+
} finally {
|
|
120
|
+
prompt.close()
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
94
124
|
async function main() {
|
|
95
125
|
const args = process.argv.slice(2)
|
|
96
126
|
const command = args[0]
|
|
@@ -125,6 +155,16 @@ async function main() {
|
|
|
125
155
|
process.exit(result.status ?? 1)
|
|
126
156
|
}
|
|
127
157
|
|
|
158
|
+
if (command === 'reconcile') {
|
|
159
|
+
const { spawnSync } = await import('node:child_process')
|
|
160
|
+
const reconcileScript = path.join(ROOT, 'scripts', 'reconcile.mjs')
|
|
161
|
+
const result = spawnSync(process.execPath, [reconcileScript, ...args.slice(1)], {
|
|
162
|
+
stdio: 'inherit',
|
|
163
|
+
cwd: process.cwd(),
|
|
164
|
+
})
|
|
165
|
+
process.exit(result.status ?? 1)
|
|
166
|
+
}
|
|
167
|
+
|
|
128
168
|
// restore delegates to scripts/restore.mjs (same spawnSync pattern as prune).
|
|
129
169
|
if (command === 'restore') {
|
|
130
170
|
const { spawnSync } = await import('node:child_process')
|
|
@@ -152,72 +192,12 @@ async function main() {
|
|
|
152
192
|
// beta.5: one-command update — check npm dist-tag, install, re-init.
|
|
153
193
|
// Default channel is `beta` (the prerelease line); `--stable` uses `latest`.
|
|
154
194
|
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,
|
|
195
|
+
const status = runUpdate(args, {
|
|
196
|
+
root: ROOT,
|
|
197
|
+
currentVersion: readVersion(),
|
|
198
|
+
runningScript: process.argv[1],
|
|
164
199
|
})
|
|
165
|
-
|
|
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',
|
|
202
|
-
})
|
|
203
|
-
if (install.status !== 0) {
|
|
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}.`)
|
|
200
|
+
if (status !== 0) process.exit(status)
|
|
221
201
|
return
|
|
222
202
|
}
|
|
223
203
|
|
|
@@ -301,15 +281,89 @@ async function main() {
|
|
|
301
281
|
}
|
|
302
282
|
|
|
303
283
|
if (command === 'init' || !command) {
|
|
304
|
-
const
|
|
305
|
-
const
|
|
284
|
+
const applyPlanIndex = args.indexOf('--apply-plan')
|
|
285
|
+
const inlineApplyPlan = args.find((arg) => arg.startsWith('--apply-plan='))
|
|
286
|
+
const applyPlanPath =
|
|
287
|
+
applyPlanIndex >= 0
|
|
288
|
+
? (args[applyPlanIndex + 1] ?? null)
|
|
289
|
+
: (inlineApplyPlan?.split('=', 2)[1] ?? null)
|
|
290
|
+
if (applyPlanIndex >= 0 && !applyPlanPath) {
|
|
291
|
+
console.error('❌ --apply-plan requires a plan file path')
|
|
292
|
+
process.exit(1)
|
|
293
|
+
}
|
|
294
|
+
let reviewedPlan = null
|
|
295
|
+
if (applyPlanPath !== null) {
|
|
296
|
+
const allowed = new Set(['init', '--apply-plan', applyPlanPath, '--yes', '-y', '--headless'])
|
|
297
|
+
const normalized = args.map((value) =>
|
|
298
|
+
value.startsWith('--apply-plan=') ? '--apply-plan' : value,
|
|
299
|
+
)
|
|
300
|
+
if (normalized.some((value) => !allowed.has(value))) {
|
|
301
|
+
console.error('❌ --apply-plan accepts only the plan path and fresh authorization flags')
|
|
302
|
+
process.exit(1)
|
|
303
|
+
}
|
|
304
|
+
reviewedPlan = loadPlanArtifact(applyPlanPath)
|
|
305
|
+
if (
|
|
306
|
+
reviewedPlan.artifact_path &&
|
|
307
|
+
path.resolve(applyPlanPath) !== reviewedPlan.artifact_path
|
|
308
|
+
) {
|
|
309
|
+
console.error('❌ The reviewed plan artifact was moved; use its original path')
|
|
310
|
+
process.exit(1)
|
|
311
|
+
}
|
|
312
|
+
const options = reviewedPlan.options
|
|
313
|
+
if (
|
|
314
|
+
!Array.isArray(options.components) ||
|
|
315
|
+
options.components.some(
|
|
316
|
+
(component) =>
|
|
317
|
+
!['agents', 'skills', 'instructions', 'commands', 'plugins', 'runtime'].includes(
|
|
318
|
+
component,
|
|
319
|
+
),
|
|
320
|
+
) ||
|
|
321
|
+
options.clean !== false ||
|
|
322
|
+
!['v1', 'v2', 'auto'].includes(options.version) ||
|
|
323
|
+
typeof options.isProject !== 'boolean'
|
|
324
|
+
) {
|
|
325
|
+
console.error('❌ Install-plan options are invalid or unsupported')
|
|
326
|
+
process.exit(1)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const isProject = reviewedPlan ? reviewedPlan.options.isProject : args.includes('--project')
|
|
330
|
+
const isDryRun = reviewedPlan ? false : args.includes('--dry-run')
|
|
306
331
|
const skipMCP = args.includes('--no-mcp')
|
|
307
332
|
// --clean is an alias of --force (both wipe component dirs + recreate venv).
|
|
308
|
-
const forceReinstall =
|
|
309
|
-
|
|
310
|
-
|
|
333
|
+
const forceReinstall = reviewedPlan
|
|
334
|
+
? reviewedPlan.options.clean
|
|
335
|
+
: args.includes('--force') || args.includes('--clean')
|
|
336
|
+
const runDoctor = !reviewedPlan && args.includes('--doctor')
|
|
337
|
+
const forceInteractive = !reviewedPlan && args.includes('--interactive')
|
|
311
338
|
const forceHeadless = args.includes('--headless')
|
|
312
|
-
|
|
339
|
+
let autoYes = args.includes('--yes') || args.includes('-y')
|
|
340
|
+
const isJson = args.includes('--json')
|
|
341
|
+
const savePlanIndex = args.indexOf('--save-plan')
|
|
342
|
+
const inlineSavePlan = args.find((arg) => arg.startsWith('--save-plan='))
|
|
343
|
+
const savePlanPath =
|
|
344
|
+
savePlanIndex >= 0
|
|
345
|
+
? (args[savePlanIndex + 1] ?? null)
|
|
346
|
+
: (inlineSavePlan?.split('=', 2)[1] ?? null)
|
|
347
|
+
if (savePlanIndex >= 0 && !savePlanPath) {
|
|
348
|
+
console.error('❌ --save-plan requires a plan file path')
|
|
349
|
+
process.exit(1)
|
|
350
|
+
}
|
|
351
|
+
if (savePlanPath !== null && (!isDryRun || reviewedPlan)) {
|
|
352
|
+
console.error('❌ --save-plan is available only with init --dry-run')
|
|
353
|
+
process.exit(1)
|
|
354
|
+
}
|
|
355
|
+
if (savePlanPath !== null && forceReinstall) {
|
|
356
|
+
console.error('❌ Saving a reviewed plan with --force/--clean is not supported safely')
|
|
357
|
+
process.exit(1)
|
|
358
|
+
}
|
|
359
|
+
if (isJson && (!isDryRun || reviewedPlan)) {
|
|
360
|
+
console.error('❌ --json is supported only with init --dry-run')
|
|
361
|
+
process.exit(1)
|
|
362
|
+
}
|
|
363
|
+
if (isJson && forceReinstall) {
|
|
364
|
+
console.error('❌ --force/--clean cleanup is not supported with --json')
|
|
365
|
+
process.exit(1)
|
|
366
|
+
}
|
|
313
367
|
|
|
314
368
|
// beta.5: warn on unrecognized flags instead of silently ignoring them.
|
|
315
369
|
const VALUE_FLAGS = new Set([
|
|
@@ -320,6 +374,8 @@ async function main() {
|
|
|
320
374
|
'--opencode-version',
|
|
321
375
|
'--components',
|
|
322
376
|
'--merge-dev-config',
|
|
377
|
+
'--apply-plan',
|
|
378
|
+
'--save-plan',
|
|
323
379
|
])
|
|
324
380
|
for (let i = 1; i < args.length; i++) {
|
|
325
381
|
const arg = args[i]
|
|
@@ -342,6 +398,7 @@ async function main() {
|
|
|
342
398
|
'--headless',
|
|
343
399
|
'--yes',
|
|
344
400
|
'-y',
|
|
401
|
+
'--json',
|
|
345
402
|
].includes(bare)
|
|
346
403
|
) {
|
|
347
404
|
console.error(`⚠️ Unknown option ignored: ${arg}`)
|
|
@@ -349,20 +406,33 @@ async function main() {
|
|
|
349
406
|
}
|
|
350
407
|
|
|
351
408
|
const presetIndex = args.indexOf('--preset')
|
|
352
|
-
const presetOpt =
|
|
409
|
+
const presetOpt = reviewedPlan
|
|
410
|
+
? (reviewedPlan.options.preset ?? null)
|
|
411
|
+
: presetIndex >= 0
|
|
412
|
+
? (args[presetIndex + 1] ?? null)
|
|
413
|
+
: null
|
|
353
414
|
// Explicit top-level model overrides for the generated config.
|
|
354
415
|
const modelIndex = args.indexOf('--model')
|
|
355
|
-
const modelOpt =
|
|
416
|
+
const modelOpt = reviewedPlan
|
|
417
|
+
? (reviewedPlan.options.model ?? null)
|
|
418
|
+
: modelIndex >= 0
|
|
419
|
+
? (args[modelIndex + 1] ?? null)
|
|
420
|
+
: null
|
|
356
421
|
const smallModelIndex = args.indexOf('--small-model')
|
|
357
|
-
const smallModelOpt =
|
|
422
|
+
const smallModelOpt = reviewedPlan
|
|
423
|
+
? (reviewedPlan.options.smallModel ?? null)
|
|
424
|
+
: smallModelIndex >= 0
|
|
425
|
+
? (args[smallModelIndex + 1] ?? null)
|
|
426
|
+
: null
|
|
358
427
|
// --version v1|v2|auto labels the install target. `auto` resolves the
|
|
359
428
|
// generation from the opencode binary at install time (see
|
|
360
429
|
// scripts/install/opencode-version.mjs). Invalid values fail fast.
|
|
361
430
|
const versionIndex = args.indexOf('--version')
|
|
362
431
|
const legacyVersionIndex = args.indexOf('--opencode-version')
|
|
363
432
|
const inlineVersion = args.find((arg) => arg.startsWith('--opencode-version='))
|
|
364
|
-
const versionOpt =
|
|
365
|
-
|
|
433
|
+
const versionOpt = reviewedPlan
|
|
434
|
+
? reviewedPlan.options.version
|
|
435
|
+
: versionIndex >= 0
|
|
366
436
|
? (args[versionIndex + 1] ?? null)
|
|
367
437
|
: legacyVersionIndex >= 0
|
|
368
438
|
? (args[legacyVersionIndex + 1] ?? null)
|
|
@@ -386,8 +456,8 @@ async function main() {
|
|
|
386
456
|
componentsIndex >= 0
|
|
387
457
|
? (args[componentsIndex + 1] ?? null)
|
|
388
458
|
: (inlineComponents?.split('=', 2)[1] ?? null)
|
|
389
|
-
let requestedComponents = null
|
|
390
|
-
if (componentsOpt !== null) {
|
|
459
|
+
let requestedComponents = reviewedPlan ? [...reviewedPlan.options.components] : null
|
|
460
|
+
if (!reviewedPlan && componentsOpt !== null) {
|
|
391
461
|
requestedComponents = componentsOpt
|
|
392
462
|
.split(',')
|
|
393
463
|
.map((c) => c.trim())
|
|
@@ -408,8 +478,9 @@ async function main() {
|
|
|
408
478
|
// from the installer code, never from a dev checkout).
|
|
409
479
|
const mergeDevConfigIndex = args.indexOf('--merge-dev-config')
|
|
410
480
|
const inlineMergeDevConfig = args.find((arg) => arg.startsWith('--merge-dev-config='))
|
|
411
|
-
const mergeDevConfigOpt =
|
|
412
|
-
|
|
481
|
+
const mergeDevConfigOpt = reviewedPlan
|
|
482
|
+
? (reviewedPlan.options.mergeDevConfig ?? null)
|
|
483
|
+
: mergeDevConfigIndex >= 0
|
|
413
484
|
? (args[mergeDevConfigIndex + 1] ?? null)
|
|
414
485
|
: (inlineMergeDevConfig?.split('=', 2)[1] ?? null)
|
|
415
486
|
if (mergeDevConfigIndex >= 0 && mergeDevConfigOpt === null) {
|
|
@@ -417,15 +488,24 @@ async function main() {
|
|
|
417
488
|
process.exit(1)
|
|
418
489
|
}
|
|
419
490
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
491
|
+
if (reviewedPlan && !autoYes) {
|
|
492
|
+
if (!forceHeadless && process.stdin.isTTY && process.stdout.isTTY) {
|
|
493
|
+
if (!(await authorizeReviewedPlan(reviewedPlan))) {
|
|
494
|
+
console.log('❌ Install canceled; no files were written.')
|
|
495
|
+
return
|
|
496
|
+
}
|
|
497
|
+
} else {
|
|
498
|
+
console.error('❌ Applying a reviewed plan requires --yes or an interactive TTY prompt.')
|
|
499
|
+
process.exit(2)
|
|
500
|
+
}
|
|
501
|
+
autoYes = true
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
const components = reviewedPlan
|
|
505
|
+
? requestedComponents
|
|
506
|
+
: (requestedComponents ?? ['agents', 'skills', 'instructions', 'commands', 'plugins'])
|
|
507
|
+
if (!reviewedPlan && !skipMCP && !components.includes('runtime')) components.push('runtime')
|
|
508
|
+
if (!reviewedPlan && skipMCP) {
|
|
429
509
|
const runtimeIndex = components.indexOf('runtime')
|
|
430
510
|
if (runtimeIndex >= 0) components.splice(runtimeIndex, 1)
|
|
431
511
|
}
|
|
@@ -435,33 +515,131 @@ async function main() {
|
|
|
435
515
|
const { strings } = await import('../scripts/install/strings.mjs')
|
|
436
516
|
const S = strings()
|
|
437
517
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
518
|
+
const planTimestamp = reviewedPlan?.planned_at ?? new Date().toISOString()
|
|
519
|
+
const stdoutWrite = process.stdout.write
|
|
520
|
+
if (isJson) process.stdout.write = () => true
|
|
521
|
+
if (!isJson) {
|
|
441
522
|
console.log('')
|
|
523
|
+
if (!forceInteractive || isDryRun) {
|
|
524
|
+
console.log(`Pantheon OpenCode v${version} — ${isDryRun ? S.dryRun : S.installing}`)
|
|
525
|
+
console.log('')
|
|
526
|
+
}
|
|
442
527
|
}
|
|
443
528
|
|
|
444
529
|
let result
|
|
530
|
+
let installError = null
|
|
445
531
|
try {
|
|
446
532
|
const { installOpenCode } = await import('../scripts/install/opencode.mjs')
|
|
447
|
-
const target = isProject ? process.cwd() : undefined
|
|
533
|
+
const target = reviewedPlan ? reviewedPlan.target.path : isProject ? process.cwd() : undefined
|
|
448
534
|
result = await installOpenCode(target, isDryRun, forceReinstall, components, {
|
|
449
535
|
interactive: forceInteractive,
|
|
450
|
-
headless: forceHeadless,
|
|
536
|
+
headless: reviewedPlan ? true : forceHeadless,
|
|
451
537
|
yes: autoYes,
|
|
452
538
|
model: modelOpt,
|
|
453
539
|
smallModel: smallModelOpt,
|
|
454
540
|
preset: presetOpt,
|
|
455
541
|
version: versionOpt ?? 'auto',
|
|
456
542
|
mergeDevConfig: mergeDevConfigOpt,
|
|
543
|
+
expectedPlan: reviewedPlan,
|
|
544
|
+
planTimestamp,
|
|
545
|
+
quiet: isJson,
|
|
457
546
|
})
|
|
458
547
|
} catch (err) {
|
|
459
|
-
|
|
548
|
+
installError = err
|
|
549
|
+
} finally {
|
|
550
|
+
if (isJson) process.stdout.write = stdoutWrite
|
|
551
|
+
}
|
|
552
|
+
if (installError) {
|
|
553
|
+
if (isJson) {
|
|
554
|
+
process.stdout.write(
|
|
555
|
+
`${JSON.stringify({
|
|
556
|
+
schema_version: 1,
|
|
557
|
+
command: 'init',
|
|
558
|
+
mode: 'dry-run',
|
|
559
|
+
status: 'error',
|
|
560
|
+
error: installError.message,
|
|
561
|
+
})}\n`,
|
|
562
|
+
)
|
|
563
|
+
console.error(`❌ Init dry-run failed: ${installError.message}`)
|
|
564
|
+
} else if (installError?.message === 'Canceled') {
|
|
460
565
|
console.error(S.canceled)
|
|
461
566
|
process.exit(130)
|
|
567
|
+
} else {
|
|
568
|
+
console.error(
|
|
569
|
+
`${S.installFailed(installError.message)}\n${S.failHintNoMcp}\n${S.failHintForce}`,
|
|
570
|
+
)
|
|
462
571
|
}
|
|
463
|
-
|
|
464
|
-
|
|
572
|
+
process.exitCode = 1
|
|
573
|
+
return
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (savePlanPath !== null && result?.reason === 'plan') {
|
|
577
|
+
try {
|
|
578
|
+
const artifact = createPlanArtifact({
|
|
579
|
+
target: result.target,
|
|
580
|
+
packageRoot: ROOT,
|
|
581
|
+
packageVersion: version,
|
|
582
|
+
plannedAt: planTimestamp,
|
|
583
|
+
artifactPath: savePlanPath,
|
|
584
|
+
options: {
|
|
585
|
+
clean: forceReinstall,
|
|
586
|
+
components,
|
|
587
|
+
version: result.openCodeVersion,
|
|
588
|
+
isProject,
|
|
589
|
+
model: modelOpt,
|
|
590
|
+
smallModel: smallModelOpt,
|
|
591
|
+
preset: presetOpt,
|
|
592
|
+
mergeDevConfig: mergeDevConfigOpt,
|
|
593
|
+
},
|
|
594
|
+
files: result.files,
|
|
595
|
+
snapshots: result.snapshots,
|
|
596
|
+
})
|
|
597
|
+
savePlanArtifact(savePlanPath, artifact)
|
|
598
|
+
} catch (error) {
|
|
599
|
+
if (!isJson) throw error
|
|
600
|
+
process.stdout.write(
|
|
601
|
+
`${JSON.stringify({
|
|
602
|
+
schema_version: 1,
|
|
603
|
+
command: 'init',
|
|
604
|
+
mode: 'dry-run',
|
|
605
|
+
status: 'error',
|
|
606
|
+
error: error.message,
|
|
607
|
+
})}\n`,
|
|
608
|
+
)
|
|
609
|
+
console.error(`❌ Could not save reviewed install plan: ${error.message}`)
|
|
610
|
+
process.exitCode = 1
|
|
611
|
+
return
|
|
612
|
+
}
|
|
613
|
+
if (!isJson) console.log(`Reviewed plan saved to ${path.resolve(savePlanPath)} (mode 0600).`)
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
if (isJson) {
|
|
617
|
+
const files = result.files ?? []
|
|
618
|
+
const totals = { create: 0, update: 0, remove: 0, unchanged: 0, conflict: 0 }
|
|
619
|
+
for (const file of files) {
|
|
620
|
+
if (Object.hasOwn(totals, file.action)) totals[file.action] += 1
|
|
621
|
+
}
|
|
622
|
+
process.stdout.write(
|
|
623
|
+
`${JSON.stringify({
|
|
624
|
+
schema_version: 1,
|
|
625
|
+
command: 'init',
|
|
626
|
+
mode: 'dry-run',
|
|
627
|
+
status: 'planned',
|
|
628
|
+
target: result.target,
|
|
629
|
+
open_code_version: result.openCodeVersion,
|
|
630
|
+
advisories: result.versionAssumed
|
|
631
|
+
? ['OpenCode host probe skipped to keep dry-run write-free; V1 was assumed.']
|
|
632
|
+
: [],
|
|
633
|
+
totals,
|
|
634
|
+
files: files.map(({ path: filePath, action, bytes }) => ({
|
|
635
|
+
path: path.relative(result.target, filePath) || '.',
|
|
636
|
+
action,
|
|
637
|
+
bytes,
|
|
638
|
+
})),
|
|
639
|
+
saved_plan: savePlanPath ? path.resolve(savePlanPath) : null,
|
|
640
|
+
})}\n`,
|
|
641
|
+
)
|
|
642
|
+
return
|
|
465
643
|
}
|
|
466
644
|
|
|
467
645
|
// 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.12",
|
|
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",
|
|
@@ -138,7 +139,7 @@
|
|
|
138
139
|
"author": "ils15",
|
|
139
140
|
"license": "MIT",
|
|
140
141
|
"engines": {
|
|
141
|
-
"node": "
|
|
142
|
+
"node": ">=26.4.0"
|
|
142
143
|
},
|
|
143
144
|
"preferGlobal": true
|
|
144
145
|
}
|
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.12",
|
|
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