@clawling/clawchat-plugin-openclaw 2026.6.23-1 → 2026.6.24-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.
@@ -0,0 +1,93 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
4
+ export const LIVEWARE_BASE_URL = "https://media.clawling.chat/liveware/";
5
+ // A non-default User-Agent is required: the CDN (Cloudflare) returns 403 for
6
+ // known scripting/default UAs, so download requests must identify themselves.
7
+ const LIVEWARE_USER_AGENT = "clawchat-liveware-installer";
8
+ /** Directory where the downloaded binaries live: <stateDir>/clawchat/liveware. */
9
+ export function livewareDir(stateDir) {
10
+ const root = stateDir ?? resolveStateDir();
11
+ return path.join(root, "clawchat", "liveware");
12
+ }
13
+ /** Map host platform/arch to the two asset filenames, or null if unsupported. */
14
+ export function platformAssets(plat = process.platform, arch = process.arch) {
15
+ const os = plat === "darwin" ? "darwin" : plat === "linux" ? "linux" : null;
16
+ const cpu = arch === "x64" ? "amd64" : arch === "arm64" ? "arm64" : null;
17
+ if (!os || !cpu)
18
+ return null;
19
+ return { liveware: `liveware-${os}-${cpu}`, tunneAgent: `tunnel-agent-${os}-${cpu}` };
20
+ }
21
+ function findOnPath(name) {
22
+ const raw = process.env.PATH ?? "";
23
+ for (const dir of raw.split(path.delimiter)) {
24
+ if (!dir)
25
+ continue;
26
+ const candidate = path.join(dir, name);
27
+ try {
28
+ fs.accessSync(candidate, fs.constants.X_OK);
29
+ return candidate;
30
+ }
31
+ catch {
32
+ // not here; keep scanning
33
+ }
34
+ }
35
+ return null;
36
+ }
37
+ /**
38
+ * Resolve the liveware executable to invoke:
39
+ * 1. on PATH → "liveware" (let the OS resolve it)
40
+ * 2. downloaded dir → absolute path
41
+ * 3. otherwise → null
42
+ */
43
+ export function resolveLivewarePath(stateDir) {
44
+ if (findOnPath("liveware"))
45
+ return "liveware";
46
+ const local = path.join(livewareDir(stateDir), "liveware");
47
+ try {
48
+ fs.accessSync(local, fs.constants.X_OK);
49
+ return local;
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ async function downloadTo(url, dest) {
56
+ const res = await fetch(url, { headers: { "user-agent": LIVEWARE_USER_AGENT } });
57
+ if (!res.ok)
58
+ throw new Error(`download ${url} failed: HTTP ${res.status}`);
59
+ const buf = Buffer.from(await res.arrayBuffer());
60
+ if (buf.length === 0)
61
+ throw new Error(`download ${url} returned empty body`);
62
+ const tmp = `${dest}.tmp`;
63
+ await fs.promises.writeFile(tmp, buf);
64
+ await fs.promises.chmod(tmp, 0o755);
65
+ await fs.promises.rename(tmp, dest);
66
+ }
67
+ /**
68
+ * Startup hook: ensure the liveware CLI is available, downloading it when not.
69
+ * Fire-and-forget — never throws; logs and returns on any failure.
70
+ */
71
+ export async function ensureLivewareCli(opts = {}) {
72
+ const { stateDir, log } = opts;
73
+ try {
74
+ if (findOnPath("liveware"))
75
+ return; // PATH wins
76
+ const dir = livewareDir(stateDir);
77
+ const target = path.join(dir, "liveware");
78
+ if (fs.existsSync(target))
79
+ return;
80
+ const assets = platformAssets();
81
+ if (!assets) {
82
+ log?.warn?.(`clawchat-plugin-openclaw: unsupported platform ${process.platform}/${process.arch}; skipping liveware download`);
83
+ return;
84
+ }
85
+ await fs.promises.mkdir(dir, { recursive: true });
86
+ await downloadTo(LIVEWARE_BASE_URL + assets.liveware, path.join(dir, "liveware"));
87
+ await downloadTo(LIVEWARE_BASE_URL + assets.tunneAgent, path.join(dir, "tunnel-agent"));
88
+ log?.debug?.(`clawchat-plugin-openclaw: liveware CLI downloaded to ${dir}`);
89
+ }
90
+ catch (err) {
91
+ log?.warn?.(`clawchat-plugin-openclaw: liveware CLI download skipped: ${String(err)}`);
92
+ }
93
+ }
@@ -5,6 +5,7 @@ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
5
5
  import { createOpenclawClawlingClient, resolveOpenclawClawlingDeviceId } from "./client.js";
6
6
  import { createOpenclawClawlingApiClient } from "./api-client.js";
7
7
  import { reportPluginVersionSafe, resolvePluginVersion } from "./plugin-report.js";
8
+ import { ensureLivewareCli } from "./liveware-cli.js";
8
9
  import { ClawlingApiError } from "./api-types.js";
9
10
  import { RefreshManager } from "./refresh-manager.js";
10
11
  import { runOpenclawClawlingLogin, } from "./login.runtime.js";
@@ -618,6 +619,15 @@ export async function startOpenclawClawlingGateway(params) {
618
619
  authenticated: false,
619
620
  log,
620
621
  });
622
+ // Ensure the liveware CLI is installed (async, never blocks startup).
623
+ void ensureLivewareCli({
624
+ stateDir: runtime.state?.resolveStateDir?.(),
625
+ log: {
626
+ warn: (m) => log?.info?.(m),
627
+ debug: (m) => log?.debug?.(m),
628
+ error: (m) => log?.error?.(m),
629
+ },
630
+ });
621
631
  // §B self-heal — non-interactive env connect-code activation BEFORE blocking on
622
632
  // the wait-for-activation loop. Gated so the common path (no env code, or an
623
633
  // already-credentialed account) adds NO async work: on success it credentials
package/dist/src/tools.js CHANGED
@@ -2,6 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { execFile } from "node:child_process";
4
4
  import { createOpenclawClawlingApiClient } from "./api-client.js";
5
+ import { resolveLivewarePath } from "./liveware-cli.js";
5
6
  import { ClawlingApiError, } from "./api-types.js";
6
7
  import { resolveOpenclawClawlingAccount } from "./config.js";
7
8
  import { clawChatDbPathForStateDir, getClawChatStore, } from "./storage.js";
@@ -1048,14 +1049,20 @@ export function registerOpenclawClawlingTools(api, options = {}) {
1048
1049
  return configError("clawchat-plugin-openclaw: token is required");
1049
1050
  }
1050
1051
  return await new Promise((resolve) => {
1052
+ const livewarePath = resolveLivewarePath(api.runtime.state?.resolveStateDir?.());
1053
+ if (!livewarePath) {
1054
+ resolve(jsonResponse({
1055
+ error: "config",
1056
+ message: "liveware CLI not found in PATH; install liveware before calling this tool",
1057
+ }));
1058
+ return;
1059
+ }
1051
1060
  // Security: the token is passed as a child process argv value using
1052
1061
  // liveware's documented --access-token interface. It is in the child
1053
1062
  // argv briefly during process creation; env-var or stdin delivery
1054
1063
  // would be preferred if liveware supported it.
1055
1064
  // NEVER log the argv array; scrub token from any captured output below.
1056
- execFile("liveware", ["login", "--access-token", token], { timeout: 30000 }, (err, stdout, stderr) => {
1057
- // Scrub token from any output before returning — token must never
1058
- // appear in tool results visible to the LLM.
1065
+ execFile(livewarePath, ["login", "--access-token", token], { timeout: 30000 }, (err, stdout, stderr) => {
1059
1066
  const scrub = (s) => (token ? s.split(token).join("***") : s);
1060
1067
  const safeStdout = scrub(stdout ?? "").trim();
1061
1068
  const safeStderr = scrub(stderr ?? "").trim();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clawling/clawchat-plugin-openclaw",
3
- "version": "2026.6.23-1",
3
+ "version": "2026.6.24-1",
4
4
  "description": "OpenClaw ClawChat channel plugin",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -0,0 +1,106 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { resolveStateDir } from "openclaw/plugin-sdk/state-paths";
4
+
5
+ export const LIVEWARE_BASE_URL = "https://media.clawling.chat/liveware/";
6
+
7
+ // A non-default User-Agent is required: the CDN (Cloudflare) returns 403 for
8
+ // known scripting/default UAs, so download requests must identify themselves.
9
+ const LIVEWARE_USER_AGENT = "clawchat-liveware-installer";
10
+
11
+ export type LivewareLogger = {
12
+ warn?: (m: string) => void;
13
+ debug?: (m: string) => void;
14
+ error?: (m: string) => void;
15
+ };
16
+
17
+ /** Directory where the downloaded binaries live: <stateDir>/clawchat/liveware. */
18
+ export function livewareDir(stateDir?: string): string {
19
+ const root = stateDir ?? resolveStateDir();
20
+ return path.join(root, "clawchat", "liveware");
21
+ }
22
+
23
+ /** Map host platform/arch to the two asset filenames, or null if unsupported. */
24
+ export function platformAssets(
25
+ plat: string = process.platform,
26
+ arch: string = process.arch,
27
+ ): { liveware: string; tunneAgent: string } | null {
28
+ const os = plat === "darwin" ? "darwin" : plat === "linux" ? "linux" : null;
29
+ const cpu = arch === "x64" ? "amd64" : arch === "arm64" ? "arm64" : null;
30
+ if (!os || !cpu) return null;
31
+ return { liveware: `liveware-${os}-${cpu}`, tunneAgent: `tunnel-agent-${os}-${cpu}` };
32
+ }
33
+
34
+ function findOnPath(name: string): string | null {
35
+ const raw = process.env.PATH ?? "";
36
+ for (const dir of raw.split(path.delimiter)) {
37
+ if (!dir) continue;
38
+ const candidate = path.join(dir, name);
39
+ try {
40
+ fs.accessSync(candidate, fs.constants.X_OK);
41
+ return candidate;
42
+ } catch {
43
+ // not here; keep scanning
44
+ }
45
+ }
46
+ return null;
47
+ }
48
+
49
+ /**
50
+ * Resolve the liveware executable to invoke:
51
+ * 1. on PATH → "liveware" (let the OS resolve it)
52
+ * 2. downloaded dir → absolute path
53
+ * 3. otherwise → null
54
+ */
55
+ export function resolveLivewarePath(stateDir?: string): string | null {
56
+ if (findOnPath("liveware")) return "liveware";
57
+ const local = path.join(livewareDir(stateDir), "liveware");
58
+ try {
59
+ fs.accessSync(local, fs.constants.X_OK);
60
+ return local;
61
+ } catch {
62
+ return null;
63
+ }
64
+ }
65
+
66
+ async function downloadTo(url: string, dest: string): Promise<void> {
67
+ const res = await fetch(url, { headers: { "user-agent": LIVEWARE_USER_AGENT } });
68
+ if (!res.ok) throw new Error(`download ${url} failed: HTTP ${res.status}`);
69
+ const buf = Buffer.from(await res.arrayBuffer());
70
+ if (buf.length === 0) throw new Error(`download ${url} returned empty body`);
71
+ const tmp = `${dest}.tmp`;
72
+ await fs.promises.writeFile(tmp, buf);
73
+ await fs.promises.chmod(tmp, 0o755);
74
+ await fs.promises.rename(tmp, dest);
75
+ }
76
+
77
+ /**
78
+ * Startup hook: ensure the liveware CLI is available, downloading it when not.
79
+ * Fire-and-forget — never throws; logs and returns on any failure.
80
+ */
81
+ export async function ensureLivewareCli(
82
+ opts: { stateDir?: string; log?: LivewareLogger } = {},
83
+ ): Promise<void> {
84
+ const { stateDir, log } = opts;
85
+ try {
86
+ if (findOnPath("liveware")) return; // PATH wins
87
+ const dir = livewareDir(stateDir);
88
+ const target = path.join(dir, "liveware");
89
+ if (fs.existsSync(target)) return;
90
+
91
+ const assets = platformAssets();
92
+ if (!assets) {
93
+ log?.warn?.(
94
+ `clawchat-plugin-openclaw: unsupported platform ${process.platform}/${process.arch}; skipping liveware download`,
95
+ );
96
+ return;
97
+ }
98
+
99
+ await fs.promises.mkdir(dir, { recursive: true });
100
+ await downloadTo(LIVEWARE_BASE_URL + assets.liveware, path.join(dir, "liveware"));
101
+ await downloadTo(LIVEWARE_BASE_URL + assets.tunneAgent, path.join(dir, "tunnel-agent"));
102
+ log?.debug?.(`clawchat-plugin-openclaw: liveware CLI downloaded to ${dir}`);
103
+ } catch (err) {
104
+ log?.warn?.(`clawchat-plugin-openclaw: liveware CLI download skipped: ${String(err)}`);
105
+ }
106
+ }
package/src/runtime.ts CHANGED
@@ -16,6 +16,7 @@ import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
16
16
  import { createOpenclawClawlingClient, resolveOpenclawClawlingDeviceId } from "./client.ts";
17
17
  import { createOpenclawClawlingApiClient } from "./api-client.ts";
18
18
  import { reportPluginVersionSafe, resolvePluginVersion } from "./plugin-report.ts";
19
+ import { ensureLivewareCli } from "./liveware-cli.ts";
19
20
  import { ClawlingApiError } from "./api-types.ts";
20
21
  import { RefreshManager } from "./refresh-manager.ts";
21
22
  import {
@@ -920,6 +921,15 @@ export async function startOpenclawClawlingGateway(params: StartGatewayParams):
920
921
  authenticated: false,
921
922
  log,
922
923
  });
924
+ // Ensure the liveware CLI is installed (async, never blocks startup).
925
+ void ensureLivewareCli({
926
+ stateDir: runtime.state?.resolveStateDir?.(),
927
+ log: {
928
+ warn: (m) => log?.info?.(m),
929
+ debug: (m) => log?.debug?.(m),
930
+ error: (m) => log?.error?.(m),
931
+ },
932
+ });
923
933
  // §B self-heal — non-interactive env connect-code activation BEFORE blocking on
924
934
  // the wait-for-activation loop. Gated so the common path (no env code, or an
925
935
  // already-credentialed account) adds NO async work: on success it credentials
package/src/tools.ts CHANGED
@@ -4,6 +4,7 @@ import { execFile } from "node:child_process";
4
4
  import type { OpenClawAgentToolResult } from "openclaw/plugin-sdk/agent-harness-runtime";
5
5
  import type { OpenClawPluginApi, OpenClawPluginToolContext } from "openclaw/plugin-sdk/core";
6
6
  import { createOpenclawClawlingApiClient } from "./api-client.ts";
7
+ import { resolveLivewarePath } from "./liveware-cli.ts";
7
8
  import {
8
9
  ClawlingApiError,
9
10
  type Profile,
@@ -1366,18 +1367,24 @@ export function registerOpenclawClawlingTools(
1366
1367
  }
1367
1368
 
1368
1369
  return await new Promise<ReturnType<typeof jsonResponse>>((resolve) => {
1370
+ const livewarePath = resolveLivewarePath(api.runtime.state?.resolveStateDir?.());
1371
+ if (!livewarePath) {
1372
+ resolve(jsonResponse({
1373
+ error: "config",
1374
+ message: "liveware CLI not found in PATH; install liveware before calling this tool",
1375
+ }));
1376
+ return;
1377
+ }
1369
1378
  // Security: the token is passed as a child process argv value using
1370
1379
  // liveware's documented --access-token interface. It is in the child
1371
1380
  // argv briefly during process creation; env-var or stdin delivery
1372
1381
  // would be preferred if liveware supported it.
1373
1382
  // NEVER log the argv array; scrub token from any captured output below.
1374
1383
  execFile(
1375
- "liveware",
1384
+ livewarePath,
1376
1385
  ["login", "--access-token", token],
1377
1386
  { timeout: 30000 },
1378
1387
  (err, stdout, stderr) => {
1379
- // Scrub token from any output before returning — token must never
1380
- // appear in tool results visible to the LLM.
1381
1388
  const scrub = (s: string) => (token ? s.split(token).join("***") : s);
1382
1389
  const safeStdout = scrub(stdout ?? "").trim();
1383
1390
  const safeStderr = scrub(stderr ?? "").trim();