@ax-llm/ax 16.0.11 → 16.0.13

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 ADDED
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CLI for @ax-llm/ax
5
+ *
6
+ * Commands:
7
+ * setup-claude [--force] Install/upgrade Claude Code skill to .claude/skills/ax/ (project-local)
8
+ * remove-claude Remove the Claude Code skill
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 the skill
14
+ */
15
+
16
+ import {
17
+ existsSync,
18
+ mkdirSync,
19
+ readFileSync,
20
+ rmSync,
21
+ writeFileSync,
22
+ } from 'node:fs';
23
+ import { dirname, join } from 'node:path';
24
+ import { fileURLToPath } from 'node:url';
25
+
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = dirname(__filename);
28
+
29
+ // Skill file location in the package
30
+ const SKILL_SOURCE = join(__dirname, '..', 'skills', 'ax-llm.md');
31
+
32
+ // Target location in current working directory (project-local)
33
+ const SKILL_TARGET_DIR = join(process.cwd(), '.claude', 'skills', 'ax');
34
+ const SKILL_TARGET = join(SKILL_TARGET_DIR, 'ax-llm.md');
35
+
36
+ /**
37
+ * Compare semver versions
38
+ * Returns: 1 if a > b, -1 if a < b, 0 if equal
39
+ */
40
+ function compareSemver(a, b) {
41
+ const parseVersion = (v) => {
42
+ const match = v.match(/^(\d+)\.(\d+)\.(\d+)/);
43
+ if (!match) return [0, 0, 0];
44
+ return [
45
+ Number.parseInt(match[1], 10),
46
+ Number.parseInt(match[2], 10),
47
+ Number.parseInt(match[3], 10),
48
+ ];
49
+ };
50
+
51
+ const [aMajor, aMinor, aPatch] = parseVersion(a);
52
+ const [bMajor, bMinor, bPatch] = parseVersion(b);
53
+
54
+ if (aMajor !== bMajor) return aMajor > bMajor ? 1 : -1;
55
+ if (aMinor !== bMinor) return aMinor > bMinor ? 1 : -1;
56
+ if (aPatch !== bPatch) return aPatch > bPatch ? 1 : -1;
57
+ return 0;
58
+ }
59
+
60
+ /**
61
+ * Get the installed skill version from the file
62
+ */
63
+ function getInstalledVersion() {
64
+ if (!existsSync(SKILL_TARGET)) {
65
+ return null;
66
+ }
67
+
68
+ try {
69
+ const content = readFileSync(SKILL_TARGET, 'utf8');
70
+ const match = content.match(/^version:\s*["']?([^"'\n\r]+)/m);
71
+ return match ? match[1].trim() : null;
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Get the package version from the skill source file
79
+ */
80
+ function getPackageVersion() {
81
+ if (!existsSync(SKILL_SOURCE)) {
82
+ // Fallback: try to read from package.json
83
+ const packageJsonPath = join(__dirname, '..', 'package.json');
84
+ if (existsSync(packageJsonPath)) {
85
+ try {
86
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
87
+ return pkg.version || null;
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
92
+ return null;
93
+ }
94
+
95
+ try {
96
+ const content = readFileSync(SKILL_SOURCE, 'utf8');
97
+ const match = content.match(/^version:\s*["']?([^"'\n\r]+)/m);
98
+ return match ? match[1].trim() : null;
99
+ } catch {
100
+ return null;
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Install or upgrade the Claude Code skill
106
+ */
107
+ function setupClaude(force = false) {
108
+ // Check if skill source exists
109
+ if (!existsSync(SKILL_SOURCE)) {
110
+ console.error('Error: Skill file not found. The package may be corrupted.');
111
+ process.exit(1);
112
+ }
113
+
114
+ const packageVersion = getPackageVersion();
115
+ const installedVersion = getInstalledVersion();
116
+
117
+ // Determine if we should install
118
+ let shouldInstall = false;
119
+ let action = 'Installed';
120
+
121
+ if (!existsSync(SKILL_TARGET)) {
122
+ // Fresh install
123
+ shouldInstall = true;
124
+ } else if (force) {
125
+ // Force overwrite
126
+ shouldInstall = true;
127
+ action = installedVersion ? 'Reinstalled' : 'Installed';
128
+ } else if (installedVersion && packageVersion) {
129
+ // Compare versions
130
+ const comparison = compareSemver(packageVersion, installedVersion);
131
+ if (comparison > 0) {
132
+ shouldInstall = true;
133
+ action = `Upgraded`;
134
+ } else if (comparison === 0) {
135
+ console.log(`Ax Claude Code skill is up to date (v${installedVersion})`);
136
+ return;
137
+ } else {
138
+ // Installed version is newer (shouldn't happen normally)
139
+ console.log(
140
+ `Ax Claude Code skill is already at v${installedVersion} (package has v${packageVersion})`
141
+ );
142
+ return;
143
+ }
144
+ } else if (!installedVersion && existsSync(SKILL_TARGET)) {
145
+ // File exists but no version - upgrade
146
+ shouldInstall = true;
147
+ action = 'Upgraded';
148
+ }
149
+
150
+ if (!shouldInstall) {
151
+ console.log(
152
+ `Ax Claude Code skill is up to date (v${installedVersion || 'unknown'})`
153
+ );
154
+ return;
155
+ }
156
+
157
+ // Create target directory if it doesn't exist
158
+ if (!existsSync(SKILL_TARGET_DIR)) {
159
+ mkdirSync(SKILL_TARGET_DIR, { recursive: true });
160
+ }
161
+
162
+ // Copy the skill file
163
+ try {
164
+ const content = readFileSync(SKILL_SOURCE, 'utf8');
165
+ writeFileSync(SKILL_TARGET, content, 'utf8');
166
+
167
+ if (action === 'Upgraded' && installedVersion && packageVersion) {
168
+ console.log(
169
+ `\u2713 ${action} Ax Claude Code skill (v${installedVersion} \u2192 v${packageVersion})`
170
+ );
171
+ } else {
172
+ console.log(
173
+ `\u2713 ${action} Ax Claude Code skill (v${packageVersion || 'unknown'})`
174
+ );
175
+ }
176
+ } catch (err) {
177
+ console.error(`Error installing skill: ${err.message}`);
178
+ process.exit(1);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Remove the Claude Code skill
184
+ */
185
+ function removeClaude() {
186
+ if (!existsSync(SKILL_TARGET)) {
187
+ console.log('Ax Claude Code skill is not installed.');
188
+ return;
189
+ }
190
+
191
+ try {
192
+ rmSync(SKILL_TARGET, { force: true });
193
+
194
+ // Try to remove the directory if empty
195
+ try {
196
+ const dir = dirname(SKILL_TARGET);
197
+ const files = readdirSync(dir);
198
+ if (files.length === 0) {
199
+ rmSync(dir, { recursive: true, force: true });
200
+ }
201
+ } catch {
202
+ // Ignore errors when trying to clean up directory
203
+ }
204
+
205
+ console.log('\u2713 Removed Ax Claude Code skill');
206
+ } catch (err) {
207
+ console.error(`Error removing skill: ${err.message}`);
208
+ process.exit(1);
209
+ }
210
+ }
211
+
212
+ // Import readdirSync for cleanup
213
+ import { readdirSync } from 'node:fs';
214
+
215
+ /**
216
+ * Show help
217
+ */
218
+ function showHelp() {
219
+ console.log(`
220
+ @ax-llm/ax CLI
221
+
222
+ Usage:
223
+ npx @ax-llm/ax <command> [options]
224
+
225
+ Commands:
226
+ setup-claude [--force] Install/upgrade Claude Code skill
227
+ remove-claude Remove the Claude Code skill
228
+ help Show this help message
229
+
230
+ Options:
231
+ --force Force reinstall regardless of version
232
+
233
+ Examples:
234
+ npx @ax-llm/ax setup-claude # Install or upgrade
235
+ npx @ax-llm/ax setup-claude --force # Force reinstall
236
+ npx @ax-llm/ax remove-claude # Remove skill
237
+ `);
238
+ }
239
+
240
+ // Parse command line arguments
241
+ const args = process.argv.slice(2);
242
+ const command = args[0];
243
+ const flags = args.slice(1);
244
+
245
+ switch (command) {
246
+ case 'setup-claude':
247
+ setupClaude(flags.includes('--force'));
248
+ break;
249
+ case 'remove-claude':
250
+ removeClaude();
251
+ break;
252
+ case 'help':
253
+ case '--help':
254
+ case '-h':
255
+ showHelp();
256
+ break;
257
+ case undefined:
258
+ showHelp();
259
+ break;
260
+ default:
261
+ console.error(`Unknown command: ${command}`);
262
+ console.error('Run "npx @ax-llm/ax help" for usage information.');
263
+ process.exit(1);
264
+ }