@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.
- package/cli.js +36 -134
- 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
|
|
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
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
const
|
|
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
|
|
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
|
-
//
|
|
101
|
+
// UserPromptSubmit — periodic 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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
166
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
191
|
-
python3 -c "import json,sys; json.dump({'ts': int(sys.argv[1])
|
|
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
|
-
|
|
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
|
-
|
|
223
|
-
|
|
224
|
-
.replaceAll('&', '&')
|
|
225
|
-
.replaceAll('<', '<')
|
|
226
|
-
.replaceAll('>', '>')
|
|
227
|
-
.replaceAll('"', '"')
|
|
228
|
-
.replaceAll("'", ''');
|
|
229
|
-
}
|
|
153
|
+
# Validate semver
|
|
154
|
+
[[ "$LATEST" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]] || exit 0
|
|
230
155
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
}
|