@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 +5 -8
- package/dist/index.js +41 -158
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
- package/CURSOR_RULES_TEMPLATE.md +0 -157
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
|
-
#
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
8959
|
-
|
|
8960
|
-
|
|
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
|
|
8967
|
-
|
|
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 ${
|
|
8857
|
+
log(`Found ${queryResult.results.length} matches using ${queryResult.method} method`);
|
|
8975
8858
|
return {
|
|
8976
8859
|
indexInfo: getIndexMetadata(),
|
|
8977
|
-
method:
|
|
8978
|
-
results,
|
|
8979
|
-
note:
|
|
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
|
|
9381
|
-
var
|
|
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(
|
|
9268
|
+
packageJson2 = require3(join2(__dirname2, "../package.json"));
|
|
9386
9269
|
} catch {
|
|
9387
|
-
packageJson2 = require3(join2(
|
|
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
|
|
9723
|
-
var
|
|
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(
|
|
9610
|
+
packageJson3 = require4(join3(__dirname3, "../package.json"));
|
|
9728
9611
|
} catch {
|
|
9729
|
-
packageJson3 = require4(join3(
|
|
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);
|