wtf-p 0.3.0 → 0.5.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 (183) hide show
  1. package/README.md +146 -208
  2. package/bin/commands/doctor.js +88 -2
  3. package/bin/commands/install-logic.js +127 -36
  4. package/bin/commands/status.js +22 -4
  5. package/bin/commands/update.js +5 -4
  6. package/bin/install.js +66 -26
  7. package/bin/lib/analyze-impact.js +105 -0
  8. package/bin/lib/bib-format.js +161 -0
  9. package/bin/lib/bib-index.js +104 -0
  10. package/bin/lib/checkpoint.js +183 -0
  11. package/bin/lib/citation-fetcher.js +299 -0
  12. package/bin/lib/citation-ranker.js +133 -0
  13. package/bin/lib/context-primer.js +214 -0
  14. package/bin/lib/manifest.js +80 -2
  15. package/bin/lib/scholar-lookup.js +188 -0
  16. package/bin/lib/semantic-scholar.js +184 -0
  17. package/bin/lib/utils.js +22 -1
  18. package/bin/uninstall.js +270 -111
  19. package/core/write-the-f-paper/references/agent-model-matrix.md +62 -0
  20. package/core/write-the-f-paper/references/checkpoints.md +160 -9
  21. package/core/write-the-f-paper/references/context-fidelity.md +153 -0
  22. package/core/write-the-f-paper/references/deviation-rules.md +150 -0
  23. package/core/write-the-f-paper/references/git-integration.md +80 -0
  24. package/core/write-the-f-paper/references/orchestrator-pattern.md +167 -0
  25. package/core/write-the-f-paper/references/planning-config.md +135 -0
  26. package/core/write-the-f-paper/references/ui-brand.md +186 -0
  27. package/core/write-the-f-paper/templates/UAT.md +80 -0
  28. package/core/write-the-f-paper/templates/base-prefs.yaml +42 -0
  29. package/core/write-the-f-paper/templates/config.json +31 -5
  30. package/core/write-the-f-paper/workflows/create-outline.wcn.md +1 -1
  31. package/core/write-the-f-paper/workflows/execute-outline.md +163 -0
  32. package/core/write-the-f-paper/workflows/execute-outline.wcn.md +73 -0
  33. package/core/write-the-f-paper/workflows/execute-section.wcn.md +141 -169
  34. package/core/write-the-f-paper/workflows/plan-section.wcn.md +96 -103
  35. package/core/write-the-f-paper/workflows/research-gap.md +27 -3
  36. package/core/write-the-f-paper/workflows/research-gap.wcn.md +18 -1
  37. package/core/write-the-f-paper/workflows/verify-work.md +251 -0
  38. package/core/write-the-f-paper/workflows/verify-work.wcn.md +74 -0
  39. package/package.json +19 -7
  40. package/vendors/claude/.claude-plugin/plugin.json +2 -2
  41. package/vendors/claude/agents/wtfp/argument-verifier.md +189 -0
  42. package/vendors/claude/agents/wtfp/citation-expert.md +45 -0
  43. package/vendors/claude/agents/wtfp/citation-formatter.md +42 -0
  44. package/vendors/claude/agents/wtfp/coherence-checker.md +167 -0
  45. package/vendors/claude/agents/wtfp/outliner.md +259 -0
  46. package/vendors/claude/agents/wtfp/plan-checker.md +249 -0
  47. package/vendors/claude/agents/wtfp/prose-polisher.md +166 -0
  48. package/vendors/claude/agents/wtfp/research-synthesizer.md +215 -0
  49. package/vendors/claude/agents/wtfp/section-planner.md +364 -0
  50. package/vendors/claude/agents/wtfp/section-reviewer.md +182 -0
  51. package/vendors/claude/agents/wtfp/section-writer.md +312 -0
  52. package/vendors/claude/commands/wtfp/add-todo.md +108 -0
  53. package/vendors/claude/commands/wtfp/analyze-bib.md +60 -249
  54. package/vendors/claude/commands/wtfp/audit-milestone.md +286 -0
  55. package/vendors/claude/commands/wtfp/check-refs.md +60 -131
  56. package/vendors/claude/commands/wtfp/check-todos.md +152 -0
  57. package/vendors/claude/commands/wtfp/checkpoint.md +105 -0
  58. package/vendors/claude/commands/wtfp/contribute.md +1 -1
  59. package/vendors/claude/commands/wtfp/create-outline.md +109 -195
  60. package/vendors/claude/commands/wtfp/create-poster.md +1 -1
  61. package/vendors/claude/commands/wtfp/create-slides.md +1 -1
  62. package/vendors/claude/commands/wtfp/discuss-section.md +1 -1
  63. package/vendors/claude/commands/wtfp/execute-outline.md +257 -0
  64. package/vendors/claude/commands/wtfp/export-latex.md +1 -1
  65. package/vendors/claude/commands/wtfp/help.md +174 -280
  66. package/vendors/claude/commands/wtfp/insert-section.md +1 -1
  67. package/vendors/claude/commands/wtfp/list-assumptions.md +1 -1
  68. package/vendors/claude/commands/wtfp/map-project.md +35 -124
  69. package/vendors/claude/commands/wtfp/new-paper.md +88 -362
  70. package/vendors/claude/commands/wtfp/pause-writing.md +1 -1
  71. package/vendors/claude/commands/wtfp/plan-milestone-gaps.md +255 -0
  72. package/vendors/claude/commands/wtfp/plan-revision.md +1 -1
  73. package/vendors/claude/commands/wtfp/plan-section.md +165 -190
  74. package/vendors/claude/commands/wtfp/polish-prose.md +66 -118
  75. package/vendors/claude/commands/wtfp/progress.md +82 -254
  76. package/vendors/claude/commands/wtfp/quick.md +107 -0
  77. package/vendors/claude/commands/wtfp/remove-section.md +1 -1
  78. package/vendors/claude/commands/wtfp/report-bug.md +1 -1
  79. package/vendors/claude/commands/wtfp/request-feature.md +1 -1
  80. package/vendors/claude/commands/wtfp/research-gap.md +138 -151
  81. package/vendors/claude/commands/wtfp/resume-writing.md +1 -1
  82. package/vendors/claude/commands/wtfp/review-section.md +55 -333
  83. package/vendors/claude/commands/wtfp/settings.md +197 -0
  84. package/vendors/claude/commands/wtfp/submit-milestone.md +248 -99
  85. package/vendors/claude/commands/wtfp/update.md +112 -0
  86. package/vendors/claude/commands/wtfp/verify-work.md +254 -0
  87. package/vendors/claude/commands/wtfp/write-section.md +144 -180
  88. package/vendors/claude/mcp/research-server/package.json +13 -0
  89. package/vendors/claude/mcp/research-server/src/index.js +133 -0
  90. package/vendors/gemini/agents/wtfp/argument-verifier.md +184 -0
  91. package/vendors/gemini/agents/wtfp/citation-expert.md +41 -0
  92. package/vendors/gemini/agents/wtfp/citation-formatter.md +38 -0
  93. package/vendors/gemini/agents/wtfp/coherence-checker.md +162 -0
  94. package/vendors/gemini/agents/wtfp/outliner.md +252 -0
  95. package/vendors/gemini/agents/wtfp/plan-checker.md +244 -0
  96. package/vendors/gemini/agents/wtfp/prose-polisher.md +158 -0
  97. package/vendors/gemini/agents/wtfp/research-synthesizer.md +205 -0
  98. package/vendors/gemini/agents/wtfp/section-planner.md +355 -0
  99. package/vendors/gemini/agents/wtfp/section-reviewer.md +175 -0
  100. package/vendors/gemini/agents/wtfp/section-writer.md +304 -0
  101. package/vendors/gemini/commands/wtfp/add-todo.toml +102 -0
  102. package/vendors/gemini/commands/wtfp/analyze-bib.toml +126 -0
  103. package/vendors/gemini/commands/wtfp/audit-milestone.toml +278 -0
  104. package/vendors/gemini/commands/wtfp/check-refs.toml +123 -0
  105. package/vendors/gemini/commands/wtfp/check-todos.toml +145 -0
  106. package/vendors/gemini/commands/wtfp/checkpoint.toml +96 -0
  107. package/vendors/gemini/commands/wtfp/contribute.toml +269 -0
  108. package/vendors/gemini/commands/wtfp/create-outline.toml +180 -0
  109. package/vendors/gemini/commands/wtfp/create-poster.toml +36 -0
  110. package/vendors/gemini/commands/wtfp/create-slides.toml +35 -0
  111. package/vendors/gemini/commands/wtfp/discuss-section.toml +62 -0
  112. package/vendors/gemini/commands/wtfp/execute-outline.toml +247 -0
  113. package/vendors/gemini/commands/wtfp/export-latex.toml +209 -0
  114. package/vendors/gemini/commands/wtfp/help.toml +184 -0
  115. package/vendors/gemini/commands/wtfp/insert-section.toml +123 -0
  116. package/vendors/gemini/commands/wtfp/list-assumptions.toml +104 -0
  117. package/vendors/gemini/commands/wtfp/map-project.toml +99 -0
  118. package/vendors/gemini/commands/wtfp/new-paper.toml +160 -0
  119. package/vendors/gemini/commands/wtfp/pause-writing.toml +140 -0
  120. package/vendors/gemini/commands/wtfp/plan-milestone-gaps.toml +249 -0
  121. package/vendors/gemini/commands/wtfp/plan-revision.toml +171 -0
  122. package/vendors/gemini/commands/wtfp/plan-section.toml +212 -0
  123. package/vendors/gemini/commands/wtfp/polish-prose.toml +114 -0
  124. package/vendors/gemini/commands/wtfp/progress.toml +145 -0
  125. package/vendors/gemini/commands/wtfp/quick.toml +96 -0
  126. package/vendors/gemini/commands/wtfp/remove-section.toml +140 -0
  127. package/vendors/gemini/commands/wtfp/report-bug.toml +126 -0
  128. package/vendors/gemini/commands/wtfp/request-feature.toml +155 -0
  129. package/vendors/gemini/commands/wtfp/research-gap.toml +214 -0
  130. package/vendors/gemini/commands/wtfp/resume-writing.toml +136 -0
  131. package/vendors/gemini/commands/wtfp/review-section.toml +121 -0
  132. package/vendors/gemini/commands/wtfp/settings.toml +191 -0
  133. package/vendors/gemini/commands/wtfp/submit-milestone.toml +351 -0
  134. package/vendors/gemini/commands/wtfp/update.toml +107 -0
  135. package/vendors/gemini/commands/wtfp/verify-work.toml +245 -0
  136. package/vendors/gemini/commands/wtfp/write-section.toml +228 -0
  137. package/vendors/opencode/agents/wtfp/argument-verifier.md +184 -0
  138. package/vendors/opencode/agents/wtfp/citation-expert.md +41 -0
  139. package/vendors/opencode/agents/wtfp/citation-formatter.md +38 -0
  140. package/vendors/opencode/agents/wtfp/coherence-checker.md +162 -0
  141. package/vendors/opencode/agents/wtfp/outliner.md +252 -0
  142. package/vendors/opencode/agents/wtfp/plan-checker.md +244 -0
  143. package/vendors/opencode/agents/wtfp/prose-polisher.md +158 -0
  144. package/vendors/opencode/agents/wtfp/research-synthesizer.md +205 -0
  145. package/vendors/opencode/agents/wtfp/section-planner.md +355 -0
  146. package/vendors/opencode/agents/wtfp/section-reviewer.md +175 -0
  147. package/vendors/opencode/agents/wtfp/section-writer.md +304 -0
  148. package/vendors/opencode/commands/wtfp/add-todo.md +103 -0
  149. package/vendors/opencode/commands/wtfp/analyze-bib.md +127 -0
  150. package/vendors/opencode/commands/wtfp/audit-milestone.md +279 -0
  151. package/vendors/opencode/commands/wtfp/check-refs.md +124 -0
  152. package/vendors/opencode/commands/wtfp/check-todos.md +146 -0
  153. package/vendors/opencode/commands/wtfp/checkpoint.md +97 -0
  154. package/vendors/opencode/commands/wtfp/contribute.md +266 -0
  155. package/vendors/opencode/commands/wtfp/create-outline.md +181 -0
  156. package/vendors/opencode/commands/wtfp/create-poster.md +37 -0
  157. package/vendors/opencode/commands/wtfp/create-slides.md +36 -0
  158. package/vendors/opencode/commands/wtfp/discuss-section.md +63 -0
  159. package/vendors/opencode/commands/wtfp/execute-outline.md +248 -0
  160. package/vendors/opencode/commands/wtfp/export-latex.md +210 -0
  161. package/vendors/opencode/commands/wtfp/help.md +185 -0
  162. package/vendors/opencode/commands/wtfp/insert-section.md +124 -0
  163. package/vendors/opencode/commands/wtfp/list-assumptions.md +105 -0
  164. package/vendors/opencode/commands/wtfp/map-project.md +100 -0
  165. package/vendors/opencode/commands/wtfp/new-paper.md +161 -0
  166. package/vendors/opencode/commands/wtfp/pause-writing.md +141 -0
  167. package/vendors/opencode/commands/wtfp/plan-milestone-gaps.md +250 -0
  168. package/vendors/opencode/commands/wtfp/plan-revision.md +174 -0
  169. package/vendors/opencode/commands/wtfp/plan-section.md +214 -0
  170. package/vendors/opencode/commands/wtfp/polish-prose.md +115 -0
  171. package/vendors/opencode/commands/wtfp/progress.md +146 -0
  172. package/vendors/opencode/commands/wtfp/quick.md +97 -0
  173. package/vendors/opencode/commands/wtfp/remove-section.md +141 -0
  174. package/vendors/opencode/commands/wtfp/report-bug.md +127 -0
  175. package/vendors/opencode/commands/wtfp/request-feature.md +156 -0
  176. package/vendors/opencode/commands/wtfp/research-gap.md +215 -0
  177. package/vendors/opencode/commands/wtfp/resume-writing.md +137 -0
  178. package/vendors/opencode/commands/wtfp/review-section.md +123 -0
  179. package/vendors/opencode/commands/wtfp/settings.md +192 -0
  180. package/vendors/opencode/commands/wtfp/submit-milestone.md +352 -0
  181. package/vendors/opencode/commands/wtfp/update.md +108 -0
  182. package/vendors/opencode/commands/wtfp/verify-work.md +246 -0
  183. package/vendors/opencode/commands/wtfp/write-section.md +228 -0
@@ -2,16 +2,43 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const os = require('os');
4
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
5
+ const {
6
+ isValidPath,
7
+ expandTilde,
8
+ getPathLabel,
9
+ getBackupPath,
10
+ writeVersionFile,
11
+ detectInstallation,
12
+ createRL,
13
+ prompt
13
14
  } = require('../lib/utils');
14
15
 
16
+ /**
17
+ * Get vendor-specific config directory
18
+ */
19
+ function getVendorDir(runtime, explicitConfigDir) {
20
+ // Handle 'claude-local' special case
21
+ if (runtime === 'claude-local') {
22
+ return path.join(process.cwd(), '.claude');
23
+ }
24
+
25
+ const vendorConfig = MANIFEST[runtime];
26
+ if (!vendorConfig) {
27
+ throw new Error(`Unknown runtime: ${runtime}`);
28
+ }
29
+
30
+ if (explicitConfigDir) {
31
+ return expandTilde(explicitConfigDir);
32
+ }
33
+
34
+ const envDir = process.env[vendorConfig.configDirEnv];
35
+ if (envDir) {
36
+ return expandTilde(envDir);
37
+ }
38
+
39
+ return path.join(os.homedir(), vendorConfig.defaultDir);
40
+ }
41
+
15
42
  /**
16
43
  * Process file content with path replacement
17
44
  */
@@ -68,15 +95,15 @@ async function installWithConflictResolution(files, pathPrefix, targetDir, optio
68
95
 
69
96
  if (existingFiles.length > 0 && !hasForce && !hasBackupAll && isInteractive) {
70
97
  const relPath = getPathLabel(targetDir, true);
71
- out.log(` ${c.yellow(`Found ${existingFiles.length} existing WTF-P file(s) in ${relPath}`)}
98
+ out.log(` ${c.yellow(`Found ${existingFiles.length} existing file(s) in ${relPath}`)}
72
99
  `);
73
100
 
74
101
  if (showExplanations) {
75
102
  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`);
103
+ out.log(` ${c.dim('[o]verwrite Replace with new version')}`);
104
+ out.log(` ${c.dim('[s]kip Keep your existing file')}`);
105
+ out.log(` ${c.dim('[b]ackup Save existing, then install new')}`);
106
+ out.log(` ${c.dim('[a]ll Apply same choice to all remaining')}\n`);
80
107
  }
81
108
  }
82
109
 
@@ -132,6 +159,18 @@ async function installWithConflictResolution(files, pathPrefix, targetDir, optio
132
159
  stats.backed++;
133
160
  }
134
161
 
162
+ // Safety: never overwrite a non-WTF-P plugin.json
163
+ if (file.name === 'plugin.json' && file.componentId === 'plugin' && exists) {
164
+ try {
165
+ const existing = JSON.parse(fs.readFileSync(file.dest, 'utf8'));
166
+ if (existing.name && existing.name !== 'wtf-p' && existing.name !== 'write-the-f-paper') {
167
+ out.verbose(` ${c.yellow('!')} ${c.dim(relDest)} belongs to another plugin — skipped`);
168
+ stats.skipped++;
169
+ continue;
170
+ }
171
+ } catch { /* corrupt JSON — safe to overwrite */ }
172
+ }
173
+
135
174
  const content = processContent(file.src, pathPrefix);
136
175
  if (content !== null) {
137
176
  fs.writeFileSync(file.dest, content);
@@ -153,17 +192,27 @@ async function installWithConflictResolution(files, pathPrefix, targetDir, optio
153
192
 
154
193
  /**
155
194
  * Main install logic
195
+ * @param {string} runtime - 'claude' | 'gemini' | 'claude-local'
196
+ * @param {boolean} isUpdate - Whether this is an update operation
197
+ * @param {object} options - CLI options
198
+ * @param {object} pkg - Package.json contents
156
199
  */
157
- async function install(isGlobal, isUpdate, options, pkg) {
200
+ async function install(runtime, isUpdate, options, pkg) {
158
201
  const { out, explicitConfigDir, hasQuiet, onlyInstall, showExplanations } = options;
159
202
  const c = out.colors;
160
203
 
161
- // Use Claude vendor by default for now (can be passed as arg later)
162
- const vendorConfig = MANIFEST.claude;
163
-
204
+ // Handle 'claude-local' by mapping to 'claude' vendor config
205
+ const vendorKey = runtime === 'claude-local' ? 'claude' : runtime;
206
+ const vendorConfig = MANIFEST[vendorKey];
207
+
208
+ if (!vendorConfig) {
209
+ out.error(`Unknown runtime: ${runtime}`);
210
+ process.exit(1);
211
+ }
212
+
164
213
  // Resolve Target Directory using the Vendor Strategy
165
- // TODO: Use getVendorDir generic
166
- const targetDir = getClaudeDir(explicitConfigDir, isGlobal);
214
+ const targetDir = getVendorDir(runtime, explicitConfigDir);
215
+ const isGlobal = runtime !== 'claude-local';
167
216
  const locationLabel = getPathLabel(targetDir, isGlobal);
168
217
 
169
218
  // Validate path
@@ -174,7 +223,53 @@ async function install(isGlobal, isUpdate, options, pkg) {
174
223
 
175
224
  const pathPrefix = isGlobal
176
225
  ? (explicitConfigDir ? `${targetDir}/` : `~/${vendorConfig.defaultDir}/`)
177
- : `./${vendorConfig.defaultDir}/`;
226
+ : `./.claude/`;
227
+
228
+ // ---- Cross-installation conflict detection ----
229
+ // If installing locally, warn if a global install already exists (and vice versa).
230
+ // Claude Code loads commands from BOTH ~/.claude/ and ./.claude/, causing duplicates.
231
+ if (vendorKey === 'claude' && !explicitConfigDir) {
232
+ const globalDir = path.join(os.homedir(), '.claude');
233
+ const localDir = path.join(process.cwd(), '.claude');
234
+
235
+ if (runtime === 'claude-local') {
236
+ // Installing local — check if global exists
237
+ const globalInstall = detectInstallation(globalDir);
238
+ if (globalInstall.hasCommands) {
239
+ out.log(` ${c.yellow('⚠ WTF-P is already installed globally')} ${c.dim('(' + getPathLabel(globalDir, true) + ')')}`);
240
+ out.log(` Installing locally too will cause every command to appear twice.`);
241
+ out.log('');
242
+ out.log(` ${c.cyan('Options:')}`);
243
+ out.log(` • Use your global install as-is (recommended)`);
244
+ out.log(` • Remove global first: ${c.dim('npx wtf-p uninstall --global')}`);
245
+ out.log(` • Continue anyway (commands will be duplicated)`);
246
+ out.log('');
247
+
248
+ if (options.isInteractive && !options.hasForce) {
249
+ const rl = createRL();
250
+ const answer = await prompt(rl, ` Continue with local install? [y/N]: `);
251
+ rl.close();
252
+ if (answer !== 'y' && answer !== 'yes') {
253
+ out.log(`\n ${c.yellow('Aborted.')} Using global install.\n`);
254
+ return;
255
+ }
256
+ out.log('');
257
+ } else if (!options.hasForce) {
258
+ out.log(` ${c.yellow('Aborted.')} Use --force to install anyway.\n`);
259
+ return;
260
+ }
261
+ }
262
+ } else if (runtime === 'claude') {
263
+ // Installing global — check if local exists in cwd
264
+ const localInstall = detectInstallation(localDir);
265
+ if (localInstall.hasCommands && globalDir !== localDir) {
266
+ out.log(` ${c.yellow('⚠ WTF-P is also installed locally')} ${c.dim('(' + getPathLabel(localDir, false) + ')')}`);
267
+ out.log(` Commands may appear twice in this directory.`);
268
+ out.log(` To fix: ${c.dim('npx wtf-p uninstall --local')}`);
269
+ out.log('');
270
+ }
271
+ }
272
+ }
178
273
 
179
274
  if (!hasQuiet) {
180
275
  out.log(` Installing to ${c.cyan(locationLabel)}
@@ -187,16 +282,12 @@ async function install(isGlobal, isUpdate, options, pkg) {
187
282
  vendorConfig.components.forEach(component => {
188
283
  // Check --only filters
189
284
  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
- }
285
+ const allowedIds = [onlyInstall];
286
+ // Legacy mapping: 'commands' includes 'skills'
287
+ if (onlyInstall === 'commands') allowedIds.push('skills');
288
+
289
+ if (!allowedIds.includes(component.id)) {
290
+ return;
200
291
  }
201
292
  }
202
293
 
@@ -218,16 +309,16 @@ async function install(isGlobal, isUpdate, options, pkg) {
218
309
  // Summary
219
310
  if (!hasQuiet) {
220
311
  out.log(`
221
- ${c.green('Done!')} Installed: ${stats.installed}, Skipped: ${stats.skipped}, Backed up: ${stats.backed}
312
+ ${c.green('Done!')} ${c.dim(`[${vendorConfig.name}]`)} Installed: ${stats.installed}, Skipped: ${stats.skipped}, Backed up: ${stats.backed}
222
313
 
223
- Run ${c.cyan('/wtfp:help')} in Claude Code to get started.
314
+ Run ${c.cyan('/wtfp:help')} in ${vendorConfig.name} to get started.
224
315
  `);
225
316
 
226
317
  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`);
318
+ out.log(` ${c.yellow('Get started:')}`);
319
+ out.log(` ${c.cyan('/wtfp:new-paper')} Define your paper's vision and structure`);
320
+ out.log(` ${c.cyan('/wtfp:progress')} See where you are and what's next`);
321
+ out.log(` ${c.cyan('/wtfp:help')} Browse all available commands\n`);
231
322
  }
232
323
  }
233
324
  }
@@ -57,6 +57,7 @@ async function showStatus(options, pkg) {
57
57
  if (loc.hasCommands) components.push(`commands (${loc.commandFiles.length} files)`);
58
58
  if (loc.hasWorkflows) components.push(`workflows (${loc.workflowFiles.length} files)`);
59
59
  if (loc.hasSkills) components.push(`skills (${loc.skillFiles.length} files)`);
60
+ if (loc.hasAgents) components.push(`agents (${loc.agentFiles.length} files)`);
60
61
  out.log(` Installed: ${components.join(', ')}`);
61
62
 
62
63
  // Warnings
@@ -70,12 +71,29 @@ async function showStatus(options, pkg) {
70
71
  out.log('');
71
72
  }
72
73
 
73
- // Check for updates
74
+ // Check for dual-install conflict (global + local both present)
75
+ const bothInstalled = locations.filter(l =>
76
+ l.hasCommands || l.hasWorkflows || l.hasSkills
77
+ );
78
+ if (bothInstalled.length > 1) {
79
+ out.log(` ${c.red('⚠ Duplicate installation detected!')}`);
80
+ out.log(` WTF-P is installed in both locations. This causes every command to appear twice.`);
81
+ out.log(` Remove one to fix:`);
82
+ out.log(` ${c.cyan('npx wtf-p uninstall --local')} Remove local copy`);
83
+ out.log(` ${c.cyan('npx wtf-p uninstall --global')} Remove global copy\n`);
84
+ }
85
+
86
+ // Check for updates (only suggest if registry version is actually newer)
74
87
  try {
75
88
  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`);
89
+ const isNewer = (a, b) => {
90
+ const pa = a.split('.').map(Number), pb = b.split('.').map(Number);
91
+ for (let i = 0; i < 3; i++) { if ((pa[i] || 0) > (pb[i] || 0)) return true; if ((pa[i] || 0) < (pb[i] || 0)) return false; }
92
+ return false;
93
+ };
94
+ if (latest && isNewer(latest, pkg.version)) {
95
+ out.log(` ${c.yellow('Update available:')} ${pkg.version} -> ${c.green(latest)}`);
96
+ out.log(` Run: ${c.cyan('npx wtf-p update')}\n`);
79
97
  }
80
98
  } catch {
81
99
  // Ignore
@@ -25,8 +25,8 @@ async function runUpdate(options, pkg, installFunc) {
25
25
  }
26
26
 
27
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`);
28
+ out.log(` ${c.yellow('No WTF-P installation found.')}`);
29
+ out.log(` Install with: ${c.cyan('npx wtf-p')}\n`);
30
30
  return;
31
31
  }
32
32
 
@@ -43,8 +43,9 @@ async function runUpdate(options, pkg, installFunc) {
43
43
 
44
44
  out.log(`\n ${c.cyan('Updating...')}\n`);
45
45
 
46
- // Run install with backup
47
- await installFunc(isGlobal, true);
46
+ // Run install with backup (convert isGlobal to runtime string)
47
+ const runtime = isGlobal ? 'claude' : 'claude-local';
48
+ await installFunc(runtime, true, options, pkg);
48
49
  }
49
50
 
50
51
  module.exports = runUpdate;
package/bin/install.js CHANGED
@@ -22,7 +22,7 @@ const showList = require('./commands/list');
22
22
  const args = process.argv.slice(2);
23
23
 
24
24
  // Detect subcommand
25
- const subcommands = ['status', 'doctor', 'update'];
25
+ const subcommands = ['status', 'doctor', 'update', 'uninstall'];
26
26
  const subcommand = args.find(arg => !arg.startsWith('-') && subcommands.includes(arg));
27
27
  const subcommandIndex = args.indexOf(subcommand);
28
28
  if (subcommandIndex !== -1) {
@@ -32,6 +32,9 @@ if (subcommandIndex !== -1) {
32
32
  // Parse flags
33
33
  const hasGlobal = args.includes('--global') || args.includes('-g');
34
34
  const hasLocal = args.includes('--local') || args.includes('-l');
35
+ const hasGemini = args.includes('--gemini');
36
+ const hasOpenCode = args.includes('--opencode');
37
+ const hasAll = args.includes('--all');
35
38
  const hasForce = args.includes('--force') || args.includes('-f');
36
39
  const hasBackupAll = args.includes('--backup-all') || args.includes('-b');
37
40
  const hasHelp = args.includes('--help') || args.includes('-h');
@@ -90,7 +93,7 @@ ${out.colors.magenta('╚███╔███╔╝ ██║ ██║
90
93
  ${out.colors.magenta(' ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝')}
91
94
 
92
95
  ${out.colors.cyan('Write The F***ing Paper')} ${out.colors.dim(`v${pkg.version}`)}
93
- Context engineering for academic writing.
96
+ Academic writing commands for your AI coding assistant.
94
97
  `;
95
98
 
96
99
  // ============ Help Text ============
@@ -101,16 +104,20 @@ function showHelp() {
101
104
  console.log(` ${c.yellow('Usage:')} npx wtf-p [command] [options]
102
105
 
103
106
  ${c.yellow('Commands:')}
104
- ${c.cyan('status')} Show current installation state
105
- ${c.cyan('doctor')} Diagnose installation issues
106
- ${c.cyan('update')} Update existing installation
107
+ ${c.cyan('status')} Show installation status
108
+ ${c.cyan('doctor')} Check for installation problems
109
+ ${c.cyan('update')} Update to latest version
110
+ ${c.cyan('uninstall')} Remove WTF-P
107
111
 
108
112
  ${c.yellow('Install Options:')}
109
- ${c.cyan('-g, --global')} Install globally (to Claude config directory)
110
- ${c.cyan('-l, --local')} Install locally (to ./.claude in current directory)
111
- ${c.cyan('-c, --config-dir <path>')} Specify custom Claude config directory
112
- ${c.cyan('-f, --force')} Overwrite all existing files without prompting
113
- ${c.cyan('-b, --backup-all')} Backup all existing files before overwriting
113
+ ${c.cyan('-g, --global')} Install to your home directory (recommended)
114
+ ${c.cyan('-l, --local')} Install to current project only
115
+ ${c.cyan('--gemini')} Install for Gemini CLI
116
+ ${c.cyan('--opencode')} Install for OpenCode
117
+ ${c.cyan('--all')} Install for all supported tools
118
+ ${c.cyan('-c, --config-dir <path>')} Install to a custom directory
119
+ ${c.cyan('-f, --force')} Overwrite existing files without asking
120
+ ${c.cyan('-b, --backup-all')} Backup existing files before overwriting
114
121
  ${c.cyan('--only=<type>')} Install only: commands, workflows, or all
115
122
 
116
123
  ${c.yellow('Output Options:')}
@@ -121,18 +128,18 @@ function showHelp() {
121
128
  ${c.cyan('--verbose')} Show detailed progress
122
129
 
123
130
  ${c.yellow('Other:')}
124
- ${c.cyan('-v, --version')} Show version information
125
- ${c.cyan('-h, --help')} Show this help message
131
+ ${c.cyan('-v, --version')} Show version
132
+ ${c.cyan('-h, --help')} Show this help
126
133
 
127
134
  ${c.yellow('Examples:')}
128
- ${c.dim('# Interactive install with prompts')}
135
+ ${c.dim('# Install with interactive prompts')}
129
136
  npx wtf-p
130
137
 
131
- ${c.dim('# Quick global install')}
138
+ ${c.dim('# Quick global install (no prompts)')}
132
139
  npx wtf-p --global --advanced
133
140
 
134
- ${c.yellow('After installation:')}
135
- Run ${c.cyan('/wtfp:help')} in Claude Code to get started.
141
+ ${c.yellow('After installing:')}
142
+ Open your AI assistant and run ${c.cyan('/wtfp:help')} to see all commands.
136
143
  `);
137
144
  }
138
145
 
@@ -174,28 +181,61 @@ async function main() {
174
181
  return;
175
182
  }
176
183
 
184
+ if (subcommand === 'uninstall') {
185
+ // Delegate to uninstall script with remaining args
186
+ const { execFileSync } = require('child_process');
187
+ const uninstallArgs = process.argv.slice(2).filter(a => a !== 'uninstall');
188
+ try {
189
+ execFileSync(process.execPath, [path.join(__dirname, 'uninstall.js'), ...uninstallArgs], { stdio: 'inherit' });
190
+ } catch (e) {
191
+ process.exit(e.status || 1);
192
+ }
193
+ return;
194
+ }
195
+
177
196
  // Default: Install
178
197
  if (!hasQuiet) console.log(banner);
179
198
 
180
- if (hasGlobal) {
181
- await install(true, false, options, pkg);
199
+ if (hasAll) {
200
+ // Install for all supported runtimes
201
+ await install('claude', false, options, pkg);
202
+ await install('gemini', false, options, pkg);
203
+ await install('opencode', false, options, pkg);
204
+ } else if (hasGemini) {
205
+ await install('gemini', false, options, pkg);
206
+ } else if (hasOpenCode) {
207
+ await install('opencode', false, options, pkg);
208
+ } else if (hasGlobal) {
209
+ await install('claude', false, options, pkg);
182
210
  } else if (hasLocal) {
183
- await install(false, false, options, pkg);
211
+ await install('claude-local', false, options, pkg);
184
212
  } else if (options.isInteractive) {
185
213
  const rl = createRL();
186
- out.log(` ${out.colors.yellow('Where would you like to install?')}
214
+ out.log(` ${out.colors.yellow('Which tool do you use for AI-assisted coding?')}
187
215
  `);
188
- out.log(` ${out.colors.cyan('1)')} Global (~/.claude) - available in all projects`);
189
- out.log(` ${out.colors.cyan('2)')} Local (./.claude) - this project only
216
+ out.log(` ${out.colors.cyan('1)')} Claude Code ${out.colors.dim('(~/.claude)')}`);
217
+ out.log(` ${out.colors.cyan('2)')} Gemini CLI ${out.colors.dim('(~/.config/gemini)')}`);
218
+ out.log(` ${out.colors.cyan('3)')} OpenCode ${out.colors.dim('(~/.opencode)')}`);
219
+ out.log(` ${out.colors.cyan('4)')} All of the above
190
220
  `);
191
-
221
+
192
222
  const answer = await prompt(rl, ` Choice ${out.colors.dim('[1]')}: `);
193
223
  rl.close();
194
-
224
+
195
225
  const choice = answer || '1';
196
- await install(choice !== '2', false, options, pkg);
226
+ if (choice === '4') {
227
+ await install('claude', false, options, pkg);
228
+ await install('gemini', false, options, pkg);
229
+ await install('opencode', false, options, pkg);
230
+ } else if (choice === '3') {
231
+ await install('opencode', false, options, pkg);
232
+ } else if (choice === '2') {
233
+ await install('gemini', false, options, pkg);
234
+ } else {
235
+ await install('claude', false, options, pkg);
236
+ }
197
237
  } else {
198
- await install(true, false, options, pkg);
238
+ await install('claude', false, options, pkg);
199
239
  }
200
240
  }
201
241
 
@@ -0,0 +1,105 @@
1
+ const fs = require('fs');
2
+ const s2 = require('./semantic-scholar');
3
+ const ranker = require('./citation-ranker');
4
+ const bibIndex = require('./bib-index'); // Assuming this exists or I need to use the one in bin/lib if available
5
+
6
+ // Check if bib-index exists in bin/lib or I need to implement basic parsing
7
+ // The command says `node ~/.claude/bin/bib-index.js`.
8
+ // In this project structure, it is likely `bin/lib/bib-index.js`.
9
+
10
+ const BIB_INDEX_PATH = './bib-index.js';
11
+
12
+ /**
13
+ * Impact Analyzer
14
+ *
15
+ * Analyzes a BibTeX file or JSON index:
16
+ * 1. Fetches metrics for each paper (via S2)
17
+ * 2. Categorizes them (Seminal, Rising, Outdated)
18
+ * 3. Returns summary
19
+ */
20
+
21
+ async function analyze(filePath) {
22
+ // 1. Index/Parse
23
+ let entries = [];
24
+ try {
25
+ // We'll require bib-index locally if possible
26
+ const indexer = require(BIB_INDEX_PATH);
27
+ const json = indexer.index(fs.readFileSync(filePath, 'utf8'));
28
+ entries = JSON.parse(json);
29
+ } catch (e) {
30
+ // Fallback: simple regex parsing if bib-index lib isn't easily require-able
31
+ console.error("Could not use bib-index, falling back to simple parse", e);
32
+ const content = fs.readFileSync(filePath, 'utf8');
33
+ const regex = /@\w+\s*{\s*([^,]+),[\s\S]*?title\s*=\s*[{"'](.+?)[}"'][\s\S]*?year\s*=\s*[{"'](\d{4})[}"']/gi;
34
+ let match;
35
+ while ((match = regex.exec(content)) !== null) {
36
+ entries.push({ key: match[1], title: match[2], year: parseInt(match[3]) });
37
+ }
38
+ }
39
+
40
+ const results = {
41
+ seminal: [], // > 1000 citations
42
+ rising: [], // Velocity > 50/mo, < 1000 total
43
+ outdated: [], // > 10 years, < 50 citations
44
+ unknown: []
45
+ };
46
+
47
+ // Process in batches to avoid rate limits
48
+ const BATCH_SIZE = 5;
49
+ for (let i = 0; i < entries.length; i += BATCH_SIZE) {
50
+ const batch = entries.slice(i, i + BATCH_SIZE);
51
+ await Promise.all(batch.map(async (entry) => {
52
+ try {
53
+ // Search by title to get metrics (most reliable if DOI missing)
54
+ // Ideally we'd use DOI if we parsed it.
55
+ const papers = await s2.search(entry.title, { limit: 1 });
56
+ if (papers && papers.length > 0) {
57
+ const p = papers[0];
58
+ const velocity = ranker.calculateVelocity(p.citationCount, p.year);
59
+ const age = new Date().getFullYear() - (p.year || new Date().getFullYear());
60
+
61
+ const enriched = {
62
+ key: entry.key,
63
+ title: p.title,
64
+ year: p.year,
65
+ citations: p.citationCount,
66
+ velocity: velocity.toFixed(1)
67
+ };
68
+
69
+ if (p.citationCount > 1000) {
70
+ results.seminal.push(enriched);
71
+ } else if (velocity > 10 && p.citationCount < 1000) { // Threshold adjusted: 50/mo is very high. 10/mo is solid.
72
+ results.rising.push(enriched);
73
+ } else if (age > 15 && p.citationCount < 50) {
74
+ results.outdated.push({ ...enriched, concern: `${age} years old, low citations` });
75
+ } else {
76
+ results.unknown.push(enriched);
77
+ }
78
+ } else {
79
+ results.unknown.push({ key: entry.key, title: entry.title, error: "Not found" });
80
+ }
81
+ } catch (e) {
82
+ results.unknown.push({ key: entry.key, title: entry.title, error: e.message });
83
+ }
84
+ }));
85
+ // Small delay between batches
86
+ await new Promise(r => setTimeout(r, 1000));
87
+ }
88
+
89
+ // Sort
90
+ results.seminal.sort((a,b) => b.citations - a.citations);
91
+ results.rising.sort((a,b) => b.velocity - a.velocity);
92
+
93
+ return results;
94
+ }
95
+
96
+ if (require.main === module) {
97
+ const file = process.argv[2];
98
+ if (!file) {
99
+ console.error("Usage: node analyze-impact.js <bibfile>");
100
+ process.exit(1);
101
+ }
102
+ analyze(file).then(r => console.log(JSON.stringify(r, null, 2)));
103
+ }
104
+
105
+ module.exports = { analyze };