@duckmind/dm-windows-x64 0.62.3 → 0.63.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 (93) hide show
  1. package/dm.exe +0 -0
  2. package/extensions/.dm-extensions.json +43 -183
  3. package/extensions/dm-context/src/context.js +1 -2
  4. package/extensions/dm-context/src/index.js +1 -2
  5. package/extensions/{greedysearch-dm/bin/cdp.mjs → dm-cua/bin/browser-cdp.mjs} +3 -3
  6. package/extensions/dm-cua/bin/browser-cua.mjs +1 -1
  7. package/extensions/{greedysearch-dm/bin/launch.mjs → dm-cua/bin/browser-launch.mjs} +12 -11
  8. package/extensions/dm-cua/index.js +1 -1
  9. package/extensions/dm-cua/src/browser-cua-lib.mjs +17 -12
  10. package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/constants.mjs +11 -11
  11. package/extensions/{greedysearch-dm/src/utils → dm-cua/src/browser-helpers}/node-runtime.mjs +1 -1
  12. package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/port-pid.mjs +1 -1
  13. package/extensions/dm-ponytail/extensions/ponytail.mjs +186 -0
  14. package/extensions/dm-ponytail/hooks/claude-codex-hooks.json +41 -0
  15. package/extensions/dm-ponytail/hooks/copilot-hooks.json +21 -0
  16. package/extensions/dm-ponytail/hooks/ponytail-activate.js +59 -0
  17. package/extensions/dm-ponytail/hooks/ponytail-config.js +116 -0
  18. package/extensions/dm-ponytail/hooks/ponytail-instructions.js +81 -0
  19. package/extensions/dm-ponytail/hooks/ponytail-mode-tracker.js +96 -0
  20. package/extensions/dm-ponytail/hooks/ponytail-runtime.js +79 -0
  21. package/extensions/dm-ponytail/hooks/ponytail-statusline.ps1 +24 -0
  22. package/extensions/dm-ponytail/hooks/ponytail-statusline.sh +18 -0
  23. package/extensions/dm-ponytail/hooks/ponytail-subagent.js +50 -0
  24. package/extensions/dm-ponytail/hooks/qoder-hooks.json +26 -0
  25. package/extensions/dm-ponytail/package.json +19 -0
  26. package/extensions/dm-ponytail/pi-extension/index.js +186 -0
  27. package/extensions/dm-ponytail/pi-extension/package.json +8 -0
  28. package/extensions/dm-ponytail/skills/ponytail/SKILL.md +120 -0
  29. package/extensions/dm-ponytail/skills/ponytail-audit/SKILL.md +41 -0
  30. package/extensions/dm-ponytail/skills/ponytail-debt/SKILL.md +44 -0
  31. package/extensions/dm-ponytail/skills/ponytail-gain/SKILL.md +50 -0
  32. package/extensions/dm-ponytail/skills/ponytail-help/SKILL.md +71 -0
  33. package/extensions/dm-ponytail/skills/ponytail-review/SKILL.md +57 -0
  34. package/package.json +2 -6
  35. package/extensions/dm-caveman/extensions/caveman.js +0 -300
  36. package/extensions/dm-caveman/package.json +0 -20
  37. package/extensions/dm-goal/package.json +0 -23
  38. package/extensions/dm-goal/src/goal.js +0 -914
  39. package/extensions/greedysearch-dm/bin/cdp-greedy.mjs +0 -40
  40. package/extensions/greedysearch-dm/bin/cdp-headless.mjs +0 -5
  41. package/extensions/greedysearch-dm/bin/cdp-visible.mjs +0 -5
  42. package/extensions/greedysearch-dm/bin/gschrome.mjs +0 -30
  43. package/extensions/greedysearch-dm/bin/kill-visible.mjs +0 -7
  44. package/extensions/greedysearch-dm/bin/launch-visible.mjs +0 -13
  45. package/extensions/greedysearch-dm/bin/mcp.mjs +0 -400
  46. package/extensions/greedysearch-dm/bin/search.mjs +0 -635
  47. package/extensions/greedysearch-dm/bin/visible.mjs +0 -22
  48. package/extensions/greedysearch-dm/extractors/bing-copilot.mjs +0 -344
  49. package/extensions/greedysearch-dm/extractors/chatgpt.mjs +0 -354
  50. package/extensions/greedysearch-dm/extractors/common.mjs +0 -794
  51. package/extensions/greedysearch-dm/extractors/consensus.mjs +0 -527
  52. package/extensions/greedysearch-dm/extractors/consent.mjs +0 -449
  53. package/extensions/greedysearch-dm/extractors/gemini.mjs +0 -291
  54. package/extensions/greedysearch-dm/extractors/google-ai.mjs +0 -86
  55. package/extensions/greedysearch-dm/extractors/logically.mjs +0 -589
  56. package/extensions/greedysearch-dm/extractors/perplexity.mjs +0 -370
  57. package/extensions/greedysearch-dm/extractors/selectors.mjs +0 -32
  58. package/extensions/greedysearch-dm/extractors/semantic-scholar.mjs +0 -199
  59. package/extensions/greedysearch-dm/index.js +0 -123
  60. package/extensions/greedysearch-dm/package.json +0 -27
  61. package/extensions/greedysearch-dm/skills/greedy-search/skill.md +0 -18
  62. package/extensions/greedysearch-dm/src/fetcher.mjs +0 -577
  63. package/extensions/greedysearch-dm/src/formatters/results.js +0 -95
  64. package/extensions/greedysearch-dm/src/formatters/sources.js +0 -57
  65. package/extensions/greedysearch-dm/src/formatters/synthesis.js +0 -49
  66. package/extensions/greedysearch-dm/src/github.mjs +0 -228
  67. package/extensions/greedysearch-dm/src/reddit.mjs +0 -154
  68. package/extensions/greedysearch-dm/src/search/browser-lifecycle.mjs +0 -340
  69. package/extensions/greedysearch-dm/src/search/challenge-detect.mjs +0 -156
  70. package/extensions/greedysearch-dm/src/search/chrome.mjs +0 -510
  71. package/extensions/greedysearch-dm/src/search/defaults.mjs +0 -10
  72. package/extensions/greedysearch-dm/src/search/engines.mjs +0 -79
  73. package/extensions/greedysearch-dm/src/search/fetch-source.mjs +0 -470
  74. package/extensions/greedysearch-dm/src/search/file-sources.mjs +0 -31
  75. package/extensions/greedysearch-dm/src/search/output.mjs +0 -51
  76. package/extensions/greedysearch-dm/src/search/paths.mjs +0 -48
  77. package/extensions/greedysearch-dm/src/search/pdf.mjs +0 -64
  78. package/extensions/greedysearch-dm/src/search/progress.mjs +0 -109
  79. package/extensions/greedysearch-dm/src/search/query.mjs +0 -21
  80. package/extensions/greedysearch-dm/src/search/recovery.mjs +0 -49
  81. package/extensions/greedysearch-dm/src/search/research.mjs +0 -2255
  82. package/extensions/greedysearch-dm/src/search/scale-aware.mjs +0 -61
  83. package/extensions/greedysearch-dm/src/search/simple-research.mjs +0 -418
  84. package/extensions/greedysearch-dm/src/search/sources.mjs +0 -412
  85. package/extensions/greedysearch-dm/src/search/synthesis-runner.mjs +0 -127
  86. package/extensions/greedysearch-dm/src/search/synthesis.mjs +0 -202
  87. package/extensions/greedysearch-dm/src/tools/greedy-search-handler.js +0 -209
  88. package/extensions/greedysearch-dm/src/tools/shared.js +0 -226
  89. package/extensions/greedysearch-dm/src/types.js +0 -0
  90. package/extensions/greedysearch-dm/src/utils/content.mjs +0 -38
  91. package/extensions/greedysearch-dm/src/utils/helpers.js +0 -22
  92. /package/extensions/{greedysearch-dm/src/search → dm-cua/src/browser-helpers}/minimize.mjs +0 -0
  93. /package/extensions/{greedysearch-dm/src/utils → dm-cua/src/browser-helpers}/system-cmds.mjs +0 -0
@@ -1,527 +0,0 @@
1
- import {
2
- buildEnvelope,
3
- cdp,
4
- cdpWithInput,
5
- formatAnswer,
6
- getOrOpenTab,
7
- handleError,
8
- jitter,
9
- logStage,
10
- outputJson,
11
- parseArgs,
12
- prepareArgs,
13
- TIMING,
14
- validateQuery,
15
- verifyInputText,
16
- waitForSelector
17
- } from "./common.mjs";
18
- import { ensureChrome } from "../src/search/chrome.mjs";
19
- import { dismissConsent } from "./consent.mjs";
20
- const SELECTORS = {
21
- input: 'textarea[name="newMessage"]',
22
- submitButton: 'button[aria-label="Submit search"]',
23
- answerContainer: 'div[class*="prose"]',
24
- paperCard: 'a[data-testid="search-result"]',
25
- exportButton: 'button[aria-label="Export"]'
26
- };
27
- async function detectSignUpWall(tab) {
28
- const code = `(() => {
29
- const url = document.location.href || '';
30
- return url.indexOf('/sign-up/') !== -1 || url.indexOf('redirect_url=') !== -1;
31
- })()`;
32
- const result = await cdp(["eval", tab, code]).catch(() => "false");
33
- return result === "true";
34
- }
35
- async function detectStaleClerkSession(tab) {
36
- const code = `(() => {
37
- const url = document.location.href || '';
38
- const title = document.title || '';
39
- const text = document.body?.innerText || '';
40
- const stale =
41
- url.includes('clerk.consensus.app') ||
42
- title.includes('clerk.consensus.app') ||
43
- text.includes('session-token-expired') ||
44
- text.includes('refresh_request_origin_azp_mismatch') ||
45
- (text.includes('HTTP ERROR 405') && text.includes('This page isn'));
46
- return JSON.stringify({ stale, url, title, text: text.slice(0, 500) });
47
- })()`;
48
- try {
49
- return JSON.parse(await cdp(["eval", tab, code], 5000));
50
- } catch {
51
- return { stale: false, url: "", title: "", text: "" };
52
- }
53
- }
54
- async function clearConsensusAuthStorage(tab) {
55
- for (const origin of [
56
- "https://consensus.app",
57
- "https://clerk.consensus.app"
58
- ]) {
59
- await cdp([
60
- "evalraw",
61
- tab,
62
- "Storage.clearDataForOrigin",
63
- JSON.stringify({ origin, storageTypes: "all" })
64
- ]).catch((e) => {
65
- console.error(`[consensus] Warning: failed to clear stale auth storage for ${origin}: ${e.message}`);
66
- });
67
- }
68
- }
69
- async function recoverStaleClerkSession(tab, env, startTime) {
70
- const before = await detectStaleClerkSession(tab);
71
- if (!before.stale)
72
- return false;
73
- logStage(env, "auth-storage-reset", startTime);
74
- console.error(`[consensus] Detected stale Clerk/Consensus auth state (${before.title || before.url}) — clearing per-origin storage and retrying navigation`);
75
- env.fallbackUsed = "clear-stale-consensus-auth";
76
- await clearConsensusAuthStorage(tab);
77
- await cdp(["nav", tab, "https://consensus.app/"], 20000);
78
- await new Promise((r) => setTimeout(r, 900));
79
- const after = await detectStaleClerkSession(tab);
80
- if (after.stale) {
81
- env.blockedBy = "signin";
82
- env.verificationResult = "needs-human";
83
- throw new Error("Consensus auth session is stale — visible Chrome is open. Please sign in again, then rerun the search.");
84
- }
85
- return true;
86
- }
87
- async function typeIntoConsensus(tab, text) {
88
- await cdp(["click", tab, SELECTORS.input]);
89
- await new Promise((r) => setTimeout(r, jitter(200)));
90
- const type = async () => {
91
- await cdpWithInput(["type", tab, "--stdin"], text);
92
- await new Promise((r) => setTimeout(r, jitter(300)));
93
- };
94
- const readInput = async () => {
95
- const raw = await cdp([
96
- "eval",
97
- tab,
98
- `JSON.stringify(document.querySelector('${SELECTORS.input}')?.value || '')`
99
- ]);
100
- try {
101
- return JSON.parse(raw);
102
- } catch {
103
- return raw;
104
- }
105
- };
106
- const verify = async () => verifyInputText(await readInput(), text);
107
- const clear = async () => {
108
- await cdp([
109
- "eval",
110
- tab,
111
- `(() => {
112
- const el = document.querySelector('${SELECTORS.input}');
113
- if (!el) return false;
114
- el.focus();
115
- el.select();
116
- document.execCommand('delete');
117
- return true;
118
- })()`
119
- ]);
120
- };
121
- await type();
122
- if (await verify())
123
- return;
124
- await clear();
125
- await type();
126
- if (!await verify()) {
127
- throw new Error("Consensus input did not accept text — input verification failed");
128
- }
129
- }
130
- async function installPapersDetailsInterceptor(tab) {
131
- const code = `(() => {
132
- if (window.__pdiInstalled) return 'already';
133
- window.__pdiInstalled = true;
134
- const _origOpen = XMLHttpRequest.prototype.open;
135
- const _origSend = XMLHttpRequest.prototype.send;
136
- XMLHttpRequest.prototype.open = function(method, url) {
137
- this.__url = String(url);
138
- return _origOpen.apply(this, arguments);
139
- };
140
- XMLHttpRequest.prototype.send = function(body) {
141
- if (this.__url && this.__url.indexOf('/api/papers/details') !== -1) {
142
- this.addEventListener('load', function() {
143
- if (this.status === 200) {
144
- try {
145
- const parsed = JSON.parse(this.responseText);
146
- // Keep only {largest, latest} instead of every
147
- // partial Load-More response, to bound page
148
- // memory. "latest" lets the caller detect the
149
- // .CSV response fired after clickExportCsv;
150
- // "largest" is the timeout fallback.
151
- const entry = {
152
- at: Date.now(),
153
- count: Object.keys(parsed?.paperDetailsListByPaperId || {}).length,
154
- data: parsed,
155
- };
156
- const store = window.__papersDetailsResps ||
157
- (window.__papersDetailsResps = { largest: null, latest: null });
158
- store.latest = entry;
159
- if (!store.largest || entry.count > store.largest.count) {
160
- store.largest = entry;
161
- }
162
- } catch (e) {
163
- window.__papersDetailsErrors = window.__papersDetailsErrors || [];
164
- window.__papersDetailsErrors.push(String(e.message || e));
165
- }
166
- }
167
- });
168
- }
169
- return _origSend.apply(this, arguments);
170
- };
171
- return 'installed';
172
- })()`;
173
- const r = await cdp(["eval", tab, code]);
174
- return r === "installed" || r === "already";
175
- }
176
- async function fetchPapersDetailsResponse(tab, timeoutMs = 1e4, afterTs = 0) {
177
- const code = `new Promise((resolve) => {
178
- const _deadline = Date.now() + ${timeoutMs};
179
- const _after = ${afterTs};
180
- function _check() {
181
- const store = window.__papersDetailsResps;
182
- const latest = store && store.latest;
183
- if (latest && latest.at > _after) {
184
- resolve(JSON.stringify({ ok: true, count: latest.count, data: latest.data }));
185
- return;
186
- }
187
- if (Date.now() < _deadline) {
188
- setTimeout(_check, 200);
189
- } else {
190
- const largest = store && store.largest;
191
- if (largest) {
192
- resolve(JSON.stringify({ ok: true, count: largest.count, data: largest.data, stale: true }));
193
- } else {
194
- resolve(JSON.stringify({ ok: false, reason: 'timeout' }));
195
- }
196
- }
197
- }
198
- _check();
199
- })`;
200
- const result = await cdp(["eval", tab, code], timeoutMs + 5000);
201
- try {
202
- return JSON.parse(result);
203
- } catch {
204
- return { ok: false, reason: "invalid-json" };
205
- }
206
- }
207
- async function expandReferences(tab, maxClicks = 8) {
208
- const ready = await waitForSelector(tab, SELECTORS.paperCard, 20000, 500).catch(() => false);
209
- if (!ready) {
210
- console.error("[consensus] Warning: no paper cards appeared within 20s — Load More will be skipped");
211
- return 0;
212
- }
213
- const code = String.raw`
214
- new Promise((resolve) => {
215
- const _paperSel = '${SELECTORS.paperCard}';
216
- const _maxClicks = ${maxClicks};
217
- function _findLoadMore() {
218
- const btns = Array.from(document.querySelectorAll('button'));
219
- return btns.find(b => {
220
- const t = (b.innerText || '').trim();
221
- return /load more/i.test(t) || /more results/i.test(t) || /show more/i.test(t);
222
- }) || null;
223
- }
224
- function _cardCount() { return document.querySelectorAll(_paperSel).length; }
225
- let _clicks = 0;
226
- function _waitButton(deadline, cb) {
227
- const btn = _findLoadMore();
228
- if (btn) { cb(btn); return; }
229
- if (Date.now() < deadline) { setTimeout(() => _waitButton(deadline, cb), 200); }
230
- else { cb(null); }
231
- }
232
- function _loop() {
233
- if (_clicks >= _maxClicks) { resolve(JSON.stringify({ clicks: _clicks, cards: _cardCount() })); return; }
234
- const mountDeadline = Date.now() + (_clicks === 0 ? 3000 : 500);
235
- _waitButton(mountDeadline, (btn) => {
236
- if (!btn || btn.disabled) { resolve(JSON.stringify({ clicks: _clicks, cards: _cardCount() })); return; }
237
- const before = _cardCount();
238
- btn.click();
239
- _clicks++;
240
- const clickDeadline = Date.now() + 4000;
241
- (function _pollCards() {
242
- if (_cardCount() > before || Date.now() >= clickDeadline) { _loop(); return; }
243
- setTimeout(_pollCards, 200);
244
- })();
245
- });
246
- }
247
- _loop();
248
- })
249
- `;
250
- const result = await cdp(["eval", tab, code], maxClicks * 5000 + 5000).catch(() => null);
251
- if (!result)
252
- return 0;
253
- try {
254
- const { clicks, cards } = JSON.parse(result);
255
- console.error(`[consensus] expandReferences: ${clicks} click(s), ${cards} card(s)`);
256
- return clicks;
257
- } catch {
258
- return 0;
259
- }
260
- }
261
- async function waitForCardsStable(tab, maxMs = 1200) {
262
- const code = String.raw`
263
- new Promise((resolve) => {
264
- const _sel = '${SELECTORS.paperCard}';
265
- const _deadline = Date.now() + ${maxMs};
266
- let _last = document.querySelectorAll(_sel).length;
267
- function _poll() {
268
- const cur = document.querySelectorAll(_sel).length;
269
- if (cur === _last) { resolve(cur); return; }
270
- _last = cur;
271
- if (Date.now() < _deadline) { setTimeout(_poll, 300); }
272
- else { resolve(_last); }
273
- }
274
- setTimeout(_poll, 300);
275
- })
276
- `;
277
- await cdp(["eval", tab, code], maxMs + 3000).catch(() => null);
278
- }
279
- async function clickExportCsv(tab) {
280
- await cdp([
281
- "eval",
282
- tab,
283
- `(() => {
284
- const btn = document.querySelector('${SELECTORS.exportButton}');
285
- if (!btn) return 'no-export';
286
- btn.scrollIntoView({ block: 'center' });
287
- btn.click();
288
- return 'opened';
289
- })()`
290
- ]);
291
- await new Promise((r) => setTimeout(r, 600));
292
- const clicked = await cdp([
293
- "eval",
294
- tab,
295
- `(() => {
296
- const btn = Array.from(document.querySelectorAll('button'))
297
- .find(b => /\\.CSV/.test((b.innerText || '').trim()));
298
- if (!btn) return 'no-csv';
299
- btn.click();
300
- return 'clicked';
301
- })()`
302
- ]);
303
- return clicked;
304
- }
305
- function buildSourcesFromApi(respData) {
306
- const map = respData?.paperDetailsListByPaperId || {};
307
- const ids = Object.keys(map);
308
- const sources = [];
309
- for (let i = 0;i < ids.length; i++) {
310
- const p = map[ids[i]] || {};
311
- const urlSlug = p.url_slug || "";
312
- const paperId = p.paper_id || p.hash_paper_id || ids[i];
313
- let url;
314
- if (urlSlug) {
315
- url = `https://consensus.app/papers/${urlSlug}/${paperId}/`;
316
- } else if (p.provider_url) {
317
- url = p.provider_url;
318
- } else if (p.doi) {
319
- url = `https://doi.org/${p.doi}`;
320
- } else {
321
- url = `https://consensus.app/paper/${paperId}`;
322
- }
323
- const tags = [];
324
- const badges = p.badges || {};
325
- if (badges.study_type === "rct")
326
- tags.push("RCT");
327
- else if (badges.study_type === "meta_analysis")
328
- tags.push("META-ANALYSIS");
329
- else if (badges.study_type === "systematic_review")
330
- tags.push("SYSTEMATIC REVIEW");
331
- else if (badges.study_type)
332
- tags.push(badges.study_type.toUpperCase());
333
- if (badges.rigorous_journal)
334
- tags.push("RIGOROUS JOURNAL");
335
- if (badges.very_rigorous_journal)
336
- tags.push("VERY RIGOROUS JOURNAL");
337
- if (badges.highly_cited_paper)
338
- tags.push("HIGHLY CITED");
339
- if (badges.large_human_trial)
340
- tags.push("LARGE HUMAN TRIAL");
341
- if (p.is_retracted)
342
- tags.push("RETRACTED");
343
- if (p.open_access_pdf_url)
344
- tags.push("OPEN ACCESS");
345
- sources.push({
346
- title: p.title || "",
347
- url,
348
- rank: i + 1,
349
- authors: Array.isArray(p.authors) ? p.authors : [],
350
- year: p.year || null,
351
- journal: p.journal || p.publisher_name || null,
352
- doi: p.doi || null,
353
- citation_count: p.citation_count || 0,
354
- snippet: p.abstract_takeaway || "",
355
- tags
356
- });
357
- }
358
- return sources;
359
- }
360
- async function extractSourcesFromDom(tab) {
361
- const code = `(() => {
362
- const cards = Array.from(document.querySelectorAll('${SELECTORS.paperCard}'));
363
- const sources = [];
364
- const seen = new Set();
365
- for (const card of cards) {
366
- const url = card.href || '';
367
- if (!url || seen.has(url)) continue;
368
- seen.add(url);
369
- const rankText = card.querySelector('span[data-testid="tag"]')?.innerText?.trim() || '';
370
- const rank = parseInt(rankText, 10) || null;
371
- const title = (card.querySelector('h2')?.innerText || '').trim();
372
- // Key takeaway: the span after the "KEY TAKEAWAY" label
373
- const takeawaySpan = card.querySelector('span.sm-normal');
374
- const snippet = takeawaySpan
375
- ? takeawaySpan.innerText.replace(/^KEY TAKEAWAY\\s*[·\\-]\\s*/i, '').trim()
376
- : '';
377
- // Tags: collect chip text from meta-analysis-tag, journal tags, etc.
378
- const tags = Array.from(card.querySelectorAll('span[data-testid$="-tag"]'))
379
- .map(t => t.innerText?.trim())
380
- .filter(Boolean);
381
- sources.push({ title, url, rank, snippet, tags });
382
- }
383
- return JSON.stringify(sources);
384
- })()`;
385
- const result = await cdp(["eval", tab, code], 1e4);
386
- try {
387
- return JSON.parse(result);
388
- } catch {
389
- return [];
390
- }
391
- }
392
- async function extractAnswer(tab) {
393
- const code = `(() => {
394
- const prose = document.querySelector('${SELECTORS.answerContainer}');
395
- return prose?.innerText?.trim() || '';
396
- })()`;
397
- return await cdp(["eval", tab, code], 1e4) || "";
398
- }
399
- const USAGE = `Usage: node extractors/consensus.mjs "<query>" [--tab <prefix>]
400
- `;
401
- async function main() {
402
- const args = await prepareArgs(process.argv.slice(2));
403
- validateQuery(args, USAGE);
404
- const { query, tabPrefix, short } = parseArgs(args);
405
- const startTime = Date.now();
406
- const mode = process.env.GREEDY_SEARCH_VISIBLE === "1" ? "visible" : "headless";
407
- const env = {
408
- engine: "consensus",
409
- mode,
410
- clipboardEmpty: null,
411
- fallbackUsed: null,
412
- blockedBy: null,
413
- verificationResult: null,
414
- inputReady: null
415
- };
416
- try {
417
- if (process.env.GREEDY_SEARCH_VISIBLE !== "1" && process.env.GREEDY_SEARCH_ALWAYS_VISIBLE !== "1") {
418
- process.env.GREEDY_SEARCH_HEADLESS = "1";
419
- }
420
- await ensureChrome();
421
- if (!tabPrefix)
422
- await cdp(["list"]);
423
- const tab = await getOrOpenTab(tabPrefix);
424
- const currentUrl = await cdp(["eval", tab, "document.location.href"]).catch(() => "");
425
- let onConsensus = false;
426
- try {
427
- const host = new URL(currentUrl).hostname.toLowerCase();
428
- onConsensus = host === "consensus.app" || host.endsWith(".consensus.app");
429
- } catch {}
430
- if (!onConsensus) {
431
- logStage(env, "nav", startTime);
432
- await cdp(["nav", tab, "https://consensus.app/"], 20000);
433
- await new Promise((r) => setTimeout(r, 600));
434
- }
435
- await recoverStaleClerkSession(tab, env, startTime);
436
- await dismissConsent(tab, cdp);
437
- logStage(env, "input-wait", startTime);
438
- const inputReady = await waitForSelector(tab, SELECTORS.input, 15000, 400);
439
- env.inputReady = inputReady;
440
- if (!inputReady) {
441
- const recovered = await recoverStaleClerkSession(tab, env, startTime);
442
- if (recovered) {
443
- const retryInputReady = await waitForSelector(tab, SELECTORS.input, 15000, 400);
444
- env.inputReady = retryInputReady;
445
- if (retryInputReady) {
446
- await dismissConsent(tab, cdp);
447
- } else {
448
- throw new Error("Consensus input not found after stale auth recovery — page may not have loaded or is in unexpected state");
449
- }
450
- } else {
451
- throw new Error("Consensus input not found — page may not have loaded or is in unexpected state");
452
- }
453
- }
454
- await new Promise((r) => setTimeout(r, jitter(TIMING.postClick)));
455
- logStage(env, "type-and-submit", startTime);
456
- await typeIntoConsensus(tab, query);
457
- await new Promise((r) => setTimeout(r, jitter(TIMING.postType)));
458
- await cdp([
459
- "eval",
460
- tab,
461
- `document.querySelector('${SELECTORS.submitButton}')?.click()`
462
- ]);
463
- await new Promise((r) => setTimeout(r, 2000));
464
- if (await detectSignUpWall(tab)) {
465
- env.blockedBy = "signin";
466
- throw new Error("Consensus requires sign-in — please sign in or create a free account in the visible browser window. Once signed in, cookies persist for future runs.");
467
- }
468
- logStage(env, "answer-wait", startTime);
469
- await waitForSelector(tab, SELECTORS.answerContainer, 30000, 500);
470
- await installPapersDetailsInterceptor(tab);
471
- logStage(env, "expand-refs", startTime);
472
- const clicks = await expandReferences(tab, 8);
473
- if (clicks === 0) {
474
- console.error("[consensus] Note: 'Load more results' button not present (initial page has all references)");
475
- } else {
476
- console.error(`[consensus] Clicked 'Load more results' ${clicks} time(s) to expand references`);
477
- }
478
- await waitForCardsStable(tab, 1200);
479
- logStage(env, "csv-click", startTime);
480
- const preClickTs = Number(await cdp(["eval", tab, "Date.now()"]).catch(() => "0") || 0);
481
- const csvResult = await clickExportCsv(tab);
482
- if (csvResult !== "clicked") {
483
- console.error(`[consensus] Export → .CSV click did not register (${csvResult}) — falling back to DOM`);
484
- }
485
- logStage(env, "wait-csv-resp", startTime);
486
- const csvResp = await fetchPapersDetailsResponse(tab, 12000, preClickTs);
487
- let sources = [];
488
- let sourcePath = "dom";
489
- if (csvResp.ok) {
490
- sources = buildSourcesFromApi(csvResp.data);
491
- sourcePath = "api-intercept";
492
- console.error(`[consensus] Captured /api/papers/details/ response with ${csvResp.count} paper(s)`);
493
- } else {
494
- console.error(`[consensus] /api/papers/details/ response not captured (${csvResp.reason}) — falling back to DOM cards`);
495
- }
496
- logStage(env, "extract", startTime);
497
- const answer = await extractAnswer(tab);
498
- if (!answer) {
499
- throw new Error("No answer extracted — Consensus may not have responded");
500
- }
501
- if (sources.length === 0) {
502
- sources = await extractSourcesFromDom(tab);
503
- } else {
504
- const domSources = await extractSourcesFromDom(tab);
505
- const apiUrls = new Set(sources.map((s) => s.url));
506
- for (const ds of domSources) {
507
- if (!apiUrls.has(ds.url))
508
- sources.push(ds);
509
- }
510
- }
511
- const finalUrl = await cdp(["eval", tab, "document.location.href"]).catch(() => "");
512
- env.durationMs = Date.now() - startTime;
513
- env.sourcePath = sourcePath;
514
- logStage(env, "done", startTime);
515
- outputJson({
516
- query,
517
- url: finalUrl,
518
- answer: formatAnswer(answer, short),
519
- sources,
520
- _envelope: buildEnvelope(env)
521
- });
522
- } catch (e) {
523
- env.durationMs = Date.now() - startTime;
524
- handleError(e, buildEnvelope(env));
525
- }
526
- }
527
- main();