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.
- package/README.md +146 -208
- package/bin/commands/doctor.js +88 -2
- package/bin/commands/install-logic.js +127 -36
- package/bin/commands/status.js +22 -4
- package/bin/commands/update.js +5 -4
- package/bin/install.js +66 -26
- package/bin/lib/analyze-impact.js +105 -0
- package/bin/lib/bib-format.js +161 -0
- package/bin/lib/bib-index.js +104 -0
- package/bin/lib/checkpoint.js +183 -0
- package/bin/lib/citation-fetcher.js +299 -0
- package/bin/lib/citation-ranker.js +133 -0
- package/bin/lib/context-primer.js +214 -0
- package/bin/lib/manifest.js +80 -2
- package/bin/lib/scholar-lookup.js +188 -0
- package/bin/lib/semantic-scholar.js +184 -0
- package/bin/lib/utils.js +22 -1
- package/bin/uninstall.js +270 -111
- package/core/write-the-f-paper/references/agent-model-matrix.md +62 -0
- package/core/write-the-f-paper/references/checkpoints.md +160 -9
- package/core/write-the-f-paper/references/context-fidelity.md +153 -0
- package/core/write-the-f-paper/references/deviation-rules.md +150 -0
- package/core/write-the-f-paper/references/git-integration.md +80 -0
- package/core/write-the-f-paper/references/orchestrator-pattern.md +167 -0
- package/core/write-the-f-paper/references/planning-config.md +135 -0
- package/core/write-the-f-paper/references/ui-brand.md +186 -0
- package/core/write-the-f-paper/templates/UAT.md +80 -0
- package/core/write-the-f-paper/templates/base-prefs.yaml +42 -0
- package/core/write-the-f-paper/templates/config.json +31 -5
- package/core/write-the-f-paper/workflows/create-outline.wcn.md +1 -1
- package/core/write-the-f-paper/workflows/execute-outline.md +163 -0
- package/core/write-the-f-paper/workflows/execute-outline.wcn.md +73 -0
- package/core/write-the-f-paper/workflows/execute-section.wcn.md +141 -169
- package/core/write-the-f-paper/workflows/plan-section.wcn.md +96 -103
- package/core/write-the-f-paper/workflows/research-gap.md +27 -3
- package/core/write-the-f-paper/workflows/research-gap.wcn.md +18 -1
- package/core/write-the-f-paper/workflows/verify-work.md +251 -0
- package/core/write-the-f-paper/workflows/verify-work.wcn.md +74 -0
- package/package.json +19 -7
- package/vendors/claude/.claude-plugin/plugin.json +2 -2
- package/vendors/claude/agents/wtfp/argument-verifier.md +189 -0
- package/vendors/claude/agents/wtfp/citation-expert.md +45 -0
- package/vendors/claude/agents/wtfp/citation-formatter.md +42 -0
- package/vendors/claude/agents/wtfp/coherence-checker.md +167 -0
- package/vendors/claude/agents/wtfp/outliner.md +259 -0
- package/vendors/claude/agents/wtfp/plan-checker.md +249 -0
- package/vendors/claude/agents/wtfp/prose-polisher.md +166 -0
- package/vendors/claude/agents/wtfp/research-synthesizer.md +215 -0
- package/vendors/claude/agents/wtfp/section-planner.md +364 -0
- package/vendors/claude/agents/wtfp/section-reviewer.md +182 -0
- package/vendors/claude/agents/wtfp/section-writer.md +312 -0
- package/vendors/claude/commands/wtfp/add-todo.md +108 -0
- package/vendors/claude/commands/wtfp/analyze-bib.md +60 -249
- package/vendors/claude/commands/wtfp/audit-milestone.md +286 -0
- package/vendors/claude/commands/wtfp/check-refs.md +60 -131
- package/vendors/claude/commands/wtfp/check-todos.md +152 -0
- package/vendors/claude/commands/wtfp/checkpoint.md +105 -0
- package/vendors/claude/commands/wtfp/contribute.md +1 -1
- package/vendors/claude/commands/wtfp/create-outline.md +109 -195
- package/vendors/claude/commands/wtfp/create-poster.md +1 -1
- package/vendors/claude/commands/wtfp/create-slides.md +1 -1
- package/vendors/claude/commands/wtfp/discuss-section.md +1 -1
- package/vendors/claude/commands/wtfp/execute-outline.md +257 -0
- package/vendors/claude/commands/wtfp/export-latex.md +1 -1
- package/vendors/claude/commands/wtfp/help.md +174 -280
- package/vendors/claude/commands/wtfp/insert-section.md +1 -1
- package/vendors/claude/commands/wtfp/list-assumptions.md +1 -1
- package/vendors/claude/commands/wtfp/map-project.md +35 -124
- package/vendors/claude/commands/wtfp/new-paper.md +88 -362
- package/vendors/claude/commands/wtfp/pause-writing.md +1 -1
- package/vendors/claude/commands/wtfp/plan-milestone-gaps.md +255 -0
- package/vendors/claude/commands/wtfp/plan-revision.md +1 -1
- package/vendors/claude/commands/wtfp/plan-section.md +165 -190
- package/vendors/claude/commands/wtfp/polish-prose.md +66 -118
- package/vendors/claude/commands/wtfp/progress.md +82 -254
- package/vendors/claude/commands/wtfp/quick.md +107 -0
- package/vendors/claude/commands/wtfp/remove-section.md +1 -1
- package/vendors/claude/commands/wtfp/report-bug.md +1 -1
- package/vendors/claude/commands/wtfp/request-feature.md +1 -1
- package/vendors/claude/commands/wtfp/research-gap.md +138 -151
- package/vendors/claude/commands/wtfp/resume-writing.md +1 -1
- package/vendors/claude/commands/wtfp/review-section.md +55 -333
- package/vendors/claude/commands/wtfp/settings.md +197 -0
- package/vendors/claude/commands/wtfp/submit-milestone.md +248 -99
- package/vendors/claude/commands/wtfp/update.md +112 -0
- package/vendors/claude/commands/wtfp/verify-work.md +254 -0
- package/vendors/claude/commands/wtfp/write-section.md +144 -180
- package/vendors/claude/mcp/research-server/package.json +13 -0
- package/vendors/claude/mcp/research-server/src/index.js +133 -0
- package/vendors/gemini/agents/wtfp/argument-verifier.md +184 -0
- package/vendors/gemini/agents/wtfp/citation-expert.md +41 -0
- package/vendors/gemini/agents/wtfp/citation-formatter.md +38 -0
- package/vendors/gemini/agents/wtfp/coherence-checker.md +162 -0
- package/vendors/gemini/agents/wtfp/outliner.md +252 -0
- package/vendors/gemini/agents/wtfp/plan-checker.md +244 -0
- package/vendors/gemini/agents/wtfp/prose-polisher.md +158 -0
- package/vendors/gemini/agents/wtfp/research-synthesizer.md +205 -0
- package/vendors/gemini/agents/wtfp/section-planner.md +355 -0
- package/vendors/gemini/agents/wtfp/section-reviewer.md +175 -0
- package/vendors/gemini/agents/wtfp/section-writer.md +304 -0
- package/vendors/gemini/commands/wtfp/add-todo.toml +102 -0
- package/vendors/gemini/commands/wtfp/analyze-bib.toml +126 -0
- package/vendors/gemini/commands/wtfp/audit-milestone.toml +278 -0
- package/vendors/gemini/commands/wtfp/check-refs.toml +123 -0
- package/vendors/gemini/commands/wtfp/check-todos.toml +145 -0
- package/vendors/gemini/commands/wtfp/checkpoint.toml +96 -0
- package/vendors/gemini/commands/wtfp/contribute.toml +269 -0
- package/vendors/gemini/commands/wtfp/create-outline.toml +180 -0
- package/vendors/gemini/commands/wtfp/create-poster.toml +36 -0
- package/vendors/gemini/commands/wtfp/create-slides.toml +35 -0
- package/vendors/gemini/commands/wtfp/discuss-section.toml +62 -0
- package/vendors/gemini/commands/wtfp/execute-outline.toml +247 -0
- package/vendors/gemini/commands/wtfp/export-latex.toml +209 -0
- package/vendors/gemini/commands/wtfp/help.toml +184 -0
- package/vendors/gemini/commands/wtfp/insert-section.toml +123 -0
- package/vendors/gemini/commands/wtfp/list-assumptions.toml +104 -0
- package/vendors/gemini/commands/wtfp/map-project.toml +99 -0
- package/vendors/gemini/commands/wtfp/new-paper.toml +160 -0
- package/vendors/gemini/commands/wtfp/pause-writing.toml +140 -0
- package/vendors/gemini/commands/wtfp/plan-milestone-gaps.toml +249 -0
- package/vendors/gemini/commands/wtfp/plan-revision.toml +171 -0
- package/vendors/gemini/commands/wtfp/plan-section.toml +212 -0
- package/vendors/gemini/commands/wtfp/polish-prose.toml +114 -0
- package/vendors/gemini/commands/wtfp/progress.toml +145 -0
- package/vendors/gemini/commands/wtfp/quick.toml +96 -0
- package/vendors/gemini/commands/wtfp/remove-section.toml +140 -0
- package/vendors/gemini/commands/wtfp/report-bug.toml +126 -0
- package/vendors/gemini/commands/wtfp/request-feature.toml +155 -0
- package/vendors/gemini/commands/wtfp/research-gap.toml +214 -0
- package/vendors/gemini/commands/wtfp/resume-writing.toml +136 -0
- package/vendors/gemini/commands/wtfp/review-section.toml +121 -0
- package/vendors/gemini/commands/wtfp/settings.toml +191 -0
- package/vendors/gemini/commands/wtfp/submit-milestone.toml +351 -0
- package/vendors/gemini/commands/wtfp/update.toml +107 -0
- package/vendors/gemini/commands/wtfp/verify-work.toml +245 -0
- package/vendors/gemini/commands/wtfp/write-section.toml +228 -0
- package/vendors/opencode/agents/wtfp/argument-verifier.md +184 -0
- package/vendors/opencode/agents/wtfp/citation-expert.md +41 -0
- package/vendors/opencode/agents/wtfp/citation-formatter.md +38 -0
- package/vendors/opencode/agents/wtfp/coherence-checker.md +162 -0
- package/vendors/opencode/agents/wtfp/outliner.md +252 -0
- package/vendors/opencode/agents/wtfp/plan-checker.md +244 -0
- package/vendors/opencode/agents/wtfp/prose-polisher.md +158 -0
- package/vendors/opencode/agents/wtfp/research-synthesizer.md +205 -0
- package/vendors/opencode/agents/wtfp/section-planner.md +355 -0
- package/vendors/opencode/agents/wtfp/section-reviewer.md +175 -0
- package/vendors/opencode/agents/wtfp/section-writer.md +304 -0
- package/vendors/opencode/commands/wtfp/add-todo.md +103 -0
- package/vendors/opencode/commands/wtfp/analyze-bib.md +127 -0
- package/vendors/opencode/commands/wtfp/audit-milestone.md +279 -0
- package/vendors/opencode/commands/wtfp/check-refs.md +124 -0
- package/vendors/opencode/commands/wtfp/check-todos.md +146 -0
- package/vendors/opencode/commands/wtfp/checkpoint.md +97 -0
- package/vendors/opencode/commands/wtfp/contribute.md +266 -0
- package/vendors/opencode/commands/wtfp/create-outline.md +181 -0
- package/vendors/opencode/commands/wtfp/create-poster.md +37 -0
- package/vendors/opencode/commands/wtfp/create-slides.md +36 -0
- package/vendors/opencode/commands/wtfp/discuss-section.md +63 -0
- package/vendors/opencode/commands/wtfp/execute-outline.md +248 -0
- package/vendors/opencode/commands/wtfp/export-latex.md +210 -0
- package/vendors/opencode/commands/wtfp/help.md +185 -0
- package/vendors/opencode/commands/wtfp/insert-section.md +124 -0
- package/vendors/opencode/commands/wtfp/list-assumptions.md +105 -0
- package/vendors/opencode/commands/wtfp/map-project.md +100 -0
- package/vendors/opencode/commands/wtfp/new-paper.md +161 -0
- package/vendors/opencode/commands/wtfp/pause-writing.md +141 -0
- package/vendors/opencode/commands/wtfp/plan-milestone-gaps.md +250 -0
- package/vendors/opencode/commands/wtfp/plan-revision.md +174 -0
- package/vendors/opencode/commands/wtfp/plan-section.md +214 -0
- package/vendors/opencode/commands/wtfp/polish-prose.md +115 -0
- package/vendors/opencode/commands/wtfp/progress.md +146 -0
- package/vendors/opencode/commands/wtfp/quick.md +97 -0
- package/vendors/opencode/commands/wtfp/remove-section.md +141 -0
- package/vendors/opencode/commands/wtfp/report-bug.md +127 -0
- package/vendors/opencode/commands/wtfp/request-feature.md +156 -0
- package/vendors/opencode/commands/wtfp/research-gap.md +215 -0
- package/vendors/opencode/commands/wtfp/resume-writing.md +137 -0
- package/vendors/opencode/commands/wtfp/review-section.md +123 -0
- package/vendors/opencode/commands/wtfp/settings.md +192 -0
- package/vendors/opencode/commands/wtfp/submit-milestone.md +352 -0
- package/vendors/opencode/commands/wtfp/update.md +108 -0
- package/vendors/opencode/commands/wtfp/verify-work.md +246 -0
- 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
|
-
|
|
8
|
-
getPathLabel,
|
|
9
|
-
getBackupPath,
|
|
10
|
-
writeVersionFile,
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
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
|
|
77
|
-
out.log(` ${c.dim('[s]kip
|
|
78
|
-
out.log(` ${c.dim('[b]ackup
|
|
79
|
-
out.log(` ${c.dim('[a]ll
|
|
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(
|
|
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
|
-
//
|
|
162
|
-
const
|
|
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
|
-
|
|
166
|
-
const
|
|
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
|
-
:
|
|
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
|
-
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
|
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('
|
|
228
|
-
out.log(` ${c.cyan('/wtfp:new-paper')}
|
|
229
|
-
out.log(` ${c.cyan('/wtfp:progress')}
|
|
230
|
-
out.log(` ${c.cyan('/wtfp:help')}
|
|
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
|
}
|
package/bin/commands/status.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
package/bin/commands/update.js
CHANGED
|
@@ -25,8 +25,8 @@ async function runUpdate(options, pkg, installFunc) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if (!targetDir) {
|
|
28
|
-
out.log(` ${c.yellow('No
|
|
29
|
-
out.log(`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
105
|
-
${c.cyan('doctor')}
|
|
106
|
-
${c.cyan('update')} Update
|
|
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
|
|
110
|
-
${c.cyan('-l, --local')} Install
|
|
111
|
-
${c.cyan('
|
|
112
|
-
${c.cyan('
|
|
113
|
-
${c.cyan('
|
|
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
|
|
125
|
-
${c.cyan('-h, --help')} Show this help
|
|
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('#
|
|
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
|
|
135
|
-
|
|
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 (
|
|
181
|
-
|
|
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(
|
|
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('
|
|
214
|
+
out.log(` ${out.colors.yellow('Which tool do you use for AI-assisted coding?')}
|
|
187
215
|
`);
|
|
188
|
-
out.log(` ${out.colors.cyan('1)')}
|
|
189
|
-
out.log(` ${out.colors.cyan('2)')}
|
|
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
|
-
|
|
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(
|
|
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 };
|