gewe-openclaw 2026.1.30 → 2026.2.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 +24 -10
- package/openclaw.plugin.json +1 -1
- package/package.json +7 -6
- package/src/accounts.ts +6 -6
- package/src/channel.ts +50 -29
- package/src/config-schema.ts +10 -2
- package/src/constants.ts +10 -0
- package/src/delivery.ts +43 -20
- package/src/download.ts +1 -1
- package/src/inbound.ts +32 -12
- package/src/monitor.ts +2 -2
- package/src/normalize.ts +3 -4
- package/src/onboarding.ts +265 -0
- package/src/policy.ts +6 -5
- package/src/send.ts +1 -1
- package/src/silk.ts +465 -0
- package/src/types.ts +7 -1
package/src/silk.ts
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createReadStream, existsSync } from "node:fs";
|
|
3
|
+
import fs from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { Readable } from "node:stream";
|
|
7
|
+
import { pipeline } from "node:stream/promises";
|
|
8
|
+
|
|
9
|
+
import type { ResolvedGeweAccount } from "./types.js";
|
|
10
|
+
import { getGeweRuntime } from "./runtime.js";
|
|
11
|
+
import { CHANNEL_ID } from "./constants.js";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_SILK_VERSION = "latest";
|
|
14
|
+
const DEFAULT_SILK_BASE_URL =
|
|
15
|
+
"https://github.com/Wangnov/rust-silk/releases/download";
|
|
16
|
+
const DEFAULT_DOWNLOAD_TIMEOUT_MS = 120_000;
|
|
17
|
+
const DEFAULT_EXTRACT_TIMEOUT_MS = 60_000;
|
|
18
|
+
|
|
19
|
+
type RustSilkAsset = {
|
|
20
|
+
name: string;
|
|
21
|
+
archive: "tar.xz" | "zip";
|
|
22
|
+
binary: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
type ResolvedVersion = {
|
|
26
|
+
tag: string;
|
|
27
|
+
folder: string;
|
|
28
|
+
isLatest: boolean;
|
|
29
|
+
resolvedTag?: string;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const installCache = new Map<string, Promise<string | null>>();
|
|
33
|
+
|
|
34
|
+
export function buildRustSilkEncodeArgs(params: {
|
|
35
|
+
input: string;
|
|
36
|
+
output: string;
|
|
37
|
+
sampleRate: number;
|
|
38
|
+
tencent?: boolean;
|
|
39
|
+
}): string[] {
|
|
40
|
+
const args = [
|
|
41
|
+
"encode",
|
|
42
|
+
"-i",
|
|
43
|
+
params.input,
|
|
44
|
+
"-o",
|
|
45
|
+
params.output,
|
|
46
|
+
"--sample-rate",
|
|
47
|
+
String(params.sampleRate),
|
|
48
|
+
"--quiet",
|
|
49
|
+
];
|
|
50
|
+
if (params.tencent ?? true) {
|
|
51
|
+
args.push("--tencent");
|
|
52
|
+
}
|
|
53
|
+
return args;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function buildRustSilkDecodeArgs(params: {
|
|
57
|
+
input: string;
|
|
58
|
+
output: string;
|
|
59
|
+
sampleRate: number;
|
|
60
|
+
wav?: boolean;
|
|
61
|
+
}): string[] {
|
|
62
|
+
const args = [
|
|
63
|
+
"decode",
|
|
64
|
+
"-i",
|
|
65
|
+
params.input,
|
|
66
|
+
"-o",
|
|
67
|
+
params.output,
|
|
68
|
+
"--sample-rate",
|
|
69
|
+
String(params.sampleRate),
|
|
70
|
+
"--quiet",
|
|
71
|
+
];
|
|
72
|
+
if (params.wav) args.push("--wav");
|
|
73
|
+
return args;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function ensureRustSilkBinary(
|
|
77
|
+
account: ResolvedGeweAccount,
|
|
78
|
+
): Promise<string | null> {
|
|
79
|
+
if (account.config.silkAutoDownload === false) return null;
|
|
80
|
+
|
|
81
|
+
const asset = resolveRustSilkAsset(process.platform, process.arch);
|
|
82
|
+
if (!asset) return null;
|
|
83
|
+
|
|
84
|
+
const versionInput = account.config.silkVersion?.trim() || DEFAULT_SILK_VERSION;
|
|
85
|
+
const baseUrl = account.config.silkBaseUrl?.trim() || DEFAULT_SILK_BASE_URL;
|
|
86
|
+
const resolved = await resolveRequestedVersion(versionInput, baseUrl);
|
|
87
|
+
const { tag, folder } = resolved;
|
|
88
|
+
const cacheKey = [
|
|
89
|
+
baseUrl,
|
|
90
|
+
tag,
|
|
91
|
+
folder,
|
|
92
|
+
asset.name,
|
|
93
|
+
process.platform,
|
|
94
|
+
process.arch,
|
|
95
|
+
].join("|");
|
|
96
|
+
|
|
97
|
+
if (installCache.has(cacheKey)) {
|
|
98
|
+
return installCache.get(cacheKey) ?? null;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const installPromise = installRustSilk({
|
|
102
|
+
account,
|
|
103
|
+
asset,
|
|
104
|
+
baseUrl,
|
|
105
|
+
tag,
|
|
106
|
+
folder,
|
|
107
|
+
isLatest: resolved.isLatest,
|
|
108
|
+
resolvedTag: resolved.resolvedTag,
|
|
109
|
+
}).finally(() => {
|
|
110
|
+
installCache.delete(cacheKey);
|
|
111
|
+
});
|
|
112
|
+
installCache.set(cacheKey, installPromise);
|
|
113
|
+
return installPromise;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function installRustSilk(params: {
|
|
117
|
+
account: ResolvedGeweAccount;
|
|
118
|
+
asset: RustSilkAsset;
|
|
119
|
+
baseUrl: string;
|
|
120
|
+
tag: string;
|
|
121
|
+
folder: string;
|
|
122
|
+
isLatest: boolean;
|
|
123
|
+
resolvedTag?: string;
|
|
124
|
+
}): Promise<string | null> {
|
|
125
|
+
const core = getGeweRuntime();
|
|
126
|
+
const logger = core.logging.getChildLogger({ channel: CHANNEL_ID, module: "silk" });
|
|
127
|
+
const customInstall = params.account.config.silkInstallDir?.trim();
|
|
128
|
+
const installRoot = customInstall
|
|
129
|
+
? resolveUserPath(customInstall)
|
|
130
|
+
: path.join(resolveConfigDir(), "tools", "rust-silk");
|
|
131
|
+
const installDir = path.join(installRoot, params.folder);
|
|
132
|
+
const binaryPath = path.join(installDir, params.asset.binary);
|
|
133
|
+
|
|
134
|
+
if (existsSync(binaryPath)) {
|
|
135
|
+
if (params.isLatest && params.folder !== "latest") {
|
|
136
|
+
await cleanupOldVersions(installRoot, params.folder);
|
|
137
|
+
}
|
|
138
|
+
return binaryPath;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-gewe-silk-"));
|
|
142
|
+
try {
|
|
143
|
+
const archivePath = path.join(tmpDir, params.asset.name);
|
|
144
|
+
const archiveUrl = `${params.baseUrl}/${params.tag}/${params.asset.name}`;
|
|
145
|
+
await downloadFile(archiveUrl, archivePath);
|
|
146
|
+
|
|
147
|
+
const expectedHash = await resolveChecksum({
|
|
148
|
+
account: params.account,
|
|
149
|
+
baseUrl: params.baseUrl,
|
|
150
|
+
tag: params.tag,
|
|
151
|
+
assetName: params.asset.name,
|
|
152
|
+
});
|
|
153
|
+
if (!expectedHash && params.account.config.silkAllowUnverified !== true) {
|
|
154
|
+
throw new Error("missing checksum for rust-silk download");
|
|
155
|
+
}
|
|
156
|
+
if (expectedHash) {
|
|
157
|
+
const actual = await sha256File(archivePath);
|
|
158
|
+
if (actual !== expectedHash) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`checksum mismatch for ${params.asset.name}: expected ${expectedHash} got ${actual}`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
await extractArchive(core, archivePath, tmpDir, params.asset.archive);
|
|
166
|
+
const extracted = await findBinary(tmpDir, params.asset.binary);
|
|
167
|
+
if (!extracted) {
|
|
168
|
+
throw new Error(`rust-silk binary not found in ${params.asset.name}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await fs.mkdir(installDir, { recursive: true });
|
|
172
|
+
await fs.copyFile(extracted, binaryPath);
|
|
173
|
+
await fs.chmod(binaryPath, 0o755).catch(() => {});
|
|
174
|
+
await fs.writeFile(
|
|
175
|
+
path.join(installDir, "install.json"),
|
|
176
|
+
JSON.stringify(
|
|
177
|
+
{
|
|
178
|
+
version: params.folder,
|
|
179
|
+
tag: params.tag,
|
|
180
|
+
resolvedTag: params.resolvedTag ?? null,
|
|
181
|
+
asset: params.asset.name,
|
|
182
|
+
installedAt: new Date().toISOString(),
|
|
183
|
+
},
|
|
184
|
+
null,
|
|
185
|
+
2,
|
|
186
|
+
),
|
|
187
|
+
"utf8",
|
|
188
|
+
);
|
|
189
|
+
if (params.isLatest && params.folder !== "latest") {
|
|
190
|
+
await cleanupOldVersions(installRoot, params.folder);
|
|
191
|
+
}
|
|
192
|
+
return binaryPath;
|
|
193
|
+
} catch (err) {
|
|
194
|
+
logger.warn?.(`rust-silk install failed: ${String(err)}`);
|
|
195
|
+
return null;
|
|
196
|
+
} finally {
|
|
197
|
+
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function resolveRequestedVersion(
|
|
202
|
+
versionInput: string,
|
|
203
|
+
baseUrl: string,
|
|
204
|
+
): Promise<ResolvedVersion> {
|
|
205
|
+
const trimmed = versionInput.trim();
|
|
206
|
+
if (!trimmed || trimmed === "latest") {
|
|
207
|
+
const latestTag = await resolveLatestTag(baseUrl).catch(() => null);
|
|
208
|
+
if (latestTag) {
|
|
209
|
+
const folder = latestTag.startsWith("v") ? latestTag.slice(1) : latestTag;
|
|
210
|
+
return {
|
|
211
|
+
tag: latestTag,
|
|
212
|
+
folder,
|
|
213
|
+
isLatest: true,
|
|
214
|
+
resolvedTag: latestTag,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
return { tag: "latest", folder: "latest", isLatest: true };
|
|
218
|
+
}
|
|
219
|
+
const { tag, folder } = normalizeVersion(trimmed);
|
|
220
|
+
return { tag, folder, isLatest: false };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function normalizeVersion(version: string): { tag: string; folder: string } {
|
|
224
|
+
const trimmed = version.trim();
|
|
225
|
+
const tag = trimmed.startsWith("v") ? trimmed : `v${trimmed}`;
|
|
226
|
+
const folder = trimmed.startsWith("v") ? trimmed.slice(1) : trimmed;
|
|
227
|
+
return { tag, folder };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async function resolveLatestTag(baseUrl: string): Promise<string | null> {
|
|
231
|
+
const repoUrl = deriveRepoUrl(baseUrl);
|
|
232
|
+
if (!repoUrl) return null;
|
|
233
|
+
const response = await fetchWithTimeout(
|
|
234
|
+
`${repoUrl}/releases/latest`,
|
|
235
|
+
DEFAULT_DOWNLOAD_TIMEOUT_MS,
|
|
236
|
+
);
|
|
237
|
+
const finalUrl = response.url || "";
|
|
238
|
+
const match = finalUrl.match(/\/tag\/([^/?#]+)/);
|
|
239
|
+
if (!match) return null;
|
|
240
|
+
return decodeURIComponent(match[1]);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function deriveRepoUrl(baseUrl: string): string | null {
|
|
244
|
+
const trimmed = baseUrl.replace(/\/+$/, "");
|
|
245
|
+
const match = trimmed.match(
|
|
246
|
+
/^(https?:\/\/github\.com\/[^/]+\/[^/]+)(?:\/releases\/download)?$/i,
|
|
247
|
+
);
|
|
248
|
+
if (match) return match[1];
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function resolveRustSilkAsset(
|
|
253
|
+
platform: NodeJS.Platform,
|
|
254
|
+
arch: string,
|
|
255
|
+
): RustSilkAsset | null {
|
|
256
|
+
if (platform === "darwin" && arch === "arm64") {
|
|
257
|
+
return {
|
|
258
|
+
name: "rust-silk-aarch64-apple-darwin.tar.xz",
|
|
259
|
+
archive: "tar.xz",
|
|
260
|
+
binary: "rust-silk",
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (platform === "darwin" && (arch === "x64" || arch === "amd64")) {
|
|
264
|
+
return {
|
|
265
|
+
name: "rust-silk-x86_64-apple-darwin.tar.xz",
|
|
266
|
+
archive: "tar.xz",
|
|
267
|
+
binary: "rust-silk",
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
if (platform === "linux" && arch === "arm64") {
|
|
271
|
+
return {
|
|
272
|
+
name: "rust-silk-aarch64-unknown-linux-gnu.tar.xz",
|
|
273
|
+
archive: "tar.xz",
|
|
274
|
+
binary: "rust-silk",
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
if (platform === "linux" && (arch === "x64" || arch === "amd64")) {
|
|
278
|
+
return {
|
|
279
|
+
name: "rust-silk-x86_64-unknown-linux-gnu.tar.xz",
|
|
280
|
+
archive: "tar.xz",
|
|
281
|
+
binary: "rust-silk",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
if (platform === "win32" && (arch === "x64" || arch === "amd64")) {
|
|
285
|
+
return {
|
|
286
|
+
name: "rust-silk-x86_64-pc-windows-msvc.zip",
|
|
287
|
+
archive: "zip",
|
|
288
|
+
binary: "rust-silk.exe",
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async function resolveChecksum(params: {
|
|
295
|
+
account: ResolvedGeweAccount;
|
|
296
|
+
baseUrl: string;
|
|
297
|
+
tag: string;
|
|
298
|
+
assetName: string;
|
|
299
|
+
}): Promise<string | null> {
|
|
300
|
+
if (params.account.config.silkSha256?.trim()) {
|
|
301
|
+
return params.account.config.silkSha256.trim().toLowerCase();
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const sumUrl = `${params.baseUrl}/${params.tag}/sha256.sum`;
|
|
305
|
+
const sum = await fetchText(sumUrl).catch(() => "");
|
|
306
|
+
if (sum) {
|
|
307
|
+
const parsed = parseChecksum(sum, params.assetName);
|
|
308
|
+
if (parsed) return parsed;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const assetSumUrl = `${params.baseUrl}/${params.tag}/${params.assetName}.sha256`;
|
|
312
|
+
const assetSum = await fetchText(assetSumUrl).catch(() => "");
|
|
313
|
+
if (assetSum) {
|
|
314
|
+
const parsed = parseChecksum(assetSum, params.assetName);
|
|
315
|
+
if (parsed) return parsed;
|
|
316
|
+
const fallback = assetSum.trim().split(/\s+/)[0];
|
|
317
|
+
if (/^[a-f0-9]{64}$/i.test(fallback)) return fallback.toLowerCase();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function parseChecksum(contents: string, assetName: string): string | null {
|
|
324
|
+
const lines = contents.split(/\r?\n/);
|
|
325
|
+
for (const line of lines) {
|
|
326
|
+
const trimmed = line.trim();
|
|
327
|
+
if (!trimmed) continue;
|
|
328
|
+
const match = trimmed.match(/^([a-f0-9]{64})\s+\*?(.+)$/i);
|
|
329
|
+
if (!match) continue;
|
|
330
|
+
const [, hash, name] = match;
|
|
331
|
+
if (name.trim() === assetName) return hash.toLowerCase();
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function downloadFile(url: string, dest: string): Promise<void> {
|
|
337
|
+
const response = await fetchWithTimeout(url, DEFAULT_DOWNLOAD_TIMEOUT_MS);
|
|
338
|
+
if (!response.ok) {
|
|
339
|
+
throw new Error(`download failed: ${response.status} ${response.statusText}`);
|
|
340
|
+
}
|
|
341
|
+
if (!response.body) {
|
|
342
|
+
throw new Error("download failed: empty response body");
|
|
343
|
+
}
|
|
344
|
+
const stream = Readable.fromWeb(response.body as unknown as ReadableStream);
|
|
345
|
+
await pipeline(stream, createWriteStream(dest));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async function fetchText(url: string): Promise<string> {
|
|
349
|
+
const response = await fetchWithTimeout(url, DEFAULT_DOWNLOAD_TIMEOUT_MS);
|
|
350
|
+
if (!response.ok) {
|
|
351
|
+
throw new Error(`download failed: ${response.status} ${response.statusText}`);
|
|
352
|
+
}
|
|
353
|
+
return response.text();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
async function fetchWithTimeout(url: string, timeoutMs: number): Promise<Response> {
|
|
357
|
+
const controller = new AbortController();
|
|
358
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
359
|
+
try {
|
|
360
|
+
return await fetch(url, { signal: controller.signal });
|
|
361
|
+
} finally {
|
|
362
|
+
clearTimeout(timer);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function sha256File(filePath: string): Promise<string> {
|
|
367
|
+
const hash = createHash("sha256");
|
|
368
|
+
const fileStream = createReadStream(filePath);
|
|
369
|
+
for await (const chunk of fileStream) {
|
|
370
|
+
hash.update(chunk as Buffer);
|
|
371
|
+
}
|
|
372
|
+
return hash.digest("hex");
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function extractArchive(
|
|
376
|
+
core: ReturnType<typeof getGeweRuntime>,
|
|
377
|
+
archivePath: string,
|
|
378
|
+
destDir: string,
|
|
379
|
+
archiveType: RustSilkAsset["archive"],
|
|
380
|
+
): Promise<void> {
|
|
381
|
+
const args =
|
|
382
|
+
archiveType === "zip"
|
|
383
|
+
? ["-xf", archivePath, "-C", destDir]
|
|
384
|
+
: ["-xJf", archivePath, "-C", destDir];
|
|
385
|
+
let result = await core.system.runCommandWithTimeout(["tar", ...args], {
|
|
386
|
+
timeoutMs: DEFAULT_EXTRACT_TIMEOUT_MS,
|
|
387
|
+
});
|
|
388
|
+
if (result.code === 0) return;
|
|
389
|
+
|
|
390
|
+
if (archiveType === "zip") {
|
|
391
|
+
result = await core.system.runCommandWithTimeout(
|
|
392
|
+
["powershell", "-Command", `Expand-Archive -Path "${archivePath}" -DestinationPath "${destDir}" -Force`],
|
|
393
|
+
{ timeoutMs: DEFAULT_EXTRACT_TIMEOUT_MS },
|
|
394
|
+
);
|
|
395
|
+
if (result.code === 0) return;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
throw new Error(result.stderr.trim() || `extract failed with code ${result.code ?? "?"}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async function findBinary(root: string, fileName: string): Promise<string | null> {
|
|
402
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
403
|
+
for (const entry of entries) {
|
|
404
|
+
const fullPath = path.join(root, entry.name);
|
|
405
|
+
if (entry.isFile() && entry.name === fileName) {
|
|
406
|
+
return fullPath;
|
|
407
|
+
}
|
|
408
|
+
if (entry.isDirectory()) {
|
|
409
|
+
const nested = await findBinary(fullPath, fileName);
|
|
410
|
+
if (nested) return nested;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async function cleanupOldVersions(
|
|
417
|
+
installRoot: string,
|
|
418
|
+
keepFolder: string,
|
|
419
|
+
): Promise<void> {
|
|
420
|
+
const entries = await fs.readdir(installRoot, { withFileTypes: true }).catch(() => []);
|
|
421
|
+
const tasks: Promise<void>[] = [];
|
|
422
|
+
for (const entry of entries) {
|
|
423
|
+
if (!entry.isDirectory()) continue;
|
|
424
|
+
if (entry.name === keepFolder) continue;
|
|
425
|
+
const fullPath = path.join(installRoot, entry.name);
|
|
426
|
+
tasks.push(fs.rm(fullPath, { recursive: true, force: true }).then(() => {}));
|
|
427
|
+
}
|
|
428
|
+
if (tasks.length) await Promise.all(tasks);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function resolveConfigDir(
|
|
432
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
433
|
+
homedir: () => string = os.homedir,
|
|
434
|
+
): string {
|
|
435
|
+
const override = env.OPENCLAW_STATE_DIR?.trim() || env.CLAWDBOT_STATE_DIR?.trim();
|
|
436
|
+
if (override) return resolveUserPath(override);
|
|
437
|
+
const legacyDirs = [".clawdbot", ".moltbot", ".moldbot"].map((dir) =>
|
|
438
|
+
path.join(homedir(), dir),
|
|
439
|
+
);
|
|
440
|
+
const newDir = path.join(homedir(), ".openclaw");
|
|
441
|
+
try {
|
|
442
|
+
if (existsSync(newDir)) return newDir;
|
|
443
|
+
const existingLegacy = legacyDirs.find((dir) => {
|
|
444
|
+
try {
|
|
445
|
+
return existsSync(dir);
|
|
446
|
+
} catch {
|
|
447
|
+
return false;
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
if (existingLegacy) return existingLegacy;
|
|
451
|
+
} catch {
|
|
452
|
+
// best-effort
|
|
453
|
+
}
|
|
454
|
+
return newDir;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function resolveUserPath(input: string): string {
|
|
458
|
+
const trimmed = input.trim();
|
|
459
|
+
if (!trimmed) return trimmed;
|
|
460
|
+
if (trimmed.startsWith("~")) {
|
|
461
|
+
const expanded = trimmed.replace(/^~(?=$|[\\/])/, os.homedir());
|
|
462
|
+
return path.resolve(expanded);
|
|
463
|
+
}
|
|
464
|
+
return path.resolve(trimmed);
|
|
465
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -42,6 +42,12 @@ export type GeweAccountConfig = {
|
|
|
42
42
|
voiceDecodeArgs?: string[];
|
|
43
43
|
voiceDecodeSampleRate?: number;
|
|
44
44
|
voiceDecodeOutput?: "pcm" | "wav";
|
|
45
|
+
silkAutoDownload?: boolean;
|
|
46
|
+
silkVersion?: string;
|
|
47
|
+
silkBaseUrl?: string;
|
|
48
|
+
silkSha256?: string;
|
|
49
|
+
silkAllowUnverified?: boolean;
|
|
50
|
+
silkInstallDir?: string;
|
|
45
51
|
videoFfmpegPath?: string;
|
|
46
52
|
videoFfprobePath?: string;
|
|
47
53
|
videoThumbUrl?: string;
|
|
@@ -67,7 +73,7 @@ export type GeweConfig = {
|
|
|
67
73
|
|
|
68
74
|
export type CoreConfig = {
|
|
69
75
|
channels?: {
|
|
70
|
-
gewe?: GeweConfig;
|
|
76
|
+
"gewe-openclaw"?: GeweConfig;
|
|
71
77
|
};
|
|
72
78
|
[key: string]: unknown;
|
|
73
79
|
};
|