@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 +127 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +135 -0
- package/package.json +32 -0
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)
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|