@apmantza/greedysearch-pi 1.4.2 → 1.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/.pi-lens/cache/jscpd.json +112 -0
- package/.pi-lens/cache/jscpd.meta.json +3 -0
- package/.pi-lens/cache/knip.json +111 -0
- package/.pi-lens/cache/knip.meta.json +4 -0
- package/.pi-lens/fix-plan.md +13 -0
- package/.pi-lens/fix-session.json +11 -0
- package/.pi-lens/metrics-history.json +182 -0
- package/.pi-lens/reports/fix-plan.tsv +38 -0
- package/.pi-lens/turn-state.json +6 -0
- package/CHANGELOG.md +30 -0
- package/README.md +233 -219
- package/cdp.mjs +1002 -797
- package/coding-task.mjs +392 -369
- package/extractors/bing-copilot.mjs +167 -195
- package/extractors/common.mjs +237 -0
- package/extractors/consent.mjs +273 -255
- package/extractors/gemini.mjs +142 -180
- package/extractors/google-ai.mjs +156 -162
- package/extractors/perplexity.mjs +126 -181
- package/extractors/selectors.mjs +43 -43
- package/index.ts +230 -93
- package/launch.mjs +283 -161
- package/package.json +26 -26
- package/search.mjs +1219 -997
- package/skills/greedy-search/SKILL.md +38 -109
- package/test.mjs +308 -0
- package/test.sh +298 -298
- package/newfeaturesideas.md +0 -105
package/extractors/gemini.mjs
CHANGED
|
@@ -1,180 +1,142 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const S = SELECTORS.gemini;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const tab = await getOrOpenTab(tabPrefix);
|
|
144
|
-
|
|
145
|
-
// Each search = fresh conversation
|
|
146
|
-
await cdp(['nav', tab, 'https://gemini.google.com/app'], 35000);
|
|
147
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
148
|
-
await dismissConsent(tab, cdp);
|
|
149
|
-
await handleVerification(tab, cdp, 60000);
|
|
150
|
-
|
|
151
|
-
// Wait for input to be ready
|
|
152
|
-
const deadline = Date.now() + 10000;
|
|
153
|
-
while (Date.now() < deadline) {
|
|
154
|
-
const ready = await cdp(['eval', tab, `!!document.querySelector('${S.input}')`]).catch(() => 'false');
|
|
155
|
-
if (ready === 'true') break;
|
|
156
|
-
await new Promise(r => setTimeout(r, 400));
|
|
157
|
-
}
|
|
158
|
-
await new Promise(r => setTimeout(r, 300));
|
|
159
|
-
|
|
160
|
-
await injectClipboardInterceptor(tab);
|
|
161
|
-
await typeIntoGemini(tab, query);
|
|
162
|
-
await new Promise(r => setTimeout(r, 400));
|
|
163
|
-
|
|
164
|
-
await cdp(['eval', tab, `document.querySelector('${S.sendButton}')?.click()`]);
|
|
165
|
-
|
|
166
|
-
await waitForCopyButton(tab);
|
|
167
|
-
|
|
168
|
-
const { answer, sources } = await extractAnswer(tab);
|
|
169
|
-
if (!answer) throw new Error('No answer captured from Gemini clipboard');
|
|
170
|
-
const out = short ? answer.slice(0, 300).replace(/\s+\S*$/, '') + '…' : answer;
|
|
171
|
-
|
|
172
|
-
const finalUrl = await cdp(['eval', tab, 'document.location.href']).catch(() => 'https://gemini.google.com/app');
|
|
173
|
-
process.stdout.write(JSON.stringify({ query, url: finalUrl, answer: out, sources }, null, 2) + '\n');
|
|
174
|
-
} catch (e) {
|
|
175
|
-
process.stderr.write(`Error: ${e.message}\n`);
|
|
176
|
-
process.exit(1);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
main();
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// extractors/gemini.mjs
|
|
4
|
+
// Navigate gemini.google.com/app, submit query, wait for answer, return clean answer + sources.
|
|
5
|
+
//
|
|
6
|
+
// Usage:
|
|
7
|
+
// node extractors/gemini.mjs "<query>" [--tab <prefix>]
|
|
8
|
+
//
|
|
9
|
+
// Output (stdout): JSON { answer, sources, query, url }
|
|
10
|
+
// Errors go to stderr only — stdout is always clean JSON for piping.
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
cdp,
|
|
14
|
+
formatAnswer,
|
|
15
|
+
getOrOpenTab,
|
|
16
|
+
handleError,
|
|
17
|
+
injectClipboardInterceptor,
|
|
18
|
+
outputJson,
|
|
19
|
+
parseArgs,
|
|
20
|
+
parseSourcesFromMarkdown,
|
|
21
|
+
validateQuery,
|
|
22
|
+
} from "./common.mjs";
|
|
23
|
+
import { dismissConsent, handleVerification } from "./consent.mjs";
|
|
24
|
+
import { SELECTORS } from "./selectors.mjs";
|
|
25
|
+
|
|
26
|
+
const S = SELECTORS.gemini;
|
|
27
|
+
const GLOBAL_VAR = "__geminiClipboard";
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Gemini-specific helpers
|
|
31
|
+
// ============================================================================
|
|
32
|
+
|
|
33
|
+
async function typeIntoGemini(tab, text) {
|
|
34
|
+
await cdp([
|
|
35
|
+
"eval",
|
|
36
|
+
tab,
|
|
37
|
+
`
|
|
38
|
+
(function(t) {
|
|
39
|
+
var el = document.querySelector('${S.input}');
|
|
40
|
+
if (!el) return false;
|
|
41
|
+
el.focus();
|
|
42
|
+
document.execCommand('insertText', false, t);
|
|
43
|
+
return true;
|
|
44
|
+
})(${JSON.stringify(text)})
|
|
45
|
+
`,
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function waitForCopyButton(tab, timeout = 120000) {
|
|
50
|
+
const deadline = Date.now() + timeout;
|
|
51
|
+
while (Date.now() < deadline) {
|
|
52
|
+
await new Promise((r) => setTimeout(r, 600));
|
|
53
|
+
const found = await cdp([
|
|
54
|
+
"eval",
|
|
55
|
+
tab,
|
|
56
|
+
`!!document.querySelector('${S.copyButton}')`,
|
|
57
|
+
]).catch(() => "false");
|
|
58
|
+
if (found === "true") return;
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`Gemini copy button did not appear within ${timeout}ms`);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function extractAnswer(tab) {
|
|
64
|
+
await cdp([
|
|
65
|
+
"eval",
|
|
66
|
+
tab,
|
|
67
|
+
`document.querySelector('${S.copyButton}')?.click()`,
|
|
68
|
+
]);
|
|
69
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
70
|
+
|
|
71
|
+
const answer = await cdp(["eval", tab, `window.${GLOBAL_VAR} || ''`]);
|
|
72
|
+
if (!answer) throw new Error("Clipboard interceptor returned empty text");
|
|
73
|
+
|
|
74
|
+
const sources = parseSourcesFromMarkdown(answer);
|
|
75
|
+
return { answer: answer.trim(), sources };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ============================================================================
|
|
79
|
+
// Main
|
|
80
|
+
// ============================================================================
|
|
81
|
+
|
|
82
|
+
const USAGE = 'Usage: node extractors/gemini.mjs "<query>" [--tab <prefix>]\n';
|
|
83
|
+
|
|
84
|
+
async function main() {
|
|
85
|
+
const args = process.argv.slice(2);
|
|
86
|
+
validateQuery(args, USAGE);
|
|
87
|
+
|
|
88
|
+
const { query, tabPrefix, short } = parseArgs(args);
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await cdp(["list"]);
|
|
92
|
+
const tab = await getOrOpenTab(tabPrefix);
|
|
93
|
+
|
|
94
|
+
// Each search = fresh conversation
|
|
95
|
+
await cdp(["nav", tab, "https://gemini.google.com/app"], 35000);
|
|
96
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
97
|
+
await dismissConsent(tab, cdp);
|
|
98
|
+
await handleVerification(tab, cdp, 60000);
|
|
99
|
+
|
|
100
|
+
// Wait for input to be ready
|
|
101
|
+
const deadline = Date.now() + 10000;
|
|
102
|
+
while (Date.now() < deadline) {
|
|
103
|
+
const ready = await cdp([
|
|
104
|
+
"eval",
|
|
105
|
+
tab,
|
|
106
|
+
`!!document.querySelector('${S.input}')`,
|
|
107
|
+
]).catch(() => "false");
|
|
108
|
+
if (ready === "true") break;
|
|
109
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
110
|
+
}
|
|
111
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
112
|
+
|
|
113
|
+
await injectClipboardInterceptor(tab, GLOBAL_VAR);
|
|
114
|
+
await typeIntoGemini(tab, query);
|
|
115
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
116
|
+
|
|
117
|
+
await cdp([
|
|
118
|
+
"eval",
|
|
119
|
+
tab,
|
|
120
|
+
`document.querySelector('${S.sendButton}')?.click()`,
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
await waitForCopyButton(tab);
|
|
124
|
+
|
|
125
|
+
const { answer, sources } = await extractAnswer(tab);
|
|
126
|
+
if (!answer) throw new Error("No answer captured from Gemini clipboard");
|
|
127
|
+
|
|
128
|
+
const finalUrl = await cdp(["eval", tab, "document.location.href"]).catch(
|
|
129
|
+
() => "https://gemini.google.com/app",
|
|
130
|
+
);
|
|
131
|
+
outputJson({
|
|
132
|
+
query,
|
|
133
|
+
url: finalUrl,
|
|
134
|
+
answer: formatAnswer(answer, short),
|
|
135
|
+
sources,
|
|
136
|
+
});
|
|
137
|
+
} catch (e) {
|
|
138
|
+
handleError(e);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
main();
|