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
package/bin/lib/manifest.js
CHANGED
|
@@ -26,6 +26,24 @@ const MANIFEST = {
|
|
|
26
26
|
dest: 'skills/wtfp',
|
|
27
27
|
type: 'dir'
|
|
28
28
|
},
|
|
29
|
+
{
|
|
30
|
+
id: 'agents',
|
|
31
|
+
src: path.join(ROOT, 'vendors', 'claude', 'agents', 'wtfp'),
|
|
32
|
+
dest: 'agents/wtfp',
|
|
33
|
+
type: 'dir'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: 'mcp',
|
|
37
|
+
src: path.join(ROOT, 'vendors', 'claude', 'mcp'),
|
|
38
|
+
dest: 'mcp',
|
|
39
|
+
type: 'dir'
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'scripts',
|
|
43
|
+
src: path.join(ROOT, 'bin', 'lib'),
|
|
44
|
+
dest: 'bin',
|
|
45
|
+
type: 'dir'
|
|
46
|
+
},
|
|
29
47
|
{
|
|
30
48
|
id: 'plugin',
|
|
31
49
|
src: path.join(ROOT, 'vendors', 'claude', '.claude-plugin'),
|
|
@@ -34,8 +52,68 @@ const MANIFEST = {
|
|
|
34
52
|
}
|
|
35
53
|
]
|
|
36
54
|
},
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
gemini: {
|
|
56
|
+
name: 'Gemini CLI',
|
|
57
|
+
configDirEnv: 'GEMINI_CONFIG_DIR',
|
|
58
|
+
defaultDir: '.config/gemini',
|
|
59
|
+
components: [
|
|
60
|
+
{
|
|
61
|
+
id: 'workflows',
|
|
62
|
+
src: path.join(ROOT, 'core', 'write-the-f-paper'),
|
|
63
|
+
dest: 'write-the-f-paper',
|
|
64
|
+
type: 'dir'
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: 'commands',
|
|
68
|
+
src: path.join(ROOT, 'vendors', 'gemini', 'commands', 'wtfp'),
|
|
69
|
+
dest: 'commands/wtfp',
|
|
70
|
+
type: 'dir'
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'agents',
|
|
74
|
+
src: path.join(ROOT, 'vendors', 'gemini', 'agents', 'wtfp'),
|
|
75
|
+
dest: 'agents/wtfp',
|
|
76
|
+
type: 'dir'
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'scripts',
|
|
80
|
+
src: path.join(ROOT, 'bin', 'lib'),
|
|
81
|
+
dest: 'bin',
|
|
82
|
+
type: 'dir'
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
opencode: {
|
|
87
|
+
name: 'OpenCode',
|
|
88
|
+
configDirEnv: 'OPENCODE_CONFIG_DIR',
|
|
89
|
+
defaultDir: '.opencode',
|
|
90
|
+
components: [
|
|
91
|
+
{
|
|
92
|
+
id: 'workflows',
|
|
93
|
+
src: path.join(ROOT, 'core', 'write-the-f-paper'),
|
|
94
|
+
dest: 'write-the-f-paper',
|
|
95
|
+
type: 'dir'
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
id: 'commands',
|
|
99
|
+
src: path.join(ROOT, 'vendors', 'opencode', 'commands', 'wtfp'),
|
|
100
|
+
dest: 'commands/wtfp',
|
|
101
|
+
type: 'dir'
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 'agents',
|
|
105
|
+
src: path.join(ROOT, 'vendors', 'opencode', 'agents', 'wtfp'),
|
|
106
|
+
dest: 'agents/wtfp',
|
|
107
|
+
type: 'dir'
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'scripts',
|
|
111
|
+
src: path.join(ROOT, 'bin', 'lib'),
|
|
112
|
+
dest: 'bin',
|
|
113
|
+
type: 'dir'
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
39
117
|
};
|
|
40
118
|
|
|
41
119
|
module.exports = MANIFEST;
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const querystring = require('querystring');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* SerpAPI Google Scholar Wrapper
|
|
6
|
+
*
|
|
7
|
+
* Provides access to Google Scholar data via SerpAPI.
|
|
8
|
+
* Used for high-value "seminal" queries to get citation velocity and cluster IDs.
|
|
9
|
+
*
|
|
10
|
+
* Env:
|
|
11
|
+
* - SERPAPI_KEY: Required for operation.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const HOST = 'serpapi.com';
|
|
15
|
+
const PATH = '/search';
|
|
16
|
+
|
|
17
|
+
const usage = {
|
|
18
|
+
queries: 0,
|
|
19
|
+
// Default budget: 100 queries/month (~$5)
|
|
20
|
+
monthlyBudget: 100,
|
|
21
|
+
|
|
22
|
+
canQuery() {
|
|
23
|
+
return this.queries < this.monthlyBudget;
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
recordQuery() {
|
|
27
|
+
this.queries++;
|
|
28
|
+
if (this.queries % 10 === 0) {
|
|
29
|
+
// console.error(`[Scholar] API usage: ${this.queries}/${this.monthlyBudget}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function getApiKey() {
|
|
35
|
+
return process.env.SERPAPI_KEY;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isAvailable() {
|
|
39
|
+
return !!getApiKey();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function request(params) {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
const key = getApiKey();
|
|
45
|
+
if (!key) {
|
|
46
|
+
return reject(new Error('SERPAPI_KEY not set'));
|
|
47
|
+
}
|
|
48
|
+
if (!usage.canQuery()) {
|
|
49
|
+
return reject(new Error('Monthly budget exceeded'));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
usage.recordQuery();
|
|
53
|
+
|
|
54
|
+
const q = querystring.stringify({
|
|
55
|
+
...params,
|
|
56
|
+
api_key: key,
|
|
57
|
+
engine: 'google_scholar'
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const options = {
|
|
61
|
+
hostname: HOST,
|
|
62
|
+
path: `${PATH}?${q}`,
|
|
63
|
+
method: 'GET'
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const req = https.request(options, (res) => {
|
|
67
|
+
let data = '';
|
|
68
|
+
res.on('data', c => data += c);
|
|
69
|
+
res.on('end', () => {
|
|
70
|
+
if (res.statusCode !== 200) {
|
|
71
|
+
return reject(new Error(`SerpAPI Error: ${res.statusCode} ${data}`));
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
resolve(JSON.parse(data));
|
|
75
|
+
} catch (e) {
|
|
76
|
+
reject(e);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
req.on('error', reject);
|
|
82
|
+
req.end();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Search Google Scholar
|
|
88
|
+
* @param {string} query
|
|
89
|
+
* @param {Object} options { yearLow, yearHigh, limit }
|
|
90
|
+
*/
|
|
91
|
+
async function search(query, options = {}) {
|
|
92
|
+
const params = {
|
|
93
|
+
q: query,
|
|
94
|
+
num: options.limit || 10
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
if (options.yearLow) params.as_ylo = options.yearLow;
|
|
98
|
+
if (options.yearHigh) params.as_yhi = options.yearHigh;
|
|
99
|
+
|
|
100
|
+
const data = await request(params);
|
|
101
|
+
return (data.organic_results || []).map(mapResult);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get paper by Cluster ID
|
|
106
|
+
* @param {string} clusterId
|
|
107
|
+
*/
|
|
108
|
+
async function getByClusterId(clusterId) {
|
|
109
|
+
const data = await request({ cluster: clusterId });
|
|
110
|
+
// When searching by cluster, it returns list of versions.
|
|
111
|
+
// We usually want the first one or metadata about the cluster.
|
|
112
|
+
// SerpAPI "cluster" usually returns "organic_results" which are the versions.
|
|
113
|
+
const results = data.organic_results || [];
|
|
114
|
+
if (results.length > 0) return mapResult(results[0]);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get citing papers
|
|
120
|
+
* @param {string} clusterId
|
|
121
|
+
* @param {number} limit
|
|
122
|
+
*/
|
|
123
|
+
async function getCitingPapers(clusterId, limit = 10) {
|
|
124
|
+
const data = await request({
|
|
125
|
+
cites: clusterId,
|
|
126
|
+
num: limit
|
|
127
|
+
});
|
|
128
|
+
return (data.organic_results || []).map(mapResult);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function mapResult(item) {
|
|
132
|
+
// Extract cluster ID
|
|
133
|
+
let clusterId = null;
|
|
134
|
+
// Try to find it in links or specific fields if SerpAPI provides it directly
|
|
135
|
+
// SerpAPI usually puts it in inline_links -> cited_by -> serpapi_link (cites=ID)
|
|
136
|
+
// or versions -> serpapi_link (cluster=ID)
|
|
137
|
+
|
|
138
|
+
if (item.inline_links && item.inline_links.cited_by && item.inline_links.cited_by.serpapi_link) {
|
|
139
|
+
const match = item.inline_links.cited_by.serpapi_link.match(/cites=(\w+)/);
|
|
140
|
+
if (match) clusterId = match[1];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
source: 'google_scholar',
|
|
145
|
+
title: item.title,
|
|
146
|
+
clusterId: clusterId || item.cluster_id, // Sometimes provided directly
|
|
147
|
+
link: item.link,
|
|
148
|
+
snippet: item.snippet,
|
|
149
|
+
publication_info: item.publication_info,
|
|
150
|
+
citationCount: item.inline_links && item.inline_links.cited_by ? item.inline_links.cited_by.total : 0,
|
|
151
|
+
year: extractYear(item.publication_info ? item.publication_info.summary : ''),
|
|
152
|
+
authors: extractAuthors(item.publication_info ? item.publication_info.summary : ''),
|
|
153
|
+
venue: extractVenue(item.publication_info ? item.publication_info.summary : '')
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function extractYear(summary) {
|
|
158
|
+
const match = summary.match(/\b(19|20)\d{2}\b/);
|
|
159
|
+
return match ? parseInt(match[0]) : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function extractAuthors(summary) {
|
|
163
|
+
// "A Author, B Author - Venue, 2020 - publisher"
|
|
164
|
+
if (!summary) return [];
|
|
165
|
+
const parts = summary.split('-');
|
|
166
|
+
if (parts.length > 0) {
|
|
167
|
+
return parts[0].split(',').map(s => ({ name: s.trim() }));
|
|
168
|
+
}
|
|
169
|
+
return [];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function extractVenue(summary) {
|
|
173
|
+
if (!summary) return null;
|
|
174
|
+
const parts = summary.split('-');
|
|
175
|
+
if (parts.length > 1) {
|
|
176
|
+
// "Venue, Year" usually
|
|
177
|
+
return parts[1].split(',')[0].trim();
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
module.exports = {
|
|
183
|
+
isAvailable,
|
|
184
|
+
search,
|
|
185
|
+
getByClusterId,
|
|
186
|
+
getCitingPapers,
|
|
187
|
+
usage
|
|
188
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
const https = require('https');
|
|
2
|
+
const querystring = require('querystring');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Semantic Scholar API Wrapper
|
|
6
|
+
*
|
|
7
|
+
* Provides search and lookup capabilities for Semantic Scholar Graph API v1.
|
|
8
|
+
* Handles rate limiting with exponential backoff.
|
|
9
|
+
*
|
|
10
|
+
* Env:
|
|
11
|
+
* - S2_API_KEY: Optional API key for higher rate limits.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const API_KEY = process.env.S2_API_KEY;
|
|
15
|
+
const BASE_HOST = 'api.semanticscholar.org';
|
|
16
|
+
const BASE_PATH = '/graph/v1';
|
|
17
|
+
|
|
18
|
+
const RETRY_CONFIG = {
|
|
19
|
+
maxRetries: 5,
|
|
20
|
+
baseDelay: 1000,
|
|
21
|
+
maxDelay: 16000
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
class SemanticScholarError extends Error {
|
|
25
|
+
constructor(message, statusCode, retryAfter) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'SemanticScholarError';
|
|
28
|
+
this.statusCode = statusCode;
|
|
29
|
+
this.retryAfter = retryAfter;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Sleep helper
|
|
35
|
+
*/
|
|
36
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Make an HTTP request with retry logic
|
|
40
|
+
*/
|
|
41
|
+
async function request(endpoint, params = {}) {
|
|
42
|
+
const query = querystring.stringify(params);
|
|
43
|
+
const path = `${BASE_PATH}${endpoint}?${query}`;
|
|
44
|
+
|
|
45
|
+
let attempt = 0;
|
|
46
|
+
|
|
47
|
+
while (attempt <= RETRY_CONFIG.maxRetries) {
|
|
48
|
+
try {
|
|
49
|
+
return await doRequest(path);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error instanceof SemanticScholarError && error.statusCode === 429) {
|
|
52
|
+
attempt++;
|
|
53
|
+
if (attempt > RETRY_CONFIG.maxRetries) throw error;
|
|
54
|
+
|
|
55
|
+
// Use Retry-After header if available, else exponential backoff
|
|
56
|
+
const delay = error.retryAfter
|
|
57
|
+
? parseInt(error.retryAfter, 10) * 1000
|
|
58
|
+
: Math.min(RETRY_CONFIG.maxDelay, RETRY_CONFIG.baseDelay * Math.pow(2, attempt - 1));
|
|
59
|
+
|
|
60
|
+
// console.error(`[S2] Rate limited. Retrying in ${delay}ms...`);
|
|
61
|
+
await sleep(delay);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function doRequest(path) {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const options = {
|
|
72
|
+
hostname: BASE_HOST,
|
|
73
|
+
path: path,
|
|
74
|
+
method: 'GET',
|
|
75
|
+
headers: {
|
|
76
|
+
'User-Agent': 'WTF-P/0.4.0 (citation-expert)',
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
if (API_KEY) {
|
|
81
|
+
options.headers['x-api-key'] = API_KEY;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const req = https.request(options, (res) => {
|
|
85
|
+
let data = '';
|
|
86
|
+
|
|
87
|
+
res.on('data', (chunk) => data += chunk);
|
|
88
|
+
|
|
89
|
+
res.on('end', () => {
|
|
90
|
+
if (res.statusCode === 429) {
|
|
91
|
+
const retryAfter = res.headers['retry-after'];
|
|
92
|
+
return reject(new SemanticScholarError('Rate limit exceeded', 429, retryAfter));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (res.statusCode >= 400) {
|
|
96
|
+
return reject(new SemanticScholarError(`API Error: ${res.statusCode} ${data}`, res.statusCode));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const json = JSON.parse(data);
|
|
101
|
+
resolve(json);
|
|
102
|
+
} catch (e) {
|
|
103
|
+
reject(new Error(`Failed to parse response: ${e.message}`));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
req.on('error', (e) => reject(new Error(`Request failed: ${e.message}`)));
|
|
109
|
+
req.on('timeout', () => {
|
|
110
|
+
req.destroy();
|
|
111
|
+
reject(new Error('Request timed out'));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Set a reasonable timeout (10s)
|
|
115
|
+
req.setTimeout(10000);
|
|
116
|
+
req.end();
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Search for papers
|
|
122
|
+
* @param {string} query
|
|
123
|
+
* @param {Object} options
|
|
124
|
+
*/
|
|
125
|
+
async function search(query, options = {}) {
|
|
126
|
+
const limit = Math.min(options.limit || 10, 100);
|
|
127
|
+
const fields = options.fields || [
|
|
128
|
+
'paperId', 'externalIds', 'title', 'abstract', 'venue', 'year',
|
|
129
|
+
'citationCount', 'influentialCitationCount', 'authors',
|
|
130
|
+
'publicationTypes', 'openAccessPdf'
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
const params = {
|
|
134
|
+
query,
|
|
135
|
+
limit,
|
|
136
|
+
fields: fields.join(',')
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
if (options.year) params.year = options.year;
|
|
140
|
+
|
|
141
|
+
const response = await request('/paper/search', params);
|
|
142
|
+
return response.data || [];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get paper by ID
|
|
147
|
+
* @param {string} id - S2 PaperId, DOI, ArXivId, etc.
|
|
148
|
+
* @param {string[]} fields
|
|
149
|
+
*/
|
|
150
|
+
async function getPaper(id, fields = []) {
|
|
151
|
+
const defaultFields = [
|
|
152
|
+
'paperId', 'externalIds', 'title', 'abstract', 'venue', 'year',
|
|
153
|
+
'citationCount', 'influentialCitationCount', 'authors',
|
|
154
|
+
'publicationTypes', 'openAccessPdf'
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
const params = {
|
|
158
|
+
fields: (fields.length > 0 ? fields : defaultFields).join(',')
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
return await request(`/paper/${id}`, params);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get citations for a paper
|
|
166
|
+
* @param {string} paperId
|
|
167
|
+
* @param {number} limit
|
|
168
|
+
*/
|
|
169
|
+
async function getCitations(paperId, limit = 20) {
|
|
170
|
+
const params = {
|
|
171
|
+
limit,
|
|
172
|
+
fields: 'title,year,citationCount,authors' // Minimal fields for citations
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const response = await request(`/paper/${paperId}/citations`, params);
|
|
176
|
+
return response.data || [];
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = {
|
|
180
|
+
search,
|
|
181
|
+
getPaper,
|
|
182
|
+
getCitations,
|
|
183
|
+
SemanticScholarError
|
|
184
|
+
};
|
package/bin/lib/utils.js
CHANGED
|
@@ -160,21 +160,33 @@ function detectInstallation(vendorDir) {
|
|
|
160
160
|
hasCommands: false,
|
|
161
161
|
hasWorkflows: false,
|
|
162
162
|
hasSkills: false,
|
|
163
|
+
hasAgents: false,
|
|
164
|
+
hasMcp: false,
|
|
165
|
+
hasBin: false,
|
|
163
166
|
version: null,
|
|
164
167
|
partial: false,
|
|
165
168
|
corrupt: false,
|
|
166
169
|
commandFiles: [],
|
|
167
170
|
workflowFiles: [],
|
|
168
|
-
skillFiles: []
|
|
171
|
+
skillFiles: [],
|
|
172
|
+
agentFiles: [],
|
|
173
|
+
mcpFiles: [],
|
|
174
|
+
binFiles: []
|
|
169
175
|
};
|
|
170
176
|
|
|
171
177
|
const commandsDir = path.join(vendorDir, 'commands', 'wtfp');
|
|
172
178
|
const workflowsDir = path.join(vendorDir, 'write-the-f-paper');
|
|
173
179
|
const skillsDir = path.join(vendorDir, 'skills', 'wtfp');
|
|
180
|
+
const agentsDir = path.join(vendorDir, 'agents', 'wtfp');
|
|
181
|
+
const mcpDir = path.join(vendorDir, 'mcp');
|
|
182
|
+
const binDir = path.join(vendorDir, 'bin');
|
|
174
183
|
|
|
175
184
|
result.hasCommands = fs.existsSync(commandsDir);
|
|
176
185
|
result.hasWorkflows = fs.existsSync(workflowsDir);
|
|
177
186
|
result.hasSkills = fs.existsSync(skillsDir);
|
|
187
|
+
result.hasAgents = fs.existsSync(agentsDir);
|
|
188
|
+
result.hasMcp = fs.existsSync(mcpDir);
|
|
189
|
+
result.hasBin = fs.existsSync(binDir);
|
|
178
190
|
|
|
179
191
|
if (result.hasCommands) {
|
|
180
192
|
result.commandFiles = collectFiles(commandsDir);
|
|
@@ -185,6 +197,15 @@ function detectInstallation(vendorDir) {
|
|
|
185
197
|
if (result.hasSkills) {
|
|
186
198
|
result.skillFiles = collectFiles(skillsDir);
|
|
187
199
|
}
|
|
200
|
+
if (result.hasAgents) {
|
|
201
|
+
result.agentFiles = collectFiles(agentsDir);
|
|
202
|
+
}
|
|
203
|
+
if (result.hasMcp) {
|
|
204
|
+
result.mcpFiles = collectFiles(mcpDir);
|
|
205
|
+
}
|
|
206
|
+
if (result.hasBin) {
|
|
207
|
+
result.binFiles = collectFiles(binDir);
|
|
208
|
+
}
|
|
188
209
|
|
|
189
210
|
// Read version info
|
|
190
211
|
const versionData = readInstalledVersion(vendorDir);
|