@autotask/atools-tool 0.1.8 → 0.1.9
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 +4 -1
- package/lib/config-openclaw.mjs +76 -12
- package/lib/install.mjs +75 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,8 @@ Selection UX:
|
|
|
31
31
|
- TTY terminal: use `↑/↓` and `Enter`
|
|
32
32
|
- Non-TTY: falls back to number input
|
|
33
33
|
|
|
34
|
+
If proxy startup fails during `atools-tool openclaw`, the CLI now prints environment-aware troubleshooting hints (systemd user bus, permissions, port readiness) and does not silently fail.
|
|
35
|
+
|
|
34
36
|
Rollback to codex-compatible defaults:
|
|
35
37
|
|
|
36
38
|
```bash
|
|
@@ -64,7 +66,7 @@ atools-tool-install --help
|
|
|
64
66
|
|
|
65
67
|
`atools-tool-install` service behavior by platform:
|
|
66
68
|
|
|
67
|
-
- Linux: systemd user service
|
|
69
|
+
- Linux: systemd user service (fallback to detached process if `systemctl --user` is unavailable)
|
|
68
70
|
- macOS: launchd user agent (`~/Library/LaunchAgents`)
|
|
69
71
|
- Windows: Task Scheduler user task (ONLOGON + start now)
|
|
70
72
|
|
|
@@ -98,6 +100,7 @@ curl -fsSL https://raw.githubusercontent.com/aak1247/sub2api-openclaw-proxy/main
|
|
|
98
100
|
- patches `~/.openclaw/agents/main/agent/auth-profiles.json` (if API key is available)
|
|
99
101
|
- service install by platform:
|
|
100
102
|
- Linux: creates/enables `~/.config/systemd/user/openclaw-atools-proxy.service`
|
|
103
|
+
- if user bus/systemd is unavailable (for example container/root without login bus), installer auto-falls back to detached proxy process
|
|
101
104
|
- macOS: creates/enables `~/Library/LaunchAgents/openclaw-atools-proxy.plist`
|
|
102
105
|
- Windows: creates/updates scheduled task `openclaw-atools-proxy`
|
|
103
106
|
- restarts `openclaw-gateway.service` only on Linux (unless `--no-restart`)
|
package/lib/config-openclaw.mjs
CHANGED
|
@@ -296,9 +296,22 @@ function isLinuxProxyServiceActive() {
|
|
|
296
296
|
|
|
297
297
|
function startLinuxProxyService() {
|
|
298
298
|
const reload = runSystemctl(['daemon-reload']);
|
|
299
|
-
if (reload.status !== 0)
|
|
299
|
+
if (reload.status !== 0) {
|
|
300
|
+
return {
|
|
301
|
+
ok: false,
|
|
302
|
+
step: 'daemon-reload',
|
|
303
|
+
detail: String(reload.stderr || reload.stdout || '').trim()
|
|
304
|
+
};
|
|
305
|
+
}
|
|
300
306
|
const start = runSystemctl(['enable', '--now', PROXY_SERVICE]);
|
|
301
|
-
|
|
307
|
+
if (start.status !== 0) {
|
|
308
|
+
return {
|
|
309
|
+
ok: false,
|
|
310
|
+
step: 'enable-start',
|
|
311
|
+
detail: String(start.stderr || start.stdout || '').trim()
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
return { ok: true, step: 'enable-start', detail: '' };
|
|
302
315
|
}
|
|
303
316
|
|
|
304
317
|
function startDetachedProxy() {
|
|
@@ -350,6 +363,31 @@ async function isPortOpen(host, port, timeoutMs = 900) {
|
|
|
350
363
|
});
|
|
351
364
|
}
|
|
352
365
|
|
|
366
|
+
function buildProxyTroubleshootingHints({ linuxSystemdError } = {}) {
|
|
367
|
+
const hints = [];
|
|
368
|
+
const err = String(linuxSystemdError || '');
|
|
369
|
+
|
|
370
|
+
if (/Failed to connect to bus|No medium found/i.test(err)) {
|
|
371
|
+
hints.push('当前环境没有可用的 systemd 用户会话(常见于容器、root、无登录会话)。');
|
|
372
|
+
hints.push('建议:直接重新执行 `atools-tool-install`,新版会自动降级到非-systemd进程模式。');
|
|
373
|
+
hints.push('如果是容器环境,请确保进程管理策略允许后台常驻进程。');
|
|
374
|
+
return hints;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (/permission denied|access denied/i.test(err)) {
|
|
378
|
+
hints.push('权限不足,无法通过 systemd --user 启动服务。');
|
|
379
|
+
hints.push('建议:使用普通登录用户执行安装与配置,不要混用 root 与普通用户的 OpenClaw 目录。');
|
|
380
|
+
return hints;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
if (err) {
|
|
384
|
+
hints.push(`systemd 启动失败:${err}`);
|
|
385
|
+
}
|
|
386
|
+
hints.push('建议:检查 OpenClaw Home 路径与 Node 可执行路径是否存在且可访问。');
|
|
387
|
+
hints.push(`建议:确认端口 ${PROXY_PORT} 未被防火墙或策略阻断。`);
|
|
388
|
+
return hints;
|
|
389
|
+
}
|
|
390
|
+
|
|
353
391
|
async function ensureProxyReady() {
|
|
354
392
|
if (process.platform === 'linux') {
|
|
355
393
|
const service = configureLinuxProxyService();
|
|
@@ -360,13 +398,13 @@ async function ensureProxyReady() {
|
|
|
360
398
|
}
|
|
361
399
|
}
|
|
362
400
|
|
|
363
|
-
if (await isPortOpen(PROXY_HOST, PROXY_PORT)) return true;
|
|
401
|
+
if (await isPortOpen(PROXY_HOST, PROXY_PORT)) return { ok: true, mode: 'already-running' };
|
|
364
402
|
|
|
365
403
|
// Linux retry: service may exist but not running.
|
|
366
404
|
if (process.platform === 'linux') {
|
|
367
405
|
const service = configureLinuxProxyService();
|
|
368
406
|
if (service.exists && isLinuxProxyServiceActive()) {
|
|
369
|
-
if (await isPortOpen(PROXY_HOST, PROXY_PORT)) return true;
|
|
407
|
+
if (await isPortOpen(PROXY_HOST, PROXY_PORT)) return { ok: true, mode: 'already-running' };
|
|
370
408
|
}
|
|
371
409
|
}
|
|
372
410
|
|
|
@@ -388,26 +426,45 @@ async function ensureProxyReady() {
|
|
|
388
426
|
}
|
|
389
427
|
|
|
390
428
|
if (action === '取消并退出') {
|
|
391
|
-
return false;
|
|
429
|
+
return { ok: false, reason: 'user-cancelled', hints: [] };
|
|
392
430
|
}
|
|
393
431
|
|
|
394
432
|
let started = false;
|
|
433
|
+
let linuxSystemdError = '';
|
|
395
434
|
if (process.platform === 'linux') {
|
|
396
|
-
|
|
435
|
+
const systemdStart = startLinuxProxyService();
|
|
436
|
+
started = systemdStart.ok;
|
|
437
|
+
linuxSystemdError = systemdStart.detail || '';
|
|
397
438
|
}
|
|
439
|
+
let mode = 'systemd';
|
|
398
440
|
if (!started) {
|
|
399
441
|
started = startDetachedProxy();
|
|
442
|
+
mode = 'detached';
|
|
400
443
|
}
|
|
401
444
|
|
|
402
|
-
if (!started)
|
|
445
|
+
if (!started) {
|
|
446
|
+
return {
|
|
447
|
+
ok: false,
|
|
448
|
+
reason: 'start-failed',
|
|
449
|
+
hints: buildProxyTroubleshootingHints({ linuxSystemdError })
|
|
450
|
+
};
|
|
451
|
+
}
|
|
403
452
|
|
|
404
453
|
for (let i = 0; i < 5; i += 1) {
|
|
405
454
|
await sleep(500);
|
|
406
455
|
if (await isPortOpen(PROXY_HOST, PROXY_PORT)) {
|
|
407
|
-
return true;
|
|
456
|
+
return { ok: true, mode };
|
|
408
457
|
}
|
|
409
458
|
}
|
|
410
|
-
return
|
|
459
|
+
return {
|
|
460
|
+
ok: false,
|
|
461
|
+
reason: 'port-not-open',
|
|
462
|
+
hints: [
|
|
463
|
+
'代理进程启动后端口仍未就绪。',
|
|
464
|
+
`请检查日志:${PROXY_LOG_FILE}`,
|
|
465
|
+
'如为受限环境(容器/最小系统),请确认允许本地监听 127.0.0.1。'
|
|
466
|
+
]
|
|
467
|
+
};
|
|
411
468
|
}
|
|
412
469
|
|
|
413
470
|
function printRestartHint() {
|
|
@@ -442,11 +499,18 @@ export async function runConfigOpenclaw({ openclawHome } = {}) {
|
|
|
442
499
|
process.stdout.write(`固定代理目标: ${PROXY_UPSTREAM}\n`);
|
|
443
500
|
process.stdout.write(`固定 Provider Base URL: ${PROXY_BASE_URL}\n`);
|
|
444
501
|
|
|
445
|
-
const
|
|
446
|
-
if (!
|
|
447
|
-
process.stdout.write('\n
|
|
502
|
+
const proxyStatus = await ensureProxyReady();
|
|
503
|
+
if (!proxyStatus.ok) {
|
|
504
|
+
process.stdout.write('\n本地代理未就绪,未修改 OpenClaw 模型配置。\n');
|
|
505
|
+
if (Array.isArray(proxyStatus.hints) && proxyStatus.hints.length > 0) {
|
|
506
|
+
process.stdout.write('可行解决方案:\n');
|
|
507
|
+
proxyStatus.hints.forEach((hint) => process.stdout.write(`- ${hint}\n`));
|
|
508
|
+
}
|
|
448
509
|
return;
|
|
449
510
|
}
|
|
511
|
+
if (proxyStatus.mode === 'detached') {
|
|
512
|
+
process.stdout.write('\n已通过非-systemd模式启动代理(兼容无 user bus 环境)。\n');
|
|
513
|
+
}
|
|
450
514
|
rl = readlinePromises.createInterface({
|
|
451
515
|
input: process.stdin,
|
|
452
516
|
output: process.stdout
|
package/lib/install.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import os from 'node:os';
|
|
4
|
-
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
|
|
7
7
|
const DEFAULT_PROVIDER = 'sub2api';
|
|
@@ -350,7 +350,56 @@ function runSystemctl(args, { dryRun } = {}) {
|
|
|
350
350
|
execFileSync('systemctl', ['--user', ...args], { stdio: 'inherit' });
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
function
|
|
353
|
+
function printSystemdFallbackHints(detail) {
|
|
354
|
+
const text = String(detail || '');
|
|
355
|
+
if (/Failed to connect to bus|No medium found/i.test(text)) {
|
|
356
|
+
info('detected: no systemd user bus in current environment');
|
|
357
|
+
info('hint: this is common in containers/root/non-login sessions');
|
|
358
|
+
info('hint: installer will continue with detached process mode');
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (/permission denied|access denied/i.test(text)) {
|
|
362
|
+
info('detected: permission issue for systemd --user');
|
|
363
|
+
info('hint: run install under the same normal user as OpenClaw');
|
|
364
|
+
info('hint: installer will continue with detached process mode');
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
info('detected: systemd --user is not usable in current environment');
|
|
368
|
+
info('hint: installer will continue with detached process mode');
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function startDetachedProxyProcess({ nodeBin, cliPath, serveArgs, dryRun }) {
|
|
372
|
+
if (dryRun) {
|
|
373
|
+
info(`dry-run: detached start ${[nodeBin, cliPath, ...serveArgs].join(' ')}`);
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
const child = spawn(nodeBin, [cliPath, ...serveArgs], {
|
|
378
|
+
detached: true,
|
|
379
|
+
stdio: 'ignore',
|
|
380
|
+
cwd: os.homedir(),
|
|
381
|
+
env: {
|
|
382
|
+
...process.env,
|
|
383
|
+
HOME: process.env.HOME || os.homedir(),
|
|
384
|
+
TMPDIR: process.env.TMPDIR || '/tmp',
|
|
385
|
+
SUB2API_COMPAT_USER_AGENT: 'codex_cli_rs/0.101.0 (Ubuntu 24.4.0; x86_64) WindowsTerminal'
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
child.unref();
|
|
389
|
+
return true;
|
|
390
|
+
} catch {
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function installLinuxService({
|
|
396
|
+
serviceName,
|
|
397
|
+
content,
|
|
398
|
+
nodeBin,
|
|
399
|
+
cliPath,
|
|
400
|
+
serveArgs,
|
|
401
|
+
dryRun
|
|
402
|
+
}) {
|
|
354
403
|
const serviceDir = path.join(os.homedir(), '.config/systemd/user');
|
|
355
404
|
const servicePath = path.join(serviceDir, serviceName);
|
|
356
405
|
ensureDir(serviceDir);
|
|
@@ -366,8 +415,22 @@ function installLinuxService({ serviceName, content, dryRun }) {
|
|
|
366
415
|
info(`wrote: ${servicePath}`);
|
|
367
416
|
}
|
|
368
417
|
|
|
369
|
-
|
|
370
|
-
|
|
418
|
+
try {
|
|
419
|
+
runSystemctl(['daemon-reload'], { dryRun });
|
|
420
|
+
runSystemctl(['enable', '--now', serviceName], { dryRun });
|
|
421
|
+
info(`service started with systemd --user: ${serviceName}`);
|
|
422
|
+
return;
|
|
423
|
+
} catch (err) {
|
|
424
|
+
const detail = String(err?.message || err);
|
|
425
|
+
info(`systemd --user unavailable, fallback to detached process (${detail})`);
|
|
426
|
+
printSystemdFallbackHints(detail);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const started = startDetachedProxyProcess({ nodeBin, cliPath, serveArgs, dryRun });
|
|
430
|
+
if (!started) {
|
|
431
|
+
throw new Error(`failed to start proxy with both systemd --user and detached mode`);
|
|
432
|
+
}
|
|
433
|
+
info('proxy started in detached mode (non-systemd fallback)');
|
|
371
434
|
}
|
|
372
435
|
|
|
373
436
|
function runLaunchctl(args, { dryRun, allowFailure = false } = {}) {
|
|
@@ -444,7 +507,14 @@ function installService({
|
|
|
444
507
|
dryRun
|
|
445
508
|
}) {
|
|
446
509
|
if (process.platform === 'linux') {
|
|
447
|
-
installLinuxService({
|
|
510
|
+
installLinuxService({
|
|
511
|
+
serviceName,
|
|
512
|
+
content: linuxServiceContent,
|
|
513
|
+
nodeBin,
|
|
514
|
+
cliPath,
|
|
515
|
+
serveArgs,
|
|
516
|
+
dryRun
|
|
517
|
+
});
|
|
448
518
|
return;
|
|
449
519
|
}
|
|
450
520
|
if (process.platform === 'darwin') {
|