overmux 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +2 -2
- package/dist/auth/.vite/manifest.json +2 -2
- package/dist/auth/assets/{index-Bx8JhwVi.css → index-BM9gETvx.css} +1 -1
- package/dist/auth/assets/{index-CHIE7qYj.js → index-DabAfcs1.js} +1 -1
- package/dist/auth/index.html +2 -2
- package/dist/bin.js +219 -126
- 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/{docs/100-introduction/300-why-overmux.md → dist/docs/100-introduction/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/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/100-api.md +370 -0
- package/dist/docs/400-reference/600-client/200-theming.md +94 -0
- package/dist/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
- package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/050-init.md +2 -4
- package/dist/docs/400-reference/{400-cli → 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/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/300-call.md +1 -4
- package/dist/docs/400-reference/700-cli/350-instance.md +21 -0
- package/dist/docs/400-reference/700-cli/600-docs.md +128 -0
- package/{docs/400-reference/400-cli → dist/docs/400-reference/700-cli}/700-desktop.md +1 -3
- package/dist/docs/500-hosted-pages.md +0 -1
- package/dist/exports/client.d.ts +19 -15
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +240 -58
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/{index-D3_Dpy_T.d.ts → index-Cz3xkCa4.d.ts} +62 -36
- package/dist/exports/index-Cz3xkCa4.d.ts.map +1 -0
- package/dist/exports/index.d.ts +2 -2
- package/dist/exports/index.js +19 -5
- package/dist/exports/index.js.map +1 -1
- package/dist/exports/{notifications-kDp16bU_.js → notifications-BFAD3QQl.js} +34 -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 +8 -1018
- package/dist/exports/server.js.map +1 -1
- package/dist/internal/server/coordinator/server-child.js +153 -85
- 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/{dist/docs/100-introduction/300-why-overmux.md → docs/100-introduction/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/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/100-api.md +370 -0
- package/docs/400-reference/600-client/200-theming.md +94 -0
- package/docs/400-reference/600-client/300-tech-stack-recommendations.md +12 -0
- package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/050-init.md +2 -4
- package/docs/400-reference/{400-cli → 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/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/300-call.md +1 -4
- package/docs/400-reference/700-cli/350-instance.md +21 -0
- package/docs/400-reference/700-cli/600-docs.md +128 -0
- package/{dist/docs/400-reference/400-cli → docs/400-reference/700-cli}/700-desktop.md +1 -3
- package/docs/500-hosted-pages.md +0 -1
- package/package.json +3 -2
- package/src/internal/cli/app.ts +5 -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/instance.ts +58 -0
- package/src/internal/cli/commands/serve.ts +3 -0
- package/src/internal/cli/login.ts +9 -9
- package/src/internal/cli/parse-port.ts +2 -1
- package/src/internal/client/auth/auth-shell.css +17 -1
- package/src/internal/client/auth/auth-shell.tsx +2 -11
- package/src/internal/client/auth/login-code-input.tsx +140 -0
- package/src/internal/client/browser-api.ts +17 -3
- 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/desktop-host.ts +3 -0
- package/src/internal/client/host/environment.ts +67 -0
- package/src/internal/client/host/instance-forwarding.ts +21 -0
- package/src/internal/client/host/overmux-host.tsx +17 -0
- package/src/internal/client/index.ts +0 -6
- package/src/internal/client/overmux-react.ts +95 -42
- package/src/internal/client/websocket.ts +32 -12
- package/src/internal/server/auth/auth-service.ts +2 -2
- package/src/internal/server/auth/instance-control.ts +89 -17
- package/src/internal/server/coordinator/ipc-protocol.ts +0 -1
- package/src/internal/server/runtime/create-runtime.ts +15 -2
- package/src/internal/server/runtime/runtime-instance.ts +47 -0
- package/src/internal/server/runtime/runtime-operations.ts +9 -3
- package/src/internal/server/runtime/runtime-resources.ts +15 -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 +14 -14
- package/src/internal/server/start-application-server.ts +24 -19
- package/src/internal/server/websocket-server.ts +7 -0
- package/src/internal/shared/index.ts +2 -0
- package/src/internal/shared/protocol.ts +10 -1
- package/src/public/ai-context.ts +7 -29
- package/src/public/client.ts +1 -6
- package/src/public/config.ts +163 -31
- package/src/public/index.ts +2 -0
- 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 -3
- 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 -3
- package/dist/docs/400-reference/200-server-api.md +0 -3
- package/dist/docs/400-reference/300-client-api.md +0 -20
- 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-D3_Dpy_T.d.ts.map +0 -1
- package/dist/exports/notifications-kDp16bU_.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 -3
- 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 -3
- package/docs/400-reference/200-server-api.md +0 -3
- package/docs/400-reference/300-client-api.md +0 -20
- 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.5",
|
|
4
4
|
"homepage": "https://github.com/richardgill/overmux",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -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,14 +6,14 @@ 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";
|
|
16
|
+
import { createInstanceCommand } from "./commands/instance";
|
|
17
17
|
import { serveCommand } from "./commands/serve";
|
|
18
18
|
import { detectCliEnvironment, type CliCommandContext } from "./environment";
|
|
19
19
|
|
|
@@ -22,17 +22,17 @@ export const createCliApp = (process = globalThis.process) =>
|
|
|
22
22
|
buildRouteMap<string, CliCommandContext>({
|
|
23
23
|
docs: { brief: "Initialize and manage Overmux applications" },
|
|
24
24
|
routes: {
|
|
25
|
-
ai: createAiRoute(process),
|
|
26
25
|
auth: authRoute,
|
|
27
26
|
check: checkCommand,
|
|
28
27
|
call: callCommand,
|
|
29
28
|
desktop: desktopRoute,
|
|
30
|
-
docs:
|
|
29
|
+
docs: createDocsRoute({
|
|
31
30
|
docsRoot: resolveInstalledDocsRoot(import.meta.url),
|
|
32
|
-
|
|
31
|
+
process,
|
|
33
32
|
}),
|
|
34
33
|
init: createInitCommand(process),
|
|
35
34
|
integration: integrationCommand,
|
|
35
|
+
instance: createInstanceCommand(process.stdout),
|
|
36
36
|
serve: serveCommand,
|
|
37
37
|
},
|
|
38
38
|
}),
|
|
@@ -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
|
? {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { buildCommand } from "@stricli/core";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
discoverInstance,
|
|
5
|
+
sendControlRequest,
|
|
6
|
+
} from "../../server/auth/instance-control";
|
|
7
|
+
import { createInstanceIdentity } from "../../shared/index";
|
|
8
|
+
import { parsePort } from "../parse-port";
|
|
9
|
+
|
|
10
|
+
type InstanceFlags = { json?: boolean; port?: string };
|
|
11
|
+
|
|
12
|
+
// Read the already-resolved identity through the private same-user control socket.
|
|
13
|
+
// Config may have changed since startup, and re-evaluating it can repeat side effects.
|
|
14
|
+
// The running server resolved its identity once using the actual bound port.
|
|
15
|
+
const runInstance = async (
|
|
16
|
+
flags: InstanceFlags,
|
|
17
|
+
output: Pick<NodeJS.WriteStream, "write">,
|
|
18
|
+
) => {
|
|
19
|
+
const port = flags.port === undefined ? undefined : parsePort(flags.port);
|
|
20
|
+
if (port instanceof Error) {
|
|
21
|
+
throw port;
|
|
22
|
+
}
|
|
23
|
+
const instance = await discoverInstance(port);
|
|
24
|
+
const response = await sendControlRequest(instance, {
|
|
25
|
+
type: "get-instance",
|
|
26
|
+
});
|
|
27
|
+
const identity = createInstanceIdentity(response.instanceId);
|
|
28
|
+
output.write(
|
|
29
|
+
flags.json
|
|
30
|
+
? `${JSON.stringify(identity)}\n`
|
|
31
|
+
: `Instance ID: ${identity.instanceId}\nDeep-link prefix: ${identity.deepLinkPrefix}\n`,
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const createInstanceCommand = (
|
|
36
|
+
output: Pick<NodeJS.WriteStream, "write">,
|
|
37
|
+
) =>
|
|
38
|
+
buildCommand({
|
|
39
|
+
func: (flags: InstanceFlags) => runInstance(flags, output),
|
|
40
|
+
parameters: {
|
|
41
|
+
aliases: { p: "port" },
|
|
42
|
+
flags: {
|
|
43
|
+
json: {
|
|
44
|
+
brief: "Print machine-readable JSON",
|
|
45
|
+
kind: "boolean",
|
|
46
|
+
optional: true,
|
|
47
|
+
},
|
|
48
|
+
port: {
|
|
49
|
+
brief: "Running local server port",
|
|
50
|
+
kind: "parsed",
|
|
51
|
+
optional: true,
|
|
52
|
+
parse: String,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
positional: { kind: "tuple", parameters: [] },
|
|
56
|
+
},
|
|
57
|
+
docs: { brief: "Print the identity of a running Overmux server" },
|
|
58
|
+
});
|
|
@@ -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
|
};
|
|
@@ -2,7 +2,8 @@ import { serverConfigRuntimeSchema } from "@overmux/shared/node";
|
|
|
2
2
|
|
|
3
3
|
export const parsePort = (value: string): Error | number => {
|
|
4
4
|
const port = Number(value);
|
|
5
|
-
return
|
|
5
|
+
return port > 0 &&
|
|
6
|
+
serverConfigRuntimeSchema.shape.port.safeParse(port).success
|
|
6
7
|
? port
|
|
7
8
|
: new Error(`Invalid port: ${value}`);
|
|
8
9
|
};
|
|
@@ -46,10 +46,26 @@ body {
|
|
|
46
46
|
border: 1px solid #666;
|
|
47
47
|
background: #111;
|
|
48
48
|
color: inherit;
|
|
49
|
-
|
|
49
|
+
font-family: ui-monospace, monospace;
|
|
50
|
+
font-size: 1.25rem;
|
|
51
|
+
letter-spacing: 0.12em;
|
|
50
52
|
text-transform: uppercase;
|
|
51
53
|
}
|
|
52
54
|
|
|
55
|
+
[data-om-auth-shell] input[aria-invalid="true"] {
|
|
56
|
+
border-color: #ff9b9b;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#auth-code-error {
|
|
60
|
+
margin: 0;
|
|
61
|
+
color: #ff9b9b;
|
|
62
|
+
font-size: 0.875rem;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#auth-code-error:not(:empty) {
|
|
66
|
+
margin-top: 0.5rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
53
69
|
[data-om-auth-shell] button {
|
|
54
70
|
margin-top: 1rem;
|
|
55
71
|
border: 0;
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
// React Query is intentionally excluded to keep the public pre-authentication shell lightweight.
|
|
16
16
|
// Its mutations may still run concurrently, so it would not replace latest-request cancellation.
|
|
17
17
|
import { getAuthState, login, type LoginInput } from "./auth-api";
|
|
18
|
+
import { LoginCodeInput } from "./login-code-input";
|
|
18
19
|
import "./auth-shell.css";
|
|
19
20
|
|
|
20
21
|
type AuthStatus =
|
|
@@ -147,17 +148,7 @@ export const AuthShell = ({ ticket }: AuthShellProps) => {
|
|
|
147
148
|
</p>
|
|
148
149
|
<form aria-busy={pending} onSubmit={submitCode}>
|
|
149
150
|
<label htmlFor="auth-code">Login code</label>
|
|
150
|
-
<
|
|
151
|
-
aria-describedby="auth-status"
|
|
152
|
-
autoCapitalize="characters"
|
|
153
|
-
autoComplete="one-time-code"
|
|
154
|
-
disabled={pending}
|
|
155
|
-
id="auth-code"
|
|
156
|
-
name="code"
|
|
157
|
-
pattern="[A-Za-z0-9]{4}-[A-Za-z0-9]{4}"
|
|
158
|
-
required
|
|
159
|
-
spellCheck={false}
|
|
160
|
-
/>
|
|
151
|
+
<LoginCodeInput disabled={pending} />
|
|
161
152
|
<button disabled={pending} type="submit">
|
|
162
153
|
Unlock Overmux
|
|
163
154
|
</button>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { useState, type ClipboardEvent, type KeyboardEvent } from "react";
|
|
2
|
+
|
|
3
|
+
type LoginCodeInputProps = {
|
|
4
|
+
disabled: boolean;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const maxCodeLength = 8;
|
|
8
|
+
const codeError = "Enter 8 letters or numbers, with no spaces inside the code.";
|
|
9
|
+
|
|
10
|
+
const formatCode = (value: string) => {
|
|
11
|
+
const trimmed = value.trim();
|
|
12
|
+
const ungrouped = trimmed.replace("-", "");
|
|
13
|
+
if (!/^[A-Za-z0-9]*$/.test(ungrouped)) {
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
16
|
+
const characters = ungrouped.slice(0, maxCodeLength).toUpperCase();
|
|
17
|
+
return characters.length < 4
|
|
18
|
+
? characters
|
|
19
|
+
: `${characters.slice(0, 4)}-${characters.slice(4)}`;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const formatInput = (input: HTMLInputElement) => {
|
|
23
|
+
const { value, selectionStart, selectionEnd, selectionDirection } = input;
|
|
24
|
+
const formatted = formatCode(value);
|
|
25
|
+
if (formatted === value) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
// Formatting the prefix maps the native selection past an inserted dash without
|
|
29
|
+
// moving edits in the middle of the code to the end of the field.
|
|
30
|
+
const start = formatCode(
|
|
31
|
+
value.slice(0, selectionStart ?? value.length),
|
|
32
|
+
).length;
|
|
33
|
+
const end = formatCode(value.slice(0, selectionEnd ?? value.length)).length;
|
|
34
|
+
input.value = formatted;
|
|
35
|
+
input.setSelectionRange(start, end, selectionDirection ?? undefined);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const pasteCode = (event: ClipboardEvent<HTMLInputElement>) => {
|
|
39
|
+
event.preventDefault();
|
|
40
|
+
const input = event.currentTarget;
|
|
41
|
+
// Text inputs silently remove line breaks; preserve internal ones as invalid
|
|
42
|
+
// spaces rather than accidentally accepting a code with embedded whitespace.
|
|
43
|
+
const pasted = event.clipboardData
|
|
44
|
+
.getData("text/plain")
|
|
45
|
+
.trim()
|
|
46
|
+
.replace(/[\r\n]/g, " ");
|
|
47
|
+
// A whole code replaces both groups regardless of the caret. Reject oversized
|
|
48
|
+
// pastes before changing the field, including partial pastes that would overflow.
|
|
49
|
+
const replaceWholeCode = pasted.length >= maxCodeLength;
|
|
50
|
+
const start = replaceWholeCode ? 0 : (input.selectionStart ?? 0);
|
|
51
|
+
const end = replaceWholeCode ? input.value.length : (input.selectionEnd ?? 0);
|
|
52
|
+
const nextValue =
|
|
53
|
+
input.value.slice(0, start) + pasted + input.value.slice(end);
|
|
54
|
+
if (nextValue.trim().replace("-", "").length > maxCodeLength) {
|
|
55
|
+
input.setCustomValidity(codeError);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
input.setCustomValidity("");
|
|
59
|
+
input.setRangeText(pasted, start, end, "end");
|
|
60
|
+
formatInput(input);
|
|
61
|
+
return true;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const editSeparator = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
65
|
+
const input = event.currentTarget;
|
|
66
|
+
const caret = input.selectionStart;
|
|
67
|
+
if (
|
|
68
|
+
event.ctrlKey ||
|
|
69
|
+
event.metaKey ||
|
|
70
|
+
event.altKey ||
|
|
71
|
+
caret !== input.selectionEnd ||
|
|
72
|
+
input.value[4] !== "-"
|
|
73
|
+
) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (event.key === "-" && (caret === 4 || caret === 5)) {
|
|
77
|
+
event.preventDefault();
|
|
78
|
+
input.setSelectionRange(5, 5);
|
|
79
|
+
}
|
|
80
|
+
// Treat the separator as formatting: deletion at its boundary removes the
|
|
81
|
+
// adjacent character rather than restoring the dash without making progress.
|
|
82
|
+
if (event.key === "Backspace" && caret === 5) {
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
input.setRangeText("", 3, 5, "end");
|
|
85
|
+
formatInput(input);
|
|
86
|
+
}
|
|
87
|
+
if (event.key === "Delete" && caret === 4) {
|
|
88
|
+
event.preventDefault();
|
|
89
|
+
input.setRangeText("", 4, Math.min(6, input.value.length), "end");
|
|
90
|
+
formatInput(input);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const LoginCodeInput = ({ disabled }: LoginCodeInputProps) => {
|
|
95
|
+
const [invalid, setInvalid] = useState(false);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<>
|
|
99
|
+
<input
|
|
100
|
+
aria-describedby="auth-code-error auth-status"
|
|
101
|
+
aria-invalid={invalid || undefined}
|
|
102
|
+
autoCapitalize="characters"
|
|
103
|
+
autoComplete="one-time-code"
|
|
104
|
+
disabled={disabled}
|
|
105
|
+
id="auth-code"
|
|
106
|
+
maxLength={maxCodeLength + 1}
|
|
107
|
+
name="code"
|
|
108
|
+
onBlur={(event) => formatInput(event.currentTarget)}
|
|
109
|
+
onInput={(event) => {
|
|
110
|
+
event.currentTarget.setCustomValidity("");
|
|
111
|
+
// Do not discard a typed space before learning whether it is internal.
|
|
112
|
+
if (!/\s$/.test(event.currentTarget.value)) {
|
|
113
|
+
formatInput(event.currentTarget);
|
|
114
|
+
}
|
|
115
|
+
setInvalid(false);
|
|
116
|
+
}}
|
|
117
|
+
onInvalid={() => setInvalid(true)}
|
|
118
|
+
onKeyDown={(event) => {
|
|
119
|
+
if (event.key === "Enter") {
|
|
120
|
+
formatInput(event.currentTarget);
|
|
121
|
+
}
|
|
122
|
+
editSeparator(event);
|
|
123
|
+
if (event.defaultPrevented) {
|
|
124
|
+
event.currentTarget.setCustomValidity("");
|
|
125
|
+
setInvalid(false);
|
|
126
|
+
}
|
|
127
|
+
}}
|
|
128
|
+
onPaste={(event) => setInvalid(!pasteCode(event))}
|
|
129
|
+
pattern="[A-Za-z0-9]{4}-[A-Za-z0-9]{4}"
|
|
130
|
+
placeholder="XXXX-XXXX"
|
|
131
|
+
required
|
|
132
|
+
spellCheck={false}
|
|
133
|
+
type="text"
|
|
134
|
+
/>
|
|
135
|
+
<p aria-live="polite" id="auth-code-error">
|
|
136
|
+
{invalid ? codeError : undefined}
|
|
137
|
+
</p>
|
|
138
|
+
</>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { RuntimeManifest } from "../shared/index";
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
ContractInputArguments,
|
|
4
|
+
InstanceIdentity,
|
|
5
|
+
} from "../../public/index";
|
|
3
6
|
import type { z } from "zod";
|
|
4
7
|
|
|
5
8
|
import type { ClientTransport } from "./transport";
|
|
@@ -25,14 +28,21 @@ type OperationArguments<TInput extends z.ZodType> = [
|
|
|
25
28
|
signal?: AbortSignal,
|
|
26
29
|
];
|
|
27
30
|
|
|
28
|
-
|
|
31
|
+
type InstanceGetters = {
|
|
32
|
+
getInstanceId: () => string | undefined;
|
|
33
|
+
getDeepLinkPrefix: () => string | undefined;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export type OvermuxServerApi<TServerConfig> = InstanceGetters & {
|
|
29
37
|
executeOperation: <TId extends OperationId<TServerConfig>>(
|
|
30
38
|
id: TId,
|
|
31
39
|
...args: OperationArguments<OperationInput<TServerConfig, TId>>
|
|
32
40
|
) => Promise<OperationOutput<TServerConfig, TId>>;
|
|
33
41
|
};
|
|
34
42
|
|
|
35
|
-
export type RuntimeOvermuxServerApi = {
|
|
43
|
+
export type RuntimeOvermuxServerApi = InstanceGetters & {
|
|
44
|
+
getInstance: () => InstanceIdentity | undefined;
|
|
45
|
+
subscribeInstance: (listener: () => void) => () => void;
|
|
36
46
|
executeOperation: (
|
|
37
47
|
id: string,
|
|
38
48
|
input?: unknown,
|
|
@@ -73,6 +83,10 @@ export const createOvermuxServerApi = ({
|
|
|
73
83
|
manifest: RuntimeManifest;
|
|
74
84
|
transport: ClientTransport;
|
|
75
85
|
}): RuntimeOvermuxServerApi => ({
|
|
86
|
+
getInstance: transport.getInstance,
|
|
87
|
+
subscribeInstance: transport.subscribeInstance,
|
|
88
|
+
getInstanceId: () => transport.getInstance()?.instanceId,
|
|
89
|
+
getDeepLinkPrefix: () => transport.getInstance()?.deepLinkPrefix,
|
|
76
90
|
executeOperation: (id, input, signal) => {
|
|
77
91
|
if (!manifest.operations.includes(id)) {
|
|
78
92
|
throw new Error(`Operation is not available: ${id}`);
|