deepharness 0.0.1 → 0.0.2
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 +62 -3
- package/bin/dh.js +95 -19
- package/package.json +9 -2
- package/scripts/postinstall.js +123 -0
package/README.md
CHANGED
|
@@ -4,14 +4,73 @@
|
|
|
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:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
https://github.com/deepharness/deepharness-ent-desktop
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
#### 方式 B:从源码编译(需要 Rust 工具链)
|
|
22
|
+
|
|
7
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"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### 2. 再安装 npm 包
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 全局安装(推荐安装到用户目录,避免权限问题)
|
|
8
40
|
npm install -g deepharness
|
|
9
41
|
```
|
|
10
42
|
|
|
11
|
-
|
|
43
|
+
> **权限问题?** 如果看到 `EACCES` 错误,说明 npm 默认全局目录需要 root 权限。推荐以下两种方案:
|
|
44
|
+
|
|
45
|
+
#### 方案 1:更改 npm 全局目录到用户主目录
|
|
12
46
|
|
|
13
|
-
|
|
14
|
-
-
|
|
47
|
+
```bash
|
|
48
|
+
mkdir -p ~/.npm-global
|
|
49
|
+
npm config set prefix '~/.npm-global'
|
|
50
|
+
export PATH="$HOME/.npm-global/bin:$PATH"
|
|
51
|
+
# 将上面这行 export 也加入 ~/.bashrc 或 ~/.zshrc
|
|
52
|
+
|
|
53
|
+
npm install -g deepharness
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
#### 方案 2:使用 `npx`(无需全局安装)
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx deepharness --version
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. 验证安装
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
dh --version
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
如果包装器找不到二进制文件,可以通过环境变量显式指定:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
export DH_BINARY_PATH=/path/to/dh
|
|
72
|
+
dh --version
|
|
73
|
+
```
|
|
15
74
|
|
|
16
75
|
## 使用
|
|
17
76
|
|
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 } from 'fs';
|
|
3
|
+
import { existsSync, realpathSync } from 'fs';
|
|
4
4
|
import { dirname, join, resolve } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { homedir } from 'os';
|
|
@@ -8,8 +8,44 @@ 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';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the real path of the wrapper script, following symlinks created by
|
|
15
|
+
* `npm link` or package managers.
|
|
16
|
+
*/
|
|
17
|
+
function resolveWrapperPath() {
|
|
18
|
+
try {
|
|
19
|
+
return realpathSync(__filename);
|
|
20
|
+
} catch {
|
|
21
|
+
return __filename;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check whether a candidate path resolves to this wrapper script itself.
|
|
27
|
+
* This prevents `which dh` from returning the npm-installed JS wrapper and
|
|
28
|
+
* causing an infinite subprocess loop.
|
|
29
|
+
*/
|
|
30
|
+
const WRAPPER_PATH = resolveWrapperPath();
|
|
31
|
+
|
|
32
|
+
function isWrapperItself(candidate) {
|
|
33
|
+
try {
|
|
34
|
+
return realpathSync(candidate) === WRAPPER_PATH;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Find the project root when the npm package is installed directly inside the
|
|
42
|
+
* source repository (development / `npm link`). Returns `null` when installed
|
|
43
|
+
* from the registry.
|
|
44
|
+
*/
|
|
11
45
|
function findProjectRoot() {
|
|
12
|
-
|
|
46
|
+
const wrapperPath = resolveWrapperPath();
|
|
47
|
+
let current = dirname(wrapperPath);
|
|
48
|
+
|
|
13
49
|
while (true) {
|
|
14
50
|
if (existsSync(join(current, 'Cargo.toml')) && existsSync(join(current, 'package.json'))) {
|
|
15
51
|
return current;
|
|
@@ -22,43 +58,83 @@ function findProjectRoot() {
|
|
|
22
58
|
}
|
|
23
59
|
}
|
|
24
60
|
|
|
25
|
-
|
|
26
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Build a list of candidate paths where the `dh` native binary may live.
|
|
63
|
+
*/
|
|
64
|
+
function buildSearchPaths() {
|
|
65
|
+
const paths = [];
|
|
27
66
|
|
|
67
|
+
// 1. Explicit override from environment.
|
|
68
|
+
if (process.env.DH_BINARY_PATH) {
|
|
69
|
+
paths.push(resolve(process.env.DH_BINARY_PATH));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2. Project-local builds when developing from source.
|
|
28
73
|
try {
|
|
29
74
|
const projectRoot = findProjectRoot();
|
|
30
75
|
if (projectRoot) {
|
|
31
|
-
|
|
32
|
-
|
|
76
|
+
paths.push(join(projectRoot, 'target', 'release', 'dh'));
|
|
77
|
+
paths.push(join(projectRoot, 'target', 'debug', 'dh'));
|
|
33
78
|
}
|
|
34
79
|
} catch {}
|
|
35
80
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
'/usr/bin/dh',
|
|
81
|
+
// 3. Common user-level install locations.
|
|
82
|
+
paths.push(
|
|
39
83
|
join(homedir(), '.local', 'bin', 'dh'),
|
|
40
84
|
join(homedir(), '.cargo', 'bin', 'dh'),
|
|
41
85
|
);
|
|
42
86
|
|
|
43
|
-
|
|
44
|
-
|
|
87
|
+
// 4. System-wide install locations.
|
|
88
|
+
paths.push('/usr/local/bin/dh', '/usr/bin/dh');
|
|
89
|
+
|
|
90
|
+
return paths;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Locate the `dh` binary, or return `null` if it cannot be found.
|
|
95
|
+
*/
|
|
96
|
+
function findDhBinary() {
|
|
97
|
+
for (const p of buildSearchPaths()) {
|
|
98
|
+
if (existsSync(p) && !isWrapperItself(p)) return p;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Fallback: rely on the user's PATH. Skip the fallback when we are already
|
|
102
|
+
// inside a wrapper-spawned process to avoid infinite recursion if `which dh`
|
|
103
|
+
// points back at this wrapper.
|
|
104
|
+
if (process.env.DH_NPM_WRAPPER === '1') {
|
|
105
|
+
return null;
|
|
45
106
|
}
|
|
46
107
|
|
|
47
108
|
try {
|
|
48
109
|
const which = execSync('which dh', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
49
|
-
if (which && existsSync(which)) return which;
|
|
110
|
+
if (which && existsSync(which) && !isWrapperItself(which)) return which;
|
|
50
111
|
} catch {}
|
|
51
112
|
|
|
52
113
|
return null;
|
|
53
114
|
}
|
|
54
115
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!dhBin) {
|
|
116
|
+
function printInstallInstructions() {
|
|
58
117
|
console.error('Error: `dh` binary not found.');
|
|
59
118
|
console.error('');
|
|
60
|
-
console.error('
|
|
61
|
-
console.error('
|
|
119
|
+
console.error('The `deepharness` npm package is a thin wrapper around the native `dh` binary.');
|
|
120
|
+
console.error('Please install the binary using one of the following methods:');
|
|
121
|
+
console.error('');
|
|
122
|
+
console.error(' 1. Install DeepHarness Desktop:');
|
|
123
|
+
console.error(` ${SYSTEM_INSTALL_URL}`);
|
|
124
|
+
console.error('');
|
|
125
|
+
console.error(' 2. Build and install from source (requires Rust):');
|
|
126
|
+
console.error(' cargo build --release -p deepharness-cli');
|
|
127
|
+
console.error(' mkdir -p ~/.local/bin');
|
|
128
|
+
console.error(' cp target/release/dh ~/.local/bin/dh');
|
|
129
|
+
console.error('');
|
|
130
|
+
console.error(' 3. If the binary is already installed in a non-standard location:');
|
|
131
|
+
console.error(' export DH_BINARY_PATH=/path/to/dh');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const dhBin = findDhBinary();
|
|
135
|
+
|
|
136
|
+
if (!dhBin) {
|
|
137
|
+
printInstallInstructions();
|
|
62
138
|
process.exit(1);
|
|
63
139
|
}
|
|
64
140
|
|
|
@@ -69,8 +145,8 @@ const proc = spawn(dhBin, args, {
|
|
|
69
145
|
env: { ...process.env, DH_NPM_WRAPPER: '1' },
|
|
70
146
|
});
|
|
71
147
|
|
|
72
|
-
proc.on('exit', (code) => process.exit(code));
|
|
148
|
+
proc.on('exit', (code) => process.exit(code ?? 1));
|
|
73
149
|
proc.on('error', (err) => {
|
|
74
|
-
console.error(
|
|
150
|
+
console.error(`Failed to start dh from ${dhBin}:`, err.message);
|
|
75
151
|
process.exit(1);
|
|
76
152
|
});
|
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "deepharness",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DeepHarness CLI - Connect to AI coding agents with Claude Code, OpenCode, and more",
|
|
6
6
|
"bin": {
|
|
7
7
|
"dh": "bin/dh.js"
|
|
8
8
|
},
|
|
9
|
-
"scripts": {
|
|
9
|
+
"scripts": {
|
|
10
|
+
"postinstall": "node scripts/postinstall.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"scripts/",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
10
17
|
"keywords": [
|
|
11
18
|
"ai",
|
|
12
19
|
"coding",
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Post-install hook for the `deepharness` npm package.
|
|
4
|
+
*
|
|
5
|
+
* The npm package is only a wrapper; it needs the native `dh` binary to be
|
|
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`.
|
|
9
|
+
*/
|
|
10
|
+
import { existsSync, realpathSync } from 'fs';
|
|
11
|
+
import { dirname, join } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { homedir } from 'os';
|
|
14
|
+
import { execSync } from 'child_process';
|
|
15
|
+
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = dirname(__filename);
|
|
18
|
+
|
|
19
|
+
const SYSTEM_INSTALL_URL = 'https://github.com/deepharness/deepharness-ent-desktop';
|
|
20
|
+
|
|
21
|
+
function resolveWrapperPath() {
|
|
22
|
+
try {
|
|
23
|
+
return realpathSync(__filename);
|
|
24
|
+
} catch {
|
|
25
|
+
return __filename;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const WRAPPER_PATH = resolveWrapperPath();
|
|
30
|
+
|
|
31
|
+
function isWrapperItself(candidate) {
|
|
32
|
+
try {
|
|
33
|
+
return realpathSync(candidate) === WRAPPER_PATH;
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function findProjectRoot() {
|
|
40
|
+
const wrapperPath = resolveWrapperPath();
|
|
41
|
+
let current = dirname(wrapperPath);
|
|
42
|
+
|
|
43
|
+
while (true) {
|
|
44
|
+
if (existsSync(join(current, 'Cargo.toml')) && existsSync(join(current, 'package.json'))) {
|
|
45
|
+
return current;
|
|
46
|
+
}
|
|
47
|
+
const parent = dirname(current);
|
|
48
|
+
if (parent === current) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
current = parent;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function findExistingDhBinary() {
|
|
56
|
+
const candidates = [
|
|
57
|
+
join(homedir(), '.local', 'bin', 'dh'),
|
|
58
|
+
join(homedir(), '.cargo', 'bin', 'dh'),
|
|
59
|
+
'/usr/local/bin/dh',
|
|
60
|
+
'/usr/bin/dh',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
for (const p of candidates) {
|
|
64
|
+
if (existsSync(p) && !isWrapperItself(p)) return p;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const which = execSync('which dh', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
69
|
+
if (which && existsSync(which) && !isWrapperItself(which)) return which;
|
|
70
|
+
} catch {}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isBinaryUsable() {
|
|
76
|
+
return !!findExistingDhBinary();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function buildFromSource(projectRoot) {
|
|
80
|
+
console.log(`[deepharness] Building native dh binary from ${projectRoot}...`);
|
|
81
|
+
try {
|
|
82
|
+
execSync('cargo build --release -p deepharness-cli', {
|
|
83
|
+
cwd: projectRoot,
|
|
84
|
+
stdio: 'inherit',
|
|
85
|
+
});
|
|
86
|
+
return true;
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error('[deepharness] Failed to build dh from source.');
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function main() {
|
|
94
|
+
// 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}`);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const projectRoot = findProjectRoot();
|
|
102
|
+
|
|
103
|
+
if (projectRoot) {
|
|
104
|
+
console.log('[deepharness] dh binary not found; attempting to build from source...');
|
|
105
|
+
const built = buildFromSource(projectRoot);
|
|
106
|
+
if (built) {
|
|
107
|
+
const releaseBinary = join(projectRoot, 'target', 'release', 'dh');
|
|
108
|
+
if (existsSync(releaseBinary)) {
|
|
109
|
+
console.log(`[deepharness] Built dh binary at ${releaseBinary}`);
|
|
110
|
+
console.log('[deepharness] Add it to your PATH to use the `dh` command globally:');
|
|
111
|
+
console.log(` export PATH="${join(projectRoot, 'target', 'release')}:$PATH"`);
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.warn('[deepharness] The native `dh` binary is not installed.');
|
|
118
|
+
console.warn('[deepharness] The `dh` command will not work until it is available.');
|
|
119
|
+
console.warn(`[deepharness] Install DeepHarness Desktop from: ${SYSTEM_INSTALL_URL}`);
|
|
120
|
+
console.warn('[deepharness] Or build from source: cargo build --release -p deepharness-cli');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
main();
|