@soyeht/soyeht 0.2.3 → 0.2.5

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.
@@ -5,7 +5,7 @@
5
5
  ],
6
6
  "name": "Soyeht",
7
7
  "description": "Channel plugin for the Soyeht Flutter mobile app",
8
- "version": "0.2.3",
8
+ "version": "0.2.5",
9
9
  "configSchema": {
10
10
  "type": "object",
11
11
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soyeht/soyeht",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "OpenClaw channel plugin for the Soyeht Flutter mobile app",
5
5
  "type": "module",
6
6
  "main": "src/index.ts",
package/src/qr.ts CHANGED
@@ -426,11 +426,19 @@ export function renderQrTerminal(text: string): string | null {
426
426
  }
427
427
 
428
428
  // ▄ (lower half block): foreground = lower half, background = upper half
429
+ // Use 24-bit true color (ESC[48;2;R;G;Bm) for pure black/white contrast.
430
+ // Generic ANSI colors (40/47) map to theme colors which may not be pure B&W.
431
+ const BG_BLACK = "\x1b[48;2;0;0;0m";
432
+ const BG_WHITE = "\x1b[48;2;255;255;255m";
433
+ const FG_BLACK = "\x1b[38;2;0;0;0m";
434
+ const FG_WHITE = "\x1b[38;2;255;255;255m";
435
+ const RST = "\x1b[0m";
436
+
429
437
  function cell(top: number, bottom: number): string {
430
- if (top === 1 && bottom === 1) return "\x1b[40m \x1b[0m"; // both black
431
- if (top === 0 && bottom === 0) return "\x1b[47m \x1b[0m"; // both white
432
- if (top === 1 && bottom === 0) return "\x1b[37;40m\u2584\x1b[0m"; // top black, bottom white
433
- return "\x1b[30;47m\u2584\x1b[0m"; // top white, bottom black
438
+ if (top === 1 && bottom === 1) return `${BG_BLACK} ${RST}`;
439
+ if (top === 0 && bottom === 0) return `${BG_WHITE} ${RST}`;
440
+ if (top === 1 && bottom === 0) return `${FG_WHITE}${BG_BLACK}\u2584${RST}`;
441
+ return `${FG_BLACK}${BG_WHITE}\u2584${RST}`;
434
442
  }
435
443
 
436
444
  const lines: string[] = [];
package/src/service.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { readFile, writeFile, rm } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
+ import { networkInterfaces } from "node:os";
4
5
  import type { OpenClawPluginApi, OpenClawPluginService, OpenClawPluginServiceContext } from "openclaw/plugin-sdk";
5
6
  import {
6
7
  createNonceCache,
@@ -73,6 +74,26 @@ export function createSecurityV2Deps(): SecurityV2Deps {
73
74
  // ---------------------------------------------------------------------------
74
75
 
75
76
  const AUTO_PAIRING_TTL_MS = 90_000;
77
+ const DEFAULT_GATEWAY_PORT = 18789;
78
+
79
+ function detectLanIp(): string | null {
80
+ const nets = networkInterfaces();
81
+ for (const ifaces of Object.values(nets)) {
82
+ if (!ifaces) continue;
83
+ for (const iface of ifaces) {
84
+ // Skip loopback and internal, only IPv4
85
+ if (iface.internal || iface.family !== "IPv4") continue;
86
+ return iface.address;
87
+ }
88
+ }
89
+ return null;
90
+ }
91
+
92
+ function detectGatewayPort(cfg: Record<string, unknown>): number {
93
+ const gw = (cfg as Record<string, unknown>)["gateway"] as Record<string, unknown> | undefined;
94
+ const port = gw?.["port"];
95
+ return typeof port === "number" && port > 0 ? port : DEFAULT_GATEWAY_PORT;
96
+ }
76
97
 
77
98
  async function showPairingQr(api: OpenClawPluginApi, v2deps: SecurityV2Deps): Promise<void> {
78
99
  const identity = v2deps.identity;
@@ -91,10 +112,24 @@ async function showPairingQr(api: OpenClawPluginApi, v2deps: SecurityV2Deps): Pr
91
112
  const expiresAt = Date.now() + AUTO_PAIRING_TTL_MS;
92
113
  const fingerprint = computeFingerprint(identity);
93
114
 
94
- // Resolve gatewayUrl from config
115
+ // Resolve gatewayUrl: config > runtime > auto-detect LAN IP
95
116
  const cfg = await api.runtime.config.loadConfig();
96
117
  const account = resolveSoyehtAccount(cfg, accountId);
97
- const gatewayUrl = account.gatewayUrl;
118
+ let gatewayUrl = account.gatewayUrl;
119
+ if (!gatewayUrl) {
120
+ const runtime = api.runtime as Record<string, unknown>;
121
+ if (typeof runtime["gatewayUrl"] === "string" && runtime["gatewayUrl"]) {
122
+ gatewayUrl = runtime["gatewayUrl"];
123
+ } else if (typeof runtime["baseUrl"] === "string" && runtime["baseUrl"]) {
124
+ gatewayUrl = runtime["baseUrl"];
125
+ } else {
126
+ const ip = detectLanIp();
127
+ if (ip) {
128
+ const port = detectGatewayPort(cfg);
129
+ gatewayUrl = `http://${ip}:${port}`;
130
+ }
131
+ }
132
+ }
98
133
 
99
134
  v2deps.pairingSessions.set(pairingToken, {
100
135
  token: pairingToken,
@@ -104,8 +139,9 @@ async function showPairingQr(api: OpenClawPluginApi, v2deps: SecurityV2Deps): Pr
104
139
  });
105
140
 
106
141
  // Compact QR: soyeht://pair?g=<gatewayUrl>&t=<token>&fp=<fingerprint>
107
- // App fetches full key material via RPC soyeht.security.pairing.info
108
- const qrText = `soyeht://pair?g=${encodeURIComponent(gatewayUrl)}&t=${pairingToken}&fp=${fingerprint}`;
142
+ // No encodeURIComponent custom scheme, no escaping needed.
143
+ // App fetches full key material via HTTP GET /soyeht/pairing/info
144
+ const qrText = `soyeht://pair?g=${gatewayUrl}&t=${pairingToken}&fp=${fingerprint}`;
109
145
  const rendered = renderQrTerminal(qrText);
110
146
 
111
147
  if (rendered) {
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const PLUGIN_VERSION = "0.2.3";
1
+ export const PLUGIN_VERSION = "0.2.5";