@ax-llm/ax 16.0.11 → 16.0.12

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