@apmantza/greedysearch-pi 1.6.2 → 1.6.3

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/coding-task.mjs DELETED
@@ -1,392 +0,0 @@
1
- #!/usr/bin/env node
2
- // coding-task.mjs — delegate a coding task to Gemini or Copilot via browser CDP
3
- //
4
- // Usage:
5
- // node coding-task.mjs "<task>" --engine gemini|copilot [--tab <prefix>]
6
- // node coding-task.mjs "<task>" --engine gemini --context "<code snippet>"
7
- // node coding-task.mjs all "<task>" — run both engines in parallel
8
- //
9
- // Output (stdout): JSON { engine, task, code: [{language, code}], explanation, raw }
10
- // Errors go to stderr only.
11
-
12
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
13
- import { tmpdir } from "node:os";
14
- import { fileURLToPath } from "node:url";
15
- import { cdp, injectClipboardInterceptor } from "./extractors/common.mjs";
16
- import { dismissConsent, handleVerification } from "./extractors/consent.mjs";
17
-
18
- const __dir = fileURLToPath(new URL(".", import.meta.url));
19
- const PAGES_CACHE = `${tmpdir().replace(/\\/g, "/")}/cdp-pages.json`;
20
-
21
- // Target the dedicated GreedySearch Chrome instance (port 9222)
22
- const GREEDY_PROFILE_DIR = `${tmpdir().replace(/\\/g, "/")}/greedysearch-chrome-profile`;
23
- process.env.CDP_PROFILE_DIR = GREEDY_PROFILE_DIR;
24
-
25
- // Mode system prompts — prepended to the user's task
26
- const MODE_PROMPTS = {
27
- code: null, // no preamble — default behaviour
28
- review: `You are a senior software engineer doing a thorough code review. Analyse the code below for: correctness and edge cases, security issues, performance problems, readability and naming, missing error handling, and anything that would not survive a production incident. Be specific — cite line-level issues where relevant. Suggest concrete fixes, not vague advice.`,
29
- plan: `You are a senior software architect. The user will describe something they want to build and their current plan. Your job is to: (1) identify risks, gaps, and hidden assumptions in the plan, (2) flag anything that will cause pain later (scaling, ops, security, maintainability), (3) suggest better alternatives where the plan is suboptimal, (4) call out what's missing entirely. Be direct and opinionated — the goal is to find problems before they're built.`,
30
- test: `You are a senior engineer writing tests for code written by someone else. Your goal is to find what they missed. Write a comprehensive test suite that covers: edge cases the author likely didn't think of, boundary conditions (empty input, nulls, max values, type coercion), error paths and exception handling, concurrency or ordering issues if relevant, and any behaviour that differs from what the function name implies. Use the same language and testing framework as the code if apparent, otherwise default to the most common one for that language. Output runnable test code — not a list of what to test.`,
31
- debug: `You are a senior engineer debugging someone else's code. You have fresh eyes — no prior assumptions about what should work. Given the bug description and relevant code: (1) identify the most likely root cause, being specific about the exact line or condition, (2) explain why it manifests the way it does, (3) suggest the minimal fix, (4) flag any other latent bugs you notice while reading. Do not guess vaguely — reason from the code. If you need information that isn't provided, say exactly what you'd add to narrow it down.`,
32
- };
33
-
34
- const STREAM_POLL_INTERVAL = 800;
35
- const STREAM_STABLE_ROUNDS = 4;
36
- const STREAM_TIMEOUT = 120000; // coding tasks take longer
37
- const MIN_RESPONSE_LENGTH = 50;
38
-
39
- // ---------------------------------------------------------------------------
40
- // Tab management
41
- // ---------------------------------------------------------------------------
42
-
43
- async function getAnyTab() {
44
- const list = await cdp(["list"]);
45
- return list.split("\n")[0].slice(0, 8);
46
- }
47
-
48
- async function openNewTab() {
49
- const anchor = await getAnyTab();
50
- const raw = await cdp([
51
- "evalraw",
52
- anchor,
53
- "Target.createTarget",
54
- '{"url":"about:blank"}',
55
- ]);
56
- const { targetId } = JSON.parse(raw);
57
- await cdp(["list"]); // refresh cache so cdp nav can find the new tab
58
- return targetId.slice(0, 8);
59
- }
60
-
61
- // ---------------------------------------------------------------------------
62
- // Engine implementations
63
- // ---------------------------------------------------------------------------
64
-
65
- const ENGINES = {
66
- gemini: {
67
- url: "https://gemini.google.com/app",
68
- domain: "gemini.google.com",
69
-
70
- async type(tab, text) {
71
- await cdp([
72
- "eval",
73
- tab,
74
- `
75
- (function(t) {
76
- var el = document.querySelector('rich-textarea .ql-editor');
77
- el.focus();
78
- document.execCommand('insertText', false, t);
79
- })(${JSON.stringify(text)})
80
- `,
81
- ]);
82
- },
83
-
84
- async send(tab) {
85
- await cdp([
86
- "eval",
87
- tab,
88
- `document.querySelector('button[aria-label*="Send"]')?.click()`,
89
- ]);
90
- },
91
-
92
- async waitReady(tab) {
93
- const deadline = Date.now() + 12000;
94
- while (Date.now() < deadline) {
95
- const ok = await cdp([
96
- "eval",
97
- tab,
98
- `!!document.querySelector('rich-textarea .ql-editor')`,
99
- ]).catch(() => "false");
100
- if (ok === "true") return;
101
- await new Promise((r) => setTimeout(r, 400));
102
- }
103
- throw new Error("Gemini input never appeared");
104
- },
105
-
106
- async waitForCopyButton(tab) {
107
- const deadline = Date.now() + STREAM_TIMEOUT;
108
- while (Date.now() < deadline) {
109
- await new Promise((r) => setTimeout(r, STREAM_POLL_INTERVAL));
110
- const found = await cdp([
111
- "eval",
112
- tab,
113
- `!!document.querySelector('button[aria-label="Copy"]')`,
114
- ]).catch(() => "false");
115
- if (found === "true") return;
116
- }
117
- throw new Error("Gemini copy button did not appear within timeout");
118
- },
119
-
120
- async extract(tab) {
121
- // Click copy button → clipboard interceptor captures the markdown
122
- await cdp([
123
- "eval",
124
- tab,
125
- `document.querySelector('button[aria-label="Copy"]')?.click()`,
126
- ]);
127
- await new Promise((r) => setTimeout(r, 400));
128
- return cdp(["eval", tab, "window.__codingTaskClipboard || ''"]);
129
- },
130
- },
131
-
132
- copilot: {
133
- url: "https://copilot.microsoft.com/",
134
- domain: "copilot.microsoft.com",
135
-
136
- async type(tab, text) {
137
- await cdp(["click", tab, "#userInput"]);
138
- await new Promise((r) => setTimeout(r, 300));
139
- await cdp(["type", tab, text]);
140
- },
141
-
142
- async send(tab) {
143
- await cdp([
144
- "eval",
145
- tab,
146
- `document.querySelector('#userInput')?.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',bubbles:true,keyCode:13})), 'ok'`,
147
- ]);
148
- },
149
-
150
- async waitReady(tab) {
151
- const deadline = Date.now() + 10000;
152
- while (Date.now() < deadline) {
153
- const ok = await cdp([
154
- "eval",
155
- tab,
156
- `!!document.querySelector('#userInput')`,
157
- ]).catch(() => "false");
158
- if (ok === "true") return;
159
- await new Promise((r) => setTimeout(r, 400));
160
- }
161
- throw new Error("Copilot input never appeared");
162
- },
163
-
164
- async waitForCopyButton(tab) {
165
- const deadline = Date.now() + STREAM_TIMEOUT;
166
- while (Date.now() < deadline) {
167
- await new Promise((r) => setTimeout(r, STREAM_POLL_INTERVAL));
168
- const found = await cdp([
169
- "eval",
170
- tab,
171
- `!!document.querySelector('button[data-testid="copy-ai-message-button"]')`,
172
- ]).catch(() => "false");
173
- if (found === "true") return;
174
- }
175
- throw new Error("Copilot copy button did not appear within timeout");
176
- },
177
-
178
- async extract(tab) {
179
- // Click copy button → clipboard interceptor captures the markdown
180
- await cdp([
181
- "eval",
182
- tab,
183
- `document.querySelector('button[data-testid="copy-ai-message-button"]')?.click()`,
184
- ]);
185
- await new Promise((r) => setTimeout(r, 400));
186
- return cdp(["eval", tab, "window.__codingTaskClipboard || ''"]);
187
- },
188
- },
189
- };
190
-
191
- // ---------------------------------------------------------------------------
192
- // Code extraction
193
- // ---------------------------------------------------------------------------
194
-
195
- function extractCodeBlocks(text) {
196
- const blocks = [];
197
- const regex = /```(\w+)?\n([\s\S]*?)```/g;
198
- let match = regex.exec(text);
199
- while (match !== null) {
200
- blocks.push({ language: match[1] || "text", code: match[2].trim() });
201
- match = regex.exec(text);
202
- }
203
- // If no fenced blocks, look for indented blocks as fallback
204
- if (blocks.length === 0) {
205
- const lines = text.split("\n");
206
- const indented = lines
207
- .filter((l) => l.startsWith(" "))
208
- .map((l) => l.slice(4));
209
- if (indented.length > 3)
210
- blocks.push({ language: "text", code: indented.join("\n") });
211
- }
212
- return blocks;
213
- }
214
-
215
- function extractExplanation(text, _codeBlocks) {
216
- // Remove code blocks from text to get the explanation
217
- let explanation = text.replace(/```[\s\S]*?```/g, "").trim();
218
- explanation = explanation.replace(/\n{3,}/g, "\n\n").trim();
219
- return explanation.slice(0, 1000); // cap explanation at 1000 chars
220
- }
221
-
222
- async function runEngine(engineName, task, context, mode, tabPrefix) {
223
- const engine = ENGINES[engineName];
224
- if (!engine) throw new Error(`Unknown engine: ${engineName}`);
225
-
226
- // Find or open a tab
227
- let tab = tabPrefix;
228
- if (!tab) {
229
- if (existsSync(PAGES_CACHE)) {
230
- const pages = JSON.parse(readFileSync(PAGES_CACHE, "utf8"));
231
- const existing = pages.find((p) => p.url.includes(engine.domain));
232
- if (existing) tab = existing.targetId.slice(0, 8);
233
- }
234
- if (!tab) tab = await openNewTab();
235
- }
236
-
237
- // Navigate to fresh conversation — fall back to new tab if cached tab is stale
238
- try {
239
- await cdp(["nav", tab, engine.url], 35000);
240
- } catch (e) {
241
- if (e.message.includes("No target matching")) {
242
- tab = await openNewTab();
243
- await cdp(["nav", tab, engine.url], 35000);
244
- } else throw e;
245
- }
246
- await new Promise((r) => setTimeout(r, 2000));
247
- await dismissConsent(tab, cdp);
248
- await handleVerification(tab, cdp, 60000);
249
- await engine.waitReady(tab);
250
- await new Promise((r) => setTimeout(r, 300));
251
-
252
- // Inject clipboard interceptor to capture markdown when copy button clicked
253
- await injectClipboardInterceptor(tab, "__codingTaskClipboard");
254
-
255
- // Build the prompt
256
- const preamble = MODE_PROMPTS[mode] || null;
257
- const body = context
258
- ? `${task}\n\nHere is the relevant code/context:\n\`\`\`\n${context}\n\`\`\``
259
- : task;
260
- const prompt = preamble ? `${preamble}\n\n---\n\n${body}` : body;
261
-
262
- await engine.type(tab, prompt);
263
- await new Promise((r) => setTimeout(r, 400));
264
- await engine.send(tab);
265
- await engine.waitForCopyButton(tab);
266
-
267
- const raw = await engine.extract(tab);
268
- if (!raw) throw new Error(`No response from ${engineName}`);
269
-
270
- const code = extractCodeBlocks(raw);
271
- const explanation = extractExplanation(raw, code);
272
- const url = await cdp(["eval", tab, "document.location.href"]).catch(
273
- () => engine.url,
274
- );
275
-
276
- return { engine: engineName, task, code, explanation, raw, url };
277
- }
278
-
279
- // ---------------------------------------------------------------------------
280
- // Main
281
- // ---------------------------------------------------------------------------
282
-
283
- async function main() {
284
- const args = process.argv.slice(2);
285
- if (!args.length || args[0] === "--help") {
286
- process.stderr.write(
287
- `${[
288
- 'Usage: node coding-task.mjs "<task>" --engine gemini|copilot|all [--mode code|review|plan]',
289
- ' node coding-task.mjs "<task>" --engine gemini --context "<code>"',
290
- "",
291
- "Modes:",
292
- " code (default) — write or modify code",
293
- " review — senior engineer code review: correctness, security, performance",
294
- " plan — architect review: risks, gaps, alternatives for a build plan",
295
- " test — write tests an author would miss: edge cases, error paths, boundary conditions",
296
- " debug — fresh-eyes root cause analysis: exact line, why it manifests, minimal fix",
297
- "",
298
- "Examples:",
299
- ' node coding-task.mjs "write a debounce function in JS" --engine gemini',
300
- ' node coding-task.mjs "review this module" --mode review --engine all --file src/myfile.mjs',
301
- ' node coding-task.mjs "debug this" --mode debug --engine all --file a.mjs --file b.mjs',
302
- ' node coding-task.mjs "I want to build X, here is my plan: ..." --mode plan --engine all',
303
- ].join("\n")}\n`,
304
- );
305
- process.exit(1);
306
- }
307
-
308
- const engineFlagIdx = args.indexOf("--engine");
309
- const engineArg = engineFlagIdx !== -1 ? args[engineFlagIdx + 1] : "gemini";
310
- const contextFlagIdx = args.indexOf("--context");
311
- const outIdx = args.indexOf("--out");
312
- const outFile = outIdx !== -1 ? args[outIdx + 1] : null;
313
- const tabFlagIdx = args.indexOf("--tab");
314
- const tabPrefix = tabFlagIdx !== -1 ? args[tabFlagIdx + 1] : null;
315
- const modeFlagIdx = args.indexOf("--mode");
316
- const mode = modeFlagIdx !== -1 ? args[modeFlagIdx + 1] : "code";
317
-
318
- if (!Object.hasOwn(MODE_PROMPTS, mode)) {
319
- process.stderr.write(
320
- `Error: unknown mode "${mode}". Use: code, review, plan, test, debug\n`,
321
- );
322
- process.exit(1);
323
- }
324
-
325
- // --file can be repeated: --file a.mjs --file b.mjs
326
- const fileIndices = [];
327
- const filePaths = [];
328
- for (let i = 0; i < args.length; i++) {
329
- if (args[i] === "--file" && args[i + 1]) {
330
- fileIndices.push(i, i + 1);
331
- filePaths.push(args[i + 1]);
332
- }
333
- }
334
- const fileContext =
335
- filePaths.length > 0
336
- ? filePaths
337
- .map((p) => `// FILE: ${p}\n${readFileSync(p, "utf8")}`)
338
- .join("\n\n")
339
- : null;
340
- const context =
341
- fileContext || (contextFlagIdx !== -1 ? args[contextFlagIdx + 1] : null);
342
-
343
- const skipFlags = new Set([
344
- ...(engineFlagIdx >= 0 ? [engineFlagIdx, engineFlagIdx + 1] : []),
345
- ...(contextFlagIdx >= 0 ? [contextFlagIdx, contextFlagIdx + 1] : []),
346
- ...(outIdx >= 0 ? [outIdx, outIdx + 1] : []),
347
- ...(tabFlagIdx >= 0 ? [tabFlagIdx, tabFlagIdx + 1] : []),
348
- ...(modeFlagIdx >= 0 ? [modeFlagIdx, modeFlagIdx + 1] : []),
349
- ...fileIndices,
350
- ]);
351
- const task = args.filter((_, i) => !skipFlags.has(i)).join(" ");
352
-
353
- if (!task) {
354
- process.stderr.write("Error: no task provided\n");
355
- process.exit(1);
356
- }
357
-
358
- await cdp(["list"]); // ensure Chrome is reachable
359
-
360
- let result;
361
-
362
- if (engineArg === "all") {
363
- const results = await Promise.allSettled(
364
- Object.keys(ENGINES).map((e) => runEngine(e, task, context, mode, null)),
365
- );
366
- result = {};
367
- for (const [i, r] of results.entries()) {
368
- const name = Object.keys(ENGINES)[i];
369
- result[name] =
370
- r.status === "fulfilled"
371
- ? r.value
372
- : { engine: name, error: r.reason?.message };
373
- }
374
- } else {
375
- try {
376
- result = await runEngine(engineArg, task, context, mode, tabPrefix);
377
- } catch (e) {
378
- process.stderr.write(`Error: ${e.message}\n`);
379
- process.exit(1);
380
- }
381
- }
382
-
383
- const json = `${JSON.stringify(result, null, 2)}\n`;
384
- if (outFile) {
385
- writeFileSync(outFile, json, "utf8");
386
- process.stderr.write(`Results written to ${outFile}\n`);
387
- } else {
388
- process.stdout.write(json);
389
- }
390
- }
391
-
392
- main();
@@ -1,167 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // extractors/bing-copilot.mjs
4
- // Navigate copilot.microsoft.com, wait for answer to complete, return clean answer + sources.
5
- //
6
- // Usage:
7
- // node extractors/bing-copilot.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.bing;
27
- const GLOBAL_VAR = "__bingClipboard";
28
-
29
- // ============================================================================
30
- // Bing Copilot-specific helpers
31
- // ============================================================================
32
-
33
- async function waitForCopyButton(tab, timeout = 60000) {
34
- const deadline = Date.now() + timeout;
35
- while (Date.now() < deadline) {
36
- await new Promise((r) => setTimeout(r, 700));
37
- const found = await cdp([
38
- "eval",
39
- tab,
40
- `!!document.querySelector('${S.copyButton}')`,
41
- ]).catch(() => "false");
42
- if (found === "true") return;
43
- }
44
- throw new Error(`Copilot copy button did not appear within ${timeout}ms`);
45
- }
46
-
47
- async function extractAnswer(tab) {
48
- await cdp([
49
- "eval",
50
- tab,
51
- `document.querySelector('${S.copyButton}')?.click()`,
52
- ]);
53
- await new Promise((r) => setTimeout(r, 400));
54
-
55
- const answer = await cdp(["eval", tab, `window.${GLOBAL_VAR} || ''`]);
56
- if (!answer) throw new Error("Clipboard interceptor returned empty text");
57
-
58
- const sources = parseSourcesFromMarkdown(answer);
59
- return { answer: answer.trim(), sources };
60
- }
61
-
62
- // ============================================================================
63
- // Main
64
- // ============================================================================
65
-
66
- const USAGE =
67
- 'Usage: node extractors/bing-copilot.mjs "<query>" [--tab <prefix>]\n';
68
-
69
- async function main() {
70
- const args = process.argv.slice(2);
71
- validateQuery(args, USAGE);
72
-
73
- const { query, tabPrefix, short } = parseArgs(args);
74
-
75
- try {
76
- await cdp(["list"]);
77
- const tab = await getOrOpenTab(tabPrefix);
78
-
79
- // Navigate to Copilot homepage and use the chat input
80
- await cdp(["nav", tab, "https://copilot.microsoft.com/"], 35000);
81
- await new Promise((r) => setTimeout(r, 2000));
82
- await dismissConsent(tab, cdp);
83
-
84
- // Handle verification challenges (Cloudflare Turnstile, Microsoft auth, etc.)
85
- const verifyResult = await handleVerification(tab, cdp, 90000);
86
- if (verifyResult === "needs-human") {
87
- throw new Error(
88
- "Copilot verification required — please solve it manually in the browser window",
89
- );
90
- }
91
-
92
- // After verification, page may have redirected or reloaded — wait for it to settle
93
- if (verifyResult === "clicked") {
94
- await new Promise((r) => setTimeout(r, 3000));
95
-
96
- // Re-navigate if we got redirected
97
- const currentUrl = await cdp([
98
- "eval",
99
- tab,
100
- "document.location.href",
101
- ]).catch(() => "");
102
- if (!currentUrl.includes("copilot.microsoft.com")) {
103
- await cdp(["nav", tab, "https://copilot.microsoft.com/"], 35000);
104
- await new Promise((r) => setTimeout(r, 2000));
105
- await dismissConsent(tab, cdp);
106
- }
107
- }
108
-
109
- // Wait for React app to mount input (up to 15s, longer after verification)
110
- const inputDeadline = Date.now() + 15000;
111
- while (Date.now() < inputDeadline) {
112
- const found = await cdp([
113
- "eval",
114
- tab,
115
- `!!document.querySelector('${S.input}')`,
116
- ]).catch(() => "false");
117
- if (found === "true") break;
118
- await new Promise((r) => setTimeout(r, 500));
119
- }
120
- await new Promise((r) => setTimeout(r, 300));
121
-
122
- // Verify input is actually there before proceeding
123
- const inputReady = await cdp([
124
- "eval",
125
- tab,
126
- `!!document.querySelector('${S.input}')`,
127
- ]).catch(() => "false");
128
- if (inputReady !== "true") {
129
- throw new Error(
130
- "Copilot input not found — verification may have failed or page is in unexpected state",
131
- );
132
- }
133
-
134
- await injectClipboardInterceptor(tab, GLOBAL_VAR);
135
- await cdp(["click", tab, S.input]);
136
- await new Promise((r) => setTimeout(r, 400));
137
- await cdp(["type", tab, query]);
138
- await new Promise((r) => setTimeout(r, 400));
139
-
140
- // Submit with Enter (most reliable across locales and Chrome instances)
141
- await cdp([
142
- "eval",
143
- tab,
144
- `document.querySelector('${S.input}')?.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',bubbles:true,keyCode:13})), 'ok'`,
145
- ]);
146
-
147
- await waitForCopyButton(tab);
148
-
149
- const { answer, sources } = await extractAnswer(tab);
150
- if (!answer)
151
- throw new Error("No answer extracted — Copilot may not have responded");
152
-
153
- const finalUrl = await cdp(["eval", tab, "document.location.href"]).catch(
154
- () => "",
155
- );
156
- outputJson({
157
- query,
158
- url: finalUrl,
159
- answer: formatAnswer(answer, short),
160
- sources,
161
- });
162
- } catch (e) {
163
- handleError(e);
164
- }
165
- }
166
-
167
- main();