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/dist/cli/main.js CHANGED
@@ -290,11 +290,13 @@ class InlineProgressLogger {
290
290
  }
291
291
  }
292
292
  progress(message) {
293
- process.stdout.write(`\r${message}`);
294
- const padding = Math.max(0, this.lastProgressLength - message.length);
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
- logger.progress(` [${completedCount}/${totalToProcess}] Indexing: ${relativePath}`);
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
- logger.progress(` [${completedCount}/${totalFiles}] Processing: ${relativePath}`);
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.12.2",
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 opencode integration
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 the raggrep tool for opencode
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 the raggrep tool to ~/.config/opencode/tool/raggrep.ts
14765
- This allows opencode to use raggrep for semantic code search.
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
- const os4 = await import("os");
14774
- const fs10 = await import("fs/promises");
14775
- const path25 = await import("path");
14776
- const homeDir = os4.homedir();
14777
- const toolDir = path25.join(homeDir, ".config", "opencode", "tool");
14778
- const toolPath = path25.join(toolDir, "raggrep.ts");
14779
- const toolContent = `import { tool } from "@opencode-ai/plugin";
14780
-
14781
- /**
14782
- * Get the package executor command (pnpx if available, otherwise npx)
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
- await fs10.mkdir(toolDir, { recursive: true });
14871
- await fs10.writeFile(toolPath, toolContent, "utf-8");
14872
- console.log(`Installed raggrep tool for opencode.`);
14873
- console.log(` Location: ${toolPath}`);
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
- The raggrep tool is now available in opencode.`);
15596
+ Installation completed successfully!`);
14876
15597
  } catch (error) {
14877
- console.error("Error installing opencode tool:", 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=049DF49BCF57EE1664756E2164756E21
15643
+ //# debugId=C8DA1DE58CD9C19A64756E2164756E21