lnai 0.3.0 → 0.4.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 +28 -29
- package/dist/index.js +112 -24
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
<a href="https://www.npmjs.com/package/lnai">
|
|
7
7
|
<img alt="npm version" src="https://img.shields.io/npm/v/lnai">
|
|
8
8
|
</a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/lnai">
|
|
10
|
+
<img alt="npm downloads" src="https://img.shields.io/npm/dm/lnai">
|
|
11
|
+
</a>
|
|
12
|
+
<a href="https://github.com/KrystianJonca/lnai/actions/workflows/ci.yml">
|
|
13
|
+
<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/KrystianJonca/lnai/ci.yml?branch=main">
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://github.com/KrystianJonca/lnai/blob/main/LICENSE">
|
|
16
|
+
<img alt="license" src="https://img.shields.io/github/license/KrystianJonca/lnai">
|
|
17
|
+
</a>
|
|
9
18
|
<a href="https://github.com/KrystianJonca/lnai/stargazers">
|
|
10
19
|
<img alt="GitHub stars" src="https://img.shields.io/github/stars/KrystianJonca/lnai?style=social">
|
|
11
20
|
</a>
|
|
@@ -13,47 +22,37 @@
|
|
|
13
22
|
|
|
14
23
|
# LNAI
|
|
15
24
|
|
|
16
|
-
|
|
25
|
+
Stop maintaining separate config files for every AI coding tool. Define once in `.ai/`, sync everywhere.
|
|
17
26
|
|
|
18
|
-
##
|
|
27
|
+
## Why LNAI?
|
|
19
28
|
|
|
20
|
-
|
|
29
|
+
- **One source of truth** — Write your project rules, MCP servers, and permissions once
|
|
30
|
+
- **Works with your tools** — Syncs to native formats each tool actually reads
|
|
31
|
+
- **Stay in sync** — Update `.ai/` and run `lnai sync` to propagate changes instantly
|
|
21
32
|
|
|
22
|
-
##
|
|
33
|
+
## Supported Tools
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
| Tool | Config Generated |
|
|
36
|
+
| -------------- | --------------------------------- |
|
|
37
|
+
| Claude Code | `.claude/` |
|
|
38
|
+
| Cursor | `.cursor/` |
|
|
39
|
+
| GitHub Copilot | `.github/copilot-instructions.md` |
|
|
40
|
+
| OpenCode | `.opencode/` |
|
|
41
|
+
| Windsurf | `.windsurf/` |
|
|
27
42
|
|
|
28
43
|
## Quick Start
|
|
29
44
|
|
|
30
45
|
```bash
|
|
31
|
-
|
|
32
|
-
lnai init
|
|
33
|
-
|
|
34
|
-
# Validate your configuration
|
|
35
|
-
lnai validate
|
|
46
|
+
npm install -g lnai
|
|
36
47
|
|
|
37
|
-
#
|
|
38
|
-
lnai
|
|
48
|
+
lnai init # Create .ai/ configuration
|
|
49
|
+
lnai validate # Check for errors
|
|
50
|
+
lnai sync # Export to native tool configs
|
|
39
51
|
```
|
|
40
52
|
|
|
41
|
-
##
|
|
42
|
-
|
|
43
|
-
- `lnai init` - Create a new `.ai/` configuration directory
|
|
44
|
-
- `lnai validate` - Validate your `.ai/` configuration
|
|
45
|
-
- `lnai sync` - Export `.ai/` to native tool configs
|
|
46
|
-
|
|
47
|
-
## Configuration Structure
|
|
53
|
+
## Documentation
|
|
48
54
|
|
|
49
|
-
|
|
50
|
-
.ai/
|
|
51
|
-
├── config.json # Tool settings and enabled tools
|
|
52
|
-
├── settings.json # Permissions and MCP servers
|
|
53
|
-
├── AGENTS.md # Project instructions
|
|
54
|
-
├── rules/ # Path-based rules
|
|
55
|
-
└── skills/ # Custom commands
|
|
56
|
-
```
|
|
55
|
+
Full guides and configuration reference at [lnai.sh](https://lnai.sh)
|
|
57
56
|
|
|
58
57
|
## License
|
|
59
58
|
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,69 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'module';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
-
import { initUnifiedConfig, runSyncPipeline, parseUnifiedConfig, validateUnifiedState, pluginRegistry } from '@lnai/core';
|
|
5
|
-
import
|
|
4
|
+
import { initUnifiedConfig, runSyncPipeline, parseUnifiedConfig, validateUnifiedState, pluginRegistry, TOOL_IDS } from '@lnai/core';
|
|
5
|
+
import chalk2 from 'chalk';
|
|
6
6
|
import ora from 'ora';
|
|
7
|
+
import { checkbox, select } from '@inquirer/prompts';
|
|
7
8
|
|
|
9
|
+
async function runInitPrompts() {
|
|
10
|
+
const tools = await promptForTools();
|
|
11
|
+
const versionControl = await promptForVersionControl(tools);
|
|
12
|
+
return { tools, versionControl };
|
|
13
|
+
}
|
|
14
|
+
function isInteractiveEnvironment() {
|
|
15
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
16
|
+
}
|
|
17
|
+
var TOOL_DISPLAY_NAMES = {
|
|
18
|
+
claudeCode: "Claude Code (.claude/)",
|
|
19
|
+
opencode: "Opencode (.opencode/)",
|
|
20
|
+
cursor: "Cursor (.cursor/)",
|
|
21
|
+
copilot: "Copilot (.github/)",
|
|
22
|
+
windsurf: "Windsurf (.windsurf/)"
|
|
23
|
+
};
|
|
24
|
+
async function promptForTools() {
|
|
25
|
+
return checkbox({
|
|
26
|
+
message: "Which tools would you like to configure?",
|
|
27
|
+
choices: TOOL_IDS.map((id) => ({
|
|
28
|
+
name: TOOL_DISPLAY_NAMES[id],
|
|
29
|
+
value: id,
|
|
30
|
+
checked: true
|
|
31
|
+
})),
|
|
32
|
+
required: true
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
async function promptForVersionControl(tools) {
|
|
36
|
+
const mode = await select({
|
|
37
|
+
message: "How would you like to handle version control?",
|
|
38
|
+
choices: [
|
|
39
|
+
{ name: "Ignore all (add to .gitignore)", value: "ignore-all" },
|
|
40
|
+
{ name: "Version control all", value: "version-all" },
|
|
41
|
+
{ name: "Configure per tool", value: "per-tool" }
|
|
42
|
+
]
|
|
43
|
+
});
|
|
44
|
+
const result = {};
|
|
45
|
+
if (mode === "ignore-all") {
|
|
46
|
+
for (const tool of tools) {
|
|
47
|
+
result[tool] = false;
|
|
48
|
+
}
|
|
49
|
+
} else if (mode === "version-all") {
|
|
50
|
+
for (const tool of tools) {
|
|
51
|
+
result[tool] = true;
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
const versionControlled = await checkbox({
|
|
55
|
+
message: "Select tools to version control:",
|
|
56
|
+
choices: tools.map((id) => ({ name: TOOL_DISPLAY_NAMES[id], value: id }))
|
|
57
|
+
});
|
|
58
|
+
for (const tool of tools) {
|
|
59
|
+
result[tool] = versionControlled.includes(tool);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
8
64
|
var GITHUB_URL = "https://github.com/KrystianJonca/lnai";
|
|
9
65
|
function formatValidationItem(item, color) {
|
|
10
|
-
const colorFn = color === "red" ?
|
|
66
|
+
const colorFn = color === "red" ? chalk2.red : chalk2.yellow;
|
|
11
67
|
return colorFn(` - ${item.path.join(".")}: ${item.message}`);
|
|
12
68
|
}
|
|
13
69
|
function printValidationItems(items, color) {
|
|
@@ -17,39 +73,71 @@ function printValidationItems(items, color) {
|
|
|
17
73
|
}
|
|
18
74
|
function printGitHubPromo() {
|
|
19
75
|
console.log(
|
|
20
|
-
|
|
76
|
+
chalk2.gray("\nIf you find LNAI helpful, please star us on GitHub:")
|
|
21
77
|
);
|
|
22
|
-
console.log(
|
|
78
|
+
console.log(chalk2.blue(GITHUB_URL));
|
|
23
79
|
}
|
|
24
80
|
|
|
25
81
|
// src/commands/init.ts
|
|
26
|
-
var initCommand = new Command("init").description("Initialize a new .ai/ configuration directory").option("--force", "Overwrite existing .ai/ directory").option("--minimal", "Create only config.json (no subdirectories)").option("-t, --tools <tools...>", "Enable only specific tools").action(async (options) => {
|
|
82
|
+
var initCommand = new Command("init").description("Initialize a new .ai/ configuration directory").option("--force", "Overwrite existing .ai/ directory").option("--minimal", "Create only config.json (no subdirectories)").option("-t, --tools <tools...>", "Enable only specific tools").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
|
|
27
83
|
const rootDir = process.cwd();
|
|
84
|
+
let tools = options.tools;
|
|
85
|
+
let versionControl;
|
|
86
|
+
if (shouldRunInteractive(options)) {
|
|
87
|
+
try {
|
|
88
|
+
const answers = await runInitPrompts();
|
|
89
|
+
tools = answers.tools;
|
|
90
|
+
versionControl = answers.versionControl;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (error instanceof Error && error.message.includes("User force closed")) {
|
|
93
|
+
console.log(chalk2.gray("\nAborted."));
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
28
99
|
const spinner = ora("Initializing .ai/ configuration...").start();
|
|
29
100
|
try {
|
|
30
101
|
const result = await initUnifiedConfig({
|
|
31
102
|
rootDir,
|
|
32
|
-
tools
|
|
103
|
+
tools,
|
|
33
104
|
minimal: options.minimal,
|
|
34
|
-
force: options.force
|
|
105
|
+
force: options.force,
|
|
106
|
+
versionControl
|
|
35
107
|
});
|
|
36
108
|
spinner.succeed("Initialized .ai/ configuration");
|
|
37
|
-
console.log(
|
|
109
|
+
console.log(chalk2.gray("\nCreated:"));
|
|
38
110
|
for (const file of result.created) {
|
|
39
|
-
console.log(
|
|
111
|
+
console.log(chalk2.green(` + ${file}`));
|
|
40
112
|
}
|
|
113
|
+
console.log(chalk2.gray("\nNext steps:"));
|
|
114
|
+
console.log(
|
|
115
|
+
chalk2.gray(" 1. Configure ") + chalk2.cyan(".ai/") + chalk2.gray(" (rules, skills, mcps, permissions)")
|
|
116
|
+
);
|
|
41
117
|
console.log(
|
|
42
|
-
|
|
118
|
+
chalk2.gray(" 2. Run ") + chalk2.cyan("lnai sync") + chalk2.gray(" to generate tool configs")
|
|
43
119
|
);
|
|
44
120
|
printGitHubPromo();
|
|
45
121
|
} catch (error) {
|
|
46
122
|
spinner.fail("Initialization failed");
|
|
47
123
|
console.error(
|
|
48
|
-
|
|
124
|
+
chalk2.red(error instanceof Error ? error.message : String(error))
|
|
49
125
|
);
|
|
50
126
|
process.exit(1);
|
|
51
127
|
}
|
|
52
128
|
});
|
|
129
|
+
function shouldRunInteractive(options) {
|
|
130
|
+
if (options.yes) {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
if (options.tools?.length) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
if (!isInteractiveEnvironment()) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
53
141
|
var syncCommand = new Command("sync").description("Export .ai/ to native configs").option("--dry-run", "Preview without writing").option("-t, --tools <tools...>", "Filter to specific tools").option("-v, --verbose", "Detailed output").action(async (options) => {
|
|
54
142
|
const spinner = ora("Syncing configuration...").start();
|
|
55
143
|
try {
|
|
@@ -61,23 +149,23 @@ var syncCommand = new Command("sync").description("Export .ai/ to native configs
|
|
|
61
149
|
});
|
|
62
150
|
spinner.succeed("Sync complete");
|
|
63
151
|
if (results.length === 0) {
|
|
64
|
-
console.log(
|
|
152
|
+
console.log(chalk2.yellow("\nNo tools configured or enabled."));
|
|
65
153
|
return;
|
|
66
154
|
}
|
|
67
155
|
for (const result of results) {
|
|
68
|
-
console.log(
|
|
156
|
+
console.log(chalk2.blue(`
|
|
69
157
|
${result.tool}:`));
|
|
70
158
|
if (result.changes.length === 0) {
|
|
71
|
-
console.log(
|
|
159
|
+
console.log(chalk2.gray(" No changes"));
|
|
72
160
|
}
|
|
73
161
|
for (const change of result.changes) {
|
|
74
|
-
const icon = change.action === "create" ?
|
|
162
|
+
const icon = change.action === "create" ? chalk2.green("+") : change.action === "update" ? chalk2.yellow("~") : change.action === "delete" ? chalk2.red("-") : chalk2.gray("=");
|
|
75
163
|
console.log(` ${icon} ${change.path}`);
|
|
76
164
|
}
|
|
77
165
|
}
|
|
78
166
|
for (const result of results) {
|
|
79
167
|
if (result.validation.warnings.length > 0) {
|
|
80
|
-
console.log(
|
|
168
|
+
console.log(chalk2.yellow(`
|
|
81
169
|
${result.tool} warnings:`));
|
|
82
170
|
printValidationItems(result.validation.warnings, "yellow");
|
|
83
171
|
}
|
|
@@ -86,7 +174,7 @@ ${result.tool} warnings:`));
|
|
|
86
174
|
} catch (error) {
|
|
87
175
|
spinner.fail("Sync failed");
|
|
88
176
|
console.error(
|
|
89
|
-
|
|
177
|
+
chalk2.red(error instanceof Error ? error.message : String(error))
|
|
90
178
|
);
|
|
91
179
|
process.exit(1);
|
|
92
180
|
}
|
|
@@ -99,7 +187,7 @@ var validateCommand = new Command("validate").description("Validate .ai/ configu
|
|
|
99
187
|
const unifiedResult = validateUnifiedState(state);
|
|
100
188
|
if (!unifiedResult.valid) {
|
|
101
189
|
spinner.fail("Validation failed");
|
|
102
|
-
console.log(
|
|
190
|
+
console.log(chalk2.red("\nUnified config errors:"));
|
|
103
191
|
printValidationItems(unifiedResult.errors, "red");
|
|
104
192
|
process.exit(1);
|
|
105
193
|
}
|
|
@@ -126,7 +214,7 @@ var validateCommand = new Command("validate").description("Validate .ai/ configu
|
|
|
126
214
|
if (toolErrors.length > 0) {
|
|
127
215
|
spinner.fail("Validation failed");
|
|
128
216
|
for (const { plugin, errors } of toolErrors) {
|
|
129
|
-
console.log(
|
|
217
|
+
console.log(chalk2.red(`
|
|
130
218
|
${plugin} errors:`));
|
|
131
219
|
printValidationItems(errors, "red");
|
|
132
220
|
}
|
|
@@ -134,21 +222,21 @@ ${plugin} errors:`));
|
|
|
134
222
|
}
|
|
135
223
|
spinner.succeed("Validation passed");
|
|
136
224
|
for (const { plugin, warnings } of toolWarnings) {
|
|
137
|
-
console.log(
|
|
225
|
+
console.log(chalk2.yellow(`
|
|
138
226
|
${plugin} warnings:`));
|
|
139
227
|
printValidationItems(warnings, "yellow");
|
|
140
228
|
}
|
|
141
229
|
for (const { plugin, skipped } of toolSkipped) {
|
|
142
|
-
console.log(
|
|
230
|
+
console.log(chalk2.gray(`
|
|
143
231
|
${plugin} skipped features:`));
|
|
144
232
|
for (const item of skipped) {
|
|
145
|
-
console.log(
|
|
233
|
+
console.log(chalk2.gray(` - ${item.feature}: ${item.reason}`));
|
|
146
234
|
}
|
|
147
235
|
}
|
|
148
236
|
} catch (error) {
|
|
149
237
|
spinner.fail("Validation failed");
|
|
150
238
|
console.error(
|
|
151
|
-
|
|
239
|
+
chalk2.red(error instanceof Error ? error.message : String(error))
|
|
152
240
|
);
|
|
153
241
|
process.exit(1);
|
|
154
242
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lnai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "CLI tool that syncs a unified .ai/ config to native formats for AI coding tools",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"opencode",
|
|
24
24
|
"copilot",
|
|
25
25
|
"github-copilot",
|
|
26
|
+
"windsurf",
|
|
26
27
|
"cli",
|
|
27
28
|
"ai-tools",
|
|
28
29
|
"lnai"
|
|
@@ -35,10 +36,11 @@
|
|
|
35
36
|
"README.md"
|
|
36
37
|
],
|
|
37
38
|
"dependencies": {
|
|
39
|
+
"@inquirer/prompts": "^7.0.0",
|
|
38
40
|
"chalk": "^5.6.2",
|
|
39
41
|
"commander": "^14.0.2",
|
|
40
42
|
"ora": "^9.1.0",
|
|
41
|
-
"@lnai/core": "0.
|
|
43
|
+
"@lnai/core": "0.4.0"
|
|
42
44
|
},
|
|
43
45
|
"devDependencies": {
|
|
44
46
|
"@types/node": "^25.0.10",
|