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.
- package/CHANGELOG.md +43 -0
- package/README.md +16 -2
- package/dist/bin.js +140 -127
- package/dist/bin.js.map +1 -1
- package/dist/docs/000-index.md +2 -4
- package/dist/docs/100-introduction/200-how-overmux-works.md +126 -2
- package/dist/docs/100-introduction/{300-why-overmux.md → 300-why-i-built-overmux.md} +1 -1
- package/dist/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
- package/dist/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
- package/dist/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
- package/dist/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
- package/dist/docs/200-getting-started/500-set-up-overmux-with-packages.md +42 -0
- package/dist/docs/400-reference/100-project-structure.md +83 -0
- package/dist/docs/400-reference/200-configuration.md +257 -0
- package/dist/docs/400-reference/300-storage-locations.md +46 -0
- package/dist/docs/400-reference/400-authentication-and-security.md +37 -0
- package/dist/docs/400-reference/500-server/000-index.md +13 -0
- package/dist/docs/400-reference/500-server/100-resources.md +191 -0
- package/dist/docs/400-reference/500-server/200-operations.md +111 -0
- package/dist/docs/400-reference/500-server/300-streams.md +140 -0
- package/dist/docs/400-reference/500-server/400-notifications.md +32 -0
- package/dist/docs/400-reference/500-server/500-api.md +118 -0
- package/dist/docs/400-reference/600-client/000-index.md +9 -0
- package/dist/docs/400-reference/600-client/005-setting-up-your-ui.md +137 -0
- package/dist/docs/400-reference/600-client/007-deep-links.md +87 -0
- package/dist/docs/400-reference/600-client/010-commands.md +189 -0
- package/dist/docs/400-reference/600-client/020-shortcuts.md +171 -0
- package/dist/docs/400-reference/600-client/100-api.md +370 -0
- package/dist/docs/400-reference/600-client/200-theming.md +209 -0
- package/dist/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
- package/dist/docs/400-reference/{400-cli → 700-cli}/050-init.md +2 -4
- package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/100-serve.md +9 -4
- package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/200-auth.md +1 -4
- package/dist/docs/400-reference/{400-cli → 700-cli}/300-call.md +1 -4
- package/dist/docs/400-reference/{400-cli → 700-cli}/350-instance.md +1 -4
- package/dist/docs/400-reference/700-cli/600-docs.md +128 -0
- package/dist/docs/400-reference/{400-cli → 700-cli}/700-desktop.md +1 -3
- package/dist/docs/500-hosted-pages.md +0 -1
- package/dist/exports/client.d.ts +5 -14
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +141 -46
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/{index-DS70rKzo.d.ts → index-Cz3xkCa4.d.ts} +50 -36
- package/dist/exports/index-Cz3xkCa4.d.ts.map +1 -0
- package/dist/exports/index.d.ts +1 -1
- package/dist/exports/index.js +19 -5
- package/dist/exports/index.js.map +1 -1
- package/dist/exports/{notifications-av0FK0yZ.js → notifications-BFAD3QQl.js} +29 -4
- package/dist/exports/notifications-BFAD3QQl.js.map +1 -0
- package/dist/exports/server.d.ts +1 -44
- package/dist/exports/server.d.ts.map +1 -1
- package/dist/exports/server.js +9 -1061
- package/dist/exports/server.js.map +1 -1
- package/dist/internal/server/coordinator/server-child.js +47 -43
- package/dist/internal/server/coordinator/server-child.js.map +1 -1
- package/docs/000-index.md +2 -4
- package/docs/100-introduction/200-how-overmux-works.md +126 -2
- package/docs/100-introduction/{300-why-overmux.md → 300-why-i-built-overmux.md} +1 -1
- package/docs/200-getting-started/100-install-and-run-overmux.md +2 -2
- package/docs/200-getting-started/400-secure-with-https/200-tailscale-serve.md +3 -3
- package/docs/200-getting-started/400-secure-with-https/300-cloudflare-tunnel.md +2 -2
- package/docs/200-getting-started/400-secure-with-https/400-self-hosted-reverse-proxy.md +2 -2
- package/docs/200-getting-started/500-set-up-overmux-with-packages.md +42 -0
- package/docs/400-reference/100-project-structure.md +83 -0
- package/docs/400-reference/200-configuration.md +257 -0
- package/docs/400-reference/300-storage-locations.md +46 -0
- package/docs/400-reference/400-authentication-and-security.md +37 -0
- package/docs/400-reference/500-server/000-index.md +13 -0
- package/docs/400-reference/500-server/100-resources.md +191 -0
- package/docs/400-reference/500-server/200-operations.md +111 -0
- package/docs/400-reference/500-server/300-streams.md +140 -0
- package/docs/400-reference/500-server/400-notifications.md +32 -0
- package/docs/400-reference/500-server/500-api.md +118 -0
- package/docs/400-reference/600-client/000-index.md +9 -0
- package/docs/400-reference/600-client/005-setting-up-your-ui.md +137 -0
- package/docs/400-reference/600-client/007-deep-links.md +87 -0
- package/docs/400-reference/600-client/010-commands.md +189 -0
- package/docs/400-reference/600-client/020-shortcuts.md +171 -0
- package/docs/400-reference/600-client/100-api.md +370 -0
- package/docs/400-reference/600-client/200-theming.md +209 -0
- package/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
- package/docs/400-reference/{400-cli → 700-cli}/050-init.md +2 -4
- package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/100-serve.md +9 -4
- package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/200-auth.md +1 -4
- package/docs/400-reference/{400-cli → 700-cli}/300-call.md +1 -4
- package/docs/400-reference/{400-cli → 700-cli}/350-instance.md +1 -4
- package/docs/400-reference/700-cli/600-docs.md +128 -0
- package/docs/400-reference/{400-cli → 700-cli}/700-desktop.md +1 -3
- package/docs/500-hosted-pages.md +0 -1
- package/package.json +4 -3
- package/src/internal/cli/app.ts +3 -5
- package/src/internal/cli/commands/docs-ai-context.ts +33 -0
- package/src/internal/cli/commands/docs.ts +19 -2
- package/src/internal/cli/commands/init-template.ts +1 -1
- package/src/internal/cli/commands/serve.ts +3 -0
- package/src/internal/cli/login.ts +9 -9
- package/src/internal/client/client-definition.ts +6 -8
- package/src/internal/client/host/deep-link-navigation.ts +75 -0
- package/src/internal/client/host/overmux-host.tsx +9 -0
- package/src/internal/client/index.ts +0 -6
- package/src/internal/client/overmux-react.ts +71 -40
- package/src/internal/server/auth/auth-service.ts +2 -2
- package/src/internal/server/auth/instance-control.ts +65 -14
- package/src/internal/server/coordinator/ipc-protocol.ts +0 -1
- package/src/internal/server/runtime/create-runtime.ts +5 -3
- package/src/internal/server/runtime/runtime-instance.ts +5 -4
- package/src/internal/server/runtime/runtime-operations.ts +9 -3
- package/src/internal/server/runtime/runtime-resources.ts +12 -32
- package/src/internal/server/runtime/runtime-streams.ts +5 -6
- package/src/internal/server/server-logger.ts +5 -10
- package/src/internal/server/server-startup-options.ts +5 -10
- package/src/internal/server/start-application-server.ts +4 -7
- package/src/public/ai-context.ts +7 -29
- package/src/public/client.ts +0 -6
- package/src/public/config.ts +156 -31
- package/src/public/server.ts +1 -16
- package/dist/docs/100-introduction/100-what-is-overmux.md +0 -7
- package/dist/docs/300-fundamentals/100-project-structure.md +0 -23
- package/dist/docs/300-fundamentals/200-configuration.md +0 -3
- package/dist/docs/300-fundamentals/300-theming.md +0 -54
- package/dist/docs/300-fundamentals/400-server.md +0 -3
- package/dist/docs/300-fundamentals/500-client.md +0 -3
- package/dist/docs/300-fundamentals/600-operations.md +0 -3
- package/dist/docs/300-fundamentals/700-resources.md +0 -3
- package/dist/docs/300-fundamentals/800-streams.md +0 -3
- package/dist/docs/300-fundamentals/900-authentication-and-security.md +0 -3
- package/dist/docs/400-reference/100-configuration.md +0 -23
- package/dist/docs/400-reference/200-server-api.md +0 -21
- package/dist/docs/400-reference/300-client-api.md +0 -39
- package/dist/docs/400-reference/400-cli/400-check.md +0 -20
- package/dist/docs/400-reference/400-cli/500-ai-context.md +0 -102
- package/dist/docs/400-reference/400-cli/600-docs.md +0 -23
- package/dist/exports/index-DS70rKzo.d.ts.map +0 -1
- package/dist/exports/notifications-av0FK0yZ.js.map +0 -1
- package/docs/100-introduction/100-what-is-overmux.md +0 -7
- package/docs/300-fundamentals/100-project-structure.md +0 -23
- package/docs/300-fundamentals/200-configuration.md +0 -3
- package/docs/300-fundamentals/300-theming.md +0 -54
- package/docs/300-fundamentals/400-server.md +0 -3
- package/docs/300-fundamentals/500-client.md +0 -3
- package/docs/300-fundamentals/600-operations.md +0 -3
- package/docs/300-fundamentals/700-resources.md +0 -3
- package/docs/300-fundamentals/800-streams.md +0 -3
- package/docs/300-fundamentals/900-authentication-and-security.md +0 -3
- package/docs/400-reference/100-configuration.md +0 -23
- package/docs/400-reference/200-server-api.md +0 -21
- package/docs/400-reference/300-client-api.md +0 -39
- package/docs/400-reference/400-cli/400-check.md +0 -20
- package/docs/400-reference/400-cli/500-ai-context.md +0 -102
- package/docs/400-reference/400-cli/600-docs.md +0 -23
- package/src/internal/cli/commands/ai.ts +0 -44
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
-
import {
|
|
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
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
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<
|
|
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) =>
|
|
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) =>
|
|
32
|
+
context: (signal?: AbortSignal) => RuntimeHandlerContext;
|
|
32
33
|
get: (name: string) => RuntimeResource | undefined;
|
|
33
|
-
invalidate:
|
|
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 = (
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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):
|
|
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) =>
|
|
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
|
|
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
|
|
64
|
+
logFile,
|
|
66
65
|
}: {
|
|
67
66
|
enabled: boolean;
|
|
68
|
-
logFile
|
|
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(
|
|
92
|
-
appendFileSync(
|
|
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
|
-
|
|
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 ??
|
|
365
|
-
logFile:
|
|
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(
|
|
378
|
-
if (startup.logFile) {
|
|
379
|
-
logger.setLogFile(startup.logFile);
|
|
380
|
-
}
|
|
377
|
+
logger.setEnabled(startup.debug);
|
|
381
378
|
const {
|
|
382
379
|
auth,
|
|
383
380
|
authOrigins,
|
package/src/public/ai-context.ts
CHANGED
|
@@ -1,38 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
package/src/public/client.ts
CHANGED
|
@@ -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,
|