overmux 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +2 -2
  3. package/dist/auth/.vite/manifest.json +2 -2
  4. package/dist/auth/assets/{index-Bx8JhwVi.css → index-BM9gETvx.css} +1 -1
  5. package/dist/auth/assets/{index-CHIE7qYj.js → index-DabAfcs1.js} +1 -1
  6. package/dist/auth/index.html +2 -2
  7. package/dist/bin.js +219 -126
  8. package/dist/bin.js.map +1 -1
  9. package/dist/docs/000-index.md +2 -4
  10. package/dist/docs/100-introduction/200-how-overmux-works.md +126 -2
  11. package/{docs/100-introduction/300-why-overmux.md → dist/docs/100-introduction/300-why-i-built-overmux.md} +1 -1
  12. package/dist/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
  13. package/dist/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
  14. package/dist/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
  15. package/dist/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
  16. package/dist/docs/400-reference/100-project-structure.md +83 -0
  17. package/dist/docs/400-reference/200-configuration.md +257 -0
  18. package/dist/docs/400-reference/300-storage-locations.md +46 -0
  19. package/dist/docs/400-reference/400-authentication-and-security.md +37 -0
  20. package/dist/docs/400-reference/500-server/000-index.md +13 -0
  21. package/dist/docs/400-reference/500-server/100-resources.md +191 -0
  22. package/dist/docs/400-reference/500-server/200-operations.md +111 -0
  23. package/dist/docs/400-reference/500-server/300-streams.md +140 -0
  24. package/dist/docs/400-reference/500-server/400-notifications.md +32 -0
  25. package/dist/docs/400-reference/500-server/500-api.md +118 -0
  26. package/dist/docs/400-reference/600-client/000-index.md +9 -0
  27. package/dist/docs/400-reference/600-client/100-api.md +370 -0
  28. package/dist/docs/400-reference/600-client/200-theming.md +94 -0
  29. package/dist/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
  30. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/050-init.md +2 -4
  31. package/dist/docs/400-reference/{400-cli → 700-cli}/100-serve.md +9 -4
  32. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/200-auth.md +1 -4
  33. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/300-call.md +1 -4
  34. package/dist/docs/400-reference/700-cli/350-instance.md +21 -0
  35. package/dist/docs/400-reference/700-cli/600-docs.md +128 -0
  36. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/700-desktop.md +1 -3
  37. package/dist/docs/500-hosted-pages.md +0 -1
  38. package/dist/exports/client.d.ts +19 -15
  39. package/dist/exports/client.d.ts.map +1 -1
  40. package/dist/exports/client.js +240 -58
  41. package/dist/exports/client.js.map +1 -1
  42. package/dist/exports/{index-D3_Dpy_T.d.ts → index-Cz3xkCa4.d.ts} +62 -36
  43. package/dist/exports/index-Cz3xkCa4.d.ts.map +1 -0
  44. package/dist/exports/index.d.ts +2 -2
  45. package/dist/exports/index.js +19 -5
  46. package/dist/exports/index.js.map +1 -1
  47. package/dist/exports/{notifications-kDp16bU_.js → notifications-BFAD3QQl.js} +34 -4
  48. package/dist/exports/notifications-BFAD3QQl.js.map +1 -0
  49. package/dist/exports/server.d.ts +1 -44
  50. package/dist/exports/server.d.ts.map +1 -1
  51. package/dist/exports/server.js +8 -1018
  52. package/dist/exports/server.js.map +1 -1
  53. package/dist/internal/server/coordinator/server-child.js +153 -85
  54. package/dist/internal/server/coordinator/server-child.js.map +1 -1
  55. package/docs/000-index.md +2 -4
  56. package/docs/100-introduction/200-how-overmux-works.md +126 -2
  57. package/{dist/docs/100-introduction/300-why-overmux.md → docs/100-introduction/300-why-i-built-overmux.md} +1 -1
  58. package/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
  59. package/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
  60. package/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
  61. package/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
  62. package/docs/400-reference/100-project-structure.md +83 -0
  63. package/docs/400-reference/200-configuration.md +257 -0
  64. package/docs/400-reference/300-storage-locations.md +46 -0
  65. package/docs/400-reference/400-authentication-and-security.md +37 -0
  66. package/docs/400-reference/500-server/000-index.md +13 -0
  67. package/docs/400-reference/500-server/100-resources.md +191 -0
  68. package/docs/400-reference/500-server/200-operations.md +111 -0
  69. package/docs/400-reference/500-server/300-streams.md +140 -0
  70. package/docs/400-reference/500-server/400-notifications.md +32 -0
  71. package/docs/400-reference/500-server/500-api.md +118 -0
  72. package/docs/400-reference/600-client/000-index.md +9 -0
  73. package/docs/400-reference/600-client/100-api.md +370 -0
  74. package/docs/400-reference/600-client/200-theming.md +94 -0
  75. package/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
  76. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/050-init.md +2 -4
  77. package/docs/400-reference/{400-cli → 700-cli}/100-serve.md +9 -4
  78. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/200-auth.md +1 -4
  79. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/300-call.md +1 -4
  80. package/docs/400-reference/700-cli/350-instance.md +21 -0
  81. package/docs/400-reference/700-cli/600-docs.md +128 -0
  82. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/700-desktop.md +1 -3
  83. package/docs/500-hosted-pages.md +0 -1
  84. package/package.json +3 -2
  85. package/src/internal/cli/app.ts +5 -5
  86. package/src/internal/cli/commands/docs-ai-context.ts +33 -0
  87. package/src/internal/cli/commands/docs.ts +19 -2
  88. package/src/internal/cli/commands/init-template.ts +1 -1
  89. package/src/internal/cli/commands/instance.ts +58 -0
  90. package/src/internal/cli/commands/serve.ts +3 -0
  91. package/src/internal/cli/login.ts +9 -9
  92. package/src/internal/cli/parse-port.ts +2 -1
  93. package/src/internal/client/auth/auth-shell.css +17 -1
  94. package/src/internal/client/auth/auth-shell.tsx +2 -11
  95. package/src/internal/client/auth/login-code-input.tsx +140 -0
  96. package/src/internal/client/browser-api.ts +17 -3
  97. package/src/internal/client/client-definition.ts +6 -8
  98. package/src/internal/client/host/deep-link-navigation.ts +75 -0
  99. package/src/internal/client/host/desktop-host.ts +3 -0
  100. package/src/internal/client/host/environment.ts +67 -0
  101. package/src/internal/client/host/instance-forwarding.ts +21 -0
  102. package/src/internal/client/host/overmux-host.tsx +17 -0
  103. package/src/internal/client/index.ts +0 -6
  104. package/src/internal/client/overmux-react.ts +95 -42
  105. package/src/internal/client/websocket.ts +32 -12
  106. package/src/internal/server/auth/auth-service.ts +2 -2
  107. package/src/internal/server/auth/instance-control.ts +89 -17
  108. package/src/internal/server/coordinator/ipc-protocol.ts +0 -1
  109. package/src/internal/server/runtime/create-runtime.ts +15 -2
  110. package/src/internal/server/runtime/runtime-instance.ts +47 -0
  111. package/src/internal/server/runtime/runtime-operations.ts +9 -3
  112. package/src/internal/server/runtime/runtime-resources.ts +15 -32
  113. package/src/internal/server/runtime/runtime-streams.ts +5 -6
  114. package/src/internal/server/server-logger.ts +5 -10
  115. package/src/internal/server/server-startup-options.ts +14 -14
  116. package/src/internal/server/start-application-server.ts +24 -19
  117. package/src/internal/server/websocket-server.ts +7 -0
  118. package/src/internal/shared/index.ts +2 -0
  119. package/src/internal/shared/protocol.ts +10 -1
  120. package/src/public/ai-context.ts +7 -29
  121. package/src/public/client.ts +1 -6
  122. package/src/public/config.ts +163 -31
  123. package/src/public/index.ts +2 -0
  124. package/src/public/server.ts +1 -16
  125. package/dist/docs/100-introduction/100-what-is-overmux.md +0 -7
  126. package/dist/docs/300-fundamentals/100-project-structure.md +0 -23
  127. package/dist/docs/300-fundamentals/200-configuration.md +0 -3
  128. package/dist/docs/300-fundamentals/300-theming.md +0 -3
  129. package/dist/docs/300-fundamentals/400-server.md +0 -3
  130. package/dist/docs/300-fundamentals/500-client.md +0 -3
  131. package/dist/docs/300-fundamentals/600-operations.md +0 -3
  132. package/dist/docs/300-fundamentals/700-resources.md +0 -3
  133. package/dist/docs/300-fundamentals/800-streams.md +0 -3
  134. package/dist/docs/300-fundamentals/900-authentication-and-security.md +0 -3
  135. package/dist/docs/400-reference/100-configuration.md +0 -3
  136. package/dist/docs/400-reference/200-server-api.md +0 -3
  137. package/dist/docs/400-reference/300-client-api.md +0 -20
  138. package/dist/docs/400-reference/400-cli/400-check.md +0 -20
  139. package/dist/docs/400-reference/400-cli/500-ai-context.md +0 -102
  140. package/dist/docs/400-reference/400-cli/600-docs.md +0 -23
  141. package/dist/exports/index-D3_Dpy_T.d.ts.map +0 -1
  142. package/dist/exports/notifications-kDp16bU_.js.map +0 -1
  143. package/docs/100-introduction/100-what-is-overmux.md +0 -7
  144. package/docs/300-fundamentals/100-project-structure.md +0 -23
  145. package/docs/300-fundamentals/200-configuration.md +0 -3
  146. package/docs/300-fundamentals/300-theming.md +0 -3
  147. package/docs/300-fundamentals/400-server.md +0 -3
  148. package/docs/300-fundamentals/500-client.md +0 -3
  149. package/docs/300-fundamentals/600-operations.md +0 -3
  150. package/docs/300-fundamentals/700-resources.md +0 -3
  151. package/docs/300-fundamentals/800-streams.md +0 -3
  152. package/docs/300-fundamentals/900-authentication-and-security.md +0 -3
  153. package/docs/400-reference/100-configuration.md +0 -3
  154. package/docs/400-reference/200-server-api.md +0 -3
  155. package/docs/400-reference/300-client-api.md +0 -20
  156. package/docs/400-reference/400-cli/400-check.md +0 -20
  157. package/docs/400-reference/400-cli/500-ai-context.md +0 -102
  158. package/docs/400-reference/400-cli/600-docs.md +0 -23
  159. package/src/internal/cli/commands/ai.ts +0 -44
@@ -4,13 +4,13 @@ import { createServer, isIP } from "node:net";
4
4
  import { dirname, isAbsolute, join, resolve } from "node:path";
5
5
  import { createJiti } from "jiti";
6
6
  import { ZodError, z } from "zod";
7
- import { homedir, tmpdir } from "node:os";
7
+ import { homedir, hostname } from "node:os";
8
8
  import { once } from "node:events";
9
9
  import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
10
10
  import { generateCookie } from "hono/cookie";
11
11
  import { bodyLimit } from "hono/body-limit";
12
12
  import { parse } from "hono/utils/cookie";
13
- import { appendFileSync, mkdirSync, readFileSync } from "node:fs";
13
+ import { appendFileSync, constants, mkdirSync, readFileSync } from "node:fs";
14
14
  import { fileURLToPath } from "node:url";
15
15
  import { Hono } from "hono";
16
16
  import { serveStatic } from "@hono/node-server/serve-static";
@@ -35,6 +35,7 @@ const durationToMilliseconds$1 = (duration) => {
35
35
  return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
36
36
  };
37
37
  const aiContextSnippets = ["package-source", "tech-stack-recommendations"];
38
+ const instanceIdSchema$1 = z.string().max(253).regex(/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?![\s\S])/, "Expected a lowercase instance ID with only ASCII letters, digits, internal dots and hyphens");
38
39
  const zodTypeSchema = z.custom((value) => value instanceof z.ZodType, "Expected a Zod schema");
39
40
  const functionSchema = z.custom((value) => typeof value === "function", "Expected a function");
40
41
  const contractRuntimeSchema = z.object({
@@ -83,12 +84,11 @@ const loopbackPeerSchema = z.string().refine((value) => isIP(value) !== 0 && (va
83
84
  const authConfigRuntimeSchema = z.object({
84
85
  mode: z.literal("cli-login"),
85
86
  origins: z.array(exactOriginSchema).min(1).refine((origins) => new Set(origins).size === origins.length, "Expected unique origins").optional(),
86
- sessionLifetime: z.union([z.literal("never"), authDurationSchema]).default("never"),
87
+ sessionLifetime: z.union([z.literal("forever"), authDurationSchema]).default("forever"),
87
88
  trustedProxyPeer: loopbackPeerSchema.optional()
88
89
  }).strict();
89
90
  const serverConfigRuntimeSchema = z.object({
90
91
  host: z.string().min(1).optional(),
91
- logFile: z.string().min(1).optional(),
92
92
  port: z.number().int().min(1).max(65535).optional(),
93
93
  productionWebAssetsDir: z.string().min(1).optional(),
94
94
  watch: z.boolean().optional()
@@ -102,6 +102,7 @@ const configDefinitionRuntimeSchema = serverConfigRuntimeSchema.extend({
102
102
  aiContextSnippets: z.array(z.enum(aiContextSnippets)).optional(),
103
103
  auth: authConfigRuntimeSchema,
104
104
  debug: z.boolean().optional(),
105
+ instanceId: z.union([instanceIdSchema$1, z.custom((value) => typeof value === "function", "Expected an instance ID or a function of the bound port")]).optional(),
105
106
  server: serverDefinitionRuntimeSchema,
106
107
  vite: z.string().min(1).optional()
107
108
  });
@@ -400,7 +401,7 @@ const createAuthService = ({ config, environment = process.env, homeDirectory =
400
401
  const createSession = ({ kind, lifetimeMs, origin }) => {
401
402
  const token = randomToken();
402
403
  const createdAt = new Date(now()).toISOString();
403
- const configuredLifetime = config.sessionLifetime && config.sessionLifetime !== "never" ? durationMilliseconds(config.sessionLifetime) : void 0;
404
+ const configuredLifetime = config.sessionLifetime && config.sessionLifetime !== "forever" ? durationMilliseconds(config.sessionLifetime) : void 0;
404
405
  const expiresIn = lifetimeMs ?? configuredLifetime;
405
406
  return {
406
407
  stored: {
@@ -844,9 +845,41 @@ const createHttpAuthenticationHandlers = ({ auth, authAssetsDirectory }) => {
844
845
  };
845
846
  };
846
847
  //#endregion
848
+ //#region ../shared/dist/index.js
849
+ const overmuxSettingsPath = "/_overmux/settings";
850
+ const overmuxLogoutPath = "/_overmux/logout";
851
+ const safeRelativeNotificationLink = (link) => {
852
+ let decoded;
853
+ try {
854
+ decoded = decodeURIComponent(link);
855
+ } catch {
856
+ return false;
857
+ }
858
+ return !decoded.startsWith("//") && !decoded.includes("\\") && ![...decoded].some((character) => {
859
+ const codePoint = character.codePointAt(0) ?? 0;
860
+ return codePoint < 32 || codePoint === 127;
861
+ });
862
+ };
863
+ const notificationLinkSchema = z.union([z.string().max(4096).startsWith("/").refine(safeRelativeNotificationLink), z.string().max(4096).url().refine((link) => {
864
+ if (!URL.canParse(link)) return false;
865
+ const url = new URL(link);
866
+ return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password;
867
+ })]);
868
+ const notificationSchema = z.object({
869
+ body: z.string().max(4096).optional(),
870
+ open: z.object({ link: notificationLinkSchema }).strict().optional(),
871
+ title: z.string().min(1).max(4096)
872
+ }).strict();
873
+ const instanceIdSchema = z.string().max(253).regex(/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?(?![\s\S])/, "Expected a lowercase instance ID with only ASCII letters, digits, internal dots and hyphens");
874
+ const createInstanceIdentity = (instanceId) => ({
875
+ instanceId: instanceIdSchema.parse(instanceId),
876
+ deepLinkPrefix: `overmux://${instanceId}`
877
+ });
878
+ //#endregion
847
879
  //#region src/internal/server/auth/instance-control.ts
848
880
  const maxControlMessageBytes = 16384;
849
881
  const controlRequestSchema = z.discriminatedUnion("type", [
882
+ z.object({ type: z.literal("get-instance") }).strict(),
850
883
  z.object({ type: z.literal("ping") }).strict(),
851
884
  z.object({ type: z.literal("create-login") }).strict(),
852
885
  z.object({ type: z.literal("issue-bearer") }).strict(),
@@ -872,6 +905,9 @@ const sessionSchema = z.object({
872
905
  revokedAt: z.string().optional()
873
906
  }).strict();
874
907
  z.object({
908
+ instanceId: instanceIdSchema,
909
+ ok: z.literal(true)
910
+ }).strict(), z.object({
875
911
  login: loginGrantSchema,
876
912
  ok: z.literal(true)
877
913
  }).strict(), z.object({
@@ -902,12 +938,26 @@ z.object({
902
938
  port: z.number().int().min(1).max(65535),
903
939
  url: z.string().url()
904
940
  });
941
+ const currentUid = () => {
942
+ if (!process.getuid) throw new Error("Overmux instance control requires a Unix user ID");
943
+ return process.getuid();
944
+ };
905
945
  const runtimeDirectory = () => {
906
- const paths = getOvermuxPaths();
907
- const preferred = paths.runtimeDir ?? join(paths.stateDir, "runtime");
946
+ const fallback = `/tmp/overmux-${currentUid()}`;
947
+ const preferred = getOvermuxPaths().runtimeDir;
948
+ if (!preferred) return fallback;
908
949
  if (Buffer.byteLength(preferred) <= 80) return preferred;
909
- const suffix = createHash("sha256").update(preferred).digest("hex").slice(0, 12);
910
- return join(tmpdir(), `overmux-${process.getuid?.() ?? "user"}-${suffix}`);
950
+ return `${fallback}-${createHash("sha256").update(preferred).digest("hex").slice(0, 12)}`;
951
+ };
952
+ const secureRuntimeDirectory = async (directory) => {
953
+ const handle = await open(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
954
+ try {
955
+ const metadata = await handle.stat();
956
+ if (!metadata.isDirectory() || metadata.uid !== currentUid()) throw new Error(`Unsafe Overmux runtime directory: ${directory}`);
957
+ await handle.chmod(448);
958
+ } finally {
959
+ await handle.close();
960
+ }
911
961
  };
912
962
  const readNewlineFrame = ({ maximumBytes, socket, timeout }) => new Promise((resolve, reject) => {
913
963
  let buffer = Buffer.alloc(0);
@@ -950,7 +1000,11 @@ const readNewlineFrame = ({ maximumBytes, socket, timeout }) => new Promise((res
950
1000
  const writeResponse = (socket, response) => {
951
1001
  socket.end(`${JSON.stringify(response)}\n`);
952
1002
  };
953
- const handleRequest = async (auth, request) => {
1003
+ const handleRequest = async ({ auth, instanceId, request }) => {
1004
+ if (request.type === "get-instance") return {
1005
+ instanceId,
1006
+ ok: true
1007
+ };
954
1008
  if (request.type === "ping") return { ok: true };
955
1009
  if (request.type === "create-login") return {
956
1010
  login: await auth.createLoginGrant(),
@@ -973,14 +1027,18 @@ const handleRequest = async (auth, request) => {
973
1027
  ok: true
974
1028
  };
975
1029
  };
976
- const handleConnection = async ({ auth, socket }) => {
1030
+ const handleConnection = async ({ auth, instanceId, socket }) => {
977
1031
  try {
978
1032
  const frame = await readNewlineFrame({
979
1033
  maximumBytes: maxControlMessageBytes,
980
1034
  socket
981
1035
  });
982
1036
  const request = controlRequestSchema.parse(JSON.parse(frame.toString()));
983
- writeResponse(socket, await handleRequest(auth, request));
1037
+ writeResponse(socket, await handleRequest({
1038
+ auth,
1039
+ instanceId,
1040
+ request
1041
+ }));
984
1042
  } catch (cause) {
985
1043
  writeResponse(socket, {
986
1044
  error: cause instanceof Error ? cause.message : "Invalid control request",
@@ -988,13 +1046,13 @@ const handleConnection = async ({ auth, socket }) => {
988
1046
  });
989
1047
  }
990
1048
  };
991
- const startInstanceControl = async ({ auth, apiUrl, port, url }) => {
1049
+ const startInstanceControl = async ({ auth, apiUrl, instanceId, port, url }) => {
992
1050
  const directory = runtimeDirectory();
993
1051
  await mkdir(directory, {
994
1052
  mode: 448,
995
1053
  recursive: true
996
1054
  });
997
- await chmod(directory, 448);
1055
+ await secureRuntimeDirectory(directory);
998
1056
  const id = randomBytes(8).toString("hex");
999
1057
  const socketPath = join(directory, `${id}.sock`);
1000
1058
  const registrationPath = join(directory, `${id}.json`);
@@ -1004,6 +1062,7 @@ const startInstanceControl = async ({ auth, apiUrl, port, url }) => {
1004
1062
  socket.once("close", () => sockets.delete(socket));
1005
1063
  handleConnection({
1006
1064
  auth,
1065
+ instanceId,
1007
1066
  socket
1008
1067
  });
1009
1068
  });
@@ -1054,30 +1113,6 @@ const backgroundNotificationSubscriptionSchema = z.object({
1054
1113
  }).strict();
1055
1114
  z.object({ publicKey: deliveryKeySchema }).strict();
1056
1115
  const backgroundNotificationDisableRequestSchema = z.object({ endpoint: backgroundNotificationSubscriptionSchema.shape.endpoint }).strict();
1057
- const overmuxSettingsPath = "/_overmux/settings";
1058
- const overmuxLogoutPath = "/_overmux/logout";
1059
- const safeRelativeNotificationLink = (link) => {
1060
- let decoded;
1061
- try {
1062
- decoded = decodeURIComponent(link);
1063
- } catch {
1064
- return false;
1065
- }
1066
- return !decoded.startsWith("//") && !decoded.includes("\\") && ![...decoded].some((character) => {
1067
- const codePoint = character.codePointAt(0) ?? 0;
1068
- return codePoint < 32 || codePoint === 127;
1069
- });
1070
- };
1071
- const notificationLinkSchema = z.union([z.string().max(4096).startsWith("/").refine(safeRelativeNotificationLink), z.string().max(4096).url().refine((link) => {
1072
- if (!URL.canParse(link)) return false;
1073
- const url = new URL(link);
1074
- return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password;
1075
- })]);
1076
- const notificationSchema = z.object({
1077
- body: z.string().max(4096).optional(),
1078
- open: z.object({ link: notificationLinkSchema }).strict().optional(),
1079
- title: z.string().min(1).max(4096)
1080
- }).strict();
1081
1116
  const webSocketCloseCode = {
1082
1117
  authenticationRevoked: 4001,
1083
1118
  authenticationUnavailable: 4002
@@ -1085,7 +1120,7 @@ const webSocketCloseCode = {
1085
1120
  z.object({
1086
1121
  debug: z.boolean(),
1087
1122
  operations: z.array(z.string().min(1)),
1088
- protocolVersion: z.literal(9),
1123
+ protocolVersion: z.literal(10),
1089
1124
  resources: z.array(z.string().min(1)),
1090
1125
  streams: z.array(z.string().min(1))
1091
1126
  });
@@ -1191,7 +1226,12 @@ const protocolErrorMessageSchema = z.object({
1191
1226
  subscriptionId: operationIdSchema.optional(),
1192
1227
  type: z.literal("error")
1193
1228
  });
1229
+ const serverInfoMessageSchema = z.object({
1230
+ type: z.literal("server-info"),
1231
+ instanceId: instanceIdSchema
1232
+ }).strict();
1194
1233
  const serverProtocolMessageSchema = z.discriminatedUnion("type", [
1234
+ serverInfoMessageSchema,
1195
1235
  resourceResultMessageSchema,
1196
1236
  resourceInvalidatedMessageSchema,
1197
1237
  streamOpenedMessageSchema,
@@ -1308,7 +1348,7 @@ const lifecycleEvents = /* @__PURE__ */ new Set([
1308
1348
  "websocket-connect",
1309
1349
  "websocket-open"
1310
1350
  ]);
1311
- const getDefaultServerLogFile = ({ homeDir, xdgStateHome } = {}) => join(getOvermuxPaths({
1351
+ const getServerLogFile = ({ homeDir, xdgStateHome } = {}) => join(getOvermuxPaths({
1312
1352
  homeDir,
1313
1353
  xdgStateHome
1314
1354
  }).stateDir, "overmux.log");
@@ -1324,8 +1364,7 @@ const safeJson = (value) => {
1324
1364
  });
1325
1365
  }
1326
1366
  };
1327
- const createServerLogger = ({ enabled, logFile = getDefaultServerLogFile() }) => {
1328
- let activeLogFile = logFile;
1367
+ const createServerLogger = ({ enabled, logFile }) => {
1329
1368
  let debugEnabled = enabled;
1330
1369
  return {
1331
1370
  get enabled() {
@@ -1340,15 +1379,12 @@ const createServerLogger = ({ enabled, logFile = getDefaultServerLogFile() }) =>
1340
1379
  source: entry.source ?? "server",
1341
1380
  ...entry
1342
1381
  });
1343
- mkdirSync(dirname(activeLogFile), { recursive: true });
1344
- appendFileSync(activeLogFile, `${record}\n`, "utf8");
1382
+ mkdirSync(dirname(logFile), { recursive: true });
1383
+ appendFileSync(logFile, `${record}\n`, "utf8");
1345
1384
  process.stderr.write(`[overmux:debug] ${record}\n`);
1346
1385
  },
1347
1386
  setEnabled: (nextEnabled) => {
1348
1387
  debugEnabled = nextEnabled;
1349
- },
1350
- setLogFile: (nextLogFile) => {
1351
- activeLogFile = nextLogFile;
1352
1388
  }
1353
1389
  };
1354
1390
  };
@@ -2334,6 +2370,10 @@ const createWebSocketServer = ({ auth, developmentWebTarget, runtime, server, se
2334
2370
  sessionSockets,
2335
2371
  socket: webSocket
2336
2372
  });
2373
+ webSocket.send(encodeProtocolFrame({
2374
+ type: "server-info",
2375
+ instanceId: runtime.instance.getInstanceId()
2376
+ }));
2337
2377
  webSocketServer.emit("connection", webSocket, request);
2338
2378
  attachWebSocketConnection({
2339
2379
  runtime,
@@ -2607,6 +2647,31 @@ const createNotificationService = ({ backgroundNotifications, logger }) => {
2607
2647
  };
2608
2648
  };
2609
2649
  //#endregion
2650
+ //#region src/internal/server/runtime/runtime-instance.ts
2651
+ const defaultInstanceId = ({ port }) => `${hostname()}-${port}`;
2652
+ const createRuntimeInstance = (configured) => {
2653
+ let identity;
2654
+ const get = () => {
2655
+ if (!identity) throw new Error("Overmux instance identity is not ready");
2656
+ return identity;
2657
+ };
2658
+ return {
2659
+ context: {
2660
+ getInstanceId: () => get().instanceId,
2661
+ getDeepLinkPrefix: () => get().deepLinkPrefix
2662
+ },
2663
+ establish: (port) => {
2664
+ if (identity) throw new Error("Overmux instance identity is already established");
2665
+ try {
2666
+ identity = createInstanceIdentity(typeof configured === "function" ? configured({ port }) : configured);
2667
+ } catch (cause) {
2668
+ throw new Error("Invalid Overmux instance ID; configure instanceId with a canonical lowercase ID", { cause });
2669
+ }
2670
+ return identity;
2671
+ }
2672
+ };
2673
+ };
2674
+ //#endregion
2610
2675
  //#region src/internal/server/runtime/runtime-lifecycle.ts
2611
2676
  const asError = (cause) => cause instanceof Error ? cause : new Error(String(cause));
2612
2677
  const disposeAll = async (disposers) => {
@@ -2728,14 +2793,10 @@ const getInvalidatedResourceIds = (graph, resourceId) => {
2728
2793
  };
2729
2794
  //#endregion
2730
2795
  //#region src/internal/server/runtime/runtime-resources.ts
2731
- const createRuntimeResources = ({ definitions, lifecycle }) => {
2796
+ const createRuntimeResources = ({ definitions, instance, lifecycle }) => {
2732
2797
  const graph = createResourceDependencyGraph(definitions);
2733
2798
  const invalidationListeners = /* @__PURE__ */ new Set();
2734
- const contracts = /* @__PURE__ */ new Map();
2735
2799
  const resources = /* @__PURE__ */ new Map();
2736
- Object.entries(definitions).forEach(([id, definition]) => {
2737
- contracts.set(definition.contract, [...contracts.get(definition.contract) ?? [], id]);
2738
- });
2739
2800
  const invalidateResource = (id, input, allInputs = false) => {
2740
2801
  if (lifecycle.signal.aborted) return;
2741
2802
  const ids = getInvalidatedResourceIds(graph, id);
@@ -2743,16 +2804,14 @@ const createRuntimeResources = ({ definitions, lifecycle }) => {
2743
2804
  if (ids.has(entry.resourceId) && (allInputs || isDeepStrictEqual(entry.input, input))) entry.listener();
2744
2805
  });
2745
2806
  };
2746
- const invalidate = (contract, input) => {
2747
- const ids = contracts.get(contract) ?? Object.entries(definitions).filter(([, definition]) => definition.contract.input === contract.input && definition.contract.output === contract.output).map(([id]) => id);
2748
- if (!ids.length) throw new Error("Cannot invalidate a resource outside this runtime");
2749
- ids.forEach((id) => {
2750
- const definition = definitions[id];
2751
- const parsedInput = input === void 0 ? void 0 : definition.contract.input.parse(input);
2752
- invalidateResource(id, parsedInput, input === void 0);
2753
- });
2807
+ const invalidate = (resourceId, input) => {
2808
+ if (!resources.has(resourceId)) throw new Error(`Unknown resource ID: ${resourceId}`);
2809
+ const definition = definitions[resourceId];
2810
+ const parsedInput = input === void 0 ? void 0 : definition.contract.input.parse(input);
2811
+ invalidateResource(resourceId, parsedInput, input === void 0);
2754
2812
  };
2755
2813
  const context = (signal) => ({
2814
+ instance,
2756
2815
  invalidate,
2757
2816
  signal: lifecycle.requestSignal(signal)
2758
2817
  });
@@ -2885,11 +2944,13 @@ const createRuntimeStreams = ({ context, definitions, lifecycle }) => {
2885
2944
  };
2886
2945
  //#endregion
2887
2946
  //#region src/internal/server/runtime/create-runtime.ts
2888
- const createRuntime = async ({ config, debug = false, notifications = { send: async () => void 0 }, signal }) => {
2947
+ const createRuntime = async ({ config, debug = true, notifications = { send: async () => void 0 }, signal }) => {
2889
2948
  const lifecycle = createRuntimeLifecycle(signal);
2949
+ const instance = createRuntimeInstance(config.instanceId ?? defaultInstanceId);
2890
2950
  try {
2891
2951
  const resources = createRuntimeResources({
2892
2952
  definitions: config.server.resources ?? {},
2953
+ instance: instance.context,
2893
2954
  lifecycle
2894
2955
  });
2895
2956
  const context = resources.context;
@@ -2905,14 +2966,17 @@ const createRuntime = async ({ config, debug = false, notifications = { send: as
2905
2966
  });
2906
2967
  lifecycle.signal.throwIfAborted();
2907
2968
  lifecycle.completePreparation();
2969
+ const manifest = {
2970
+ debug,
2971
+ operations: [...operations.names],
2972
+ protocolVersion: 10,
2973
+ resources: [...resources.names],
2974
+ streams: [...streams.names]
2975
+ };
2908
2976
  return {
2909
- manifest: {
2910
- debug,
2911
- operations: [...operations.names],
2912
- protocolVersion: 9,
2913
- resources: [...resources.names],
2914
- streams: [...streams.names]
2915
- },
2977
+ instance: instance.context,
2978
+ establishInstance: instance.establish,
2979
+ manifest,
2916
2980
  dispose: lifecycle.dispose,
2917
2981
  getOperation: operations.get,
2918
2982
  getResource: resources.get,
@@ -2925,16 +2989,18 @@ const createRuntime = async ({ config, debug = false, notifications = { send: as
2925
2989
  };
2926
2990
  //#endregion
2927
2991
  //#region src/internal/server/server-startup-options.ts
2992
+ const serverStartupOverridesRuntimeSchema = serverConfigRuntimeSchema.omit({ port: true }).extend({
2993
+ debug: z.boolean().optional(),
2994
+ port: z.number().int().min(0).max(65535).optional()
2995
+ });
2928
2996
  const resolveServerStartupOptions = ({ config, configPath, overrides = {} }) => {
2929
- const validatedOverrides = serverConfigRuntimeSchema.omit({ port: true }).extend({ port: z.number().int().min(0).max(65535).optional() }).parse(overrides);
2930
- const configuredLogFile = config.logFile ? resolve(dirname(configPath), config.logFile) : void 0;
2931
- const logFile = validatedOverrides.logFile ? resolve(validatedOverrides.logFile) : configuredLogFile;
2997
+ const validatedOverrides = serverStartupOverridesRuntimeSchema.parse(overrides);
2932
2998
  const configuredWebAssetsDir = config.productionWebAssetsDir ? resolve(dirname(configPath), config.productionWebAssetsDir) : void 0;
2933
2999
  const productionWebAssetsDir = validatedOverrides.productionWebAssetsDir ? resolve(validatedOverrides.productionWebAssetsDir) : configuredWebAssetsDir;
2934
3000
  return {
3001
+ debug: validatedOverrides.debug ?? config.debug ?? true,
2935
3002
  host: validatedOverrides.host ?? config.host ?? "localhost",
2936
- ...logFile === void 0 ? {} : { logFile },
2937
- port: validatedOverrides.port ?? config.port ?? 4200,
3003
+ port: validatedOverrides.port ?? config.port ?? 4242,
2938
3004
  ...productionWebAssetsDir === void 0 ? {} : { productionWebAssetsDir },
2939
3005
  watch: validatedOverrides.watch ?? config.watch ?? true
2940
3006
  };
@@ -3076,12 +3142,20 @@ const startApplicationTransport = async ({ auth, authOrigins, authService, backg
3076
3142
  serverLogger: logger,
3077
3143
  webAssetsDir: developmentWebTarget ? void 0 : startup.productionWebAssetsDir
3078
3144
  });
3145
+ let ready = false;
3079
3146
  const server = serve({
3080
- fetch: app.fetch,
3147
+ fetch: (request, env) => ready ? app.fetch(request, env) : new Response("Starting", { status: 503 }),
3081
3148
  hostname: startup.host,
3082
3149
  port: startup.port
3083
3150
  });
3084
3151
  resources.add(() => closeNodeServer(server, runtime.dispose));
3152
+ const address = await getServerAddress(server);
3153
+ runtime.establishInstance(address.port);
3154
+ const urlHost = startup.host.includes(":") ? `[${startup.host}]` : startup.host;
3155
+ const localUrl = `http://${urlHost}:${address.port}`;
3156
+ const controlApiHost = startup.host === "0.0.0.0" ? "127.0.0.1" : startup.host === "::" ? "[::1]" : urlHost;
3157
+ const origins = authOrigins.establish(localUrl);
3158
+ authService.setOrigins(origins);
3085
3159
  const webSockets = createWebSocketServer({
3086
3160
  auth,
3087
3161
  developmentWebTarget,
@@ -3092,15 +3166,11 @@ const startApplicationTransport = async ({ auth, authOrigins, authService, backg
3092
3166
  resources.add(webSockets.close);
3093
3167
  announceRestarting = webSockets.announceRestarting;
3094
3168
  notifications.attachLiveDelivery(webSockets.broadcastNotification);
3095
- const address = await getServerAddress(server);
3096
- const urlHost = startup.host.includes(":") ? `[${startup.host}]` : startup.host;
3097
- const localUrl = `http://${urlHost}:${address.port}`;
3098
- const controlApiHost = startup.host === "0.0.0.0" ? "127.0.0.1" : startup.host === "::" ? "[::1]" : urlHost;
3099
- const origins = authOrigins.establish(localUrl);
3100
- authService.setOrigins(origins);
3169
+ ready = true;
3101
3170
  const instanceControl = await startInstanceControl({
3102
3171
  auth: authService,
3103
3172
  apiUrl: `http://${controlApiHost}:${address.port}`,
3173
+ instanceId: runtime.instance.getInstanceId(),
3104
3174
  port: address.port,
3105
3175
  url: origins[0]
3106
3176
  });
@@ -3127,8 +3197,8 @@ const failApplicationServerStartup = async (cause, resources) => {
3127
3197
  };
3128
3198
  const startApplicationServer = async ({ onRestartRequested, options }) => {
3129
3199
  const logger = createServerLogger({
3130
- enabled: options.debug ?? false,
3131
- logFile: options.logFile ? resolve(options.logFile) : void 0
3200
+ enabled: options.debug ?? true,
3201
+ logFile: getServerLogFile()
3132
3202
  });
3133
3203
  const resources = createResources();
3134
3204
  resources.add(registerProcessErrorLogging(logger));
@@ -3142,8 +3212,7 @@ const startApplicationServer = async ({ onRestartRequested, options }) => {
3142
3212
  configPath,
3143
3213
  options
3144
3214
  });
3145
- logger.setEnabled(Boolean(options.debug || config.debug));
3146
- if (startup.logFile) logger.setLogFile(startup.logFile);
3215
+ logger.setEnabled(startup.debug);
3147
3216
  const { auth, authOrigins, authService, backgroundNotifications, notifications, runtime } = await createApplicationServices({
3148
3217
  config,
3149
3218
  logger,
@@ -3187,7 +3256,6 @@ const childOptionsSchema = z.object({
3187
3256
  debug: z.boolean().optional(),
3188
3257
  developmentWebTarget: z.url().optional(),
3189
3258
  host: z.string().optional(),
3190
- logFile: z.string().optional(),
3191
3259
  port: z.number().int().min(0).max(65535).optional(),
3192
3260
  productionWebAssetsDir: z.string().optional(),
3193
3261
  watch: z.boolean().optional()