raggrep 0.12.2 → 0.13.1
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 +58 -14
- package/dist/app/cli/opencode/index.d.ts +10 -0
- package/dist/app/cli/opencode/install-skill.d.ts +30 -0
- package/dist/app/cli/opencode/install-tool.d.ts +28 -0
- package/dist/app/cli/opencode/version-check.d.ts +30 -0
- package/dist/cli/main.js +838 -117
- package/dist/cli/main.js.map +12 -7
- package/dist/index.js +260 -8
- package/dist/index.js.map +8 -6
- package/dist/infrastructure/logger/index.d.ts +2 -0
- package/dist/infrastructure/logger/multiModuleProgressManager.d.ts +19 -0
- package/dist/infrastructure/logger/progressManager.d.ts +17 -0
- package/package.json +1 -1
package/dist/cli/main.js
CHANGED
|
@@ -290,11 +290,13 @@ class InlineProgressLogger {
|
|
|
290
290
|
}
|
|
291
291
|
}
|
|
292
292
|
progress(message) {
|
|
293
|
-
|
|
294
|
-
|
|
293
|
+
const maxCols = 120;
|
|
294
|
+
process.stdout.write("\r" + message);
|
|
295
|
+
const padding = Math.max(0, maxCols - message.length);
|
|
295
296
|
if (padding > 0) {
|
|
296
297
|
process.stdout.write(" ".repeat(padding));
|
|
297
298
|
}
|
|
299
|
+
process.stdout.write("\r");
|
|
298
300
|
this.lastProgressLength = message.length;
|
|
299
301
|
this.hasProgress = true;
|
|
300
302
|
}
|
|
@@ -325,6 +327,130 @@ function createSilentLogger() {
|
|
|
325
327
|
return new SilentLogger;
|
|
326
328
|
}
|
|
327
329
|
|
|
330
|
+
// src/infrastructure/logger/progressManager.ts
|
|
331
|
+
class ProgressManager {
|
|
332
|
+
logger;
|
|
333
|
+
state = {
|
|
334
|
+
completed: 0,
|
|
335
|
+
total: 0,
|
|
336
|
+
message: "",
|
|
337
|
+
timestamp: 0
|
|
338
|
+
};
|
|
339
|
+
intervalId = null;
|
|
340
|
+
constructor(logger) {
|
|
341
|
+
this.logger = logger;
|
|
342
|
+
}
|
|
343
|
+
start() {
|
|
344
|
+
if (this.intervalId) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
this.intervalId = setInterval(() => {
|
|
348
|
+
this.writeProgress();
|
|
349
|
+
}, PROGRESS_UPDATE_INTERVAL_MS);
|
|
350
|
+
}
|
|
351
|
+
stop() {
|
|
352
|
+
if (this.intervalId) {
|
|
353
|
+
clearInterval(this.intervalId);
|
|
354
|
+
this.intervalId = null;
|
|
355
|
+
}
|
|
356
|
+
this.logger.clearProgress();
|
|
357
|
+
}
|
|
358
|
+
reportProgress(completed, total, message, indexed, skipped) {
|
|
359
|
+
this.state = {
|
|
360
|
+
completed,
|
|
361
|
+
total,
|
|
362
|
+
message,
|
|
363
|
+
indexed,
|
|
364
|
+
skipped,
|
|
365
|
+
timestamp: Date.now()
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
writeProgress() {
|
|
369
|
+
let progressMessage = ` [${this.state.completed}/${this.state.total}] ${this.state.message}`;
|
|
370
|
+
if (this.state.indexed !== undefined || this.state.skipped !== undefined) {
|
|
371
|
+
const parts2 = [];
|
|
372
|
+
if (this.state.indexed !== undefined && this.state.indexed > 0) {
|
|
373
|
+
parts2.push(`${this.state.indexed} indexed`);
|
|
374
|
+
}
|
|
375
|
+
if (this.state.skipped !== undefined && this.state.skipped > 0) {
|
|
376
|
+
parts2.push(`${this.state.skipped} skipped`);
|
|
377
|
+
}
|
|
378
|
+
if (parts2.length > 0) {
|
|
379
|
+
progressMessage += ` (${parts2.join(", ")})`;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
this.logger.progress(progressMessage);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
var PROGRESS_UPDATE_INTERVAL_MS = 50;
|
|
386
|
+
|
|
387
|
+
// src/infrastructure/logger/multiModuleProgressManager.ts
|
|
388
|
+
class MultiModuleProgressManager {
|
|
389
|
+
logger;
|
|
390
|
+
modules = new Map;
|
|
391
|
+
intervalId = null;
|
|
392
|
+
constructor(logger) {
|
|
393
|
+
this.logger = logger;
|
|
394
|
+
}
|
|
395
|
+
start() {
|
|
396
|
+
if (this.intervalId) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
this.intervalId = setInterval(() => {
|
|
400
|
+
this.writeProgress();
|
|
401
|
+
}, PROGRESS_UPDATE_INTERVAL_MS2);
|
|
402
|
+
}
|
|
403
|
+
stop() {
|
|
404
|
+
if (this.intervalId) {
|
|
405
|
+
clearInterval(this.intervalId);
|
|
406
|
+
this.intervalId = null;
|
|
407
|
+
}
|
|
408
|
+
this.logger.clearProgress();
|
|
409
|
+
}
|
|
410
|
+
registerModule(moduleId, moduleName, totalFiles) {
|
|
411
|
+
this.modules.set(moduleId, {
|
|
412
|
+
moduleName,
|
|
413
|
+
completed: 0,
|
|
414
|
+
total: totalFiles,
|
|
415
|
+
currentFile: "",
|
|
416
|
+
active: true
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
unregisterModule(moduleId) {
|
|
420
|
+
const module2 = this.modules.get(moduleId);
|
|
421
|
+
if (module2) {
|
|
422
|
+
module2.active = false;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
reportProgress(moduleId, completed, currentFile) {
|
|
426
|
+
const module2 = this.modules.get(moduleId);
|
|
427
|
+
if (!module2) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
module2.completed = completed;
|
|
431
|
+
module2.currentFile = currentFile;
|
|
432
|
+
}
|
|
433
|
+
writeProgress() {
|
|
434
|
+
const activeModules = Array.from(this.modules.values()).filter((m) => m.active);
|
|
435
|
+
if (activeModules.length === 0) {
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (activeModules.length === 1) {
|
|
439
|
+
const m = activeModules[0];
|
|
440
|
+
const progressMessage = `[${m.moduleName}] ${m.completed}/${m.total}: ${m.currentFile}`;
|
|
441
|
+
this.logger.progress(progressMessage);
|
|
442
|
+
} else {
|
|
443
|
+
const parts2 = activeModules.map((m) => {
|
|
444
|
+
const percent = m.total > 0 ? Math.round(m.completed / m.total * 100) : 100;
|
|
445
|
+
return `[${m.moduleName} ${m.completed}/${m.total} ${percent}%]`;
|
|
446
|
+
});
|
|
447
|
+
const progressMessage = parts2.join(" ");
|
|
448
|
+
this.logger.progress(progressMessage);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
var PROGRESS_UPDATE_INTERVAL_MS2 = 50;
|
|
453
|
+
|
|
328
454
|
// src/infrastructure/logger/index.ts
|
|
329
455
|
var init_logger = () => {};
|
|
330
456
|
|
|
@@ -932,7 +1058,24 @@ var init_config = __esm(() => {
|
|
|
932
1058
|
".pytest_cache",
|
|
933
1059
|
"*.egg-info",
|
|
934
1060
|
".idea",
|
|
935
|
-
".raggrep"
|
|
1061
|
+
".raggrep",
|
|
1062
|
+
".DS_Store",
|
|
1063
|
+
"Thumbs.db",
|
|
1064
|
+
".env",
|
|
1065
|
+
".env.local",
|
|
1066
|
+
".env.development.local",
|
|
1067
|
+
".env.test.local",
|
|
1068
|
+
".env.production.local",
|
|
1069
|
+
"*.lock",
|
|
1070
|
+
"package-lock.json",
|
|
1071
|
+
"yarn.lock",
|
|
1072
|
+
"pnpm-lock.yaml",
|
|
1073
|
+
"Cargo.lock",
|
|
1074
|
+
"poetry.lock",
|
|
1075
|
+
"Gemfile.lock",
|
|
1076
|
+
"go.sum",
|
|
1077
|
+
"*.min.js",
|
|
1078
|
+
"*.min.css"
|
|
936
1079
|
];
|
|
937
1080
|
DEFAULT_EXTENSIONS = [
|
|
938
1081
|
".ts",
|
|
@@ -11966,6 +12109,8 @@ async function indexDirectory(rootDir, options = {}) {
|
|
|
11966
12109
|
const moduleStart = Date.now();
|
|
11967
12110
|
logger.info(`
|
|
11968
12111
|
[${module2.name}] Starting indexing...`);
|
|
12112
|
+
const moduleFiles = module2.supportsFile ? files.filter((f) => module2.supportsFile(f)) : files;
|
|
12113
|
+
logger.info(` Processing ${moduleFiles.length} files...`);
|
|
11969
12114
|
const moduleConfig = getModuleConfig(config, module2.id);
|
|
11970
12115
|
if (module2.initialize && moduleConfig) {
|
|
11971
12116
|
const configWithOverrides = { ...moduleConfig };
|
|
@@ -11981,8 +12126,6 @@ async function indexDirectory(rootDir, options = {}) {
|
|
|
11981
12126
|
};
|
|
11982
12127
|
await module2.initialize(configWithOverrides);
|
|
11983
12128
|
}
|
|
11984
|
-
const moduleFiles = module2.supportsFile ? files.filter((f) => module2.supportsFile(f)) : files;
|
|
11985
|
-
logger.info(` Processing ${moduleFiles.length} files...`);
|
|
11986
12129
|
const result = await indexWithModule(rootDir, moduleFiles, module2, config, verbose, introspection, logger, concurrency);
|
|
11987
12130
|
results.push(result);
|
|
11988
12131
|
if (module2.finalize) {
|
|
@@ -12215,13 +12358,22 @@ async function ensureIndexFresh(rootDir, options = {}) {
|
|
|
12215
12358
|
}
|
|
12216
12359
|
let completedCount = 0;
|
|
12217
12360
|
const totalToProcess = filesToProcess.length;
|
|
12361
|
+
const progressManager = new ProgressManager(logger);
|
|
12362
|
+
progressManager.start();
|
|
12363
|
+
let indexedCount = 0;
|
|
12364
|
+
let mtimeUpdatedCount = 0;
|
|
12218
12365
|
const processChangedFile = async (fileToProcess) => {
|
|
12219
12366
|
const { filepath, relativePath, lastModified, isNew, existingContentHash } = fileToProcess;
|
|
12367
|
+
if (isLikelyBinary(filepath)) {
|
|
12368
|
+
completedCount++;
|
|
12369
|
+
return { relativePath, status: "unchanged" };
|
|
12370
|
+
}
|
|
12220
12371
|
try {
|
|
12221
12372
|
const content = await fs8.readFile(filepath, "utf-8");
|
|
12222
12373
|
const contentHash = computeContentHash(content);
|
|
12223
12374
|
if (!isNew && existingContentHash && existingContentHash === contentHash) {
|
|
12224
12375
|
completedCount++;
|
|
12376
|
+
mtimeUpdatedCount++;
|
|
12225
12377
|
return {
|
|
12226
12378
|
relativePath,
|
|
12227
12379
|
status: "mtime_updated",
|
|
@@ -12230,7 +12382,8 @@ async function ensureIndexFresh(rootDir, options = {}) {
|
|
|
12230
12382
|
};
|
|
12231
12383
|
}
|
|
12232
12384
|
completedCount++;
|
|
12233
|
-
|
|
12385
|
+
indexedCount++;
|
|
12386
|
+
progressManager.reportProgress(completedCount, totalToProcess, `Indexing: ${relativePath}`, indexedCount, mtimeUpdatedCount);
|
|
12234
12387
|
introspection.addFile(relativePath, content);
|
|
12235
12388
|
const fileIndex = await module2.indexFile(relativePath, content, ctx);
|
|
12236
12389
|
if (!fileIndex) {
|
|
@@ -12253,6 +12406,7 @@ async function ensureIndexFresh(rootDir, options = {}) {
|
|
|
12253
12406
|
const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
12254
12407
|
const results = await parallelMap(filesToProcess, processChangedFile, concurrency);
|
|
12255
12408
|
indexingMs += Date.now() - indexingStart;
|
|
12409
|
+
progressManager.stop();
|
|
12256
12410
|
totalUnchanged += unchangedCount;
|
|
12257
12411
|
logger.clearProgress();
|
|
12258
12412
|
let mtimeUpdates = 0;
|
|
@@ -12290,6 +12444,19 @@ async function ensureIndexFresh(rootDir, options = {}) {
|
|
|
12290
12444
|
break;
|
|
12291
12445
|
}
|
|
12292
12446
|
}
|
|
12447
|
+
if (totalIndexed > 0 || mtimeUpdates > 0) {
|
|
12448
|
+
const parts2 = [];
|
|
12449
|
+
if (totalIndexed > 0) {
|
|
12450
|
+
parts2.push(`${totalIndexed} indexed`);
|
|
12451
|
+
}
|
|
12452
|
+
if (mtimeUpdates > 0) {
|
|
12453
|
+
parts2.push(`${mtimeUpdates} mtime-only`);
|
|
12454
|
+
}
|
|
12455
|
+
if (totalRemoved > 0) {
|
|
12456
|
+
parts2.push(`${totalRemoved} removed`);
|
|
12457
|
+
}
|
|
12458
|
+
logger.info(` [${module2.name}] ${parts2.join(", ")}`);
|
|
12459
|
+
}
|
|
12293
12460
|
const hasManifestChanges = totalIndexed > 0 || totalRemoved > 0 || mtimeUpdates > 0;
|
|
12294
12461
|
if (hasManifestChanges) {
|
|
12295
12462
|
manifest.lastUpdated = new Date().toISOString();
|
|
@@ -12387,7 +12554,11 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
|
|
|
12387
12554
|
getIntrospection: (filepath) => introspection.getFile(filepath)
|
|
12388
12555
|
};
|
|
12389
12556
|
const totalFiles = files.length;
|
|
12557
|
+
const progressManager = new ProgressManager(logger);
|
|
12558
|
+
progressManager.start();
|
|
12390
12559
|
let completedCount = 0;
|
|
12560
|
+
let indexedCount = 0;
|
|
12561
|
+
let skippedCount = 0;
|
|
12391
12562
|
const processFile = async (filepath, _index) => {
|
|
12392
12563
|
const relativePath = path22.relative(rootDir, filepath);
|
|
12393
12564
|
try {
|
|
@@ -12396,6 +12567,8 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
|
|
|
12396
12567
|
const existingEntry = manifest.files[relativePath];
|
|
12397
12568
|
if (existingEntry && existingEntry.lastModified === lastModified) {
|
|
12398
12569
|
completedCount++;
|
|
12570
|
+
skippedCount++;
|
|
12571
|
+
progressManager.reportProgress(completedCount, totalFiles, `Processing: ${relativePath}`, indexedCount, skippedCount);
|
|
12399
12572
|
logger.debug(` [${completedCount}/${totalFiles}] Skipped ${relativePath} (unchanged)`);
|
|
12400
12573
|
return { relativePath, status: "skipped" };
|
|
12401
12574
|
}
|
|
@@ -12403,6 +12576,8 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
|
|
|
12403
12576
|
const contentHash = computeContentHash(content);
|
|
12404
12577
|
if (existingEntry?.contentHash && existingEntry.contentHash === contentHash) {
|
|
12405
12578
|
completedCount++;
|
|
12579
|
+
skippedCount++;
|
|
12580
|
+
progressManager.reportProgress(completedCount, totalFiles, `Processing: ${relativePath}`, indexedCount, skippedCount);
|
|
12406
12581
|
logger.debug(` [${completedCount}/${totalFiles}] Skipped ${relativePath} (content unchanged)`);
|
|
12407
12582
|
return {
|
|
12408
12583
|
relativePath,
|
|
@@ -12413,9 +12588,12 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
|
|
|
12413
12588
|
}
|
|
12414
12589
|
introspection.addFile(relativePath, content);
|
|
12415
12590
|
completedCount++;
|
|
12416
|
-
|
|
12591
|
+
indexedCount++;
|
|
12592
|
+
progressManager.reportProgress(completedCount, totalFiles, `Processing: ${relativePath}`, indexedCount, skippedCount);
|
|
12417
12593
|
const fileIndex = await module2.indexFile(relativePath, content, ctx);
|
|
12418
12594
|
if (!fileIndex) {
|
|
12595
|
+
skippedCount++;
|
|
12596
|
+
progressManager.reportProgress(completedCount, totalFiles, `Processing: ${relativePath}`, indexedCount, skippedCount);
|
|
12419
12597
|
logger.debug(` [${completedCount}/${totalFiles}] Skipped ${relativePath} (no chunks)`);
|
|
12420
12598
|
return { relativePath, status: "skipped" };
|
|
12421
12599
|
}
|
|
@@ -12434,6 +12612,7 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
|
|
|
12434
12612
|
};
|
|
12435
12613
|
logger.debug(` Using concurrency: ${concurrency}`);
|
|
12436
12614
|
const results = await parallelMap(files, processFile, concurrency);
|
|
12615
|
+
progressManager.stop();
|
|
12437
12616
|
logger.clearProgress();
|
|
12438
12617
|
for (const item of results) {
|
|
12439
12618
|
if (!item.success) {
|
|
@@ -12473,6 +12652,79 @@ async function indexWithModule(rootDir, files, module2, config, verbose, introsp
|
|
|
12473
12652
|
await writeModuleManifest(rootDir, module2.id, manifest, config);
|
|
12474
12653
|
return result;
|
|
12475
12654
|
}
|
|
12655
|
+
function isLikelyBinary(filepath) {
|
|
12656
|
+
const ext = path22.extname(filepath).toLowerCase();
|
|
12657
|
+
const basename15 = path22.basename(filepath).toLowerCase();
|
|
12658
|
+
const binaryExtensions = new Set([
|
|
12659
|
+
".png",
|
|
12660
|
+
".jpg",
|
|
12661
|
+
".jpeg",
|
|
12662
|
+
".gif",
|
|
12663
|
+
".ico",
|
|
12664
|
+
".svg",
|
|
12665
|
+
".webp",
|
|
12666
|
+
".bmp",
|
|
12667
|
+
".tiff",
|
|
12668
|
+
".pdf",
|
|
12669
|
+
".doc",
|
|
12670
|
+
".docx",
|
|
12671
|
+
".xls",
|
|
12672
|
+
".xlsx",
|
|
12673
|
+
".ppt",
|
|
12674
|
+
".pptx",
|
|
12675
|
+
".zip",
|
|
12676
|
+
".tar",
|
|
12677
|
+
".gz",
|
|
12678
|
+
".7z",
|
|
12679
|
+
".rar",
|
|
12680
|
+
".bz2",
|
|
12681
|
+
".exe",
|
|
12682
|
+
".dll",
|
|
12683
|
+
".so",
|
|
12684
|
+
".dylib",
|
|
12685
|
+
".bin",
|
|
12686
|
+
".dat",
|
|
12687
|
+
".obj",
|
|
12688
|
+
".o",
|
|
12689
|
+
".mp3",
|
|
12690
|
+
".mp4",
|
|
12691
|
+
".wav",
|
|
12692
|
+
".avi",
|
|
12693
|
+
".mov",
|
|
12694
|
+
".flac",
|
|
12695
|
+
".ogg",
|
|
12696
|
+
".woff",
|
|
12697
|
+
".woff2",
|
|
12698
|
+
".ttf",
|
|
12699
|
+
".eot",
|
|
12700
|
+
".otf"
|
|
12701
|
+
]);
|
|
12702
|
+
if (binaryExtensions.has(ext)) {
|
|
12703
|
+
return true;
|
|
12704
|
+
}
|
|
12705
|
+
const minifiedPatterns = [
|
|
12706
|
+
".min.js",
|
|
12707
|
+
".min.css",
|
|
12708
|
+
".bundle.js",
|
|
12709
|
+
".bundle.css",
|
|
12710
|
+
".prod.js",
|
|
12711
|
+
".prod.css",
|
|
12712
|
+
".chunk.js",
|
|
12713
|
+
".chunk.css"
|
|
12714
|
+
];
|
|
12715
|
+
if (minifiedPatterns.some((pattern) => filepath.includes(pattern))) {
|
|
12716
|
+
return true;
|
|
12717
|
+
}
|
|
12718
|
+
const systemFiles = [
|
|
12719
|
+
".ds_store",
|
|
12720
|
+
"thumbs.db",
|
|
12721
|
+
"desktop.ini"
|
|
12722
|
+
];
|
|
12723
|
+
if (systemFiles.includes(basename15)) {
|
|
12724
|
+
return true;
|
|
12725
|
+
}
|
|
12726
|
+
return false;
|
|
12727
|
+
}
|
|
12476
12728
|
async function findFilesWithStats(rootDir, config, lastIndexStarted) {
|
|
12477
12729
|
const validExtensions = new Set(config.extensions);
|
|
12478
12730
|
const ignoreDirs = new Set(config.ignorePaths);
|
|
@@ -14270,13 +14522,529 @@ var init_search = __esm(() => {
|
|
|
14270
14522
|
init_indexer();
|
|
14271
14523
|
});
|
|
14272
14524
|
|
|
14525
|
+
// src/app/cli/opencode/version-check.ts
|
|
14526
|
+
function parseOpenCodeVersion(version) {
|
|
14527
|
+
const match2 = version.match(/v?(\d+)\.(\d+)\.(\d+)/);
|
|
14528
|
+
if (!match2) {
|
|
14529
|
+
return null;
|
|
14530
|
+
}
|
|
14531
|
+
return {
|
|
14532
|
+
major: parseInt(match2[1], 10),
|
|
14533
|
+
minor: parseInt(match2[2], 10),
|
|
14534
|
+
patch: parseInt(match2[3], 10)
|
|
14535
|
+
};
|
|
14536
|
+
}
|
|
14537
|
+
function supportsSkills(version) {
|
|
14538
|
+
const parsed = parseOpenCodeVersion(version);
|
|
14539
|
+
if (!parsed) {
|
|
14540
|
+
return true;
|
|
14541
|
+
}
|
|
14542
|
+
if (parsed.major > 1)
|
|
14543
|
+
return true;
|
|
14544
|
+
if (parsed.major === 1 && parsed.minor > 0)
|
|
14545
|
+
return true;
|
|
14546
|
+
if (parsed.major === 1 && parsed.minor === 0 && parsed.patch >= 186)
|
|
14547
|
+
return true;
|
|
14548
|
+
return false;
|
|
14549
|
+
}
|
|
14550
|
+
function getInstallationMethod(openCodeVersion) {
|
|
14551
|
+
if (!openCodeVersion) {
|
|
14552
|
+
return "skill";
|
|
14553
|
+
}
|
|
14554
|
+
return supportsSkills(openCodeVersion) ? "skill" : "tool";
|
|
14555
|
+
}
|
|
14556
|
+
async function detectOpenCodeVersion() {
|
|
14557
|
+
try {
|
|
14558
|
+
const os4 = await import("os");
|
|
14559
|
+
const fs10 = await import("fs/promises");
|
|
14560
|
+
const path25 = await import("path");
|
|
14561
|
+
const homeDir = os4.homedir();
|
|
14562
|
+
const possiblePaths = [
|
|
14563
|
+
path25.join(homeDir, ".local", "share", "opencode", "package.json"),
|
|
14564
|
+
path25.join(homeDir, ".config", "opencode", "package.json"),
|
|
14565
|
+
path25.join(homeDir, ".npm", "global", "node_modules", "opencode", "package.json")
|
|
14566
|
+
];
|
|
14567
|
+
for (const packagePath of possiblePaths) {
|
|
14568
|
+
try {
|
|
14569
|
+
const content = await fs10.readFile(packagePath, "utf-8");
|
|
14570
|
+
const pkg = JSON.parse(content);
|
|
14571
|
+
if (pkg.version) {
|
|
14572
|
+
return pkg.version;
|
|
14573
|
+
}
|
|
14574
|
+
} catch {}
|
|
14575
|
+
}
|
|
14576
|
+
try {
|
|
14577
|
+
const { spawn } = await import("child_process");
|
|
14578
|
+
return new Promise((resolve6) => {
|
|
14579
|
+
const proc = spawn("opencode", ["--version"], { stdio: "pipe" });
|
|
14580
|
+
let version = "";
|
|
14581
|
+
proc.stdout.on("data", (data) => {
|
|
14582
|
+
version += data.toString();
|
|
14583
|
+
});
|
|
14584
|
+
proc.on("close", (code) => {
|
|
14585
|
+
if (code === 0) {
|
|
14586
|
+
const match2 = version.match(/v?(\d+\.\d+\.\d+)/);
|
|
14587
|
+
resolve6(match2 ? match2[1] : null);
|
|
14588
|
+
} else {
|
|
14589
|
+
resolve6(null);
|
|
14590
|
+
}
|
|
14591
|
+
});
|
|
14592
|
+
setTimeout(() => {
|
|
14593
|
+
proc.kill();
|
|
14594
|
+
resolve6(null);
|
|
14595
|
+
}, 3000);
|
|
14596
|
+
});
|
|
14597
|
+
} catch {}
|
|
14598
|
+
return null;
|
|
14599
|
+
} catch {
|
|
14600
|
+
return null;
|
|
14601
|
+
}
|
|
14602
|
+
}
|
|
14603
|
+
|
|
14604
|
+
// src/app/cli/opencode/install-tool.ts
|
|
14605
|
+
async function installTool(options = {}) {
|
|
14606
|
+
const { logger, checkForOldSkill = true } = options;
|
|
14607
|
+
const os4 = await import("os");
|
|
14608
|
+
const fs10 = await import("fs/promises");
|
|
14609
|
+
const path25 = await import("path");
|
|
14610
|
+
const homeDir = os4.homedir();
|
|
14611
|
+
const toolDir = path25.join(homeDir, ".config", "opencode", "tool");
|
|
14612
|
+
const toolPath = path25.join(toolDir, "raggrep.ts");
|
|
14613
|
+
let removedOldSkill = false;
|
|
14614
|
+
const toolContent = `import { tool } from "@opencode-ai/plugin";
|
|
14615
|
+
|
|
14616
|
+
/**
|
|
14617
|
+
* Get the package executor command (pnpx if available, otherwise npx)
|
|
14618
|
+
*/
|
|
14619
|
+
async function getExecutor(): Promise<string> {
|
|
14620
|
+
try {
|
|
14621
|
+
// Try to find pnpm first (faster)
|
|
14622
|
+
await Bun.spawn(['pnpm', '--version'], { stdout: 'pipe', stderr: 'pipe' }).exited;
|
|
14623
|
+
return 'pnpx';
|
|
14624
|
+
} catch {
|
|
14625
|
+
// Fall back to npx
|
|
14626
|
+
return 'npx';
|
|
14627
|
+
}
|
|
14628
|
+
}
|
|
14629
|
+
|
|
14630
|
+
/**
|
|
14631
|
+
* Get the installed raggrep version
|
|
14632
|
+
*/
|
|
14633
|
+
async function getRagrepVersion(executor: string): Promise<string | null> {
|
|
14634
|
+
try {
|
|
14635
|
+
const proc = Bun.spawn([executor, 'raggrep', '--version'], { stdout: 'pipe', stderr: 'pipe' });
|
|
14636
|
+
const output = await new Response(proc.stdout).text();
|
|
14637
|
+
const match = output.match(/v([\\\\d.]+)/);
|
|
14638
|
+
return match ? match[1] : null;
|
|
14639
|
+
} catch {
|
|
14640
|
+
return null;
|
|
14641
|
+
}
|
|
14642
|
+
}
|
|
14643
|
+
|
|
14644
|
+
export default tool({
|
|
14645
|
+
description:
|
|
14646
|
+
"Semantic code search powered by RAG - understands INTENT, not just literal text. Parses code using AST to extract functions, classes, and symbols with full context. Finds relevant code even when exact keywords don't match. Superior to grep for exploratory searches like 'authentication logic', 'error handling patterns', or 'configuration loading'.\\\\n\\\\n\uD83C\uDFAF USE THIS TOOL FIRST when you need to:\\\\n• Find WHERE code is located (functions, components, services)\\\\n• Understand HOW code is structured\\\\n• Discover RELATED code across multiple files\\\\n• Get a QUICK overview of a topic\\\\n\\\\n❌ DON'T read multiple files manually when you can:\\\\n raggrep(\\"user authentication\\", { filter: [\\"src/\\"] })\\\\n\\\\n✅ INSTEAD of reading files one-by-one, search semantically:\\\\n • \\"Find the auth middleware\\" vs read: auth.ts, middleware.ts, index.ts...\\\\n • \\"Where are React components?\\" vs read: App.tsx, components/*, pages/*...\\\\n • \\"Database connection logic?\\" vs read: db.ts, config.ts, models/*...\\\\n • \\"Error handling patterns\\" vs read: error.ts, middleware.ts, handlers/*...\\\\n\\\\nThis saves ~10x tool calls and provides BETTER context by showing related code across the entire codebase.",
|
|
14647
|
+
args: {
|
|
14648
|
+
query: tool.schema
|
|
14649
|
+
.string()
|
|
14650
|
+
.describe(
|
|
14651
|
+
"Natural language search query describing what you want to find. Be specific: 'auth middleware that checks JWT', 'React hooks for data fetching', 'database connection pool config'. This is MUCH faster than reading files manually."
|
|
14652
|
+
),
|
|
14653
|
+
filter: tool.schema
|
|
14654
|
+
.array(tool.schema.string())
|
|
14655
|
+
.describe(
|
|
14656
|
+
"Array of path prefixes or glob patterns to narrow search scope (OR logic). If user mentions a directory, use it. Otherwise infer from context. Common patterns: ['src/auth'], ['*.tsx', 'components/'], ['api/', 'routes/'], ['docs/', '*.md'], ['*.test.ts']. For broad search use ['src/'] or ['**/*']."
|
|
14657
|
+
),
|
|
14658
|
+
top: tool.schema
|
|
14659
|
+
.number()
|
|
14660
|
+
.optional()
|
|
14661
|
+
.describe("Number of results to return (default: 10)"),
|
|
14662
|
+
minScore: tool.schema
|
|
14663
|
+
.number()
|
|
14664
|
+
.optional()
|
|
14665
|
+
.describe("Minimum similarity score 0-1 (default: 0.15)"),
|
|
14666
|
+
type: tool.schema
|
|
14667
|
+
.string()
|
|
14668
|
+
.optional()
|
|
14669
|
+
.describe(
|
|
14670
|
+
"Filter by single file extension without dot (e.g., 'ts', 'tsx', 'js', 'md'). Prefer using 'filter' with glob patterns like '*.ts' for more flexibility."
|
|
14671
|
+
),
|
|
14672
|
+
},
|
|
14673
|
+
async execute(args) {
|
|
14674
|
+
const executor = await getExecutor();
|
|
14675
|
+
const version = await getRagrepVersion(executor);
|
|
14676
|
+
|
|
14677
|
+
if (!version) {
|
|
14678
|
+
return \`Error: raggrep not found. Install it with: \${executor} install -g raggrep\`;
|
|
14679
|
+
}
|
|
14680
|
+
|
|
14681
|
+
const cmdArgs = [args.query];
|
|
14682
|
+
|
|
14683
|
+
if (args.top !== undefined) {
|
|
14684
|
+
cmdArgs.push("--top", String(args.top));
|
|
14685
|
+
}
|
|
14686
|
+
if (args.minScore !== undefined) {
|
|
14687
|
+
cmdArgs.push("--min-score", String(args.minScore));
|
|
14688
|
+
}
|
|
14689
|
+
if (args.type !== undefined) {
|
|
14690
|
+
cmdArgs.push("--type", args.type);
|
|
14691
|
+
}
|
|
14692
|
+
if (args.filter !== undefined && args.filter.length > 0) {
|
|
14693
|
+
for (const f of args.filter) {
|
|
14694
|
+
cmdArgs.push("--filter", f);
|
|
14695
|
+
}
|
|
14696
|
+
}
|
|
14697
|
+
|
|
14698
|
+
const proc = Bun.spawn([executor, 'raggrep', 'query', ...cmdArgs], { stdout: 'pipe' });
|
|
14699
|
+
const result = await new Response(proc.stdout).text();
|
|
14700
|
+
return result.trim();
|
|
14701
|
+
},
|
|
14702
|
+
});
|
|
14703
|
+
`;
|
|
14704
|
+
try {
|
|
14705
|
+
if (checkForOldSkill) {
|
|
14706
|
+
const oldSkillDir = path25.join(homeDir, ".config", "opencode", "skill", "raggrep");
|
|
14707
|
+
const oldSkillPath = path25.join(oldSkillDir, "SKILL.md");
|
|
14708
|
+
let oldSkillExists = false;
|
|
14709
|
+
try {
|
|
14710
|
+
await fs10.access(oldSkillPath);
|
|
14711
|
+
oldSkillExists = true;
|
|
14712
|
+
} catch {}
|
|
14713
|
+
if (oldSkillExists) {
|
|
14714
|
+
const message2 = "Found existing raggrep skill from previous installation.";
|
|
14715
|
+
const locationMessage = ` Location: ${oldSkillPath}`;
|
|
14716
|
+
if (logger) {
|
|
14717
|
+
logger.info(message2);
|
|
14718
|
+
logger.info(locationMessage);
|
|
14719
|
+
} else {
|
|
14720
|
+
console.log(message2);
|
|
14721
|
+
console.log(locationMessage);
|
|
14722
|
+
}
|
|
14723
|
+
const readline = await import("readline");
|
|
14724
|
+
const rl = readline.createInterface({
|
|
14725
|
+
input: process.stdin,
|
|
14726
|
+
output: process.stdout
|
|
14727
|
+
});
|
|
14728
|
+
const answer = await new Promise((resolve6) => {
|
|
14729
|
+
rl.question("Remove the existing skill and install tool? (Y/n): ", resolve6);
|
|
14730
|
+
});
|
|
14731
|
+
rl.close();
|
|
14732
|
+
const shouldDelete = answer.toLowerCase() !== "n";
|
|
14733
|
+
if (shouldDelete) {
|
|
14734
|
+
try {
|
|
14735
|
+
await fs10.unlink(oldSkillPath);
|
|
14736
|
+
const skillDirContents = await fs10.readdir(oldSkillDir);
|
|
14737
|
+
if (skillDirContents.length === 0) {
|
|
14738
|
+
try {
|
|
14739
|
+
await fs10.rmdir(oldSkillDir);
|
|
14740
|
+
console.log("✓ Removed old skill directory.");
|
|
14741
|
+
} catch (rmdirError) {
|
|
14742
|
+
console.log("✓ Removed old skill file. (Directory not empty or other error)");
|
|
14743
|
+
}
|
|
14744
|
+
} else {
|
|
14745
|
+
console.log("✓ Removed old skill file. (Directory not empty, keeping structure)");
|
|
14746
|
+
}
|
|
14747
|
+
removedOldSkill = true;
|
|
14748
|
+
const successMessage = "✓ Removed old skill file.";
|
|
14749
|
+
if (logger) {
|
|
14750
|
+
logger.info(successMessage);
|
|
14751
|
+
} else {
|
|
14752
|
+
console.log(successMessage);
|
|
14753
|
+
}
|
|
14754
|
+
} catch (error) {
|
|
14755
|
+
const warnMessage = `Warning: Could not remove old skill file: ${error}`;
|
|
14756
|
+
if (logger) {
|
|
14757
|
+
logger.warn(warnMessage);
|
|
14758
|
+
} else {
|
|
14759
|
+
console.warn(warnMessage);
|
|
14760
|
+
}
|
|
14761
|
+
}
|
|
14762
|
+
} else {
|
|
14763
|
+
const keepMessage = "Keeping existing skill. Tool installation cancelled.";
|
|
14764
|
+
if (logger) {
|
|
14765
|
+
logger.info(keepMessage);
|
|
14766
|
+
} else {
|
|
14767
|
+
console.log(keepMessage);
|
|
14768
|
+
}
|
|
14769
|
+
return {
|
|
14770
|
+
success: false,
|
|
14771
|
+
message: keepMessage
|
|
14772
|
+
};
|
|
14773
|
+
}
|
|
14774
|
+
}
|
|
14775
|
+
}
|
|
14776
|
+
await fs10.mkdir(toolDir, { recursive: true });
|
|
14777
|
+
await fs10.writeFile(toolPath, toolContent, "utf-8");
|
|
14778
|
+
const message = `Installed raggrep tool for OpenCode.
|
|
14779
|
+
Location: ${toolPath}
|
|
14780
|
+
|
|
14781
|
+
The raggrep tool is now available in OpenCode.`;
|
|
14782
|
+
if (logger) {
|
|
14783
|
+
logger.info(message);
|
|
14784
|
+
} else {
|
|
14785
|
+
console.log(message);
|
|
14786
|
+
}
|
|
14787
|
+
return {
|
|
14788
|
+
success: true,
|
|
14789
|
+
toolPath,
|
|
14790
|
+
message,
|
|
14791
|
+
removedOldSkill
|
|
14792
|
+
};
|
|
14793
|
+
} catch (error) {
|
|
14794
|
+
const message = `Error installing OpenCode tool: ${error}`;
|
|
14795
|
+
if (logger) {
|
|
14796
|
+
logger.error(message);
|
|
14797
|
+
} else {
|
|
14798
|
+
console.error(message);
|
|
14799
|
+
}
|
|
14800
|
+
return {
|
|
14801
|
+
success: false,
|
|
14802
|
+
message
|
|
14803
|
+
};
|
|
14804
|
+
}
|
|
14805
|
+
}
|
|
14806
|
+
|
|
14807
|
+
// src/app/cli/opencode/install-skill.ts
|
|
14808
|
+
async function installSkill(options = {}) {
|
|
14809
|
+
const { logger, checkForOldTool = true } = options;
|
|
14810
|
+
const os4 = await import("os");
|
|
14811
|
+
const fs10 = await import("fs/promises");
|
|
14812
|
+
const path25 = await import("path");
|
|
14813
|
+
const homeDir = os4.homedir();
|
|
14814
|
+
const skillDir = path25.join(homeDir, ".config", "opencode", "skill", "raggrep");
|
|
14815
|
+
const skillPath = path25.join(skillDir, "SKILL.md");
|
|
14816
|
+
const skillContent = `---
|
|
14817
|
+
name: raggrep
|
|
14818
|
+
description: AST-powered semantic code search that understands intent, not just text. Superior to grep/rg - finds functions, classes, and logic even when keywords differ. Saves 10x tool calls by searching the actual code structure.
|
|
14819
|
+
license: MIT
|
|
14820
|
+
compatibility: opencode
|
|
14821
|
+
metadata:
|
|
14822
|
+
audience: developers
|
|
14823
|
+
use_case: code-discovery
|
|
14824
|
+
---
|
|
14825
|
+
|
|
14826
|
+
## What I do
|
|
14827
|
+
|
|
14828
|
+
I'm a **MUCH better alternative to grep/rg** for code search. I provide semantic search that actually **understands your code** rather than just matching text:
|
|
14829
|
+
|
|
14830
|
+
- **Parse code using AST** to extract functions, classes, variables, and symbols with full context
|
|
14831
|
+
- **Understand code structure** and relationships across files
|
|
14832
|
+
- **Search by intent** - find "authentication logic" even if the code uses "auth", "login", "verifyToken", etc.
|
|
14833
|
+
- **Find relevant code** even when exact keywords don't appear anywhere in the source
|
|
14834
|
+
- **Save ~10x tool calls** compared to manually reading files or using grep
|
|
14835
|
+
|
|
14836
|
+
## When to use me
|
|
14837
|
+
|
|
14838
|
+
Use me when you need to:
|
|
14839
|
+
- **Find WHERE code is located** (functions, components, services)
|
|
14840
|
+
- **Understand HOW code is structured** (architecture, patterns, dependencies)
|
|
14841
|
+
- **Discover RELATED code** across multiple files
|
|
14842
|
+
- **Get a QUICK overview** of a topic or feature area
|
|
14843
|
+
|
|
14844
|
+
## How to use me
|
|
14845
|
+
|
|
14846
|
+
First, install raggrep if not already available:
|
|
14847
|
+
\`\`\`bash
|
|
14848
|
+
# Install raggrep globally
|
|
14849
|
+
npm install -g raggrep
|
|
14850
|
+
# or
|
|
14851
|
+
pnpm add -g raggrep
|
|
14852
|
+
\`\`\`
|
|
14853
|
+
|
|
14854
|
+
### Step 1: Index your codebase
|
|
14855
|
+
\`\`\`bash
|
|
14856
|
+
# Navigate to your project directory and index it
|
|
14857
|
+
cd /path/to/your/project
|
|
14858
|
+
raggrep index
|
|
14859
|
+
\`\`\`
|
|
14860
|
+
|
|
14861
|
+
### Step 2: Use semantic search
|
|
14862
|
+
\`\`\`bash
|
|
14863
|
+
# Search for specific functionality
|
|
14864
|
+
raggrep query "user authentication"
|
|
14865
|
+
|
|
14866
|
+
# Search with filters
|
|
14867
|
+
raggrep query "React hooks for data fetching" --filter "src/components"
|
|
14868
|
+
|
|
14869
|
+
# Search with specific file types
|
|
14870
|
+
raggrep query "database connection" --type ts
|
|
14871
|
+
|
|
14872
|
+
# Get more results
|
|
14873
|
+
raggrep query "error handling" --top 15
|
|
14874
|
+
\`\`\`
|
|
14875
|
+
|
|
14876
|
+
### Step 3: Use in OpenCode agents
|
|
14877
|
+
|
|
14878
|
+
Load this skill in your agent conversation:
|
|
14879
|
+
\`\`\`
|
|
14880
|
+
skill({ name: "raggrep" })
|
|
14881
|
+
\`\`\`
|
|
14882
|
+
|
|
14883
|
+
Then the agent can use raggrep commands to search your codebase efficiently.
|
|
14884
|
+
|
|
14885
|
+
## Why I'm Better Than grep/rg
|
|
14886
|
+
|
|
14887
|
+
❌ **grep/rg limitations:**
|
|
14888
|
+
- Only matches literal text patterns
|
|
14889
|
+
- Can't understand code structure or intent
|
|
14890
|
+
- Requires exact keyword matches
|
|
14891
|
+
- Often returns irrelevant results
|
|
14892
|
+
|
|
14893
|
+
✅ **My advantages:**
|
|
14894
|
+
- Understands code semantics and intent
|
|
14895
|
+
- Finds relevant code even with different terminology
|
|
14896
|
+
- Works with AST-extracted symbols, not just raw text
|
|
14897
|
+
- Provides contextual, ranked results
|
|
14898
|
+
|
|
14899
|
+
## Search Examples
|
|
14900
|
+
|
|
14901
|
+
Instead of using grep/rg or manually reading files:
|
|
14902
|
+
|
|
14903
|
+
❌ **DON'T do this:**
|
|
14904
|
+
- \`rg "auth" --type ts\` (might miss middleware, login, verifyToken)
|
|
14905
|
+
- Read: auth.ts, middleware.ts, index.ts to find auth logic
|
|
14906
|
+
- Read: App.tsx, components/*, pages/* to find React components
|
|
14907
|
+
- Read: db.ts, config.ts, models/* to find database code
|
|
14908
|
+
|
|
14909
|
+
✅ **DO this instead:**
|
|
14910
|
+
- \`raggrep query "Find the auth middleware"\` (finds ALL auth-related code)
|
|
14911
|
+
- \`raggrep query "Where are React components?"\`
|
|
14912
|
+
- \`raggrep query "Database connection logic?"\`
|
|
14913
|
+
- \`raggrep query "Error handling patterns"\`
|
|
14914
|
+
|
|
14915
|
+
## Best Practices
|
|
14916
|
+
|
|
14917
|
+
1. **Think intent, not keywords**: "user authentication logic" works better than \`rg "auth"\`
|
|
14918
|
+
2. **Use filters strategically**: \`--filter "src/auth"\`, \`--filter "*.test.ts"\`
|
|
14919
|
+
3. **Adjust result count**: Use \`--top 5\` for focused results, \`--top 20\` for comprehensive search
|
|
14920
|
+
4. **Replace grep/rg habits**: Instead of \`rg "pattern"\`, try \`raggrep query "what the code does"\`
|
|
14921
|
+
|
|
14922
|
+
**Result**: 10x fewer tool calls, BETTER results, deeper code understanding.
|
|
14923
|
+
`;
|
|
14924
|
+
let removedOldTool = false;
|
|
14925
|
+
try {
|
|
14926
|
+
if (checkForOldTool) {
|
|
14927
|
+
const oldToolDir = path25.join(homeDir, ".config", "opencode", "tool");
|
|
14928
|
+
const oldToolPath = path25.join(oldToolDir, "raggrep.ts");
|
|
14929
|
+
let oldToolExists = false;
|
|
14930
|
+
try {
|
|
14931
|
+
await fs10.access(oldToolPath);
|
|
14932
|
+
oldToolExists = true;
|
|
14933
|
+
} catch {}
|
|
14934
|
+
if (oldToolExists) {
|
|
14935
|
+
const message2 = "Found old raggrep tool file from previous version.";
|
|
14936
|
+
const locationMessage = ` Location: ${oldToolPath}`;
|
|
14937
|
+
if (logger) {
|
|
14938
|
+
logger.info(message2);
|
|
14939
|
+
logger.info(locationMessage);
|
|
14940
|
+
} else {
|
|
14941
|
+
console.log(message2);
|
|
14942
|
+
console.log(locationMessage);
|
|
14943
|
+
}
|
|
14944
|
+
const readline = await import("readline");
|
|
14945
|
+
const rl = readline.createInterface({
|
|
14946
|
+
input: process.stdin,
|
|
14947
|
+
output: process.stdout
|
|
14948
|
+
});
|
|
14949
|
+
const answer = await new Promise((resolve6) => {
|
|
14950
|
+
rl.question("Do you want to remove the old tool file? (Y/n): ", resolve6);
|
|
14951
|
+
});
|
|
14952
|
+
rl.close();
|
|
14953
|
+
const shouldDelete = answer.toLowerCase() !== "n";
|
|
14954
|
+
if (shouldDelete) {
|
|
14955
|
+
try {
|
|
14956
|
+
await fs10.unlink(oldToolPath);
|
|
14957
|
+
const toolDirContents = await fs10.readdir(oldToolDir);
|
|
14958
|
+
if (toolDirContents.length === 0) {
|
|
14959
|
+
try {
|
|
14960
|
+
await fs10.rmdir(oldToolDir);
|
|
14961
|
+
console.log("✓ Removed old tool directory.");
|
|
14962
|
+
} catch (rmdirError) {
|
|
14963
|
+
console.log("✓ Removed old tool file. (Directory not empty or other error)");
|
|
14964
|
+
}
|
|
14965
|
+
} else {
|
|
14966
|
+
console.log("✓ Removed old tool file. (Directory not empty, keeping structure)");
|
|
14967
|
+
}
|
|
14968
|
+
removedOldTool = true;
|
|
14969
|
+
const successMessage = "✓ Removed old tool file.";
|
|
14970
|
+
if (logger) {
|
|
14971
|
+
logger.info(successMessage);
|
|
14972
|
+
} else {
|
|
14973
|
+
console.log(successMessage);
|
|
14974
|
+
}
|
|
14975
|
+
} catch (error) {
|
|
14976
|
+
const warnMessage = `Warning: Could not remove old tool file: ${error}`;
|
|
14977
|
+
if (logger) {
|
|
14978
|
+
logger.warn(warnMessage);
|
|
14979
|
+
} else {
|
|
14980
|
+
console.warn(warnMessage);
|
|
14981
|
+
}
|
|
14982
|
+
}
|
|
14983
|
+
} else {
|
|
14984
|
+
const keepMessage = "Keeping old tool file.";
|
|
14985
|
+
if (logger) {
|
|
14986
|
+
logger.info(keepMessage);
|
|
14987
|
+
} else {
|
|
14988
|
+
console.log(keepMessage);
|
|
14989
|
+
}
|
|
14990
|
+
}
|
|
14991
|
+
}
|
|
14992
|
+
}
|
|
14993
|
+
await fs10.mkdir(skillDir, { recursive: true });
|
|
14994
|
+
await fs10.writeFile(skillPath, skillContent, "utf-8");
|
|
14995
|
+
const message = `Installed raggrep skill for OpenCode.
|
|
14996
|
+
Location: ${skillPath}
|
|
14997
|
+
|
|
14998
|
+
The raggrep skill is now available to OpenCode agents.
|
|
14999
|
+
|
|
15000
|
+
To use this skill:
|
|
15001
|
+
1. Install raggrep: npm install -g raggrep
|
|
15002
|
+
2. Index your codebase: raggrep index
|
|
15003
|
+
3. In OpenCode, load the skill: skill({ name: "raggrep" })`;
|
|
15004
|
+
if (logger) {
|
|
15005
|
+
logger.info(message);
|
|
15006
|
+
} else {
|
|
15007
|
+
console.log(message);
|
|
15008
|
+
}
|
|
15009
|
+
return {
|
|
15010
|
+
success: true,
|
|
15011
|
+
skillPath,
|
|
15012
|
+
message,
|
|
15013
|
+
removedOldTool
|
|
15014
|
+
};
|
|
15015
|
+
} catch (error) {
|
|
15016
|
+
const message = `Error installing OpenCode skill: ${error}`;
|
|
15017
|
+
if (logger) {
|
|
15018
|
+
logger.error(message);
|
|
15019
|
+
} else {
|
|
15020
|
+
console.error(message);
|
|
15021
|
+
}
|
|
15022
|
+
return {
|
|
15023
|
+
success: false,
|
|
15024
|
+
message
|
|
15025
|
+
};
|
|
15026
|
+
}
|
|
15027
|
+
}
|
|
15028
|
+
|
|
15029
|
+
// src/app/cli/opencode/index.ts
|
|
15030
|
+
var exports_opencode = {};
|
|
15031
|
+
__export(exports_opencode, {
|
|
15032
|
+
supportsSkills: () => supportsSkills,
|
|
15033
|
+
parseOpenCodeVersion: () => parseOpenCodeVersion,
|
|
15034
|
+
installTool: () => installTool,
|
|
15035
|
+
installSkill: () => installSkill,
|
|
15036
|
+
getInstallationMethod: () => getInstallationMethod,
|
|
15037
|
+
detectOpenCodeVersion: () => detectOpenCodeVersion
|
|
15038
|
+
});
|
|
15039
|
+
var init_opencode = () => {};
|
|
15040
|
+
|
|
14273
15041
|
// src/app/cli/main.ts
|
|
14274
15042
|
init_embeddings();
|
|
14275
15043
|
init_logger();
|
|
14276
15044
|
// package.json
|
|
14277
15045
|
var package_default = {
|
|
14278
15046
|
name: "raggrep",
|
|
14279
|
-
version: "0.
|
|
15047
|
+
version: "0.13.1",
|
|
14280
15048
|
description: "Local filesystem-based RAG system for codebases - semantic search using local embeddings",
|
|
14281
15049
|
type: "module",
|
|
14282
15050
|
main: "./dist/index.js",
|
|
@@ -14375,6 +15143,8 @@ function parseFlags(args3) {
|
|
|
14375
15143
|
verbose: false,
|
|
14376
15144
|
watch: false,
|
|
14377
15145
|
timing: false,
|
|
15146
|
+
forceTool: false,
|
|
15147
|
+
forceSkill: false,
|
|
14378
15148
|
remaining: []
|
|
14379
15149
|
};
|
|
14380
15150
|
for (let i2 = 0;i2 < args3.length; i2++) {
|
|
@@ -14436,6 +15206,10 @@ function parseFlags(args3) {
|
|
|
14436
15206
|
console.error('--filter requires a path or glob pattern (e.g., src/auth, "*.ts")');
|
|
14437
15207
|
process.exit(1);
|
|
14438
15208
|
}
|
|
15209
|
+
} else if (arg === "--tool") {
|
|
15210
|
+
flags2.forceTool = true;
|
|
15211
|
+
} else if (arg === "--skill") {
|
|
15212
|
+
flags2.forceSkill = true;
|
|
14439
15213
|
} else if (!arg.startsWith("-")) {
|
|
14440
15214
|
flags2.remaining.push(arg);
|
|
14441
15215
|
}
|
|
@@ -14752,129 +15526,76 @@ Examples:
|
|
|
14752
15526
|
const subcommand = flags2.remaining[0];
|
|
14753
15527
|
if (flags2.help || !subcommand) {
|
|
14754
15528
|
console.log(`
|
|
14755
|
-
raggrep opencode - Manage
|
|
15529
|
+
raggrep opencode - Manage OpenCode integration
|
|
14756
15530
|
|
|
14757
15531
|
Usage:
|
|
14758
|
-
raggrep opencode <subcommand>
|
|
15532
|
+
raggrep opencode <subcommand> [options]
|
|
14759
15533
|
|
|
14760
15534
|
Subcommands:
|
|
14761
|
-
install Install or update
|
|
15535
|
+
install Install or update raggrep for OpenCode
|
|
15536
|
+
|
|
15537
|
+
Options:
|
|
15538
|
+
--tool Force tool-based installation (default)
|
|
15539
|
+
--skill Force skill-based installation
|
|
14762
15540
|
|
|
14763
15541
|
Description:
|
|
14764
|
-
Installs
|
|
14765
|
-
|
|
15542
|
+
Installs raggrep for OpenCode with mutual exclusivity:
|
|
15543
|
+
- Tool installation (default): ~/.config/opencode/tool/raggrep.ts
|
|
15544
|
+
- Skill installation: ~/.config/opencode/skill/raggrep/SKILL.md
|
|
15545
|
+
Installing one will prompt to remove the other (default: yes)
|
|
14766
15546
|
|
|
14767
15547
|
Examples:
|
|
14768
|
-
raggrep opencode install
|
|
15548
|
+
raggrep opencode install # Install tool (default)
|
|
15549
|
+
raggrep opencode install --tool # Force tool installation
|
|
15550
|
+
raggrep opencode install --skill # Force skill installation
|
|
14769
15551
|
`);
|
|
14770
15552
|
process.exit(0);
|
|
14771
15553
|
}
|
|
14772
15554
|
if (subcommand === "install") {
|
|
14773
|
-
|
|
14774
|
-
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
const
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
|
|
14781
|
-
|
|
14782
|
-
|
|
14783
|
-
*/
|
|
14784
|
-
async function getExecutor(): Promise<string> {
|
|
14785
|
-
try {
|
|
14786
|
-
// Try to find pnpm first (faster)
|
|
14787
|
-
await Bun.spawn(['pnpm', '--version'], { stdout: 'pipe', stderr: 'pipe' }).exited;
|
|
14788
|
-
return 'pnpx';
|
|
14789
|
-
} catch {
|
|
14790
|
-
// Fall back to npx
|
|
14791
|
-
return 'npx';
|
|
14792
|
-
}
|
|
14793
|
-
}
|
|
14794
|
-
|
|
14795
|
-
/**
|
|
14796
|
-
* Get the installed raggrep version
|
|
14797
|
-
*/
|
|
14798
|
-
async function getRagrepVersion(executor: string): Promise<string | null> {
|
|
14799
|
-
try {
|
|
14800
|
-
const proc = Bun.spawn([executor, 'raggrep', '--version'], { stdout: 'pipe', stderr: 'pipe' });
|
|
14801
|
-
const output = await new Response(proc.stdout).text();
|
|
14802
|
-
const match = output.match(/v([\\d.]+)/);
|
|
14803
|
-
return match ? match[1] : null;
|
|
14804
|
-
} catch {
|
|
14805
|
-
return null;
|
|
14806
|
-
}
|
|
14807
|
-
}
|
|
14808
|
-
|
|
14809
|
-
export default tool({
|
|
14810
|
-
description:
|
|
14811
|
-
"Semantic code search powered by RAG - understands INTENT, not just literal text. Parses code using AST to extract functions, classes, and symbols with full context. Finds relevant code even when exact keywords don't match. Superior to grep for exploratory searches like 'authentication logic', 'error handling patterns', or 'configuration loading'.\\n\\n\uD83C\uDFAF USE THIS TOOL FIRST when you need to:\\n• Find WHERE code is located (functions, components, services)\\n• Understand HOW code is structured\\n• Discover RELATED code across multiple files\\n• Get a QUICK overview of a topic\\n\\n❌ DON'T read multiple files manually when you can:\\n raggrep(\\"user authentication\\", { filter: [\\"src/\\"] })\\n\\n✅ INSTEAD of reading files one-by-one, search semantically:\\n • \\"Find the auth middleware\\" vs read: auth.ts, middleware.ts, index.ts...\\n • \\"Where are React components?\\" vs read: App.tsx, components/*, pages/*...\\n • \\"Database connection logic?\\" vs read: db.ts, config.ts, models/*...\\n • \\"Error handling patterns\\" vs read: error.ts, middleware.ts, handlers/*...\\n\\nThis saves ~10x tool calls and provides BETTER context by showing related code across the entire codebase.",
|
|
14812
|
-
args: {
|
|
14813
|
-
query: tool.schema
|
|
14814
|
-
.string()
|
|
14815
|
-
.describe(
|
|
14816
|
-
"Natural language search query describing what you want to find. Be specific: 'auth middleware that checks JWT', 'React hooks for data fetching', 'database connection pool config'. This is MUCH faster than reading files manually."
|
|
14817
|
-
),
|
|
14818
|
-
filter: tool.schema
|
|
14819
|
-
.array(tool.schema.string())
|
|
14820
|
-
.describe(
|
|
14821
|
-
"Array of path prefixes or glob patterns to narrow search scope (OR logic). If user mentions a directory, use it. Otherwise infer from context. Common patterns: ['src/auth'], ['*.tsx', 'components/'], ['api/', 'routes/'], ['docs/', '*.md'], ['*.test.ts']. For broad search use ['src/'] or ['**/*']."
|
|
14822
|
-
),
|
|
14823
|
-
top: tool.schema
|
|
14824
|
-
.number()
|
|
14825
|
-
.optional()
|
|
14826
|
-
.describe("Number of results to return (default: 10)"),
|
|
14827
|
-
minScore: tool.schema
|
|
14828
|
-
.number()
|
|
14829
|
-
.optional()
|
|
14830
|
-
.describe("Minimum similarity score 0-1 (default: 0.15)"),
|
|
14831
|
-
type: tool.schema
|
|
14832
|
-
.string()
|
|
14833
|
-
.optional()
|
|
14834
|
-
.describe(
|
|
14835
|
-
"Filter by single file extension without dot (e.g., 'ts', 'tsx', 'js', 'md'). Prefer using 'filter' with glob patterns like '*.ts' for more flexibility."
|
|
14836
|
-
),
|
|
14837
|
-
},
|
|
14838
|
-
async execute(args) {
|
|
14839
|
-
const executor = await getExecutor();
|
|
14840
|
-
const version = await getRagrepVersion(executor);
|
|
14841
|
-
|
|
14842
|
-
if (!version) {
|
|
14843
|
-
return \`Error: raggrep not found. Install it with: \${executor} install -g raggrep\`;
|
|
14844
|
-
}
|
|
14845
|
-
|
|
14846
|
-
const cmdArgs = [args.query];
|
|
14847
|
-
|
|
14848
|
-
if (args.top !== undefined) {
|
|
14849
|
-
cmdArgs.push("--top", String(args.top));
|
|
14850
|
-
}
|
|
14851
|
-
if (args.minScore !== undefined) {
|
|
14852
|
-
cmdArgs.push("--min-score", String(args.minScore));
|
|
14853
|
-
}
|
|
14854
|
-
if (args.type !== undefined) {
|
|
14855
|
-
cmdArgs.push("--type", args.type);
|
|
14856
|
-
}
|
|
14857
|
-
if (args.filter !== undefined && args.filter.length > 0) {
|
|
14858
|
-
for (const f of args.filter) {
|
|
14859
|
-
cmdArgs.push("--filter", f);
|
|
14860
|
-
}
|
|
14861
|
-
}
|
|
14862
|
-
|
|
14863
|
-
const proc = Bun.spawn([executor, 'raggrep', 'query', ...cmdArgs], { stdout: 'pipe' });
|
|
14864
|
-
const result = await new Response(proc.stdout).text();
|
|
14865
|
-
return result.trim();
|
|
14866
|
-
},
|
|
14867
|
-
});
|
|
14868
|
-
`;
|
|
15555
|
+
if (flags2.forceTool && flags2.forceSkill) {
|
|
15556
|
+
console.error("Error: --tool and --skill flags are mutually exclusive");
|
|
15557
|
+
process.exit(1);
|
|
15558
|
+
}
|
|
15559
|
+
const {
|
|
15560
|
+
detectOpenCodeVersion: detectOpenCodeVersion2,
|
|
15561
|
+
getInstallationMethod: getInstallationMethod2,
|
|
15562
|
+
installTool: installTool2,
|
|
15563
|
+
installSkill: installSkill2
|
|
15564
|
+
} = await Promise.resolve().then(() => (init_opencode(), exports_opencode));
|
|
14869
15565
|
try {
|
|
14870
|
-
|
|
14871
|
-
|
|
14872
|
-
|
|
14873
|
-
|
|
15566
|
+
let method;
|
|
15567
|
+
if (flags2.forceTool) {
|
|
15568
|
+
method = "tool";
|
|
15569
|
+
} else if (flags2.forceSkill) {
|
|
15570
|
+
method = "skill";
|
|
15571
|
+
} else {
|
|
15572
|
+
method = "tool";
|
|
15573
|
+
}
|
|
15574
|
+
console.log("RAGgrep OpenCode Installer");
|
|
15575
|
+
console.log(`==========================
|
|
15576
|
+
`);
|
|
15577
|
+
if (flags2.forceTool) {
|
|
15578
|
+
console.log("Forced tool-based installation (--tool flag)");
|
|
15579
|
+
} else if (flags2.forceSkill) {
|
|
15580
|
+
console.log("Forced skill-based installation (--skill flag)");
|
|
15581
|
+
} else {
|
|
15582
|
+
console.log("Default tool-based installation");
|
|
15583
|
+
}
|
|
15584
|
+
console.log(`Installing ${method}...
|
|
15585
|
+
`);
|
|
15586
|
+
let result;
|
|
15587
|
+
if (method === "tool") {
|
|
15588
|
+
result = await installTool2({ checkForOldSkill: true });
|
|
15589
|
+
} else {
|
|
15590
|
+
result = await installSkill2({ checkForOldTool: true });
|
|
15591
|
+
}
|
|
15592
|
+
if (!result.success) {
|
|
15593
|
+
process.exit(1);
|
|
15594
|
+
}
|
|
14874
15595
|
console.log(`
|
|
14875
|
-
|
|
15596
|
+
Installation completed successfully!`);
|
|
14876
15597
|
} catch (error) {
|
|
14877
|
-
console.error("Error
|
|
15598
|
+
console.error("Error during installation:", error);
|
|
14878
15599
|
process.exit(1);
|
|
14879
15600
|
}
|
|
14880
15601
|
} else {
|
|
@@ -14919,4 +15640,4 @@ Run 'raggrep <command> --help' for more information.
|
|
|
14919
15640
|
}
|
|
14920
15641
|
main();
|
|
14921
15642
|
|
|
14922
|
-
//# debugId=
|
|
15643
|
+
//# debugId=C8DA1DE58CD9C19A64756E2164756E21
|