openclaw-telegram-manager 1.3.0 → 1.3.2
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/dist/lib/include-generator.d.ts +1 -1
- package/dist/lib/include-generator.d.ts.map +1 -1
- package/dist/lib/include-generator.js +33 -2
- package/dist/lib/include-generator.js.map +1 -1
- package/dist/plugin.js +29 -2
- package/dist/setup.js +33 -15
- package/dist/setup.js.map +1 -1
- package/package.json +2 -3
- package/src/commands/archive.ts +0 -89
- package/src/commands/doctor-all.ts +0 -243
- package/src/commands/doctor.ts +0 -100
- package/src/commands/help.ts +0 -11
- package/src/commands/init.ts +0 -376
- package/src/commands/list.ts +0 -28
- package/src/commands/rename.ts +0 -140
- package/src/commands/snooze.ts +0 -69
- package/src/commands/status.ts +0 -59
- package/src/commands/sync.ts +0 -46
- package/src/commands/upgrade.ts +0 -64
- package/src/index.ts +0 -91
- package/src/lib/audit.ts +0 -44
- package/src/lib/auth.ts +0 -96
- package/src/lib/capsule.ts +0 -206
- package/src/lib/config-restart.ts +0 -167
- package/src/lib/doctor-checks.ts +0 -639
- package/src/lib/include-generator.ts +0 -174
- package/src/lib/registry.ts +0 -197
- package/src/lib/security.ts +0 -174
- package/src/lib/telegram.ts +0 -311
- package/src/lib/types.ts +0 -172
- package/src/setup.ts +0 -475
- package/src/templates/base/COMMANDS.md +0 -3
- package/src/templates/base/CRON.md +0 -3
- package/src/templates/base/LINKS.md +0 -3
- package/src/templates/base/NOTES.md +0 -3
- package/src/templates/base/README.md +0 -3
- package/src/templates/base/TODO.md +0 -11
- package/src/templates/overlays/coding/ARCHITECTURE.md +0 -3
- package/src/templates/overlays/coding/DEPLOY.md +0 -3
- package/src/templates/overlays/marketing/CAMPAIGNS.md +0 -3
- package/src/templates/overlays/marketing/METRICS.md +0 -3
- package/src/templates/overlays/research/FINDINGS.md +0 -3
- package/src/templates/overlays/research/SOURCES.md +0 -3
- package/src/tool.ts +0 -282
package/src/setup.ts
DELETED
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import * as fs from 'node:fs';
|
|
4
|
-
import * as path from 'node:path';
|
|
5
|
-
import * as crypto from 'node:crypto';
|
|
6
|
-
import { execSync } from 'node:child_process';
|
|
7
|
-
|
|
8
|
-
// ── Constants ──────────────────────────────────────────────────────────
|
|
9
|
-
|
|
10
|
-
const PLUGIN_NAME = 'openclaw-telegram-manager';
|
|
11
|
-
const PLUGIN_VERSION: string = JSON.parse(
|
|
12
|
-
fs.readFileSync(new URL('../package.json', import.meta.url), 'utf-8'),
|
|
13
|
-
).version;
|
|
14
|
-
const MIN_OPENCLAW_VERSION = '2026.1.0';
|
|
15
|
-
const INCLUDE_FILENAME = 'telegram-manager.generated.groups.json5';
|
|
16
|
-
const REGISTRY_FILENAME = 'topics.json';
|
|
17
|
-
const PLUGIN_FILES = ['openclaw.plugin.json', 'dist/plugin.js', 'skills', 'package.json'];
|
|
18
|
-
|
|
19
|
-
// ── Colors (zero dependencies, respects NO_COLOR / non-TTY) ──────────
|
|
20
|
-
|
|
21
|
-
const useColor =
|
|
22
|
-
process.stdout.isTTY === true &&
|
|
23
|
-
!process.env['NO_COLOR'] &&
|
|
24
|
-
process.env['TERM'] !== 'dumb';
|
|
25
|
-
|
|
26
|
-
const c = {
|
|
27
|
-
reset: useColor ? '\x1b[0m' : '',
|
|
28
|
-
bold: useColor ? '\x1b[1m' : '',
|
|
29
|
-
dim: useColor ? '\x1b[2m' : '',
|
|
30
|
-
green: useColor ? '\x1b[32m' : '',
|
|
31
|
-
yellow: useColor ? '\x1b[33m' : '',
|
|
32
|
-
red: useColor ? '\x1b[31m' : '',
|
|
33
|
-
cyan: useColor ? '\x1b[36m' : '',
|
|
34
|
-
magenta: useColor ? '\x1b[35m' : '',
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// ── Spinner (zero dependencies, TTY-only) ────────────────────────────
|
|
38
|
-
|
|
39
|
-
let spinnerTimer: ReturnType<typeof setInterval> | null = null;
|
|
40
|
-
|
|
41
|
-
function startSpinner(msg: string): void {
|
|
42
|
-
stopSpinner();
|
|
43
|
-
if (!process.stdout.isTTY) return;
|
|
44
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
45
|
-
let i = 0;
|
|
46
|
-
const render = () => {
|
|
47
|
-
process.stdout.write(`\r ${c.cyan}${frames[i++ % frames.length]}${c.reset} ${msg}`);
|
|
48
|
-
};
|
|
49
|
-
render();
|
|
50
|
-
spinnerTimer = setInterval(render, 80);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function stopSpinner(): void {
|
|
54
|
-
if (spinnerTimer) {
|
|
55
|
-
clearInterval(spinnerTimer);
|
|
56
|
-
spinnerTimer = null;
|
|
57
|
-
process.stdout.write('\r\x1b[2K');
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ── Logging helpers ───────────────────────────────────────────────────
|
|
62
|
-
|
|
63
|
-
function ok(msg: string): void {
|
|
64
|
-
stopSpinner();
|
|
65
|
-
console.log(` ${c.green}✓${c.reset} ${msg}`);
|
|
66
|
-
}
|
|
67
|
-
function warn(msg: string): void {
|
|
68
|
-
stopSpinner();
|
|
69
|
-
console.warn(` ${c.yellow}⚠${c.reset} ${c.yellow}${msg}${c.reset}`);
|
|
70
|
-
}
|
|
71
|
-
function fail(msg: string): void {
|
|
72
|
-
stopSpinner();
|
|
73
|
-
console.error(` ${c.red}✗${c.reset} ${c.red}${msg}${c.reset}`);
|
|
74
|
-
}
|
|
75
|
-
function info(msg: string): void {
|
|
76
|
-
console.log(` ${c.dim}${msg}${c.reset}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function banner(title: string, subtitle?: string): void {
|
|
80
|
-
console.log('');
|
|
81
|
-
console.log(` ${c.cyan}◆${c.reset} ${c.bold}${title}${c.reset}${subtitle ? ` ${c.dim}${subtitle}${c.reset}` : ''}`);
|
|
82
|
-
console.log(` ${c.dim}│${c.reset}`);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function footer(msg: string): void {
|
|
86
|
-
console.log(` ${c.dim}│${c.reset}`);
|
|
87
|
-
console.log(` ${c.green}◆${c.reset} ${c.bold}${msg}${c.reset}`);
|
|
88
|
-
console.log('');
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// ── Entry point ───────────────────────────────────────────────────────
|
|
92
|
-
|
|
93
|
-
const command = process.argv[2] ?? 'setup';
|
|
94
|
-
|
|
95
|
-
if (command === 'setup') {
|
|
96
|
-
runSetup().catch((err) => {
|
|
97
|
-
stopSpinner();
|
|
98
|
-
console.error('Setup failed:', err instanceof Error ? err.message : String(err));
|
|
99
|
-
process.exit(1);
|
|
100
|
-
});
|
|
101
|
-
} else if (command === 'uninstall') {
|
|
102
|
-
runUninstall().catch((err) => {
|
|
103
|
-
stopSpinner();
|
|
104
|
-
console.error('Uninstall failed:', err instanceof Error ? err.message : String(err));
|
|
105
|
-
process.exit(1);
|
|
106
|
-
});
|
|
107
|
-
} else {
|
|
108
|
-
console.error(`Unknown command: ${command}`);
|
|
109
|
-
console.error(`Usage: ${PLUGIN_NAME} [setup|uninstall]`);
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// ── Setup ─────────────────────────────────────────────────────────────
|
|
114
|
-
|
|
115
|
-
async function runSetup(): Promise<void> {
|
|
116
|
-
banner(PLUGIN_NAME, `v${PLUGIN_VERSION}`);
|
|
117
|
-
|
|
118
|
-
startSpinner('Checking OpenClaw version…');
|
|
119
|
-
const version = checkOpenClawVersion();
|
|
120
|
-
ok(`OpenClaw ${c.dim}${version}${c.reset}`);
|
|
121
|
-
|
|
122
|
-
startSpinner('Locating config…');
|
|
123
|
-
const configDir = locateConfigDir();
|
|
124
|
-
checkDirPermissions(configDir);
|
|
125
|
-
ok(`Config ${c.dim}${configDir}${c.reset}`);
|
|
126
|
-
|
|
127
|
-
startSpinner('Installing plugin…');
|
|
128
|
-
installPlugin(configDir);
|
|
129
|
-
|
|
130
|
-
startSpinner('Patching config…');
|
|
131
|
-
patchConfig(configDir);
|
|
132
|
-
|
|
133
|
-
startSpinner('Preparing workspace…');
|
|
134
|
-
const projectsDir = path.join(configDir, 'workspace', 'projects');
|
|
135
|
-
ensureDir(projectsDir);
|
|
136
|
-
initRegistry(projectsDir);
|
|
137
|
-
createEmptyInclude(configDir);
|
|
138
|
-
ok('Workspace ready');
|
|
139
|
-
|
|
140
|
-
startSpinner('Restarting gateway…');
|
|
141
|
-
triggerRestart();
|
|
142
|
-
ok('Gateway restarted');
|
|
143
|
-
|
|
144
|
-
footer('Setup complete');
|
|
145
|
-
|
|
146
|
-
console.log(` ${c.dim}Next steps:${c.reset}`);
|
|
147
|
-
console.log(` ${c.dim}1.${c.reset} Open any Telegram forum topic`);
|
|
148
|
-
console.log(` ${c.dim}2.${c.reset} Type ${c.cyan}/topic init${c.reset}`);
|
|
149
|
-
console.log(` ${c.dim}3.${c.reset} The topic will be registered and a capsule created`);
|
|
150
|
-
console.log('');
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// ── Uninstall ─────────────────────────────────────────────────────────
|
|
154
|
-
|
|
155
|
-
async function runUninstall(): Promise<void> {
|
|
156
|
-
banner(PLUGIN_NAME, 'uninstall');
|
|
157
|
-
|
|
158
|
-
startSpinner('Locating config…');
|
|
159
|
-
const configDir = locateConfigDir();
|
|
160
|
-
ok(`Config ${c.dim}${configDir}${c.reset}`);
|
|
161
|
-
|
|
162
|
-
startSpinner('Removing plugin…');
|
|
163
|
-
unpatchConfig(configDir);
|
|
164
|
-
removeFile(path.join(configDir, INCLUDE_FILENAME));
|
|
165
|
-
removePluginDir(configDir);
|
|
166
|
-
ok('Plugin files removed');
|
|
167
|
-
|
|
168
|
-
startSpinner('Restarting gateway…');
|
|
169
|
-
triggerRestart();
|
|
170
|
-
ok('Gateway restarted');
|
|
171
|
-
|
|
172
|
-
const projectsDir = path.join(configDir, 'workspace', 'projects');
|
|
173
|
-
if (fs.existsSync(projectsDir)) {
|
|
174
|
-
info('Workspace data kept: ' + projectsDir);
|
|
175
|
-
info('To remove: rm -rf ' + projectsDir);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
footer('Uninstall complete');
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// ── Setup step implementations ────────────────────────────────────────
|
|
182
|
-
|
|
183
|
-
function checkOpenClawVersion(): string {
|
|
184
|
-
let version: string;
|
|
185
|
-
try {
|
|
186
|
-
version = execSync('openclaw --version', { encoding: 'utf-8' }).trim();
|
|
187
|
-
} catch {
|
|
188
|
-
fail('OpenClaw not found. Install OpenClaw (>=2026.1.0) first.');
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const match = version.match(/(\d+\.\d+\.\d+)/);
|
|
193
|
-
if (!match) {
|
|
194
|
-
warn(`Could not parse version from "${version}". Proceeding anyway.`);
|
|
195
|
-
return version;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const versionStr = match[1]!;
|
|
199
|
-
if (compareVersions(versionStr, MIN_OPENCLAW_VERSION) < 0) {
|
|
200
|
-
fail(`OpenClaw ${versionStr} found, requires >=${MIN_OPENCLAW_VERSION}. Please upgrade.`);
|
|
201
|
-
process.exit(1);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return versionStr;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function locateConfigDir(): string {
|
|
208
|
-
const envDir = process.env['OPENCLAW_CONFIG_DIR'];
|
|
209
|
-
if (envDir && fs.existsSync(envDir)) {
|
|
210
|
-
return path.resolve(envDir);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
const homeDir = process.env['HOME'] ?? process.env['USERPROFILE'] ?? '';
|
|
214
|
-
const defaultDir = path.join(homeDir, '.openclaw');
|
|
215
|
-
if (fs.existsSync(defaultDir)) {
|
|
216
|
-
return defaultDir;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
let dir = process.cwd();
|
|
220
|
-
while (dir !== path.dirname(dir)) {
|
|
221
|
-
if (fs.existsSync(path.join(dir, 'openclaw.json'))) {
|
|
222
|
-
return dir;
|
|
223
|
-
}
|
|
224
|
-
dir = path.dirname(dir);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
fail('Could not find OpenClaw config directory. Set $OPENCLAW_CONFIG_DIR or ensure ~/.openclaw/ exists.');
|
|
228
|
-
process.exit(1);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function checkDirPermissions(dir: string): void {
|
|
232
|
-
try {
|
|
233
|
-
const stat = fs.statSync(dir);
|
|
234
|
-
const mode = stat.mode;
|
|
235
|
-
const permissions = (mode & 0o777).toString(8);
|
|
236
|
-
|
|
237
|
-
if (mode & 0o002) {
|
|
238
|
-
warn(`${dir} is world-writable (${permissions}). Consider chmod 700.`);
|
|
239
|
-
} else if (mode & 0o020) {
|
|
240
|
-
warn(`${dir} is group-writable (${permissions}). Consider chmod 700.`);
|
|
241
|
-
}
|
|
242
|
-
} catch {
|
|
243
|
-
warn(`Could not check permissions for ${dir}.`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function installPlugin(configDir: string): void {
|
|
248
|
-
const extDir = path.join(configDir, 'extensions', PLUGIN_NAME);
|
|
249
|
-
|
|
250
|
-
if (fs.existsSync(path.join(extDir, 'openclaw.plugin.json'))) {
|
|
251
|
-
ok('Plugin already installed');
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const pkgRoot = findPackageRoot();
|
|
256
|
-
if (pkgRoot) {
|
|
257
|
-
fs.mkdirSync(extDir, { recursive: true });
|
|
258
|
-
for (const entry of PLUGIN_FILES) {
|
|
259
|
-
const src = path.join(pkgRoot, entry);
|
|
260
|
-
if (!fs.existsSync(src)) continue;
|
|
261
|
-
const dest = path.join(extDir, entry);
|
|
262
|
-
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
263
|
-
copyRecursive(src, dest);
|
|
264
|
-
}
|
|
265
|
-
ok('Plugin installed');
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
try {
|
|
270
|
-
execSync(`openclaw plugins install ${PLUGIN_NAME}`, {
|
|
271
|
-
encoding: 'utf-8',
|
|
272
|
-
stdio: 'inherit',
|
|
273
|
-
});
|
|
274
|
-
ok('Plugin installed');
|
|
275
|
-
} catch {
|
|
276
|
-
warn('Could not install plugin. You may need to install manually.');
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
function patchConfig(configDir: string): void {
|
|
281
|
-
const configPath = path.join(configDir, 'openclaw.json');
|
|
282
|
-
|
|
283
|
-
if (!fs.existsSync(configPath)) {
|
|
284
|
-
warn(`${configPath} not found. Skipping config patch.`);
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
let content: string;
|
|
289
|
-
try {
|
|
290
|
-
content = fs.readFileSync(configPath, 'utf-8');
|
|
291
|
-
} catch {
|
|
292
|
-
warn(`Could not read ${configPath}. Skipping config patch.`);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
if (content.includes(INCLUDE_FILENAME)) {
|
|
297
|
-
ok('Config already patched');
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
let config: Record<string, unknown>;
|
|
302
|
-
try {
|
|
303
|
-
config = JSON.parse(content) as Record<string, unknown>;
|
|
304
|
-
} catch {
|
|
305
|
-
warn('Could not parse openclaw.json. Please manually add the $include reference.');
|
|
306
|
-
info(`Add to channels.telegram.groups: { "$include": "./${INCLUDE_FILENAME}" }`);
|
|
307
|
-
return;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (!config['channels']) config['channels'] = {};
|
|
311
|
-
const channels = config['channels'] as Record<string, unknown>;
|
|
312
|
-
|
|
313
|
-
if (!channels['telegram']) channels['telegram'] = {};
|
|
314
|
-
const telegram = channels['telegram'] as Record<string, unknown>;
|
|
315
|
-
|
|
316
|
-
telegram['groups'] = { $include: `./${INCLUDE_FILENAME}` };
|
|
317
|
-
|
|
318
|
-
const bakPath = configPath + '.bak';
|
|
319
|
-
fs.copyFileSync(configPath, bakPath);
|
|
320
|
-
|
|
321
|
-
const newContent = JSON.stringify(config, null, 2) + '\n';
|
|
322
|
-
fs.writeFileSync(configPath, newContent, { mode: 0o600 });
|
|
323
|
-
ok('Config patched');
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function initRegistry(projectsDir: string): void {
|
|
327
|
-
const registryPath = path.join(projectsDir, REGISTRY_FILENAME);
|
|
328
|
-
|
|
329
|
-
if (fs.existsSync(registryPath)) {
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const callbackSecret = crypto.randomBytes(32).toString('hex');
|
|
334
|
-
const registry = {
|
|
335
|
-
version: 1,
|
|
336
|
-
topicManagerAdmins: [],
|
|
337
|
-
callbackSecret,
|
|
338
|
-
lastDoctorAllRunAt: null,
|
|
339
|
-
maxTopics: 100,
|
|
340
|
-
topics: {},
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + '\n', {
|
|
344
|
-
mode: 0o600,
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function createEmptyInclude(configDir: string): void {
|
|
349
|
-
const includePath = path.join(configDir, INCLUDE_FILENAME);
|
|
350
|
-
|
|
351
|
-
if (fs.existsSync(includePath)) {
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
const content = [
|
|
356
|
-
'// This file is generated by telegram-manager. Do not hand-edit.',
|
|
357
|
-
'{}',
|
|
358
|
-
'',
|
|
359
|
-
].join('\n');
|
|
360
|
-
|
|
361
|
-
fs.writeFileSync(includePath, content, { mode: 0o600 });
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
// ── Uninstall step implementations ────────────────────────────────────
|
|
365
|
-
|
|
366
|
-
function unpatchConfig(configDir: string): void {
|
|
367
|
-
const configPath = path.join(configDir, 'openclaw.json');
|
|
368
|
-
const bakPath = configPath + '.bak';
|
|
369
|
-
|
|
370
|
-
if (!fs.existsSync(configPath)) {
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
let content: string;
|
|
375
|
-
try {
|
|
376
|
-
content = fs.readFileSync(configPath, 'utf-8');
|
|
377
|
-
} catch {
|
|
378
|
-
warn(`Could not read ${configPath}.`);
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (!content.includes(INCLUDE_FILENAME)) {
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
let config: Record<string, unknown>;
|
|
387
|
-
try {
|
|
388
|
-
config = JSON.parse(content) as Record<string, unknown>;
|
|
389
|
-
} catch {
|
|
390
|
-
warn('Could not parse openclaw.json. Please manually remove the $include reference.');
|
|
391
|
-
return;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
const channels = config['channels'] as Record<string, unknown> | undefined;
|
|
395
|
-
const telegram = channels?.['telegram'] as Record<string, unknown> | undefined;
|
|
396
|
-
if (telegram) {
|
|
397
|
-
delete telegram['groups'];
|
|
398
|
-
if (Object.keys(telegram).length === 0) delete channels!['telegram'];
|
|
399
|
-
if (Object.keys(channels!).length === 0) delete config['channels'];
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
|
|
403
|
-
|
|
404
|
-
// Clean up stale backup from install time
|
|
405
|
-
if (fs.existsSync(bakPath)) {
|
|
406
|
-
fs.unlinkSync(bakPath);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function removeFile(filePath: string): void {
|
|
411
|
-
if (fs.existsSync(filePath)) {
|
|
412
|
-
fs.unlinkSync(filePath);
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
function removePluginDir(configDir: string): void {
|
|
417
|
-
const extDir = path.join(configDir, 'extensions', PLUGIN_NAME);
|
|
418
|
-
if (fs.existsSync(extDir)) {
|
|
419
|
-
fs.rmSync(extDir, { recursive: true });
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// ── Shared helpers ────────────────────────────────────────────────────
|
|
424
|
-
|
|
425
|
-
function triggerRestart(): void {
|
|
426
|
-
try {
|
|
427
|
-
execSync('openclaw gateway restart', {
|
|
428
|
-
encoding: 'utf-8',
|
|
429
|
-
timeout: 10_000,
|
|
430
|
-
});
|
|
431
|
-
} catch {
|
|
432
|
-
warn('Could not restart gateway. Run `openclaw gateway restart` manually.');
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
function ensureDir(dir: string): void {
|
|
437
|
-
if (!fs.existsSync(dir)) {
|
|
438
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
function compareVersions(a: string, b: string): number {
|
|
443
|
-
const aParts = a.split('.').map(Number);
|
|
444
|
-
const bParts = b.split('.').map(Number);
|
|
445
|
-
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
446
|
-
const aVal = aParts[i] ?? 0;
|
|
447
|
-
const bVal = bParts[i] ?? 0;
|
|
448
|
-
if (aVal !== bVal) return aVal - bVal;
|
|
449
|
-
}
|
|
450
|
-
return 0;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
function findPackageRoot(): string | null {
|
|
455
|
-
let dir = path.dirname(new URL(import.meta.url).pathname);
|
|
456
|
-
for (let i = 0; i < 5; i++) {
|
|
457
|
-
if (fs.existsSync(path.join(dir, 'openclaw.plugin.json'))) {
|
|
458
|
-
return dir;
|
|
459
|
-
}
|
|
460
|
-
dir = path.dirname(dir);
|
|
461
|
-
}
|
|
462
|
-
return null;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
function copyRecursive(src: string, dest: string): void {
|
|
466
|
-
const stat = fs.statSync(src);
|
|
467
|
-
if (stat.isDirectory()) {
|
|
468
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
469
|
-
for (const entry of fs.readdirSync(src)) {
|
|
470
|
-
copyRecursive(path.join(src, entry), path.join(dest, entry));
|
|
471
|
-
}
|
|
472
|
-
} else {
|
|
473
|
-
fs.copyFileSync(src, dest);
|
|
474
|
-
}
|
|
475
|
-
}
|