nodus-wechat 0.6.2 → 0.7.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 CHANGED
@@ -18,6 +18,7 @@ npx nodus-wechat install-openilink
18
18
  npx nodus-wechat doctor
19
19
  npx nodus-wechat start
20
20
  npx nodus-wechat start --no-install
21
+ npx nodus-wechat start --no-hermes
21
22
  npx nodus-wechat start --docker
22
23
  npx nodus-wechat status
23
24
  npx nodus-wechat logs
@@ -52,8 +53,8 @@ the official OpeniLink installer.
52
53
  - Writes runtime `.env`, Docker Compose, webhook server, helper scripts, and the OpeniLink reply plugin.
53
54
  - Stores gateway base URL, api key, model, Hermes paths, OpeniLink origin, webhook port, and runtime path.
54
55
  - 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`.
55
- - Starts/stops the local runtime with native local processes by default.
56
- - Installs OpeniLink automatically during `start` when the native `oih` CLI is missing.
56
+ - Starts/stops Hermes Gateway, OpeniLink, and the local webhook with native host processes by default.
57
+ - Installs Hermes and OpeniLink automatically during `start` when the native CLIs are missing.
57
58
  - Keeps Docker Compose available only when `--docker` is passed.
58
59
  - Removes Nodus WeChat config/runtime files with `uninstall --yes`.
59
60
  - Cleans Nodus WeChat config/runtime plus generated Hermes settings with `clean --yes`.
@@ -77,9 +78,8 @@ the config generated by this CLI.
77
78
 
78
79
  ## Current non-goals
79
80
 
80
- - Does not run the Hermes installer unless `--install-hermes` or `install-hermes` is explicitly requested.
81
81
  - Does not automate, inject into, read, or control WeChat directly.
82
- - Does not start a daemon, LaunchAgent, or background worker outside Docker Compose.
82
+ - Does not install a LaunchAgent or system service; `start` uses managed host processes that `stop` can terminate.
83
83
  - Does not redeem real CDKs or mutate sub2api accounts; the bundled webhook keeps the existing dry-run POC boundary.
84
84
 
85
85
  ## Runtime wiring
@@ -8,7 +8,7 @@ const os = require("node:os");
8
8
  const path = require("node:path");
9
9
  const childProcess = require("node:child_process");
10
10
 
11
- const VERSION = "0.6.2";
11
+ const VERSION = "0.7.1";
12
12
  const DEFAULT_BASE_URL = "https://api.nodus.sbs/";
13
13
  const DEFAULT_MODEL = "gpt-5.5";
14
14
  const DEFAULT_OPENILINK_ORIGIN = "http://localhost:9800";
@@ -43,7 +43,7 @@ Usage:
43
43
  nodus-wechat install-hermes
44
44
  nodus-wechat install-openilink
45
45
  nodus-wechat doctor
46
- nodus-wechat start [--docker] [--no-install]
46
+ nodus-wechat start [--docker] [--no-install] [--no-hermes]
47
47
  nodus-wechat status [--docker]
48
48
  nodus-wechat logs [--docker]
49
49
  nodus-wechat stop [--docker]
@@ -56,7 +56,7 @@ Commands:
56
56
  install-openilink
57
57
  Install OpeniLink Hub native CLI with the official installer.
58
58
  doctor Check local prerequisites and configuration.
59
- start Start OpeniLink + webhook with local processes by default; installs OpeniLink if missing.
59
+ start Start Hermes Gateway, OpeniLink, and webhook on the host by default.
60
60
  status Show local process status.
61
61
  logs Follow local runtime logs.
62
62
  stop Stop the local runtime.
@@ -78,7 +78,7 @@ function parseArgs(argv) {
78
78
  }
79
79
 
80
80
  const key = item.slice(2);
81
- if (key === "help" || key === "yes" || key === "install-hermes" || key === "docker" || key === "no-install") {
81
+ if (key === "help" || key === "yes" || key === "install-hermes" || key === "docker" || key === "no-install" || key === "no-hermes") {
82
82
  result[key] = true;
83
83
  continue;
84
84
  }
@@ -247,12 +247,24 @@ function openiLinkInstallCommand() {
247
247
  }
248
248
 
249
249
  function runHermesInstaller(hermesDir) {
250
+ const checkoutDir = path.join(hermesDir, "hermes-agent");
251
+ if (fs.existsSync(checkoutDir) && fs.existsSync(path.join(checkoutDir, ".git")) && !fs.existsSync(path.join(checkoutDir, "pyproject.toml"))) {
252
+ fs.rmSync(checkoutDir, { recursive: true, force: true });
253
+ }
254
+
250
255
  const args = ["--skip-setup", "--hermes-home", hermesDir];
251
256
  const command = `${hermesInstallCommand()} ${args.map(shellQuote).join(" ")}`;
252
257
  const result = childProcess.spawnSync(command, {
253
258
  shell: true,
254
259
  stdio: "inherit",
255
- env: { ...process.env, HERMES_HOME: hermesDir },
260
+ env: {
261
+ ...process.env,
262
+ HERMES_HOME: hermesDir,
263
+ GIT_TERMINAL_PROMPT: "0",
264
+ GIT_CONFIG_COUNT: "1",
265
+ GIT_CONFIG_KEY_0: "url.https://github.com/.insteadOf",
266
+ GIT_CONFIG_VALUE_0: "git@github.com:",
267
+ },
256
268
  });
257
269
 
258
270
  if (result.error) {
@@ -377,7 +389,7 @@ function setup(options) {
377
389
  console.log(`OpeniLink Hub: ${config.openilink.publicOrigin}`);
378
390
  console.log(`Webhook URL for OpeniLink: http://poc-webhook:${config.webhook.port}/webhook`);
379
391
  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; it will install OpeniLink if missing.");
392
+ console.log("Run `nodus-wechat start` to start Hermes Gateway plus the local host runtime.");
381
393
  }
382
394
 
383
395
  function installHermes() {
@@ -519,6 +531,16 @@ function commandPath(name) {
519
531
  return result.stdout.trim() || null;
520
532
  }
521
533
 
534
+ function hermesPath(config) {
535
+ return (
536
+ commandPath("hermes") ||
537
+ (fs.existsSync(path.join(os.homedir(), ".local", "bin", "hermes")) ? path.join(os.homedir(), ".local", "bin", "hermes") : null) ||
538
+ (config?.hermes?.home && fs.existsSync(path.join(config.hermes.home, "hermes-agent", "venv", "bin", "hermes"))
539
+ ? path.join(config.hermes.home, "hermes-agent", "venv", "bin", "hermes")
540
+ : null)
541
+ );
542
+ }
543
+
522
544
  function runtimePath(config, name) {
523
545
  return path.join(config.runtime.dir, name);
524
546
  }
@@ -597,6 +619,7 @@ function localRuntimeEnv(config) {
597
619
  return {
598
620
  ...process.env,
599
621
  ...env,
622
+ HERMES_HOME: config.hermes?.home || hermesHome(),
600
623
  LISTEN: `:${config.openilink?.port || DEFAULT_OPENILINK_PORT}`,
601
624
  RP_ORIGIN: config.openilink?.publicOrigin || DEFAULT_OPENILINK_ORIGIN,
602
625
  RP_ID: config.openilink?.rpId || DEFAULT_OPENILINK_RP_ID,
@@ -682,6 +705,24 @@ function startLocal(options) {
682
705
  return 1;
683
706
  }
684
707
 
708
+ let hermes = options["no-hermes"] ? null : hermesPath(config);
709
+ if (!hermes && !options["no-hermes"]) {
710
+ if (options["no-install"]) {
711
+ console.error("Hermes CLI `hermes` is not installed.");
712
+ console.error("Run: nodus-wechat install-hermes");
713
+ return 1;
714
+ }
715
+
716
+ console.log("Hermes CLI `hermes` is not installed; installing it now.");
717
+ runHermesInstaller(config.hermes?.home || hermesHome());
718
+ hermes = hermesPath(config);
719
+ if (!hermes) {
720
+ console.error("Hermes installer completed, but `hermes` is still not on PATH.");
721
+ console.error("Open a new terminal or add the installed Hermes path to PATH, then rerun `nodus-wechat start`.");
722
+ return 1;
723
+ }
724
+ }
725
+
685
726
  let oih = commandPath("oih");
686
727
  if (!oih) {
687
728
  if (options["no-install"]) {
@@ -707,6 +748,11 @@ function startLocal(options) {
707
748
  const webhookPath = path.join(config.runtime.dir, "poc-webhook", "server.py");
708
749
  startManagedProcess(config, "webhook", python, [webhookPath], env);
709
750
  startManagedProcess(config, "openilink", oih, [], env);
751
+ if (hermes) {
752
+ startManagedProcess(config, "hermes", hermes, ["gateway", "run"], env);
753
+ } else {
754
+ console.log("hermes: skipped (--no-hermes)");
755
+ }
710
756
  console.log(`OpeniLink Hub: ${config.openilink?.publicOrigin || DEFAULT_OPENILINK_ORIGIN}`);
711
757
  console.log(`Webhook health: http://127.0.0.1:${config.webhook?.port || DEFAULT_WEBHOOK_PORT}/health`);
712
758
  return 0;
@@ -731,7 +777,7 @@ async function statusLocal() {
731
777
  return 1;
732
778
  }
733
779
 
734
- for (const name of ["openilink", "webhook"]) {
780
+ for (const name of ["hermes", "openilink", "webhook"]) {
735
781
  const pid = readPid(pidPath(config, name));
736
782
  console.log(`${name}: ${processRunning(pid) ? `running (pid ${pid})` : "stopped"}`);
737
783
  }
@@ -759,7 +805,7 @@ function logsLocal() {
759
805
  return 1;
760
806
  }
761
807
 
762
- const files = ["openilink", "webhook"].map((name) => logPath(config, name)).filter((filePath) => fs.existsSync(filePath));
808
+ const files = ["hermes", "openilink", "webhook"].map((name) => logPath(config, name)).filter((filePath) => fs.existsSync(filePath));
763
809
  if (files.length === 0) {
764
810
  console.error("No local runtime logs found.");
765
811
  return 1;
@@ -799,6 +845,7 @@ function stopLocal() {
799
845
 
800
846
  stopManagedProcess(config, "webhook");
801
847
  stopManagedProcess(config, "openilink");
848
+ stopManagedProcess(config, "hermes");
802
849
  return 0;
803
850
  }
804
851
 
@@ -856,7 +903,7 @@ function clean(options) {
856
903
 
857
904
  const config = fs.existsSync(configPath()) ? readConfig() : null;
858
905
  if (config) {
859
- for (const name of ["webhook", "openilink"]) {
906
+ for (const name of ["webhook", "openilink", "hermes"]) {
860
907
  stopManagedProcess({ ...config, runtime: { ...config.runtime, dir: config.runtime?.dir || path.join(configHome(), "runtime") } }, name);
861
908
  }
862
909
  const docker = dockerComposeAvailable();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nodus-wechat",
3
- "version": "0.6.2",
3
+ "version": "0.7.1",
4
4
  "description": "CLI installer for Nodus WeChat, Hermes, and the local OpeniLink webhook runtime.",
5
5
  "license": "MIT",
6
6
  "private": false,