@mickaelroger78/opencode-manager 0.1.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.
- package/LICENSE +21 -0
- package/README.md +213 -0
- package/bin/opencode-manager +22 -0
- package/dist/opencode-manager-darwin-arm64 +0 -0
- package/dist/opencode-manager-darwin-x64 +0 -0
- package/dist/opencode-manager-linux-arm64 +0 -0
- package/dist/opencode-manager-linux-x64 +0 -0
- package/modules/.gitkeep +0 -0
- package/package.json +34 -0
- package/scripts/postinstall.js +78 -0
- package/scripts/verify-package.js +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 opencode-manager contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
# opencode-manager
|
|
2
|
+
|
|
3
|
+
`opencode-manager` is a Go TUI for managing isolated OpenCode workspaces.
|
|
4
|
+
|
|
5
|
+
It lets you run OpenCode inside a dedicated per-project container and attach to it from the host. Each workspace gets its own home directory, generated OpenCode configuration, selected tools, selected credentials, and long-lived container.
|
|
6
|
+
|
|
7
|
+
## Why
|
|
8
|
+
|
|
9
|
+
Running OpenCode directly on a developer machine can give it access to too much: environment variables, cloud accounts, Kubernetes clusters, SSH keys, tokens, and local config files.
|
|
10
|
+
|
|
11
|
+
Running OpenCode in a generic VM or container gives it access to almost nothing, but then every project needs to be manually reconfigured.
|
|
12
|
+
|
|
13
|
+
`opencode-manager` solves this by creating one isolated environment per project. Each workspace receives only the modules and credentials explicitly selected for that project.
|
|
14
|
+
|
|
15
|
+
## Status
|
|
16
|
+
|
|
17
|
+
This repository is in early implementation. The initial Go module, configuration loading, workspace manifest foundations, runtime detection, and minimal TUI shell are in place.
|
|
18
|
+
|
|
19
|
+
See [PROJECT.md](PROJECT.md) for the implementation plan.
|
|
20
|
+
|
|
21
|
+
## Planned Features
|
|
22
|
+
|
|
23
|
+
- TUI application written in Go, with a minimal CLI for automation.
|
|
24
|
+
- Linux and macOS support.
|
|
25
|
+
- Docker and Podman support.
|
|
26
|
+
- Global configuration for workspace root, runtime, base image, and module directories.
|
|
27
|
+
- One long-lived container per workspace.
|
|
28
|
+
- One generated image per workspace.
|
|
29
|
+
- Host TUI attaches to the workspace container.
|
|
30
|
+
- The workspace container runs a small entrypoint that starts `opencode` for the first session and `opencode -c` when sessions already exist.
|
|
31
|
+
- Dedicated home directory per workspace.
|
|
32
|
+
- Module-based configuration for packages, environment variables, config files, commands, skills, agents, and plugins.
|
|
33
|
+
- Module hooks implemented as Go binaries, scripts, or other executables.
|
|
34
|
+
- Main TUI actions to create, attach, edit, stop, delete, and update workspaces.
|
|
35
|
+
- CLI commands limited to `list` and `attach`.
|
|
36
|
+
|
|
37
|
+
## Concept
|
|
38
|
+
|
|
39
|
+
```text
|
|
40
|
+
Host machine
|
|
41
|
+
opencode-manager TUI
|
|
42
|
+
Workspace container
|
|
43
|
+
opencode
|
|
44
|
+
Dedicated home directory
|
|
45
|
+
/home/debian/workspace
|
|
46
|
+
Selected tools
|
|
47
|
+
Selected credentials
|
|
48
|
+
Selected OpenCode commands, skills, agents, and plugins
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Workspace Model
|
|
52
|
+
|
|
53
|
+
Each workspace has:
|
|
54
|
+
|
|
55
|
+
- A name.
|
|
56
|
+
- A dedicated directory under the configured workspace root.
|
|
57
|
+
- A dedicated home directory.
|
|
58
|
+
- A global `opencode.json` mounted read-only at `home/.config/opencode/opencode.json`.
|
|
59
|
+
- Globally shared OpenCode commands, skills, agents, and plugins, mounted read-only.
|
|
60
|
+
- Selected module configuration.
|
|
61
|
+
- A generated image.
|
|
62
|
+
- A long-lived container.
|
|
63
|
+
- A long-lived attachable container that runs OpenCode interactively.
|
|
64
|
+
|
|
65
|
+
Users can clone any needed repositories inside the workspace home directory.
|
|
66
|
+
|
|
67
|
+
At the workspace root, only `workspace.yaml` and `home/` should be created. Environment values, image/package requirements, module state, and generated OpenCode paths are tracked through `workspace.yaml` and files under `home/`.
|
|
68
|
+
|
|
69
|
+
## Module Model
|
|
70
|
+
|
|
71
|
+
Modules are responsible for adding capabilities to a workspace.
|
|
72
|
+
|
|
73
|
+
A module can provide:
|
|
74
|
+
|
|
75
|
+
- Debian packages for the workspace image.
|
|
76
|
+
- Environment variables.
|
|
77
|
+
- Files written into the workspace home directory.
|
|
78
|
+
- OpenCode config fragments.
|
|
79
|
+
- OpenCode commands.
|
|
80
|
+
- OpenCode skills.
|
|
81
|
+
- OpenCode agents and plugins.
|
|
82
|
+
- TUI prompts for required values.
|
|
83
|
+
- Executable hooks for discovery, validation, generation, and updates.
|
|
84
|
+
|
|
85
|
+
Example modules include `ssh`, `git`, `aws`, `kubernetes`, `github`, and `opencode`.
|
|
86
|
+
|
|
87
|
+
## Configuration
|
|
88
|
+
|
|
89
|
+
The global config file defines where workspaces are stored and which container runtime is used.
|
|
90
|
+
|
|
91
|
+
Suggested location:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
~/.config/opencode-manager/config.yaml
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
workspaceRoot: /home/user/.local/share/opencode-manager
|
|
101
|
+
runtime: docker
|
|
102
|
+
useLocalOpenCodeAuth: false
|
|
103
|
+
baseImage:
|
|
104
|
+
name: debian:stable-slim
|
|
105
|
+
packages:
|
|
106
|
+
- htop
|
|
107
|
+
- unzip
|
|
108
|
+
commands:
|
|
109
|
+
- update-ca-certificates
|
|
110
|
+
moduleDirs:
|
|
111
|
+
- /home/user/.config/opencode-manager/modules
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`runtime` must be either `docker` or `podman`.
|
|
115
|
+
|
|
116
|
+
Set `useLocalOpenCodeAuth: true` to mount the host file
|
|
117
|
+
`~/.local/share/opencode/auth.json` read-write into the same path in each
|
|
118
|
+
workspace container. The default `false` keeps auth isolated from the host.
|
|
119
|
+
|
|
120
|
+
Generated workspace images always include `brew`, `npx`, `uvx`, `git`, `ripgrep`, and `jq`. Add project-specific extras with `baseImage.packages` and `baseImage.commands`.
|
|
121
|
+
|
|
122
|
+
`opencode-manager` builds a managed base image from the `baseImage` definition and reuses it while that definition stays unchanged. Changing `baseImage.name`, `baseImage.packages`, or `baseImage.commands` produces a new managed base image tag.
|
|
123
|
+
|
|
124
|
+
When the TUI starts, it ensures the managed base image exists and shows `Creating the base image...` while the image is being built.
|
|
125
|
+
|
|
126
|
+
### Global OpenCode Templates
|
|
127
|
+
|
|
128
|
+
OpenCode configuration is shared across all workspaces from the global config
|
|
129
|
+
directory:
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
~/.config/opencode-manager/
|
|
133
|
+
├── AGENTS.md
|
|
134
|
+
├── opencode.json
|
|
135
|
+
├── agents/
|
|
136
|
+
├── commands/
|
|
137
|
+
├── plugins/
|
|
138
|
+
└── skills/
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
These entries are **mounted read-only** into every workspace container at
|
|
142
|
+
`/home/debian/.config/opencode/`. Editing a file on the host propagates live to
|
|
143
|
+
all running workspaces — no copy is made and no recreation is needed. Adding or
|
|
144
|
+
removing a template takes effect the next time a workspace container is
|
|
145
|
+
(re)created.
|
|
146
|
+
|
|
147
|
+
On startup, `opencode-manager` creates any missing entries (so the mounts always
|
|
148
|
+
have a source):
|
|
149
|
+
|
|
150
|
+
- `AGENTS.md` and the `agents/`, `commands/`, `plugins/`, `skills/` directories
|
|
151
|
+
are created empty.
|
|
152
|
+
- `opencode.json`, which OpenCode requires to be non-empty, is seeded with a
|
|
153
|
+
minimal valid config:
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"$schema": "https://opencode.ai/config.json"
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Existing files are never overwritten, so your edits are preserved. Per-project
|
|
162
|
+
overrides are still possible via an `opencode.json` in the workspace project
|
|
163
|
+
directory.
|
|
164
|
+
|
|
165
|
+
## Security Principle
|
|
166
|
+
|
|
167
|
+
A workspace starts with no implicit host access.
|
|
168
|
+
|
|
169
|
+
Every cloud credential, Kubernetes context, SSH key, token, config file, tool, command, skill, agent, or environment variable must be added by a selected module.
|
|
170
|
+
|
|
171
|
+
Secrets may be stored as environment variables or plain text files inside the workspace when a module needs them.
|
|
172
|
+
|
|
173
|
+
## Installation
|
|
174
|
+
|
|
175
|
+
Install the npm package with:
|
|
176
|
+
|
|
177
|
+
```sh
|
|
178
|
+
npm install -g opencode-manager
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The package ships prebuilt Linux and macOS binaries for x64 and arm64. During
|
|
182
|
+
installation it creates the global `opencode-manager` config directory, writes a
|
|
183
|
+
default `config.yaml` when one does not already exist, and copies bundled
|
|
184
|
+
modules into the default `modules/` directory without overwriting existing
|
|
185
|
+
entries.
|
|
186
|
+
|
|
187
|
+
Docker or Podman must be installed separately on the host.
|
|
188
|
+
|
|
189
|
+
## Development
|
|
190
|
+
|
|
191
|
+
Run the current TUI shell with:
|
|
192
|
+
|
|
193
|
+
```sh
|
|
194
|
+
go run ./cmd/opencode-manager
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Minimal CLI:
|
|
198
|
+
|
|
199
|
+
```sh
|
|
200
|
+
opencode-manager list
|
|
201
|
+
opencode-manager attach <workspace>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The planned stack is:
|
|
205
|
+
|
|
206
|
+
- Go
|
|
207
|
+
- Bubble Tea
|
|
208
|
+
- Bubbles
|
|
209
|
+
- Lip Gloss
|
|
210
|
+
- Docker CLI
|
|
211
|
+
- Podman CLI
|
|
212
|
+
|
|
213
|
+
See [PROJECT.md](PROJECT.md) for phases and architecture.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require("node:child_process");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
const supportedPlatforms = new Set(["darwin", "linux"]);
|
|
7
|
+
const supportedArchitectures = new Set(["x64", "arm64"]);
|
|
8
|
+
|
|
9
|
+
if (!supportedPlatforms.has(process.platform) || !supportedArchitectures.has(process.arch)) {
|
|
10
|
+
console.error(`opencode-manager does not ship a binary for ${process.platform}/${process.arch}.`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const binary = path.join(__dirname, "..", "dist", `opencode-manager-${process.platform}-${process.arch}`);
|
|
15
|
+
const result = spawnSync(binary, process.argv.slice(2), { stdio: "inherit" });
|
|
16
|
+
|
|
17
|
+
if (result.error) {
|
|
18
|
+
console.error(`failed to run opencode-manager: ${result.error.message}`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
process.exit(result.status ?? 1);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/modules/.gitkeep
ADDED
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mickaelroger78/opencode-manager",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TUI for managing isolated OpenCode workspaces.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"opencode-manager": "bin/opencode-manager"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "go build -o dist/opencode-manager-$(node -p \"process.platform\")-$(node -p \"process.arch\") ./cmd/opencode-manager",
|
|
11
|
+
"test": "go test ./...",
|
|
12
|
+
"prepack": "node scripts/verify-package.js",
|
|
13
|
+
"postinstall": "node scripts/postinstall.js"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"bin/",
|
|
17
|
+
"dist/",
|
|
18
|
+
"modules/",
|
|
19
|
+
"scripts/postinstall.js",
|
|
20
|
+
"scripts/verify-package.js"
|
|
21
|
+
],
|
|
22
|
+
"os": [
|
|
23
|
+
"darwin",
|
|
24
|
+
"linux"
|
|
25
|
+
],
|
|
26
|
+
"cpu": [
|
|
27
|
+
"x64",
|
|
28
|
+
"arm64"
|
|
29
|
+
],
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public",
|
|
32
|
+
"provenance": true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const os = require("node:os");
|
|
3
|
+
const path = require("node:path");
|
|
4
|
+
|
|
5
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
6
|
+
const managerDir = path.join(userConfigDir(), "opencode-manager");
|
|
7
|
+
const modulesDir = path.join(managerDir, "modules");
|
|
8
|
+
|
|
9
|
+
fs.mkdirSync(managerDir, { mode: 0o700, recursive: true });
|
|
10
|
+
fs.mkdirSync(modulesDir, { mode: 0o700, recursive: true });
|
|
11
|
+
|
|
12
|
+
ensureConfig();
|
|
13
|
+
copyMissingEntries(path.join(packageRoot, "modules"), modulesDir);
|
|
14
|
+
|
|
15
|
+
function userConfigDir() {
|
|
16
|
+
if (process.env.XDG_CONFIG_HOME) {
|
|
17
|
+
return process.env.XDG_CONFIG_HOME;
|
|
18
|
+
}
|
|
19
|
+
if (process.platform === "darwin") {
|
|
20
|
+
return path.join(os.homedir(), "Library", "Application Support");
|
|
21
|
+
}
|
|
22
|
+
return path.join(os.homedir(), ".config");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function ensureConfig() {
|
|
26
|
+
const configPath = path.join(managerDir, "config.yaml");
|
|
27
|
+
if (fs.existsSync(configPath)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const workspaceRoot = path.join(os.homedir(), ".local", "share", "opencode-manager");
|
|
32
|
+
const config = [
|
|
33
|
+
`workspaceRoot: ${yamlString(workspaceRoot)}`,
|
|
34
|
+
"runtime: docker",
|
|
35
|
+
"useLocalOpenCodeAuth: false",
|
|
36
|
+
"baseImage:",
|
|
37
|
+
" name: debian:stable-slim",
|
|
38
|
+
" packages: []",
|
|
39
|
+
" commands: []",
|
|
40
|
+
"moduleDirs:",
|
|
41
|
+
` - ${yamlString(modulesDir)}`,
|
|
42
|
+
"",
|
|
43
|
+
].join("\n");
|
|
44
|
+
|
|
45
|
+
fs.writeFileSync(configPath, config, { mode: 0o600 });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function copyMissingEntries(source, destination) {
|
|
49
|
+
if (!fs.existsSync(source)) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const entry of fs.readdirSync(source, { withFileTypes: true })) {
|
|
54
|
+
if (entry.name === ".gitkeep") {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const sourcePath = path.join(source, entry.name);
|
|
59
|
+
const destinationPath = path.join(destination, entry.name);
|
|
60
|
+
|
|
61
|
+
if (fs.existsSync(destinationPath)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (entry.isDirectory()) {
|
|
66
|
+
fs.cpSync(sourcePath, destinationPath, { recursive: true, mode: fs.constants.COPYFILE_EXCL });
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (entry.isFile()) {
|
|
71
|
+
fs.copyFileSync(sourcePath, destinationPath, fs.constants.COPYFILE_EXCL);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function yamlString(value) {
|
|
77
|
+
return JSON.stringify(value);
|
|
78
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const path = require("node:path");
|
|
3
|
+
|
|
4
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
5
|
+
const binaries = [
|
|
6
|
+
"opencode-manager-linux-x64",
|
|
7
|
+
"opencode-manager-linux-arm64",
|
|
8
|
+
"opencode-manager-darwin-x64",
|
|
9
|
+
"opencode-manager-darwin-arm64",
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
const missing = binaries.filter((binary) => !fs.existsSync(path.join(packageRoot, "dist", binary)));
|
|
13
|
+
|
|
14
|
+
if (missing.length > 0) {
|
|
15
|
+
console.error("Cannot pack opencode-manager; missing prebuilt binaries:");
|
|
16
|
+
for (const binary of missing) {
|
|
17
|
+
console.error(` dist/${binary}`);
|
|
18
|
+
}
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|