OverviewFilesCommitsIssuesDocsReleasesPeers

pgh-legacy commits / db9c2e1

Implement app install/list/remove and PGH_TOKEN env support
- pgh app install: clones release branch, creates launcher in ~/.local/bin/ - pgh app list: scans installed launchers - pgh app remove: deletes install dir + launcher - PGH_TOKEN env var overrides config file token Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
db9c2e1b42c1f81d1f9279722b6ba71cd580aa3f
by plankatron <fleeky@gmail.com> · 2026-03-30T14:11:58.000Z

Changed files (1) +83 -7

bin/pgh.js +83 -7
……@@ -27,6 +27,10 @@ try { config = { ...config, ...JSON.parse(fs.readFileSync(configPath, 'utf-8'))
2727const serverFlag = args.indexOf('--server')
2828if (serverFlag >= 0) config.server = args[serverFlag + 1]
2929
30// Token from env takes priority
31const envToken = typeof Bare !== 'undefined' ? Bare.env?.PGH_TOKEN : process.env.PGH_TOKEN
32if (envToken) config.token = envToken
33
3034const client = new PghClient(config)
3135
3236async function run () {
……@@ -104,15 +108,71 @@ async function run () {
104108 case 'app': {
105109 if (sub === 'install') {
106110 const repo = args[2]
107 const name = args[3] || repo
108 if (!repo) return usage('pgh app install <repo> [name]')
111 const nameFlag = args.indexOf('--name')
112 const name = nameFlag >= 0 ? args[nameFlag + 1] : (args[3] || repo)
113 if (!repo) return usage('pgh app install <repo> [--name name]')
114
109115 const installDir = path.join(os.homedir(), '.local', 'share', name)
110 console.log(`Cloning ${repo} release branch to ${installDir}...`)
111 // TODO: implement git clone of release branch
112 console.log('Not yet implemented — use: git clone --depth 1 -b release <url>')
116 const binDir = path.join(os.homedir(), '.local', 'bin')
117 const binPath = path.join(binDir, name)
118
119 console.log(`Installing ${repo} → ${installDir}`)
120
121 // Determine clone URL (punch:// key or server-based)
122 const cloneUrl = repo.startsWith('punch://') ? repo : `${config.server}/${repo}.git`
123
124 const { spawn: spawnChild } = await import('bare-subprocess')
125 try {
126 await execCmd(spawnChild, ['git', 'clone', '--depth', '1', '-b', 'release', cloneUrl, installDir])
127 } catch (err) {
128 // If dir exists, do a pull instead
129 if (err.message.includes('already exists')) {
130 console.log('Directory exists, pulling latest...')
131 await execCmd(spawnChild, ['git', '-C', installDir, 'fetch', 'origin', 'release'])
132 await execCmd(spawnChild, ['git', '-C', installDir, 'reset', '--hard', 'FETCH_HEAD'])
133 } else {
134 throw err
135 }
136 }
137
138 // Create launcher script
139 try { fs.mkdirSync(binDir, { recursive: true }) } catch {}
140 fs.writeFileSync(binPath, `#!/bin/sh\nexec bare "${installDir}/index.js" "$@"\n`)
141 fs.chmodSync(binPath, 0o755)
142
143 console.log(`Installed. Run: ${name}`)
113144 return
114145 }
115 return usage('pgh app install <repo> [name]')
146
147 if (sub === 'list') {
148 const shareDir = path.join(os.homedir(), '.local', 'share')
149 const binDir = path.join(os.homedir(), '.local', 'bin')
150 try {
151 const bins = fs.readdirSync(binDir)
152 for (const b of bins) {
153 try {
154 const content = fs.readFileSync(path.join(binDir, b), 'utf-8')
155 if (content.includes('.local/share/') && content.includes('bare')) {
156 console.log(b)
157 }
158 } catch {}
159 }
160 } catch { console.log('No apps installed') }
161 return
162 }
163
164 if (sub === 'remove') {
165 const name = args[2]
166 if (!name) return usage('pgh app remove <name>')
167 const installDir = path.join(os.homedir(), '.local', 'share', name)
168 const binPath = path.join(os.homedir(), '.local', 'bin', name)
169 try { fs.rmSync(binPath) } catch {}
170 try { fs.rmSync(installDir, { recursive: true }) } catch {}
171 console.log(`Removed ${name}`)
172 return
173 }
174
175 return usage('pgh app install|list|remove')
116176 }
117177
118178 default:
……@@ -132,14 +192,30 @@ Commands:
132192 pgh auth Start auth flow
133193 pgh config Show config
134194 pgh config-set <key> <value> Set config
135 pgh app install <repo> [name] Install app
195 pgh app install <repo> [--name n] Install app from release branch
196 pgh app list List installed apps
197 pgh app remove <name> Uninstall app
136198
137199Options:
138200 --server <url> Override server URL
201
202Environment:
203 PGH_TOKEN Auth token (overrides config)
139204`)
140205 }
141206}
142207
208function execCmd (spawnChild, [cmd, ...cmdArgs]) {
209 return new Promise((resolve, reject) => {
210 const child = spawnChild(cmd, cmdArgs, { stdio: ['ignore', 'pipe', 'pipe'] })
211 const out = []; const err = []
212 child.stdout.on('data', d => out.push(d))
213 child.stderr.on('data', d => err.push(d))
214 child.on('exit', code => code === 0 ? resolve(Buffer.concat(out).toString()) : reject(new Error(Buffer.concat(err).toString() || `exit ${code}`)))
215 child.on('error', reject)
216 })
217}
218
143219function log (data) { console.log(JSON.stringify(data, null, 2)) }
144220function logList (arr, fmt) { arr.forEach(item => console.log(fmt(item))) }
145221function usage (msg) { console.log('Usage:', msg) }