pantheon-opencode 1.1.1-beta.3 → 1.1.1-beta.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pantheon-opencode",
3
- "version": "1.1.1-beta.3",
3
+ "version": "1.1.1-beta.4",
4
4
  "description": "Multi-agent orchestration for OpenCode — 14 specialized agents, TDD, Themis review gates.",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -0,0 +1,160 @@
1
+ export const icons = {
2
+ success: '\u2705',
3
+ warning: '\u26a0\ufe0f ',
4
+ error: '\u274c',
5
+ info: '\u2139\ufe0f ',
6
+ progress: '\u23f3',
7
+ done: '\u2705',
8
+ arrow: '\u2192',
9
+ bullet: '\u2022',
10
+ star: '\u2b50',
11
+ wrench: '\U0001f527',
12
+ rocket: '\U0001f680',
13
+ package: '\U0001f4e6',
14
+ gear: '\u2699\ufe0f',
15
+ }
16
+
17
+ export const colors = {
18
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
19
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
20
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
21
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
22
+ dim: (s) => `\x1b[2m${s}\x1b[0m`,
23
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
24
+ }
25
+
26
+ let _quiet = false
27
+ let _dryRun = false
28
+
29
+ export function configure(opts = {}) {
30
+ if (opts.quiet !== undefined) _quiet = opts.quiet
31
+ if (opts.dryRun !== undefined) _dryRun = opts.dryRun
32
+ }
33
+
34
+ function tag(msg) {
35
+ return _dryRun ? `[DRY-RUN] ${msg}` : msg
36
+ }
37
+
38
+ function tty() {
39
+ return process.stdout.isTTY
40
+ }
41
+
42
+ export function section(title) {
43
+ if (_quiet) return
44
+ const width = Math.min(process.stdout.columns || 60, 72)
45
+ const text = `\u2500\u2500 ${title} `
46
+ const line = text + '\u2500'.repeat(Math.max(0, width - text.length + 1))
47
+ console.log(tag(line))
48
+ }
49
+
50
+ export function step(message) {
51
+ const text = tag(`${icons.progress} ${message}...`)
52
+ if (!_quiet) process.stdout.write(text)
53
+ return (ok = true) => {
54
+ const icon = ok ? icons.success : icons.error
55
+ const doneText = tag(`${icon} ${message}`)
56
+ if (_quiet && !ok) {
57
+ console.warn(doneText)
58
+ } else if (!_quiet) {
59
+ process.stdout.write(`\r${doneText}\n`)
60
+ }
61
+ }
62
+ }
63
+
64
+ export function success(message) {
65
+ console.log(tag(`${icons.success} ${message}`))
66
+ }
67
+
68
+ export function warning(message) {
69
+ console.warn(tag(`${icons.warning}${message}`))
70
+ }
71
+
72
+ export function error(message) {
73
+ console.error(tag(`${icons.error} ${message}`))
74
+ }
75
+
76
+ export function info(message) {
77
+ if (_quiet) return
78
+ console.log(tag(`${icons.info}${message}`))
79
+ }
80
+
81
+ export function bullet(text, indent = 0) {
82
+ if (_quiet) return
83
+ const pad = ' '.repeat(indent)
84
+ console.log(tag(`${pad}${icons.bullet} ${text}`))
85
+ }
86
+
87
+ export function summaryTable(items) {
88
+ if (_quiet || items.length === 0) return
89
+
90
+ const nameWidth = Math.max(...items.map((i) => i.name.length), 15)
91
+ const statusWidth = Math.max(...items.map((i) => String(i.status).length), 8)
92
+
93
+ const top = `\u250c${'\u2500'.repeat(nameWidth + 2)}\u252c${'\u2500'.repeat(statusWidth + 2)}\u2510`
94
+ const sep = `\u251c${'\u2500'.repeat(nameWidth + 2)}\u253c${'\u2500'.repeat(statusWidth + 2)}\u2524`
95
+ const bot = `\u2514${'\u2500'.repeat(nameWidth + 2)}\u2534${'\u2500'.repeat(statusWidth + 2)}\u2518`
96
+
97
+ console.log(tag(top))
98
+ console.log(tag(`\u2502 ${'Componente'.padEnd(nameWidth)} \u2502 ${'Status'.padEnd(statusWidth)} \u2502`))
99
+ console.log(tag(sep))
100
+ for (const item of items) {
101
+ console.log(tag(`\u2502 ${item.name.padEnd(nameWidth)} \u2502 ${String(item.status).padEnd(statusWidth)} \u2502`))
102
+ }
103
+ console.log(tag(bot))
104
+ }
105
+
106
+ export function spinner(message) {
107
+ const frames = ['\u280b', '\u2819', '\u2839', '\u2838', '\u283c', '\u2834', '\u2826', '\u2827', '\u2807', '\u280f']
108
+ let i = 0
109
+ let running = true
110
+
111
+ if (tty() && !_quiet) {
112
+ const interval = setInterval(() => {
113
+ if (!running) { clearInterval(interval); return }
114
+ process.stdout.write(`\r${frames[i % frames.length]} ${tag(message)}...`)
115
+ i++
116
+ }, 80)
117
+ process.stdout.write(`\r${frames[0]} ${tag(message)}...`)
118
+
119
+ return (ok = true) => {
120
+ running = false
121
+ clearInterval(interval)
122
+ const icon = ok ? icons.success : icons.error
123
+ process.stdout.write(`\r${icon} ${tag(message)}\n`)
124
+ }
125
+ } else {
126
+ const text = tag(`${icons.progress} ${message}...`)
127
+ if (!_quiet) process.stdout.write(text)
128
+ return (ok = true) => {
129
+ const icon = ok ? icons.success : icons.error
130
+ const doneText = tag(`${icon} ${message}`)
131
+ if (_quiet && !ok) {
132
+ console.warn(doneText)
133
+ } else if (!_quiet) {
134
+ process.stdout.write(`\r${doneText}\n`)
135
+ }
136
+ }
137
+ }
138
+ }
139
+
140
+ export function printSummary(target, platforms, stats) {
141
+ if (_quiet) return
142
+
143
+ const line = '\u2500'.repeat(Math.min(process.stdout.columns || 60, 60))
144
+ console.log(tag(`${icons.rocket} OpenCode instalado em ${target}`))
145
+ console.log(tag(line))
146
+ console.log('')
147
+
148
+ if (stats) {
149
+ console.log(tag(` Componentes:\t${stats.created} instalados, ${stats.skipped} pulados`))
150
+ if (stats.errors > 0) {
151
+ console.warn(tag(` ${icons.warning}${stats.errors} erro(s) encontrados`))
152
+ }
153
+ console.log('')
154
+ }
155
+
156
+ console.log(tag(` ${icons.star} Próximos passos:`))
157
+ console.log(tag(` ${icons.bullet} Configure seus agentes em opencode.json`))
158
+ console.log(tag(` ${icons.bullet} Adicione MCP servers em mcp.json`))
159
+ console.log(tag(` ${icons.bullet} Rode 'opencode doctor' para verificar a instalação`))
160
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * component.mjs — Component resolution utilities for the OpenCode installer
3
+ */
4
+
5
+ /** Available component identifiers */
6
+ export const COMPONENT_NAMES = [
7
+ 'agents',
8
+ 'skills',
9
+ 'instructions',
10
+ 'prompts',
11
+ 'commands',
12
+ 'plugins',
13
+ 'runtime',
14
+ ]
15
+
16
+ /**
17
+ * Parse --components flag into a Set.
18
+ * @param {string|string[]} spec - Comma-separated string or array of component names
19
+ * @returns {Set<string>}
20
+ */
21
+ export function resolveComponents(spec) {
22
+ if (Array.isArray(spec)) return new Set(spec)
23
+ return new Set(spec.split(',').map((s) => s.trim()))
24
+ }
@@ -0,0 +1,308 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * migrate-config.mjs — Standalone migration CLI with checkpoint crash recovery
4
+ *
5
+ * Runs configuration migrations with an atomic checkpoint system.
6
+ * If the process crashes mid-migration, the checkpoint file persists
7
+ * so that --resume picks up where it left off.
8
+ *
9
+ * Usage:
10
+ * node scripts/install/migrate-config.mjs --from=1.1.0 --to=1.1.1 --target=~/.config/opencode
11
+ * node scripts/install/migrate-config.mjs --resume --target=~/.config/opencode
12
+ * node scripts/install/migrate-config.mjs --list --target=~/.config/opencode
13
+ * node scripts/install/migrate-config.mjs --dry-run --from=1.1.0 --to=1.1.1
14
+ */
15
+
16
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
17
+ import { join } from 'node:path'
18
+ import {
19
+ readState,
20
+ writeState,
21
+ createInitialState,
22
+ addMigrationRecord,
23
+ updateVersion,
24
+ } from './state.mjs'
25
+ import {
26
+ readCheckpoint,
27
+ writeCheckpoint,
28
+ advanceCheckpoint,
29
+ removeCheckpoint,
30
+ resumePendingMigration,
31
+ listAppliedMigrations,
32
+ } from './migrate.mjs'
33
+
34
+ // ---------------------------------------------------------------------------
35
+ // Migration step definitions
36
+ // Each step is an idempotent function(target, state, { dryRun }) => void
37
+ // ---------------------------------------------------------------------------
38
+
39
+ const MIGRATION_STEPS = {
40
+ /**
41
+ * migrate_state_v2: Upgrade install-state.json from schema v1 to v2.
42
+ * Adds schema_version field, components tracking, and migration history.
43
+ */
44
+ migrate_state_v2: (target, state, { dryRun }) => {
45
+ if (state.schema_version && state.schema_version >= 2) {
46
+ return
47
+ }
48
+ if (!dryRun) {
49
+ state.schema_version = 2
50
+ state.components = state.components || {
51
+ agents: { version: 1, status: 'pending' },
52
+ skills: { version: 1, status: 'pending' },
53
+ plugins: { version: 1, status: 'pending' },
54
+ runtime: { version: 1, status: 'pending' },
55
+ }
56
+ state.applied_migrations = state.applied_migrations || []
57
+ state.migration_checkpoint = null
58
+ }
59
+ },
60
+
61
+ /**
62
+ * update_schema: Ensure all v2 schema fields exist with defaults.
63
+ */
64
+ update_schema: (target, state, { dryRun }) => {
65
+ if (!dryRun) {
66
+ if (!state.components) {
67
+ state.components = {
68
+ agents: { version: 1, status: 'pending' },
69
+ skills: { version: 1, status: 'pending' },
70
+ plugins: { version: 1, status: 'pending' },
71
+ runtime: { version: 1, status: 'pending' },
72
+ }
73
+ }
74
+ for (const key of ['agents', 'skills', 'plugins', 'runtime']) {
75
+ if (!state.components[key]) {
76
+ state.components[key] = { version: 1, status: 'pending' }
77
+ }
78
+ }
79
+ state.applied_migrations = state.applied_migrations || []
80
+ if (state.migration_checkpoint === undefined) {
81
+ state.migration_checkpoint = null
82
+ }
83
+ }
84
+ },
85
+
86
+ /**
87
+ * migrate_plugins: Normalize plugin component tracking.
88
+ */
89
+ migrate_plugins: (target, state, { dryRun }) => {
90
+ if (!dryRun) {
91
+ if (!state.components) state.components = {}
92
+ const p = state.components.plugins || { version: 1, status: 'pending' }
93
+ p.version = Math.max(p.version || 0, 1)
94
+ state.components.plugins = p
95
+ }
96
+ },
97
+ }
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // Migration step ordering (a step runs ONLY after all predecessors complete)
101
+ // ---------------------------------------------------------------------------
102
+
103
+ const STEP_ORDER = Object.keys(MIGRATION_STEPS)
104
+
105
+ /**
106
+ * Resolve which steps to run based on from/to version comparison.
107
+ * For now, runs all steps. In the future this can be gated by version.
108
+ * @param {string} fromVersion
109
+ * @param {string} toVersion
110
+ * @returns {string[]} Ordered list of step names
111
+ */
112
+ function resolveSteps(fromVersion, toVersion) {
113
+ return [...STEP_ORDER]
114
+ }
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // CLI
118
+ // ---------------------------------------------------------------------------
119
+
120
+ function parseArgs() {
121
+ const args = process.argv.slice(2)
122
+ const result = {
123
+ from: null,
124
+ to: null,
125
+ target: process.cwd(),
126
+ dryRun: false,
127
+ resume: false,
128
+ list: false,
129
+ help: false,
130
+ }
131
+
132
+ for (let i = 0; i < args.length; i++) {
133
+ const arg = args[i]
134
+ if (arg === '--dry-run') result.dryRun = true
135
+ else if (arg === '--resume') result.resume = true
136
+ else if (arg === '--list') result.list = true
137
+ else if (arg === '--help' || arg === '-h') result.help = true
138
+ else if (arg.startsWith('--from=')) result.from = arg.split('=', 2)[1]
139
+ else if (arg.startsWith('--to=')) result.to = arg.split('=', 2)[1]
140
+ else if (arg.startsWith('--target=')) result.target = arg.split('=', 2)[1]
141
+ else if (arg === '--from') result.from = args[++i]
142
+ else if (arg === '--to') result.to = args[++i]
143
+ else if (arg === '--target') result.target = args[++i]
144
+ }
145
+
146
+ return result
147
+ }
148
+
149
+ function showHelp() {
150
+ console.log(`
151
+ migrate-config.mjs — Pantheon configuration migration CLI
152
+
153
+ Usage:
154
+ node scripts/install/migrate-config.mjs --from=1.1.0 --to=1.1.1 --target=~/.config/opencode
155
+ node scripts/install/migrate-config.mjs --resume --target=~/.config/opencode
156
+ node scripts/install/migrate-config.mjs --list --target=~/.config/opencode
157
+ node scripts/install/migrate-config.mjs --dry-run --from=1.1.0 --to=1.1.1
158
+ node scripts/install/migrate-config.mjs --help
159
+
160
+ Options:
161
+ --from=<version> Source version (required for new migrations)
162
+ --to=<version> Target version (required for new migrations)
163
+ --target=<path> Installation directory (default: cwd)
164
+ --dry-run Preview without writing
165
+ --resume Resume from pending checkpoint (crash recovery)
166
+ --list Show applied migration history
167
+ --help Show this help
168
+
169
+ Crash Recovery:
170
+ If the process is interrupted mid-migration, a checkpoint file is left
171
+ at .pantheon/.migration-checkpoint. Run with --resume to continue.
172
+ `)
173
+ }
174
+
175
+ function main() {
176
+ const config = parseArgs()
177
+
178
+ if (config.help) {
179
+ showHelp()
180
+ return
181
+ }
182
+
183
+ // ── List mode ──
184
+ if (config.list) {
185
+ const history = listAppliedMigrations(config.target)
186
+ if (!history.currentVersion) {
187
+ console.log(' \u2139\uFE0F No install state found in: ' + config.target)
188
+ return
189
+ }
190
+ console.log(` Current version: ${history.currentVersion}`)
191
+ console.log(` Previous version: ${history.previousVersion || 'N/A'}`)
192
+ console.log(` Applied migrations: ${history.migrations.length}`)
193
+ for (const m of history.migrations) {
194
+ const icon =
195
+ m.status === 'completed'
196
+ ? '\u2705'
197
+ : m.status === 'rolled_back'
198
+ ? '\u{1F504}'
199
+ : '\u26A0\uFE0F'
200
+ console.log(` ${icon} ${m.from} \u2192 ${m.to} [${m.status}] (${m.applied_at})`)
201
+ }
202
+ return
203
+ }
204
+
205
+ // ── Resume mode ──
206
+ if (config.resume) {
207
+ const cp = resumePendingMigration(config.target)
208
+ if (!cp) {
209
+ console.log(' \u23ED\uFE0F No pending checkpoint found — nothing to resume')
210
+ return
211
+ }
212
+ config.from = cp.from
213
+ config.to = cp.to
214
+ } else {
215
+ if (!config.from || !config.to) {
216
+ console.error('\u274C --from and --to are required (or use --resume)')
217
+ showHelp()
218
+ process.exit(1)
219
+ }
220
+ }
221
+
222
+ // ── Read state ──
223
+ let state = readState(config.target)
224
+ if (!state) {
225
+ state = createInitialState(config.from)
226
+ console.log(` \u{1F4DD} Creating new install state (v${state.schema_version})`)
227
+ if (!config.dryRun) writeState(config.target, state)
228
+ } else {
229
+ console.log(
230
+ ` \u{1F4CB} Current state: v${state.schema_version}, version ${state.pantheon_version}`,
231
+ )
232
+ }
233
+
234
+ // ── Resolve checkpoint ──
235
+ let cp = readCheckpoint(config.target)
236
+
237
+ if (!cp) {
238
+ const steps = resolveSteps(config.from, config.to)
239
+ cp = writeCheckpoint(config.target, config.from, config.to, steps)
240
+ console.log(` \u{1F4DD} Checkpoint written: ${config.from} \u2192 ${config.to}`)
241
+ } else {
242
+ console.log(
243
+ ` \u{1F504} Resuming from checkpoint (${cp.steps_completed.length} steps done, ${cp.steps_pending.length} remaining)`,
244
+ )
245
+ }
246
+
247
+ // ── Run pending steps ──
248
+ const stepsToRun = [...cp.steps_pending]
249
+ let failed = false
250
+
251
+ for (const step of stepsToRun) {
252
+ const stepFn = MIGRATION_STEPS[step]
253
+ if (!stepFn) {
254
+ console.warn(` \u26A0\uFE0F Unknown migration step: ${step} — skipping`)
255
+ if (!config.dryRun) {
256
+ advanceCheckpoint(config.target, step)
257
+ }
258
+ continue
259
+ }
260
+
261
+ console.log(` \u25B6\uFE0F Running: ${step}`)
262
+ try {
263
+ stepFn(config.target, state, { dryRun: config.dryRun })
264
+
265
+ if (!config.dryRun) {
266
+ advanceCheckpoint(config.target, step)
267
+ writeState(config.target, state)
268
+ console.log(` \u2705 ${step} completed`)
269
+ } else {
270
+ console.log(` \u2705 ${step} would complete (dry-run)`)
271
+ }
272
+ } catch (err) {
273
+ console.error(` \u274C ${step} failed: ${err.message}`)
274
+ failed = true
275
+ break
276
+ }
277
+ }
278
+
279
+ // ── Finalize ──
280
+ if (failed) {
281
+ console.error(`\n \u274C Migration failed at checkpoint.`)
282
+ console.error(
283
+ ` Resume with: node scripts/install/migrate-config.mjs --resume --target=${config.target}`,
284
+ )
285
+ process.exit(1)
286
+ }
287
+
288
+ if (!config.dryRun) {
289
+ updateVersion(state, config.to)
290
+ addMigrationRecord(state, config.from, config.to, 'completed')
291
+ removeCheckpoint(config.target)
292
+ writeState(config.target, state)
293
+ console.log(
294
+ `\n \u2705 Migration complete: ${config.from} \u2192 ${config.to}`,
295
+ )
296
+ console.log(
297
+ ` State written to: ${join(config.target, '.pantheon', 'install-state.json')}`,
298
+ )
299
+ } else {
300
+ console.log(
301
+ `\n \u2705 Migration would complete (dry-run): ${config.from} \u2192 ${config.to}`,
302
+ )
303
+ }
304
+ }
305
+
306
+ if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) {
307
+ main()
308
+ }