@trendai-crem/claude-skills 0.5.2 → 0.6.0

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.
Files changed (2) hide show
  1. package/cli.js +101 -10
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -65,10 +65,12 @@ function setupAutoUpdate() {
65
65
  readFileSync(join(__dir, 'package.json'), 'utf8')
66
66
  );
67
67
 
68
- const claudeDir = join(homedir(), '.claude');
69
- const hooksDir = join(claudeDir, 'hooks');
70
- const hookScript = join(hooksDir, 'auto-update-claude-skills.sh');
71
- const stampFile = join(homedir(), '.cache', 'claude-skills-update.json');
68
+ const homeDir = homedir();
69
+ const claudeDir = join(homeDir, '.claude');
70
+ const hooksDir = join(claudeDir, 'hooks');
71
+ const cacheDir = join(homeDir, '.cache');
72
+ const hookScript = join(hooksDir, 'auto-update-claude-skills.sh');
73
+ const stampFile = join(cacheDir, 'claude-skills-update.json');
72
74
  const settingsPath = join(claudeDir, 'settings.json');
73
75
 
74
76
  // Write the auto-update hook script
@@ -104,7 +106,45 @@ function setupAutoUpdate() {
104
106
  writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\n');
105
107
  renameSync(tmpPath, settingsPath);
106
108
 
107
- console.log('\n✓ Auto-update configured (runs daily at session start)');
109
+ if (process.platform === 'darwin') {
110
+ const launchAgentsDir = join(homeDir, 'Library', 'LaunchAgents');
111
+ const launchdPlistPath = join(
112
+ launchAgentsDir,
113
+ 'com.trendai.claude-skills-updater.plist'
114
+ );
115
+ const launchdLogPath = join(cacheDir, 'claude-skills-launchd.log');
116
+ const plistTmpPath = join(tmpdir(), `claude-skills-launchd-${process.pid}.plist`);
117
+
118
+ mkdirSync(cacheDir, { recursive: true });
119
+ mkdirSync(launchAgentsDir, { recursive: true });
120
+
121
+ // Unload existing agent before writing new plist — ensures reinstall applies updated config
122
+ try {
123
+ execFileSync('launchctl', ['unload', launchdPlistPath], { stdio: 'pipe' });
124
+ } catch (_) {
125
+ // Not yet loaded — expected on first install
126
+ }
127
+
128
+ // Bake current PATH into plist so npm/npx are findable in launchd's stripped environment
129
+ const launchdPath = process.env.PATH ?? '/usr/local/bin:/usr/bin:/bin';
130
+ writeFileSync(
131
+ plistTmpPath,
132
+ buildLaunchAgentPlist(hookScript, launchdLogPath, launchdPath)
133
+ );
134
+ renameSync(plistTmpPath, launchdPlistPath);
135
+
136
+ try {
137
+ execFileSync('launchctl', ['load', '-w', launchdPlistPath], { stdio: 'pipe' });
138
+ } catch (error) {
139
+ const details = error.stderr?.toString().trim() || error.stdout?.toString().trim();
140
+ console.warn(`\nWARN: launchctl load failed — agent may need manual activation.${details ? ` ${details}` : ''}`);
141
+ }
142
+ }
143
+
144
+ const schedule = process.platform === 'darwin'
145
+ ? 'every 24h via launchd and at session start'
146
+ : 'daily at session start';
147
+ console.log(`\n✓ Auto-update configured (runs ${schedule})`);
108
148
  }
109
149
 
110
150
  function buildHookScript(stampFile, installedVersion) {
@@ -113,17 +153,26 @@ function buildHookScript(stampFile, installedVersion) {
113
153
  throw new Error(`Invalid version format in package.json: ${installedVersion}`);
114
154
  }
115
155
 
156
+ const notifyFile = stampFile.replace('.json', '-pending-notification.txt');
157
+
116
158
  return `#!/usr/bin/env bash
117
- # Auto-update @trendai-crem/claude-skills at Claude Code session start (max once per 24h).
159
+ # Auto-update @trendai-crem/claude-skills runs at session start and via launchd (macOS).
118
160
  set -euo pipefail
119
161
 
120
162
  STAMP="${stampFile}"
163
+ NOTIFY="${notifyFile}"
121
164
  PACKAGE="@trendai-crem/claude-skills"
122
165
  MIN_INTERVAL=$((60 * 60 * 24))
123
166
  LOG="$(dirname "$STAMP")/claude-skills-update.log"
124
167
 
125
168
  mkdir -p "$(dirname "$STAMP")"
126
169
 
170
+ # Show pending update notification from previous background update
171
+ if [ -f "$NOTIFY" ]; then
172
+ cat "$NOTIFY"
173
+ rm -f "$NOTIFY"
174
+ fi
175
+
127
176
  # Read stamp using sys.argv to avoid shell injection
128
177
  LAST_TS=0
129
178
  CURRENT_VER="${installedVersion}"
@@ -147,16 +196,58 @@ LATEST=$(npm view "$PACKAGE" version 2>/dev/null || echo "")
147
196
  # Validate LATEST is semver before executing
148
197
  [[ "$LATEST" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]] || { echo "[$(date -Iseconds)] Invalid version from npm: $LATEST" >> "$LOG"; exit 1; }
149
198
 
150
- # Update in background — stamp written only on success
151
- echo "🔄 Auto-updating claude-skills: $CURRENT_VER → $LATEST"
199
+ # Update in background — notification and stamp written only on success
200
+ echo "🔄 Updating claude-skills: $CURRENT_VER → $LATEST (background)"
152
201
  (
153
202
  echo "[$(date -Iseconds)] Updating $CURRENT_VER → $LATEST" >> "$LOG"
154
- if npx --yes "${PACKAGE}@${LATEST}" >> "$LOG" 2>&1; then
155
- python3 -c "import json,sys; json.dump({'ts': int(sys.argv[1]), 'version': sys.argv[2]}, open(sys.argv[3],'w'))" "$NOW" "$LATEST" "$STAMP"
203
+ if npx --yes "\${PACKAGE}@\${LATEST}" >> "$LOG" 2>&1; then
204
+ python3 -c "import json,sys; json.dump({'ts': int(sys.argv[1]), 'version': sys.argv[2]}, open(sys.argv[3],'w'))" "$NOW" "\${LATEST}" "$STAMP"
156
205
  echo "[$(date -Iseconds)] Update successful" >> "$LOG"
206
+ # Write pending notification for next session start
207
+ echo "✓ claude-skills updated: $CURRENT_VER → \${LATEST}" > "$NOTIFY"
208
+ # macOS system notification (via launchd or background)
209
+ osascript -e "display notification \"Updated to \${LATEST}\" with title \"claude-skills\"" 2>/dev/null || true
157
210
  else
158
211
  echo "[$(date -Iseconds)] Update failed — will retry next session" >> "$LOG"
159
212
  fi
160
213
  ) &
161
214
  `;
162
215
  }
216
+
217
+ function escapeXml(value) {
218
+ return value
219
+ .replaceAll('&', '&')
220
+ .replaceAll('<', '&lt;')
221
+ .replaceAll('>', '&gt;')
222
+ .replaceAll('"', '&quot;')
223
+ .replaceAll("'", '&apos;');
224
+ }
225
+
226
+ function buildLaunchAgentPlist(hookScript, logPath, envPath) {
227
+ return `<?xml version="1.0" encoding="UTF-8"?>
228
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
229
+ <plist version="1.0">
230
+ <dict>
231
+ <key>Label</key>
232
+ <string>com.trendai.claude-skills-updater</string>
233
+ <key>ProgramArguments</key>
234
+ <array>
235
+ <string>${escapeXml(hookScript)}</string>
236
+ </array>
237
+ <key>EnvironmentVariables</key>
238
+ <dict>
239
+ <key>PATH</key>
240
+ <string>${escapeXml(envPath)}</string>
241
+ </dict>
242
+ <key>RunAtLoad</key>
243
+ <false/>
244
+ <key>StartInterval</key>
245
+ <integer>86400</integer>
246
+ <key>StandardOutPath</key>
247
+ <string>${escapeXml(logPath)}</string>
248
+ <key>StandardErrorPath</key>
249
+ <string>${escapeXml(logPath)}</string>
250
+ </dict>
251
+ </plist>
252
+ `;
253
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trendai-crem/claude-skills",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Claude Code skills installer for the trendai-crem team",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {