adhdev 0.9.82-rc.2 → 0.9.82-rc.20

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/cli/index.js CHANGED
@@ -784,8 +784,14 @@ async function getGitRepoStatus(workspace, options = {}) {
784
784
  const includeSubmodules = options.includeSubmodules !== false;
785
785
  try {
786
786
  const repo = await resolveGitRepository(workspace, options);
787
- const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
788
- const parsed = parsePorcelainV2Status(statusOutput.stdout);
787
+ let parsed = await readPorcelainStatus(repo, options);
788
+ let upstreamProbe = getInitialUpstreamProbe(parsed);
789
+ if (options.refreshUpstream) {
790
+ upstreamProbe = await refreshTrackedUpstream(repo, parsed, options);
791
+ if (upstreamProbe.upstreamStatus === "fresh") {
792
+ parsed = await readPorcelainStatus(repo, options);
793
+ }
794
+ }
789
795
  const head = await readHead(repo, options);
790
796
  const stashCount = await readStashCount(repo, options);
791
797
  let submodules;
@@ -800,6 +806,9 @@ async function getGitRepoStatus(workspace, options = {}) {
800
806
  headCommit: head.commit,
801
807
  headMessage: head.message,
802
808
  upstream: parsed.upstream,
809
+ upstreamStatus: parsed.upstream ? upstreamProbe.upstreamStatus : "no_upstream",
810
+ upstreamFetchedAt: upstreamProbe.upstreamFetchedAt,
811
+ upstreamFetchError: upstreamProbe.upstreamFetchError,
803
812
  ahead: parsed.ahead,
804
813
  behind: parsed.behind,
805
814
  staged: parsed.staged,
@@ -824,6 +833,60 @@ async function getGitRepoStatus(workspace, options = {}) {
824
833
  );
825
834
  }
826
835
  }
836
+ async function readPorcelainStatus(repo, options) {
837
+ const statusOutput = await runGit(repo, ["status", "--porcelain=v2", "--branch"], options);
838
+ return parsePorcelainV2Status(statusOutput.stdout);
839
+ }
840
+ function getInitialUpstreamProbe(parsed) {
841
+ return {
842
+ upstreamStatus: parsed.upstream ? "unchecked" : "no_upstream"
843
+ };
844
+ }
845
+ async function refreshTrackedUpstream(repo, parsed, options) {
846
+ if (!parsed.upstream || !parsed.branch) {
847
+ return { upstreamStatus: "no_upstream" };
848
+ }
849
+ const remoteName = await readBranchRemote(repo, parsed.branch, options) ?? inferRemoteName(parsed.upstream);
850
+ if (!remoteName) {
851
+ return {
852
+ upstreamStatus: "stale",
853
+ upstreamFetchError: `Unable to resolve remote for upstream '${parsed.upstream}'`
854
+ };
855
+ }
856
+ try {
857
+ await runGit(repo, ["fetch", "--quiet", "--prune", "--no-tags", remoteName], options);
858
+ return {
859
+ upstreamStatus: "fresh",
860
+ upstreamFetchedAt: Date.now()
861
+ };
862
+ } catch (error48) {
863
+ return {
864
+ upstreamStatus: "stale",
865
+ upstreamFetchError: formatGitError(error48)
866
+ };
867
+ }
868
+ }
869
+ async function readBranchRemote(repo, branch, options) {
870
+ try {
871
+ const result = await runGit(repo, ["config", "--get", `branch.${branch}.remote`], options);
872
+ return result.stdout.trim() || null;
873
+ } catch {
874
+ return null;
875
+ }
876
+ }
877
+ function inferRemoteName(upstream) {
878
+ const [remoteName] = upstream.split("/");
879
+ return remoteName?.trim() || null;
880
+ }
881
+ function formatGitError(error48) {
882
+ if (error48 instanceof GitCommandError) {
883
+ return error48.stderr || error48.message;
884
+ }
885
+ if (error48 instanceof Error) {
886
+ return error48.message;
887
+ }
888
+ return String(error48);
889
+ }
827
890
  function parsePorcelainV2Status(output) {
828
891
  const parsed = {
829
892
  branch: null,
@@ -918,6 +981,7 @@ function emptyStatus(workspace, lastCheckedAt, error48) {
918
981
  headCommit: null,
919
982
  headMessage: null,
920
983
  upstream: null,
984
+ upstreamStatus: "unavailable",
921
985
  ahead: 0,
922
986
  behind: 0,
923
987
  staged: 0,
@@ -1210,6 +1274,9 @@ function createGitCompactSummary(status, diffSummary) {
1210
1274
  isGitRepo: status.isGitRepo,
1211
1275
  repoRoot: status.repoRoot,
1212
1276
  branch: status.branch,
1277
+ upstreamStatus: status.upstreamStatus,
1278
+ upstreamFetchedAt: status.upstreamFetchedAt,
1279
+ upstreamFetchError: status.upstreamFetchError,
1213
1280
  dirty: status.staged > 0 || status.modified > 0 || status.untracked > 0 || status.deleted > 0 || status.renamed > 0 || conflictCount > 0 || changedFiles > 0,
1214
1281
  changedFiles,
1215
1282
  ahead: status.ahead,
@@ -1537,7 +1604,7 @@ function serviceNotImplemented(command) {
1537
1604
  }
1538
1605
  function createDefaultGitCommandServices() {
1539
1606
  return {
1540
- getStatus: ({ workspace }) => getGitRepoStatus(workspace),
1607
+ getStatus: ({ workspace, refreshUpstream }) => getGitRepoStatus(workspace, { refreshUpstream }),
1541
1608
  getDiffSummary: ({ workspace }) => getGitDiffSummary(workspace),
1542
1609
  getDiffFile: ({ workspace, path: filePath }) => getGitFileDiff(workspace, filePath),
1543
1610
  createSnapshot: ({ workspace, reason, sessionId, turnId }) => defaultSnapshotStore.create({
@@ -1622,7 +1689,7 @@ async function handleGitCommand(command, args, services = defaultGitCommandServi
1622
1689
  switch (command) {
1623
1690
  case "git_status": {
1624
1691
  if (!services.getStatus) return serviceNotImplemented(command);
1625
- const status = await runService(() => services.getStatus({ workspace }));
1692
+ const status = await runService(() => services.getStatus({ workspace, refreshUpstream: optionalBoolean(args?.refreshUpstream) }));
1626
1693
  return "success" in status ? status : { success: true, status };
1627
1694
  }
1628
1695
  case "git_diff_summary": {
@@ -4279,10 +4346,18 @@ __export(mesh_events_exports, {
4279
4346
  drainPendingMeshCoordinatorEvents: () => drainPendingMeshCoordinatorEvents,
4280
4347
  getPendingMeshCoordinatorEvents: () => getPendingMeshCoordinatorEvents,
4281
4348
  handleMeshForwardEvent: () => handleMeshForwardEvent,
4349
+ queuePendingMeshCoordinatorEvent: () => queuePendingMeshCoordinatorEvent,
4282
4350
  setupMeshEventForwarding: () => setupMeshEventForwarding,
4283
4351
  triggerMeshQueue: () => triggerMeshQueue,
4284
4352
  tryAssignQueueTask: () => tryAssignQueueTask
4285
4353
  });
4354
+ function queuePendingMeshCoordinatorEvent(event) {
4355
+ if (pendingMeshCoordinatorEvents.length >= MAX_PENDING_EVENTS) {
4356
+ return false;
4357
+ }
4358
+ pendingMeshCoordinatorEvents.push(event);
4359
+ return true;
4360
+ }
4286
4361
  function drainPendingMeshCoordinatorEvents() {
4287
4362
  return pendingMeshCoordinatorEvents.splice(0);
4288
4363
  }
@@ -4854,17 +4929,18 @@ function injectMeshSystemMessage(components, args) {
4854
4929
  return true;
4855
4930
  });
4856
4931
  if (coordinatorInstances.length === 0) {
4857
- if (pendingMeshCoordinatorEvents.length < MAX_PENDING_EVENTS) {
4858
- pendingMeshCoordinatorEvents.push({
4859
- event: args.event,
4860
- meshId: args.meshId,
4861
- nodeLabel: args.nodeLabel,
4862
- metadataEvent: {
4863
- ...args.metadataEvent,
4864
- ...recoveryContext ? { recoveryContext } : {}
4865
- },
4866
- queuedAt: Date.now()
4867
- });
4932
+ if (queuePendingMeshCoordinatorEvent({
4933
+ event: args.event,
4934
+ meshId: args.meshId,
4935
+ nodeLabel: args.nodeLabel,
4936
+ nodeId: args.nodeId || void 0,
4937
+ workspace: readNonEmptyString(args.metadataEvent.workspace),
4938
+ metadataEvent: {
4939
+ ...args.metadataEvent,
4940
+ ...recoveryContext ? { recoveryContext } : {}
4941
+ },
4942
+ queuedAt: Date.now()
4943
+ })) {
4868
4944
  LOG.info("MeshEvents", `Queued ${args.event} for MCP coordinator (mesh ${args.meshId})`);
4869
4945
  }
4870
4946
  return { success: true, forwarded: 0 };
@@ -7199,7 +7275,7 @@ var init_status_monitor = __esm({
7199
7275
  });
7200
7276
 
7201
7277
  // ../../oss/packages/daemon-core/src/providers/chat-message-normalization.ts
7202
- function extractFinalSummaryFromMessages(messages, maxChars = 500) {
7278
+ function extractFinalSummaryFromMessages(messages, maxChars = DEFAULT_FINAL_SUMMARY_MAX_CHARS) {
7203
7279
  if (!Array.isArray(messages) || messages.length === 0) return "";
7204
7280
  for (let i = messages.length - 1; i >= 0; i--) {
7205
7281
  const msg = messages[i];
@@ -7501,11 +7577,12 @@ function filterInternalChatMessages(messages) {
7501
7577
  function filterChatMessagesByVisibility(messages, surface) {
7502
7578
  return (Array.isArray(messages) ? messages : []).filter((message) => classifyChatMessageVisibility(message).surface === surface);
7503
7579
  }
7504
- var BUILTIN_CHAT_MESSAGE_KINDS, CHAT_MESSAGE_VISIBILITIES, CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES, CHAT_MESSAGE_AUDIENCES, CHAT_MESSAGE_SOURCES, CHAT_MESSAGE_ACTIVITY_SOURCES, CHAT_MESSAGE_INTERNAL_SOURCES, KNOWN_CHAT_MESSAGE_KINDS, CHAT_MESSAGE_KIND_ALIASES, EXPLICIT_HIDDEN_VISIBILITIES, EXPLICIT_VISIBLE_VISIBILITIES, HIDDEN_AUDIENCES, ACTIVITY_SOURCE_SET, INTERNAL_SOURCE_SET;
7580
+ var DEFAULT_FINAL_SUMMARY_MAX_CHARS, BUILTIN_CHAT_MESSAGE_KINDS, CHAT_MESSAGE_VISIBILITIES, CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES, CHAT_MESSAGE_AUDIENCES, CHAT_MESSAGE_SOURCES, CHAT_MESSAGE_ACTIVITY_SOURCES, CHAT_MESSAGE_INTERNAL_SOURCES, KNOWN_CHAT_MESSAGE_KINDS, CHAT_MESSAGE_KIND_ALIASES, EXPLICIT_HIDDEN_VISIBILITIES, EXPLICIT_VISIBLE_VISIBILITIES, HIDDEN_AUDIENCES, ACTIVITY_SOURCE_SET, INTERNAL_SOURCE_SET;
7505
7581
  var init_chat_message_normalization = __esm({
7506
7582
  "../../oss/packages/daemon-core/src/providers/chat-message-normalization.ts"() {
7507
7583
  "use strict";
7508
7584
  init_contracts();
7585
+ DEFAULT_FINAL_SUMMARY_MAX_CHARS = 4e3;
7509
7586
  BUILTIN_CHAT_MESSAGE_KINDS = ["standard", "thought", "tool", "terminal", "system"];
7510
7587
  CHAT_MESSAGE_VISIBILITIES = ["user", "debug", "internal", "hidden"];
7511
7588
  CHAT_MESSAGE_TRANSCRIPT_VISIBILITIES = ["visible", "chat", "user", "debug", "internal", "hidden"];
@@ -46402,6 +46479,26 @@ function readBooleanValue(...values) {
46402
46479
  }
46403
46480
  return void 0;
46404
46481
  }
46482
+ function readGitSubmodules(value) {
46483
+ if (!Array.isArray(value)) return void 0;
46484
+ const submodules = value.map((entry) => {
46485
+ const submodule = readObjectRecord(entry);
46486
+ const path42 = readStringValue(submodule.path);
46487
+ const commit = readStringValue(submodule.commit);
46488
+ const repoPath = readStringValue(submodule.repoPath, submodule.repo_root);
46489
+ if (!path42 || !commit || !repoPath) return null;
46490
+ return {
46491
+ path: path42,
46492
+ commit,
46493
+ repoPath,
46494
+ dirty: readBooleanValue(submodule.dirty) ?? false,
46495
+ outOfSync: readBooleanValue(submodule.outOfSync, submodule.out_of_sync) ?? false,
46496
+ lastCheckedAt: readNumberValue(submodule.lastCheckedAt, submodule.last_checked_at) ?? Date.now(),
46497
+ ...readStringValue(submodule.error) ? { error: readStringValue(submodule.error) } : {}
46498
+ };
46499
+ }).filter((entry) => entry !== null);
46500
+ return submodules.length > 0 ? submodules : void 0;
46501
+ }
46405
46502
  function buildCachedInlineMeshGitStatus(node) {
46406
46503
  const cachedStatus = readObjectRecord(node?.cachedStatus);
46407
46504
  const cachedGit = readObjectRecord(cachedStatus.git);
@@ -46411,6 +46508,7 @@ function buildCachedInlineMeshGitStatus(node) {
46411
46508
  const hasConflicts2 = readBooleanValue(cachedGit.hasConflicts) ?? conflictCount2 > 0;
46412
46509
  const isGitRepo2 = readBooleanValue(cachedGit.isGitRepo);
46413
46510
  if (isGitRepo2 !== void 0) {
46511
+ const submodules2 = readGitSubmodules(cachedGit.submodules);
46414
46512
  return {
46415
46513
  workspace: readStringValue(cachedGit.workspace, node?.workspace) || "",
46416
46514
  repoRoot: readStringValue(cachedGit.repoRoot, node?.repoRoot, node?.workspace) || null,
@@ -46429,7 +46527,8 @@ function buildCachedInlineMeshGitStatus(node) {
46429
46527
  hasConflicts: hasConflicts2,
46430
46528
  conflictFiles: conflictFiles2,
46431
46529
  stashCount: readNumberValue(cachedGit.stashCount) ?? 0,
46432
- lastCheckedAt: readNumberValue(cachedGit.lastCheckedAt) ?? Date.now()
46530
+ lastCheckedAt: readNumberValue(cachedGit.lastCheckedAt) ?? Date.now(),
46531
+ ...submodules2 ? { submodules: submodules2 } : {}
46433
46532
  };
46434
46533
  }
46435
46534
  }
@@ -46448,6 +46547,7 @@ function buildCachedInlineMeshGitStatus(node) {
46448
46547
  const conflictFiles = Array.isArray(status.conflictFiles) ? status.conflictFiles.filter((value) => typeof value === "string") : [];
46449
46548
  const conflictCount = readNumberValue(status.conflicts) ?? conflictFiles.length;
46450
46549
  const hasConflicts = readBooleanValue(status.hasConflicts) ?? conflictCount > 0;
46550
+ const submodules = readGitSubmodules(status.submodules);
46451
46551
  return {
46452
46552
  workspace: readStringValue(status.workspace, node?.workspace) || "",
46453
46553
  repoRoot: readStringValue(status.repoRoot, node?.repoRoot, node?.workspace) || null,
@@ -46466,29 +46566,161 @@ function buildCachedInlineMeshGitStatus(node) {
46466
46566
  hasConflicts,
46467
46567
  conflictFiles,
46468
46568
  stashCount: readNumberValue(status.stashCount) ?? 0,
46469
- lastCheckedAt: Date.now()
46569
+ lastCheckedAt: Date.now(),
46570
+ ...submodules ? { submodules } : {}
46470
46571
  };
46471
46572
  }
46573
+ function hasGitWorktreeChanges(git) {
46574
+ if (!git) return false;
46575
+ return Number(git.staged || 0) + Number(git.modified || 0) + Number(git.untracked || 0) + Number(git.deleted || 0) + Number(git.renamed || 0) > 0;
46576
+ }
46577
+ function getGitSubmoduleDriftState(git) {
46578
+ const submodules = Array.isArray(git?.submodules) ? git.submodules : [];
46579
+ let dirty = false;
46580
+ let outOfSync = false;
46581
+ for (const entry of submodules) {
46582
+ const submodule = readObjectRecord(entry);
46583
+ if (readBooleanValue(submodule.dirty) === true) dirty = true;
46584
+ if (readBooleanValue(submodule.outOfSync) === true || !!readStringValue(submodule.error)) outOfSync = true;
46585
+ }
46586
+ return { dirty, outOfSync };
46587
+ }
46588
+ function deriveMeshNodeHealthFromGit(git) {
46589
+ if (!git || readBooleanValue(git.isGitRepo) === false) return "degraded";
46590
+ const branch = readStringValue(git.branch);
46591
+ if (!branch) return "degraded";
46592
+ const submoduleDrift = getGitSubmoduleDriftState(git);
46593
+ if (submoduleDrift.outOfSync) return "degraded";
46594
+ if (submoduleDrift.dirty || hasGitWorktreeChanges(git)) return "dirty";
46595
+ return "online";
46596
+ }
46597
+ function readCachedInlineMeshActiveSessions(node) {
46598
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
46599
+ const activeSession = readObjectRecord(cachedStatus.activeSession);
46600
+ const fallbackSession = Object.keys(activeSession).length ? activeSession : readObjectRecord(node?.activeSession ?? node?.active_session);
46601
+ const sessionId = readStringValue(fallbackSession.id, fallbackSession.sessionId, fallbackSession.session_id, node?.activeSessionId, node?.active_session_id, node?.sessionId, node?.session_id);
46602
+ return sessionId ? [sessionId] : [];
46603
+ }
46604
+ function readCachedInlineMeshActiveSessionDetails(node) {
46605
+ const cachedStatus = readObjectRecord(node?.cachedStatus);
46606
+ const activeSession = readObjectRecord(cachedStatus.activeSession);
46607
+ const fallbackSession = Object.keys(activeSession).length ? activeSession : readObjectRecord(node?.activeSession ?? node?.active_session);
46608
+ const sessionId = readStringValue(
46609
+ fallbackSession.id,
46610
+ fallbackSession.sessionId,
46611
+ fallbackSession.session_id,
46612
+ node?.activeSessionId,
46613
+ node?.active_session_id,
46614
+ node?.sessionId,
46615
+ node?.session_id
46616
+ );
46617
+ if (!sessionId) return [];
46618
+ return [{
46619
+ sessionId,
46620
+ providerType: readStringValue(
46621
+ fallbackSession.providerType,
46622
+ fallbackSession.provider_type,
46623
+ fallbackSession.cliType,
46624
+ fallbackSession.cli_type,
46625
+ fallbackSession.provider,
46626
+ node?.providerType,
46627
+ node?.provider_type
46628
+ ),
46629
+ state: readStringValue(fallbackSession.status, fallbackSession.state, fallbackSession.lifecycle),
46630
+ lifecycle: readStringValue(fallbackSession.lifecycle),
46631
+ title: readStringValue(fallbackSession.title, fallbackSession.displayName, fallbackSession.display_name) ?? null,
46632
+ workspace: readStringValue(fallbackSession.workspace, node?.workspace) ?? null,
46633
+ lastActivityAt: readStringValue(fallbackSession.lastActivityAt, fallbackSession.last_activity_at) ?? null,
46634
+ recoveryState: readStringValue(fallbackSession.recoveryState, fallbackSession.recovery_state) ?? null,
46635
+ isCached: true
46636
+ }];
46637
+ }
46638
+ function readLiveMeshSessionState(record2) {
46639
+ return readStringValue(
46640
+ record2?.meta?.sessionStatus,
46641
+ record2?.meta?.status,
46642
+ record2?.meta?.providerStatus,
46643
+ record2?.status,
46644
+ record2?.state,
46645
+ record2?.lifecycle
46646
+ );
46647
+ }
46648
+ function toIsoTimestamp(value) {
46649
+ if (typeof value === "number" && Number.isFinite(value)) return new Date(value).toISOString();
46650
+ const stringValue = readStringValue(value);
46651
+ return stringValue || null;
46652
+ }
46653
+ function summarizeMeshSessionRecord(record2) {
46654
+ return {
46655
+ sessionId: readStringValue(record2?.sessionId) || "unknown",
46656
+ providerType: readStringValue(record2?.providerType),
46657
+ state: readLiveMeshSessionState(record2),
46658
+ lifecycle: readStringValue(record2?.lifecycle),
46659
+ surfaceKind: getSessionHostSurfaceKind(record2),
46660
+ recoveryState: readStringValue(record2?.meta?.runtimeRecoveryState) ?? null,
46661
+ workspace: readStringValue(record2?.workspace) ?? null,
46662
+ title: readStringValue(record2?.displayName, record2?.workspaceLabel) ?? null,
46663
+ lastActivityAt: toIsoTimestamp(record2?.updatedAt ?? record2?.lastActivityAt ?? record2?.last_activity_at),
46664
+ isCached: false
46665
+ };
46666
+ }
46667
+ function readLiveMeshNodeWorkspace(args) {
46668
+ const directNodeWorkspace = args.liveSessionRecords.find((record2) => readStringValue(record2?.meta?.meshNodeId) === args.nodeId && readStringValue(record2?.workspace));
46669
+ if (directNodeWorkspace) {
46670
+ return readStringValue(directNodeWorkspace.workspace) || "";
46671
+ }
46672
+ if (args.allowCoordinatorSession) {
46673
+ const coordinatorWorkspace = args.liveSessionRecords.find((record2) => readStringValue(record2?.meta?.meshCoordinatorFor) === args.meshId && readStringValue(record2?.workspace));
46674
+ if (coordinatorWorkspace) {
46675
+ return readStringValue(coordinatorWorkspace.workspace) || "";
46676
+ }
46677
+ }
46678
+ return "";
46679
+ }
46680
+ function collectLiveMeshSessionRecords(args) {
46681
+ const matches = args.liveSessionRecords.filter((record2) => {
46682
+ if (readStringValue(record2?.meta?.meshNodeId) === args.nodeId) return true;
46683
+ const recordWorkspace = readStringValue(record2?.workspace);
46684
+ const nodeWorkspace = readStringValue(args.node?.workspace);
46685
+ return !!recordWorkspace && !!nodeWorkspace && recordWorkspace === nodeWorkspace;
46686
+ });
46687
+ if (args.allowCoordinatorSession) {
46688
+ for (const record2 of args.liveSessionRecords) {
46689
+ if (readStringValue(record2?.meta?.meshCoordinatorFor) !== args.meshId) continue;
46690
+ const sessionId = readStringValue(record2?.sessionId);
46691
+ if (sessionId && matches.some((entry) => readStringValue(entry?.sessionId) === sessionId)) continue;
46692
+ matches.push(record2);
46693
+ }
46694
+ }
46695
+ return matches;
46696
+ }
46472
46697
  function applyCachedInlineMeshNodeStatus(status, node) {
46473
46698
  const cachedStatus = readObjectRecord(node?.cachedStatus);
46474
46699
  const git = buildCachedInlineMeshGitStatus(node);
46475
46700
  const error48 = readStringValue(cachedStatus.error, node?.error);
46476
46701
  const health = readStringValue(cachedStatus.health, node?.health);
46477
46702
  const machineStatus = readStringValue(cachedStatus.machineStatus, node?.machineStatus);
46478
- if (!git && !error48 && !health) return false;
46479
- if (!machineStatus && !git && !error48) return false;
46703
+ const lastSeenAt = toIsoTimestamp(cachedStatus.lastSeenAt ?? cachedStatus.last_seen_at ?? node?.lastSeenAt ?? node?.last_seen_at);
46704
+ const updatedAt = toIsoTimestamp(cachedStatus.updatedAt ?? cachedStatus.updated_at ?? node?.updatedAt ?? node?.updated_at);
46705
+ const activeSessions = readCachedInlineMeshActiveSessions(node);
46706
+ const activeSessionDetails = readCachedInlineMeshActiveSessionDetails(node);
46707
+ if (!git && !error48 && !health && !machineStatus && !lastSeenAt && !updatedAt && activeSessions.length === 0) return false;
46480
46708
  if (git) status.git = git;
46481
46709
  if (error48) status.error = error48;
46710
+ if (machineStatus) status.machineStatus = machineStatus;
46711
+ if (lastSeenAt) status.lastSeenAt = lastSeenAt;
46712
+ if (updatedAt) status.updatedAt = updatedAt;
46713
+ if (activeSessions.length > 0) status.activeSessions = activeSessions;
46714
+ if (activeSessionDetails.length > 0) status.activeSessionDetails = activeSessionDetails;
46482
46715
  if (health) {
46483
46716
  status.health = health;
46484
46717
  return true;
46485
46718
  }
46486
46719
  if (git) {
46487
- const dirty = Number(git.staged || 0) + Number(git.modified || 0) + Number(git.untracked || 0) + Number(git.deleted || 0) + Number(git.renamed || 0) > 0;
46488
- status.health = git.isGitRepo === false ? "degraded" : dirty ? "dirty" : "online";
46720
+ status.health = deriveMeshNodeHealthFromGit(git);
46489
46721
  return true;
46490
46722
  }
46491
- return false;
46723
+ return activeSessions.length > 0 || !!machineStatus || !!lastSeenAt || !!updatedAt;
46492
46724
  }
46493
46725
  async function resolveProviderTypeFromPriority(args) {
46494
46726
  if (!args.providerPriority.length) {
@@ -46878,6 +47110,7 @@ var init_router = __esm({
46878
47110
  init_chat_history();
46879
47111
  init_ide_detector();
46880
47112
  init_cli_detector();
47113
+ init_git_status();
46881
47114
  init_logger();
46882
47115
  init_command_log();
46883
47116
  init_js_yaml();
@@ -46922,25 +47155,35 @@ var init_router = __esm({
46922
47155
  }
46923
47156
  getCachedInlineMesh(meshId, inlineMesh) {
46924
47157
  if (inlineMesh && typeof inlineMesh === "object") {
46925
- this.inlineMeshCache.set(meshId, inlineMesh);
46926
- return inlineMesh;
47158
+ return this.warmInlineMeshCache(meshId, inlineMesh);
46927
47159
  }
46928
47160
  return this.inlineMeshCache.get(meshId);
46929
47161
  }
47162
+ warmInlineMeshCache(meshId, inlineMesh) {
47163
+ if (!inlineMesh || typeof inlineMesh !== "object") return void 0;
47164
+ const cached2 = this.inlineMeshCache.get(meshId);
47165
+ if (cached2) return cached2;
47166
+ this.inlineMeshCache.set(meshId, inlineMesh);
47167
+ return inlineMesh;
47168
+ }
46930
47169
  async getMeshForCommand(meshId, inlineMesh, options) {
46931
47170
  const preferInline = options?.preferInline === true;
46932
47171
  if (preferInline) {
46933
- const cached3 = this.getCachedInlineMesh(meshId, inlineMesh);
46934
- if (cached3) return { mesh: cached3, inline: true };
47172
+ const cached3 = this.getCachedInlineMesh(meshId);
47173
+ if (cached3) return { mesh: cached3, inline: true, source: "inline_cache" };
47174
+ const warmedInline2 = this.warmInlineMeshCache(meshId, inlineMesh);
47175
+ if (warmedInline2) return { mesh: warmedInline2, inline: true, source: "inline_bootstrap" };
46935
47176
  }
46936
47177
  try {
46937
47178
  const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
46938
47179
  const mesh = getMesh3(meshId);
46939
- if (mesh) return { mesh, inline: false };
47180
+ if (mesh) return { mesh, inline: false, source: "local_config" };
46940
47181
  } catch {
46941
47182
  }
46942
- const cached2 = this.getCachedInlineMesh(meshId, inlineMesh);
46943
- return cached2 ? { mesh: cached2, inline: true } : null;
47183
+ const cached2 = this.getCachedInlineMesh(meshId);
47184
+ if (cached2) return { mesh: cached2, inline: true, source: "inline_cache" };
47185
+ const warmedInline = this.warmInlineMeshCache(meshId, inlineMesh);
47186
+ return warmedInline ? { mesh: warmedInline, inline: true, source: "inline_bootstrap" } : null;
46944
47187
  }
46945
47188
  updateInlineMeshNode(meshId, mesh, node) {
46946
47189
  if (!mesh || !Array.isArray(mesh.nodes) || !node?.id) return;
@@ -47169,6 +47412,7 @@ var init_router = __esm({
47169
47412
  const deletedSessionIds = [];
47170
47413
  const skippedSessionIds = [];
47171
47414
  const skippedLiveSessionIds = [];
47415
+ const skippedCoordinatorSessionIds = [];
47172
47416
  const deleteUnsupportedSessionIds = [];
47173
47417
  const recordsRemainSessionIds = [];
47174
47418
  const errors = [];
@@ -47201,6 +47445,12 @@ var init_router = __esm({
47201
47445
  const completed = this.isCompletedHostedSession(record2);
47202
47446
  const surfaceKind = getSessionHostSurfaceKind(record2);
47203
47447
  const liveRuntime = surfaceKind === "live_runtime";
47448
+ const coordinatorSession = readStringValue(record2?.meta?.meshCoordinatorFor) === args.meshId;
47449
+ if (!hasExplicitSessionIds && coordinatorSession) {
47450
+ skippedSessionIds.push(sessionId);
47451
+ skippedCoordinatorSessionIds.push(sessionId);
47452
+ continue;
47453
+ }
47204
47454
  if (!hasExplicitSessionIds && liveRuntime) {
47205
47455
  skippedSessionIds.push(sessionId);
47206
47456
  skippedLiveSessionIds.push(sessionId);
@@ -47266,6 +47516,7 @@ var init_router = __esm({
47266
47516
  deletedSessionIds,
47267
47517
  skippedSessionIds,
47268
47518
  skippedLiveSessionIds,
47519
+ skippedCoordinatorSessionIds,
47269
47520
  ...deleteUnsupported ? {
47270
47521
  deleteUnsupported: true,
47271
47522
  effectiveCleanup: args.mode === "stop_and_delete" ? "stopped_only_records_remain" : "delete_unsupported_records_remain",
@@ -47927,14 +48178,8 @@ var init_router = __esm({
47927
48178
  case "get_mesh": {
47928
48179
  const meshId = typeof args?.meshId === "string" ? args.meshId.trim() : "";
47929
48180
  if (!meshId) return { success: false, error: "meshId required" };
47930
- try {
47931
- const { getMesh: getMesh3 } = await Promise.resolve().then(() => (init_mesh_config(), mesh_config_exports));
47932
- const mesh = getMesh3(meshId);
47933
- if (mesh) return { success: true, mesh };
47934
- } catch {
47935
- }
47936
- const cached2 = this.inlineMeshCache.get(meshId);
47937
- if (cached2) return { success: true, mesh: cached2 };
48181
+ const meshRecord = await this.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
48182
+ if (meshRecord?.mesh) return { success: true, mesh: meshRecord.mesh };
47938
48183
  return { success: false, error: "Mesh not found" };
47939
48184
  }
47940
48185
  case "create_mesh": {
@@ -48456,7 +48701,14 @@ var init_router = __esm({
48456
48701
  cliType
48457
48702
  };
48458
48703
  }
48459
- const workspace = typeof coordinatorNode.workspace === "string" ? coordinatorNode.workspace.trim() : "";
48704
+ const sessionHostRecords = this.deps.sessionHostControl?.listSessions ? await this.deps.sessionHostControl.listSessions().catch(() => []) : [];
48705
+ const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
48706
+ const workspace = readLiveMeshNodeWorkspace({
48707
+ meshId,
48708
+ nodeId: String(coordinatorNode.id || coordinatorNode.nodeId || preferredCoordinatorNodeId || ""),
48709
+ liveSessionRecords: liveMeshSessions,
48710
+ allowCoordinatorSession: true
48711
+ }) || (typeof coordinatorNode.workspace === "string" ? coordinatorNode.workspace.trim() : "");
48460
48712
  if (!workspace) return { success: false, error: "Coordinator node workspace required", meshId, cliType };
48461
48713
  if (!cliType) {
48462
48714
  const resolved = await resolveProviderTypeFromPriority({
@@ -48764,84 +49016,111 @@ ${block}`);
48764
49016
  const { readLedgerEntries: readLedgerEntries2, getLedgerSummary: getLedgerSummary2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
48765
49017
  const ledgerEntries = readLedgerEntries2(meshId, { tail: 20 });
48766
49018
  const ledgerSummary = getLedgerSummary2(meshId);
49019
+ const sessionHostRecords = this.deps.sessionHostControl?.listSessions ? await this.deps.sessionHostControl.listSessions().catch(() => []) : [];
49020
+ const liveMeshSessions = partitionSessionHostRecords(Array.isArray(sessionHostRecords) ? sessionHostRecords : []).liveRuntimes;
49021
+ const localMachineId = loadConfig().machineId || "";
49022
+ const selectedCoordinatorNodeId = readStringValue(
49023
+ mesh.coordinator?.preferredNodeId,
49024
+ mesh.nodes?.[0]?.id,
49025
+ mesh.nodes?.[0]?.nodeId
49026
+ );
49027
+ const inlineCoordinatorNodeId = meshRecord?.inline && Array.isArray(mesh.nodes) ? selectedCoordinatorNodeId : void 0;
49028
+ const refreshedAt = (/* @__PURE__ */ new Date()).toISOString();
48767
49029
  const nodeStatuses = [];
48768
- for (const node of mesh.nodes || []) {
49030
+ for (const [nodeIndex, node] of (mesh.nodes || []).entries()) {
49031
+ const nodeId = String(node.id || node.nodeId || "");
49032
+ const daemonId = readStringValue(node.daemonId);
49033
+ const providerPriority = readProviderPriorityFromPolicy(node.policy);
49034
+ const isSelfNode = Boolean(
49035
+ nodeId && inlineCoordinatorNodeId && nodeId === inlineCoordinatorNodeId
49036
+ ) || Boolean(
49037
+ daemonId && (daemonId === localMachineId || daemonId === this.deps.statusInstanceId)
49038
+ ) || Boolean(meshRecord?.inline && nodeIndex === 0);
48769
49039
  const status = {
48770
- nodeId: node.id || node.nodeId,
49040
+ nodeId,
48771
49041
  machineLabel: node.machineLabel || node.id || node.nodeId,
48772
49042
  workspace: node.workspace,
48773
49043
  repoRoot: node.repoRoot,
48774
49044
  isLocalWorktree: node.isLocalWorktree,
48775
49045
  worktreeBranch: node.worktreeBranch,
48776
- daemonId: node.daemonId,
49046
+ daemonId,
48777
49047
  machineId: node.machineId,
49048
+ machineStatus: node.machineStatus,
48778
49049
  health: "unknown",
48779
49050
  providers: node.providers || [],
48780
- activeSessions: []
49051
+ providerPriority,
49052
+ activeSessions: [],
49053
+ activeSessionDetails: [],
49054
+ launchReady: false
48781
49055
  };
48782
- if (node.workspace && typeof node.workspace === "string") {
48783
- if (!fs10.existsSync(node.workspace) && applyCachedInlineMeshNodeStatus(status, node)) {
49056
+ if (isSelfNode) {
49057
+ status.connection = {
49058
+ perspective: "selected_coordinator",
49059
+ source: "mesh_peer_status",
49060
+ state: "self",
49061
+ transport: "local",
49062
+ reported: true,
49063
+ reason: "Selected coordinator daemon",
49064
+ lastStateChangeAt: refreshedAt
49065
+ };
49066
+ } else if (daemonId) {
49067
+ const connection = this.deps.getMeshPeerConnectionStatus?.(daemonId);
49068
+ status.connection = connection ?? {
49069
+ perspective: "selected_coordinator",
49070
+ source: "not_reported",
49071
+ state: "unknown",
49072
+ transport: "unknown",
49073
+ reported: false,
49074
+ reason: "No live mesh peer telemetry reported by the selected coordinator yet."
49075
+ };
49076
+ } else {
49077
+ status.connection = {
49078
+ perspective: "selected_coordinator",
49079
+ source: "not_reported",
49080
+ state: "unknown",
49081
+ transport: "unknown",
49082
+ reported: false,
49083
+ reason: "Node has no daemon id, so mesh transport cannot be reported from the selected coordinator."
49084
+ };
49085
+ }
49086
+ const matchedLiveSessionRecords = collectLiveMeshSessionRecords({
49087
+ meshId,
49088
+ node,
49089
+ nodeId,
49090
+ liveSessionRecords: liveMeshSessions,
49091
+ allowCoordinatorSession: nodeId === selectedCoordinatorNodeId
49092
+ });
49093
+ const workspace = readLiveMeshNodeWorkspace({
49094
+ meshId,
49095
+ nodeId,
49096
+ liveSessionRecords: matchedLiveSessionRecords,
49097
+ allowCoordinatorSession: nodeId === selectedCoordinatorNodeId
49098
+ }) || (typeof node.workspace === "string" ? node.workspace : "");
49099
+ status.workspace = workspace || node.workspace;
49100
+ if (matchedLiveSessionRecords.length > 0) {
49101
+ const sessionIds = matchedLiveSessionRecords.map((record2) => typeof record2?.sessionId === "string" ? record2.sessionId : "").filter(Boolean);
49102
+ const providerTypes = matchedLiveSessionRecords.map((record2) => readStringValue(record2?.providerType)).filter(Boolean);
49103
+ status.activeSessions = sessionIds;
49104
+ status.activeSessionDetails = matchedLiveSessionRecords.map(summarizeMeshSessionRecord);
49105
+ if (providerTypes.length > 0) {
49106
+ status.providers = Array.from(/* @__PURE__ */ new Set([...Array.isArray(status.providers) ? status.providers : [], ...providerTypes]));
49107
+ }
49108
+ }
49109
+ if (workspace) {
49110
+ if (!fs10.existsSync(workspace) && applyCachedInlineMeshNodeStatus(status, node)) {
49111
+ status.launchReady = !!daemonId && (readStringValue(status.machineStatus) === "online" || isSelfNode);
48784
49112
  nodeStatuses.push(status);
48785
49113
  continue;
48786
49114
  }
48787
49115
  try {
48788
- const { execFile: execFile3 } = await import("child_process");
48789
- const { promisify: promisify3 } = await import("util");
48790
- const execFileAsync3 = promisify3(execFile3);
48791
- const runGit2 = async (args2) => {
48792
- const result = await execFileAsync3("git", ["-C", node.workspace, ...args2], {
48793
- encoding: "utf8",
48794
- timeout: 1e4
48795
- });
48796
- return result.stdout.trim();
48797
- };
48798
- const branch = await runGit2(["branch", "--show-current"]).catch(() => "");
48799
- const porc = await runGit2(["status", "--porcelain"]).catch(() => "");
48800
- const headCommit = await runGit2(["rev-parse", "--short", "HEAD"]).catch(() => null);
48801
- const headMessage = await runGit2(["log", "-1", "--format=%s"]).catch(() => null);
48802
- const upstream = await runGit2(["rev-parse", "--abbrev-ref", "@{upstream}"]).catch(() => null);
48803
- const aheadBehind = await runGit2(["rev-list", "--left-right", "--count", "@{upstream}...HEAD"]).catch(() => "");
48804
- const stashCount = await runGit2(["stash", "list"]).catch(() => "");
48805
- let ahead = 0, behind = 0;
48806
- if (aheadBehind) {
48807
- const parts = aheadBehind.split(/\s+/);
48808
- if (parts.length >= 2) {
48809
- behind = parseInt(parts[0], 10) || 0;
48810
- ahead = parseInt(parts[1], 10) || 0;
48811
- }
48812
- }
48813
- const dirty = porc.length > 0;
48814
- const lines = porc ? porc.split("\n").filter(Boolean) : [];
48815
- let staged = 0, modified = 0, untracked = 0, deleted = 0, renamed2 = 0;
48816
- for (const line of lines) {
48817
- const xy = line.slice(0, 2);
48818
- if (xy[0] !== " " && xy[0] !== "?") staged++;
48819
- if (xy[1] === "M") modified++;
48820
- if (xy[1] === "D") deleted++;
48821
- if (xy[0] === "R" || xy[1] === "R") renamed2++;
48822
- if (xy === "??") untracked++;
49116
+ const gitStatus = await getGitRepoStatus(workspace, { timeoutMs: 1e4, refreshUpstream: true });
49117
+ status.git = gitStatus;
49118
+ if (gitStatus.isGitRepo) {
49119
+ status.health = deriveMeshNodeHealthFromGit(gitStatus);
49120
+ } else {
49121
+ status.health = "degraded";
49122
+ if (gitStatus.error && !status.error) status.error = gitStatus.error;
48823
49123
  }
48824
- status.git = {
48825
- workspace: node.workspace,
48826
- repoRoot: node.workspace,
48827
- isGitRepo: true,
48828
- branch: branch || null,
48829
- headCommit,
48830
- headMessage,
48831
- upstream,
48832
- ahead,
48833
- behind,
48834
- staged,
48835
- modified,
48836
- untracked,
48837
- deleted,
48838
- renamed: renamed2,
48839
- hasConflicts: false,
48840
- conflictFiles: [],
48841
- stashCount: stashCount ? stashCount.split("\n").filter(Boolean).length : 0,
48842
- lastCheckedAt: Date.now()
48843
- };
48844
- status.health = branch ? dirty ? "dirty" : "online" : "degraded";
48845
49124
  } catch {
48846
49125
  if (!applyCachedInlineMeshNodeStatus(status, node)) {
48847
49126
  status.health = "degraded";
@@ -48850,6 +49129,7 @@ ${block}`);
48850
49129
  } else {
48851
49130
  applyCachedInlineMeshNodeStatus(status, node);
48852
49131
  }
49132
+ status.launchReady = !!daemonId && (readStringValue(status.machineStatus) === "online" || isSelfNode);
48853
49133
  nodeStatuses.push(status);
48854
49134
  }
48855
49135
  return {
@@ -48858,6 +49138,12 @@ ${block}`);
48858
49138
  meshName: mesh.name,
48859
49139
  repoIdentity: mesh.repoIdentity,
48860
49140
  defaultBranch: mesh.defaultBranch,
49141
+ refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
49142
+ sourceOfTruth: {
49143
+ membership: meshRecord?.source === "inline_cache" ? "coordinator_inline_mesh_cache" : meshRecord?.source === "local_config" ? "local_mesh_config" : "inline_bootstrap_snapshot",
49144
+ coordinatorOwnsLiveTruth: meshRecord?.source !== "inline_bootstrap",
49145
+ historicalEvidenceOnly: ["recoveryHints", "ledger.summary", "queue.summary"]
49146
+ },
48861
49147
  nodes: nodeStatuses,
48862
49148
  queue: { tasks: queue, summary: queueSummary },
48863
49149
  ledger: { entries: ledgerEntries, summary: ledgerSummary }
@@ -56936,6 +57222,7 @@ async function initDaemonComponents(config2) {
56936
57222
  sessionHostControl: config2.sessionHostControl,
56937
57223
  statusInstanceId: config2.statusInstanceId,
56938
57224
  statusVersion: config2.statusVersion,
57225
+ getMeshPeerConnectionStatus: config2.getMeshPeerConnectionStatus,
56939
57226
  getCdpLogFn: config2.getCdpLogFn || ((ideType) => LOG.forComponent(`CDP:${ideType}`).asLogFn())
56940
57227
  });
56941
57228
  poller = new AgentStreamPoller({
@@ -57237,6 +57524,7 @@ __export(src_exports, {
57237
57524
  prepareSessionChatTailUpdate: () => prepareSessionChatTailUpdate,
57238
57525
  prepareSessionModalUpdate: () => prepareSessionModalUpdate,
57239
57526
  probeCdpPort: () => probeCdpPort,
57527
+ queuePendingMeshCoordinatorEvent: () => queuePendingMeshCoordinatorEvent,
57240
57528
  readChatHistory: () => readChatHistory,
57241
57529
  readLedgerEntries: () => readLedgerEntries,
57242
57530
  readLedgerSlice: () => readLedgerSlice,
@@ -57693,7 +57981,7 @@ var require_yoctocolors_cjs = __commonJS({
57693
57981
  }
57694
57982
  });
57695
57983
 
57696
- // ../../node_modules/@inquirer/figures/dist/esm/index.js
57984
+ // ../../node_modules/@inquirer/figures/dist/esm/index.mjs
57697
57985
  function isUnicodeSupported() {
57698
57986
  if (import_node_process3.default.platform !== "win32") {
57699
57987
  return import_node_process3.default.env["TERM"] !== "linux";
@@ -57705,7 +57993,7 @@ function isUnicodeSupported() {
57705
57993
  }
57706
57994
  var import_node_process3, common2, specialMainSymbols, specialFallbackSymbols, mainSymbols, fallbackSymbols, shouldUseMain, figures, esm_default, replacements;
57707
57995
  var init_esm3 = __esm({
57708
- "../../node_modules/@inquirer/figures/dist/esm/index.js"() {
57996
+ "../../node_modules/@inquirer/figures/dist/esm/index.mjs"() {
57709
57997
  "use strict";
57710
57998
  import_node_process3 = __toESM(require("process"), 1);
57711
57999
  common2 = {
@@ -57976,10 +58264,7 @@ var init_esm3 = __esm({
57976
58264
  oneNinth: "1/9",
57977
58265
  oneTenth: "1/10"
57978
58266
  };
57979
- mainSymbols = {
57980
- ...common2,
57981
- ...specialMainSymbols
57982
- };
58267
+ mainSymbols = { ...common2, ...specialMainSymbols };
57983
58268
  fallbackSymbols = {
57984
58269
  ...common2,
57985
58270
  ...specialFallbackSymbols
@@ -97213,11 +97498,30 @@ var init_daemon_mesh_manager = __esm({
97213
97498
  nodeDatachannel = null;
97214
97499
  peers = /* @__PURE__ */ new Map();
97215
97500
  // Map<targetDaemonId, PeerEntry>
97501
+ peerSnapshots = /* @__PURE__ */ new Map();
97216
97502
  pendingRequests = /* @__PURE__ */ new Map();
97217
97503
  commandCallback;
97218
97504
  p2pFailure(message, command, targetDaemonId) {
97219
97505
  return new P2pRelayFailureError(message, { command, targetDaemonId });
97220
97506
  }
97507
+ updatePeerSnapshot(targetDaemonId, state, patch = {}) {
97508
+ const previous = this.peerSnapshots.get(targetDaemonId);
97509
+ const now = (/* @__PURE__ */ new Date()).toISOString();
97510
+ this.peerSnapshots.set(targetDaemonId, {
97511
+ perspective: "selected_coordinator",
97512
+ source: "mesh_peer_status",
97513
+ reported: true,
97514
+ state,
97515
+ transport: patch.transport ?? previous?.transport ?? "unknown",
97516
+ reason: patch.reason ?? previous?.reason,
97517
+ lastStateChangeAt: now,
97518
+ lastConnectedAt: patch.lastConnectedAt ?? previous?.lastConnectedAt,
97519
+ lastCommandAt: patch.lastCommandAt ?? previous?.lastCommandAt
97520
+ });
97521
+ }
97522
+ getPeerConnectionStatus(targetDaemonId) {
97523
+ return this.peerSnapshots.get(targetDaemonId) ?? null;
97524
+ }
97221
97525
  invalidatePeer(targetDaemonId, reason, options = {}) {
97222
97526
  const peer = this.peers.get(targetDaemonId);
97223
97527
  if (peer?.commandQueue) {
@@ -97232,6 +97536,11 @@ var init_daemon_mesh_manager = __esm({
97232
97536
  pending.reject(this.p2pFailure(reason, pending.command, targetDaemonId));
97233
97537
  }
97234
97538
  }
97539
+ const snapshotState = peer?.state === "closed" ? "closed" : peer?.state === "disconnected" ? "disconnected" : "failed";
97540
+ this.updatePeerSnapshot(targetDaemonId, snapshotState, {
97541
+ reason,
97542
+ transport: peer?.isRelay === true ? "relay" : peer?.isRelay === false ? "direct" : "unknown"
97543
+ });
97235
97544
  if (options.closeResources !== false && peer) {
97236
97545
  try {
97237
97546
  peer.dataChannel?.close?.();
@@ -97264,6 +97573,7 @@ var init_daemon_mesh_manager = __esm({
97264
97573
  "send_chat",
97265
97574
  "read_chat",
97266
97575
  "get_chat_debug_bundle",
97576
+ "get_pending_mesh_events",
97267
97577
  "git_status",
97268
97578
  "git_diff_summary",
97269
97579
  "launch_cli",
@@ -97351,6 +97661,20 @@ var init_daemon_mesh_manager = __esm({
97351
97661
  if (!peer) {
97352
97662
  throw this.p2pFailure("Failed to initiate P2P connection entry", command, targetDaemonId);
97353
97663
  }
97664
+ const lastCommandAt = (/* @__PURE__ */ new Date()).toISOString();
97665
+ if (peer.state === "connected") {
97666
+ this.updatePeerSnapshot(targetDaemonId, "connected", {
97667
+ transport: peer.isRelay === true ? "relay" : peer.isRelay === false ? "direct" : "unknown",
97668
+ lastConnectedAt: this.peerSnapshots.get(targetDaemonId)?.lastConnectedAt,
97669
+ lastCommandAt
97670
+ });
97671
+ } else {
97672
+ this.updatePeerSnapshot(targetDaemonId, "connecting", {
97673
+ transport: peer.isRelay === true ? "relay" : peer.isRelay === false ? "direct" : "unknown",
97674
+ reason: "Waiting for mesh DataChannel to open.",
97675
+ lastCommandAt
97676
+ });
97677
+ }
97354
97678
  return new Promise((resolve23, reject) => {
97355
97679
  const requestId = `req_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
97356
97680
  const timer = setTimeout(() => {
@@ -97494,6 +97818,9 @@ var init_daemon_mesh_manager = __esm({
97494
97818
  remoteDescriptionSet: false
97495
97819
  };
97496
97820
  this.peers.set(targetDaemonId, entry);
97821
+ this.updatePeerSnapshot(targetDaemonId, "connecting", {
97822
+ reason: isInitiator ? "P2P mesh connection initiated by the selected coordinator." : "Waiting for the remote daemon to finish the mesh DataChannel handshake."
97823
+ });
97497
97824
  pc.onLocalDescription((sdp, type2) => {
97498
97825
  this.serverConn.sendMeshCommand(targetDaemonId, type2 === "offer" ? "mesh_p2p_offer" : "mesh_p2p_answer", { sdp, type: type2 });
97499
97826
  });
@@ -97504,7 +97831,26 @@ var init_daemon_mesh_manager = __esm({
97504
97831
  LOG.info("Mesh", `[Mesh] P2P state with ${targetDaemonId.slice(0, 12)}: ${state}`);
97505
97832
  if (state === "connected") {
97506
97833
  entry.state = "connected";
97834
+ let transport = "unknown";
97835
+ try {
97836
+ const pair = pc.getSelectedCandidatePair?.();
97837
+ if (pair) {
97838
+ const localType = pair.local?.type || "unknown";
97839
+ const remoteType = pair.remote?.type || "unknown";
97840
+ entry.isRelay = localType === "relay" || remoteType === "relay";
97841
+ transport = entry.isRelay ? "relay" : "direct";
97842
+ LOG.info("Mesh", `[Mesh] Candidate pair with ${targetDaemonId.slice(0, 12)}: local=${localType} remote=${remoteType} \u2192 ${transport}`);
97843
+ }
97844
+ } catch {
97845
+ transport = entry.isRelay === true ? "relay" : entry.isRelay === false ? "direct" : "unknown";
97846
+ }
97847
+ this.updatePeerSnapshot(targetDaemonId, "connected", {
97848
+ transport,
97849
+ reason: transport === "relay" ? "Connected over TURN relay." : transport === "direct" ? "Connected directly peer-to-peer." : "Connected, but selected candidate pair details are unavailable.",
97850
+ lastConnectedAt: (/* @__PURE__ */ new Date()).toISOString()
97851
+ });
97507
97852
  } else if (state === "failed" || state === "closed" || state === "disconnected") {
97853
+ entry.state = state;
97508
97854
  this.invalidatePeer(targetDaemonId, `P2P state changed to ${state}`, { rejectPending: true, closeResources: false });
97509
97855
  }
97510
97856
  });
@@ -97522,6 +97868,11 @@ var init_daemon_mesh_manager = __esm({
97522
97868
  dc.onOpen(() => {
97523
97869
  LOG.info("Mesh", `[Mesh] DataChannel OPEN with ${targetDaemonId.slice(0, 12)}`);
97524
97870
  entry.state = "connected";
97871
+ this.updatePeerSnapshot(targetDaemonId, "connected", {
97872
+ transport: entry.isRelay === true ? "relay" : entry.isRelay === false ? "direct" : "unknown",
97873
+ reason: entry.isRelay === true ? "Connected over TURN relay." : entry.isRelay === false ? "Connected directly peer-to-peer." : "DataChannel open; transport details not reported yet.",
97874
+ lastConnectedAt: (/* @__PURE__ */ new Date()).toISOString()
97875
+ });
97525
97876
  if (entry.commandQueue) {
97526
97877
  const queue = entry.commandQueue;
97527
97878
  entry.commandQueue = [];
@@ -97801,6 +98152,7 @@ var init_adhdev_daemon = __esm({
97801
98152
  "use strict";
97802
98153
  init_server_connection();
97803
98154
  init_src();
98155
+ init_mesh_events();
97804
98156
  init_daemon_p2p2();
97805
98157
  init_screenshot_controller();
97806
98158
  init_session_host();
@@ -97817,7 +98169,7 @@ var init_adhdev_daemon = __esm({
97817
98169
  init_version();
97818
98170
  init_src();
97819
98171
  init_runtime_defaults();
97820
- pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.82-rc.2" });
98172
+ pkgVersion = resolvePackageVersion({ injectedVersion: "0.9.82-rc.20" });
97821
98173
  AdhdevDaemon = class _AdhdevDaemon {
97822
98174
  localHttpServer = null;
97823
98175
  localWss = null;
@@ -98341,6 +98693,7 @@ ${err?.stack || ""}`);
98341
98693
  if (!this.meshManager) throw new Error("Mesh manager not initialized");
98342
98694
  return this.meshManager.sendCommand(daemonId, command, args);
98343
98695
  },
98696
+ getMeshPeerConnectionStatus: (daemonId) => this.meshManager?.getPeerConnectionStatus(daemonId) ?? null,
98344
98697
  onStatusChange: () => {
98345
98698
  this.invalidateHotChatSnapshotCache();
98346
98699
  this.statusReporter?.onStatusChange();
@@ -98733,6 +99086,21 @@ ${err?.stack || ""}`);
98733
99086
  await this.meshManager.sendCommand(coordinatorDaemonId, "mesh_forward_event", payload);
98734
99087
  LOG.info("MeshEvents", `Relayed ${payload.event} for mesh ${meshId} to coordinator daemon ${coordinatorDaemonId.slice(0, 12)}\u2026`);
98735
99088
  } catch (error48) {
99089
+ queuePendingMeshCoordinatorEvent({
99090
+ event: payload.event,
99091
+ meshId,
99092
+ nodeLabel: payload.nodeId ? `Node '${payload.nodeId}'` : payload.workspace ? `Agent at ${payload.workspace}` : "Remote agent",
99093
+ nodeId: payload.nodeId || void 0,
99094
+ workspace: payload.workspace || void 0,
99095
+ metadataEvent: {
99096
+ targetSessionId: payload.targetSessionId,
99097
+ providerType: payload.providerType,
99098
+ providerSessionId: payload.providerSessionId,
99099
+ finalSummary: payload.finalSummary,
99100
+ workspace: payload.workspace
99101
+ },
99102
+ queuedAt: Date.now()
99103
+ });
98736
99104
  LOG.warn("MeshEvents", `Failed to relay ${payload.event} for mesh ${meshId}: ${error48?.message || error48}`);
98737
99105
  }
98738
99106
  }
@@ -99639,6 +100007,420 @@ var init_runtime_target_trace = __esm({
99639
100007
  }
99640
100008
  });
99641
100009
 
100010
+ // src/cli/service-commands.ts
100011
+ var service_commands_exports = {};
100012
+ __export(service_commands_exports, {
100013
+ buildPlist: () => buildPlist,
100014
+ installAutoStartServiceForCurrentProcess: () => installAutoStartServiceForCurrentProcess,
100015
+ isAutoStartServiceInstalled: () => isAutoStartServiceInstalled,
100016
+ registerServiceCommands: () => registerServiceCommands
100017
+ });
100018
+ function getDarwinPlistPath() {
100019
+ return import_node_path5.default.join(import_node_os4.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
100020
+ }
100021
+ function getWindowsStartupDir() {
100022
+ const appData = process.env.APPDATA || import_node_path5.default.join(import_node_os4.default.homedir(), "AppData", "Roaming");
100023
+ return import_node_path5.default.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
100024
+ }
100025
+ function getWindowsVbsPath() {
100026
+ return import_node_path5.default.join(getWindowsStartupDir(), "adhdev-daemon.vbs");
100027
+ }
100028
+ function resolveCliPath() {
100029
+ return import_node_fs4.default.realpathSync(process.argv[1]);
100030
+ }
100031
+ function ensureDir(dir) {
100032
+ if (!import_node_fs4.default.existsSync(dir)) import_node_fs4.default.mkdirSync(dir, { recursive: true });
100033
+ }
100034
+ async function fetchHealth() {
100035
+ const controller = new AbortController();
100036
+ const timer = setTimeout(() => controller.abort(), DEFAULT_LOCAL_DAEMON_HEALTH_TIMEOUT_MS2);
100037
+ try {
100038
+ const res = await fetch(`http://127.0.0.1:${DEFAULT_DAEMON_PORT}/health`, { signal: controller.signal });
100039
+ if (!res.ok) return null;
100040
+ return await res.json();
100041
+ } catch {
100042
+ return null;
100043
+ } finally {
100044
+ clearTimeout(timer);
100045
+ }
100046
+ }
100047
+ function getProcessInfo(pid) {
100048
+ try {
100049
+ if (process.platform === "win32") {
100050
+ const out = (0, import_node_child_process4.execSync)(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, { encoding: "utf-8" });
100051
+ const match = out.match(/"(\d[\d,]+)\sK"/);
100052
+ const memKB = match ? parseInt(match[1].replace(/,/g, ""), 10) : 0;
100053
+ return { uptime: "-", memMB: Math.round(memKB / 1024) };
100054
+ } else {
100055
+ const out = (0, import_node_child_process4.execSync)(`ps -o etime=,rss= -p ${pid}`, { encoding: "utf-8" }).trim();
100056
+ const parts = out.split(/\s+/);
100057
+ const etime = parts[0] || "-";
100058
+ const rssKB = parseInt(parts[1] || "0", 10);
100059
+ return { uptime: formatElapsed(etime), memMB: Math.round(rssKB / 1024) };
100060
+ }
100061
+ } catch {
100062
+ return null;
100063
+ }
100064
+ }
100065
+ function formatElapsed(etime) {
100066
+ const parts = etime.replace("-", ":").split(":").map(Number);
100067
+ if (parts.length === 4) return `${parts[0]}d ${parts[1]}h ${parts[2]}m`;
100068
+ if (parts.length === 3) return `${parts[0]}h ${parts[1]}m`;
100069
+ if (parts.length === 2) return `${parts[0]}m ${parts[1]}s`;
100070
+ return etime;
100071
+ }
100072
+ function rotateLogIfNeeded(logPath) {
100073
+ try {
100074
+ if (!import_node_fs4.default.existsSync(logPath)) return;
100075
+ const stat5 = import_node_fs4.default.statSync(logPath);
100076
+ if (stat5.size > MAX_LOG_SIZE2) {
100077
+ const rotated = logPath + ".old";
100078
+ if (import_node_fs4.default.existsSync(rotated)) import_node_fs4.default.unlinkSync(rotated);
100079
+ import_node_fs4.default.renameSync(logPath, rotated);
100080
+ import_node_fs4.default.writeFileSync(logPath, `[log rotated at ${(/* @__PURE__ */ new Date()).toISOString()}]
100081
+ `, "utf-8");
100082
+ }
100083
+ } catch {
100084
+ }
100085
+ }
100086
+ function rotateLogs() {
100087
+ rotateLogIfNeeded(LOG_OUT);
100088
+ rotateLogIfNeeded(LOG_ERR);
100089
+ }
100090
+ function normalizeLaunchdPathEntry(entry) {
100091
+ const trimmed = String(entry || "").trim();
100092
+ if (!trimmed) return null;
100093
+ if (trimmed.startsWith("~")) {
100094
+ return import_node_path5.default.join(import_node_os4.default.homedir(), trimmed.slice(1));
100095
+ }
100096
+ return import_node_path5.default.isAbsolute(trimmed) ? trimmed : null;
100097
+ }
100098
+ function buildLaunchdPath(nodeExe, currentPath = process.env.PATH || "") {
100099
+ const brewPrefix = import_node_fs4.default.existsSync("/opt/homebrew/bin") ? "/opt/homebrew/bin" : "/usr/local/bin";
100100
+ const entries = [];
100101
+ const seen = /* @__PURE__ */ new Set();
100102
+ const addEntry = (value) => {
100103
+ if (!value) return;
100104
+ const normalized = normalizeLaunchdPathEntry(value);
100105
+ if (!normalized || seen.has(normalized)) return;
100106
+ seen.add(normalized);
100107
+ entries.push(normalized);
100108
+ };
100109
+ addEntry(import_node_path5.default.dirname(nodeExe));
100110
+ for (const entry of String(currentPath || "").split(import_node_path5.default.delimiter)) {
100111
+ addEntry(entry);
100112
+ }
100113
+ for (const entry of [brewPrefix, "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"]) {
100114
+ addEntry(entry);
100115
+ }
100116
+ return entries.join(":");
100117
+ }
100118
+ function buildPlist(nodeExe, cliExe, currentPath = process.env.PATH || "") {
100119
+ const pathValue = buildLaunchdPath(nodeExe, currentPath);
100120
+ return `<?xml version="1.0" encoding="UTF-8"?>
100121
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
100122
+ <plist version="1.0">
100123
+ <dict>
100124
+ <key>Label</key>
100125
+ <string>${LAUNCHD_LABEL}</string>
100126
+ <key>ProgramArguments</key>
100127
+ <array>
100128
+ <string>${nodeExe}</string>
100129
+ <string>${cliExe}</string>
100130
+ <string>daemon</string>
100131
+ </array>
100132
+ <key>RunAtLoad</key>
100133
+ <true/>
100134
+ <key>KeepAlive</key>
100135
+ <dict>
100136
+ <key>SuccessfulExit</key>
100137
+ <false/>
100138
+ </dict>
100139
+ <key>ThrottleInterval</key>
100140
+ <integer>30</integer>
100141
+ <key>StandardOutPath</key>
100142
+ <string>${LOG_OUT}</string>
100143
+ <key>StandardErrorPath</key>
100144
+ <string>${LOG_ERR}</string>
100145
+ <key>EnvironmentVariables</key>
100146
+ <dict>
100147
+ <key>PATH</key>
100148
+ <string>${pathValue}</string>
100149
+ </dict>
100150
+ </dict>
100151
+ </plist>`;
100152
+ }
100153
+ function installDarwin(nodeExe, cliExe) {
100154
+ const plistPath = getDarwinPlistPath();
100155
+ ensureDir(ADHDEV_DIR);
100156
+ ensureDir(import_node_path5.default.dirname(plistPath));
100157
+ import_node_fs4.default.writeFileSync(plistPath, buildPlist(nodeExe, cliExe), "utf-8");
100158
+ console.log(source_default.gray(` Plist: ${plistPath}`));
100159
+ try {
100160
+ (0, import_node_child_process4.execSync)(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
100161
+ } catch {
100162
+ }
100163
+ try {
100164
+ (0, import_node_child_process4.execSync)(`launchctl load -w "${plistPath}"`, { stdio: "ignore" });
100165
+ console.log(source_default.green("\n \u2713 Registered as LaunchAgent \u2014 daemon will start on login."));
100166
+ console.log(source_default.gray(` Logs: ~/.adhdev/daemon-launchd.{out,err}`));
100167
+ } catch (e) {
100168
+ console.log(source_default.red(`
100169
+ \u2717 launchctl load failed: ${e.message}`));
100170
+ }
100171
+ }
100172
+ function uninstallDarwin() {
100173
+ const plistPath = getDarwinPlistPath();
100174
+ if (!import_node_fs4.default.existsSync(plistPath)) {
100175
+ console.log(source_default.yellow("\n \u26A0 Service is not installed."));
100176
+ return;
100177
+ }
100178
+ try {
100179
+ (0, import_node_child_process4.execSync)(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
100180
+ } catch {
100181
+ }
100182
+ import_node_fs4.default.unlinkSync(plistPath);
100183
+ console.log(source_default.green("\n \u2713 Removed LaunchAgent. Daemon will no longer auto-start."));
100184
+ }
100185
+ function isInstalledDarwin() {
100186
+ return import_node_fs4.default.existsSync(getDarwinPlistPath());
100187
+ }
100188
+ function buildVbs(nodeExe, cliExe) {
100189
+ const logFile = import_node_path5.default.join(ADHDEV_DIR, "daemon-service.log").replace(/\\/g, "\\\\");
100190
+ const escapedNodeExe = nodeExe.replace(/\\/g, "\\\\");
100191
+ const escapedCliExe = cliExe.replace(/\\/g, "\\\\");
100192
+ return `' ADHDev Daemon Auto-Start (generated by adhdev service install)
100193
+ Set WshShell = CreateObject("WScript.Shell")
100194
+ WshShell.Run "cmd.exe /c """"${escapedNodeExe}"""" """"${escapedCliExe}"""" daemon >> """"${logFile}"""" 2>&1", 0, False
100195
+ `;
100196
+ }
100197
+ function installWindows(nodeExe, cliExe) {
100198
+ const vbsPath = getWindowsVbsPath();
100199
+ ensureDir(ADHDEV_DIR);
100200
+ ensureDir(import_node_path5.default.dirname(vbsPath));
100201
+ import_node_fs4.default.writeFileSync(vbsPath, buildVbs(nodeExe, cliExe), "utf-8");
100202
+ console.log(source_default.gray(` Startup script: ${vbsPath}`));
100203
+ console.log(source_default.green("\n \u2713 Registered in Startup folder \u2014 daemon will start on login (hidden)."));
100204
+ console.log(source_default.gray(` Logs: ${import_node_path5.default.join(ADHDEV_DIR, "daemon-service.log")}`));
100205
+ console.log(source_default.gray(" To start now without rebooting, run: adhdev daemon"));
100206
+ }
100207
+ function uninstallWindows() {
100208
+ const vbsPath = getWindowsVbsPath();
100209
+ if (!import_node_fs4.default.existsSync(vbsPath)) {
100210
+ console.log(source_default.yellow("\n \u26A0 Service is not installed."));
100211
+ return;
100212
+ }
100213
+ import_node_fs4.default.unlinkSync(vbsPath);
100214
+ console.log(source_default.green("\n \u2713 Removed Startup script. Daemon will no longer auto-start."));
100215
+ console.log(source_default.gray(" Note: a currently running daemon is not affected. Stop with: adhdev daemon:stop"));
100216
+ }
100217
+ function isInstalledWindows() {
100218
+ return import_node_fs4.default.existsSync(getWindowsVbsPath());
100219
+ }
100220
+ function isAutoStartServiceInstalled(platform12 = import_node_os4.default.platform()) {
100221
+ if (platform12 === "darwin") return isInstalledDarwin();
100222
+ if (platform12 === "win32") return isInstalledWindows();
100223
+ return false;
100224
+ }
100225
+ function installAutoStartServiceForCurrentProcess(platform12 = import_node_os4.default.platform()) {
100226
+ const nodeExe = process.execPath;
100227
+ const cliExe = resolveCliPath();
100228
+ if (platform12 === "darwin") {
100229
+ installDarwin(nodeExe, cliExe);
100230
+ return true;
100231
+ }
100232
+ if (platform12 === "win32") {
100233
+ installWindows(nodeExe, cliExe);
100234
+ return true;
100235
+ }
100236
+ return false;
100237
+ }
100238
+ function registerServiceCommands(program2) {
100239
+ const svc = program2.command("service").description("\u{1F50C} Manage ADHDev as an OS background auto-start service");
100240
+ svc.command("install").description("Register ADHDev daemon to start automatically on login").action(async () => {
100241
+ console.log(source_default.bold("\n \u{1F680} Installing ADHDev Background Service"));
100242
+ const platform12 = import_node_os4.default.platform();
100243
+ const nodeExe = process.execPath;
100244
+ const cliExe = resolveCliPath();
100245
+ console.log(source_default.gray(` Node: ${nodeExe}`));
100246
+ console.log(source_default.gray(` CLI: ${cliExe}`));
100247
+ console.log(source_default.gray(` Platform: ${platform12}`));
100248
+ if (!installAutoStartServiceForCurrentProcess(platform12)) {
100249
+ console.log(source_default.yellow("\n \u26A0 Auto-start service install is not supported on this platform."));
100250
+ console.log(source_default.gray(" On Linux, create a systemd user unit manually:"));
100251
+ console.log(source_default.gray(" ~/.config/systemd/user/adhdev-daemon.service"));
100252
+ }
100253
+ console.log();
100254
+ });
100255
+ svc.command("uninstall").description("Remove the OS background service").action(async () => {
100256
+ console.log(source_default.bold("\n \u{1F5D1}\uFE0F Removing ADHDev Background Service"));
100257
+ const platform12 = import_node_os4.default.platform();
100258
+ if (platform12 === "darwin") {
100259
+ uninstallDarwin();
100260
+ } else if (platform12 === "win32") {
100261
+ uninstallWindows();
100262
+ } else {
100263
+ console.log(source_default.yellow("\n \u26A0 Not supported on this platform."));
100264
+ }
100265
+ console.log();
100266
+ });
100267
+ svc.command("status").description("Show service installation state and live daemon health").action(async () => {
100268
+ const platform12 = import_node_os4.default.platform();
100269
+ const installed = isAutoStartServiceInstalled(platform12);
100270
+ if (installed) {
100271
+ console.log(source_default.green("\n \u2713 Service is installed."));
100272
+ if (platform12 === "darwin") console.log(source_default.gray(` Plist: ${getDarwinPlistPath()}`));
100273
+ else console.log(source_default.gray(` Script: ${getWindowsVbsPath()}`));
100274
+ } else {
100275
+ console.log(source_default.gray("\n \u2717 Service is not installed. Run: adhdev service install"));
100276
+ }
100277
+ const health = await fetchHealth();
100278
+ if (health?.ok && health.pid) {
100279
+ const info = getProcessInfo(health.pid);
100280
+ if (info) {
100281
+ console.log(source_default.green(` \u2713 Daemon running \u2014 PID ${health.pid}, uptime ${info.uptime}, ${info.memMB} MB`));
100282
+ } else {
100283
+ console.log(source_default.green(` \u2713 Daemon running \u2014 PID ${health.pid}`));
100284
+ }
100285
+ } else {
100286
+ console.log(source_default.yellow(" \u2717 Daemon is not running."));
100287
+ }
100288
+ const outSize = import_node_fs4.default.existsSync(LOG_OUT) ? import_node_fs4.default.statSync(LOG_OUT).size : 0;
100289
+ const errSize = import_node_fs4.default.existsSync(LOG_ERR) ? import_node_fs4.default.statSync(LOG_ERR).size : 0;
100290
+ if (outSize > 0 || errSize > 0) {
100291
+ console.log(source_default.gray(` Logs: stdout ${formatBytes(outSize)}, stderr ${formatBytes(errSize)}`));
100292
+ }
100293
+ console.log();
100294
+ });
100295
+ svc.command("logs").description("View daemon service logs").option("--err", "Show stderr log instead of stdout").option("--clear", "Truncate all log files").option("-n, --lines <count>", "Number of lines to show", "30").action(async (options) => {
100296
+ if (options.clear) {
100297
+ for (const f of [LOG_OUT, LOG_ERR]) {
100298
+ if (import_node_fs4.default.existsSync(f)) import_node_fs4.default.writeFileSync(f, "", "utf-8");
100299
+ }
100300
+ console.log(source_default.green("\n \u2713 Logs cleared.\n"));
100301
+ return;
100302
+ }
100303
+ const logFile = options.err ? LOG_ERR : LOG_OUT;
100304
+ if (!import_node_fs4.default.existsSync(logFile)) {
100305
+ console.log(source_default.gray(`
100306
+ No log file found: ${logFile}
100307
+ `));
100308
+ return;
100309
+ }
100310
+ const lines = parseInt(options.lines, 10) || 30;
100311
+ console.log(source_default.gray(`
100312
+ \u2500\u2500 ${options.err ? "stderr" : "stdout"}: ${logFile} (last ${lines} lines) \u2500\u2500
100313
+ `));
100314
+ const content = import_node_fs4.default.readFileSync(logFile, "utf-8");
100315
+ const allLines = content.split("\n");
100316
+ const lastLines = allLines.slice(-lines).join("\n");
100317
+ if (lastLines.trim()) console.log(lastLines);
100318
+ console.log(source_default.gray("\n (watching for new output, Ctrl+C to stop)\n"));
100319
+ let offset = Buffer.byteLength(content, "utf-8");
100320
+ const watcher = import_node_fs4.default.watchFile(logFile, { interval: 500 }, () => {
100321
+ try {
100322
+ const stat5 = import_node_fs4.default.statSync(logFile);
100323
+ if (stat5.size > offset) {
100324
+ const fd = import_node_fs4.default.openSync(logFile, "r");
100325
+ const buf = Buffer.alloc(stat5.size - offset);
100326
+ import_node_fs4.default.readSync(fd, buf, 0, buf.length, offset);
100327
+ import_node_fs4.default.closeSync(fd);
100328
+ process.stdout.write(buf.toString("utf-8"));
100329
+ offset = stat5.size;
100330
+ } else if (stat5.size < offset) {
100331
+ offset = 0;
100332
+ }
100333
+ } catch {
100334
+ }
100335
+ });
100336
+ const cleanup = () => {
100337
+ import_node_fs4.default.unwatchFile(logFile);
100338
+ process.exit(0);
100339
+ };
100340
+ process.on("SIGINT", cleanup);
100341
+ process.on("SIGTERM", cleanup);
100342
+ });
100343
+ svc.command("restart").description("Restart the daemon process (service will auto-relaunch)").action(async () => {
100344
+ const platform12 = import_node_os4.default.platform();
100345
+ const installed = isAutoStartServiceInstalled(platform12);
100346
+ if (!installed) {
100347
+ console.log(source_default.yellow("\n \u26A0 Service is not installed. Use `adhdev daemon:restart` for manual restart."));
100348
+ console.log(source_default.gray(" Or install the service first: adhdev service install\n"));
100349
+ return;
100350
+ }
100351
+ rotateLogs();
100352
+ if (platform12 === "darwin") {
100353
+ console.log(source_default.cyan("\n Refreshing LaunchAgent definition and reloading service..."));
100354
+ installAutoStartServiceForCurrentProcess(platform12);
100355
+ console.log();
100356
+ return;
100357
+ }
100358
+ const health = await fetchHealth();
100359
+ if (!health?.pid) {
100360
+ console.log(source_default.yellow("\n \u26A0 Daemon is not currently running."));
100361
+ console.log(source_default.gray(" Start with: adhdev daemon\n"));
100362
+ return;
100363
+ }
100364
+ console.log(source_default.cyan(`
100365
+ Stopping daemon (PID ${health.pid})...`));
100366
+ try {
100367
+ process.kill(health.pid, "SIGTERM");
100368
+ } catch {
100369
+ console.log(source_default.yellow(" Could not send SIGTERM. Process may have already exited."));
100370
+ }
100371
+ await new Promise((r) => setTimeout(r, 2e3));
100372
+ if (platform12 === "win32") {
100373
+ const vbsPath = getWindowsVbsPath();
100374
+ if (import_node_fs4.default.existsSync(vbsPath)) {
100375
+ try {
100376
+ (0, import_node_child_process4.execSync)(`wscript.exe "${vbsPath}"`, { stdio: "ignore", windowsHide: true });
100377
+ } catch {
100378
+ }
100379
+ }
100380
+ }
100381
+ let restarted = false;
100382
+ for (let i = 0; i < 8; i++) {
100383
+ await new Promise((r) => setTimeout(r, 1e3));
100384
+ const newHealth = await fetchHealth();
100385
+ if (newHealth?.ok && newHealth.pid !== health.pid) {
100386
+ restarted = true;
100387
+ const info = getProcessInfo(newHealth.pid);
100388
+ console.log(source_default.green(` \u2713 Daemon restarted \u2014 new PID ${newHealth.pid}${info ? `, ${info.memMB} MB` : ""}`));
100389
+ break;
100390
+ }
100391
+ }
100392
+ if (!restarted) {
100393
+ console.log(source_default.yellow(" \u26A0 Daemon did not restart within 10s."));
100394
+ console.log(source_default.gray(" Check: adhdev service logs --err"));
100395
+ }
100396
+ console.log();
100397
+ });
100398
+ }
100399
+ function formatBytes(bytes) {
100400
+ if (bytes === 0) return "0 B";
100401
+ if (bytes < 1024) return `${bytes} B`;
100402
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
100403
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
100404
+ }
100405
+ var import_node_fs4, import_node_path5, import_node_os4, import_node_child_process4, DEFAULT_LOCAL_DAEMON_HEALTH_TIMEOUT_MS2, LAUNCHD_LABEL, ADHDEV_DIR, LOG_OUT, LOG_ERR, MAX_LOG_SIZE2;
100406
+ var init_service_commands = __esm({
100407
+ "src/cli/service-commands.ts"() {
100408
+ "use strict";
100409
+ import_node_fs4 = __toESM(require("fs"));
100410
+ import_node_path5 = __toESM(require("path"));
100411
+ import_node_os4 = __toESM(require("os"));
100412
+ import_node_child_process4 = require("child_process");
100413
+ init_source();
100414
+ init_src();
100415
+ DEFAULT_LOCAL_DAEMON_HEALTH_TIMEOUT_MS2 = 1500;
100416
+ LAUNCHD_LABEL = "dev.adhf.daemon";
100417
+ ADHDEV_DIR = import_node_path5.default.join(import_node_os4.default.homedir(), ".adhdev");
100418
+ LOG_OUT = import_node_path5.default.join(ADHDEV_DIR, "daemon-launchd.out");
100419
+ LOG_ERR = import_node_path5.default.join(ADHDEV_DIR, "daemon-launchd.err");
100420
+ MAX_LOG_SIZE2 = 10 * 1024 * 1024;
100421
+ }
100422
+ });
100423
+
99642
100424
  // ../../oss/packages/web-core/src/constants/supported.ts
99643
100425
  var supported_exports = {};
99644
100426
  __export(supported_exports, {
@@ -101612,7 +102394,7 @@ function formatDebugTraceEntryLine(entry) {
101612
102394
  }
101613
102395
 
101614
102396
  // src/cli/daemon-commands.ts
101615
- var DEFAULT_LOCAL_DAEMON_HEALTH_TIMEOUT_MS2 = 1500;
102397
+ var DEFAULT_LOCAL_DAEMON_HEALTH_TIMEOUT_MS3 = 1500;
101616
102398
  var DEFAULT_TRACE_FOLLOW_INTERVAL_MS = 1500;
101617
102399
  var DEFAULT_DAEMON_PORT_TEXT = String(DEFAULT_DAEMON_PORT);
101618
102400
  var DEV_SERVER_PORT2 = 19280;
@@ -101640,6 +102422,15 @@ async function resolveConfiguredUpdateChannel() {
101640
102422
  function releaseChannelLabel(channel) {
101641
102423
  return `${channel} (${CHANNEL_NPM_TAG3[channel]})`;
101642
102424
  }
102425
+ function buildDaemonRestartCommandArgv(options = {}) {
102426
+ const cliPath = options.cliPath || process.argv[1];
102427
+ const daemonPort = options.daemonPort || DEFAULT_DAEMON_PORT_TEXT;
102428
+ const platform12 = options.platform || process.platform;
102429
+ if (platform12 === "darwin" && options.serviceInstalled) {
102430
+ return [cliPath, "service", "install"];
102431
+ }
102432
+ return [cliPath, "daemon", "-p", daemonPort];
102433
+ }
101643
102434
  async function persistReleaseChannel(channel) {
101644
102435
  const { updateConfig: updateConfig2 } = await Promise.resolve().then(() => (init_src(), src_exports));
101645
102436
  updateConfig2({ updateChannel: channel, serverUrl: CHANNEL_SERVER_URL3[channel] });
@@ -101650,7 +102441,7 @@ function hideCommand(command) {
101650
102441
  }
101651
102442
  async function fetchLocalDaemonHealth(port) {
101652
102443
  const controller = new AbortController();
101653
- const timer = setTimeout(() => controller.abort(), DEFAULT_LOCAL_DAEMON_HEALTH_TIMEOUT_MS2);
102444
+ const timer = setTimeout(() => controller.abort(), DEFAULT_LOCAL_DAEMON_HEALTH_TIMEOUT_MS3);
101654
102445
  try {
101655
102446
  const res = await fetch(`http://127.0.0.1:${port}/health`, { signal: controller.signal });
101656
102447
  if (!res.ok) return null;
@@ -102016,6 +102807,9 @@ async function runDaemonUpgrade(options, pkgVersion3) {
102016
102807
  } else {
102017
102808
  console.log(source_default.cyan(`
102018
102809
  Upgrading v${currentVersion} \u2192 v${latest}...`));
102810
+ const { isAutoStartServiceInstalled: isAutoStartServiceInstalled2 } = await Promise.resolve().then(() => (init_service_commands(), service_commands_exports));
102811
+ const serviceInstalled = isAutoStartServiceInstalled2();
102812
+ const shouldRefreshInstalledService = options.restart !== false && process.platform === "darwin" && serviceInstalled;
102019
102813
  const daemonWasRunning = options.restart !== false && isDaemonRunning2();
102020
102814
  if (daemonWasRunning) {
102021
102815
  stopDaemon2();
@@ -102027,10 +102821,22 @@ async function runDaemonUpgrade(options, pkgVersion3) {
102027
102821
  packageName: "adhdev",
102028
102822
  targetVersion: latest,
102029
102823
  parentPid: process.pid,
102030
- restartArgv: daemonWasRunning ? [process.argv[1], "daemon", "-p", DEFAULT_DAEMON_PORT_TEXT] : [],
102824
+ restartArgv: shouldRefreshInstalledService ? buildDaemonRestartCommandArgv({
102825
+ cliPath: process.argv[1],
102826
+ daemonPort: DEFAULT_DAEMON_PORT_TEXT,
102827
+ platform: process.platform,
102828
+ serviceInstalled
102829
+ }) : daemonWasRunning ? buildDaemonRestartCommandArgv({
102830
+ cliPath: process.argv[1],
102831
+ daemonPort: DEFAULT_DAEMON_PORT_TEXT,
102832
+ platform: process.platform,
102833
+ serviceInstalled
102834
+ }) : [],
102031
102835
  cwd: process.cwd()
102032
102836
  });
102033
- if (daemonWasRunning) {
102837
+ if (shouldRefreshInstalledService) {
102838
+ console.log(source_default.cyan(" Upgrading and refreshing background service in background..."));
102839
+ } else if (daemonWasRunning) {
102034
102840
  console.log(source_default.cyan(" Upgrading and restarting daemon in background..."));
102035
102841
  } else {
102036
102842
  console.log(source_default.cyan(" Upgrading in background..."));
@@ -102042,29 +102848,44 @@ async function runDaemonUpgrade(options, pkgVersion3) {
102042
102848
  return;
102043
102849
  }
102044
102850
  }
102045
- if (options.restart !== false && isDaemonRunning2()) {
102046
- console.log(source_default.yellow("\n Restarting daemon..."));
102047
- stopDaemon2();
102048
- await new Promise((r) => setTimeout(r, 2e3));
102049
- stopManagedSessionHostProcess2();
102050
- await new Promise((r) => setTimeout(r, 500));
102051
- const child = spawn7(process.execPath, [process.argv[1], "daemon", "-p", DEFAULT_DAEMON_PORT_TEXT], {
102052
- detached: true,
102053
- stdio: "ignore",
102054
- windowsHide: true,
102055
- env: { ...process.env }
102056
- });
102057
- child.unref();
102058
- await new Promise((r) => setTimeout(r, 3e3));
102851
+ if (options.restart !== false) {
102852
+ const { installAutoStartServiceForCurrentProcess: installAutoStartServiceForCurrentProcess2, isAutoStartServiceInstalled: isAutoStartServiceInstalled2 } = await Promise.resolve().then(() => (init_service_commands(), service_commands_exports));
102853
+ const serviceInstalled = isAutoStartServiceInstalled2();
102854
+ if (process.platform === "darwin" && serviceInstalled) {
102855
+ console.log(source_default.yellow("\n Refreshing LaunchAgent definition..."));
102856
+ installAutoStartServiceForCurrentProcess2(process.platform);
102857
+ console.log();
102858
+ return;
102859
+ }
102059
102860
  if (isDaemonRunning2()) {
102060
- console.log(source_default.green(` \u2713 Daemon restarted with new version
102861
+ console.log(source_default.yellow("\n Restarting daemon..."));
102862
+ stopDaemon2();
102863
+ await new Promise((r) => setTimeout(r, 2e3));
102864
+ stopManagedSessionHostProcess2();
102865
+ await new Promise((r) => setTimeout(r, 500));
102866
+ const child = spawn7(process.execPath, buildDaemonRestartCommandArgv({
102867
+ cliPath: process.argv[1],
102868
+ daemonPort: DEFAULT_DAEMON_PORT_TEXT,
102869
+ platform: process.platform,
102870
+ serviceInstalled
102871
+ }), {
102872
+ detached: true,
102873
+ stdio: "ignore",
102874
+ windowsHide: true,
102875
+ env: { ...process.env }
102876
+ });
102877
+ child.unref();
102878
+ await new Promise((r) => setTimeout(r, 3e3));
102879
+ if (isDaemonRunning2()) {
102880
+ console.log(source_default.green(` \u2713 Daemon restarted with new version
102061
102881
  `));
102062
- } else {
102063
- console.log(source_default.yellow(` \u26A0 Daemon not detected. Start manually: adhdev daemon
102882
+ } else {
102883
+ console.log(source_default.yellow(` \u26A0 Daemon not detected. Start manually: adhdev daemon
102064
102884
  `));
102885
+ }
102886
+ } else {
102887
+ console.log(source_default.gray("\n Daemon was not running. Start with: adhdev daemon\n"));
102065
102888
  }
102066
- } else if (options.restart !== false) {
102067
- console.log(source_default.gray("\n Daemon was not running. Start with: adhdev daemon\n"));
102068
102889
  } else {
102069
102890
  console.log(source_default.green("\n \u2713 Upgrade complete (daemon not restarted)\n"));
102070
102891
  }
@@ -102235,7 +103056,7 @@ function registerDaemonCommands(program2, pkgVersion3) {
102235
103056
  }
102236
103057
  try {
102237
103058
  const controller = new AbortController();
102238
- const timer = setTimeout(() => controller.abort(), DEFAULT_LOCAL_DAEMON_HEALTH_TIMEOUT_MS2);
103059
+ const timer = setTimeout(() => controller.abort(), DEFAULT_LOCAL_DAEMON_HEALTH_TIMEOUT_MS3);
102239
103060
  const res = await fetch(`${DEV_SERVER_BASE_URL}/api/status`, { signal: controller.signal });
102240
103061
  clearTimeout(timer);
102241
103062
  if (res.ok) {
@@ -102782,373 +103603,8 @@ function buildDoctorAdvice(input) {
102782
103603
  return advice;
102783
103604
  }
102784
103605
 
102785
- // src/cli/service-commands.ts
102786
- var import_node_fs4 = __toESM(require("fs"));
102787
- var import_node_path5 = __toESM(require("path"));
102788
- var import_node_os4 = __toESM(require("os"));
102789
- var import_node_child_process4 = require("child_process");
102790
- init_source();
102791
- init_src();
102792
- var DEFAULT_LOCAL_DAEMON_HEALTH_TIMEOUT_MS3 = 1500;
102793
- var LAUNCHD_LABEL = "dev.adhf.daemon";
102794
- var ADHDEV_DIR = import_node_path5.default.join(import_node_os4.default.homedir(), ".adhdev");
102795
- var LOG_OUT = import_node_path5.default.join(ADHDEV_DIR, "daemon-launchd.out");
102796
- var LOG_ERR = import_node_path5.default.join(ADHDEV_DIR, "daemon-launchd.err");
102797
- var MAX_LOG_SIZE2 = 10 * 1024 * 1024;
102798
- function getDarwinPlistPath() {
102799
- return import_node_path5.default.join(import_node_os4.default.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
102800
- }
102801
- function getWindowsStartupDir() {
102802
- const appData = process.env.APPDATA || import_node_path5.default.join(import_node_os4.default.homedir(), "AppData", "Roaming");
102803
- return import_node_path5.default.join(appData, "Microsoft", "Windows", "Start Menu", "Programs", "Startup");
102804
- }
102805
- function getWindowsVbsPath() {
102806
- return import_node_path5.default.join(getWindowsStartupDir(), "adhdev-daemon.vbs");
102807
- }
102808
- function resolveCliPath() {
102809
- return import_node_fs4.default.realpathSync(process.argv[1]);
102810
- }
102811
- function ensureDir(dir) {
102812
- if (!import_node_fs4.default.existsSync(dir)) import_node_fs4.default.mkdirSync(dir, { recursive: true });
102813
- }
102814
- async function fetchHealth() {
102815
- const controller = new AbortController();
102816
- const timer = setTimeout(() => controller.abort(), DEFAULT_LOCAL_DAEMON_HEALTH_TIMEOUT_MS3);
102817
- try {
102818
- const res = await fetch(`http://127.0.0.1:${DEFAULT_DAEMON_PORT}/health`, { signal: controller.signal });
102819
- if (!res.ok) return null;
102820
- return await res.json();
102821
- } catch {
102822
- return null;
102823
- } finally {
102824
- clearTimeout(timer);
102825
- }
102826
- }
102827
- function getProcessInfo(pid) {
102828
- try {
102829
- if (process.platform === "win32") {
102830
- const out = (0, import_node_child_process4.execSync)(`tasklist /FI "PID eq ${pid}" /FO CSV /NH`, { encoding: "utf-8" });
102831
- const match = out.match(/"(\d[\d,]+)\sK"/);
102832
- const memKB = match ? parseInt(match[1].replace(/,/g, ""), 10) : 0;
102833
- return { uptime: "-", memMB: Math.round(memKB / 1024) };
102834
- } else {
102835
- const out = (0, import_node_child_process4.execSync)(`ps -o etime=,rss= -p ${pid}`, { encoding: "utf-8" }).trim();
102836
- const parts = out.split(/\s+/);
102837
- const etime = parts[0] || "-";
102838
- const rssKB = parseInt(parts[1] || "0", 10);
102839
- return { uptime: formatElapsed(etime), memMB: Math.round(rssKB / 1024) };
102840
- }
102841
- } catch {
102842
- return null;
102843
- }
102844
- }
102845
- function formatElapsed(etime) {
102846
- const parts = etime.replace("-", ":").split(":").map(Number);
102847
- if (parts.length === 4) return `${parts[0]}d ${parts[1]}h ${parts[2]}m`;
102848
- if (parts.length === 3) return `${parts[0]}h ${parts[1]}m`;
102849
- if (parts.length === 2) return `${parts[0]}m ${parts[1]}s`;
102850
- return etime;
102851
- }
102852
- function rotateLogIfNeeded(logPath) {
102853
- try {
102854
- if (!import_node_fs4.default.existsSync(logPath)) return;
102855
- const stat5 = import_node_fs4.default.statSync(logPath);
102856
- if (stat5.size > MAX_LOG_SIZE2) {
102857
- const rotated = logPath + ".old";
102858
- if (import_node_fs4.default.existsSync(rotated)) import_node_fs4.default.unlinkSync(rotated);
102859
- import_node_fs4.default.renameSync(logPath, rotated);
102860
- import_node_fs4.default.writeFileSync(logPath, `[log rotated at ${(/* @__PURE__ */ new Date()).toISOString()}]
102861
- `, "utf-8");
102862
- }
102863
- } catch {
102864
- }
102865
- }
102866
- function rotateLogs() {
102867
- rotateLogIfNeeded(LOG_OUT);
102868
- rotateLogIfNeeded(LOG_ERR);
102869
- }
102870
- function buildPlist(nodeExe, cliExe) {
102871
- const brewPrefix = import_node_fs4.default.existsSync("/opt/homebrew/bin") ? "/opt/homebrew/bin" : "/usr/local/bin";
102872
- const nodeDir = import_node_path5.default.dirname(nodeExe);
102873
- const pathEntries = /* @__PURE__ */ new Set([nodeDir, brewPrefix, "/usr/local/bin", "/usr/bin", "/bin", "/usr/sbin", "/sbin"]);
102874
- const pathValue = Array.from(pathEntries).join(":");
102875
- return `<?xml version="1.0" encoding="UTF-8"?>
102876
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
102877
- <plist version="1.0">
102878
- <dict>
102879
- <key>Label</key>
102880
- <string>${LAUNCHD_LABEL}</string>
102881
- <key>ProgramArguments</key>
102882
- <array>
102883
- <string>${nodeExe}</string>
102884
- <string>${cliExe}</string>
102885
- <string>daemon</string>
102886
- </array>
102887
- <key>RunAtLoad</key>
102888
- <true/>
102889
- <key>KeepAlive</key>
102890
- <dict>
102891
- <key>SuccessfulExit</key>
102892
- <false/>
102893
- </dict>
102894
- <key>ThrottleInterval</key>
102895
- <integer>30</integer>
102896
- <key>StandardOutPath</key>
102897
- <string>${LOG_OUT}</string>
102898
- <key>StandardErrorPath</key>
102899
- <string>${LOG_ERR}</string>
102900
- <key>EnvironmentVariables</key>
102901
- <dict>
102902
- <key>PATH</key>
102903
- <string>${pathValue}</string>
102904
- </dict>
102905
- </dict>
102906
- </plist>`;
102907
- }
102908
- function installDarwin(nodeExe, cliExe) {
102909
- const plistPath = getDarwinPlistPath();
102910
- ensureDir(ADHDEV_DIR);
102911
- ensureDir(import_node_path5.default.dirname(plistPath));
102912
- import_node_fs4.default.writeFileSync(plistPath, buildPlist(nodeExe, cliExe), "utf-8");
102913
- console.log(source_default.gray(` Plist: ${plistPath}`));
102914
- try {
102915
- (0, import_node_child_process4.execSync)(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
102916
- } catch {
102917
- }
102918
- try {
102919
- (0, import_node_child_process4.execSync)(`launchctl load -w "${plistPath}"`, { stdio: "ignore" });
102920
- console.log(source_default.green("\n \u2713 Registered as LaunchAgent \u2014 daemon will start on login."));
102921
- console.log(source_default.gray(` Logs: ~/.adhdev/daemon-launchd.{out,err}`));
102922
- } catch (e) {
102923
- console.log(source_default.red(`
102924
- \u2717 launchctl load failed: ${e.message}`));
102925
- }
102926
- }
102927
- function uninstallDarwin() {
102928
- const plistPath = getDarwinPlistPath();
102929
- if (!import_node_fs4.default.existsSync(plistPath)) {
102930
- console.log(source_default.yellow("\n \u26A0 Service is not installed."));
102931
- return;
102932
- }
102933
- try {
102934
- (0, import_node_child_process4.execSync)(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: "ignore" });
102935
- } catch {
102936
- }
102937
- import_node_fs4.default.unlinkSync(plistPath);
102938
- console.log(source_default.green("\n \u2713 Removed LaunchAgent. Daemon will no longer auto-start."));
102939
- }
102940
- function isInstalledDarwin() {
102941
- return import_node_fs4.default.existsSync(getDarwinPlistPath());
102942
- }
102943
- function buildVbs(nodeExe, cliExe) {
102944
- const logFile = import_node_path5.default.join(ADHDEV_DIR, "daemon-service.log").replace(/\\/g, "\\\\");
102945
- const escapedNodeExe = nodeExe.replace(/\\/g, "\\\\");
102946
- const escapedCliExe = cliExe.replace(/\\/g, "\\\\");
102947
- return `' ADHDev Daemon Auto-Start (generated by adhdev service install)
102948
- Set WshShell = CreateObject("WScript.Shell")
102949
- WshShell.Run "cmd.exe /c """"${escapedNodeExe}"""" """"${escapedCliExe}"""" daemon >> """"${logFile}"""" 2>&1", 0, False
102950
- `;
102951
- }
102952
- function installWindows(nodeExe, cliExe) {
102953
- const vbsPath = getWindowsVbsPath();
102954
- ensureDir(ADHDEV_DIR);
102955
- ensureDir(import_node_path5.default.dirname(vbsPath));
102956
- import_node_fs4.default.writeFileSync(vbsPath, buildVbs(nodeExe, cliExe), "utf-8");
102957
- console.log(source_default.gray(` Startup script: ${vbsPath}`));
102958
- console.log(source_default.green("\n \u2713 Registered in Startup folder \u2014 daemon will start on login (hidden)."));
102959
- console.log(source_default.gray(` Logs: ${import_node_path5.default.join(ADHDEV_DIR, "daemon-service.log")}`));
102960
- console.log(source_default.gray(" To start now without rebooting, run: adhdev daemon"));
102961
- }
102962
- function uninstallWindows() {
102963
- const vbsPath = getWindowsVbsPath();
102964
- if (!import_node_fs4.default.existsSync(vbsPath)) {
102965
- console.log(source_default.yellow("\n \u26A0 Service is not installed."));
102966
- return;
102967
- }
102968
- import_node_fs4.default.unlinkSync(vbsPath);
102969
- console.log(source_default.green("\n \u2713 Removed Startup script. Daemon will no longer auto-start."));
102970
- console.log(source_default.gray(" Note: a currently running daemon is not affected. Stop with: adhdev daemon:stop"));
102971
- }
102972
- function isInstalledWindows() {
102973
- return import_node_fs4.default.existsSync(getWindowsVbsPath());
102974
- }
102975
- function registerServiceCommands(program2) {
102976
- const svc = program2.command("service").description("\u{1F50C} Manage ADHDev as an OS background auto-start service");
102977
- svc.command("install").description("Register ADHDev daemon to start automatically on login").action(async () => {
102978
- console.log(source_default.bold("\n \u{1F680} Installing ADHDev Background Service"));
102979
- const platform12 = import_node_os4.default.platform();
102980
- const nodeExe = process.execPath;
102981
- const cliExe = resolveCliPath();
102982
- console.log(source_default.gray(` Node: ${nodeExe}`));
102983
- console.log(source_default.gray(` CLI: ${cliExe}`));
102984
- console.log(source_default.gray(` Platform: ${platform12}`));
102985
- if (platform12 === "darwin") {
102986
- installDarwin(nodeExe, cliExe);
102987
- } else if (platform12 === "win32") {
102988
- installWindows(nodeExe, cliExe);
102989
- } else {
102990
- console.log(source_default.yellow("\n \u26A0 Auto-start service install is not supported on this platform."));
102991
- console.log(source_default.gray(" On Linux, create a systemd user unit manually:"));
102992
- console.log(source_default.gray(" ~/.config/systemd/user/adhdev-daemon.service"));
102993
- }
102994
- console.log();
102995
- });
102996
- svc.command("uninstall").description("Remove the OS background service").action(async () => {
102997
- console.log(source_default.bold("\n \u{1F5D1}\uFE0F Removing ADHDev Background Service"));
102998
- const platform12 = import_node_os4.default.platform();
102999
- if (platform12 === "darwin") {
103000
- uninstallDarwin();
103001
- } else if (platform12 === "win32") {
103002
- uninstallWindows();
103003
- } else {
103004
- console.log(source_default.yellow("\n \u26A0 Not supported on this platform."));
103005
- }
103006
- console.log();
103007
- });
103008
- svc.command("status").description("Show service installation state and live daemon health").action(async () => {
103009
- const platform12 = import_node_os4.default.platform();
103010
- const installed = platform12 === "darwin" ? isInstalledDarwin() : platform12 === "win32" ? isInstalledWindows() : false;
103011
- if (installed) {
103012
- console.log(source_default.green("\n \u2713 Service is installed."));
103013
- if (platform12 === "darwin") console.log(source_default.gray(` Plist: ${getDarwinPlistPath()}`));
103014
- else console.log(source_default.gray(` Script: ${getWindowsVbsPath()}`));
103015
- } else {
103016
- console.log(source_default.gray("\n \u2717 Service is not installed. Run: adhdev service install"));
103017
- }
103018
- const health = await fetchHealth();
103019
- if (health?.ok && health.pid) {
103020
- const info = getProcessInfo(health.pid);
103021
- if (info) {
103022
- console.log(source_default.green(` \u2713 Daemon running \u2014 PID ${health.pid}, uptime ${info.uptime}, ${info.memMB} MB`));
103023
- } else {
103024
- console.log(source_default.green(` \u2713 Daemon running \u2014 PID ${health.pid}`));
103025
- }
103026
- } else {
103027
- console.log(source_default.yellow(" \u2717 Daemon is not running."));
103028
- }
103029
- const outSize = import_node_fs4.default.existsSync(LOG_OUT) ? import_node_fs4.default.statSync(LOG_OUT).size : 0;
103030
- const errSize = import_node_fs4.default.existsSync(LOG_ERR) ? import_node_fs4.default.statSync(LOG_ERR).size : 0;
103031
- if (outSize > 0 || errSize > 0) {
103032
- console.log(source_default.gray(` Logs: stdout ${formatBytes(outSize)}, stderr ${formatBytes(errSize)}`));
103033
- }
103034
- console.log();
103035
- });
103036
- svc.command("logs").description("View daemon service logs").option("--err", "Show stderr log instead of stdout").option("--clear", "Truncate all log files").option("-n, --lines <count>", "Number of lines to show", "30").action(async (options) => {
103037
- if (options.clear) {
103038
- for (const f of [LOG_OUT, LOG_ERR]) {
103039
- if (import_node_fs4.default.existsSync(f)) import_node_fs4.default.writeFileSync(f, "", "utf-8");
103040
- }
103041
- console.log(source_default.green("\n \u2713 Logs cleared.\n"));
103042
- return;
103043
- }
103044
- const logFile = options.err ? LOG_ERR : LOG_OUT;
103045
- if (!import_node_fs4.default.existsSync(logFile)) {
103046
- console.log(source_default.gray(`
103047
- No log file found: ${logFile}
103048
- `));
103049
- return;
103050
- }
103051
- const lines = parseInt(options.lines, 10) || 30;
103052
- console.log(source_default.gray(`
103053
- \u2500\u2500 ${options.err ? "stderr" : "stdout"}: ${logFile} (last ${lines} lines) \u2500\u2500
103054
- `));
103055
- const content = import_node_fs4.default.readFileSync(logFile, "utf-8");
103056
- const allLines = content.split("\n");
103057
- const lastLines = allLines.slice(-lines).join("\n");
103058
- if (lastLines.trim()) console.log(lastLines);
103059
- console.log(source_default.gray("\n (watching for new output, Ctrl+C to stop)\n"));
103060
- let offset = Buffer.byteLength(content, "utf-8");
103061
- const watcher = import_node_fs4.default.watchFile(logFile, { interval: 500 }, () => {
103062
- try {
103063
- const stat5 = import_node_fs4.default.statSync(logFile);
103064
- if (stat5.size > offset) {
103065
- const fd = import_node_fs4.default.openSync(logFile, "r");
103066
- const buf = Buffer.alloc(stat5.size - offset);
103067
- import_node_fs4.default.readSync(fd, buf, 0, buf.length, offset);
103068
- import_node_fs4.default.closeSync(fd);
103069
- process.stdout.write(buf.toString("utf-8"));
103070
- offset = stat5.size;
103071
- } else if (stat5.size < offset) {
103072
- offset = 0;
103073
- }
103074
- } catch {
103075
- }
103076
- });
103077
- const cleanup = () => {
103078
- import_node_fs4.default.unwatchFile(logFile);
103079
- process.exit(0);
103080
- };
103081
- process.on("SIGINT", cleanup);
103082
- process.on("SIGTERM", cleanup);
103083
- });
103084
- svc.command("restart").description("Restart the daemon process (service will auto-relaunch)").action(async () => {
103085
- const platform12 = import_node_os4.default.platform();
103086
- const installed = platform12 === "darwin" ? isInstalledDarwin() : platform12 === "win32" ? isInstalledWindows() : false;
103087
- if (!installed) {
103088
- console.log(source_default.yellow("\n \u26A0 Service is not installed. Use `adhdev daemon:restart` for manual restart."));
103089
- console.log(source_default.gray(" Or install the service first: adhdev service install\n"));
103090
- return;
103091
- }
103092
- rotateLogs();
103093
- const health = await fetchHealth();
103094
- if (!health?.pid) {
103095
- console.log(source_default.yellow("\n \u26A0 Daemon is not currently running."));
103096
- if (platform12 === "darwin") {
103097
- console.log(source_default.gray(" Starting via launchctl..."));
103098
- try {
103099
- (0, import_node_child_process4.execSync)(`launchctl start ${LAUNCHD_LABEL}`, { stdio: "ignore" });
103100
- console.log(source_default.green(" \u2713 Started.\n"));
103101
- } catch {
103102
- console.log(source_default.red(" \u2717 Failed to start. Check: adhdev service logs --err\n"));
103103
- }
103104
- } else {
103105
- console.log(source_default.gray(" Start with: adhdev daemon\n"));
103106
- }
103107
- return;
103108
- }
103109
- console.log(source_default.cyan(`
103110
- Stopping daemon (PID ${health.pid})...`));
103111
- try {
103112
- process.kill(health.pid, "SIGTERM");
103113
- } catch {
103114
- console.log(source_default.yellow(" Could not send SIGTERM. Process may have already exited."));
103115
- }
103116
- await new Promise((r) => setTimeout(r, 2e3));
103117
- if (platform12 === "win32") {
103118
- const vbsPath = getWindowsVbsPath();
103119
- if (import_node_fs4.default.existsSync(vbsPath)) {
103120
- try {
103121
- (0, import_node_child_process4.execSync)(`wscript.exe "${vbsPath}"`, { stdio: "ignore", windowsHide: true });
103122
- } catch {
103123
- }
103124
- }
103125
- }
103126
- let restarted = false;
103127
- for (let i = 0; i < 8; i++) {
103128
- await new Promise((r) => setTimeout(r, 1e3));
103129
- const newHealth = await fetchHealth();
103130
- if (newHealth?.ok && newHealth.pid !== health.pid) {
103131
- restarted = true;
103132
- const info = getProcessInfo(newHealth.pid);
103133
- console.log(source_default.green(` \u2713 Daemon restarted \u2014 new PID ${newHealth.pid}${info ? `, ${info.memMB} MB` : ""}`));
103134
- break;
103135
- }
103136
- }
103137
- if (!restarted) {
103138
- console.log(source_default.yellow(" \u26A0 Daemon did not restart within 10s."));
103139
- console.log(source_default.gray(" Check: adhdev service logs --err"));
103140
- }
103141
- console.log();
103142
- });
103143
- }
103144
- function formatBytes(bytes) {
103145
- if (bytes === 0) return "0 B";
103146
- if (bytes < 1024) return `${bytes} B`;
103147
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
103148
- return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
103149
- }
103150
-
103151
103606
  // src/cli/doctor-commands.ts
103607
+ init_service_commands();
103152
103608
  function resolvePackageRoot() {
103153
103609
  return path39.resolve(__dirname, "..", "..");
103154
103610
  }
@@ -104640,6 +105096,9 @@ function registerCdpCommands(program2) {
104640
105096
  });
104641
105097
  }
104642
105098
 
105099
+ // src/cli/index.ts
105100
+ init_service_commands();
105101
+
104643
105102
  // src/cli/mcp-commands.ts
104644
105103
  var import_node_child_process5 = require("child_process");
104645
105104
  var fs23 = __toESM(require("fs"));