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
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "`overmux docs`"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Overmux documentation and AI context commands
|
|
6
|
+
|
|
7
|
+
## `overmux docs path`
|
|
8
|
+
|
|
9
|
+
`overmux docs path` prints the installed Overmux CLI’s absolute documentation path.
|
|
10
|
+
|
|
11
|
+
```console
|
|
12
|
+
$ overmux docs path
|
|
13
|
+
/path/to/somewhere/node_modules/overmux/docs
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## `overmux docs ai-context`
|
|
17
|
+
|
|
18
|
+
`overmux docs ai-context` prints `CLAUDE.md`/`AGENTS.md` guidance for coding agents.
|
|
19
|
+
|
|
20
|
+
### Usage
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
overmux docs ai-context
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Prints the selected snippets shown in [available snippets](#available-snippets).
|
|
27
|
+
|
|
28
|
+
### Set up your coding agent
|
|
29
|
+
|
|
30
|
+
Tell your agent how to load Overmux guidance using `overmux docs ai-context`.
|
|
31
|
+
|
|
32
|
+
<CodeBlockTabs defaultValue="agents">
|
|
33
|
+
<CodeBlockTabsList>
|
|
34
|
+
<CodeBlockTabsTrigger value="agents">AGENTS.md</CodeBlockTabsTrigger>
|
|
35
|
+
<CodeBlockTabsTrigger value="claude">CLAUDE.md</CodeBlockTabsTrigger>
|
|
36
|
+
</CodeBlockTabsList>
|
|
37
|
+
<CodeBlockTab value="agents">
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
echo 'Run `overmux docs ai-context` for help configuring Overmux.' >> AGENTS.md
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
</CodeBlockTab>
|
|
44
|
+
<CodeBlockTab value="claude">
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
echo 'Run `overmux docs ai-context` for help configuring Overmux.' >> CLAUDE.md
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
</CodeBlockTab>
|
|
51
|
+
</CodeBlockTabs>
|
|
52
|
+
|
|
53
|
+
### Configure context snippets
|
|
54
|
+
|
|
55
|
+
Use `aiContextSnippets` in `overmux.config.ts` to select which snippets `overmux docs ai-context` prints.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import {
|
|
59
|
+
coreAiContextSnippets,
|
|
60
|
+
defineOvermuxConfig,
|
|
61
|
+
} from "overmux";
|
|
62
|
+
|
|
63
|
+
export default defineOvermuxConfig({
|
|
64
|
+
aiContextSnippets: coreAiContextSnippets,
|
|
65
|
+
// ...
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
When `aiContextSnippets` is omitted, Overmux uses `defaultAiContextSnippets`. Set it to an empty array to emit no context.
|
|
70
|
+
|
|
71
|
+
You can compose an explicit selection from the individual exports:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import {
|
|
75
|
+
defineOvermuxConfig,
|
|
76
|
+
packageSourceSnippet,
|
|
77
|
+
techStackRecommendationsSnippet,
|
|
78
|
+
} from "overmux";
|
|
79
|
+
|
|
80
|
+
export default defineOvermuxConfig({
|
|
81
|
+
aiContextSnippets: [
|
|
82
|
+
packageSourceSnippet,
|
|
83
|
+
techStackRecommendationsSnippet,
|
|
84
|
+
],
|
|
85
|
+
// ...
|
|
86
|
+
});
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Options
|
|
90
|
+
|
|
91
|
+
| Flag | Description | Default |
|
|
92
|
+
| --- | --- | --- |
|
|
93
|
+
| `--config <path>, -c <path>` | Configuration file | `$XDG_CONFIG_HOME/overmux/overmux.config.ts` |
|
|
94
|
+
|
|
95
|
+
### Snippet presets
|
|
96
|
+
|
|
97
|
+
[//]: # (BEGIN GENERATED AI CONTEXT PRESETS)
|
|
98
|
+
|
|
99
|
+
| Export | Included snippets |
|
|
100
|
+
| --- | --- |
|
|
101
|
+
| `defaultAiContextSnippets` | `package-source`, `tech-stack-recommendations` |
|
|
102
|
+
| `coreAiContextSnippets` | `package-source` |
|
|
103
|
+
|
|
104
|
+
[//]: # (END GENERATED AI CONTEXT PRESETS)
|
|
105
|
+
|
|
106
|
+
### Available snippets
|
|
107
|
+
|
|
108
|
+
[//]: # (BEGIN GENERATED AI CONTEXT)
|
|
109
|
+
|
|
110
|
+
#### `package-source`
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
Overmux ships with documentation and TypeScript source.
|
|
114
|
+
|
|
115
|
+
Run `overmux docs path` to find the installed documentation directory for the Overmux CLI. This prints a documentation directory, not source paths.
|
|
116
|
+
|
|
117
|
+
Inspect TypeScript source in installed packages, e.g. `node_modules/overmux/src` and `node_modules/@overmux/xterm/src`.
|
|
118
|
+
|
|
119
|
+
Inspect these files so guidance matches the versions used by the application.
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### `tech-stack-recommendations`
|
|
123
|
+
|
|
124
|
+
```text
|
|
125
|
+
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.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
[//]: # (END GENERATED AI CONTEXT)
|
package/docs/500-hosted-pages.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "overmux",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"homepage": "https://github.com/richardgill/overmux",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"web-push": "3.6.7",
|
|
53
53
|
"ws": "8.21.3",
|
|
54
54
|
"zod": "4.4.3",
|
|
55
|
-
"@overmux/keybindings": "0.0.
|
|
55
|
+
"@overmux/keybindings": "0.0.4"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@playwright/test": "1.58.0",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"react-dom": "19.2.3",
|
|
66
66
|
"vite": "8.2.2",
|
|
67
67
|
"vitest": "4.1.10",
|
|
68
|
+
"@overmux/ai-context": "0.0.1",
|
|
68
69
|
"@overmux/lib": "0.0.1",
|
|
69
70
|
"@overmux/shared": "0.0.1",
|
|
70
71
|
"@overmux/tsconfig": "0.0.1"
|
|
@@ -78,7 +79,7 @@
|
|
|
78
79
|
"access": "public"
|
|
79
80
|
},
|
|
80
81
|
"scripts": {
|
|
81
|
-
"build": "rm -rf dist && vite build && vite build --config vite.auth.config.ts && vp pack && node scripts/clean-declarations.ts && node scripts/add-client-styles.mjs && cp -R docs dist/docs",
|
|
82
|
+
"build": "node ../../../scripts/generate-ai-context-docs.ts && rm -rf dist && vite build && vite build --config vite.auth.config.ts && vp pack && node scripts/clean-declarations.ts && node scripts/add-client-styles.mjs && cp -R docs dist/docs",
|
|
82
83
|
"dev": "pnpm run dev:cli",
|
|
83
84
|
"dev:cli": "node --import jiti/register src/internal/cli/bin.ts serve",
|
|
84
85
|
"serve": "node dist/bin.js serve",
|
package/src/internal/cli/app.ts
CHANGED
|
@@ -6,12 +6,11 @@ import {
|
|
|
6
6
|
text_en,
|
|
7
7
|
} from "@stricli/core";
|
|
8
8
|
|
|
9
|
-
import { createAiRoute } from "./commands/ai";
|
|
10
9
|
import { authRoute } from "./commands/auth";
|
|
11
10
|
import { checkCommand } from "./commands/check";
|
|
12
11
|
import { callCommand } from "./commands/call";
|
|
13
12
|
import { desktopRoute } from "./commands/desktop";
|
|
14
|
-
import {
|
|
13
|
+
import { createDocsRoute, resolveInstalledDocsRoot } from "./commands/docs";
|
|
15
14
|
import { createInitCommand } from "./commands/init";
|
|
16
15
|
import { integrationCommand } from "./commands/integration";
|
|
17
16
|
import { createInstanceCommand } from "./commands/instance";
|
|
@@ -23,14 +22,13 @@ export const createCliApp = (process = globalThis.process) =>
|
|
|
23
22
|
buildRouteMap<string, CliCommandContext>({
|
|
24
23
|
docs: { brief: "Initialize and manage Overmux applications" },
|
|
25
24
|
routes: {
|
|
26
|
-
ai: createAiRoute(process),
|
|
27
25
|
auth: authRoute,
|
|
28
26
|
check: checkCommand,
|
|
29
27
|
call: callCommand,
|
|
30
28
|
desktop: desktopRoute,
|
|
31
|
-
docs:
|
|
29
|
+
docs: createDocsRoute({
|
|
32
30
|
docsRoot: resolveInstalledDocsRoot(import.meta.url),
|
|
33
|
-
|
|
31
|
+
process,
|
|
34
32
|
}),
|
|
35
33
|
init: createInitCommand(process),
|
|
36
34
|
integration: integrationCommand,
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { loadOvermuxConfig } from "@overmux/shared/node";
|
|
2
|
+
import { buildCommand } from "@stricli/core";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
composeAiContext,
|
|
6
|
+
defaultAiContextSnippets,
|
|
7
|
+
} from "../../../public/ai-context";
|
|
8
|
+
import { getDefaultConfigPath } from "../../server/paths";
|
|
9
|
+
|
|
10
|
+
type AiContextFlags = { config: string };
|
|
11
|
+
|
|
12
|
+
export const createAiContextCommand = (process: NodeJS.Process) =>
|
|
13
|
+
buildCommand({
|
|
14
|
+
func: async (flags: AiContextFlags) => {
|
|
15
|
+
const { config } = await loadOvermuxConfig({ configPath: flags.config });
|
|
16
|
+
process.stdout.write(
|
|
17
|
+
composeAiContext(config.aiContextSnippets ?? defaultAiContextSnippets),
|
|
18
|
+
);
|
|
19
|
+
},
|
|
20
|
+
parameters: {
|
|
21
|
+
aliases: { c: "config" },
|
|
22
|
+
flags: {
|
|
23
|
+
config: {
|
|
24
|
+
brief: "Configuration file",
|
|
25
|
+
default: getDefaultConfigPath(),
|
|
26
|
+
kind: "parsed",
|
|
27
|
+
parse: String,
|
|
28
|
+
placeholder: "path",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
docs: { brief: "Print the configured Overmux AI context" },
|
|
33
|
+
});
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import { buildCommand } from "@stricli/core";
|
|
1
|
+
import { buildCommand, buildRouteMap } from "@stricli/core";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
|
+
import { createAiContextCommand } from "./docs-ai-context";
|
|
7
|
+
|
|
6
8
|
type DocsOutput = { write: (text: string) => unknown };
|
|
7
9
|
|
|
8
10
|
export const resolveInstalledDocsRoot = (moduleUrl: string) => {
|
|
@@ -16,7 +18,7 @@ export const resolveInstalledDocsRoot = (moduleUrl: string) => {
|
|
|
16
18
|
return resolve(docsRoot);
|
|
17
19
|
};
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
const createDocsPathCommand = ({
|
|
20
22
|
docsRoot,
|
|
21
23
|
output,
|
|
22
24
|
}: {
|
|
@@ -32,3 +34,18 @@ export const createDocsCommand = ({
|
|
|
32
34
|
brief: "Print the installed documentation directory",
|
|
33
35
|
},
|
|
34
36
|
});
|
|
37
|
+
|
|
38
|
+
export const createDocsRoute = ({
|
|
39
|
+
docsRoot,
|
|
40
|
+
process,
|
|
41
|
+
}: {
|
|
42
|
+
docsRoot: string;
|
|
43
|
+
process: NodeJS.Process;
|
|
44
|
+
}) =>
|
|
45
|
+
buildRouteMap({
|
|
46
|
+
docs: { brief: "Find documentation and print coding-agent guidance" },
|
|
47
|
+
routes: {
|
|
48
|
+
path: createDocsPathCommand({ docsRoot, output: process.stdout }),
|
|
49
|
+
"ai-context": createAiContextCommand(process),
|
|
50
|
+
},
|
|
51
|
+
});
|
|
@@ -53,7 +53,7 @@ export const createInitScaffold = ({
|
|
|
53
53
|
}): Readonly<Record<string, string>> => ({
|
|
54
54
|
".gitignore": "node_modules\ndist\n",
|
|
55
55
|
"AGENTS.md":
|
|
56
|
-
"Immediately run `overmux ai
|
|
56
|
+
"Immediately run `overmux docs ai-context` for documentation about Overmux.\n",
|
|
57
57
|
"CLAUDE.md": "@AGENTS.md\n",
|
|
58
58
|
...(toolchain === "mise"
|
|
59
59
|
? {
|
|
@@ -184,6 +184,9 @@ const announceServeLogin = async ({
|
|
|
184
184
|
return;
|
|
185
185
|
}
|
|
186
186
|
printLoginGrant({ ...(await issueLoginGrant(port)), output: process });
|
|
187
|
+
process.stdout.write(
|
|
188
|
+
`\nNeed another grant? Run \`overmux auth login --port ${port}\`.\n`,
|
|
189
|
+
);
|
|
187
190
|
};
|
|
188
191
|
|
|
189
192
|
export const runServe = async ({
|
|
@@ -19,11 +19,9 @@ export const printLoginFallback = (port: number, { stdout }: CliOutput) => {
|
|
|
19
19
|
export const printLoginGrant = ({
|
|
20
20
|
login,
|
|
21
21
|
output,
|
|
22
|
-
port,
|
|
23
22
|
}: {
|
|
24
23
|
login: LoginGrant;
|
|
25
24
|
output: CliOutput;
|
|
26
|
-
port: number;
|
|
27
25
|
}) => {
|
|
28
26
|
const expiresIn = Math.max(
|
|
29
27
|
0,
|
|
@@ -31,16 +29,18 @@ export const printLoginGrant = ({
|
|
|
31
29
|
);
|
|
32
30
|
output.stdout.write(
|
|
33
31
|
[
|
|
34
|
-
`Code: ${login.code}`,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
32
|
+
`Code: ${login.code} (enter on the login page)`,
|
|
33
|
+
"",
|
|
34
|
+
...(login.urls.length === 1
|
|
35
|
+
? [`Login URL: ${login.urls[0]}`]
|
|
36
|
+
: ["Login URLs:", ...login.urls.map((url) => ` ${url}`)]),
|
|
37
|
+
"",
|
|
38
|
+
`Expiry in ${expiresIn} minutes (at ${login.expiresAt}).`,
|
|
39
|
+
"",
|
|
40
40
|
"",
|
|
41
41
|
].join("\n"),
|
|
42
42
|
);
|
|
43
43
|
output.stderr.write(
|
|
44
|
-
|
|
44
|
+
`Keep the code and link${login.urls.length === 1 ? "" : "s"} private.\n`,
|
|
45
45
|
);
|
|
46
46
|
};
|
|
@@ -6,11 +6,6 @@ import {
|
|
|
6
6
|
matchesKeyBinding,
|
|
7
7
|
type KeyBinding,
|
|
8
8
|
} from "@overmux/keybindings";
|
|
9
|
-
export {
|
|
10
|
-
formatKeyBinding,
|
|
11
|
-
keyBindingSchema,
|
|
12
|
-
matchesKeyBinding,
|
|
13
|
-
} from "@overmux/keybindings";
|
|
14
9
|
export type { KeyBinding } from "@overmux/keybindings";
|
|
15
10
|
import { z } from "zod";
|
|
16
11
|
|
|
@@ -28,18 +23,18 @@ export type ChordPrefix = {
|
|
|
28
23
|
unmatched: "replay-to-focused-input";
|
|
29
24
|
};
|
|
30
25
|
|
|
31
|
-
|
|
26
|
+
const shortcutBindingSchema = z.union([
|
|
32
27
|
keyBindingSchema,
|
|
33
28
|
z.array(keyBindingSchema).min(2),
|
|
34
29
|
]);
|
|
35
|
-
|
|
30
|
+
const commandBindingSchema = z.union([
|
|
36
31
|
shortcutBindingSchema,
|
|
37
32
|
z.object({
|
|
38
33
|
binding: shortcutBindingSchema,
|
|
39
34
|
when: z.object({ media: z.string().trim().min(1) }),
|
|
40
35
|
}),
|
|
41
36
|
]);
|
|
42
|
-
|
|
37
|
+
const chordPrefixSchema = z.object({
|
|
43
38
|
binding: keyBindingSchema,
|
|
44
39
|
unmatched: z.literal("replay-to-focused-input"),
|
|
45
40
|
});
|
|
@@ -132,6 +127,9 @@ export type ClientAppDefinition<
|
|
|
132
127
|
chordPrefixes?: readonly ChordPrefix[];
|
|
133
128
|
commands: TCommands;
|
|
134
129
|
component: ComponentType;
|
|
130
|
+
// Receives validated same-instance deep-link routes, including query and fragment.
|
|
131
|
+
// Defaults to document navigation on the current origin; supply your router to avoid a reload.
|
|
132
|
+
navigate?: (route: string) => unknown;
|
|
135
133
|
shortcutOverrides?: Partial<
|
|
136
134
|
Record<Extract<keyof TCommands, string>, readonly CommandBinding[]>
|
|
137
135
|
>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { parseDeepLink } from "@overmux/shared";
|
|
2
|
+
import type { ClientTransport } from "../transport";
|
|
3
|
+
import type {} from "./desktop-host";
|
|
4
|
+
|
|
5
|
+
const defaultNavigate = (route: string) => window.location.assign(route);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Intercepts overmux:// anchor activation inside the running app, including the
|
|
9
|
+
* temporary anchors created by xterm's default link handler. Without this,
|
|
10
|
+
* browser/PWA clicks dispatch the custom protocol to the OS, which fails in
|
|
11
|
+
* Android PWAs where it is not possible to install an Overmux protocol handler.
|
|
12
|
+
*
|
|
13
|
+
* Validates the original URL and compares its instance ID with authenticated
|
|
14
|
+
* runtime discovery. For same-instance links, preventDefault stops OS dispatch
|
|
15
|
+
* and navigate receives only the local path, query, and fragment. By default,
|
|
16
|
+
* this loads that route on the current origin; a supplied router callback avoids
|
|
17
|
+
* a document reload. Instance IDs are never treated as network hostnames.
|
|
18
|
+
*
|
|
19
|
+
* Valid cross-instance links remain Desktop's responsibility. Browsers/PWAs
|
|
20
|
+
* block unknown or different instances; all hosts block invalid links. Ordinary
|
|
21
|
+
* links and clicks already cancelled by application code are left untouched.
|
|
22
|
+
* Returns cleanup for the document listeners when the runtime unmounts.
|
|
23
|
+
*/
|
|
24
|
+
export const installDeepLinkNavigation = ({
|
|
25
|
+
getInstance,
|
|
26
|
+
navigate = defaultNavigate,
|
|
27
|
+
}: {
|
|
28
|
+
getInstance: ClientTransport["getInstance"];
|
|
29
|
+
navigate?: (route: string) => unknown;
|
|
30
|
+
}) => {
|
|
31
|
+
const onClick = (event: MouseEvent) => {
|
|
32
|
+
if (event.defaultPrevented || event.button > 1) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const anchor = event
|
|
36
|
+
.composedPath()
|
|
37
|
+
.find(
|
|
38
|
+
(target): target is HTMLAnchorElement =>
|
|
39
|
+
target instanceof HTMLAnchorElement,
|
|
40
|
+
);
|
|
41
|
+
if (anchor?.protocol !== "overmux:") {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
let link;
|
|
45
|
+
try {
|
|
46
|
+
// Inspect the original attribute before URL normalization can hide unsafe text.
|
|
47
|
+
link = parseDeepLink(anchor.getAttribute("href") ?? "");
|
|
48
|
+
} catch {
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
console.warn("Cannot open an invalid Overmux deep link.");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (link.instanceId !== getInstance()?.instanceId) {
|
|
54
|
+
// Desktop owns registry lookup and cross-instance confirmation. Browsers must
|
|
55
|
+
// never interpret an instance ID as a hostname or assume it names this server.
|
|
56
|
+
if (window.overmuxHost?.version !== 1) {
|
|
57
|
+
event.preventDefault();
|
|
58
|
+
console.warn(
|
|
59
|
+
"Cannot open an Overmux link to an unknown or different instance in this browser.",
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
event.preventDefault();
|
|
65
|
+
navigate(link.route);
|
|
66
|
+
};
|
|
67
|
+
// Bubble after application handlers so preventDefault remains an explicit opt-out.
|
|
68
|
+
// Read identity on activation, not installation: discovery and reconnects can change it.
|
|
69
|
+
document.addEventListener("click", onClick);
|
|
70
|
+
document.addEventListener("auxclick", onClick);
|
|
71
|
+
return () => {
|
|
72
|
+
document.removeEventListener("click", onClick);
|
|
73
|
+
document.removeEventListener("auxclick", onClick);
|
|
74
|
+
};
|
|
75
|
+
};
|
|
@@ -22,6 +22,7 @@ import { createOvermuxServerApi } from "../browser-api";
|
|
|
22
22
|
import { installBrowserLogForwarding } from "./browser-log-forwarding";
|
|
23
23
|
import { installNotificationForwarding } from "./notification-forwarding";
|
|
24
24
|
import { installInstanceForwarding } from "./instance-forwarding";
|
|
25
|
+
import { installDeepLinkNavigation } from "./deep-link-navigation";
|
|
25
26
|
import type { ClientAppDefinition } from "../client-definition";
|
|
26
27
|
import { type RegisteredCommand, RuntimeContext } from "../commands";
|
|
27
28
|
import { ShortcutHost } from "../shortcuts";
|
|
@@ -137,6 +138,14 @@ const OvermuxRuntime = ({
|
|
|
137
138
|
);
|
|
138
139
|
useEffect(() => installNotificationForwarding(transport), [transport]);
|
|
139
140
|
useEffect(() => installInstanceForwarding(transport), [transport]);
|
|
141
|
+
useEffect(
|
|
142
|
+
() =>
|
|
143
|
+
installDeepLinkNavigation({
|
|
144
|
+
getInstance: transport.getInstance,
|
|
145
|
+
navigate: definition.navigate,
|
|
146
|
+
}),
|
|
147
|
+
[transport, definition.navigate],
|
|
148
|
+
);
|
|
140
149
|
const refreshCommands = useCallback(
|
|
141
150
|
() => setRegistrationRevision((revision) => revision + 1),
|
|
142
151
|
[],
|
|
@@ -19,15 +19,9 @@ export type {
|
|
|
19
19
|
OvermuxThemeToken,
|
|
20
20
|
} from "./theme-scope";
|
|
21
21
|
export {
|
|
22
|
-
chordPrefixSchema,
|
|
23
|
-
commandBindingSchema,
|
|
24
22
|
defineCommandRegistry,
|
|
25
23
|
defineOvermuxClient,
|
|
26
|
-
formatKeyBinding,
|
|
27
24
|
formatShortcutBinding,
|
|
28
|
-
keyBindingSchema,
|
|
29
|
-
matchesKeyBinding,
|
|
30
|
-
shortcutBindingSchema,
|
|
31
25
|
} from "./client-definition";
|
|
32
26
|
export { skipToken, useCommand, useCommands } from "./commands";
|
|
33
27
|
export { useShortcutInputTarget } from "./shortcuts";
|
|
@@ -99,6 +99,10 @@ export type ResourceResult<T> =
|
|
|
99
99
|
};
|
|
100
100
|
|
|
101
101
|
export type StreamResult<TClientMessage, TServerMessage> = {
|
|
102
|
+
// Identifies one server-confirmed stream lifetime, not this hook or its stable methods.
|
|
103
|
+
// Undefined until stream-opened; reconnects and effect restarts receive a fresh identity.
|
|
104
|
+
// Replacement renders report opening, never readiness inherited from the old stream.
|
|
105
|
+
connectionId: symbol | undefined;
|
|
102
106
|
close: () => void;
|
|
103
107
|
error?: Error;
|
|
104
108
|
send: (message: TClientMessage) => boolean;
|
|
@@ -200,67 +204,94 @@ const useStreamById = <TClientMessage, TServerMessage>(
|
|
|
200
204
|
): StreamResult<TClientMessage, TServerMessage> => {
|
|
201
205
|
const { manifest, overmuxServerApi } = useRuntime();
|
|
202
206
|
const streamAvailable = manifest.streams.includes(id);
|
|
207
|
+
const canonicalInput = useCanonicalInput(input);
|
|
208
|
+
// This scope owns the stream-opening effect. Fast Refresh invalidates the memo
|
|
209
|
+
// and restarts that effect; the previous scope's open state is never exposed as
|
|
210
|
+
// readiness for the replacement, even before its passive effect has run.
|
|
211
|
+
const scope = useMemo(
|
|
212
|
+
() => ({ canonicalInput, id, overmuxServerApi, streamAvailable }),
|
|
213
|
+
[canonicalInput, id, overmuxServerApi, streamAvailable],
|
|
214
|
+
);
|
|
203
215
|
const [state, setState] = useState<{
|
|
216
|
+
scope: typeof scope;
|
|
217
|
+
connectionId?: symbol;
|
|
204
218
|
error?: Error;
|
|
205
219
|
status: "closed" | "open" | "opening";
|
|
206
|
-
}>({ status: "opening" });
|
|
207
|
-
|
|
220
|
+
}>({ scope, status: "opening" });
|
|
221
|
+
// Unlike memoized callbacks, state survives Fast Refresh. Existing consumers
|
|
222
|
+
// keep their subscriptions and methods while the real stream lifetime changes.
|
|
223
|
+
const [listeners] = useState(
|
|
208
224
|
() => new Set<(message: TServerMessage) => void>(),
|
|
209
|
-
[],
|
|
210
225
|
);
|
|
211
|
-
const canonicalInput = useCanonicalInput(input);
|
|
212
226
|
const streamRef = useRef<
|
|
213
227
|
ReturnType<typeof overmuxServerApi.openStream> | undefined
|
|
214
228
|
>(undefined);
|
|
215
229
|
useEffect(() => {
|
|
216
230
|
streamRef.current?.close();
|
|
217
231
|
streamRef.current = undefined;
|
|
218
|
-
if (!streamAvailable) {
|
|
219
|
-
setState({ status: "closed" });
|
|
232
|
+
if (!scope.streamAvailable) {
|
|
233
|
+
setState({ scope, status: "closed" });
|
|
220
234
|
return;
|
|
221
235
|
}
|
|
222
|
-
setState({ status: "opening" });
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
236
|
+
setState({ scope, status: "opening" });
|
|
237
|
+
let active = true;
|
|
238
|
+
const stream = scope.overmuxServerApi.openStream({
|
|
239
|
+
id: scope.id,
|
|
240
|
+
input: scope.canonicalInput,
|
|
241
|
+
onClose: () => active && setState({ scope, status: "closed" }),
|
|
242
|
+
onError: (error) =>
|
|
243
|
+
active && setState({ scope, error, status: "closed" }),
|
|
244
|
+
// The transport reuses its handle on reconnect, but acknowledges a new server stream.
|
|
245
|
+
onOpen: () =>
|
|
246
|
+
active &&
|
|
247
|
+
setState({ scope, connectionId: Symbol(scope.id), status: "open" }),
|
|
229
248
|
onMessage: (message) =>
|
|
249
|
+
active &&
|
|
230
250
|
listeners.forEach((listener) => listener(message as TServerMessage)),
|
|
231
251
|
});
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
252
|
+
const connection = {
|
|
253
|
+
close: () => {
|
|
254
|
+
if (!active) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
// Old callbacks must not overwrite the stream created by an effect restart (including HMR).
|
|
258
|
+
active = false;
|
|
259
|
+
if (streamRef.current === connection) {
|
|
260
|
+
streamRef.current = undefined;
|
|
261
|
+
}
|
|
262
|
+
setState({ scope, status: "closed" });
|
|
263
|
+
stream.close();
|
|
264
|
+
},
|
|
265
|
+
send: stream.send,
|
|
238
266
|
};
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
(message:
|
|
247
|
-
[],
|
|
248
|
-
);
|
|
249
|
-
const subscribe = useCallback(
|
|
250
|
-
(listener: (message: TServerMessage) => void) => {
|
|
267
|
+
streamRef.current = connection;
|
|
268
|
+
return connection.close;
|
|
269
|
+
}, [listeners, scope]);
|
|
270
|
+
const [methods] = useState(() => ({
|
|
271
|
+
close: () => streamRef.current?.close(),
|
|
272
|
+
send: (message: TClientMessage) =>
|
|
273
|
+
streamRef.current?.send(message) ?? false,
|
|
274
|
+
subscribe: (listener: (message: TServerMessage) => void) => {
|
|
251
275
|
listeners.add(listener);
|
|
252
276
|
return () => listeners.delete(listener);
|
|
253
277
|
},
|
|
254
|
-
|
|
255
|
-
)
|
|
278
|
+
}));
|
|
279
|
+
if (!streamAvailable) {
|
|
280
|
+
return {
|
|
281
|
+
...methods,
|
|
282
|
+
connectionId: undefined,
|
|
283
|
+
error: new Error(`Stream is not available: ${id}`),
|
|
284
|
+
status: "closed",
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
if (state.scope !== scope) {
|
|
288
|
+
return { ...methods, connectionId: undefined, status: "opening" };
|
|
289
|
+
}
|
|
256
290
|
return {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
send,
|
|
262
|
-
status: streamAvailable ? state.status : "closed",
|
|
263
|
-
subscribe,
|
|
291
|
+
...methods,
|
|
292
|
+
connectionId: state.connectionId,
|
|
293
|
+
error: state.error,
|
|
294
|
+
status: state.status,
|
|
264
295
|
};
|
|
265
296
|
};
|
|
266
297
|
|
|
@@ -129,7 +129,7 @@ const randomCode = () => {
|
|
|
129
129
|
const durationMilliseconds = (
|
|
130
130
|
duration: Exclude<
|
|
131
131
|
AuthConfigDefinition["sessionLifetime"],
|
|
132
|
-
"
|
|
132
|
+
"forever" | undefined
|
|
133
133
|
>,
|
|
134
134
|
) => {
|
|
135
135
|
const milliseconds = durationToMilliseconds(
|
|
@@ -357,7 +357,7 @@ export const createAuthService = ({
|
|
|
357
357
|
const token = randomToken();
|
|
358
358
|
const createdAt = new Date(now()).toISOString();
|
|
359
359
|
const configuredLifetime =
|
|
360
|
-
config.sessionLifetime && config.sessionLifetime !== "
|
|
360
|
+
config.sessionLifetime && config.sessionLifetime !== "forever"
|
|
361
361
|
? durationMilliseconds(config.sessionLifetime)
|
|
362
362
|
: undefined;
|
|
363
363
|
const expiresIn = lifetimeMs ?? configuredLifetime;
|