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
package/dist/bin.js CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire, findPackageJSON } from "node:module";
3
- import { access, chmod, readFile, readdir, realpath } from "node:fs/promises";
3
+ import { createHash } from "node:crypto";
4
+ import { constants, cpSync, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
5
+ import { access, open, readFile, readdir, realpath } from "node:fs/promises";
4
6
  import { createConnection, isIP } from "node:net";
5
7
  import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
6
- import { createJiti } from "jiti";
7
8
  import { ZodError, z } from "zod";
8
- import { homedir, tmpdir } from "node:os";
9
- import { createHash } from "node:crypto";
9
+ import { createJiti } from "jiti";
10
+ import { homedir, hostname } from "node:os";
10
11
  import ts from "typescript";
11
12
  import { isDeepStrictEqual } from "node:util";
12
13
  import { fork, spawnSync } from "node:child_process";
13
14
  import { fileURLToPath, pathToFileURL } from "node:url";
14
- import { cpSync, createWriteStream, existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
15
15
  import { createInterface } from "node:readline/promises";
16
16
  import { Readable, Transform } from "node:stream";
17
17
  import { pipeline } from "node:stream/promises";
@@ -1825,6 +1825,35 @@ async function run(app, inputs, context) {
1825
1825
  /* v8 ignore if -- @preserve */
1826
1826
  /* v8 ignore else -- @preserve */
1827
1827
  //#endregion
1828
+ //#region ../shared/dist/index.js
1829
+ var safeRelativeNotificationLink = (link) => {
1830
+ let decoded;
1831
+ try {
1832
+ decoded = decodeURIComponent(link);
1833
+ } catch {
1834
+ return false;
1835
+ }
1836
+ return !decoded.startsWith("//") && !decoded.includes("\\") && ![...decoded].some((character) => {
1837
+ const codePoint = character.codePointAt(0) ?? 0;
1838
+ return codePoint < 32 || codePoint === 127;
1839
+ });
1840
+ };
1841
+ var notificationLinkSchema = z.union([z.string().max(4096).startsWith("/").refine(safeRelativeNotificationLink), z.string().max(4096).url().refine((link) => {
1842
+ if (!URL.canParse(link)) return false;
1843
+ const url = new URL(link);
1844
+ return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password;
1845
+ })]);
1846
+ var notificationSchema = z.object({
1847
+ body: z.string().max(4096).optional(),
1848
+ open: z.object({ link: notificationLinkSchema }).strict().optional(),
1849
+ title: z.string().min(1).max(4096)
1850
+ }).strict();
1851
+ var 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");
1852
+ var createInstanceIdentity = (instanceId) => ({
1853
+ instanceId: instanceIdSchema$1.parse(instanceId),
1854
+ deepLinkPrefix: `overmux://${instanceId}`
1855
+ });
1856
+ //#endregion
1828
1857
  //#region ../shared/dist/node/index.js
1829
1858
  var durationPattern = /^([1-9]\d*)(ms|s|m|h|d)$/;
1830
1859
  var millisecondsByUnit = {
@@ -1842,6 +1871,7 @@ var durationToMilliseconds = (duration) => {
1842
1871
  return Number.isSafeInteger(milliseconds) ? milliseconds : void 0;
1843
1872
  };
1844
1873
  var aiContextSnippets = ["package-source", "tech-stack-recommendations"];
1874
+ var 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");
1845
1875
  var zodTypeSchema = z.custom((value) => value instanceof z.ZodType, "Expected a Zod schema");
1846
1876
  var functionSchema = z.custom((value) => typeof value === "function", "Expected a function");
1847
1877
  var contractRuntimeSchema = z.object({
@@ -1890,12 +1920,11 @@ var loopbackPeerSchema = z.string().refine((value) => isIP(value) !== 0 && (valu
1890
1920
  var authConfigRuntimeSchema = z.object({
1891
1921
  mode: z.literal("cli-login"),
1892
1922
  origins: z.array(exactOriginSchema).min(1).refine((origins) => new Set(origins).size === origins.length, "Expected unique origins").optional(),
1893
- sessionLifetime: z.union([z.literal("never"), authDurationSchema]).default("never"),
1923
+ sessionLifetime: z.union([z.literal("forever"), authDurationSchema]).default("forever"),
1894
1924
  trustedProxyPeer: loopbackPeerSchema.optional()
1895
1925
  }).strict();
1896
1926
  var serverConfigRuntimeSchema = z.object({
1897
1927
  host: z.string().min(1).optional(),
1898
- logFile: z.string().min(1).optional(),
1899
1928
  port: z.number().int().min(1).max(65535).optional(),
1900
1929
  productionWebAssetsDir: z.string().min(1).optional(),
1901
1930
  watch: z.boolean().optional()
@@ -1909,6 +1938,7 @@ var configDefinitionRuntimeSchema = serverConfigRuntimeSchema.extend({
1909
1938
  aiContextSnippets: z.array(z.enum(aiContextSnippets)).optional(),
1910
1939
  auth: authConfigRuntimeSchema,
1911
1940
  debug: z.boolean().optional(),
1941
+ instanceId: z.union([instanceIdSchema, z.custom((value) => typeof value === "function", "Expected an instance ID or a function of the bound port")]).optional(),
1912
1942
  server: serverDefinitionRuntimeSchema,
1913
1943
  vite: z.string().min(1).optional()
1914
1944
  });
@@ -1964,89 +1994,13 @@ var getOvermuxPaths = ({ environment = process.env, homeDir = homedir(), xdgCach
1964
1994
  };
1965
1995
  };
1966
1996
  //#endregion
1967
- //#region ../shared/dist/index.js
1968
- var packageSourceSnippet = "package-source";
1969
- var techStackRecommendationsSnippet = "tech-stack-recommendations";
1970
- var safeRelativeNotificationLink = (link) => {
1971
- let decoded;
1972
- try {
1973
- decoded = decodeURIComponent(link);
1974
- } catch {
1975
- return false;
1976
- }
1977
- return !decoded.startsWith("//") && !decoded.includes("\\") && ![...decoded].some((character) => {
1978
- const codePoint = character.codePointAt(0) ?? 0;
1979
- return codePoint < 32 || codePoint === 127;
1980
- });
1981
- };
1982
- var notificationLinkSchema = z.union([z.string().max(4096).startsWith("/").refine(safeRelativeNotificationLink), z.string().max(4096).url().refine((link) => {
1983
- if (!URL.canParse(link)) return false;
1984
- const url = new URL(link);
1985
- return ["http:", "https:"].includes(url.protocol) && !url.username && !url.password;
1986
- })]);
1987
- var notificationSchema = z.object({
1988
- body: z.string().max(4096).optional(),
1989
- open: z.object({ link: notificationLinkSchema }).strict().optional(),
1990
- title: z.string().min(1).max(4096)
1991
- }).strict();
1992
- var defaultAiContextSnippets = [...[packageSourceSnippet], techStackRecommendationsSnippet];
1993
- var packageSourceSnippetContent = `Overmux ships with documentation and TypeScript source.
1994
-
1995
- Run \`overmux docs\` to find the installed documentation and source for the overmux cli.
1996
-
1997
- E.g. \`node_modules/overmux\` and \`node_modules/@overmux/xterm\`.
1998
-
1999
- Inspect these files so guidance matches the versions used by the application.`;
2000
- var techStackRecommendationsSnippetContent = `Prefer pnpm as the package manager, TanStack Router for routing, shadcn/ui for UI components, and Zod for schemas and runtime validation. Follow the application's established stack when it already differs.`;
2001
- var aiContextContentBySnippet = {
2002
- [packageSourceSnippet]: packageSourceSnippetContent,
2003
- [techStackRecommendationsSnippet]: techStackRecommendationsSnippetContent
2004
- };
2005
- var composeAiContext = (snippets) => snippets.length === 0 ? "" : `${snippets.map((snippet) => `## ${snippet}\n\n${aiContextContentBySnippet[snippet]}`).join("\n\n")}\n`;
2006
- //#endregion
2007
1997
  //#region src/internal/server/paths.ts
2008
1998
  var getDefaultConfigPath = (options = {}) => join(getOvermuxPaths(options).configDir, "overmux.config.ts");
2009
1999
  //#endregion
2010
- //#region src/internal/cli/commands/ai.ts
2011
- var printAiContext = async (flags, process) => {
2012
- const { config } = await loadOvermuxConfig({ configPath: flags.config });
2013
- process.stdout.write(composeAiContext(config.aiContextSnippets ?? defaultAiContextSnippets));
2014
- };
2015
- var createAiContextCommand = (process) => buildCommand({
2016
- func: (flags) => printAiContext(flags, process),
2017
- parameters: {
2018
- aliases: { c: "config" },
2019
- flags: { config: {
2020
- brief: "Configuration file",
2021
- default: getDefaultConfigPath(),
2022
- kind: "parsed",
2023
- parse: String,
2024
- placeholder: "path"
2025
- } }
2026
- },
2027
- docs: { brief: "Print the configured Overmux AI context" }
2028
- });
2029
- var createAiRoute = (process) => buildRouteMap({
2030
- docs: { brief: "Print Overmux AI context" },
2031
- routes: { context: createAiContextCommand(process) }
2032
- });
2033
- //#endregion
2034
- //#region src/internal/server/notifications/private-json-file.ts
2035
- var missingFile = (cause) => cause.code === "ENOENT";
2036
- var readPrivateJson = async (path, schema) => {
2037
- try {
2038
- const contents = await readFile(path, "utf8");
2039
- await chmod(path, 384);
2040
- return schema.parse(JSON.parse(contents));
2041
- } catch (cause) {
2042
- if (missingFile(cause)) return;
2043
- throw cause;
2044
- }
2045
- };
2046
- //#endregion
2047
2000
  //#region src/internal/server/auth/instance-control.ts
2048
2001
  var maxControlMessageBytes = 16384;
2049
2002
  z.discriminatedUnion("type", [
2003
+ z.object({ type: z.literal("get-instance") }).strict(),
2050
2004
  z.object({ type: z.literal("ping") }).strict(),
2051
2005
  z.object({ type: z.literal("create-login") }).strict(),
2052
2006
  z.object({ type: z.literal("issue-bearer") }).strict(),
@@ -2072,6 +2026,10 @@ var sessionSchema = z.object({
2072
2026
  revokedAt: z.string().optional()
2073
2027
  }).strict();
2074
2028
  var controlResponseSchemas = {
2029
+ "get-instance": z.object({
2030
+ instanceId: instanceIdSchema$1,
2031
+ ok: z.literal(true)
2032
+ }).strict(),
2075
2033
  "create-login": z.object({
2076
2034
  login: loginGrantSchema,
2077
2035
  ok: z.literal(true)
@@ -2109,12 +2067,36 @@ var registrationSchema = z.object({
2109
2067
  port: z.number().int().min(1).max(65535),
2110
2068
  url: z.string().url()
2111
2069
  });
2070
+ var currentUid = () => {
2071
+ if (!process.getuid) throw new Error("Overmux instance control requires a Unix user ID");
2072
+ return process.getuid();
2073
+ };
2112
2074
  var runtimeDirectory = () => {
2113
- const paths = getOvermuxPaths();
2114
- const preferred = paths.runtimeDir ?? join(paths.stateDir, "runtime");
2075
+ const fallback = `/tmp/overmux-${currentUid()}`;
2076
+ const preferred = getOvermuxPaths().runtimeDir;
2077
+ if (!preferred) return fallback;
2115
2078
  if (Buffer.byteLength(preferred) <= 80) return preferred;
2116
- const suffix = createHash("sha256").update(preferred).digest("hex").slice(0, 12);
2117
- return join(tmpdir(), `overmux-${process.getuid?.() ?? "user"}-${suffix}`);
2079
+ return `${fallback}-${createHash("sha256").update(preferred).digest("hex").slice(0, 12)}`;
2080
+ };
2081
+ var secureRuntimeDirectory = async (directory) => {
2082
+ const handle = await open(directory, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
2083
+ try {
2084
+ const metadata = await handle.stat();
2085
+ if (!metadata.isDirectory() || metadata.uid !== currentUid()) throw new Error(`Unsafe Overmux runtime directory: ${directory}`);
2086
+ await handle.chmod(448);
2087
+ } finally {
2088
+ await handle.close();
2089
+ }
2090
+ };
2091
+ var readRegistration = async (path) => {
2092
+ const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
2093
+ try {
2094
+ const metadata = await handle.stat();
2095
+ if (!metadata.isFile() || metadata.uid !== currentUid() || (metadata.mode & 63) !== 0) return;
2096
+ return registrationSchema.parse(JSON.parse(await handle.readFile("utf8")));
2097
+ } finally {
2098
+ await handle.close();
2099
+ }
2118
2100
  };
2119
2101
  var readNewlineFrame = ({ maximumBytes, socket, timeout }) => new Promise((resolve, reject) => {
2120
2102
  let buffer = Buffer.alloc(0);
@@ -2166,12 +2148,13 @@ var discoverInstance = async (port) => {
2166
2148
  const directory = runtimeDirectory();
2167
2149
  let names;
2168
2150
  try {
2151
+ await secureRuntimeDirectory(directory);
2169
2152
  names = await readdir(directory);
2170
2153
  } catch (cause) {
2171
2154
  if (cause.code === "ENOENT") throw new Error("No running Overmux server was found");
2172
2155
  throw cause;
2173
2156
  }
2174
- const candidates = (await Promise.all(names.filter((name) => name.endsWith(".json")).map((name) => readPrivateJson(join(directory, name), registrationSchema).catch(() => void 0)))).filter((registration) => Boolean(registration && processIsLive(registration.pid) && (port === void 0 || registration.port === port)));
2157
+ const candidates = (await Promise.all(names.filter((name) => name.endsWith(".json")).map((name) => readRegistration(join(directory, name)).catch(() => void 0)))).filter((registration) => Boolean(registration && processIsLive(registration.pid) && (port === void 0 || registration.port === port)));
2175
2158
  const registrations = (await Promise.all(candidates.map(async (registration) => {
2176
2159
  try {
2177
2160
  await sendControlRequest(registration, { type: "ping" });
@@ -2217,24 +2200,24 @@ var issueLoginGrant = async (port) => {
2217
2200
  var printLoginFallback = (port, { stdout }) => {
2218
2201
  stdout.write(`Login with \`overmux auth login --port ${port}\`\n`);
2219
2202
  };
2220
- var printLoginGrant = ({ login, output, port }) => {
2203
+ var printLoginGrant = ({ login, output }) => {
2221
2204
  const expiresIn = Math.max(0, Math.ceil((Date.parse(login.expiresAt) - Date.now()) / 6e4));
2222
2205
  output.stdout.write([
2223
- `Code: ${login.code}`,
2224
- ...login.urls.map((url) => `Open: ${url}`),
2225
- "This single-use grant authenticates one origin. Create another grant to authenticate another origin.",
2226
- "Enter the code on the login page shown by an unauthenticated Overmux origin.",
2227
- `Expires at ${login.expiresAt} (in ${expiresIn} minutes).`,
2228
- `Need another grant? Run \`overmux auth login --port ${port}\`.`,
2206
+ `Code: ${login.code} (enter on the login page)`,
2207
+ "",
2208
+ ...login.urls.length === 1 ? [`Login URL: ${login.urls[0]}`] : ["Login URLs:", ...login.urls.map((url) => ` ${url}`)],
2209
+ "",
2210
+ `Expiry in ${expiresIn} minutes (at ${login.expiresAt}).`,
2211
+ "",
2229
2212
  ""
2230
2213
  ].join("\n"));
2231
- output.stderr.write("The login URL is a temporary credential; avoid sharing terminal output or screenshots.\n");
2214
+ output.stderr.write(`Keep the code and link${login.urls.length === 1 ? "" : "s"} private.\n`);
2232
2215
  };
2233
2216
  //#endregion
2234
2217
  //#region src/internal/cli/parse-port.ts
2235
2218
  var parsePort = (value) => {
2236
2219
  const port = Number(value);
2237
- return serverConfigRuntimeSchema.shape.port.safeParse(port).success ? port : /* @__PURE__ */ new Error(`Invalid port: ${value}`);
2220
+ return port > 0 && serverConfigRuntimeSchema.shape.port.safeParse(port).success ? port : /* @__PURE__ */ new Error(`Invalid port: ${value}`);
2238
2221
  };
2239
2222
  //#endregion
2240
2223
  //#region src/internal/cli/commands/auth.ts
@@ -2415,7 +2398,7 @@ z.object({ endpoint: backgroundNotificationSubscriptionSchema.shape.endpoint }).
2415
2398
  z.object({
2416
2399
  debug: z.boolean(),
2417
2400
  operations: z.array(z.string().min(1)),
2418
- protocolVersion: z.literal(9),
2401
+ protocolVersion: z.literal(10),
2419
2402
  resources: z.array(z.string().min(1)),
2420
2403
  streams: z.array(z.string().min(1))
2421
2404
  });
@@ -2521,7 +2504,12 @@ var protocolErrorMessageSchema = z.object({
2521
2504
  subscriptionId: operationIdSchema.optional(),
2522
2505
  type: z.literal("error")
2523
2506
  });
2507
+ var serverInfoMessageSchema = z.object({
2508
+ type: z.literal("server-info"),
2509
+ instanceId: instanceIdSchema$1
2510
+ }).strict();
2524
2511
  z.discriminatedUnion("type", [
2512
+ serverInfoMessageSchema,
2525
2513
  resourceResultMessageSchema,
2526
2514
  resourceInvalidatedMessageSchema,
2527
2515
  streamOpenedMessageSchema,
@@ -2533,6 +2521,31 @@ z.discriminatedUnion("type", [
2533
2521
  notificationEventSchema
2534
2522
  ]);
2535
2523
  //#endregion
2524
+ //#region src/internal/server/runtime/runtime-instance.ts
2525
+ var defaultInstanceId = ({ port }) => `${hostname()}-${port}`;
2526
+ var createRuntimeInstance = (configured) => {
2527
+ let identity;
2528
+ const get = () => {
2529
+ if (!identity) throw new Error("Overmux instance identity is not ready");
2530
+ return identity;
2531
+ };
2532
+ return {
2533
+ context: {
2534
+ getInstanceId: () => get().instanceId,
2535
+ getDeepLinkPrefix: () => get().deepLinkPrefix
2536
+ },
2537
+ establish: (port) => {
2538
+ if (identity) throw new Error("Overmux instance identity is already established");
2539
+ try {
2540
+ identity = createInstanceIdentity(typeof configured === "function" ? configured({ port }) : configured);
2541
+ } catch (cause) {
2542
+ throw new Error("Invalid Overmux instance ID; configure instanceId with a canonical lowercase ID", { cause });
2543
+ }
2544
+ return identity;
2545
+ }
2546
+ };
2547
+ };
2548
+ //#endregion
2536
2549
  //#region src/internal/server/runtime/runtime-lifecycle.ts
2537
2550
  var asError = (cause) => cause instanceof Error ? cause : new Error(String(cause));
2538
2551
  var disposeAll = async (disposers) => {
@@ -2623,6 +2636,26 @@ var createRuntimeOperations = ({ context, definitions, notifications }) => {
2623
2636
  names: [...operations.keys()]
2624
2637
  };
2625
2638
  };
2639
+ //#endregion
2640
+ //#region ../ai-context/dist/index.js
2641
+ var packageSourceSnippet = "package-source";
2642
+ var techStackRecommendationsSnippet = "tech-stack-recommendations";
2643
+ var defaultAiContextSnippets = [...[packageSourceSnippet], techStackRecommendationsSnippet];
2644
+ var packageSourceSnippetContent = `Overmux ships with documentation and TypeScript source.
2645
+
2646
+ Run \`overmux docs path\` to find the installed documentation directory for the Overmux CLI. This prints a documentation directory, not source paths.
2647
+
2648
+ Inspect TypeScript source in installed packages, e.g. \`node_modules/overmux/src\` and \`node_modules/@overmux/xterm/src\`.
2649
+
2650
+ Inspect these files so guidance matches the versions used by the application.`;
2651
+ var techStackRecommendationsSnippetContent = `Prefer pnpm as the package manager, TanStack Router for routing, shadcn/ui for UI components, and Zod for schemas and runtime validation. Follow the application's established stack when it already differs.`;
2652
+ var aiContextContentBySnippet = {
2653
+ [packageSourceSnippet]: packageSourceSnippetContent,
2654
+ [techStackRecommendationsSnippet]: techStackRecommendationsSnippetContent
2655
+ };
2656
+ //#endregion
2657
+ //#region src/public/ai-context.ts
2658
+ var composeAiContext = (snippets) => snippets.length === 0 ? "" : `${snippets.map((snippet) => `## ${snippet}\n\n${aiContextContentBySnippet[snippet]}`).join("\n\n")}\n`;
2626
2659
  z.void();
2627
2660
  var jsonValueSchema = z.lazy(() => z.union([
2628
2661
  z.string(),
@@ -2699,14 +2732,10 @@ var getInvalidatedResourceIds = (graph, resourceId) => {
2699
2732
  };
2700
2733
  //#endregion
2701
2734
  //#region src/internal/server/runtime/runtime-resources.ts
2702
- var createRuntimeResources = ({ definitions, lifecycle }) => {
2735
+ var createRuntimeResources = ({ definitions, instance, lifecycle }) => {
2703
2736
  const graph = createResourceDependencyGraph(definitions);
2704
2737
  const invalidationListeners = /* @__PURE__ */ new Set();
2705
- const contracts = /* @__PURE__ */ new Map();
2706
2738
  const resources = /* @__PURE__ */ new Map();
2707
- Object.entries(definitions).forEach(([id, definition]) => {
2708
- contracts.set(definition.contract, [...contracts.get(definition.contract) ?? [], id]);
2709
- });
2710
2739
  const invalidateResource = (id, input, allInputs = false) => {
2711
2740
  if (lifecycle.signal.aborted) return;
2712
2741
  const ids = getInvalidatedResourceIds(graph, id);
@@ -2714,16 +2743,14 @@ var createRuntimeResources = ({ definitions, lifecycle }) => {
2714
2743
  if (ids.has(entry.resourceId) && (allInputs || isDeepStrictEqual(entry.input, input))) entry.listener();
2715
2744
  });
2716
2745
  };
2717
- const invalidate = (contract, input) => {
2718
- const ids = contracts.get(contract) ?? Object.entries(definitions).filter(([, definition]) => definition.contract.input === contract.input && definition.contract.output === contract.output).map(([id]) => id);
2719
- if (!ids.length) throw new Error("Cannot invalidate a resource outside this runtime");
2720
- ids.forEach((id) => {
2721
- const definition = definitions[id];
2722
- const parsedInput = input === void 0 ? void 0 : definition.contract.input.parse(input);
2723
- invalidateResource(id, parsedInput, input === void 0);
2724
- });
2746
+ const invalidate = (resourceId, input) => {
2747
+ if (!resources.has(resourceId)) throw new Error(`Unknown resource ID: ${resourceId}`);
2748
+ const definition = definitions[resourceId];
2749
+ const parsedInput = input === void 0 ? void 0 : definition.contract.input.parse(input);
2750
+ invalidateResource(resourceId, parsedInput, input === void 0);
2725
2751
  };
2726
2752
  const context = (signal) => ({
2753
+ instance,
2727
2754
  invalidate,
2728
2755
  signal: lifecycle.requestSignal(signal)
2729
2756
  });
@@ -2856,11 +2883,13 @@ var createRuntimeStreams = ({ context, definitions, lifecycle }) => {
2856
2883
  };
2857
2884
  //#endregion
2858
2885
  //#region src/internal/server/runtime/create-runtime.ts
2859
- var createRuntime = async ({ config, debug = false, notifications = { send: async () => void 0 }, signal }) => {
2886
+ var createRuntime = async ({ config, debug = true, notifications = { send: async () => void 0 }, signal }) => {
2860
2887
  const lifecycle = createRuntimeLifecycle(signal);
2888
+ const instance = createRuntimeInstance(config.instanceId ?? defaultInstanceId);
2861
2889
  try {
2862
2890
  const resources = createRuntimeResources({
2863
2891
  definitions: config.server.resources ?? {},
2892
+ instance: instance.context,
2864
2893
  lifecycle
2865
2894
  });
2866
2895
  const context = resources.context;
@@ -2876,14 +2905,17 @@ var createRuntime = async ({ config, debug = false, notifications = { send: asyn
2876
2905
  });
2877
2906
  lifecycle.signal.throwIfAborted();
2878
2907
  lifecycle.completePreparation();
2908
+ const manifest = {
2909
+ debug,
2910
+ operations: [...operations.names],
2911
+ protocolVersion: 10,
2912
+ resources: [...resources.names],
2913
+ streams: [...streams.names]
2914
+ };
2879
2915
  return {
2880
- manifest: {
2881
- debug,
2882
- operations: [...operations.names],
2883
- protocolVersion: 9,
2884
- resources: [...resources.names],
2885
- streams: [...streams.names]
2886
- },
2916
+ instance: instance.context,
2917
+ establishInstance: instance.establish,
2918
+ manifest,
2887
2919
  dispose: lifecycle.dispose,
2888
2920
  getOperation: operations.get,
2889
2921
  getResource: resources.get,
@@ -3098,7 +3130,6 @@ var childOptionsSchema = z.object({
3098
3130
  debug: z.boolean().optional(),
3099
3131
  developmentWebTarget: z.url().optional(),
3100
3132
  host: z.string().optional(),
3101
- logFile: z.string().optional(),
3102
3133
  port: z.number().int().min(0).max(65535).optional(),
3103
3134
  productionWebAssetsDir: z.string().optional(),
3104
3135
  watch: z.boolean().optional()
@@ -3796,6 +3827,25 @@ var desktopRoute = buildRouteMap({
3796
3827
  }
3797
3828
  });
3798
3829
  //#endregion
3830
+ //#region src/internal/cli/commands/docs-ai-context.ts
3831
+ var createAiContextCommand = (process) => buildCommand({
3832
+ func: async (flags) => {
3833
+ const { config } = await loadOvermuxConfig({ configPath: flags.config });
3834
+ process.stdout.write(composeAiContext(config.aiContextSnippets ?? defaultAiContextSnippets));
3835
+ },
3836
+ parameters: {
3837
+ aliases: { c: "config" },
3838
+ flags: { config: {
3839
+ brief: "Configuration file",
3840
+ default: getDefaultConfigPath(),
3841
+ kind: "parsed",
3842
+ parse: String,
3843
+ placeholder: "path"
3844
+ } }
3845
+ },
3846
+ docs: { brief: "Print the configured Overmux AI context" }
3847
+ });
3848
+ //#endregion
3799
3849
  //#region src/internal/cli/commands/docs.ts
3800
3850
  var resolveInstalledDocsRoot = (moduleUrl) => {
3801
3851
  const docsRoot = [
@@ -3806,13 +3856,23 @@ var resolveInstalledDocsRoot = (moduleUrl) => {
3806
3856
  if (!docsRoot) throw new Error("Installed Overmux documentation is missing");
3807
3857
  return resolve(docsRoot);
3808
3858
  };
3809
- var createDocsCommand = ({ docsRoot, output }) => buildCommand({
3859
+ var createDocsPathCommand = ({ docsRoot, output }) => buildCommand({
3810
3860
  func: () => {
3811
3861
  output.write(`${resolve(docsRoot)}\n`);
3812
3862
  },
3813
3863
  parameters: {},
3814
3864
  docs: { brief: "Print the installed documentation directory" }
3815
3865
  });
3866
+ var createDocsRoute = ({ docsRoot, process }) => buildRouteMap({
3867
+ docs: { brief: "Find documentation and print coding-agent guidance" },
3868
+ routes: {
3869
+ path: createDocsPathCommand({
3870
+ docsRoot,
3871
+ output: process.stdout
3872
+ }),
3873
+ "ai-context": createAiContextCommand(process)
3874
+ }
3875
+ });
3816
3876
  //#endregion
3817
3877
  //#region src/internal/cli/commands/init-template.ts
3818
3878
  var initScaffoldPaths = [
@@ -3854,7 +3914,7 @@ var packageJson = (overmuxVersion) => `${JSON.stringify({
3854
3914
  }, null, 2)}\n`;
3855
3915
  var createInitScaffold = ({ overmuxVersion, toolchain }) => ({
3856
3916
  ".gitignore": "node_modules\ndist\n",
3857
- "AGENTS.md": "Immediately run `overmux ai context` for documentation about Overmux.\n",
3917
+ "AGENTS.md": "Immediately run `overmux docs ai-context` for documentation about Overmux.\n",
3858
3918
  "CLAUDE.md": "@AGENTS.md\n",
3859
3919
  ...toolchain === "mise" ? { "mise.toml": `[tools]
3860
3920
  node = "22"
@@ -4144,6 +4204,38 @@ var integrationCommand = buildRouteMap({
4144
4204
  }) }
4145
4205
  });
4146
4206
  //#endregion
4207
+ //#region src/internal/cli/commands/instance.ts
4208
+ var runInstance = async (flags, output) => {
4209
+ const port = flags.port === void 0 ? void 0 : parsePort(flags.port);
4210
+ if (port instanceof Error) throw port;
4211
+ const identity = createInstanceIdentity((await sendControlRequest(await discoverInstance(port), { type: "get-instance" })).instanceId);
4212
+ output.write(flags.json ? `${JSON.stringify(identity)}\n` : `Instance ID: ${identity.instanceId}\nDeep-link prefix: ${identity.deepLinkPrefix}\n`);
4213
+ };
4214
+ var createInstanceCommand = (output) => buildCommand({
4215
+ func: (flags) => runInstance(flags, output),
4216
+ parameters: {
4217
+ aliases: { p: "port" },
4218
+ flags: {
4219
+ json: {
4220
+ brief: "Print machine-readable JSON",
4221
+ kind: "boolean",
4222
+ optional: true
4223
+ },
4224
+ port: {
4225
+ brief: "Running local server port",
4226
+ kind: "parsed",
4227
+ optional: true,
4228
+ parse: String
4229
+ }
4230
+ },
4231
+ positional: {
4232
+ kind: "tuple",
4233
+ parameters: []
4234
+ }
4235
+ },
4236
+ docs: { brief: "Print the identity of a running Overmux server" }
4237
+ });
4238
+ //#endregion
4147
4239
  //#region src/internal/shared/routes.ts
4148
4240
  var viteHmrPath = "/_overmux/vite/hmr";
4149
4241
  //#endregion
@@ -4393,6 +4485,7 @@ var announceServeLogin = async ({ createLogin, port, process }) => {
4393
4485
  ...await issueLoginGrant(port),
4394
4486
  output: process
4395
4487
  });
4488
+ process.stdout.write(`\nNeed another grant? Run \`overmux auth login --port ${port}\`.\n`);
4396
4489
  };
4397
4490
  var runServe = async ({ environment, flags, process }) => {
4398
4491
  if (flags.noBuild && !flags.production) throw new Error("--no-build requires --production");
@@ -5053,17 +5146,17 @@ var detectCliEnvironment = async (process = globalThis.process) => {
5053
5146
  var createCliApp = (process = globalThis.process) => buildApplication(buildRouteMap({
5054
5147
  docs: { brief: "Initialize and manage Overmux applications" },
5055
5148
  routes: {
5056
- ai: createAiRoute(process),
5057
5149
  auth: authRoute,
5058
5150
  check: checkCommand,
5059
5151
  call: callCommand,
5060
5152
  desktop: desktopRoute,
5061
- docs: createDocsCommand({
5153
+ docs: createDocsRoute({
5062
5154
  docsRoot: resolveInstalledDocsRoot(import.meta.url),
5063
- output: process.stdout
5155
+ process
5064
5156
  }),
5065
5157
  init: createInitCommand(process),
5066
5158
  integration: integrationCommand,
5159
+ instance: createInstanceCommand(process.stdout),
5067
5160
  serve: serveCommand
5068
5161
  }
5069
5162
  }), {