@adhdev/daemon-standalone 0.8.76 → 0.8.77

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,
@@ -44167,59 +44251,6 @@ Run 'adhdev doctor' for detailed diagnostics.`
44167
44251
  }
44168
44252
  cleanOldFiles();
44169
44253
  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
44254
  var os16 = __toESM2(require("os"));
44224
44255
  init_config();
44225
44256
  init_terminal_screen();
@@ -52547,6 +52578,8 @@ data: ${JSON.stringify(msg.data)}
52547
52578
  runtimeKey: record2.runtimeKey,
52548
52579
  displayName: record2.displayName,
52549
52580
  workspaceLabel: record2.workspaceLabel,
52581
+ lifecycle: typeof record2.lifecycle === "string" ? record2.lifecycle : null,
52582
+ surfaceKind: record2.surfaceKind,
52550
52583
  writeOwner: record2.writeOwner ? {
52551
52584
  clientId: record2.writeOwner.clientId,
52552
52585
  ownerType: record2.writeOwner.ownerType
@@ -53149,6 +53182,7 @@ var import_ws = require("ws");
53149
53182
  var path4 = __toESM(require("path"));
53150
53183
  var fs3 = __toESM(require("fs"));
53151
53184
  var os5 = __toESM(require("os"));
53185
+ var import_crypto2 = require("crypto");
53152
53186
  var import_daemon_core2 = __toESM(require_dist2());
53153
53187
 
53154
53188
  // src/session-host.ts
@@ -53654,6 +53688,165 @@ async function getWorkspaceSocketInfo(workspaceName) {
53654
53688
  var import_daemon_core3 = __toESM(require_dist2());
53655
53689
  var DEFAULT_PORT = 3847;
53656
53690
  var STATUS_INTERVAL = 2e3;
53691
+ var STANDALONE_AUTH_SESSION_COOKIE = "adhdev_standalone_session";
53692
+ var STANDALONE_PASSWORD_CONFIG_FILE = "standalone-auth.json";
53693
+ var STANDALONE_BIND_HOST_CONFIG_FILE = "standalone-network.json";
53694
+ var STANDALONE_BIND_HOST_DEFAULT = "127.0.0.1";
53695
+ var PASSWORD_KEYLEN = 64;
53696
+ var DEFAULT_SESSION_TTL_MS = 1e3 * 60 * 60 * 24 * 30;
53697
+ function getStandalonePasswordConfigPath() {
53698
+ const dir = path4.join(os5.homedir(), ".adhdev");
53699
+ if (!fs3.existsSync(dir)) {
53700
+ fs3.mkdirSync(dir, { recursive: true, mode: 448 });
53701
+ }
53702
+ return path4.join(dir, STANDALONE_PASSWORD_CONFIG_FILE);
53703
+ }
53704
+ function getStandaloneConfigJsonPath() {
53705
+ const dir = path4.join(os5.homedir(), ".adhdev");
53706
+ if (!fs3.existsSync(dir)) {
53707
+ fs3.mkdirSync(dir, { recursive: true, mode: 448 });
53708
+ }
53709
+ return path4.join(dir, STANDALONE_BIND_HOST_CONFIG_FILE);
53710
+ }
53711
+ function loadStandaloneBindHostPreference() {
53712
+ try {
53713
+ const configPath = getStandaloneConfigJsonPath();
53714
+ if (!fs3.existsSync(configPath)) return STANDALONE_BIND_HOST_DEFAULT;
53715
+ const parsed = JSON.parse(fs3.readFileSync(configPath, "utf8"));
53716
+ return parsed?.standaloneBindHost === "0.0.0.0" ? "0.0.0.0" : STANDALONE_BIND_HOST_DEFAULT;
53717
+ } catch {
53718
+ return STANDALONE_BIND_HOST_DEFAULT;
53719
+ }
53720
+ }
53721
+ function saveStandaloneBindHostPreference(bindHost) {
53722
+ const configPath = getStandaloneConfigJsonPath();
53723
+ let parsed = {};
53724
+ try {
53725
+ if (fs3.existsSync(configPath)) {
53726
+ const raw = fs3.readFileSync(configPath, "utf8");
53727
+ const next = JSON.parse(raw);
53728
+ if (next && typeof next === "object" && !Array.isArray(next)) parsed = next;
53729
+ }
53730
+ } catch {
53731
+ parsed = {};
53732
+ }
53733
+ parsed.standaloneBindHost = bindHost;
53734
+ fs3.writeFileSync(configPath, JSON.stringify(parsed, null, 2), { encoding: "utf8", mode: 384 });
53735
+ try {
53736
+ fs3.chmodSync(configPath, 384);
53737
+ } catch {
53738
+ }
53739
+ return bindHost;
53740
+ }
53741
+ function createPasswordRecord(password, salt = (0, import_crypto2.randomBytes)(16).toString("hex")) {
53742
+ return {
53743
+ passwordHash: (0, import_crypto2.scryptSync)(`${password || ""}`, salt, PASSWORD_KEYLEN).toString("hex"),
53744
+ passwordSalt: salt,
53745
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
53746
+ };
53747
+ }
53748
+ function verifyPassword(password, config2) {
53749
+ if (!config2?.passwordHash || !config2.passwordSalt) return false;
53750
+ const actual = Buffer.from((0, import_crypto2.scryptSync)(`${password || ""}`, config2.passwordSalt, PASSWORD_KEYLEN).toString("hex"), "utf8");
53751
+ const expected = Buffer.from(config2.passwordHash, "utf8");
53752
+ return actual.length === expected.length && (0, import_crypto2.timingSafeEqual)(actual, expected);
53753
+ }
53754
+ function loadStandalonePasswordConfig(filePath = getStandalonePasswordConfigPath()) {
53755
+ if (!fs3.existsSync(filePath)) return null;
53756
+ try {
53757
+ const parsed = JSON.parse(fs3.readFileSync(filePath, "utf8"));
53758
+ if (!parsed || typeof parsed !== "object") return null;
53759
+ if (typeof parsed.passwordHash !== "string" || typeof parsed.passwordSalt !== "string") return null;
53760
+ return {
53761
+ passwordHash: parsed.passwordHash,
53762
+ passwordSalt: parsed.passwordSalt,
53763
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : (/* @__PURE__ */ new Date(0)).toISOString()
53764
+ };
53765
+ } catch {
53766
+ return null;
53767
+ }
53768
+ }
53769
+ function saveStandalonePasswordConfig(filePath, config2) {
53770
+ const dir = path4.dirname(filePath);
53771
+ if (!fs3.existsSync(dir)) {
53772
+ fs3.mkdirSync(dir, { recursive: true, mode: 448 });
53773
+ }
53774
+ fs3.writeFileSync(filePath, JSON.stringify(config2, null, 2), { encoding: "utf8", mode: 384 });
53775
+ try {
53776
+ fs3.chmodSync(filePath, 384);
53777
+ } catch {
53778
+ }
53779
+ }
53780
+ function clearStandalonePasswordConfig(filePath = getStandalonePasswordConfigPath()) {
53781
+ if (fs3.existsSync(filePath)) {
53782
+ fs3.rmSync(filePath, { force: true });
53783
+ }
53784
+ }
53785
+ function shouldWarnForPublicUnauthenticatedHost(input) {
53786
+ return input.host === "0.0.0.0" && !input.hasTokenAuth && !input.hasPasswordAuth;
53787
+ }
53788
+ function parseCookies(cookieHeader) {
53789
+ if (!cookieHeader) return {};
53790
+ return Object.fromEntries(
53791
+ cookieHeader.split(";").map((part) => part.trim()).filter(Boolean).map((part) => {
53792
+ const eq = part.indexOf("=");
53793
+ if (eq === -1) return [part, ""];
53794
+ return [part.slice(0, eq), decodeURIComponent(part.slice(eq + 1))];
53795
+ })
53796
+ );
53797
+ }
53798
+ function buildSessionCookie(sessionId, secure, maxAgeMs = DEFAULT_SESSION_TTL_MS) {
53799
+ const parts = [
53800
+ `${STANDALONE_AUTH_SESSION_COOKIE}=${encodeURIComponent(sessionId)}`,
53801
+ "Path=/",
53802
+ "HttpOnly",
53803
+ "SameSite=Lax",
53804
+ `Max-Age=${Math.max(0, Math.floor(maxAgeMs / 1e3))}`
53805
+ ];
53806
+ if (secure) parts.push("Secure");
53807
+ return parts.join("; ");
53808
+ }
53809
+ function buildClearedSessionCookie(secure) {
53810
+ return buildSessionCookie("", secure, 0);
53811
+ }
53812
+ var StandaloneSessionStore = class {
53813
+ sessions = /* @__PURE__ */ new Map();
53814
+ create(ttlMs = DEFAULT_SESSION_TTL_MS) {
53815
+ const id = (0, import_crypto2.randomBytes)(24).toString("hex");
53816
+ this.sessions.set(id, Date.now() + ttlMs);
53817
+ return id;
53818
+ }
53819
+ has(sessionId) {
53820
+ if (!sessionId) return false;
53821
+ const expiresAt = this.sessions.get(sessionId);
53822
+ if (!expiresAt) return false;
53823
+ if (expiresAt <= Date.now()) {
53824
+ this.sessions.delete(sessionId);
53825
+ return false;
53826
+ }
53827
+ return true;
53828
+ }
53829
+ revoke(sessionId) {
53830
+ if (!sessionId) return;
53831
+ this.sessions.delete(sessionId);
53832
+ }
53833
+ clear() {
53834
+ this.sessions.clear();
53835
+ }
53836
+ };
53837
+ function isStandaloneRequestAuthenticated(input) {
53838
+ const hasTokenAuth = !!input.configuredToken;
53839
+ const hasPasswordAuth = !!input.passwordConfig;
53840
+ if (!hasTokenAuth && !hasPasswordAuth) return true;
53841
+ if (hasTokenAuth && (input.bearerToken === input.configuredToken || input.queryToken === input.configuredToken)) {
53842
+ return true;
53843
+ }
53844
+ if (hasPasswordAuth) {
53845
+ const cookies = parseCookies(input.cookieHeader);
53846
+ return input.sessionStore.has(cookies[STANDALONE_AUTH_SESSION_COOKIE]);
53847
+ }
53848
+ return false;
53849
+ }
53657
53850
  var pkgVersion = process.env.ADHDEV_PKG_VERSION || "unknown";
53658
53851
  if (pkgVersion === "unknown") {
53659
53852
  try {
@@ -53702,6 +53895,10 @@ var StandaloneServer = class {
53702
53895
  wsSessionModalSubscriptions = /* @__PURE__ */ new Map();
53703
53896
  wsDaemonMetadataSubscriptions = /* @__PURE__ */ new Map();
53704
53897
  authToken = null;
53898
+ passwordConfigPath = getStandalonePasswordConfigPath();
53899
+ passwordConfig = null;
53900
+ authSessions = new StandaloneSessionStore();
53901
+ listenHost = "127.0.0.1";
53705
53902
  statusTimer = null;
53706
53903
  lastStatusBroadcastAt = 0;
53707
53904
  statusBroadcastPending = false;
@@ -53758,10 +53955,88 @@ var StandaloneServer = class {
53758
53955
  const mode = this.getCliPresentationMode(sessionId);
53759
53956
  return mode === "chat" || mode === "terminal";
53760
53957
  }
53958
+ hasPasswordAuth() {
53959
+ return !!this.passwordConfig;
53960
+ }
53961
+ hasAnyAuth() {
53962
+ return !!this.authToken || this.hasPasswordAuth();
53963
+ }
53964
+ getCookieSecureFlag(req) {
53965
+ const forwardedProto = req.headers["x-forwarded-proto"];
53966
+ return !!req.socket.encrypted || typeof forwardedProto === "string" && forwardedProto.toLowerCase().includes("https");
53967
+ }
53968
+ getRequestTokens(req, rawUrl) {
53969
+ const authHeader = req.headers["authorization"];
53970
+ const bearerToken = typeof authHeader === "string" && authHeader.startsWith("Bearer ") ? authHeader.slice(7) : null;
53971
+ const queryToken = new URL(rawUrl, `http://${req.headers.host || "localhost"}`).searchParams.get("token");
53972
+ return { bearerToken, queryToken };
53973
+ }
53974
+ isRequestAuthenticated(req, rawUrl) {
53975
+ const { bearerToken, queryToken } = this.getRequestTokens(req, rawUrl);
53976
+ return isStandaloneRequestAuthenticated({
53977
+ configuredToken: this.authToken,
53978
+ passwordConfig: this.passwordConfig,
53979
+ bearerToken,
53980
+ queryToken,
53981
+ cookieHeader: typeof req.headers.cookie === "string" ? req.headers.cookie : void 0,
53982
+ sessionStore: this.authSessions
53983
+ });
53984
+ }
53985
+ getRequestSessionId(req) {
53986
+ const cookies = parseCookies(typeof req.headers.cookie === "string" ? req.headers.cookie : void 0);
53987
+ return cookies.adhdev_standalone_session || null;
53988
+ }
53989
+ buildAuthStatus(req, rawUrl) {
53990
+ const required2 = this.hasAnyAuth();
53991
+ return {
53992
+ required: required2,
53993
+ authenticated: this.isRequestAuthenticated(req, rawUrl),
53994
+ hasTokenAuth: !!this.authToken,
53995
+ hasPasswordAuth: this.hasPasswordAuth(),
53996
+ publicHostWarning: shouldWarnForPublicUnauthenticatedHost({
53997
+ host: this.listenHost,
53998
+ hasTokenAuth: !!this.authToken,
53999
+ hasPasswordAuth: this.hasPasswordAuth()
54000
+ }),
54001
+ boundHost: this.listenHost
54002
+ };
54003
+ }
54004
+ isTrustedStandaloneMutationRequest(req) {
54005
+ const originHeader = req.headers.origin;
54006
+ if (typeof originHeader !== "string" || !originHeader.trim()) return true;
54007
+ try {
54008
+ const origin = new URL(originHeader);
54009
+ const host = req.headers.host || "";
54010
+ return origin.host === host;
54011
+ } catch {
54012
+ return false;
54013
+ }
54014
+ }
54015
+ async readJsonBody(req) {
54016
+ return await new Promise((resolve4, reject) => {
54017
+ let body = "";
54018
+ req.on("data", (chunk) => {
54019
+ body += chunk;
54020
+ });
54021
+ req.on("end", () => {
54022
+ try {
54023
+ resolve4(body ? JSON.parse(body) : {});
54024
+ } catch (error48) {
54025
+ reject(error48);
54026
+ }
54027
+ });
54028
+ req.on("error", reject);
54029
+ });
54030
+ }
53761
54031
  async start(options = {}) {
53762
- const port = options.port || DEFAULT_PORT;
53763
- const host = options.host || "127.0.0.1";
54032
+ const persistedStandaloneBindHost = loadStandaloneBindHostPreference();
53764
54033
  const cfg = (0, import_daemon_core2.loadConfig)();
54034
+ if (!options.host && persistedStandaloneBindHost !== STANDALONE_BIND_HOST_DEFAULT) {
54035
+ saveStandaloneBindHostPreference(persistedStandaloneBindHost);
54036
+ }
54037
+ const port = options.port || DEFAULT_PORT;
54038
+ const host = options.host || persistedStandaloneBindHost;
54039
+ this.listenHost = host;
53765
54040
  const sessionHostEndpoint = await ensureSessionHostReady();
53766
54041
  this.sessionHostEndpoint = sessionHostEndpoint;
53767
54042
  const sessionHostControl = new StandaloneSessionHostControlPlane(
@@ -53769,6 +54044,7 @@ var StandaloneServer = class {
53769
54044
  );
53770
54045
  this.sessionHostControl = sessionHostControl;
53771
54046
  this.authToken = options.token || process.env.ADHDEV_TOKEN || null;
54047
+ this.passwordConfig = loadStandalonePasswordConfig(this.passwordConfigPath);
53772
54048
  const statusInstanceId = `standalone_${cfg.machineId || "mach_unknown"}`;
53773
54049
  this.components = await (0, import_daemon_core2.initDaemonComponents)({
53774
54050
  cliManagerDeps: {
@@ -53842,13 +54118,10 @@ var StandaloneServer = class {
53842
54118
  this.httpServer.on("upgrade", (req, socket, head) => {
53843
54119
  const wsUrl = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
53844
54120
  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
- }
54121
+ if (!this.isRequestAuthenticated(req, req.url || "/")) {
54122
+ socket.write("HTTP/1.1 401 Unauthorized\r\n\r\n");
54123
+ socket.destroy();
54124
+ return;
53852
54125
  }
53853
54126
  this.wss.handleUpgrade(req, socket, head, (ws2) => {
53854
54127
  this.handleWsConnection(ws2);
@@ -53881,7 +54154,14 @@ var StandaloneServer = class {
53881
54154
  }
53882
54155
  }
53883
54156
  if (this.authToken) {
53884
- console.log(` \u{1F511} Token: ${this.authToken}`);
54157
+ console.log(" \u{1F511} Token auth: enabled");
54158
+ }
54159
+ if (this.passwordConfig) {
54160
+ console.log(" \u{1F510} Password auth: enabled");
54161
+ }
54162
+ if (shouldWarnForPublicUnauthenticatedHost({ host, hasTokenAuth: !!this.authToken, hasPasswordAuth: !!this.passwordConfig })) {
54163
+ console.warn(" \u26A0\uFE0F Public host mode is enabled without any auth.");
54164
+ console.warn(" Anyone on your LAN can open and control this dashboard until you set a password or token.");
53885
54165
  }
53886
54166
  console.log("");
53887
54167
  const cdpCount = [...this.components.cdpManagers.values()].filter((m) => m.isConnected).length;
@@ -53921,18 +54201,148 @@ var StandaloneServer = class {
53921
54201
  res.end();
53922
54202
  return;
53923
54203
  }
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
- }
54204
+ const parsedUrl = new URL(url2, `http://${req.headers.host || "localhost"}`);
54205
+ if (parsedUrl.pathname === "/auth/session" && method === "GET") {
54206
+ res.writeHead(200, { "Content-Type": "application/json" });
54207
+ res.end(JSON.stringify(this.buildAuthStatus(req, url2)));
54208
+ return;
54209
+ }
54210
+ if (parsedUrl.pathname === "/auth/login" && method === "POST") {
54211
+ void (async () => {
54212
+ if (!this.passwordConfig) {
54213
+ res.writeHead(400, { "Content-Type": "application/json" });
54214
+ res.end(JSON.stringify({ error: "Password auth is not configured." }));
54215
+ return;
54216
+ }
54217
+ const body = await this.readJsonBody(req);
54218
+ if (!verifyPassword(typeof body.password === "string" ? body.password : "", this.passwordConfig)) {
54219
+ res.writeHead(401, { "Content-Type": "application/json" });
54220
+ res.end(JSON.stringify({ error: "Incorrect password." }));
54221
+ return;
54222
+ }
54223
+ this.authSessions.clear();
54224
+ const sessionId = this.authSessions.create();
54225
+ res.setHeader("Set-Cookie", buildSessionCookie(sessionId, this.getCookieSecureFlag(req)));
54226
+ res.writeHead(200, { "Content-Type": "application/json" });
54227
+ res.end(JSON.stringify({ ...this.buildAuthStatus(req, url2), authenticated: true }));
54228
+ })().catch((error48) => {
54229
+ res.writeHead(400, { "Content-Type": "application/json" });
54230
+ res.end(JSON.stringify({ error: error48?.message || String(error48) }));
54231
+ });
54232
+ return;
54233
+ }
54234
+ if (parsedUrl.pathname === "/auth/logout" && method === "POST") {
54235
+ this.authSessions.revoke(this.getRequestSessionId(req));
54236
+ res.setHeader("Set-Cookie", buildClearedSessionCookie(this.getCookieSecureFlag(req)));
54237
+ res.writeHead(200, { "Content-Type": "application/json" });
54238
+ res.end(JSON.stringify({ success: true }));
54239
+ return;
54240
+ }
54241
+ if (parsedUrl.pathname === "/auth/password" && method === "POST") {
54242
+ void (async () => {
54243
+ if (!this.hasAnyAuth() && !this.isTrustedStandaloneMutationRequest(req)) {
54244
+ res.writeHead(403, { "Content-Type": "application/json" });
54245
+ res.end(JSON.stringify({ error: "Cross-origin standalone settings changes are not allowed without existing auth." }));
54246
+ return;
54247
+ }
54248
+ if (this.hasAnyAuth() && !this.isRequestAuthenticated(req, url2)) {
54249
+ res.writeHead(401, { "Content-Type": "application/json" });
54250
+ res.end(JSON.stringify({ error: "Unauthorized" }));
54251
+ return;
54252
+ }
54253
+ const body = await this.readJsonBody(req);
54254
+ const currentPassword = typeof body.currentPassword === "string" ? body.currentPassword : "";
54255
+ const newPassword = typeof body.newPassword === "string" ? body.newPassword : "";
54256
+ const clearPassword = body.clear === true;
54257
+ if (this.passwordConfig && !verifyPassword(currentPassword, this.passwordConfig)) {
54258
+ res.writeHead(403, { "Content-Type": "application/json" });
54259
+ res.end(JSON.stringify({ error: "Current password is incorrect." }));
54260
+ return;
54261
+ }
54262
+ if (clearPassword) {
54263
+ clearStandalonePasswordConfig(this.passwordConfigPath);
54264
+ this.passwordConfig = null;
54265
+ this.authSessions.clear();
54266
+ res.setHeader("Set-Cookie", buildClearedSessionCookie(this.getCookieSecureFlag(req)));
54267
+ res.writeHead(200, { "Content-Type": "application/json" });
54268
+ res.end(JSON.stringify({ success: true, ...this.buildAuthStatus(req, url2), authenticated: !this.hasAnyAuth() }));
54269
+ return;
54270
+ }
54271
+ if (newPassword.trim().length < 4) {
54272
+ res.writeHead(400, { "Content-Type": "application/json" });
54273
+ res.end(JSON.stringify({ error: "Password must be at least 4 characters." }));
54274
+ return;
54275
+ }
54276
+ const nextConfig = createPasswordRecord(newPassword.trim());
54277
+ saveStandalonePasswordConfig(this.passwordConfigPath, nextConfig);
54278
+ this.passwordConfig = nextConfig;
54279
+ this.authSessions.clear();
54280
+ const sessionId = this.authSessions.create();
54281
+ res.setHeader("Set-Cookie", buildSessionCookie(sessionId, this.getCookieSecureFlag(req)));
54282
+ res.writeHead(200, { "Content-Type": "application/json" });
54283
+ res.end(JSON.stringify({ success: true, ...this.buildAuthStatus(req, url2), authenticated: true }));
54284
+ })().catch((error48) => {
54285
+ res.writeHead(400, { "Content-Type": "application/json" });
54286
+ res.end(JSON.stringify({ error: error48?.message || String(error48) }));
54287
+ });
54288
+ return;
54289
+ }
54290
+ if (parsedUrl.pathname === "/api/v1/standalone/preferences" && method === "GET") {
54291
+ const configuredBindHost = loadStandaloneBindHostPreference();
54292
+ res.writeHead(200, { "Content-Type": "application/json" });
54293
+ res.end(JSON.stringify({
54294
+ standaloneBindHost: configuredBindHost,
54295
+ currentBindHost: this.listenHost,
54296
+ hasPasswordAuth: !!this.passwordConfig,
54297
+ hasTokenAuth: !!this.authToken,
54298
+ publicHostWarning: shouldWarnForPublicUnauthenticatedHost({
54299
+ host: configuredBindHost,
54300
+ hasTokenAuth: !!this.authToken,
54301
+ hasPasswordAuth: !!this.passwordConfig
54302
+ })
54303
+ }));
54304
+ return;
54305
+ }
54306
+ if (parsedUrl.pathname === "/api/v1/standalone/preferences" && method === "POST") {
54307
+ void (async () => {
54308
+ if (!this.hasAnyAuth() && !this.isTrustedStandaloneMutationRequest(req)) {
54309
+ res.writeHead(403, { "Content-Type": "application/json" });
54310
+ res.end(JSON.stringify({ error: "Cross-origin standalone settings changes are not allowed without existing auth." }));
54311
+ return;
54312
+ }
54313
+ if (this.hasAnyAuth() && !this.isRequestAuthenticated(req, url2)) {
54314
+ res.writeHead(401, { "Content-Type": "application/json" });
54315
+ res.end(JSON.stringify({ error: "Unauthorized" }));
54316
+ return;
54317
+ }
54318
+ const body = await this.readJsonBody(req);
54319
+ const nextHost = body?.standaloneBindHost === "0.0.0.0" ? "0.0.0.0" : "127.0.0.1";
54320
+ const savedHost = saveStandaloneBindHostPreference(nextHost);
54321
+ res.writeHead(200, { "Content-Type": "application/json" });
54322
+ res.end(JSON.stringify({
54323
+ success: true,
54324
+ standaloneBindHost: savedHost,
54325
+ currentBindHost: this.listenHost,
54326
+ hasPasswordAuth: !!this.passwordConfig,
54327
+ hasTokenAuth: !!this.authToken,
54328
+ publicHostWarning: shouldWarnForPublicUnauthenticatedHost({
54329
+ host: savedHost,
54330
+ hasTokenAuth: !!this.authToken,
54331
+ hasPasswordAuth: !!this.passwordConfig
54332
+ })
54333
+ }));
54334
+ })().catch((error48) => {
54335
+ res.writeHead(400, { "Content-Type": "application/json" });
54336
+ res.end(JSON.stringify({ error: error48?.message || String(error48) }));
54337
+ });
54338
+ return;
54339
+ }
54340
+ if (url2.startsWith("/api/") && !this.isRequestAuthenticated(req, url2)) {
54341
+ res.writeHead(401, { "Content-Type": "application/json" });
54342
+ res.end(JSON.stringify({ error: "Unauthorized. Provide dashboard session cookie or token auth." }));
54343
+ return;
53933
54344
  }
53934
54345
  const apiPath = url2.startsWith("/api/v1/") ? url2.slice(7) : null;
53935
- const parsedUrl = new URL(url2, `http://${req.headers.host || "localhost"}`);
53936
54346
  if (apiPath === "/status" && method === "GET") {
53937
54347
  const status = this.getStatus(getSharedSnapshot());
53938
54348
  res.writeHead(200, { "Content-Type": "application/json" });
@@ -54820,6 +55230,7 @@ async function main() {
54820
55230
  process.exit(exitCode);
54821
55231
  }
54822
55232
  const options = {};
55233
+ let hostExplicit = false;
54823
55234
  for (let i = 0; i < args.length; i++) {
54824
55235
  if ((args[i] === "--port" || args[i] === "-p") && args[i + 1]) {
54825
55236
  options.port = parseInt(args[i + 1]);
@@ -54827,6 +55238,7 @@ async function main() {
54827
55238
  }
54828
55239
  if (args[i] === "--host" || args[i] === "-H") {
54829
55240
  options.host = "0.0.0.0";
55241
+ hostExplicit = true;
54830
55242
  }
54831
55243
  if (args[i] === "--public" && args[i + 1]) {
54832
55244
  options.publicDir = args[i + 1];
@@ -54868,6 +55280,9 @@ Runtime commands:
54868
55280
  process.exit(0);
54869
55281
  }
54870
55282
  }
55283
+ if (!hostExplicit) {
55284
+ options.host = loadStandaloneBindHostPreference();
55285
+ }
54871
55286
  if (!options.publicDir) {
54872
55287
  const candidates = [
54873
55288
  path4.join(__dirname, "../../web-standalone/dist"),
@@ -54883,6 +55298,9 @@ Runtime commands:
54883
55298
  }
54884
55299
  const server = new StandaloneServer();
54885
55300
  await server.start(options);
55301
+ if (!hostExplicit) {
55302
+ saveStandaloneBindHostPreference(options.host === "0.0.0.0" ? "0.0.0.0" : "127.0.0.1");
55303
+ }
54886
55304
  await new Promise(() => {
54887
55305
  });
54888
55306
  }