@ax-llm/ax 25.4.1 → 25.4.2

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 CHANGED
@@ -157,6 +157,35 @@ const analyzer = ax(`
157
157
  npm install @ax-llm/ax
158
158
  ```
159
159
 
160
+ Outside CI, installation sets up one Claude Code skill, `ax-llm`, with a compact
161
+ index and bundled guides your agent reads as needed. To disable future automatic
162
+ setup, run `npx @ax-llm/ax skills-auto-install off` from your project root. This
163
+ saves `ax.skills.autoInstall: false` in your project's `package.json`; commit
164
+ that setting to share it with your team. Use `skills-auto-install on` to re-enable
165
+ automatic setup on future installs. Set `AX_SKIP_SKILL_INSTALL=1` to skip setup
166
+ for a single installation; CI and this environment variable take precedence over
167
+ the saved setting.
168
+
169
+ You can also set the preference before installing Ax for the first time:
170
+
171
+ ```json
172
+ {
173
+ "ax": {
174
+ "skills": {
175
+ "autoInstall": false
176
+ }
177
+ }
178
+ }
179
+ ```
180
+
181
+ Merge this field into your existing `package.json`. Manual
182
+ `npx @ax-llm/ax setup-claude` works even when automatic setup is disabled;
183
+ add `--all` to register every specialist or `--skill ax-ai` to install only one.
184
+ Changing the preference leaves installed skills in place, and manual setup or
185
+ removal preserves the saved preference.
186
+ Existing specialists remain installed; use `remove-claude` followed by
187
+ `setup-claude` to switch an older full installation to the compact bundle.
188
+
160
189
  ### Your First AI Feature (2 minutes)
161
190
 
162
191
  ```typescript
package/cli/index.mjs CHANGED
@@ -1,294 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- /**
4
- * CLI for @ax-llm/ax
5
- *
6
- * Commands:
7
- * setup-claude [--force] Install/upgrade Claude Code skills to .claude/skills/<name>/SKILL.md (project-local)
8
- * remove-claude Remove all Claude Code skills
9
- *
10
- * Usage:
11
- * npx @ax-llm/ax setup-claude # Install or upgrade if newer version
12
- * npx @ax-llm/ax setup-claude --force # Force overwrite regardless of version
13
- * npx @ax-llm/ax remove-claude # Remove all skills
14
- */
15
-
16
- import {
17
- existsSync,
18
- mkdirSync,
19
- readdirSync,
20
- readFileSync,
21
- rmSync,
22
- writeFileSync,
23
- } from 'node:fs';
24
- import { basename, dirname, join } from 'node:path';
3
+ import { dirname, join } from 'node:path';
25
4
  import { fileURLToPath } from 'node:url';
5
+ import { setAutoSkillInstall } from '../scripts/skill-config.mjs';
6
+ import { installSkills, removeSkills } from '../scripts/skill-install.mjs';
26
7
 
27
- const __filename = fileURLToPath(import.meta.url);
28
- const __dirname = dirname(__filename);
29
-
30
- // Skill files directory in the package
31
- const SKILLS_SOURCE_DIR = join(__dirname, '..', 'skills');
32
-
33
- // Base target location for skills in current working directory (project-local)
34
- const SKILLS_BASE_DIR = join(process.cwd(), '.claude', 'skills');
35
-
36
- /**
37
- * Get all *.md skill files from the source directory
38
- */
39
- function getSkillFiles() {
40
- if (!existsSync(SKILLS_SOURCE_DIR)) {
41
- return [];
42
- }
43
- return readdirSync(SKILLS_SOURCE_DIR).filter((f) => f.endsWith('.md'));
44
- }
45
-
46
- /**
47
- * Get the skill name from YAML frontmatter, falling back to filename without .md
48
- */
49
- function getSkillName(filePath) {
50
- try {
51
- const content = readFileSync(filePath, 'utf8');
52
- const match = content.match(/^name:\s*["']?([^"'\n\r]+)/m);
53
- if (match) return match[1].trim();
54
- } catch {
55
- // Fall through to fallback
56
- }
57
- return basename(filePath, '.md');
58
- }
59
-
60
- /**
61
- * Compare semver versions
62
- * Returns: 1 if a > b, -1 if a < b, 0 if equal
63
- */
64
- function compareSemver(a, b) {
65
- const parseVersion = (v) => {
66
- const match = v.match(/^(\d+)\.(\d+)\.(\d+)/);
67
- if (!match) return [0, 0, 0];
68
- return [
69
- Number.parseInt(match[1], 10),
70
- Number.parseInt(match[2], 10),
71
- Number.parseInt(match[3], 10),
72
- ];
73
- };
74
-
75
- const [aMajor, aMinor, aPatch] = parseVersion(a);
76
- const [bMajor, bMinor, bPatch] = parseVersion(b);
77
-
78
- if (aMajor !== bMajor) return aMajor > bMajor ? 1 : -1;
79
- if (aMinor !== bMinor) return aMinor > bMinor ? 1 : -1;
80
- if (aPatch !== bPatch) return aPatch > bPatch ? 1 : -1;
81
- return 0;
82
- }
83
-
84
- /**
85
- * Get the installed skill version from a file
86
- */
87
- function getInstalledVersion(targetPath) {
88
- if (!existsSync(targetPath)) {
89
- return null;
90
- }
91
-
92
- try {
93
- const content = readFileSync(targetPath, 'utf8');
94
- const match = content.match(/^version:\s*["']?([^"'\n\r]+)/m);
95
- return match ? match[1].trim() : null;
96
- } catch {
97
- return null;
98
- }
99
- }
100
-
101
- /**
102
- * Get the package version from a skill source file
103
- */
104
- function getPackageVersion(sourcePath) {
105
- if (!existsSync(sourcePath)) {
106
- // Fallback: try to read from package.json
107
- const packageJsonPath = join(__dirname, '..', 'package.json');
108
- if (existsSync(packageJsonPath)) {
109
- try {
110
- const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
111
- return pkg.version || null;
112
- } catch {
113
- return null;
114
- }
115
- }
116
- return null;
117
- }
118
-
119
- try {
120
- const content = readFileSync(sourcePath, 'utf8');
121
- const match = content.match(/^version:\s*["']?([^"'\n\r]+)/m);
122
- return match ? match[1].trim() : null;
123
- } catch {
124
- return null;
125
- }
126
- }
127
-
128
- /**
129
- * Install or upgrade Claude Code skills
130
- */
131
- function setupClaude(force = false) {
132
- const skillFiles = getSkillFiles();
133
-
134
- if (skillFiles.length === 0) {
135
- console.error('Error: No skill files found. The package may be corrupted.');
136
- process.exit(1);
137
- }
138
-
139
- // Clean up legacy flat file structure (.claude/skills/ax/*.md except SKILL.md)
140
- const legacyDir = join(SKILLS_BASE_DIR, 'ax');
141
- if (existsSync(legacyDir)) {
142
- try {
143
- const legacyFiles = readdirSync(legacyDir).filter(
144
- (f) => f.endsWith('.md') && f !== 'SKILL.md'
145
- );
146
- for (const f of legacyFiles) {
147
- rmSync(join(legacyDir, f), { force: true });
148
- }
149
- } catch {
150
- // Ignore cleanup errors
151
- }
152
- }
153
-
154
- let allUpToDate = true;
155
-
156
- for (const file of skillFiles) {
157
- const skillSource = join(SKILLS_SOURCE_DIR, file);
158
- const skillName = getSkillName(skillSource);
159
- const skillDir = join(SKILLS_BASE_DIR, skillName);
160
- const skillTarget = join(skillDir, 'SKILL.md');
161
-
162
- const packageVersion = getPackageVersion(skillSource);
163
- const installedVersion = getInstalledVersion(skillTarget);
8
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
9
+ const [command = 'help', ...flags] = process.argv.slice(2);
164
10
 
165
- // Determine if we should install
166
- let shouldInstall = false;
167
- let action = 'Installed';
168
-
169
- if (!existsSync(skillTarget)) {
170
- shouldInstall = true;
171
- } else if (force) {
172
- shouldInstall = true;
173
- action = installedVersion ? 'Reinstalled' : 'Installed';
174
- } else if (installedVersion && packageVersion) {
175
- const comparison = compareSemver(packageVersion, installedVersion);
176
- if (comparison > 0) {
177
- shouldInstall = true;
178
- action = 'Upgraded';
179
- } else if (comparison === 0) {
180
- console.log(`${skillName} is up to date (v${installedVersion})`);
181
- continue;
182
- } else {
183
- console.log(
184
- `${skillName} is already at v${installedVersion} (package has v${packageVersion})`
185
- );
186
- continue;
187
- }
188
- } else if (!installedVersion && existsSync(skillTarget)) {
189
- shouldInstall = true;
190
- action = 'Upgraded';
191
- }
192
-
193
- if (!shouldInstall) {
194
- console.log(
195
- `${skillName} is up to date (v${installedVersion || 'unknown'})`
196
- );
197
- continue;
198
- }
199
-
200
- allUpToDate = false;
201
-
202
- // Create target directory if it doesn't exist
203
- if (!existsSync(skillDir)) {
204
- mkdirSync(skillDir, { recursive: true });
205
- }
206
-
207
- try {
208
- const content = readFileSync(skillSource, 'utf8');
209
- writeFileSync(skillTarget, content, 'utf8');
210
-
211
- if (action === 'Upgraded' && installedVersion && packageVersion) {
212
- console.log(
213
- `\u2713 ${action} ${skillName} (v${installedVersion} \u2192 v${packageVersion})`
214
- );
215
- } else {
216
- console.log(
217
- `\u2713 ${action} ${skillName} (v${packageVersion || 'unknown'})`
218
- );
219
- }
220
- } catch (err) {
221
- console.error(`Error installing ${skillName}: ${err.message}`);
222
- process.exit(1);
223
- }
224
- }
225
-
226
- if (allUpToDate) {
227
- console.log('All Ax Claude Code skills are up to date.');
228
- }
229
- }
230
-
231
- /**
232
- * Remove all Claude Code skills
233
- */
234
- function removeClaude() {
235
- const skillFiles = getSkillFiles();
236
-
237
- if (skillFiles.length === 0) {
238
- console.log('No skill definitions found in package.');
239
- return;
240
- }
241
-
242
- try {
243
- let removedCount = 0;
244
-
245
- for (const file of skillFiles) {
246
- const skillName = getSkillName(join(SKILLS_SOURCE_DIR, file));
247
- const skillDir = join(SKILLS_BASE_DIR, skillName);
248
- const skillTarget = join(skillDir, 'SKILL.md');
249
-
250
- if (existsSync(skillTarget)) {
251
- rmSync(skillTarget, { force: true });
252
- removedCount++;
253
-
254
- // Try to remove the directory if empty
255
- try {
256
- const remaining = readdirSync(skillDir);
257
- if (remaining.length === 0) {
258
- rmSync(skillDir, { recursive: true, force: true });
259
- }
260
- } catch {
261
- // Ignore errors when trying to clean up directory
262
- }
263
- }
264
- }
265
-
266
- // Also clean up legacy flat files from old .claude/skills/ax/ structure
267
- const legacyDir = join(SKILLS_BASE_DIR, 'ax');
268
- if (existsSync(legacyDir)) {
269
- const legacyFiles = readdirSync(legacyDir).filter(
270
- (f) => f.endsWith('.md') && f !== 'SKILL.md'
271
- );
272
- for (const f of legacyFiles) {
273
- rmSync(join(legacyDir, f), { force: true });
274
- }
275
- }
276
-
277
- if (removedCount > 0) {
278
- const noun = removedCount === 1 ? 'skill' : 'skills';
279
- console.log(`\u2713 Removed ${removedCount} Ax Claude Code ${noun}`);
280
- } else {
281
- console.log('Ax Claude Code skills are not installed.');
282
- }
283
- } catch (err) {
284
- console.error(`Error removing skills: ${err.message}`);
285
- process.exit(1);
286
- }
287
- }
288
-
289
- /**
290
- * Show help
291
- */
292
11
  function showHelp() {
293
12
  console.log(`
294
13
  @ax-llm/ax CLI
@@ -297,42 +16,76 @@ Usage:
297
16
  npx @ax-llm/ax <command> [options]
298
17
 
299
18
  Commands:
300
- setup-claude [--force] Install/upgrade Claude Code skills
301
- remove-claude Remove all Claude Code skills
302
- help Show this help message
19
+ setup-claude Install the Ax entry point and bundled guides locally
20
+ remove-claude Remove installed Ax skills and their bundled guides
21
+ skills-auto-install <on|off>
22
+ Save automatic setup preference in project package.json
23
+ help Show this help
303
24
 
304
- Options:
305
- --force Force reinstall regardless of version
25
+ Options for setup-claude:
26
+ --all Install every specialist as a separate skill too
27
+ --skill <name> Install only the named skill (repeatable)
28
+ --force Reinstall even if the installed version is newer
306
29
 
307
30
  Examples:
308
- npx @ax-llm/ax setup-claude # Install or upgrade
309
- npx @ax-llm/ax setup-claude --force # Force reinstall
310
- npx @ax-llm/ax remove-claude # Remove skills
31
+ npx @ax-llm/ax setup-claude
32
+ npx @ax-llm/ax setup-claude --all
33
+ npx @ax-llm/ax setup-claude --skill ax-ai
34
+ npx @ax-llm/ax skills-auto-install off
35
+ npx @ax-llm/ax skills-auto-install on
36
+
37
+ Automatic setup installs only ax-llm in .claude/skills, outside CI.
38
+ Run skills-auto-install off from your project root to disable future automatic setup.
39
+ This saves ax.skills.autoInstall in package.json; on restores automatic setup.
40
+ Manual setup-claude still works while automatic setup is disabled.
41
+ Set AX_SKIP_SKILL_INSTALL=1 to skip setup for a single package installation.
42
+ Existing specialist installs remain until you run remove-claude.
311
43
  `);
312
44
  }
313
45
 
314
- // Parse command line arguments
315
- const args = process.argv.slice(2);
316
- const command = args[0];
317
- const flags = args.slice(1);
318
-
319
- switch (command) {
320
- case 'setup-claude':
321
- setupClaude(flags.includes('--force'));
322
- break;
323
- case 'remove-claude':
324
- removeClaude();
325
- break;
326
- case 'help':
327
- case '--help':
328
- case '-h':
329
- showHelp();
330
- break;
331
- case undefined:
46
+ try {
47
+ if (command === 'setup-claude') {
48
+ const options = { all: false, force: false, names: [] };
49
+ for (let i = 0; i < flags.length; i++) {
50
+ const flag = flags[i];
51
+ if (flag === '--all') options.all = true;
52
+ else if (flag === '--force') options.force = true;
53
+ else if (
54
+ flag === '--skill' &&
55
+ flags[i + 1] &&
56
+ !flags[i + 1].startsWith('--')
57
+ )
58
+ options.names.push(flags[++i]);
59
+ else throw new Error(`Unknown or incomplete option: ${flag}`);
60
+ }
61
+ if (options.all && options.names.length)
62
+ throw new Error('Choose --all or --skill.');
63
+ const installed = installSkills(packageRoot, process.cwd(), options);
64
+ console.log(
65
+ installed.length
66
+ ? `✓ Installed Ax skills: ${installed.join(', ')}`
67
+ : 'Ax skills are up to date.'
68
+ );
69
+ } else if (command === 'remove-claude' && flags.length === 0) {
70
+ console.log(
71
+ `✓ Removed ${removeSkills(packageRoot, process.cwd())} Ax skills.`
72
+ );
73
+ } else if (command === 'skills-auto-install') {
74
+ if (flags.length !== 1 || !['on', 'off'].includes(flags[0]))
75
+ throw new Error('Usage: npx @ax-llm/ax skills-auto-install <on|off>');
76
+ const enabled = flags[0] === 'on';
77
+ setAutoSkillInstall(process.cwd(), enabled);
78
+ console.log(
79
+ `✓ Automatic Ax skill installation ${enabled ? 'enabled' : 'disabled'} in package.json.`
80
+ );
81
+ } else if (['help', '--help', '-h'].includes(command)) {
332
82
  showHelp();
333
- break;
334
- default:
335
- console.error(`Unknown command: ${command}`);
336
- console.error('Run "npx @ax-llm/ax help" for usage information.');
337
- process.exit(1);
83
+ } else {
84
+ throw new Error(
85
+ `Unknown command: ${command}. Run npx @ax-llm/ax help for usage.`
86
+ );
87
+ }
88
+ } catch (error) {
89
+ console.error(error.message);
90
+ process.exitCode = 1;
338
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ax-llm/ax",
3
- "version": "25.4.1",
3
+ "version": "25.4.2",
4
4
  "type": "module",
5
5
  "description": "The best library to work with LLMs",
6
6
  "repository": {
@@ -1,34 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- /**
4
- * Postinstall script for @ax-llm/ax
5
- *
6
- * Auto-installs/upgrades the Claude Code skill on package install.
7
- *
8
- * Features:
9
- * - Skips in CI environments (CI, CONTINUOUS_INTEGRATION env vars)
10
- * - Skips if AX_SKIP_SKILL_INSTALL=1
11
- * - Silent failure (never breaks package installation)
12
- * - Cross-platform (macOS, Linux, Windows)
13
- * - Only logs in interactive terminals (process.stdout.isTTY)
14
- * - Version-aware upgrades: only upgrades if newer version available
15
- */
16
-
17
- import {
18
- existsSync,
19
- mkdirSync,
20
- readFileSync,
21
- readdirSync,
22
- rmSync,
23
- writeFileSync,
24
- } from 'node:fs';
25
- import { basename, dirname, join, sep } from 'node:path';
3
+ // Register one entry-point skill with bundled guides. Specialist skills are
4
+ // available through the explicit setup command; package installation never
5
+ // adds them individually. Failures must never break dependency installation.
6
+ import { dirname, join, sep } from 'node:path';
26
7
  import { fileURLToPath } from 'node:url';
8
+ import { autoSkillInstallEnabled } from './skill-config.mjs';
9
+ import { installSkills } from './skill-install.mjs';
27
10
 
28
- const __filename = fileURLToPath(import.meta.url);
29
- const __dirname = dirname(__filename);
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
30
12
 
31
- // Check if we should skip installation
32
13
  function shouldSkip() {
33
14
  // Skip in CI environments
34
15
  if (process.env.CI === 'true' || process.env.CI === '1') {
@@ -73,88 +54,6 @@ function shouldSkip() {
73
54
  return false;
74
55
  }
75
56
 
76
- // Check if we're in an interactive terminal
77
- function isInteractive() {
78
- return process.stdout.isTTY === true;
79
- }
80
-
81
- /**
82
- * Compare semver versions
83
- * Returns: 1 if a > b, -1 if a < b, 0 if equal
84
- */
85
- function compareSemver(a, b) {
86
- const parseVersion = (v) => {
87
- const match = v.match(/^(\d+)\.(\d+)\.(\d+)/);
88
- if (!match) return [0, 0, 0];
89
- return [
90
- Number.parseInt(match[1], 10),
91
- Number.parseInt(match[2], 10),
92
- Number.parseInt(match[3], 10),
93
- ];
94
- };
95
-
96
- const [aMajor, aMinor, aPatch] = parseVersion(a);
97
- const [bMajor, bMinor, bPatch] = parseVersion(b);
98
-
99
- if (aMajor !== bMajor) return aMajor > bMajor ? 1 : -1;
100
- if (aMinor !== bMinor) return aMinor > bMinor ? 1 : -1;
101
- if (aPatch !== bPatch) return aPatch > bPatch ? 1 : -1;
102
- return 0;
103
- }
104
-
105
- /**
106
- * Get the installed skill version from the file
107
- */
108
- function getInstalledVersion(targetPath) {
109
- if (!existsSync(targetPath)) {
110
- return null;
111
- }
112
-
113
- try {
114
- const content = readFileSync(targetPath, 'utf8');
115
- const match = content.match(/^version:\s*["']?([^"'\n\r]+)/m);
116
- return match ? match[1].trim() : null;
117
- } catch {
118
- return null;
119
- }
120
- }
121
-
122
- /**
123
- * Get the skill name from YAML frontmatter, falling back to filename without .md
124
- */
125
- function getSkillName(filePath) {
126
- try {
127
- const content = readFileSync(filePath, 'utf8');
128
- const match = content.match(/^name:\s*["']?([^"'\n\r]+)/m);
129
- if (match) return match[1].trim();
130
- } catch {
131
- // Fall through to fallback
132
- }
133
- return basename(filePath, '.md');
134
- }
135
-
136
- /**
137
- * Get the package version from the skill source file
138
- */
139
- function getPackageVersion(sourcePath) {
140
- if (!existsSync(sourcePath)) {
141
- return null;
142
- }
143
-
144
- try {
145
- const content = readFileSync(sourcePath, 'utf8');
146
- const match = content.match(/^version:\s*["']?([^"'\n\r]+)/m);
147
- return match ? match[1].trim() : null;
148
- } catch {
149
- return null;
150
- }
151
- }
152
-
153
- /**
154
- * Find the project root by using INIT_CWD (set by npm/yarn/pnpm to the
155
- * directory where `install` was run), falling back to finding the
156
- * topmost node_modules in __dirname and taking its parent.
157
- */
158
57
  function findProjectRoot() {
159
58
  // INIT_CWD is the most reliable — npm, yarn, and pnpm all set it
160
59
  if (process.env.INIT_CWD) {
@@ -175,105 +74,20 @@ function findProjectRoot() {
175
74
  return join(__dirname, '..', '..', '..', '..');
176
75
  }
177
76
 
178
- /**
179
- * Main installation function
180
- */
181
- function install() {
182
- // Skip if needed
183
- if (shouldSkip()) {
184
- return;
185
- }
186
-
77
+ if (!shouldSkip()) {
187
78
  try {
188
- // Paths
189
- const skillsSourceDir = join(__dirname, '..', 'skills');
190
79
  const projectRoot = findProjectRoot();
191
- const skillsBaseDir = join(projectRoot, '.claude', 'skills');
192
-
193
- // Discover all *.md skill files
194
- if (!existsSync(skillsSourceDir)) {
195
- return;
196
- }
197
-
198
- const skillFiles = readdirSync(skillsSourceDir).filter((f) =>
199
- f.endsWith('.md')
200
- );
201
-
202
- if (skillFiles.length === 0) {
203
- return;
204
- }
205
-
206
- // Clean up legacy flat file structure (.claude/skills/ax/*.md except SKILL.md)
207
- const legacyDir = join(skillsBaseDir, 'ax');
208
- if (existsSync(legacyDir)) {
209
- try {
210
- const legacyFiles = readdirSync(legacyDir).filter(
211
- (f) => f.endsWith('.md') && f !== 'SKILL.md'
212
- );
213
- for (const f of legacyFiles) {
214
- rmSync(join(legacyDir, f), { force: true });
215
- }
216
- } catch {
217
- // Ignore cleanup errors
218
- }
219
- }
220
-
221
- const results = [];
222
-
223
- for (const file of skillFiles) {
224
- const skillSource = join(skillsSourceDir, file);
225
- const skillName = getSkillName(skillSource);
226
- const skillDir = join(skillsBaseDir, skillName);
227
- const skillTarget = join(skillDir, 'SKILL.md');
228
-
229
- const packageVersion = getPackageVersion(skillSource);
230
- const installedVersion = getInstalledVersion(skillTarget);
231
-
232
- let shouldInstallFile = false;
233
- let action = 'Installed';
234
- let versionInfo = '';
235
-
236
- if (!existsSync(skillTarget)) {
237
- shouldInstallFile = true;
238
- versionInfo = packageVersion ? ` (v${packageVersion})` : '';
239
- } else if (installedVersion && packageVersion) {
240
- const comparison = compareSemver(packageVersion, installedVersion);
241
- if (comparison > 0) {
242
- shouldInstallFile = true;
243
- action = 'Upgraded';
244
- versionInfo = ` (v${installedVersion} \u2192 v${packageVersion})`;
245
- }
246
- } else if (!installedVersion && existsSync(skillTarget)) {
247
- shouldInstallFile = true;
248
- action = 'Upgraded';
249
- versionInfo = packageVersion ? ` (v${packageVersion})` : '';
250
- }
251
-
252
- if (shouldInstallFile) {
253
- if (!existsSync(skillDir)) {
254
- mkdirSync(skillDir, { recursive: true });
255
- }
256
-
257
- const content = readFileSync(skillSource, 'utf8');
258
- writeFileSync(skillTarget, content, 'utf8');
259
- results.push({ file: skillName, action, versionInfo });
260
- }
261
- }
262
-
263
- if (results.length > 0 && isInteractive()) {
264
- const noun = results.length === 1 ? 'skill' : 'skills';
265
- const details = results
266
- .map((r) => `${r.action} ${r.file}${r.versionInfo}`)
267
- .join(', ');
80
+ const installed = autoSkillInstallEnabled(projectRoot)
81
+ ? installSkills(join(__dirname, '..'), projectRoot, {
82
+ upgradeInstalled: true,
83
+ })
84
+ : [];
85
+ if (installed.length > 0 && process.stdout.isTTY) {
268
86
  console.log(
269
- `\u2713 ${results.length} Ax Claude Code ${noun}: ${details}`
87
+ '✓ Ax skills updated; entry point includes local reference guides.'
270
88
  );
271
89
  }
272
90
  } catch {
273
- // Silent failure - never break npm install
274
- // The CLI command can be used for manual installation
91
+ // Manual recovery: npx @ax-llm/ax setup-claude
275
92
  }
276
93
  }
277
-
278
- // Run installation
279
- install();