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.
Files changed (3) hide show
  1. package/README.md +28 -29
  2. package/dist/index.js +112 -24
  3. 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
- Unified AI configuration management CLI. Define your AI tool configurations once in `.ai/` and sync them to native formats for Claude Code, Cursor, OpenCode, GitHub Copilot, 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,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 chalk3 from 'chalk';
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" ? chalk3.red : chalk3.yellow;
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
- chalk3.gray("\nIf you find LNAI helpful, please star us on GitHub:")
76
+ chalk2.gray("\nIf you find LNAI helpful, please star us on GitHub:")
21
77
  );
22
- console.log(chalk3.blue(GITHUB_URL));
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: options.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(chalk3.gray("\nCreated:"));
109
+ console.log(chalk2.gray("\nCreated:"));
38
110
  for (const file of result.created) {
39
- console.log(chalk3.green(` + ${file}`));
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
- chalk3.gray("\nRun ") + chalk3.cyan("lnai sync") + chalk3.gray(" to generate tool configs.")
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
- chalk3.red(error instanceof Error ? error.message : String(error))
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(chalk3.yellow("\nNo tools configured or enabled."));
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(chalk3.blue(`
156
+ console.log(chalk2.blue(`
69
157
  ${result.tool}:`));
70
158
  if (result.changes.length === 0) {
71
- console.log(chalk3.gray(" No changes"));
159
+ console.log(chalk2.gray(" No changes"));
72
160
  }
73
161
  for (const change of result.changes) {
74
- const icon = change.action === "create" ? chalk3.green("+") : change.action === "update" ? chalk3.yellow("~") : change.action === "delete" ? chalk3.red("-") : chalk3.gray("=");
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(chalk3.yellow(`
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
- chalk3.red(error instanceof Error ? error.message : String(error))
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(chalk3.red("\nUnified config errors:"));
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(chalk3.red(`
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(chalk3.yellow(`
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(chalk3.gray(`
230
+ console.log(chalk2.gray(`
143
231
  ${plugin} skipped features:`));
144
232
  for (const item of skipped) {
145
- console.log(chalk3.gray(` - ${item.feature}: ${item.reason}`));
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
- chalk3.red(error instanceof Error ? error.message : String(error))
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.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.3.0"
43
+ "@lnai/core": "0.4.0"
42
44
  },
43
45
  "devDependencies": {
44
46
  "@types/node": "^25.0.10",