@trendai-crem/claude-skills 0.4.0 → 0.5.1

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 +105 -10
  2. package/package.json +4 -1
package/cli.js CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { execFileSync } from 'child_process';
4
- import { readFileSync, readdirSync } from 'fs';
4
+ import { readFileSync, readdirSync, writeFileSync, copyFileSync, mkdirSync, existsSync } from 'fs';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { dirname, join } from 'path';
7
+ import { homedir, tmpdir } from 'os';
8
+ import { renameSync } from 'fs';
7
9
 
8
10
  const __dir = dirname(fileURLToPath(import.meta.url));
9
11
 
@@ -55,13 +57,106 @@ if (externalFailed.length > 0) {
55
57
  console.warn(`\nWARN: ${externalFailed.length} external source(s) failed — team skills installed successfully.`);
56
58
  }
57
59
 
58
- // Check for updates
59
- try {
60
- const { version: current } = JSON.parse(readFileSync(join(__dir, 'package.json'), 'utf8'));
61
- const res = await fetch('https://registry.npmjs.org/@trendai-crem/claude-skills/latest');
62
- const { version: latest } = await res.json();
63
- if (current !== latest) {
64
- console.log(`\nUpdate available: ${current} → ${latest}`);
65
- console.log(`Run: npx @trendai-crem/claude-skills@latest`);
60
+ // Configure auto-update hook in ~/.claude/settings.json
61
+ setupAutoUpdate();
62
+
63
+ function setupAutoUpdate() {
64
+ const { version: installedVersion } = JSON.parse(
65
+ readFileSync(join(__dir, 'package.json'), 'utf8')
66
+ );
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');
72
+ const settingsPath = join(claudeDir, 'settings.json');
73
+
74
+ // Write the auto-update hook script
75
+ mkdirSync(hooksDir, { recursive: true });
76
+ writeFileSync(hookScript, buildHookScript(stampFile, installedVersion), { mode: 0o755 });
77
+
78
+ // Merge SessionStart hook into ~/.claude/settings.json (idempotent)
79
+ let settings = {};
80
+ if (existsSync(settingsPath)) {
81
+ try {
82
+ settings = JSON.parse(readFileSync(settingsPath, 'utf8'));
83
+ } catch (err) {
84
+ // Back up corrupt file rather than silently overwriting
85
+ const backup = `${settingsPath}.bak.${Date.now()}`;
86
+ copyFileSync(settingsPath, backup);
87
+ console.warn(`\nWARN: ~/.claude/settings.json has invalid JSON — backed up to ${backup}`);
88
+ console.warn('A fresh settings file will be created with the auto-update hook only.');
89
+ console.warn('Manually merge your settings from the backup if needed.\n');
90
+ }
66
91
  }
67
- } catch { /* ignore update check failures */ }
92
+
93
+ settings.hooks ??= {};
94
+ settings.hooks.SessionStart ??= [];
95
+ settings.hooks.SessionStart = settings.hooks.SessionStart.filter(
96
+ e => !e.hooks?.some(h => typeof h.command === 'string' && h.command === hookScript)
97
+ );
98
+ settings.hooks.SessionStart.push({
99
+ hooks: [{ type: 'command', command: hookScript }]
100
+ });
101
+
102
+ // Atomic write — prevents corruption if process is killed mid-write
103
+ const tmpPath = join(tmpdir(), `claude-settings-${process.pid}.json`);
104
+ writeFileSync(tmpPath, JSON.stringify(settings, null, 2) + '\n');
105
+ renameSync(tmpPath, settingsPath);
106
+
107
+ console.log('\n✓ Auto-update configured (runs daily at session start)');
108
+ }
109
+
110
+ function buildHookScript(stampFile, installedVersion) {
111
+ // Validate installedVersion is semver before embedding in bash
112
+ if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(installedVersion)) {
113
+ throw new Error(`Invalid version format in package.json: ${installedVersion}`);
114
+ }
115
+
116
+ return `#!/usr/bin/env bash
117
+ # Auto-update @trendai-crem/claude-skills at Claude Code session start (max once per 24h).
118
+ set -euo pipefail
119
+
120
+ STAMP="${stampFile}"
121
+ PACKAGE="@trendai-crem/claude-skills"
122
+ MIN_INTERVAL=$((60 * 60 * 24))
123
+ LOG="$(dirname "$STAMP")/claude-skills-update.log"
124
+
125
+ mkdir -p "$(dirname "$STAMP")"
126
+
127
+ # Read stamp using sys.argv to avoid shell injection
128
+ LAST_TS=0
129
+ CURRENT_VER="${installedVersion}"
130
+ if [ -f "$STAMP" ]; then
131
+ LAST_TS=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('ts',0))" "$STAMP" 2>/dev/null || echo 0)
132
+ 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}")
133
+ fi
134
+
135
+ # Guard against corrupt stamp values
136
+ [[ "$LAST_TS" =~ ^[0-9]+$ ]] || LAST_TS=0
137
+
138
+ NOW=$(date +%s)
139
+ [ $(( NOW - LAST_TS )) -lt $MIN_INTERVAL ] && exit 0
140
+
141
+ # Write timestamp now to prevent parallel session runs
142
+ 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"
143
+
144
+ LATEST=$(npm view "$PACKAGE" version 2>/dev/null || echo "")
145
+ { [ -z "$LATEST" ] || [ "$LATEST" = "$CURRENT_VER" ]; } && exit 0
146
+
147
+ # Validate LATEST is semver before executing
148
+ [[ "$LATEST" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]] || { echo "[$(date -Iseconds)] Invalid version from npm: $LATEST" >> "$LOG"; exit 1; }
149
+
150
+ # Update in background — stamp written only on success
151
+ echo "🔄 Auto-updating claude-skills: $CURRENT_VER → $LATEST"
152
+ (
153
+ 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"
156
+ echo "[$(date -Iseconds)] Update successful" >> "$LOG"
157
+ else
158
+ echo "[$(date -Iseconds)] Update failed — will retry next session" >> "$LOG"
159
+ fi
160
+ ) &
161
+ `;
162
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trendai-crem/claude-skills",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Claude Code skills installer for the trendai-crem team",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {
@@ -18,6 +18,9 @@
18
18
  "engines": {
19
19
  "node": ">=20"
20
20
  },
21
+ "scripts": {
22
+ "prepare": "git config core.hooksPath .githooks"
23
+ },
21
24
  "dependencies": {
22
25
  "skills": "1.4.5"
23
26
  }