@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 ADDED
@@ -0,0 +1,155 @@
1
+ # cc-helper
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@unitsvc/cc-helper.svg)](https://www.npmjs.com/package/@unitsvc/cc-helper)
4
+ [![License](https://img.shields.io/badge/License-AGPL%203.0-blue.svg)](https://github.com/next-bin/cc-helper/blob/master/LICENSE)
5
+ [![Node](https://img.shields.io/badge/Node-%3E%3D14.0.0-green.svg)](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
+ ![/loop 命令提示](./docs/images/loop-1.png)
130
+
131
+ 执行 loop 命令示例:
132
+
133
+ ![/loop 执行示例](./docs/images/loop-2.png)
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
- > Enable/disable the `/loop` feature in Claude Code CLI
3
+ [![npm version](https://img.shields.io/npm/v/@unitsvc/cc-helper.svg)](https://www.npmjs.com/package/@unitsvc/cc-helper)
4
+ [![License](https://img.shields.io/badge/License-AGPL%203.0-blue.svg)](https://github.com/next-bin/cc-helper/blob/master/LICENSE)
5
+ [![Node](https://img.shields.io/badge/Node-%3E%3D14.0.0-green.svg)](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
- ## Installation
20
+ ## Usage
14
21
 
15
22
  ```bash
16
- # Using npx (no install)
23
+ # Enable /loop feature
17
24
  npx @unitsvc/cc-helper enable
18
25
 
19
- # Or install globally
20
- npm install -g @unitsvc/cc-helper
21
- cc-helper enable
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
- ## Usage
33
+ ### Proxy Support
25
34
 
26
- ### Command
35
+ If download fails, use `--proxy` flag:
27
36
 
28
- | Command | Description |
29
- | --------------------- | ----------------------- |
30
- | `cc-helper enable` | Enable `/loop` feature |
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
- ## Sponsors
41
+ # Use custom proxy
42
+ npx @unitsvc/cc-helper --proxy https://your-proxy.com enable
43
+ ```
44
+
45
+ ### Commands
36
46
 
37
- 🚀 **GLM Coding Plan**
47
+ | Command | Description |
48
+ | -------- | ---------------------- |
49
+ | `enable` | Enable `/loop` feature |
50
+ | `disable`| Restore original |
51
+ | `status` | Check current status |
52
+
53
+ ## Sponsors
38
54
 
39
- 👉 [Enjoy full support for Claude Code, Cline, and 20+ top coding tools — starting at just $10/month. Subscribe now and grab the limited-time deal!](https://z.ai/subscribe?ic=1YVKN4IRCQ)
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.9",
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": "bin/cc-helper.js"
7
+ "cc-helper": "dist/cli.js"
8
8
  },
9
9
  "scripts": {
10
- "postinstall": "node install.js",
11
- "preuninstall": "node uninstall.js"
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-code",
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
- "bin/",
34
- "install.js",
35
- "uninstall.js",
36
- "index.js"
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
@@ -1,9 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const { main } = require('./bin/cc-helper.js');
4
-
5
- // Run the main function
6
- main().catch((err) => {
7
- console.error('Error:', err.message);
8
- process.exit(1);
9
- });
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();