pantheon-opencode 1.6.0-beta.10 → 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 +42 -18
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +46 -0
- package/README.md +122 -38
- package/README.pt-BR.md +5 -4
- package/bin/pantheon-init.mjs +322 -101
- package/package.json +7 -6
- package/plugin.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/build-agents-md.mjs +128 -10
- package/scripts/check-plugin-v2-coverage.mjs +7 -7
- package/scripts/doctor.mjs +242 -19
- package/scripts/install/backup.mjs +648 -0
- package/scripts/install/cli-ui.mjs +79 -1
- package/scripts/install/exit-codes.mjs +26 -0
- package/scripts/install/file-inventory.mjs +555 -0
- package/scripts/install/interactive.mjs +15 -1
- package/scripts/install/migrate.mjs +32 -6
- package/scripts/install/model-picker.mjs +63 -22
- package/scripts/install/opencode-version.mjs +9 -1
- package/scripts/install/opencode.mjs +761 -63
- package/scripts/install/plan-artifact.mjs +517 -0
- package/scripts/install/plan.mjs +449 -0
- package/scripts/install/plugin.mjs +136 -18
- package/scripts/install/shared.mjs +249 -94
- package/scripts/install/state.mjs +63 -8
- package/scripts/install/strings.mjs +20 -0
- package/scripts/install/venv.mjs +25 -6
- package/scripts/install/verify.mjs +128 -0
- package/scripts/install/vision-prompt.mjs +13 -1
- package/scripts/install-mcp.mjs +1 -1
- package/scripts/manifest-inventory.mjs +49 -3
- package/scripts/mcp_persistence.py +50 -15
- package/scripts/node-engine-policy.mjs +74 -0
- package/scripts/package-evidence.mjs +6 -0
- package/scripts/postinstall.mjs +3 -4
- package/scripts/prune.mjs +9 -3
- package/scripts/reconcile.mjs +325 -0
- package/scripts/restore.mjs +222 -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 +4 -4
- package/src/agents/iris.md +6 -0
- package/src/agents/mnemosyne.md +7 -2
- package/src/agents/zeus.md +19 -62
- package/src/instructions/agent-return-format.instructions.md +17 -15
- package/src/instructions/memory-protocol.instructions.md +3 -2
- package/src/instructions/test-economy.instructions.md +21 -0
- package/src/instructions/zeus-anti-stall.instructions.md +6 -6
- package/src/instructions/zeus-council-synthesis.instructions.md +10 -11
- package/src/instructions/zeus-memory-operations.instructions.md +11 -6
- package/src/instructions/zeus-timeout-retry.instructions.md +28 -9
- package/src/mcp/mcp_persistence.py +50 -15
- package/src/pantheon/cost-command.ts +5 -2
- package/src/pantheon/model-agents.ts +19 -0
- package/src/pantheon/model-command.ts +218 -56
- package/src/pantheon/v2-model-overrides.ts +275 -0
- package/src/pantheon/v2-tools.ts +18 -29
- package/src/pantheon/v2-unsupported.mjs +2 -3
- package/src/plugin-v2.ts +13 -0
- 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/auto-continue/SKILL.md +14 -6
- package/src/skills/memory-bank/SKILL.md +5 -1
- package/src/skills/worktrees/SKILL.md +30 -11
package/bin/pantheon-init.mjs
CHANGED
|
@@ -20,13 +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
|
+
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'
|
|
23
34
|
|
|
24
35
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
25
36
|
const ROOT = path.resolve(__dirname, '..')
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
if (!isSupportedNodeVersion(process.versions.node)) {
|
|
39
|
+
console.error(
|
|
40
|
+
`❌ Pantheon requires Node.js >= ${MIN_SUPPORTED_NODE_VERSION} (current: ${process.versions.node})`,
|
|
41
|
+
)
|
|
30
42
|
console.error(' Install a newer Node via nvm: https://github.com/nvm-sh/nvm')
|
|
31
43
|
process.exit(1)
|
|
32
44
|
}
|
|
@@ -47,6 +59,9 @@ function printUsage() {
|
|
|
47
59
|
console.log(' npx pantheon-opencode init # Install globally')
|
|
48
60
|
console.log(' npx pantheon-opencode init --project # Install in project')
|
|
49
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')
|
|
50
65
|
console.log(' npx pantheon-opencode init --no-mcp # Skip MCP + venv')
|
|
51
66
|
console.log(' npx pantheon-opencode init --force # Overwrite + recreate venv')
|
|
52
67
|
console.log(' npx pantheon-opencode init --doctor # Run health check after install')
|
|
@@ -69,10 +84,41 @@ function printUsage() {
|
|
|
69
84
|
console.log(' npx pantheon-opencode prune # List legacy artifacts (dry-run)')
|
|
70
85
|
console.log(' npx pantheon-opencode prune --apply # Remove stale backups')
|
|
71
86
|
console.log(' npx pantheon-opencode doctor [--profile] # Run health check')
|
|
87
|
+
console.log(
|
|
88
|
+
' npx pantheon-opencode restore [--project] [--yes] # Restore an exact installer backup',
|
|
89
|
+
)
|
|
72
90
|
console.log(' npx pantheon-opencode uninstall --project # Remove project artifacts safely')
|
|
73
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')
|
|
74
93
|
console.log(' npx pantheon-opencode --version, -v # Print version and exit')
|
|
75
94
|
console.log(' npx pantheon-opencode --help # Show this help')
|
|
95
|
+
console.log('')
|
|
96
|
+
console.log('Apply authorization:')
|
|
97
|
+
console.log(' A non-interactive run without --yes prints the plan and applies NOTHING.')
|
|
98
|
+
console.log(' Re-run with --yes to apply; an interactive TTY run prompts (default: no).')
|
|
99
|
+
console.log('')
|
|
100
|
+
console.log('Exit codes:')
|
|
101
|
+
console.log(' 0 success, or plan/no-apply (declined)')
|
|
102
|
+
console.log(' 1 failure')
|
|
103
|
+
console.log(' 2 apply was requested without authorization (non-interactive, no --yes)')
|
|
104
|
+
console.log(' 3 drift detected (a planned file changed after the plan)')
|
|
105
|
+
console.log(' 130 canceled')
|
|
106
|
+
}
|
|
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
|
+
}
|
|
76
122
|
}
|
|
77
123
|
|
|
78
124
|
async function main() {
|
|
@@ -109,6 +155,27 @@ async function main() {
|
|
|
109
155
|
process.exit(result.status ?? 1)
|
|
110
156
|
}
|
|
111
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
|
+
|
|
168
|
+
// restore delegates to scripts/restore.mjs (same spawnSync pattern as prune).
|
|
169
|
+
if (command === 'restore') {
|
|
170
|
+
const { spawnSync } = await import('node:child_process')
|
|
171
|
+
const restoreScript = path.join(ROOT, 'scripts', 'restore.mjs')
|
|
172
|
+
const result = spawnSync(process.execPath, [restoreScript, ...args.slice(1)], {
|
|
173
|
+
stdio: 'inherit',
|
|
174
|
+
cwd: process.cwd(),
|
|
175
|
+
})
|
|
176
|
+
process.exit(result.status ?? 1)
|
|
177
|
+
}
|
|
178
|
+
|
|
112
179
|
// doctor delegates to scripts/doctor.mjs (same spawnSync pattern as prune).
|
|
113
180
|
// Forwarding the FULL args (profile, target, --fix, ...) keeps the packaged
|
|
114
181
|
// CLI a thin wrapper; the doctor banner reports the resolved profile.
|
|
@@ -125,72 +192,12 @@ async function main() {
|
|
|
125
192
|
// beta.5: one-command update — check npm dist-tag, install, re-init.
|
|
126
193
|
// Default channel is `beta` (the prerelease line); `--stable` uses `latest`.
|
|
127
194
|
if (command === 'update') {
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
console.log(`Pantheon OpenCode v${current} — checking the "${channel}" channel...`)
|
|
134
|
-
const view = spawnSync('npm', ['view', `pantheon-opencode@${channel}`, 'version'], {
|
|
135
|
-
encoding: 'utf8',
|
|
136
|
-
timeout: 30_000,
|
|
195
|
+
const status = runUpdate(args, {
|
|
196
|
+
root: ROOT,
|
|
197
|
+
currentVersion: readVersion(),
|
|
198
|
+
runningScript: process.argv[1],
|
|
137
199
|
})
|
|
138
|
-
|
|
139
|
-
if (view.status !== 0 || !latest) {
|
|
140
|
-
console.error(
|
|
141
|
-
`❌ Could not reach the npm registry: ${(view.stderr || view.stdout || '').trim() || 'no version returned'}`,
|
|
142
|
-
)
|
|
143
|
-
process.exit(1)
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Prerelease-aware compare: core (major.minor.patch) first, then the
|
|
147
|
-
// beta counter; a stable release outranks any beta of the same core.
|
|
148
|
-
const parsePantheonVersion = (v) => {
|
|
149
|
-
const m = String(v)
|
|
150
|
-
.trim()
|
|
151
|
-
.match(/^(\d+)\.(\d+)\.(\d+)(?:-beta\.(\d+))?$/)
|
|
152
|
-
if (!m) return null
|
|
153
|
-
return {
|
|
154
|
-
core: [+m[1], +m[2], +m[3]],
|
|
155
|
-
beta: m[4] === undefined ? Number.POSITIVE_INFINITY : +m[4],
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
const cmp = (a, b) => {
|
|
159
|
-
const pa = parsePantheonVersion(a)
|
|
160
|
-
const pb = parsePantheonVersion(b)
|
|
161
|
-
if (!pa || !pb) return 0
|
|
162
|
-
for (let i = 0; i < 3; i++) {
|
|
163
|
-
if (pa.core[i] !== pb.core[i]) return pa.core[i] - pb.core[i]
|
|
164
|
-
}
|
|
165
|
-
return pa.beta - pb.beta
|
|
166
|
-
}
|
|
167
|
-
if (cmp(latest, current) <= 0) {
|
|
168
|
-
console.log(`✅ Already up to date (installed v${current}; ${channel} channel: v${latest}).`)
|
|
169
|
-
return
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
console.log(`Updating v${current} → v${latest} (channel: ${channel})...`)
|
|
173
|
-
const install = spawnSync('npm', ['install', '-g', `pantheon-opencode@${channel}`], {
|
|
174
|
-
stdio: 'inherit',
|
|
175
|
-
})
|
|
176
|
-
if (install.status !== 0) {
|
|
177
|
-
console.error('❌ npm install failed — the previous installation is untouched.')
|
|
178
|
-
process.exit(install.status ?? 1)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Refresh config/venv/MCP entries with the NEW package (resolved via
|
|
182
|
-
// PATH — the freshly installed global bin).
|
|
183
|
-
const init = spawnSync('pantheon-opencode', ['init', '--yes', '--headless'], {
|
|
184
|
-
stdio: 'inherit',
|
|
185
|
-
shell: process.platform === 'win32',
|
|
186
|
-
})
|
|
187
|
-
if (init.status !== 0 || init.error) {
|
|
188
|
-
console.error(
|
|
189
|
-
'⚠️ Updated, but the config refresh did not complete — run `npx pantheon-opencode init` manually.',
|
|
190
|
-
)
|
|
191
|
-
process.exit(init.status ?? 1)
|
|
192
|
-
}
|
|
193
|
-
console.log(`✅ Updated to v${latest}.`)
|
|
200
|
+
if (status !== 0) process.exit(status)
|
|
194
201
|
return
|
|
195
202
|
}
|
|
196
203
|
|
|
@@ -274,15 +281,89 @@ async function main() {
|
|
|
274
281
|
}
|
|
275
282
|
|
|
276
283
|
if (command === 'init' || !command) {
|
|
277
|
-
const
|
|
278
|
-
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')
|
|
279
331
|
const skipMCP = args.includes('--no-mcp')
|
|
280
332
|
// --clean is an alias of --force (both wipe component dirs + recreate venv).
|
|
281
|
-
const forceReinstall =
|
|
282
|
-
|
|
283
|
-
|
|
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')
|
|
284
338
|
const forceHeadless = args.includes('--headless')
|
|
285
|
-
|
|
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
|
+
}
|
|
286
367
|
|
|
287
368
|
// beta.5: warn on unrecognized flags instead of silently ignoring them.
|
|
288
369
|
const VALUE_FLAGS = new Set([
|
|
@@ -293,6 +374,8 @@ async function main() {
|
|
|
293
374
|
'--opencode-version',
|
|
294
375
|
'--components',
|
|
295
376
|
'--merge-dev-config',
|
|
377
|
+
'--apply-plan',
|
|
378
|
+
'--save-plan',
|
|
296
379
|
])
|
|
297
380
|
for (let i = 1; i < args.length; i++) {
|
|
298
381
|
const arg = args[i]
|
|
@@ -315,6 +398,7 @@ async function main() {
|
|
|
315
398
|
'--headless',
|
|
316
399
|
'--yes',
|
|
317
400
|
'-y',
|
|
401
|
+
'--json',
|
|
318
402
|
].includes(bare)
|
|
319
403
|
) {
|
|
320
404
|
console.error(`⚠️ Unknown option ignored: ${arg}`)
|
|
@@ -322,20 +406,33 @@ async function main() {
|
|
|
322
406
|
}
|
|
323
407
|
|
|
324
408
|
const presetIndex = args.indexOf('--preset')
|
|
325
|
-
const presetOpt =
|
|
409
|
+
const presetOpt = reviewedPlan
|
|
410
|
+
? (reviewedPlan.options.preset ?? null)
|
|
411
|
+
: presetIndex >= 0
|
|
412
|
+
? (args[presetIndex + 1] ?? null)
|
|
413
|
+
: null
|
|
326
414
|
// Explicit top-level model overrides for the generated config.
|
|
327
415
|
const modelIndex = args.indexOf('--model')
|
|
328
|
-
const modelOpt =
|
|
416
|
+
const modelOpt = reviewedPlan
|
|
417
|
+
? (reviewedPlan.options.model ?? null)
|
|
418
|
+
: modelIndex >= 0
|
|
419
|
+
? (args[modelIndex + 1] ?? null)
|
|
420
|
+
: null
|
|
329
421
|
const smallModelIndex = args.indexOf('--small-model')
|
|
330
|
-
const smallModelOpt =
|
|
422
|
+
const smallModelOpt = reviewedPlan
|
|
423
|
+
? (reviewedPlan.options.smallModel ?? null)
|
|
424
|
+
: smallModelIndex >= 0
|
|
425
|
+
? (args[smallModelIndex + 1] ?? null)
|
|
426
|
+
: null
|
|
331
427
|
// --version v1|v2|auto labels the install target. `auto` resolves the
|
|
332
428
|
// generation from the opencode binary at install time (see
|
|
333
429
|
// scripts/install/opencode-version.mjs). Invalid values fail fast.
|
|
334
430
|
const versionIndex = args.indexOf('--version')
|
|
335
431
|
const legacyVersionIndex = args.indexOf('--opencode-version')
|
|
336
432
|
const inlineVersion = args.find((arg) => arg.startsWith('--opencode-version='))
|
|
337
|
-
const versionOpt =
|
|
338
|
-
|
|
433
|
+
const versionOpt = reviewedPlan
|
|
434
|
+
? reviewedPlan.options.version
|
|
435
|
+
: versionIndex >= 0
|
|
339
436
|
? (args[versionIndex + 1] ?? null)
|
|
340
437
|
: legacyVersionIndex >= 0
|
|
341
438
|
? (args[legacyVersionIndex + 1] ?? null)
|
|
@@ -359,8 +456,8 @@ async function main() {
|
|
|
359
456
|
componentsIndex >= 0
|
|
360
457
|
? (args[componentsIndex + 1] ?? null)
|
|
361
458
|
: (inlineComponents?.split('=', 2)[1] ?? null)
|
|
362
|
-
let requestedComponents = null
|
|
363
|
-
if (componentsOpt !== null) {
|
|
459
|
+
let requestedComponents = reviewedPlan ? [...reviewedPlan.options.components] : null
|
|
460
|
+
if (!reviewedPlan && componentsOpt !== null) {
|
|
364
461
|
requestedComponents = componentsOpt
|
|
365
462
|
.split(',')
|
|
366
463
|
.map((c) => c.trim())
|
|
@@ -381,8 +478,9 @@ async function main() {
|
|
|
381
478
|
// from the installer code, never from a dev checkout).
|
|
382
479
|
const mergeDevConfigIndex = args.indexOf('--merge-dev-config')
|
|
383
480
|
const inlineMergeDevConfig = args.find((arg) => arg.startsWith('--merge-dev-config='))
|
|
384
|
-
const mergeDevConfigOpt =
|
|
385
|
-
|
|
481
|
+
const mergeDevConfigOpt = reviewedPlan
|
|
482
|
+
? (reviewedPlan.options.mergeDevConfig ?? null)
|
|
483
|
+
: mergeDevConfigIndex >= 0
|
|
386
484
|
? (args[mergeDevConfigIndex + 1] ?? null)
|
|
387
485
|
: (inlineMergeDevConfig?.split('=', 2)[1] ?? null)
|
|
388
486
|
if (mergeDevConfigIndex >= 0 && mergeDevConfigOpt === null) {
|
|
@@ -390,15 +488,24 @@ async function main() {
|
|
|
390
488
|
process.exit(1)
|
|
391
489
|
}
|
|
392
490
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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) {
|
|
402
509
|
const runtimeIndex = components.indexOf('runtime')
|
|
403
510
|
if (runtimeIndex >= 0) components.splice(runtimeIndex, 1)
|
|
404
511
|
}
|
|
@@ -408,32 +515,146 @@ async function main() {
|
|
|
408
515
|
const { strings } = await import('../scripts/install/strings.mjs')
|
|
409
516
|
const S = strings()
|
|
410
517
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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) {
|
|
414
522
|
console.log('')
|
|
523
|
+
if (!forceInteractive || isDryRun) {
|
|
524
|
+
console.log(`Pantheon OpenCode v${version} — ${isDryRun ? S.dryRun : S.installing}`)
|
|
525
|
+
console.log('')
|
|
526
|
+
}
|
|
415
527
|
}
|
|
416
528
|
|
|
529
|
+
let result
|
|
530
|
+
let installError = null
|
|
417
531
|
try {
|
|
418
532
|
const { installOpenCode } = await import('../scripts/install/opencode.mjs')
|
|
419
|
-
const target = isProject ? process.cwd() : undefined
|
|
420
|
-
await installOpenCode(target, isDryRun, forceReinstall, components, {
|
|
533
|
+
const target = reviewedPlan ? reviewedPlan.target.path : isProject ? process.cwd() : undefined
|
|
534
|
+
result = await installOpenCode(target, isDryRun, forceReinstall, components, {
|
|
421
535
|
interactive: forceInteractive,
|
|
422
|
-
headless: forceHeadless,
|
|
536
|
+
headless: reviewedPlan ? true : forceHeadless,
|
|
423
537
|
yes: autoYes,
|
|
424
538
|
model: modelOpt,
|
|
425
539
|
smallModel: smallModelOpt,
|
|
426
540
|
preset: presetOpt,
|
|
427
541
|
version: versionOpt ?? 'auto',
|
|
428
542
|
mergeDevConfig: mergeDevConfigOpt,
|
|
543
|
+
expectedPlan: reviewedPlan,
|
|
544
|
+
planTimestamp,
|
|
545
|
+
quiet: isJson,
|
|
429
546
|
})
|
|
430
547
|
} catch (err) {
|
|
431
|
-
|
|
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') {
|
|
432
565
|
console.error(S.canceled)
|
|
433
566
|
process.exit(130)
|
|
567
|
+
} else {
|
|
568
|
+
console.error(
|
|
569
|
+
`${S.installFailed(installError.message)}\n${S.failHintNoMcp}\n${S.failHintForce}`,
|
|
570
|
+
)
|
|
434
571
|
}
|
|
435
|
-
|
|
436
|
-
|
|
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
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Beta 11 Setup phases 3-4: a run that did NOT apply (declined /
|
|
646
|
+
// unauthorized / drift) must never print a success banner. Exit codes:
|
|
647
|
+
// 0 ok / no-apply · 2 apply-requested-without-authorization · 3 drift
|
|
648
|
+
// 130 canceled · 1 failure (thrown above).
|
|
649
|
+
if (result?.applied === false) {
|
|
650
|
+
if (!isDryRun) {
|
|
651
|
+
if (result.reason === 'canceled') console.error(S.canceled)
|
|
652
|
+
process.exit(resolveInstallExitCode(result, isDryRun))
|
|
653
|
+
}
|
|
654
|
+
// Dry-run preview: report honestly instead of the "installed!" banner.
|
|
655
|
+
console.log('')
|
|
656
|
+
console.log(` ${S.dryRunNothingWritten}`)
|
|
657
|
+
return
|
|
437
658
|
}
|
|
438
659
|
|
|
439
660
|
if (runDoctor && !isDryRun) {
|
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": [
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"postinstall": "node scripts/postinstall.mjs && node scripts/sync-tui.mjs",
|
|
20
20
|
"prepare": "husky",
|
|
21
|
-
"setup": "node bin/pantheon-init.mjs init",
|
|
21
|
+
"setup": "node bin/pantheon-init.mjs init --yes",
|
|
22
22
|
"build:agents-md": "node scripts/build-agents-md.mjs",
|
|
23
|
-
"setup:lite": "node bin/pantheon-init.mjs init --clean --components agents,skills,instructions",
|
|
23
|
+
"setup:lite": "node bin/pantheon-init.mjs init --yes --clean --components agents,skills,instructions",
|
|
24
24
|
"doctor": "node scripts/doctor.mjs",
|
|
25
25
|
"prune": "node scripts/prune.mjs",
|
|
26
|
-
"init": "node bin/pantheon-init.mjs init",
|
|
26
|
+
"init": "node bin/pantheon-init.mjs init --yes",
|
|
27
27
|
"opencode": "OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode",
|
|
28
28
|
"start": "OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode",
|
|
29
29
|
"prepublishOnly": "node scripts/validate-routing.mjs",
|
|
@@ -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