@adhdev/daemon-standalone 0.8.76 → 0.8.78

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/index.js CHANGED
@@ -9244,7 +9244,7 @@ var require_dist = __commonJS({
9244
9244
  }
9245
9245
  }
9246
9246
  };
9247
- var import_crypto2 = require("crypto");
9247
+ var import_crypto3 = require("crypto");
9248
9248
  var path5 = __toESM2(require("path"));
9249
9249
  function normalizeSlug(input) {
9250
9250
  return input.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
@@ -9360,7 +9360,7 @@ var require_dist = __commonJS({
9360
9360
  var SessionHostRegistry = class {
9361
9361
  sessions = /* @__PURE__ */ new Map();
9362
9362
  createSession(payload) {
9363
- const sessionId = payload.sessionId || (0, import_crypto2.randomUUID)();
9363
+ const sessionId = payload.sessionId || (0, import_crypto3.randomUUID)();
9364
9364
  if (this.sessions.has(sessionId)) {
9365
9365
  throw new Error(`Session already exists: ${sessionId}`);
9366
9366
  }
@@ -26823,9 +26823,9 @@ var init_handler = __esm({
26823
26823
  if (this.fsw.closed) {
26824
26824
  return;
26825
26825
  }
26826
- const dirname3 = sp.dirname(file2);
26826
+ const dirname4 = sp.dirname(file2);
26827
26827
  const basename3 = sp.basename(file2);
26828
- const parent = this.fsw._getWatchedDir(dirname3);
26828
+ const parent = this.fsw._getWatchedDir(dirname4);
26829
26829
  let prevStats = stats;
26830
26830
  if (parent.has(basename3))
26831
26831
  return;
@@ -26852,7 +26852,7 @@ var init_handler = __esm({
26852
26852
  prevStats = newStats2;
26853
26853
  }
26854
26854
  } catch (error48) {
26855
- this.fsw._remove(dirname3, basename3);
26855
+ this.fsw._remove(dirname4, basename3);
26856
26856
  }
26857
26857
  } else if (parent.has(basename3)) {
26858
26858
  const at2 = newStats.atimeMs;
@@ -27927,7 +27927,7 @@ var require_dist2 = __commonJS({
27927
27927
  };
27928
27928
  }
27929
27929
  function generateMachineId() {
27930
- return `${MACHINE_ID_PREFIX}${(0, import_crypto2.randomUUID)().replace(/-/g, "")}`;
27930
+ return `${MACHINE_ID_PREFIX}${(0, import_crypto3.randomUUID)().replace(/-/g, "")}`;
27931
27931
  }
27932
27932
  function isStableMachineId(machineId) {
27933
27933
  return typeof machineId === "string" && machineId.startsWith(MACHINE_ID_PREFIX);
@@ -28046,7 +28046,7 @@ var require_dist2 = __commonJS({
28046
28046
  var import_os2;
28047
28047
  var import_path2;
28048
28048
  var import_fs;
28049
- var import_crypto2;
28049
+ var import_crypto3;
28050
28050
  var DEFAULT_CONFIG;
28051
28051
  var MACHINE_ID_PREFIX;
28052
28052
  var init_config = __esm2({
@@ -28055,7 +28055,7 @@ var require_dist2 = __commonJS({
28055
28055
  import_os2 = require("os");
28056
28056
  import_path2 = require("path");
28057
28057
  import_fs = require("fs");
28058
- import_crypto2 = require("crypto");
28058
+ import_crypto3 = require("crypto");
28059
28059
  DEFAULT_CONFIG = {
28060
28060
  serverUrl: "https://api.adhf.dev",
28061
28061
  allowServerApiProxy: false,
@@ -32229,12 +32229,66 @@ ${data.message || ""}`.trim();
32229
32229
  const availableMem = darwinAvail != null ? darwinAvail : freeMem;
32230
32230
  return { totalMem, freeMem, availableMem };
32231
32231
  }
32232
+ var LIVE_LIFECYCLES = /* @__PURE__ */ new Set(["starting", "running", "stopping", "interrupted"]);
32233
+ function isSessionHostLiveRuntime(record2) {
32234
+ const lifecycle = String(record2?.lifecycle || "").trim();
32235
+ return LIVE_LIFECYCLES.has(lifecycle);
32236
+ }
32237
+ function getSessionHostRecoveryLabel(meta3) {
32238
+ const recoveryState = typeof meta3?.runtimeRecoveryState === "string" ? String(meta3.runtimeRecoveryState).trim() : "";
32239
+ if (!recoveryState) return null;
32240
+ if (recoveryState === "auto_resumed") return "restored after restart";
32241
+ if (recoveryState === "resume_failed") return "restore failed";
32242
+ if (recoveryState === "host_restart_interrupted") return "host restart interrupted";
32243
+ if (recoveryState === "orphan_snapshot") return "snapshot recovered";
32244
+ return recoveryState.replace(/_/g, " ");
32245
+ }
32246
+ function isSessionHostRecoverySnapshot(record2) {
32247
+ if (!record2) return false;
32248
+ if (isSessionHostLiveRuntime(record2)) return false;
32249
+ const lifecycle = String(record2.lifecycle || "").trim();
32250
+ if (lifecycle && lifecycle !== "stopped" && lifecycle !== "failed") {
32251
+ return false;
32252
+ }
32253
+ const meta3 = record2.meta || void 0;
32254
+ if (meta3?.restoredFromStorage === true) return true;
32255
+ return getSessionHostRecoveryLabel(meta3) !== null;
32256
+ }
32257
+ function getSessionHostSurfaceKind(record2) {
32258
+ if (isSessionHostLiveRuntime(record2)) return "live_runtime";
32259
+ if (isSessionHostRecoverySnapshot(record2)) return "recovery_snapshot";
32260
+ return "inactive_record";
32261
+ }
32262
+ function partitionSessionHostRecords(records) {
32263
+ const liveRuntimes = [];
32264
+ const recoverySnapshots = [];
32265
+ const inactiveRecords = [];
32266
+ for (const record2 of records) {
32267
+ const kind = getSessionHostSurfaceKind(record2);
32268
+ if (kind === "live_runtime") {
32269
+ liveRuntimes.push(record2);
32270
+ } else if (kind === "recovery_snapshot") {
32271
+ recoverySnapshots.push(record2);
32272
+ } else {
32273
+ inactiveRecords.push(record2);
32274
+ }
32275
+ }
32276
+ return {
32277
+ liveRuntimes,
32278
+ recoverySnapshots,
32279
+ inactiveRecords
32280
+ };
32281
+ }
32282
+ function partitionSessionHostDiagnosticsSessions(records) {
32283
+ return partitionSessionHostRecords(records || []);
32284
+ }
32232
32285
  var DEFAULT_ACTIVE_CHAT_POLL_STATUSES = /* @__PURE__ */ new Set([
32233
32286
  "generating",
32234
32287
  "waiting_approval",
32235
32288
  "starting"
32236
32289
  ]);
32237
32290
  var DEFAULT_CHAT_TAIL_RECENT_MESSAGE_GRACE_MS = 8e3;
32291
+ var LIVE_RUNTIME_LIFECYCLES = /* @__PURE__ */ new Set(["starting", "running", "stopping", "interrupted"]);
32238
32292
  function parseMessageTimestamp(value) {
32239
32293
  if (typeof value === "number" && Number.isFinite(value)) return value;
32240
32294
  if (typeof value === "string") {
@@ -32243,6 +32297,23 @@ ${data.message || ""}`.trim();
32243
32297
  }
32244
32298
  return 0;
32245
32299
  }
32300
+ function isDefinitelyNonLiveRuntimeSession(session) {
32301
+ const surfaceKind = String(session?.runtimeSurfaceKind || "").trim();
32302
+ if (surfaceKind === "live_runtime") return false;
32303
+ if (surfaceKind === "recovery_snapshot") return true;
32304
+ if (surfaceKind === "inactive_record") return false;
32305
+ const lifecycle = String(session?.runtimeLifecycle || "").trim();
32306
+ if (lifecycle && LIVE_RUNTIME_LIFECYCLES.has(lifecycle)) return false;
32307
+ const inferredSurfaceKind = getSessionHostSurfaceKind({
32308
+ lifecycle: lifecycle || null,
32309
+ meta: {
32310
+ restoredFromStorage: session?.runtimeRestoredFromStorage === true,
32311
+ ...session?.runtimeRecoveryState ? { runtimeRecoveryState: session.runtimeRecoveryState } : {}
32312
+ }
32313
+ });
32314
+ if (inferredSurfaceKind === "recovery_snapshot") return true;
32315
+ return false;
32316
+ }
32246
32317
  function classifyHotChatSessionsForSubscriptionFlush2(sessions, previousHotSessionIds, options = {}) {
32247
32318
  const now = options.now ?? Date.now();
32248
32319
  const recentMessageGraceMs = Math.max(
@@ -32251,9 +32322,14 @@ ${data.message || ""}`.trim();
32251
32322
  );
32252
32323
  const activeStatuses = options.activeStatuses ?? DEFAULT_ACTIVE_CHAT_POLL_STATUSES;
32253
32324
  const active = /* @__PURE__ */ new Set();
32325
+ const excluded = /* @__PURE__ */ new Set();
32254
32326
  for (const session of sessions) {
32255
32327
  const sessionId = typeof session?.id === "string" ? session.id : "";
32256
32328
  if (!sessionId) continue;
32329
+ if (isDefinitelyNonLiveRuntimeSession(session)) {
32330
+ excluded.add(sessionId);
32331
+ continue;
32332
+ }
32257
32333
  const status = String(session?.status || "").toLowerCase();
32258
32334
  const lastMessageAt = parseMessageTimestamp(session?.lastMessageAt);
32259
32335
  const recentlyUpdated = lastMessageAt > 0 && now - lastMessageAt <= recentMessageGraceMs;
@@ -32262,7 +32338,7 @@ ${data.message || ""}`.trim();
32262
32338
  }
32263
32339
  }
32264
32340
  const finalizing = new Set(
32265
- Array.from(previousHotSessionIds).filter((sessionId) => !active.has(sessionId))
32341
+ Array.from(previousHotSessionIds).filter((sessionId) => !active.has(sessionId) && !excluded.has(sessionId))
32266
32342
  );
32267
32343
  return { active, finalizing };
32268
32344
  }
@@ -36481,7 +36557,7 @@ ${effect.notification.body || ""}`.trim();
36481
36557
  return profile !== "live";
36482
36558
  }
36483
36559
  function shouldIncludeRuntimeMetadata(profile) {
36484
- return profile !== "live";
36560
+ return true;
36485
36561
  }
36486
36562
  function findCdpManager(cdpManagers, key) {
36487
36563
  const exact = cdpManagers.get(key);
@@ -36636,8 +36712,12 @@ ${effect.notification.body || ""}`.trim();
36636
36712
  runtimeKey: state.runtime?.runtimeKey,
36637
36713
  runtimeDisplayName: state.runtime?.displayName,
36638
36714
  runtimeWorkspaceLabel: state.runtime?.workspaceLabel,
36715
+ runtimeLifecycle: state.runtime?.lifecycle ?? null,
36716
+ runtimeSurfaceKind: state.runtime?.surfaceKind,
36639
36717
  runtimeWriteOwner: state.runtime?.writeOwner || null,
36640
- runtimeAttachedClients: state.runtime?.attachedClients || []
36718
+ runtimeAttachedClients: state.runtime?.attachedClients || [],
36719
+ runtimeRestoredFromStorage: state.runtime?.restoredFromStorage === true,
36720
+ runtimeRecoveryState: state.runtime?.recoveryState ?? null
36641
36721
  },
36642
36722
  mode: state.mode,
36643
36723
  resume: state.resume,
@@ -39884,8 +39964,12 @@ ${effect.notification.body || ""}`.trim();
39884
39964
  runtimeKey: runtime.runtimeKey,
39885
39965
  displayName: runtime.displayName,
39886
39966
  workspaceLabel: runtime.workspaceLabel,
39967
+ lifecycle: runtime.lifecycle ?? null,
39968
+ surfaceKind: runtime.surfaceKind,
39887
39969
  writeOwner: runtime.writeOwner || null,
39888
- attachedClients: runtime.attachedClients || []
39970
+ attachedClients: runtime.attachedClients || [],
39971
+ restoredFromStorage: runtime.restoredFromStorage === true,
39972
+ recoveryState: runtime.recoveryState ?? null
39889
39973
  } : void 0,
39890
39974
  resume: this.provider.resume,
39891
39975
  controlValues: surface.controlValues,
@@ -44116,10 +44200,15 @@ Run 'adhdev doctor' for detailed diagnostics.`
44116
44200
  }
44117
44201
  var SKIP_COMMANDS = /* @__PURE__ */ new Set([
44118
44202
  "heartbeat",
44119
- "status_report"
44203
+ "status_report",
44204
+ "read_chat",
44205
+ "mark_session_seen"
44120
44206
  ]);
44207
+ function shouldLogCommand(cmd) {
44208
+ return !SKIP_COMMANDS.has(cmd);
44209
+ }
44121
44210
  function logCommand(entry) {
44122
- if (SKIP_COMMANDS.has(entry.cmd)) return;
44211
+ if (!shouldLogCommand(entry.cmd)) return;
44123
44212
  try {
44124
44213
  if (++writeCount2 % 500 === 0) {
44125
44214
  checkRotation();
@@ -44167,59 +44256,6 @@ Run 'adhdev doctor' for detailed diagnostics.`
44167
44256
  }
44168
44257
  cleanOldFiles();
44169
44258
  init_logger();
44170
- var LIVE_LIFECYCLES = /* @__PURE__ */ new Set(["starting", "running", "stopping", "interrupted"]);
44171
- function isSessionHostLiveRuntime(record2) {
44172
- const lifecycle = String(record2?.lifecycle || "").trim();
44173
- return LIVE_LIFECYCLES.has(lifecycle);
44174
- }
44175
- function getSessionHostRecoveryLabel(meta3) {
44176
- const recoveryState = typeof meta3?.runtimeRecoveryState === "string" ? String(meta3.runtimeRecoveryState).trim() : "";
44177
- if (!recoveryState) return null;
44178
- if (recoveryState === "auto_resumed") return "restored after restart";
44179
- if (recoveryState === "resume_failed") return "restore failed";
44180
- if (recoveryState === "host_restart_interrupted") return "host restart interrupted";
44181
- if (recoveryState === "orphan_snapshot") return "snapshot recovered";
44182
- return recoveryState.replace(/_/g, " ");
44183
- }
44184
- function isSessionHostRecoverySnapshot(record2) {
44185
- if (!record2) return false;
44186
- if (isSessionHostLiveRuntime(record2)) return false;
44187
- const lifecycle = String(record2.lifecycle || "").trim();
44188
- if (lifecycle && lifecycle !== "stopped" && lifecycle !== "failed") {
44189
- return false;
44190
- }
44191
- const meta3 = record2.meta || void 0;
44192
- if (meta3?.restoredFromStorage === true) return true;
44193
- return getSessionHostRecoveryLabel(meta3) !== null;
44194
- }
44195
- function getSessionHostSurfaceKind(record2) {
44196
- if (isSessionHostLiveRuntime(record2)) return "live_runtime";
44197
- if (isSessionHostRecoverySnapshot(record2)) return "recovery_snapshot";
44198
- return "inactive_record";
44199
- }
44200
- function partitionSessionHostRecords(records) {
44201
- const liveRuntimes = [];
44202
- const recoverySnapshots = [];
44203
- const inactiveRecords = [];
44204
- for (const record2 of records) {
44205
- const kind = getSessionHostSurfaceKind(record2);
44206
- if (kind === "live_runtime") {
44207
- liveRuntimes.push(record2);
44208
- } else if (kind === "recovery_snapshot") {
44209
- recoverySnapshots.push(record2);
44210
- } else {
44211
- inactiveRecords.push(record2);
44212
- }
44213
- }
44214
- return {
44215
- liveRuntimes,
44216
- recoverySnapshots,
44217
- inactiveRecords
44218
- };
44219
- }
44220
- function partitionSessionHostDiagnosticsSessions(records) {
44221
- return partitionSessionHostRecords(records || []);
44222
- }
44223
44259
  var os16 = __toESM2(require("os"));
44224
44260
  init_config();
44225
44261
  init_terminal_screen();
@@ -52547,6 +52583,8 @@ data: ${JSON.stringify(msg.data)}
52547
52583
  runtimeKey: record2.runtimeKey,
52548
52584
  displayName: record2.displayName,
52549
52585
  workspaceLabel: record2.workspaceLabel,
52586
+ lifecycle: typeof record2.lifecycle === "string" ? record2.lifecycle : null,
52587
+ surfaceKind: record2.surfaceKind,
52550
52588
  writeOwner: record2.writeOwner ? {
52551
52589
  clientId: record2.writeOwner.clientId,
52552
52590
  ownerType: record2.writeOwner.ownerType
@@ -53149,6 +53187,7 @@ var import_ws = require("ws");
53149
53187
  var path4 = __toESM(require("path"));
53150
53188
  var fs3 = __toESM(require("fs"));
53151
53189
  var os5 = __toESM(require("os"));
53190
+ var import_crypto2 = require("crypto");
53152
53191
  var import_daemon_core2 = __toESM(require_dist2());
53153
53192
 
53154
53193
  // src/session-host.ts
@@ -53654,6 +53693,165 @@ async function getWorkspaceSocketInfo(workspaceName) {
53654
53693
  var import_daemon_core3 = __toESM(require_dist2());
53655
53694
  var DEFAULT_PORT = 3847;
53656
53695
  var STATUS_INTERVAL = 2e3;
53696
+ var STANDALONE_AUTH_SESSION_COOKIE = "adhdev_standalone_session";
53697
+ var STANDALONE_PASSWORD_CONFIG_FILE = "standalone-auth.json";
53698
+ var STANDALONE_BIND_HOST_CONFIG_FILE = "standalone-network.json";
53699
+ var STANDALONE_BIND_HOST_DEFAULT = "127.0.0.1";
53700
+ var PASSWORD_KEYLEN = 64;
53701
+ var DEFAULT_SESSION_TTL_MS = 1e3 * 60 * 60 * 24 * 30;
53702
+ function getStandalonePasswordConfigPath() {
53703
+ const dir = path4.join(os5.homedir(), ".adhdev");
53704
+ if (!fs3.existsSync(dir)) {
53705
+ fs3.mkdirSync(dir, { recursive: true, mode: 448 });
53706
+ }
53707
+ return path4.join(dir, STANDALONE_PASSWORD_CONFIG_FILE);
53708
+ }
53709
+ function getStandaloneConfigJsonPath() {
53710
+ const dir = path4.join(os5.homedir(), ".adhdev");
53711
+ if (!fs3.existsSync(dir)) {
53712
+ fs3.mkdirSync(dir, { recursive: true, mode: 448 });
53713
+ }
53714
+ return path4.join(dir, STANDALONE_BIND_HOST_CONFIG_FILE);
53715
+ }
53716
+ function loadStandaloneBindHostPreference() {
53717
+ try {
53718
+ const configPath = getStandaloneConfigJsonPath();
53719
+ if (!fs3.existsSync(configPath)) return STANDALONE_BIND_HOST_DEFAULT;
53720
+ const parsed = JSON.parse(fs3.readFileSync(configPath, "utf8"));
53721
+ return parsed?.standaloneBindHost === "0.0.0.0" ? "0.0.0.0" : STANDALONE_BIND_HOST_DEFAULT;
53722
+ } catch {
53723
+ return STANDALONE_BIND_HOST_DEFAULT;
53724
+ }
53725
+ }
53726
+ function saveStandaloneBindHostPreference(bindHost) {
53727
+ const configPath = getStandaloneConfigJsonPath();
53728
+ let parsed = {};
53729
+ try {
53730
+ if (fs3.existsSync(configPath)) {
53731
+ const raw = fs3.readFileSync(configPath, "utf8");
53732
+ const next = JSON.parse(raw);
53733
+ if (next && typeof next === "object" && !Array.isArray(next)) parsed = next;
53734
+ }
53735
+ } catch {
53736
+ parsed = {};
53737
+ }
53738
+ parsed.standaloneBindHost = bindHost;
53739
+ fs3.writeFileSync(configPath, JSON.stringify(parsed, null, 2), { encoding: "utf8", mode: 384 });
53740
+ try {
53741
+ fs3.chmodSync(configPath, 384);
53742
+ } catch {
53743
+ }
53744
+ return bindHost;
53745
+ }
53746
+ function createPasswordRecord(password, salt = (0, import_crypto2.randomBytes)(16).toString("hex")) {
53747
+ return {
53748
+ passwordHash: (0, import_crypto2.scryptSync)(`${password || ""}`, salt, PASSWORD_KEYLEN).toString("hex"),
53749
+ passwordSalt: salt,
53750
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
53751
+ };
53752
+ }
53753
+ function verifyPassword(password, config2) {
53754
+ if (!config2?.passwordHash || !config2.passwordSalt) return false;
53755
+ const actual = Buffer.from((0, import_crypto2.scryptSync)(`${password || ""}`, config2.passwordSalt, PASSWORD_KEYLEN).toString("hex"), "utf8");
53756
+ const expected = Buffer.from(config2.passwordHash, "utf8");
53757
+ return actual.length === expected.length && (0, import_crypto2.timingSafeEqual)(actual, expected);
53758
+ }
53759
+ function loadStandalonePasswordConfig(filePath = getStandalonePasswordConfigPath()) {
53760
+ if (!fs3.existsSync(filePath)) return null;
53761
+ try {
53762
+ const parsed = JSON.parse(fs3.readFileSync(filePath, "utf8"));
53763
+ if (!parsed || typeof parsed !== "object") return null;
53764
+ if (typeof parsed.passwordHash !== "string" || typeof parsed.passwordSalt !== "string") return null;
53765
+ return {
53766
+ passwordHash: parsed.passwordHash,
53767
+ passwordSalt: parsed.passwordSalt,
53768
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : (/* @__PURE__ */ new Date(0)).toISOString()
53769
+ };
53770
+ } catch {
53771
+ return null;
53772
+ }
53773
+ }
53774
+ function saveStandalonePasswordConfig(filePath, config2) {
53775
+ const dir = path4.dirname(filePath);
53776
+ if (!fs3.existsSync(dir)) {
53777
+ fs3.mkdirSync(dir, { recursive: true, mode: 448 });
53778
+ }
53779
+ fs3.writeFileSync(filePath, JSON.stringify(config2, null, 2), { encoding: "utf8", mode: 384 });
53780
+ try {
53781
+ fs3.chmodSync(filePath, 384);
53782
+ } catch {
53783
+ }
53784
+ }
53785
+ function clearStandalonePasswordConfig(filePath = getStandalonePasswordConfigPath()) {
53786
+ if (fs3.existsSync(filePath)) {
53787
+ fs3.rmSync(filePath, { force: true });
53788
+ }
53789
+ }
53790
+ function shouldWarnForPublicUnauthenticatedHost(input) {
53791
+ return input.host === "0.0.0.0" && !input.hasTokenAuth && !input.hasPasswordAuth;
53792
+ }
53793
+ function parseCookies(cookieHeader) {
53794
+ if (!cookieHeader) return {};
53795
+ return Object.fromEntries(
53796
+ cookieHeader.split(";").map((part) => part.trim()).filter(Boolean).map((part) => {
53797
+ const eq = part.indexOf("=");
53798
+ if (eq === -1) return [part, ""];
53799
+ return [part.slice(0, eq), decodeURIComponent(part.slice(eq + 1))];
53800
+ })
53801
+ );
53802
+ }
53803
+ function buildSessionCookie(sessionId, secure, maxAgeMs = DEFAULT_SESSION_TTL_MS) {
53804
+ const parts = [
53805
+ `${STANDALONE_AUTH_SESSION_COOKIE}=${encodeURIComponent(sessionId)}`,
53806
+ "Path=/",
53807
+ "HttpOnly",
53808
+ "SameSite=Lax",
53809
+ `Max-Age=${Math.max(0, Math.floor(maxAgeMs / 1e3))}`
53810
+ ];
53811
+ if (secure) parts.push("Secure");
53812
+ return parts.join("; ");
53813
+ }
53814
+ function buildClearedSessionCookie(secure) {
53815
+ return buildSessionCookie("", secure, 0);
53816
+ }
53817
+ var StandaloneSessionStore = class {
53818
+ sessions = /* @__PURE__ */ new Map();
53819
+ create(ttlMs = DEFAULT_SESSION_TTL_MS) {
53820
+ const id = (0, import_crypto2.randomBytes)(24).toString("hex");
53821
+ this.sessions.set(id, Date.now() + ttlMs);
53822
+ return id;
53823
+ }
53824
+ has(sessionId) {
53825
+ if (!sessionId) return false;
53826
+ const expiresAt = this.sessions.get(sessionId);
53827
+ if (!expiresAt) return false;
53828
+ if (expiresAt <= Date.now()) {
53829
+ this.sessions.delete(sessionId);
53830
+ return false;
53831
+ }
53832
+ return true;
53833
+ }
53834
+ revoke(sessionId) {
53835
+ if (!sessionId) return;
53836
+ this.sessions.delete(sessionId);
53837
+ }
53838
+ clear() {
53839
+ this.sessions.clear();
53840
+ }
53841
+ };
53842
+ function isStandaloneRequestAuthenticated(input) {
53843
+ const hasTokenAuth = !!input.configuredToken;
53844
+ const hasPasswordAuth = !!input.passwordConfig;
53845
+ if (!hasTokenAuth && !hasPasswordAuth) return true;
53846
+ if (hasTokenAuth && (input.bearerToken === input.configuredToken || input.queryToken === input.configuredToken)) {
53847
+ return true;
53848
+ }
53849
+ if (hasPasswordAuth) {
53850
+ const cookies = parseCookies(input.cookieHeader);
53851
+ return input.sessionStore.has(cookies[STANDALONE_AUTH_SESSION_COOKIE]);
53852
+ }
53853
+ return false;
53854
+ }
53657
53855
  var pkgVersion = process.env.ADHDEV_PKG_VERSION || "unknown";
53658
53856
  if (pkgVersion === "unknown") {
53659
53857
  try {
@@ -53702,6 +53900,10 @@ var StandaloneServer = class {
53702
53900
  wsSessionModalSubscriptions = /* @__PURE__ */ new Map();
53703
53901
  wsDaemonMetadataSubscriptions = /* @__PURE__ */ new Map();
53704
53902
  authToken = null;
53903
+ passwordConfigPath = getStandalonePasswordConfigPath();
53904
+ passwordConfig = null;
53905
+ authSessions = new StandaloneSessionStore();
53906
+ listenHost = "127.0.0.1";
53705
53907
  statusTimer = null;
53706
53908
  lastStatusBroadcastAt = 0;
53707
53909
  statusBroadcastPending = false;
@@ -53758,10 +53960,88 @@ var StandaloneServer = class {
53758
53960
  const mode = this.getCliPresentationMode(sessionId);
53759
53961
  return mode === "chat" || mode === "terminal";
53760
53962
  }
53963
+ hasPasswordAuth() {
53964
+ return !!this.passwordConfig;
53965
+ }
53966
+ hasAnyAuth() {
53967
+ return !!this.authToken || this.hasPasswordAuth();
53968
+ }
53969
+ getCookieSecureFlag(req) {
53970
+ const forwardedProto = req.headers["x-forwarded-proto"];
53971
+ return !!req.socket.encrypted || typeof forwardedProto === "string" && forwardedProto.toLowerCase().includes("https");
53972
+ }
53973
+ getRequestTokens(req, rawUrl) {
53974
+ const authHeader = req.headers["authorization"];
53975
+ const bearerToken = typeof authHeader === "string" && authHeader.startsWith("Bearer ") ? authHeader.slice(7) : null;
53976
+ const queryToken = new URL(rawUrl, `http://${req.headers.host || "localhost"}`).searchParams.get("token");
53977
+ return { bearerToken, queryToken };
53978
+ }
53979
+ isRequestAuthenticated(req, rawUrl) {
53980
+ const { bearerToken, queryToken } = this.getRequestTokens(req, rawUrl);
53981
+ return isStandaloneRequestAuthenticated({
53982
+ configuredToken: this.authToken,
53983
+ passwordConfig: this.passwordConfig,
53984
+ bearerToken,
53985
+ queryToken,
53986
+ cookieHeader: typeof req.headers.cookie === "string" ? req.headers.cookie : void 0,
53987
+ sessionStore: this.authSessions
53988
+ });
53989
+ }
53990
+ getRequestSessionId(req) {
53991
+ const cookies = parseCookies(typeof req.headers.cookie === "string" ? req.headers.cookie : void 0);
53992
+ return cookies.adhdev_standalone_session || null;
53993
+ }
53994
+ buildAuthStatus(req, rawUrl) {
53995
+ const required2 = this.hasAnyAuth();
53996
+ return {
53997
+ required: required2,
53998
+ authenticated: this.isRequestAuthenticated(req, rawUrl),
53999
+ hasTokenAuth: !!this.authToken,
54000
+ hasPasswordAuth: this.hasPasswordAuth(),
54001
+ publicHostWarning: shouldWarnForPublicUnauthenticatedHost({
54002
+ host: this.listenHost,
54003
+ hasTokenAuth: !!this.authToken,
54004
+ hasPasswordAuth: this.hasPasswordAuth()
54005
+ }),
54006
+ boundHost: this.listenHost
54007
+ };
54008
+ }
54009
+ isTrustedStandaloneMutationRequest(req) {
54010
+ const originHeader = req.headers.origin;
54011
+ if (typeof originHeader !== "string" || !originHeader.trim()) return true;
54012
+ try {
54013
+ const origin = new URL(originHeader);
54014
+ const host = req.headers.host || "";
54015
+ return origin.host === host;
54016
+ } catch {
54017
+ return false;
54018
+ }
54019
+ }
54020
+ async readJsonBody(req) {
54021
+ return await new Promise((resolve4, reject) => {
54022
+ let body = "";
54023
+ req.on("data", (chunk) => {
54024
+ body += chunk;
54025
+ });
54026
+ req.on("end", () => {
54027
+ try {
54028
+ resolve4(body ? JSON.parse(body) : {});
54029
+ } catch (error48) {
54030
+ reject(error48);
54031
+ }
54032
+ });
54033
+ req.on("error", reject);
54034
+ });
54035
+ }
53761
54036
  async start(options = {}) {
53762
- const port = options.port || DEFAULT_PORT;
53763
- const host = options.host || "127.0.0.1";
54037
+ const persistedStandaloneBindHost = loadStandaloneBindHostPreference();
53764
54038
  const cfg = (0, import_daemon_core2.loadConfig)();
54039
+ if (!options.host && persistedStandaloneBindHost !== STANDALONE_BIND_HOST_DEFAULT) {
54040
+ saveStandaloneBindHostPreference(persistedStandaloneBindHost);
54041
+ }
54042
+ const port = options.port || DEFAULT_PORT;
54043
+ const host = options.host || persistedStandaloneBindHost;
54044
+ this.listenHost = host;
53765
54045
  const sessionHostEndpoint = await ensureSessionHostReady();
53766
54046
  this.sessionHostEndpoint = sessionHostEndpoint;
53767
54047
  const sessionHostControl = new StandaloneSessionHostControlPlane(
@@ -53769,6 +54049,7 @@ var StandaloneServer = class {
53769
54049
  );
53770
54050
  this.sessionHostControl = sessionHostControl;
53771
54051
  this.authToken = options.token || process.env.ADHDEV_TOKEN || null;
54052
+ this.passwordConfig = loadStandalonePasswordConfig(this.passwordConfigPath);
53772
54053
  const statusInstanceId = `standalone_${cfg.machineId || "mach_unknown"}`;
53773
54054
  this.components = await (0, import_daemon_core2.initDaemonComponents)({
53774
54055
  cliManagerDeps: {
@@ -53842,13 +54123,10 @@ var StandaloneServer = class {
53842
54123
  this.httpServer.on("upgrade", (req, socket, head) => {
53843
54124
  const wsUrl = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
53844
54125
  if (wsUrl.pathname === "/ws") {
53845
- if (this.authToken) {
53846
- const urlToken = wsUrl.searchParams.get("token");
53847
- if (urlToken !== this.authToken) {
53848
- socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
53849
- socket.destroy();
53850
- return;
53851
- }
54126
+ if (!this.isRequestAuthenticated(req, req.url || "/")) {
54127
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
54128
+ socket.destroy();
54129
+ return;
53852
54130
  }
53853
54131
  this.wss.handleUpgrade(req, socket, head, (ws2) => {
53854
54132
  this.handleWsConnection(ws2);
@@ -53881,7 +54159,14 @@ var StandaloneServer = class {
53881
54159
  }
53882
54160
  }
53883
54161
  if (this.authToken) {
53884
- console.log(` \u{1F511} Token: ${this.authToken}`);
54162
+ console.log(" \u{1F511} Token auth: enabled");
54163
+ }
54164
+ if (this.passwordConfig) {
54165
+ console.log(" \u{1F510} Password auth: enabled");
54166
+ }
54167
+ if (shouldWarnForPublicUnauthenticatedHost({ host, hasTokenAuth: !!this.authToken, hasPasswordAuth: !!this.passwordConfig })) {
54168
+ console.warn(" \u26A0\uFE0F Public host mode is enabled without any auth.");
54169
+ console.warn(" Anyone on your LAN can open and control this dashboard until you set a password or token.");
53885
54170
  }
53886
54171
  console.log("");
53887
54172
  const cdpCount = [...this.components.cdpManagers.values()].filter((m) => m.isConnected).length;
@@ -53921,18 +54206,148 @@ var StandaloneServer = class {
53921
54206
  res.end();
53922
54207
  return;
53923
54208
  }
53924
- if (this.authToken && url2.startsWith("/api/")) {
53925
- const authHeader = req.headers["authorization"];
53926
- const bearerToken = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null;
53927
- const queryToken = new URL(url2, `http://${req.headers.host || "localhost"}`).searchParams.get("token");
53928
- if (bearerToken !== this.authToken && queryToken !== this.authToken) {
53929
- res.writeHead(401, { "Content-Type": "application/json" });
53930
- res.end(JSON.stringify({ error: "Unauthorized. Provide token via Authorization header or ?token= query." }));
53931
- return;
53932
- }
54209
+ const parsedUrl = new URL(url2, `http://${req.headers.host || "localhost"}`);
54210
+ if (parsedUrl.pathname === "/auth/session" && method === "GET") {
54211
+ res.writeHead(200, { "Content-Type": "application/json" });
54212
+ res.end(JSON.stringify(this.buildAuthStatus(req, url2)));
54213
+ return;
54214
+ }
54215
+ if (parsedUrl.pathname === "/auth/login" && method === "POST") {
54216
+ void (async () => {
54217
+ if (!this.passwordConfig) {
54218
+ res.writeHead(400, { "Content-Type": "application/json" });
54219
+ res.end(JSON.stringify({ error: "Password auth is not configured." }));
54220
+ return;
54221
+ }
54222
+ const body = await this.readJsonBody(req);
54223
+ if (!verifyPassword(typeof body.password === "string" ? body.password : "", this.passwordConfig)) {
54224
+ res.writeHead(401, { "Content-Type": "application/json" });
54225
+ res.end(JSON.stringify({ error: "Incorrect password." }));
54226
+ return;
54227
+ }
54228
+ this.authSessions.clear();
54229
+ const sessionId = this.authSessions.create();
54230
+ res.setHeader("Set-Cookie", buildSessionCookie(sessionId, this.getCookieSecureFlag(req)));
54231
+ res.writeHead(200, { "Content-Type": "application/json" });
54232
+ res.end(JSON.stringify({ ...this.buildAuthStatus(req, url2), authenticated: true }));
54233
+ })().catch((error48) => {
54234
+ res.writeHead(400, { "Content-Type": "application/json" });
54235
+ res.end(JSON.stringify({ error: error48?.message || String(error48) }));
54236
+ });
54237
+ return;
54238
+ }
54239
+ if (parsedUrl.pathname === "/auth/logout" && method === "POST") {
54240
+ this.authSessions.revoke(this.getRequestSessionId(req));
54241
+ res.setHeader("Set-Cookie", buildClearedSessionCookie(this.getCookieSecureFlag(req)));
54242
+ res.writeHead(200, { "Content-Type": "application/json" });
54243
+ res.end(JSON.stringify({ success: true }));
54244
+ return;
54245
+ }
54246
+ if (parsedUrl.pathname === "/auth/password" && method === "POST") {
54247
+ void (async () => {
54248
+ if (!this.hasAnyAuth() && !this.isTrustedStandaloneMutationRequest(req)) {
54249
+ res.writeHead(403, { "Content-Type": "application/json" });
54250
+ res.end(JSON.stringify({ error: "Cross-origin standalone settings changes are not allowed without existing auth." }));
54251
+ return;
54252
+ }
54253
+ if (this.hasAnyAuth() && !this.isRequestAuthenticated(req, url2)) {
54254
+ res.writeHead(401, { "Content-Type": "application/json" });
54255
+ res.end(JSON.stringify({ error: "Unauthorized" }));
54256
+ return;
54257
+ }
54258
+ const body = await this.readJsonBody(req);
54259
+ const currentPassword = typeof body.currentPassword === "string" ? body.currentPassword : "";
54260
+ const newPassword = typeof body.newPassword === "string" ? body.newPassword : "";
54261
+ const clearPassword = body.clear === true;
54262
+ if (this.passwordConfig && !verifyPassword(currentPassword, this.passwordConfig)) {
54263
+ res.writeHead(403, { "Content-Type": "application/json" });
54264
+ res.end(JSON.stringify({ error: "Current password is incorrect." }));
54265
+ return;
54266
+ }
54267
+ if (clearPassword) {
54268
+ clearStandalonePasswordConfig(this.passwordConfigPath);
54269
+ this.passwordConfig = null;
54270
+ this.authSessions.clear();
54271
+ res.setHeader("Set-Cookie", buildClearedSessionCookie(this.getCookieSecureFlag(req)));
54272
+ res.writeHead(200, { "Content-Type": "application/json" });
54273
+ res.end(JSON.stringify({ success: true, ...this.buildAuthStatus(req, url2), authenticated: !this.hasAnyAuth() }));
54274
+ return;
54275
+ }
54276
+ if (newPassword.trim().length < 4) {
54277
+ res.writeHead(400, { "Content-Type": "application/json" });
54278
+ res.end(JSON.stringify({ error: "Password must be at least 4 characters." }));
54279
+ return;
54280
+ }
54281
+ const nextConfig = createPasswordRecord(newPassword.trim());
54282
+ saveStandalonePasswordConfig(this.passwordConfigPath, nextConfig);
54283
+ this.passwordConfig = nextConfig;
54284
+ this.authSessions.clear();
54285
+ const sessionId = this.authSessions.create();
54286
+ res.setHeader("Set-Cookie", buildSessionCookie(sessionId, this.getCookieSecureFlag(req)));
54287
+ res.writeHead(200, { "Content-Type": "application/json" });
54288
+ res.end(JSON.stringify({ success: true, ...this.buildAuthStatus(req, url2), authenticated: true }));
54289
+ })().catch((error48) => {
54290
+ res.writeHead(400, { "Content-Type": "application/json" });
54291
+ res.end(JSON.stringify({ error: error48?.message || String(error48) }));
54292
+ });
54293
+ return;
54294
+ }
54295
+ if (parsedUrl.pathname === "/api/v1/standalone/preferences" && method === "GET") {
54296
+ const configuredBindHost = loadStandaloneBindHostPreference();
54297
+ res.writeHead(200, { "Content-Type": "application/json" });
54298
+ res.end(JSON.stringify({
54299
+ standaloneBindHost: configuredBindHost,
54300
+ currentBindHost: this.listenHost,
54301
+ hasPasswordAuth: !!this.passwordConfig,
54302
+ hasTokenAuth: !!this.authToken,
54303
+ publicHostWarning: shouldWarnForPublicUnauthenticatedHost({
54304
+ host: configuredBindHost,
54305
+ hasTokenAuth: !!this.authToken,
54306
+ hasPasswordAuth: !!this.passwordConfig
54307
+ })
54308
+ }));
54309
+ return;
54310
+ }
54311
+ if (parsedUrl.pathname === "/api/v1/standalone/preferences" && method === "POST") {
54312
+ void (async () => {
54313
+ if (!this.hasAnyAuth() && !this.isTrustedStandaloneMutationRequest(req)) {
54314
+ res.writeHead(403, { "Content-Type": "application/json" });
54315
+ res.end(JSON.stringify({ error: "Cross-origin standalone settings changes are not allowed without existing auth." }));
54316
+ return;
54317
+ }
54318
+ if (this.hasAnyAuth() && !this.isRequestAuthenticated(req, url2)) {
54319
+ res.writeHead(401, { "Content-Type": "application/json" });
54320
+ res.end(JSON.stringify({ error: "Unauthorized" }));
54321
+ return;
54322
+ }
54323
+ const body = await this.readJsonBody(req);
54324
+ const nextHost = body?.standaloneBindHost === "0.0.0.0" ? "0.0.0.0" : "127.0.0.1";
54325
+ const savedHost = saveStandaloneBindHostPreference(nextHost);
54326
+ res.writeHead(200, { "Content-Type": "application/json" });
54327
+ res.end(JSON.stringify({
54328
+ success: true,
54329
+ standaloneBindHost: savedHost,
54330
+ currentBindHost: this.listenHost,
54331
+ hasPasswordAuth: !!this.passwordConfig,
54332
+ hasTokenAuth: !!this.authToken,
54333
+ publicHostWarning: shouldWarnForPublicUnauthenticatedHost({
54334
+ host: savedHost,
54335
+ hasTokenAuth: !!this.authToken,
54336
+ hasPasswordAuth: !!this.passwordConfig
54337
+ })
54338
+ }));
54339
+ })().catch((error48) => {
54340
+ res.writeHead(400, { "Content-Type": "application/json" });
54341
+ res.end(JSON.stringify({ error: error48?.message || String(error48) }));
54342
+ });
54343
+ return;
54344
+ }
54345
+ if (url2.startsWith("/api/") && !this.isRequestAuthenticated(req, url2)) {
54346
+ res.writeHead(401, { "Content-Type": "application/json" });
54347
+ res.end(JSON.stringify({ error: "Unauthorized. Provide dashboard session cookie or token auth." }));
54348
+ return;
53933
54349
  }
53934
54350
  const apiPath = url2.startsWith("/api/v1/") ? url2.slice(7) : null;
53935
- const parsedUrl = new URL(url2, `http://${req.headers.host || "localhost"}`);
53936
54351
  if (apiPath === "/status" && method === "GET") {
53937
54352
  const status = this.getStatus(getSharedSnapshot());
53938
54353
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -54820,6 +55235,7 @@ async function main() {
54820
55235
  process.exit(exitCode);
54821
55236
  }
54822
55237
  const options = {};
55238
+ let hostExplicit = false;
54823
55239
  for (let i = 0; i < args.length; i++) {
54824
55240
  if ((args[i] === "--port" || args[i] === "-p") && args[i + 1]) {
54825
55241
  options.port = parseInt(args[i + 1]);
@@ -54827,6 +55243,7 @@ async function main() {
54827
55243
  }
54828
55244
  if (args[i] === "--host" || args[i] === "-H") {
54829
55245
  options.host = "0.0.0.0";
55246
+ hostExplicit = true;
54830
55247
  }
54831
55248
  if (args[i] === "--public" && args[i + 1]) {
54832
55249
  options.publicDir = args[i + 1];
@@ -54868,6 +55285,9 @@ Runtime commands:
54868
55285
  process.exit(0);
54869
55286
  }
54870
55287
  }
55288
+ if (!hostExplicit) {
55289
+ options.host = loadStandaloneBindHostPreference();
55290
+ }
54871
55291
  if (!options.publicDir) {
54872
55292
  const candidates = [
54873
55293
  path4.join(__dirname, "../../web-standalone/dist"),
@@ -54883,6 +55303,9 @@ Runtime commands:
54883
55303
  }
54884
55304
  const server = new StandaloneServer();
54885
55305
  await server.start(options);
55306
+ if (!hostExplicit) {
55307
+ saveStandaloneBindHostPreference(options.host === "0.0.0.0" ? "0.0.0.0" : "127.0.0.1");
55308
+ }
54886
55309
  await new Promise(() => {
54887
55310
  });
54888
55311
  }