@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 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
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
+ }