overmux 0.0.4 → 0.0.6

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 (151) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +16 -2
  3. package/dist/bin.js +140 -127
  4. package/dist/bin.js.map +1 -1
  5. package/dist/docs/000-index.md +2 -4
  6. package/dist/docs/100-introduction/200-how-overmux-works.md +126 -2
  7. package/dist/docs/100-introduction/{300-why-overmux.md → 300-why-i-built-overmux.md} +1 -1
  8. package/dist/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
  9. package/dist/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
  10. package/dist/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
  11. package/dist/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
  12. package/dist/docs/200-getting-started/500-set-up-overmux-with-packages.md +42 -0
  13. package/dist/docs/400-reference/100-project-structure.md +83 -0
  14. package/dist/docs/400-reference/200-configuration.md +257 -0
  15. package/dist/docs/400-reference/300-storage-locations.md +46 -0
  16. package/dist/docs/400-reference/400-authentication-and-security.md +37 -0
  17. package/dist/docs/400-reference/500-server/000-index.md +13 -0
  18. package/dist/docs/400-reference/500-server/100-resources.md +191 -0
  19. package/dist/docs/400-reference/500-server/200-operations.md +111 -0
  20. package/dist/docs/400-reference/500-server/300-streams.md +140 -0
  21. package/dist/docs/400-reference/500-server/400-notifications.md +32 -0
  22. package/dist/docs/400-reference/500-server/500-api.md +118 -0
  23. package/dist/docs/400-reference/600-client/000-index.md +9 -0
  24. package/dist/docs/400-reference/600-client/005-setting-up-your-ui.md +137 -0
  25. package/dist/docs/400-reference/600-client/007-deep-links.md +87 -0
  26. package/dist/docs/400-reference/600-client/010-commands.md +189 -0
  27. package/dist/docs/400-reference/600-client/020-shortcuts.md +171 -0
  28. package/dist/docs/400-reference/600-client/100-api.md +370 -0
  29. package/dist/docs/400-reference/600-client/200-theming.md +209 -0
  30. package/dist/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
  31. package/dist/docs/400-reference/{400-cli → 700-cli}/050-init.md +2 -4
  32. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/100-serve.md +9 -4
  33. package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/200-auth.md +1 -4
  34. package/dist/docs/400-reference/{400-cli → 700-cli}/300-call.md +1 -4
  35. package/dist/docs/400-reference/{400-cli → 700-cli}/350-instance.md +1 -4
  36. package/dist/docs/400-reference/700-cli/600-docs.md +128 -0
  37. package/dist/docs/400-reference/{400-cli → 700-cli}/700-desktop.md +1 -3
  38. package/dist/docs/500-hosted-pages.md +0 -1
  39. package/dist/exports/client.d.ts +5 -14
  40. package/dist/exports/client.d.ts.map +1 -1
  41. package/dist/exports/client.js +141 -46
  42. package/dist/exports/client.js.map +1 -1
  43. package/dist/exports/{index-DS70rKzo.d.ts → index-Cz3xkCa4.d.ts} +50 -36
  44. package/dist/exports/index-Cz3xkCa4.d.ts.map +1 -0
  45. package/dist/exports/index.d.ts +1 -1
  46. package/dist/exports/index.js +19 -5
  47. package/dist/exports/index.js.map +1 -1
  48. package/dist/exports/{notifications-av0FK0yZ.js → notifications-BFAD3QQl.js} +29 -4
  49. package/dist/exports/notifications-BFAD3QQl.js.map +1 -0
  50. package/dist/exports/server.d.ts +1 -44
  51. package/dist/exports/server.d.ts.map +1 -1
  52. package/dist/exports/server.js +9 -1061
  53. package/dist/exports/server.js.map +1 -1
  54. package/dist/internal/server/coordinator/server-child.js +47 -43
  55. package/dist/internal/server/coordinator/server-child.js.map +1 -1
  56. package/docs/000-index.md +2 -4
  57. package/docs/100-introduction/200-how-overmux-works.md +126 -2
  58. package/docs/100-introduction/{300-why-overmux.md → 300-why-i-built-overmux.md} +1 -1
  59. package/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
  60. package/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
  61. package/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
  62. package/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
  63. package/docs/200-getting-started/500-set-up-overmux-with-packages.md +42 -0
  64. package/docs/400-reference/100-project-structure.md +83 -0
  65. package/docs/400-reference/200-configuration.md +257 -0
  66. package/docs/400-reference/300-storage-locations.md +46 -0
  67. package/docs/400-reference/400-authentication-and-security.md +37 -0
  68. package/docs/400-reference/500-server/000-index.md +13 -0
  69. package/docs/400-reference/500-server/100-resources.md +191 -0
  70. package/docs/400-reference/500-server/200-operations.md +111 -0
  71. package/docs/400-reference/500-server/300-streams.md +140 -0
  72. package/docs/400-reference/500-server/400-notifications.md +32 -0
  73. package/docs/400-reference/500-server/500-api.md +118 -0
  74. package/docs/400-reference/600-client/000-index.md +9 -0
  75. package/docs/400-reference/600-client/005-setting-up-your-ui.md +137 -0
  76. package/docs/400-reference/600-client/007-deep-links.md +87 -0
  77. package/docs/400-reference/600-client/010-commands.md +189 -0
  78. package/docs/400-reference/600-client/020-shortcuts.md +171 -0
  79. package/docs/400-reference/600-client/100-api.md +370 -0
  80. package/docs/400-reference/600-client/200-theming.md +209 -0
  81. package/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
  82. package/docs/400-reference/{400-cli → 700-cli}/050-init.md +2 -4
  83. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/100-serve.md +9 -4
  84. package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/200-auth.md +1 -4
  85. package/docs/400-reference/{400-cli → 700-cli}/300-call.md +1 -4
  86. package/docs/400-reference/{400-cli → 700-cli}/350-instance.md +1 -4
  87. package/docs/400-reference/700-cli/600-docs.md +128 -0
  88. package/docs/400-reference/{400-cli → 700-cli}/700-desktop.md +1 -3
  89. package/docs/500-hosted-pages.md +0 -1
  90. package/package.json +4 -3
  91. package/src/internal/cli/app.ts +3 -5
  92. package/src/internal/cli/commands/docs-ai-context.ts +33 -0
  93. package/src/internal/cli/commands/docs.ts +19 -2
  94. package/src/internal/cli/commands/init-template.ts +1 -1
  95. package/src/internal/cli/commands/serve.ts +3 -0
  96. package/src/internal/cli/login.ts +9 -9
  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/overmux-host.tsx +9 -0
  100. package/src/internal/client/index.ts +0 -6
  101. package/src/internal/client/overmux-react.ts +71 -40
  102. package/src/internal/server/auth/auth-service.ts +2 -2
  103. package/src/internal/server/auth/instance-control.ts +65 -14
  104. package/src/internal/server/coordinator/ipc-protocol.ts +0 -1
  105. package/src/internal/server/runtime/create-runtime.ts +5 -3
  106. package/src/internal/server/runtime/runtime-instance.ts +5 -4
  107. package/src/internal/server/runtime/runtime-operations.ts +9 -3
  108. package/src/internal/server/runtime/runtime-resources.ts +12 -32
  109. package/src/internal/server/runtime/runtime-streams.ts +5 -6
  110. package/src/internal/server/server-logger.ts +5 -10
  111. package/src/internal/server/server-startup-options.ts +5 -10
  112. package/src/internal/server/start-application-server.ts +4 -7
  113. package/src/public/ai-context.ts +7 -29
  114. package/src/public/client.ts +0 -6
  115. package/src/public/config.ts +156 -31
  116. package/src/public/server.ts +1 -16
  117. package/dist/docs/100-introduction/100-what-is-overmux.md +0 -7
  118. package/dist/docs/300-fundamentals/100-project-structure.md +0 -23
  119. package/dist/docs/300-fundamentals/200-configuration.md +0 -3
  120. package/dist/docs/300-fundamentals/300-theming.md +0 -54
  121. package/dist/docs/300-fundamentals/400-server.md +0 -3
  122. package/dist/docs/300-fundamentals/500-client.md +0 -3
  123. package/dist/docs/300-fundamentals/600-operations.md +0 -3
  124. package/dist/docs/300-fundamentals/700-resources.md +0 -3
  125. package/dist/docs/300-fundamentals/800-streams.md +0 -3
  126. package/dist/docs/300-fundamentals/900-authentication-and-security.md +0 -3
  127. package/dist/docs/400-reference/100-configuration.md +0 -23
  128. package/dist/docs/400-reference/200-server-api.md +0 -21
  129. package/dist/docs/400-reference/300-client-api.md +0 -39
  130. package/dist/docs/400-reference/400-cli/400-check.md +0 -20
  131. package/dist/docs/400-reference/400-cli/500-ai-context.md +0 -102
  132. package/dist/docs/400-reference/400-cli/600-docs.md +0 -23
  133. package/dist/exports/index-DS70rKzo.d.ts.map +0 -1
  134. package/dist/exports/notifications-av0FK0yZ.js.map +0 -1
  135. package/docs/100-introduction/100-what-is-overmux.md +0 -7
  136. package/docs/300-fundamentals/100-project-structure.md +0 -23
  137. package/docs/300-fundamentals/200-configuration.md +0 -3
  138. package/docs/300-fundamentals/300-theming.md +0 -54
  139. package/docs/300-fundamentals/400-server.md +0 -3
  140. package/docs/300-fundamentals/500-client.md +0 -3
  141. package/docs/300-fundamentals/600-operations.md +0 -3
  142. package/docs/300-fundamentals/700-resources.md +0 -3
  143. package/docs/300-fundamentals/800-streams.md +0 -3
  144. package/docs/300-fundamentals/900-authentication-and-security.md +0 -3
  145. package/docs/400-reference/100-configuration.md +0 -23
  146. package/docs/400-reference/200-server-api.md +0 -21
  147. package/docs/400-reference/300-client-api.md +0 -39
  148. package/docs/400-reference/400-cli/400-check.md +0 -20
  149. package/docs/400-reference/400-cli/500-ai-context.md +0 -102
  150. package/docs/400-reference/400-cli/600-docs.md +0 -23
  151. package/src/internal/cli/commands/ai.ts +0 -44
@@ -1,17 +1,14 @@
1
1
  import { createHash, randomBytes } from "node:crypto";
2
- import { chmod, mkdir, readdir, rm } from "node:fs/promises";
2
+ import { constants } from "node:fs";
3
+ import { chmod, mkdir, open, readdir, rm } from "node:fs/promises";
3
4
  import { createConnection, createServer, type Socket } from "node:net";
4
- import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import { z } from "zod";
7
7
 
8
8
  import { instanceIdSchema } from "@overmux/shared";
9
9
 
10
10
  import { getOvermuxPaths } from "../paths";
11
- import {
12
- readPrivateJson,
13
- replacePrivateJsonAtomically,
14
- } from "../notifications/private-json-file";
11
+ import { replacePrivateJsonAtomically } from "../notifications/private-json-file";
15
12
  import type { AuthService } from "./auth-service";
16
13
 
17
14
  const maxControlMessageBytes = 16_384;
@@ -94,9 +91,20 @@ type ControlResponseByType = {
94
91
  >;
95
92
  };
96
93
 
97
- const runtimeDirectory = () => {
98
- const paths = getOvermuxPaths();
99
- const preferred = paths.runtimeDir ?? join(paths.stateDir, "runtime");
94
+ const currentUid = () => {
95
+ if (!process.getuid) {
96
+ throw new Error("Overmux instance control requires a Unix user ID");
97
+ }
98
+ return process.getuid();
99
+ };
100
+
101
+ export const runtimeDirectory = () => {
102
+ const fallback = `/tmp/overmux-${currentUid()}`;
103
+ const preferred = getOvermuxPaths().runtimeDir;
104
+ if (!preferred) {
105
+ return fallback;
106
+ }
107
+ // Leave room for the slash, 16-character ID and .sock on macOS and Linux.
100
108
  if (Buffer.byteLength(preferred) <= 80) {
101
109
  return preferred;
102
110
  }
@@ -104,7 +112,51 @@ const runtimeDirectory = () => {
104
112
  .update(preferred)
105
113
  .digest("hex")
106
114
  .slice(0, 12);
107
- return join(tmpdir(), `overmux-${process.getuid?.() ?? "user"}-${suffix}`);
115
+ return `${fallback}-${suffix}`;
116
+ };
117
+
118
+ const secureRuntimeDirectory = async (directory: string) => {
119
+ // Only the final component must not be a symlink: macOS /tmp is a symlink.
120
+ // Validate and chmod the same inode, never a path that could be swapped
121
+ // between checking ownership and changing permissions. The sticky /tmp
122
+ // parent (or user-controlled XDG parent) then protects our owned directory
123
+ // from replacement by other users. Same-user processes are already trusted.
124
+ const handle = await open(
125
+ directory,
126
+ constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW,
127
+ );
128
+ try {
129
+ const metadata = await handle.stat();
130
+ if (!metadata.isDirectory() || metadata.uid !== currentUid()) {
131
+ throw new Error(`Unsafe Overmux runtime directory: ${directory}`);
132
+ }
133
+ await handle.chmod(0o700);
134
+ } finally {
135
+ await handle.close();
136
+ }
137
+ };
138
+
139
+ const readRegistration = async (path: string) => {
140
+ // An existing directory may have been writable before we secured it. Do not
141
+ // trust planted symlinks, foreign-owned files or publicly writable records.
142
+ // O_NONBLOCK also prevents a planted FIFO from hanging discovery on open.
143
+ const handle = await open(
144
+ path,
145
+ constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK,
146
+ );
147
+ try {
148
+ const metadata = await handle.stat();
149
+ if (
150
+ !metadata.isFile() ||
151
+ metadata.uid !== currentUid() ||
152
+ (metadata.mode & 0o077) !== 0
153
+ ) {
154
+ return undefined;
155
+ }
156
+ return registrationSchema.parse(JSON.parse(await handle.readFile("utf8")));
157
+ } finally {
158
+ await handle.close();
159
+ }
108
160
  };
109
161
 
110
162
  const readNewlineFrame = ({
@@ -245,7 +297,7 @@ export const startInstanceControl = async ({
245
297
  }): Promise<InstanceControl> => {
246
298
  const directory = runtimeDirectory();
247
299
  await mkdir(directory, { mode: 0o700, recursive: true });
248
- await chmod(directory, 0o700);
300
+ await secureRuntimeDirectory(directory);
249
301
  const id = randomBytes(8).toString("hex");
250
302
  const socketPath = join(directory, `${id}.sock`);
251
303
  const registrationPath = join(directory, `${id}.json`);
@@ -310,6 +362,7 @@ export const discoverInstance = async (
310
362
  const directory = runtimeDirectory();
311
363
  let names: string[];
312
364
  try {
365
+ await secureRuntimeDirectory(directory);
313
366
  names = await readdir(directory);
314
367
  } catch (cause) {
315
368
  if ((cause as NodeJS.ErrnoException).code === "ENOENT") {
@@ -322,9 +375,7 @@ export const discoverInstance = async (
322
375
  names
323
376
  .filter((name) => name.endsWith(".json"))
324
377
  .map((name) =>
325
- readPrivateJson(join(directory, name), registrationSchema).catch(
326
- () => undefined,
327
- ),
378
+ readRegistration(join(directory, name)).catch(() => undefined),
328
379
  ),
329
380
  )
330
381
  ).filter((registration): registration is InstanceRegistration =>
@@ -9,7 +9,6 @@ const childOptionsSchema = z.object({
9
9
  debug: z.boolean().optional(),
10
10
  developmentWebTarget: z.url().optional(),
11
11
  host: z.string().optional(),
12
- logFile: z.string().optional(),
13
12
  port: z.number().int().min(0).max(65_535).optional(),
14
13
  productionWebAssetsDir: z.string().optional(),
15
14
  watch: z.boolean().optional(),
@@ -7,7 +7,7 @@ import type {
7
7
  InstanceIdentity,
8
8
  Notifications,
9
9
  } from "../../../public/index";
10
- import { createRuntimeInstance } from "./runtime-instance";
10
+ import { createRuntimeInstance, defaultInstanceId } from "./runtime-instance";
11
11
  import { protocolVersion, type RuntimeManifest } from "../../shared/index";
12
12
 
13
13
  import { createRuntimeLifecycle } from "./runtime-lifecycle";
@@ -46,12 +46,14 @@ type CreateRuntimeOptions = {
46
46
 
47
47
  export const createRuntime = async ({
48
48
  config,
49
- debug = false,
49
+ debug = true,
50
50
  notifications = { send: async () => undefined },
51
51
  signal,
52
52
  }: CreateRuntimeOptions): Promise<Runtime> => {
53
53
  const lifecycle = createRuntimeLifecycle(signal);
54
- const instance = createRuntimeInstance(config.instanceId);
54
+ const instance = createRuntimeInstance(
55
+ config.instanceId ?? defaultInstanceId,
56
+ );
55
57
  try {
56
58
  const resources = createRuntimeResources({
57
59
  definitions: (config.server.resources ??
@@ -6,10 +6,13 @@ import {
6
6
  } from "../../shared/index";
7
7
  import type { InstanceContext } from "../../../public/config";
8
8
 
9
+ export const defaultInstanceId = ({ port }: { port: number }) =>
10
+ `${hostname()}-${port}`;
11
+
9
12
  // The scope exists during preparation, but identity is immutable once the listener
10
13
  // binds. No handler or connection may observe it before establishment.
11
14
  export const createRuntimeInstance = (
12
- configured: RuntimeConfigDefinition["instanceId"],
15
+ configured: NonNullable<RuntimeConfigDefinition["instanceId"]>,
13
16
  ) => {
14
17
  let identity: InstanceIdentity | undefined;
15
18
  const get = () => {
@@ -30,9 +33,7 @@ export const createRuntimeInstance = (
30
33
  }
31
34
  try {
32
35
  identity = createInstanceIdentity(
33
- typeof configured === "function"
34
- ? configured({ port })
35
- : (configured ?? `${hostname()}-${port}`),
36
+ typeof configured === "function" ? configured({ port }) : configured,
36
37
  );
37
38
  } catch (cause) {
38
39
  throw new Error(
@@ -2,12 +2,14 @@
2
2
  // Lookup metadata stays aligned with the names published in the runtime manifest.
3
3
 
4
4
  import type {
5
- HandlerContext,
5
+ OperationContext,
6
6
  OperationDefinition,
7
7
  } from "../../../public/index";
8
8
  import type { Notifications } from "../../../public/index";
9
9
  import { z } from "zod";
10
10
 
11
+ import type { RuntimeHandlerContext } from "./runtime-resources";
12
+
11
13
  export class OperationValidationError extends Error {
12
14
  constructor(
13
15
  readonly phase: "input" | "output",
@@ -27,14 +29,18 @@ export type RuntimeOperations = {
27
29
  names: readonly string[];
28
30
  };
29
31
 
30
- type AnyOperation = OperationDefinition<z.ZodTypeAny, z.ZodTypeAny>;
32
+ type AnyOperation = OperationDefinition<
33
+ z.ZodTypeAny,
34
+ z.ZodTypeAny,
35
+ OperationContext<Record<string, unknown>>
36
+ >;
31
37
 
32
38
  export type RuntimeOperationDefinitions = Readonly<
33
39
  Record<string, AnyOperation>
34
40
  >;
35
41
 
36
42
  type CreateRuntimeOperationsOptions = {
37
- context: (signal?: AbortSignal) => HandlerContext;
43
+ context: (signal?: AbortSignal) => RuntimeHandlerContext;
38
44
  definitions: RuntimeOperationDefinitions;
39
45
  notifications: Notifications;
40
46
  };
@@ -5,7 +5,6 @@ import {
5
5
  type ResourceDefinition,
6
6
  type RuntimeDisposer,
7
7
  } from "../../../public/index";
8
- import type { ResourceContract } from "../../../public/index";
9
8
  import { isDeepStrictEqual } from "node:util";
10
9
 
11
10
  import {
@@ -27,10 +26,12 @@ export type RuntimeResourceDefinitions = Readonly<
27
26
  Record<string, ResourceDefinition>
28
27
  >;
29
28
 
29
+ export type RuntimeHandlerContext = HandlerContext<Record<string, unknown>>;
30
+
30
31
  export type RuntimeResources = {
31
- context: (signal?: AbortSignal) => HandlerContext;
32
+ context: (signal?: AbortSignal) => RuntimeHandlerContext;
32
33
  get: (name: string) => RuntimeResource | undefined;
33
- invalidate: (contract: ResourceContract, input?: unknown) => void;
34
+ invalidate: RuntimeHandlerContext["invalidate"];
34
35
  names: readonly string[];
35
36
  };
36
37
 
@@ -53,16 +54,8 @@ export const createRuntimeResources = ({
53
54
  }: CreateRuntimeResourcesOptions): RuntimeResources => {
54
55
  const graph = createResourceDependencyGraph(definitions);
55
56
  const invalidationListeners = new Set<InvalidationListener>();
56
- const contracts = new Map<ResourceContract, string[]>();
57
57
  const resources = new Map<string, RuntimeResource>();
58
58
 
59
- Object.entries(definitions).forEach(([id, definition]) => {
60
- contracts.set(definition.contract, [
61
- ...(contracts.get(definition.contract) ?? []),
62
- id,
63
- ]);
64
- });
65
-
66
59
  const invalidateResource = (
67
60
  id: string,
68
61
  input: unknown,
@@ -82,30 +75,17 @@ export const createRuntimeResources = ({
82
75
  });
83
76
  };
84
77
 
85
- const invalidate = (contract: ResourceContract, input?: unknown) => {
86
- const ids =
87
- contracts.get(contract) ??
88
- Object.entries(definitions)
89
- .filter(
90
- ([, definition]) =>
91
- definition.contract.input === contract.input &&
92
- definition.contract.output === contract.output,
93
- )
94
- .map(([id]) => id);
95
- if (!ids.length) {
96
- throw new Error("Cannot invalidate a resource outside this runtime");
78
+ const invalidate = (resourceId: string, input?: unknown) => {
79
+ if (!resources.has(resourceId)) {
80
+ throw new Error(`Unknown resource ID: ${resourceId}`);
97
81
  }
98
- ids.forEach((id) => {
99
- const definition = definitions[id]!;
100
- const parsedInput =
101
- input === undefined
102
- ? undefined
103
- : definition.contract.input.parse(input);
104
- invalidateResource(id, parsedInput, input === undefined);
105
- });
82
+ const definition = definitions[resourceId]!;
83
+ const parsedInput =
84
+ input === undefined ? undefined : definition.contract.input.parse(input);
85
+ invalidateResource(resourceId, parsedInput, input === undefined);
106
86
  };
107
87
 
108
- const context = (signal?: AbortSignal): HandlerContext => ({
88
+ const context = (signal?: AbortSignal): RuntimeHandlerContext => ({
109
89
  instance,
110
90
  invalidate,
111
91
  signal: lifecycle.requestSignal(signal),
@@ -1,13 +1,11 @@
1
1
  // Owns named stream registration and ordered, validated session messaging.
2
2
  // Session abort, failure, and disposal remain exactly-once across every close path.
3
3
 
4
- import type {
5
- HandlerContext,
6
- StreamHandlerDefinition,
7
- } from "../../../public/index";
4
+ import type { StreamHandlerDefinition } from "../../../public/index";
8
5
  import { z } from "zod";
9
6
 
10
7
  import type { RuntimeLifecycle } from "./runtime-lifecycle";
8
+ import type { RuntimeHandlerContext } from "./runtime-resources";
11
9
 
12
10
  export type RuntimeStreamSession = {
13
11
  dispose: () => Promise<void>;
@@ -31,13 +29,14 @@ export type RuntimeStreams = {
31
29
  type AnyStream = StreamHandlerDefinition<
32
30
  z.ZodTypeAny,
33
31
  z.ZodTypeAny,
34
- z.ZodTypeAny
32
+ z.ZodTypeAny,
33
+ RuntimeHandlerContext
35
34
  >;
36
35
 
37
36
  export type RuntimeStreamDefinitions = Readonly<Record<string, AnyStream>>;
38
37
 
39
38
  type CreateRuntimeStreamsOptions = {
40
- context: (signal?: AbortSignal) => HandlerContext;
39
+ context: (signal?: AbortSignal) => RuntimeHandlerContext;
41
40
  definitions: RuntimeStreamDefinitions;
42
41
  lifecycle: RuntimeLifecycle;
43
42
  };
@@ -26,7 +26,6 @@ export type ServerLogger = {
26
26
 
27
27
  type ConfigurableServerLogger = ServerLogger & {
28
28
  setEnabled: (enabled: boolean) => void;
29
- setLogFile: (logFile: string) => void;
30
29
  };
31
30
 
32
31
  const lifecycleEvents = new Set([
@@ -38,7 +37,7 @@ const lifecycleEvents = new Set([
38
37
  "websocket-open",
39
38
  ]);
40
39
 
41
- export const getDefaultServerLogFile = ({
40
+ export const getServerLogFile = ({
42
41
  homeDir,
43
42
  xdgStateHome,
44
43
  }: {
@@ -62,12 +61,11 @@ const safeJson = (value: unknown) => {
62
61
 
63
62
  export const createServerLogger = ({
64
63
  enabled,
65
- logFile = getDefaultServerLogFile(),
64
+ logFile,
66
65
  }: {
67
66
  enabled: boolean;
68
- logFile?: string;
67
+ logFile: string;
69
68
  }): ConfigurableServerLogger => {
70
- let activeLogFile = logFile;
71
69
  let debugEnabled = enabled;
72
70
  return {
73
71
  get enabled() {
@@ -88,16 +86,13 @@ export const createServerLogger = ({
88
86
  source: entry.source ?? "server",
89
87
  ...entry,
90
88
  });
91
- mkdirSync(dirname(activeLogFile), { recursive: true });
92
- appendFileSync(activeLogFile, `${record}\n`, "utf8");
89
+ mkdirSync(dirname(logFile), { recursive: true });
90
+ appendFileSync(logFile, `${record}\n`, "utf8");
93
91
  process.stderr.write(`[overmux:debug] ${record}\n`);
94
92
  },
95
93
  setEnabled: (nextEnabled) => {
96
94
  debugEnabled = nextEnabled;
97
95
  },
98
- setLogFile: (nextLogFile) => {
99
- activeLogFile = nextLogFile;
100
- },
101
96
  };
102
97
  };
103
98
 
@@ -10,13 +10,14 @@ import type { ServerConfigDefinition } from "../../public/index";
10
10
  const serverStartupOverridesRuntimeSchema = serverConfigRuntimeSchema
11
11
  .omit({ port: true })
12
12
  .extend({
13
+ debug: z.boolean().optional(),
13
14
  // Port 0 asks the OS for a free port for internal listeners and tests; it must not leak into user config.
14
15
  port: z.number().int().min(0).max(65_535).optional(),
15
16
  });
16
17
 
17
18
  export type ResolvedServerStartupOptions = {
19
+ debug: boolean;
18
20
  host: string;
19
- logFile?: string;
20
21
  port: number;
21
22
  productionWebAssetsDir?: string;
22
23
  watch: boolean;
@@ -29,16 +30,10 @@ export const resolveServerStartupOptions = ({
29
30
  }: {
30
31
  config: RuntimeConfigDefinition;
31
32
  configPath: string;
32
- overrides?: ServerConfigDefinition;
33
+ overrides?: ServerConfigDefinition & { debug?: boolean };
33
34
  }): ResolvedServerStartupOptions => {
34
35
  const validatedOverrides =
35
36
  serverStartupOverridesRuntimeSchema.parse(overrides);
36
- const configuredLogFile = config.logFile
37
- ? resolve(dirname(configPath), config.logFile)
38
- : undefined;
39
- const logFile = validatedOverrides.logFile
40
- ? resolve(validatedOverrides.logFile)
41
- : configuredLogFile;
42
37
  const configuredWebAssetsDir = config.productionWebAssetsDir
43
38
  ? resolve(dirname(configPath), config.productionWebAssetsDir)
44
39
  : undefined;
@@ -47,9 +42,9 @@ export const resolveServerStartupOptions = ({
47
42
  : configuredWebAssetsDir;
48
43
 
49
44
  return {
45
+ debug: validatedOverrides.debug ?? config.debug ?? true,
50
46
  host: validatedOverrides.host ?? config.host ?? "localhost",
51
- ...(logFile === undefined ? {} : { logFile }),
52
- port: validatedOverrides.port ?? config.port ?? 4200,
47
+ port: validatedOverrides.port ?? config.port ?? 4242,
53
48
  ...(productionWebAssetsDir === undefined ? {} : { productionWebAssetsDir }),
54
49
  watch: validatedOverrides.watch ?? config.watch ?? true,
55
50
  };
@@ -29,6 +29,7 @@ import { createRuntime, type Runtime } from "./runtime/create-runtime";
29
29
  import {
30
30
  createServerLogger,
31
31
  errorDetails,
32
+ getServerLogFile,
32
33
  type ServerLogger,
33
34
  } from "./server-logger";
34
35
  import {
@@ -40,7 +41,6 @@ import type { OvermuxServerOptions } from "./start-overmux-server";
40
41
  type ResourceCleanup = () => void | Promise<void>;
41
42
  type ServerResources = ReturnType<typeof createResources>;
42
43
  type NotificationService = ReturnType<typeof createNotificationService>;
43
- type ApplicationLogger = ReturnType<typeof createServerLogger>;
44
44
  type StartApplicationServerOptions = {
45
45
  onRestartRequested: () => void;
46
46
  options: OvermuxServerOptions;
@@ -361,8 +361,8 @@ export const startApplicationServer = async ({
361
361
  options,
362
362
  }: StartApplicationServerOptions): Promise<ApplicationServer> => {
363
363
  const logger = createServerLogger({
364
- enabled: options.debug ?? false,
365
- logFile: options.logFile ? resolve(options.logFile) : undefined,
364
+ enabled: options.debug ?? true,
365
+ logFile: getServerLogFile(),
366
366
  });
367
367
  const resources = createResources();
368
368
  resources.add(registerProcessErrorLogging(logger));
@@ -374,10 +374,7 @@ export const startApplicationServer = async ({
374
374
  configPath,
375
375
  options,
376
376
  });
377
- logger.setEnabled(Boolean(options.debug || config.debug));
378
- if (startup.logFile) {
379
- logger.setLogFile(startup.logFile);
380
- }
377
+ logger.setEnabled(startup.debug);
381
378
  const {
382
379
  auth,
383
380
  authOrigins,
@@ -1,38 +1,16 @@
1
1
  import {
2
- packageSourceSnippet,
3
- techStackRecommendationsSnippet,
4
- type AiContextSnippet,
5
- } from "@overmux/shared";
2
+ aiContextContentBySnippet,
3
+ type AiContextSnippetDefinition,
4
+ } from "@overmux/ai-context";
6
5
 
7
6
  export {
7
+ coreAiContextSnippets,
8
+ defaultAiContextSnippets,
8
9
  packageSourceSnippet,
9
10
  techStackRecommendationsSnippet,
10
11
  type AiContextSnippet,
11
- };
12
- export type AiContextSnippetDefinition = readonly AiContextSnippet[];
13
-
14
- export const coreAiContextSnippets = [
15
- packageSourceSnippet,
16
- ] as const satisfies AiContextSnippetDefinition;
17
- export const defaultAiContextSnippets = [
18
- ...coreAiContextSnippets,
19
- techStackRecommendationsSnippet,
20
- ] as const satisfies AiContextSnippetDefinition;
21
-
22
- const packageSourceSnippetContent = `Overmux ships with documentation and TypeScript source.
23
-
24
- Run \`overmux docs\` to find the installed documentation and source for the overmux cli.
25
-
26
- E.g. \`node_modules/overmux\` and \`node_modules/@overmux/xterm\`.
27
-
28
- Inspect these files so guidance matches the versions used by the application.`;
29
-
30
- const 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.`;
31
-
32
- const aiContextContentBySnippet: Record<AiContextSnippet, string> = {
33
- [packageSourceSnippet]: packageSourceSnippetContent,
34
- [techStackRecommendationsSnippet]: techStackRecommendationsSnippetContent,
35
- };
12
+ type AiContextSnippetDefinition,
13
+ } from "@overmux/ai-context";
36
14
 
37
15
  export const composeAiContext = (snippets: AiContextSnippetDefinition) =>
38
16
  snippets.length === 0
@@ -28,15 +28,9 @@ export type {
28
28
  OvermuxThemeToken,
29
29
  } from "../internal/client/theme-scope";
30
30
  export {
31
- chordPrefixSchema,
32
- commandBindingSchema,
33
31
  defineCommandRegistry,
34
32
  defineOvermuxClient,
35
- formatKeyBinding,
36
33
  formatShortcutBinding,
37
- keyBindingSchema,
38
- matchesKeyBinding,
39
- shortcutBindingSchema,
40
34
  } from "../internal/client/client-definition";
41
35
  export {
42
36
  skipToken,