pantheon-opencode 1.0.0
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/AGENTS.md +37 -0
- package/LICENSE +21 -0
- package/README.md +1013 -0
- package/bin/pantheon-init.mjs +183 -0
- package/commands/pantheon-audit.md +25 -0
- package/commands/pantheon-bg.md +10 -0
- package/commands/pantheon-consolidate.md +11 -0
- package/commands/pantheon-deepwork.md +128 -0
- package/commands/pantheon-doc.md +10 -0
- package/commands/pantheon-focus.md +9 -0
- package/commands/pantheon-forget.md +58 -0
- package/commands/pantheon-hash.md +11 -0
- package/commands/pantheon-optimize.md +79 -0
- package/commands/pantheon-remember.md +44 -0
- package/commands/pantheon-search.md +48 -0
- package/commands/pantheon-status.md +71 -0
- package/commands/pantheon-todo.md +11 -0
- package/commands/pantheon.md +49 -0
- package/docs/AGENT-MCP.md +194 -0
- package/docs/ARCHITECTURE.md +384 -0
- package/docs/BRANCH-PROTECTION.md +142 -0
- package/docs/INDEX.md +81 -0
- package/docs/INSTALLATION.md +217 -0
- package/docs/MCP.md +238 -0
- package/docs/MEMORY.md +471 -0
- package/docs/MIGRATION-MEMORY-BANK.md +139 -0
- package/docs/PLATFORMS.md +5 -0
- package/docs/QUICKSTART.md +49 -0
- package/docs/README.md +18 -0
- package/docs/RELEASING.md +256 -0
- package/docs/SETUP.md +5 -0
- package/docs/UPGRADING.md +41 -0
- package/docs/mcp-recommendations.md +439 -0
- package/docs/mcp-tools.md +156 -0
- package/docs/mcp-user-guide.md +204 -0
- package/docs/persistence-mcp.md +111 -0
- package/package.json +72 -0
- package/pantheon.schema.json +158 -0
- package/scripts/__init__.py +0 -0
- package/scripts/_pantheon_paths.py +68 -0
- package/scripts/check-version-consistency.sh +23 -0
- package/scripts/code_mode_server.py +202 -0
- package/scripts/doctor.mjs +763 -0
- package/scripts/generate-prompts.sh +222 -0
- package/scripts/generate-routing-docs.mjs +104 -0
- package/scripts/hash_verify.py +192 -0
- package/scripts/init-pantheon-mcp.sh +118 -0
- package/scripts/install/agents-md.mjs +214 -0
- package/scripts/install/health-check.mjs +196 -0
- package/scripts/install/migrate.mjs +209 -0
- package/scripts/install/opencode.mjs +645 -0
- package/scripts/install/shared.mjs +655 -0
- package/scripts/install/venv.mjs +116 -0
- package/scripts/install-mcp.mjs +885 -0
- package/scripts/install.mjs +26 -0
- package/scripts/manifest.mjs +622 -0
- package/scripts/mcp_persistence_server.py +459 -0
- package/scripts/mcp_resources_server.py +206 -0
- package/scripts/memory_cache.py +78 -0
- package/scripts/memory_mcp_server.py +605 -0
- package/scripts/paths.py +64 -0
- package/scripts/prune_context.py +72 -0
- package/scripts/release-bundle.mjs +109 -0
- package/scripts/scrub-secrets.py +282 -0
- package/scripts/scrub_secrets.py +281 -0
- package/scripts/test-context-compression.sh +166 -0
- package/scripts/themis_heuristic_scan.py +287 -0
- package/scripts/todo_enforcer.py +242 -0
- package/scripts/uninstall.mjs +1057 -0
- package/scripts/validate-routing.mjs +160 -0
- package/scripts/validate_agent_frontmatter.py +226 -0
- package/scripts/versioning.mjs +254 -0
- package/skills-lock.json +16 -0
- package/src/agents/aphrodite.md +162 -0
- package/src/agents/apollo.md +109 -0
- package/src/agents/athena.md +226 -0
- package/src/agents/demeter.md +146 -0
- package/src/agents/gaia.md +82 -0
- package/src/agents/hephaestus.md +105 -0
- package/src/agents/hermes.md +302 -0
- package/src/agents/iris.md +99 -0
- package/src/agents/mnemosyne.md +226 -0
- package/src/agents/nyx.md +87 -0
- package/src/agents/prometheus.md +199 -0
- package/src/agents/talos.md +93 -0
- package/src/agents/themis.md +187 -0
- package/src/agents/zeus.md +209 -0
- package/src/instructions/agent-return-format.instructions.md +26 -0
- package/src/instructions/backend-standards.instructions.md +45 -0
- package/src/instructions/documentation-standards.instructions.md +53 -0
- package/src/instructions/frontend-standards.instructions.md +46 -0
- package/src/instructions/memory-protocol.instructions.md +67 -0
- package/src/instructions/yagni.instructions.md +21 -0
- package/src/instructions/zeus-anti-stall.instructions.md +72 -0
- package/src/instructions/zeus-communication-rules.instructions.md +15 -0
- package/src/instructions/zeus-council-synthesis.instructions.md +105 -0
- package/src/instructions/zeus-timeout-retry.instructions.md +127 -0
- package/src/mcp/_pantheon_paths.py +67 -0
- package/src/mcp/code_mode_server.py +202 -0
- package/src/mcp/init-pantheon-mcp.sh +118 -0
- package/src/mcp/install-mcp.mjs +885 -0
- package/src/mcp/mcp_persistence_server.py +458 -0
- package/src/mcp/mcp_resources_server.py +205 -0
- package/src/mcp/memory_mcp_server.py +604 -0
- package/src/mcp/requirements-mcp-core.txt +5 -0
- package/src/mcp/requirements-mcp.txt +5 -0
- package/src/plugin.ts +19 -0
- package/src/plugins/tui/dist/tui.js +143 -0
- package/src/plugins/tui/dist/tui.tsx +144 -0
- package/src/plugins/tui/package.json +32 -0
- package/src/plugins/tui/src/index.tsx +144 -0
- package/src/plugins/tui/tsdown.config.ts +22 -0
- package/src/routing.yml +499 -0
- package/src/skills/README.md +230 -0
- package/src/skills/agent-coordination/SKILL.md +95 -0
- package/src/skills/artifact-management/SKILL.md +118 -0
- package/src/skills/auto-continue/SKILL.md +280 -0
- package/src/skills/code-review-checklist/SKILL.md +139 -0
- package/src/skills/context-compression/SKILL.md +861 -0
- package/src/skills/git-workflow-and-versioning/SKILL.md +32 -0
- package/src/skills/incremental-implementation/SKILL.md +27 -0
- package/src/skills/memory-bank/SKILL.md +165 -0
- package/src/skills/orchestration-workflow/SKILL.md +311 -0
- package/src/skills/security-hardening/SKILL.md +36 -0
- package/src/skills/session-goal/SKILL.md +138 -0
- package/src/skills/spec-driven-development/SKILL.md +23 -0
- package/src/skills/tdd-with-agents/SKILL.md +170 -0
- package/src/skills/visual-review-pipeline/SKILL.md +200 -0
|
@@ -0,0 +1,1057 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* uninstall.mjs â Multi-platform Pantheon agent uninstaller
|
|
5
|
+
*
|
|
6
|
+
* Removes Pantheon agent files and config from a project directory.
|
|
7
|
+
* The reverse of install.mjs.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node scripts/uninstall.mjs auto-detect, cwd
|
|
11
|
+
* node scripts/uninstall.mjs --target /path/to/project auto-detect, target
|
|
12
|
+
* node scripts/uninstall.mjs --platforms opencode,claude specific platforms
|
|
13
|
+
* node scripts/uninstall.mjs --target /path --platforms all all platforms
|
|
14
|
+
* node scripts/uninstall.mjs --dry-run preview without deleting
|
|
15
|
+
* node scripts/uninstall.mjs --force skip confirmation
|
|
16
|
+
* node scripts/uninstall.mjs --help show this help
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs'
|
|
20
|
+
import { join } from 'node:path'
|
|
21
|
+
import { createInterface } from 'node:readline'
|
|
22
|
+
import { isatty } from 'node:tty'
|
|
23
|
+
import { detectPlatforms, resolveTarget } from './install/shared.mjs'
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Constants
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
const PANTHEON_AGENT_NAMES = [
|
|
30
|
+
'zeus',
|
|
31
|
+
'athena',
|
|
32
|
+
'apollo',
|
|
33
|
+
'hermes',
|
|
34
|
+
'aphrodite',
|
|
35
|
+
'demeter',
|
|
36
|
+
'themis',
|
|
37
|
+
'prometheus',
|
|
38
|
+
'hephaestus',
|
|
39
|
+
'chiron',
|
|
40
|
+
'echo',
|
|
41
|
+
'nyx',
|
|
42
|
+
'gaia',
|
|
43
|
+
'iris',
|
|
44
|
+
'mnemosyne',
|
|
45
|
+
'talos',
|
|
46
|
+
'agora',
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
const PLATFORM_LABELS = {
|
|
50
|
+
opencode: 'OpenCode',
|
|
51
|
+
claude: 'Claude Code',
|
|
52
|
+
cursor: 'Cursor',
|
|
53
|
+
windsurf: 'Windsurf',
|
|
54
|
+
copilot: 'VS Code / Copilot',
|
|
55
|
+
continue: 'Continue.dev',
|
|
56
|
+
cline: 'Cline',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const ALL_PLATFORMS = Object.keys(PLATFORM_LABELS)
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// Summary tracker
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
|
|
65
|
+
const stats = {
|
|
66
|
+
opencode: { removed: 0, skipped: 0, errors: 0 },
|
|
67
|
+
claude: { removed: 0, skipped: 0, errors: 0 },
|
|
68
|
+
cursor: { removed: 0, skipped: 0, errors: 0 },
|
|
69
|
+
windsurf: { removed: 0, skipped: 0, errors: 0 },
|
|
70
|
+
copilot: { removed: 0, skipped: 0, errors: 0 },
|
|
71
|
+
continue: { removed: 0, skipped: 0, errors: 0 },
|
|
72
|
+
cline: { removed: 0, skipped: 0, errors: 0 },
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Help
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
function showHelp() {
|
|
80
|
+
console.log(`
|
|
81
|
+
uninstall.mjs â Multi-platform Pantheon agent uninstaller
|
|
82
|
+
|
|
83
|
+
Usage:
|
|
84
|
+
node scripts/uninstall.mjs auto-detect, cwd
|
|
85
|
+
node scripts/uninstall.mjs --target /path/to/project auto-detect, target
|
|
86
|
+
node scripts/uninstall.mjs --platforms opencode,claude specific platforms, cwd
|
|
87
|
+
node scripts/uninstall.mjs --target /path --platforms all all platforms
|
|
88
|
+
node scripts/uninstall.mjs --dry-run preview without deleting
|
|
89
|
+
node scripts/uninstall.mjs --force skip confirmation prompt
|
|
90
|
+
node scripts/uninstall.mjs --help show this help
|
|
91
|
+
|
|
92
|
+
Flags:
|
|
93
|
+
--target /path Project to uninstall from (default: cwd)
|
|
94
|
+
--platforms list Comma-separated platforms (default: auto-detect or all)
|
|
95
|
+
--dry-run Preview what would be removed without deleting
|
|
96
|
+
--force Skip confirmation prompt
|
|
97
|
+
--help Show this help
|
|
98
|
+
|
|
99
|
+
Platforms:
|
|
100
|
+
opencode â .opencode/agents/ + opencode.json Pantheon entries
|
|
101
|
+
claude â .claude/agents/ + CLAUDE.md (if Pantheon content)
|
|
102
|
+
cursor â .cursor/rules/*.mdc (only Pantheon rules)
|
|
103
|
+
windsurf â .windsurf/rules/ + .windsurf/workflows/ (if empty)
|
|
104
|
+
copilot â .github/agents/ + .vscode/settings.json Pantheon keys
|
|
105
|
+
continue â .continue/rules/ + check .continue/config.yaml
|
|
106
|
+
cline â .clinerules/ (only agent rules, not commands/skills)
|
|
107
|
+
all â uninstall every platform
|
|
108
|
+
|
|
109
|
+
When --platforms is omitted, auto-detects which platforms are configured.
|
|
110
|
+
If none detected, all platforms are uninstalled.
|
|
111
|
+
`)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// CLI args
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
function parseArgs(argv) {
|
|
119
|
+
const args = {
|
|
120
|
+
target: null,
|
|
121
|
+
platforms: null,
|
|
122
|
+
dryRun: false,
|
|
123
|
+
force: false,
|
|
124
|
+
help: false,
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (let i = 2; i < argv.length; i++) {
|
|
128
|
+
switch (argv[i]) {
|
|
129
|
+
case '--target':
|
|
130
|
+
args.target = argv[++i]
|
|
131
|
+
break
|
|
132
|
+
case '--platforms':
|
|
133
|
+
args.platforms = argv[++i].split(',').map((s) => s.trim().toLowerCase())
|
|
134
|
+
break
|
|
135
|
+
case '--dry-run':
|
|
136
|
+
args.dryRun = true
|
|
137
|
+
break
|
|
138
|
+
case '--force':
|
|
139
|
+
args.force = true
|
|
140
|
+
break
|
|
141
|
+
case '--help':
|
|
142
|
+
args.help = true
|
|
143
|
+
break
|
|
144
|
+
default:
|
|
145
|
+
console.warn(`â ī¸ Unknown option: ${argv[i]}`)
|
|
146
|
+
break
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!args.target) {
|
|
151
|
+
args.target = process.cwd()
|
|
152
|
+
}
|
|
153
|
+
args.target = resolveTarget(args.target)
|
|
154
|
+
|
|
155
|
+
return args
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ---------------------------------------------------------------------------
|
|
159
|
+
// Confirmation prompt
|
|
160
|
+
// ---------------------------------------------------------------------------
|
|
161
|
+
|
|
162
|
+
async function confirmPrompt(message) {
|
|
163
|
+
if (!isatty(process.stdin.fd)) {
|
|
164
|
+
// Non-interactive â skip prompt (caller must use --force if desired)
|
|
165
|
+
return true
|
|
166
|
+
}
|
|
167
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
168
|
+
return new Promise((resolve) => {
|
|
169
|
+
rl.question(message, (answer) => {
|
|
170
|
+
rl.close()
|
|
171
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes')
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// Safety checks
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
/** Check if a filename matches a Pantheon agent file pattern. */
|
|
181
|
+
function isPantheonAgentFile(filename) {
|
|
182
|
+
const basename = filename.replace(/\.(md|mdc)$/, '')
|
|
183
|
+
return PANTHEON_AGENT_NAMES.includes(basename)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Check if a string is a Pantheon skill directory name. */
|
|
187
|
+
function _isPantheonSkillEntry(dirname) {
|
|
188
|
+
// Skill directories are dynamically fetched from ROOT/skills/
|
|
189
|
+
// We use a dynamic check via readdirSync when needed, but for safety
|
|
190
|
+
// we check against a known set of patterns.
|
|
191
|
+
return /^[a-z-]+$/.test(dirname) && dirname.length > 1
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Check if a file contains Pantheon content. */
|
|
195
|
+
function fileContainsPantheonContent(filePath) {
|
|
196
|
+
try {
|
|
197
|
+
if (!existsSync(filePath)) return false
|
|
198
|
+
const content = readFileSync(filePath, 'utf8')
|
|
199
|
+
return (
|
|
200
|
+
content.includes('Pantheon Agent System') ||
|
|
201
|
+
content.includes('# Pantheon Agent System') ||
|
|
202
|
+
content.includes('Pantheon multi-agent framework')
|
|
203
|
+
)
|
|
204
|
+
} catch {
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Platform uninstallers
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* OpenCode â remove .opencode/agents/, restore opencode.json
|
|
215
|
+
*/
|
|
216
|
+
function uninstallOpenCode(target, dryRun) {
|
|
217
|
+
const s = stats.opencode
|
|
218
|
+
|
|
219
|
+
// 1. Remove .opencode/agents/ (entire directory â Pantheon-created)
|
|
220
|
+
const agentsDir = join(target, '.opencode', 'agents')
|
|
221
|
+
if (existsSync(agentsDir)) {
|
|
222
|
+
if (!dryRun) {
|
|
223
|
+
try {
|
|
224
|
+
rmSync(agentsDir, { recursive: true, force: true })
|
|
225
|
+
s.removed++
|
|
226
|
+
} catch (err) {
|
|
227
|
+
console.error(` â ī¸ Failed to remove ${agentsDir}: ${err.message}`)
|
|
228
|
+
s.errors++
|
|
229
|
+
}
|
|
230
|
+
} else {
|
|
231
|
+
const count = readdirSync(agentsDir).length
|
|
232
|
+
s.removed++
|
|
233
|
+
console.log(` ~ Would remove .opencode/agents/ (${count} files)`)
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
s.skipped++
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 2. Remove .opencode/skills/ (entire directory â Pantheon-created)
|
|
240
|
+
const skillsDir = join(target, '.opencode', 'skills')
|
|
241
|
+
if (existsSync(skillsDir)) {
|
|
242
|
+
if (!dryRun) {
|
|
243
|
+
try {
|
|
244
|
+
rmSync(skillsDir, { recursive: true, force: true })
|
|
245
|
+
s.removed++
|
|
246
|
+
} catch (err) {
|
|
247
|
+
console.error(` â ī¸ Failed to remove ${skillsDir}: ${err.message}`)
|
|
248
|
+
s.errors++
|
|
249
|
+
}
|
|
250
|
+
} else {
|
|
251
|
+
s.removed++
|
|
252
|
+
console.log(' ~ Would remove .opencode/skills/')
|
|
253
|
+
}
|
|
254
|
+
} else {
|
|
255
|
+
s.skipped++
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// 3. Remove .opencode/commands/ (entire directory â Pantheon-created)
|
|
259
|
+
const cmdsDir = join(target, '.opencode', 'commands')
|
|
260
|
+
if (existsSync(cmdsDir)) {
|
|
261
|
+
if (!dryRun) {
|
|
262
|
+
try {
|
|
263
|
+
rmSync(cmdsDir, { recursive: true, force: true })
|
|
264
|
+
s.removed++
|
|
265
|
+
} catch (err) {
|
|
266
|
+
console.error(` â ī¸ Failed to remove ${cmdsDir}: ${err.message}`)
|
|
267
|
+
s.errors++
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
s.removed++
|
|
271
|
+
console.log(' ~ Would remove .opencode/commands/')
|
|
272
|
+
}
|
|
273
|
+
} else {
|
|
274
|
+
s.skipped++
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 4. Remove .opencode/plugins/ (Pantheon TUI plugin)
|
|
278
|
+
const pluginsDir = join(target, '.opencode', 'plugins')
|
|
279
|
+
if (existsSync(pluginsDir)) {
|
|
280
|
+
let isPluginEmpty = true
|
|
281
|
+
try {
|
|
282
|
+
isPluginEmpty = readdirSync(pluginsDir).length === 0
|
|
283
|
+
} catch {
|
|
284
|
+
/* ignore */
|
|
285
|
+
}
|
|
286
|
+
if (!dryRun) {
|
|
287
|
+
try {
|
|
288
|
+
rmSync(pluginsDir, { recursive: true, force: true })
|
|
289
|
+
s.removed++
|
|
290
|
+
} catch (err) {
|
|
291
|
+
console.error(` â ī¸ Failed to remove ${pluginsDir}: ${err.message}`)
|
|
292
|
+
s.errors++
|
|
293
|
+
}
|
|
294
|
+
} else if (!isPluginEmpty) {
|
|
295
|
+
s.removed++
|
|
296
|
+
console.log(' ~ Would remove .opencode/plugins/ (Pantheon TUI plugin)')
|
|
297
|
+
} else {
|
|
298
|
+
s.skipped++
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
s.skipped++
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// 5. Remove .opencode/package.json and .opencode/tsconfig.json (Pantheon-created)
|
|
305
|
+
for (const f of ['package.json', 'tsconfig.json']) {
|
|
306
|
+
const fp = join(target, '.opencode', f)
|
|
307
|
+
if (existsSync(fp)) {
|
|
308
|
+
if (!dryRun) {
|
|
309
|
+
try {
|
|
310
|
+
rmSync(fp, { force: true })
|
|
311
|
+
s.removed++
|
|
312
|
+
} catch (err) {
|
|
313
|
+
console.error(` â ī¸ Failed to remove ${fp}: ${err.message}`)
|
|
314
|
+
s.errors++
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
s.removed++
|
|
318
|
+
console.log(` ~ Would remove .opencode/${f}`)
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
s.skipped++
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// 6. Clean up empty .opencode/ directory
|
|
326
|
+
const opencodeDir = join(target, '.opencode')
|
|
327
|
+
if (existsSync(opencodeDir)) {
|
|
328
|
+
let isEmpty = true
|
|
329
|
+
try {
|
|
330
|
+
isEmpty = readdirSync(opencodeDir).length === 0
|
|
331
|
+
} catch {
|
|
332
|
+
/* ignore */
|
|
333
|
+
}
|
|
334
|
+
if (isEmpty) {
|
|
335
|
+
if (!dryRun) {
|
|
336
|
+
try {
|
|
337
|
+
rmSync(opencodeDir, { recursive: true, force: true })
|
|
338
|
+
s.removed++
|
|
339
|
+
} catch (_err) {
|
|
340
|
+
/* ignore */
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
s.removed++
|
|
344
|
+
console.log(' ~ Would remove empty .opencode/ directory')
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// 7. Restore opencode.json â remove Pantheon agent config entries
|
|
350
|
+
const configPath = join(target, 'opencode.json')
|
|
351
|
+
if (existsSync(configPath)) {
|
|
352
|
+
try {
|
|
353
|
+
const raw = readFileSync(configPath, 'utf8')
|
|
354
|
+
const config = JSON.parse(raw)
|
|
355
|
+
let changed = false
|
|
356
|
+
|
|
357
|
+
// Remove Pantheon-managed agent entries
|
|
358
|
+
if (config.agent && typeof config.agent === 'object') {
|
|
359
|
+
for (const [name, cfg] of Object.entries(config.agent)) {
|
|
360
|
+
const source = cfg?.source || ''
|
|
361
|
+
if (
|
|
362
|
+
source.startsWith('.opencode/agents/') ||
|
|
363
|
+
source.startsWith('agents/') ||
|
|
364
|
+
(PANTHEON_AGENT_NAMES.includes(name) && source === '')
|
|
365
|
+
) {
|
|
366
|
+
delete config.agent[name]
|
|
367
|
+
changed = true
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (Object.keys(config.agent).length === 0) {
|
|
371
|
+
delete config.agent
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Remove Pantheon-specific instructions
|
|
376
|
+
if (Array.isArray(config.instructions)) {
|
|
377
|
+
const filtered = config.instructions.filter(
|
|
378
|
+
(i) => i !== 'AGENTS.md' && i !== 'instructions/*.instructions.md',
|
|
379
|
+
)
|
|
380
|
+
if (filtered.length !== config.instructions.length) {
|
|
381
|
+
changed = true
|
|
382
|
+
}
|
|
383
|
+
if (filtered.length === 0) {
|
|
384
|
+
delete config.instructions
|
|
385
|
+
} else {
|
|
386
|
+
config.instructions = filtered
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Remove Pantheon plugin entry (stub â reserved for future plugin cleanup)
|
|
391
|
+
if (Array.isArray(config.plugin) && config.plugin.length === 0) {
|
|
392
|
+
delete config.plugin
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Remove default_agent if it points to zeus
|
|
396
|
+
if (config.default_agent === 'zeus') {
|
|
397
|
+
delete config.default_agent
|
|
398
|
+
changed = true
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (changed) {
|
|
402
|
+
if (!dryRun) {
|
|
403
|
+
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8')
|
|
404
|
+
s.removed++
|
|
405
|
+
} else {
|
|
406
|
+
s.removed++
|
|
407
|
+
console.log(' ~ Would restore opencode.json (remove Pantheon agent config entries)')
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
s.skipped++
|
|
411
|
+
}
|
|
412
|
+
} catch (err) {
|
|
413
|
+
console.error(` â ī¸ Failed to process opencode.json: ${err.message}`)
|
|
414
|
+
s.errors++
|
|
415
|
+
}
|
|
416
|
+
} else {
|
|
417
|
+
s.skipped++
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// 8. Remove Pantheon-installed instructions/ directory
|
|
421
|
+
const instrDir = join(target, 'instructions')
|
|
422
|
+
if (existsSync(instrDir)) {
|
|
423
|
+
// Only remove if it was Pantheon-installed (heuristic: contains .instructions.md files)
|
|
424
|
+
try {
|
|
425
|
+
const entries = readdirSync(instrDir)
|
|
426
|
+
const hasInstructions = entries.some((e) => e.endsWith('.instructions.md'))
|
|
427
|
+
if (hasInstructions) {
|
|
428
|
+
if (!dryRun) {
|
|
429
|
+
rmSync(instrDir, { recursive: true, force: true })
|
|
430
|
+
s.removed++
|
|
431
|
+
} else {
|
|
432
|
+
s.removed++
|
|
433
|
+
console.log(' ~ Would remove instructions/ (Pantheon-installed)')
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
s.skipped++
|
|
437
|
+
}
|
|
438
|
+
} catch {
|
|
439
|
+
s.skipped++
|
|
440
|
+
}
|
|
441
|
+
} else {
|
|
442
|
+
s.skipped++
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// 9. Remove Pantheon-installed prompts/ directory
|
|
446
|
+
const promptsDir = join(target, 'prompts')
|
|
447
|
+
if (existsSync(promptsDir)) {
|
|
448
|
+
try {
|
|
449
|
+
const entries = readdirSync(promptsDir)
|
|
450
|
+
const hasPromptMds = entries.some((e) => e.endsWith('.prompt.md'))
|
|
451
|
+
if (hasPromptMds) {
|
|
452
|
+
if (!dryRun) {
|
|
453
|
+
rmSync(promptsDir, { recursive: true, force: true })
|
|
454
|
+
s.removed++
|
|
455
|
+
} else {
|
|
456
|
+
s.removed++
|
|
457
|
+
console.log(' ~ Would remove prompts/ (Pantheon-installed)')
|
|
458
|
+
}
|
|
459
|
+
} else {
|
|
460
|
+
s.skipped++
|
|
461
|
+
}
|
|
462
|
+
} catch {
|
|
463
|
+
s.skipped++
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
s.skipped++
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// 10. Remove AGENTS.md if it has Pantheon content
|
|
470
|
+
const agentsMd = join(target, 'AGENTS.md')
|
|
471
|
+
if (fileContainsPantheonContent(agentsMd)) {
|
|
472
|
+
if (!dryRun) {
|
|
473
|
+
try {
|
|
474
|
+
rmSync(agentsMd, { force: true })
|
|
475
|
+
s.removed++
|
|
476
|
+
} catch (err) {
|
|
477
|
+
console.error(` â ī¸ Failed to remove AGENTS.md: ${err.message}`)
|
|
478
|
+
s.errors++
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
s.removed++
|
|
482
|
+
console.log(' ~ Would remove AGENTS.md (Pantheon content)')
|
|
483
|
+
}
|
|
484
|
+
} else {
|
|
485
|
+
s.skipped++
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Claude Code â remove .claude/agents/, remove CLAUDE.md if Pantheon content
|
|
491
|
+
*/
|
|
492
|
+
function uninstallClaude(target, dryRun) {
|
|
493
|
+
const s = stats.claude
|
|
494
|
+
|
|
495
|
+
// 1. Remove .claude/agents/
|
|
496
|
+
const agentsDir = join(target, '.claude', 'agents')
|
|
497
|
+
if (existsSync(agentsDir)) {
|
|
498
|
+
if (!dryRun) {
|
|
499
|
+
try {
|
|
500
|
+
rmSync(agentsDir, { recursive: true, force: true })
|
|
501
|
+
s.removed++
|
|
502
|
+
} catch (err) {
|
|
503
|
+
console.error(` â ī¸ Failed to remove ${agentsDir}: ${err.message}`)
|
|
504
|
+
s.errors++
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
const count = existsSync(agentsDir) ? readdirSync(agentsDir).length : 0
|
|
508
|
+
s.removed++
|
|
509
|
+
console.log(` ~ Would remove .claude/agents/ (${count} files)`)
|
|
510
|
+
}
|
|
511
|
+
} else {
|
|
512
|
+
s.skipped++
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// 2. Remove .claude/settings.json Pantheon section (or entire file if Pantheon-only)
|
|
516
|
+
const settingsPath = join(target, '.claude', 'settings.json')
|
|
517
|
+
if (existsSync(settingsPath)) {
|
|
518
|
+
try {
|
|
519
|
+
const raw = readFileSync(settingsPath, 'utf8')
|
|
520
|
+
const settings = JSON.parse(raw)
|
|
521
|
+
// The installer creates settings with only permissions.allow
|
|
522
|
+
// If the file only has permissions, it's Pantheon-created
|
|
523
|
+
if (settings.permissions && Object.keys(settings).length === 1 && !dryRun) {
|
|
524
|
+
rmSync(settingsPath, { force: true })
|
|
525
|
+
s.removed++
|
|
526
|
+
} else if (settings.permissions && !dryRun) {
|
|
527
|
+
// User had other settings â just remove the permissions key
|
|
528
|
+
delete settings.permissions
|
|
529
|
+
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, 'utf8')
|
|
530
|
+
s.removed++
|
|
531
|
+
} else if (settings.permissions) {
|
|
532
|
+
s.removed++
|
|
533
|
+
console.log(' ~ Would remove Pantheon permissions from .claude/settings.json')
|
|
534
|
+
} else {
|
|
535
|
+
s.skipped++
|
|
536
|
+
}
|
|
537
|
+
} catch {
|
|
538
|
+
s.skipped++
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
s.skipped++
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// 3. Remove CLAUDE.md if it has Pantheon content
|
|
545
|
+
const claudeMdPath = join(target, 'CLAUDE.md')
|
|
546
|
+
if (fileContainsPantheonContent(claudeMdPath)) {
|
|
547
|
+
if (!dryRun) {
|
|
548
|
+
try {
|
|
549
|
+
rmSync(claudeMdPath, { force: true })
|
|
550
|
+
s.removed++
|
|
551
|
+
} catch (err) {
|
|
552
|
+
console.error(` â ī¸ Failed to remove CLAUDE.md: ${err.message}`)
|
|
553
|
+
s.errors++
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
s.removed++
|
|
557
|
+
console.log(' ~ Would remove CLAUDE.md (Pantheon content)')
|
|
558
|
+
}
|
|
559
|
+
} else {
|
|
560
|
+
s.skipped++
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// 4. Remove .claude/ (if empty after cleanup)
|
|
564
|
+
const claudeDir = join(target, '.claude')
|
|
565
|
+
if (existsSync(claudeDir)) {
|
|
566
|
+
try {
|
|
567
|
+
const entries = readdirSync(claudeDir)
|
|
568
|
+
if (entries.length === 0) {
|
|
569
|
+
if (!dryRun) {
|
|
570
|
+
rmSync(claudeDir, { recursive: true, force: true })
|
|
571
|
+
}
|
|
572
|
+
// No need to count this â it's a cleanup step
|
|
573
|
+
}
|
|
574
|
+
} catch {
|
|
575
|
+
/* ignore */
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
// Note: AGENTS.md is handled by opencode uninstaller
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Cursor â remove .cursor/rules/*.mdc (only Pantheon rules)
|
|
583
|
+
*/
|
|
584
|
+
function uninstallCursor(target, dryRun) {
|
|
585
|
+
const s = stats.cursor
|
|
586
|
+
|
|
587
|
+
const rulesDir = join(target, '.cursor', 'rules')
|
|
588
|
+
if (!existsSync(rulesDir)) {
|
|
589
|
+
s.skipped++
|
|
590
|
+
return
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
try {
|
|
594
|
+
const entries = readdirSync(rulesDir)
|
|
595
|
+
const pantheonFiles = entries.filter((f) => f.endsWith('.mdc') && isPantheonAgentFile(f))
|
|
596
|
+
|
|
597
|
+
if (pantheonFiles.length === 0) {
|
|
598
|
+
s.skipped++
|
|
599
|
+
return
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
for (const f of pantheonFiles) {
|
|
603
|
+
const fp = join(rulesDir, f)
|
|
604
|
+
if (!dryRun) {
|
|
605
|
+
try {
|
|
606
|
+
rmSync(fp, { force: true })
|
|
607
|
+
s.removed++
|
|
608
|
+
} catch (err) {
|
|
609
|
+
console.error(` â ī¸ Failed to remove ${fp}: ${err.message}`)
|
|
610
|
+
s.errors++
|
|
611
|
+
}
|
|
612
|
+
} else {
|
|
613
|
+
s.removed++
|
|
614
|
+
console.log(` ~ Would remove .cursor/rules/${f}`)
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Remove rules/ directory if empty
|
|
619
|
+
if (!dryRun && readdirSync(rulesDir).length === 0) {
|
|
620
|
+
try {
|
|
621
|
+
rmSync(rulesDir, { recursive: true, force: true })
|
|
622
|
+
} catch {
|
|
623
|
+
/* ignore */
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
} catch (err) {
|
|
627
|
+
console.error(` â ī¸ Failed to read ${rulesDir}: ${err.message}`)
|
|
628
|
+
s.errors++
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Note: AGENTS.md is handled by opencode uninstaller
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* Windsurf â remove .windsurf/rules/, remove .windsurf/workflows/ if empty
|
|
636
|
+
*/
|
|
637
|
+
function uninstallWindsurf(target, dryRun) {
|
|
638
|
+
const s = stats.windsurf
|
|
639
|
+
|
|
640
|
+
// 1. Remove .windsurf/rules/
|
|
641
|
+
const rulesDir = join(target, '.windsurf', 'rules')
|
|
642
|
+
if (existsSync(rulesDir)) {
|
|
643
|
+
if (!dryRun) {
|
|
644
|
+
try {
|
|
645
|
+
rmSync(rulesDir, { recursive: true, force: true })
|
|
646
|
+
s.removed++
|
|
647
|
+
} catch (err) {
|
|
648
|
+
console.error(` â ī¸ Failed to remove ${rulesDir}: ${err.message}`)
|
|
649
|
+
s.errors++
|
|
650
|
+
}
|
|
651
|
+
} else {
|
|
652
|
+
const count = readdirSync(rulesDir).length
|
|
653
|
+
s.removed++
|
|
654
|
+
console.log(` ~ Would remove .windsurf/rules/ (${count} files)`)
|
|
655
|
+
}
|
|
656
|
+
} else {
|
|
657
|
+
s.skipped++
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
// 2. Remove workflows that match Pantheon workflow names
|
|
661
|
+
const wfDir = join(target, '.windsurf', 'workflows')
|
|
662
|
+
if (existsSync(wfDir)) {
|
|
663
|
+
try {
|
|
664
|
+
const entries = readdirSync(wfDir)
|
|
665
|
+
const pantheonWorkflows = entries.filter(
|
|
666
|
+
(f) =>
|
|
667
|
+
f.endsWith('.md') &&
|
|
668
|
+
[
|
|
669
|
+
'orchestrate.md',
|
|
670
|
+
'code-review.md',
|
|
671
|
+
'audit.md',
|
|
672
|
+
'cancel.md',
|
|
673
|
+
'deepwork.md',
|
|
674
|
+
'focus.md',
|
|
675
|
+
'forge.md',
|
|
676
|
+
'metamorphosis.md',
|
|
677
|
+
'mirrordeps.md',
|
|
678
|
+
'optimize.md',
|
|
679
|
+
'pantheon-status.md',
|
|
680
|
+
'pantheon.md',
|
|
681
|
+
'ping.md',
|
|
682
|
+
'praxis.md',
|
|
683
|
+
'reflect.md',
|
|
684
|
+
'sketch.md',
|
|
685
|
+
'stop-continuation.md',
|
|
686
|
+
'subtask.md',
|
|
687
|
+
].includes(f),
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
for (const f of pantheonWorkflows) {
|
|
691
|
+
const fp = join(wfDir, f)
|
|
692
|
+
if (!dryRun) {
|
|
693
|
+
try {
|
|
694
|
+
rmSync(fp, { force: true })
|
|
695
|
+
s.removed++
|
|
696
|
+
} catch (err) {
|
|
697
|
+
console.error(` â ī¸ Failed to remove ${fp}: ${err.message}`)
|
|
698
|
+
s.errors++
|
|
699
|
+
}
|
|
700
|
+
} else {
|
|
701
|
+
s.removed++
|
|
702
|
+
console.log(` ~ Would remove .windsurf/workflows/${f}`)
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// Remove workflows/ directory if empty
|
|
707
|
+
if (!dryRun && existsSync(wfDir) && readdirSync(wfDir).length === 0) {
|
|
708
|
+
try {
|
|
709
|
+
rmSync(wfDir, { recursive: true, force: true })
|
|
710
|
+
} catch {
|
|
711
|
+
/* ignore */
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
} catch (err) {
|
|
715
|
+
console.error(` â ī¸ Failed to read ${wfDir}: ${err.message}`)
|
|
716
|
+
s.errors++
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
719
|
+
s.skipped++
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// Note: AGENTS.md is handled by opencode uninstaller
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* VS Code / Copilot â remove .github/agents/, restore .vscode/settings.json
|
|
727
|
+
*/
|
|
728
|
+
function uninstallCopilot(target, dryRun) {
|
|
729
|
+
const s = stats.copilot
|
|
730
|
+
|
|
731
|
+
// 1. Remove .github/agents/ (entire directory â Pantheon-created)
|
|
732
|
+
const agentsDir = join(target, '.github', 'agents')
|
|
733
|
+
if (existsSync(agentsDir)) {
|
|
734
|
+
if (!dryRun) {
|
|
735
|
+
try {
|
|
736
|
+
rmSync(agentsDir, { recursive: true, force: true })
|
|
737
|
+
s.removed++
|
|
738
|
+
} catch (err) {
|
|
739
|
+
console.error(` â ī¸ Failed to remove ${agentsDir}: ${err.message}`)
|
|
740
|
+
s.errors++
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
const count = existsSync(agentsDir) ? readdirSync(agentsDir).length : 0
|
|
744
|
+
s.removed++
|
|
745
|
+
console.log(` ~ Would remove .github/agents/ (${count} files)`)
|
|
746
|
+
}
|
|
747
|
+
} else {
|
|
748
|
+
s.skipped++
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// 2. Restore .vscode/settings.json â remove Pantheon-added keys
|
|
752
|
+
const settingsPath = join(target, '.vscode', 'settings.json')
|
|
753
|
+
if (existsSync(settingsPath)) {
|
|
754
|
+
try {
|
|
755
|
+
const raw = readFileSync(settingsPath, 'utf8')
|
|
756
|
+
const settings = JSON.parse(raw)
|
|
757
|
+
let changed = false
|
|
758
|
+
|
|
759
|
+
// Remove the Pantheon-specific settings that the installer added
|
|
760
|
+
const pantheonKeys = ['chat.subagents.allowInvocationsFromSubagents', 'chat.plugins.enabled']
|
|
761
|
+
|
|
762
|
+
for (const key of pantheonKeys) {
|
|
763
|
+
if (key in settings) {
|
|
764
|
+
delete settings[key]
|
|
765
|
+
changed = true
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
if (changed) {
|
|
770
|
+
if (!dryRun) {
|
|
771
|
+
writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, 'utf8')
|
|
772
|
+
s.removed++
|
|
773
|
+
} else {
|
|
774
|
+
s.removed++
|
|
775
|
+
console.log(' ~ Would restore .vscode/settings.json (remove Pantheon keys)')
|
|
776
|
+
}
|
|
777
|
+
} else {
|
|
778
|
+
s.skipped++
|
|
779
|
+
}
|
|
780
|
+
} catch (err) {
|
|
781
|
+
console.error(` â ī¸ Failed to process .vscode/settings.json: ${err.message}`)
|
|
782
|
+
s.errors++
|
|
783
|
+
}
|
|
784
|
+
} else {
|
|
785
|
+
s.skipped++
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// Note: AGENTS.md is handled by opencode uninstaller
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* Continue.dev â remove .continue/rules/, check .continue/config.yaml
|
|
793
|
+
*/
|
|
794
|
+
function uninstallContinue(target, dryRun) {
|
|
795
|
+
const s = stats.continue
|
|
796
|
+
|
|
797
|
+
// 1. Remove .continue/rules/ (entire directory â Pantheon-created)
|
|
798
|
+
const rulesDir = join(target, '.continue', 'rules')
|
|
799
|
+
if (existsSync(rulesDir)) {
|
|
800
|
+
if (!dryRun) {
|
|
801
|
+
try {
|
|
802
|
+
rmSync(rulesDir, { recursive: true, force: true })
|
|
803
|
+
s.removed++
|
|
804
|
+
} catch (err) {
|
|
805
|
+
console.error(` â ī¸ Failed to remove ${rulesDir}: ${err.message}`)
|
|
806
|
+
s.errors++
|
|
807
|
+
}
|
|
808
|
+
} else {
|
|
809
|
+
const count = readdirSync(rulesDir).length
|
|
810
|
+
s.removed++
|
|
811
|
+
console.log(` ~ Would remove .continue/rules/ (${count} files)`)
|
|
812
|
+
}
|
|
813
|
+
} else {
|
|
814
|
+
s.skipped++
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// 2. Check .continue/config.yaml for Pantheon references
|
|
818
|
+
const configPath = join(target, '.continue', 'config.yaml')
|
|
819
|
+
if (existsSync(configPath)) {
|
|
820
|
+
try {
|
|
821
|
+
const content = readFileSync(configPath, 'utf8')
|
|
822
|
+
if (
|
|
823
|
+
content.includes('Pantheon') ||
|
|
824
|
+
content.includes('pantheon') ||
|
|
825
|
+
/rules\/\w+\.md/.test(content)
|
|
826
|
+
) {
|
|
827
|
+
if (!dryRun) {
|
|
828
|
+
// Note: We don't auto-edit yaml (too risky with formatting),
|
|
829
|
+
// but we inform the user
|
|
830
|
+
console.log(
|
|
831
|
+
' âšī¸ .continue/config.yaml references Pantheon rules â manual cleanup may be needed',
|
|
832
|
+
)
|
|
833
|
+
} else {
|
|
834
|
+
console.log(' ~ Would check .continue/config.yaml for Pantheon references')
|
|
835
|
+
}
|
|
836
|
+
s.removed++
|
|
837
|
+
} else {
|
|
838
|
+
s.skipped++
|
|
839
|
+
}
|
|
840
|
+
} catch {
|
|
841
|
+
s.skipped++
|
|
842
|
+
}
|
|
843
|
+
} else {
|
|
844
|
+
s.skipped++
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// 3. Remove .continue/.opencode/ if present (from sync)
|
|
848
|
+
const opencodeDir = join(target, '.continue', '.opencode')
|
|
849
|
+
if (existsSync(opencodeDir)) {
|
|
850
|
+
if (!dryRun) {
|
|
851
|
+
try {
|
|
852
|
+
rmSync(opencodeDir, { recursive: true, force: true })
|
|
853
|
+
s.removed++
|
|
854
|
+
} catch (_err) {
|
|
855
|
+
/* ignore */
|
|
856
|
+
}
|
|
857
|
+
} else {
|
|
858
|
+
s.removed++
|
|
859
|
+
console.log(' ~ Would remove .continue/.opencode/ shared config')
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
/**
|
|
865
|
+
* Cline â remove .clinerules/ (only agent rules, not commands/skills if shared)
|
|
866
|
+
*/
|
|
867
|
+
function uninstallCline(target, dryRun) {
|
|
868
|
+
const s = stats.cline
|
|
869
|
+
|
|
870
|
+
const clineDir = join(target, '.clinerules')
|
|
871
|
+
if (!existsSync(clineDir)) {
|
|
872
|
+
s.skipped++
|
|
873
|
+
return
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
try {
|
|
877
|
+
const entries = readdirSync(clineDir)
|
|
878
|
+
const agentFiles = entries.filter(
|
|
879
|
+
(f) => statSync(join(clineDir, f)).isFile() && isPantheonAgentFile(f),
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
if (agentFiles.length === 0) {
|
|
883
|
+
s.skipped++
|
|
884
|
+
return
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
for (const f of agentFiles) {
|
|
888
|
+
const fp = join(clineDir, f)
|
|
889
|
+
if (!dryRun) {
|
|
890
|
+
try {
|
|
891
|
+
rmSync(fp, { force: true })
|
|
892
|
+
s.removed++
|
|
893
|
+
} catch (err) {
|
|
894
|
+
console.error(` â ī¸ Failed to remove ${fp}: ${err.message}`)
|
|
895
|
+
s.errors++
|
|
896
|
+
}
|
|
897
|
+
} else {
|
|
898
|
+
s.removed++
|
|
899
|
+
console.log(` ~ Would remove .clinerules/${f}`)
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// Remove .clinerules/ directory if empty (no shared commands/skills remain)
|
|
904
|
+
if (!dryRun && readdirSync(clineDir).length === 0) {
|
|
905
|
+
try {
|
|
906
|
+
rmSync(clineDir, { recursive: true, force: true })
|
|
907
|
+
} catch {
|
|
908
|
+
/* ignore */
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
} catch (err) {
|
|
912
|
+
console.error(` â ī¸ Failed to read ${clineDir}: ${err.message}`)
|
|
913
|
+
s.errors++
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
// ---------------------------------------------------------------------------
|
|
918
|
+
// Summary
|
|
919
|
+
// ---------------------------------------------------------------------------
|
|
920
|
+
|
|
921
|
+
function printUninstallSummary(target, platforms) {
|
|
922
|
+
console.log('')
|
|
923
|
+
console.log('='.repeat(60))
|
|
924
|
+
console.log('đī¸ Uninstall Summary')
|
|
925
|
+
console.log(` Target: ${target}`)
|
|
926
|
+
console.log('='.repeat(60))
|
|
927
|
+
|
|
928
|
+
let totalRemoved = 0
|
|
929
|
+
let totalSkipped = 0
|
|
930
|
+
let totalErrors = 0
|
|
931
|
+
|
|
932
|
+
for (const platform of platforms) {
|
|
933
|
+
const label = PLATFORM_LABELS[platform] ?? platform
|
|
934
|
+
const pStats = stats[platform]
|
|
935
|
+
totalRemoved += pStats.removed
|
|
936
|
+
totalSkipped += pStats.skipped
|
|
937
|
+
totalErrors += pStats.errors
|
|
938
|
+
|
|
939
|
+
const status = pStats.errors > 0 ? 'â ī¸' : 'â
'
|
|
940
|
+
console.log(
|
|
941
|
+
` ${status} ${label}: ${pStats.removed} removed, ${pStats.skipped} skipped${pStats.errors > 0 ? `, ${pStats.errors} errors` : ''}`,
|
|
942
|
+
)
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
console.log('-'.repeat(60))
|
|
946
|
+
console.log(
|
|
947
|
+
` Total: ${totalRemoved} items removed, ${totalSkipped} items skipped, ${totalErrors} errors`,
|
|
948
|
+
)
|
|
949
|
+
|
|
950
|
+
if (totalErrors > 0) {
|
|
951
|
+
console.log(' â ī¸ Some platforms had errors â review warnings above.')
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (totalRemoved === 0 && totalSkipped > 0) {
|
|
955
|
+
console.log(' âšī¸ No Pantheon files found for the selected platforms.')
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
console.log('')
|
|
959
|
+
if (totalRemoved > 0) {
|
|
960
|
+
console.log(' đ To verify, check the target directory for remaining Pantheon files.')
|
|
961
|
+
console.log(` đ Reinstall: node scripts/install.mjs --target ${target}`)
|
|
962
|
+
}
|
|
963
|
+
console.log('')
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
// ---------------------------------------------------------------------------
|
|
967
|
+
// Main
|
|
968
|
+
// ---------------------------------------------------------------------------
|
|
969
|
+
|
|
970
|
+
async function main() {
|
|
971
|
+
const args = parseArgs(process.argv)
|
|
972
|
+
|
|
973
|
+
if (args.help) {
|
|
974
|
+
showHelp()
|
|
975
|
+
process.exit(0)
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
const target = args.target
|
|
979
|
+
|
|
980
|
+
if (!existsSync(target)) {
|
|
981
|
+
console.error(`â Target directory does not exist: ${target}`)
|
|
982
|
+
process.exit(1)
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
let platforms = args.platforms
|
|
986
|
+
|
|
987
|
+
if (platforms?.includes('all')) {
|
|
988
|
+
platforms = [...ALL_PLATFORMS]
|
|
989
|
+
} else if (!platforms) {
|
|
990
|
+
const detected = detectPlatforms(target)
|
|
991
|
+
if (detected.length === 0) {
|
|
992
|
+
console.log(`đ No Pantheon platform config detected in ${target}`)
|
|
993
|
+
console.log(' Uninstalling all platforms.\n')
|
|
994
|
+
platforms = [...ALL_PLATFORMS]
|
|
995
|
+
} else {
|
|
996
|
+
console.log(`đ Detected platforms in ${target}: ${detected.join(', ')}\n`)
|
|
997
|
+
platforms = detected
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
console.log(args.dryRun ? 'đ Dry-run mode â no files will be deleted\n' : '')
|
|
1002
|
+
console.log(`đĻ Pantheon Uninstaller`)
|
|
1003
|
+
console.log(` Target: ${target}`)
|
|
1004
|
+
console.log(` Platforms: ${platforms.join(', ')}\n`)
|
|
1005
|
+
|
|
1006
|
+
// Confirmation (skip in dry-run mode, unless --force)
|
|
1007
|
+
if (!args.dryRun && !args.force) {
|
|
1008
|
+
const ok = await confirmPrompt(
|
|
1009
|
+
`â ī¸ This will remove Pantheon agent files from ${target}\n Proceed? [y/N] `,
|
|
1010
|
+
)
|
|
1011
|
+
if (!ok) {
|
|
1012
|
+
console.log('â Uninstall cancelled.')
|
|
1013
|
+
process.exit(0)
|
|
1014
|
+
}
|
|
1015
|
+
console.log('')
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
for (const platform of platforms) {
|
|
1019
|
+
const label = PLATFORM_LABELS[platform] ?? platform
|
|
1020
|
+
console.log(`đī¸ ${label}`)
|
|
1021
|
+
|
|
1022
|
+
switch (platform) {
|
|
1023
|
+
case 'opencode':
|
|
1024
|
+
uninstallOpenCode(target, args.dryRun)
|
|
1025
|
+
break
|
|
1026
|
+
case 'claude':
|
|
1027
|
+
uninstallClaude(target, args.dryRun)
|
|
1028
|
+
break
|
|
1029
|
+
case 'cursor':
|
|
1030
|
+
uninstallCursor(target, args.dryRun)
|
|
1031
|
+
break
|
|
1032
|
+
case 'windsurf':
|
|
1033
|
+
uninstallWindsurf(target, args.dryRun)
|
|
1034
|
+
break
|
|
1035
|
+
case 'copilot':
|
|
1036
|
+
uninstallCopilot(target, args.dryRun)
|
|
1037
|
+
break
|
|
1038
|
+
case 'continue':
|
|
1039
|
+
uninstallContinue(target, args.dryRun)
|
|
1040
|
+
break
|
|
1041
|
+
case 'cline':
|
|
1042
|
+
uninstallCline(target, args.dryRun)
|
|
1043
|
+
break
|
|
1044
|
+
default:
|
|
1045
|
+
console.warn(` â ī¸ Unknown platform: ${platform} â skipping`)
|
|
1046
|
+
break
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
printUninstallSummary(target, platforms)
|
|
1051
|
+
process.exit(0)
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
main().catch((err) => {
|
|
1055
|
+
console.error(`â Uninstall failed: ${err.message}`)
|
|
1056
|
+
process.exit(1)
|
|
1057
|
+
})
|