From a69a44a89065166c7636f03982c640d22a8b9e6e Mon Sep 17 00:00:00 2001 From: dan bachelder <325706+dbachelder@users.noreply.github.com> Date: Wed, 11 Mar 2026 13:34:03 -0700 Subject: [PATCH] Add pi global install scripts (#57) --- package.json | 2 ++ scripts/install-pi-global.js | 44 +++++++++++++++++++++++ scripts/uninstall-pi-global.js | 66 ++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+) create mode 100644 scripts/install-pi-global.js create mode 100644 scripts/uninstall-pi-global.js diff --git a/package.json b/package.json index 8577a3b39..10574be0b 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,8 @@ "test": "node --import ./src/resources/extensions/gsd/tests/resolve-ts.mjs --experimental-strip-types --test 'src/resources/extensions/gsd/tests/*.test.ts' 'src/resources/extensions/gsd/tests/*.test.mjs' 'src/tests/*.test.ts'", "dev": "tsc --watch", "postinstall": "node scripts/postinstall.js", + "pi:install-global": "node scripts/install-pi-global.js", + "pi:uninstall-global": "node scripts/uninstall-pi-global.js", "sync-pkg-version": "node scripts/sync-pkg-version.cjs", "prepublishOnly": "npm run sync-pkg-version && npm run build" }, diff --git a/scripts/install-pi-global.js b/scripts/install-pi-global.js new file mode 100644 index 000000000..6f4f36a3e --- /dev/null +++ b/scripts/install-pi-global.js @@ -0,0 +1,44 @@ +#!/usr/bin/env node +import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' +import os from 'node:os' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const resourcesDir = resolve(__dirname, '..', 'src', 'resources') +const piRoot = join(os.homedir(), '.pi') +const piAgentDir = join(piRoot, 'agent') + +const copyDir = (name) => { + const src = join(resourcesDir, name) + const dest = join(piAgentDir, name) + if (!existsSync(src)) return false + mkdirSync(dest, { recursive: true }) + cpSync(src, dest, { recursive: true, force: true }) + return true +} + +mkdirSync(piAgentDir, { recursive: true }) + +const copied = [] +if (copyDir('extensions')) copied.push('extensions') +if (copyDir('skills')) copied.push('skills') +if (copyDir('agents')) copied.push('agents') + +const agentsMdSrc = join(resourcesDir, 'AGENTS.md') +if (existsSync(agentsMdSrc)) { + writeFileSync(join(piAgentDir, 'AGENTS.md'), readFileSync(agentsMdSrc)) + copied.push('AGENTS.md') +} + +const workflowSrc = join(resourcesDir, 'GSD-WORKFLOW.md') +if (existsSync(workflowSrc)) { + writeFileSync(join(piRoot, 'GSD-WORKFLOW.md'), readFileSync(workflowSrc)) + copied.push('GSD-WORKFLOW.md') +} + +process.stdout.write( + `Installed GSD resources for pi in ${piRoot}\n` + + `Copied: ${copied.join(', ')}\n` + + `Extensions are now available under ${join(piAgentDir, 'extensions')}\n` +) diff --git a/scripts/uninstall-pi-global.js b/scripts/uninstall-pi-global.js new file mode 100644 index 000000000..616ec503e --- /dev/null +++ b/scripts/uninstall-pi-global.js @@ -0,0 +1,66 @@ +#!/usr/bin/env node +import { existsSync, readFileSync, readdirSync, rmSync, rmdirSync } from 'node:fs' +import os from 'node:os' +import { dirname, join, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = dirname(fileURLToPath(import.meta.url)) +const resourcesDir = resolve(__dirname, '..', 'src', 'resources') +const piRoot = join(os.homedir(), '.pi') +const piAgentDir = join(piRoot, 'agent') + +const removed = [] +const skipped = [] + +function safeRemove(path, label) { + if (!existsSync(path)) return + rmSync(path, { recursive: true, force: true }) + removed.push(label) +} + +function removeResourceEntries(containerName) { + const srcDir = join(resourcesDir, containerName) + const destDir = join(piAgentDir, containerName) + if (!existsSync(srcDir) || !existsSync(destDir)) return + + for (const entry of readdirSync(srcDir)) { + safeRemove(join(destDir, entry), `${containerName}/${entry}`) + } + + try { + if (readdirSync(destDir).length === 0) { + rmdirSync(destDir) + removed.push(`${containerName}/`) + } + } catch { + // ignore non-empty or missing dirs + } +} + +function removeIfContentMatches(targetPath, sourcePath, label) { + if (!existsSync(targetPath) || !existsSync(sourcePath)) return + try { + const target = readFileSync(targetPath, 'utf8') + const source = readFileSync(sourcePath, 'utf8') + if (target === source) { + rmSync(targetPath, { force: true }) + removed.push(label) + } else { + skipped.push(`${label} (modified, left in place)`) + } + } catch { + skipped.push(`${label} (could not verify, left in place)`) + } +} + +removeResourceEntries('extensions') +removeResourceEntries('skills') +removeResourceEntries('agents') +removeIfContentMatches(join(piAgentDir, 'AGENTS.md'), join(resourcesDir, 'AGENTS.md'), 'agent/AGENTS.md') +removeIfContentMatches(join(piRoot, 'GSD-WORKFLOW.md'), join(resourcesDir, 'GSD-WORKFLOW.md'), 'GSD-WORKFLOW.md') + +process.stdout.write( + `Removed GSD resources from ${piRoot}\n` + + `Removed: ${removed.length ? removed.join(', ') : '(nothing)'}\n` + + (skipped.length ? `Skipped: ${skipped.join(', ')}\n` : '') +)