weacpx 0.4.1 → 0.4.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.
Files changed (2) hide show
  1. package/dist/cli.js +129 -21
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -2610,18 +2610,22 @@ class DaemonController {
2610
2610
  statusStore;
2611
2611
  startupPollIntervalMs;
2612
2612
  startupTimeoutMs;
2613
+ onboardingStartupTimeoutMs;
2613
2614
  onStartupPoll;
2614
2615
  shutdownPollIntervalMs;
2615
2616
  shutdownTimeoutMs;
2616
2617
  onShutdownPoll;
2618
+ now;
2617
2619
  constructor(paths, deps) {
2618
2620
  this.paths = paths;
2619
2621
  this.deps = deps;
2620
2622
  this.statusStore = new DaemonStatusStore(paths.statusFile);
2621
2623
  this.startupPollIntervalMs = deps.startupPollIntervalMs ?? 50;
2622
2624
  this.startupTimeoutMs = deps.startupTimeoutMs ?? 5000;
2625
+ this.onboardingStartupTimeoutMs = deps.onboardingStartupTimeoutMs ?? 300000;
2623
2626
  this.shutdownPollIntervalMs = deps.shutdownPollIntervalMs ?? 50;
2624
2627
  this.shutdownTimeoutMs = deps.shutdownTimeoutMs ?? 5000;
2628
+ this.now = deps.now ?? (() => Date.now());
2625
2629
  this.onStartupPoll = deps.onStartupPoll ?? (async () => {
2626
2630
  await new Promise((resolve) => setTimeout(resolve, this.startupPollIntervalMs));
2627
2631
  });
@@ -2659,7 +2663,7 @@ class DaemonController {
2659
2663
  await this.statusStore.clear();
2660
2664
  const pid = await this.deps.spawnDetached(options);
2661
2665
  await this.writePid(pid);
2662
- await this.waitForStartupMetadata(pid);
2666
+ await this.waitForStartupMetadata(pid, options.firstRunOnboarding ? this.onboardingStartupTimeoutMs : this.startupTimeoutMs, options.startupWait);
2663
2667
  return { state: "started", pid };
2664
2668
  }
2665
2669
  async stop() {
@@ -2695,9 +2699,10 @@ class DaemonController {
2695
2699
  await rm2(this.paths.pidFile, { force: true });
2696
2700
  await this.statusStore.clear();
2697
2701
  }
2698
- async waitForStartupMetadata(pid) {
2699
- const deadline = Date.now() + this.startupTimeoutMs;
2700
- while (Date.now() < deadline) {
2702
+ async waitForStartupMetadata(pid, timeoutMs, startupWait) {
2703
+ const startedAt = this.now();
2704
+ const deadline = startedAt + timeoutMs;
2705
+ while (this.now() < deadline) {
2701
2706
  const status = await this.statusStore.load();
2702
2707
  if (status?.pid === pid) {
2703
2708
  return;
@@ -2706,9 +2711,17 @@ class DaemonController {
2706
2711
  await this.clearRuntimeFiles();
2707
2712
  throw new Error(`weacpx daemon exited before reporting ready state (pid ${pid})`);
2708
2713
  }
2714
+ if (startupWait?.shouldStopWaiting?.()) {
2715
+ return;
2716
+ }
2717
+ await startupWait?.onPoll?.({
2718
+ elapsedMs: this.now() - startedAt,
2719
+ timeoutMs,
2720
+ pid
2721
+ });
2709
2722
  await this.onStartupPoll();
2710
2723
  }
2711
- throw new Error(`weacpx daemon did not report ready state within ${this.startupTimeoutMs}ms (pid ${pid})`);
2724
+ throw new Error(`weacpx daemon did not report ready state within ${timeoutMs}ms (pid ${pid})`);
2712
2725
  }
2713
2726
  async waitForShutdown(pid) {
2714
2727
  const deadline = Date.now() + this.shutdownTimeoutMs;
@@ -26172,7 +26185,7 @@ init_create_daemon_controller();
26172
26185
  init_daemon_files();
26173
26186
  import { randomUUID as randomUUID4 } from "node:crypto";
26174
26187
  import { homedir as homedir13 } from "node:os";
26175
- import { sep } from "node:path";
26188
+ import { dirname as dirname13, join as join16, sep } from "node:path";
26176
26189
  import { fileURLToPath as fileURLToPath6 } from "node:url";
26177
26190
 
26178
26191
  // src/daemon/daemon-runtime.ts
@@ -41139,6 +41152,79 @@ async function runRestart2(deps) {
41139
41152
  }
41140
41153
  }
41141
41154
 
41155
+ // src/cli/startup-wait-ui.ts
41156
+ var SPINNER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
41157
+ var DEFAULT_SPINNER_FRAME = "⠋";
41158
+ var ENVIRONMENT_HINT_DELAY_MS = 20000;
41159
+ function renderStartupWaitLine(input) {
41160
+ const elapsedSeconds = Math.floor(input.elapsedMs / 1000);
41161
+ const timeoutSeconds = Math.ceil(input.timeoutMs / 1000);
41162
+ const detail = input.elapsedMs >= ENVIRONMENT_HINT_DELAY_MS ? ",首次启动可能需要准备依赖和运行环境" : "";
41163
+ return `${input.frame} 正在创建初始会话${detail} ${elapsedSeconds}s / ${timeoutSeconds}s,按 Ctrl+B 跳过等待`;
41164
+ }
41165
+ function createStartupWaitUi(input) {
41166
+ const stdin2 = input.stdin ?? process.stdin;
41167
+ const stdout2 = input.stdout ?? process.stdout;
41168
+ if (!input.isInteractive() || !stdin2.isTTY || !stdout2.isTTY) {
41169
+ return { stop: () => {} };
41170
+ }
41171
+ let skipped = false;
41172
+ let interrupted = false;
41173
+ let frameIndex = 0;
41174
+ let rawModeEnabled = false;
41175
+ let stopped = false;
41176
+ const cleanup = () => {
41177
+ if (stopped) {
41178
+ return;
41179
+ }
41180
+ stopped = true;
41181
+ stdin2.off("data", onData);
41182
+ if (rawModeEnabled && typeof stdin2.setRawMode === "function") {
41183
+ stdin2.setRawMode(false);
41184
+ }
41185
+ stdin2.pause();
41186
+ stdout2.write("\r\x1B[2K");
41187
+ };
41188
+ const onData = (chunk) => {
41189
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
41190
+ if (buffer.includes(2)) {
41191
+ skipped = true;
41192
+ }
41193
+ if (buffer.includes(3)) {
41194
+ interrupted = true;
41195
+ cleanup();
41196
+ (input.onInterrupt ?? (() => process.kill(process.pid, "SIGINT")))();
41197
+ }
41198
+ };
41199
+ if (typeof stdin2.setRawMode === "function") {
41200
+ stdin2.setRawMode(true);
41201
+ rawModeEnabled = true;
41202
+ }
41203
+ stdin2.resume();
41204
+ stdin2.on("data", onData);
41205
+ const render = (poll) => {
41206
+ const frame = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length] ?? DEFAULT_SPINNER_FRAME;
41207
+ frameIndex += 1;
41208
+ stdout2.write(`\r\x1B[2K${renderStartupWaitLine({
41209
+ elapsedMs: poll.elapsedMs,
41210
+ timeoutMs: poll.timeoutMs,
41211
+ frame
41212
+ })}`);
41213
+ };
41214
+ return {
41215
+ wait: {
41216
+ onPoll: render,
41217
+ shouldStopWaiting: () => {
41218
+ if (interrupted) {
41219
+ throw new Error("startup wait interrupted");
41220
+ }
41221
+ return skipped;
41222
+ }
41223
+ },
41224
+ stop: cleanup
41225
+ };
41226
+ }
41227
+
41142
41228
  // src/cli.ts
41143
41229
  init_bootstrap();
41144
41230
  async function prepareMcpCoordinatorStartup(input) {
@@ -41363,6 +41449,7 @@ async function runCli(args, deps = {}) {
41363
41449
  case "start": {
41364
41450
  const controller = deps.controller ?? createDefaultController();
41365
41451
  try {
41452
+ const isInteractive = deps.isInteractive ?? defaultIsInteractive;
41366
41453
  const status = await controller.getStatus();
41367
41454
  if (status.state === "running") {
41368
41455
  print("weacpx 已在后台运行");
@@ -41375,10 +41462,19 @@ async function runCli(args, deps = {}) {
41375
41462
  const onboarding2 = await runOnboardingBeforeStart({
41376
41463
  print,
41377
41464
  cwd: deps.cwd ?? (() => process.cwd()),
41378
- isInteractive: deps.isInteractive,
41465
+ isInteractive,
41379
41466
  promptText: deps.promptText
41380
41467
  });
41381
- const result = await controller.start({ firstRunOnboarding: onboarding2 ?? undefined });
41468
+ const startupWaitUi = onboarding2 ? createStartupWaitUi({ isInteractive }) : null;
41469
+ let result;
41470
+ try {
41471
+ result = await controller.start({
41472
+ firstRunOnboarding: onboarding2 ?? undefined,
41473
+ ...startupWaitUi?.wait ? { startupWait: startupWaitUi.wait } : {}
41474
+ });
41475
+ } finally {
41476
+ startupWaitUi?.stop();
41477
+ }
41382
41478
  if (result.state === "already-running") {
41383
41479
  print("weacpx 已在后台运行");
41384
41480
  print(`PID: ${result.pid}`);
@@ -41389,9 +41485,7 @@ async function runCli(args, deps = {}) {
41389
41485
  return 0;
41390
41486
  } catch (error2) {
41391
41487
  print(`weacpx 启动失败:${describeFriendlyError(error2)}`);
41392
- const stderrPath = safeStderrLogPath();
41393
- if (stderrPath)
41394
- print(`请查看 Stderr: ${stderrPath}`);
41488
+ printDaemonLogHints(print);
41395
41489
  return 1;
41396
41490
  }
41397
41491
  }
@@ -41434,9 +41528,7 @@ async function runCli(args, deps = {}) {
41434
41528
  return await restartDaemonCli(controller, print);
41435
41529
  } catch (error2) {
41436
41530
  print(`weacpx 重启失败:${describeFriendlyError(error2)}`);
41437
- const stderrPath = safeStderrLogPath();
41438
- if (stderrPath)
41439
- print(`请查看 Stderr: ${stderrPath}`);
41531
+ printDaemonLogHints(print);
41440
41532
  return 1;
41441
41533
  }
41442
41534
  }
@@ -41454,7 +41546,7 @@ async function defaultUpdate(args, input) {
41454
41546
  saveConfig: async (config2) => await store.save(config2),
41455
41547
  readCurrentVersion: readVersion,
41456
41548
  print: input.print,
41457
- isInteractive: input.isInteractive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)),
41549
+ isInteractive: input.isInteractive ?? defaultIsInteractive,
41458
41550
  promptText: input.promptText ?? defaultPromptText,
41459
41551
  ...input.overrides
41460
41552
  };
@@ -41474,7 +41566,7 @@ async function runOnboardingBeforeStart(input) {
41474
41566
  deps: {
41475
41567
  print: input.print,
41476
41568
  cwd: input.cwd,
41477
- isInteractive: input.isInteractive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)),
41569
+ isInteractive: input.isInteractive ?? defaultIsInteractive,
41478
41570
  promptText: input.promptText ?? defaultPromptText
41479
41571
  }
41480
41572
  });
@@ -41741,7 +41833,7 @@ async function createChannelCliDeps(input) {
41741
41833
  saveConfig: async (config2) => await store.save(config2),
41742
41834
  print: input.print,
41743
41835
  stderr: input.stderr ?? ((text) => process.stderr.write(text)),
41744
- isInteractive: input.isInteractive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)),
41836
+ isInteractive: input.isInteractive ?? defaultIsInteractive,
41745
41837
  promptText: input.promptText ?? defaultPromptText,
41746
41838
  promptSecret: input.promptSecret ?? defaultPromptSecret,
41747
41839
  getDaemonStatus: async () => {
@@ -41763,7 +41855,7 @@ async function createPluginCliDeps(input) {
41763
41855
  loadConfig: async () => await store.load(),
41764
41856
  saveConfig: async (config2) => await store.save(config2),
41765
41857
  print: input.print,
41766
- isInteractive: input.isInteractive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)),
41858
+ isInteractive: input.isInteractive ?? defaultIsInteractive,
41767
41859
  promptText: input.promptText ?? defaultPromptText,
41768
41860
  getDaemonStatus: async () => {
41769
41861
  const status = await controller.getStatus();
@@ -41849,7 +41941,8 @@ function createDefaultController() {
41849
41941
  getStatus: () => controller.getStatus(),
41850
41942
  stop: () => controller.stop(),
41851
41943
  start: (options) => controller.start({
41852
- ...options?.firstRunOnboarding ? { firstRunOnboarding: encodeFirstRunOnboarding(options.firstRunOnboarding) } : {}
41944
+ ...options?.firstRunOnboarding ? { firstRunOnboarding: encodeFirstRunOnboarding(options.firstRunOnboarding) } : {},
41945
+ ...options?.startupWait ? { startupWait: options.startupWait } : {}
41853
41946
  })
41854
41947
  };
41855
41948
  }
@@ -41883,12 +41976,27 @@ function requireHome2() {
41883
41976
  }
41884
41977
  return home;
41885
41978
  }
41979
+ function defaultIsInteractive() {
41980
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
41981
+ }
41886
41982
  function describeFriendlyError(error2) {
41887
41983
  return error2 instanceof Error ? error2.message : String(error2);
41888
41984
  }
41889
- function safeStderrLogPath() {
41985
+ function printDaemonLogHints(print) {
41986
+ const paths = safeDaemonLogPaths();
41987
+ if (!paths)
41988
+ return;
41989
+ print(`请查看 App Log: ${paths.appLog}`);
41990
+ print(`请查看 Stderr: ${paths.stderrLog}`);
41991
+ }
41992
+ function safeDaemonLogPaths() {
41890
41993
  try {
41891
- return resolveDaemonPaths({ home: requireHome2() }).stderrLog;
41994
+ const configPath = process.env.WEACPX_CONFIG ?? `${requireHome2()}/.weacpx/config.json`;
41995
+ const paths = resolveDaemonPaths({ home: requireHome2() });
41996
+ return {
41997
+ appLog: join16(dirname13(configPath), "runtime", "app.log"),
41998
+ stderrLog: paths.stderrLog
41999
+ };
41892
42000
  } catch {
41893
42001
  return null;
41894
42002
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weacpx",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "使用微信 ClawBot 随时随地通过 `acpx` 控制 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",