@mu-cabin/muc-cli 0.5.0 → 0.5.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/muc.mjs CHANGED
@@ -22241,10 +22241,9 @@ async function request(opts) {
22241
22241
  recovered = true;
22242
22242
  } catch (refreshErr) {
22243
22243
  if (!(refreshErr instanceof CliError) || cred.rememberedPwd == null) throw refreshErr;
22244
- if (isAssistantProfile()) throw refreshErr;
22245
22244
  }
22246
22245
  }
22247
- if (!recovered && cred.rememberedPwd && !isAssistantProfile()) {
22246
+ if (!recovered && cred.rememberedPwd) {
22248
22247
  try {
22249
22248
  await refreshViaRememberedPwd();
22250
22249
  recovered = true;
@@ -22343,9 +22342,6 @@ async function doRefreshViaRememberedPwd() {
22343
22342
  throw new CliError(
22344
22343
  "AUTH_EXPIRED",
22345
22344
  `rememberedPwd refresh rejected: code=${res.code} msg=${res.msg ?? ""}`.trim(),
22346
- // This arm is gated off on the assistant profile (see request()'s
22347
- // cascade), so in practice loginCmd() always returns `muc login`
22348
- // here. Templated anyway for consistency + future-proofing.
22349
22345
  `rerun \`${loginCmd()} --account <UID>\` interactively to mint fresh credentials`
22350
22346
  );
22351
22347
  }
@@ -22522,13 +22518,6 @@ function register(program3, variant) {
22522
22518
  "rerun without --token-pwd, or pass --account + (--password-stdin or interactive prompt)"
22523
22519
  );
22524
22520
  }
22525
- if (isAssistant && opts.remember === true) {
22526
- throw new CliError(
22527
- "USAGE",
22528
- "--remember is not supported on the assistant profile",
22529
- "the assistant daemon's refresh cascade stops at Step 2 by design (see http.ts); drop --remember (and run with --no-remember to wipe any pre-existing blob)"
22530
- );
22531
- }
22532
22521
  const existing = loadCredentials();
22533
22522
  const accountRaw = opts.account ?? process.env.MUC_ACCOUNT ?? existing?.uid;
22534
22523
  if (!accountRaw) {
@@ -23163,6 +23152,7 @@ var Output3 = external_exports.object({
23163
23152
  tokenPwdSet: external_exports.boolean(),
23164
23153
  tokenPwdExpireAt: external_exports.number().optional(),
23165
23154
  tokenPwdExpiresIn: external_exports.string().optional(),
23155
+ remembered: external_exports.boolean(),
23166
23156
  savedAt: external_exports.number(),
23167
23157
  credentialsPath: external_exports.string()
23168
23158
  });
@@ -23216,6 +23206,7 @@ function register3(program3) {
23216
23206
  tokenPwdSet: Boolean(c.tokenPwd),
23217
23207
  tokenPwdExpireAt: c.tokenPwdExpireAt,
23218
23208
  tokenPwdExpiresIn: relativeFromNow(c.tokenPwdExpireAt),
23209
+ remembered: Boolean(c.rememberedPwd),
23219
23210
  savedAt: c.savedAt,
23220
23211
  credentialsPath: credentialsPath()
23221
23212
  };
@@ -24016,9 +24007,13 @@ async function openChatSession(cred, opts = {}) {
24016
24007
  } catch (refreshErr) {
24017
24008
  const msg = String(refreshErr?.message ?? refreshErr);
24018
24009
  if (msg.includes("61018") || /tokenPwd|自动登陆/.test(msg)) {
24010
+ if (cred.rememberedPwd) {
24011
+ const rememberedFresh = await refreshViaRememberedPwd();
24012
+ return await tryChatLogin(rememberedFresh, opts, cfg);
24013
+ }
24019
24014
  const lc = isAssistantProfile() ? "muc assistant-login" : "muc login";
24020
24015
  throw new Error(
24021
- `chat login failed: access token expired AND saved tokenPwd is no longer valid (server returned 61018). Run \`${lc}\` with your password to mint fresh credentials, then retry.`
24016
+ `chat login failed: access token expired AND saved tokenPwd is no longer valid (server returned 61018). Run \`${lc}\` with your password (add --remember to allow unattended daemon recovery) to mint fresh credentials, then retry.`
24022
24017
  );
24023
24018
  }
24024
24019
  throw refreshErr;
@@ -24128,6 +24123,7 @@ function daemonPaths(kind, opts = {}) {
24128
24123
  sock: join7(runtimeDir2, "daemon.sock"),
24129
24124
  pid: join7(runtimeDir2, "daemon.pid"),
24130
24125
  state: join7(runtimeDir2, "daemon-state.json"),
24126
+ lifecycle: join7(runtimeDir2, "daemon-lifecycle.ndjson"),
24131
24127
  eventsLegacy: join7(runtimeDir2, "daemon-events.ndjson"),
24132
24128
  eventsPrefix: prefix2,
24133
24129
  eventsFile: (date) => join7(runtimeDir2, `${prefix2}-${date}.ndjson`)
@@ -24144,6 +24140,7 @@ function daemonPaths(kind, opts = {}) {
24144
24140
  sock: join7(runtimeDir2, "assistant.sock"),
24145
24141
  pid: join7(runtimeDir2, "assistant.pid"),
24146
24142
  state: join7(runtimeDir2, "assistant-state.json"),
24143
+ lifecycle: join7(runtimeDir2, "assistant-lifecycle.ndjson"),
24147
24144
  eventsLegacy: join7(runtimeDir2, "assistant-events.ndjson"),
24148
24145
  eventsPrefix: prefix2,
24149
24146
  eventsFile: (date) => join7(runtimeDir2, `${prefix2}-${date}.ndjson`),
@@ -24163,6 +24160,7 @@ function daemonPaths(kind, opts = {}) {
24163
24160
  sock: join7(runtimeDir, "assistant.sock"),
24164
24161
  pid: join7(runtimeDir, "daemon.pid"),
24165
24162
  state: join7(runtimeDir, "state.json"),
24163
+ lifecycle: join7(runtimeDir, "lifecycle.ndjson"),
24166
24164
  eventsLegacy: join7(runtimeDir, "events.ndjson"),
24167
24165
  eventsPrefix: prefix,
24168
24166
  eventsFile: (date) => join7(runtimeDir, `${prefix}-${date}.ndjson`),
@@ -28279,7 +28277,7 @@ function formatHuman(m) {
28279
28277
  import { existsSync as existsSync11, unlinkSync as unlinkSync4 } from "node:fs";
28280
28278
 
28281
28279
  // src/daemon/broker.ts
28282
- import { existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync7, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
28280
+ import { appendFileSync as appendFileSync2, existsSync as existsSync10, mkdirSync as mkdirSync8, readFileSync as readFileSync7, unlinkSync as unlinkSync3, writeFileSync as writeFileSync5 } from "node:fs";
28283
28281
  import { setTimeout as delay } from "node:timers/promises";
28284
28282
 
28285
28283
  // src/daemon/kicked-policy.ts
@@ -28336,6 +28334,10 @@ function formatDuration(ms) {
28336
28334
  }
28337
28335
 
28338
28336
  // src/daemon/broker.ts
28337
+ function credentialsForReconnect(current, onDisk) {
28338
+ if (!onDisk || onDisk.uid !== current.uid) return current;
28339
+ return onDisk.savedAt >= current.savedAt ? onDisk : current;
28340
+ }
28339
28341
  var ChatBroker = class {
28340
28342
  kind;
28341
28343
  paths;
@@ -28353,6 +28355,8 @@ var ChatBroker = class {
28353
28355
  status = "starting";
28354
28356
  lastMessage;
28355
28357
  startedAt = Date.now();
28358
+ lifecycleDetails = {};
28359
+ exitRecorded = false;
28356
28360
  socketEndDeferred = null;
28357
28361
  constructor(opts) {
28358
28362
  this.kind = opts.kind;
@@ -28402,11 +28406,10 @@ var ChatBroker = class {
28402
28406
  });
28403
28407
  this.eventLog.migrateLegacy();
28404
28408
  this.eventLog.prune();
28409
+ this.installProcessHandlers();
28405
28410
  this.ipc = new IpcServer(this.paths, (cmd) => this.handleIpc(cmd), { eventLog: this.eventLog });
28406
28411
  await this.ipc.start();
28407
28412
  this.publishState("starting", `${this.kind} daemon initializing (os=${this.os}, uid=${this.cred.uid})`);
28408
- process.on("SIGINT", () => void this.shutdown("SIGINT"));
28409
- process.on("SIGTERM", () => void this.shutdown("SIGTERM"));
28410
28413
  await this.connectChatLoop();
28411
28414
  }
28412
28415
  broadcast(event) {
@@ -28425,7 +28428,8 @@ var ChatBroker = class {
28425
28428
  uid: this.cred?.uid,
28426
28429
  os: this.os,
28427
28430
  kind: this.kind,
28428
- on_kicked: formatKickedRecoveryPolicy(this.kickedPolicy)
28431
+ on_kicked: formatKickedRecoveryPolicy(this.kickedPolicy),
28432
+ ...this.lifecycleDetails
28429
28433
  }, null, 2), { mode: 384 });
28430
28434
  } catch {
28431
28435
  }
@@ -28434,6 +28438,7 @@ var ChatBroker = class {
28434
28438
  async shutdown(reason) {
28435
28439
  if (this.shuttingDown) return;
28436
28440
  this.shuttingDown = true;
28441
+ this.recordLifecycle("shutdown_requested", { last_exit_reason: reason, last_signal: isSignal(reason) ? reason : void 0 });
28437
28442
  process.stderr.write(`[${this.kind}-daemon] shutting down (${reason})
28438
28443
  `);
28439
28444
  if (this.session) {
@@ -28451,11 +28456,24 @@ var ChatBroker = class {
28451
28456
  unlinkSync3(this.paths.pid);
28452
28457
  } catch {
28453
28458
  }
28459
+ this.lifecycleDetails = {
28460
+ ...this.lifecycleDetails,
28461
+ last_exit_reason: reason,
28462
+ last_exit_at: Date.now(),
28463
+ ...isSignal(reason) ? { last_signal: reason } : {}
28464
+ };
28454
28465
  this.publishState("stopped", `${this.kind} daemon stopped (${reason})`);
28466
+ this.recordLifecycle("process_exit", { last_exit_reason: reason, last_exit_at: Date.now() });
28467
+ this.exitRecorded = true;
28455
28468
  process.exit(0);
28456
28469
  }
28457
28470
  async connectChatLoop() {
28471
+ let connectionAttempt = 0;
28458
28472
  while (!this.shuttingDown) {
28473
+ connectionAttempt++;
28474
+ this.recordLifecycle(connectionAttempt === 1 ? "connection_attempt" : "reconnect_attempt", {
28475
+ reconnect_attempt: Math.max(0, connectionAttempt - 1)
28476
+ });
28459
28477
  let endReason;
28460
28478
  try {
28461
28479
  await this.openSocket();
@@ -28470,6 +28488,9 @@ var ChatBroker = class {
28470
28488
  }
28471
28489
  }
28472
28490
  async handleSocketEnd(reason) {
28491
+ this.recordLifecycle("socket_end", {
28492
+ last_error: socketEndDetail(reason)
28493
+ });
28473
28494
  if (reason.kind === "kicked") {
28474
28495
  const prefix = `chat slot taken by another device: ${reason.detail}`;
28475
28496
  if (this.kickedPolicy.mode === "pause") {
@@ -28486,6 +28507,10 @@ var ChatBroker = class {
28486
28507
  policy: waitMs === 0 ? "immediate" : "delayed",
28487
28508
  retry_in_ms: waitMs
28488
28509
  });
28510
+ this.recordLifecycle("reconnect_scheduled", {
28511
+ retry_in_ms: waitMs,
28512
+ retry_at: Date.now() + waitMs
28513
+ });
28489
28514
  if (waitMs > 0) await this.sleep(waitMs);
28490
28515
  return true;
28491
28516
  }
@@ -28505,15 +28530,23 @@ var ChatBroker = class {
28505
28530
  const backoff = expBackoffMs(this.reconnectAttempts);
28506
28531
  this.publishState("paused_network", `reconnect attempt ${this.reconnectAttempts} in ${Math.round(backoff / 1e3)}s: ${msg}`);
28507
28532
  this.broadcast({ event: "paused", reason: "network", detail: msg });
28533
+ this.recordLifecycle("reconnect_scheduled", {
28534
+ reconnect_attempt: this.reconnectAttempts,
28535
+ retry_in_ms: backoff,
28536
+ retry_at: Date.now() + backoff,
28537
+ last_error: msg
28538
+ });
28508
28539
  await this.sleep(backoff);
28509
28540
  return true;
28510
28541
  }
28511
28542
  async openSocket() {
28543
+ this.cred = credentialsForReconnect(this.cred, loadCredentials());
28512
28544
  const session = await openChatSession(this.cred, { os: this.os, debug: this.opts.debug });
28513
28545
  const reloaded = loadCredentials();
28514
28546
  if (reloaded) this.cred = reloaded;
28515
28547
  this.session = session;
28516
28548
  this.publishState("connected", `chat socket open (os=${this.os}, uid=${this.cred.uid})`);
28549
+ this.recordLifecycle("connected", { reconnect_attempt: this.reconnectAttempts });
28517
28550
  this.broadcast({ event: "hello", os: this.os });
28518
28551
  const sock = session.socket;
28519
28552
  sock.on("kicked", (payload) => {
@@ -28564,6 +28597,78 @@ var ChatBroker = class {
28564
28597
  this.socketEndDeferred = null;
28565
28598
  }
28566
28599
  }
28600
+ installProcessHandlers() {
28601
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
28602
+ process.on(signal, () => void this.shutdown(signal));
28603
+ }
28604
+ process.on("uncaughtException", (error) => this.crash("uncaught_exception", error));
28605
+ process.on("unhandledRejection", (reason) => this.crash("unhandled_rejection", reason));
28606
+ process.on("exit", (code) => {
28607
+ if (this.exitRecorded) return;
28608
+ this.recordLifecycle("process_exit", {
28609
+ last_exit_reason: `process_exit_${code}`,
28610
+ last_exit_at: Date.now()
28611
+ });
28612
+ });
28613
+ }
28614
+ crash(reason, error) {
28615
+ if (this.shuttingDown) process.exit(1);
28616
+ this.shuttingDown = true;
28617
+ const detail = errorDetail(error);
28618
+ this.lifecycleDetails = {
28619
+ ...this.lifecycleDetails,
28620
+ last_exit_reason: reason,
28621
+ last_exit_at: Date.now(),
28622
+ last_error: detail
28623
+ };
28624
+ this.publishState("stopped", `${this.kind} daemon crashed (${reason}): ${detail}`);
28625
+ this.recordLifecycle("process_crash", this.lifecycleDetails);
28626
+ this.exitRecorded = true;
28627
+ process.stderr.write(`[${this.kind}-daemon] ${reason}: ${detail}
28628
+ `);
28629
+ process.exit(1);
28630
+ }
28631
+ recordLifecycle(event, details = {}) {
28632
+ const now = Date.now();
28633
+ if (event === "connected" || event === "connection_attempt" || event === "reconnect_attempt" || event === "shutdown_requested" || event === "process_exit" || event === "process_crash") {
28634
+ delete this.lifecycleDetails.retry_in_ms;
28635
+ delete this.lifecycleDetails.retry_at;
28636
+ }
28637
+ this.lifecycleDetails = {
28638
+ ...this.lifecycleDetails,
28639
+ lifecycle_event: event,
28640
+ lifecycle_at: now,
28641
+ ...withoutUndefined(details)
28642
+ };
28643
+ ensureRuntimeDir(this.paths);
28644
+ try {
28645
+ appendFileSync2(this.paths.lifecycle, JSON.stringify({
28646
+ ts: now,
28647
+ event,
28648
+ pid: process.pid,
28649
+ kind: this.kind,
28650
+ status: this.status,
28651
+ uid: this.cred?.uid,
28652
+ os: this.os,
28653
+ ...withoutUndefined(details)
28654
+ }) + "\n", { mode: 384 });
28655
+ } catch {
28656
+ }
28657
+ try {
28658
+ const previous = readState(this.paths) ?? {};
28659
+ writeFileSync5(this.paths.state, JSON.stringify({
28660
+ ...previous,
28661
+ status: this.status,
28662
+ pid: process.pid,
28663
+ uid: this.cred?.uid,
28664
+ os: this.os,
28665
+ kind: this.kind,
28666
+ on_kicked: formatKickedRecoveryPolicy(this.kickedPolicy),
28667
+ ...this.lifecycleDetails
28668
+ }, null, 2), { mode: 384 });
28669
+ } catch {
28670
+ }
28671
+ }
28567
28672
  async handleIpc(cmd) {
28568
28673
  const extra = await this.opts.extraIpc?.(cmd, this);
28569
28674
  if (extra) return extra;
@@ -28684,6 +28789,22 @@ function readState(paths2) {
28684
28789
  return null;
28685
28790
  }
28686
28791
  }
28792
+ function inferredExitState(state, pid) {
28793
+ if (!pid || state?.status === "stopped") return {};
28794
+ try {
28795
+ process.kill(pid, 0);
28796
+ return {};
28797
+ } catch (e) {
28798
+ if (e?.code !== "ESRCH") return {};
28799
+ }
28800
+ if (state?.last_exit_reason) return {};
28801
+ const lastStateAt = state?.lifecycle_at ?? state?.ts;
28802
+ return {
28803
+ last_exit_reason: "process_disappeared",
28804
+ exit_inferred: true,
28805
+ ...lastStateAt ? { last_state_at: lastStateAt } : {}
28806
+ };
28807
+ }
28687
28808
  function readPid(paths2) {
28688
28809
  if (!existsSync10(paths2.pid)) return void 0;
28689
28810
  try {
@@ -28729,6 +28850,25 @@ function expBackoffMs(attempt) {
28729
28850
  const base = [5e3, 15e3, 6e4, 3e5, 3e5, 3e5, 3e5];
28730
28851
  return base[Math.min(attempt - 1, base.length - 1)];
28731
28852
  }
28853
+ function socketEndDetail(reason) {
28854
+ if (reason.kind === "kicked") return reason.detail;
28855
+ if (reason.kind === "error" || reason.kind === "open_failed") return reason.message;
28856
+ return "socket closed";
28857
+ }
28858
+ function errorDetail(error) {
28859
+ if (error instanceof Error) return (error.stack ?? error.message).slice(0, 4e3);
28860
+ try {
28861
+ return JSON.stringify(error).slice(0, 4e3);
28862
+ } catch {
28863
+ return String(error).slice(0, 4e3);
28864
+ }
28865
+ }
28866
+ function isSignal(reason) {
28867
+ return reason === "SIGINT" || reason === "SIGTERM" || reason === "SIGHUP";
28868
+ }
28869
+ function withoutUndefined(value) {
28870
+ return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
28871
+ }
28732
28872
  function ok(id, data) {
28733
28873
  return { id, ok: true, data };
28734
28874
  }
@@ -28773,7 +28913,18 @@ var StatusOutput = external_exports.object({
28773
28913
  pid: external_exports.number().optional(),
28774
28914
  socket: external_exports.string(),
28775
28915
  history_days: external_exports.number().optional(),
28776
- on_kicked: external_exports.string().optional()
28916
+ on_kicked: external_exports.string().optional(),
28917
+ lifecycle_event: external_exports.string().optional(),
28918
+ lifecycle_at: external_exports.number().optional(),
28919
+ reconnect_attempt: external_exports.number().optional(),
28920
+ retry_in_ms: external_exports.number().optional(),
28921
+ retry_at: external_exports.number().optional(),
28922
+ last_exit_reason: external_exports.string().optional(),
28923
+ last_exit_at: external_exports.number().optional(),
28924
+ exit_inferred: external_exports.boolean().optional(),
28925
+ last_state_at: external_exports.number().optional(),
28926
+ last_signal: external_exports.string().optional(),
28927
+ last_error: external_exports.string().optional()
28777
28928
  });
28778
28929
  var StopOutput = external_exports.object({
28779
28930
  stopped: external_exports.boolean(),
@@ -28851,6 +29002,7 @@ function register29(program3) {
28851
29002
  const probe = await probeDaemon(paths);
28852
29003
  const state = readState(paths);
28853
29004
  const pid = readPid(paths);
29005
+ const diagnostics = { ...stateDiagnostics(state), ...inferredExitState(state, pid) };
28854
29006
  if (probe === "absent" || probe === "stale") {
28855
29007
  return {
28856
29008
  running: false,
@@ -28861,7 +29013,8 @@ function register29(program3) {
28861
29013
  os: state?.os,
28862
29014
  ...pid ? { pid } : {},
28863
29015
  socket: paths.sock,
28864
- on_kicked: state?.on_kicked
29016
+ on_kicked: state?.on_kicked,
29017
+ ...diagnostics
28865
29018
  };
28866
29019
  }
28867
29020
  if (probe === "indeterminate") {
@@ -28872,7 +29025,8 @@ function register29(program3) {
28872
29025
  message: "socket present but unresponsive within probe budget",
28873
29026
  ...pid ? { pid } : {},
28874
29027
  socket: paths.sock,
28875
- on_kicked: state?.on_kicked
29028
+ on_kicked: state?.on_kicked,
29029
+ ...diagnostics
28876
29030
  };
28877
29031
  }
28878
29032
  const res = await callIpc(paths, { cmd: "status" }, 5e3).catch(() => null);
@@ -28888,7 +29042,8 @@ function register29(program3) {
28888
29042
  pid: typeof data.pid === "number" ? data.pid : pid,
28889
29043
  socket: typeof data.socket === "string" ? data.socket : paths.sock,
28890
29044
  history_days: typeof data.history_days === "number" ? data.history_days : void 0,
28891
- on_kicked: typeof data.on_kicked === "string" ? data.on_kicked : state?.on_kicked
29045
+ on_kicked: typeof data.on_kicked === "string" ? data.on_kicked : state?.on_kicked,
29046
+ ...diagnostics
28892
29047
  };
28893
29048
  }
28894
29049
  return {
@@ -28900,7 +29055,8 @@ function register29(program3) {
28900
29055
  os: state?.os,
28901
29056
  ...pid ? { pid } : {},
28902
29057
  socket: paths.sock,
28903
- on_kicked: state?.on_kicked
29058
+ on_kicked: state?.on_kicked,
29059
+ ...diagnostics
28904
29060
  };
28905
29061
  }
28906
29062
  });
@@ -28995,6 +29151,21 @@ function register29(program3) {
28995
29151
  }
28996
29152
  });
28997
29153
  }
29154
+ function stateDiagnostics(state) {
29155
+ if (!state) return {};
29156
+ const keys = [
29157
+ "lifecycle_event",
29158
+ "lifecycle_at",
29159
+ "reconnect_attempt",
29160
+ "retry_in_ms",
29161
+ "retry_at",
29162
+ "last_exit_reason",
29163
+ "last_exit_at",
29164
+ "last_signal",
29165
+ "last_error"
29166
+ ];
29167
+ return Object.fromEntries(keys.flatMap((key) => state[key] === void 0 ? [] : [[key, state[key]]]));
29168
+ }
28998
29169
  function parseIfMissing2(raw) {
28999
29170
  if (raw === "error" || raw === "earliest" || raw === "latest") return raw;
29000
29171
  throw new CliError("USAGE", `invalid --if-missing: ${raw}`, "expected error|earliest|latest");
@@ -30377,7 +30548,7 @@ function formatBytes(n) {
30377
30548
  }
30378
30549
 
30379
30550
  // src/assistant/memory.ts
30380
- import { appendFileSync as appendFileSync2, readFileSync as readFileSync10, readdirSync as readdirSync5, existsSync as existsSync15, writeFileSync as writeFileSync7, mkdirSync as mkdirSync11, renameSync as renameSync5, unlinkSync as unlinkSync7, rmSync as rmSync4 } from "node:fs";
30551
+ import { appendFileSync as appendFileSync3, readFileSync as readFileSync10, readdirSync as readdirSync5, existsSync as existsSync15, writeFileSync as writeFileSync7, mkdirSync as mkdirSync11, renameSync as renameSync5, unlinkSync as unlinkSync7, rmSync as rmSync4 } from "node:fs";
30381
30552
  import { dirname as dirname6, join as join12 } from "node:path";
30382
30553
  function scopeKey(scene, toId, fromUid, selfUid) {
30383
30554
  if (scene === "team_g") return `team_g:${toId}`;
@@ -30388,7 +30559,7 @@ function appendMemory(name, entry) {
30388
30559
  migrateLegacyMemoryFile(name);
30389
30560
  const file4 = assistantThreadFile(name, entry.tid);
30390
30561
  mkdirSync11(dirname6(file4), { recursive: true, mode: 448 });
30391
- appendFileSync2(file4, JSON.stringify(entry) + "\n", { mode: 384 });
30562
+ appendFileSync3(file4, JSON.stringify(entry) + "\n", { mode: 384 });
30392
30563
  }
30393
30564
  function readWindow(name, tid, limit, excludeMid) {
30394
30565
  migrateLegacyMemoryFile(name);
@@ -30504,7 +30675,7 @@ function migrateLegacyMemoryFile(name) {
30504
30675
  if (!obj || typeof obj !== "object") continue;
30505
30676
  if (typeof obj.tid !== "string" || !obj.tid) continue;
30506
30677
  const dest = assistantThreadFile(name, obj.tid);
30507
- appendFileSync2(dest, JSON.stringify(obj) + "\n", { mode: 384 });
30678
+ appendFileSync3(dest, JSON.stringify(obj) + "\n", { mode: 384 });
30508
30679
  }
30509
30680
  try {
30510
30681
  renameSync5(legacy, legacy + ".legacy");
@@ -30518,7 +30689,7 @@ function migrateLegacyMemoryFile(name) {
30518
30689
 
30519
30690
  // src/assistant/ipc.ts
30520
30691
  import { createServer as createServer2, connect as netConnect2 } from "node:net";
30521
- import { existsSync as existsSync16, mkdirSync as mkdirSync12, unlinkSync as unlinkSync8, chmodSync as chmodSync7, appendFileSync as appendFileSync3 } from "node:fs";
30692
+ import { existsSync as existsSync16, mkdirSync as mkdirSync12, unlinkSync as unlinkSync8, chmodSync as chmodSync7, appendFileSync as appendFileSync4 } from "node:fs";
30522
30693
  import { randomUUID as randomUUID2 } from "node:crypto";
30523
30694
  import { dirname as dirname7 } from "node:path";
30524
30695
  var IpcServer2 = class {
@@ -30623,7 +30794,7 @@ var IpcServer2 = class {
30623
30794
  }
30624
30795
  const date = historyDateString2(ts);
30625
30796
  if (this.currentDate !== date) this.currentDate = date;
30626
- appendFileSync3(historyFile(date), line, { mode: 384 });
30797
+ appendFileSync4(historyFile(date), line, { mode: 384 });
30627
30798
  } catch {
30628
30799
  }
30629
30800
  }
@@ -31659,7 +31830,7 @@ function parseConflictLine(line) {
31659
31830
  import { existsSync as existsSync23, readFileSync as readFileSync16, unlinkSync as unlinkSync11 } from "node:fs";
31660
31831
 
31661
31832
  // src/assistant/daemon.ts
31662
- import { existsSync as existsSync22, writeFileSync as writeFileSync12, readFileSync as readFileSync15, unlinkSync as unlinkSync10, appendFileSync as appendFileSync4, mkdirSync as mkdirSync17 } from "node:fs";
31833
+ import { existsSync as existsSync22, writeFileSync as writeFileSync12, readFileSync as readFileSync15, unlinkSync as unlinkSync10, appendFileSync as appendFileSync5, mkdirSync as mkdirSync17 } from "node:fs";
31663
31834
  import { promises as fsp4 } from "node:fs";
31664
31835
  import path7 from "node:path";
31665
31836
  import { setTimeout as delay2 } from "node:timers/promises";
@@ -33607,10 +33778,10 @@ var AssistantDaemon = class {
33607
33778
  const loginCmd2 = isAssistantProfile() ? "muc assistant-login" : "muc login";
33608
33779
  this.publishState(
33609
33780
  "paused_auth",
33610
- `access token expired and saved tokenPwd is invalid. Run \`${loginCmd2}\` to mint fresh credentials, then restart the daemon.`
33781
+ `access token expired and saved tokenPwd is invalid. Run \`${loginCmd2} --remember\` to mint fresh credentials and enable unattended recovery, then restart the daemon.`
33611
33782
  );
33612
33783
  this.statusBoard?.onPause("auth", msg);
33613
- notifyMac("MUC Assistant paused", `auth expired \u2014 run \`${loginCmd2}\`, then restart the daemon`);
33784
+ notifyMac("MUC Assistant paused", `auth expired \u2014 run \`${loginCmd2} --remember\`, then restart the daemon`);
33614
33785
  return false;
33615
33786
  }
33616
33787
  this.reconnectAttempts++;
@@ -33651,6 +33822,10 @@ var AssistantDaemon = class {
33651
33822
  }
33652
33823
  }
33653
33824
  async openSocket() {
33825
+ const onDisk = loadCredentials();
33826
+ if (onDisk?.uid === this.cred.uid && onDisk.savedAt >= this.cred.savedAt) {
33827
+ this.cred = onDisk;
33828
+ }
33654
33829
  const session = await openChatSession(this.cred, { os: this.os, debug: this.opts.debug });
33655
33830
  const reloaded = loadCredentials();
33656
33831
  if (reloaded) this.cred = reloaded;
@@ -35164,14 +35339,14 @@ function writePid2(pid) {
35164
35339
  function appendInboxLog(line) {
35165
35340
  ensureRuntimeDir2();
35166
35341
  try {
35167
- appendFileSync4(INBOX_LOG, JSON.stringify({ ts: Date.now(), ...line }) + "\n", { mode: 384 });
35342
+ appendFileSync5(INBOX_LOG, JSON.stringify({ ts: Date.now(), ...line }) + "\n", { mode: 384 });
35168
35343
  } catch {
35169
35344
  }
35170
35345
  }
35171
35346
  function appendConflictsLog(msg) {
35172
35347
  ensureRuntimeDir2();
35173
35348
  try {
35174
- appendFileSync4(CONFLICTS_LOG, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
35349
+ appendFileSync5(CONFLICTS_LOG, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
35175
35350
  `, { mode: 384 });
35176
35351
  } catch {
35177
35352
  }
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mu-cabin/muc-cli",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "Terminal client for MUC IM (com.meicloud.im.dh).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -22,6 +22,16 @@
22
22
  "im",
23
23
  "cli"
24
24
  ],
25
+ "scripts": {
26
+ "dev": "tsx src/index.ts",
27
+ "test": "tsx --test \"src/**/*.test.ts\"",
28
+ "typecheck": "tsc --noEmit",
29
+ "clean": "rm -rf dist",
30
+ "build": "node scripts/build.mjs",
31
+ "prepack": "npm run typecheck && npm run build",
32
+ "verify": "npm pack --dry-run",
33
+ "pack:offline": "bash ../../scripts/pack-offline.sh"
34
+ },
25
35
  "dependencies": {
26
36
  "commander": "^12.1.0",
27
37
  "crypto-js": "^4.2.0",
@@ -39,14 +49,5 @@
39
49
  },
40
50
  "publishConfig": {
41
51
  "access": "public"
42
- },
43
- "scripts": {
44
- "dev": "tsx src/index.ts",
45
- "test": "tsx --test \"src/**/*.test.ts\"",
46
- "typecheck": "tsc --noEmit",
47
- "clean": "rm -rf dist",
48
- "build": "node scripts/build.mjs",
49
- "verify": "npm pack --dry-run",
50
- "pack:offline": "bash ../../scripts/pack-offline.sh"
51
52
  }
52
- }
53
+ }