wtf-p 0.1.0 → 0.3.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 (110) hide show
  1. package/README.md +215 -230
  2. package/bin/commands/doctor.js +100 -0
  3. package/bin/commands/install-logic.js +235 -0
  4. package/bin/commands/list.js +88 -0
  5. package/bin/commands/status.js +85 -0
  6. package/bin/commands/update.js +50 -0
  7. package/bin/install.js +157 -309
  8. package/bin/lib/manifest.js +41 -0
  9. package/bin/lib/utils.js +338 -0
  10. package/bin/lib/wcn-compiler.js +226 -0
  11. package/bin/uninstall.js +202 -206
  12. package/core/write-the-f-paper/references/github-contrib.md +216 -0
  13. package/core/write-the-f-paper/templates/posters/basic.md +34 -0
  14. package/core/write-the-f-paper/templates/slides/basic.md +31 -0
  15. package/package.json +14 -4
  16. package/vendors/claude/.claude-plugin/plugin.json +12 -0
  17. package/vendors/claude/commands/wtfp/contribute.md +278 -0
  18. package/vendors/claude/commands/wtfp/create-poster.md +42 -0
  19. package/vendors/claude/commands/wtfp/create-slides.md +41 -0
  20. package/{commands → vendors/claude/commands}/wtfp/help.md +28 -0
  21. package/vendors/claude/commands/wtfp/report-bug.md +131 -0
  22. package/vendors/claude/commands/wtfp/request-feature.md +160 -0
  23. package/vendors/claude/skills/wtfp/echarts/SKILL.md +47 -0
  24. package/vendors/claude/skills/wtfp/marp/SKILL.md +49 -0
  25. /package/{write-the-f-paper → core/write-the-f-paper}/references/checkpoints.md +0 -0
  26. /package/{write-the-f-paper → core/write-the-f-paper}/references/continuation-format.md +0 -0
  27. /package/{write-the-f-paper → core/write-the-f-paper}/references/git-integration.md +0 -0
  28. /package/{write-the-f-paper → core/write-the-f-paper}/references/imrad-structure.md +0 -0
  29. /package/{write-the-f-paper → core/write-the-f-paper}/references/length-estimation.md +0 -0
  30. /package/{write-the-f-paper → core/write-the-f-paper}/references/plan-format.md +0 -0
  31. /package/{write-the-f-paper → core/write-the-f-paper}/references/principles.md +0 -0
  32. /package/{write-the-f-paper → core/write-the-f-paper}/references/questioning.md +0 -0
  33. /package/{write-the-f-paper → core/write-the-f-paper}/references/research-pitfalls.md +0 -0
  34. /package/{write-the-f-paper → core/write-the-f-paper}/templates/config.json +0 -0
  35. /package/{write-the-f-paper → core/write-the-f-paper}/templates/context.md +0 -0
  36. /package/{write-the-f-paper → core/write-the-f-paper}/templates/continue-here.md +0 -0
  37. /package/{write-the-f-paper → core/write-the-f-paper}/templates/data-inventory.md +0 -0
  38. /package/{write-the-f-paper → core/write-the-f-paper}/templates/discovery.md +0 -0
  39. /package/{write-the-f-paper → core/write-the-f-paper}/templates/issues.md +0 -0
  40. /package/{write-the-f-paper → core/write-the-f-paper}/templates/manuscript.md +0 -0
  41. /package/{write-the-f-paper → core/write-the-f-paper}/templates/narrative-arc.md +0 -0
  42. /package/{write-the-f-paper → core/write-the-f-paper}/templates/outline.md +0 -0
  43. /package/{write-the-f-paper → core/write-the-f-paper}/templates/phase-prompt.md +0 -0
  44. /package/{write-the-f-paper → core/write-the-f-paper}/templates/prior-drafts.md +0 -0
  45. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/argument-structure.md +0 -0
  46. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/bibliography.md +0 -0
  47. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/gaps.md +0 -0
  48. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/imrad.md +0 -0
  49. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/peer-review.md +0 -0
  50. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/style-guide.md +0 -0
  51. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/tools.md +0 -0
  52. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project.md +0 -0
  53. /package/{write-the-f-paper → core/write-the-f-paper}/templates/research.md +0 -0
  54. /package/{write-the-f-paper → core/write-the-f-paper}/templates/review-comments.md +0 -0
  55. /package/{write-the-f-paper → core/write-the-f-paper}/templates/revision-issues.md +0 -0
  56. /package/{write-the-f-paper → core/write-the-f-paper}/templates/roadmap.md +0 -0
  57. /package/{write-the-f-paper → core/write-the-f-paper}/templates/section-archive.md +0 -0
  58. /package/{write-the-f-paper → core/write-the-f-paper}/templates/section-context.md +0 -0
  59. /package/{write-the-f-paper → core/write-the-f-paper}/templates/section.md +0 -0
  60. /package/{write-the-f-paper → core/write-the-f-paper}/templates/state.md +0 -0
  61. /package/{write-the-f-paper → core/write-the-f-paper}/templates/summary.md +0 -0
  62. /package/{write-the-f-paper → core/write-the-f-paper}/venues/acm-cs.yaml +0 -0
  63. /package/{write-the-f-paper → core/write-the-f-paper}/venues/arxiv-ml.yaml +0 -0
  64. /package/{write-the-f-paper → core/write-the-f-paper}/venues/ieee-cs.yaml +0 -0
  65. /package/{write-the-f-paper → core/write-the-f-paper}/venues/nature.yaml +0 -0
  66. /package/{write-the-f-paper → core/write-the-f-paper}/venues/thesis-chapter.yaml +0 -0
  67. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/create-outline.md +0 -0
  68. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/create-outline.wcn.md +0 -0
  69. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/discuss-section.md +0 -0
  70. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/discuss-section.wcn.md +0 -0
  71. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/execute-section.md +0 -0
  72. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/execute-section.wcn.md +0 -0
  73. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/list-assumptions.md +0 -0
  74. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/list-assumptions.wcn.md +0 -0
  75. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/lit-review-phase.md +0 -0
  76. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/lit-review-phase.wcn.md +0 -0
  77. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/map-project.md +0 -0
  78. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/map-project.wcn.md +0 -0
  79. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/plan-section.md +0 -0
  80. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/plan-section.wcn.md +0 -0
  81. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/research-gap.md +0 -0
  82. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/research-gap.wcn.md +0 -0
  83. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/resume-paper.md +0 -0
  84. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/resume-paper.wcn.md +0 -0
  85. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/review-section.md +0 -0
  86. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/review-section.wcn.md +0 -0
  87. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/submit-draft.md +0 -0
  88. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/submit-draft.wcn.md +0 -0
  89. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/transition.md +0 -0
  90. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/transition.wcn.md +0 -0
  91. /package/{commands → vendors/claude/commands}/wtfp/analyze-bib.md +0 -0
  92. /package/{commands → vendors/claude/commands}/wtfp/check-refs.md +0 -0
  93. /package/{commands → vendors/claude/commands}/wtfp/create-outline.md +0 -0
  94. /package/{commands → vendors/claude/commands}/wtfp/discuss-section.md +0 -0
  95. /package/{commands → vendors/claude/commands}/wtfp/export-latex.md +0 -0
  96. /package/{commands → vendors/claude/commands}/wtfp/insert-section.md +0 -0
  97. /package/{commands → vendors/claude/commands}/wtfp/list-assumptions.md +0 -0
  98. /package/{commands → vendors/claude/commands}/wtfp/map-project.md +0 -0
  99. /package/{commands → vendors/claude/commands}/wtfp/new-paper.md +0 -0
  100. /package/{commands → vendors/claude/commands}/wtfp/pause-writing.md +0 -0
  101. /package/{commands → vendors/claude/commands}/wtfp/plan-revision.md +0 -0
  102. /package/{commands → vendors/claude/commands}/wtfp/plan-section.md +0 -0
  103. /package/{commands → vendors/claude/commands}/wtfp/polish-prose.md +0 -0
  104. /package/{commands → vendors/claude/commands}/wtfp/progress.md +0 -0
  105. /package/{commands → vendors/claude/commands}/wtfp/remove-section.md +0 -0
  106. /package/{commands → vendors/claude/commands}/wtfp/research-gap.md +0 -0
  107. /package/{commands → vendors/claude/commands}/wtfp/resume-writing.md +0 -0
  108. /package/{commands → vendors/claude/commands}/wtfp/review-section.md +0 -0
  109. /package/{commands → vendors/claude/commands}/wtfp/submit-milestone.md +0 -0
  110. /package/{commands → vendors/claude/commands}/wtfp/write-section.md +0 -0
package/bin/uninstall.js CHANGED
@@ -3,16 +3,20 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
- const readline = require('readline');
7
6
 
8
- // Colors
9
- const cyan = '\x1b[36m';
10
- const green = '\x1b[32m';
11
- const yellow = '\x1b[33m';
12
- const red = '\x1b[31m';
13
- const magenta = '\x1b[35m';
14
- const dim = '\x1b[2m';
15
- const reset = '\x1b[0m';
7
+ const {
8
+ expandTilde,
9
+ normalizePath,
10
+ getClaudeDir,
11
+ getPathLabel,
12
+ detectInstallation,
13
+ collectFiles,
14
+ createColors,
15
+ createOutput,
16
+ createRL,
17
+ prompt,
18
+ VERSION_FILE
19
+ } = require('./lib/utils');
16
20
 
17
21
  // Get version from package.json
18
22
  let version = 'unknown';
@@ -23,18 +27,8 @@ try {
23
27
  // Running standalone
24
28
  }
25
29
 
26
- const banner = `
27
- ${magenta}██╗ ██╗████████╗███████╗ ██████╗
28
- ██║ ██║╚══██╔══╝██╔════╝ ██╔══██╗
29
- ██║ █╗ ██║ ██║ █████╗ █████╗██████╔╝
30
- ██║███╗██║ ██║ ██╔══╝ ╚════╝██╔═══╝
31
- ╚███╔███╔╝ ██║ ██║ ██║
32
- ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝${reset}
33
-
34
- ${cyan}WTF-P Uninstaller${reset} ${dim}v${version}${reset}
35
- `;
30
+ // ============ Argument Parsing ============
36
31
 
37
- // Parse args
38
32
  const args = process.argv.slice(2);
39
33
  const hasGlobal = args.includes('--global') || args.includes('-g');
40
34
  const hasLocal = args.includes('--local') || args.includes('-l');
@@ -42,6 +36,9 @@ const hasForce = args.includes('--force') || args.includes('-f');
42
36
  const hasBackup = args.includes('--backup') || args.includes('-b');
43
37
  const hasDryRun = args.includes('--dry-run') || args.includes('-n');
44
38
  const hasCleanBackups = args.includes('--clean-backups');
39
+ const hasHelp = args.includes('--help') || args.includes('-h');
40
+ const hasNoColor = args.includes('--no-color');
41
+ const hasQuiet = args.includes('--quiet') || args.includes('-q');
45
42
 
46
43
  // Parse --config-dir argument
47
44
  function parseConfigDirArg() {
@@ -49,7 +46,7 @@ function parseConfigDirArg() {
49
46
  if (configDirIndex !== -1) {
50
47
  const nextArg = args[configDirIndex + 1];
51
48
  if (!nextArg || nextArg.startsWith('-')) {
52
- console.error(` ${yellow}--config-dir requires a path argument${reset}`);
49
+ console.error(' --config-dir requires a path argument');
53
50
  process.exit(1);
54
51
  }
55
52
  return nextArg;
@@ -60,82 +57,82 @@ function parseConfigDirArg() {
60
57
  }
61
58
  return null;
62
59
  }
60
+
63
61
  const explicitConfigDir = parseConfigDirArg();
64
- const hasHelp = args.includes('--help') || args.includes('-h');
65
62
 
66
- console.log(banner);
63
+ // Setup output helpers
64
+ const isTTY = process.stdout.isTTY && process.stdin.isTTY;
65
+ const useColors = !hasNoColor && (isTTY || process.env.FORCE_COLOR);
66
+ const out = createOutput({ quiet: hasQuiet, useColors });
67
+ const c = out.colors;
68
+
69
+ // ============ Banner ============
70
+
71
+ const banner = `
72
+ ${c.magenta('██╗ ██╗████████╗███████╗ ██████╗')}
73
+ ${c.magenta('██║ ██║╚══██╔══╝██╔════╝ ██╔══██╗')}
74
+ ${c.magenta('██║ █╗ ██║ ██║ █████╗ █████╗██████╔╝')}
75
+ ${c.magenta('██║███╗██║ ██║ ██╔══╝ ╚════╝██╔═══╝')}
76
+ ${c.magenta('╚███╔███╔╝ ██║ ██║ ██║')}
77
+ ${c.magenta(' ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝')}
78
+
79
+ ${c.cyan('WTF-P Uninstaller')} ${c.dim(`v${version}`)}
80
+ `;
81
+
82
+ if (!hasQuiet) {
83
+ console.log(banner);
84
+ }
85
+
86
+ // ============ Help Text ============
67
87
 
68
- // Show help if requested
69
88
  if (hasHelp) {
70
- console.log(` ${yellow}Usage:${reset} npx wtf-p-uninstall [options]
71
-
72
- ${yellow}Options:${reset}
73
- ${cyan}-g, --global${reset} Uninstall from Claude config directory
74
- ${cyan}-l, --local${reset} Uninstall from ./.claude in current directory
75
- ${cyan}-c, --config-dir <path>${reset} Specify custom Claude config directory
76
- ${cyan}-f, --force${reset} Skip confirmation prompts
77
- ${cyan}-b, --backup${reset} Backup files before removing
78
- ${cyan}-n, --dry-run${reset} Show what would be removed without removing
79
- ${cyan}--clean-backups${reset} Also remove .backup-* files from prior installs
80
- ${cyan}-h, --help${reset} Show this help message
81
-
82
- ${yellow}What gets removed:${reset}
83
- ${dim}commands/wtfp/${reset} - WTF-P slash commands
84
- ${dim}write-the-f-paper/${reset} - WTF-P skill and workflows
85
-
86
- ${yellow}What stays intact:${reset}
87
- ${dim}CLAUDE.md${reset} - Your personal instructions (never touched)
88
- ${dim}commands/${reset} - Other slash commands you may have
89
- ${dim}settings.json${reset} - Your Claude settings
90
- ${dim}Other directories${reset} - Any other config or plugins
91
-
92
- ${yellow}Examples:${reset}
93
- ${dim}# Interactive uninstall from ~/.claude${reset}
89
+ console.log(` ${c.yellow('Usage:')} npx wtf-p-uninstall [options]
90
+
91
+ ${c.yellow('Options:')}
92
+ ${c.cyan('-g, --global')} Uninstall from Claude config directory
93
+ ${c.cyan('-l, --local')} Uninstall from ./.claude in current directory
94
+ ${c.cyan('-c, --config-dir <path>')} Specify custom Claude config directory
95
+ ${c.cyan('-f, --force')} Skip confirmation prompts
96
+ ${c.cyan('-b, --backup')} Backup files before removing
97
+ ${c.cyan('-n, --dry-run')} Show what would be removed without removing
98
+ ${c.cyan('--clean-backups')} Also remove .backup-* files from prior installs
99
+ ${c.cyan('--no-color')} Disable colored output
100
+ ${c.cyan('-q, --quiet')} Suppress non-essential output
101
+ ${c.cyan('-h, --help')} Show this help message
102
+
103
+ ${c.yellow('What gets removed:')}
104
+ ${c.dim('commands/wtfp/')} - WTF-P slash commands
105
+ ${c.dim('write-the-f-paper/')} - WTF-P workflows
106
+ ${c.dim('skills/wtfp/')} - WTF-P skills
107
+ ${c.dim('.claude-plugin/')} - Plugin manifest
108
+ ${c.dim('.wtfp-version')} - Version tracking file
109
+
110
+ ${c.yellow('What stays intact:')}
111
+ ${c.dim('CLAUDE.md')} - Your personal instructions (never touched)
112
+ ${c.dim('commands/')} - Other slash commands you may have
113
+ ${c.dim('settings.json')} - Your Claude settings
114
+ ${c.dim('Other directories')} - Any other config or plugins
115
+
116
+ ${c.yellow('Examples:')}
117
+ ${c.dim('# Interactive uninstall from ~/.claude')}
94
118
  npx wtf-p-uninstall --global
95
119
 
96
- ${dim}# Preview what would be removed${reset}
120
+ ${c.dim('# Preview what would be removed')}
97
121
  npx wtf-p-uninstall --global --dry-run
98
122
 
99
- ${dim}# Backup before removing${reset}
123
+ ${c.dim('# Backup before removing')}
100
124
  npx wtf-p-uninstall --global --backup
101
125
 
102
- ${dim}# Also clean up backup files from prior installs${reset}
126
+ ${c.dim('# Also clean up backup files from prior installs')}
103
127
  npx wtf-p-uninstall --global --clean-backups
104
128
 
105
- ${dim}# Uninstall from custom location${reset}
129
+ ${c.dim('# Uninstall from custom location')}
106
130
  npx wtf-p-uninstall --global --config-dir ~/.claude-research
107
131
  `);
108
132
  process.exit(0);
109
133
  }
110
134
 
111
- /**
112
- * Expand ~ to home directory
113
- */
114
- function expandTilde(filePath) {
115
- if (filePath && filePath.startsWith('~/')) {
116
- return path.join(os.homedir(), filePath.slice(2));
117
- }
118
- return filePath;
119
- }
120
-
121
- /**
122
- * Create readline interface for prompts
123
- */
124
- function createRL() {
125
- return readline.createInterface({
126
- input: process.stdin,
127
- output: process.stdout
128
- });
129
- }
130
-
131
- /**
132
- * Prompt user with a question
133
- */
134
- function prompt(rl, question) {
135
- return new Promise(resolve => {
136
- rl.question(question, answer => resolve(answer.trim().toLowerCase()));
137
- });
138
- }
135
+ // ============ Utilities ============
139
136
 
140
137
  /**
141
138
  * Generate backup directory path with timestamp
@@ -145,24 +142,6 @@ function getBackupDir(claudeDir) {
145
142
  return path.join(claudeDir, `.wtfp-backup-${timestamp}`);
146
143
  }
147
144
 
148
- /**
149
- * Recursively collect all files in a directory
150
- */
151
- function collectFiles(dir, files = []) {
152
- if (!fs.existsSync(dir)) return files;
153
-
154
- const entries = fs.readdirSync(dir, { withFileTypes: true });
155
- for (const entry of entries) {
156
- const fullPath = path.join(dir, entry.name);
157
- if (entry.isDirectory()) {
158
- collectFiles(fullPath, files);
159
- } else {
160
- files.push(fullPath);
161
- }
162
- }
163
- return files;
164
- }
165
-
166
145
  /**
167
146
  * Recursively remove directory
168
147
  */
@@ -198,7 +177,6 @@ function findBackupFiles(dir, backups = []) {
198
177
  for (const entry of entries) {
199
178
  const fullPath = path.join(dir, entry.name);
200
179
  if (entry.isDirectory()) {
201
- // Check for .wtfp-backup-* directories
202
180
  if (entry.name.startsWith('.wtfp-backup-')) {
203
181
  backups.push(fullPath);
204
182
  } else {
@@ -211,72 +189,43 @@ function findBackupFiles(dir, backups = []) {
211
189
  return backups;
212
190
  }
213
191
 
214
- /**
215
- * Detect what's installed
216
- */
217
- function detectInstallation(claudeDir) {
218
- const wtfpCommands = path.join(claudeDir, 'commands', 'wtfp');
219
- const wtfpSkill = path.join(claudeDir, 'write-the-f-paper');
220
-
221
- const installed = {
222
- commands: fs.existsSync(wtfpCommands) ? wtfpCommands : null,
223
- skill: fs.existsSync(wtfpSkill) ? wtfpSkill : null,
224
- commandFiles: [],
225
- skillFiles: []
226
- };
227
-
228
- if (installed.commands) {
229
- installed.commandFiles = collectFiles(installed.commands);
230
- }
231
- if (installed.skill) {
232
- installed.skillFiles = collectFiles(installed.skill);
233
- }
234
-
235
- return installed;
236
- }
192
+ // ============ Uninstall Logic ============
237
193
 
238
194
  /**
239
195
  * Uninstall from the specified directory
240
196
  */
241
197
  async function uninstall(isGlobal) {
242
- const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.CLAUDE_CONFIG_DIR);
243
- const defaultGlobalDir = configDir || path.join(os.homedir(), '.claude');
244
- const claudeDir = isGlobal
245
- ? defaultGlobalDir
246
- : path.join(process.cwd(), '.claude');
247
-
248
- const locationLabel = isGlobal
249
- ? claudeDir.replace(os.homedir(), '~')
250
- : claudeDir.replace(process.cwd(), '.');
198
+ const claudeDir = getClaudeDir(explicitConfigDir, isGlobal);
199
+ const locationLabel = getPathLabel(claudeDir, isGlobal);
251
200
 
252
- console.log(` Checking ${cyan}${locationLabel}${reset} for WTF-P installation...\n`);
201
+ out.log(` Checking ${c.cyan(locationLabel)} for WTF-P installation...\n`);
253
202
 
254
203
  // Detect installation
255
204
  const installed = detectInstallation(claudeDir);
256
- const totalFiles = installed.commandFiles.length + installed.skillFiles.length;
205
+ const totalFiles = installed.commandFiles.length + installed.workflowFiles.length + installed.skillFiles.length;
257
206
 
258
- if (!installed.commands && !installed.skill) {
259
- console.log(` ${yellow}No WTF-P installation found in ${locationLabel}${reset}\n`);
207
+ if (!installed.hasCommands && !installed.hasWorkflows && !installed.hasSkills) {
208
+ out.log(` ${c.yellow('No WTF-P installation found in ' + locationLabel)}\n`);
260
209
 
261
210
  // Check for backups if requested
262
211
  if (hasCleanBackups) {
263
212
  const backups = findBackupFiles(claudeDir);
264
213
  if (backups.length > 0) {
265
- console.log(` Found ${backups.length} backup file(s)/folder(s) to clean:\n`);
214
+ out.log(` Found ${backups.length} backup file(s)/folder(s) to clean:\n`);
266
215
  for (const b of backups.slice(0, 10)) {
267
- console.log(` ${dim}${b.replace(claudeDir, '.')}${reset}`);
216
+ out.log(` ${c.dim(b.replace(claudeDir, '.'))}`);
268
217
  }
269
218
  if (backups.length > 10) {
270
- console.log(` ${dim}... and ${backups.length - 10} more${reset}`);
219
+ out.log(` ${c.dim('... and ' + (backups.length - 10) + ' more')}`);
271
220
  }
272
- console.log();
221
+ out.log('');
273
222
 
274
223
  if (!hasDryRun && !hasForce) {
275
224
  const rl = createRL();
276
225
  const answer = await prompt(rl, ` Remove these backup files? [y/N]: `);
277
226
  rl.close();
278
227
  if (answer !== 'y' && answer !== 'yes') {
279
- console.log(`\n ${yellow}Aborted.${reset}\n`);
228
+ out.log(`\n ${c.yellow('Aborted.')}\n`);
280
229
  return;
281
230
  }
282
231
  }
@@ -288,48 +237,61 @@ async function uninstall(isGlobal) {
288
237
  } else {
289
238
  fs.unlinkSync(b);
290
239
  }
291
- console.log(` ${red}-${reset} ${dim}${b.replace(claudeDir, '.')}${reset}`);
240
+ out.log(` ${c.red('-')} ${c.dim(b.replace(claudeDir, '.'))}`);
292
241
  }
293
- console.log(`\n ${green}Cleaned ${backups.length} backup file(s).${reset}\n`);
242
+ out.log(`\n ${c.green('Cleaned ' + backups.length + ' backup file(s).')}\n`);
294
243
  } else {
295
- console.log(` ${yellow}Dry run: would remove ${backups.length} backup file(s).${reset}\n`);
244
+ out.log(` ${c.yellow('Dry run: would remove ' + backups.length + ' backup file(s).')}\n`);
296
245
  }
297
246
  } else {
298
- console.log(` No backup files found.\n`);
247
+ out.log(` No backup files found.\n`);
299
248
  }
300
249
  }
301
250
  return;
302
251
  }
303
252
 
304
253
  // Show what will be removed
305
- console.log(` ${yellow}Found WTF-P installation:${reset}\n`);
254
+ out.log(` ${c.yellow('Found WTF-P installation:')}\n`);
306
255
 
307
- if (installed.commands) {
308
- console.log(` ${cyan}commands/wtfp/${reset} (${installed.commandFiles.length} files)`);
256
+ const commandsDir = path.join(claudeDir, 'commands', 'wtfp');
257
+ const workflowsDir = path.join(claudeDir, 'write-the-f-paper');
258
+ const skillsDir = path.join(claudeDir, 'skills', 'wtfp');
259
+ const pluginDir = path.join(claudeDir, '.claude-plugin');
260
+
261
+ if (installed.hasCommands) {
262
+ out.log(` ${c.cyan('commands/wtfp/')} (${installed.commandFiles.length} files)`);
263
+ }
264
+ if (installed.hasWorkflows) {
265
+ out.log(` ${c.cyan('write-the-f-paper/')} (${installed.workflowFiles.length} files)`);
266
+ }
267
+ if (installed.hasSkills) {
268
+ out.log(` ${c.cyan('skills/wtfp/')} (${installed.skillFiles.length} files)`);
269
+ }
270
+ if (fs.existsSync(pluginDir)) {
271
+ out.log(` ${c.cyan('.claude-plugin/')} (manifest)`);
309
272
  }
310
- if (installed.skill) {
311
- console.log(` ${cyan}write-the-f-paper/${reset} (${installed.skillFiles.length} files)`);
273
+ if (installed.version) {
274
+ out.log(` ${c.cyan(VERSION_FILE)} (version tracking)`);
312
275
  }
313
- console.log();
276
+ out.log('');
314
277
 
315
- // Check for backups to clean (only outside wtfp directories since those get removed anyway)
278
+ // Check for backups to clean
316
279
  let backups = [];
317
280
  if (hasCleanBackups) {
318
281
  const allBackups = findBackupFiles(claudeDir);
319
- // Filter out backups inside directories we're already removing
320
282
  backups = allBackups.filter(b => {
321
- if (installed.commands && b.startsWith(installed.commands)) return false;
322
- if (installed.skill && b.startsWith(installed.skill)) return false;
283
+ if (installed.hasCommands && b.startsWith(commandsDir)) return false;
284
+ if (installed.hasSkill && b.startsWith(skillDir)) return false;
323
285
  return true;
324
286
  });
325
287
  if (backups.length > 0) {
326
- console.log(` ${dim}+ ${backups.length} backup file(s) outside wtfp dirs will also be removed${reset}\n`);
288
+ out.log(` ${c.dim('+ ' + backups.length + ' backup file(s) outside wtfp dirs will also be removed')}\n`);
327
289
  }
328
290
  }
329
291
 
330
292
  // Dry run stops here
331
293
  if (hasDryRun) {
332
- console.log(` ${yellow}Dry run: would remove ${totalFiles} file(s) in 2 directories.${reset}\n`);
294
+ out.log(` ${c.yellow('Dry run: would remove ' + totalFiles + ' file(s) in 2 directories.')}\n`);
333
295
  return;
334
296
  }
335
297
 
@@ -340,10 +302,10 @@ async function uninstall(isGlobal) {
340
302
  rl.close();
341
303
 
342
304
  if (answer !== 'y' && answer !== 'yes') {
343
- console.log(`\n ${yellow}Aborted.${reset}\n`);
305
+ out.log(`\n ${c.yellow('Aborted.')}\n`);
344
306
  return;
345
307
  }
346
- console.log();
308
+ out.log('');
347
309
  }
348
310
 
349
311
  // Backup if requested
@@ -351,38 +313,69 @@ async function uninstall(isGlobal) {
351
313
  const backupDir = getBackupDir(claudeDir);
352
314
  fs.mkdirSync(backupDir, { recursive: true });
353
315
 
354
- if (installed.commands) {
355
- const dest = path.join(backupDir, 'commands', 'wtfp');
356
- copyDir(installed.commands, dest);
316
+ if (installed.hasCommands) {
317
+ copyDir(commandsDir, path.join(backupDir, 'commands', 'wtfp'));
357
318
  }
358
- if (installed.skill) {
359
- const dest = path.join(backupDir, 'write-the-f-paper');
360
- copyDir(installed.skill, dest);
319
+ if (installed.hasWorkflows) {
320
+ copyDir(workflowsDir, path.join(backupDir, 'write-the-f-paper'));
321
+ }
322
+ if (installed.hasSkills) {
323
+ copyDir(skillsDir, path.join(backupDir, 'skills', 'wtfp'));
324
+ }
325
+ if (fs.existsSync(pluginDir)) {
326
+ copyDir(pluginDir, path.join(backupDir, '.claude-plugin'));
361
327
  }
362
328
 
363
329
  const backupLabel = backupDir.replace(os.homedir(), '~').replace(process.cwd(), '.');
364
- console.log(` ${cyan}↻${reset} Backed up to ${dim}${backupLabel}${reset}\n`);
330
+ out.log(` ${c.cyan('↻')} Backed up to ${c.dim(backupLabel)}\n`);
365
331
  }
366
332
 
367
333
  // Remove directories
368
- if (installed.commands) {
369
- removeDir(installed.commands);
370
- console.log(` ${red}-${reset} ${dim}commands/wtfp/${reset}`);
334
+ if (installed.hasCommands) {
335
+ removeDir(commandsDir);
336
+ out.log(` ${c.red('-')} ${c.dim('commands/wtfp/')}`);
371
337
 
372
338
  // Clean up empty commands dir
373
- const commandsDir = path.join(claudeDir, 'commands');
374
- if (fs.existsSync(commandsDir)) {
375
- const remaining = fs.readdirSync(commandsDir);
339
+ const parentCommandsDir = path.join(claudeDir, 'commands');
340
+ if (fs.existsSync(parentCommandsDir)) {
341
+ const remaining = fs.readdirSync(parentCommandsDir);
376
342
  if (remaining.length === 0) {
377
- fs.rmdirSync(commandsDir);
378
- console.log(` ${red}-${reset} ${dim}commands/${reset} ${dim}(empty)${reset}`);
343
+ fs.rmdirSync(parentCommandsDir);
344
+ out.log(` ${c.red('-')} ${c.dim('commands/')} ${c.dim('(empty)')}`);
379
345
  }
380
346
  }
381
347
  }
382
348
 
383
- if (installed.skill) {
384
- removeDir(installed.skill);
385
- console.log(` ${red}-${reset} ${dim}write-the-f-paper/${reset}`);
349
+ if (installed.hasWorkflows) {
350
+ removeDir(workflowsDir);
351
+ out.log(` ${c.red('-')} ${c.dim('write-the-f-paper/')}`);
352
+ }
353
+
354
+ if (installed.hasSkills) {
355
+ removeDir(skillsDir);
356
+ out.log(` ${c.red('-')} ${c.dim('skills/wtfp/')}`);
357
+
358
+ // Clean up empty skills dir
359
+ const parentSkillsDir = path.join(claudeDir, 'skills');
360
+ if (fs.existsSync(parentSkillsDir)) {
361
+ const remaining = fs.readdirSync(parentSkillsDir);
362
+ if (remaining.length === 0) {
363
+ fs.rmdirSync(parentSkillsDir);
364
+ out.log(` ${c.red('-')} ${c.dim('skills/')} ${c.dim('(empty)')}`);
365
+ }
366
+ }
367
+ }
368
+
369
+ if (fs.existsSync(pluginDir)) {
370
+ removeDir(pluginDir);
371
+ out.log(` ${c.red('-')} ${c.dim('.claude-plugin/')}`);
372
+ }
373
+
374
+ // Remove version file
375
+ const versionFile = path.join(claudeDir, VERSION_FILE);
376
+ if (fs.existsSync(versionFile)) {
377
+ fs.unlinkSync(versionFile);
378
+ out.log(` ${c.red('-')} ${c.dim(VERSION_FILE)}`);
386
379
  }
387
380
 
388
381
  // Clean backups if requested
@@ -394,15 +387,15 @@ async function uninstall(isGlobal) {
394
387
  fs.unlinkSync(b);
395
388
  }
396
389
  }
397
- console.log(` ${red}-${reset} ${dim}${backups.length} backup file(s)${reset}`);
390
+ out.log(` ${c.red('-')} ${c.dim(backups.length + ' backup file(s)')}`);
398
391
  }
399
392
 
400
- console.log(`
401
- ${green}Done!${reset} WTF-P has been uninstalled.
393
+ out.log(`
394
+ ${c.green('Done!')} WTF-P has been uninstalled.
402
395
 
403
- ${dim}Your CLAUDE.md and other configs remain untouched.${reset}
396
+ ${c.dim('Your CLAUDE.md and other configs remain untouched.')}
404
397
 
405
- To reinstall: ${cyan}npx wtf-p --global${reset}
398
+ To reinstall: ${c.cyan('npx wtf-p --global')}
406
399
  `);
407
400
  }
408
401
 
@@ -412,43 +405,42 @@ async function uninstall(isGlobal) {
412
405
  async function promptLocation() {
413
406
  const rl = createRL();
414
407
 
415
- const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.CLAUDE_CONFIG_DIR);
416
- const globalPath = configDir || path.join(os.homedir(), '.claude');
417
- const globalLabel = globalPath.replace(os.homedir(), '~');
418
- const localPath = path.join(process.cwd(), '.claude');
408
+ const globalPath = getClaudeDir(explicitConfigDir, true);
409
+ const globalLabel = getPathLabel(globalPath, true);
410
+ const localPath = getClaudeDir(null, false);
419
411
 
420
412
  // Check what's installed where
421
413
  const globalInstalled = detectInstallation(globalPath);
422
414
  const localInstalled = detectInstallation(localPath);
423
415
 
424
- const hasGlobalInstall = globalInstalled.commands || globalInstalled.skill;
425
- const hasLocalInstall = localInstalled.commands || localInstalled.skill;
416
+ const hasGlobalInstall = globalInstalled.hasCommands || globalInstalled.hasWorkflows || globalInstalled.hasSkills;
417
+ const hasLocalInstall = localInstalled.hasCommands || localInstalled.hasWorkflows || localInstalled.hasSkills;
426
418
 
427
419
  if (!hasGlobalInstall && !hasLocalInstall) {
428
- console.log(` ${yellow}No WTF-P installation found.${reset}\n`);
429
- console.log(` Checked:`);
430
- console.log(` ${dim}${globalLabel}${reset}`);
431
- console.log(` ${dim}./.claude${reset}\n`);
420
+ out.log(` ${c.yellow('No WTF-P installation found.')}\n`);
421
+ out.log(` Checked:`);
422
+ out.log(` ${c.dim(globalLabel)}`);
423
+ out.log(` ${c.dim('./.claude')}\n`);
432
424
  rl.close();
433
425
  return;
434
426
  }
435
427
 
436
- console.log(` ${yellow}Where would you like to uninstall from?${reset}\n`);
428
+ out.log(` ${c.yellow('Where would you like to uninstall from?')}\n`);
437
429
 
438
430
  if (hasGlobalInstall) {
439
- const count = globalInstalled.commandFiles.length + globalInstalled.skillFiles.length;
440
- console.log(` ${cyan}1${reset}) Global ${dim}(${globalLabel})${reset} - ${count} files`);
431
+ const count = globalInstalled.commandFiles.length + globalInstalled.workflowFiles.length + globalInstalled.skillFiles.length;
432
+ out.log(` ${c.cyan('1)')} Global ${c.dim('(' + globalLabel + ')')} - ${count} files`);
441
433
  } else {
442
- console.log(` ${dim}1) Global (${globalLabel}) - not installed${reset}`);
434
+ out.log(` ${c.dim('1) Global (' + globalLabel + ') - not installed')}`);
443
435
  }
444
436
 
445
437
  if (hasLocalInstall) {
446
- const count = localInstalled.commandFiles.length + localInstalled.skillFiles.length;
447
- console.log(` ${cyan}2${reset}) Local ${dim}(./.claude)${reset} - ${count} files`);
438
+ const count = localInstalled.commandFiles.length + localInstalled.workflowFiles.length + localInstalled.skillFiles.length;
439
+ out.log(` ${c.cyan('2)')} Local ${c.dim('(./.claude)')} - ${count} files`);
448
440
  } else {
449
- console.log(` ${dim}2) Local (./.claude) - not installed${reset}`);
441
+ out.log(` ${c.dim('2) Local (./.claude) - not installed')}`);
450
442
  }
451
- console.log();
443
+ out.log('');
452
444
 
453
445
  const answer = await prompt(rl, ` Choice: `);
454
446
  rl.close();
@@ -458,19 +450,23 @@ async function promptLocation() {
458
450
  } else if (answer === '2' && hasLocalInstall) {
459
451
  await uninstall(false);
460
452
  } else {
461
- console.log(`\n ${yellow}Invalid choice or no installation at that location.${reset}\n`);
453
+ out.log(`\n ${c.yellow('Invalid choice or no installation at that location.')}\n`);
462
454
  }
463
455
  }
464
456
 
465
- // Main
457
+ // ============ Main ============
458
+
466
459
  async function main() {
467
460
  if (hasGlobal && hasLocal) {
468
- console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
461
+ out.error('Cannot specify both --global and --local');
469
462
  process.exit(1);
470
- } else if (explicitConfigDir && hasLocal) {
471
- console.error(` ${yellow}Cannot use --config-dir with --local${reset}`);
463
+ }
464
+ if (explicitConfigDir && hasLocal) {
465
+ out.error('Cannot use --config-dir with --local');
472
466
  process.exit(1);
473
- } else if (hasGlobal) {
467
+ }
468
+
469
+ if (hasGlobal) {
474
470
  await uninstall(true);
475
471
  } else if (hasLocal) {
476
472
  await uninstall(false);
@@ -480,6 +476,6 @@ async function main() {
480
476
  }
481
477
 
482
478
  main().catch(err => {
483
- console.error(` ${red}Error:${reset} ${err.message}`);
479
+ out.error(err.message);
484
480
  process.exit(1);
485
481
  });