deepharness 0.0.2 → 0.0.3

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.md CHANGED
@@ -4,74 +4,54 @@
4
4
 
5
5
  ## 安装
6
6
 
7
- `deepharness` npm 包是一个轻量级 JS 包装器,它需要本地 `dh` 二进制文件才能工作。
8
-
9
- ### 1. 先安装 `dh` 二进制文件(必须)
10
-
11
- 选择以下任一方式:
12
-
13
- #### 方式 A:安装 DeepHarness Desktop
14
-
15
- 从发布页下载并安装桌面应用,它会自动将 `dh` 放入 PATH:
7
+ `deepharness` npm 包会在安装时自动下载对应平台的原生 `dh` 二进制文件。
16
8
 
17
9
  ```bash
18
- https://github.com/deepharness/deepharness-ent-desktop
19
- ```
20
-
21
- #### 方式 B:从源码编译(需要 Rust 工具链)
22
-
23
- ```bash
24
- git clone https://github.com/deepharness/deepharness-ent-desktop.git
25
- cd deepharness-ent-desktop
26
- cargo build --release -p deepharness-cli
27
-
28
- # 安装到用户级可执行目录
29
- mkdir -p ~/.local/bin
30
- cp target/release/dh ~/.local/bin/dh
31
-
32
- # 确保 ~/.local/bin 在 PATH 中
33
- export PATH="$HOME/.local/bin:$PATH"
10
+ npm install -g deepharness
34
11
  ```
35
12
 
36
- ### 2. 再安装 npm 包
13
+ 安装完成后验证:
37
14
 
38
15
  ```bash
39
- # 全局安装(推荐安装到用户目录,避免权限问题)
40
- npm install -g deepharness
16
+ dh --version
41
17
  ```
42
18
 
43
- > **权限问题?** 如果看到 `EACCES` 错误,说明 npm 默认全局目录需要 root 权限。推荐以下两种方案:
19
+ ### 权限问题
44
20
 
45
- #### 方案 1:更改 npm 全局目录到用户主目录
21
+ 如果 `npm install -g` 出现 `EACCES` 权限错误,推荐将 npm 全局目录改到用户主目录:
46
22
 
47
23
  ```bash
48
24
  mkdir -p ~/.npm-global
49
25
  npm config set prefix '~/.npm-global'
50
26
  export PATH="$HOME/.npm-global/bin:$PATH"
51
- # 将上面这行 export 也加入 ~/.bashrc 或 ~/.zshrc
27
+ # 将上面 export 加入 ~/.bashrc 或 ~/.zshrc
52
28
 
53
29
  npm install -g deepharness
54
30
  ```
55
31
 
56
- #### 方案 2:使用 `npx`(无需全局安装)
32
+ 或者使用 `npx`(无需全局安装):
57
33
 
58
34
  ```bash
59
35
  npx deepharness --version
60
36
  ```
61
37
 
62
- ### 3. 验证安装
63
-
64
- ```bash
65
- dh --version
66
- ```
38
+ ### 手动指定二进制路径
67
39
 
68
- 如果包装器找不到二进制文件,可以通过环境变量显式指定:
40
+ 如果自动下载失败,或你想使用自己编译的 `dh`,可通过环境变量指定:
69
41
 
70
42
  ```bash
71
43
  export DH_BINARY_PATH=/path/to/dh
72
44
  dh --version
73
45
  ```
74
46
 
47
+ ## 支持的平台
48
+
49
+ 安装脚本会根据当前系统自动下载对应二进制:
50
+
51
+ - Linux x64 / arm64
52
+ - macOS x64 / arm64
53
+ - Windows x64
54
+
75
55
  ## 使用
76
56
 
77
57
  ```bash
package/bin/dh.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn, execSync } from 'child_process';
3
- import { existsSync, realpathSync } from 'fs';
3
+ import { existsSync, realpathSync, readFileSync } from 'fs';
4
4
  import { dirname, join, resolve } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { homedir } from 'os';
@@ -8,7 +8,7 @@ import { homedir } from 'os';
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = dirname(__filename);
10
10
 
11
- const SYSTEM_INSTALL_URL = 'https://github.com/deepharness/deepharness-ent-desktop';
11
+ const SYSTEM_INSTALL_URL = 'https://github.com/WraithN/deepharness-ent-desktop';
12
12
 
13
13
  /**
14
14
  * Resolve the real path of the wrapper script, following symlinks created by
@@ -22,13 +22,13 @@ function resolveWrapperPath() {
22
22
  }
23
23
  }
24
24
 
25
+ const WRAPPER_PATH = resolveWrapperPath();
26
+
25
27
  /**
26
28
  * Check whether a candidate path resolves to this wrapper script itself.
27
29
  * This prevents `which dh` from returning the npm-installed JS wrapper and
28
30
  * causing an infinite subprocess loop.
29
31
  */
30
- const WRAPPER_PATH = resolveWrapperPath();
31
-
32
32
  function isWrapperItself(candidate) {
33
33
  try {
34
34
  return realpathSync(candidate) === WRAPPER_PATH;
@@ -59,7 +59,7 @@ function findProjectRoot() {
59
59
  }
60
60
 
61
61
  /**
62
- * Build a list of candidate paths where the `dh` native binary may live.
62
+ * Build a list of candidate paths where the `dh` binary may live.
63
63
  */
64
64
  function buildSearchPaths() {
65
65
  const paths = [];
@@ -113,6 +113,16 @@ function findDhBinary() {
113
113
  return null;
114
114
  }
115
115
 
116
+ function readPackageVersion() {
117
+ try {
118
+ const pkgPath = join(__dirname, '..', 'package.json');
119
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
120
+ return pkg.version;
121
+ } catch {
122
+ return null;
123
+ }
124
+ }
125
+
116
126
  function printInstallInstructions() {
117
127
  console.error('Error: `dh` binary not found.');
118
128
  console.error('');
@@ -131,22 +141,44 @@ function printInstallInstructions() {
131
141
  console.error(' export DH_BINARY_PATH=/path/to/dh');
132
142
  }
133
143
 
134
- const dhBin = findDhBinary();
144
+ async function ensureDhBinary() {
145
+ const existing = findDhBinary();
146
+ if (existing) return existing;
147
+
148
+ // Try to download the binary from GitHub release on first use.
149
+ try {
150
+ const { downloadDhBinary } = await import('../scripts/download-binary.js');
151
+ const version = readPackageVersion();
152
+ if (!version) {
153
+ throw new Error('Cannot determine package version');
154
+ }
155
+ return await downloadDhBinary(version);
156
+ } catch (err) {
157
+ console.error(`[deepharness] Failed to download dh binary: ${err.message}`);
158
+ return null;
159
+ }
160
+ }
161
+
162
+ async function main() {
163
+ const dhBin = await ensureDhBinary();
164
+
165
+ if (!dhBin) {
166
+ printInstallInstructions();
167
+ process.exit(1);
168
+ }
135
169
 
136
- if (!dhBin) {
137
- printInstallInstructions();
138
- process.exit(1);
170
+ const args = process.argv.slice(2);
171
+ const proc = spawn(dhBin, args, {
172
+ stdio: 'inherit',
173
+ cwd: process.cwd(),
174
+ env: { ...process.env, DH_NPM_WRAPPER: '1' },
175
+ });
176
+
177
+ proc.on('exit', (code) => process.exit(code ?? 1));
178
+ proc.on('error', (err) => {
179
+ console.error(`Failed to start dh from ${dhBin}:`, err.message);
180
+ process.exit(1);
181
+ });
139
182
  }
140
183
 
141
- const args = process.argv.slice(2);
142
- const proc = spawn(dhBin, args, {
143
- stdio: 'inherit',
144
- cwd: process.cwd(),
145
- env: { ...process.env, DH_NPM_WRAPPER: '1' },
146
- });
147
-
148
- proc.on('exit', (code) => process.exit(code ?? 1));
149
- proc.on('error', (err) => {
150
- console.error(`Failed to start dh from ${dhBin}:`, err.message);
151
- process.exit(1);
152
- });
184
+ main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepharness",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
5
  "description": "DeepHarness CLI - Connect to AI coding agents with Claude Code, OpenCode, and more",
6
6
  "bin": {
@@ -27,17 +27,20 @@
27
27
  ],
28
28
  "author": "DeepHarness Team",
29
29
  "license": "MIT",
30
+ "dependencies": {
31
+ "undici": "^6.0.0"
32
+ },
30
33
  "engines": {
31
34
  "node": ">= 18"
32
35
  },
33
36
  "repository": {
34
37
  "type": "git",
35
- "url": "git+https://github.com/deepharness/deepharness-ent-desktop.git"
38
+ "url": "git+https://github.com/WraithN/deepharness-ent-desktop.git"
36
39
  },
37
40
  "bugs": {
38
- "url": "https://github.com/deepharness/deepharness-ent-desktop/issues"
41
+ "url": "https://github.com/WraithN/deepharness-ent-desktop/issues"
39
42
  },
40
- "homepage": "https://github.com/deepharness/deepharness-ent-desktop",
43
+ "homepage": "https://github.com/WraithN/deepharness-ent-desktop",
41
44
  "publishConfig": {
42
45
  "access": "public"
43
46
  }
@@ -0,0 +1,139 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Download the native `dh` binary from the GitHub release that matches the
4
+ * current platform and architecture.
5
+ */
6
+ import { existsSync, mkdirSync, writeFileSync, chmodSync, readFileSync } from 'fs';
7
+ import { dirname, join } from 'path';
8
+ import { homedir } from 'os';
9
+ import { fileURLToPath } from 'url';
10
+
11
+ const GITHUB_OWNER = 'WraithN';
12
+ const GITHUB_REPO = 'deepharness-ent-desktop';
13
+ const DOWNLOAD_TIMEOUT_MS = 60_000;
14
+
15
+ const PLATFORM_ASSET_NAMES = {
16
+ 'linux:x64': 'dh-linux-x64',
17
+ 'linux:arm64': 'dh-linux-arm64',
18
+ 'darwin:x64': 'dh-darwin-x64',
19
+ 'darwin:arm64': 'dh-darwin-arm64',
20
+ 'win32:x64': 'dh-windows-x64.exe',
21
+ };
22
+
23
+ function getProxyUrl() {
24
+ return process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || null;
25
+ }
26
+
27
+ async function fetchWithProxy(url) {
28
+ const proxyUrl = getProxyUrl();
29
+ const controller = new AbortController();
30
+ const timeoutId = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT_MS);
31
+
32
+ try {
33
+ if (proxyUrl) {
34
+ try {
35
+ const { ProxyAgent } = await import('undici');
36
+ return await fetch(url, {
37
+ signal: controller.signal,
38
+ dispatcher: new ProxyAgent(proxyUrl),
39
+ });
40
+ } catch (err) {
41
+ // If undici/ProxyAgent fails, fall back to default fetch so users
42
+ // without a problematic proxy still work.
43
+ console.warn(`[deepharness] Proxy download failed (${err.message}), retrying without proxy.`);
44
+ }
45
+ }
46
+
47
+ return await fetch(url, { signal: controller.signal });
48
+ } finally {
49
+ clearTimeout(timeoutId);
50
+ }
51
+ }
52
+
53
+ function getPackageVersion() {
54
+ const __filename = fileURLToPath(import.meta.url);
55
+ const packageJsonPath = join(dirname(__filename), '..', 'package.json');
56
+ try {
57
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
58
+ return pkg.version;
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+
64
+ function getAssetName() {
65
+ const key = `${process.platform}:${process.arch}`;
66
+ const assetName = PLATFORM_ASSET_NAMES[key];
67
+ if (!assetName) {
68
+ throw new Error(`Unsupported platform/architecture: ${key}. Supported platforms: ${Object.keys(PLATFORM_ASSET_NAMES).join(', ')}`);
69
+ }
70
+ return assetName;
71
+ }
72
+
73
+ function getBinaryName() {
74
+ return process.platform === 'win32' ? 'dh.exe' : 'dh';
75
+ }
76
+
77
+ function getInstallDir() {
78
+ return join(homedir(), '.local', 'bin');
79
+ }
80
+
81
+ function getBinaryPath() {
82
+ return join(getInstallDir(), getBinaryName());
83
+ }
84
+
85
+ function getDownloadUrl(version, assetName) {
86
+ return `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}/releases/download/dh-v${version}/${assetName}`;
87
+ }
88
+
89
+ /**
90
+ * Download the binary for the current platform and install it to ~/.local/bin.
91
+ * Returns the path to the installed binary.
92
+ */
93
+ export async function downloadDhBinary(version) {
94
+ const assetName = getAssetName();
95
+ const binaryPath = getBinaryPath();
96
+ const downloadUrl = getDownloadUrl(version, assetName);
97
+
98
+ console.log(`[deepharness] Downloading dh ${version} for ${process.platform}-${process.arch}...`);
99
+ console.log(`[deepharness] URL: ${downloadUrl}`);
100
+
101
+ const response = await fetchWithProxy(downloadUrl);
102
+ if (!response.ok) {
103
+ throw new Error(`Download failed: ${response.status} ${response.statusText} (${downloadUrl})`);
104
+ }
105
+
106
+ const buffer = Buffer.from(await response.arrayBuffer());
107
+
108
+ mkdirSync(getInstallDir(), { recursive: true });
109
+ writeFileSync(binaryPath, buffer);
110
+
111
+ if (process.platform !== 'win32') {
112
+ chmodSync(binaryPath, 0o755);
113
+ }
114
+
115
+ console.log(`[deepharness] Installed dh to ${binaryPath}`);
116
+ return binaryPath;
117
+ }
118
+
119
+ /**
120
+ * Check whether the installed binary is present.
121
+ */
122
+ export function isBinaryInstalled() {
123
+ return existsSync(getBinaryPath());
124
+ }
125
+
126
+ export { getBinaryPath, getPackageVersion };
127
+
128
+ // CLI entry point for testing or manual download.
129
+ if (import.meta.url === `file://${process.argv[1]}`) {
130
+ const version = process.argv[2] || getPackageVersion();
131
+ if (!version) {
132
+ console.error('Usage: node download-binary.js <version>');
133
+ process.exit(1);
134
+ }
135
+ downloadDhBinary(version).catch((err) => {
136
+ console.error(err.message);
137
+ process.exit(1);
138
+ });
139
+ }
@@ -4,10 +4,10 @@
4
4
  *
5
5
  * The npm package is only a wrapper; it needs the native `dh` binary to be
6
6
  * present on the system. This script verifies that the binary exists and,
7
- * when the package is installed from the source repository, attempts to build
8
- * it automatically so that `dh --version` works immediately after `npm link`.
7
+ * when possible, installs it automatically by downloading from the matching
8
+ * GitHub release or by building from source.
9
9
  */
10
- import { existsSync, realpathSync } from 'fs';
10
+ import { existsSync, realpathSync, readFileSync } from 'fs';
11
11
  import { dirname, join } from 'path';
12
12
  import { fileURLToPath } from 'url';
13
13
  import { homedir } from 'os';
@@ -16,7 +16,7 @@ import { execSync } from 'child_process';
16
16
  const __filename = fileURLToPath(import.meta.url);
17
17
  const __dirname = dirname(__filename);
18
18
 
19
- const SYSTEM_INSTALL_URL = 'https://github.com/deepharness/deepharness-ent-desktop';
19
+ const SYSTEM_INSTALL_URL = 'https://github.com/WraithN/deepharness-ent-desktop';
20
20
 
21
21
  function resolveWrapperPath() {
22
22
  try {
@@ -72,8 +72,14 @@ function findExistingDhBinary() {
72
72
  return null;
73
73
  }
74
74
 
75
- function isBinaryUsable() {
76
- return !!findExistingDhBinary();
75
+ function readPackageVersion() {
76
+ try {
77
+ const pkgPath = join(__dirname, '..', 'package.json');
78
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
79
+ return pkg.version;
80
+ } catch {
81
+ return null;
82
+ }
77
83
  }
78
84
 
79
85
  function buildFromSource(projectRoot) {
@@ -84,22 +90,32 @@ function buildFromSource(projectRoot) {
84
90
  stdio: 'inherit',
85
91
  });
86
92
  return true;
87
- } catch (err) {
93
+ } catch {
88
94
  console.error('[deepharness] Failed to build dh from source.');
89
95
  return false;
90
96
  }
91
97
  }
92
98
 
93
- function main() {
99
+ async function downloadBinary() {
100
+ const { downloadDhBinary } = await import('./download-binary.js');
101
+ const version = readPackageVersion();
102
+ if (!version) {
103
+ throw new Error('Cannot determine package version');
104
+ }
105
+ return downloadDhBinary(version);
106
+ }
107
+
108
+ async function main() {
94
109
  // If a usable binary already exists, nothing more to do.
95
- if (isBinaryUsable()) {
96
- const location = findExistingDhBinary();
97
- console.log(`[deepharness] Found dh binary at ${location}`);
110
+ const existing = findExistingDhBinary();
111
+ if (existing) {
112
+ console.log(`[deepharness] Found dh binary at ${existing}`);
98
113
  return;
99
114
  }
100
115
 
101
116
  const projectRoot = findProjectRoot();
102
117
 
118
+ // Prefer building from source when installed inside the repository.
103
119
  if (projectRoot) {
104
120
  console.log('[deepharness] dh binary not found; attempting to build from source...');
105
121
  const built = buildFromSource(projectRoot);
@@ -114,10 +130,20 @@ function main() {
114
130
  }
115
131
  }
116
132
 
133
+ // Otherwise try to download the matching release binary.
134
+ try {
135
+ await downloadBinary();
136
+ return;
137
+ } catch (err) {
138
+ console.warn(`[deepharness] Could not download binary: ${err.message}`);
139
+ }
140
+
117
141
  console.warn('[deepharness] The native `dh` binary is not installed.');
118
142
  console.warn('[deepharness] The `dh` command will not work until it is available.');
119
143
  console.warn(`[deepharness] Install DeepHarness Desktop from: ${SYSTEM_INSTALL_URL}`);
120
144
  console.warn('[deepharness] Or build from source: cargo build --release -p deepharness-cli');
121
145
  }
122
146
 
123
- main();
147
+ main().catch((err) => {
148
+ console.warn(`[deepharness] Post-install hook failed: ${err.message}`);
149
+ });