pantheon-opencode 1.6.0-beta.12 → 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/CHANGELOG.md +17 -0
- package/README.md +16 -1
- package/README.pt-BR.md +3 -1
- package/bin/pantheon-init.mjs +12 -14
- package/package.json +2 -1
- package/plugin.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/install/interactive.mjs +69 -391
- package/scripts/install/opencode.mjs +14 -0
- package/scripts/install/prompt-result.mjs +2 -0
- package/scripts/install/shared.mjs +6 -4
- package/scripts/node-engine-policy.mjs +11 -0
- package/scripts/postinstall.mjs +25 -24
- package/scripts/sync-tui.mjs +5 -1
- 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 +2 -2
- package/src/plugins/tui/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -18,6 +18,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
18
18
|
|
|
19
19
|
## ✅ Closed Issues
|
|
20
20
|
|
|
21
|
+
## [v1.6.0-beta.13] - 2026-10-17
|
|
22
|
+
|
|
23
|
+
## 🆕 What's New
|
|
24
|
+
|
|
25
|
+
- **Modernize interactive TTY setup** with `@clack/prompts` while keeping the
|
|
26
|
+
existing component, install-location, authorization, and worktree guidance
|
|
27
|
+
choices. Canceling the setup wizard leaves configuration unchanged; headless,
|
|
28
|
+
`--yes`, dry-run, and non-TTY behavior remain available.
|
|
29
|
+
|
|
30
|
+
## 🐞 Fixed
|
|
31
|
+
|
|
32
|
+
- **Let npm finish on an unsupported Node runtime:** postinstall now warns and
|
|
33
|
+
skips Node-dependent TUI/config sync, leaving the CLI to show its explicit
|
|
34
|
+
Node >=26.4.0 hard gate and user-managed upgrade instructions.
|
|
35
|
+
- **Synchronize Beta 13 package and release metadata** across manifests and
|
|
36
|
+
lockfiles without changing the Node >=26.4.0 engine floor.
|
|
37
|
+
|
|
21
38
|
## [v1.6.0-beta.12] - 2026-10-17
|
|
22
39
|
|
|
23
40
|
## 🆕 What's New
|
package/README.md
CHANGED
|
@@ -43,6 +43,18 @@ asserted here. The optional `pantheon_cost` SQLite backend has its own capabilit
|
|
|
43
43
|
threshold, `node:sqlite` on Node >= 22.5; that feature threshold does not lower
|
|
44
44
|
Pantheon's package or CLI support floor.
|
|
45
45
|
|
|
46
|
+
With npm's default configuration, an unsupported Node version may produce an
|
|
47
|
+
`EBADENGINE` warning during install. Pantheon's postinstall then skips Node-based
|
|
48
|
+
sync without changing OpenCode configuration, and the CLI itself exits with a
|
|
49
|
+
clear error before setup. If needed, switch using a user-managed version
|
|
50
|
+
manager—no system-wide install is required:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
nvm install 26.4.0 && nvm use 26.4.0
|
|
54
|
+
# or: fnm install 26.4.0 && fnm use 26.4.0
|
|
55
|
+
# or: volta install node@26.4.0
|
|
56
|
+
```
|
|
57
|
+
|
|
46
58
|
From the project where you want to use Pantheon:
|
|
47
59
|
|
|
48
60
|
```bash
|
|
@@ -50,7 +62,10 @@ npx pantheon-opencode init
|
|
|
50
62
|
opencode
|
|
51
63
|
```
|
|
52
64
|
|
|
53
|
-
|
|
65
|
+
In a TTY, the interactive setup uses Clack prompts to choose components and a
|
|
66
|
+
global or project location before reviewing the apply. Canceling writes nothing.
|
|
67
|
+
`--yes`, `--headless`, `--dry-run`, and non-TTY workflows retain their existing
|
|
68
|
+
behavior. The installer guides you through the available setup. For optional MCP servers,
|
|
54
69
|
project-local installation, or non-interactive setup, see the
|
|
55
70
|
[installation guide](docs/INSTALLATION.md).
|
|
56
71
|
|
package/README.pt-BR.md
CHANGED
|
@@ -33,6 +33,8 @@ de planejar o trabalho, avançar, conferir resultados e preservar o contexto
|
|
|
33
33
|
|
|
34
34
|
Requisitos: [OpenCode 1.18.4+](https://opencode.ai/docs/) e Node.js `>=26.4.0` para a CLI, o instalador e as ferramentas Node do Pantheon. Node 22, Node 24 e Node 26.0–26.3 não são compatíveis com esta versão.
|
|
35
35
|
|
|
36
|
+
Com a configuração padrão do npm, versões incompatíveis podem gerar um aviso `EBADENGINE`; o postinstall pula a sincronização Node/config, e a CLI bloqueia a configuração antes de gravar arquivos. Para atualizar sem instalação de sistema, use um gerenciador de Node do usuário: `nvm install 26.4.0 && nvm use 26.4.0`, `fnm install 26.4.0 && fnm use 26.4.0` ou `volta install node@26.4.0`.
|
|
37
|
+
|
|
36
38
|
Esse requisito do pacote não significa que todo host OpenCode execute Node: o OpenCode V2.0.31 oficial oferece um caminho de runtime Bun e um build Node separado fixado em Node 26.4.0. Não há aqui um requisito mínimo para Bun. O backend SQLite opcional de `pantheon_cost` tem um limite de capacidade distinto (`node:sqlite` requer Node >=22.5), que não reduz o requisito de suporte do pacote.
|
|
37
39
|
|
|
38
40
|
No projeto em que você quer usar o Pantheon:
|
|
@@ -77,7 +79,7 @@ verificações repetíveis e passagens claras entre etapas do trabalho.
|
|
|
77
79
|
|
|
78
80
|
## Status
|
|
79
81
|
|
|
80
|
-
Versão operacional neste checkout: **v1.6.0-beta.
|
|
82
|
+
Versão operacional neste checkout: **v1.6.0-beta.13** (esta página não afirma publicação). O Pantheon foi feito para OpenCode e depende da
|
|
81
83
|
disponibilidade e da configuração do OpenCode e dos serviços opcionais que você
|
|
82
84
|
escolher. Veja as [releases](https://github.com/ils15/pantheon-opencode/releases)
|
|
83
85
|
e o [changelog](CHANGELOG.md) para acompanhar as mudanças publicadas.
|
package/bin/pantheon-init.mjs
CHANGED
|
@@ -26,9 +26,10 @@ import {
|
|
|
26
26
|
loadPlanArtifact,
|
|
27
27
|
savePlanArtifact,
|
|
28
28
|
} from '../scripts/install/plan-artifact.mjs'
|
|
29
|
+
import { PROMPT_CANCELED } from '../scripts/install/prompt-result.mjs'
|
|
29
30
|
import {
|
|
30
31
|
isSupportedNodeVersion,
|
|
31
|
-
|
|
32
|
+
unsupportedNodeVersionMessage,
|
|
32
33
|
} from '../scripts/node-engine-policy.mjs'
|
|
33
34
|
import { runUpdate } from '../scripts/update.mjs'
|
|
34
35
|
|
|
@@ -36,10 +37,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
|
36
37
|
const ROOT = path.resolve(__dirname, '..')
|
|
37
38
|
|
|
38
39
|
if (!isSupportedNodeVersion(process.versions.node)) {
|
|
39
|
-
console.error(
|
|
40
|
-
`❌ Pantheon requires Node.js >= ${MIN_SUPPORTED_NODE_VERSION} (current: ${process.versions.node})`,
|
|
41
|
-
)
|
|
42
|
-
console.error(' Install a newer Node via nvm: https://github.com/nvm-sh/nvm')
|
|
40
|
+
console.error(unsupportedNodeVersionMessage(process.versions.node))
|
|
43
41
|
process.exit(1)
|
|
44
42
|
}
|
|
45
43
|
|
|
@@ -111,14 +109,8 @@ async function authorizeReviewedPlan(plan) {
|
|
|
111
109
|
for (const operation of operations) {
|
|
112
110
|
console.log(` ${operation.action.padEnd(9)} ${operation.path}`)
|
|
113
111
|
}
|
|
114
|
-
const
|
|
115
|
-
|
|
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
|
-
}
|
|
112
|
+
const { promptReview } = await import('../scripts/install/interactive.mjs')
|
|
113
|
+
return promptReview('Apply this reviewed plan?', { default: false })
|
|
122
114
|
}
|
|
123
115
|
|
|
124
116
|
async function main() {
|
|
@@ -490,8 +482,14 @@ async function main() {
|
|
|
490
482
|
|
|
491
483
|
if (reviewedPlan && !autoYes) {
|
|
492
484
|
if (!forceHeadless && process.stdin.isTTY && process.stdout.isTTY) {
|
|
493
|
-
|
|
485
|
+
const authorization = await authorizeReviewedPlan(reviewedPlan)
|
|
486
|
+
if (authorization === PROMPT_CANCELED) {
|
|
494
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.')
|
|
495
493
|
return
|
|
496
494
|
}
|
|
497
495
|
} else {
|
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": [
|
|
@@ -99,6 +99,7 @@
|
|
|
99
99
|
"!**/*.pyc"
|
|
100
100
|
],
|
|
101
101
|
"dependencies": {
|
|
102
|
+
"@clack/prompts": "1.8.1",
|
|
102
103
|
"@opencode-ai/plugin": "1.18.33",
|
|
103
104
|
"js-yaml": "^5.4.2",
|
|
104
105
|
"zod": "4.1.8",
|
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
|
@@ -1,420 +1,98 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import * as clack from '@clack/prompts'
|
|
4
|
+
import { PROMPT_CANCELED } from './prompt-result.mjs'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
const cursorShow = () => process.stdout.write('\x1b[?25h')
|
|
8
|
-
const cursorUp = (n) => process.stdout.write(`\x1b[${n}A`)
|
|
9
|
-
const clearDown = () => process.stdout.write('\x1b[J')
|
|
6
|
+
export { PROMPT_CANCELED }
|
|
10
7
|
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
if (!process.stdout.isTTY) return false
|
|
14
|
-
emitKeypressEvents(process.stdin)
|
|
15
|
-
try {
|
|
16
|
-
process.stdin.setRawMode(true)
|
|
17
|
-
} catch {
|
|
18
|
-
return false
|
|
19
|
-
}
|
|
20
|
-
process.stdin.resume()
|
|
21
|
-
return true
|
|
8
|
+
function isTerminal() {
|
|
9
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
22
10
|
}
|
|
23
11
|
|
|
24
|
-
function
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
process.stdin.pause()
|
|
29
|
-
} catch {
|
|
30
|
-
/* safe to ignore */
|
|
31
|
-
}
|
|
12
|
+
function cancelPrompt(prompts, value) {
|
|
13
|
+
if (!prompts.isCancel(value)) return false
|
|
14
|
+
prompts.cancel('Installation canceled.')
|
|
15
|
+
return true
|
|
32
16
|
}
|
|
33
17
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Prompt for authorization to apply a reviewed installer plan.
|
|
20
|
+
*
|
|
21
|
+
* @param {string} planSummary Summary of the changes that require approval.
|
|
22
|
+
* @param {{ default?: boolean, prompts?: typeof clack, isTTY?: boolean }} [options]
|
|
23
|
+
* @returns {Promise<boolean | typeof PROMPT_CANCELED>} True if authorized, false if declined, or the cancellation sentinel.
|
|
24
|
+
*/
|
|
25
|
+
export async function promptReview(
|
|
26
|
+
planSummary,
|
|
27
|
+
{ default: defaultValue = false, prompts = clack, isTTY = isTerminal() } = {},
|
|
37
28
|
) {
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
name: o.name,
|
|
43
|
-
value: o.value,
|
|
44
|
-
description: o.description || '',
|
|
45
|
-
checked: o.checked || preSelected.has(o.value),
|
|
46
|
-
}))
|
|
47
|
-
|
|
48
|
-
let cursor = 0
|
|
49
|
-
let scroll = 0
|
|
50
|
-
const maxVisible = Math.min(pageSize, options.length)
|
|
51
|
-
let previousLineCount = 0
|
|
52
|
-
|
|
53
|
-
const isTTY = setupStdin()
|
|
54
|
-
if (!isTTY) {
|
|
55
|
-
return new Set(items.filter((i) => i.checked).map((i) => i.value))
|
|
56
|
-
}
|
|
57
|
-
cursorHide()
|
|
58
|
-
|
|
59
|
-
function render() {
|
|
60
|
-
if (cursor < scroll) scroll = cursor
|
|
61
|
-
if (cursor >= scroll + maxVisible) scroll = cursor - maxVisible + 1
|
|
62
|
-
|
|
63
|
-
const visible = items.slice(scroll, scroll + maxVisible)
|
|
64
|
-
const lines = []
|
|
65
|
-
|
|
66
|
-
if (title) lines.push(colors.bold(title))
|
|
67
|
-
|
|
68
|
-
const hasMoreAbove = scroll > 0
|
|
69
|
-
const hasMoreBelow = scroll + maxVisible < options.length
|
|
70
|
-
|
|
71
|
-
if (hasMoreAbove) lines.push(colors.dim(' ...'))
|
|
72
|
-
|
|
73
|
-
for (let i = 0; i < visible.length; i++) {
|
|
74
|
-
const item = visible[i]
|
|
75
|
-
const idx = scroll + i
|
|
76
|
-
const active = idx === cursor
|
|
77
|
-
const box = item.checked ? '[x]' : '[ ]'
|
|
78
|
-
const ptr = active ? `${colors.cyan('>')} ` : ' '
|
|
79
|
-
const name = active ? colors.cyan(item.name) : item.name
|
|
80
|
-
const desc = item.description ? colors.dim(` — ${item.description}`) : ''
|
|
81
|
-
lines.push(`${ptr}${colors.bold(box)} ${name}${desc}`)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
if (hasMoreBelow) lines.push(colors.dim(' ...'))
|
|
85
|
-
|
|
86
|
-
const count = items.filter((i) => i.checked).length
|
|
87
|
-
const total = items.length
|
|
88
|
-
const noun = count === 1 ? 'selected' : 'selected'
|
|
89
|
-
lines.push(
|
|
90
|
-
colors.dim(
|
|
91
|
-
`(${count}/${total} ${noun}) — ${colors.cyan('↑')}${colors.dim('/')}${colors.cyan('↓')} navigate · ${colors.cyan('Space')}${colors.dim(' toggle · ')}${colors.cyan('Enter')}${colors.dim(' confirm')}`,
|
|
92
|
-
),
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
const lineCount = lines.length
|
|
96
|
-
if (previousLineCount > 0) cursorUp(previousLineCount)
|
|
97
|
-
clearDown()
|
|
98
|
-
process.stdout.write(`${lines.join('\n')}\n`)
|
|
99
|
-
previousLineCount = lineCount
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
return new Promise((resolve, reject) => {
|
|
103
|
-
let settled = false
|
|
104
|
-
|
|
105
|
-
function finalize(result) {
|
|
106
|
-
if (settled) return
|
|
107
|
-
settled = true
|
|
108
|
-
cursorShow()
|
|
109
|
-
teardownStdin()
|
|
110
|
-
if (previousLineCount > 0) cursorUp(previousLineCount)
|
|
111
|
-
clearDown()
|
|
112
|
-
resolve(result)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function cancel() {
|
|
116
|
-
if (settled) return
|
|
117
|
-
settled = true
|
|
118
|
-
cursorShow()
|
|
119
|
-
teardownStdin()
|
|
120
|
-
if (previousLineCount > 0) cursorUp(previousLineCount)
|
|
121
|
-
clearDown()
|
|
122
|
-
reject(new Error('Canceled'))
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function handler(_str, key) {
|
|
126
|
-
if (!key) return
|
|
127
|
-
if ((key.ctrl && key.name === 'c') || key.name === 'escape') {
|
|
128
|
-
cancel()
|
|
129
|
-
return
|
|
130
|
-
}
|
|
131
|
-
if (key.name === 'return' || key.name === 'enter') {
|
|
132
|
-
const result = new Set(items.filter((i) => i.checked).map((i) => i.value))
|
|
133
|
-
finalize(result)
|
|
134
|
-
return
|
|
135
|
-
}
|
|
136
|
-
if (key.name === 'up') {
|
|
137
|
-
cursor = (cursor - 1 + items.length) % items.length
|
|
138
|
-
render()
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
|
-
if (key.name === 'down') {
|
|
142
|
-
cursor = (cursor + 1) % items.length
|
|
143
|
-
render()
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
if (key.name === 'space') {
|
|
147
|
-
items[cursor].checked = !items[cursor].checked
|
|
148
|
-
render()
|
|
149
|
-
return
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
process.stdin.on('keypress', handler)
|
|
154
|
-
render()
|
|
155
|
-
})
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export async function promptConfirm(message, { default: def = true } = {}) {
|
|
159
|
-
const label = def ? 'Y/n' : 'y/N'
|
|
160
|
-
const isTTY = setupStdin()
|
|
161
|
-
if (!isTTY) return def
|
|
162
|
-
|
|
163
|
-
cursorHide()
|
|
164
|
-
process.stdout.write(`${colors.bold('?')} ${message} ${colors.cyan(label)} `)
|
|
165
|
-
|
|
166
|
-
return new Promise((resolve, reject) => {
|
|
167
|
-
let settled = false
|
|
168
|
-
|
|
169
|
-
function finalize(result) {
|
|
170
|
-
if (settled) return
|
|
171
|
-
settled = true
|
|
172
|
-
cursorShow()
|
|
173
|
-
teardownStdin()
|
|
174
|
-
process.stdout.write(`${result ? 'Yes' : 'No'}\n`)
|
|
175
|
-
resolve(result)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function cancel() {
|
|
179
|
-
if (settled) return
|
|
180
|
-
settled = true
|
|
181
|
-
cursorShow()
|
|
182
|
-
teardownStdin()
|
|
183
|
-
process.stdout.write('\n')
|
|
184
|
-
reject(new Error('Canceled'))
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function handler(str, key) {
|
|
188
|
-
if (!key) return
|
|
189
|
-
if ((key.ctrl && key.name === 'c') || key.name === 'escape') {
|
|
190
|
-
cancel()
|
|
191
|
-
return
|
|
192
|
-
}
|
|
193
|
-
if (key.name === 'return' || key.name === 'enter') {
|
|
194
|
-
finalize(def)
|
|
195
|
-
return
|
|
196
|
-
}
|
|
197
|
-
if (str) {
|
|
198
|
-
const ch = str.toLowerCase()
|
|
199
|
-
if (ch === 'y' || ch === 'yes') {
|
|
200
|
-
finalize(true)
|
|
201
|
-
} else if (ch === 'n' || ch === 'no') {
|
|
202
|
-
finalize(false)
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
process.stdin.on('keypress', handler)
|
|
208
|
-
})
|
|
29
|
+
if (!isTTY) return false
|
|
30
|
+
const result = await prompts.confirm({ message: planSummary, initialValue: defaultValue })
|
|
31
|
+
if (cancelPrompt(prompts, result)) return PROMPT_CANCELED
|
|
32
|
+
return result === true
|
|
209
33
|
}
|
|
210
34
|
|
|
211
35
|
/**
|
|
212
|
-
*
|
|
213
|
-
* bare Enter never authorizes a write. In a non-TTY it returns the default
|
|
214
|
-
* (`false`) without blocking — callers that must not prompt at all should branch
|
|
215
|
-
* on TTY before calling.
|
|
36
|
+
* Collect interactive setup choices before the installer begins any writes.
|
|
216
37
|
*
|
|
217
|
-
* @param {string
|
|
218
|
-
* @
|
|
219
|
-
* @returns {Promise<boolean>}
|
|
38
|
+
* @param {{ components?: string[], defaultComponents?: string[], prompts?: typeof clack, isTTY?: boolean }} [options]
|
|
39
|
+
* @returns {Promise<{ components?: Set<string>, target?: string, confirmed?: boolean, canceled?: boolean }>}
|
|
220
40
|
*/
|
|
221
|
-
export async function
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const items = options.map((o) => ({
|
|
230
|
-
name: o.name,
|
|
231
|
-
value: o.value,
|
|
232
|
-
description: o.description || '',
|
|
233
|
-
}))
|
|
234
|
-
|
|
235
|
-
let cursor = 0
|
|
236
|
-
const pageSize = Math.min(8, options.length)
|
|
237
|
-
let scroll = 0
|
|
238
|
-
const maxVisible = Math.min(pageSize, options.length)
|
|
239
|
-
let previousLineCount = 0
|
|
240
|
-
|
|
241
|
-
const isTTY = setupStdin()
|
|
242
|
-
if (!isTTY) return items[0].value
|
|
243
|
-
cursorHide()
|
|
244
|
-
|
|
245
|
-
function render() {
|
|
246
|
-
if (cursor < scroll) scroll = cursor
|
|
247
|
-
if (cursor >= scroll + maxVisible) scroll = cursor - maxVisible + 1
|
|
248
|
-
|
|
249
|
-
const visible = items.slice(scroll, scroll + maxVisible)
|
|
250
|
-
const lines = []
|
|
251
|
-
|
|
252
|
-
if (title) lines.push(colors.bold(title))
|
|
253
|
-
|
|
254
|
-
const hasMoreAbove = scroll > 0
|
|
255
|
-
const hasMoreBelow = scroll + maxVisible < options.length
|
|
256
|
-
|
|
257
|
-
if (hasMoreAbove) lines.push(colors.dim(' ...'))
|
|
258
|
-
|
|
259
|
-
for (let i = 0; i < visible.length; i++) {
|
|
260
|
-
const item = visible[i]
|
|
261
|
-
const idx = scroll + i
|
|
262
|
-
const active = idx === cursor
|
|
263
|
-
const ptr = active ? `${colors.cyan('>')} ` : ' '
|
|
264
|
-
const name = active ? colors.cyan(item.name) : item.name
|
|
265
|
-
const desc = item.description ? colors.dim(` — ${item.description}`) : ''
|
|
266
|
-
lines.push(`${ptr}${colors.bold('◉')} ${name}${desc}`)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (hasMoreBelow) lines.push(colors.dim(' ...'))
|
|
270
|
-
|
|
271
|
-
lines.push(
|
|
272
|
-
colors.dim(
|
|
273
|
-
`(${colors.cyan('↑')}${colors.dim('/')}${colors.cyan('↓')} navigate · ${colors.cyan('Enter')}${colors.dim(' select')}`,
|
|
274
|
-
),
|
|
275
|
-
)
|
|
41
|
+
export async function runInteractiveInstall({
|
|
42
|
+
components = [],
|
|
43
|
+
defaultComponents = [],
|
|
44
|
+
prompts = clack,
|
|
45
|
+
isTTY = isTerminal(),
|
|
46
|
+
} = {}) {
|
|
47
|
+
const defaults = defaultComponents.length > 0 ? defaultComponents : components
|
|
48
|
+
const defaultSet = new Set(defaults)
|
|
276
49
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
process.stdout.write(`${lines.join('\n')}\n`)
|
|
281
|
-
previousLineCount = lineCount
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return new Promise((resolve, reject) => {
|
|
285
|
-
let settled = false
|
|
286
|
-
|
|
287
|
-
function finalize(result) {
|
|
288
|
-
if (settled) return
|
|
289
|
-
settled = true
|
|
290
|
-
cursorShow()
|
|
291
|
-
teardownStdin()
|
|
292
|
-
if (previousLineCount > 0) cursorUp(previousLineCount)
|
|
293
|
-
clearDown()
|
|
294
|
-
resolve(result)
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function cancel() {
|
|
298
|
-
if (settled) return
|
|
299
|
-
settled = true
|
|
300
|
-
cursorShow()
|
|
301
|
-
teardownStdin()
|
|
302
|
-
if (previousLineCount > 0) cursorUp(previousLineCount)
|
|
303
|
-
clearDown()
|
|
304
|
-
reject(new Error('Canceled'))
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function handler(_str, key) {
|
|
308
|
-
if (!key) return
|
|
309
|
-
if ((key.ctrl && key.name === 'c') || key.name === 'escape') {
|
|
310
|
-
cancel()
|
|
311
|
-
return
|
|
312
|
-
}
|
|
313
|
-
if (key.name === 'return' || key.name === 'enter') {
|
|
314
|
-
finalize(items[cursor].value)
|
|
315
|
-
return
|
|
316
|
-
}
|
|
317
|
-
if (key.name === 'up') {
|
|
318
|
-
cursor = (cursor - 1 + items.length) % items.length
|
|
319
|
-
render()
|
|
320
|
-
return
|
|
321
|
-
}
|
|
322
|
-
if (key.name === 'down') {
|
|
323
|
-
cursor = (cursor + 1) % items.length
|
|
324
|
-
render()
|
|
325
|
-
return
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
process.stdin.on('keypress', handler)
|
|
330
|
-
render()
|
|
331
|
-
})
|
|
332
|
-
}
|
|
50
|
+
// Preserve the prior no-TTY fallback (defaults, global target, and no apply
|
|
51
|
+
// authorization) without asking a terminal prompt library to access stdin.
|
|
52
|
+
if (!isTTY) return { components: defaultSet, target: 'global', confirmed: true }
|
|
333
53
|
|
|
334
|
-
|
|
335
|
-
const defaultSet = new Set(defaultComponents.length > 0 ? defaultComponents : components)
|
|
54
|
+
process.stdout.write('\n📦 OpenCode Installer\n\n')
|
|
336
55
|
|
|
337
56
|
const componentOptions = [
|
|
338
|
-
{
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
},
|
|
343
|
-
{
|
|
344
|
-
|
|
345
|
-
value: 'skills',
|
|
346
|
-
description: `Reusable skill workflows (${20} skills)`,
|
|
347
|
-
},
|
|
348
|
-
{
|
|
349
|
-
name: 'instructions',
|
|
350
|
-
value: 'instructions',
|
|
351
|
-
description: 'AGENTS.md and instructional files',
|
|
352
|
-
},
|
|
353
|
-
{
|
|
354
|
-
name: 'prompts',
|
|
355
|
-
value: 'prompts',
|
|
356
|
-
description: 'Prompt templates for agents',
|
|
357
|
-
},
|
|
358
|
-
{
|
|
359
|
-
name: 'commands',
|
|
360
|
-
value: 'commands',
|
|
361
|
-
description: 'OpenCode command shortcuts',
|
|
362
|
-
},
|
|
363
|
-
{
|
|
364
|
-
name: 'plugins',
|
|
365
|
-
value: 'plugins',
|
|
366
|
-
description: 'TUI sidebar plugin',
|
|
367
|
-
},
|
|
368
|
-
{
|
|
369
|
-
name: 'runtime',
|
|
370
|
-
value: 'runtime',
|
|
371
|
-
description: 'Python venv + MCP server scripts',
|
|
372
|
-
},
|
|
57
|
+
{ label: 'agents', value: 'agents', hint: 'Agent configurations and routing (14 agents)' },
|
|
58
|
+
{ label: 'skills', value: 'skills', hint: `Reusable skill workflows (${20} skills)` },
|
|
59
|
+
{ label: 'instructions', value: 'instructions', hint: 'AGENTS.md and instructional files' },
|
|
60
|
+
{ label: 'prompts', value: 'prompts', hint: 'Prompt templates for agents' },
|
|
61
|
+
{ label: 'commands', value: 'commands', hint: 'OpenCode command shortcuts' },
|
|
62
|
+
{ label: 'plugins', value: 'plugins', hint: 'TUI sidebar plugin' },
|
|
63
|
+
{ label: 'runtime', value: 'runtime', hint: 'Python venv + MCP server scripts' },
|
|
373
64
|
]
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
title: 'Select components to install:',
|
|
380
|
-
selected: defaultSet,
|
|
381
|
-
pageSize: 7,
|
|
65
|
+
const chosen = await prompts.multiselect({
|
|
66
|
+
message: 'Select components to install:',
|
|
67
|
+
options: componentOptions,
|
|
68
|
+
initialValues: [...defaultSet],
|
|
69
|
+
required: false,
|
|
382
70
|
})
|
|
71
|
+
if (cancelPrompt(prompts, chosen)) return { canceled: true }
|
|
383
72
|
|
|
384
|
-
|
|
385
|
-
|
|
73
|
+
const selectedComponents = new Set(chosen)
|
|
386
74
|
if (selectedComponents.size === 0) {
|
|
387
|
-
|
|
388
|
-
defaultSet.forEach((v) => {
|
|
389
|
-
selectedComponents.add(v)
|
|
390
|
-
})
|
|
75
|
+
for (const value of defaults) selectedComponents.add(value)
|
|
391
76
|
}
|
|
392
77
|
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
value: 'project',
|
|
402
|
-
|
|
403
|
-
},
|
|
404
|
-
]
|
|
405
|
-
|
|
406
|
-
const target = await promptList(targetOptions, {
|
|
407
|
-
title: 'Install location:',
|
|
78
|
+
const target = await prompts.select({
|
|
79
|
+
message: 'Install location:',
|
|
80
|
+
options: [
|
|
81
|
+
{
|
|
82
|
+
label: 'Global directory',
|
|
83
|
+
value: 'global',
|
|
84
|
+
hint: '~/.config/opencode (shared across projects)',
|
|
85
|
+
},
|
|
86
|
+
{ label: 'Current project', value: 'project', hint: './.opencode (scoped to this project)' },
|
|
87
|
+
],
|
|
408
88
|
})
|
|
89
|
+
if (cancelPrompt(prompts, target)) return { canceled: true }
|
|
409
90
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
)
|
|
416
|
-
|
|
417
|
-
process.stdout.write('\n')
|
|
91
|
+
const confirmed = await prompts.confirm({
|
|
92
|
+
message: `Install ${selectedComponents.size} component(s) in ${target === 'global' ? 'global config' : 'current project'}?`,
|
|
93
|
+
initialValue: true,
|
|
94
|
+
})
|
|
95
|
+
if (cancelPrompt(prompts, confirmed)) return { canceled: true }
|
|
418
96
|
|
|
419
97
|
return { components: selectedComponents, target, confirmed }
|
|
420
98
|
}
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
} from './plan.mjs'
|
|
64
64
|
import { findPlanDrift } from './plan-artifact.mjs'
|
|
65
65
|
import { copyPluginFiles, installPlugin, registerPlugin, unregisterPlugin } from './plugin.mjs'
|
|
66
|
+
import { PROMPT_CANCELED } from './prompt-result.mjs'
|
|
66
67
|
import {
|
|
67
68
|
checkRuntimePrerequisites,
|
|
68
69
|
collectSkillNames,
|
|
@@ -1152,8 +1153,15 @@ async function installPass(
|
|
|
1152
1153
|
const result = await runInteractiveInstall({
|
|
1153
1154
|
components,
|
|
1154
1155
|
defaultComponents: COMPONENT_NAMES,
|
|
1156
|
+
prompts: opts.deps?.prompts,
|
|
1157
|
+
isTTY: opts.deps?.isTTY,
|
|
1155
1158
|
})
|
|
1156
1159
|
|
|
1160
|
+
if (result.canceled) {
|
|
1161
|
+
info('Installation canceled.')
|
|
1162
|
+
return { ...stats, applied: false, reason: 'canceled' }
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1157
1165
|
if (!result.confirmed && !autoYes) {
|
|
1158
1166
|
info('Installation canceled.')
|
|
1159
1167
|
return { ...stats, applied: false, reason: 'canceled' }
|
|
@@ -1266,7 +1274,13 @@ async function installPass(
|
|
|
1266
1274
|
const ask = typeof injected === 'function' ? injected : promptReview
|
|
1267
1275
|
const accepted = await ask(`Apply these ${planned?.planned ?? 0} changes?`, {
|
|
1268
1276
|
default: false,
|
|
1277
|
+
prompts: opts.deps?.prompts,
|
|
1278
|
+
isTTY: opts.deps?.isTTY,
|
|
1269
1279
|
})
|
|
1280
|
+
if (accepted === PROMPT_CANCELED) {
|
|
1281
|
+
info(S.nothingApplied)
|
|
1282
|
+
return { ...stats, applied: false, reason: 'canceled' }
|
|
1283
|
+
}
|
|
1270
1284
|
if (!accepted) {
|
|
1271
1285
|
info(S.nothingApplied)
|
|
1272
1286
|
return { ...stats, applied: false, reason: 'declined' }
|
|
@@ -19,12 +19,14 @@ import {
|
|
|
19
19
|
} from 'node:fs'
|
|
20
20
|
import { dirname, join } from 'node:path'
|
|
21
21
|
import { fileURLToPath } from 'node:url'
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
isSupportedNodeVersion,
|
|
24
|
+
MIN_SUPPORTED_NODE_VERSION,
|
|
25
|
+
unsupportedNodeVersionMessage,
|
|
26
|
+
} from '../node-engine-policy.mjs'
|
|
23
27
|
|
|
24
28
|
if (!isSupportedNodeVersion(process.versions.node)) {
|
|
25
|
-
console.error(
|
|
26
|
-
`❌ Pantheon requires Node.js >= ${MIN_SUPPORTED_NODE_VERSION} (current: ${process.versions.node})`,
|
|
27
|
-
)
|
|
29
|
+
console.error(unsupportedNodeVersionMessage(process.versions.node))
|
|
28
30
|
process.exit(1)
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -4,6 +4,17 @@ import { join } from 'node:path'
|
|
|
4
4
|
export const MIN_SUPPORTED_NODE_VERSION = '26.4.0'
|
|
5
5
|
export const REQUIRED_NODE_ENGINE_RANGE = `>=${MIN_SUPPORTED_NODE_VERSION}`
|
|
6
6
|
|
|
7
|
+
/** Return actionable, user-managed upgrade guidance for an unsupported runtime. */
|
|
8
|
+
export function unsupportedNodeVersionMessage(version) {
|
|
9
|
+
return [
|
|
10
|
+
`❌ Pantheon requires Node.js >= ${MIN_SUPPORTED_NODE_VERSION} (current: ${version})`,
|
|
11
|
+
' Install and switch with a user-managed Node version manager:',
|
|
12
|
+
` nvm install ${MIN_SUPPORTED_NODE_VERSION} && nvm use ${MIN_SUPPORTED_NODE_VERSION}`,
|
|
13
|
+
` fnm install ${MIN_SUPPORTED_NODE_VERSION} && fnm use ${MIN_SUPPORTED_NODE_VERSION}`,
|
|
14
|
+
` volta install node@${MIN_SUPPORTED_NODE_VERSION}`,
|
|
15
|
+
].join('\n')
|
|
16
|
+
}
|
|
17
|
+
|
|
7
18
|
function parseNodeVersion(version) {
|
|
8
19
|
const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([^+]+))?(?:\+([\w.-]+))?$/.exec(String(version))
|
|
9
20
|
if (!match) return null
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -5,36 +5,37 @@
|
|
|
5
5
|
* Runs after `npm install` completes. Validates environment and prints
|
|
6
6
|
* setup instructions. Does NOT install Pantheon platform components.
|
|
7
7
|
*/
|
|
8
|
-
import { isSupportedNodeVersion,
|
|
8
|
+
import { isSupportedNodeVersion, unsupportedNodeVersionMessage } from './node-engine-policy.mjs'
|
|
9
9
|
|
|
10
10
|
try {
|
|
11
|
-
//
|
|
11
|
+
// npm may warn about `engines.node`, but must still finish installing so the
|
|
12
|
+
// bin can print the actionable hard-gate message. No Node-dependent setup is
|
|
13
|
+
// attempted from an incompatible lifecycle runtime.
|
|
12
14
|
if (!isSupportedNodeVersion(process.versions.node)) {
|
|
13
|
-
console.
|
|
14
|
-
|
|
15
|
+
console.warn(
|
|
16
|
+
`⚠️ Pantheon postinstall skipped because this runtime is unsupported.\n${unsupportedNodeVersionMessage(process.versions.node)}\n Switch to Node.js >= 26.4.0 before running Pantheon setup.`,
|
|
15
17
|
)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
} else {
|
|
19
|
+
// Validate js-yaml is available
|
|
20
|
+
try {
|
|
21
|
+
await import('js-yaml')
|
|
22
|
+
} catch {
|
|
23
|
+
console.warn('⚠️ js-yaml not found — run `npm install` to install dependencies')
|
|
24
|
+
}
|
|
18
25
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
console.
|
|
26
|
+
console.log('')
|
|
27
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
28
|
+
console.log(' Pantheon OpenCode — npm dependencies installed ✅')
|
|
29
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
30
|
+
console.log('')
|
|
31
|
+
console.log(' Next steps:')
|
|
32
|
+
console.log(' 1. Configure Pantheon:')
|
|
33
|
+
console.log(' npm run setup')
|
|
34
|
+
console.log(' # or: npx pantheon-opencode init')
|
|
35
|
+
console.log(' 2. Verify installation:')
|
|
36
|
+
console.log(' npm run doctor')
|
|
37
|
+
console.log('')
|
|
24
38
|
}
|
|
25
|
-
|
|
26
|
-
console.log('')
|
|
27
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
28
|
-
console.log(' Pantheon OpenCode — npm dependencies installed ✅')
|
|
29
|
-
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
|
|
30
|
-
console.log('')
|
|
31
|
-
console.log(' Next steps:')
|
|
32
|
-
console.log(' 1. Configure Pantheon:')
|
|
33
|
-
console.log(' npm run setup')
|
|
34
|
-
console.log(' # or: npx pantheon-opencode init')
|
|
35
|
-
console.log(' 2. Verify installation:')
|
|
36
|
-
console.log(' npm run doctor')
|
|
37
|
-
console.log('')
|
|
38
39
|
} catch (err) {
|
|
39
40
|
console.error(`❌ Post-install check failed: ${err.message}`)
|
|
40
41
|
process.exit(1)
|
package/scripts/sync-tui.mjs
CHANGED
|
@@ -15,6 +15,7 @@ import { homedir } from 'node:os'
|
|
|
15
15
|
import { join } from 'node:path'
|
|
16
16
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
17
17
|
import { resolveOpenCodeVersion } from './install/opencode-version.mjs'
|
|
18
|
+
import { isSupportedNodeVersion } from './node-engine-policy.mjs'
|
|
18
19
|
|
|
19
20
|
const __dirname = fileURLToPath(new URL('.', import.meta.url))
|
|
20
21
|
const ROOT = join(__dirname, '..')
|
|
@@ -257,7 +258,10 @@ async function main() {
|
|
|
257
258
|
const isDirectRun =
|
|
258
259
|
Boolean(process.argv[1]) && pathToFileURL(process.argv[1]).href === import.meta.url
|
|
259
260
|
|
|
260
|
-
if (isDirectRun) {
|
|
261
|
+
if (isDirectRun && !isSupportedNodeVersion(process.versions.node)) {
|
|
262
|
+
// postinstall.mjs already reports the engine mismatch. Do not call npm ci or
|
|
263
|
+
// touch the user's config with an incompatible lifecycle runtime.
|
|
264
|
+
} else if (isDirectRun) {
|
|
261
265
|
main().catch((err) => {
|
|
262
266
|
console.error(`❌ TUI sync failed: ${err.message}`)
|
|
263
267
|
process.exitCode = 1
|
|
@@ -477,7 +477,7 @@ function Sidebar(props) {
|
|
|
477
477
|
setProp(_el$, "width", "100%");
|
|
478
478
|
setProp(_el$, "padding", 1);
|
|
479
479
|
setProp(_el$2, "attributes", 1);
|
|
480
|
-
insert(_el$2, `Pantheon v1.6.0-beta.
|
|
480
|
+
insert(_el$2, `Pantheon v1.6.0-beta.13`);
|
|
481
481
|
insertNode(_el$3, createTextNode(`OpenCode V2 · plugin TUI ativo`));
|
|
482
482
|
insert(_el$, createComponent(Show, {
|
|
483
483
|
get when() {
|
|
@@ -623,7 +623,7 @@ function HomeStatus(props) {
|
|
|
623
623
|
const muted = () => props.context.theme?.text?.muted ?? props.context.theme?.text?.base;
|
|
624
624
|
return (() => {
|
|
625
625
|
var _el$20 = createElement("text");
|
|
626
|
-
insert(_el$20, () => `Pantheon v1.6.0-beta.
|
|
626
|
+
insert(_el$20, () => `Pantheon v1.6.0-beta.13${preset().name ? ` ⚡ ${preset().name}` : ""}`);
|
|
627
627
|
effect((_$p) => setProp(_el$20, "fg", muted(), _$p));
|
|
628
628
|
return _el$20;
|
|
629
629
|
})();
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-tui",
|
|
3
|
-
"version": "1.6.0-beta.
|
|
3
|
+
"version": "1.6.0-beta.13",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "pantheon-tui",
|
|
9
|
-
"version": "1.6.0-beta.
|
|
9
|
+
"version": "1.6.0-beta.13",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@opencode-ai/plugin": "1.18.33",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pantheon-tui",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.6.0-beta.
|
|
4
|
+
"version": "1.6.0-beta.13",
|
|
5
5
|
"description": "Pantheon TUI plugin — V1 rich sidebar and usage gauges; native V2 status, session and delegation sidebar",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"engines": {
|