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.
Files changed (128) hide show
  1. package/AGENTS.md +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1013 -0
  4. package/bin/pantheon-init.mjs +183 -0
  5. package/commands/pantheon-audit.md +25 -0
  6. package/commands/pantheon-bg.md +10 -0
  7. package/commands/pantheon-consolidate.md +11 -0
  8. package/commands/pantheon-deepwork.md +128 -0
  9. package/commands/pantheon-doc.md +10 -0
  10. package/commands/pantheon-focus.md +9 -0
  11. package/commands/pantheon-forget.md +58 -0
  12. package/commands/pantheon-hash.md +11 -0
  13. package/commands/pantheon-optimize.md +79 -0
  14. package/commands/pantheon-remember.md +44 -0
  15. package/commands/pantheon-search.md +48 -0
  16. package/commands/pantheon-status.md +71 -0
  17. package/commands/pantheon-todo.md +11 -0
  18. package/commands/pantheon.md +49 -0
  19. package/docs/AGENT-MCP.md +194 -0
  20. package/docs/ARCHITECTURE.md +384 -0
  21. package/docs/BRANCH-PROTECTION.md +142 -0
  22. package/docs/INDEX.md +81 -0
  23. package/docs/INSTALLATION.md +217 -0
  24. package/docs/MCP.md +238 -0
  25. package/docs/MEMORY.md +471 -0
  26. package/docs/MIGRATION-MEMORY-BANK.md +139 -0
  27. package/docs/PLATFORMS.md +5 -0
  28. package/docs/QUICKSTART.md +49 -0
  29. package/docs/README.md +18 -0
  30. package/docs/RELEASING.md +256 -0
  31. package/docs/SETUP.md +5 -0
  32. package/docs/UPGRADING.md +41 -0
  33. package/docs/mcp-recommendations.md +439 -0
  34. package/docs/mcp-tools.md +156 -0
  35. package/docs/mcp-user-guide.md +204 -0
  36. package/docs/persistence-mcp.md +111 -0
  37. package/package.json +72 -0
  38. package/pantheon.schema.json +158 -0
  39. package/scripts/__init__.py +0 -0
  40. package/scripts/_pantheon_paths.py +68 -0
  41. package/scripts/check-version-consistency.sh +23 -0
  42. package/scripts/code_mode_server.py +202 -0
  43. package/scripts/doctor.mjs +763 -0
  44. package/scripts/generate-prompts.sh +222 -0
  45. package/scripts/generate-routing-docs.mjs +104 -0
  46. package/scripts/hash_verify.py +192 -0
  47. package/scripts/init-pantheon-mcp.sh +118 -0
  48. package/scripts/install/agents-md.mjs +214 -0
  49. package/scripts/install/health-check.mjs +196 -0
  50. package/scripts/install/migrate.mjs +209 -0
  51. package/scripts/install/opencode.mjs +645 -0
  52. package/scripts/install/shared.mjs +655 -0
  53. package/scripts/install/venv.mjs +116 -0
  54. package/scripts/install-mcp.mjs +885 -0
  55. package/scripts/install.mjs +26 -0
  56. package/scripts/manifest.mjs +622 -0
  57. package/scripts/mcp_persistence_server.py +459 -0
  58. package/scripts/mcp_resources_server.py +206 -0
  59. package/scripts/memory_cache.py +78 -0
  60. package/scripts/memory_mcp_server.py +605 -0
  61. package/scripts/paths.py +64 -0
  62. package/scripts/prune_context.py +72 -0
  63. package/scripts/release-bundle.mjs +109 -0
  64. package/scripts/scrub-secrets.py +282 -0
  65. package/scripts/scrub_secrets.py +281 -0
  66. package/scripts/test-context-compression.sh +166 -0
  67. package/scripts/themis_heuristic_scan.py +287 -0
  68. package/scripts/todo_enforcer.py +242 -0
  69. package/scripts/uninstall.mjs +1057 -0
  70. package/scripts/validate-routing.mjs +160 -0
  71. package/scripts/validate_agent_frontmatter.py +226 -0
  72. package/scripts/versioning.mjs +254 -0
  73. package/skills-lock.json +16 -0
  74. package/src/agents/aphrodite.md +162 -0
  75. package/src/agents/apollo.md +109 -0
  76. package/src/agents/athena.md +226 -0
  77. package/src/agents/demeter.md +146 -0
  78. package/src/agents/gaia.md +82 -0
  79. package/src/agents/hephaestus.md +105 -0
  80. package/src/agents/hermes.md +302 -0
  81. package/src/agents/iris.md +99 -0
  82. package/src/agents/mnemosyne.md +226 -0
  83. package/src/agents/nyx.md +87 -0
  84. package/src/agents/prometheus.md +199 -0
  85. package/src/agents/talos.md +93 -0
  86. package/src/agents/themis.md +187 -0
  87. package/src/agents/zeus.md +209 -0
  88. package/src/instructions/agent-return-format.instructions.md +26 -0
  89. package/src/instructions/backend-standards.instructions.md +45 -0
  90. package/src/instructions/documentation-standards.instructions.md +53 -0
  91. package/src/instructions/frontend-standards.instructions.md +46 -0
  92. package/src/instructions/memory-protocol.instructions.md +67 -0
  93. package/src/instructions/yagni.instructions.md +21 -0
  94. package/src/instructions/zeus-anti-stall.instructions.md +72 -0
  95. package/src/instructions/zeus-communication-rules.instructions.md +15 -0
  96. package/src/instructions/zeus-council-synthesis.instructions.md +105 -0
  97. package/src/instructions/zeus-timeout-retry.instructions.md +127 -0
  98. package/src/mcp/_pantheon_paths.py +67 -0
  99. package/src/mcp/code_mode_server.py +202 -0
  100. package/src/mcp/init-pantheon-mcp.sh +118 -0
  101. package/src/mcp/install-mcp.mjs +885 -0
  102. package/src/mcp/mcp_persistence_server.py +458 -0
  103. package/src/mcp/mcp_resources_server.py +205 -0
  104. package/src/mcp/memory_mcp_server.py +604 -0
  105. package/src/mcp/requirements-mcp-core.txt +5 -0
  106. package/src/mcp/requirements-mcp.txt +5 -0
  107. package/src/plugin.ts +19 -0
  108. package/src/plugins/tui/dist/tui.js +143 -0
  109. package/src/plugins/tui/dist/tui.tsx +144 -0
  110. package/src/plugins/tui/package.json +32 -0
  111. package/src/plugins/tui/src/index.tsx +144 -0
  112. package/src/plugins/tui/tsdown.config.ts +22 -0
  113. package/src/routing.yml +499 -0
  114. package/src/skills/README.md +230 -0
  115. package/src/skills/agent-coordination/SKILL.md +95 -0
  116. package/src/skills/artifact-management/SKILL.md +118 -0
  117. package/src/skills/auto-continue/SKILL.md +280 -0
  118. package/src/skills/code-review-checklist/SKILL.md +139 -0
  119. package/src/skills/context-compression/SKILL.md +861 -0
  120. package/src/skills/git-workflow-and-versioning/SKILL.md +32 -0
  121. package/src/skills/incremental-implementation/SKILL.md +27 -0
  122. package/src/skills/memory-bank/SKILL.md +165 -0
  123. package/src/skills/orchestration-workflow/SKILL.md +311 -0
  124. package/src/skills/security-hardening/SKILL.md +36 -0
  125. package/src/skills/session-goal/SKILL.md +138 -0
  126. package/src/skills/spec-driven-development/SKILL.md +23 -0
  127. package/src/skills/tdd-with-agents/SKILL.md +170 -0
  128. 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
+ })