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.
Files changed (3) hide show
  1. package/README.md +28 -29
  2. package/dist/index.js +115 -31
  3. 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
- Unified AI configuration management CLI. Define your AI tool configurations once in `.ai/` and sync them to native formats for Claude Code, Cursor, OpenCode, and more.
25
+ Stop maintaining separate config files for every AI coding tool. Define once in `.ai/`, sync everywhere.
17
26
 
18
- ## Documentation
27
+ ## Why LNAI?
19
28
 
20
- See the [documentation](https://lnai.sh) for detailed guides.
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
- ## Installation
33
+ ## Supported Tools
23
34
 
24
- ```bash
25
- npm install -g lnai
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
- # Initialize a new .ai/ configuration
32
- lnai init
33
-
34
- # Validate your configuration
35
- lnai validate
46
+ npm install -g lnai
36
47
 
37
- # Sync to native tool configs
38
- lnai sync
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
- ## Commands
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
- 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) => {
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: options.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("\nRun ") + chalk2.cyan("lnai sync") + chalk2.gray(" to generate tool configs.")
115
+ chalk2.gray(" 1. Configure ") + chalk2.cyan(".ai/") + chalk2.gray(" (rules, skills, mcps, permissions)")
25
116
  );
26
117
  console.log(
27
- chalk2.gray("\nIf you find LNAI helpful, please star us on GitHub:")
118
+ chalk2.gray(" 2. Run ") + chalk2.cyan("lnai sync") + chalk2.gray(" to generate tool configs")
28
119
  );
29
- console.log(chalk2.blue("https://github.com/KrystianJonca/lnai"));
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
- for (const warning of result.validation.warnings) {
68
- console.log(
69
- chalk2.yellow(` - ${warning.path.join(".")}: ${warning.message}`)
70
- );
71
- }
170
+ printValidationItems(result.validation.warnings, "yellow");
72
171
  }
73
172
  }
74
- console.log(
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
- for (const error of unifiedResult.errors) {
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
- for (const error of errors) {
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
- for (const warning of warnings) {
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.2.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.2.0"
43
+ "@lnai/core": "0.4.0"
40
44
  },
41
45
  "devDependencies": {
42
46
  "@types/node": "^25.0.10",