@vividcodeai/embeddedcowork 0.0.3 → 0.0.5
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 +24 -24
- package/dist/auth/auth-store.js +2 -2
- package/dist/auth/manager.js +3 -3
- package/dist/background-processes/manager.js +1 -1
- package/dist/config/location.js +4 -4
- package/dist/opencode-config/README.md +4 -4
- package/dist/opencode-config/opencode.jsonc +33 -1
- package/dist/opencode-config/plugin/{embedcowork.ts → embeddedcowork.ts} +3 -3
- package/dist/opencode-config/plugin/lib/request.ts +2 -2
- package/dist/opencode-config.js +1 -1
- package/dist/plugins/handlers.js +2 -2
- package/dist/plugins/voice-mode.js +1 -1
- package/dist/runtime-paths.js +1 -1
- package/dist/server/__tests__/remote-proxy.test.js +3 -3
- package/dist/server/remote-proxy.js +2 -2
- package/dist/server/routes/events.js +1 -1
- package/dist/storage/instance-store.js +1 -1
- package/dist/ui/__tests__/remote-ui.test.js +1 -1
- package/dist/ui/remote-ui.js +2 -2
- package/dist/workspaces/__tests__/spawn.test.js +12 -12
- package/dist/workspaces/git-worktrees.js +1 -1
- package/dist/workspaces/manager.js +2 -2
- package/dist/workspaces/opencode-auth.js +1 -1
- package/dist/workspaces/runtime.js +1 -1
- package/dist/workspaces/spawn.js +1 -1
- package/dist/workspaces/worktree-map.js +4 -4
- package/package.json +2 -2
- package/public/assets/{ChangesTab-DFEdssRe.js → ChangesTab-C2DJXDf9.js} +2 -2
- package/public/assets/{DiffToolbar-CFFsbjNY.js → DiffToolbar-De-3SRCF.js} +1 -1
- package/public/assets/{FilesTab-DzZTwCeV.js → FilesTab-BuQ00MEc.js} +2 -2
- package/public/assets/{GitChangesTab-D8D157YD.js → GitChangesTab-D9bf2jkM.js} +2 -2
- package/public/assets/{SplitFilePanel-Civ71TMb.js → SplitFilePanel-B-3h60o2.js} +1 -1
- package/public/assets/{StatusTab-BzzCSXZS.js → StatusTab-D5s19fRN.js} +1 -1
- package/public/assets/{bundle-full-BtMSZAAD.js → bundle-full-CdNbUxmo.js} +1 -1
- package/public/assets/{diff-viewer-CW-3Ud61.js → diff-viewer-B1l_VZc1.js} +1 -1
- package/public/assets/{index-BLk2Gwi8.js → index-B2LsA7hD.js} +1 -1
- package/public/assets/{index-DRnrsrln.js → index-BErmCqgL.js} +1 -1
- package/public/assets/{index-CW9M2b5d.js → index-BKvZBimW.js} +1 -1
- package/public/assets/{index-DhequqTZ.js → index-BQeBs108.js} +1 -1
- package/public/assets/{index-_At0Dxz7.js → index-BqQARTCd.js} +1 -1
- package/public/assets/{index-BKkzfqNi.js → index-C9Tl2tHH.js} +2 -2
- package/public/assets/{index-AL8sjoID.js → index-ixx_g9gD.js} +1 -1
- package/public/assets/{loading-CztloVdu.js → loading-CQjaT4lJ.js} +1 -1
- package/public/assets/{main-CPG32lf7.js → main-C1yBw4P8.js} +8 -8
- package/public/assets/{markdown-B9y-GSCb.js → markdown-D5eIdNMf.js} +3 -3
- package/public/assets/{monaco-viewer-pWHOUbzL.js → monaco-viewer-9Byc1Kpy.js} +4 -4
- package/public/assets/{todo-DN0SfBX2.js → todo-uxdyLWei.js} +1 -1
- package/public/assets/{tool-call-D3h_ubYo.js → tool-call-C_JEoVSV.js} +3 -3
- package/public/assets/{unified-picker-DR7RnKXW.js → unified-picker-ePaJEYDm.js} +1 -1
- package/public/assets/{wrap-text-BufZr0C4.js → wrap-text-S8HH4qqP.js} +1 -1
- package/public/index.html +3 -3
- package/public/loading.html +3 -3
- package/public/sw.js +1 -1
- package/public/ui-version.json +1 -1
package/README.md
CHANGED
|
@@ -32,13 +32,13 @@
|
|
|
32
32
|
You can run EmbeddedCowork directly without installing it:
|
|
33
33
|
|
|
34
34
|
```sh
|
|
35
|
-
npx @
|
|
35
|
+
npx @vividcodeai/embeddedcowork --launch
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
To list all CLI options:
|
|
39
39
|
|
|
40
40
|
```sh
|
|
41
|
-
npx @
|
|
41
|
+
npx @vividcodeai/embeddedcowork --help
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
On startup, EmbeddedCowork prints two URLs:
|
|
@@ -48,11 +48,11 @@ On startup, EmbeddedCowork prints two URLs:
|
|
|
48
48
|
|
|
49
49
|
### Install Globally
|
|
50
50
|
|
|
51
|
-
Or install it globally to use the `
|
|
51
|
+
Or install it globally to use the `embeddedcowork` command:
|
|
52
52
|
|
|
53
53
|
```sh
|
|
54
|
-
npm install -g @
|
|
55
|
-
|
|
54
|
+
npm install -g @vividcodeai/embeddedcowork
|
|
55
|
+
embeddedcowork --launch
|
|
56
56
|
```
|
|
57
57
|
|
|
58
58
|
### Install Locally (per-project)
|
|
@@ -60,11 +60,11 @@ embedcowork --launch
|
|
|
60
60
|
If you prefer to install EmbeddedCowork into a project and run the local binary:
|
|
61
61
|
|
|
62
62
|
```sh
|
|
63
|
-
npm install @
|
|
64
|
-
npx
|
|
63
|
+
npm install @vividcodeai/embeddedcowork
|
|
64
|
+
npx embeddedcowork --launch
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
(`npx
|
|
67
|
+
(`npx embeddedcowork ...` will use `./node_modules/.bin/embeddedcowork` when present.)
|
|
68
68
|
|
|
69
69
|
### Common Flags
|
|
70
70
|
|
|
@@ -81,16 +81,16 @@ You can configure the server using flags or environment variables:
|
|
|
81
81
|
| `--tls-ca <path>` | `CLI_TLS_CA` | Optional CA chain/bundle (PEM) |
|
|
82
82
|
| `--tlsSANs <list>` | `CLI_TLS_SANS` | Additional TLS SANs (comma-separated) |
|
|
83
83
|
| `--host <addr>` | `CLI_HOST` | Interface to bind (default 127.0.0.1) |
|
|
84
|
-
| `--workspace-root <path>` | `CLI_WORKSPACE_ROOT` | Restricts the root path where new workspaces can be opened. Git worktrees are created in `.
|
|
84
|
+
| `--workspace-root <path>` | `CLI_WORKSPACE_ROOT` | Restricts the root path where new workspaces can be opened. Git worktrees are created in `.embeddedcowork/worktrees` inside the project folder. |
|
|
85
85
|
| `--unrestricted-root` | `CLI_UNRESTRICTED_ROOT` | Allow full-filesystem browsing |
|
|
86
86
|
| `--config <path>` | `CLI_CONFIG` | Config file location |
|
|
87
87
|
| `--launch` | `CLI_LAUNCH` | Open the UI in a Chromium-based browser |
|
|
88
88
|
| `--log-level <level>` | `CLI_LOG_LEVEL` | Logging level (trace, debug, info, warn, error) |
|
|
89
89
|
| `--log-destination <path>` | `CLI_LOG_DESTINATION` | Log destination file (defaults to stdout) |
|
|
90
|
-
| `--username <username>` | `
|
|
91
|
-
| `--password <password>` | `
|
|
92
|
-
| `--generate-token` | `
|
|
93
|
-
| `--dangerously-skip-auth` | `
|
|
90
|
+
| `--username <username>` | `EMBEDDEDCOWORK_SERVER_USERNAME` | Username for EmbeddedCowork's internal auth (default `embeddedcowork`) |
|
|
91
|
+
| `--password <password>` | `EMBEDDEDCOWORK_SERVER_PASSWORD` | Password for EmbeddedCowork's internal auth |
|
|
92
|
+
| `--generate-token` | `EMBEDDEDCOWORK_GENERATE_TOKEN` | Emit a one-time local bootstrap token for desktop flows |
|
|
93
|
+
| `--dangerously-skip-auth` | `EMBEDDEDCOWORK_SKIP_AUTH` | Disable EmbeddedCowork's internal auth (use only behind a trusted perimeter) |
|
|
94
94
|
| `--ui-dir <path>` | `CLI_UI_DIR` | Directory containing the built UI bundle |
|
|
95
95
|
| `--ui-dev-server <url>` | `CLI_UI_DEV_SERVER` | Proxy UI requests to a running dev server (requires `--https=false --http=true`) |
|
|
96
96
|
| `--ui-no-update` | `CLI_UI_NO_UPDATE` | Disable remote UI updates |
|
|
@@ -102,15 +102,15 @@ You can configure the server using flags or environment variables:
|
|
|
102
102
|
If you want the latest bleeding-edge builds (published as GitHub pre-releases), use the dev package:
|
|
103
103
|
|
|
104
104
|
```sh
|
|
105
|
-
npx @
|
|
105
|
+
npx @vividcodeai/embeddedcowork-dev --launch
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
These environment variables control how EmbeddedCowork checks for dev updates:
|
|
109
109
|
|
|
110
110
|
| Env Variable | Description |
|
|
111
111
|
|-------------|-------------|
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
112
|
+
| `EMBEDDEDCOWORK_UPDATE_CHANNEL` | Update channel (use `dev` to enable dev build update checks) |
|
|
113
|
+
| `EMBEDDEDCOWORK_GITHUB_REPO` | GitHub repo used for dev release checks (default `VividCodeAI/EmbeddedCowork`) |
|
|
114
114
|
|
|
115
115
|
### HTTP vs HTTPS
|
|
116
116
|
|
|
@@ -118,13 +118,13 @@ These environment variables control how EmbeddedCowork checks for dev updates:
|
|
|
118
118
|
- To run plain HTTP only (useful for development):
|
|
119
119
|
|
|
120
120
|
```sh
|
|
121
|
-
|
|
121
|
+
embeddedcowork --https=false --http=true
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
- To run both HTTPS (for remote) and HTTP loopback (for desktop):
|
|
125
125
|
|
|
126
126
|
```sh
|
|
127
|
-
|
|
127
|
+
embeddedcowork --https=true --http=true
|
|
128
128
|
```
|
|
129
129
|
|
|
130
130
|
### Remote Access Binding Rules
|
|
@@ -139,19 +139,19 @@ embedcowork --https=true --http=true
|
|
|
139
139
|
|
|
140
140
|
If `--https=true` and you do not provide `--tls-key/--tls-cert`, EmbeddedCowork generates a local certificate automatically under your config directory:
|
|
141
141
|
|
|
142
|
-
- `~/.config/
|
|
143
|
-
- `~/.config/
|
|
142
|
+
- `~/.config/embeddedcowork/tls/ca-cert.pem`
|
|
143
|
+
- `~/.config/embeddedcowork/tls/server-cert.pem`
|
|
144
144
|
|
|
145
145
|
Certificates are valid for about 30 days and rotate automatically on startup when needed. You can add extra SANs via:
|
|
146
146
|
|
|
147
147
|
```sh
|
|
148
|
-
|
|
148
|
+
embeddedcowork --tlsSANs "localhost,127.0.0.1,my-hostname,192.168.1.10"
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
### Authentication
|
|
152
152
|
|
|
153
153
|
- Default behavior: EmbeddedCowork requires a login (username/password) and stores a session cookie in the browser.
|
|
154
|
-
- `--dangerously-skip-auth` / `
|
|
154
|
+
- `--dangerously-skip-auth` / `EMBEDDEDCOWORK_SKIP_AUTH=true` disables the login prompt and treats all requests as authenticated.
|
|
155
155
|
Use this only when access is already protected by another layer (SSO proxy, VPN, Coder workspace auth, etc.).
|
|
156
156
|
If you bind to `0.0.0.0` while skipping auth, anyone who can reach the port can access the API.
|
|
157
157
|
|
|
@@ -169,5 +169,5 @@ When running as a server EmbeddedCowork can also be installed as a PWA from any
|
|
|
169
169
|
|
|
170
170
|
### Data Storage
|
|
171
171
|
|
|
172
|
-
- **Config**: `~/.config/
|
|
173
|
-
- **Instance Data**: `~/.config/
|
|
172
|
+
- **Config**: `~/.config/embeddedcowork/config.json`
|
|
173
|
+
- **Instance Data**: `~/.config/embeddedcowork/instances` (chat history, etc.)
|
package/dist/auth/auth-store.js
CHANGED
|
@@ -68,7 +68,7 @@ export class AuthStore {
|
|
|
68
68
|
this.logger.debug({ authFilePath: this.authFilePath }, "No auth file present; bootstrap-only mode enabled");
|
|
69
69
|
return;
|
|
70
70
|
}
|
|
71
|
-
throw new Error(`No server password configured. Create ${this.authFilePath} or start with --password /
|
|
71
|
+
throw new Error(`No server password configured. Create ${this.authFilePath} or start with --password / EMBEDDEDCOWORK_SERVER_PASSWORD.`);
|
|
72
72
|
}
|
|
73
73
|
validateCredentials(username, password) {
|
|
74
74
|
const auth = this.load();
|
|
@@ -82,7 +82,7 @@ export class AuthStore {
|
|
|
82
82
|
}
|
|
83
83
|
setPassword(params) {
|
|
84
84
|
if (this.overrideAuth) {
|
|
85
|
-
throw new Error("Server password is provided via CLI/env and cannot be changed while running. Restart without --password /
|
|
85
|
+
throw new Error("Server password is provided via CLI/env and cannot be changed while running. Restart without --password / EMBEDDEDCOWORK_SERVER_PASSWORD to use auth.json.");
|
|
86
86
|
}
|
|
87
87
|
const current = this.load();
|
|
88
88
|
if (!current) {
|
package/dist/auth/manager.js
CHANGED
|
@@ -3,9 +3,9 @@ import { AuthStore } from "./auth-store";
|
|
|
3
3
|
import { TokenManager } from "./token-manager";
|
|
4
4
|
import { SessionManager } from "./session-manager";
|
|
5
5
|
import { isLoopbackAddress, parseCookies } from "./http-auth";
|
|
6
|
-
export const BOOTSTRAP_TOKEN_STDOUT_PREFIX = "
|
|
7
|
-
export const DEFAULT_AUTH_USERNAME = "
|
|
8
|
-
export const DEFAULT_AUTH_COOKIE_NAME = "
|
|
6
|
+
export const BOOTSTRAP_TOKEN_STDOUT_PREFIX = "EMBEDDEDCOWORK_BOOTSTRAP_TOKEN:";
|
|
7
|
+
export const DEFAULT_AUTH_USERNAME = "embeddedcowork";
|
|
8
|
+
export const DEFAULT_AUTH_COOKIE_NAME = "embeddedcowork_session";
|
|
9
9
|
export class AuthManager {
|
|
10
10
|
constructor(init, logger) {
|
|
11
11
|
this.init = init;
|
|
@@ -2,7 +2,7 @@ import { spawn, spawnSync } from "child_process";
|
|
|
2
2
|
import { createWriteStream, existsSync, promises as fs } from "fs";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { randomBytes } from "crypto";
|
|
5
|
-
const ROOT_DIR = ".
|
|
5
|
+
const ROOT_DIR = ".embeddedcowork/background_processes";
|
|
6
6
|
const INDEX_FILE = "index.json";
|
|
7
7
|
const OUTPUT_FILE = "output.txt";
|
|
8
8
|
const STOP_TIMEOUT_MS = 2000;
|
package/dist/config/location.js
CHANGED
|
@@ -17,13 +17,13 @@ function isJsonPath(filePath) {
|
|
|
17
17
|
* Resolve EmbeddedCowork's config location into a stable base directory + derived file paths.
|
|
18
18
|
*
|
|
19
19
|
* Supported inputs:
|
|
20
|
-
* - Directory: "~/.config/
|
|
21
|
-
* - YAML file: "~/.config/
|
|
22
|
-
* - Legacy JSON file: "~/.config/
|
|
20
|
+
* - Directory: "~/.config/embeddedcowork"
|
|
21
|
+
* - YAML file: "~/.config/embeddedcowork/config.yaml" (or any *.yml/*.yaml)
|
|
22
|
+
* - Legacy JSON file: "~/.config/embeddedcowork/config.json"
|
|
23
23
|
*/
|
|
24
24
|
export function resolveConfigLocation(raw) {
|
|
25
25
|
const trimmed = (raw ?? "").trim();
|
|
26
|
-
const fallback = "~/.config/
|
|
26
|
+
const fallback = "~/.config/embeddedcowork/config.json";
|
|
27
27
|
const input = trimmed.length > 0 ? trimmed : fallback;
|
|
28
28
|
const resolvedInput = resolvePath(input);
|
|
29
29
|
if (isYamlPath(resolvedInput)) {
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
Template config + plugins injected into every OpenCode instance that EmbeddedCowork launches. It provides an EmbeddedCowork bridge plugin for local event exchange between the CLI server and opencode.
|
|
5
5
|
|
|
6
6
|
## What it is
|
|
7
|
-
A packaged config directory that EmbeddedCowork copies into `~/.config/
|
|
7
|
+
A packaged config directory that EmbeddedCowork copies into `~/.config/embeddedcowork/opencode-config` for production builds or uses directly in dev. OpenCode autoloads any `plugin/*.ts` or `plugin/*.js` from this directory.
|
|
8
8
|
|
|
9
9
|
## How it works
|
|
10
10
|
- EmbeddedCowork sets `OPENCODE_CONFIG_DIR` when spawning each opencode instance (`packages/server/src/workspaces/manager.ts`).
|
|
11
11
|
- This template is synced from `packages/opencode-config` (`packages/server/src/opencode-config.ts`, `packages/server/scripts/copy-opencode-config.mjs`).
|
|
12
|
-
- OpenCode autoloads plugins from `plugin/` (`packages/opencode-config/plugin/
|
|
13
|
-
- The `EmbeddedCoworkPlugin` reads `
|
|
12
|
+
- OpenCode autoloads plugins from `plugin/` (`packages/opencode-config/plugin/embeddedcowork.ts`).
|
|
13
|
+
- The `EmbeddedCoworkPlugin` reads `EMBEDDEDCOWORK_INSTANCE_ID` + `EMBEDDEDCOWORK_BASE_URL`, connects to `GET /workspaces/:id/plugin/events`, and posts to `POST /workspaces/:id/plugin/event` (`packages/opencode-config/plugin/lib/client.ts`).
|
|
14
14
|
- The server exposes the plugin routes and maps events into the UI SSE pipeline (`packages/server/src/server/routes/plugin.ts`, `packages/server/src/plugins/handlers.ts`).
|
|
15
15
|
|
|
16
16
|
## Expectations
|
|
@@ -25,7 +25,7 @@ A packaged config directory that EmbeddedCowork copies into `~/.config/embedcowo
|
|
|
25
25
|
- Promote stable event shapes and version tags once the protocol settles.
|
|
26
26
|
|
|
27
27
|
## Pointers
|
|
28
|
-
- Plugin entry: `packages/opencode-config/plugin/
|
|
28
|
+
- Plugin entry: `packages/opencode-config/plugin/embeddedcowork.ts`
|
|
29
29
|
- Plugin client: `packages/opencode-config/plugin/lib/client.ts`
|
|
30
30
|
- Plugin server routes: `packages/server/src/server/routes/plugin.ts`
|
|
31
31
|
- Plugin event handling: `packages/server/src/plugins/handlers.ts`
|
|
@@ -1,3 +1,35 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://opencode.ai/config.json"
|
|
2
|
+
"$schema": "https://opencode.ai/config.json",
|
|
3
|
+
"mcp": {
|
|
4
|
+
"embedded-xLink-mcp": {
|
|
5
|
+
"type": "local",
|
|
6
|
+
"command": [
|
|
7
|
+
"npx",
|
|
8
|
+
"-y",
|
|
9
|
+
"@vividcodeai/embedded-xlink-mcp"
|
|
10
|
+
],
|
|
11
|
+
"enabled": false,
|
|
12
|
+
"environment": {}
|
|
13
|
+
},
|
|
14
|
+
"embedded-serial-mcp": {
|
|
15
|
+
"type": "local",
|
|
16
|
+
"command": [
|
|
17
|
+
"npx",
|
|
18
|
+
"-y",
|
|
19
|
+
"@vividcodeai/embedded-serial-mcp"
|
|
20
|
+
],
|
|
21
|
+
"enabled": false,
|
|
22
|
+
"environment": {}
|
|
23
|
+
},
|
|
24
|
+
"embedded-gdb-mcp": {
|
|
25
|
+
"type": "local",
|
|
26
|
+
"command": [
|
|
27
|
+
"npx",
|
|
28
|
+
"-y",
|
|
29
|
+
"@vividcodeai/embedded-gdb-mcp"
|
|
30
|
+
],
|
|
31
|
+
"enabled": false,
|
|
32
|
+
"environment": {}
|
|
33
|
+
},
|
|
34
|
+
},
|
|
3
35
|
}
|
|
@@ -10,9 +10,9 @@ export async function EmbeddedCoworkPlugin(input: PluginInput) {
|
|
|
10
10
|
const backgroundProcessTools = createBackgroundProcessTools(config, { baseDir: input.directory })
|
|
11
11
|
|
|
12
12
|
await client.startEvents((event) => {
|
|
13
|
-
if (event.type === "
|
|
13
|
+
if (event.type === "embeddedcowork.ping") {
|
|
14
14
|
void client.postEvent({
|
|
15
|
-
type: "
|
|
15
|
+
type: "embeddedcowork.pong",
|
|
16
16
|
properties: {
|
|
17
17
|
ts: Date.now(),
|
|
18
18
|
pingTs: (event.properties as any)?.ts,
|
|
@@ -21,7 +21,7 @@ export async function EmbeddedCoworkPlugin(input: PluginInput) {
|
|
|
21
21
|
return
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
if (event.type === "
|
|
24
|
+
if (event.type === "embeddedcowork.voiceMode") {
|
|
25
25
|
voiceModeEnabled = Boolean((event.properties as { enabled?: unknown } | undefined)?.enabled)
|
|
26
26
|
}
|
|
27
27
|
})
|
|
@@ -14,8 +14,8 @@ export type EmbeddedCoworkConfig = {
|
|
|
14
14
|
|
|
15
15
|
export function getEmbeddedCoworkConfig(): EmbeddedCoworkConfig {
|
|
16
16
|
return {
|
|
17
|
-
instanceId: requireEnv("
|
|
18
|
-
baseUrl: requireEnv("
|
|
17
|
+
instanceId: requireEnv("EMBEDDEDCOWORK_INSTANCE_ID"),
|
|
18
|
+
baseUrl: requireEnv("EMBEDDEDCOWORK_BASE_URL"),
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
package/dist/opencode-config.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createLogger } from "./logger";
|
|
|
3
3
|
import { resolveOpencodeTemplateDir } from "./runtime-paths";
|
|
4
4
|
const log = createLogger({ component: "opencode-config" });
|
|
5
5
|
const templateDir = resolveOpencodeTemplateDir(import.meta.url);
|
|
6
|
-
const isDevBuild = Boolean(process.env.
|
|
6
|
+
const isDevBuild = Boolean(process.env.EMBEDDEDCOWORK_DEV ?? process.env.CLI_UI_DEV_SERVER);
|
|
7
7
|
export function getOpencodeConfigDir() {
|
|
8
8
|
if (!existsSync(templateDir)) {
|
|
9
9
|
throw new Error(`EmbeddedCowork Opencode config template missing at ${templateDir}`);
|
package/dist/plugins/handlers.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export function handlePluginEvent(workspaceId, event, deps) {
|
|
2
2
|
switch (event.type) {
|
|
3
|
-
case "
|
|
3
|
+
case "embeddedcowork.pong":
|
|
4
4
|
deps.logger.debug({ workspaceId, properties: event.properties }, "Plugin pong received");
|
|
5
5
|
return;
|
|
6
6
|
default:
|
|
@@ -9,7 +9,7 @@ export function handlePluginEvent(workspaceId, event, deps) {
|
|
|
9
9
|
}
|
|
10
10
|
export function buildPingEvent() {
|
|
11
11
|
return {
|
|
12
|
-
type: "
|
|
12
|
+
type: "embeddedcowork.ping",
|
|
13
13
|
properties: {
|
|
14
14
|
ts: Date.now(),
|
|
15
15
|
},
|
package/dist/runtime-paths.js
CHANGED
|
@@ -24,7 +24,7 @@ export function getPackagedDistDir() {
|
|
|
24
24
|
}
|
|
25
25
|
export function resolveServerPackageRoot(importMetaUrl) {
|
|
26
26
|
const moduleDir = safeModuleDir(importMetaUrl);
|
|
27
|
-
const configuredRoot = process.env.
|
|
27
|
+
const configuredRoot = process.env.EMBEDDEDCOWORK_SERVER_ROOT?.trim();
|
|
28
28
|
const candidates = [
|
|
29
29
|
configuredRoot ? path.resolve(configuredRoot) : null,
|
|
30
30
|
moduleDir ? path.resolve(moduleDir, "..") : null,
|
|
@@ -7,7 +7,7 @@ import path from "node:path";
|
|
|
7
7
|
import { Agent, fetch } from "undici";
|
|
8
8
|
import { RemoteProxySessionManager } from "../remote-proxy";
|
|
9
9
|
import { resolveHttpsOptions } from "../tls";
|
|
10
|
-
const sharedTempDir = fs.mkdtempSync(path.join(os.tmpdir(), "
|
|
10
|
+
const sharedTempDir = fs.mkdtempSync(path.join(os.tmpdir(), "embeddedcowork-remote-proxy-test-"));
|
|
11
11
|
const sharedTls = resolveHttpsOptions({
|
|
12
12
|
enabled: true,
|
|
13
13
|
configDir: sharedTempDir,
|
|
@@ -38,7 +38,7 @@ describe("RemoteProxySessionManager", () => {
|
|
|
38
38
|
const session2 = await createSession(manager, `${upstreamBaseUrl}/base`);
|
|
39
39
|
const blocked = await proxyFetch(`${session1.proxyOrigin}/status`);
|
|
40
40
|
assert.equal(blocked.status, 403);
|
|
41
|
-
const wrongTokenResponse = await proxyFetch(`${session1.proxyOrigin}/
|
|
41
|
+
const wrongTokenResponse = await proxyFetch(`${session1.proxyOrigin}/__embeddedcowork/api/auth/token`, {
|
|
42
42
|
method: "POST",
|
|
43
43
|
headers: { "content-type": "application/json" },
|
|
44
44
|
body: JSON.stringify({ token: session2.token }),
|
|
@@ -148,7 +148,7 @@ async function createSession(manager, baseUrl) {
|
|
|
148
148
|
};
|
|
149
149
|
}
|
|
150
150
|
async function activateSession(session) {
|
|
151
|
-
const response = await proxyFetch(`${session.proxyOrigin}/
|
|
151
|
+
const response = await proxyFetch(`${session.proxyOrigin}/__embeddedcowork/api/auth/token`, {
|
|
152
152
|
method: "POST",
|
|
153
153
|
headers: { "content-type": "application/json" },
|
|
154
154
|
body: JSON.stringify({ token: session.token }),
|
|
@@ -4,8 +4,8 @@ import { Readable } from "stream";
|
|
|
4
4
|
import { pipeline } from "stream/promises";
|
|
5
5
|
import { Agent, fetch } from "undici";
|
|
6
6
|
const LOOPBACK_HOST = "127.0.0.1";
|
|
7
|
-
const BOOTSTRAP_PAGE_PATH = "/
|
|
8
|
-
const BOOTSTRAP_EXCHANGE_PATH = "/
|
|
7
|
+
const BOOTSTRAP_PAGE_PATH = "/__embeddedcowork/auth/token";
|
|
8
|
+
const BOOTSTRAP_EXCHANGE_PATH = "/__embeddedcowork/api/auth/token";
|
|
9
9
|
const SESSION_IDLE_TTL_MS = 30 * 60000;
|
|
10
10
|
export class RemoteProxySessionManager {
|
|
11
11
|
constructor(options) {
|
|
@@ -30,7 +30,7 @@ export function registerEventRoutes(app, deps) {
|
|
|
30
30
|
const unsubscribe = deps.eventBus.onEvent(send);
|
|
31
31
|
const heartbeat = setInterval(() => {
|
|
32
32
|
const ping = { ts: Date.now() };
|
|
33
|
-
reply.raw.write(`event:
|
|
33
|
+
reply.raw.write(`event: embeddedcowork.client.ping\ndata: ${JSON.stringify(ping)}\n\n`);
|
|
34
34
|
}, 15000);
|
|
35
35
|
let closed = false;
|
|
36
36
|
const close = () => {
|
|
@@ -7,7 +7,7 @@ const DEFAULT_INSTANCE_DATA = {
|
|
|
7
7
|
agentModelSelections: {},
|
|
8
8
|
};
|
|
9
9
|
export class InstanceStore {
|
|
10
|
-
constructor(baseDir = path.join(os.homedir(), ".config", "
|
|
10
|
+
constructor(baseDir = path.join(os.homedir(), ".config", "embeddedcowork", "instances")) {
|
|
11
11
|
this.instancesDir = baseDir;
|
|
12
12
|
fs.mkdirSync(this.instancesDir, { recursive: true });
|
|
13
13
|
}
|
|
@@ -16,7 +16,7 @@ const noopLogger = {
|
|
|
16
16
|
};
|
|
17
17
|
let tempRoot;
|
|
18
18
|
beforeEach(() => {
|
|
19
|
-
tempRoot = mkdtempSync(path.join(os.tmpdir(), "
|
|
19
|
+
tempRoot = mkdtempSync(path.join(os.tmpdir(), "embeddedcowork-ui-test-"));
|
|
20
20
|
});
|
|
21
21
|
afterEach(() => {
|
|
22
22
|
rmSync(tempRoot, { recursive: true, force: true });
|
package/dist/ui/remote-ui.js
CHANGED
|
@@ -6,7 +6,7 @@ import path from "path";
|
|
|
6
6
|
import { Readable } from "stream";
|
|
7
7
|
import { fetch } from "undici";
|
|
8
8
|
import yauzl from "yauzl";
|
|
9
|
-
const DEFAULT_MANIFEST_URL = "https://ui.
|
|
9
|
+
const DEFAULT_MANIFEST_URL = "https://ui.embeddedcowork.vividcode.ai/version.json";
|
|
10
10
|
const MANIFEST_TIMEOUT_MS = 5000;
|
|
11
11
|
const ZIP_TIMEOUT_MS = 30000;
|
|
12
12
|
export async function resolveUi(options) {
|
|
@@ -139,7 +139,7 @@ function resolveUiCacheRoot(configDir) {
|
|
|
139
139
|
if (configDir) {
|
|
140
140
|
return path.join(configDir, "ui");
|
|
141
141
|
}
|
|
142
|
-
return path.join(os.homedir(), ".config", "
|
|
142
|
+
return path.join(os.homedir(), ".config", "embeddedcowork", "ui");
|
|
143
143
|
}
|
|
144
144
|
async function resolveFromCacheOrBundled(args) {
|
|
145
145
|
const bestLocal = await pickBestLocalUi({
|
|
@@ -35,10 +35,10 @@ describe("buildWindowsSpawnSpec", () => {
|
|
|
35
35
|
cwd: String.raw `\\wsl.localhost\Ubuntu\home\dev\workspace`,
|
|
36
36
|
env: {
|
|
37
37
|
OPENCODE_CONFIG_DIR: String.raw `C:\Users\dev\AppData\Roaming\EmbeddedCowork\opencode-config`,
|
|
38
|
-
|
|
38
|
+
EMBEDDEDCOWORK_INSTANCE_ID: "workspace-123",
|
|
39
39
|
OPENCODE_SERVER_PASSWORD: "secret",
|
|
40
40
|
},
|
|
41
|
-
propagateEnvKeys: ["OPENCODE_CONFIG_DIR", "
|
|
41
|
+
propagateEnvKeys: ["OPENCODE_CONFIG_DIR", "EMBEDDEDCOWORK_INSTANCE_ID", "OPENCODE_SERVER_PASSWORD"],
|
|
42
42
|
});
|
|
43
43
|
assert.equal(spec.command, "wsl.exe");
|
|
44
44
|
assert.deepEqual(spec.args, [
|
|
@@ -53,17 +53,17 @@ describe("buildWindowsSpawnSpec", () => {
|
|
|
53
53
|
"0",
|
|
54
54
|
]);
|
|
55
55
|
assert.equal(spec.cwd, undefined);
|
|
56
|
-
assert.equal(spec.env?.WSLENV, "OPENCODE_CONFIG_DIR/p:
|
|
56
|
+
assert.equal(spec.env?.WSLENV, "OPENCODE_CONFIG_DIR/p:EMBEDDEDCOWORK_INSTANCE_ID:OPENCODE_SERVER_PASSWORD");
|
|
57
57
|
});
|
|
58
58
|
it("upgrades existing WSLENV path entries to include /p", () => {
|
|
59
59
|
const spec = buildWindowsSpawnSpec(String.raw `\\wsl.localhost\Ubuntu\home\dev\.opencode\bin\opencode`, ["serve"], {
|
|
60
60
|
env: {
|
|
61
61
|
OPENCODE_CONFIG_DIR: String.raw `C:\Users\dev\AppData\Roaming\EmbeddedCowork\opencode-config`,
|
|
62
|
-
WSLENV: "OPENCODE_CONFIG_DIR:
|
|
62
|
+
WSLENV: "OPENCODE_CONFIG_DIR:EMBEDDEDCOWORK_INSTANCE_ID/u",
|
|
63
63
|
},
|
|
64
|
-
propagateEnvKeys: ["OPENCODE_CONFIG_DIR", "
|
|
64
|
+
propagateEnvKeys: ["OPENCODE_CONFIG_DIR", "EMBEDDEDCOWORK_INSTANCE_ID"],
|
|
65
65
|
});
|
|
66
|
-
assert.equal(spec.env?.WSLENV, "OPENCODE_CONFIG_DIR/p:
|
|
66
|
+
assert.equal(spec.env?.WSLENV, "OPENCODE_CONFIG_DIR/p:EMBEDDEDCOWORK_INSTANCE_ID/u");
|
|
67
67
|
});
|
|
68
68
|
it("propagates inherited known path variables even when they are not explicitly requested", () => {
|
|
69
69
|
const spec = buildWindowsSpawnSpec(String.raw `\\wsl.localhost\Ubuntu\home\dev\.opencode\bin\opencode`, ["serve"], {
|
|
@@ -85,7 +85,7 @@ describe("buildWindowsSpawnSpec", () => {
|
|
|
85
85
|
"sh",
|
|
86
86
|
"-lc",
|
|
87
87
|
'cd "$(wslpath -au "$1")" && shift && exec "$@"',
|
|
88
|
-
"
|
|
88
|
+
"embeddedcowork-wsl-launch",
|
|
89
89
|
String.raw `C:\Users\dev\workspace`,
|
|
90
90
|
"/home/dev/.opencode/bin/opencode",
|
|
91
91
|
"serve",
|
|
@@ -105,7 +105,7 @@ describe("buildWindowsSpawnSpec", () => {
|
|
|
105
105
|
"sh",
|
|
106
106
|
"-lc",
|
|
107
107
|
'cd "$(wslpath -au "$1")" && shift && exec "$@"',
|
|
108
|
-
"
|
|
108
|
+
"embeddedcowork-wsl-launch",
|
|
109
109
|
String.raw `\\server\share\workspace`,
|
|
110
110
|
"/home/dev/.opencode/bin/opencode",
|
|
111
111
|
"serve",
|
|
@@ -114,7 +114,7 @@ describe("buildWindowsSpawnSpec", () => {
|
|
|
114
114
|
it("can wrap WSL launches to emit the Linux PID marker", () => {
|
|
115
115
|
const spec = buildWindowsSpawnSpec(String.raw `\\wsl.localhost\Ubuntu\home\dev\.opencode\bin\opencode`, ["serve"], {
|
|
116
116
|
cwd: String.raw `\\wsl.localhost\Ubuntu\home\dev\workspace`,
|
|
117
|
-
wslPidMarker: "
|
|
117
|
+
wslPidMarker: "__EMBEDDEDCOWORK_WSL_PID__:",
|
|
118
118
|
});
|
|
119
119
|
assert.equal(spec.command, "wsl.exe");
|
|
120
120
|
assert.deepEqual(spec.args, [
|
|
@@ -123,13 +123,13 @@ describe("buildWindowsSpawnSpec", () => {
|
|
|
123
123
|
"--exec",
|
|
124
124
|
"sh",
|
|
125
125
|
"-lc",
|
|
126
|
-
`printf '%s%s\\n' '
|
|
127
|
-
"
|
|
126
|
+
`printf '%s%s\\n' '__EMBEDDEDCOWORK_WSL_PID__:' "$$" && cd "$1" && shift && exec "$@"`,
|
|
127
|
+
"embeddedcowork-wsl-launch",
|
|
128
128
|
"/home/dev/workspace",
|
|
129
129
|
"/home/dev/.opencode/bin/opencode",
|
|
130
130
|
"serve",
|
|
131
131
|
]);
|
|
132
|
-
assert.equal(spec.wsl?.pidMarker, "
|
|
132
|
+
assert.equal(spec.wsl?.pidMarker, "__EMBEDDEDCOWORK_WSL_PID__:");
|
|
133
133
|
});
|
|
134
134
|
it("builds the WSL kill command for tracked Linux PIDs", () => {
|
|
135
135
|
const spec = buildWslSignalSpec("Ubuntu", 4321, "SIGTERM");
|
|
@@ -163,7 +163,7 @@ export async function createManagedWorktree(params) {
|
|
|
163
163
|
.replace(/^-+|-+$/g, "");
|
|
164
164
|
return normalized || "worktree";
|
|
165
165
|
};
|
|
166
|
-
const worktreesDir = path.join(repoRoot, ".
|
|
166
|
+
const worktreesDir = path.join(repoRoot, ".embeddedcowork", "worktrees");
|
|
167
167
|
const targetDir = path.join(worktreesDir, sanitizeDirName(branch));
|
|
168
168
|
await fsp.mkdir(worktreesDir, { recursive: true });
|
|
169
169
|
try {
|
|
@@ -139,8 +139,8 @@ export class WorkspaceManager {
|
|
|
139
139
|
const environment = {
|
|
140
140
|
...userEnvironment,
|
|
141
141
|
OPENCODE_CONFIG_DIR: this.opencodeConfigDir,
|
|
142
|
-
|
|
143
|
-
|
|
142
|
+
EMBEDDEDCOWORK_INSTANCE_ID: id,
|
|
143
|
+
EMBEDDEDCOWORK_BASE_URL: this.options.getServerBaseUrl(),
|
|
144
144
|
...(this.options.nodeExtraCaCertsPath ? { NODE_EXTRA_CA_CERTS: this.options.nodeExtraCaCertsPath } : {}),
|
|
145
145
|
[OPENCODE_SERVER_USERNAME_ENV]: opencodeUsername,
|
|
146
146
|
[OPENCODE_SERVER_PASSWORD_ENV]: opencodePassword,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
export const OPENCODE_SERVER_USERNAME_ENV = "OPENCODE_SERVER_USERNAME";
|
|
3
3
|
export const OPENCODE_SERVER_PASSWORD_ENV = "OPENCODE_SERVER_PASSWORD";
|
|
4
|
-
export const DEFAULT_OPENCODE_USERNAME = "
|
|
4
|
+
export const DEFAULT_OPENCODE_USERNAME = "embeddedcowork";
|
|
5
5
|
export function generateOpencodeServerPassword() {
|
|
6
6
|
return crypto.randomBytes(32).toString("base64url");
|
|
7
7
|
}
|
|
@@ -3,7 +3,7 @@ import { existsSync, statSync } from "fs";
|
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { buildSpawnSpec, buildWslSignalSpec } from "./spawn";
|
|
5
5
|
const SENSITIVE_ENV_KEY = /(PASSWORD|TOKEN|SECRET)/i;
|
|
6
|
-
const WSL_PID_MARKER = "
|
|
6
|
+
const WSL_PID_MARKER = "__EMBEDDEDCOWORK_WSL_PID__:";
|
|
7
7
|
function redactEnvironment(env) {
|
|
8
8
|
const redacted = {};
|
|
9
9
|
for (const [key, value] of Object.entries(env)) {
|
package/dist/workspaces/spawn.js
CHANGED
|
@@ -139,7 +139,7 @@ function buildWslSpawnSpec(wslPath, args, options) {
|
|
|
139
139
|
}
|
|
140
140
|
if (shouldWrapWithShell) {
|
|
141
141
|
const launchScript = buildWslLaunchScript(workingDirectory ?? undefined, options.wslPidMarker);
|
|
142
|
-
wslArgs.push("--exec", "sh", "-lc", launchScript, "
|
|
142
|
+
wslArgs.push("--exec", "sh", "-lc", launchScript, "embeddedcowork-wsl-launch");
|
|
143
143
|
if (workingDirectory) {
|
|
144
144
|
wslArgs.push(workingDirectory.path);
|
|
145
145
|
}
|
|
@@ -8,7 +8,7 @@ const DEFAULT_MAP = {
|
|
|
8
8
|
parentSessionWorktreeSlug: {},
|
|
9
9
|
};
|
|
10
10
|
function getMapPath(repoRoot) {
|
|
11
|
-
return path.join(repoRoot, ".
|
|
11
|
+
return path.join(repoRoot, ".embeddedcowork", "worktreeMap.json");
|
|
12
12
|
}
|
|
13
13
|
function getGitExcludePath(repoRoot) {
|
|
14
14
|
return path.join(repoRoot, ".git", "info", "exclude");
|
|
@@ -22,8 +22,8 @@ async function ensureGitExclude(repoRoot, logger) {
|
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
const entries = [
|
|
25
|
-
".
|
|
26
|
-
".
|
|
25
|
+
".embeddedcowork/worktrees/",
|
|
26
|
+
".embeddedcowork/worktreeMap.json",
|
|
27
27
|
];
|
|
28
28
|
let existing = "";
|
|
29
29
|
try {
|
|
@@ -42,7 +42,7 @@ async function ensureGitExclude(repoRoot, logger) {
|
|
|
42
42
|
if (missing.length === 0) {
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
|
-
const header = existing.includes("#
|
|
45
|
+
const header = existing.includes("# embeddedcowork") ? "" : (existing.trim() ? "\n" : "") + "# embeddedcowork\n";
|
|
46
46
|
const suffix = missing.map((e) => `${e}\n`).join("");
|
|
47
47
|
await fsp.writeFile(excludePath, `${existing}${header}${suffix}`, "utf-8");
|
|
48
48
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vividcodeai/embeddedcowork",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "EmbeddedCowork Server",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"build:ui": "npm run build --prefix ../ui",
|
|
28
28
|
"prepare-ui": "node ./scripts/copy-ui-dist.mjs",
|
|
29
29
|
"prepare-config": "node ./scripts/copy-opencode-config.mjs",
|
|
30
|
-
"dev": "cross-env
|
|
30
|
+
"dev": "cross-env EMBEDDEDCOWORK_DEV=1 EMBEDDEDCOWORK_SERVER_PASSWORD=embeddedcowork-dev CLI_UI_DEV_SERVER=http://localhost:3000 CLI_HTTPS=false CLI_HTTP=true tsx src/index.ts",
|
|
31
31
|
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|