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
@@ -0,0 +1,235 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+ const MANIFEST = require('../lib/manifest');
5
+ const {
6
+ isValidPath,
7
+ getClaudeDir, // Legacy support, will refactor
8
+ getPathLabel,
9
+ getBackupPath,
10
+ writeVersionFile,
11
+ createRL,
12
+ prompt
13
+ } = require('../lib/utils');
14
+
15
+ /**
16
+ * Process file content with path replacement
17
+ */
18
+ function processContent(srcPath, pathPrefix) {
19
+ if (srcPath.endsWith('.md') || srcPath.endsWith('.json')) {
20
+ let content = fs.readFileSync(srcPath, 'utf8');
21
+ return content.replace(/~\/\.claude\//g, pathPrefix);
22
+ }
23
+ return null;
24
+ }
25
+
26
+ /**
27
+ * Collect files from a manifest component
28
+ */
29
+ function collectComponentFiles(component, destBase, files = []) {
30
+ if (!fs.existsSync(component.src)) return files;
31
+
32
+ function recurse(currentSrc, currentRel) {
33
+ const entries = fs.readdirSync(currentSrc, { withFileTypes: true });
34
+
35
+ for (const entry of entries) {
36
+ const srcPath = path.join(currentSrc, entry.name);
37
+ const relPath = path.join(currentRel, entry.name);
38
+ const destPath = path.join(destBase, component.dest, relPath);
39
+
40
+ if (entry.isDirectory()) {
41
+ recurse(srcPath, relPath);
42
+ } else {
43
+ files.push({
44
+ src: srcPath,
45
+ dest: destPath,
46
+ name: entry.name,
47
+ componentId: component.id
48
+ });
49
+ }
50
+ }
51
+ }
52
+
53
+ recurse(component.src, '.');
54
+ return files;
55
+ }
56
+
57
+ /**
58
+ * Install files with conflict resolution
59
+ */
60
+ async function installWithConflictResolution(files, pathPrefix, targetDir, options) {
61
+ const { out, hasForce, hasBackupAll, isInteractive, showExplanations } = options;
62
+ const c = out.colors;
63
+ const rl = createRL();
64
+ let globalChoice = null;
65
+ const stats = { installed: 0, skipped: 0, backed: 0 };
66
+
67
+ const existingFiles = files.filter(f => fs.existsSync(f.dest));
68
+
69
+ if (existingFiles.length > 0 && !hasForce && !hasBackupAll && isInteractive) {
70
+ const relPath = getPathLabel(targetDir, true);
71
+ out.log(` ${c.yellow(`Found ${existingFiles.length} existing WTF-P file(s) in ${relPath}`)}
72
+ `);
73
+
74
+ if (showExplanations) {
75
+ out.log(` ${c.dim('For each file, choose:')}`);
76
+ out.log(` ${c.dim('[o]verwrite - Replace with new version')}`);
77
+ out.log(` ${c.dim('[s]kip - Keep your existing file')}`);
78
+ out.log(` ${c.dim('[b]ackup - Save existing, then install new')}`);
79
+ out.log(` ${c.dim('[a]ll - Apply same choice to remaining')}\n`);
80
+ }
81
+ }
82
+
83
+ for (const file of files) {
84
+ const exists = fs.existsSync(file.dest);
85
+ const relDest = file.dest.replace(os.homedir(), '~').replace(process.cwd(), '.');
86
+
87
+ fs.mkdirSync(path.dirname(file.dest), { recursive: true });
88
+
89
+ if (exists && !hasForce && !hasBackupAll) {
90
+ let choice = globalChoice;
91
+
92
+ if (!choice && isInteractive) {
93
+ const answer = await prompt(rl,
94
+ ` ${c.yellow('?')} ${c.dim(relDest)} exists. [o]verwrite/[s]kip/[b]ackup/[a]ll: `
95
+ );
96
+
97
+ if (answer.startsWith('a')) {
98
+ const allAnswer = await prompt(rl,
99
+ ` Apply to all: [o]verwrite/[s]kip/[b]ackup: `
100
+ );
101
+ if (allAnswer.startsWith('o')) globalChoice = 'overwrite';
102
+ else if (allAnswer.startsWith('b')) globalChoice = 'backup';
103
+ else globalChoice = 'skip';
104
+ choice = globalChoice;
105
+ } else if (answer.startsWith('o')) {
106
+ choice = 'overwrite';
107
+ } else if (answer.startsWith('b')) {
108
+ choice = 'backup';
109
+ } else {
110
+ choice = 'skip';
111
+ }
112
+ } else if (!choice) {
113
+ choice = 'skip';
114
+ }
115
+
116
+ if (choice === 'skip') {
117
+ out.verbose(` ${c.dim('○')} Skipped ${c.dim(relDest)}`);
118
+ stats.skipped++;
119
+ continue;
120
+ }
121
+
122
+ if (choice === 'backup') {
123
+ const backupPath = getBackupPath(file.dest);
124
+ fs.copyFileSync(file.dest, backupPath);
125
+ out.log(` ${c.cyan('↻')} Backed up to ${c.dim(path.basename(backupPath))}`);
126
+ stats.backed++;
127
+ }
128
+ } else if (exists && hasBackupAll) {
129
+ const backupPath = getBackupPath(file.dest);
130
+ fs.copyFileSync(file.dest, backupPath);
131
+ out.verbose(` ${c.cyan('↻')} Backed up ${c.dim(relDest)}`);
132
+ stats.backed++;
133
+ }
134
+
135
+ const content = processContent(file.src, pathPrefix);
136
+ if (content !== null) {
137
+ fs.writeFileSync(file.dest, content);
138
+ } else {
139
+ fs.copyFileSync(file.src, file.dest);
140
+ }
141
+
142
+ if (!exists) {
143
+ out.verbose(` ${c.green('+')} ${c.dim(relDest)}`);
144
+ } else {
145
+ out.verbose(` ${c.green('✓')} ${c.dim(relDest)}`);
146
+ }
147
+ stats.installed++;
148
+ }
149
+
150
+ rl.close();
151
+ return stats;
152
+ }
153
+
154
+ /**
155
+ * Main install logic
156
+ */
157
+ async function install(isGlobal, isUpdate, options, pkg) {
158
+ const { out, explicitConfigDir, hasQuiet, onlyInstall, showExplanations } = options;
159
+ const c = out.colors;
160
+
161
+ // Use Claude vendor by default for now (can be passed as arg later)
162
+ const vendorConfig = MANIFEST.claude;
163
+
164
+ // Resolve Target Directory using the Vendor Strategy
165
+ // TODO: Use getVendorDir generic
166
+ const targetDir = getClaudeDir(explicitConfigDir, isGlobal);
167
+ const locationLabel = getPathLabel(targetDir, isGlobal);
168
+
169
+ // Validate path
170
+ if (!isValidPath(targetDir)) {
171
+ out.error(`Invalid path: ${targetDir}`);
172
+ process.exit(1);
173
+ }
174
+
175
+ const pathPrefix = isGlobal
176
+ ? (explicitConfigDir ? `${targetDir}/` : `~/${vendorConfig.defaultDir}/`)
177
+ : `./${vendorConfig.defaultDir}/`;
178
+
179
+ if (!hasQuiet) {
180
+ out.log(` Installing to ${c.cyan(locationLabel)}
181
+ `);
182
+ }
183
+
184
+ // Collect files based on Manifest
185
+ const allFiles = [];
186
+
187
+ vendorConfig.components.forEach(component => {
188
+ // Check --only filters
189
+ if (onlyInstall !== 'all') {
190
+ // Map 'workflows' flag to 'workflows' component ID
191
+ // Map 'commands' flag to 'commands' component ID
192
+ // Map 'skills' flag to 'skills' component ID
193
+ if (onlyInstall !== component.id) {
194
+ // Special case: 'commands' usually implies 'skills' too in legacy logic
195
+ if (onlyInstall === 'commands' && component.id === 'skills') {
196
+ // Allow
197
+ } else {
198
+ return;
199
+ }
200
+ }
201
+ }
202
+
203
+ // DEBUG LOG
204
+ // console.log(`Collecting: ${component.id} from ${component.src}`);
205
+
206
+ collectComponentFiles(component, targetDir, allFiles);
207
+ });
208
+
209
+ // DEBUG
210
+ // console.log(`Total files found: ${allFiles.length}`);
211
+
212
+ // Install with conflict resolution
213
+ const stats = await installWithConflictResolution(allFiles, pathPrefix, targetDir, options);
214
+
215
+ // Write version tracking file
216
+ writeVersionFile(targetDir, pkg.version, allFiles);
217
+
218
+ // Summary
219
+ if (!hasQuiet) {
220
+ out.log(`
221
+ ${c.green('Done!')} Installed: ${stats.installed}, Skipped: ${stats.skipped}, Backed up: ${stats.backed}
222
+
223
+ Run ${c.cyan('/wtfp:help')} in Claude Code to get started.
224
+ `);
225
+
226
+ if (showExplanations && !isUpdate) {
227
+ out.log(` ${c.yellow('Quick Start:')}`);
228
+ out.log(` ${c.cyan('/wtfp:new-paper')} Start a new academic paper`);
229
+ out.log(` ${c.cyan('/wtfp:progress')} Check your writing status`);
230
+ out.log(` ${c.cyan('/wtfp:help')} Show all commands\n`);
231
+ }
232
+ }
233
+ }
234
+
235
+ module.exports = install;
@@ -0,0 +1,88 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const MANIFEST = require('../lib/manifest');
4
+ const { collectComponentFiles } = require('./install-logic'); // Need to export this or copy logic
5
+
6
+ // Re-implementing logic here to avoid circular dep if install-logic is messy
7
+ // But better to export valid logic.
8
+
9
+ /**
10
+ * Collect files from a manifest component
11
+ */
12
+ function collectComponentFilesLocal(component, files = []) {
13
+ if (!fs.existsSync(component.src)) return files;
14
+
15
+ function recurse(currentSrc, currentRel) {
16
+ const entries = fs.readdirSync(currentSrc, { withFileTypes: true });
17
+
18
+ for (const entry of entries) {
19
+ const srcPath = path.join(currentSrc, entry.name);
20
+ const relPath = path.join(currentRel, entry.name);
21
+ // For listing, we just want the relative destination path
22
+ const destPath = path.join(component.dest, relPath);
23
+
24
+ if (entry.isDirectory()) {
25
+ recurse(srcPath, relPath);
26
+ } else {
27
+ files.push({
28
+ src: srcPath,
29
+ path: destPath, // display path
30
+ type: component.id
31
+ });
32
+ }
33
+ }
34
+ }
35
+
36
+ recurse(component.src, '.');
37
+ return files;
38
+ }
39
+
40
+ function showList(options) {
41
+ const { out, hasQuiet, onlyInstall } = options;
42
+ const c = out.colors;
43
+
44
+ // Use Claude by default
45
+ const vendorConfig = MANIFEST.claude;
46
+
47
+ const files = [];
48
+
49
+ vendorConfig.components.forEach(component => {
50
+ if (onlyInstall !== 'all') {
51
+ if (onlyInstall !== component.id) {
52
+ if (onlyInstall === 'commands' && component.id === 'skills') {
53
+ // Allow
54
+ } else {
55
+ return;
56
+ }
57
+ }
58
+ }
59
+ collectComponentFilesLocal(component, files);
60
+ });
61
+
62
+ if (!hasQuiet) {
63
+ out.log(` ${c.yellow('Files to install:')}\n`);
64
+ }
65
+
66
+ // Group by component
67
+ const groups = {};
68
+ files.forEach(f => {
69
+ if (!groups[f.type]) groups[f.type] = [];
70
+ groups[f.type].push(f);
71
+ });
72
+
73
+ Object.keys(groups).forEach(type => {
74
+ const groupFiles = groups[type];
75
+ out.log(` ${c.cyan(type.charAt(0).toUpperCase() + type.slice(1))} (${groupFiles.length} files):`);
76
+ for (const f of groupFiles.slice(0, 10)) {
77
+ out.log(` ${c.dim(f.path)}`);
78
+ }
79
+ if (groupFiles.length > 10) {
80
+ out.log(` ${c.dim(`... and ${groupFiles.length - 10} more`)}`);
81
+ }
82
+ out.log('');
83
+ });
84
+
85
+ out.log(` Total: ${files.length} files\n`);
86
+ }
87
+
88
+ module.exports = showList;
@@ -0,0 +1,85 @@
1
+ const fs = require('fs');
2
+ const { execSync } = require('child_process');
3
+ const { getClaudeDir, detectInstallation, getPathLabel } = require('../lib/utils');
4
+
5
+ async function showStatus(options, pkg) {
6
+ const { out, explicitConfigDir, hasQuiet } = options;
7
+ const c = out.colors;
8
+
9
+ if (!hasQuiet) {
10
+ // Note: banner should be handled by the main entry point or passed in
11
+ }
12
+
13
+ const locations = [];
14
+
15
+ // Check global
16
+ const globalDir = getClaudeDir(explicitConfigDir, true);
17
+ const globalDetection = detectInstallation(globalDir);
18
+ locations.push({
19
+ label: 'Global',
20
+ path: globalDir,
21
+ pathLabel: getPathLabel(globalDir, true),
22
+ ...globalDetection
23
+ });
24
+
25
+ // Check local if different
26
+ const localDir = getClaudeDir(null, false);
27
+ if (localDir !== globalDir) {
28
+ const localDetection = detectInstallation(localDir);
29
+ locations.push({
30
+ label: 'Local',
31
+ path: localDir,
32
+ pathLabel: getPathLabel(localDir, false),
33
+ ...localDetection
34
+ });
35
+ }
36
+
37
+ out.log(` ${c.yellow('WTF-P Installation Status')}\n`);
38
+
39
+ for (const loc of locations) {
40
+ const installed = loc.hasCommands || loc.hasWorkflows || loc.hasSkills;
41
+
42
+ out.log(` ${c.cyan(loc.label)} ${c.dim(`(${loc.pathLabel})`)}`);
43
+
44
+ if (!installed) {
45
+ out.log(` ${c.dim('Not installed')}\n`);
46
+ continue;
47
+ }
48
+
49
+ // Version
50
+ if (loc.version) {
51
+ const versionLabel = loc.version === 'legacy' ? 'legacy (pre-0.2.0)' : loc.version;
52
+ out.log(` Version: ${c.green(versionLabel)}`);
53
+ }
54
+
55
+ // Components
56
+ const components = [];
57
+ if (loc.hasCommands) components.push(`commands (${loc.commandFiles.length} files)`);
58
+ if (loc.hasWorkflows) components.push(`workflows (${loc.workflowFiles.length} files)`);
59
+ if (loc.hasSkills) components.push(`skills (${loc.skillFiles.length} files)`);
60
+ out.log(` Installed: ${components.join(', ')}`);
61
+
62
+ // Warnings
63
+ if (loc.partial) {
64
+ out.log(` ${c.yellow('⚠ Partial installation detected')}`);
65
+ }
66
+ if (loc.corrupt) {
67
+ out.log(` ${c.red('⚠ Corrupt version file')}`);
68
+ }
69
+
70
+ out.log('');
71
+ }
72
+
73
+ // Check for updates
74
+ try {
75
+ const latest = execSync('npm view wtf-p version 2>/dev/null', { encoding: 'utf8' }).trim();
76
+ if (latest && latest !== pkg.version) {
77
+ out.log(` ${c.yellow('Update available:')} ${pkg.version} → ${c.green(latest)}`);
78
+ out.log(` Run: ${c.cyan('npx wtf-p update --global')}\n`);
79
+ }
80
+ } catch {
81
+ // Ignore
82
+ }
83
+ }
84
+
85
+ module.exports = showStatus;
@@ -0,0 +1,50 @@
1
+ const { getClaudeDir, detectInstallation, getPathLabel } = require('../lib/utils');
2
+
3
+ async function runUpdate(options, pkg, installFunc) {
4
+ const { out, explicitConfigDir, hasGlobal, hasLocal } = options;
5
+ const c = out.colors;
6
+
7
+ out.log(` ${c.yellow('Checking for updates...')}\n`);
8
+
9
+ // Find installed location
10
+ const globalDir = getClaudeDir(explicitConfigDir, true);
11
+ const localDir = getClaudeDir(null, false);
12
+
13
+ let targetDir = null;
14
+ let isGlobal = true;
15
+
16
+ const globalDetection = detectInstallation(globalDir);
17
+ const localDetection = detectInstallation(localDir);
18
+
19
+ if (hasGlobal || (!hasLocal && (globalDetection.hasCommands || globalDetection.hasWorkflows || globalDetection.hasSkills))) {
20
+ targetDir = globalDir;
21
+ isGlobal = true;
22
+ } else if (hasLocal || (localDetection.hasCommands || localDetection.hasWorkflows || localDetection.hasSkills)) {
23
+ targetDir = localDir;
24
+ isGlobal = false;
25
+ }
26
+
27
+ if (!targetDir) {
28
+ out.log(` ${c.yellow('No existing installation found.')}`);
29
+ out.log(` Run ${c.cyan('npx wtf-p --global')} to install.\n`);
30
+ return;
31
+ }
32
+
33
+ const detection = isGlobal ? globalDetection : localDetection;
34
+ const currentVersion = detection.version === 'legacy' ? '0.0.0' : detection.version;
35
+
36
+ out.log(` Current: ${c.dim(`v${currentVersion}`)} in ${c.dim(getPathLabel(targetDir, isGlobal))}`);
37
+ out.log(` Package: ${c.dim(`v${pkg.version}`)}`);
38
+
39
+ if (pkg.version === currentVersion) {
40
+ out.log(`\n ${c.green('Already up to date!')}\n`);
41
+ return;
42
+ }
43
+
44
+ out.log(`\n ${c.cyan('Updating...')}\n`);
45
+
46
+ // Run install with backup
47
+ await installFunc(isGlobal, true);
48
+ }
49
+
50
+ module.exports = runUpdate;