abelworkflow 0.1.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/.gitignore +13 -0
- package/.skill-lock.json +29 -0
- package/AGENTS.md +45 -0
- package/README.md +147 -0
- package/bin/abelworkflow.mjs +2 -0
- package/commands/oc/diagnose.md +63 -0
- package/commands/oc/implementation.md +157 -0
- package/commands/oc/init.md +27 -0
- package/commands/oc/plan.md +88 -0
- package/commands/oc/research.md +126 -0
- package/lib/cli.mjs +222 -0
- package/package.json +23 -0
- package/skills/confidence-check/SKILL.md +124 -0
- package/skills/confidence-check/confidence.ts +335 -0
- package/skills/context7-auto-research/.env +4 -0
- package/skills/context7-auto-research/.env.example +4 -0
- package/skills/context7-auto-research/SKILL.md +83 -0
- package/skills/context7-auto-research/context7-api.js +283 -0
- package/skills/dev-browser/SKILL.md +225 -0
- package/skills/dev-browser/bun.lock +443 -0
- package/skills/dev-browser/package-lock.json +2988 -0
- package/skills/dev-browser/package.json +31 -0
- package/skills/dev-browser/references/scraping.md +155 -0
- package/skills/dev-browser/resolve-skill-dir.sh +35 -0
- package/skills/dev-browser/scripts/start-relay.ts +32 -0
- package/skills/dev-browser/scripts/start-server.ts +117 -0
- package/skills/dev-browser/server.sh +24 -0
- package/skills/dev-browser/src/client.ts +474 -0
- package/skills/dev-browser/src/index.ts +287 -0
- package/skills/dev-browser/src/relay.ts +731 -0
- package/skills/dev-browser/src/snapshot/browser-script.ts +877 -0
- package/skills/dev-browser/src/snapshot/index.ts +14 -0
- package/skills/dev-browser/src/snapshot/inject.ts +13 -0
- package/skills/dev-browser/src/types.ts +34 -0
- package/skills/dev-browser/tsconfig.json +36 -0
- package/skills/dev-browser/vitest.config.ts +12 -0
- package/skills/git-commit/SKILL.md +124 -0
- package/skills/grok-search/.env.example +24 -0
- package/skills/grok-search/SKILL.md +114 -0
- package/skills/grok-search/requirements.txt +2 -0
- package/skills/grok-search/scripts/groksearch_cli.py +1214 -0
- package/skills/grok-search/scripts/groksearch_entry.py +116 -0
- package/skills/prompt-enhancer/ADVANCED.md +74 -0
- package/skills/prompt-enhancer/SKILL.md +71 -0
- package/skills/prompt-enhancer/TEMPLATE.md +91 -0
- package/skills/prompt-enhancer/scripts/enhance.py +142 -0
- package/skills/sequential-think/SKILL.md +198 -0
- package/skills/sequential-think/scripts/.env.example +5 -0
- package/skills/sequential-think/scripts/sequential_think_cli.py +253 -0
- package/skills/time/SKILL.md +116 -0
- package/skills/time/scripts/time_cli.py +104 -0
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confidence Check - Pre-implementation confidence assessment
|
|
3
|
+
*
|
|
4
|
+
* Prevents wrong-direction execution by assessing confidence BEFORE starting.
|
|
5
|
+
* Requires ≥90% confidence to proceed with implementation.
|
|
6
|
+
*
|
|
7
|
+
* Token Budget: 100-200 tokens
|
|
8
|
+
* ROI: 25-250x token savings when stopping wrong direction
|
|
9
|
+
*
|
|
10
|
+
* Test Results (2025-10-21):
|
|
11
|
+
* - Precision: 1.000 (no false positives)
|
|
12
|
+
* - Recall: 1.000 (no false negatives)
|
|
13
|
+
* - 8/8 test cases passed
|
|
14
|
+
*
|
|
15
|
+
* Confidence Levels:
|
|
16
|
+
* - High (≥90%): Root cause identified, solution verified, no duplication, architecture-compliant
|
|
17
|
+
* - Medium (70-89%): Multiple approaches possible, trade-offs require consideration
|
|
18
|
+
* - Low (<70%): Investigation incomplete, unclear root cause, missing official docs
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync, readdirSync } from 'fs';
|
|
22
|
+
import { join, dirname } from 'path';
|
|
23
|
+
|
|
24
|
+
export interface Context {
|
|
25
|
+
task?: string;
|
|
26
|
+
test_file?: string;
|
|
27
|
+
test_name?: string;
|
|
28
|
+
markers?: string[];
|
|
29
|
+
duplicate_check_complete?: boolean;
|
|
30
|
+
architecture_check_complete?: boolean;
|
|
31
|
+
official_docs_verified?: boolean;
|
|
32
|
+
oss_reference_complete?: boolean;
|
|
33
|
+
root_cause_identified?: boolean;
|
|
34
|
+
confidence_checks?: string[];
|
|
35
|
+
[key: string]: any;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Pre-implementation confidence assessment
|
|
40
|
+
*
|
|
41
|
+
* Usage:
|
|
42
|
+
* const checker = new ConfidenceChecker();
|
|
43
|
+
* const confidence = await checker.assess(context);
|
|
44
|
+
*
|
|
45
|
+
* if (confidence >= 0.9) {
|
|
46
|
+
* // High confidence - proceed immediately
|
|
47
|
+
* } else if (confidence >= 0.7) {
|
|
48
|
+
* // Medium confidence - present options to user
|
|
49
|
+
* } else {
|
|
50
|
+
* // Low confidence - STOP and request clarification
|
|
51
|
+
* }
|
|
52
|
+
*/
|
|
53
|
+
export class ConfidenceChecker {
|
|
54
|
+
/**
|
|
55
|
+
* Assess confidence level (0.0 - 1.0)
|
|
56
|
+
*
|
|
57
|
+
* Investigation Phase Checks:
|
|
58
|
+
* 1. No duplicate implementations? (25%)
|
|
59
|
+
* 2. Architecture compliance? (25%)
|
|
60
|
+
* 3. Official documentation verified? (20%)
|
|
61
|
+
* 4. Working OSS implementations referenced? (15%)
|
|
62
|
+
* 5. Root cause identified? (15%)
|
|
63
|
+
*
|
|
64
|
+
* @param context - Task context with investigation flags
|
|
65
|
+
* @returns Confidence score (0.0 = no confidence, 1.0 = absolute certainty)
|
|
66
|
+
*/
|
|
67
|
+
async assess(context: Context): Promise<number> {
|
|
68
|
+
let score = 0.0;
|
|
69
|
+
const checks: string[] = [];
|
|
70
|
+
|
|
71
|
+
// Check 1: No duplicate implementations (25%)
|
|
72
|
+
if (this.noDuplicates(context)) {
|
|
73
|
+
score += 0.25;
|
|
74
|
+
checks.push("✅ No duplicate implementations found");
|
|
75
|
+
} else {
|
|
76
|
+
checks.push("❌ Check for existing implementations first");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check 2: Architecture compliance (25%)
|
|
80
|
+
if (this.architectureCompliant(context)) {
|
|
81
|
+
score += 0.25;
|
|
82
|
+
checks.push("✅ Uses existing tech stack (e.g., Supabase)");
|
|
83
|
+
} else {
|
|
84
|
+
checks.push("❌ Verify architecture compliance (avoid reinventing)");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check 3: Official documentation verified (20%)
|
|
88
|
+
if (this.hasOfficialDocs(context)) {
|
|
89
|
+
score += 0.2;
|
|
90
|
+
checks.push("✅ Official documentation verified");
|
|
91
|
+
} else {
|
|
92
|
+
checks.push("❌ Read official docs first");
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check 4: Working OSS implementations referenced (15%)
|
|
96
|
+
if (this.hasOssReference(context)) {
|
|
97
|
+
score += 0.15;
|
|
98
|
+
checks.push("✅ Working OSS implementation found");
|
|
99
|
+
} else {
|
|
100
|
+
checks.push("❌ Search for OSS implementations");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Check 5: Root cause identified (15%)
|
|
104
|
+
if (this.rootCauseIdentified(context)) {
|
|
105
|
+
score += 0.15;
|
|
106
|
+
checks.push("✅ Root cause identified");
|
|
107
|
+
} else {
|
|
108
|
+
checks.push("❌ Continue investigation to identify root cause");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Store check results for reporting
|
|
112
|
+
context.confidence_checks = checks;
|
|
113
|
+
|
|
114
|
+
// Display checks
|
|
115
|
+
console.log("📋 Confidence Checks:");
|
|
116
|
+
checks.forEach(check => console.log(` ${check}`));
|
|
117
|
+
console.log("");
|
|
118
|
+
|
|
119
|
+
return score;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if official documentation exists
|
|
124
|
+
*
|
|
125
|
+
* Looks for:
|
|
126
|
+
* - README.md in project
|
|
127
|
+
* - CLAUDE.md and AGENTS.md with relevant patterns
|
|
128
|
+
* - docs/ directory with related content
|
|
129
|
+
*/
|
|
130
|
+
private hasOfficialDocs(context: Context): boolean {
|
|
131
|
+
// Check context flag first (for testing)
|
|
132
|
+
if ('official_docs_verified' in context) {
|
|
133
|
+
return context.official_docs_verified ?? false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check for test file path
|
|
137
|
+
const testFile = context.test_file;
|
|
138
|
+
if (!testFile) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Walk up directory tree to find project root (same logic as Python version)
|
|
143
|
+
let projectRoot = dirname(testFile);
|
|
144
|
+
|
|
145
|
+
while (true) {
|
|
146
|
+
// Check for documentation files
|
|
147
|
+
if (existsSync(join(projectRoot, 'README.md'))) {
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
if (existsSync(join(projectRoot, 'CLAUDE.md'))) {
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (existsSync(join(projectRoot, 'AGENTS.md'))) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if (existsSync(join(projectRoot, 'docs'))) {
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Move up one directory
|
|
161
|
+
const parent = dirname(projectRoot);
|
|
162
|
+
if (parent === projectRoot) break; // Reached root (same as Python: parent != project_root)
|
|
163
|
+
projectRoot = parent;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Check for duplicate implementations
|
|
171
|
+
*
|
|
172
|
+
* Before implementing, verify:
|
|
173
|
+
* - No existing similar functions/modules (Glob/Grep)
|
|
174
|
+
* - No helper functions that solve the same problem
|
|
175
|
+
* - No libraries that provide this functionality
|
|
176
|
+
*
|
|
177
|
+
* Returns true if no duplicates found (investigation complete)
|
|
178
|
+
*/
|
|
179
|
+
private noDuplicates(context: Context): boolean {
|
|
180
|
+
// This is a placeholder - actual implementation should:
|
|
181
|
+
// 1. Search codebase with Glob/Grep for similar patterns
|
|
182
|
+
// 2. Check project dependencies for existing solutions
|
|
183
|
+
// 3. Verify no helper modules provide this functionality
|
|
184
|
+
return context.duplicate_check_complete ?? false;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Check architecture compliance
|
|
189
|
+
*
|
|
190
|
+
* Verify solution uses existing tech stack:
|
|
191
|
+
* - Supabase project → Use Supabase APIs (not custom API)
|
|
192
|
+
* - Next.js project → Use Next.js patterns (not custom routing)
|
|
193
|
+
* - Turborepo → Use workspace patterns (not manual scripts)
|
|
194
|
+
*
|
|
195
|
+
* Returns true if solution aligns with project architecture
|
|
196
|
+
*/
|
|
197
|
+
private architectureCompliant(context: Context): boolean {
|
|
198
|
+
// This is a placeholder - actual implementation should:
|
|
199
|
+
// 1. Read CLAUDE.md and AGENTS.md for project tech stack
|
|
200
|
+
// 2. Verify solution uses existing infrastructure
|
|
201
|
+
// 3. Check not reinventing provided functionality
|
|
202
|
+
return context.architecture_check_complete ?? false;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Check if working OSS implementations referenced
|
|
207
|
+
*
|
|
208
|
+
* Search for:
|
|
209
|
+
* - Similar open-source solutions
|
|
210
|
+
* - Reference implementations in popular projects
|
|
211
|
+
* - Community best practices
|
|
212
|
+
*
|
|
213
|
+
* Returns true if OSS reference found and analyzed
|
|
214
|
+
*/
|
|
215
|
+
private hasOssReference(context: Context): boolean {
|
|
216
|
+
// This is a placeholder - actual implementation should:
|
|
217
|
+
// 1. Search GitHub for similar implementations
|
|
218
|
+
// 2. Read popular OSS projects solving same problem
|
|
219
|
+
// 3. Verify approach matches community patterns
|
|
220
|
+
return context.oss_reference_complete ?? false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Check if root cause is identified with high certainty
|
|
225
|
+
*
|
|
226
|
+
* Verify:
|
|
227
|
+
* - Problem source pinpointed (not guessing)
|
|
228
|
+
* - Solution addresses root cause (not symptoms)
|
|
229
|
+
* - Fix verified against official docs/OSS patterns
|
|
230
|
+
*
|
|
231
|
+
* Returns true if root cause clearly identified
|
|
232
|
+
*/
|
|
233
|
+
private rootCauseIdentified(context: Context): boolean {
|
|
234
|
+
// This is a placeholder - actual implementation should:
|
|
235
|
+
// 1. Verify problem analysis complete
|
|
236
|
+
// 2. Check solution addresses root cause
|
|
237
|
+
// 3. Confirm fix aligns with best practices
|
|
238
|
+
return context.root_cause_identified ?? false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Check if existing patterns can be followed
|
|
243
|
+
*
|
|
244
|
+
* Looks for:
|
|
245
|
+
* - Similar test files
|
|
246
|
+
* - Common naming conventions
|
|
247
|
+
* - Established directory structure
|
|
248
|
+
*/
|
|
249
|
+
private hasExistingPatterns(context: Context): boolean {
|
|
250
|
+
const testFile = context.test_file;
|
|
251
|
+
if (!testFile) {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const testDir = dirname(testFile);
|
|
256
|
+
|
|
257
|
+
// Check for other test files in same directory
|
|
258
|
+
if (existsSync(testDir)) {
|
|
259
|
+
try {
|
|
260
|
+
const files = readdirSync(testDir);
|
|
261
|
+
const testFiles = files.filter(f =>
|
|
262
|
+
f.startsWith('test_') && f.endsWith('.py')
|
|
263
|
+
);
|
|
264
|
+
return testFiles.length > 1;
|
|
265
|
+
} catch {
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Check if implementation path is clear
|
|
275
|
+
*
|
|
276
|
+
* Considers:
|
|
277
|
+
* - Test name suggests clear purpose
|
|
278
|
+
* - Markers indicate test type
|
|
279
|
+
* - Context has sufficient information
|
|
280
|
+
*/
|
|
281
|
+
private hasClearPath(context: Context): boolean {
|
|
282
|
+
// Check test name clarity
|
|
283
|
+
const testName = context.test_name ?? '';
|
|
284
|
+
if (!testName || testName === 'test_example') {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Check for markers indicating test type
|
|
289
|
+
const markers = context.markers ?? [];
|
|
290
|
+
const knownMarkers = new Set([
|
|
291
|
+
'unit', 'integration', 'hallucination',
|
|
292
|
+
'performance', 'confidence_check', 'self_check'
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
const hasMarkers = markers.some(m => knownMarkers.has(m));
|
|
296
|
+
|
|
297
|
+
return hasMarkers || testName.length > 10;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Get recommended action based on confidence level
|
|
302
|
+
*
|
|
303
|
+
* @param confidence - Confidence score (0.0 - 1.0)
|
|
304
|
+
* @returns Recommended action
|
|
305
|
+
*/
|
|
306
|
+
getRecommendation(confidence: number): string {
|
|
307
|
+
if (confidence >= 0.9) {
|
|
308
|
+
return "✅ High confidence (≥90%) - Proceed with implementation";
|
|
309
|
+
} else if (confidence >= 0.7) {
|
|
310
|
+
return "⚠️ Medium confidence (70-89%) - Continue investigation, DO NOT implement yet";
|
|
311
|
+
} else {
|
|
312
|
+
return "❌ Low confidence (<70%) - STOP and continue investigation loop";
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Legacy function-based API for backward compatibility
|
|
319
|
+
*
|
|
320
|
+
* @deprecated Use ConfidenceChecker class instead
|
|
321
|
+
*/
|
|
322
|
+
export async function confidenceCheck(context: Context): Promise<number> {
|
|
323
|
+
const checker = new ConfidenceChecker();
|
|
324
|
+
return checker.assess(context);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Legacy getRecommendation for backward compatibility
|
|
329
|
+
*
|
|
330
|
+
* @deprecated Use ConfidenceChecker.getRecommendation() instead
|
|
331
|
+
*/
|
|
332
|
+
export function getRecommendation(confidence: number): string {
|
|
333
|
+
const checker = new ConfidenceChecker();
|
|
334
|
+
return checker.getRecommendation(confidence);
|
|
335
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: context7-auto-research
|
|
3
|
+
version: 1.0.1
|
|
4
|
+
author: BenedictKing
|
|
5
|
+
description: Automatically fetches up-to-date documentation from Context7 when users ask about libraries, frameworks, APIs, or need code examples. Triggers proactively without explicit user request.
|
|
6
|
+
allowed-tools:
|
|
7
|
+
- Bash
|
|
8
|
+
user-invocable: true
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Context7 Auto Research
|
|
12
|
+
|
|
13
|
+
Fetch current official documentation from Context7 when a user asks about a library, framework, package, or API.
|
|
14
|
+
|
|
15
|
+
## Trigger
|
|
16
|
+
|
|
17
|
+
Activate proactively when the request includes any of these:
|
|
18
|
+
|
|
19
|
+
- How to use, configure, install, migrate, or implement something in a named library/framework
|
|
20
|
+
- Documentation, reference, API, examples, or best practices for a library/framework
|
|
21
|
+
- Version-specific behavior such as `React 19`, `Next.js 15`, `Vue 3.5`
|
|
22
|
+
- Code generation that depends on framework or package APIs
|
|
23
|
+
|
|
24
|
+
Common cues:
|
|
25
|
+
|
|
26
|
+
- Chinese requests such as `如何实现` `怎么写` `配置` `安装` `文档` `参考` `示例`
|
|
27
|
+
- English requests such as `how to` `configure` `install` `docs` `reference` `example`
|
|
28
|
+
- Library mentions such as `React` `Next.js` `Vue` `Prisma` `Tailwind` `Express`
|
|
29
|
+
- Package or repository names from npm, PyPI, GitHub, or official docs sites
|
|
30
|
+
|
|
31
|
+
## Workflow
|
|
32
|
+
|
|
33
|
+
1. Run from this skill directory and use `./context7-api.js`.
|
|
34
|
+
2. Search with the full user query:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
cd skills/context7-auto-research
|
|
38
|
+
node ./context7-api.js --help
|
|
39
|
+
node ./context7-api.js search '<library-name>' - <<'__C7_QUERY__'
|
|
40
|
+
<full user query>
|
|
41
|
+
__C7_QUERY__
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
3. Pick the best match in this order:
|
|
45
|
+
- Exact library name
|
|
46
|
+
- Official package over forks
|
|
47
|
+
- Version match when the user specified one
|
|
48
|
+
- Higher trust score
|
|
49
|
+
|
|
50
|
+
4. Fetch focused documentation:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
node ./context7-api.js context '<library-id>' - <<'__C7_QUERY__'
|
|
54
|
+
<feature name or focused question>
|
|
55
|
+
__C7_QUERY__
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
5. Answer with the retrieved documentation:
|
|
59
|
+
- Prefer current behavior over memory
|
|
60
|
+
- Mention version when relevant
|
|
61
|
+
- Extract only the needed points
|
|
62
|
+
- Include concise code examples when useful
|
|
63
|
+
- If the CLI returns `ok: false`, treat the lookup as failed even if JSON was printed
|
|
64
|
+
|
|
65
|
+
## Fallback
|
|
66
|
+
|
|
67
|
+
- If search returns no useful match, ask for the exact package or repository name.
|
|
68
|
+
- If Context7 fails, say the lookup failed and label the answer as based on local knowledge.
|
|
69
|
+
- If multiple libraries are involved, fetch only the ones needed for the answer.
|
|
70
|
+
|
|
71
|
+
## Environment
|
|
72
|
+
|
|
73
|
+
- Preferred: `CONTEXT7_API_KEY` environment variable
|
|
74
|
+
- Also supported: `<skill-dir>/.env`
|
|
75
|
+
- CLI help: `node ./context7-api.js --help`
|
|
76
|
+
|
|
77
|
+
## Notes
|
|
78
|
+
|
|
79
|
+
- The helper accepts either inline query text or `-` to read the query from stdin.
|
|
80
|
+
- Use the full user question for search; use a narrower feature query for context when possible.
|
|
81
|
+
- The CLI prints structured JSON for `search` and `context`, including error cases.
|
|
82
|
+
- The CLI exits non-zero when the lookup or argument parsing fails.
|
|
83
|
+
- Requires network access and is subject to Context7 coverage and rate limits.
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Context7 API Helper Script
|
|
5
|
+
* Provides simple CLI interface to Context7 API for skill integration
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const https = require('https');
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
const API_BASE = 'https://context7.com/api/v2';
|
|
13
|
+
const STDIN_TIMEOUT_MS = 3000;
|
|
14
|
+
const MAX_STDIN_BYTES = 100 * 1024;
|
|
15
|
+
|
|
16
|
+
function printJson(value) {
|
|
17
|
+
console.log(JSON.stringify(value, null, 2));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildErrorResult(commandName, error) {
|
|
21
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
22
|
+
const result = {
|
|
23
|
+
ok: false,
|
|
24
|
+
fallback: 'local-knowledge',
|
|
25
|
+
error: {
|
|
26
|
+
command: commandName,
|
|
27
|
+
message
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if (commandName === 'search') {
|
|
32
|
+
result.libraries = [];
|
|
33
|
+
} else if (commandName === 'context') {
|
|
34
|
+
result.results = [];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function printErrorResult(commandName, error, exitCode = 1) {
|
|
41
|
+
printJson(buildErrorResult(commandName, error));
|
|
42
|
+
process.exit(exitCode);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function printUsage() {
|
|
46
|
+
console.error(`Usage:
|
|
47
|
+
context7-api.js search <libraryName> <query|->
|
|
48
|
+
context7-api.js context <libraryId> <query|->
|
|
49
|
+
|
|
50
|
+
Examples:
|
|
51
|
+
node context7-api.js search react "useEffect cleanup"
|
|
52
|
+
node context7-api.js context /facebook/react -
|
|
53
|
+
|
|
54
|
+
Config:
|
|
55
|
+
CONTEXT7_API_KEY from environment, or .env in the script directory`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Load API key from .env file in skill directory or from environment variable
|
|
59
|
+
function loadApiKey() {
|
|
60
|
+
// First try environment variable
|
|
61
|
+
if (process.env.CONTEXT7_API_KEY) {
|
|
62
|
+
return process.env.CONTEXT7_API_KEY;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Then try .env file in skill directory
|
|
66
|
+
const envPath = path.join(__dirname, '.env');
|
|
67
|
+
let envContent;
|
|
68
|
+
try {
|
|
69
|
+
envContent = fs.readFileSync(envPath, 'utf8');
|
|
70
|
+
} catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
for (const line of envContent.split(/\r?\n/)) {
|
|
74
|
+
const trimmed = line.trim();
|
|
75
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const separatorIndex = trimmed.indexOf('=');
|
|
79
|
+
if (separatorIndex === -1) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const key = trimmed.slice(0, separatorIndex).trim();
|
|
83
|
+
if (key !== 'CONTEXT7_API_KEY') {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const value = trimmed.slice(separatorIndex + 1).trim();
|
|
87
|
+
if (value) {
|
|
88
|
+
return value.replace(/^["']|["']$/g, '');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const API_KEY = loadApiKey();
|
|
96
|
+
|
|
97
|
+
function makeRequest(path, params = {}) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const queryString = new URLSearchParams(params).toString();
|
|
100
|
+
const url = `${API_BASE}${path}?${queryString}`;
|
|
101
|
+
const headers = {
|
|
102
|
+
'User-Agent': 'Context7-Skill/1.0'
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (API_KEY) {
|
|
106
|
+
headers.Authorization = `Bearer ${API_KEY}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const options = {
|
|
110
|
+
headers
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const req = https.get(url, options, (res) => {
|
|
114
|
+
let data = '';
|
|
115
|
+
|
|
116
|
+
res.on('data', (chunk) => {
|
|
117
|
+
data += chunk;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
res.on('end', () => {
|
|
121
|
+
if (res.statusCode === 200) {
|
|
122
|
+
try {
|
|
123
|
+
resolve(JSON.parse(data));
|
|
124
|
+
} catch (e) {
|
|
125
|
+
resolve(data);
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
reject(new Error(`API Error ${res.statusCode}: ${data}`));
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
req.setTimeout(10000, () => {
|
|
134
|
+
req.destroy(new Error('Request timeout after 10s'));
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
req.on('error', reject);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function searchLibrary(libraryName, query) {
|
|
142
|
+
return makeRequest('/libs/search', {
|
|
143
|
+
libraryName,
|
|
144
|
+
query
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function getContext(libraryId, query) {
|
|
149
|
+
return makeRequest('/context', {
|
|
150
|
+
libraryId,
|
|
151
|
+
query,
|
|
152
|
+
type: 'json'
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Read query from stdin (avoids all shell escaping issues)
|
|
157
|
+
function readStdin() {
|
|
158
|
+
return new Promise((resolve, reject) => {
|
|
159
|
+
if (process.stdin.isTTY) {
|
|
160
|
+
reject(new Error('Cannot read query from stdin: no piped input detected. Pass the query as an argument, or use - with piped stdin.'));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let data = '';
|
|
165
|
+
let byteLength = 0;
|
|
166
|
+
let timeoutId;
|
|
167
|
+
|
|
168
|
+
function cleanup() {
|
|
169
|
+
clearTimeout(timeoutId);
|
|
170
|
+
process.stdin.removeListener('data', onData);
|
|
171
|
+
process.stdin.removeListener('end', onEnd);
|
|
172
|
+
process.stdin.removeListener('error', onError);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function fail(error) {
|
|
176
|
+
cleanup();
|
|
177
|
+
reject(error);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function resetTimeout() {
|
|
181
|
+
clearTimeout(timeoutId);
|
|
182
|
+
timeoutId = setTimeout(() => {
|
|
183
|
+
fail(new Error('Timed out waiting for stdin query input. Pass the query as an argument, or pipe it with -.'));
|
|
184
|
+
}, STDIN_TIMEOUT_MS);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function onData(chunk) {
|
|
188
|
+
data += chunk;
|
|
189
|
+
byteLength += Buffer.byteLength(chunk, 'utf8');
|
|
190
|
+
if (byteLength > MAX_STDIN_BYTES) {
|
|
191
|
+
fail(new Error(`Stdin query input exceeded ${MAX_STDIN_BYTES} bytes. Pass a shorter query.`));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
resetTimeout();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function onEnd() {
|
|
198
|
+
const query = data.trim();
|
|
199
|
+
cleanup();
|
|
200
|
+
if (!query) {
|
|
201
|
+
reject(new Error('Stdin query input was empty. Pass a non-empty query as an argument, or pipe it with -.'));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
resolve(query);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function onError(error) {
|
|
208
|
+
fail(error);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
process.stdin.setEncoding('utf8');
|
|
212
|
+
process.stdin.on('data', onData);
|
|
213
|
+
process.stdin.on('end', onEnd);
|
|
214
|
+
process.stdin.on('error', onError);
|
|
215
|
+
resetTimeout();
|
|
216
|
+
process.stdin.resume();
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function resolveQuery(queryArgs) {
|
|
221
|
+
if (queryArgs.length === 0) {
|
|
222
|
+
return '';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (queryArgs.length === 1 && queryArgs[0] === '-') {
|
|
226
|
+
return readStdin();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (queryArgs.includes('-')) {
|
|
230
|
+
throw new Error('Invalid query arguments: when using -, it must be the only query argument.');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return queryArgs.join(' ');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// CLI Interface
|
|
237
|
+
const command = process.argv[2];
|
|
238
|
+
const args = process.argv.slice(3);
|
|
239
|
+
|
|
240
|
+
async function main() {
|
|
241
|
+
if (!command || command === '--help' || command === '-h' || command === 'help') {
|
|
242
|
+
printUsage();
|
|
243
|
+
process.exit(command ? 0 : 1);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (command === 'search') {
|
|
247
|
+
const libraryName = args[0];
|
|
248
|
+
try {
|
|
249
|
+
const query = await resolveQuery(args.slice(1));
|
|
250
|
+
if (!libraryName || !query) {
|
|
251
|
+
printErrorResult('search', new Error('Missing required arguments: search requires <libraryName> and a non-empty <query|->.'));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const result = await searchLibrary(libraryName, query);
|
|
255
|
+
printJson(result);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
printErrorResult('search', error);
|
|
258
|
+
}
|
|
259
|
+
} else if (command === 'context') {
|
|
260
|
+
const libraryId = args[0];
|
|
261
|
+
try {
|
|
262
|
+
const query = await resolveQuery(args.slice(1));
|
|
263
|
+
if (!libraryId || !query) {
|
|
264
|
+
printErrorResult('context', new Error('Missing required arguments: context requires <libraryId> and a non-empty <query|->.'));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const result = await getContext(libraryId, query);
|
|
268
|
+
printJson(result);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
printErrorResult('context', error);
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
printUsage();
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
main().catch((error) => {
|
|
279
|
+
if (command === 'search' || command === 'context') {
|
|
280
|
+
printErrorResult(command, error);
|
|
281
|
+
}
|
|
282
|
+
process.exit(1);
|
|
283
|
+
});
|