@liendev/lien 0.38.0 → 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/README.md +1 -1
- package/dist/index.js +362 -124
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -58,6 +58,7 @@ function wrapInBox(text, footer, padding = 1) {
|
|
|
58
58
|
return [top, ...paddedLines, separator, paddedFooter, bottom].join("\n");
|
|
59
59
|
}
|
|
60
60
|
function showBanner() {
|
|
61
|
+
if (!process.stderr.isTTY) return;
|
|
61
62
|
const banner = figlet.textSync("LIEN", {
|
|
62
63
|
font: "ANSI Shadow",
|
|
63
64
|
horizontalLayout: "fitted",
|
|
@@ -69,6 +70,7 @@ function showBanner() {
|
|
|
69
70
|
console.error();
|
|
70
71
|
}
|
|
71
72
|
function showCompactBanner() {
|
|
73
|
+
if (!process.stdout.isTTY) return;
|
|
72
74
|
const banner = figlet.textSync("LIEN", {
|
|
73
75
|
font: "ANSI Shadow",
|
|
74
76
|
horizontalLayout: "fitted",
|
|
@@ -3656,50 +3658,154 @@ var require_dist = __commonJS({
|
|
|
3656
3658
|
});
|
|
3657
3659
|
|
|
3658
3660
|
// src/cli/index.ts
|
|
3659
|
-
import { Command } from "commander";
|
|
3660
|
-
import { createRequire as
|
|
3661
|
-
import { fileURLToPath as
|
|
3661
|
+
import { Command, Option } from "commander";
|
|
3662
|
+
import { createRequire as createRequire4 } from "module";
|
|
3663
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
3662
3664
|
import { dirname as dirname3, join as join3 } from "path";
|
|
3663
3665
|
|
|
3664
3666
|
// src/cli/init.ts
|
|
3665
3667
|
init_banner();
|
|
3666
3668
|
import fs from "fs/promises";
|
|
3669
|
+
import os from "os";
|
|
3667
3670
|
import path from "path";
|
|
3668
3671
|
import chalk2 from "chalk";
|
|
3669
|
-
var
|
|
3670
|
-
|
|
3671
|
-
|
|
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
|
+
}
|
|
3672
3718
|
};
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
const
|
|
3678
|
-
|
|
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) {
|
|
3679
3730
|
try {
|
|
3680
|
-
const raw = await fs.readFile(
|
|
3731
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
3681
3732
|
const parsed = JSON.parse(raw);
|
|
3682
|
-
|
|
3683
|
-
existingConfig = parsed;
|
|
3684
|
-
}
|
|
3733
|
+
return isPlainObject(parsed) ? parsed : null;
|
|
3685
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;
|
|
3686
3749
|
}
|
|
3687
|
-
if (existingConfig
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
await fs.writeFile(mcpConfigPath, JSON.stringify(existingConfig, null, 2) + "\n");
|
|
3695
|
-
console.log(chalk2.green("\n\u2713 Added lien to existing .cursor/mcp.json"));
|
|
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}`));
|
|
3696
3757
|
} else {
|
|
3697
|
-
await fs.mkdir(
|
|
3698
|
-
const config = {
|
|
3699
|
-
await fs.writeFile(
|
|
3700
|
-
console.log(chalk2.green(
|
|
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);
|
|
3794
|
+
} else {
|
|
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));
|
|
3701
3808
|
}
|
|
3702
|
-
console.log(chalk2.dim(" Restart Cursor to activate.\n"));
|
|
3703
3809
|
const legacyConfigPath = path.join(rootDir, ".lien.config.json");
|
|
3704
3810
|
try {
|
|
3705
3811
|
await fs.access(legacyConfigPath);
|
|
@@ -3714,7 +3820,9 @@ init_banner();
|
|
|
3714
3820
|
import chalk3 from "chalk";
|
|
3715
3821
|
import fs2 from "fs/promises";
|
|
3716
3822
|
import path2 from "path";
|
|
3717
|
-
import
|
|
3823
|
+
import os2 from "os";
|
|
3824
|
+
import { createRequire as createRequire2 } from "module";
|
|
3825
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3718
3826
|
import {
|
|
3719
3827
|
isGitRepo,
|
|
3720
3828
|
getCurrentBranch,
|
|
@@ -3727,75 +3835,179 @@ import {
|
|
|
3727
3835
|
DEFAULT_CHUNK_OVERLAP,
|
|
3728
3836
|
DEFAULT_GIT_POLL_INTERVAL_MS
|
|
3729
3837
|
} from "@liendev/core";
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
const repoId = extractRepoId(rootDir);
|
|
3733
|
-
const indexPath = path2.join(os.homedir(), ".lien", "indices", repoId);
|
|
3734
|
-
showCompactBanner();
|
|
3735
|
-
console.log(chalk3.bold("Status\n"));
|
|
3736
|
-
console.log(
|
|
3737
|
-
chalk3.dim("Configuration:"),
|
|
3738
|
-
chalk3.green("\u2713 Using defaults (no per-project config needed)")
|
|
3739
|
-
);
|
|
3838
|
+
var VALID_FORMATS = ["text", "json"];
|
|
3839
|
+
async function getFileStats(filePath) {
|
|
3740
3840
|
try {
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
}
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3841
|
+
return await fs2.stat(filePath);
|
|
3842
|
+
} catch {
|
|
3843
|
+
return null;
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
async function getFileCount(dirPath) {
|
|
3847
|
+
try {
|
|
3848
|
+
const files = await fs2.readdir(dirPath, { recursive: true });
|
|
3849
|
+
return files.length;
|
|
3850
|
+
} catch {
|
|
3851
|
+
return null;
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
async function getLastReindex(indexPath) {
|
|
3855
|
+
try {
|
|
3856
|
+
const version = await readVersionFile(indexPath);
|
|
3857
|
+
return version > 0 ? version : null;
|
|
3858
|
+
} catch {
|
|
3859
|
+
return null;
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
function getPackageVersion() {
|
|
3863
|
+
const __filename4 = fileURLToPath2(import.meta.url);
|
|
3864
|
+
const __dirname4 = path2.dirname(__filename4);
|
|
3865
|
+
const require5 = createRequire2(import.meta.url);
|
|
3866
|
+
try {
|
|
3867
|
+
return require5(path2.join(__dirname4, "../package.json")).version;
|
|
3868
|
+
} catch {
|
|
3869
|
+
return require5(path2.join(__dirname4, "../../package.json")).version;
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
async function getGitState(rootDir) {
|
|
3873
|
+
try {
|
|
3874
|
+
const branch = await getCurrentBranch(rootDir);
|
|
3875
|
+
const commit = await getCurrentCommit(rootDir);
|
|
3876
|
+
return { branch, commit };
|
|
3758
3877
|
} catch {
|
|
3878
|
+
return null;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
async function getStoredGitState(indexPath) {
|
|
3882
|
+
try {
|
|
3883
|
+
const content = await fs2.readFile(path2.join(indexPath, ".git-state.json"), "utf-8");
|
|
3884
|
+
return JSON.parse(content);
|
|
3885
|
+
} catch {
|
|
3886
|
+
return null;
|
|
3887
|
+
}
|
|
3888
|
+
}
|
|
3889
|
+
async function printIndexStatus(indexPath) {
|
|
3890
|
+
const stats = await getFileStats(indexPath);
|
|
3891
|
+
if (!stats) {
|
|
3759
3892
|
console.log(chalk3.dim("Index status:"), chalk3.yellow("\u2717 Not indexed"));
|
|
3760
3893
|
console.log(
|
|
3761
3894
|
chalk3.yellow("\nRun"),
|
|
3762
3895
|
chalk3.bold("lien index"),
|
|
3763
3896
|
chalk3.yellow("to index your codebase")
|
|
3764
3897
|
);
|
|
3898
|
+
return;
|
|
3765
3899
|
}
|
|
3766
|
-
console.log(chalk3.
|
|
3900
|
+
console.log(chalk3.dim("Index location:"), indexPath);
|
|
3901
|
+
console.log(chalk3.dim("Index status:"), chalk3.green("\u2713 Exists"));
|
|
3902
|
+
const fileCount = await getFileCount(indexPath);
|
|
3903
|
+
if (fileCount !== null) {
|
|
3904
|
+
console.log(chalk3.dim("Index files:"), fileCount);
|
|
3905
|
+
}
|
|
3906
|
+
console.log(chalk3.dim("Last modified:"), stats.mtime.toLocaleString());
|
|
3907
|
+
const reindexTs = await getLastReindex(indexPath);
|
|
3908
|
+
if (reindexTs !== null) {
|
|
3909
|
+
console.log(chalk3.dim("Last reindex:"), new Date(reindexTs).toLocaleString());
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
async function printGitStatus(rootDir, indexPath) {
|
|
3767
3913
|
const isRepo = await isGitRepo(rootDir);
|
|
3768
|
-
if (isRepo) {
|
|
3769
|
-
console.log(chalk3.dim("Git detection:"), chalk3.green("\u2713 Enabled"));
|
|
3770
|
-
console.log(chalk3.dim(" Poll interval:"), `${DEFAULT_GIT_POLL_INTERVAL_MS / 1e3}s`);
|
|
3771
|
-
try {
|
|
3772
|
-
const branch = await getCurrentBranch(rootDir);
|
|
3773
|
-
const commit = await getCurrentCommit(rootDir);
|
|
3774
|
-
console.log(chalk3.dim(" Current branch:"), branch);
|
|
3775
|
-
console.log(chalk3.dim(" Current commit:"), commit.substring(0, 8));
|
|
3776
|
-
const gitStateFile = path2.join(indexPath, ".git-state.json");
|
|
3777
|
-
try {
|
|
3778
|
-
const gitStateContent = await fs2.readFile(gitStateFile, "utf-8");
|
|
3779
|
-
const gitState = JSON.parse(gitStateContent);
|
|
3780
|
-
if (gitState.branch !== branch || gitState.commit !== commit) {
|
|
3781
|
-
console.log(chalk3.yellow(" \u26A0\uFE0F Git state changed - will reindex on next serve"));
|
|
3782
|
-
}
|
|
3783
|
-
} catch {
|
|
3784
|
-
}
|
|
3785
|
-
} catch {
|
|
3786
|
-
}
|
|
3787
|
-
} else {
|
|
3914
|
+
if (!isRepo) {
|
|
3788
3915
|
console.log(chalk3.dim("Git detection:"), chalk3.yellow("Not a git repo"));
|
|
3916
|
+
return;
|
|
3917
|
+
}
|
|
3918
|
+
console.log(chalk3.dim("Git detection:"), chalk3.green("\u2713 Enabled"));
|
|
3919
|
+
console.log(chalk3.dim(" Poll interval:"), `${DEFAULT_GIT_POLL_INTERVAL_MS / 1e3}s`);
|
|
3920
|
+
const gitState = await getGitState(rootDir);
|
|
3921
|
+
if (!gitState) return;
|
|
3922
|
+
console.log(chalk3.dim(" Current branch:"), gitState.branch);
|
|
3923
|
+
console.log(chalk3.dim(" Current commit:"), gitState.commit.substring(0, 8));
|
|
3924
|
+
const storedGit = await getStoredGitState(indexPath);
|
|
3925
|
+
if (storedGit && (storedGit.branch !== gitState.branch || storedGit.commit !== gitState.commit)) {
|
|
3926
|
+
console.log(chalk3.yellow(" \u26A0\uFE0F Git state changed - will reindex on next serve"));
|
|
3789
3927
|
}
|
|
3928
|
+
}
|
|
3929
|
+
function printWatchStatus() {
|
|
3790
3930
|
console.log(chalk3.dim("File watching:"), chalk3.green("\u2713 Enabled (default)"));
|
|
3791
3931
|
console.log(chalk3.dim(" Batch window:"), "500ms (collects rapid changes, force-flush after 5s)");
|
|
3792
3932
|
console.log(chalk3.dim(" Disable with:"), chalk3.bold("lien serve --no-watch"));
|
|
3933
|
+
}
|
|
3934
|
+
function printIndexingSettings() {
|
|
3793
3935
|
console.log(chalk3.bold("\nIndexing Settings (defaults):"));
|
|
3794
3936
|
console.log(chalk3.dim("Concurrency:"), DEFAULT_CONCURRENCY);
|
|
3795
3937
|
console.log(chalk3.dim("Batch size:"), DEFAULT_EMBEDDING_BATCH_SIZE);
|
|
3796
3938
|
console.log(chalk3.dim("Chunk size:"), DEFAULT_CHUNK_SIZE);
|
|
3797
3939
|
console.log(chalk3.dim("Chunk overlap:"), DEFAULT_CHUNK_OVERLAP);
|
|
3798
3940
|
}
|
|
3941
|
+
async function statusCommand(options = {}) {
|
|
3942
|
+
const format = options.format || "text";
|
|
3943
|
+
if (!VALID_FORMATS.includes(format)) {
|
|
3944
|
+
console.error(
|
|
3945
|
+
chalk3.red(`Error: Invalid --format value "${format}". Must be one of: text, json`)
|
|
3946
|
+
);
|
|
3947
|
+
process.exit(1);
|
|
3948
|
+
}
|
|
3949
|
+
const rootDir = process.cwd();
|
|
3950
|
+
const repoId = extractRepoId(rootDir);
|
|
3951
|
+
const indexPath = path2.join(os2.homedir(), ".lien", "indices", repoId);
|
|
3952
|
+
if (format === "json") {
|
|
3953
|
+
await outputJson(rootDir, indexPath);
|
|
3954
|
+
return;
|
|
3955
|
+
}
|
|
3956
|
+
showCompactBanner();
|
|
3957
|
+
console.log(chalk3.bold("Status\n"));
|
|
3958
|
+
console.log(
|
|
3959
|
+
chalk3.dim("Configuration:"),
|
|
3960
|
+
chalk3.green("\u2713 Using defaults (no per-project config needed)")
|
|
3961
|
+
);
|
|
3962
|
+
await printIndexStatus(indexPath);
|
|
3963
|
+
console.log(chalk3.bold("\nFeatures:"));
|
|
3964
|
+
await printGitStatus(rootDir, indexPath);
|
|
3965
|
+
printWatchStatus();
|
|
3966
|
+
if (options.verbose) {
|
|
3967
|
+
printIndexingSettings();
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
async function outputJson(rootDir, indexPath) {
|
|
3971
|
+
const data = {
|
|
3972
|
+
version: getPackageVersion(),
|
|
3973
|
+
indexPath,
|
|
3974
|
+
indexStatus: "not_indexed",
|
|
3975
|
+
indexFiles: 0,
|
|
3976
|
+
lastModified: null,
|
|
3977
|
+
lastReindex: null,
|
|
3978
|
+
git: { enabled: false, branch: null, commit: null },
|
|
3979
|
+
features: { fileWatching: true, gitDetection: true },
|
|
3980
|
+
settings: {
|
|
3981
|
+
concurrency: DEFAULT_CONCURRENCY,
|
|
3982
|
+
batchSize: DEFAULT_EMBEDDING_BATCH_SIZE,
|
|
3983
|
+
chunkSize: DEFAULT_CHUNK_SIZE,
|
|
3984
|
+
chunkOverlap: DEFAULT_CHUNK_OVERLAP
|
|
3985
|
+
}
|
|
3986
|
+
};
|
|
3987
|
+
const stats = await getFileStats(indexPath);
|
|
3988
|
+
if (stats) {
|
|
3989
|
+
data.indexStatus = "exists";
|
|
3990
|
+
data.lastModified = stats.mtime.toISOString();
|
|
3991
|
+
const fileCount = await getFileCount(indexPath);
|
|
3992
|
+
if (fileCount !== null) {
|
|
3993
|
+
data.indexFiles = fileCount;
|
|
3994
|
+
}
|
|
3995
|
+
const reindexTs = await getLastReindex(indexPath);
|
|
3996
|
+
if (reindexTs !== null) {
|
|
3997
|
+
data.lastReindex = new Date(reindexTs).toISOString();
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
const isRepo = await isGitRepo(rootDir);
|
|
4001
|
+
if (isRepo) {
|
|
4002
|
+
const gitState = await getGitState(rootDir);
|
|
4003
|
+
data.git = {
|
|
4004
|
+
enabled: true,
|
|
4005
|
+
branch: gitState?.branch ?? null,
|
|
4006
|
+
commit: gitState ? gitState.commit.substring(0, 8) : null
|
|
4007
|
+
};
|
|
4008
|
+
}
|
|
4009
|
+
console.log(JSON.stringify(data, null, 2));
|
|
4010
|
+
}
|
|
3799
4011
|
|
|
3800
4012
|
// src/cli/index-cmd.ts
|
|
3801
4013
|
init_banner();
|
|
@@ -4027,8 +4239,8 @@ import path7 from "path";
|
|
|
4027
4239
|
// src/mcp/server.ts
|
|
4028
4240
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4029
4241
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4030
|
-
import { createRequire as
|
|
4031
|
-
import { fileURLToPath as
|
|
4242
|
+
import { createRequire as createRequire3 } from "module";
|
|
4243
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4032
4244
|
import { dirname as dirname2, join as join2 } from "path";
|
|
4033
4245
|
import { WorkerEmbeddings, VERSION_CHECK_INTERVAL_MS, createVectorDB } from "@liendev/core";
|
|
4034
4246
|
|
|
@@ -8595,6 +8807,7 @@ var GetComplexitySchema = external_exports.object({
|
|
|
8595
8807
|
threshold: external_exports.number().int().min(1, "Threshold must be at least 1").optional().describe(
|
|
8596
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."
|
|
8597
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."),
|
|
8598
8811
|
crossRepo: external_exports.boolean().default(false).describe(
|
|
8599
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."
|
|
8600
8813
|
),
|
|
@@ -8750,7 +8963,7 @@ Use for tech debt analysis and refactoring prioritization:
|
|
|
8750
8963
|
|
|
8751
8964
|
Examples:
|
|
8752
8965
|
get_complexity({ top: 10 })
|
|
8753
|
-
get_complexity({ files: ["src/auth.ts", "
|
|
8966
|
+
get_complexity({ files: ["src/auth.ts"], metricType: "cognitive" })
|
|
8754
8967
|
get_complexity({ threshold: 15 })
|
|
8755
8968
|
|
|
8756
8969
|
Returns:
|
|
@@ -10075,15 +10288,15 @@ async function fetchCrossRepoChunks(vectorDB, crossRepo, repoIds, log) {
|
|
|
10075
10288
|
}
|
|
10076
10289
|
return { chunks: [], fallback: true };
|
|
10077
10290
|
}
|
|
10078
|
-
function processViolations(report, threshold, top) {
|
|
10291
|
+
function processViolations(report, threshold, top, metricType) {
|
|
10079
10292
|
const allViolations = (0, import_collect.default)(Object.entries(report.files)).flatMap(
|
|
10080
10293
|
([
|
|
10081
10294
|
,
|
|
10082
10295
|
/* filepath unused */
|
|
10083
10296
|
fileData
|
|
10084
|
-
]) => 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))
|
|
10085
10298
|
).sortByDesc("complexity").all();
|
|
10086
|
-
const violations =
|
|
10299
|
+
const violations = allViolations;
|
|
10087
10300
|
const severityCounts = (0, import_collect.default)(violations).countBy("severity").all();
|
|
10088
10301
|
return {
|
|
10089
10302
|
violations,
|
|
@@ -10100,7 +10313,7 @@ function buildCrossRepoFallbackNote(fallback) {
|
|
|
10100
10313
|
async function handleGetComplexity(args, ctx) {
|
|
10101
10314
|
const { vectorDB, log, checkAndReconnect, getIndexMetadata } = ctx;
|
|
10102
10315
|
return await wrapToolHandler(GetComplexitySchema, async (validatedArgs) => {
|
|
10103
|
-
const { crossRepo, repoIds, files, top, threshold } = validatedArgs;
|
|
10316
|
+
const { crossRepo, repoIds, files, top, threshold, metricType } = validatedArgs;
|
|
10104
10317
|
log(`Analyzing complexity${crossRepo ? " (cross-repo)" : ""}...`);
|
|
10105
10318
|
await checkAndReconnect();
|
|
10106
10319
|
const { chunks: allChunks, fallback } = await fetchCrossRepoChunks(
|
|
@@ -10115,7 +10328,8 @@ async function handleGetComplexity(args, ctx) {
|
|
|
10115
10328
|
const { violations, topViolations, bySeverity } = processViolations(
|
|
10116
10329
|
report,
|
|
10117
10330
|
threshold,
|
|
10118
|
-
top ?? 10
|
|
10331
|
+
top ?? 10,
|
|
10332
|
+
metricType
|
|
10119
10333
|
);
|
|
10120
10334
|
const note = buildCrossRepoFallbackNote(fallback);
|
|
10121
10335
|
if (note) {
|
|
@@ -10366,9 +10580,8 @@ import {
|
|
|
10366
10580
|
normalizeToRelativePath as normalizeToRelativePath2,
|
|
10367
10581
|
createGitignoreFilter
|
|
10368
10582
|
} from "@liendev/core";
|
|
10369
|
-
async function handleFileDeletion(filepath, vectorDB, log) {
|
|
10583
|
+
async function handleFileDeletion(filepath, vectorDB, manifest, log) {
|
|
10370
10584
|
log(`\u{1F5D1}\uFE0F File deleted: ${filepath}`);
|
|
10371
|
-
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
10372
10585
|
try {
|
|
10373
10586
|
await vectorDB.deleteByFile(filepath);
|
|
10374
10587
|
await manifest.removeFile(filepath);
|
|
@@ -10378,8 +10591,7 @@ async function handleFileDeletion(filepath, vectorDB, log) {
|
|
|
10378
10591
|
throw error;
|
|
10379
10592
|
}
|
|
10380
10593
|
}
|
|
10381
|
-
async function handleBatchDeletions(deletedFiles, vectorDB, log) {
|
|
10382
|
-
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
10594
|
+
async function handleBatchDeletions(deletedFiles, vectorDB, manifest, log) {
|
|
10383
10595
|
const failures = [];
|
|
10384
10596
|
for (const filepath of deletedFiles) {
|
|
10385
10597
|
log(`\u{1F5D1}\uFE0F File deleted: ${filepath}`);
|
|
@@ -10396,8 +10608,7 @@ async function handleBatchDeletions(deletedFiles, vectorDB, log) {
|
|
|
10396
10608
|
throw new Error(`Failed to delete ${failures.length} file(s): ${failures.join(", ")}`);
|
|
10397
10609
|
}
|
|
10398
10610
|
}
|
|
10399
|
-
async function canSkipReindex(filepath, rootDir,
|
|
10400
|
-
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
10611
|
+
async function canSkipReindex(filepath, rootDir, manifest, log) {
|
|
10401
10612
|
const normalizedPath = normalizeToRelativePath2(filepath, rootDir);
|
|
10402
10613
|
const manifestData = await manifest.load();
|
|
10403
10614
|
const existingEntry = manifestData?.files[normalizedPath];
|
|
@@ -10415,11 +10626,11 @@ async function canSkipReindex(filepath, rootDir, vectorDB, log) {
|
|
|
10415
10626
|
}
|
|
10416
10627
|
return false;
|
|
10417
10628
|
}
|
|
10418
|
-
async function handleSingleFileChange(filepath, type, rootDir, vectorDB, embeddings, log, reindexStateManager) {
|
|
10629
|
+
async function handleSingleFileChange(filepath, type, rootDir, vectorDB, embeddings, manifest, log, reindexStateManager) {
|
|
10419
10630
|
const action = type === "add" ? "added" : "changed";
|
|
10420
10631
|
if (type === "change") {
|
|
10421
10632
|
try {
|
|
10422
|
-
if (await canSkipReindex(filepath, rootDir,
|
|
10633
|
+
if (await canSkipReindex(filepath, rootDir, manifest, log)) return;
|
|
10423
10634
|
} catch (error) {
|
|
10424
10635
|
log(`Content hash check failed, will reindex: ${error}`, "warning");
|
|
10425
10636
|
}
|
|
@@ -10477,9 +10688,8 @@ async function updateUnchangedMtimes(manifest, results) {
|
|
|
10477
10688
|
return null;
|
|
10478
10689
|
});
|
|
10479
10690
|
}
|
|
10480
|
-
async function filterModifiedFilesByHash(modifiedFiles, rootDir,
|
|
10691
|
+
async function filterModifiedFilesByHash(modifiedFiles, rootDir, manifest, log) {
|
|
10481
10692
|
if (modifiedFiles.length === 0) return [];
|
|
10482
|
-
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
10483
10693
|
const manifestData = await manifest.load();
|
|
10484
10694
|
if (!manifestData) return modifiedFiles;
|
|
10485
10695
|
const checkResults = await checkFilesAgainstManifest(
|
|
@@ -10491,13 +10701,13 @@ async function filterModifiedFilesByHash(modifiedFiles, rootDir, vectorDB, log)
|
|
|
10491
10701
|
await updateUnchangedMtimes(manifest, checkResults);
|
|
10492
10702
|
return checkResults.filter((r) => r.shouldReindex).map((r) => r.filepath);
|
|
10493
10703
|
}
|
|
10494
|
-
async function prepareFilesForReindexing(event, rootDir,
|
|
10704
|
+
async function prepareFilesForReindexing(event, rootDir, manifest, log) {
|
|
10495
10705
|
const addedFiles = event.added || [];
|
|
10496
10706
|
const modifiedFiles = event.modified || [];
|
|
10497
10707
|
const deletedFiles = event.deleted || [];
|
|
10498
10708
|
let modifiedFilesToReindex = [];
|
|
10499
10709
|
try {
|
|
10500
|
-
modifiedFilesToReindex = await filterModifiedFilesByHash(modifiedFiles, rootDir,
|
|
10710
|
+
modifiedFilesToReindex = await filterModifiedFilesByHash(modifiedFiles, rootDir, manifest, log);
|
|
10501
10711
|
} catch (error) {
|
|
10502
10712
|
log(`Hash-based filtering failed, will reindex all modified files: ${error}`, "warning");
|
|
10503
10713
|
modifiedFilesToReindex = modifiedFiles;
|
|
@@ -10505,24 +10715,20 @@ async function prepareFilesForReindexing(event, rootDir, vectorDB, log) {
|
|
|
10505
10715
|
const filesToIndex = [...addedFiles, ...modifiedFilesToReindex];
|
|
10506
10716
|
return { filesToIndex, deletedFiles };
|
|
10507
10717
|
}
|
|
10508
|
-
async function executeReindexOperations(filesToIndex, deletedFiles, rootDir, vectorDB, embeddings, log) {
|
|
10509
|
-
const operations = [];
|
|
10718
|
+
async function executeReindexOperations(filesToIndex, deletedFiles, rootDir, vectorDB, embeddings, manifest, log) {
|
|
10510
10719
|
if (filesToIndex.length > 0) {
|
|
10511
10720
|
log(`\u{1F4C1} ${filesToIndex.length} file(s) changed, reindexing...`);
|
|
10512
|
-
|
|
10513
|
-
indexMultipleFiles(filesToIndex, vectorDB, embeddings, { verbose: false, rootDir })
|
|
10514
|
-
);
|
|
10721
|
+
await indexMultipleFiles(filesToIndex, vectorDB, embeddings, { verbose: false, rootDir });
|
|
10515
10722
|
}
|
|
10516
10723
|
if (deletedFiles.length > 0) {
|
|
10517
|
-
|
|
10724
|
+
await handleBatchDeletions(deletedFiles, vectorDB, manifest, log);
|
|
10518
10725
|
}
|
|
10519
|
-
await Promise.all(operations);
|
|
10520
10726
|
}
|
|
10521
|
-
async function handleBatchEvent(event, rootDir, vectorDB, embeddings, log, reindexStateManager) {
|
|
10727
|
+
async function handleBatchEvent(event, rootDir, vectorDB, embeddings, manifest, log, reindexStateManager) {
|
|
10522
10728
|
const { filesToIndex, deletedFiles } = await prepareFilesForReindexing(
|
|
10523
10729
|
event,
|
|
10524
10730
|
rootDir,
|
|
10525
|
-
|
|
10731
|
+
manifest,
|
|
10526
10732
|
log
|
|
10527
10733
|
);
|
|
10528
10734
|
const allFiles = [...filesToIndex, ...deletedFiles];
|
|
@@ -10532,7 +10738,15 @@ async function handleBatchEvent(event, rootDir, vectorDB, embeddings, log, reind
|
|
|
10532
10738
|
const startTime = Date.now();
|
|
10533
10739
|
reindexStateManager.startReindex(allFiles);
|
|
10534
10740
|
try {
|
|
10535
|
-
await executeReindexOperations(
|
|
10741
|
+
await executeReindexOperations(
|
|
10742
|
+
filesToIndex,
|
|
10743
|
+
deletedFiles,
|
|
10744
|
+
rootDir,
|
|
10745
|
+
vectorDB,
|
|
10746
|
+
embeddings,
|
|
10747
|
+
manifest,
|
|
10748
|
+
log
|
|
10749
|
+
);
|
|
10536
10750
|
const duration = Date.now() - startTime;
|
|
10537
10751
|
reindexStateManager.completeReindex(duration);
|
|
10538
10752
|
log(
|
|
@@ -10543,11 +10757,11 @@ async function handleBatchEvent(event, rootDir, vectorDB, embeddings, log, reind
|
|
|
10543
10757
|
log(`Batch reindex failed: ${error}`, "warning");
|
|
10544
10758
|
}
|
|
10545
10759
|
}
|
|
10546
|
-
async function handleUnlinkEvent(filepath, vectorDB, log, reindexStateManager) {
|
|
10760
|
+
async function handleUnlinkEvent(filepath, vectorDB, manifest, log, reindexStateManager) {
|
|
10547
10761
|
const startTime = Date.now();
|
|
10548
10762
|
reindexStateManager.startReindex([filepath]);
|
|
10549
10763
|
try {
|
|
10550
|
-
await handleFileDeletion(filepath, vectorDB, log);
|
|
10764
|
+
await handleFileDeletion(filepath, vectorDB, manifest, log);
|
|
10551
10765
|
const duration = Date.now() - startTime;
|
|
10552
10766
|
reindexStateManager.completeReindex(duration);
|
|
10553
10767
|
} catch (error) {
|
|
@@ -10579,6 +10793,7 @@ function hasGitignoreChange(event) {
|
|
|
10579
10793
|
}
|
|
10580
10794
|
function createFileChangeHandler(rootDir, vectorDB, embeddings, log, reindexStateManager, checkAndReconnect) {
|
|
10581
10795
|
let ignoreFilter = null;
|
|
10796
|
+
const manifest = new ManifestManager(vectorDB.dbPath);
|
|
10582
10797
|
return async (event) => {
|
|
10583
10798
|
if (hasGitignoreChange(event)) {
|
|
10584
10799
|
ignoreFilter = null;
|
|
@@ -10592,10 +10807,18 @@ function createFileChangeHandler(rootDir, vectorDB, embeddings, log, reindexStat
|
|
|
10592
10807
|
const totalToProcess = filtered.added.length + filtered.modified.length + filtered.deleted.length;
|
|
10593
10808
|
if (totalToProcess === 0) return;
|
|
10594
10809
|
await checkAndReconnect();
|
|
10595
|
-
await handleBatchEvent(
|
|
10810
|
+
await handleBatchEvent(
|
|
10811
|
+
filtered,
|
|
10812
|
+
rootDir,
|
|
10813
|
+
vectorDB,
|
|
10814
|
+
embeddings,
|
|
10815
|
+
manifest,
|
|
10816
|
+
log,
|
|
10817
|
+
reindexStateManager
|
|
10818
|
+
);
|
|
10596
10819
|
} else if (type === "unlink") {
|
|
10597
10820
|
await checkAndReconnect();
|
|
10598
|
-
await handleUnlinkEvent(event.filepath, vectorDB, log, reindexStateManager);
|
|
10821
|
+
await handleUnlinkEvent(event.filepath, vectorDB, manifest, log, reindexStateManager);
|
|
10599
10822
|
} else {
|
|
10600
10823
|
if (isFileIgnored(event.filepath, rootDir, ignoreFilter)) return;
|
|
10601
10824
|
await checkAndReconnect();
|
|
@@ -10605,6 +10828,7 @@ function createFileChangeHandler(rootDir, vectorDB, embeddings, log, reindexStat
|
|
|
10605
10828
|
rootDir,
|
|
10606
10829
|
vectorDB,
|
|
10607
10830
|
embeddings,
|
|
10831
|
+
manifest,
|
|
10608
10832
|
log,
|
|
10609
10833
|
reindexStateManager
|
|
10610
10834
|
);
|
|
@@ -10867,9 +11091,9 @@ function setupCleanupHandlers(server, versionCheckInterval, gitPollInterval, fil
|
|
|
10867
11091
|
}
|
|
10868
11092
|
|
|
10869
11093
|
// src/mcp/server.ts
|
|
10870
|
-
var __filename2 =
|
|
11094
|
+
var __filename2 = fileURLToPath3(import.meta.url);
|
|
10871
11095
|
var __dirname2 = dirname2(__filename2);
|
|
10872
|
-
var require3 =
|
|
11096
|
+
var require3 = createRequire3(import.meta.url);
|
|
10873
11097
|
var packageJson2;
|
|
10874
11098
|
try {
|
|
10875
11099
|
packageJson2 = require3(join2(__dirname2, "../package.json"));
|
|
@@ -11133,7 +11357,7 @@ import { VectorDB } from "@liendev/core";
|
|
|
11133
11357
|
import { ComplexityAnalyzer as ComplexityAnalyzer2 } from "@liendev/core";
|
|
11134
11358
|
import { formatReport } from "@liendev/core";
|
|
11135
11359
|
var VALID_FAIL_ON = ["error", "warning"];
|
|
11136
|
-
var
|
|
11360
|
+
var VALID_FORMATS2 = ["text", "json", "sarif"];
|
|
11137
11361
|
function validateFailOn(failOn) {
|
|
11138
11362
|
if (failOn && !VALID_FAIL_ON.includes(failOn)) {
|
|
11139
11363
|
console.error(
|
|
@@ -11143,7 +11367,7 @@ function validateFailOn(failOn) {
|
|
|
11143
11367
|
}
|
|
11144
11368
|
}
|
|
11145
11369
|
function validateFormat(format) {
|
|
11146
|
-
if (!
|
|
11370
|
+
if (!VALID_FORMATS2.includes(format)) {
|
|
11147
11371
|
console.error(
|
|
11148
11372
|
chalk7.red(`Error: Invalid --format value "${format}". Must be one of: text, json, sarif`)
|
|
11149
11373
|
);
|
|
@@ -11200,9 +11424,9 @@ async function complexityCommand(options) {
|
|
|
11200
11424
|
// src/cli/config.ts
|
|
11201
11425
|
import chalk8 from "chalk";
|
|
11202
11426
|
import path9 from "path";
|
|
11203
|
-
import
|
|
11427
|
+
import os3 from "os";
|
|
11204
11428
|
import { loadGlobalConfig, mergeGlobalConfig } from "@liendev/core";
|
|
11205
|
-
var CONFIG_PATH = path9.join(
|
|
11429
|
+
var CONFIG_PATH = path9.join(os3.homedir(), ".lien", "config.json");
|
|
11206
11430
|
var ALLOWED_KEYS = {
|
|
11207
11431
|
backend: {
|
|
11208
11432
|
values: ["lancedb", "qdrant"],
|
|
@@ -11289,9 +11513,9 @@ async function configListCommand() {
|
|
|
11289
11513
|
}
|
|
11290
11514
|
|
|
11291
11515
|
// src/cli/index.ts
|
|
11292
|
-
var __filename3 =
|
|
11516
|
+
var __filename3 = fileURLToPath4(import.meta.url);
|
|
11293
11517
|
var __dirname3 = dirname3(__filename3);
|
|
11294
|
-
var require4 =
|
|
11518
|
+
var require4 = createRequire4(import.meta.url);
|
|
11295
11519
|
var packageJson3;
|
|
11296
11520
|
try {
|
|
11297
11521
|
packageJson3 = require4(join3(__dirname3, "../package.json"));
|
|
@@ -11300,17 +11524,31 @@ try {
|
|
|
11300
11524
|
}
|
|
11301
11525
|
var program = new Command();
|
|
11302
11526
|
program.name("lien").description("Local semantic code search for AI assistants via MCP").version(packageJson3.version);
|
|
11303
|
-
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);
|
|
11304
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);
|
|
11305
11538
|
program.command("serve").description(
|
|
11306
11539
|
"Start the MCP server (works with Cursor, Claude Code, Windsurf, and any MCP client)"
|
|
11307
|
-
).option("-p, --port <port>", "Port number (for future use)", "7133").option("--no-watch", "Disable file watching for this session").
|
|
11308
|
-
|
|
11540
|
+
).option("-p, --port <port>", "Port number (for future use)", "7133").option("--no-watch", "Disable file watching for this session").addOption(
|
|
11541
|
+
new Option("-w, --watch", "[DEPRECATED] File watching is now enabled by default").hideHelp()
|
|
11542
|
+
).option("-r, --root <path>", "Root directory to serve (defaults to current directory)").action(serveCommand);
|
|
11543
|
+
program.command("status").description("Show indexing status and statistics").option("-v, --verbose", "Show detailed settings").option("--format <type>", "Output format: text, json", "text").action(statusCommand);
|
|
11309
11544
|
program.command("complexity").description("Analyze code complexity").option("--files <paths...>", "Specific files to analyze").option("--format <type>", "Output format: text, json, sarif", "text").option("--fail-on <severity>", "Exit 1 if violations: error, warning").action(complexityCommand);
|
|
11310
11545
|
var configCmd = program.command("config").description("Manage global configuration (~/.lien/config.json)");
|
|
11311
11546
|
configCmd.command("set <key> <value>").description("Set a global config value").action(configSetCommand);
|
|
11312
11547
|
configCmd.command("get <key>").description("Get a config value").action(configGetCommand);
|
|
11313
11548
|
configCmd.command("list").description("Show all current config").action(configListCommand);
|
|
11549
|
+
program.action(() => {
|
|
11550
|
+
program.help();
|
|
11551
|
+
});
|
|
11314
11552
|
program.addHelpText("beforeAll", `Quick start: run 'lien serve' in your project directory
|
|
11315
11553
|
`);
|
|
11316
11554
|
|