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
@@ -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
- // Future:
38
- // gemini: { ... }
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);