@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 +29 -0
- package/cli/index.mjs +70 -317
- package/package.json +1 -1
- package/scripts/postinstall.mjs +16 -202
- package/scripts/skill-bundles.mjs +70 -0
- package/scripts/skill-config.mjs +59 -0
- package/scripts/skill-install.mjs +123 -0
- package/skills/ax-agent-context.md +1 -1
- package/skills/ax-agent-memory-skills.md +1 -1
- package/skills/ax-agent-observability.md +1 -1
- package/skills/ax-agent-optimize.md +1 -1
- package/skills/ax-agent-rlm.md +1 -1
- package/skills/ax-agent.md +1 -1
- package/skills/ax-ai.md +1 -1
- package/skills/ax-audio.md +1 -1
- package/skills/ax-flow.md +1 -1
- package/skills/ax-gen.md +1 -1
- package/skills/ax-gepa.md +1 -1
- package/skills/ax-llm.md +46 -353
- package/skills/ax-mcp.md +1 -1
- package/skills/ax-playbook.md +1 -1
- package/skills/ax-refine.md +1 -1
- package/skills/ax-signature.md +1 -1
- package/skills/ax-typesafe.md +1 -1
- package/skills/references/quickstart.md +354 -0
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
|
|
28
|
-
const
|
|
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
|
|
301
|
-
remove-claude Remove
|
|
302
|
-
|
|
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
|
-
--
|
|
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
|
|
309
|
-
npx @ax-llm/ax setup-claude --
|
|
310
|
-
npx @ax-llm/ax
|
|
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
|
-
|
|
315
|
-
|
|
316
|
-
const
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
package/scripts/postinstall.mjs
CHANGED
|
@@ -1,34 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
|
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
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
87
|
+
'✓ Ax skills updated; entry point includes local reference guides.'
|
|
270
88
|
);
|
|
271
89
|
}
|
|
272
90
|
} catch {
|
|
273
|
-
//
|
|
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();
|