@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 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`)
@@ -296,9 +296,22 @@ function isLinuxProxyServiceActive() {
296
296
 
297
297
  function startLinuxProxyService() {
298
298
  const reload = runSystemctl(['daemon-reload']);
299
- if (reload.status !== 0) return false;
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
- return start.status === 0;
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
- started = startLinuxProxyService();
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) return false;
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 false;
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 proxyReady = await ensureProxyReady();
446
- if (!proxyReady) {
447
- process.stdout.write('\n已取消:本地代理未就绪,未修改 OpenClaw 模型配置。\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 installLinuxService({ serviceName, content, dryRun }) {
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
- runSystemctl(['daemon-reload'], { dryRun });
370
- runSystemctl(['enable', '--now', serviceName], { dryRun });
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({ serviceName, content: linuxServiceContent, dryRun });
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') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autotask/atools-tool",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "ATools CLI for OpenClaw proxy compatibility and interactive model configuration",
5
5
  "type": "module",
6
6
  "license": "MIT",