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.
Files changed (51) hide show
  1. package/.gitignore +13 -0
  2. package/.skill-lock.json +29 -0
  3. package/AGENTS.md +45 -0
  4. package/README.md +147 -0
  5. package/bin/abelworkflow.mjs +2 -0
  6. package/commands/oc/diagnose.md +63 -0
  7. package/commands/oc/implementation.md +157 -0
  8. package/commands/oc/init.md +27 -0
  9. package/commands/oc/plan.md +88 -0
  10. package/commands/oc/research.md +126 -0
  11. package/lib/cli.mjs +222 -0
  12. package/package.json +23 -0
  13. package/skills/confidence-check/SKILL.md +124 -0
  14. package/skills/confidence-check/confidence.ts +335 -0
  15. package/skills/context7-auto-research/.env +4 -0
  16. package/skills/context7-auto-research/.env.example +4 -0
  17. package/skills/context7-auto-research/SKILL.md +83 -0
  18. package/skills/context7-auto-research/context7-api.js +283 -0
  19. package/skills/dev-browser/SKILL.md +225 -0
  20. package/skills/dev-browser/bun.lock +443 -0
  21. package/skills/dev-browser/package-lock.json +2988 -0
  22. package/skills/dev-browser/package.json +31 -0
  23. package/skills/dev-browser/references/scraping.md +155 -0
  24. package/skills/dev-browser/resolve-skill-dir.sh +35 -0
  25. package/skills/dev-browser/scripts/start-relay.ts +32 -0
  26. package/skills/dev-browser/scripts/start-server.ts +117 -0
  27. package/skills/dev-browser/server.sh +24 -0
  28. package/skills/dev-browser/src/client.ts +474 -0
  29. package/skills/dev-browser/src/index.ts +287 -0
  30. package/skills/dev-browser/src/relay.ts +731 -0
  31. package/skills/dev-browser/src/snapshot/browser-script.ts +877 -0
  32. package/skills/dev-browser/src/snapshot/index.ts +14 -0
  33. package/skills/dev-browser/src/snapshot/inject.ts +13 -0
  34. package/skills/dev-browser/src/types.ts +34 -0
  35. package/skills/dev-browser/tsconfig.json +36 -0
  36. package/skills/dev-browser/vitest.config.ts +12 -0
  37. package/skills/git-commit/SKILL.md +124 -0
  38. package/skills/grok-search/.env.example +24 -0
  39. package/skills/grok-search/SKILL.md +114 -0
  40. package/skills/grok-search/requirements.txt +2 -0
  41. package/skills/grok-search/scripts/groksearch_cli.py +1214 -0
  42. package/skills/grok-search/scripts/groksearch_entry.py +116 -0
  43. package/skills/prompt-enhancer/ADVANCED.md +74 -0
  44. package/skills/prompt-enhancer/SKILL.md +71 -0
  45. package/skills/prompt-enhancer/TEMPLATE.md +91 -0
  46. package/skills/prompt-enhancer/scripts/enhance.py +142 -0
  47. package/skills/sequential-think/SKILL.md +198 -0
  48. package/skills/sequential-think/scripts/.env.example +5 -0
  49. package/skills/sequential-think/scripts/sequential_think_cli.py +253 -0
  50. package/skills/time/SKILL.md +116 -0
  51. 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,4 @@
1
+ # Context7 API Key Configuration
2
+ # Get your API key from: https://context7.com/dashboard
3
+
4
+ CONTEXT7_API_KEY=ctx7sk-d4d4d513-e3ae-44ae-b67d-30c046898ecf
@@ -0,0 +1,4 @@
1
+ # Context7 API Key Configuration
2
+ # Get your API key from: https://context7.com/dashboard
3
+
4
+ CONTEXT7_API_KEY=
@@ -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
+ });