@liendev/lien 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,15 +26,10 @@ Lien connects AI coding assistants like Cursor to your codebase through the Mode
26
26
  ## Quick Start
27
27
 
28
28
  ```bash
29
- # Install
29
+ # 1. Install
30
30
  npm install -g @liendev/lien
31
31
 
32
- # Setup in your project
33
- cd /path/to/your/project
34
- lien init
35
- lien index
36
-
37
- # Configure Cursor - create .cursor/mcp.json
32
+ # 2. Add to your project - create .cursor/mcp.json
38
33
  {
39
34
  "mcpServers": {
40
35
  "lien": {
@@ -44,9 +39,11 @@ lien index
44
39
  }
45
40
  }
46
41
 
47
- # Restart Cursor and start asking questions!
42
+ # 3. Restart Cursor and start asking questions!
48
43
  ```
49
44
 
45
+ That's it—zero configuration needed. Lien auto-detects your project and indexes on first use.
46
+
50
47
  **👉 [Full installation guide](https://lien.dev/guide/installation)**
51
48
 
52
49
  ### Qdrant Backend (Cross-Repo Search)
package/dist/index.js CHANGED
@@ -3591,15 +3591,13 @@ var require_dist = __commonJS({
3591
3591
  // src/cli/index.ts
3592
3592
  import { Command } from "commander";
3593
3593
  import { createRequire as createRequire3 } from "module";
3594
- import { fileURLToPath as fileURLToPath4 } from "url";
3594
+ import { fileURLToPath as fileURLToPath3 } from "url";
3595
3595
  import { dirname as dirname3, join as join3 } from "path";
3596
3596
 
3597
3597
  // src/cli/init.ts
3598
3598
  import fs from "fs/promises";
3599
3599
  import path from "path";
3600
- import { fileURLToPath as fileURLToPath2 } from "url";
3601
3600
  import chalk2 from "chalk";
3602
- import inquirer from "inquirer";
3603
3601
 
3604
3602
  // src/utils/banner.ts
3605
3603
  import figlet from "figlet";
@@ -3661,8 +3659,6 @@ function showCompactBanner() {
3661
3659
  }
3662
3660
 
3663
3661
  // src/cli/init.ts
3664
- var __filename2 = fileURLToPath2(import.meta.url);
3665
- var __dirname2 = path.dirname(__filename2);
3666
3662
  async function initCommand(options = {}) {
3667
3663
  showCompactBanner();
3668
3664
  console.log(chalk2.bold("\nLien Initialization\n"));
@@ -3695,129 +3691,6 @@ async function initCommand(options = {}) {
3695
3691
  console.log(chalk2.dim(" You can safely delete it."));
3696
3692
  } catch {
3697
3693
  }
3698
- await promptAndInstallCursorRules(rootDir, options);
3699
- }
3700
- async function getPathType(filepath) {
3701
- try {
3702
- const stats = await fs.stat(filepath);
3703
- if (stats.isDirectory()) return "directory";
3704
- if (stats.isFile()) return "file";
3705
- return "other";
3706
- } catch {
3707
- return "none";
3708
- }
3709
- }
3710
- async function convertRulesFileToDirectory(rulesPath, templatePath) {
3711
- const existingRules = await fs.readFile(rulesPath, "utf-8");
3712
- const parentDir = path.dirname(rulesPath);
3713
- const baseName = path.basename(rulesPath);
3714
- const tempDir = await fs.mkdtemp(path.join(parentDir, baseName + "_tmp_"));
3715
- const backupPath = rulesPath + ".backup";
3716
- try {
3717
- await fs.writeFile(path.join(tempDir, "project.mdc"), existingRules);
3718
- await fs.copyFile(templatePath, path.join(tempDir, "lien.mdc"));
3719
- try {
3720
- await fs.unlink(backupPath);
3721
- } catch {
3722
- }
3723
- await fs.rename(rulesPath, backupPath);
3724
- try {
3725
- await fs.rename(tempDir, rulesPath);
3726
- try {
3727
- await fs.unlink(backupPath);
3728
- } catch {
3729
- console.log(chalk2.yellow("\u26A0\uFE0F Could not remove backup file, but conversion succeeded"));
3730
- console.log(chalk2.dim(`Backup file: ${backupPath}`));
3731
- }
3732
- } catch (renameErr) {
3733
- try {
3734
- await fs.rename(backupPath, rulesPath);
3735
- } catch (restoreErr) {
3736
- console.log(chalk2.red("\u274C Failed to restore original .cursor/rules from backup after failed conversion."));
3737
- console.log(chalk2.red(` - Original error: ${renameErr instanceof Error ? renameErr.message : renameErr}`));
3738
- console.log(chalk2.red(` - Restore error: ${restoreErr instanceof Error ? restoreErr.message : restoreErr}`));
3739
- console.log(chalk2.red(` - Backup file location: ${backupPath}`));
3740
- throw new Error("Failed to convert .cursor/rules to directory and failed to restore from backup. Manual recovery needed.");
3741
- }
3742
- throw renameErr;
3743
- }
3744
- console.log(chalk2.green("\u2713 Converted .cursor/rules to directory"));
3745
- console.log(chalk2.green(" - Your project rules: .cursor/rules/project.mdc"));
3746
- console.log(chalk2.green(" - Lien rules: .cursor/rules/lien.mdc"));
3747
- } catch (err) {
3748
- try {
3749
- await fs.rm(tempDir, { recursive: true, force: true });
3750
- } catch {
3751
- }
3752
- throw err;
3753
- }
3754
- }
3755
- async function handleExistingRulesDirectory(rulesPath, templatePath) {
3756
- const targetPath = path.join(rulesPath, "lien.mdc");
3757
- try {
3758
- await fs.access(targetPath);
3759
- console.log(chalk2.dim("lien.mdc already exists in .cursor/rules/, skipping..."));
3760
- return;
3761
- } catch {
3762
- }
3763
- await fs.copyFile(templatePath, targetPath);
3764
- console.log(chalk2.green("\u2713 Installed Cursor rules as .cursor/rules/lien.mdc"));
3765
- }
3766
- async function handleExistingRulesFile(rulesPath, templatePath, options) {
3767
- if (options.yes) {
3768
- console.log(chalk2.dim("Skipped Cursor rules installation (preserving existing .cursor/rules file)"));
3769
- return;
3770
- }
3771
- const { convertToDir } = await inquirer.prompt([{
3772
- type: "confirm",
3773
- name: "convertToDir",
3774
- message: "Existing .cursor/rules file found. Convert to directory and preserve your rules?",
3775
- default: true
3776
- }]);
3777
- if (convertToDir) {
3778
- await convertRulesFileToDirectory(rulesPath, templatePath);
3779
- } else {
3780
- console.log(chalk2.dim("Skipped Cursor rules installation (preserving existing file)"));
3781
- }
3782
- }
3783
- async function handleInvalidRulesPath() {
3784
- console.log(chalk2.yellow("\u26A0\uFE0F .cursor/rules exists but is not a regular file or directory"));
3785
- console.log(chalk2.dim("Skipped Cursor rules installation"));
3786
- }
3787
- async function handleFreshRulesInstall(rulesPath, templatePath) {
3788
- await fs.mkdir(rulesPath, { recursive: true });
3789
- await fs.copyFile(templatePath, path.join(rulesPath, "lien.mdc"));
3790
- console.log(chalk2.green("\u2713 Installed Cursor rules as .cursor/rules/lien.mdc"));
3791
- }
3792
- async function installCursorRulesFiles(rootDir, options) {
3793
- const cursorRulesDir = path.join(rootDir, ".cursor");
3794
- await fs.mkdir(cursorRulesDir, { recursive: true });
3795
- const templatePath = path.join(__dirname2, "../CURSOR_RULES_TEMPLATE.md");
3796
- const rulesPath = path.join(cursorRulesDir, "rules");
3797
- const pathType = await getPathType(rulesPath);
3798
- const handlers = {
3799
- directory: () => handleExistingRulesDirectory(rulesPath, templatePath),
3800
- file: () => handleExistingRulesFile(rulesPath, templatePath, options),
3801
- other: () => handleInvalidRulesPath(),
3802
- none: () => handleFreshRulesInstall(rulesPath, templatePath)
3803
- };
3804
- await handlers[pathType]();
3805
- }
3806
- async function promptAndInstallCursorRules(rootDir, options) {
3807
- const shouldInstall = options.yes || (await inquirer.prompt([{
3808
- type: "confirm",
3809
- name: "installCursorRules",
3810
- message: "Install recommended Cursor rules?",
3811
- default: true
3812
- }])).installCursorRules;
3813
- if (!shouldInstall) return;
3814
- try {
3815
- await installCursorRulesFiles(rootDir, options);
3816
- } catch (error) {
3817
- console.log(chalk2.yellow("\u26A0\uFE0F Could not install Cursor rules"));
3818
- console.log(chalk2.dim(`Error: ${error instanceof Error ? error.message : "Unknown error"}`));
3819
- console.log(chalk2.dim("You can manually copy CURSOR_RULES_TEMPLATE.md to .cursor/rules/lien.mdc"));
3820
- }
3821
3694
  }
3822
3695
 
3823
3696
  // src/cli/status.ts
@@ -4119,7 +3992,7 @@ import path4 from "path";
4119
3992
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4120
3993
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4121
3994
  import { createRequire as createRequire2 } from "module";
4122
- import { fileURLToPath as fileURLToPath3 } from "url";
3995
+ import { fileURLToPath as fileURLToPath2 } from "url";
4123
3996
  import { dirname as dirname2, join as join2 } from "path";
4124
3997
  import {
4125
3998
  LocalEmbeddings,
@@ -8487,7 +8360,7 @@ var tools = [
8487
8360
  toMCPToolSchema(
8488
8361
  SemanticSearchSchema,
8489
8362
  "semantic_search",
8490
- `Search codebase by MEANING, not text. USE THIS INSTEAD OF grep/ripgrep for finding implementations, features, or understanding how code works.
8363
+ `Search codebase by MEANING, not text. Complements grep - use this for discovery and understanding, grep for exact matches.
8491
8364
 
8492
8365
  Examples:
8493
8366
  - "Where is authentication handled?" \u2192 semantic_search({ query: "handles user authentication" })
@@ -8938,6 +8811,25 @@ async function handleGetFilesContext(args, ctx) {
8938
8811
  }
8939
8812
 
8940
8813
  // src/mcp/handlers/list-functions.ts
8814
+ async function performContentScan(vectorDB, args, log) {
8815
+ log("Falling back to content scan...");
8816
+ let results = await vectorDB.scanWithFilter({
8817
+ language: args.language,
8818
+ limit: 200
8819
+ // Fetch more, we'll filter by symbolName
8820
+ });
8821
+ if (args.pattern) {
8822
+ const regex = new RegExp(args.pattern, "i");
8823
+ results = results.filter((r) => {
8824
+ const symbolName = r.metadata?.symbolName;
8825
+ return symbolName && regex.test(symbolName);
8826
+ });
8827
+ }
8828
+ return {
8829
+ results: results.slice(0, 50),
8830
+ method: "content"
8831
+ };
8832
+ }
8941
8833
  async function handleListFunctions(args, ctx) {
8942
8834
  const { vectorDB, log, checkAndReconnect, getIndexMetadata } = ctx;
8943
8835
  return await wrapToolHandler(
@@ -8945,38 +8837,29 @@ async function handleListFunctions(args, ctx) {
8945
8837
  async (validatedArgs) => {
8946
8838
  log("Listing functions with symbol metadata...");
8947
8839
  await checkAndReconnect();
8948
- let results;
8949
- let usedMethod = "symbols";
8840
+ let queryResult;
8950
8841
  try {
8951
- results = await vectorDB.querySymbols({
8842
+ const results = await vectorDB.querySymbols({
8952
8843
  language: validatedArgs.language,
8953
8844
  pattern: validatedArgs.pattern,
8954
8845
  limit: 50
8955
8846
  });
8956
8847
  if (results.length === 0 && (validatedArgs.language || validatedArgs.pattern)) {
8957
8848
  log("No symbol results, falling back to content scan...");
8958
- results = await vectorDB.scanWithFilter({
8959
- language: validatedArgs.language,
8960
- pattern: validatedArgs.pattern,
8961
- limit: 50
8962
- });
8963
- usedMethod = "content";
8849
+ queryResult = await performContentScan(vectorDB, validatedArgs, log);
8850
+ } else {
8851
+ queryResult = { results, method: "symbols" };
8964
8852
  }
8965
8853
  } catch (error) {
8966
- log(`Symbol query failed, falling back to content scan: ${error}`);
8967
- results = await vectorDB.scanWithFilter({
8968
- language: validatedArgs.language,
8969
- pattern: validatedArgs.pattern,
8970
- limit: 50
8971
- });
8972
- usedMethod = "content";
8854
+ log(`Symbol query failed: ${error}`);
8855
+ queryResult = await performContentScan(vectorDB, validatedArgs, log);
8973
8856
  }
8974
- log(`Found ${results.length} matches using ${usedMethod} method`);
8857
+ log(`Found ${queryResult.results.length} matches using ${queryResult.method} method`);
8975
8858
  return {
8976
8859
  indexInfo: getIndexMetadata(),
8977
- method: usedMethod,
8978
- results,
8979
- note: usedMethod === "content" ? 'Using content search. Run "lien reindex" to enable faster symbol-based queries.' : void 0
8860
+ method: queryResult.method,
8861
+ results: queryResult.results,
8862
+ note: queryResult.method === "content" ? 'Using content search. Run "lien reindex" to enable faster symbol-based queries.' : void 0
8980
8863
  };
8981
8864
  }
8982
8865
  )(args);
@@ -9377,14 +9260,14 @@ function registerMCPHandlers(server, toolContext, log) {
9377
9260
  }
9378
9261
 
9379
9262
  // src/mcp/server.ts
9380
- var __filename3 = fileURLToPath3(import.meta.url);
9381
- var __dirname3 = dirname2(__filename3);
9263
+ var __filename2 = fileURLToPath2(import.meta.url);
9264
+ var __dirname2 = dirname2(__filename2);
9382
9265
  var require3 = createRequire2(import.meta.url);
9383
9266
  var packageJson2;
9384
9267
  try {
9385
- packageJson2 = require3(join2(__dirname3, "../package.json"));
9268
+ packageJson2 = require3(join2(__dirname2, "../package.json"));
9386
9269
  } catch {
9387
- packageJson2 = require3(join2(__dirname3, "../../package.json"));
9270
+ packageJson2 = require3(join2(__dirname2, "../../package.json"));
9388
9271
  }
9389
9272
  async function initializeDatabase(rootDir, log) {
9390
9273
  const embeddings = new LocalEmbeddings();
@@ -9719,14 +9602,14 @@ async function complexityCommand(options) {
9719
9602
  }
9720
9603
 
9721
9604
  // src/cli/index.ts
9722
- var __filename4 = fileURLToPath4(import.meta.url);
9723
- var __dirname4 = dirname3(__filename4);
9605
+ var __filename3 = fileURLToPath3(import.meta.url);
9606
+ var __dirname3 = dirname3(__filename3);
9724
9607
  var require4 = createRequire3(import.meta.url);
9725
9608
  var packageJson3;
9726
9609
  try {
9727
- packageJson3 = require4(join3(__dirname4, "../package.json"));
9610
+ packageJson3 = require4(join3(__dirname3, "../package.json"));
9728
9611
  } catch {
9729
- packageJson3 = require4(join3(__dirname4, "../../package.json"));
9612
+ packageJson3 = require4(join3(__dirname3, "../../package.json"));
9730
9613
  }
9731
9614
  var program = new Command();
9732
9615
  program.name("lien").description("Local semantic code search for AI assistants via MCP").version(packageJson3.version);