lnai 0.2.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 +115 -31
- package/package.json +6 -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,32 +1,123 @@
|
|
|
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';
|
|
4
|
+
import { initUnifiedConfig, runSyncPipeline, parseUnifiedConfig, validateUnifiedState, pluginRegistry, TOOL_IDS } from '@lnai/core';
|
|
5
5
|
import chalk2 from 'chalk';
|
|
6
6
|
import ora from 'ora';
|
|
7
|
+
import { checkbox, select } from '@inquirer/prompts';
|
|
7
8
|
|
|
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
|
+
}
|
|
64
|
+
var GITHUB_URL = "https://github.com/KrystianJonca/lnai";
|
|
65
|
+
function formatValidationItem(item, color) {
|
|
66
|
+
const colorFn = color === "red" ? chalk2.red : chalk2.yellow;
|
|
67
|
+
return colorFn(` - ${item.path.join(".")}: ${item.message}`);
|
|
68
|
+
}
|
|
69
|
+
function printValidationItems(items, color) {
|
|
70
|
+
for (const item of items) {
|
|
71
|
+
console.log(formatValidationItem(item, color));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function printGitHubPromo() {
|
|
75
|
+
console.log(
|
|
76
|
+
chalk2.gray("\nIf you find LNAI helpful, please star us on GitHub:")
|
|
77
|
+
);
|
|
78
|
+
console.log(chalk2.blue(GITHUB_URL));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/commands/init.ts
|
|
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) => {
|
|
9
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
|
+
}
|
|
10
99
|
const spinner = ora("Initializing .ai/ configuration...").start();
|
|
11
100
|
try {
|
|
12
101
|
const result = await initUnifiedConfig({
|
|
13
102
|
rootDir,
|
|
14
|
-
tools
|
|
103
|
+
tools,
|
|
15
104
|
minimal: options.minimal,
|
|
16
|
-
force: options.force
|
|
105
|
+
force: options.force,
|
|
106
|
+
versionControl
|
|
17
107
|
});
|
|
18
108
|
spinner.succeed("Initialized .ai/ configuration");
|
|
19
109
|
console.log(chalk2.gray("\nCreated:"));
|
|
20
110
|
for (const file of result.created) {
|
|
21
111
|
console.log(chalk2.green(` + ${file}`));
|
|
22
112
|
}
|
|
113
|
+
console.log(chalk2.gray("\nNext steps:"));
|
|
23
114
|
console.log(
|
|
24
|
-
chalk2.gray("
|
|
115
|
+
chalk2.gray(" 1. Configure ") + chalk2.cyan(".ai/") + chalk2.gray(" (rules, skills, mcps, permissions)")
|
|
25
116
|
);
|
|
26
117
|
console.log(
|
|
27
|
-
chalk2.gray("
|
|
118
|
+
chalk2.gray(" 2. Run ") + chalk2.cyan("lnai sync") + chalk2.gray(" to generate tool configs")
|
|
28
119
|
);
|
|
29
|
-
|
|
120
|
+
printGitHubPromo();
|
|
30
121
|
} catch (error) {
|
|
31
122
|
spinner.fail("Initialization failed");
|
|
32
123
|
console.error(
|
|
@@ -35,6 +126,18 @@ var initCommand = new Command("init").description("Initialize a new .ai/ configu
|
|
|
35
126
|
process.exit(1);
|
|
36
127
|
}
|
|
37
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
|
+
}
|
|
38
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) => {
|
|
39
142
|
const spinner = ora("Syncing configuration...").start();
|
|
40
143
|
try {
|
|
@@ -64,17 +167,10 @@ ${result.tool}:`));
|
|
|
64
167
|
if (result.validation.warnings.length > 0) {
|
|
65
168
|
console.log(chalk2.yellow(`
|
|
66
169
|
${result.tool} warnings:`));
|
|
67
|
-
|
|
68
|
-
console.log(
|
|
69
|
-
chalk2.yellow(` - ${warning.path.join(".")}: ${warning.message}`)
|
|
70
|
-
);
|
|
71
|
-
}
|
|
170
|
+
printValidationItems(result.validation.warnings, "yellow");
|
|
72
171
|
}
|
|
73
172
|
}
|
|
74
|
-
|
|
75
|
-
chalk2.gray("\nIf you find LNAI helpful, please star us on GitHub:")
|
|
76
|
-
);
|
|
77
|
-
console.log(chalk2.blue("https://github.com/KrystianJonca/lnai"));
|
|
173
|
+
printGitHubPromo();
|
|
78
174
|
} catch (error) {
|
|
79
175
|
spinner.fail("Sync failed");
|
|
80
176
|
console.error(
|
|
@@ -92,11 +188,7 @@ var validateCommand = new Command("validate").description("Validate .ai/ configu
|
|
|
92
188
|
if (!unifiedResult.valid) {
|
|
93
189
|
spinner.fail("Validation failed");
|
|
94
190
|
console.log(chalk2.red("\nUnified config errors:"));
|
|
95
|
-
|
|
96
|
-
console.log(
|
|
97
|
-
chalk2.red(` - ${error.path.join(".")}: ${error.message}`)
|
|
98
|
-
);
|
|
99
|
-
}
|
|
191
|
+
printValidationItems(unifiedResult.errors, "red");
|
|
100
192
|
process.exit(1);
|
|
101
193
|
}
|
|
102
194
|
const tools = options.tools ?? pluginRegistry.getIds();
|
|
@@ -124,11 +216,7 @@ var validateCommand = new Command("validate").description("Validate .ai/ configu
|
|
|
124
216
|
for (const { plugin, errors } of toolErrors) {
|
|
125
217
|
console.log(chalk2.red(`
|
|
126
218
|
${plugin} errors:`));
|
|
127
|
-
|
|
128
|
-
console.log(
|
|
129
|
-
chalk2.red(` - ${error.path.join(".")}: ${error.message}`)
|
|
130
|
-
);
|
|
131
|
-
}
|
|
219
|
+
printValidationItems(errors, "red");
|
|
132
220
|
}
|
|
133
221
|
process.exit(1);
|
|
134
222
|
}
|
|
@@ -136,11 +224,7 @@ ${plugin} errors:`));
|
|
|
136
224
|
for (const { plugin, warnings } of toolWarnings) {
|
|
137
225
|
console.log(chalk2.yellow(`
|
|
138
226
|
${plugin} warnings:`));
|
|
139
|
-
|
|
140
|
-
console.log(
|
|
141
|
-
chalk2.yellow(` - ${warning.path.join(".")}: ${warning.message}`)
|
|
142
|
-
);
|
|
143
|
-
}
|
|
227
|
+
printValidationItems(warnings, "yellow");
|
|
144
228
|
}
|
|
145
229
|
for (const { plugin, skipped } of toolSkipped) {
|
|
146
230
|
console.log(chalk2.gray(`
|
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",
|
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
"claude",
|
|
22
22
|
"cursor",
|
|
23
23
|
"opencode",
|
|
24
|
+
"copilot",
|
|
25
|
+
"github-copilot",
|
|
26
|
+
"windsurf",
|
|
24
27
|
"cli",
|
|
25
28
|
"ai-tools",
|
|
26
29
|
"lnai"
|
|
@@ -33,10 +36,11 @@
|
|
|
33
36
|
"README.md"
|
|
34
37
|
],
|
|
35
38
|
"dependencies": {
|
|
39
|
+
"@inquirer/prompts": "^7.0.0",
|
|
36
40
|
"chalk": "^5.6.2",
|
|
37
41
|
"commander": "^14.0.2",
|
|
38
42
|
"ora": "^9.1.0",
|
|
39
|
-
"@lnai/core": "0.
|
|
43
|
+
"@lnai/core": "0.4.0"
|
|
40
44
|
},
|
|
41
45
|
"devDependencies": {
|
|
42
46
|
"@types/node": "^25.0.10",
|