@freestyle-sh/with-opencode 0.0.1

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/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # @freestyle-sh/with-opencode
2
+
3
+ [OpenCode](https://opencode.ai) integration for [Freestyle](https://freestyle.sh) VMs. Provides a fully configured OpenCode AI coding assistant server in your Freestyle VM.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @freestyle-sh/with-opencode freestyle-sandboxes
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### Basic Setup
14
+
15
+ ```typescript
16
+ import { freestyle, VmSpec } from "freestyle-sandboxes";
17
+ import { VmOpenCode } from "@freestyle-sh/with-opencode";
18
+
19
+ const { vm } = await freestyle.vms.create({
20
+ spec: new VmSpec({
21
+ with: {
22
+ opencode: new VmOpenCode(),
23
+ },
24
+ }),
25
+ });
26
+
27
+ // Expose the web UI
28
+ const { url } = await vm.opencode.routeWeb();
29
+ console.log(`OpenCode UI: ${url}`);
30
+ ```
31
+
32
+ ### Using the API Client
33
+
34
+ ```typescript
35
+ const { client } = await vm.opencode.client();
36
+
37
+ // Use the OpenCode SDK client to interact with the server
38
+ const sessions = await client.session.list();
39
+ ```
40
+
41
+ ### With Custom Domain
42
+
43
+ ```typescript
44
+ const { url } = await vm.opencode.routeWeb({
45
+ domain: "my-opencode.example.com",
46
+ });
47
+
48
+ const { client } = await vm.opencode.client({
49
+ domain: "my-opencode-api.example.com",
50
+ });
51
+ ```
52
+
53
+ ## Options
54
+
55
+ ```typescript
56
+ new VmOpenCode({
57
+ serverPort: 4096, // Optional: API server port (default: 4096)
58
+ webPort: 4097, // Optional: Web UI port (default: 4097)
59
+ })
60
+ ```
61
+
62
+ | Option | Type | Default | Description |
63
+ |--------|------|---------|-------------|
64
+ | `serverPort` | `number` | `4096` | Port for the OpenCode API server |
65
+ | `webPort` | `number` | `4097` | Port for the OpenCode web UI |
66
+
67
+ ## API
68
+
69
+ ### `vm.opencode.routeWeb(options?)`
70
+
71
+ Exposes the OpenCode web UI on a public domain.
72
+
73
+ **Options:**
74
+ - `domain?: string` - Custom domain to use. If not specified, generates a random subdomain.
75
+
76
+ **Returns:** `Promise<{ url: string }>`
77
+
78
+ ```typescript
79
+ const { url } = await vm.opencode.routeWeb();
80
+ console.log(`OpenCode UI available at: ${url}`);
81
+ ```
82
+
83
+ ### `vm.opencode.client(options?)`
84
+
85
+ Creates an OpenCode SDK client connected to the server.
86
+
87
+ **Options:**
88
+ - `domain?: string` - Custom domain for the API endpoint. If not specified, generates a random subdomain.
89
+
90
+ **Returns:** `Promise<{ client: OpencodeClient }>`
91
+
92
+ ```typescript
93
+ const { client } = await vm.opencode.client();
94
+
95
+ // List sessions
96
+ const sessions = await client.session.list();
97
+
98
+ // Create a new session
99
+ const session = await client.session.create({ path: "/workspace" });
100
+ ```
101
+
102
+ ### `vm.opencode.serverPort()`
103
+
104
+ Returns the configured API server port.
105
+
106
+ **Returns:** `number`
107
+
108
+ ### `vm.opencode.webPort()`
109
+
110
+ Returns the configured web UI port.
111
+
112
+ **Returns:** `number`
113
+
114
+ ## How It Works
115
+
116
+ The package uses systemd services to install and run OpenCode during VM creation:
117
+
118
+ 1. Installs OpenCode via the official install script
119
+ 2. Starts the OpenCode API server (`opencode serve`)
120
+ 3. Starts the OpenCode web UI (`opencode web`)
121
+
122
+ Both services are configured to restart automatically and listen on all interfaces (0.0.0.0).
123
+
124
+ ## Documentation
125
+
126
+ - [Freestyle Documentation](https://docs.freestyle.sh)
127
+ - [OpenCode Documentation](https://opencode.ai/docs)
@@ -0,0 +1,40 @@
1
+ import * as freestyle_sandboxes from 'freestyle-sandboxes';
2
+ import { VmWith, VmWithInstance, VmSpec } from 'freestyle-sandboxes';
3
+ import * as _opencode_ai_sdk from '@opencode-ai/sdk';
4
+
5
+ type OpenCodeOptions = {
6
+ serverPort?: number;
7
+ webPort?: number;
8
+ };
9
+ type OpenCodeResolvedOptions = {
10
+ serverPort: number;
11
+ webPort: number;
12
+ };
13
+ declare class VmOpenCode extends VmWith<VmOpenCodeInstance> {
14
+ options: OpenCodeResolvedOptions;
15
+ constructor(options?: OpenCodeOptions);
16
+ configureSnapshotSpec(spec: VmSpec): VmSpec;
17
+ createInstance(): VmOpenCodeInstance;
18
+ installServiceName(): string;
19
+ }
20
+ declare class VmOpenCodeInstance extends VmWithInstance {
21
+ builder: VmOpenCode;
22
+ constructor(builder: VmOpenCode);
23
+ webPort(): number;
24
+ serverPort(): number;
25
+ client({ domain }?: {
26
+ domain?: string;
27
+ }): Promise<{
28
+ client: _opencode_ai_sdk.OpencodeClient;
29
+ }>;
30
+ routeWeb({ domain }?: {
31
+ domain?: string;
32
+ }): Promise<{
33
+ url: string;
34
+ }>;
35
+ /** @internal */
36
+ get _vm(): freestyle_sandboxes.Vm;
37
+ }
38
+
39
+ export { VmOpenCode };
40
+ export type { OpenCodeOptions, OpenCodeResolvedOptions };
package/dist/index.js ADDED
@@ -0,0 +1,135 @@
1
+ import { VmWith, VmSpec, VmWithInstance } from 'freestyle-sandboxes';
2
+ import { createOpencodeClient } from '@opencode-ai/sdk';
3
+
4
+ class VmOpenCode extends VmWith {
5
+ options;
6
+ constructor(options) {
7
+ super();
8
+ this.options = {
9
+ serverPort: options?.serverPort ?? 4096,
10
+ webPort: options?.webPort ?? 4097
11
+ };
12
+ }
13
+ configureSnapshotSpec(spec) {
14
+ return this.composeSpecs(
15
+ spec,
16
+ new VmSpec({
17
+ additionalFiles: {
18
+ "/opt/install-opencode.sh": {
19
+ content: `
20
+ #!/bin/bash
21
+
22
+ curl -fsSL https://opencode.ai/install | bash
23
+ `
24
+ },
25
+ "/opt/run-opencode-web.sh": {
26
+ content: `
27
+ #!/bin/bash
28
+
29
+ export PATH="$HOME/.local/bin:$PATH"
30
+ /root/.opencode/bin/opencode web --hostname 0.0.0.0 --port ${this.options.webPort}
31
+ `
32
+ },
33
+ "/opt/run-opencode-server.sh": {
34
+ content: `
35
+ #!/bin/bash
36
+
37
+ export PATH="$HOME/.local/bin:$PATH"
38
+ /root/.opencode/bin/opencode serve --hostname 0.0.0.0 --port ${this.options.serverPort}
39
+ `
40
+ }
41
+ },
42
+ systemd: {
43
+ services: [
44
+ {
45
+ name: "install-opencode",
46
+ mode: "oneshot",
47
+ env: {
48
+ HOME: "/root"
49
+ },
50
+ exec: ["bash /opt/install-opencode.sh"],
51
+ timeoutSec: 300
52
+ },
53
+ {
54
+ name: "opencode-server",
55
+ mode: "service",
56
+ env: {
57
+ HOME: "/root"
58
+ },
59
+ after: ["install-opencode.service"],
60
+ requires: ["install-opencode.service"],
61
+ restartPolicy: {
62
+ policy: "always"
63
+ },
64
+ exec: ["bash /opt/run-opencode-server.sh"]
65
+ },
66
+ {
67
+ name: "opencode-web",
68
+ mode: "service",
69
+ env: {
70
+ HOME: "/root"
71
+ },
72
+ after: ["install-opencode.service"],
73
+ requires: ["install-opencode.service"],
74
+ restartPolicy: {
75
+ policy: "always"
76
+ },
77
+ exec: ["bash /opt/run-opencode-web.sh"]
78
+ }
79
+ ]
80
+ }
81
+ })
82
+ );
83
+ }
84
+ createInstance() {
85
+ return new VmOpenCodeInstance(this);
86
+ }
87
+ installServiceName() {
88
+ return "install-opencode.service";
89
+ }
90
+ }
91
+ class VmOpenCodeInstance extends VmWithInstance {
92
+ builder;
93
+ constructor(builder) {
94
+ super();
95
+ this.builder = builder;
96
+ }
97
+ webPort() {
98
+ return this.builder.options.webPort;
99
+ }
100
+ serverPort() {
101
+ return this.builder.options.serverPort;
102
+ }
103
+ async client({ domain } = {}) {
104
+ const resolvedDomain = domain ?? `${crypto.randomUUID()}-opencode.style.dev`;
105
+ const freestyle = this._vm._freestyle;
106
+ await freestyle.domains.mappings.create({
107
+ domain: resolvedDomain,
108
+ vmId: this.vm.vmId,
109
+ vmPort: this.serverPort()
110
+ });
111
+ return {
112
+ client: createOpencodeClient({
113
+ baseUrl: `https://${resolvedDomain}`
114
+ })
115
+ };
116
+ }
117
+ async routeWeb({ domain } = {}) {
118
+ const resolvedDomain = domain ?? `${crypto.randomUUID()}-opencode.style.dev`;
119
+ const freestyle = this._vm._freestyle;
120
+ await freestyle.domains.mappings.create({
121
+ domain: resolvedDomain,
122
+ vmId: this.vm.vmId,
123
+ vmPort: this.webPort()
124
+ });
125
+ return {
126
+ url: `https://${resolvedDomain}`
127
+ };
128
+ }
129
+ /** @internal */
130
+ get _vm() {
131
+ return this.vm;
132
+ }
133
+ }
134
+
135
+ export { VmOpenCode };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@freestyle-sh/with-opencode",
3
+ "version": "0.0.1",
4
+ "packageManager": "pnpm@10.11.0",
5
+ "private": false,
6
+ "dependencies": {
7
+ "@opencode-ai/sdk": "^1.1.42",
8
+ "freestyle-sandboxes": "^0.1.13"
9
+ },
10
+ "devDependencies": {
11
+ "@freestyle-sh/with-web-terminal": "workspace:*",
12
+ "pkgroll": "^2.11.2",
13
+ "typescript": "^5.8.3"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ }
23
+ },
24
+ "source": "./src/index.ts",
25
+ "files": [
26
+ "dist"
27
+ ],
28
+ "scripts": {
29
+ "build": "pkgroll",
30
+ "prepublishOnly": "pnpm run build"
31
+ }
32
+ }