@hmharness/cli 0.1.0
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/dist/main.d.ts +2 -0
- package/dist/main.js +641 -0
- package/dist/prompt.d.ts +7 -0
- package/dist/prompt.js +14 -0
- package/dist/runner.d.ts +68 -0
- package/dist/runner.js +179 -0
- package/dist/spawn.d.ts +29 -0
- package/dist/spawn.js +64 -0
- package/dist/tools.d.ts +7 -0
- package/dist/tools.js +137 -0
- package/dist/tui.d.ts +110 -0
- package/dist/tui.js +1085 -0
- package/dist/web-daemon.d.ts +15 -0
- package/dist/web-daemon.js +106 -0
- package/package.json +53 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const DEFAULT_WEB_PORT = 7788;
|
|
2
|
+
export declare function readWebPid(): number;
|
|
3
|
+
/** cheap probe: does OUR server answer on the port (not just any listener)? */
|
|
4
|
+
export declare function hmhWebUp(port: number): Promise<boolean>;
|
|
5
|
+
export declare function stopWebDaemon(): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Idempotent: if our web UI is already up (pid file alive, or the port
|
|
8
|
+
* answers as hmh), keep it; otherwise spawn it and wait up to ~6s for
|
|
9
|
+
* readiness. Returns true when the web UI is usable.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ensureWebDaemon(port?: number, entry?: string): Promise<boolean>;
|
|
12
|
+
export declare function startWebDaemon(port?: number, entry?: string): {
|
|
13
|
+
already: boolean;
|
|
14
|
+
pid: number;
|
|
15
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @hmharness/cli - web daemon helpers
|
|
3
|
+
* Shared by `hmh web start|stop|status` and the TUI auto-link (hmh tui
|
|
4
|
+
* brings the web UI up unless --no-web). One pid file + one log file under
|
|
5
|
+
* HMH_HOME; the daemon runs detached with no window and survives terminals.
|
|
6
|
+
*/
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { openSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { homeDir } from '@hmharness/kernel';
|
|
11
|
+
export const DEFAULT_WEB_PORT = 7788;
|
|
12
|
+
export function readWebPid() {
|
|
13
|
+
try {
|
|
14
|
+
const p = Number(readFileSync(join(homeDir(), 'web.pid'), 'utf8').trim());
|
|
15
|
+
return Number.isFinite(p) && p > 0 ? p : 0;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function alive(pid) {
|
|
22
|
+
try {
|
|
23
|
+
process.kill(pid, 0);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/** cheap probe: does OUR server answer on the port (not just any listener)? */
|
|
31
|
+
export async function hmhWebUp(port) {
|
|
32
|
+
try {
|
|
33
|
+
const r = await fetch(`http://127.0.0.1:${port}/api/state`, { signal: AbortSignal.timeout(1500) });
|
|
34
|
+
if (!r.ok)
|
|
35
|
+
return false;
|
|
36
|
+
const d = (await r.json());
|
|
37
|
+
return typeof d.model === 'string';
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function stopWebDaemon() {
|
|
44
|
+
const pid = readWebPid();
|
|
45
|
+
if (!pid || !alive(pid)) {
|
|
46
|
+
try {
|
|
47
|
+
unlinkSync(join(homeDir(), 'web.pid'));
|
|
48
|
+
}
|
|
49
|
+
catch { /* absent */ }
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
if (process.platform === 'win32')
|
|
53
|
+
spawn('taskkill', ['/PID', String(pid), '/T', '/F'], { windowsHide: true });
|
|
54
|
+
else {
|
|
55
|
+
try {
|
|
56
|
+
process.kill(pid);
|
|
57
|
+
}
|
|
58
|
+
catch { /* gone */ }
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
unlinkSync(join(homeDir(), 'web.pid'));
|
|
62
|
+
}
|
|
63
|
+
catch { /* absent */ }
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
/** Spawn the daemon (no window, detached). Returns the pid. */
|
|
67
|
+
function spawnWebDaemon(port, entry = process.argv[1]) {
|
|
68
|
+
const home = homeDir();
|
|
69
|
+
const log = openSync(join(home, 'web.log'), 'a');
|
|
70
|
+
const child = spawn(process.execPath, [entry, 'web', `--port=${port}`], {
|
|
71
|
+
detached: true,
|
|
72
|
+
stdio: ['ignore', log, log],
|
|
73
|
+
windowsHide: true,
|
|
74
|
+
cwd: process.cwd(),
|
|
75
|
+
});
|
|
76
|
+
child.unref();
|
|
77
|
+
const pid = child.pid ?? 0;
|
|
78
|
+
if (pid > 0)
|
|
79
|
+
writeFileSync(join(home, 'web.pid'), String(pid));
|
|
80
|
+
return pid;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Idempotent: if our web UI is already up (pid file alive, or the port
|
|
84
|
+
* answers as hmh), keep it; otherwise spawn it and wait up to ~6s for
|
|
85
|
+
* readiness. Returns true when the web UI is usable.
|
|
86
|
+
*/
|
|
87
|
+
export async function ensureWebDaemon(port = DEFAULT_WEB_PORT, entry = process.argv[1]) {
|
|
88
|
+
const pid = readWebPid();
|
|
89
|
+
if (pid && alive(pid))
|
|
90
|
+
return true;
|
|
91
|
+
if (await hmhWebUp(port))
|
|
92
|
+
return true;
|
|
93
|
+
spawnWebDaemon(port, entry);
|
|
94
|
+
for (let i = 0; i < 12; i++) {
|
|
95
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
96
|
+
if (await hmhWebUp(port))
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
export function startWebDaemon(port = DEFAULT_WEB_PORT, entry = process.argv[1]) {
|
|
102
|
+
const pid = readWebPid();
|
|
103
|
+
if (pid && alive(pid))
|
|
104
|
+
return { already: true, pid };
|
|
105
|
+
return { already: false, pid: spawnWebDaemon(port, entry) };
|
|
106
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hmharness/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "hmharness command line: one-shot tasks, an interactive REPL, a fullscreen TUI, the web frontend, and direct tool invocation.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/main.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/main.d.ts",
|
|
10
|
+
"default": "./dist/main.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"types": "dist/main.d.ts",
|
|
14
|
+
"bin": {
|
|
15
|
+
"hmh": "./dist/main.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"harmonyos",
|
|
25
|
+
"openharmony",
|
|
26
|
+
"ohos",
|
|
27
|
+
"agent",
|
|
28
|
+
"ai",
|
|
29
|
+
"cli",
|
|
30
|
+
"coding-agent",
|
|
31
|
+
"arkts",
|
|
32
|
+
"self-evolving",
|
|
33
|
+
"mcp"
|
|
34
|
+
],
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/swsgbl/hmharness.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://swsgbl.github.io/hmharness/",
|
|
40
|
+
"bugs": "https://github.com/swsgbl/hmharness/issues",
|
|
41
|
+
"license": "Apache-2.0",
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsc -p tsconfig.build.json"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@hmharness/kernel": "0.1.0",
|
|
47
|
+
"@hmharness/evolution": "0.1.0",
|
|
48
|
+
"@hmharness/domain-harmony": "0.1.0",
|
|
49
|
+
"@hmharness/domain-ops": "0.1.0",
|
|
50
|
+
"@hmharness/agent": "0.1.0",
|
|
51
|
+
"@hmharness/web": "0.1.0"
|
|
52
|
+
}
|
|
53
|
+
}
|