claude-cli-advanced-starter-pack 1.0.4 → 1.0.7

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.
@@ -0,0 +1,241 @@
1
+ /**
2
+ * CCASP Update Check Hook
3
+ *
4
+ * Automatically checks for npm updates when Claude Code starts.
5
+ * Runs on first UserPromptSubmit per session, caches results for 1 hour.
6
+ *
7
+ * Event: UserPromptSubmit
8
+ */
9
+
10
+ const { execSync } = require('child_process');
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const PACKAGE_NAME = 'claude-cli-advanced-starter-pack';
15
+ const CACHE_DURATION = 60 * 60 * 1000; // 1 hour
16
+ const STATE_FILE = '.claude/config/ccasp-state.json';
17
+ const SESSION_MARKER = '.claude/config/.ccasp-session-checked';
18
+
19
+ /**
20
+ * Load state from file
21
+ */
22
+ function loadState() {
23
+ const statePath = path.join(process.cwd(), STATE_FILE);
24
+
25
+ if (fs.existsSync(statePath)) {
26
+ try {
27
+ return JSON.parse(fs.readFileSync(statePath, 'utf8'));
28
+ } catch {
29
+ // Return default state
30
+ }
31
+ }
32
+
33
+ return {
34
+ lastCheckTimestamp: 0,
35
+ lastCheckResult: null,
36
+ currentVersion: null,
37
+ latestVersion: null,
38
+ updateAvailable: false,
39
+ updateHighlights: [],
40
+ updateFirstDisplayed: false,
41
+ dismissedUntil: 0,
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Save state to file
47
+ */
48
+ function saveState(state) {
49
+ const statePath = path.join(process.cwd(), STATE_FILE);
50
+ const stateDir = path.dirname(statePath);
51
+
52
+ if (!fs.existsSync(stateDir)) {
53
+ fs.mkdirSync(stateDir, { recursive: true });
54
+ }
55
+
56
+ fs.writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8');
57
+ }
58
+
59
+ /**
60
+ * Get installed version
61
+ */
62
+ function getCurrentVersion() {
63
+ try {
64
+ // Try global install first
65
+ const result = execSync(`npm list -g ${PACKAGE_NAME} --json 2>/dev/null`, {
66
+ encoding: 'utf8',
67
+ timeout: 5000,
68
+ });
69
+ const data = JSON.parse(result);
70
+ return data.dependencies?.[PACKAGE_NAME]?.version || null;
71
+ } catch {
72
+ // Try local install
73
+ try {
74
+ const pkgPath = path.join(process.cwd(), 'node_modules', PACKAGE_NAME, 'package.json');
75
+ if (fs.existsSync(pkgPath)) {
76
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
77
+ return pkg.version;
78
+ }
79
+ } catch {
80
+ // Ignore
81
+ }
82
+ }
83
+ return null;
84
+ }
85
+
86
+ /**
87
+ * Check npm for latest version
88
+ */
89
+ function checkLatestVersion() {
90
+ try {
91
+ const result = execSync(`npm view ${PACKAGE_NAME} version`, {
92
+ encoding: 'utf8',
93
+ timeout: 10000,
94
+ stdio: ['pipe', 'pipe', 'pipe'],
95
+ });
96
+ return result.trim();
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Get release highlights from releases.json
104
+ */
105
+ function getReleaseHighlights(fromVersion, toVersion) {
106
+ try {
107
+ // Try to read from global install
108
+ const globalPath = execSync('npm root -g', { encoding: 'utf8', timeout: 5000 }).trim();
109
+ const releasesPath = path.join(globalPath, PACKAGE_NAME, 'src', 'data', 'releases.json');
110
+
111
+ if (fs.existsSync(releasesPath)) {
112
+ const releases = JSON.parse(fs.readFileSync(releasesPath, 'utf8'));
113
+ const highlights = [];
114
+
115
+ for (const release of releases) {
116
+ if (compareVersions(release.version, fromVersion) > 0 &&
117
+ compareVersions(release.version, toVersion) <= 0) {
118
+ highlights.push({
119
+ version: release.version,
120
+ summary: release.summary,
121
+ highlights: release.highlights || [],
122
+ });
123
+ }
124
+ }
125
+
126
+ return highlights;
127
+ }
128
+ } catch {
129
+ // Ignore
130
+ }
131
+ return [];
132
+ }
133
+
134
+ /**
135
+ * Compare semantic versions
136
+ */
137
+ function compareVersions(v1, v2) {
138
+ if (!v1 || !v2) return 0;
139
+ const parts1 = v1.split('.').map(Number);
140
+ const parts2 = v2.split('.').map(Number);
141
+
142
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
143
+ const p1 = parts1[i] || 0;
144
+ const p2 = parts2[i] || 0;
145
+ if (p1 > p2) return 1;
146
+ if (p1 < p2) return -1;
147
+ }
148
+ return 0;
149
+ }
150
+
151
+ /**
152
+ * Check if we've already run this session
153
+ */
154
+ function hasCheckedThisSession() {
155
+ const markerPath = path.join(process.cwd(), SESSION_MARKER);
156
+
157
+ if (fs.existsSync(markerPath)) {
158
+ try {
159
+ const content = fs.readFileSync(markerPath, 'utf8');
160
+ const timestamp = parseInt(content, 10);
161
+ // Consider session valid for 4 hours
162
+ if (Date.now() - timestamp < 4 * 60 * 60 * 1000) {
163
+ return true;
164
+ }
165
+ } catch {
166
+ // Ignore
167
+ }
168
+ }
169
+ return false;
170
+ }
171
+
172
+ /**
173
+ * Mark session as checked
174
+ */
175
+ function markSessionChecked() {
176
+ const markerPath = path.join(process.cwd(), SESSION_MARKER);
177
+ const markerDir = path.dirname(markerPath);
178
+
179
+ if (!fs.existsSync(markerDir)) {
180
+ fs.mkdirSync(markerDir, { recursive: true });
181
+ }
182
+
183
+ fs.writeFileSync(markerPath, Date.now().toString(), 'utf8');
184
+ }
185
+
186
+ /**
187
+ * Main hook handler
188
+ */
189
+ module.exports = async function ccaspUpdateCheck(context) {
190
+ // Only run once per session
191
+ if (hasCheckedThisSession()) {
192
+ return { continue: true };
193
+ }
194
+
195
+ // Mark this session as checked
196
+ markSessionChecked();
197
+
198
+ // Load current state
199
+ const state = loadState();
200
+ const now = Date.now();
201
+
202
+ // Check if cache is still valid
203
+ if (state.lastCheckTimestamp && (now - state.lastCheckTimestamp) < CACHE_DURATION) {
204
+ return { continue: true };
205
+ }
206
+
207
+ // Get current version
208
+ const currentVersion = getCurrentVersion();
209
+ if (!currentVersion) {
210
+ return { continue: true };
211
+ }
212
+
213
+ // Check for latest version
214
+ const latestVersion = checkLatestVersion();
215
+ if (!latestVersion) {
216
+ return { continue: true };
217
+ }
218
+
219
+ // Update state
220
+ state.lastCheckTimestamp = now;
221
+ state.currentVersion = currentVersion;
222
+ state.latestVersion = latestVersion;
223
+ state.updateAvailable = compareVersions(latestVersion, currentVersion) > 0;
224
+
225
+ // Get release highlights if update available
226
+ if (state.updateAvailable) {
227
+ state.updateHighlights = getReleaseHighlights(currentVersion, latestVersion);
228
+ // Reset first displayed flag for new updates
229
+ if (state.lastSeenUpdateVersion !== latestVersion) {
230
+ state.updateFirstDisplayed = false;
231
+ state.lastSeenUpdateVersion = latestVersion;
232
+ }
233
+ } else {
234
+ state.updateHighlights = [];
235
+ }
236
+
237
+ // Save state
238
+ saveState(state);
239
+
240
+ return { continue: true };
241
+ };