nodus-wechat 0.5.2 → 0.6.1
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 +13 -3
- package/bin/nodus-wechat.js +281 -22
- package/package.json +5 -1
- package/templates/wechat-agent-poc/poc-webhook/server.py +3 -1
- package/templates/wechat-agent-poc/scripts/logs.sh +1 -1
- package/templates/wechat-agent-poc/scripts/start.sh +48 -2
- package/templates/wechat-agent-poc/scripts/status.sh +15 -2
- package/templates/wechat-agent-poc/scripts/stop.sh +10 -1
package/README.md
CHANGED
|
@@ -14,8 +14,10 @@ npx nodus-wechat
|
|
|
14
14
|
npx nodus-wechat setup
|
|
15
15
|
npx nodus-wechat setup --install-hermes
|
|
16
16
|
npx nodus-wechat install-hermes
|
|
17
|
+
npx nodus-wechat install-openilink
|
|
17
18
|
npx nodus-wechat doctor
|
|
18
19
|
npx nodus-wechat start
|
|
20
|
+
npx nodus-wechat start --docker
|
|
19
21
|
npx nodus-wechat status
|
|
20
22
|
npx nodus-wechat logs
|
|
21
23
|
npx nodus-wechat stop
|
|
@@ -35,18 +37,22 @@ npx nodus-wechat setup \
|
|
|
35
37
|
The default gateway base URL is `https://api.nodus.sbs/`.
|
|
36
38
|
Use `--install-hermes` or `install-hermes` to run the official Hermes installer
|
|
37
39
|
with `--skip-setup` and the same Hermes home used by this CLI.
|
|
40
|
+
Use `install-openilink` to install the native OpeniLink Hub CLI (`oih`) through
|
|
41
|
+
the official OpeniLink installer.
|
|
38
42
|
|
|
39
43
|
## Current behavior
|
|
40
44
|
|
|
41
45
|
- Creates local configuration at `~/.nodus-wechat/config.json`.
|
|
42
46
|
- Can install Hermes Agent CLI through the official NousResearch installer.
|
|
47
|
+
- Can install OpeniLink Hub native CLI through the official OpeniLink installer.
|
|
43
48
|
- Installs the OpeniLink + webhook POC runtime at `~/.nodus-wechat/runtime`.
|
|
44
49
|
- Writes Hermes common settings to `~/.hermes/config.yaml`.
|
|
45
50
|
- Writes the AstraGate key to `~/.hermes/.env` as `ASTRAGATE_API_KEY`.
|
|
46
51
|
- Writes runtime `.env`, Docker Compose, webhook server, helper scripts, and the OpeniLink reply plugin.
|
|
47
52
|
- Stores gateway base URL, api key, model, Hermes paths, OpeniLink origin, webhook port, and runtime path.
|
|
48
|
-
- Checks Node.js, local configuration, Hermes files, runtime files, Docker Compose availability, Hermes CLI availability, and WeChat app detection with `doctor`.
|
|
49
|
-
- Starts/stops the local runtime
|
|
53
|
+
- Checks Node.js, local configuration, Hermes files, runtime files, Python, OpeniLink CLI, optional Docker Compose availability, Hermes CLI availability, and WeChat app detection with `doctor`.
|
|
54
|
+
- Starts/stops the local runtime with native local processes by default.
|
|
55
|
+
- Keeps Docker Compose available only when `--docker` is passed.
|
|
50
56
|
- Removes Nodus WeChat config/runtime files with `uninstall --yes`.
|
|
51
57
|
- Cleans Nodus WeChat config/runtime plus generated Hermes settings with `clean --yes`.
|
|
52
58
|
|
|
@@ -69,7 +75,7 @@ the config generated by this CLI.
|
|
|
69
75
|
|
|
70
76
|
## Current non-goals
|
|
71
77
|
|
|
72
|
-
- Does not run a third-party installer unless `--install-hermes` or `install-
|
|
78
|
+
- Does not run a third-party installer unless `--install-hermes`, `install-hermes`, or `install-openilink` is explicitly requested.
|
|
73
79
|
- Does not automate, inject into, read, or control WeChat directly.
|
|
74
80
|
- Does not start a daemon, LaunchAgent, or background worker outside Docker Compose.
|
|
75
81
|
- Does not redeem real CDKs or mutate sub2api accounts; the bundled webhook keeps the existing dry-run POC boundary.
|
|
@@ -106,3 +112,7 @@ npm publish
|
|
|
106
112
|
|
|
107
113
|
The package is configured for public npm access. If npm reports `E401`, run
|
|
108
114
|
`npm login` first.
|
|
115
|
+
|
|
116
|
+
For npm Trusted Publishing, push this package to GitHub and configure npm to
|
|
117
|
+
trust the workflow at `.github/workflows/publish.yml`. The workflow publishes
|
|
118
|
+
with OIDC and does not need an `NPM_TOKEN` secret.
|
package/bin/nodus-wechat.js
CHANGED
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
"use strict";
|
|
4
4
|
|
|
5
5
|
const fs = require("node:fs");
|
|
6
|
+
const http = require("node:http");
|
|
6
7
|
const os = require("node:os");
|
|
7
8
|
const path = require("node:path");
|
|
8
9
|
const childProcess = require("node:child_process");
|
|
9
10
|
|
|
10
|
-
const VERSION = "0.
|
|
11
|
+
const VERSION = "0.6.1";
|
|
11
12
|
const DEFAULT_BASE_URL = "https://api.nodus.sbs/";
|
|
12
13
|
const DEFAULT_MODEL = "gpt-5.5";
|
|
13
14
|
const DEFAULT_OPENILINK_ORIGIN = "http://localhost:9800";
|
|
@@ -40,21 +41,24 @@ Usage:
|
|
|
40
41
|
[--openilink-rp-id <id>] [--webhook-port <port>]
|
|
41
42
|
[--webhook-token <token>] [--install-hermes]
|
|
42
43
|
nodus-wechat install-hermes
|
|
44
|
+
nodus-wechat install-openilink
|
|
43
45
|
nodus-wechat doctor
|
|
44
|
-
nodus-wechat start
|
|
45
|
-
nodus-wechat status
|
|
46
|
-
nodus-wechat logs
|
|
47
|
-
nodus-wechat stop
|
|
46
|
+
nodus-wechat start [--docker]
|
|
47
|
+
nodus-wechat status [--docker]
|
|
48
|
+
nodus-wechat logs [--docker]
|
|
49
|
+
nodus-wechat stop [--docker]
|
|
48
50
|
nodus-wechat uninstall --yes
|
|
49
51
|
nodus-wechat clean --yes
|
|
50
52
|
|
|
51
53
|
Commands:
|
|
52
54
|
setup Create or update local configuration and runtime files. This is the default.
|
|
53
55
|
install-hermes Install Hermes Agent CLI with the official installer.
|
|
56
|
+
install-openilink
|
|
57
|
+
Install OpeniLink Hub native CLI with the official installer.
|
|
54
58
|
doctor Check local prerequisites and configuration.
|
|
55
|
-
start Start
|
|
56
|
-
status Show
|
|
57
|
-
logs Follow
|
|
59
|
+
start Start OpeniLink + webhook with local processes by default.
|
|
60
|
+
status Show local process status.
|
|
61
|
+
logs Follow local runtime logs.
|
|
58
62
|
stop Stop the local runtime.
|
|
59
63
|
uninstall Remove Nodus WeChat config and runtime files.
|
|
60
64
|
clean Stop runtime if possible, then remove Nodus WeChat files and generated Hermes settings.
|
|
@@ -74,7 +78,7 @@ function parseArgs(argv) {
|
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
const key = item.slice(2);
|
|
77
|
-
if (key === "help" || key === "yes" || key === "install-hermes") {
|
|
81
|
+
if (key === "help" || key === "yes" || key === "install-hermes" || key === "docker") {
|
|
78
82
|
result[key] = true;
|
|
79
83
|
continue;
|
|
80
84
|
}
|
|
@@ -235,6 +239,13 @@ function hermesInstallCommand() {
|
|
|
235
239
|
);
|
|
236
240
|
}
|
|
237
241
|
|
|
242
|
+
function openiLinkInstallCommand() {
|
|
243
|
+
return (
|
|
244
|
+
process.env.NODUS_WECHAT_OPENILINK_INSTALL_COMMAND ||
|
|
245
|
+
"curl -fsSL https://raw.githubusercontent.com/openilink/openilink-hub/main/install.sh | sh"
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
238
249
|
function runHermesInstaller(hermesDir) {
|
|
239
250
|
const args = ["--skip-setup", "--hermes-home", hermesDir];
|
|
240
251
|
const command = `${hermesInstallCommand()} ${args.map(shellQuote).join(" ")}`;
|
|
@@ -365,7 +376,8 @@ function setup(options) {
|
|
|
365
376
|
console.log(`Model: ${config.agent.model}`);
|
|
366
377
|
console.log(`OpeniLink Hub: ${config.openilink.publicOrigin}`);
|
|
367
378
|
console.log(`Webhook URL for OpeniLink: http://poc-webhook:${config.webhook.port}/webhook`);
|
|
368
|
-
console.log("
|
|
379
|
+
console.log("Runtime mode: host process by default; Docker is used only with `--docker`.");
|
|
380
|
+
console.log("Run `nodus-wechat start` to start the local host runtime.");
|
|
369
381
|
}
|
|
370
382
|
|
|
371
383
|
function installHermes() {
|
|
@@ -378,6 +390,21 @@ function installHermes() {
|
|
|
378
390
|
return 0;
|
|
379
391
|
}
|
|
380
392
|
|
|
393
|
+
function installOpeniLink() {
|
|
394
|
+
const result = childProcess.spawnSync(openiLinkInstallCommand(), {
|
|
395
|
+
shell: true,
|
|
396
|
+
stdio: "inherit",
|
|
397
|
+
});
|
|
398
|
+
if (result.error) {
|
|
399
|
+
throw result.error;
|
|
400
|
+
}
|
|
401
|
+
if (result.status !== 0) {
|
|
402
|
+
throw new Error(`OpeniLink installer failed with exit code ${result.status}`);
|
|
403
|
+
}
|
|
404
|
+
console.log("OpeniLink installer completed. Run `nodus-wechat start`.");
|
|
405
|
+
return 0;
|
|
406
|
+
}
|
|
407
|
+
|
|
381
408
|
function doctor() {
|
|
382
409
|
let ok = true;
|
|
383
410
|
const major = Number.parseInt(process.versions.node.split(".")[0], 10);
|
|
@@ -417,11 +444,24 @@ function doctor() {
|
|
|
417
444
|
ok = false;
|
|
418
445
|
console.log(`runtime: missing (${config.runtime?.dir || path.join(configHome(), "runtime")})`);
|
|
419
446
|
}
|
|
447
|
+
const python = commandPath("python3") || commandPath("python");
|
|
448
|
+
if (python) {
|
|
449
|
+
console.log(`python: ok (${python})`);
|
|
450
|
+
} else {
|
|
451
|
+
ok = false;
|
|
452
|
+
console.log("python: failed (needed for local webhook runtime)");
|
|
453
|
+
}
|
|
454
|
+
const oih = commandPath("oih");
|
|
455
|
+
if (oih) {
|
|
456
|
+
console.log(`openilink cli: ok (${oih})`);
|
|
457
|
+
} else {
|
|
458
|
+
console.log("openilink cli: missing (run `nodus-wechat install-openilink`)");
|
|
459
|
+
}
|
|
420
460
|
const docker = dockerComposeAvailable();
|
|
421
461
|
if (docker.ok) {
|
|
422
|
-
console.log(`docker compose: ok (${docker.version})`);
|
|
462
|
+
console.log(`docker compose: ok (${docker.version}; optional with --docker)`);
|
|
423
463
|
} else {
|
|
424
|
-
console.log("docker compose: missing (needed for `
|
|
464
|
+
console.log("docker compose: missing (optional; only needed for `--docker`)");
|
|
425
465
|
}
|
|
426
466
|
console.log(`openilink: ${config.openilink?.publicOrigin || DEFAULT_OPENILINK_ORIGIN}`);
|
|
427
467
|
console.log(`webhook: http://127.0.0.1:${config.webhook?.port || DEFAULT_WEBHOOK_PORT}/health`);
|
|
@@ -469,6 +509,116 @@ function dockerComposeAvailable() {
|
|
|
469
509
|
};
|
|
470
510
|
}
|
|
471
511
|
|
|
512
|
+
function commandPath(name) {
|
|
513
|
+
const result = childProcess.spawnSync("/bin/sh", ["-c", `command -v ${shellQuote(name)}`], {
|
|
514
|
+
encoding: "utf8",
|
|
515
|
+
});
|
|
516
|
+
if (result.error || result.status !== 0) {
|
|
517
|
+
return null;
|
|
518
|
+
}
|
|
519
|
+
return result.stdout.trim() || null;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function runtimePath(config, name) {
|
|
523
|
+
return path.join(config.runtime.dir, name);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function pidPath(config, name) {
|
|
527
|
+
return runtimePath(config, `.nodus-${name}.pid`);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function logPath(config, name) {
|
|
531
|
+
return runtimePath(config, `${name}.log`);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function readPid(filePath) {
|
|
535
|
+
if (!fs.existsSync(filePath)) {
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
const pid = Number.parseInt(fs.readFileSync(filePath, "utf8"), 10);
|
|
539
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function processRunning(pid) {
|
|
543
|
+
if (!pid) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
try {
|
|
547
|
+
process.kill(pid, 0);
|
|
548
|
+
return true;
|
|
549
|
+
} catch (_error) {
|
|
550
|
+
return false;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function startManagedProcess(config, name, command, args, env) {
|
|
555
|
+
const filePath = pidPath(config, name);
|
|
556
|
+
const existingPid = readPid(filePath);
|
|
557
|
+
if (processRunning(existingPid)) {
|
|
558
|
+
console.log(`${name}: already running (pid ${existingPid})`);
|
|
559
|
+
return 0;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
fs.mkdirSync(config.runtime.dir, { recursive: true, mode: 0o700 });
|
|
563
|
+
const out = fs.openSync(logPath(config, name), "a");
|
|
564
|
+
const child = childProcess.spawn(command, args, {
|
|
565
|
+
cwd: config.runtime.dir,
|
|
566
|
+
env,
|
|
567
|
+
detached: true,
|
|
568
|
+
stdio: ["ignore", out, out],
|
|
569
|
+
});
|
|
570
|
+
child.unref();
|
|
571
|
+
fs.writeFileSync(filePath, `${child.pid}\n`, { mode: 0o600 });
|
|
572
|
+
console.log(`${name}: started (pid ${child.pid}, log ${logPath(config, name)})`);
|
|
573
|
+
return 0;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
function stopManagedProcess(config, name) {
|
|
577
|
+
const filePath = pidPath(config, name);
|
|
578
|
+
const pid = readPid(filePath);
|
|
579
|
+
if (!processRunning(pid)) {
|
|
580
|
+
fs.rmSync(filePath, { force: true });
|
|
581
|
+
console.log(`${name}: stopped`);
|
|
582
|
+
return 0;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
try {
|
|
586
|
+
process.kill(-pid, "SIGTERM");
|
|
587
|
+
} catch (_error) {
|
|
588
|
+
process.kill(pid, "SIGTERM");
|
|
589
|
+
}
|
|
590
|
+
fs.rmSync(filePath, { force: true });
|
|
591
|
+
console.log(`${name}: stopped (pid ${pid})`);
|
|
592
|
+
return 0;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function localRuntimeEnv(config) {
|
|
596
|
+
const env = parseDotEnv(path.join(config.runtime.dir, ".env"));
|
|
597
|
+
return {
|
|
598
|
+
...process.env,
|
|
599
|
+
...env,
|
|
600
|
+
LISTEN: `:${config.openilink?.port || DEFAULT_OPENILINK_PORT}`,
|
|
601
|
+
RP_ORIGIN: config.openilink?.publicOrigin || DEFAULT_OPENILINK_ORIGIN,
|
|
602
|
+
RP_ID: config.openilink?.rpId || DEFAULT_OPENILINK_RP_ID,
|
|
603
|
+
POC_WEBHOOK_BIND: config.webhook?.bind || "127.0.0.1",
|
|
604
|
+
POC_WEBHOOK_PORT: String(config.webhook?.port || DEFAULT_WEBHOOK_PORT),
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
function httpGet(url, timeoutMs = 1200) {
|
|
609
|
+
return new Promise((resolve) => {
|
|
610
|
+
const request = http.get(url, { timeout: timeoutMs }, (response) => {
|
|
611
|
+
response.resume();
|
|
612
|
+
response.on("end", () => resolve({ ok: response.statusCode >= 200 && response.statusCode < 500, statusCode: response.statusCode }));
|
|
613
|
+
});
|
|
614
|
+
request.on("timeout", () => {
|
|
615
|
+
request.destroy();
|
|
616
|
+
resolve({ ok: false });
|
|
617
|
+
});
|
|
618
|
+
request.on("error", () => resolve({ ok: false }));
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
472
622
|
function loadRuntimeConfig() {
|
|
473
623
|
if (!fs.existsSync(configPath())) {
|
|
474
624
|
console.error(`Config missing: ${configPath()}`);
|
|
@@ -511,7 +661,7 @@ function runDockerCompose(config, args, stdio = "inherit") {
|
|
|
511
661
|
return result.status || 0;
|
|
512
662
|
}
|
|
513
663
|
|
|
514
|
-
function
|
|
664
|
+
function startDocker() {
|
|
515
665
|
const config = loadRuntimeConfig();
|
|
516
666
|
if (!config) {
|
|
517
667
|
return 1;
|
|
@@ -520,7 +670,39 @@ function start() {
|
|
|
520
670
|
return runDockerCompose(config, ["up", "-d"]);
|
|
521
671
|
}
|
|
522
672
|
|
|
523
|
-
function
|
|
673
|
+
function startLocal() {
|
|
674
|
+
const config = loadRuntimeConfig();
|
|
675
|
+
if (!config) {
|
|
676
|
+
return 1;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const python = commandPath("python3") || commandPath("python");
|
|
680
|
+
if (!python) {
|
|
681
|
+
console.error("Python is required for the local webhook runtime.");
|
|
682
|
+
return 1;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const oih = commandPath("oih");
|
|
686
|
+
if (!oih) {
|
|
687
|
+
console.error("OpeniLink Hub CLI `oih` is not installed.");
|
|
688
|
+
console.error("Run: nodus-wechat install-openilink");
|
|
689
|
+
return 1;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const env = localRuntimeEnv(config);
|
|
693
|
+
const webhookPath = path.join(config.runtime.dir, "poc-webhook", "server.py");
|
|
694
|
+
startManagedProcess(config, "webhook", python, [webhookPath], env);
|
|
695
|
+
startManagedProcess(config, "openilink", oih, [], env);
|
|
696
|
+
console.log(`OpeniLink Hub: ${config.openilink?.publicOrigin || DEFAULT_OPENILINK_ORIGIN}`);
|
|
697
|
+
console.log(`Webhook health: http://127.0.0.1:${config.webhook?.port || DEFAULT_WEBHOOK_PORT}/health`);
|
|
698
|
+
return 0;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function start(options) {
|
|
702
|
+
return options.docker ? startDocker() : startLocal();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
function statusDocker() {
|
|
524
706
|
const config = loadRuntimeConfig();
|
|
525
707
|
if (!config) {
|
|
526
708
|
return 1;
|
|
@@ -529,7 +711,26 @@ function status() {
|
|
|
529
711
|
return runDockerCompose(config, ["ps"]);
|
|
530
712
|
}
|
|
531
713
|
|
|
532
|
-
function
|
|
714
|
+
async function statusLocal() {
|
|
715
|
+
const config = loadRuntimeConfig();
|
|
716
|
+
if (!config) {
|
|
717
|
+
return 1;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
for (const name of ["openilink", "webhook"]) {
|
|
721
|
+
const pid = readPid(pidPath(config, name));
|
|
722
|
+
console.log(`${name}: ${processRunning(pid) ? `running (pid ${pid})` : "stopped"}`);
|
|
723
|
+
}
|
|
724
|
+
const health = await httpGet(`http://127.0.0.1:${config.webhook?.port || DEFAULT_WEBHOOK_PORT}/health`);
|
|
725
|
+
console.log(`webhook health: ${health.ok ? `ok (${health.statusCode})` : "unreachable"}`);
|
|
726
|
+
return 0;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function status(options) {
|
|
730
|
+
return options.docker ? statusDocker() : statusLocal();
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function logsDocker() {
|
|
533
734
|
const config = loadRuntimeConfig();
|
|
534
735
|
if (!config) {
|
|
535
736
|
return 1;
|
|
@@ -538,7 +739,36 @@ function logs() {
|
|
|
538
739
|
return runDockerCompose(config, ["logs", "-f", "poc-webhook"]);
|
|
539
740
|
}
|
|
540
741
|
|
|
541
|
-
function
|
|
742
|
+
function logsLocal() {
|
|
743
|
+
const config = loadRuntimeConfig();
|
|
744
|
+
if (!config) {
|
|
745
|
+
return 1;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
const files = ["openilink", "webhook"].map((name) => logPath(config, name)).filter((filePath) => fs.existsSync(filePath));
|
|
749
|
+
if (files.length === 0) {
|
|
750
|
+
console.error("No local runtime logs found.");
|
|
751
|
+
return 1;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const tail = commandPath("tail");
|
|
755
|
+
if (!tail) {
|
|
756
|
+
for (const filePath of files) {
|
|
757
|
+
console.log(`==> ${filePath} <==`);
|
|
758
|
+
console.log(fs.readFileSync(filePath, "utf8"));
|
|
759
|
+
}
|
|
760
|
+
return 0;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const result = childProcess.spawnSync(tail, ["-f", ...files], { stdio: "inherit" });
|
|
764
|
+
return result.status || 0;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function logs(options) {
|
|
768
|
+
return options.docker ? logsDocker() : logsLocal();
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
function stopDocker() {
|
|
542
772
|
const config = loadRuntimeConfig();
|
|
543
773
|
if (!config) {
|
|
544
774
|
return 1;
|
|
@@ -547,6 +777,21 @@ function stop() {
|
|
|
547
777
|
return runDockerCompose(config, ["down"]);
|
|
548
778
|
}
|
|
549
779
|
|
|
780
|
+
function stopLocal() {
|
|
781
|
+
const config = loadRuntimeConfig();
|
|
782
|
+
if (!config) {
|
|
783
|
+
return 1;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
stopManagedProcess(config, "webhook");
|
|
787
|
+
stopManagedProcess(config, "openilink");
|
|
788
|
+
return 0;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
function stop(options) {
|
|
792
|
+
return options.docker ? stopDocker() : stopLocal();
|
|
793
|
+
}
|
|
794
|
+
|
|
550
795
|
function uninstall(options) {
|
|
551
796
|
if (!options.yes) {
|
|
552
797
|
console.error("Refusing to uninstall without --yes.");
|
|
@@ -597,6 +842,9 @@ function clean(options) {
|
|
|
597
842
|
|
|
598
843
|
const config = fs.existsSync(configPath()) ? readConfig() : null;
|
|
599
844
|
if (config) {
|
|
845
|
+
for (const name of ["webhook", "openilink"]) {
|
|
846
|
+
stopManagedProcess({ ...config, runtime: { ...config.runtime, dir: config.runtime?.dir || path.join(configHome(), "runtime") } }, name);
|
|
847
|
+
}
|
|
600
848
|
const docker = dockerComposeAvailable();
|
|
601
849
|
if (docker.ok && config.runtime?.dir && fs.existsSync(path.join(config.runtime.dir, "docker-compose.yml"))) {
|
|
602
850
|
runDockerCompose(config, ["down"], "pipe");
|
|
@@ -613,7 +861,7 @@ function clean(options) {
|
|
|
613
861
|
return 0;
|
|
614
862
|
}
|
|
615
863
|
|
|
616
|
-
function main() {
|
|
864
|
+
async function main() {
|
|
617
865
|
let args;
|
|
618
866
|
try {
|
|
619
867
|
args = parseArgs(process.argv.slice(2));
|
|
@@ -638,24 +886,28 @@ function main() {
|
|
|
638
886
|
return installHermes();
|
|
639
887
|
}
|
|
640
888
|
|
|
889
|
+
if (command === "install-openilink") {
|
|
890
|
+
return installOpeniLink();
|
|
891
|
+
}
|
|
892
|
+
|
|
641
893
|
if (command === "doctor") {
|
|
642
894
|
return doctor();
|
|
643
895
|
}
|
|
644
896
|
|
|
645
897
|
if (command === "start") {
|
|
646
|
-
return start();
|
|
898
|
+
return start(args);
|
|
647
899
|
}
|
|
648
900
|
|
|
649
901
|
if (command === "status") {
|
|
650
|
-
return status();
|
|
902
|
+
return await status(args);
|
|
651
903
|
}
|
|
652
904
|
|
|
653
905
|
if (command === "logs") {
|
|
654
|
-
return logs();
|
|
906
|
+
return logs(args);
|
|
655
907
|
}
|
|
656
908
|
|
|
657
909
|
if (command === "stop") {
|
|
658
|
-
return stop();
|
|
910
|
+
return stop(args);
|
|
659
911
|
}
|
|
660
912
|
|
|
661
913
|
if (command === "uninstall") {
|
|
@@ -675,4 +927,11 @@ function main() {
|
|
|
675
927
|
return 1;
|
|
676
928
|
}
|
|
677
929
|
|
|
678
|
-
|
|
930
|
+
main()
|
|
931
|
+
.then((code) => {
|
|
932
|
+
process.exitCode = code;
|
|
933
|
+
})
|
|
934
|
+
.catch((error) => {
|
|
935
|
+
console.error(error.message);
|
|
936
|
+
process.exitCode = 1;
|
|
937
|
+
});
|
package/package.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodus-wechat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "CLI installer for Nodus WeChat, Hermes, and the local OpeniLink webhook runtime.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"private": false,
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/zyaireleo/nodus-wechat.git"
|
|
10
|
+
},
|
|
7
11
|
"bin": {
|
|
8
12
|
"nodus-wechat": "bin/nodus-wechat.js"
|
|
9
13
|
},
|
|
@@ -115,4 +115,6 @@ class Handler(BaseHTTPRequestHandler):
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
if __name__ == "__main__":
|
|
118
|
-
|
|
118
|
+
bind = os.environ.get("POC_WEBHOOK_BIND", "127.0.0.1")
|
|
119
|
+
port = int(os.environ.get("POC_WEBHOOK_PORT", "9811"))
|
|
120
|
+
HTTPServer((bind, port), Handler).serve_forever()
|
|
@@ -7,5 +7,51 @@ if [ ! -f .env ]; then
|
|
|
7
7
|
cp .env.example .env
|
|
8
8
|
fi
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
set -a
|
|
11
|
+
. ./.env
|
|
12
|
+
set +a
|
|
13
|
+
|
|
14
|
+
if ! command -v oih >/dev/null 2>&1; then
|
|
15
|
+
echo "OpeniLink Hub CLI 'oih' is not installed. Run: npx nodus-wechat install-openilink" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
PYTHON_BIN="${PYTHON_BIN:-}"
|
|
20
|
+
if [ -z "$PYTHON_BIN" ]; then
|
|
21
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
22
|
+
PYTHON_BIN=python3
|
|
23
|
+
elif command -v python >/dev/null 2>&1; then
|
|
24
|
+
PYTHON_BIN=python
|
|
25
|
+
else
|
|
26
|
+
echo "Python is required for the local webhook runtime." >&2
|
|
27
|
+
exit 1
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
OPENILINK_PID=".nodus-openilink.pid"
|
|
32
|
+
WEBHOOK_PID=".nodus-webhook.pid"
|
|
33
|
+
|
|
34
|
+
if [ -f "$WEBHOOK_PID" ] && kill -0 "$(cat "$WEBHOOK_PID")" >/dev/null 2>&1; then
|
|
35
|
+
echo "webhook: already running (pid $(cat "$WEBHOOK_PID"))"
|
|
36
|
+
else
|
|
37
|
+
POC_WEBHOOK_BIND="${POC_WEBHOOK_BIND:-127.0.0.1}" \
|
|
38
|
+
POC_WEBHOOK_PORT="${POC_WEBHOOK_PORT:-9811}" \
|
|
39
|
+
POC_WEBHOOK_TOKEN="${POC_WEBHOOK_TOKEN:-}" \
|
|
40
|
+
"$PYTHON_BIN" poc-webhook/server.py >>webhook.log 2>&1 &
|
|
41
|
+
echo $! >"$WEBHOOK_PID"
|
|
42
|
+
echo "webhook: started (pid $(cat "$WEBHOOK_PID"))"
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
if [ -f "$OPENILINK_PID" ] && kill -0 "$(cat "$OPENILINK_PID")" >/dev/null 2>&1; then
|
|
46
|
+
echo "openilink: already running (pid $(cat "$OPENILINK_PID"))"
|
|
47
|
+
else
|
|
48
|
+
LISTEN=":${OPENILINK_PORT:-9800}" \
|
|
49
|
+
RP_ORIGIN="${OPENILINK_PUBLIC_ORIGIN:-http://localhost:9800}" \
|
|
50
|
+
RP_ID="${OPENILINK_RP_ID:-localhost}" \
|
|
51
|
+
oih >>openilink.log 2>&1 &
|
|
52
|
+
echo $! >"$OPENILINK_PID"
|
|
53
|
+
echo "openilink: started (pid $(cat "$OPENILINK_PID"))"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
echo "OpeniLink Hub: ${OPENILINK_PUBLIC_ORIGIN:-http://localhost:9800}"
|
|
57
|
+
echo "Webhook health: http://127.0.0.1:${POC_WEBHOOK_PORT:-9811}/health"
|
|
@@ -3,8 +3,21 @@ set -euo pipefail
|
|
|
3
3
|
|
|
4
4
|
cd "$(dirname "$0")/.."
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
for name in openilink webhook; do
|
|
7
|
+
pid_file=".nodus-${name}.pid"
|
|
8
|
+
if [ -f "$pid_file" ] && kill -0 "$(cat "$pid_file")" >/dev/null 2>&1; then
|
|
9
|
+
echo "$name: running (pid $(cat "$pid_file"))"
|
|
10
|
+
else
|
|
11
|
+
echo "$name: stopped"
|
|
12
|
+
fi
|
|
13
|
+
done
|
|
14
|
+
|
|
15
|
+
if [ -f .env ]; then
|
|
16
|
+
set -a
|
|
17
|
+
. ./.env
|
|
18
|
+
set +a
|
|
19
|
+
fi
|
|
7
20
|
printf "\nWebhook health:\n"
|
|
8
21
|
curl -fsS "http://127.0.0.1:${POC_WEBHOOK_PORT:-9811}/health" || true
|
|
9
22
|
printf "\n\nRecent webhook logs:\n"
|
|
10
|
-
|
|
23
|
+
tail -n 80 webhook.log 2>/dev/null || true
|
|
@@ -2,4 +2,13 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
cd "$(dirname "$0")/.."
|
|
5
|
-
|
|
5
|
+
for name in webhook openilink; do
|
|
6
|
+
pid_file=".nodus-${name}.pid"
|
|
7
|
+
if [ -f "$pid_file" ] && kill -0 "$(cat "$pid_file")" >/dev/null 2>&1; then
|
|
8
|
+
kill "$(cat "$pid_file")" || true
|
|
9
|
+
echo "$name: stopped (pid $(cat "$pid_file"))"
|
|
10
|
+
else
|
|
11
|
+
echo "$name: stopped"
|
|
12
|
+
fi
|
|
13
|
+
rm -f "$pid_file"
|
|
14
|
+
done
|