@ax-llm/ax 18.0.0 → 18.0.1

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/cli/index.mjs CHANGED
@@ -4,7 +4,7 @@
4
4
  * CLI for @ax-llm/ax
5
5
  *
6
6
  * Commands:
7
- * setup-claude [--force] Install/upgrade Claude Code skills to .claude/skills/ax/ (project-local)
7
+ * setup-claude [--force] Install/upgrade Claude Code skills to .claude/skills/<name>/SKILL.md (project-local)
8
8
  * remove-claude Remove all Claude Code skills
9
9
  *
10
10
  * Usage:
@@ -21,7 +21,7 @@ import {
21
21
  rmSync,
22
22
  writeFileSync,
23
23
  } from 'node:fs';
24
- import { dirname, join } from 'node:path';
24
+ import { basename, dirname, join } from 'node:path';
25
25
  import { fileURLToPath } from 'node:url';
26
26
 
27
27
  const __filename = fileURLToPath(import.meta.url);
@@ -30,8 +30,8 @@ const __dirname = dirname(__filename);
30
30
  // Skill files directory in the package
31
31
  const SKILLS_SOURCE_DIR = join(__dirname, '..', 'skills');
32
32
 
33
- // Target location in current working directory (project-local)
34
- const SKILL_TARGET_DIR = join(process.cwd(), '.claude', 'skills', 'ax');
33
+ // Base target location for skills in current working directory (project-local)
34
+ const SKILLS_BASE_DIR = join(process.cwd(), '.claude', 'skills');
35
35
 
36
36
  /**
37
37
  * Get all *.md skill files from the source directory
@@ -43,6 +43,20 @@ function getSkillFiles() {
43
43
  return readdirSync(SKILLS_SOURCE_DIR).filter((f) => f.endsWith('.md'));
44
44
  }
45
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
+
46
60
  /**
47
61
  * Compare semver versions
48
62
  * Returns: 1 if a > b, -1 if a < b, 0 if equal
@@ -122,11 +136,28 @@ function setupClaude(force = false) {
122
136
  process.exit(1);
123
137
  }
124
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
+
125
154
  let allUpToDate = true;
126
155
 
127
156
  for (const file of skillFiles) {
128
157
  const skillSource = join(SKILLS_SOURCE_DIR, file);
129
- const skillTarget = join(SKILL_TARGET_DIR, file);
158
+ const skillName = getSkillName(skillSource);
159
+ const skillDir = join(SKILLS_BASE_DIR, skillName);
160
+ const skillTarget = join(skillDir, 'SKILL.md');
130
161
 
131
162
  const packageVersion = getPackageVersion(skillSource);
132
163
  const installedVersion = getInstalledVersion(skillTarget);
@@ -146,11 +177,11 @@ function setupClaude(force = false) {
146
177
  shouldInstall = true;
147
178
  action = 'Upgraded';
148
179
  } else if (comparison === 0) {
149
- console.log(`${file} is up to date (v${installedVersion})`);
180
+ console.log(`${skillName} is up to date (v${installedVersion})`);
150
181
  continue;
151
182
  } else {
152
183
  console.log(
153
- `${file} is already at v${installedVersion} (package has v${packageVersion})`
184
+ `${skillName} is already at v${installedVersion} (package has v${packageVersion})`
154
185
  );
155
186
  continue;
156
187
  }
@@ -160,15 +191,17 @@ function setupClaude(force = false) {
160
191
  }
161
192
 
162
193
  if (!shouldInstall) {
163
- console.log(`${file} is up to date (v${installedVersion || 'unknown'})`);
194
+ console.log(
195
+ `${skillName} is up to date (v${installedVersion || 'unknown'})`
196
+ );
164
197
  continue;
165
198
  }
166
199
 
167
200
  allUpToDate = false;
168
201
 
169
202
  // Create target directory if it doesn't exist
170
- if (!existsSync(SKILL_TARGET_DIR)) {
171
- mkdirSync(SKILL_TARGET_DIR, { recursive: true });
203
+ if (!existsSync(skillDir)) {
204
+ mkdirSync(skillDir, { recursive: true });
172
205
  }
173
206
 
174
207
  try {
@@ -177,15 +210,15 @@ function setupClaude(force = false) {
177
210
 
178
211
  if (action === 'Upgraded' && installedVersion && packageVersion) {
179
212
  console.log(
180
- `\u2713 ${action} ${file} (v${installedVersion} \u2192 v${packageVersion})`
213
+ `\u2713 ${action} ${skillName} (v${installedVersion} \u2192 v${packageVersion})`
181
214
  );
182
215
  } else {
183
216
  console.log(
184
- `\u2713 ${action} ${file} (v${packageVersion || 'unknown'})`
217
+ `\u2713 ${action} ${skillName} (v${packageVersion || 'unknown'})`
185
218
  );
186
219
  }
187
220
  } catch (err) {
188
- console.error(`Error installing ${file}: ${err.message}`);
221
+ console.error(`Error installing ${skillName}: ${err.message}`);
189
222
  process.exit(1);
190
223
  }
191
224
  }
@@ -199,39 +232,54 @@ function setupClaude(force = false) {
199
232
  * Remove all Claude Code skills
200
233
  */
201
234
  function removeClaude() {
202
- if (!existsSync(SKILL_TARGET_DIR)) {
203
- console.log('Ax Claude Code skills are not installed.');
235
+ const skillFiles = getSkillFiles();
236
+
237
+ if (skillFiles.length === 0) {
238
+ console.log('No skill definitions found in package.');
204
239
  return;
205
240
  }
206
241
 
207
242
  try {
208
- const installedFiles = readdirSync(SKILL_TARGET_DIR).filter((f) =>
209
- f.endsWith('.md')
210
- );
211
-
212
- if (installedFiles.length === 0) {
213
- console.log('Ax Claude Code skills are not installed.');
214
- return;
215
- }
216
-
217
- for (const file of installedFiles) {
218
- rmSync(join(SKILL_TARGET_DIR, file), { force: true });
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
+ }
219
264
  }
220
265
 
221
- // Try to remove the directory if empty
222
- try {
223
- const remaining = readdirSync(SKILL_TARGET_DIR);
224
- if (remaining.length === 0) {
225
- rmSync(SKILL_TARGET_DIR, { recursive: true, force: true });
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 });
226
274
  }
227
- } catch {
228
- // Ignore errors when trying to clean up directory
229
275
  }
230
276
 
231
- const noun = installedFiles.length === 1 ? 'skill' : 'skills';
232
- console.log(
233
- `\u2713 Removed ${installedFiles.length} Ax Claude Code ${noun}`
234
- );
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
+ }
235
283
  } catch (err) {
236
284
  console.error(`Error removing skills: ${err.message}`);
237
285
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ax-llm/ax",
3
- "version": "18.0.0",
3
+ "version": "18.0.1",
4
4
  "type": "module",
5
5
  "description": "The best library to work with LLMs",
6
6
  "repository": {
@@ -19,9 +19,10 @@ import {
19
19
  mkdirSync,
20
20
  readFileSync,
21
21
  readdirSync,
22
+ rmSync,
22
23
  writeFileSync,
23
24
  } from 'node:fs';
24
- import { dirname, join, sep } from 'node:path';
25
+ import { basename, dirname, join, sep } from 'node:path';
25
26
  import { fileURLToPath } from 'node:url';
26
27
 
27
28
  const __filename = fileURLToPath(import.meta.url);
@@ -118,6 +119,20 @@ function getInstalledVersion(targetPath) {
118
119
  }
119
120
  }
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
+
121
136
  /**
122
137
  * Get the package version from the skill source file
123
138
  */
@@ -173,7 +188,7 @@ function install() {
173
188
  // Paths
174
189
  const skillsSourceDir = join(__dirname, '..', 'skills');
175
190
  const projectRoot = findProjectRoot();
176
- const skillTargetDir = join(projectRoot, '.claude', 'skills', 'ax');
191
+ const skillsBaseDir = join(projectRoot, '.claude', 'skills');
177
192
 
178
193
  // Discover all *.md skill files
179
194
  if (!existsSync(skillsSourceDir)) {
@@ -188,11 +203,28 @@ function install() {
188
203
  return;
189
204
  }
190
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
+
191
221
  const results = [];
192
222
 
193
223
  for (const file of skillFiles) {
194
224
  const skillSource = join(skillsSourceDir, file);
195
- const skillTarget = join(skillTargetDir, file);
225
+ const skillName = getSkillName(skillSource);
226
+ const skillDir = join(skillsBaseDir, skillName);
227
+ const skillTarget = join(skillDir, 'SKILL.md');
196
228
 
197
229
  const packageVersion = getPackageVersion(skillSource);
198
230
  const installedVersion = getInstalledVersion(skillTarget);
@@ -218,13 +250,13 @@ function install() {
218
250
  }
219
251
 
220
252
  if (shouldInstallFile) {
221
- if (!existsSync(skillTargetDir)) {
222
- mkdirSync(skillTargetDir, { recursive: true });
253
+ if (!existsSync(skillDir)) {
254
+ mkdirSync(skillDir, { recursive: true });
223
255
  }
224
256
 
225
257
  const content = readFileSync(skillSource, 'utf8');
226
258
  writeFileSync(skillTarget, content, 'utf8');
227
- results.push({ file, action, versionInfo });
259
+ results.push({ file: skillName, action, versionInfo });
228
260
  }
229
261
  }
230
262