@ax-llm/ax 16.0.10 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ax-llm/ax",
3
- "version": "16.0.10",
3
+ "version": "16.0.12",
4
4
  "type": "module",
5
5
  "description": "The best library to work with LLMs",
6
6
  "repository": {
@@ -56,5 +56,11 @@
56
56
  "import": "./*.js",
57
57
  "require": "./*.cjs"
58
58
  }
59
+ },
60
+ "bin": {
61
+ "ax": "./cli/index.mjs"
62
+ },
63
+ "scripts": {
64
+ "postinstall": "node ./scripts/postinstall.mjs"
59
65
  }
60
66
  }
@@ -0,0 +1,209 @@
1
+ #!/usr/bin/env node
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 { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
18
+ import { dirname, join } from 'node:path';
19
+ import { fileURLToPath } from 'node:url';
20
+ import { homedir } from 'node:os';
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = dirname(__filename);
24
+
25
+ // Check if we should skip installation
26
+ function shouldSkip() {
27
+ // Skip in CI environments
28
+ if (process.env.CI === 'true' || process.env.CI === '1') {
29
+ return true;
30
+ }
31
+ if (
32
+ process.env.CONTINUOUS_INTEGRATION === 'true' ||
33
+ process.env.CONTINUOUS_INTEGRATION === '1'
34
+ ) {
35
+ return true;
36
+ }
37
+
38
+ // Skip if explicitly disabled
39
+ if (
40
+ process.env.AX_SKIP_SKILL_INSTALL === '1' ||
41
+ process.env.AX_SKIP_SKILL_INSTALL === 'true'
42
+ ) {
43
+ return true;
44
+ }
45
+
46
+ // Common CI environment variables
47
+ const ciEnvVars = [
48
+ 'GITHUB_ACTIONS',
49
+ 'GITLAB_CI',
50
+ 'CIRCLECI',
51
+ 'TRAVIS',
52
+ 'JENKINS_URL',
53
+ 'BUILDKITE',
54
+ 'DRONE',
55
+ 'TEAMCITY_VERSION',
56
+ 'BITBUCKET_BUILD_NUMBER',
57
+ 'CODEBUILD_BUILD_ID',
58
+ 'TF_BUILD', // Azure Pipelines
59
+ ];
60
+
61
+ for (const envVar of ciEnvVars) {
62
+ if (process.env[envVar]) {
63
+ return true;
64
+ }
65
+ }
66
+
67
+ return false;
68
+ }
69
+
70
+ // Check if we're in an interactive terminal
71
+ function isInteractive() {
72
+ return process.stdout.isTTY === true;
73
+ }
74
+
75
+ /**
76
+ * Compare semver versions
77
+ * Returns: 1 if a > b, -1 if a < b, 0 if equal
78
+ */
79
+ function compareSemver(a, b) {
80
+ const parseVersion = (v) => {
81
+ const match = v.match(/^(\d+)\.(\d+)\.(\d+)/);
82
+ if (!match) return [0, 0, 0];
83
+ return [
84
+ Number.parseInt(match[1], 10),
85
+ Number.parseInt(match[2], 10),
86
+ Number.parseInt(match[3], 10),
87
+ ];
88
+ };
89
+
90
+ const [aMajor, aMinor, aPatch] = parseVersion(a);
91
+ const [bMajor, bMinor, bPatch] = parseVersion(b);
92
+
93
+ if (aMajor !== bMajor) return aMajor > bMajor ? 1 : -1;
94
+ if (aMinor !== bMinor) return aMinor > bMinor ? 1 : -1;
95
+ if (aPatch !== bPatch) return aPatch > bPatch ? 1 : -1;
96
+ return 0;
97
+ }
98
+
99
+ /**
100
+ * Get the installed skill version from the file
101
+ */
102
+ function getInstalledVersion(targetPath) {
103
+ if (!existsSync(targetPath)) {
104
+ return null;
105
+ }
106
+
107
+ try {
108
+ const content = readFileSync(targetPath, 'utf8');
109
+ const match = content.match(/^version:\s*["']?([^"'\n\r]+)/m);
110
+ return match ? match[1].trim() : null;
111
+ } catch {
112
+ return null;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Get the package version from the skill source file
118
+ */
119
+ function getPackageVersion(sourcePath) {
120
+ if (!existsSync(sourcePath)) {
121
+ return null;
122
+ }
123
+
124
+ try {
125
+ const content = readFileSync(sourcePath, 'utf8');
126
+ const match = content.match(/^version:\s*["']?([^"'\n\r]+)/m);
127
+ return match ? match[1].trim() : null;
128
+ } catch {
129
+ return null;
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Main installation function
135
+ */
136
+ function install() {
137
+ // Skip if needed
138
+ if (shouldSkip()) {
139
+ return;
140
+ }
141
+
142
+ try {
143
+ // Paths
144
+ const skillSource = join(__dirname, '..', 'skills', 'ax-llm.md');
145
+ const skillTargetDir = join(homedir(), '.claude', 'skills', 'ax');
146
+ const skillTarget = join(skillTargetDir, 'ax-llm.md');
147
+
148
+ // Check if source exists
149
+ if (!existsSync(skillSource)) {
150
+ // Skill file not found - this can happen during development
151
+ // Silently exit
152
+ return;
153
+ }
154
+
155
+ const packageVersion = getPackageVersion(skillSource);
156
+ const installedVersion = getInstalledVersion(skillTarget);
157
+
158
+ // Determine if we should install
159
+ let shouldInstall = false;
160
+ let action = 'Installed';
161
+ let versionInfo = '';
162
+
163
+ if (!existsSync(skillTarget)) {
164
+ // Fresh install
165
+ shouldInstall = true;
166
+ versionInfo = packageVersion ? ` (v${packageVersion})` : '';
167
+ } else if (installedVersion && packageVersion) {
168
+ // Compare versions
169
+ const comparison = compareSemver(packageVersion, installedVersion);
170
+ if (comparison > 0) {
171
+ // New version is higher - upgrade
172
+ shouldInstall = true;
173
+ action = 'Upgraded';
174
+ versionInfo = ` (v${installedVersion} \u2192 v${packageVersion})`;
175
+ }
176
+ // If same or lower version, don't install
177
+ } else if (!installedVersion && existsSync(skillTarget)) {
178
+ // File exists but no version - upgrade it
179
+ shouldInstall = true;
180
+ action = 'Upgraded';
181
+ versionInfo = packageVersion ? ` (v${packageVersion})` : '';
182
+ }
183
+
184
+ if (!shouldInstall) {
185
+ // Already up to date, silently exit
186
+ return;
187
+ }
188
+
189
+ // Create target directory
190
+ if (!existsSync(skillTargetDir)) {
191
+ mkdirSync(skillTargetDir, { recursive: true });
192
+ }
193
+
194
+ // Copy skill file
195
+ const content = readFileSync(skillSource, 'utf8');
196
+ writeFileSync(skillTarget, content, 'utf8');
197
+
198
+ // Only log in interactive terminals
199
+ if (isInteractive()) {
200
+ console.log(`\u2713 ${action} Ax Claude Code skill${versionInfo}`);
201
+ }
202
+ } catch {
203
+ // Silent failure - never break npm install
204
+ // The CLI command can be used for manual installation
205
+ }
206
+ }
207
+
208
+ // Run installation
209
+ install();