pantheon-opencode 1.1.1-beta.2 → 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.
@@ -0,0 +1,139 @@
1
+ /**
2
+ * plugin.mjs — Plugin installation utilities for the OpenCode installer
3
+ */
4
+
5
+ import { execSync } from 'node:child_process'
6
+ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
7
+ import { join } from 'node:path'
8
+ import { warning } from './cli-ui.mjs'
9
+ import { writeIfChanged } from './shared.mjs'
10
+
11
+ /**
12
+ * Install a plugin from source to destination.
13
+ * @param {string} srcDir - Source plugin directory (e.g. ROOT/src/plugins/tui)
14
+ * @param {string} dstDir - Destination plugin directory
15
+ * @param {object} [options]
16
+ * @param {boolean} [options.dryRun=false] - If true, don't write files
17
+ * @param {boolean} [options.clean=false] - If true, remove existing before install
18
+ * @returns {{ created: number, skipped: number, errors: number }}
19
+ */
20
+ export function installPlugin(srcDir, dstDir, { dryRun = false, clean = false } = {}) {
21
+ const result = { created: 0, skipped: 0, errors: 0 }
22
+
23
+ if (!dryRun) mkdirSync(dstDir, { recursive: true })
24
+ if (clean && existsSync(dstDir) && !dryRun) {
25
+ rmSync(dstDir, { recursive: true, force: true })
26
+ mkdirSync(dstDir, { recursive: true })
27
+ }
28
+ if (!dryRun) mkdirSync(join(dstDir, 'dist'), { recursive: true })
29
+
30
+ // Copy src/index.tsx -> index.tsx
31
+ const srcIdx = join(srcDir, 'src', 'index.tsx')
32
+ if (existsSync(srcIdx)) {
33
+ const idxStatus = writeIfChanged(join(dstDir, 'index.tsx'), readFileSync(srcIdx, 'utf8'), dryRun)
34
+ if (idxStatus === 'created') result.created++
35
+ else result.skipped++
36
+ }
37
+
38
+ // Copy dist/* files
39
+ const distSrc = join(srcDir, 'dist')
40
+ if (existsSync(distSrc)) {
41
+ for (const f of readdirSync(distSrc)) {
42
+ const c = readFileSync(join(distSrc, f), 'utf8')
43
+ writeIfChanged(join(dstDir, 'dist', f), c, dryRun)
44
+ result.created++
45
+ }
46
+ }
47
+
48
+ // Copy package.json
49
+ const pkgSrc = join(srcDir, 'package.json')
50
+ if (existsSync(pkgSrc)) {
51
+ const pkgStatus = writeIfChanged(join(dstDir, 'package.json'), readFileSync(pkgSrc, 'utf8'), dryRun)
52
+ if (pkgStatus === 'created') result.created++
53
+ else result.skipped++
54
+ }
55
+
56
+ // Install plugin dependencies (@opentui/core, @opentui/solid, solid-js)
57
+ if (!dryRun) {
58
+ try {
59
+ execSync('npm install --omit=dev --no-audit', { cwd: dstDir, stdio: 'pipe' })
60
+ } catch (e) {
61
+ warning('Failed to install TUI plugin dependencies: ' + e.message)
62
+ result.errors++
63
+ }
64
+ }
65
+
66
+ return result
67
+ }
68
+
69
+ /**
70
+ * Install plugin dependencies via npm.
71
+ * @param {string} pluginDir - Directory containing package.json
72
+ * @param {object} [options]
73
+ * @param {boolean} [options.dryRun=false] - If true, skip npm install
74
+ */
75
+ export function installPluginDependencies(pluginDir, { dryRun = false } = {}) {
76
+ if (dryRun) return
77
+ try {
78
+ execSync('npm install --omit=dev --no-audit', { cwd: pluginDir, stdio: 'pipe' })
79
+ } catch (e) {
80
+ warning('Failed to install plugin dependencies: ' + e.message)
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Register a plugin in tui.json.
86
+ * @param {string} tuiJsonPath - Path to tui.json
87
+ * @param {string} pluginId - Plugin identifier string
88
+ * @param {object} [options]
89
+ * @param {boolean} [options.dryRun=false]
90
+ * @returns {'created'|'skipped'}
91
+ */
92
+ export function registerPlugin(tuiJsonPath, pluginId, { dryRun = false } = {}) {
93
+ let tuiConfig = { plugin: [] }
94
+ if (existsSync(tuiJsonPath)) {
95
+ try {
96
+ tuiConfig = JSON.parse(readFileSync(tuiJsonPath, 'utf8'))
97
+ } catch {
98
+ /* use default */
99
+ }
100
+ }
101
+ if (!Array.isArray(tuiConfig.plugin)) {
102
+ tuiConfig.plugin = []
103
+ }
104
+ if (!tuiConfig.plugin.includes(pluginId)) {
105
+ tuiConfig.plugin.push(pluginId)
106
+ }
107
+ const tuiContent = `${JSON.stringify(tuiConfig, null, 2)}\n`
108
+ return writeIfChanged(tuiJsonPath, tuiContent, dryRun)
109
+ }
110
+
111
+ /**
112
+ * Unregister stale plugin references from tui.json.
113
+ * @param {string} tuiJsonPath - Path to tui.json
114
+ * @param {string} pluginId - Plugin to unregister
115
+ * @param {object} [options]
116
+ * @param {boolean} [options.dryRun=false]
117
+ * @returns {'created'|'skipped'}
118
+ */
119
+ export function unregisterPlugin(tuiJsonPath, pluginId, { dryRun = false } = {}) {
120
+ let tuiConfig = { plugin: [] }
121
+ if (existsSync(tuiJsonPath)) {
122
+ try {
123
+ tuiConfig = JSON.parse(readFileSync(tuiJsonPath, 'utf8'))
124
+ } catch {
125
+ /* use default */
126
+ }
127
+ }
128
+ if (!Array.isArray(tuiConfig.plugin)) {
129
+ tuiConfig.plugin = []
130
+ }
131
+ // Remove stale plugin refs (old dist/tui.tsx path)
132
+ const staleRefs = [`${pluginId}/dist/tui.tsx`, `${pluginId}/dist/tui.js`]
133
+ for (const stale of staleRefs) {
134
+ const idx = tuiConfig.plugin.indexOf(stale)
135
+ if (idx !== -1) tuiConfig.plugin.splice(idx, 1)
136
+ }
137
+ const tuiContent = `${JSON.stringify(tuiConfig, null, 2)}\n`
138
+ return writeIfChanged(tuiJsonPath, tuiContent, dryRun)
139
+ }
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * state.mjs — State manifest for Pantheon installations (schema v2)
4
+ *
5
+ * Manages the install-state.json manifest with version tracking,
6
+ * component state, and applied migration history.
7
+ *
8
+ * Schema v2:
9
+ * {
10
+ * "schema_version": 2,
11
+ * "pantheon_version": "1.1.1",
12
+ * "previous_version": "1.1.0",
13
+ * "updated_at": "2026-07-26T10:30:00Z",
14
+ * "migration_checkpoint": null,
15
+ * "applied_migrations": [
16
+ * { "from": "1.1.0", "to": "1.1.1", "applied_at": "2026-07-26T10:30:00Z", "status": "completed" }
17
+ * ],
18
+ * "components": {
19
+ * "agents": { "version": 1, "status": "installed" },
20
+ * "skills": { "version": 1, "status": "installed" },
21
+ * "plugins": { "version": 1, "status": "installed" },
22
+ * "runtime": { "version": 1, "status": "installed" }
23
+ * }
24
+ * }
25
+ */
26
+
27
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
28
+ import { join } from 'node:path'
29
+
30
+ const STATE_FILE = 'install-state.json'
31
+ const STATE_DIR = '.pantheon'
32
+
33
+ /**
34
+ * Create a fresh v2 state object for a new install.
35
+ * @param {string} version - Pantheon version string
36
+ * @returns {object} v2 state
37
+ */
38
+ export function createInitialState(version) {
39
+ return {
40
+ schema_version: 2,
41
+ pantheon_version: version || '0.0.0',
42
+ previous_version: null,
43
+ updated_at: new Date().toISOString(),
44
+ migration_checkpoint: null,
45
+ applied_migrations: [],
46
+ components: {
47
+ agents: { version: 1, status: 'pending' },
48
+ skills: { version: 1, status: 'pending' },
49
+ plugins: { version: 1, status: 'pending' },
50
+ runtime: { version: 1, status: 'pending' },
51
+ },
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Read install-state.json from a target directory.
57
+ * Backward-compatible with v1 schema (auto-upgrades on read).
58
+ * @param {string} target - Installation directory
59
+ * @returns {object|null} v2 state object, or null if not found
60
+ */
61
+ export function readState(target) {
62
+ const stateFile = join(target, STATE_DIR, STATE_FILE)
63
+ if (!existsSync(stateFile)) return null
64
+ try {
65
+ const raw = JSON.parse(readFileSync(stateFile, 'utf8'))
66
+ return normalizeState(raw)
67
+ } catch {
68
+ return null
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Detect whether the raw state is v1 format (pre-schema_version).
74
+ * v1 state has pantheon_version (or version) but no schema_version.
75
+ * @param {object} state
76
+ * @returns {boolean}
77
+ */
78
+ export function isV1State(state) {
79
+ if (!state || typeof state !== 'object') return false
80
+ return !state.schema_version && (!!state.pantheon_version || !!state.version)
81
+ }
82
+
83
+ /**
84
+ * Upgrade a v1 state to v2 schema.
85
+ * Preserves all existing fields, adds defaults for new ones.
86
+ * @param {object} state - v1 state object
87
+ * @returns {object} v2 state object
88
+ */
89
+ export function upgradeStateV1toV2(state) {
90
+ const now = new Date().toISOString()
91
+ return {
92
+ schema_version: 2,
93
+ pantheon_version: state.pantheon_version || state.version || 'unknown',
94
+ previous_version: state.previous_version || null,
95
+ updated_at: state.updated_at || now,
96
+ migration_checkpoint: null,
97
+ applied_migrations: Array.isArray(state.applied_migrations) ? state.applied_migrations : [],
98
+ components: state.components && typeof state.components === 'object' ? state.components : {
99
+ agents: { version: 1, status: 'installed' },
100
+ skills: { version: 1, status: 'installed' },
101
+ plugins: { version: 1, status: 'unknown' },
102
+ runtime: { version: 1, status: 'unknown' },
103
+ },
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Normalize any state to v2 (handles v1 → v2 upgrade).
109
+ * @param {object} state
110
+ * @returns {object|null}
111
+ */
112
+ function normalizeState(state) {
113
+ if (!state || typeof state !== 'object') return null
114
+ if (isV1State(state)) return upgradeStateV1toV2(state)
115
+ if (!state.schema_version || state.schema_version < 2) return upgradeStateV1toV2(state)
116
+ return state
117
+ }
118
+
119
+ /**
120
+ * Write state to target's install-state.json (always v2 format).
121
+ * @param {string} target - Installation directory
122
+ * @param {object} state - v2 state object
123
+ */
124
+ export function writeState(target, state) {
125
+ const stateDir = join(target, STATE_DIR)
126
+ if (!existsSync(stateDir)) {
127
+ mkdirSync(stateDir, { recursive: true })
128
+ }
129
+ const stateFile = join(stateDir, STATE_FILE)
130
+ state.updated_at = new Date().toISOString()
131
+ state.schema_version = 2
132
+ writeFileSync(stateFile, `${JSON.stringify(state, null, 2)}\n`, 'utf8')
133
+ }
134
+
135
+ /**
136
+ * Check whether install-state.json exists in target.
137
+ * @param {string} target
138
+ * @returns {boolean}
139
+ */
140
+ export function hasState(target) {
141
+ return existsSync(join(target, STATE_DIR, STATE_FILE))
142
+ }
143
+
144
+ /**
145
+ * Add a migration record to the applied_migrations array.
146
+ * @param {object} state
147
+ * @param {string} fromVersion
148
+ * @param {string} toVersion
149
+ * @param {string} [status='completed']
150
+ */
151
+ export function addMigrationRecord(state, fromVersion, toVersion, status = 'completed') {
152
+ if (!Array.isArray(state.applied_migrations)) {
153
+ state.applied_migrations = []
154
+ }
155
+ state.applied_migrations.push({
156
+ from: fromVersion,
157
+ to: toVersion,
158
+ applied_at: new Date().toISOString(),
159
+ status,
160
+ })
161
+ }
162
+
163
+ /**
164
+ * Update the pantheon_version and previous_version.
165
+ * @param {object} state
166
+ * @param {string} newVersion
167
+ */
168
+ export function updateVersion(state, newVersion) {
169
+ state.previous_version = state.pantheon_version || null
170
+ state.pantheon_version = newVersion
171
+ }
172
+
173
+ /**
174
+ * Get a component's state object.
175
+ * @param {object} state
176
+ * @param {string} component - Component name (agents, skills, plugins, runtime)
177
+ * @returns {{ version: number, status: string }|null}
178
+ */
179
+ export function getComponentState(state, component) {
180
+ if (!state.components) return null
181
+ return state.components[component] || null
182
+ }
183
+
184
+ /**
185
+ * Set a component's version and status.
186
+ * @param {object} state
187
+ * @param {string} component
188
+ * @param {number} version
189
+ * @param {string} status
190
+ */
191
+ export function setComponentState(state, component, version, status) {
192
+ if (!state.components) state.components = {}
193
+ state.components[component] = { version, status }
194
+ }
@@ -18,15 +18,15 @@
18
18
  "peerDependencies": {
19
19
  "@opencode-ai/plugin": ">=1.14.0",
20
20
  "@opencode-ai/sdk": ">=1.14.0",
21
- "@opentui/core": ">=0.2.0",
22
- "@opentui/solid": ">=0.2.0",
21
+ "@opentui/core": ">=0.4.5",
22
+ "@opentui/solid": ">=0.4.5",
23
23
  "solid-js": ">=1.9.0"
24
24
  },
25
25
  "devDependencies": {},
26
26
  "dependencies": {
27
27
  "@opencode-ai/plugin": "^1.14.0",
28
- "@opentui/core": "^0.2.0",
29
- "@opentui/solid": "^0.2.0",
28
+ "@opentui/core": "^0.4.5",
29
+ "@opentui/solid": "^0.4.5",
30
30
  "solid-js": "^1.9.0"
31
31
  }
32
32
  }