tmux-ide 1.3.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/bin/cli.js +4 -216
  2. package/bin/cli.ts +536 -0
  3. package/package.json +27 -14
  4. package/src/__tests__/support.ts +186 -0
  5. package/src/attach.ts +20 -0
  6. package/src/cli.test.ts +314 -0
  7. package/src/command-center/discovery.test.ts +265 -0
  8. package/src/command-center/discovery.ts +296 -0
  9. package/src/command-center/index.ts +31 -0
  10. package/src/command-center/schemas.ts +57 -0
  11. package/src/command-center/server.test.ts +818 -0
  12. package/src/command-center/server.ts +1426 -0
  13. package/src/config-cli.test.ts +86 -0
  14. package/src/config.test.ts +205 -0
  15. package/src/config.ts +313 -0
  16. package/src/detect.test.ts +142 -0
  17. package/src/detect.ts +248 -0
  18. package/src/dispatch.ts +102 -0
  19. package/src/doctor.test.ts +119 -0
  20. package/src/doctor.ts +167 -0
  21. package/src/init.test.ts +101 -0
  22. package/src/init.ts +144 -0
  23. package/src/inspect.test.ts +67 -0
  24. package/src/inspect.ts +212 -0
  25. package/src/integration.test.ts.skip +276 -0
  26. package/src/js-yaml.d.ts +10 -0
  27. package/src/launch.test.ts +222 -0
  28. package/src/launch.ts +494 -0
  29. package/src/lib/auth/auth-service.test.ts +178 -0
  30. package/src/lib/auth/auth-service.ts +227 -0
  31. package/src/lib/auth/middleware.test.ts +119 -0
  32. package/src/lib/auth/middleware.ts +56 -0
  33. package/src/lib/auth/types.ts +13 -0
  34. package/src/lib/authorship.test.ts +242 -0
  35. package/src/lib/authorship.ts +280 -0
  36. package/src/lib/cast/recorder.test.ts +127 -0
  37. package/src/lib/cast/recorder.ts +148 -0
  38. package/src/lib/daemon-watchdog.ts +102 -0
  39. package/src/lib/daemon.ts +417 -0
  40. package/src/lib/dot-path.test.ts +66 -0
  41. package/src/lib/dot-path.ts +17 -0
  42. package/src/lib/errors.ts +44 -0
  43. package/src/lib/event-log.test.ts +189 -0
  44. package/src/lib/event-log.ts +237 -0
  45. package/src/lib/github-pr.ts +182 -0
  46. package/src/lib/hq/client.test.ts +154 -0
  47. package/src/lib/hq/client.ts +142 -0
  48. package/src/lib/hq/mdns.test.ts +54 -0
  49. package/src/lib/hq/mdns.ts +88 -0
  50. package/src/lib/hq/registry.test.ts +237 -0
  51. package/src/lib/hq/registry.ts +145 -0
  52. package/src/lib/hq/types.ts +44 -0
  53. package/src/lib/ipc/socket-protocol.test.ts +126 -0
  54. package/src/lib/ipc/socket-protocol.ts +324 -0
  55. package/src/lib/launch-plan.test.ts +119 -0
  56. package/src/lib/launch-plan.ts +90 -0
  57. package/src/lib/log.test.ts +39 -0
  58. package/src/lib/log.ts +71 -0
  59. package/src/lib/metrics.test.ts +213 -0
  60. package/src/lib/metrics.ts +345 -0
  61. package/src/lib/orchestrator.test.ts +2358 -0
  62. package/src/lib/orchestrator.ts +1792 -0
  63. package/src/lib/output.ts +76 -0
  64. package/src/lib/plan-store.test.ts +148 -0
  65. package/src/lib/plan-store.ts +131 -0
  66. package/src/lib/research.test.ts +208 -0
  67. package/src/lib/research.ts +475 -0
  68. package/src/lib/round-trip.test.ts +341 -0
  69. package/src/lib/session-monitor.test.ts +152 -0
  70. package/src/lib/session-monitor.ts +282 -0
  71. package/src/lib/session-options.test.ts +146 -0
  72. package/src/lib/session-options.ts +100 -0
  73. package/src/lib/shell.test.ts +20 -0
  74. package/src/lib/shell.ts +8 -0
  75. package/src/lib/sizes.test.ts +73 -0
  76. package/src/lib/sizes.ts +36 -0
  77. package/src/lib/skill-registry.test.ts +134 -0
  78. package/src/lib/skill-registry.ts +86 -0
  79. package/src/lib/slugify.test.ts +35 -0
  80. package/src/lib/slugify.ts +10 -0
  81. package/src/lib/task-store.ts +352 -0
  82. package/src/lib/tmux.test.ts +500 -0
  83. package/src/lib/tmux.ts +372 -0
  84. package/src/lib/token-tracker.test.ts +102 -0
  85. package/src/lib/token-tracker.ts +70 -0
  86. package/src/lib/tunnels/cloudflare.test.ts +73 -0
  87. package/src/lib/tunnels/cloudflare.ts +254 -0
  88. package/src/lib/tunnels/manager.test.ts +63 -0
  89. package/src/lib/tunnels/manager.ts +132 -0
  90. package/src/lib/tunnels/ngrok.test.ts +94 -0
  91. package/src/lib/tunnels/ngrok.ts +261 -0
  92. package/src/lib/tunnels/tailscale.test.ts +103 -0
  93. package/src/lib/tunnels/tailscale.ts +1048 -0
  94. package/src/lib/tunnels/types.ts +44 -0
  95. package/src/lib/validation.test.ts +289 -0
  96. package/src/lib/validation.ts +123 -0
  97. package/src/lib/webhook.ts +51 -0
  98. package/src/lib/workflow-store.ts +209 -0
  99. package/src/lib/ws-v3/protocol.test.ts +127 -0
  100. package/src/lib/ws-v3/protocol.ts +160 -0
  101. package/src/lib/yaml-io.test.ts +125 -0
  102. package/src/lib/yaml-io.ts +27 -0
  103. package/src/ls.test.ts +76 -0
  104. package/src/ls.ts +40 -0
  105. package/src/metrics-cli.ts +177 -0
  106. package/src/notify.ts +85 -0
  107. package/src/orchestrator-status.ts +228 -0
  108. package/src/plan.ts +117 -0
  109. package/src/postinstall.test.ts +79 -0
  110. package/src/remote.test.ts +115 -0
  111. package/src/remote.ts +148 -0
  112. package/src/restart.ts +24 -0
  113. package/src/schemas/domain.ts +422 -0
  114. package/src/schemas/ide-config.ts +148 -0
  115. package/src/schemas/index.ts +53 -0
  116. package/src/send.test.ts +70 -0
  117. package/src/send.ts +181 -0
  118. package/src/skill.test.ts +114 -0
  119. package/src/skill.ts +138 -0
  120. package/src/status.ts +66 -0
  121. package/src/stop.test.ts +176 -0
  122. package/src/stop.ts +47 -0
  123. package/src/task.test.ts +1466 -0
  124. package/src/task.ts +1143 -0
  125. package/src/tunnel.test.ts +80 -0
  126. package/src/tunnel.ts +133 -0
  127. package/src/types.ts +15 -0
  128. package/src/ui/index.ts +32 -0
  129. package/src/ui/terminal/index.ts +9 -0
  130. package/src/ui/types.ts +91 -0
  131. package/src/ui/web/base.css +80 -0
  132. package/src/ui/web/components/Box.tsx +59 -0
  133. package/src/ui/web/components/Input.tsx +32 -0
  134. package/src/ui/web/components/ScrollBox.tsx +60 -0
  135. package/src/ui/web/components/Text.tsx +28 -0
  136. package/src/ui/web/hooks.ts +106 -0
  137. package/src/ui/web/index.ts +27 -0
  138. package/src/ui/web/render.ts +77 -0
  139. package/src/ui/web/utils/color.test.ts +93 -0
  140. package/src/ui/web/utils/color.ts +27 -0
  141. package/src/validate.test.ts +385 -0
  142. package/src/validate.ts +217 -0
  143. package/src/widgets/changes/index.tsx +576 -0
  144. package/src/widgets/config/index.tsx +440 -0
  145. package/src/widgets/costs/index.tsx +216 -0
  146. package/src/widgets/explorer/breadcrumbs.tsx +77 -0
  147. package/src/widgets/explorer/footer.tsx +20 -0
  148. package/src/widgets/explorer/header.tsx +23 -0
  149. package/src/widgets/explorer/index.tsx +408 -0
  150. package/src/widgets/explorer/tree-model.test.ts +214 -0
  151. package/src/widgets/explorer/tree-model.ts +103 -0
  152. package/src/widgets/explorer/tree.tsx +156 -0
  153. package/src/widgets/lib/config-model.ts +116 -0
  154. package/src/widgets/lib/files.test.ts +103 -0
  155. package/src/widgets/lib/files.ts +88 -0
  156. package/src/widgets/lib/git.test.ts +151 -0
  157. package/src/widgets/lib/git.ts +88 -0
  158. package/src/widgets/lib/pane-comms.test.ts +178 -0
  159. package/src/widgets/lib/pane-comms.ts +192 -0
  160. package/src/widgets/lib/theme.ts +151 -0
  161. package/src/widgets/lib/watcher.ts +50 -0
  162. package/src/widgets/mission-control/activity-feed.tsx +59 -0
  163. package/src/widgets/mission-control/agent-panel.tsx +132 -0
  164. package/src/widgets/mission-control/command-bar.tsx +63 -0
  165. package/src/widgets/mission-control/index.tsx +751 -0
  166. package/src/widgets/mission-control/task-panel.tsx +238 -0
  167. package/src/widgets/preview/index.tsx +415 -0
  168. package/src/widgets/resolve.ts +40 -0
  169. package/src/widgets/setup/agent-naming.tsx +112 -0
  170. package/src/widgets/setup/config-tree.tsx +238 -0
  171. package/src/widgets/setup/detect-panel.tsx +72 -0
  172. package/src/widgets/setup/field-editor.tsx +265 -0
  173. package/src/widgets/setup/footer.tsx +107 -0
  174. package/src/widgets/setup/index.tsx +340 -0
  175. package/src/widgets/setup/layout-picker.tsx +96 -0
  176. package/src/widgets/setup/orchestrator-panel.tsx +200 -0
  177. package/src/widgets/setup/review-panel.tsx +140 -0
  178. package/src/widgets/setup/setup-model.test.ts +303 -0
  179. package/src/widgets/setup/setup-model.ts +188 -0
  180. package/src/widgets/tasks/index.tsx +129 -0
  181. package/src/widgets/tasks/task-detail.tsx +309 -0
  182. package/src/widgets/tasks/task-form.tsx +193 -0
  183. package/src/widgets/tasks/task-list.tsx +205 -0
  184. package/src/widgets/tasks/task-model.test.ts +473 -0
  185. package/src/widgets/tasks/task-model.ts +157 -0
  186. package/templates/AGENTS.md +20 -0
  187. package/templates/missions.yml +41 -0
  188. package/templates/skills/backend.md +30 -0
  189. package/templates/skills/frontend.md +30 -0
  190. package/templates/skills/general-worker.md +30 -0
  191. package/templates/skills/researcher.md +69 -0
  192. package/templates/skills/reviewer.md +30 -0
  193. package/dist/attach.d.ts +0 -3
  194. package/dist/attach.js +0 -14
  195. package/dist/config.d.ts +0 -5
  196. package/dist/config.js +0 -269
  197. package/dist/detect.d.ts +0 -15
  198. package/dist/detect.js +0 -228
  199. package/dist/doctor.d.ts +0 -3
  200. package/dist/doctor.js +0 -66
  201. package/dist/init.d.ts +0 -4
  202. package/dist/init.js +0 -67
  203. package/dist/inspect.d.ts +0 -54
  204. package/dist/inspect.js +0 -118
  205. package/dist/launch.d.ts +0 -22
  206. package/dist/launch.js +0 -174
  207. package/dist/lib/dot-path.d.ts +0 -2
  208. package/dist/lib/dot-path.js +0 -17
  209. package/dist/lib/errors.d.ts +0 -29
  210. package/dist/lib/errors.js +0 -37
  211. package/dist/lib/launch-plan.d.ts +0 -6
  212. package/dist/lib/launch-plan.js +0 -40
  213. package/dist/lib/output.d.ts +0 -9
  214. package/dist/lib/output.js +0 -65
  215. package/dist/lib/session-monitor.d.ts +0 -12
  216. package/dist/lib/session-monitor.js +0 -160
  217. package/dist/lib/session-options.d.ts +0 -13
  218. package/dist/lib/session-options.js +0 -85
  219. package/dist/lib/sizes.d.ts +0 -16
  220. package/dist/lib/sizes.js +0 -36
  221. package/dist/lib/tmux.d.ts +0 -42
  222. package/dist/lib/tmux.js +0 -225
  223. package/dist/lib/yaml-io.d.ts +0 -10
  224. package/dist/lib/yaml-io.js +0 -24
  225. package/dist/ls.d.ts +0 -3
  226. package/dist/ls.js +0 -35
  227. package/dist/restart.d.ts +0 -4
  228. package/dist/restart.js +0 -13
  229. package/dist/status.d.ts +0 -3
  230. package/dist/status.js +0 -29
  231. package/dist/stop.d.ts +0 -3
  232. package/dist/stop.js +0 -21
  233. package/dist/types.d.ts +0 -41
  234. package/dist/types.js +0 -1
  235. package/dist/validate.d.ts +0 -4
  236. package/dist/validate.js +0 -172
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect } from "bun:test";
2
+ import { tunnelConfigSchema } from "./lib/tunnels/types.ts";
3
+
4
+ describe("tunnel CLI", () => {
5
+ describe("TunnelConfigSchema from types.ts", () => {
6
+ it("validates tailscale config", () => {
7
+ const result = tunnelConfigSchema.safeParse({
8
+ provider: "tailscale",
9
+ port: 4000,
10
+ });
11
+ expect(result.success).toBe(true);
12
+ });
13
+
14
+ it("validates ngrok config with all options", () => {
15
+ const result = tunnelConfigSchema.safeParse({
16
+ provider: "ngrok",
17
+ port: 4000,
18
+ authToken: "tok_123",
19
+ domain: "my.ngrok.io",
20
+ region: "us",
21
+ });
22
+ expect(result.success).toBe(true);
23
+ });
24
+
25
+ it("validates cloudflare config", () => {
26
+ const result = tunnelConfigSchema.safeParse({
27
+ provider: "cloudflare",
28
+ port: 4000,
29
+ });
30
+ expect(result.success).toBe(true);
31
+ });
32
+
33
+ it("rejects unknown provider", () => {
34
+ const result = tunnelConfigSchema.safeParse({
35
+ provider: "unknown",
36
+ port: 4000,
37
+ });
38
+ expect(result.success).toBe(false);
39
+ });
40
+
41
+ it("rejects missing port", () => {
42
+ const result = tunnelConfigSchema.safeParse({
43
+ provider: "ngrok",
44
+ });
45
+ expect(result.success).toBe(false);
46
+ });
47
+
48
+ it("rejects negative port", () => {
49
+ const result = tunnelConfigSchema.safeParse({
50
+ provider: "ngrok",
51
+ port: -1,
52
+ });
53
+ expect(result.success).toBe(false);
54
+ });
55
+ });
56
+
57
+ describe("TunnelConfigSchema in ide-config", () => {
58
+ it("accepts tunnel field in IdeConfig", async () => {
59
+ const { IdeConfigSchema } = await import("./schemas/ide-config.ts");
60
+ const result = IdeConfigSchema.safeParse({
61
+ rows: [{ panes: [{ title: "Shell" }] }],
62
+ tunnel: {
63
+ provider: "ngrok",
64
+ auto_start: true,
65
+ port: 4000,
66
+ authtoken: "tok_abc",
67
+ },
68
+ });
69
+ expect(result.success).toBe(true);
70
+ });
71
+
72
+ it("allows omitting tunnel", async () => {
73
+ const { IdeConfigSchema } = await import("./schemas/ide-config.ts");
74
+ const result = IdeConfigSchema.safeParse({
75
+ rows: [{ panes: [{ title: "Shell" }] }],
76
+ });
77
+ expect(result.success).toBe(true);
78
+ });
79
+ });
80
+ });
package/src/tunnel.ts ADDED
@@ -0,0 +1,133 @@
1
+ import { resolve } from "node:path";
2
+ import { IdeError } from "./lib/errors.ts";
3
+ import { TunnelManager } from "./lib/tunnels/manager.ts";
4
+ import { tunnelConfigSchema } from "./lib/tunnels/types.ts";
5
+ import type { TunnelConfig } from "./lib/tunnels/types.ts";
6
+
7
+ // Singleton per CLI invocation
8
+ let manager: TunnelManager | null = null;
9
+
10
+ function getManager(dir: string, session?: string): TunnelManager {
11
+ if (!manager) {
12
+ manager = new TunnelManager({ session, dir });
13
+ }
14
+ return manager;
15
+ }
16
+
17
+ async function resolveSession(): Promise<string | undefined> {
18
+ try {
19
+ const { readConfig } = await import("./lib/yaml-io.ts");
20
+ const { config } = readConfig(process.cwd());
21
+ return config.name ?? undefined;
22
+ } catch {
23
+ return undefined;
24
+ }
25
+ }
26
+
27
+ async function resolveTunnelConfig(
28
+ dir: string,
29
+ overrides?: Partial<TunnelConfig>,
30
+ ): Promise<TunnelConfig> {
31
+ try {
32
+ const { readConfig } = await import("./lib/yaml-io.ts");
33
+ const { config } = readConfig(dir);
34
+ if (config.tunnel) {
35
+ const merged = { ...config.tunnel, ...overrides };
36
+ return tunnelConfigSchema.parse(merged);
37
+ }
38
+ } catch {
39
+ // no config
40
+ }
41
+ if (overrides?.provider) {
42
+ return tunnelConfigSchema.parse(overrides);
43
+ }
44
+ throw new IdeError(
45
+ 'No tunnel configuration found. Add a "tunnel" section to ide.yml or pass --provider.',
46
+ { code: "CONFIG_MISSING" },
47
+ );
48
+ }
49
+
50
+ interface TunnelOptions {
51
+ json?: boolean;
52
+ sub?: string;
53
+ args?: string[];
54
+ values?: Record<string, string | boolean | undefined>;
55
+ }
56
+
57
+ export async function tunnelCommand(
58
+ targetDir: string | null | undefined,
59
+ opts: TunnelOptions,
60
+ ): Promise<void> {
61
+ const dir = resolve(targetDir ?? ".");
62
+ const { json, sub } = opts;
63
+ const session = await resolveSession();
64
+ const mgr = getManager(dir, session);
65
+
66
+ switch (sub) {
67
+ case "start": {
68
+ const provider = (opts.values?.provider as string) ?? undefined;
69
+ const port = opts.values?.port ? parseInt(opts.values.port as string, 10) : undefined;
70
+ const domain = (opts.values?.domain as string) ?? undefined;
71
+ const authtoken = (opts.values?.authtoken as string) ?? undefined;
72
+
73
+ const config = await resolveTunnelConfig(dir, {
74
+ provider: provider as TunnelConfig["provider"],
75
+ port,
76
+ domain,
77
+ authToken: authtoken,
78
+ } as Partial<TunnelConfig>);
79
+
80
+ const status = await mgr.start(config);
81
+ if (json) {
82
+ console.log(JSON.stringify(status));
83
+ } else {
84
+ if (status.publicUrl) {
85
+ console.log(`Tunnel running: ${status.publicUrl}`);
86
+ } else {
87
+ console.log("Tunnel started (no public URL yet)");
88
+ }
89
+ }
90
+ break;
91
+ }
92
+
93
+ case "stop": {
94
+ await mgr.stop();
95
+ if (json) {
96
+ console.log(JSON.stringify({ ok: true }));
97
+ } else {
98
+ console.log("Tunnel stopped");
99
+ }
100
+ break;
101
+ }
102
+
103
+ case "status": {
104
+ const status = await mgr.status();
105
+ if (json) {
106
+ console.log(JSON.stringify(status));
107
+ } else {
108
+ if (status.running) {
109
+ console.log(`Tunnel: ${status.provider} — ${status.publicUrl ?? "starting..."}`);
110
+ } else {
111
+ console.log("Tunnel: not running");
112
+ }
113
+ }
114
+ break;
115
+ }
116
+
117
+ case "url": {
118
+ const url = await mgr.url();
119
+ if (json) {
120
+ console.log(JSON.stringify({ url }));
121
+ } else {
122
+ console.log(url ?? "No tunnel running");
123
+ }
124
+ break;
125
+ }
126
+
127
+ default:
128
+ throw new IdeError(
129
+ `Unknown tunnel subcommand: ${sub ?? "(none)"}\nUsage: tmux-ide tunnel start|stop|status|url`,
130
+ { code: "USAGE" },
131
+ );
132
+ }
133
+ }
package/src/types.ts ADDED
@@ -0,0 +1,15 @@
1
+ export type {
2
+ Pane,
3
+ Row,
4
+ ThemeConfig,
5
+ OrchestratorYamlConfig,
6
+ CommandCenterConfig,
7
+ DashboardConfig,
8
+ IdeConfig,
9
+ PaneAction,
10
+ SessionState,
11
+ } from "./schemas/ide-config.ts";
12
+
13
+ export type { ProofSchema } from "./schemas/index.ts";
14
+
15
+ export type TmuxCommand = string[];
@@ -0,0 +1,32 @@
1
+ // Environment-detecting UI entry point.
2
+ //
3
+ // Detects whether code is running in a browser (window exists) or terminal
4
+ // (Node/Bun) and re-exports the appropriate backend. Widgets import from
5
+ // this module to work in both environments without code changes.
6
+ //
7
+ // Usage:
8
+ // import { render, useKeyboard, RGBA, TextAttributes } from "../../ui/index.ts";
9
+ //
10
+ // Terminal backend: re-exports @opentui/solid + @opentui/core
11
+ // Web backend: re-exports DOM-based mirror components
12
+
13
+ // Use a compile-time constant so bundlers can tree-shake the unused backend.
14
+ // In browser builds, `typeof window !== "undefined"` is true and the terminal
15
+ // branch is eliminated. In Node/Bun, the web branch is eliminated.
16
+ const IS_BROWSER = typeof window !== "undefined";
17
+
18
+ if (IS_BROWSER) {
19
+ // Re-export web backend at module level for static analysis
20
+ }
21
+
22
+ // We use conditional re-export via a barrel pattern.
23
+ // Bundlers (Bun, webpack, turbopack) resolve this statically.
24
+ // For runtime detection, we export from web by default (browser-first)
25
+ // and terminal widgets import from ./terminal/index.ts directly.
26
+ //
27
+ // The web backend is safe to import in Node because it only touches
28
+ // the DOM inside render() and hooks (which are never called in terminal mode).
29
+ export * from "./web/index.ts";
30
+
31
+ // Also re-export shared types so they're available regardless of backend
32
+ export type { BoxProps, TextProps, ScrollBoxProps, InputProps } from "./types.ts";
@@ -0,0 +1,9 @@
1
+ // Terminal backend — re-exports @opentui/solid and @opentui/core for terminal rendering.
2
+ // Widgets importing from src/ui/ will get these exports when running in Node/Bun.
3
+
4
+ // Render and hooks
5
+ export { render, useKeyboard, useTerminalDimensions } from "@opentui/solid";
6
+
7
+ // Core primitives and types
8
+ export { RGBA, TextAttributes } from "@opentui/core";
9
+ export type { ScrollBoxRenderable, InputRenderable } from "@opentui/core";
@@ -0,0 +1,91 @@
1
+ export interface RGBA {
2
+ r: number;
3
+ g: number;
4
+ b: number;
5
+ a: number;
6
+ }
7
+
8
+ export function rgbaFromInts(r: number, g: number, b: number, a?: number): RGBA {
9
+ return { r, g, b, a: a ?? 255 };
10
+ }
11
+
12
+ export const TextAttributes = {
13
+ NONE: 0,
14
+ BOLD: 1,
15
+ DIM: 2,
16
+ ITALIC: 4,
17
+ UNDERLINE: 8,
18
+ BLINK: 16,
19
+ INVERSE: 32,
20
+ HIDDEN: 64,
21
+ STRIKETHROUGH: 128,
22
+ } as const;
23
+
24
+ export interface BoxProps {
25
+ id?: string;
26
+ flexDirection?: "row" | "column";
27
+ flexGrow?: number;
28
+ flexShrink?: number;
29
+ gap?: number;
30
+ justifyContent?: "flex-start" | "center" | "flex-end" | "space-between";
31
+ alignItems?: "flex-start" | "center" | "flex-end" | "stretch";
32
+ position?: "relative" | "absolute";
33
+ overflow?: "visible" | "hidden";
34
+ width?: number | string;
35
+ height?: number | string;
36
+ maxWidth?: number | string;
37
+ maxHeight?: number | string;
38
+ padding?: number;
39
+ paddingLeft?: number;
40
+ paddingRight?: number;
41
+ paddingTop?: number;
42
+ paddingBottom?: number;
43
+ backgroundColor?: RGBA;
44
+ border?: string[];
45
+ borderColor?: RGBA;
46
+ top?: number;
47
+ left?: number;
48
+ right?: number;
49
+ bottom?: number;
50
+ // Mouse events
51
+ onMouseDown?: (e: unknown) => void;
52
+ onMouseUp?: (e: unknown) => void;
53
+ onMouseMove?: (e: unknown) => void;
54
+ onMouseOver?: (e: unknown) => void;
55
+ // Children
56
+ children?: unknown;
57
+ ref?: (el: unknown) => void;
58
+ }
59
+
60
+ export interface TextProps {
61
+ fg?: RGBA;
62
+ bg?: RGBA;
63
+ attributes?: number;
64
+ wrapMode?: "none" | "word" | "char";
65
+ width?: number | string;
66
+ height?: number | string;
67
+ flexGrow?: number;
68
+ flexShrink?: number;
69
+ // Mouse events
70
+ onMouseUp?: (e: unknown) => void;
71
+ onMouseDown?: (e: unknown) => void;
72
+ children?: unknown;
73
+ }
74
+
75
+ export interface ScrollBoxProps extends BoxProps {
76
+ verticalScrollbarOptions?: unknown;
77
+ stickyScroll?: boolean;
78
+ stickyStart?: "bottom" | "top";
79
+ }
80
+
81
+ export interface InputProps {
82
+ value?: string;
83
+ placeholder?: string;
84
+ onInput?: (value: string) => void;
85
+ focusedBackgroundColor?: RGBA;
86
+ cursorColor?: RGBA;
87
+ focusedTextColor?: RGBA;
88
+ onMouseDown?: (e: unknown) => void;
89
+ ref?: (el: unknown) => void;
90
+ children?: unknown;
91
+ }
@@ -0,0 +1,80 @@
1
+ /* Base styles for the web terminal backend.
2
+ * Import this in your HTML or bundler entry point to get terminal-like appearance.
3
+ * These styles are also injected automatically by render() at runtime.
4
+ */
5
+
6
+ :root {
7
+ --ui-line-height: 1.4em;
8
+ --ui-font-family:
9
+ "IBM Plex Mono", ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas, "Liberation Mono",
10
+ monospace;
11
+ --ui-font-size: 13px;
12
+ --ui-bg: rgb(19, 16, 16);
13
+ --ui-fg: rgb(232, 228, 228);
14
+ --ui-scrollbar-track: rgb(25, 25, 35);
15
+ --ui-scrollbar-thumb: rgb(60, 60, 80);
16
+ --ui-scrollbar-thumb-hover: rgb(80, 80, 100);
17
+ }
18
+
19
+ body,
20
+ #root {
21
+ font-family: var(--ui-font-family);
22
+ font-size: var(--ui-font-size);
23
+ line-height: var(--ui-line-height);
24
+ background: var(--ui-bg);
25
+ color: var(--ui-fg);
26
+ margin: 0;
27
+ padding: 0;
28
+ -webkit-font-smoothing: antialiased;
29
+ -moz-osx-font-smoothing: grayscale;
30
+ }
31
+
32
+ * {
33
+ box-sizing: border-box;
34
+ }
35
+
36
+ /* Flex-column by default — matches terminal box model */
37
+ div {
38
+ display: flex;
39
+ flex-direction: column;
40
+ min-width: 0;
41
+ min-height: 0;
42
+ }
43
+
44
+ span {
45
+ font-family: inherit;
46
+ font-size: inherit;
47
+ line-height: inherit;
48
+ }
49
+
50
+ input {
51
+ font-family: inherit;
52
+ font-size: inherit;
53
+ line-height: inherit;
54
+ background: transparent;
55
+ color: inherit;
56
+ border: none;
57
+ outline: none;
58
+ }
59
+
60
+ input::placeholder {
61
+ color: rgba(232, 228, 228, 0.4);
62
+ }
63
+
64
+ /* Scrollbar styling */
65
+ ::-webkit-scrollbar {
66
+ width: 8px;
67
+ }
68
+
69
+ ::-webkit-scrollbar-track {
70
+ background: var(--ui-scrollbar-track);
71
+ }
72
+
73
+ ::-webkit-scrollbar-thumb {
74
+ background: var(--ui-scrollbar-thumb);
75
+ border-radius: 4px;
76
+ }
77
+
78
+ ::-webkit-scrollbar-thumb:hover {
79
+ background: var(--ui-scrollbar-thumb-hover);
80
+ }
@@ -0,0 +1,59 @@
1
+ import type { JSX, ParentProps } from "solid-js";
2
+ import { rgbaToCSS } from "../utils/color.ts";
3
+ import type { BoxProps } from "../../types.ts";
4
+
5
+ export function Box(props: ParentProps<BoxProps>) {
6
+ const style = (): JSX.CSSProperties => ({
7
+ display: "flex",
8
+ "flex-direction": props.flexDirection ?? "column",
9
+ "flex-grow": props.flexGrow,
10
+ "flex-shrink": props.flexShrink,
11
+ gap: props.gap !== undefined ? `${props.gap}ch` : undefined,
12
+ "justify-content": props.justifyContent,
13
+ "align-items": props.alignItems,
14
+ position: props.position ?? "relative",
15
+ overflow: props.overflow,
16
+ width: typeof props.width === "number" ? `${props.width}ch` : props.width,
17
+ height:
18
+ typeof props.height === "number"
19
+ ? `calc(${props.height} * var(--line-height))`
20
+ : props.height,
21
+ "max-width": typeof props.maxWidth === "number" ? `${props.maxWidth}ch` : props.maxWidth,
22
+ "max-height":
23
+ typeof props.maxHeight === "number"
24
+ ? `calc(${props.maxHeight} * var(--line-height))`
25
+ : props.maxHeight,
26
+ padding:
27
+ props.padding !== undefined
28
+ ? `calc(${props.padding} * var(--line-height)) ${props.padding}ch`
29
+ : undefined,
30
+ "padding-left": props.paddingLeft !== undefined ? `${props.paddingLeft}ch` : undefined,
31
+ "padding-right": props.paddingRight !== undefined ? `${props.paddingRight}ch` : undefined,
32
+ "padding-top":
33
+ props.paddingTop !== undefined ? `calc(${props.paddingTop} * var(--line-height))` : undefined,
34
+ "padding-bottom":
35
+ props.paddingBottom !== undefined
36
+ ? `calc(${props.paddingBottom} * var(--line-height))`
37
+ : undefined,
38
+ background: props.backgroundColor ? rgbaToCSS(props.backgroundColor) : undefined,
39
+ "border-color": props.borderColor ? rgbaToCSS(props.borderColor) : undefined,
40
+ top: props.top !== undefined ? `calc(${props.top} * var(--line-height))` : undefined,
41
+ left: props.left !== undefined ? `${props.left}ch` : undefined,
42
+ right: props.right !== undefined ? `${props.right}ch` : undefined,
43
+ bottom: props.bottom !== undefined ? `calc(${props.bottom} * var(--line-height))` : undefined,
44
+ });
45
+
46
+ return (
47
+ <div
48
+ id={props.id}
49
+ style={style()}
50
+ onMouseDown={props.onMouseDown}
51
+ onMouseUp={props.onMouseUp}
52
+ onMouseMove={props.onMouseMove}
53
+ onMouseOver={props.onMouseOver}
54
+ ref={props.ref}
55
+ >
56
+ {props.children}
57
+ </div>
58
+ );
59
+ }
@@ -0,0 +1,32 @@
1
+ import type { JSX } from "solid-js";
2
+ import { rgbaToCSS } from "../utils/color.ts";
3
+ import type { InputProps } from "../../types.ts";
4
+
5
+ export function Input(props: InputProps) {
6
+ const style = (): JSX.CSSProperties => ({
7
+ "font-family": "inherit",
8
+ "font-size": "inherit",
9
+ "line-height": "inherit",
10
+ background: props.focusedBackgroundColor
11
+ ? rgbaToCSS(props.focusedBackgroundColor)
12
+ : "rgb(25,25,35)",
13
+ color: props.focusedTextColor ? rgbaToCSS(props.focusedTextColor) : "inherit",
14
+ border: "none",
15
+ outline: "none",
16
+ padding: "0 1ch",
17
+ width: "100%",
18
+ "caret-color": props.cursorColor ? rgbaToCSS(props.cursorColor) : "inherit",
19
+ });
20
+
21
+ return (
22
+ <input
23
+ type="text"
24
+ value={props.value ?? ""}
25
+ placeholder={props.placeholder}
26
+ style={style()}
27
+ onInput={(e) => props.onInput?.(e.currentTarget.value)}
28
+ onMouseDown={props.onMouseDown}
29
+ ref={props.ref}
30
+ />
31
+ );
32
+ }
@@ -0,0 +1,60 @@
1
+ import type { ParentProps, JSX } from "solid-js";
2
+ import { rgbaToCSS } from "../utils/color.ts";
3
+ import type { ScrollBoxProps } from "../../types.ts";
4
+
5
+ export function ScrollBox(props: ParentProps<ScrollBoxProps>) {
6
+ const style = (): JSX.CSSProperties => ({
7
+ display: "flex",
8
+ "flex-direction": props.flexDirection ?? "column",
9
+ "flex-grow": props.flexGrow,
10
+ "flex-shrink": props.flexShrink,
11
+ gap: props.gap !== undefined ? `${props.gap}ch` : undefined,
12
+ "justify-content": props.justifyContent,
13
+ "align-items": props.alignItems,
14
+ position: props.position ?? "relative",
15
+ overflow: "hidden",
16
+ "overflow-y": "auto",
17
+ width: typeof props.width === "number" ? `${props.width}ch` : props.width,
18
+ height:
19
+ typeof props.height === "number"
20
+ ? `calc(${props.height} * var(--line-height))`
21
+ : props.height,
22
+ "max-width": typeof props.maxWidth === "number" ? `${props.maxWidth}ch` : props.maxWidth,
23
+ "max-height":
24
+ typeof props.maxHeight === "number"
25
+ ? `calc(${props.maxHeight} * var(--line-height))`
26
+ : props.maxHeight,
27
+ padding:
28
+ props.padding !== undefined
29
+ ? `calc(${props.padding} * var(--line-height)) ${props.padding}ch`
30
+ : undefined,
31
+ "padding-left": props.paddingLeft !== undefined ? `${props.paddingLeft}ch` : undefined,
32
+ "padding-right": props.paddingRight !== undefined ? `${props.paddingRight}ch` : undefined,
33
+ "padding-top":
34
+ props.paddingTop !== undefined ? `calc(${props.paddingTop} * var(--line-height))` : undefined,
35
+ "padding-bottom":
36
+ props.paddingBottom !== undefined
37
+ ? `calc(${props.paddingBottom} * var(--line-height))`
38
+ : undefined,
39
+ background: props.backgroundColor ? rgbaToCSS(props.backgroundColor) : undefined,
40
+ "border-color": props.borderColor ? rgbaToCSS(props.borderColor) : undefined,
41
+ top: props.top !== undefined ? `calc(${props.top} * var(--line-height))` : undefined,
42
+ left: props.left !== undefined ? `${props.left}ch` : undefined,
43
+ right: props.right !== undefined ? `${props.right}ch` : undefined,
44
+ bottom: props.bottom !== undefined ? `calc(${props.bottom} * var(--line-height))` : undefined,
45
+ });
46
+
47
+ return (
48
+ <div
49
+ id={props.id}
50
+ style={style()}
51
+ onMouseDown={props.onMouseDown}
52
+ onMouseUp={props.onMouseUp}
53
+ onMouseMove={props.onMouseMove}
54
+ onMouseOver={props.onMouseOver}
55
+ ref={props.ref}
56
+ >
57
+ {props.children}
58
+ </div>
59
+ );
60
+ }
@@ -0,0 +1,28 @@
1
+ import type { ParentProps, JSX } from "solid-js";
2
+ import { rgbaToCSS, attributesToStyle } from "../utils/color.ts";
3
+ import type { TextProps } from "../../types.ts";
4
+
5
+ export function Text(props: ParentProps<TextProps>) {
6
+ const style = (): JSX.CSSProperties => ({
7
+ color: props.fg ? rgbaToCSS(props.fg) : undefined,
8
+ background: props.bg ? rgbaToCSS(props.bg) : undefined,
9
+ "white-space":
10
+ props.wrapMode === "none" ? "nowrap" : props.wrapMode === "word" ? "pre-wrap" : undefined,
11
+ overflow: props.wrapMode === "none" ? "hidden" : undefined,
12
+ "word-break": props.wrapMode === "word" ? "break-word" : undefined,
13
+ width: typeof props.width === "number" ? `${props.width}ch` : props.width,
14
+ height:
15
+ typeof props.height === "number"
16
+ ? `calc(${props.height} * var(--line-height))`
17
+ : props.height,
18
+ "flex-grow": props.flexGrow,
19
+ "flex-shrink": props.flexShrink,
20
+ ...attributesToStyle(props.attributes ?? 0),
21
+ });
22
+
23
+ return (
24
+ <span style={style()} onMouseUp={props.onMouseUp} onMouseDown={props.onMouseDown}>
25
+ {props.children}
26
+ </span>
27
+ );
28
+ }