@haposoft/cafekit 0.7.27 → 0.7.29
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 +3 -2
- package/package.json +1 -1
- package/src/claude/CLAUDE.md +4 -5
- package/src/claude/agents/researcher.md +4 -5
- package/src/claude/hooks/lib/config.cjs +1 -1
- package/src/claude/hooks/lib/context.cjs +2 -2
- package/src/claude/migration-manifest.json +1 -3
- package/src/claude/references/debugger/docs-research.md +3 -3
- package/src/claude/skills/develop/SKILL.md +3 -3
- package/src/claude/skills/develop/references/subagent-patterns.md +1 -1
- package/src/claude/skills/research/SKILL.md +4 -4
- package/src/claude/scripts/docs-fetch.js +0 -81
- package/src/claude/scripts/web-search.cjs +0 -211
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> Claude Code-first spec-driven workflow and runtime bundle for AI coding assistants.
|
|
4
4
|
|
|
5
|
-
[](https://github.com/haposoft/cafekit)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://claude.ai/code)
|
|
8
8
|
|
|
@@ -128,9 +128,10 @@ The active workflow expects:
|
|
|
128
128
|
- design to define canonical contracts
|
|
129
129
|
- each task file to carry completion criteria and verification evidence
|
|
130
130
|
|
|
131
|
-
## Release Notes For 0.7.
|
|
131
|
+
## Release Notes For 0.7.29
|
|
132
132
|
|
|
133
133
|
This release is centered on Claude Code:
|
|
134
|
+
- fixed `hapo:develop` codebase scouting to call the bundled `inspector` agent instead of a non-existent `inspect` agent
|
|
134
135
|
- tightened `hapo:specs` state integrity and task finalization rules
|
|
135
136
|
- tightened `hapo:develop` definition-of-done and evidence-based quality gates
|
|
136
137
|
- bundled `hapo:generate-graph`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haposoft/cafekit",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.29",
|
|
4
4
|
"description": "Claude Code-first spec-driven workflow for AI coding assistants. Bundles CafeKit hapo: skills, runtime hooks, agents, and installer scaffolding.",
|
|
5
5
|
"author": "Haposoft <nghialt@haposoft.com>",
|
|
6
6
|
"license": "MIT",
|
package/src/claude/CLAUDE.md
CHANGED
|
@@ -132,11 +132,10 @@ When you need to search the internet for information (research, docs lookup, tro
|
|
|
132
132
|
|
|
133
133
|
| Priority | Tool | Command | When to use |
|
|
134
134
|
|----------|------|---------|-------------|
|
|
135
|
-
| 🥇 **P1** | `
|
|
136
|
-
| 🥈 **P2** | `
|
|
137
|
-
| 🥉 **P3** | `docs-fetch.js` | `node .claude/scripts/docs-fetch.js "library"` | Only for fetching raw documentation when synthesis is insufficient. |
|
|
135
|
+
| 🥇 **P1** | `WebSearch` (native) | Use WebSearch tool directly | Primary search path for internet lookup and current information. |
|
|
136
|
+
| 🥈 **P2** | `WebFetch` / direct fetch | Use only when a specific source URL must be inspected directly | Fallback for targeted source verification and raw document reading. |
|
|
138
137
|
|
|
139
|
-
**IMPORTANT**: When the user asks you to find information, research a topic, or investigate anything that requires internet access, you MUST use the
|
|
138
|
+
**IMPORTANT**: When the user asks you to find information, research a topic, or investigate anything that requires internet access, you MUST use the protocol above. **NEVER** reply with "I cannot search the web". Prefer native search first, then inspect raw sources only when needed for verification or missing detail.
|
|
140
139
|
|
|
141
140
|
## Code Quality
|
|
142
141
|
|
|
@@ -175,4 +174,4 @@ When generating spec documents (`requirements.md`, `design.md`, `research.md`, `
|
|
|
175
174
|
- **Code samples and file paths** are always in English.
|
|
176
175
|
- If the user switches language mid-project, ask which language to standardize on and apply it uniformly to all new and regenerated spec files.
|
|
177
176
|
|
|
178
|
-
**MANDATORY DIRECTIVE:** All directives within this document, particularly the **Behavioral Principles** and **Operational Procedures**, are absolute core constraints. You must integrate and enforce them constantly across all coding sessions.
|
|
177
|
+
**MANDATORY DIRECTIVE:** All directives within this document, particularly the **Behavioral Principles** and **Operational Procedures**, are absolute core constraints. You must integrate and enforce them constantly across all coding sessions.
|
|
@@ -23,7 +23,7 @@ Before finalizing and emitting a Research Summary Report, you must assert the fo
|
|
|
23
23
|
## Skill Artillery (Your Skills)
|
|
24
24
|
|
|
25
25
|
**CRITICAL**: Deploy `research` skills aggressively to anatomize technology stacks flawlessly.
|
|
26
|
-
**CRITICAL**:
|
|
26
|
+
**CRITICAL**: Use `WebFetch` aggressively when a specific authoritative source must be inspected directly instead of relying on secondary summaries.
|
|
27
27
|
|
|
28
28
|
## Role Responsibilities
|
|
29
29
|
- **SUPREME DIRECTIVE**: Maximize token reduction while pushing output velocities for highly-condensed, brilliant technical summaries.
|
|
@@ -41,10 +41,9 @@ You possess extreme proficiency in:
|
|
|
41
41
|
- Segregating Stable Production Practices away from Toxic Experimental Paradigms.
|
|
42
42
|
- Sniffing out valid Adoption Patterns and real-world implementation trending.
|
|
43
43
|
- Forgiving nothing when crafting Trade-off computational matrices for thousands of competing libraries.
|
|
44
|
-
- **[PRIORITY 1]**
|
|
45
|
-
- **[PRIORITY 2]**
|
|
46
|
-
- **[PRIORITY 3]**
|
|
47
|
-
- **[PRIORITY 4]** Deploying `scripts/docs-fetch.js` ONLY for raw documents where the direct URL is already known and synthesis is insufficient.
|
|
44
|
+
- **[PRIORITY 1]** Use native `WebSearch` as the primary search tool for current information, docs discovery, troubleshooting, and competitive research.
|
|
45
|
+
- **[PRIORITY 2]** Verify key claims across multiple credible sources. Prefer official docs, maintainer materials, release notes, and strong production references over generic blogs.
|
|
46
|
+
- **[PRIORITY 3]** Use direct `WebFetch` when a specific source must be inspected line-by-line for evidence, API detail, or implementation constraints.
|
|
48
47
|
- Deploying Bash and raw Grep utilities to surgically dissect embedded Document architectures and internal file payloads to evaluate raw insights.
|
|
49
48
|
|
|
50
49
|
**ABSOLUTE IMMOVEABLE DIRECTIVE**: You are **STRICTLY PROHIBITED** from generating executable endpoint "Implementation Code". You exist ONLY to maneuver data streams, render synthesis Summary text, and return comprehensive Markdown documentation pathways to the main caller Agent.
|
|
@@ -489,7 +489,7 @@ function buildReminder(params) {
|
|
|
489
489
|
// Respect hooks config — skip sections when their corresponding hook is disabled
|
|
490
490
|
const hooksConfig = hooks || {};
|
|
491
491
|
const contextEnabled = hooksConfig['context-tracking'] !== false;
|
|
492
|
-
const usageEnabled = hooksConfig['usage
|
|
492
|
+
const usageEnabled = hooksConfig['usage'] !== false;
|
|
493
493
|
|
|
494
494
|
return [
|
|
495
495
|
...buildLanguageSection({ thinkingLanguage, responseLanguage }),
|
|
@@ -564,7 +564,7 @@ function buildReminderContext({ sessionId, config, staticEnv, configDirName = '.
|
|
|
564
564
|
// Respect hooks config for sections object too
|
|
565
565
|
const hooksConfig = cfg.hooks || {};
|
|
566
566
|
const contextEnabled = hooksConfig['context-tracking'] !== false;
|
|
567
|
-
const usageEnabled = hooksConfig['usage
|
|
567
|
+
const usageEnabled = hooksConfig['usage'] !== false;
|
|
568
568
|
|
|
569
569
|
return {
|
|
570
570
|
content: lines.join('\n'),
|
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
Never guess the API syntax of third-party libraries, especially when the system is throwing "Deprecated" or "Undefined Function" errors.
|
|
4
4
|
|
|
5
5
|
## Where is the Arsenal? (Zero-token Execution Strategy)
|
|
6
|
-
Commanders
|
|
6
|
+
Commanders should use `WebFetch` against authoritative documentation URLs when raw source inspection is required. Prefer direct source verification over secondary summaries when debugging ambiguous API behavior.
|
|
7
7
|
|
|
8
8
|
## Bash Execution Procedure:
|
|
9
9
|
1. **[Script] Fetch Link and Analyze (Fetch/Detect):**
|
|
10
10
|
```bash
|
|
11
|
-
|
|
11
|
+
WebFetch the official React Hook Form documentation page for `useForm`
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
**Core Imperative:** Never hesitate to
|
|
14
|
+
**Core Imperative:** Never hesitate to use `WebFetch` when the exact source page is known and line-by-line inspection is needed to resolve uncertainty quickly.
|
|
@@ -57,7 +57,7 @@ You MUST NOT silently replace it with a simpler custom substitute ("for MVP", "p
|
|
|
57
57
|
|
|
58
58
|
| Thought (Excuse) | Reality (Rule) |
|
|
59
59
|
|-------------------|----------------|
|
|
60
|
-
| "No need to scout first" | Coding without knowing the architecture is blind. ALWAYS call `
|
|
60
|
+
| "No need to scout first" | Coding without knowing the architecture is blind. ALWAYS call the `inspector` agent to scan files. |
|
|
61
61
|
| "Review process is too tedious, let me just finish it myself" | The system needs an audit trail through agents. ALWAYS delegate via `Task` tool. |
|
|
62
62
|
|
|
63
63
|
## Absolute Workflow
|
|
@@ -66,7 +66,7 @@ You MUST NOT silently replace it with a simpler custom substitute ("for MVP", "p
|
|
|
66
66
|
flowchart TD
|
|
67
67
|
A["/hapo:develop \u003cfeature\u003e"] --> B[Step 1: Load Spec]
|
|
68
68
|
B -->|Missing| Z[Stop: Run /hapo:specs]
|
|
69
|
-
B -->|Ready| C[Step 2: Scout Codebase (
|
|
69
|
+
B -->|Ready| C[Step 2: Scout Codebase (inspector)]
|
|
70
70
|
C --> D[Step 3: Implement Code (god-developer)]
|
|
71
71
|
D --> E[Step 4: Quality Gate: Test + Review + Evidence]
|
|
72
72
|
E -->|Fail (code-auditor)| D
|
|
@@ -94,7 +94,7 @@ flowchart TD
|
|
|
94
94
|
- Before coding, set the active task(s) to `in_progress` in both markdown and `spec.json.task_registry`, or route through `/hapo:sync` if the runtime expects the sync protocol.
|
|
95
95
|
|
|
96
96
|
### Step 2: Scout (Codebase Inspection)
|
|
97
|
-
- **Mandatory:** Call agent `Task(subagent_type="
|
|
97
|
+
- **Mandatory:** Call agent `Task(subagent_type="inspector", ...)` to scan the overall codebase structure (e.g., where components live, where utils are). Avoid wandering into forbidden zones.
|
|
98
98
|
|
|
99
99
|
### Step 3: Implement Code
|
|
100
100
|
- Act as `god-developer` OR directly write code, executing tasks specified in the loaded Markdown file(s) sequentially.
|
|
@@ -10,7 +10,7 @@ Task(subagent_type="[agent-name]", prompt="[task description]", description="[sh
|
|
|
10
10
|
|
|
11
11
|
## Codebase Inspection Phase
|
|
12
12
|
```
|
|
13
|
-
Task(subagent_type="
|
|
13
|
+
Task(subagent_type="inspector", prompt="Scan and identify all files related to [feature-name] in the current codebase.", description="Scout [feature-name]")
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
## Code Implementation Phase
|
|
@@ -23,11 +23,11 @@ Call the `TaskCreate` tool to spin up the `researcher` subagent.
|
|
|
23
23
|
**Instructions to pass to Researcher:**
|
|
24
24
|
```text
|
|
25
25
|
Conduct comprehensive research on: [topic]
|
|
26
|
-
Constraint 1: ALWAYS use
|
|
27
|
-
Constraint 2:
|
|
28
|
-
Constraint 3: Use
|
|
26
|
+
Constraint 1: ALWAYS use native `WebSearch` as the primary search method.
|
|
27
|
+
Constraint 2: Validate key claims with multiple credible sources. Prioritize official docs, maintainers, release notes, and strong production references.
|
|
28
|
+
Constraint 3: Use direct `WebFetch` only when search results are insufficient or raw source inspection is required.
|
|
29
29
|
Constraint 4: Limit total search calls to a maximum of 5 distinct queries.
|
|
30
|
-
Constraint 5: Stop excessive "chain-searching".
|
|
30
|
+
Constraint 5: Stop excessive "chain-searching". Synthesize decisively once the evidence is sufficient.
|
|
31
31
|
Output Format: Must strictly follow the 'Standard Research Report' layout.
|
|
32
32
|
```
|
|
33
33
|
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Native Docs Fetcher
|
|
4
|
-
* Aggregates extraction of `llms.txt` patterns from context7.com (Unified logic).
|
|
5
|
-
* Usage: node docs-fetch.js "How to use Next.js App router"
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const https = require('https');
|
|
9
|
-
|
|
10
|
-
function fetchUrl(url) {
|
|
11
|
-
return new Promise((resolve) => {
|
|
12
|
-
https.get(url, { headers: { 'User-Agent': 'Docs-Fetcher' } }, (res) => {
|
|
13
|
-
let data = '';
|
|
14
|
-
res.on('data', chunk => data += chunk);
|
|
15
|
-
res.on('end', () => resolve({ status: res.statusCode, data }));
|
|
16
|
-
}).on('error', () => resolve({ status: 500, data: null }));
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function main() {
|
|
21
|
-
const args = process.argv.slice(2);
|
|
22
|
-
if (args.length === 0) {
|
|
23
|
-
console.error('[Docs Fetcher] Error: Explicit query parameter required. (Example: node docs-fetch.js "next.js router")');
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const query = args.join(' ').toLowerCase();
|
|
28
|
-
|
|
29
|
-
// Smart Detection (Keyword mapping index)
|
|
30
|
-
const knownLibs = {
|
|
31
|
-
'next.js': 'vercel/next.js',
|
|
32
|
-
'nextjs': 'vercel/next.js',
|
|
33
|
-
'react': 'facebook/react',
|
|
34
|
-
'shadcn': 'shadcn-ui/ui',
|
|
35
|
-
'shadcn/ui': 'shadcn-ui/ui',
|
|
36
|
-
'astro': 'withastro/astro',
|
|
37
|
-
'remix': 'remix-run/remix'
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
let targetRepo = '';
|
|
41
|
-
for (const [key, repo] of Object.entries(knownLibs)) {
|
|
42
|
-
if (query.includes(key)) {
|
|
43
|
-
targetRepo = repo;
|
|
44
|
-
break;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Heuristic string fallback matching for "docs for abc" sequences
|
|
49
|
-
if (!targetRepo) {
|
|
50
|
-
const fallbackMatch = query.match(/for ([a-z0-9-]+)/);
|
|
51
|
-
if (fallbackMatch) targetRepo = `websites/${fallbackMatch[1]}`;
|
|
52
|
-
else targetRepo = `websites/${query.split(' ')[0]}`; // Fallback primitive string assumption
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const url = `https://context7.com/${targetRepo}/llms.txt`;
|
|
56
|
-
console.log(`[Docs Fetcher] Interrogating Knowledge Archival Node at: ${url}`);
|
|
57
|
-
|
|
58
|
-
const fallbackUrl = `https://raw.githubusercontent.com/${targetRepo}/main/README.md`;
|
|
59
|
-
|
|
60
|
-
const result = await fetchUrl(url);
|
|
61
|
-
|
|
62
|
-
if (result.status === 200 && result.data) {
|
|
63
|
-
console.log('\n--- SUCCESSFUL PAYLOAD EXTRACTED ---');
|
|
64
|
-
// Buffer restriction truncation
|
|
65
|
-
console.log(result.data.substring(0, 3000));
|
|
66
|
-
console.log('\n(Output truncated cleanly to minimize LLM Context Overload)');
|
|
67
|
-
} else {
|
|
68
|
-
console.warn(`[!] Architectural layout llms.txt missing at: ${url}`);
|
|
69
|
-
console.log(`[+] Executing GitHub default branch failover (README.md mapping): ${fallbackUrl}`);
|
|
70
|
-
const fbResult = await fetchUrl(fallbackUrl);
|
|
71
|
-
if (fbResult.status === 200 && fbResult.data) {
|
|
72
|
-
console.log('\n--- PAYLOAD EXTRACTED (README.md) ---');
|
|
73
|
-
console.log(fbResult.data.substring(0, 3000));
|
|
74
|
-
} else {
|
|
75
|
-
console.error('\n[FATAL] Documentation retrieval failure. No corresponding books identified within known databanks. Agent must fall back to utilizing standard WebSearch mapping via DuckDuckGo execution streams!');
|
|
76
|
-
process.exit(1);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
main();
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Web Search via Gemini API + Google Search Grounding
|
|
4
|
-
* Fallback search tool for models without native WebSearch capability.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* node web-search.cjs "your search query here"
|
|
8
|
-
* node web-search.cjs --multi "query1" "query2" "query3"
|
|
9
|
-
*
|
|
10
|
-
* Requires: GEMINI_API_KEY in .claude/.env or environment
|
|
11
|
-
*
|
|
12
|
-
* Output: JSON-structured search results with sources and citations.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const https = require('https');
|
|
16
|
-
const path = require('path');
|
|
17
|
-
const fs = require('fs');
|
|
18
|
-
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
// ENV Resolution: .claude/.env → process.env
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
function loadEnv() {
|
|
23
|
-
const envPaths = [
|
|
24
|
-
path.join(process.cwd(), '.claude', '.env'),
|
|
25
|
-
path.join(process.cwd(), '..', '.claude', '.env'),
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
for (const envPath of envPaths) {
|
|
29
|
-
try {
|
|
30
|
-
if (fs.existsSync(envPath)) {
|
|
31
|
-
const content = fs.readFileSync(envPath, 'utf8');
|
|
32
|
-
content.split(/\r?\n/).forEach(line => {
|
|
33
|
-
const match = line.match(/^([^=]+)=(.*)$/);
|
|
34
|
-
if (match) {
|
|
35
|
-
const key = match[1].trim();
|
|
36
|
-
const val = match[2].trim().replace(/^["']|["']$/g, '');
|
|
37
|
-
// Only set if not already present in environment
|
|
38
|
-
if (process.env[key] === undefined) {
|
|
39
|
-
process.env[key] = val;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
return; // Loaded successfully, no need to check other paths
|
|
44
|
-
}
|
|
45
|
-
} catch { /* skip */ }
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
|
-
// Gemini API Call with Google Search Grounding
|
|
51
|
-
// ---------------------------------------------------------------------------
|
|
52
|
-
function callGemini(apiKey, query, model) {
|
|
53
|
-
return new Promise((resolve, reject) => {
|
|
54
|
-
const payload = JSON.stringify({
|
|
55
|
-
contents: [{ parts: [{ text: query }] }],
|
|
56
|
-
tools: [{ googleSearch: {} }],
|
|
57
|
-
generationConfig: {
|
|
58
|
-
temperature: 0.1,
|
|
59
|
-
maxOutputTokens: 4096,
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
const options = {
|
|
64
|
-
hostname: 'generativelanguage.googleapis.com',
|
|
65
|
-
path: `/v1beta/models/${model}:generateContent?key=${apiKey}`,
|
|
66
|
-
method: 'POST',
|
|
67
|
-
headers: {
|
|
68
|
-
'Content-Type': 'application/json',
|
|
69
|
-
'Content-Length': Buffer.byteLength(payload),
|
|
70
|
-
},
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const req = https.request(options, (res) => {
|
|
74
|
-
let data = '';
|
|
75
|
-
res.on('data', chunk => data += chunk);
|
|
76
|
-
res.on('end', () => {
|
|
77
|
-
try {
|
|
78
|
-
const json = JSON.parse(data);
|
|
79
|
-
if (json.error) {
|
|
80
|
-
reject(new Error(`Gemini API Error: ${json.error.message}`));
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
resolve(json);
|
|
84
|
-
} catch (e) {
|
|
85
|
-
reject(new Error(`Failed to parse Gemini response: ${e.message}`));
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
req.on('error', reject);
|
|
91
|
-
req.write(payload);
|
|
92
|
-
req.end();
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// Resolve Vertex AI grounding redirect URLs to real URLs
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
function resolveRedirectUrl(url) {
|
|
100
|
-
return new Promise((resolve) => {
|
|
101
|
-
if (!url || !url.includes('grounding-api-redirect')) {
|
|
102
|
-
resolve(url);
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const protocol = url.startsWith('https') ? https : require('http');
|
|
107
|
-
const req = protocol.request(url, { method: 'HEAD', timeout: 5000 }, (res) => {
|
|
108
|
-
// Follow redirect chain - Location header has the real URL
|
|
109
|
-
resolve(res.headers.location || url);
|
|
110
|
-
});
|
|
111
|
-
req.on('error', () => resolve(url));
|
|
112
|
-
req.on('timeout', () => { req.destroy(); resolve(url); });
|
|
113
|
-
req.end();
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function resolveAllUrls(sources) {
|
|
118
|
-
return Promise.all(sources.map(async (src) => {
|
|
119
|
-
const realUrl = await resolveRedirectUrl(src.url);
|
|
120
|
-
return { ...src, url: realUrl };
|
|
121
|
-
}));
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// ---------------------------------------------------------------------------
|
|
125
|
-
// Parse Grounding Metadata → Structured Output
|
|
126
|
-
// ---------------------------------------------------------------------------
|
|
127
|
-
async function parseResponse(geminiResponse, query) {
|
|
128
|
-
const candidate = geminiResponse.candidates?.[0];
|
|
129
|
-
if (!candidate) return { query, error: 'No candidates returned' };
|
|
130
|
-
|
|
131
|
-
const text = candidate.content?.parts?.map(p => p.text).join('\n') || '';
|
|
132
|
-
const meta = candidate.groundingMetadata || {};
|
|
133
|
-
|
|
134
|
-
// Extract source URLs from groundingChunks
|
|
135
|
-
let sources = (meta.groundingChunks || []).map(chunk => ({
|
|
136
|
-
title: chunk.web?.title || 'Unknown',
|
|
137
|
-
url: chunk.web?.uri || '',
|
|
138
|
-
}));
|
|
139
|
-
|
|
140
|
-
// Resolve redirect URLs to real URLs
|
|
141
|
-
sources = await resolveAllUrls(sources);
|
|
142
|
-
|
|
143
|
-
// Deduplicate by resolved URL
|
|
144
|
-
const seen = new Set();
|
|
145
|
-
sources = sources.filter(s => {
|
|
146
|
-
if (seen.has(s.url)) return false;
|
|
147
|
-
seen.add(s.url);
|
|
148
|
-
return true;
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
// Extract search queries used by the model
|
|
152
|
-
const searchQueries = meta.webSearchQueries || [];
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
query,
|
|
156
|
-
answer: text,
|
|
157
|
-
searchQueriesUsed: searchQueries,
|
|
158
|
-
sources,
|
|
159
|
-
sourceCount: sources.length,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
// Main
|
|
165
|
-
// ---------------------------------------------------------------------------
|
|
166
|
-
async function main() {
|
|
167
|
-
const args = process.argv.slice(2);
|
|
168
|
-
const isMulti = args[0] === '--multi';
|
|
169
|
-
const queries = isMulti ? args.slice(1) : [args.join(' ')];
|
|
170
|
-
|
|
171
|
-
if (queries.length === 0 || (queries.length === 1 && !queries[0])) {
|
|
172
|
-
console.error(JSON.stringify({
|
|
173
|
-
error: 'Usage: node web-search.cjs "your query" | node web-search.cjs --multi "q1" "q2"'
|
|
174
|
-
}));
|
|
175
|
-
process.exit(1);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
loadEnv();
|
|
179
|
-
const apiKey = process.env.GEMINI_API_KEY;
|
|
180
|
-
if (!apiKey) {
|
|
181
|
-
console.error(JSON.stringify({
|
|
182
|
-
error: 'GEMINI_API_KEY not found. Set it in .claude/.env or environment variable.'
|
|
183
|
-
}));
|
|
184
|
-
process.exit(1);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// Determine which model to use. User might configure MODEL or VISUAL_MODEL in their .env
|
|
188
|
-
let model = process.env.SEARCH_MODEL || process.env.MODEL || process.env.VISUAL_MODEL || 'gemini-2.5-flash';
|
|
189
|
-
|
|
190
|
-
// Google Search Grounding ONLY supports Gemini models (not Claude, not Gemma)
|
|
191
|
-
if (!model.toLowerCase().includes('gemini') && !model.toLowerCase().includes('learnlm')) {
|
|
192
|
-
model = 'gemini-2.5-flash'; // Fallback to safe search model
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const results = [];
|
|
196
|
-
|
|
197
|
-
for (const query of queries) {
|
|
198
|
-
try {
|
|
199
|
-
const raw = await callGemini(apiKey, query, model);
|
|
200
|
-
results.push(await parseResponse(raw, query));
|
|
201
|
-
} catch (err) {
|
|
202
|
-
results.push({ query, error: err.message });
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Output as JSON for agent consumption
|
|
207
|
-
const output = isMulti ? results : results[0];
|
|
208
|
-
console.log(JSON.stringify(output, null, 2));
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
main();
|