@trendai-crem/claude-skills 0.6.3 → 0.7.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 +36 -134
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -57,7 +57,7 @@ if (externalFailed.length > 0) {
57
57
  console.warn(`\nWARN: ${externalFailed.length} external source(s) failed — team skills installed successfully.`);
58
58
  }
59
59
 
60
- // Configure auto-update hook in ~/.claude/settings.json
60
+ // Configure update-check hooks
61
61
  setupAutoUpdate();
62
62
 
63
63
  function setupAutoUpdate() {
@@ -65,34 +65,31 @@ function setupAutoUpdate() {
65
65
  readFileSync(join(__dir, 'package.json'), 'utf8')
66
66
  );
67
67
 
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');
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-version-check.json');
74
72
  const settingsPath = join(claudeDir, 'settings.json');
75
73
 
76
- // Write the auto-update hook script
77
74
  mkdirSync(hooksDir, { recursive: true });
75
+ mkdirSync(join(homedir(), '.cache'), { recursive: true });
78
76
  writeFileSync(hookScript, buildHookScript(stampFile, installedVersion), { mode: 0o755 });
79
77
 
80
- // Merge SessionStart hook into ~/.claude/settings.json (idempotent)
78
+ // Merge hooks into ~/.claude/settings.json (idempotent)
81
79
  let settings = {};
82
80
  if (existsSync(settingsPath)) {
83
81
  try {
84
82
  settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
85
83
  } catch (err) {
86
- // Back up corrupt file rather than silently overwriting
87
84
  const backup = `${settingsPath}.bak.${Date.now()}`;
88
85
  copyFileSync(settingsPath, backup);
89
- console.warn(`\nWARN: ~/.claude/settings.json has invalid JSON — backed up to ${backup}`);
90
- console.warn('A fresh settings file will be created with the auto-update hook only.');
91
- console.warn('Manually merge your settings from the backup if needed.\n');
86
+ console.warn(`\nWARN: ~/.claude/settings.json has invalid JSON — backed up to ${backup}\n`);
92
87
  }
93
88
  }
94
89
 
95
90
  settings.hooks ??= {};
91
+
92
+ // SessionStart — check once at session start
96
93
  settings.hooks.SessionStart ??= [];
97
94
  settings.hooks.SessionStart = settings.hooks.SessionStart.filter(
98
95
  e => !e.hooks?.some(h => typeof h.command === 'string' && h.command === hookScript)
@@ -101,158 +98,63 @@ function setupAutoUpdate() {
101
98
  hooks: [{ type: 'command', command: hookScript }]
102
99
  });
103
100
 
104
- // Atomic write prevents corruption if process is killed mid-write
101
+ // UserPromptSubmitperiodic in-session check (throttled by stamp file)
102
+ settings.hooks.UserPromptSubmit ??= [];
103
+ settings.hooks.UserPromptSubmit = settings.hooks.UserPromptSubmit.filter(
104
+ e => !e.hooks?.some(h => typeof h.command === 'string' && h.command === hookScript)
105
+ );
106
+ settings.hooks.UserPromptSubmit.push({
107
+ hooks: [{ type: 'command', command: hookScript, async: true }]
108
+ });
109
+
110
+ // Atomic write
105
111
  const tmpPath = join(tmpdir(), `claude-settings-${process.pid}.json`);
106
112
  writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\n');
107
113
  renameSync(tmpPath, settingsPath);
108
114
 
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})`);
115
+ console.log('\n✓ Update-check hooks configured (SessionStart + UserPromptSubmit)');
148
116
  }
149
117
 
150
118
  function buildHookScript(stampFile, installedVersion) {
151
- // Validate installedVersion is semver before embedding in bash
152
119
  if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(installedVersion)) {
153
120
  throw new Error(`Invalid version format in package.json: ${installedVersion}`);
154
121
  }
155
122
 
156
- const notifyFile = stampFile.replace('.json', '-pending-notification.txt');
157
-
158
123
  return `#!/usr/bin/env bash
159
- # Auto-update @trendai-crem/claude-skills — runs at session start and via launchd (macOS).
124
+ # claude-skills update check — runs at session start and periodically during session.
125
+ # If a newer version is available, outputs a systemMessage notification (no auto-install).
160
126
  set -euo pipefail
161
127
 
162
128
  STAMP="${stampFile}"
163
- NOTIFY="${notifyFile}"
164
129
  PACKAGE="@trendai-crem/claude-skills"
165
- MIN_INTERVAL=$((60 * 60 * 24))
166
- LOG="$(dirname "$STAMP")/claude-skills-update.log"
130
+ INSTALLED="${installedVersion}"
131
+ MIN_INTERVAL=$((60 * 60 * 2)) # check at most every 2h
167
132
 
168
133
  mkdir -p "$(dirname "$STAMP")"
169
134
 
170
- # Show pending update notification from previous background update
171
- if [ -f "$NOTIFY" ]; then
172
- cat "$NOTIFY"
173
- rm -f "$NOTIFY"
174
- fi
175
-
176
- # Read stamp using sys.argv to avoid shell injection
135
+ # Throttle: skip if checked recently
177
136
  LAST_TS=0
178
- CURRENT_VER="${installedVersion}"
179
137
  if [ -f "$STAMP" ]; then
180
138
  LAST_TS=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('ts',0))" "$STAMP" 2>/dev/null || echo 0)
181
- CURRENT_VER=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('version',sys.argv[2]))" "$STAMP" "${installedVersion}" 2>/dev/null || echo "${installedVersion}")
182
139
  fi
183
-
184
- # Guard against corrupt stamp values
185
140
  [[ "$LAST_TS" =~ ^[0-9]+$ ]] || LAST_TS=0
186
141
 
187
142
  NOW=$(date +%s)
188
143
  [ $(( NOW - LAST_TS )) -lt $MIN_INTERVAL ] && exit 0
189
144
 
190
- # Write timestamp now to prevent parallel session runs
191
- python3 -c "import json,sys; json.dump({'ts': int(sys.argv[1]), 'version': sys.argv[2]}, open(sys.argv[3],'w'))" "$NOW" "$CURRENT_VER" "$STAMP"
145
+ # Update timestamp
146
+ python3 -c "import json,sys; json.dump({'ts': int(sys.argv[1])}, open(sys.argv[2],'w'))" "$NOW" "$STAMP"
192
147
 
148
+ # Check latest version on npm
193
149
  LATEST=$(npm view "$PACKAGE" version 2>/dev/null || echo "")
194
- { [ -z "$LATEST" ] || [ "$LATEST" = "$CURRENT_VER" ]; } && exit 0
195
-
196
- # Validate LATEST is semver before executing
197
- [[ "$LATEST" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]] || { echo "[$(date -Iseconds)] Invalid version from npm: $LATEST" >> "$LOG"; exit 1; }
198
-
199
- # Update in background — use nohup+disown so launchd doesn't kill child on job exit
200
- echo "🔄 Updating claude-skills: $CURRENT_VER → $LATEST (background)"
201
- echo "[$(date -Iseconds)] Updating $CURRENT_VER → $LATEST" >> "$LOG"
202
- STAMP_V="$STAMP" NOTIFY_V="$NOTIFY" LOG_V="$LOG" NOW_V="$NOW" \
203
- PKG_V="\${PACKAGE}" LATEST_V="\${LATEST}" OLD_V="$CURRENT_VER" \
204
- nohup bash -s << 'UPDATER' >> "$LOG" 2>&1 &
205
- INSTALL_DIR="$(mktemp -d)"
206
- if npm install --prefix "$INSTALL_DIR" "$PKG_V@$LATEST_V" --silent \
207
- && node "$INSTALL_DIR/node_modules/$PKG_V/cli.js"; then
208
- rm -rf "$INSTALL_DIR"
209
- python3 -c "import json,sys; json.dump({'ts': int(sys.argv[1]), 'version': sys.argv[2]}, open(sys.argv[3],'w'))" "$NOW_V" "$LATEST_V" "$STAMP_V"
210
- echo "[$(date -Iseconds)] Update successful"
211
- echo "✓ claude-skills updated: $OLD_V → $LATEST_V" > "$NOTIFY_V"
212
- osascript -e "display notification \"Updated to $LATEST_V\" with title \"claude-skills\"" 2>/dev/null || true
213
- else
214
- rm -rf "$INSTALL_DIR"
215
- echo "[$(date -Iseconds)] Update failed — will retry next session"
216
- fi
217
- UPDATER
218
- disown
219
- `;
220
- }
150
+ [ -z "$LATEST" ] && exit 0
151
+ [ "$LATEST" = "$INSTALLED" ] && exit 0
221
152
 
222
- function escapeXml(value) {
223
- return value
224
- .replaceAll('&', '&amp;')
225
- .replaceAll('<', '&lt;')
226
- .replaceAll('>', '&gt;')
227
- .replaceAll('"', '&quot;')
228
- .replaceAll("'", '&apos;');
229
- }
153
+ # Validate semver
154
+ [[ "$LATEST" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]] || exit 0
230
155
 
231
- function buildLaunchAgentPlist(hookScript, logPath, envPath) {
232
- return `<?xml version="1.0" encoding="UTF-8"?>
233
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
234
- <plist version="1.0">
235
- <dict>
236
- <key>Label</key>
237
- <string>com.trendai.claude-skills-updater</string>
238
- <key>ProgramArguments</key>
239
- <array>
240
- <string>${escapeXml(hookScript)}</string>
241
- </array>
242
- <key>EnvironmentVariables</key>
243
- <dict>
244
- <key>PATH</key>
245
- <string>${escapeXml(envPath)}</string>
246
- </dict>
247
- <key>RunAtLoad</key>
248
- <false/>
249
- <key>StartInterval</key>
250
- <integer>86400</integer>
251
- <key>StandardOutPath</key>
252
- <string>${escapeXml(logPath)}</string>
253
- <key>StandardErrorPath</key>
254
- <string>${escapeXml(logPath)}</string>
255
- </dict>
256
- </plist>
156
+ # Notify via Claude Code systemMessage (shows in UI)
157
+ printf '{"systemMessage": "claude-skills update available: %s → %s\\nRun: npx @trendai-crem/claude-skills@latest"}' \
158
+ "$INSTALLED" "$LATEST"
257
159
  `;
258
160
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trendai-crem/claude-skills",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "Claude Code skills installer for the trendai-crem team",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {