wtf-p 0.2.0 → 0.4.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 (122) hide show
  1. package/README.md +24 -2
  2. package/bin/commands/doctor.js +100 -0
  3. package/bin/commands/install-logic.js +231 -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 +78 -629
  8. package/bin/lib/analyze-impact.js +105 -0
  9. package/bin/lib/bib-format.js +161 -0
  10. package/bin/lib/bib-index.js +104 -0
  11. package/bin/lib/citation-fetcher.js +299 -0
  12. package/bin/lib/citation-ranker.js +133 -0
  13. package/bin/lib/manifest.js +59 -0
  14. package/bin/lib/scholar-lookup.js +188 -0
  15. package/bin/lib/semantic-scholar.js +184 -0
  16. package/bin/lib/utils.js +37 -17
  17. package/bin/lib/wcn-compiler.js +226 -0
  18. package/bin/uninstall.js +51 -17
  19. package/core/write-the-f-paper/templates/posters/basic.md +34 -0
  20. package/core/write-the-f-paper/templates/slides/basic.md +31 -0
  21. package/package.json +12 -4
  22. package/vendors/claude/.claude-plugin/plugin.json +12 -0
  23. package/vendors/claude/agents/wtfp/citation-expert.md +45 -0
  24. package/vendors/claude/agents/wtfp/citation-formatter.md +42 -0
  25. package/vendors/claude/agents/wtfp/citation-retriever.md +31 -0
  26. package/{commands → vendors/claude/commands}/wtfp/analyze-bib.md +41 -52
  27. package/{commands → vendors/claude/commands}/wtfp/check-refs.md +11 -9
  28. package/vendors/claude/commands/wtfp/create-poster.md +42 -0
  29. package/vendors/claude/commands/wtfp/create-slides.md +41 -0
  30. package/{commands → vendors/claude/commands}/wtfp/help.md +3 -0
  31. package/{commands → vendors/claude/commands}/wtfp/new-paper.md +42 -81
  32. package/{commands → vendors/claude/commands}/wtfp/research-gap.md +3 -4
  33. package/vendors/claude/mcp/research-server/package.json +13 -0
  34. package/vendors/claude/mcp/research-server/src/index.js +133 -0
  35. package/vendors/claude/skills/wtfp/echarts/SKILL.md +47 -0
  36. package/vendors/claude/skills/wtfp/marp/SKILL.md +49 -0
  37. /package/{write-the-f-paper → core/write-the-f-paper}/references/checkpoints.md +0 -0
  38. /package/{write-the-f-paper → core/write-the-f-paper}/references/continuation-format.md +0 -0
  39. /package/{write-the-f-paper → core/write-the-f-paper}/references/git-integration.md +0 -0
  40. /package/{write-the-f-paper → core/write-the-f-paper}/references/github-contrib.md +0 -0
  41. /package/{write-the-f-paper → core/write-the-f-paper}/references/imrad-structure.md +0 -0
  42. /package/{write-the-f-paper → core/write-the-f-paper}/references/length-estimation.md +0 -0
  43. /package/{write-the-f-paper → core/write-the-f-paper}/references/plan-format.md +0 -0
  44. /package/{write-the-f-paper → core/write-the-f-paper}/references/principles.md +0 -0
  45. /package/{write-the-f-paper → core/write-the-f-paper}/references/questioning.md +0 -0
  46. /package/{write-the-f-paper → core/write-the-f-paper}/references/research-pitfalls.md +0 -0
  47. /package/{write-the-f-paper → core/write-the-f-paper}/templates/config.json +0 -0
  48. /package/{write-the-f-paper → core/write-the-f-paper}/templates/context.md +0 -0
  49. /package/{write-the-f-paper → core/write-the-f-paper}/templates/continue-here.md +0 -0
  50. /package/{write-the-f-paper → core/write-the-f-paper}/templates/data-inventory.md +0 -0
  51. /package/{write-the-f-paper → core/write-the-f-paper}/templates/discovery.md +0 -0
  52. /package/{write-the-f-paper → core/write-the-f-paper}/templates/issues.md +0 -0
  53. /package/{write-the-f-paper → core/write-the-f-paper}/templates/manuscript.md +0 -0
  54. /package/{write-the-f-paper → core/write-the-f-paper}/templates/narrative-arc.md +0 -0
  55. /package/{write-the-f-paper → core/write-the-f-paper}/templates/outline.md +0 -0
  56. /package/{write-the-f-paper → core/write-the-f-paper}/templates/phase-prompt.md +0 -0
  57. /package/{write-the-f-paper → core/write-the-f-paper}/templates/prior-drafts.md +0 -0
  58. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/argument-structure.md +0 -0
  59. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/bibliography.md +0 -0
  60. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/gaps.md +0 -0
  61. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/imrad.md +0 -0
  62. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/peer-review.md +0 -0
  63. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/style-guide.md +0 -0
  64. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/tools.md +0 -0
  65. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project.md +0 -0
  66. /package/{write-the-f-paper → core/write-the-f-paper}/templates/research.md +0 -0
  67. /package/{write-the-f-paper → core/write-the-f-paper}/templates/review-comments.md +0 -0
  68. /package/{write-the-f-paper → core/write-the-f-paper}/templates/revision-issues.md +0 -0
  69. /package/{write-the-f-paper → core/write-the-f-paper}/templates/roadmap.md +0 -0
  70. /package/{write-the-f-paper → core/write-the-f-paper}/templates/section-archive.md +0 -0
  71. /package/{write-the-f-paper → core/write-the-f-paper}/templates/section-context.md +0 -0
  72. /package/{write-the-f-paper → core/write-the-f-paper}/templates/section.md +0 -0
  73. /package/{write-the-f-paper → core/write-the-f-paper}/templates/state.md +0 -0
  74. /package/{write-the-f-paper → core/write-the-f-paper}/templates/summary.md +0 -0
  75. /package/{write-the-f-paper → core/write-the-f-paper}/venues/acm-cs.yaml +0 -0
  76. /package/{write-the-f-paper → core/write-the-f-paper}/venues/arxiv-ml.yaml +0 -0
  77. /package/{write-the-f-paper → core/write-the-f-paper}/venues/ieee-cs.yaml +0 -0
  78. /package/{write-the-f-paper → core/write-the-f-paper}/venues/nature.yaml +0 -0
  79. /package/{write-the-f-paper → core/write-the-f-paper}/venues/thesis-chapter.yaml +0 -0
  80. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/create-outline.md +0 -0
  81. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/create-outline.wcn.md +0 -0
  82. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/discuss-section.md +0 -0
  83. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/discuss-section.wcn.md +0 -0
  84. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/execute-section.md +0 -0
  85. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/execute-section.wcn.md +0 -0
  86. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/list-assumptions.md +0 -0
  87. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/list-assumptions.wcn.md +0 -0
  88. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/lit-review-phase.md +0 -0
  89. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/lit-review-phase.wcn.md +0 -0
  90. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/map-project.md +0 -0
  91. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/map-project.wcn.md +0 -0
  92. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/plan-section.md +0 -0
  93. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/plan-section.wcn.md +0 -0
  94. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/research-gap.md +0 -0
  95. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/research-gap.wcn.md +0 -0
  96. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/resume-paper.md +0 -0
  97. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/resume-paper.wcn.md +0 -0
  98. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/review-section.md +0 -0
  99. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/review-section.wcn.md +0 -0
  100. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/submit-draft.md +0 -0
  101. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/submit-draft.wcn.md +0 -0
  102. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/transition.md +0 -0
  103. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/transition.wcn.md +0 -0
  104. /package/{commands → vendors/claude/commands}/wtfp/contribute.md +0 -0
  105. /package/{commands → vendors/claude/commands}/wtfp/create-outline.md +0 -0
  106. /package/{commands → vendors/claude/commands}/wtfp/discuss-section.md +0 -0
  107. /package/{commands → vendors/claude/commands}/wtfp/export-latex.md +0 -0
  108. /package/{commands → vendors/claude/commands}/wtfp/insert-section.md +0 -0
  109. /package/{commands → vendors/claude/commands}/wtfp/list-assumptions.md +0 -0
  110. /package/{commands → vendors/claude/commands}/wtfp/map-project.md +0 -0
  111. /package/{commands → vendors/claude/commands}/wtfp/pause-writing.md +0 -0
  112. /package/{commands → vendors/claude/commands}/wtfp/plan-revision.md +0 -0
  113. /package/{commands → vendors/claude/commands}/wtfp/plan-section.md +0 -0
  114. /package/{commands → vendors/claude/commands}/wtfp/polish-prose.md +0 -0
  115. /package/{commands → vendors/claude/commands}/wtfp/progress.md +0 -0
  116. /package/{commands → vendors/claude/commands}/wtfp/remove-section.md +0 -0
  117. /package/{commands → vendors/claude/commands}/wtfp/report-bug.md +0 -0
  118. /package/{commands → vendors/claude/commands}/wtfp/request-feature.md +0 -0
  119. /package/{commands → vendors/claude/commands}/wtfp/resume-writing.md +0 -0
  120. /package/{commands → vendors/claude/commands}/wtfp/review-section.md +0 -0
  121. /package/{commands → vendors/claude/commands}/wtfp/submit-milestone.md +0 -0
  122. /package/{commands → vendors/claude/commands}/wtfp/write-section.md +0 -0
package/README.md CHANGED
@@ -2,14 +2,18 @@
2
2
 
3
3
  # WTF-P
4
4
 
5
+ **Write The Freaking...**
6
+ - **P**aper
7
+ - **P**roposal
8
+ - **P**resentation
9
+ - **P**oster
10
+
5
11
  **Context engineering for academic writing with Claude Code.**
6
12
 
7
13
  ```bash
8
14
  npx wtf-p
9
15
  ```
10
16
 
11
- *Write The Freaking Paper. Finally.*
12
-
13
17
  </div>
14
18
 
15
19
  ---
@@ -209,6 +213,24 @@ The mapping phase analyzes:
209
213
 
210
214
  ---
211
215
 
216
+ ## Citation Expert (v0.4.0)
217
+
218
+ WTF-P includes a specialized tiered pipeline for bibliography management.
219
+
220
+ ```bash
221
+ /wtfp:analyze-bib # Deep analysis of impact (seminal vs rising)
222
+ /wtfp:research-gap # Intent-aware search (seminal/recent)
223
+ /wtfp:check-refs # Auto-suggest missing citations
224
+ ```
225
+
226
+ Capabilities:
227
+ - **Tiered Search:** Integrated Semantic Scholar, SerpAPI (Google Scholar), and CrossRef.
228
+ - **Impact Ranking:** Automatically scores papers by citations, velocity, and venue prestige.
229
+ - **Deduplication:** universal anchoring via DOI and Scholar Cluster IDs.
230
+ - **Provenance:** Entries track their metadata source and verification status.
231
+
232
+ ---
233
+
212
234
  ## Command Reference
213
235
 
214
236
  ### Setup
@@ -0,0 +1,100 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const { getClaudeDir, normalizePath, getPathLabel, detectInstallation } = require('../lib/utils');
4
+
5
+ async function runDoctor(options) {
6
+ const { out, explicitConfigDir } = options;
7
+ const c = out.colors;
8
+
9
+ out.log(` ${c.yellow('WTF-P Installation Doctor')}\n`);
10
+
11
+ const issues = [];
12
+ const checks = [];
13
+
14
+ // Check 1: Node.js version
15
+ const nodeVersion = process.version;
16
+ const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0], 10);
17
+ if (nodeMajor < 16) {
18
+ issues.push(`Node.js ${nodeVersion} is below minimum (16.7.0)`);
19
+ checks.push({ name: 'Node.js version', status: 'fail', detail: nodeVersion });
20
+ } else {
21
+ checks.push({ name: 'Node.js version', status: 'pass', detail: nodeVersion });
22
+ }
23
+
24
+ // Check 2: Claude directory exists
25
+ const globalDir = getClaudeDir(explicitConfigDir, true);
26
+ if (fs.existsSync(globalDir)) {
27
+ checks.push({ name: 'Claude config directory', status: 'pass', detail: getPathLabel(globalDir, true) });
28
+ } else {
29
+ checks.push({ name: 'Claude config directory', status: 'warn', detail: 'Does not exist (will be created on install)' });
30
+ }
31
+
32
+ // Check 3: Write permissions
33
+ const testDir = globalDir.replace(/\.claude$/, '.claude-test-' + Date.now());
34
+ try {
35
+ fs.mkdirSync(testDir, { recursive: true });
36
+ fs.writeFileSync(path.join(testDir, 'test'), 'test');
37
+ fs.rmSync(testDir, { recursive: true });
38
+ checks.push({ name: 'Write permissions', status: 'pass', detail: 'Can write to config directory' });
39
+ } catch (err) {
40
+ issues.push(`Cannot write to ${getPathLabel(globalDir, true)}: ${err.message}`);
41
+ checks.push({ name: 'Write permissions', status: 'fail', detail: err.message });
42
+ }
43
+
44
+ // Check 4: Installation state
45
+ const detection = detectInstallation(globalDir);
46
+ if (detection.hasCommands || detection.hasWorkflows || detection.hasSkills) {
47
+ if (detection.partial) {
48
+ issues.push('Partial installation detected - some files are missing');
49
+ checks.push({ name: 'Installation integrity', status: 'warn', detail: 'Partial install' });
50
+ } else if (detection.corrupt) {
51
+ issues.push('Version file is corrupt - reinstall recommended');
52
+ checks.push({ name: 'Installation integrity', status: 'warn', detail: 'Corrupt version file' });
53
+ } else {
54
+ checks.push({ name: 'Installation integrity', status: 'pass', detail: `v${detection.version}` });
55
+ }
56
+ } else {
57
+ checks.push({ name: 'Installation integrity', status: 'info', detail: 'Not installed' });
58
+ }
59
+
60
+ // Check 5: CLAUDE_CONFIG_DIR env var
61
+ const configDirEnv = process.env.CLAUDE_CONFIG_DIR;
62
+ if (configDirEnv) {
63
+ const expanded = normalizePath(configDirEnv);
64
+ if (fs.existsSync(expanded)) {
65
+ checks.push({ name: 'CLAUDE_CONFIG_DIR', status: 'pass', detail: expanded });
66
+ } else {
67
+ issues.push(`CLAUDE_CONFIG_DIR points to non-existent path: ${configDirEnv}`);
68
+ checks.push({ name: 'CLAUDE_CONFIG_DIR', status: 'warn', detail: `${configDirEnv} (does not exist)` });
69
+ }
70
+ } else {
71
+ checks.push({ name: 'CLAUDE_CONFIG_DIR', status: 'info', detail: 'Not set (using default)' });
72
+ }
73
+
74
+ // Output results
75
+ for (const check of checks) {
76
+ let icon, color;
77
+ switch (check.status) {
78
+ case 'pass': icon = '✓'; color = c.green; break;
79
+ case 'fail': icon = '✗'; color = c.red; break;
80
+ case 'warn': icon = '⚠'; color = c.yellow; break;
81
+ default: icon = 'ℹ'; color = c.cyan;
82
+ }
83
+ out.log(` ${color(icon)} ${check.name}: ${c.dim(check.detail)}`);
84
+ }
85
+
86
+ out.log('');
87
+
88
+ if (issues.length > 0) {
89
+ out.log(` ${c.yellow('Issues found:')}`);
90
+ for (const issue of issues) {
91
+ out.log(` ${c.yellow('•')} ${issue}`);
92
+ }
93
+ out.log('');
94
+ out.log(` ${c.dim('Run')} ${c.cyan('npx wtf-p --global --force')} ${c.dim('to reinstall')}\n`);
95
+ } else {
96
+ out.log(` ${c.green('No issues found!')}\n`);
97
+ }
98
+ }
99
+
100
+ module.exports = runDoctor;
@@ -0,0 +1,231 @@
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
+ const allowedIds = [onlyInstall];
191
+ // Legacy mapping: 'commands' includes 'skills'
192
+ if (onlyInstall === 'commands') allowedIds.push('skills');
193
+
194
+ if (!allowedIds.includes(component.id)) {
195
+ return;
196
+ }
197
+ }
198
+
199
+ // DEBUG LOG
200
+ // console.log(`Collecting: ${component.id} from ${component.src}`);
201
+
202
+ collectComponentFiles(component, targetDir, allFiles);
203
+ });
204
+
205
+ // DEBUG
206
+ // console.log(`Total files found: ${allFiles.length}`);
207
+
208
+ // Install with conflict resolution
209
+ const stats = await installWithConflictResolution(allFiles, pathPrefix, targetDir, options);
210
+
211
+ // Write version tracking file
212
+ writeVersionFile(targetDir, pkg.version, allFiles);
213
+
214
+ // Summary
215
+ if (!hasQuiet) {
216
+ out.log(`
217
+ ${c.green('Done!')} Installed: ${stats.installed}, Skipped: ${stats.skipped}, Backed up: ${stats.backed}
218
+
219
+ Run ${c.cyan('/wtfp:help')} in Claude Code to get started.
220
+ `);
221
+
222
+ if (showExplanations && !isUpdate) {
223
+ out.log(` ${c.yellow('Quick Start:')}`);
224
+ out.log(` ${c.cyan('/wtfp:new-paper')} Start a new academic paper`);
225
+ out.log(` ${c.cyan('/wtfp:progress')} Check your writing status`);
226
+ out.log(` ${c.cyan('/wtfp:help')} Show all commands\n`);
227
+ }
228
+ }
229
+ }
230
+
231
+ 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;