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 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 through Docker Compose.
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-hermes` is explicitly requested.
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.
@@ -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.5.2";
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 the local OpeniLink + webhook runtime with Docker Compose.
56
- status Show Docker Compose service status.
57
- logs Follow webhook logs.
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("Run `nodus-wechat start` to start the local runtime.");
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 `nodus-wechat start`)");
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 start() {
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 status() {
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 logs() {
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 stop() {
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
- process.exitCode = main();
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.5.2",
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
- HTTPServer(("0.0.0.0", 9811), Handler).serve_forever()
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()
@@ -2,4 +2,4 @@
2
2
  set -euo pipefail
3
3
 
4
4
  cd "$(dirname "$0")/.."
5
- docker compose logs -f poc-webhook
5
+ tail -f openilink.log webhook.log
@@ -7,5 +7,51 @@ if [ ! -f .env ]; then
7
7
  cp .env.example .env
8
8
  fi
9
9
 
10
- docker compose up -d
11
- docker compose ps
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
- docker compose ps
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
- docker compose logs --tail=80 poc-webhook
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
- docker compose down
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