@freestyle-sh/with-ttyd 0.0.5 → 0.0.7

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/dist/index.d.ts CHANGED
@@ -1,11 +1,14 @@
1
1
  import * as freestyle_sandboxes from 'freestyle-sandboxes';
2
- import { VmWith, VmWithInstance, CreateVmOptions } from 'freestyle-sandboxes';
2
+ import { VmWith, VmWithInstance, VmSpec } from 'freestyle-sandboxes';
3
+ import { VmPtySessionLike } from '@freestyle-sh/with-pty';
3
4
 
4
5
  type TtydConfig = {
5
6
  /** Port to run ttyd on (default: auto-assigned starting at 7682) */
6
7
  port?: number;
7
8
  /** Shell or command to run (default: /bin/bash) */
8
9
  command?: string;
10
+ /** Attach ttyd to a PTY session */
11
+ pty?: VmPtySessionLike;
9
12
  /** User to run terminal as (default: current user) */
10
13
  user?: string;
11
14
  /** Working directory (default: user home) */
@@ -19,6 +22,8 @@ type TtydConfig = {
19
22
  title?: string;
20
23
  /** Read-only terminal (no input allowed) */
21
24
  readOnly?: boolean;
25
+ /** Xterm.js theme settings passed to ttyd client options */
26
+ theme?: Record<string, string | number | boolean>;
22
27
  };
23
28
  type ResolvedTerminalConfig = {
24
29
  port: number;
@@ -31,11 +36,15 @@ type ResolvedTerminalConfig = {
31
36
  };
32
37
  title: string;
33
38
  readOnly: boolean;
39
+ theme?: Record<string, string | number | boolean>;
34
40
  };
35
41
  declare class VmWebTerminal<T extends TtydConfig[] = TtydConfig[]> extends VmWith<VmWebTerminalInstance<T>> {
36
42
  private resolvedTerminals;
43
+ private ptySessions;
37
44
  constructor(terminals: T | TtydConfig);
38
- configure(existingConfig: CreateVmOptions): CreateVmOptions | Promise<CreateVmOptions>;
45
+ private composeTtydSpec;
46
+ configureSnapshotSpec(spec: VmSpec): VmSpec;
47
+ configureSpec(spec: VmSpec): VmSpec;
39
48
  createInstance(): VmWebTerminalInstance<T>;
40
49
  installServiceName(): string;
41
50
  }
@@ -46,7 +55,7 @@ declare class WebTerminal {
46
55
  constructor({ port, command, instance, }: {
47
56
  port: number;
48
57
  command: string;
49
- instance: VmWebTerminalInstance<any>;
58
+ instance: VmWebTerminalInstance<TtydConfig[]>;
50
59
  });
51
60
  /** Expose this terminal publicly via Freestyle routing */
52
61
  route({ domain }: {
package/dist/index.js CHANGED
@@ -1,33 +1,50 @@
1
- import { VmWith, VmTemplate, VmWithInstance } from 'freestyle-sandboxes';
1
+ import { VmWith, VmSpec, VmWithInstance } from 'freestyle-sandboxes';
2
2
 
3
3
  class VmWebTerminal extends VmWith {
4
4
  resolvedTerminals;
5
+ ptySessions;
5
6
  constructor(terminals) {
6
7
  super();
7
8
  const terminalList = Array.isArray(terminals) ? terminals : [terminals];
9
+ this.ptySessions = terminalList.map((config) => config.pty).filter((value) => value !== void 0);
8
10
  let nextPort = 7682;
9
11
  this.resolvedTerminals = terminalList.map((config) => {
12
+ if (config.pty && config.command) {
13
+ throw new Error(
14
+ "VmWebTerminal config cannot include both pty and command. Use one or the other."
15
+ );
16
+ }
10
17
  const port = config.port ?? nextPort++;
18
+ const resolvedCommand = config.pty ? config.pty.attachCommand(config.readOnly ?? false) : config.command ?? "bash -l";
11
19
  return {
12
20
  port,
13
- command: config.command ?? "bash -l",
21
+ command: resolvedCommand,
14
22
  user: config.user ?? "root",
15
23
  cwd: config.cwd ?? "/root",
16
24
  credential: config.credential,
17
25
  title: config.title ?? `terminal-${port}`,
18
- readOnly: config.readOnly ?? false
26
+ readOnly: config.readOnly ?? false,
27
+ theme: config.theme
19
28
  };
20
29
  });
21
30
  }
22
- configure(existingConfig) {
31
+ composeTtydSpec(spec) {
32
+ const uniquePtySessions = Array.from(new Set(this.ptySessions));
33
+ let composed = spec;
34
+ for (const pty of uniquePtySessions) {
35
+ composed = pty.applyToSpec(composed);
36
+ }
23
37
  const installScript = `#!/bin/bash
24
- set -e
38
+ set -e
25
39
 
26
- TTYD_VERSION="1.7.7"
27
- curl -fsSL -o /usr/local/bin/ttyd "https://github.com/tsl0922/ttyd/releases/download/\${TTYD_VERSION}/ttyd.x86_64"
28
- chmod +x /usr/local/bin/ttyd
29
- /usr/local/bin/ttyd --version
30
- `;
40
+ TTYD_VERSION="1.7.7"
41
+ mkdir -p /usr/local/bin
42
+ tmpfile="$(mktemp)"
43
+ curl -fsSL -o "$tmpfile" "https://github.com/tsl0922/ttyd/releases/download/\${TTYD_VERSION}/ttyd.x86_64"
44
+ mv "$tmpfile" /usr/local/bin/ttyd
45
+ chmod +x /usr/local/bin/ttyd
46
+ /usr/local/bin/ttyd --version
47
+ `;
31
48
  const services = this.resolvedTerminals.map((t) => {
32
49
  const args = [`/usr/local/bin/ttyd`, `-p ${t.port}`];
33
50
  if (t.credential) {
@@ -50,6 +67,10 @@ chmod +x /usr/local/bin/ttyd
50
67
  } else {
51
68
  args.push(`--writable`);
52
69
  }
70
+ if (t.theme) {
71
+ const themeJson = JSON.stringify(t.theme);
72
+ args.push(`-t 'theme=${themeJson}'`);
73
+ }
53
74
  args.push(t.command);
54
75
  return {
55
76
  name: `web-terminal-${t.port}`,
@@ -63,8 +84,9 @@ chmod +x /usr/local/bin/ttyd
63
84
  requires: ["systemd-sysusers.service"]
64
85
  };
65
86
  });
66
- const config = {
67
- template: new VmTemplate({
87
+ return this.composeSpecs(
88
+ composed,
89
+ new VmSpec({
68
90
  additionalFiles: {
69
91
  "/opt/install-ttyd.sh": { content: installScript }
70
92
  },
@@ -81,8 +103,13 @@ chmod +x /usr/local/bin/ttyd
81
103
  ]
82
104
  }
83
105
  })
84
- };
85
- return this.compose(existingConfig, config);
106
+ );
107
+ }
108
+ configureSnapshotSpec(spec) {
109
+ return this.composeTtydSpec(spec);
110
+ }
111
+ configureSpec(spec) {
112
+ return this.composeTtydSpec(spec);
86
113
  }
87
114
  createInstance() {
88
115
  return new VmWebTerminalInstance(this, this.resolvedTerminals);
package/examples/basic.ts CHANGED
@@ -2,10 +2,15 @@ import "dotenv/config";
2
2
  import { freestyle, VmSpec } from "freestyle-sandboxes";
3
3
  import { VmWebTerminal as VmTtyd } from "../src/index.ts";
4
4
  import { VmDevServer } from "../../with-dev-server/src/index.ts";
5
+ import { VmPtySession } from "../../with-pty/src/index.ts";
5
6
 
6
- const devLogs = new VmTtyd({
7
+ const devPty = new VmPtySession({
8
+ sessionId: "npm-dev",
9
+ });
10
+
11
+ const devCommandTtyd = new VmTtyd({
7
12
  port: 3010,
8
- command: "tmux attach -t dev-server",
13
+ pty: devPty,
9
14
  readOnly: true,
10
15
  cwd: "/root/repo",
11
16
  });
@@ -17,44 +22,26 @@ const otherTerminals = new VmTtyd({
17
22
 
18
23
  const domain = `${crypto.randomUUID()}.style.dev`;
19
24
 
20
- const innerDeps = new VmSpec({
21
- discriminator: "inner-deps",
22
- with: {
23
- devLogs: devLogs,
24
- otherTerminals: otherTerminals,
25
- },
26
- aptDeps: ["tmux"],
27
- // systemd: {
28
- // services: [
29
- // {
30
- // name: "install-tmux",
31
- // mode: "oneshot",
32
- // bash: "apt-get update && apt-get install -y tmux",
33
- // timeoutSec: 120,
34
- // },
35
- // ],
36
- // },
37
- additionalFiles: {
38
- "/root/.tmux.conf": {
39
- content: `set -g mouse on`,
40
- },
41
- },
42
- });
43
-
44
25
  const snapshot = new VmSpec({
45
- snapshot: innerDeps,
46
26
  with: {
27
+ devCommandTtyd: devCommandTtyd,
28
+ otherTerminals: otherTerminals,
47
29
  devServer: new VmDevServer({
48
30
  templateRepo: "https://github.com/freestyle-sh/freestyle-next",
49
31
  workdir: "/root/repo",
50
- devCommand: "tmux new -s dev-server npm run dev",
32
+ devCommand: "npm run dev",
33
+ devCommandPty: devPty,
51
34
  }),
52
35
  },
53
36
  });
54
37
 
55
- const { vm, vmId } = await freestyle.vms.create({
38
+ const { vmId, vm } = await freestyle.vms.create({
56
39
  snapshot: snapshot,
57
40
  domains: [
41
+ {
42
+ domain: domain,
43
+ vmPort: 3000,
44
+ },
58
45
  {
59
46
  domain: "dev-logs-" + domain,
60
47
  vmPort: 3010,
@@ -66,7 +53,9 @@ const { vm, vmId } = await freestyle.vms.create({
66
53
  ],
67
54
  });
68
55
 
69
- console.log("npx freestyle-sandboxes vm ssh " + vmId);
56
+ await vm.devServer.getLogs().then(console.log);
70
57
 
58
+ console.log("npx freestyle-sandboxes vm ssh " + vmId);
59
+ console.log(`Dev server available at: https://${domain}`);
71
60
  console.log(`Terminal available at: https://dev-logs-${domain}`);
72
61
  console.log(`Other terminals available at: https://other-terminals-${domain}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@freestyle-sh/with-ttyd",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Web terminal for freestyle sandboxes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -24,7 +24,8 @@
24
24
  "typescript": "^5.8.3"
25
25
  },
26
26
  "dependencies": {
27
- "freestyle-sandboxes": "^0.1.27"
27
+ "freestyle-sandboxes": "^0.1.28",
28
+ "@freestyle-sh/with-pty": "^0.0.3"
28
29
  },
29
30
  "scripts": {
30
31
  "build": "pkgroll"
package/src/index.ts CHANGED
@@ -1,10 +1,6 @@
1
- import {
2
- VmTemplate,
3
- type CreateVmOptions,
4
- VmWith,
5
- VmWithInstance,
6
- Freestyle,
7
- } from "freestyle-sandboxes";
1
+ import { VmSpec, VmWith, VmWithInstance } from "freestyle-sandboxes";
2
+ import type { VmPtySessionLike } from "@freestyle-sh/with-pty";
3
+ import type { Freestyle } from "freestyle-sandboxes";
8
4
 
9
5
  // ============================================================================
10
6
  // Configuration Types
@@ -15,6 +11,8 @@ export type TtydConfig = {
15
11
  port?: number;
16
12
  /** Shell or command to run (default: /bin/bash) */
17
13
  command?: string;
14
+ /** Attach ttyd to a PTY session */
15
+ pty?: VmPtySessionLike;
18
16
  /** User to run terminal as (default: current user) */
19
17
  user?: string;
20
18
  /** Working directory (default: user home) */
@@ -25,6 +23,8 @@ export type TtydConfig = {
25
23
  title?: string;
26
24
  /** Read-only terminal (no input allowed) */
27
25
  readOnly?: boolean;
26
+ /** Xterm.js theme settings passed to ttyd client options */
27
+ theme?: Record<string, string | number | boolean>;
28
28
  };
29
29
 
30
30
  export type ResolvedTerminalConfig = {
@@ -35,6 +35,7 @@ export type ResolvedTerminalConfig = {
35
35
  credential?: { username: string; password: string };
36
36
  title: string;
37
37
  readOnly: boolean;
38
+ theme?: Record<string, string | number | boolean>;
38
39
  };
39
40
 
40
41
  // ============================================================================
@@ -45,38 +46,61 @@ export class VmWebTerminal<
45
46
  T extends TtydConfig[] = TtydConfig[],
46
47
  > extends VmWith<VmWebTerminalInstance<T>> {
47
48
  private resolvedTerminals: ResolvedTerminalConfig[];
49
+ private ptySessions: VmPtySessionLike[];
48
50
 
49
51
  constructor(terminals: T | TtydConfig) {
50
52
  super();
51
53
  const terminalList = Array.isArray(terminals) ? terminals : [terminals];
54
+ this.ptySessions = terminalList
55
+ .map((config) => config.pty)
56
+ .filter((value): value is VmPtySessionLike => value !== undefined);
52
57
  // Resolve config once with defaults
53
58
  let nextPort = 7682;
54
59
  this.resolvedTerminals = terminalList.map((config) => {
60
+ if (config.pty && config.command) {
61
+ throw new Error(
62
+ "VmWebTerminal config cannot include both pty and command. Use one or the other.",
63
+ );
64
+ }
65
+
55
66
  const port = config.port ?? nextPort++;
67
+ const resolvedCommand = config.pty
68
+ ? config.pty.attachCommand(config.readOnly ?? false)
69
+ : (config.command ?? "bash -l");
70
+
56
71
  return {
57
72
  port,
58
- command: config.command ?? "bash -l",
73
+ command: resolvedCommand,
59
74
  user: config.user ?? "root",
60
75
  cwd: config.cwd ?? "/root",
61
76
  credential: config.credential,
62
77
  title: config.title ?? `terminal-${port}`,
63
78
  readOnly: config.readOnly ?? false,
79
+ theme: config.theme,
64
80
  };
65
81
  });
66
82
  }
67
83
 
68
- override configure(
69
- existingConfig: CreateVmOptions,
70
- ): CreateVmOptions | Promise<CreateVmOptions> {
84
+ private composeTtydSpec(spec: VmSpec): VmSpec {
85
+ const uniquePtySessions = Array.from(new Set(this.ptySessions));
86
+ let composed = spec;
87
+
88
+ for (const pty of uniquePtySessions) {
89
+ composed = pty.applyToSpec(composed) as VmSpec;
90
+ }
91
+
71
92
  // Generate install script
72
93
  const installScript = `#!/bin/bash
73
- set -e
94
+ set -e
74
95
 
75
- TTYD_VERSION="1.7.7"
76
- curl -fsSL -o /usr/local/bin/ttyd "https://github.com/tsl0922/ttyd/releases/download/\${TTYD_VERSION}/ttyd.x86_64"
77
- chmod +x /usr/local/bin/ttyd
78
- /usr/local/bin/ttyd --version
79
- `;
96
+ TTYD_VERSION="1.7.7"
97
+ mkdir -p /usr/local/bin
98
+ tmpfile="$(mktemp)"
99
+ curl -fsSL -o "$tmpfile" "https://github.com/tsl0922/ttyd/releases/download/\${TTYD_VERSION}/ttyd.x86_64"
100
+ mv "$tmpfile" /usr/local/bin/ttyd
101
+ chmod +x /usr/local/bin/ttyd
102
+ /usr/local/bin/ttyd --version
103
+ `;
80
104
 
81
105
  // Generate systemd service for each terminal
82
106
  const services = this.resolvedTerminals.map((t) => {
@@ -109,6 +133,11 @@ chmod +x /usr/local/bin/ttyd
109
133
  args.push(`--writable`);
110
134
  }
111
135
 
136
+ if (t.theme) {
137
+ const themeJson = JSON.stringify(t.theme);
138
+ args.push(`-t 'theme=${themeJson}'`);
139
+ }
140
+
112
141
  // Shell command at the end
113
142
  args.push(t.command);
114
143
 
@@ -125,8 +154,9 @@ chmod +x /usr/local/bin/ttyd
125
154
  };
126
155
  });
127
156
 
128
- const config: CreateVmOptions = {
129
- template: new VmTemplate({
157
+ return this.composeSpecs(
158
+ composed,
159
+ new VmSpec({
130
160
  additionalFiles: {
131
161
  "/opt/install-ttyd.sh": { content: installScript },
132
162
  },
@@ -143,9 +173,15 @@ chmod +x /usr/local/bin/ttyd
143
173
  ],
144
174
  },
145
175
  }),
146
- };
176
+ );
177
+ }
178
+
179
+ override configureSnapshotSpec(spec: VmSpec): VmSpec {
180
+ return this.composeTtydSpec(spec);
181
+ }
147
182
 
148
- return this.compose(existingConfig, config);
183
+ override configureSpec(spec: VmSpec): VmSpec {
184
+ return this.composeTtydSpec(spec);
149
185
  }
150
186
 
151
187
  createInstance(): VmWebTerminalInstance<T> {
@@ -164,7 +200,7 @@ chmod +x /usr/local/bin/ttyd
164
200
  export class WebTerminal {
165
201
  readonly port: number;
166
202
  readonly command: string;
167
- private instance: VmWebTerminalInstance<any>;
203
+ private instance: VmWebTerminalInstance<TtydConfig[]>;
168
204
 
169
205
  constructor({
170
206
  port,
@@ -173,7 +209,7 @@ export class WebTerminal {
173
209
  }: {
174
210
  port: number;
175
211
  command: string;
176
- instance: VmWebTerminalInstance<any>;
212
+ instance: VmWebTerminalInstance<TtydConfig[]>;
177
213
  }) {
178
214
  this.port = port;
179
215
  this.command = command;