@unitsvc/cc-helper 1.0.6 → 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.
Files changed (3) hide show
  1. package/bin/cc-helper.js +124 -44
  2. package/install.js +164 -44
  3. package/package.json +1 -1
package/bin/cc-helper.js CHANGED
@@ -30,15 +30,99 @@ function handleUninstall() {
30
30
  }
31
31
  }
32
32
 
33
- // Read version from package.json
34
- function getPackageVersion() {
35
- try {
36
- const packagePath = path.join(__dirname, '..', 'package.json');
37
- const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
38
- return pkg.version;
39
- } catch (err) {
40
- throw new Error('Cannot read package version: ' + err.message);
33
+ // Get latest release version from GitHub API
34
+ function getLatestVersion() {
35
+ return new Promise((resolve, reject) => {
36
+ const options = {
37
+ hostname: 'api.github.com',
38
+ path: `/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest`,
39
+ headers: {
40
+ 'User-Agent': 'cc-helper-npm-installer',
41
+ 'Accept': 'application/vnd.github.v3+json'
42
+ }
43
+ };
44
+
45
+ const req = https.get(options, (res) => {
46
+ let data = '';
47
+ res.on('data', chunk => data += chunk);
48
+ res.on('end', () => {
49
+ try {
50
+ if (res.statusCode !== 200) {
51
+ reject(new Error(`GitHub API returned status ${res.statusCode}`));
52
+ return;
53
+ }
54
+ const release = JSON.parse(data);
55
+ const version = release.tag_name.replace(/^v/, '');
56
+ resolve(version);
57
+ } catch (err) {
58
+ reject(new Error(`Failed to parse GitHub response: ${err.message}`));
59
+ }
60
+ });
61
+ });
62
+
63
+ req.on('error', reject);
64
+ req.setTimeout(10000, () => {
65
+ req.destroy();
66
+ reject(new Error('GitHub API request timeout'));
67
+ });
68
+ });
69
+ }
70
+
71
+ // Get all releases from GitHub API
72
+ function getAllReleases() {
73
+ return new Promise((resolve, reject) => {
74
+ const options = {
75
+ hostname: 'api.github.com',
76
+ path: `/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases?per_page=10`,
77
+ headers: {
78
+ 'User-Agent': 'cc-helper-npm-installer',
79
+ 'Accept': 'application/vnd.github.v3+json'
80
+ }
81
+ };
82
+
83
+ const req = https.get(options, (res) => {
84
+ let data = '';
85
+ res.on('data', chunk => data += chunk);
86
+ res.on('end', () => {
87
+ try {
88
+ if (res.statusCode !== 200) {
89
+ reject(new Error(`GitHub API returned status ${res.statusCode}`));
90
+ return;
91
+ }
92
+ const releases = JSON.parse(data);
93
+ resolve(releases);
94
+ } catch (err) {
95
+ reject(new Error(`Failed to parse GitHub response: ${err.message}`));
96
+ }
97
+ });
98
+ });
99
+
100
+ req.on('error', reject);
101
+ req.setTimeout(10000, () => {
102
+ req.destroy();
103
+ reject(new Error('GitHub API request timeout'));
104
+ });
105
+ });
106
+ }
107
+
108
+ // Find a release with binary assets for the given platform
109
+ async function findReleaseWithBinary(platform, arch) {
110
+ const releases = await getAllReleases();
111
+
112
+ for (const release of releases) {
113
+ const version = release.tag_name.replace(/^v/, '');
114
+ const expectedAsset = platform === 'windows'
115
+ ? `cc-helper_${version}_${platform}_${arch}.zip`
116
+ : `cc-helper_${version}_${platform}_${arch}.tar.gz`;
117
+
118
+ const hasBinary = release.assets && release.assets.some(asset => asset.name === expectedAsset);
119
+
120
+ if (hasBinary) {
121
+ return version;
122
+ }
41
123
  }
124
+
125
+ throw new Error('No release with binary assets found');
42
126
  }
43
127
 
44
128
  function getPlatform() {
@@ -152,17 +236,10 @@ function extractZip(zipPath, destDir) {
152
236
  try {
153
237
  if (process.platform === 'win32') {
154
238
  // Use PowerShell on Windows (built-in, no external dependencies)
155
- // -NoProfile: Skip loading user profile scripts (faster, more reliable)
156
- // -ExecutionPolicy Bypass: Allow script execution without policy restrictions
157
- // -Command: Execute inline command
158
- const psCommand = `
159
- try {
160
- Expand-Archive -Path '${zipPath.replace(/'/g, "''")}' -DestinationPath '${destDir.replace(/'/g, "''")}' -Force -ErrorAction Stop
161
- exit 0
162
- } catch {
163
- exit 1
164
- }
165
- `;
239
+ // Use single-line command to avoid PowerShell multi-line parsing issues
240
+ const escapedZipPath = zipPath.replace(/'/g, "''");
241
+ const escapedDestDir = destDir.replace(/'/g, "''");
242
+ const psCommand = `Expand-Archive -Path '${escapedZipPath}' -DestinationPath '${escapedDestDir}' -Force -ErrorAction Stop`;
166
243
  execSync(`powershell -NoProfile -ExecutionPolicy Bypass -Command "${psCommand}"`, { stdio: 'pipe' });
167
244
  } else {
168
245
  // Unix/Linux/macOS - use unzip
@@ -220,32 +297,13 @@ function findBinary(dir, platform) {
220
297
  throw new Error(`Binary ${binaryName} not found in archive`);
221
298
  }
222
299
 
223
- async function ensureBinary() {
224
- const binaryPath = getBinaryPath();
225
- const packageVersion = getPackageVersion();
226
-
227
- // Check if binary exists and matches package version
228
- if (fs.existsSync(binaryPath) && fs.existsSync(VERSION_FILE)) {
229
- const currentVersion = fs.readFileSync(VERSION_FILE, 'utf8').trim();
230
- if (currentVersion === packageVersion) {
231
- return binaryPath;
232
- }
233
- console.log(`Updating cc-helper from ${currentVersion} to ${packageVersion}...`);
234
- }
235
-
236
- // Create cache directory
237
- if (!fs.existsSync(CACHE_DIR)) {
238
- fs.mkdirSync(CACHE_DIR, { recursive: true });
239
- }
240
-
241
- // Download archive with fixed version from package.json
242
- const { platform, arch } = getPlatform();
243
- const archiveName = getArchiveName(packageVersion, platform, arch);
244
- const tagVersion = packageVersion.startsWith('v') ? packageVersion : `v${packageVersion}`;
300
+ async function downloadAndInstall(version, platform, arch, binaryPath) {
301
+ const archiveName = getArchiveName(version, platform, arch);
302
+ const tagVersion = version.startsWith('v') ? version : `v${version}`;
245
303
  const downloadUrl = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download/${tagVersion}/${archiveName}`;
246
304
  const archivePath = path.join(CACHE_DIR, archiveName);
247
305
 
248
- console.log(`Downloading cc-helper ${packageVersion} for ${platform}-${arch}...`);
306
+ console.log(`Downloading cc-helper ${version} for ${platform}-${arch}...`);
249
307
 
250
308
  try {
251
309
  await downloadFile(downloadUrl, archivePath);
@@ -274,7 +332,7 @@ async function ensureBinary() {
274
332
  fs.unlinkSync(archivePath);
275
333
 
276
334
  // Save version
277
- fs.writeFileSync(VERSION_FILE, packageVersion);
335
+ fs.writeFileSync(VERSION_FILE, version);
278
336
 
279
337
  console.log('Download complete!');
280
338
  } catch (err) {
@@ -284,6 +342,28 @@ async function ensureBinary() {
284
342
  }
285
343
  throw err;
286
344
  }
345
+ }
346
+
347
+ async function ensureBinary() {
348
+ const binaryPath = getBinaryPath();
349
+
350
+ // If binary exists, use it directly (postinstall already handled version check)
351
+ if (fs.existsSync(binaryPath)) {
352
+ return binaryPath;
353
+ }
354
+
355
+ // Binary doesn't exist, find a compatible release and download
356
+ console.log('Finding compatible release...');
357
+ const { platform, arch } = getPlatform();
358
+ const compatibleVersion = await findReleaseWithBinary(platform, arch);
359
+
360
+ // Create cache directory
361
+ if (!fs.existsSync(CACHE_DIR)) {
362
+ fs.mkdirSync(CACHE_DIR, { recursive: true });
363
+ }
364
+
365
+ // Download and install
366
+ await downloadAndInstall(compatibleVersion, platform, arch, binaryPath);
287
367
 
288
368
  return binaryPath;
289
369
  }
@@ -323,7 +403,7 @@ async function main() {
323
403
  }
324
404
 
325
405
  // Export for use as module
326
- module.exports = { main, ensureBinary, getBinaryPath, getPlatform, getPackageVersion };
406
+ module.exports = { main, ensureBinary, getBinaryPath, getPlatform, getLatestVersion };
327
407
 
328
408
  // Run if called directly
329
409
  if (require.main === module) {
package/install.js CHANGED
@@ -18,10 +18,110 @@ function getPackageVersion() {
18
18
  const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
19
19
  return pkg.version;
20
20
  } catch (err) {
21
- throw new Error('Cannot read package version: ' + err.message);
21
+ return null;
22
22
  }
23
23
  }
24
24
 
25
+ // Get latest release version from GitHub API
26
+ function getLatestVersion() {
27
+ return new Promise((resolve, reject) => {
28
+ const options = {
29
+ hostname: 'api.github.com',
30
+ path: `/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases/latest`,
31
+ headers: {
32
+ 'User-Agent': 'cc-helper-npm-installer',
33
+ 'Accept': 'application/vnd.github.v3+json'
34
+ }
35
+ };
36
+
37
+ const req = https.get(options, (res) => {
38
+ let data = '';
39
+ res.on('data', chunk => data += chunk);
40
+ res.on('end', () => {
41
+ try {
42
+ if (res.statusCode !== 200) {
43
+ reject(new Error(`GitHub API returned status ${res.statusCode}`));
44
+ return;
45
+ }
46
+ const release = JSON.parse(data);
47
+ const version = release.tag_name.replace(/^v/, '');
48
+ resolve(version);
49
+ } catch (err) {
50
+ reject(new Error(`Failed to parse GitHub response: ${err.message}`));
51
+ }
52
+ });
53
+ });
54
+
55
+ req.on('error', reject);
56
+ req.setTimeout(10000, () => {
57
+ req.destroy();
58
+ reject(new Error('GitHub API request timeout'));
59
+ });
60
+ });
61
+ }
62
+
63
+ // Get all releases from GitHub API
64
+ function getAllReleases() {
65
+ return new Promise((resolve, reject) => {
66
+ const options = {
67
+ hostname: 'api.github.com',
68
+ path: `/repos/${GITHUB_OWNER}/${GITHUB_REPO}/releases?per_page=10`,
69
+ headers: {
70
+ 'User-Agent': 'cc-helper-npm-installer',
71
+ 'Accept': 'application/vnd.github.v3+json'
72
+ }
73
+ };
74
+
75
+ const req = https.get(options, (res) => {
76
+ let data = '';
77
+ res.on('data', chunk => data += chunk);
78
+ res.on('end', () => {
79
+ try {
80
+ if (res.statusCode !== 200) {
81
+ reject(new Error(`GitHub API returned status ${res.statusCode}`));
82
+ return;
83
+ }
84
+ const releases = JSON.parse(data);
85
+ resolve(releases);
86
+ } catch (err) {
87
+ reject(new Error(`Failed to parse GitHub response: ${err.message}`));
88
+ }
89
+ });
90
+ });
91
+
92
+ req.on('error', reject);
93
+ req.setTimeout(10000, () => {
94
+ req.destroy();
95
+ reject(new Error('GitHub API request timeout'));
96
+ });
97
+ });
98
+ }
99
+
100
+ // Find a release with binary assets for the given platform
101
+ async function findReleaseWithBinary(platform, arch) {
102
+ const releases = await getAllReleases();
103
+ const expectedAssetPattern = platform === 'windows'
104
+ ? `cc-helper_${arch}.zip`
105
+ : `cc-helper_${arch}.tar.gz`;
106
+
107
+ for (const release of releases) {
108
+ const version = release.tag_name.replace(/^v/, '');
109
+ const expectedAsset = platform === 'windows'
110
+ ? `cc-helper_${version}_${platform}_${arch}.zip`
111
+ : `cc-helper_${version}_${platform}_${arch}.tar.gz`;
112
+
113
+ const hasBinary = release.assets && release.assets.some(asset =>
114
+ asset.name === expectedAsset || asset.name.includes(expectedAssetPattern)
115
+ );
116
+
117
+ if (hasBinary) {
118
+ return version;
119
+ }
120
+ }
121
+
122
+ throw new Error('No release with binary assets found');
123
+ }
124
+
25
125
  function getPlatform() {
26
126
  const platform = os.platform();
27
127
  const arch = os.arch();
@@ -125,14 +225,10 @@ function extractTarGz(tarGzPath, destDir) {
125
225
  function extractZip(zipPath, destDir) {
126
226
  try {
127
227
  if (process.platform === 'win32') {
128
- const psCommand = `
129
- try {
130
- Expand-Archive -Path '${zipPath.replace(/'/g, "''")}' -DestinationPath '${destDir.replace(/'/g, "''")}' -Force -ErrorAction Stop
131
- exit 0
132
- } catch {
133
- exit 1
134
- }
135
- `;
228
+ // Use single-line command to avoid PowerShell multi-line parsing issues
229
+ const escapedZipPath = zipPath.replace(/'/g, "''");
230
+ const escapedDestDir = destDir.replace(/'/g, "''");
231
+ const psCommand = `Expand-Archive -Path '${escapedZipPath}' -DestinationPath '${escapedDestDir}' -Force -ErrorAction Stop`;
136
232
  execSync(`powershell -NoProfile -ExecutionPolicy Bypass -Command "${psCommand}"`, { stdio: 'pipe' });
137
233
  } else {
138
234
  try {
@@ -186,9 +282,43 @@ function findBinary(dir, platform) {
186
282
  throw new Error(`Binary ${binaryName} not found in archive`);
187
283
  }
188
284
 
285
+ async function downloadAndInstall(version, platform, arch, binaryPath) {
286
+ const archiveName = getArchiveName(version, platform, arch);
287
+ const tagVersion = version.startsWith('v') ? version : `v${version}`;
288
+ const downloadUrl = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download/${tagVersion}/${archiveName}`;
289
+ const archivePath = path.join(CACHE_DIR, archiveName);
290
+
291
+ console.log(`Installing cc-helper ${version} for ${platform}-${arch}...`);
292
+
293
+ await downloadFile(downloadUrl, archivePath);
294
+
295
+ // Extract archive
296
+ console.log('Extracting archive...');
297
+ const tempDir = path.join(CACHE_DIR, 'temp_extract_' + Date.now());
298
+ fs.mkdirSync(tempDir, { recursive: true });
299
+
300
+ extractArchive(archivePath, tempDir, platform);
301
+
302
+ // Find and move binary
303
+ const extractedBinaryPath = findBinary(tempDir, platform);
304
+
305
+ if (fs.existsSync(binaryPath)) {
306
+ fs.unlinkSync(binaryPath);
307
+ }
308
+
309
+ fs.copyFileSync(extractedBinaryPath, binaryPath);
310
+ fs.chmodSync(binaryPath, 0o755);
311
+
312
+ // Clean up
313
+ fs.rmSync(tempDir, { recursive: true, force: true });
314
+ fs.unlinkSync(archivePath);
315
+
316
+ // Save version
317
+ fs.writeFileSync(VERSION_FILE, version);
318
+ }
319
+
189
320
  async function install() {
190
321
  try {
191
- const packageVersion = getPackageVersion();
192
322
  const { platform, arch } = getPlatform();
193
323
  const binaryName = getBinaryName(platform);
194
324
  const binaryPath = path.join(CACHE_DIR, binaryName);
@@ -198,49 +328,39 @@ async function install() {
198
328
  fs.mkdirSync(CACHE_DIR, { recursive: true });
199
329
  }
200
330
 
331
+ // Determine version to install
332
+ let targetVersion = getPackageVersion();
333
+
201
334
  // Check if already exists and up to date
202
- if (fs.existsSync(binaryPath) && fs.existsSync(VERSION_FILE)) {
335
+ if (targetVersion && fs.existsSync(binaryPath) && fs.existsSync(VERSION_FILE)) {
203
336
  const currentVersion = fs.readFileSync(VERSION_FILE, 'utf8').trim();
204
- if (currentVersion === packageVersion) {
337
+ if (currentVersion === targetVersion) {
205
338
  console.log(`cc-helper ${currentVersion} is already installed`);
206
339
  process.exit(0);
207
340
  }
208
341
  }
209
342
 
210
- // Download archive with fixed version from package.json
211
- const archiveName = getArchiveName(packageVersion, platform, arch);
212
- const tagVersion = packageVersion.startsWith('v') ? packageVersion : `v${packageVersion}`;
213
- const downloadUrl = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download/${tagVersion}/${archiveName}`;
214
- const archivePath = path.join(CACHE_DIR, archiveName);
215
-
216
- console.log(`Installing cc-helper ${packageVersion} for ${platform}-${arch}...`);
217
-
218
- await downloadFile(downloadUrl, archivePath);
219
-
220
- // Extract archive
221
- console.log('Extracting archive...');
222
- const tempDir = path.join(CACHE_DIR, 'temp_extract_' + Date.now());
223
- fs.mkdirSync(tempDir, { recursive: true });
224
-
225
- extractArchive(archivePath, tempDir, platform);
226
-
227
- // Find and move binary
228
- const extractedBinaryPath = findBinary(tempDir, platform);
229
-
230
- if (fs.existsSync(binaryPath)) {
231
- fs.unlinkSync(binaryPath);
343
+ // Try to download the package version first
344
+ if (targetVersion) {
345
+ try {
346
+ await downloadAndInstall(targetVersion, platform, arch, binaryPath);
347
+ console.log('Installation complete!');
348
+ console.log(`Binary location: ${binaryPath}`);
349
+ return;
350
+ } catch (err) {
351
+ // If download failed (e.g., version not found or no binary), try to find a release with binary
352
+ if (err.message.includes('404')) {
353
+ console.log(`Version ${targetVersion} not available, finding compatible release...`);
354
+ } else {
355
+ throw err;
356
+ }
357
+ }
232
358
  }
233
359
 
234
- fs.copyFileSync(extractedBinaryPath, binaryPath);
235
- fs.chmodSync(binaryPath, 0o755);
236
-
237
- // Clean up
238
- fs.rmSync(tempDir, { recursive: true, force: true });
239
- fs.unlinkSync(archivePath);
240
-
241
- // Save version
242
- fs.writeFileSync(VERSION_FILE, packageVersion);
243
-
360
+ // Find a release that has binary assets for this platform
361
+ console.log('Finding latest compatible release...');
362
+ const compatibleVersion = await findReleaseWithBinary(platform, arch);
363
+ await downloadAndInstall(compatibleVersion, platform, arch, binaryPath);
244
364
  console.log('Installation complete!');
245
365
  console.log(`Binary location: ${binaryPath}`);
246
366
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unitsvc/cc-helper",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "description": "Claude Code /loop feature bypass tool",
5
5
  "main": "index.js",
6
6
  "bin": {