@liendev/lien 0.38.1 → 0.39.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/dist/index.js +153 -38
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3666,42 +3666,146 @@ import { dirname as dirname3, join as join3 } from "path";
|
|
|
3666
3666
|
// src/cli/init.ts
|
|
3667
3667
|
init_banner();
|
|
3668
3668
|
import fs from "fs/promises";
|
|
3669
|
+
import os from "os";
|
|
3669
3670
|
import path from "path";
|
|
3670
3671
|
import chalk2 from "chalk";
|
|
3671
|
-
var
|
|
3672
|
-
|
|
3673
|
-
|
|
3672
|
+
var EDITORS = {
|
|
3673
|
+
cursor: {
|
|
3674
|
+
name: "Cursor",
|
|
3675
|
+
configPath: (rootDir) => path.join(rootDir, ".cursor", "mcp.json"),
|
|
3676
|
+
configKey: "mcpServers",
|
|
3677
|
+
buildEntry: () => ({ command: "lien", args: ["serve"] }),
|
|
3678
|
+
restartMessage: "Restart Cursor to activate."
|
|
3679
|
+
},
|
|
3680
|
+
"claude-code": {
|
|
3681
|
+
name: "Claude Code",
|
|
3682
|
+
configPath: (rootDir) => path.join(rootDir, ".mcp.json"),
|
|
3683
|
+
configKey: "mcpServers",
|
|
3684
|
+
buildEntry: () => ({ command: "lien", args: ["serve"] }),
|
|
3685
|
+
restartMessage: "Restart Claude Code to activate."
|
|
3686
|
+
},
|
|
3687
|
+
windsurf: {
|
|
3688
|
+
name: "Windsurf",
|
|
3689
|
+
configPath: () => path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
3690
|
+
configKey: "mcpServers",
|
|
3691
|
+
buildEntry: (rootDir) => ({
|
|
3692
|
+
command: "lien",
|
|
3693
|
+
args: ["serve", "--root", path.resolve(rootDir)]
|
|
3694
|
+
}),
|
|
3695
|
+
restartMessage: "Restart Windsurf to activate."
|
|
3696
|
+
},
|
|
3697
|
+
opencode: {
|
|
3698
|
+
name: "OpenCode",
|
|
3699
|
+
configPath: (rootDir) => path.join(rootDir, "opencode.json"),
|
|
3700
|
+
configKey: "mcp",
|
|
3701
|
+
buildEntry: () => ({ type: "local", command: ["lien", "serve"] }),
|
|
3702
|
+
restartMessage: "Restart OpenCode to activate."
|
|
3703
|
+
},
|
|
3704
|
+
"kilo-code": {
|
|
3705
|
+
name: "Kilo Code",
|
|
3706
|
+
configPath: (rootDir) => path.join(rootDir, ".kilocode", "mcp.json"),
|
|
3707
|
+
configKey: "mcpServers",
|
|
3708
|
+
buildEntry: () => ({ command: "lien", args: ["serve"] }),
|
|
3709
|
+
restartMessage: "Restart VS Code to activate."
|
|
3710
|
+
},
|
|
3711
|
+
antigravity: {
|
|
3712
|
+
name: "Antigravity",
|
|
3713
|
+
configPath: null,
|
|
3714
|
+
configKey: "mcpServers",
|
|
3715
|
+
buildEntry: () => ({ command: "lien", args: ["serve"] }),
|
|
3716
|
+
restartMessage: "Add this to your Antigravity MCP settings."
|
|
3717
|
+
}
|
|
3674
3718
|
};
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
const
|
|
3680
|
-
|
|
3719
|
+
function isPlainObject(value) {
|
|
3720
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
3721
|
+
}
|
|
3722
|
+
function displayPath(configPath, rootDir) {
|
|
3723
|
+
const rel = path.relative(rootDir, configPath);
|
|
3724
|
+
if (!rel.startsWith("..")) return rel;
|
|
3725
|
+
const home = os.homedir();
|
|
3726
|
+
if (configPath.startsWith(home)) return "~" + configPath.slice(home.length);
|
|
3727
|
+
return configPath;
|
|
3728
|
+
}
|
|
3729
|
+
async function readJsonFile(filePath) {
|
|
3681
3730
|
try {
|
|
3682
|
-
const raw = await fs.readFile(
|
|
3731
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
3683
3732
|
const parsed = JSON.parse(raw);
|
|
3684
|
-
|
|
3685
|
-
existingConfig = parsed;
|
|
3686
|
-
}
|
|
3733
|
+
return isPlainObject(parsed) ? parsed : null;
|
|
3687
3734
|
} catch {
|
|
3735
|
+
return null;
|
|
3736
|
+
}
|
|
3737
|
+
}
|
|
3738
|
+
async function writeEditorConfig(editor, rootDir) {
|
|
3739
|
+
const configPath = editor.configPath(rootDir);
|
|
3740
|
+
const entry = editor.buildEntry(rootDir);
|
|
3741
|
+
const key = editor.configKey;
|
|
3742
|
+
const label = displayPath(configPath, rootDir);
|
|
3743
|
+
const existingConfig = await readJsonFile(configPath);
|
|
3744
|
+
const existingSection = existingConfig?.[key];
|
|
3745
|
+
if (existingSection?.lien) {
|
|
3746
|
+
console.log(chalk2.green(`
|
|
3747
|
+
\u2713 Already configured \u2014 ${label} contains lien entry`));
|
|
3748
|
+
return;
|
|
3688
3749
|
}
|
|
3689
|
-
if (existingConfig
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3750
|
+
if (existingConfig) {
|
|
3751
|
+
const section = isPlainObject(existingSection) ? { ...existingSection } : {};
|
|
3752
|
+
section.lien = entry;
|
|
3753
|
+
existingConfig[key] = section;
|
|
3754
|
+
await fs.writeFile(configPath, JSON.stringify(existingConfig, null, 2) + "\n");
|
|
3755
|
+
console.log(chalk2.green(`
|
|
3756
|
+
\u2713 Added lien to existing ${label}`));
|
|
3757
|
+
} else {
|
|
3758
|
+
await fs.mkdir(path.dirname(configPath), { recursive: true });
|
|
3759
|
+
const config = { [key]: { lien: entry } };
|
|
3760
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
3761
|
+
console.log(chalk2.green(`
|
|
3762
|
+
\u2713 Created ${label}`));
|
|
3763
|
+
}
|
|
3764
|
+
}
|
|
3765
|
+
async function promptForEditor() {
|
|
3766
|
+
const { default: inquirer } = await import("inquirer");
|
|
3767
|
+
const { editor } = await inquirer.prompt([
|
|
3768
|
+
{
|
|
3769
|
+
type: "list",
|
|
3770
|
+
name: "editor",
|
|
3771
|
+
message: "Which editor are you using?",
|
|
3772
|
+
choices: [
|
|
3773
|
+
{ name: "Cursor", value: "cursor" },
|
|
3774
|
+
{ name: "Claude Code", value: "claude-code" },
|
|
3775
|
+
{ name: "Windsurf", value: "windsurf" },
|
|
3776
|
+
{ name: "OpenCode", value: "opencode" },
|
|
3777
|
+
{ name: "Kilo Code", value: "kilo-code" },
|
|
3778
|
+
{ name: "Antigravity", value: "antigravity" }
|
|
3779
|
+
],
|
|
3780
|
+
default: "cursor"
|
|
3781
|
+
}
|
|
3782
|
+
]);
|
|
3783
|
+
return editor;
|
|
3784
|
+
}
|
|
3785
|
+
async function initCommand(options = {}) {
|
|
3786
|
+
showCompactBanner();
|
|
3787
|
+
const rootDir = options.path || process.cwd();
|
|
3788
|
+
let editorId;
|
|
3789
|
+
if (options.editor) {
|
|
3790
|
+
editorId = options.editor;
|
|
3791
|
+
} else if (!process.stdout.isTTY) {
|
|
3792
|
+
console.error(chalk2.red("Error: Use --editor to specify your editor in non-interactive mode."));
|
|
3793
|
+
process.exit(1);
|
|
3698
3794
|
} else {
|
|
3699
|
-
await
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3795
|
+
editorId = await promptForEditor();
|
|
3796
|
+
}
|
|
3797
|
+
const editor = EDITORS[editorId];
|
|
3798
|
+
if (editor.configPath) {
|
|
3799
|
+
await writeEditorConfig(editor, rootDir);
|
|
3800
|
+
console.log(chalk2.dim(` ${editor.restartMessage}
|
|
3801
|
+
`));
|
|
3802
|
+
} else {
|
|
3803
|
+
const entry = editor.buildEntry(rootDir);
|
|
3804
|
+
const snippet = { [editor.configKey]: { lien: entry } };
|
|
3805
|
+
console.log(chalk2.yellow(`
|
|
3806
|
+
${editor.restartMessage}`));
|
|
3807
|
+
console.log(JSON.stringify(snippet, null, 2));
|
|
3703
3808
|
}
|
|
3704
|
-
console.log(chalk2.dim(" Restart Cursor to activate.\n"));
|
|
3705
3809
|
const legacyConfigPath = path.join(rootDir, ".lien.config.json");
|
|
3706
3810
|
try {
|
|
3707
3811
|
await fs.access(legacyConfigPath);
|
|
@@ -3716,7 +3820,7 @@ init_banner();
|
|
|
3716
3820
|
import chalk3 from "chalk";
|
|
3717
3821
|
import fs2 from "fs/promises";
|
|
3718
3822
|
import path2 from "path";
|
|
3719
|
-
import
|
|
3823
|
+
import os2 from "os";
|
|
3720
3824
|
import { createRequire as createRequire2 } from "module";
|
|
3721
3825
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3722
3826
|
import {
|
|
@@ -3844,7 +3948,7 @@ async function statusCommand(options = {}) {
|
|
|
3844
3948
|
}
|
|
3845
3949
|
const rootDir = process.cwd();
|
|
3846
3950
|
const repoId = extractRepoId(rootDir);
|
|
3847
|
-
const indexPath = path2.join(
|
|
3951
|
+
const indexPath = path2.join(os2.homedir(), ".lien", "indices", repoId);
|
|
3848
3952
|
if (format === "json") {
|
|
3849
3953
|
await outputJson(rootDir, indexPath);
|
|
3850
3954
|
return;
|
|
@@ -8703,6 +8807,7 @@ var GetComplexitySchema = external_exports.object({
|
|
|
8703
8807
|
threshold: external_exports.number().int().min(1, "Threshold must be at least 1").optional().describe(
|
|
8704
8808
|
"Only return functions above this complexity threshold.\n\nNote: Violations are first identified using the threshold from lien.config.json (default: 15). This parameter filters those violations to show only items above the specified value. Setting threshold below the config threshold will not show additional functions."
|
|
8705
8809
|
),
|
|
8810
|
+
metricType: external_exports.enum(["cyclomatic", "cognitive", "halstead_effort", "halstead_bugs"]).optional().describe("Filter violations to a specific metric type. If omitted, returns all types."),
|
|
8706
8811
|
crossRepo: external_exports.boolean().default(false).describe(
|
|
8707
8812
|
"If true, analyze complexity across all repos in the organization (requires a cross-repo-capable backend, currently Qdrant).\n\nDefault: false (single-repo analysis)\nWhen enabled, results are aggregated by repository."
|
|
8708
8813
|
),
|
|
@@ -8858,7 +8963,7 @@ Use for tech debt analysis and refactoring prioritization:
|
|
|
8858
8963
|
|
|
8859
8964
|
Examples:
|
|
8860
8965
|
get_complexity({ top: 10 })
|
|
8861
|
-
get_complexity({ files: ["src/auth.ts", "
|
|
8966
|
+
get_complexity({ files: ["src/auth.ts"], metricType: "cognitive" })
|
|
8862
8967
|
get_complexity({ threshold: 15 })
|
|
8863
8968
|
|
|
8864
8969
|
Returns:
|
|
@@ -10183,15 +10288,15 @@ async function fetchCrossRepoChunks(vectorDB, crossRepo, repoIds, log) {
|
|
|
10183
10288
|
}
|
|
10184
10289
|
return { chunks: [], fallback: true };
|
|
10185
10290
|
}
|
|
10186
|
-
function processViolations(report, threshold, top) {
|
|
10291
|
+
function processViolations(report, threshold, top, metricType) {
|
|
10187
10292
|
const allViolations = (0, import_collect.default)(Object.entries(report.files)).flatMap(
|
|
10188
10293
|
([
|
|
10189
10294
|
,
|
|
10190
10295
|
/* filepath unused */
|
|
10191
10296
|
fileData
|
|
10192
|
-
]) => fileData.violations.map((v) => transformViolation(v, fileData))
|
|
10297
|
+
]) => fileData.violations.filter((v) => !metricType || v.metricType === metricType).filter((v) => threshold === void 0 || v.complexity >= threshold).map((v) => transformViolation(v, fileData))
|
|
10193
10298
|
).sortByDesc("complexity").all();
|
|
10194
|
-
const violations =
|
|
10299
|
+
const violations = allViolations;
|
|
10195
10300
|
const severityCounts = (0, import_collect.default)(violations).countBy("severity").all();
|
|
10196
10301
|
return {
|
|
10197
10302
|
violations,
|
|
@@ -10208,7 +10313,7 @@ function buildCrossRepoFallbackNote(fallback) {
|
|
|
10208
10313
|
async function handleGetComplexity(args, ctx) {
|
|
10209
10314
|
const { vectorDB, log, checkAndReconnect, getIndexMetadata } = ctx;
|
|
10210
10315
|
return await wrapToolHandler(GetComplexitySchema, async (validatedArgs) => {
|
|
10211
|
-
const { crossRepo, repoIds, files, top, threshold } = validatedArgs;
|
|
10316
|
+
const { crossRepo, repoIds, files, top, threshold, metricType } = validatedArgs;
|
|
10212
10317
|
log(`Analyzing complexity${crossRepo ? " (cross-repo)" : ""}...`);
|
|
10213
10318
|
await checkAndReconnect();
|
|
10214
10319
|
const { chunks: allChunks, fallback } = await fetchCrossRepoChunks(
|
|
@@ -10223,7 +10328,8 @@ async function handleGetComplexity(args, ctx) {
|
|
|
10223
10328
|
const { violations, topViolations, bySeverity } = processViolations(
|
|
10224
10329
|
report,
|
|
10225
10330
|
threshold,
|
|
10226
|
-
top ?? 10
|
|
10331
|
+
top ?? 10,
|
|
10332
|
+
metricType
|
|
10227
10333
|
);
|
|
10228
10334
|
const note = buildCrossRepoFallbackNote(fallback);
|
|
10229
10335
|
if (note) {
|
|
@@ -11318,9 +11424,9 @@ async function complexityCommand(options) {
|
|
|
11318
11424
|
// src/cli/config.ts
|
|
11319
11425
|
import chalk8 from "chalk";
|
|
11320
11426
|
import path9 from "path";
|
|
11321
|
-
import
|
|
11427
|
+
import os3 from "os";
|
|
11322
11428
|
import { loadGlobalConfig, mergeGlobalConfig } from "@liendev/core";
|
|
11323
|
-
var CONFIG_PATH = path9.join(
|
|
11429
|
+
var CONFIG_PATH = path9.join(os3.homedir(), ".lien", "config.json");
|
|
11324
11430
|
var ALLOWED_KEYS = {
|
|
11325
11431
|
backend: {
|
|
11326
11432
|
values: ["lancedb", "qdrant"],
|
|
@@ -11418,7 +11524,16 @@ try {
|
|
|
11418
11524
|
}
|
|
11419
11525
|
var program = new Command();
|
|
11420
11526
|
program.name("lien").description("Local semantic code search for AI assistants via MCP").version(packageJson3.version);
|
|
11421
|
-
program.command("init").description("Initialize Lien in the current directory").
|
|
11527
|
+
program.command("init").description("Initialize Lien in the current directory").addOption(
|
|
11528
|
+
new Option("-e, --editor <editor>", "Editor to configure MCP for").choices([
|
|
11529
|
+
"cursor",
|
|
11530
|
+
"claude-code",
|
|
11531
|
+
"windsurf",
|
|
11532
|
+
"opencode",
|
|
11533
|
+
"kilo-code",
|
|
11534
|
+
"antigravity"
|
|
11535
|
+
])
|
|
11536
|
+
).option("-p, --path <path>", "Path to initialize (defaults to current directory)").action(initCommand);
|
|
11422
11537
|
program.command("index").description("Index the codebase for semantic search").option("-f, --force", "Force full reindex (skip incremental)").option("-v, --verbose", "Show detailed logging during indexing").action(indexCommand);
|
|
11423
11538
|
program.command("serve").description(
|
|
11424
11539
|
"Start the MCP server (works with Cursor, Claude Code, Windsurf, and any MCP client)"
|