@unitsvc/cc-helper 1.0.9 → 1.0.13
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/README-zh.md +155 -0
- package/README.md +47 -18
- package/dist/cli.js +2 -0
- package/dist/index.js +1 -0
- package/package.json +22 -13
- package/bin/cc-helper.js +0 -412
- package/index.js +0 -9
- package/install.js +0 -374
- package/uninstall.js +0 -23
package/README-zh.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# cc-helper
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@unitsvc/cc-helper)
|
|
4
|
+
[](https://github.com/next-bin/cc-helper/blob/master/LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
|
|
7
|
+
[**English**](./README.md) | **简体中文**
|
|
8
|
+
|
|
9
|
+
> ⚡ 一键启用 Claude Code CLI 的 `/loop` 功能
|
|
10
|
+
|
|
11
|
+
## 环境要求
|
|
12
|
+
|
|
13
|
+
- Node.js >= 14.0.0
|
|
14
|
+
- Claude Code v2.1.71+
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g @anthropic-ai/claude-code@v2.1.71
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## 使用方法
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# 启用 /loop 功能
|
|
24
|
+
npx @unitsvc/cc-helper enable
|
|
25
|
+
|
|
26
|
+
# 查看状态
|
|
27
|
+
npx @unitsvc/cc-helper status
|
|
28
|
+
|
|
29
|
+
# 禁用 /loop 功能(恢复原状)
|
|
30
|
+
npx @unitsvc/cc-helper disable
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 代理支持
|
|
34
|
+
|
|
35
|
+
如果下载失败,使用 `--proxy` 参数:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
# 使用默认代理 (https://edgeone.gh-proxy.org)
|
|
39
|
+
npx @unitsvc/cc-helper --proxy enable
|
|
40
|
+
|
|
41
|
+
# 使用自定义代理
|
|
42
|
+
npx @unitsvc/cc-helper --proxy https://your-proxy.com enable
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 命令说明
|
|
46
|
+
|
|
47
|
+
| 命令 | 说明 |
|
|
48
|
+
| --------- | ----------------- |
|
|
49
|
+
| `enable` | 启用 `/loop` 功能 |
|
|
50
|
+
| `disable` | 恢复原始状态 |
|
|
51
|
+
| `status` | 查看当前状态 |
|
|
52
|
+
|
|
53
|
+
## 赞助
|
|
54
|
+
|
|
55
|
+
<table>
|
|
56
|
+
<tr>
|
|
57
|
+
<td width="60" valign="middle">
|
|
58
|
+
<img src="https://img.shields.io/badge/🚀-GLM-blue?style=for-the-badge" alt="GLM"/>
|
|
59
|
+
</td>
|
|
60
|
+
<td valign="middle">
|
|
61
|
+
<b>GLM Coding Plan</b><br/>
|
|
62
|
+
<sub>Full support for <b>Claude Code</b>, <b>Cline</b>, and <b>20+ top coding tools</b> — starting at just <b>$10/month</b></sub><br/>
|
|
63
|
+
<a href="https://z.ai/subscribe?ic=1YVKN4IRCQ"><b>👉 Subscribe now — limited-time deal!</b></a>
|
|
64
|
+
</td>
|
|
65
|
+
</tr>
|
|
66
|
+
</table>
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## 什么是 `/loop`?
|
|
71
|
+
|
|
72
|
+
`/loop` 是 Claude Code CLI 的内置命令,用于创建定时重复提示。适用于:
|
|
73
|
+
|
|
74
|
+
- 轮询部署或构建状态
|
|
75
|
+
- 监控 PR 状态
|
|
76
|
+
- 设置提醒
|
|
77
|
+
- 定时执行工作流
|
|
78
|
+
|
|
79
|
+
### 使用语法
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
/loop [间隔时间] <提示内容>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**示例:**
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
/loop 5m check if the deployment finished
|
|
89
|
+
/loop 30m /review-pr 1234
|
|
90
|
+
/loop remind me to push the release at 3pm
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 间隔时间格式
|
|
94
|
+
|
|
95
|
+
| 形式 | 示例 | 解析间隔 |
|
|
96
|
+
| ---------- | --------------------------- | -------------- |
|
|
97
|
+
| 前置时间 | `/loop 30m check` | 每 30 分钟 |
|
|
98
|
+
| 后置 every | `/loop check every 2 hours` | 每 2 小时 |
|
|
99
|
+
| 无间隔 | `/loop check` | 默认每 10 分钟 |
|
|
100
|
+
|
|
101
|
+
支持单位:`s`(秒)、`m`(分)、`h`(时)、`d`(天)
|
|
102
|
+
|
|
103
|
+
### 核心特性
|
|
104
|
+
|
|
105
|
+
- **会话级别**:任务仅在当前 Claude Code 会话中存在,退出即消失
|
|
106
|
+
- **自动过期**:重复任务 3 天后自动过期
|
|
107
|
+
- **抖动保护**:添加小偏移量防止 API 惊群效应
|
|
108
|
+
- **低优先级**:定时提示在你与 Claude 交互间隙触发
|
|
109
|
+
|
|
110
|
+
### 管理任务
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
what scheduled tasks do I have? # 列出所有任务
|
|
114
|
+
cancel the deploy check job # 按描述或 ID 取消
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 功能特点
|
|
118
|
+
|
|
119
|
+
- 一键启用 `/loop` 功能
|
|
120
|
+
- 轻松恢复原始状态
|
|
121
|
+
- 自动备份原文件
|
|
122
|
+
- 零运行时依赖
|
|
123
|
+
- 跨平台支持
|
|
124
|
+
|
|
125
|
+
### 示例
|
|
126
|
+
|
|
127
|
+
启用后,在 Claude Code 中使用 `/loop` 命令:
|
|
128
|
+
|
|
129
|
+

|
|
130
|
+
|
|
131
|
+
执行 loop 命令示例:
|
|
132
|
+
|
|
133
|
+

|
|
134
|
+
|
|
135
|
+
## 支持平台
|
|
136
|
+
|
|
137
|
+
- macOS (amd64, arm64)
|
|
138
|
+
- Linux (amd64, arm64)
|
|
139
|
+
- Windows (amd64, arm64)
|
|
140
|
+
|
|
141
|
+
## 许可证
|
|
142
|
+
|
|
143
|
+
AGPL-3.0 - 详见 [LICENSE](./LICENSE)
|
|
144
|
+
|
|
145
|
+
## 安全
|
|
146
|
+
|
|
147
|
+
### 报告漏洞
|
|
148
|
+
|
|
149
|
+
如果您发现 cc-helper 的安全漏洞,请负责任地报告:
|
|
150
|
+
|
|
151
|
+
1. **不要**公开提 issue
|
|
152
|
+
2. 发送邮件给维护者说明详情
|
|
153
|
+
3. 给予合理时间修复后再公开
|
|
154
|
+
|
|
155
|
+
我们非常重视安全问题,会尽快响应。
|
package/README.md
CHANGED
|
@@ -1,42 +1,69 @@
|
|
|
1
1
|
# cc-helper
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@unitsvc/cc-helper)
|
|
4
|
+
[](https://github.com/next-bin/cc-helper/blob/master/LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
|
|
7
|
+
**English** | [**简体中文**](./README-zh.md)
|
|
8
|
+
|
|
9
|
+
> ⚡ Enable the `/loop` feature in Claude Code CLI with one command
|
|
4
10
|
|
|
5
11
|
## Requirements
|
|
6
12
|
|
|
13
|
+
- Node.js >= 14.0.0
|
|
7
14
|
- Claude Code v2.1.71+
|
|
8
15
|
|
|
9
16
|
```bash
|
|
10
17
|
npm install -g @anthropic-ai/claude-code@v2.1.71
|
|
11
18
|
```
|
|
12
19
|
|
|
13
|
-
##
|
|
20
|
+
## Usage
|
|
14
21
|
|
|
15
22
|
```bash
|
|
16
|
-
#
|
|
23
|
+
# Enable /loop feature
|
|
17
24
|
npx @unitsvc/cc-helper enable
|
|
18
25
|
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
# Check status
|
|
27
|
+
npx @unitsvc/cc-helper status
|
|
28
|
+
|
|
29
|
+
# Disable /loop feature (restore original)
|
|
30
|
+
npx @unitsvc/cc-helper disable
|
|
22
31
|
```
|
|
23
32
|
|
|
24
|
-
|
|
33
|
+
### Proxy Support
|
|
25
34
|
|
|
26
|
-
|
|
35
|
+
If download fails, use `--proxy` flag:
|
|
27
36
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
| `cc-helper disable` | Restore original |
|
|
32
|
-
| `cc-helper status` | Check current status |
|
|
33
|
-
| `cc-helper uninstall` | Uninstall (clean cache) |
|
|
37
|
+
```bash
|
|
38
|
+
# Use default proxy (https://edgeone.gh-proxy.org)
|
|
39
|
+
npx @unitsvc/cc-helper --proxy enable
|
|
34
40
|
|
|
35
|
-
|
|
41
|
+
# Use custom proxy
|
|
42
|
+
npx @unitsvc/cc-helper --proxy https://your-proxy.com enable
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Commands
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
| Command | Description |
|
|
48
|
+
| -------- | ---------------------- |
|
|
49
|
+
| `enable` | Enable `/loop` feature |
|
|
50
|
+
| `disable`| Restore original |
|
|
51
|
+
| `status` | Check current status |
|
|
52
|
+
|
|
53
|
+
## Sponsors
|
|
38
54
|
|
|
39
|
-
|
|
55
|
+
<table>
|
|
56
|
+
<tr>
|
|
57
|
+
<td width="60" valign="middle">
|
|
58
|
+
<img src="https://img.shields.io/badge/🚀-GLM-blue?style=for-the-badge" alt="GLM"/>
|
|
59
|
+
</td>
|
|
60
|
+
<td valign="middle">
|
|
61
|
+
<b>GLM Coding Plan</b><br/>
|
|
62
|
+
<sub>Full support for <b>Claude Code</b>, <b>Cline</b>, and <b>20+ top coding tools</b> — starting at just <b>$10/month</b></sub><br/>
|
|
63
|
+
<a href="https://z.ai/subscribe?ic=1YVKN4IRCQ"><b>👉 Subscribe now — limited-time deal!</b></a>
|
|
64
|
+
</td>
|
|
65
|
+
</tr>
|
|
66
|
+
</table>
|
|
40
67
|
|
|
41
68
|
---
|
|
42
69
|
|
|
@@ -92,6 +119,8 @@ cancel the deploy check job # Cancel by description or ID
|
|
|
92
119
|
- Enable `/loop` with one command
|
|
93
120
|
- Easy restore functionality
|
|
94
121
|
- Automatic backup
|
|
122
|
+
- Zero runtime dependencies
|
|
123
|
+
- Cross-platform support
|
|
95
124
|
|
|
96
125
|
### Examples
|
|
97
126
|
|
|
@@ -107,7 +136,7 @@ Example of executing a loop command:
|
|
|
107
136
|
|
|
108
137
|
- macOS (amd64, arm64)
|
|
109
138
|
- Linux (amd64, arm64)
|
|
110
|
-
- Windows (amd64)
|
|
139
|
+
- Windows (amd64, arm64)
|
|
111
140
|
|
|
112
141
|
## License
|
|
113
142
|
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";var ir=Object.create;var S=Object.defineProperty;var or=Object.getOwnPropertyDescriptor;var sr=Object.getOwnPropertyNames;var ar=Object.getPrototypeOf,cr=Object.prototype.hasOwnProperty;var lr=(e,n)=>{for(var r in n)S(e,r,{get:n[r],enumerable:!0})},F=(e,n,r,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let t of sr(n))!cr.call(e,t)&&t!==r&&S(e,t,{get:()=>n[t],enumerable:!(i=or(n,t))||i.enumerable});return e};var p=(e,n,r)=>(r=e!=null?ir(ar(e)):{},F(n||!e||!e.__esModule?S(r,"default",{value:e,enumerable:!0}):r,e)),fr=e=>F(S({},"__esModule",{value:!0}),e);var wr={};lr(wr,{ensureBinary:()=>W,getBinaryPath:()=>x,main:()=>Q,uninstall:()=>B});module.exports=fr(wr);var Y=require("child_process");var l=p(require("fs")),T=p(require("os")),$=p(require("path"));var b=p(require("os")),O=p(require("path")),pr={darwin:"darwin",linux:"linux",win32:"windows"},mr={x64:"amd64",arm64:"arm64"};function L(){let e=b.default.platform(),n=b.default.arch(),r=pr[e],i=mr[n];if(!r)throw new Error(`Unsupported platform: ${e}`);if(!i)throw new Error(`Unsupported arch: ${n}`);return{platform:r,arch:i}}var U=e=>e==="windows"?"cc-helper.exe":"cc-helper";function E(e,n,r){let i=e.replace(/^v/,"");return n==="windows"?`cc-helper_${i}_${n}_${r}.zip`:`cc-helper_${i}_${n}_${r}.tar.gz`}var x=()=>{let{platform:e}=L();return O.default.join(__dirname,"bin",U(e))};var h=p(require("fs")),Z=p(require("path")),M=p(require("http")),H=p(require("https"));async function k(e,n,r=3){for(let i=0;i<r;i++)try{await new Promise((t,o)=>{let m=(e.startsWith("https")?H.default:M.default).get(e,{headers:{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"}},c=>{if(c.statusCode===301||c.statusCode===302){let d=c.headers.location;if(d){k(d,n,r).then(t).catch(o);return}}if(c.statusCode!==200){o(new Error(`HTTP ${c.statusCode}`));return}let s=Z.default.dirname(n);h.default.existsSync(s)||h.default.mkdirSync(s,{recursive:!0});let g=h.default.createWriteStream(n);c.pipe(g),g.on("finish",()=>{g.close(d=>{d?o(d):t()})}),g.on("error",d=>{h.default.unlinkSync(n),o(d)}),c.on("error",d=>{h.default.unlinkSync(n),o(d)})});m.setTimeout(3e4,()=>{m.destroy(),o(new Error("Timeout"))}),m.on("error",c=>{h.default.existsSync(n)&&h.default.unlinkSync(n),o(c)})});return}catch(t){if(h.default.existsSync(n)&&h.default.unlinkSync(n),i===r-1)throw t;console.log(`warn cc-helper retry ${i+1}/${r}`),await new Promise(o=>setTimeout(o,1e3*(i+1)))}}var f=p(require("fs")),u=p(require("path")),R=p(require("zlib"));async function dr(e,n){let r=0;for(;r+512<=e.length;){let i=e.subarray(r,r+512);if(i.every(c=>c===0))break;let t=i.subarray(0,100).toString().replace(/\0.*$/,""),o=i.subarray(345,500).toString().replace(/\0.*$/,"");o&&(t=`${o}/${t}`),t.startsWith("package/")&&(t=t.slice(8)),t=t.replace(/\//g,u.default.sep);let a=parseInt(i.subarray(124,136).toString().trim(),8)||0,m=i[156];if(r+=512,a>0&&t&&(m===0||m===48)){let c=u.default.join(n,t);await f.default.promises.mkdir(u.default.dirname(c),{recursive:!0}),await f.default.promises.writeFile(c,e.subarray(r,r+a)),r+=Math.ceil(a/512)*512}}}async function ur(e,n){await f.default.promises.mkdir(n,{recursive:!0});let r=await f.default.promises.readFile(e),i;try{i=R.default.unzipSync(r)}catch(t){throw new Error(`Failed to decompress tar.gz: ${t.message}`)}await dr(i,n)}async function gr(e,n){await f.default.promises.mkdir(n,{recursive:!0});let r=await f.default.promises.readFile(e);if(r.length<22)throw new Error(`Invalid ZIP: file too small (${r.length} bytes)`);let i=Buffer.from([80,75,5,6]),t=r.length-22;for(;t>=0&&!r.subarray(t,t+4).equals(i);)t--;if(t<0)throw new Error(`Invalid ZIP: missing end of central directory (file may be corrupted or incomplete, size: ${r.length} bytes)`);if(t+22>r.length)throw new Error("Invalid ZIP: truncated EOCD record");let o=r.readUInt32LE(t+16),a=r.readUInt32LE(t+12),m=o+a;if(o>=r.length||m>r.length)throw new Error(`Invalid ZIP: central directory out of bounds (cdOffset: ${o}, cdEnd: ${m}, fileSize: ${r.length})`);let c=Buffer.from([80,75,1,2]),s=o;for(;s<m;){if(s+46>r.length)throw new Error(`Invalid ZIP: truncated central directory at position ${s}`);if(!r.subarray(s,s+4).equals(c))throw new Error(`Invalid ZIP: invalid central directory signature at position ${s}`);let g=r.readUInt16LE(s+28),d=r.readUInt16LE(s+30),N=r.readUInt16LE(s+32),y=r.readUInt32LE(s+42),rr=r.readUInt32LE(s+20),yr=r.readUInt32LE(s+24),xr=r.readUInt16LE(s+10);if(s+46+g+d+N>r.length)throw new Error(`Invalid ZIP: truncated central directory entry at position ${s}`);let w=r.subarray(s+46,s+46+g).toString();if(s+=46+g+d+N,w.endsWith("/")){await f.default.promises.mkdir(u.default.join(n,w.replace(/\//g,u.default.sep)),{recursive:!0});continue}if(w=w.replace(/\//g,u.default.sep),y+30>r.length)throw new Error(`Invalid ZIP: local header out of bounds for ${w}`);let v=r.readUInt16LE(y+8),er=r.readUInt16LE(y+26),tr=r.readUInt16LE(y+28),I=y+30+er+tr,C=rr;if(I+C>r.length)throw new Error(`Invalid ZIP: truncated file data for ${w}`);let D=r.subarray(I,I+C),P=u.default.join(n,w);await f.default.promises.mkdir(u.default.dirname(P),{recursive:!0});try{if(v===8)await f.default.promises.writeFile(P,R.default.inflateRawSync(D));else if(v===0)await f.default.promises.writeFile(P,D);else throw new Error(`Unsupported compression method ${v} for ${w}`)}catch(nr){throw new Error(`Failed to decompress ${w}: ${nr.message}`)}}}async function q(e,n,r){r==="windows"?await gr(e,n):await ur(e,n)}function G(e,n){let r=u.default.join(e,n);if(f.default.existsSync(r))return r;for(let i of f.default.readdirSync(e)){let t=u.default.join(e,i,n);if(f.default.statSync(u.default.join(e,i)).isDirectory()&&f.default.existsSync(t))return t}throw new Error(`Binary ${n} not found`)}var K=p(require("https"));var A="next-bin",_="cc-helper";function j(e){return new Promise((n,r)=>{let i=K.default.get({hostname:"api.github.com",path:e,headers:{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"}},t=>{let o="";t.on("data",a=>o+=a),t.on("end",()=>{t.statusCode!==200?r(new Error(`GitHub API error: ${t.statusCode}`)):n(JSON.parse(o))})});i.setTimeout(15e3,()=>{i.destroy(),r(new Error("GitHub API timeout"))}),i.on("error",r)})}async function J(){return(await j(`/repos/${A}/${_}/releases/latest`)).tag_name.replace(/^v/,"")}async function V(e,n){var t;let r=await j(`/repos/${A}/${_}/releases?per_page=10`),i=e==="windows"?`_${e}_${n}.zip`:`_${e}_${n}.tar.gz`;for(let o of r)if((t=o.assets)!=null&&t.some(a=>a.name.includes(i)))return o.tag_name.replace(/^v/,"");throw new Error(`No binary found for ${e}-${n}`)}function X(e,n,r,i){let t=e.startsWith("v")?e:`v${e}`,o=`https://github.com/${A}/${_}/releases/download/${t}/${E(e,n,r)}`;return i?`${i}/${o}`:o}async function z(e,n,r,i,t){let o=E(e,n,r),a=$.default.join(T.default.tmpdir(),o),m=X(e,n,r,t);console.log(`info cc-helper@${e} Installing for ${n}-${r}`),t&&console.log("info cc-helper Using proxy:",t);try{await k(m,a)}catch(s){throw t||console.log("warn cc-helper Download failed, try: npx @unitsvc/cc-helper --proxy enable"),s}let c=$.default.join(T.default.tmpdir(),`cc-helper_${Date.now()}`);l.default.mkdirSync(c,{recursive:!0});try{await q(a,c,n);let s=G(c,U(n)),g=$.default.dirname(i);l.default.existsSync(g)||l.default.mkdirSync(g,{recursive:!0}),l.default.existsSync(i)&&l.default.unlinkSync(i),l.default.copyFileSync(s,i),process.platform!=="win32"&&l.default.chmodSync(i,493),console.log("info cc-helper Installed to",i)}finally{let s=l.default.rmSync||l.default.rmdirSync;try{s(c,{recursive:!0,force:!0})}catch{}l.default.existsSync(a)&&l.default.unlinkSync(a)}}async function W(e=null,n){let{platform:r,arch:i}=L(),t=x();if(l.default.existsSync(t))return t;if(e)try{return await z(e,r,i,t,n),t}catch(a){if(!a.message.includes("404"))throw a;console.log(`warn cc-helper@${e} not available, using latest`)}let o=await J();try{await z(o,r,i,t,n)}catch(a){if(!a.message.includes("404"))throw a;o=await V(r,i),await z(o,r,i,t,n)}return t}function B(){let e=x(),n=$.default.dirname(e);l.default.existsSync(e)?(l.default.unlinkSync(e),console.log("info cc-helper Removed binary:",e)):console.log("info cc-helper No binary found"),l.default.existsSync(n)&&l.default.readdirSync(n).length===0&&l.default.rmdirSync(n),console.log("info cc-helper To complete uninstallation, run:"),console.log(" npm uninstall -g @unitsvc/cc-helper")}function hr(e){let n=[],r,i="https://edgeone.gh-proxy.org";for(let t=0;t<e.length;t++)e[t]==="--proxy"?t+1<e.length&&(e[t+1].startsWith("http://")||e[t+1].startsWith("https://"))?(r=e[t+1],t++):r=i:e[t].startsWith("--proxy=")?r=e[t].slice(8)||i:n.push(e[t]);return{proxy:r,remainingArgs:n}}async function Q(){let e=process.argv.slice(2);if(e[0]==="uninstall"){B();return}let{proxy:n,remainingArgs:r}=hr(e);try{let i=await W(null,n),t=(0,Y.spawn)(i,r,{stdio:"inherit"});t.on("close",o=>process.exit(o??1)),t.on("error",o=>{console.error("ERR! cc-helper",o.message),process.exit(1)})}catch(i){console.error("ERR! cc-helper",i.message),process.exit(1)}}require.main===module&&Q();0&&(module.exports={ensureBinary,getBinaryPath,main,uninstall});
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var nr=Object.create;var I=Object.defineProperty;var ir=Object.getOwnPropertyDescriptor;var or=Object.getOwnPropertyNames;var sr=Object.getPrototypeOf,ar=Object.prototype.hasOwnProperty;var cr=(e,t)=>{for(var r in t)I(e,r,{get:t[r],enumerable:!0})},O=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of or(t))!ar.call(e,n)&&n!==r&&I(e,n,{get:()=>t[n],enumerable:!(i=ir(t,n))||i.enumerable});return e};var m=(e,t,r)=>(r=e!=null?nr(sr(e)):{},O(t||!e||!e.__esModule?I(r,"default",{value:e,enumerable:!0}):r,e)),lr=e=>O(I({},"__esModule",{value:!0}),e);var gr={};cr(gr,{ensureBinary:()=>C,findReleaseWithBinary:()=>P,getArchiveName:()=>y,getBinaryName:()=>S,getBinaryPath:()=>E,getDownloadUrl:()=>L,getLatestVersion:()=>b,getPlatform:()=>x,install:()=>Q,uninstall:()=>X});module.exports=lr(gr);var l=m(require("fs")),N=m(require("os")),v=m(require("path"));var z=m(require("os")),M=m(require("path")),fr={darwin:"darwin",linux:"linux",win32:"windows"},mr={x64:"amd64",arm64:"arm64"};function x(){let e=z.default.platform(),t=z.default.arch(),r=fr[e],i=mr[t];if(!r)throw new Error(`Unsupported platform: ${e}`);if(!i)throw new Error(`Unsupported arch: ${t}`);return{platform:r,arch:i}}var S=e=>e==="windows"?"cc-helper.exe":"cc-helper";function y(e,t,r){let i=e.replace(/^v/,"");return t==="windows"?`cc-helper_${i}_${t}_${r}.zip`:`cc-helper_${i}_${t}_${r}.tar.gz`}var E=()=>{let{platform:e}=x();return M.default.join(__dirname,"bin",S(e))};var h=m(require("fs")),H=m(require("path")),G=m(require("http")),q=m(require("https"));async function _(e,t,r=3){for(let i=0;i<r;i++)try{await new Promise((n,o)=>{let d=(e.startsWith("https")?q.default:G.default).get(e,{headers:{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"}},c=>{if(c.statusCode===301||c.statusCode===302){let p=c.headers.location;if(p){_(p,t,r).then(n).catch(o);return}}if(c.statusCode!==200){o(new Error(`HTTP ${c.statusCode}`));return}let s=H.default.dirname(t);h.default.existsSync(s)||h.default.mkdirSync(s,{recursive:!0});let g=h.default.createWriteStream(t);c.pipe(g),g.on("finish",()=>{g.close(p=>{p?o(p):n()})}),g.on("error",p=>{h.default.unlinkSync(t),o(p)}),c.on("error",p=>{h.default.unlinkSync(t),o(p)})});d.setTimeout(3e4,()=>{d.destroy(),o(new Error("Timeout"))}),d.on("error",c=>{h.default.existsSync(t)&&h.default.unlinkSync(t),o(c)})});return}catch(n){if(h.default.existsSync(t)&&h.default.unlinkSync(t),i===r-1)throw n;console.log(`warn cc-helper retry ${i+1}/${r}`),await new Promise(o=>setTimeout(o,1e3*(i+1)))}}var f=m(require("fs")),u=m(require("path")),R=m(require("zlib"));async function dr(e,t){let r=0;for(;r+512<=e.length;){let i=e.subarray(r,r+512);if(i.every(c=>c===0))break;let n=i.subarray(0,100).toString().replace(/\0.*$/,""),o=i.subarray(345,500).toString().replace(/\0.*$/,"");o&&(n=`${o}/${n}`),n.startsWith("package/")&&(n=n.slice(8)),n=n.replace(/\//g,u.default.sep);let a=parseInt(i.subarray(124,136).toString().trim(),8)||0,d=i[156];if(r+=512,a>0&&n&&(d===0||d===48)){let c=u.default.join(t,n);await f.default.promises.mkdir(u.default.dirname(c),{recursive:!0}),await f.default.promises.writeFile(c,e.subarray(r,r+a)),r+=Math.ceil(a/512)*512}}}async function pr(e,t){await f.default.promises.mkdir(t,{recursive:!0});let r=await f.default.promises.readFile(e),i;try{i=R.default.unzipSync(r)}catch(n){throw new Error(`Failed to decompress tar.gz: ${n.message}`)}await dr(i,t)}async function ur(e,t){await f.default.promises.mkdir(t,{recursive:!0});let r=await f.default.promises.readFile(e);if(r.length<22)throw new Error(`Invalid ZIP: file too small (${r.length} bytes)`);let i=Buffer.from([80,75,5,6]),n=r.length-22;for(;n>=0&&!r.subarray(n,n+4).equals(i);)n--;if(n<0)throw new Error(`Invalid ZIP: missing end of central directory (file may be corrupted or incomplete, size: ${r.length} bytes)`);if(n+22>r.length)throw new Error("Invalid ZIP: truncated EOCD record");let o=r.readUInt32LE(n+16),a=r.readUInt32LE(n+12),d=o+a;if(o>=r.length||d>r.length)throw new Error(`Invalid ZIP: central directory out of bounds (cdOffset: ${o}, cdEnd: ${d}, fileSize: ${r.length})`);let c=Buffer.from([80,75,1,2]),s=o;for(;s<d;){if(s+46>r.length)throw new Error(`Invalid ZIP: truncated central directory at position ${s}`);if(!r.subarray(s,s+4).equals(c))throw new Error(`Invalid ZIP: invalid central directory signature at position ${s}`);let g=r.readUInt16LE(s+28),p=r.readUInt16LE(s+30),D=r.readUInt16LE(s+32),$=r.readUInt32LE(s+42),Y=r.readUInt32LE(s+20),hr=r.readUInt32LE(s+24),wr=r.readUInt16LE(s+10);if(s+46+g+p+D>r.length)throw new Error(`Invalid ZIP: truncated central directory entry at position ${s}`);let w=r.subarray(s+46,s+46+g).toString();if(s+=46+g+p+D,w.endsWith("/")){await f.default.promises.mkdir(u.default.join(t,w.replace(/\//g,u.default.sep)),{recursive:!0});continue}if(w=w.replace(/\//g,u.default.sep),$+30>r.length)throw new Error(`Invalid ZIP: local header out of bounds for ${w}`);let U=r.readUInt16LE($+8),rr=r.readUInt16LE($+26),er=r.readUInt16LE($+28),k=$+30+rr+er,F=Y;if(k+F>r.length)throw new Error(`Invalid ZIP: truncated file data for ${w}`);let Z=r.subarray(k,k+F),B=u.default.join(t,w);await f.default.promises.mkdir(u.default.dirname(B),{recursive:!0});try{if(U===8)await f.default.promises.writeFile(B,R.default.inflateRawSync(Z));else if(U===0)await f.default.promises.writeFile(B,Z);else throw new Error(`Unsupported compression method ${U} for ${w}`)}catch(tr){throw new Error(`Failed to decompress ${w}: ${tr.message}`)}}}async function K(e,t,r){r==="windows"?await ur(e,t):await pr(e,t)}function V(e,t){let r=u.default.join(e,t);if(f.default.existsSync(r))return r;for(let i of f.default.readdirSync(e)){let n=u.default.join(e,i,t);if(f.default.statSync(u.default.join(e,i)).isDirectory()&&f.default.existsSync(n))return n}throw new Error(`Binary ${t} not found`)}var j=m(require("https"));var T="next-bin",W="cc-helper";function J(e){return new Promise((t,r)=>{let i=j.default.get({hostname:"api.github.com",path:e,headers:{"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"}},n=>{let o="";n.on("data",a=>o+=a),n.on("end",()=>{n.statusCode!==200?r(new Error(`GitHub API error: ${n.statusCode}`)):t(JSON.parse(o))})});i.setTimeout(15e3,()=>{i.destroy(),r(new Error("GitHub API timeout"))}),i.on("error",r)})}async function b(){return(await J(`/repos/${T}/${W}/releases/latest`)).tag_name.replace(/^v/,"")}async function P(e,t){var n;let r=await J(`/repos/${T}/${W}/releases?per_page=10`),i=e==="windows"?`_${e}_${t}.zip`:`_${e}_${t}.tar.gz`;for(let o of r)if((n=o.assets)!=null&&n.some(a=>a.name.includes(i)))return o.tag_name.replace(/^v/,"");throw new Error(`No binary found for ${e}-${t}`)}function L(e,t,r,i){let n=e.startsWith("v")?e:`v${e}`,o=`https://github.com/${T}/${W}/releases/download/${n}/${y(e,t,r)}`;return i?`${i}/${o}`:o}async function A(e,t,r,i,n){let o=y(e,t,r),a=v.default.join(N.default.tmpdir(),o),d=L(e,t,r,n);console.log(`info cc-helper@${e} Installing for ${t}-${r}`),n&&console.log("info cc-helper Using proxy:",n);try{await _(d,a)}catch(s){throw n||console.log("warn cc-helper Download failed, try: npx @unitsvc/cc-helper --proxy enable"),s}let c=v.default.join(N.default.tmpdir(),`cc-helper_${Date.now()}`);l.default.mkdirSync(c,{recursive:!0});try{await K(a,c,t);let s=V(c,S(t)),g=v.default.dirname(i);l.default.existsSync(g)||l.default.mkdirSync(g,{recursive:!0}),l.default.existsSync(i)&&l.default.unlinkSync(i),l.default.copyFileSync(s,i),process.platform!=="win32"&&l.default.chmodSync(i,493),console.log("info cc-helper Installed to",i)}finally{let s=l.default.rmSync||l.default.rmdirSync;try{s(c,{recursive:!0,force:!0})}catch{}l.default.existsSync(a)&&l.default.unlinkSync(a)}}async function C(e=null,t){let{platform:r,arch:i}=x(),n=E();if(l.default.existsSync(n))return n;if(e)try{return await A(e,r,i,n,t),n}catch(a){if(!a.message.includes("404"))throw a;console.log(`warn cc-helper@${e} not available, using latest`)}let o=await b();try{await A(o,r,i,n,t)}catch(a){if(!a.message.includes("404"))throw a;o=await P(r,i),await A(o,r,i,n,t)}return n}async function Q(e=null,t){try{await C(e,t)}catch(r){console.error("ERR! cc-helper",r.message)}}function X(){let e=E(),t=v.default.dirname(e);l.default.existsSync(e)?(l.default.unlinkSync(e),console.log("info cc-helper Removed binary:",e)):console.log("info cc-helper No binary found"),l.default.existsSync(t)&&l.default.readdirSync(t).length===0&&l.default.rmdirSync(t),console.log("info cc-helper To complete uninstallation, run:"),console.log(" npm uninstall -g @unitsvc/cc-helper")}0&&(module.exports={ensureBinary,findReleaseWithBinary,getArchiveName,getBinaryName,getBinaryPath,getDownloadUrl,getLatestVersion,getPlatform,install,uninstall});
|
package/package.json
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unitsvc/cc-helper",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "Claude Code /loop feature bypass tool",
|
|
5
|
-
"main": "index.js",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"cc-helper": "
|
|
7
|
+
"cc-helper": "dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"
|
|
11
|
-
"
|
|
10
|
+
"build": "node scripts/build.js",
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest",
|
|
13
|
+
"prepublishOnly": "npm run build"
|
|
12
14
|
},
|
|
13
15
|
"keywords": [
|
|
14
|
-
"claude
|
|
16
|
+
"claude code",
|
|
15
17
|
"claude",
|
|
16
18
|
"loop",
|
|
17
|
-
"cli"
|
|
19
|
+
"cli",
|
|
20
|
+
"zai"
|
|
18
21
|
],
|
|
19
22
|
"author": "next-bin",
|
|
20
23
|
"license": "AGPL-3.0",
|
|
@@ -22,17 +25,23 @@
|
|
|
22
25
|
"type": "git",
|
|
23
26
|
"url": "git+https://github.com/next-bin/cc-helper.git"
|
|
24
27
|
},
|
|
28
|
+
"homepage": "https://github.com/next-bin/cc-helper#readme",
|
|
25
29
|
"bugs": {
|
|
26
30
|
"url": "https://github.com/next-bin/cc-helper/issues"
|
|
27
31
|
},
|
|
28
|
-
"homepage": "https://github.com/next-bin/cc-helper#readme",
|
|
29
32
|
"engines": {
|
|
30
33
|
"node": ">=14.0.0"
|
|
31
34
|
},
|
|
32
35
|
"files": [
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
]
|
|
36
|
+
"dist/",
|
|
37
|
+
"README.md",
|
|
38
|
+
"README-zh.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.11.0",
|
|
43
|
+
"esbuild": "^0.27.3",
|
|
44
|
+
"typescript": "^5.3.3",
|
|
45
|
+
"vitest": "^4.0.18"
|
|
46
|
+
}
|
|
38
47
|
}
|
package/bin/cc-helper.js
DELETED
|
@@ -1,412 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const https = require('https');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const os = require('os');
|
|
7
|
-
const { spawn, execSync } = require('child_process');
|
|
8
|
-
|
|
9
|
-
const GITHUB_OWNER = 'next-bin';
|
|
10
|
-
const GITHUB_REPO = 'cc-helper';
|
|
11
|
-
const CACHE_DIR = path.join(os.homedir(), '.cache', 'cc-helper');
|
|
12
|
-
const VERSION_FILE = path.join(CACHE_DIR, 'version.txt');
|
|
13
|
-
|
|
14
|
-
// Handle uninstall command directly
|
|
15
|
-
function handleUninstall() {
|
|
16
|
-
try {
|
|
17
|
-
if (fs.existsSync(CACHE_DIR)) {
|
|
18
|
-
console.log('Removing cached binaries...');
|
|
19
|
-
fs.rmSync(CACHE_DIR, { recursive: true, force: true });
|
|
20
|
-
console.log('Cleanup complete!');
|
|
21
|
-
} else {
|
|
22
|
-
console.log('No cached binaries found.');
|
|
23
|
-
}
|
|
24
|
-
console.log('\nTo complete uninstallation, run:');
|
|
25
|
-
console.log(' npm uninstall -g @unitsvc/cc-helper');
|
|
26
|
-
process.exit(0);
|
|
27
|
-
} catch (err) {
|
|
28
|
-
console.error('Error during uninstall:', err.message);
|
|
29
|
-
process.exit(1);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
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
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
throw new Error('No release with binary assets found');
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function getPlatform() {
|
|
129
|
-
const platform = os.platform();
|
|
130
|
-
const arch = os.arch();
|
|
131
|
-
|
|
132
|
-
const platformMap = {
|
|
133
|
-
'darwin': 'darwin',
|
|
134
|
-
'linux': 'linux',
|
|
135
|
-
'win32': 'windows'
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
const archMap = {
|
|
139
|
-
'x64': 'amd64',
|
|
140
|
-
'arm64': 'arm64'
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const mappedPlatform = platformMap[platform];
|
|
144
|
-
const mappedArch = archMap[arch];
|
|
145
|
-
|
|
146
|
-
if (!mappedPlatform || !mappedArch) {
|
|
147
|
-
throw new Error(`Unsupported platform: ${platform} ${arch}`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Windows arm64 not supported by GoReleaser
|
|
151
|
-
if (platform === 'win32' && arch === 'arm64') {
|
|
152
|
-
throw new Error('Windows arm64 is not supported. Please use Windows amd64 or macOS/Linux arm64.');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return { platform: mappedPlatform, arch: mappedArch };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function getArchiveName(version, platform, arch) {
|
|
159
|
-
// GitHub release uses version without 'v' prefix in filename
|
|
160
|
-
const ver = version.replace(/^v/, '');
|
|
161
|
-
if (platform === 'windows') {
|
|
162
|
-
return `cc-helper_${ver}_${platform}_${arch}.zip`;
|
|
163
|
-
}
|
|
164
|
-
return `cc-helper_${ver}_${platform}_${arch}.tar.gz`;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
function getBinaryName(platform) {
|
|
168
|
-
return platform === 'windows' ? 'cc-helper.exe' : 'cc-helper';
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function getBinaryPath() {
|
|
172
|
-
const { platform } = getPlatform();
|
|
173
|
-
const binaryName = getBinaryName(platform);
|
|
174
|
-
return path.join(CACHE_DIR, binaryName);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function downloadFile(url, dest) {
|
|
178
|
-
return new Promise((resolve, reject) => {
|
|
179
|
-
const file = fs.createWriteStream(dest);
|
|
180
|
-
|
|
181
|
-
const requestUrl = url.startsWith('https://') ? url : 'https://github.com' + url;
|
|
182
|
-
|
|
183
|
-
const request = https.get(requestUrl, { headers: { 'User-Agent': 'cc-helper-npm-installer' } }, (response) => {
|
|
184
|
-
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
185
|
-
file.close();
|
|
186
|
-
if (fs.existsSync(dest)) {
|
|
187
|
-
fs.unlinkSync(dest);
|
|
188
|
-
}
|
|
189
|
-
downloadFile(response.headers.location, dest).then(resolve).catch(reject);
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (response.statusCode !== 200) {
|
|
194
|
-
file.close();
|
|
195
|
-
if (fs.existsSync(dest)) {
|
|
196
|
-
fs.unlinkSync(dest);
|
|
197
|
-
}
|
|
198
|
-
reject(new Error(`Download failed with status ${response.statusCode}: ${url}`));
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
response.pipe(file);
|
|
203
|
-
|
|
204
|
-
file.on('finish', () => {
|
|
205
|
-
file.close();
|
|
206
|
-
resolve();
|
|
207
|
-
});
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
request.on('error', (err) => {
|
|
211
|
-
if (fs.existsSync(dest)) {
|
|
212
|
-
fs.unlinkSync(dest);
|
|
213
|
-
}
|
|
214
|
-
reject(err);
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
file.on('error', (err) => {
|
|
218
|
-
if (fs.existsSync(dest)) {
|
|
219
|
-
fs.unlinkSync(dest);
|
|
220
|
-
}
|
|
221
|
-
reject(err);
|
|
222
|
-
});
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function extractTarGz(tarGzPath, destDir) {
|
|
227
|
-
try {
|
|
228
|
-
// Use system tar command (available on macOS and Linux)
|
|
229
|
-
execSync(`tar -xzf "${tarGzPath}" -C "${destDir}"`, { stdio: 'pipe' });
|
|
230
|
-
} catch (err) {
|
|
231
|
-
throw new Error(`Failed to extract tar.gz archive: ${err.message}`);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
function extractZip(zipPath, destDir) {
|
|
236
|
-
try {
|
|
237
|
-
if (process.platform === 'win32') {
|
|
238
|
-
// Use PowerShell on Windows (built-in, no external dependencies)
|
|
239
|
-
// Use single-line command to avoid PowerShell multi-line parsing issues
|
|
240
|
-
// Disable progress bar to avoid Write-Progress IndexOutOfRangeException bug in PowerShell 5.1
|
|
241
|
-
const escapedZipPath = zipPath.replace(/'/g, "''");
|
|
242
|
-
const escapedDestDir = destDir.replace(/'/g, "''");
|
|
243
|
-
const psCommand = `$ProgressPreference = 'SilentlyContinue'; Expand-Archive -Path '${escapedZipPath}' -DestinationPath '${escapedDestDir}' -Force -ErrorAction Stop`;
|
|
244
|
-
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -Command "${psCommand}"`, { stdio: 'pipe' });
|
|
245
|
-
} else {
|
|
246
|
-
// Unix/Linux/macOS - use unzip
|
|
247
|
-
// Check if unzip is available first
|
|
248
|
-
try {
|
|
249
|
-
execSync('which unzip', { stdio: 'pipe' });
|
|
250
|
-
} catch {
|
|
251
|
-
throw new Error(
|
|
252
|
-
'unzip command not found. Please install unzip:\n' +
|
|
253
|
-
' Ubuntu/Debian: sudo apt-get install unzip\n' +
|
|
254
|
-
' macOS: brew install unzip\n' +
|
|
255
|
-
' CentOS/RHEL: sudo yum install unzip'
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
execSync(`unzip -o "${zipPath}" -d "${destDir}"`, { stdio: 'pipe' });
|
|
259
|
-
}
|
|
260
|
-
} catch (err) {
|
|
261
|
-
if (err.message.includes('unzip command not found')) {
|
|
262
|
-
throw err;
|
|
263
|
-
}
|
|
264
|
-
throw new Error(`Failed to extract zip archive: ${err.message}`);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
async function extractArchive(archivePath, destDir, platform) {
|
|
269
|
-
if (platform === 'windows') {
|
|
270
|
-
extractZip(archivePath, destDir);
|
|
271
|
-
} else {
|
|
272
|
-
extractTarGz(archivePath, destDir);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function findBinary(dir, platform) {
|
|
277
|
-
const binaryName = getBinaryName(platform);
|
|
278
|
-
const files = fs.readdirSync(dir);
|
|
279
|
-
|
|
280
|
-
// First check root
|
|
281
|
-
const rootBinary = path.join(dir, binaryName);
|
|
282
|
-
if (fs.existsSync(rootBinary)) {
|
|
283
|
-
return rootBinary;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Check subdirectories
|
|
287
|
-
for (const file of files) {
|
|
288
|
-
const fullPath = path.join(dir, file);
|
|
289
|
-
const stat = fs.statSync(fullPath);
|
|
290
|
-
if (stat.isDirectory()) {
|
|
291
|
-
const binaryInSubdir = path.join(fullPath, binaryName);
|
|
292
|
-
if (fs.existsSync(binaryInSubdir)) {
|
|
293
|
-
return binaryInSubdir;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
throw new Error(`Binary ${binaryName} not found in archive`);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async function downloadAndInstall(version, platform, arch, binaryPath) {
|
|
302
|
-
const archiveName = getArchiveName(version, platform, arch);
|
|
303
|
-
const tagVersion = version.startsWith('v') ? version : `v${version}`;
|
|
304
|
-
const downloadUrl = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download/${tagVersion}/${archiveName}`;
|
|
305
|
-
const archivePath = path.join(CACHE_DIR, archiveName);
|
|
306
|
-
|
|
307
|
-
console.log(`Downloading cc-helper ${version} for ${platform}-${arch}...`);
|
|
308
|
-
|
|
309
|
-
try {
|
|
310
|
-
await downloadFile(downloadUrl, archivePath);
|
|
311
|
-
|
|
312
|
-
// Extract archive
|
|
313
|
-
console.log('Extracting archive...');
|
|
314
|
-
const tempDir = path.join(CACHE_DIR, 'temp_extract_' + Date.now());
|
|
315
|
-
fs.mkdirSync(tempDir, { recursive: true });
|
|
316
|
-
|
|
317
|
-
await extractArchive(archivePath, tempDir, platform);
|
|
318
|
-
|
|
319
|
-
// Find and move binary
|
|
320
|
-
const extractedBinaryPath = findBinary(tempDir, platform);
|
|
321
|
-
|
|
322
|
-
// Remove old binary if exists
|
|
323
|
-
if (fs.existsSync(binaryPath)) {
|
|
324
|
-
fs.unlinkSync(binaryPath);
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Move binary to cache dir
|
|
328
|
-
fs.copyFileSync(extractedBinaryPath, binaryPath);
|
|
329
|
-
fs.chmodSync(binaryPath, 0o755);
|
|
330
|
-
|
|
331
|
-
// Clean up
|
|
332
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
333
|
-
fs.unlinkSync(archivePath);
|
|
334
|
-
|
|
335
|
-
// Save version
|
|
336
|
-
fs.writeFileSync(VERSION_FILE, version);
|
|
337
|
-
|
|
338
|
-
console.log('Download complete!');
|
|
339
|
-
} catch (err) {
|
|
340
|
-
// Clean up on failure
|
|
341
|
-
if (fs.existsSync(archivePath)) {
|
|
342
|
-
fs.unlinkSync(archivePath);
|
|
343
|
-
}
|
|
344
|
-
throw err;
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
async function ensureBinary() {
|
|
349
|
-
const binaryPath = getBinaryPath();
|
|
350
|
-
|
|
351
|
-
// If binary exists, use it directly (postinstall already handled version check)
|
|
352
|
-
if (fs.existsSync(binaryPath)) {
|
|
353
|
-
return binaryPath;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Binary doesn't exist, find a compatible release and download
|
|
357
|
-
console.log('Finding compatible release...');
|
|
358
|
-
const { platform, arch } = getPlatform();
|
|
359
|
-
const compatibleVersion = await findReleaseWithBinary(platform, arch);
|
|
360
|
-
|
|
361
|
-
// Create cache directory
|
|
362
|
-
if (!fs.existsSync(CACHE_DIR)) {
|
|
363
|
-
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Download and install
|
|
367
|
-
await downloadAndInstall(compatibleVersion, platform, arch, binaryPath);
|
|
368
|
-
|
|
369
|
-
return binaryPath;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
async function main() {
|
|
373
|
-
// Handle uninstall command
|
|
374
|
-
const args = process.argv.slice(2);
|
|
375
|
-
if (args.length > 0 && args[0] === 'uninstall') {
|
|
376
|
-
handleUninstall();
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
try {
|
|
381
|
-
const binaryPath = await ensureBinary();
|
|
382
|
-
|
|
383
|
-
// Run the binary with all arguments
|
|
384
|
-
// Windows requires shell: true for .exe files
|
|
385
|
-
const isWindows = process.platform === 'win32';
|
|
386
|
-
const child = spawn(binaryPath, args, {
|
|
387
|
-
stdio: 'inherit',
|
|
388
|
-
detached: false,
|
|
389
|
-
shell: isWindows
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
child.on('close', (code) => {
|
|
393
|
-
process.exit(code);
|
|
394
|
-
});
|
|
395
|
-
|
|
396
|
-
child.on('error', (err) => {
|
|
397
|
-
console.error('Failed to start cc-helper:', err.message);
|
|
398
|
-
process.exit(1);
|
|
399
|
-
});
|
|
400
|
-
} catch (err) {
|
|
401
|
-
console.error('Error:', err.message);
|
|
402
|
-
process.exit(1);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Export for use as module
|
|
407
|
-
module.exports = { main, ensureBinary, getBinaryPath, getPlatform, getLatestVersion };
|
|
408
|
-
|
|
409
|
-
// Run if called directly
|
|
410
|
-
if (require.main === module) {
|
|
411
|
-
main();
|
|
412
|
-
}
|
package/index.js
DELETED
package/install.js
DELETED
|
@@ -1,374 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
const https = require('https');
|
|
7
|
-
const { execSync } = require('child_process');
|
|
8
|
-
|
|
9
|
-
const GITHUB_OWNER = 'next-bin';
|
|
10
|
-
const GITHUB_REPO = 'cc-helper';
|
|
11
|
-
const CACHE_DIR = path.join(os.homedir(), '.cache', 'cc-helper');
|
|
12
|
-
const VERSION_FILE = path.join(CACHE_DIR, 'version.txt');
|
|
13
|
-
|
|
14
|
-
// Read version from package.json
|
|
15
|
-
function getPackageVersion() {
|
|
16
|
-
try {
|
|
17
|
-
const packagePath = path.join(__dirname, 'package.json');
|
|
18
|
-
const pkg = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
19
|
-
return pkg.version;
|
|
20
|
-
} catch (err) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
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
|
-
|
|
125
|
-
function getPlatform() {
|
|
126
|
-
const platform = os.platform();
|
|
127
|
-
const arch = os.arch();
|
|
128
|
-
|
|
129
|
-
const platformMap = {
|
|
130
|
-
'darwin': 'darwin',
|
|
131
|
-
'linux': 'linux',
|
|
132
|
-
'win32': 'windows'
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
const archMap = {
|
|
136
|
-
'x64': 'amd64',
|
|
137
|
-
'arm64': 'arm64'
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
const mappedPlatform = platformMap[platform];
|
|
141
|
-
const mappedArch = archMap[arch];
|
|
142
|
-
|
|
143
|
-
if (!mappedPlatform || !mappedArch) {
|
|
144
|
-
console.warn(`Warning: Unsupported platform ${platform} ${arch}`);
|
|
145
|
-
process.exit(0);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (platform === 'win32' && arch === 'arm64') {
|
|
149
|
-
console.warn('Warning: Windows arm64 is not supported');
|
|
150
|
-
process.exit(0);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return { platform: mappedPlatform, arch: mappedArch };
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function getArchiveName(version, platform, arch) {
|
|
157
|
-
const ver = version.replace(/^v/, '');
|
|
158
|
-
if (platform === 'windows') {
|
|
159
|
-
return `cc-helper_${ver}_${platform}_${arch}.zip`;
|
|
160
|
-
}
|
|
161
|
-
return `cc-helper_${ver}_${platform}_${arch}.tar.gz`;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function getBinaryName(platform) {
|
|
165
|
-
return platform === 'windows' ? 'cc-helper.exe' : 'cc-helper';
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function downloadFile(url, dest) {
|
|
169
|
-
return new Promise((resolve, reject) => {
|
|
170
|
-
const file = fs.createWriteStream(dest);
|
|
171
|
-
|
|
172
|
-
const requestUrl = url.startsWith('https://') ? url : 'https://github.com' + url;
|
|
173
|
-
|
|
174
|
-
const request = https.get(requestUrl, { headers: { 'User-Agent': 'cc-helper-npm-installer' } }, (response) => {
|
|
175
|
-
if (response.statusCode === 302 || response.statusCode === 301) {
|
|
176
|
-
file.close();
|
|
177
|
-
if (fs.existsSync(dest)) {
|
|
178
|
-
fs.unlinkSync(dest);
|
|
179
|
-
}
|
|
180
|
-
downloadFile(response.headers.location, dest).then(resolve).catch(reject);
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (response.statusCode !== 200) {
|
|
185
|
-
file.close();
|
|
186
|
-
if (fs.existsSync(dest)) {
|
|
187
|
-
fs.unlinkSync(dest);
|
|
188
|
-
}
|
|
189
|
-
reject(new Error(`Download failed with status ${response.statusCode}: ${url}`));
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
response.pipe(file);
|
|
194
|
-
|
|
195
|
-
file.on('finish', () => {
|
|
196
|
-
file.close();
|
|
197
|
-
resolve();
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
request.on('error', (err) => {
|
|
202
|
-
if (fs.existsSync(dest)) {
|
|
203
|
-
fs.unlinkSync(dest);
|
|
204
|
-
}
|
|
205
|
-
reject(err);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
file.on('error', (err) => {
|
|
209
|
-
if (fs.existsSync(dest)) {
|
|
210
|
-
fs.unlinkSync(dest);
|
|
211
|
-
}
|
|
212
|
-
reject(err);
|
|
213
|
-
});
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function extractTarGz(tarGzPath, destDir) {
|
|
218
|
-
try {
|
|
219
|
-
execSync(`tar -xzf "${tarGzPath}" -C "${destDir}"`, { stdio: 'pipe' });
|
|
220
|
-
} catch (err) {
|
|
221
|
-
throw new Error(`Failed to extract tar.gz archive: ${err.message}`);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function extractZip(zipPath, destDir) {
|
|
226
|
-
try {
|
|
227
|
-
if (process.platform === 'win32') {
|
|
228
|
-
// Use single-line command to avoid PowerShell multi-line parsing issues
|
|
229
|
-
// Disable progress bar to avoid Write-Progress IndexOutOfRangeException bug in PowerShell 5.1
|
|
230
|
-
const escapedZipPath = zipPath.replace(/'/g, "''");
|
|
231
|
-
const escapedDestDir = destDir.replace(/'/g, "''");
|
|
232
|
-
const psCommand = `$ProgressPreference = 'SilentlyContinue'; Expand-Archive -Path '${escapedZipPath}' -DestinationPath '${escapedDestDir}' -Force -ErrorAction Stop`;
|
|
233
|
-
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -Command "${psCommand}"`, { stdio: 'pipe' });
|
|
234
|
-
} else {
|
|
235
|
-
try {
|
|
236
|
-
execSync('which unzip', { stdio: 'pipe' });
|
|
237
|
-
} catch {
|
|
238
|
-
throw new Error(
|
|
239
|
-
'unzip command not found. Please install unzip:\n' +
|
|
240
|
-
' Ubuntu/Debian: sudo apt-get install unzip\n' +
|
|
241
|
-
' macOS: brew install unzip\n' +
|
|
242
|
-
' CentOS/RHEL: sudo yum install unzip'
|
|
243
|
-
);
|
|
244
|
-
}
|
|
245
|
-
execSync(`unzip -o "${zipPath}" -d "${destDir}"`, { stdio: 'pipe' });
|
|
246
|
-
}
|
|
247
|
-
} catch (err) {
|
|
248
|
-
if (err.message.includes('unzip command not found')) {
|
|
249
|
-
throw err;
|
|
250
|
-
}
|
|
251
|
-
throw new Error(`Failed to extract zip archive: ${err.message}`);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
function extractArchive(archivePath, destDir, platform) {
|
|
256
|
-
if (platform === 'windows') {
|
|
257
|
-
extractZip(archivePath, destDir);
|
|
258
|
-
} else {
|
|
259
|
-
extractTarGz(archivePath, destDir);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function findBinary(dir, platform) {
|
|
264
|
-
const binaryName = getBinaryName(platform);
|
|
265
|
-
const files = fs.readdirSync(dir);
|
|
266
|
-
|
|
267
|
-
const rootBinary = path.join(dir, binaryName);
|
|
268
|
-
if (fs.existsSync(rootBinary)) {
|
|
269
|
-
return rootBinary;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
for (const file of files) {
|
|
273
|
-
const fullPath = path.join(dir, file);
|
|
274
|
-
const stat = fs.statSync(fullPath);
|
|
275
|
-
if (stat.isDirectory()) {
|
|
276
|
-
const binaryInSubdir = path.join(fullPath, binaryName);
|
|
277
|
-
if (fs.existsSync(binaryInSubdir)) {
|
|
278
|
-
return binaryInSubdir;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
throw new Error(`Binary ${binaryName} not found in archive`);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
async function downloadAndInstall(version, platform, arch, binaryPath) {
|
|
287
|
-
const archiveName = getArchiveName(version, platform, arch);
|
|
288
|
-
const tagVersion = version.startsWith('v') ? version : `v${version}`;
|
|
289
|
-
const downloadUrl = `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download/${tagVersion}/${archiveName}`;
|
|
290
|
-
const archivePath = path.join(CACHE_DIR, archiveName);
|
|
291
|
-
|
|
292
|
-
console.log(`Installing cc-helper ${version} for ${platform}-${arch}...`);
|
|
293
|
-
|
|
294
|
-
await downloadFile(downloadUrl, archivePath);
|
|
295
|
-
|
|
296
|
-
// Extract archive
|
|
297
|
-
console.log('Extracting archive...');
|
|
298
|
-
const tempDir = path.join(CACHE_DIR, 'temp_extract_' + Date.now());
|
|
299
|
-
fs.mkdirSync(tempDir, { recursive: true });
|
|
300
|
-
|
|
301
|
-
extractArchive(archivePath, tempDir, platform);
|
|
302
|
-
|
|
303
|
-
// Find and move binary
|
|
304
|
-
const extractedBinaryPath = findBinary(tempDir, platform);
|
|
305
|
-
|
|
306
|
-
if (fs.existsSync(binaryPath)) {
|
|
307
|
-
fs.unlinkSync(binaryPath);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
fs.copyFileSync(extractedBinaryPath, binaryPath);
|
|
311
|
-
fs.chmodSync(binaryPath, 0o755);
|
|
312
|
-
|
|
313
|
-
// Clean up
|
|
314
|
-
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
315
|
-
fs.unlinkSync(archivePath);
|
|
316
|
-
|
|
317
|
-
// Save version
|
|
318
|
-
fs.writeFileSync(VERSION_FILE, version);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
async function install() {
|
|
322
|
-
try {
|
|
323
|
-
const { platform, arch } = getPlatform();
|
|
324
|
-
const binaryName = getBinaryName(platform);
|
|
325
|
-
const binaryPath = path.join(CACHE_DIR, binaryName);
|
|
326
|
-
|
|
327
|
-
// Create cache directory
|
|
328
|
-
if (!fs.existsSync(CACHE_DIR)) {
|
|
329
|
-
fs.mkdirSync(CACHE_DIR, { recursive: true });
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Determine version to install
|
|
333
|
-
let targetVersion = getPackageVersion();
|
|
334
|
-
|
|
335
|
-
// Check if already exists and up to date
|
|
336
|
-
if (targetVersion && fs.existsSync(binaryPath) && fs.existsSync(VERSION_FILE)) {
|
|
337
|
-
const currentVersion = fs.readFileSync(VERSION_FILE, 'utf8').trim();
|
|
338
|
-
if (currentVersion === targetVersion) {
|
|
339
|
-
console.log(`cc-helper ${currentVersion} is already installed`);
|
|
340
|
-
process.exit(0);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Try to download the package version first
|
|
345
|
-
if (targetVersion) {
|
|
346
|
-
try {
|
|
347
|
-
await downloadAndInstall(targetVersion, platform, arch, binaryPath);
|
|
348
|
-
console.log('Installation complete!');
|
|
349
|
-
console.log(`Binary location: ${binaryPath}`);
|
|
350
|
-
return;
|
|
351
|
-
} catch (err) {
|
|
352
|
-
// If download failed (e.g., version not found or no binary), try to find a release with binary
|
|
353
|
-
if (err.message.includes('404')) {
|
|
354
|
-
console.log(`Version ${targetVersion} not available, finding compatible release...`);
|
|
355
|
-
} else {
|
|
356
|
-
throw err;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Find a release that has binary assets for this platform
|
|
362
|
-
console.log('Finding latest compatible release...');
|
|
363
|
-
const compatibleVersion = await findReleaseWithBinary(platform, arch);
|
|
364
|
-
await downloadAndInstall(compatibleVersion, platform, arch, binaryPath);
|
|
365
|
-
console.log('Installation complete!');
|
|
366
|
-
console.log(`Binary location: ${binaryPath}`);
|
|
367
|
-
} catch (err) {
|
|
368
|
-
console.error('Installation failed:', err.message);
|
|
369
|
-
console.error('The binary will be downloaded on first run instead.');
|
|
370
|
-
process.exit(0);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
install();
|
package/uninstall.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
|
|
7
|
-
const CACHE_DIR = path.join(os.homedir(), '.cache', 'cc-helper');
|
|
8
|
-
|
|
9
|
-
function uninstall() {
|
|
10
|
-
try {
|
|
11
|
-
if (fs.existsSync(CACHE_DIR)) {
|
|
12
|
-
console.log('Removing cached binaries...');
|
|
13
|
-
fs.rmSync(CACHE_DIR, { recursive: true, force: true });
|
|
14
|
-
console.log('Cleanup complete!');
|
|
15
|
-
}
|
|
16
|
-
} catch (err) {
|
|
17
|
-
console.error('Cleanup warning:', err.message);
|
|
18
|
-
console.log('You can manually remove the cache directory:');
|
|
19
|
-
console.log(` rm -rf ${CACHE_DIR}`);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
uninstall();
|