@wavexzore/sandbox 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.
Files changed (149) hide show
  1. package/Dockerfile +14 -0
  2. package/LICENSE +661 -0
  3. package/NOTICE +3 -0
  4. package/README.md +153 -0
  5. package/dist/index.d.ts +9 -0
  6. package/dist/index.js +9 -0
  7. package/dist/sandbox/cli/install.d.ts +5 -0
  8. package/dist/sandbox/cli/install.js +335 -0
  9. package/dist/sandbox/cli/local-store.d.ts +87 -0
  10. package/dist/sandbox/cli/local-store.js +604 -0
  11. package/dist/sandbox/cli/opencode-config.d.ts +25 -0
  12. package/dist/sandbox/cli/opencode-config.js +240 -0
  13. package/dist/sandbox/cli/path.d.ts +64 -0
  14. package/dist/sandbox/cli/path.js +127 -0
  15. package/dist/sandbox/cli/types.d.ts +145 -0
  16. package/dist/sandbox/cli/types.js +6 -0
  17. package/dist/sandbox/cli/wavexzore-sandbox.d.ts +65 -0
  18. package/dist/sandbox/cli/wavexzore-sandbox.js +577 -0
  19. package/dist/sandbox/core/cli-helper.d.ts +19 -0
  20. package/dist/sandbox/core/cli-helper.js +64 -0
  21. package/dist/sandbox/core/docker-archive-utils.d.ts +3 -0
  22. package/dist/sandbox/core/docker-archive-utils.js +50 -0
  23. package/dist/sandbox/core/docker-sandbox.d.ts +51 -0
  24. package/dist/sandbox/core/docker-sandbox.js +675 -0
  25. package/dist/sandbox/core/edit/filediff.d.ts +16 -0
  26. package/dist/sandbox/core/edit/filediff.js +21 -0
  27. package/dist/sandbox/core/edit/index.d.ts +5 -0
  28. package/dist/sandbox/core/edit/index.js +5 -0
  29. package/dist/sandbox/core/edit/line-endings.d.ts +4 -0
  30. package/dist/sandbox/core/edit/line-endings.js +10 -0
  31. package/dist/sandbox/core/edit/lock.d.ts +1 -0
  32. package/dist/sandbox/core/edit/lock.js +18 -0
  33. package/dist/sandbox/core/edit/replace.d.ts +10 -0
  34. package/dist/sandbox/core/edit/replace.js +14 -0
  35. package/dist/sandbox/core/edit/replacers.d.ts +15 -0
  36. package/dist/sandbox/core/edit/replacers.js +241 -0
  37. package/dist/sandbox/core/logger.d.ts +15 -0
  38. package/dist/sandbox/core/logger.js +59 -0
  39. package/dist/sandbox/core/lsp/client.d.ts +63 -0
  40. package/dist/sandbox/core/lsp/client.js +533 -0
  41. package/dist/sandbox/core/lsp/config.d.ts +13 -0
  42. package/dist/sandbox/core/lsp/config.js +36 -0
  43. package/dist/sandbox/core/lsp/diagnostics.d.ts +12 -0
  44. package/dist/sandbox/core/lsp/diagnostics.js +65 -0
  45. package/dist/sandbox/core/lsp/index.d.ts +4 -0
  46. package/dist/sandbox/core/lsp/index.js +4 -0
  47. package/dist/sandbox/core/lsp/language.d.ts +24 -0
  48. package/dist/sandbox/core/lsp/language.js +249 -0
  49. package/dist/sandbox/core/lsp/manager.d.ts +77 -0
  50. package/dist/sandbox/core/lsp/manager.js +237 -0
  51. package/dist/sandbox/core/lsp/tooling.d.ts +14 -0
  52. package/dist/sandbox/core/lsp/tooling.js +78 -0
  53. package/dist/sandbox/core/patch-parser.d.ts +23 -0
  54. package/dist/sandbox/core/patch-parser.js +248 -0
  55. package/dist/sandbox/core/path-map.d.ts +9 -0
  56. package/dist/sandbox/core/path-map.js +73 -0
  57. package/dist/sandbox/core/project-data-storage.d.ts +42 -0
  58. package/dist/sandbox/core/project-data-storage.js +167 -0
  59. package/dist/sandbox/core/read/binary.d.ts +4 -0
  60. package/dist/sandbox/core/read/binary.js +80 -0
  61. package/dist/sandbox/core/read/format.d.ts +38 -0
  62. package/dist/sandbox/core/read/format.js +85 -0
  63. package/dist/sandbox/core/read/index.d.ts +3 -0
  64. package/dist/sandbox/core/read/index.js +3 -0
  65. package/dist/sandbox/core/read/permissions.d.ts +7 -0
  66. package/dist/sandbox/core/read/permissions.js +13 -0
  67. package/dist/sandbox/core/session-manager.d.ts +29 -0
  68. package/dist/sandbox/core/session-manager.js +338 -0
  69. package/dist/sandbox/core/shell/config.d.ts +7 -0
  70. package/dist/sandbox/core/shell/config.js +82 -0
  71. package/dist/sandbox/core/shell/output.d.ts +35 -0
  72. package/dist/sandbox/core/shell/output.js +80 -0
  73. package/dist/sandbox/core/shell/parser.d.ts +7 -0
  74. package/dist/sandbox/core/shell/parser.js +122 -0
  75. package/dist/sandbox/core/shell/permissions.d.ts +13 -0
  76. package/dist/sandbox/core/shell/permissions.js +33 -0
  77. package/dist/sandbox/core/shell/workdir.d.ts +4 -0
  78. package/dist/sandbox/core/shell/workdir.js +19 -0
  79. package/dist/sandbox/core/stream-utils.d.ts +23 -0
  80. package/dist/sandbox/core/stream-utils.js +97 -0
  81. package/dist/sandbox/core/toast.d.ts +47 -0
  82. package/dist/sandbox/core/toast.js +73 -0
  83. package/dist/sandbox/core/types.d.ts +159 -0
  84. package/dist/sandbox/core/types.js +11 -0
  85. package/dist/sandbox/core/write/bom.d.ts +8 -0
  86. package/dist/sandbox/core/write/bom.js +15 -0
  87. package/dist/sandbox/core/write/config.d.ts +14 -0
  88. package/dist/sandbox/core/write/config.js +188 -0
  89. package/dist/sandbox/core/write/diagnostics.d.ts +19 -0
  90. package/dist/sandbox/core/write/diagnostics.js +120 -0
  91. package/dist/sandbox/core/write/diff.d.ts +7 -0
  92. package/dist/sandbox/core/write/diff.js +21 -0
  93. package/dist/sandbox/core/write/formatter.d.ts +16 -0
  94. package/dist/sandbox/core/write/formatter.js +51 -0
  95. package/dist/sandbox/core/write/index.d.ts +6 -0
  96. package/dist/sandbox/core/write/index.js +5 -0
  97. package/dist/sandbox/core/write/permissions.d.ts +13 -0
  98. package/dist/sandbox/core/write/permissions.js +19 -0
  99. package/dist/sandbox/core/write/pipeline.d.ts +48 -0
  100. package/dist/sandbox/core/write/pipeline.js +229 -0
  101. package/dist/sandbox/core/write/read-tracker.d.ts +13 -0
  102. package/dist/sandbox/core/write/read-tracker.js +30 -0
  103. package/dist/sandbox/git/host-git-manager.d.ts +40 -0
  104. package/dist/sandbox/git/host-git-manager.js +278 -0
  105. package/dist/sandbox/git/index.d.ts +5 -0
  106. package/dist/sandbox/git/index.js +5 -0
  107. package/dist/sandbox/git/sandbox-git-manager.d.ts +14 -0
  108. package/dist/sandbox/git/sandbox-git-manager.js +54 -0
  109. package/dist/sandbox/git/session-git-manager.d.ts +18 -0
  110. package/dist/sandbox/git/session-git-manager.js +85 -0
  111. package/dist/sandbox/index.d.ts +205 -0
  112. package/dist/sandbox/index.js +70 -0
  113. package/dist/sandbox/plugins/custom-tools.d.ts +203 -0
  114. package/dist/sandbox/plugins/custom-tools.js +15 -0
  115. package/dist/sandbox/plugins/session-events.d.ts +10 -0
  116. package/dist/sandbox/plugins/session-events.js +56 -0
  117. package/dist/sandbox/plugins/system-transform.d.ts +10 -0
  118. package/dist/sandbox/plugins/system-transform.js +23 -0
  119. package/dist/sandbox/tools/bash-output.d.ts +17 -0
  120. package/dist/sandbox/tools/bash-output.js +35 -0
  121. package/dist/sandbox/tools/bash-status.d.ts +13 -0
  122. package/dist/sandbox/tools/bash-status.js +29 -0
  123. package/dist/sandbox/tools/bash-stop.d.ts +13 -0
  124. package/dist/sandbox/tools/bash-stop.js +28 -0
  125. package/dist/sandbox/tools/bash.d.ts +26 -0
  126. package/dist/sandbox/tools/bash.js +120 -0
  127. package/dist/sandbox/tools/edit.d.ts +20 -0
  128. package/dist/sandbox/tools/edit.js +87 -0
  129. package/dist/sandbox/tools/get-preview-url.d.ts +17 -0
  130. package/dist/sandbox/tools/get-preview-url.js +16 -0
  131. package/dist/sandbox/tools/glob.d.ts +17 -0
  132. package/dist/sandbox/tools/glob.js +23 -0
  133. package/dist/sandbox/tools/grep.d.ts +17 -0
  134. package/dist/sandbox/tools/grep.js +23 -0
  135. package/dist/sandbox/tools/ls.d.ts +17 -0
  136. package/dist/sandbox/tools/ls.js +21 -0
  137. package/dist/sandbox/tools/lsp.d.ts +41 -0
  138. package/dist/sandbox/tools/lsp.js +198 -0
  139. package/dist/sandbox/tools/multiedit.d.ts +24 -0
  140. package/dist/sandbox/tools/multiedit.js +83 -0
  141. package/dist/sandbox/tools/patch.d.ts +14 -0
  142. package/dist/sandbox/tools/patch.js +260 -0
  143. package/dist/sandbox/tools/read.d.ts +22 -0
  144. package/dist/sandbox/tools/read.js +105 -0
  145. package/dist/sandbox/tools/write.d.ts +16 -0
  146. package/dist/sandbox/tools/write.js +27 -0
  147. package/dist/sandbox/tools.d.ts +200 -0
  148. package/dist/sandbox/tools.js +43 -0
  149. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # @wavexzore/sandbox
2
+
3
+ 基于本地 Docker 的 OpenCode 编码沙箱插件。
4
+
5
+ ## 功能概述
6
+
7
+ - 通过 `@opencode-ai/plugin` 注册 OpenCode 插件钩子
8
+ - 用 Docker 后端实现替换 14 个核心工具:`bash`、`bash-stop`、`bash-output`、`bash-status`、`read`、`write`、`edit`、`multiedit`、`apply_patch`、`glob`、`grep`、`ls`、`lsp`、`get-preview-url`
9
+ - 通过 `dockerode` 为每个 OpenCode 会话创建独立的 Docker 容器
10
+ - 将宿主机工作目录 bind mount 到容器内
11
+ - 在容器内执行 Shell 命令,在宿主机保留 LSP 诊断能力
12
+ - OpenCode 会话删除时自动清理容器
13
+
14
+ ## 架构概览
15
+
16
+ ```text
17
+ 插件层 custom-tools · session-events · system-transform
18
+
19
+ 工具层 14 个沙箱工具 (bash/read/write/edit/patch/glob/grep/ls/lsp/...)
20
+
21
+ 核心层 session-manager ──► docker-sandbox (Sandbox 接口)
22
+ shell/ · write/ · read/ · edit/ · lsp/
23
+ path-map (宿主机↔容器) · project-data-storage
24
+
25
+ 基础设施层 dockerode · tar-stream · diff · vscode-jsonrpc
26
+ Docker Engine ──► bind mount (宿主机 worktree ↔ 容器 /repo)
27
+ ```
28
+
29
+ 关键设计要点:
30
+
31
+ - **写入管线**:权限检查 → BOM 处理 → 文件上传 → 格式化 → LSP 诊断,完整链路一条龙
32
+ - **先读后写守卫**:文件必须先被读取后才能写入(由 `readRegistry` 强制执行)
33
+ - **9 策略编辑回退**:精确匹配 → 行首匹配 → 块锚点 → 空白忽略 → 缩进忽略 → 转义处理等
34
+ - **LSP 懒加载**:语言服务器仅在首次访问文件时启动
35
+ - **双轨 Git**:容器端通过 `docker exec` 执行 git 命令,宿主机端通过 `child_process` 执行
36
+
37
+ ## 安装
38
+
39
+ 在 `$HOME` 目录下运行安装命令(默认 user scope):
40
+
41
+ ```bash
42
+ npx @wavexzore/sandbox install
43
+ ```
44
+
45
+ 安装器会自动完成:将插件写入 OpenCode 全局配置、创建版本化存储目录、生成 bin shim 并设置 PATH。
46
+
47
+ 其他命令:
48
+
49
+ ```bash
50
+ npx @wavexzore/sandbox install --dry-run --json # 预览安装内容
51
+ npx @wavexzore/sandbox doctor --json # 诊断安装状态
52
+ npx @wavexzore/sandbox uninstall # 卸载
53
+ ```
54
+
55
+ ## 运行要求
56
+
57
+ - 本地运行的 Docker 守护进程
58
+ - 沙箱镜像,默认为 `opencode-sandbox:latest`
59
+ - Node.js(安装器/helper 需要)
60
+
61
+ ### 环境变量
62
+
63
+ | 变量 | 默认值 | 说明 |
64
+ |------|--------|------|
65
+ | `SANDBOX_IMAGE` | `opencode-sandbox:latest` | 沙箱容器使用的 Docker 镜像 |
66
+ | `DOCKER_SOCKET` | `/var/run/docker.sock` | Docker 守护进程 socket 路径 |
67
+
68
+ ### 项目级配置(`.opencode/sandbox.json`)
69
+
70
+ ```json
71
+ {
72
+ "image": "opencode-sandbox:latest",
73
+ "shell": {
74
+ "defaultTimeoutMs": 120000,
75
+ "maxOutputBytes": 200000,
76
+ "maxOutputLines": 2000,
77
+ "allowWorkdirOutsideProject": false
78
+ },
79
+ "lsp": {
80
+ "enabled": true
81
+ }
82
+ }
83
+ ```
84
+
85
+ ## 沙箱镜像
86
+
87
+ ```bash
88
+ docker build -t opencode-sandbox:latest -f packages/sandbox/Dockerfile packages/sandbox
89
+ ```
90
+
91
+ 默认容器项目路径为 `/home/sandbox/project`。
92
+
93
+ ## 宿主机 LSP
94
+
95
+ 工作目录通过 bind mount 挂载,因此映射文件在宿主机和容器中均可访问。插件在宿主机上运行 LSP 服务器,提供诊断和代码导航:
96
+
97
+ - TypeScript/JavaScript:`typescript-language-server`
98
+ - Go:`gopls`
99
+ - Python:`python-lsp-server`
100
+
101
+ 这些是用于编辑中项目的工具,不是产品运行时模块。
102
+
103
+ ## 工具参考
104
+
105
+ | 工具 | 说明 | 执行位置 |
106
+ |------|------|----------|
107
+ | `bash` | 执行 Shell 命令(前台/后台模式) | 容器 |
108
+ | `bash-stop` | 停止后台命令 | 容器 |
109
+ | `bash-output` | 读取后台命令输出 | 容器 |
110
+ | `bash-status` | 查询后台命令状态 | 容器 |
111
+ | `read` | 读取文件或目录(含二进制检测) | 容器 |
112
+ | `write` | 覆写文件(diff + 格式化 + 诊断) | 容器 |
113
+ | `edit` | 文本替换编辑(9 策略回退匹配) | 容器 |
114
+ | `multiedit` | 对单个文件原子应用多个编辑 | 容器 |
115
+ | `patch` | 应用统一补丁(add/delete/update/move) | 容器 |
116
+ | `glob` | 按 glob 模式搜索文件 | 容器 |
117
+ | `grep` | 按文本模式搜索文件内容 | 容器 |
118
+ | `ls` | 列出目录内容 | 容器 |
119
+ | `lsp` | 语言服务操作(hover/定义/引用/诊断等 14 种操作) | 宿主机 |
120
+ | `get-preview-url` | 获取沙箱端口的预览 URL | 容器 |
121
+
122
+ ## 包目录结构
123
+
124
+ ```text
125
+ src/
126
+ ├── index.ts # 包入口(re-export)
127
+ └── sandbox/
128
+ ├── index.ts # 插件入口 (sandboxPlugin)
129
+ ├── tools.ts # 工具工厂注册中心
130
+ ├── cli/ # CLI: install/uninstall/doctor/setup-path/help
131
+ ├── core/ # 核心层
132
+ │ ├── docker-sandbox.ts # Sandbox 接口实现
133
+ │ ├── session-manager.ts # 会话生命周期管理
134
+ │ ├── shell/ # Shell 执行子系统
135
+ │ ├── write/ # 写入管线子系统
136
+ │ ├── read/ # 读取子系统
137
+ │ ├── edit/ # 编辑子系统(9 策略回退)
138
+ │ └── lsp/ # LSP 客户端池 + 诊断
139
+ ├── tools/ # 14 个工具实现
140
+ ├── git/ # 双轨 Git(容器 + 宿主机)
141
+ └── plugins/ # OpenCode 钩子(工具、事件、系统提示)
142
+ dist/ # 生成的 ESM(npm pack 目标)
143
+ ```
144
+
145
+ ## 开发
146
+
147
+ ```bash
148
+ npm --workspace @wavexzore/sandbox run typecheck
149
+ npm --workspace @wavexzore/sandbox run lint
150
+ npm --workspace @wavexzore/sandbox test
151
+ npm --workspace @wavexzore/sandbox run build
152
+ npm --workspace @wavexzore/sandbox run verify
153
+ ```
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copyright Daytona Platforms Inc.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * Main entry point for the OpenCode Sandbox plugin.
7
+ * Re-exports the default plugin from sandbox.
8
+ */
9
+ export { default } from './sandbox/index.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Copyright Daytona Platforms Inc.
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ /**
6
+ * Main entry point for the OpenCode Sandbox plugin.
7
+ * Re-exports the default plugin from sandbox.
8
+ */
9
+ export { default } from './sandbox/index.js';
@@ -0,0 +1,5 @@
1
+ import { type WavexzoreSandboxDoctorOptions, type WavexzoreSandboxDoctorResult, type WavexzoreSandboxInstallOptions, type WavexzoreSandboxInstallResult, type WavexzoreSandboxPathOptions, type WavexzoreSandboxPathResult, type WavexzoreSandboxUninstallOptions, type WavexzoreSandboxUninstallResult } from './types.js';
2
+ export declare function installWavexzoreSandbox(rootInput?: string, options?: WavexzoreSandboxInstallOptions): WavexzoreSandboxInstallResult;
3
+ export declare function uninstallWavexzoreSandbox(rootInput?: string, options?: WavexzoreSandboxUninstallOptions): WavexzoreSandboxUninstallResult;
4
+ export declare function doctorWavexzoreSandbox(options?: WavexzoreSandboxDoctorOptions): Promise<WavexzoreSandboxDoctorResult>;
5
+ export declare function setupWavexzoreSandboxPathCommand(options?: WavexzoreSandboxPathOptions): WavexzoreSandboxPathResult;
@@ -0,0 +1,335 @@
1
+ import { existsSync } from 'fs';
2
+ import { join, resolve } from 'path';
3
+ import { pathToFileURL } from 'url';
4
+ import { installOpenCodeConfig, normalizePluginEntries, readOpenCodeConfig, uninstallOpenCodeConfig, } from './opencode-config.js';
5
+ import { isDirectoryOnPath, pathHintForPlatform, resolveOpenCodeConfigPath, resolveWavexzoreSandboxBinDir, resolveWavexzoreSandboxHomeDir, resolveWavexzoreSandboxStoreDir, runtimePlatform, } from './path.js';
6
+ import { prepareCurrentPackageVersion, readWavexzoreSandboxManifest, removeEmptyDirectory, removePathIfExists, removeWavexzoreSandboxPath, resolveWavexzoreSandboxLoaderDir, resolveWavexzoreSandboxManifestPath, setupWavexzoreSandboxPath, nextManifest, pathToPluginFileSpec, resolveOpenCodeHelperPath, resolveWavexzoreSandboxBinShimPaths, resolveWavexzoreSandboxVersionDir, uninstallWavexzoreSandboxBinShims, writeLocalLoader, writeManifest, writeWavexzoreSandboxBinShims, } from './local-store.js';
7
+ import { WAVEXZORE_SANDBOX_CLI, } from './types.js';
8
+ function resolvedRoot(root) {
9
+ return resolve(root ?? '.');
10
+ }
11
+ function resolvedScope(scope) {
12
+ return scope ?? 'user';
13
+ }
14
+ function uniqueStrings(items) {
15
+ return Array.from(new Set(items));
16
+ }
17
+ export function installWavexzoreSandbox(rootInput = '.', options = {}) {
18
+ const root = resolvedRoot(options.root ?? rootInput);
19
+ const scope = resolvedScope(options.scope);
20
+ const platform = runtimePlatform(options.platform);
21
+ const homeDir = resolveWavexzoreSandboxHomeDir({
22
+ root,
23
+ scope,
24
+ homeDir: options.homeDir,
25
+ storeDir: options.storeDir,
26
+ env: options.env,
27
+ platform,
28
+ });
29
+ const storeDir = resolveWavexzoreSandboxStoreDir({
30
+ root,
31
+ scope,
32
+ homeDir,
33
+ storeDir: options.storeDir,
34
+ env: options.env,
35
+ platform,
36
+ });
37
+ const binDir = resolveWavexzoreSandboxBinDir({
38
+ root,
39
+ scope,
40
+ homeDir,
41
+ storeDir,
42
+ binDir: options.binDir,
43
+ env: options.env,
44
+ platform,
45
+ });
46
+ const loaderDir = resolveWavexzoreSandboxLoaderDir(storeDir);
47
+ const pluginEntry = pathToPluginFileSpec(loaderDir);
48
+ const cliPath = resolveOpenCodeHelperPath(binDir, platform);
49
+ const configPath = resolveOpenCodeConfigPath({
50
+ root,
51
+ scope,
52
+ configDir: options.configDir,
53
+ configPath: options.configPath,
54
+ env: options.env,
55
+ platform,
56
+ });
57
+ const manifestPath = resolveWavexzoreSandboxManifestPath(storeDir);
58
+ const currentManifest = existsSync(manifestPath) ? readWavexzoreSandboxManifest(storeDir) : undefined;
59
+ const prepared = prepareCurrentPackageVersion({
60
+ storeDir,
61
+ packageRoot: options.packageRoot,
62
+ dryRun: options.dryRun,
63
+ env: options.env,
64
+ });
65
+ const loaderStatus = writeLocalLoader(loaderDir, options.dryRun);
66
+ const manifest = nextManifest({
67
+ current: currentManifest,
68
+ activeVersion: prepared.version,
69
+ configPath,
70
+ pluginSpec: pluginEntry,
71
+ cliPath,
72
+ channel: options.channel,
73
+ });
74
+ const manifestStatus = writeManifest(manifestPath, manifest, options.dryRun);
75
+ const config = installOpenCodeConfig({ configPath, pluginEntry, dryRun: options.dryRun });
76
+ const installBin = options.installBin !== false;
77
+ const bin = writeWavexzoreSandboxBinShims({
78
+ binDir,
79
+ storeDir,
80
+ dryRun: options.dryRun,
81
+ enabled: installBin,
82
+ platform: options.platform,
83
+ });
84
+ const path = !installBin
85
+ ? { pathStatus: 'skipped', pathHint: pathHintForPlatform(binDir, platform), pathConfigPath: undefined }
86
+ : options.setupPath
87
+ ? setupWavexzoreSandboxPath({
88
+ root,
89
+ scope,
90
+ homeDir,
91
+ storeDir,
92
+ binDir,
93
+ dryRun: options.dryRun,
94
+ envPath: options.envPath,
95
+ pathConfigPath: options.pathConfigPath,
96
+ platform,
97
+ shell: options.shell,
98
+ env: options.env,
99
+ })
100
+ : {
101
+ pathStatus: isDirectoryOnPath(binDir, options.envPath, platform)
102
+ ? 'present'
103
+ : 'missing',
104
+ pathHint: pathHintForPlatform(binDir, platform),
105
+ pathConfigPath: undefined,
106
+ };
107
+ return {
108
+ root,
109
+ dryRun: options.dryRun === true,
110
+ scope,
111
+ homeDir,
112
+ storeDir,
113
+ binDir,
114
+ cliPath,
115
+ binStatus: bin.binStatus,
116
+ pathStatus: path.pathStatus,
117
+ pathConfigPath: path.pathConfigPath,
118
+ pathHint: path.pathHint,
119
+ manifestPath,
120
+ manifestStatus,
121
+ loaderDir,
122
+ loaderStatus,
123
+ activeVersion: prepared.version,
124
+ versionPath: prepared.versionPath,
125
+ versionStatus: prepared.versionStatus,
126
+ configPath,
127
+ configStatus: config.configStatus,
128
+ pluginStatus: config.pluginStatus,
129
+ removedPluginEntries: uniqueStrings(config.removedPluginEntries),
130
+ pluginEntry,
131
+ };
132
+ }
133
+ export function uninstallWavexzoreSandbox(rootInput = '.', options = {}) {
134
+ const root = resolvedRoot(options.root ?? rootInput);
135
+ const scope = resolvedScope(options.scope);
136
+ const platform = runtimePlatform(options.platform);
137
+ const homeDir = resolveWavexzoreSandboxHomeDir({
138
+ root,
139
+ scope,
140
+ homeDir: options.homeDir,
141
+ storeDir: options.storeDir,
142
+ env: options.env,
143
+ platform,
144
+ });
145
+ const storeDir = resolveWavexzoreSandboxStoreDir({
146
+ root,
147
+ scope,
148
+ homeDir,
149
+ storeDir: options.storeDir,
150
+ env: options.env,
151
+ platform,
152
+ });
153
+ const binDir = resolveWavexzoreSandboxBinDir({
154
+ root,
155
+ scope,
156
+ homeDir,
157
+ storeDir,
158
+ binDir: options.binDir,
159
+ env: options.env,
160
+ platform,
161
+ });
162
+ const loaderDir = resolveWavexzoreSandboxLoaderDir(storeDir);
163
+ const pluginEntry = pathToPluginFileSpec(loaderDir);
164
+ const configPath = resolveOpenCodeConfigPath({
165
+ root,
166
+ scope,
167
+ configDir: options.configDir,
168
+ configPath: options.configPath,
169
+ env: options.env,
170
+ platform,
171
+ });
172
+ const manifestPath = resolveWavexzoreSandboxManifestPath(storeDir);
173
+ const config = uninstallOpenCodeConfig({
174
+ configPath,
175
+ pluginEntry,
176
+ dryRun: options.dryRun,
177
+ keepConfig: options.keepConfig,
178
+ });
179
+ const storeStatus = options.keepStore ? 'kept' : removePathIfExists(storeDir, options.dryRun);
180
+ const bin = uninstallWavexzoreSandboxBinShims({ binDir, dryRun: options.dryRun, keepBin: options.keepBin });
181
+ const path = options.removePath
182
+ ? removeWavexzoreSandboxPath({
183
+ root,
184
+ scope,
185
+ homeDir,
186
+ storeDir,
187
+ binDir,
188
+ dryRun: options.dryRun,
189
+ envPath: options.envPath,
190
+ pathConfigPath: options.pathConfigPath,
191
+ platform: options.platform,
192
+ shell: options.shell,
193
+ env: options.env,
194
+ })
195
+ : {
196
+ pathStatus: 'kept',
197
+ pathConfigPath: undefined,
198
+ pathHint: pathHintForPlatform(binDir, platform),
199
+ homeDir,
200
+ binDir,
201
+ };
202
+ if (!options.keepBin)
203
+ removeEmptyDirectory(binDir, options.dryRun);
204
+ if (!options.keepStore && !options.keepBin)
205
+ removeEmptyDirectory(homeDir, options.dryRun);
206
+ return {
207
+ root,
208
+ dryRun: options.dryRun === true,
209
+ scope,
210
+ homeDir,
211
+ storeDir,
212
+ binDir,
213
+ binStatus: bin.binStatus,
214
+ pathStatus: path.pathStatus,
215
+ pathConfigPath: path.pathConfigPath,
216
+ storeStatus,
217
+ manifestPath,
218
+ configPath,
219
+ configStatus: config.configStatus,
220
+ pluginEntry,
221
+ removedPluginEntries: uniqueStrings(config.removedPluginEntries),
222
+ };
223
+ }
224
+ function check(name, status, message, path) {
225
+ return { name, status, message, path };
226
+ }
227
+ async function canImport(path) {
228
+ try {
229
+ await import(pathToFileURL(path).href);
230
+ return { ok: true, message: 'import succeeded' };
231
+ }
232
+ catch (error) {
233
+ return { ok: false, message: error instanceof Error ? error.message : String(error) };
234
+ }
235
+ }
236
+ function configContainsPlugin(configPath, pluginEntry) {
237
+ if (!existsSync(configPath))
238
+ return false;
239
+ const config = readOpenCodeConfig(configPath);
240
+ return normalizePluginEntries(config.plugin).some((item) => {
241
+ if (typeof item === 'string')
242
+ return item === pluginEntry;
243
+ if (Array.isArray(item) && item[0] === pluginEntry)
244
+ return true;
245
+ if (item && typeof item === 'object' && item.name === pluginEntry)
246
+ return true;
247
+ return false;
248
+ });
249
+ }
250
+ export async function doctorWavexzoreSandbox(options = {}) {
251
+ const root = resolvedRoot(options.root ?? options.projectRoot ?? '.');
252
+ const scope = resolvedScope(options.scope);
253
+ const platform = runtimePlatform(options.platform);
254
+ const homeDir = resolveWavexzoreSandboxHomeDir({
255
+ root,
256
+ scope,
257
+ homeDir: options.homeDir,
258
+ storeDir: options.storeDir,
259
+ env: options.env,
260
+ platform,
261
+ });
262
+ const storeDir = resolveWavexzoreSandboxStoreDir({
263
+ root,
264
+ scope,
265
+ homeDir,
266
+ storeDir: options.storeDir,
267
+ env: options.env,
268
+ platform,
269
+ });
270
+ const binDir = resolveWavexzoreSandboxBinDir({
271
+ root,
272
+ scope,
273
+ homeDir,
274
+ storeDir,
275
+ binDir: options.binDir,
276
+ env: options.env,
277
+ platform,
278
+ });
279
+ const configPath = resolveOpenCodeConfigPath({
280
+ root,
281
+ scope,
282
+ configDir: options.configDir,
283
+ configPath: options.configPath,
284
+ env: options.env,
285
+ platform,
286
+ });
287
+ const loaderDir = resolveWavexzoreSandboxLoaderDir(storeDir);
288
+ const pluginEntry = pathToPluginFileSpec(loaderDir);
289
+ const cliPath = resolveOpenCodeHelperPath(binDir, platform);
290
+ const manifestPath = resolveWavexzoreSandboxManifestPath(storeDir);
291
+ const checks = [];
292
+ checks.push(check('config', existsSync(configPath) ? 'passed' : 'failed', existsSync(configPath) ? 'OpenCode config exists' : 'OpenCode config is missing', configPath));
293
+ checks.push(check('config-plugin', configContainsPlugin(configPath, pluginEntry) ? 'passed' : 'failed', configContainsPlugin(configPath, pluginEntry)
294
+ ? 'OpenCode config references the local loader'
295
+ : 'OpenCode config does not reference the local loader', configPath));
296
+ checks.push(check('loader', existsSync(join(loaderDir, 'index.mjs')) ? 'passed' : 'failed', existsSync(join(loaderDir, 'index.mjs')) ? 'local loader exists' : 'local loader is missing', loaderDir));
297
+ let activeVersion;
298
+ try {
299
+ const manifest = readWavexzoreSandboxManifest(storeDir);
300
+ activeVersion = manifest?.activeVersion;
301
+ checks.push(check('manifest', manifest ? 'passed' : 'failed', manifest ? `active version ${manifest.activeVersion}` : 'manifest is missing', manifestPath));
302
+ }
303
+ catch (error) {
304
+ checks.push(check('manifest', 'failed', error instanceof Error ? error.message : String(error), manifestPath));
305
+ }
306
+ if (activeVersion) {
307
+ const pluginJs = join(resolveWavexzoreSandboxVersionDir(storeDir, activeVersion), 'dist', 'index.js');
308
+ const cliJs = join(resolveWavexzoreSandboxVersionDir(storeDir, activeVersion), 'dist', 'sandbox', 'cli', `${WAVEXZORE_SANDBOX_CLI}.js`);
309
+ checks.push(check('plugin-entry', existsSync(pluginJs) ? 'passed' : 'failed', existsSync(pluginJs) ? 'plugin entry exists' : 'plugin entry is missing', pluginJs));
310
+ checks.push(check('cli-entry', existsSync(cliJs) ? 'passed' : 'failed', existsSync(cliJs) ? 'CLI entry exists' : 'CLI entry is missing', cliJs));
311
+ if (existsSync(join(loaderDir, 'index.mjs'))) {
312
+ const imported = await canImport(join(loaderDir, 'index.mjs'));
313
+ checks.push(check('loader-import', imported.ok ? 'passed' : 'failed', imported.message, join(loaderDir, 'index.mjs')));
314
+ }
315
+ }
316
+ const shims = resolveWavexzoreSandboxBinShimPaths(binDir);
317
+ checks.push(check('bin', existsSync(shims.js) && existsSync(shims.unix) ? 'passed' : 'failed', existsSync(shims.js) && existsSync(shims.unix) ? 'bin shims exist' : 'bin shims are missing', binDir));
318
+ checks.push(check('opencode-helper', existsSync(cliPath) ? 'passed' : 'failed', existsSync(cliPath) ? `OpenCode helper exists at ${cliPath}` : `OpenCode helper is missing at ${cliPath}`, cliPath));
319
+ return {
320
+ status: checks.every((item) => item.status === 'passed') ? 'passed' : 'failed',
321
+ homeDir,
322
+ storeDir,
323
+ binDir,
324
+ cliPath,
325
+ configPath,
326
+ pluginEntry,
327
+ activeVersion,
328
+ checks,
329
+ };
330
+ }
331
+ export function setupWavexzoreSandboxPathCommand(options = {}) {
332
+ const root = resolvedRoot(options.root ?? '.');
333
+ const scope = resolvedScope(options.scope);
334
+ return setupWavexzoreSandboxPath({ ...options, root, scope });
335
+ }
@@ -0,0 +1,87 @@
1
+ import { type BinStatus, type InstallStatus, type RuntimeEnv, type WavexzoreSandboxManifest, type WavexzoreSandboxPathResult, type StoreScope, type UninstallStatus } from './types.js';
2
+ export declare function nowIso(now?: number): string;
3
+ export declare function writeFileAtomic(path: string, contents: string, dryRun?: boolean): void;
4
+ export declare function writeJsonAtomic(path: string, value: unknown, dryRun?: boolean): void;
5
+ export declare function statusForText(path: string, desired: string): InstallStatus;
6
+ export declare function assertSafeVersion(version: string): void;
7
+ export declare function resolveWavexzoreSandboxManifestPath(storeDir: string): string;
8
+ export declare function resolveWavexzoreSandboxLoaderDir(storeDir: string): string;
9
+ export declare function resolveWavexzoreSandboxVersionDir(storeDir: string, version: string): string;
10
+ export declare function pathToPluginFileSpec(loaderDir: string): string;
11
+ export declare function resolveWavexzoreSandboxBinShimPaths(binDir: string): {
12
+ js: string;
13
+ unix: string;
14
+ cmd: string;
15
+ };
16
+ export declare function resolveOpenCodeHelperPath(binDir: string, platform?: string): string;
17
+ export declare function resolveCurrentPackageRoot(explicit?: string, env?: RuntimeEnv): string;
18
+ export declare function readCurrentPackageVersion(packageRoot?: string): string;
19
+ export declare function prepareCurrentPackageVersion(args: {
20
+ storeDir: string;
21
+ packageRoot?: string;
22
+ dryRun?: boolean;
23
+ env?: RuntimeEnv;
24
+ }): {
25
+ version: string;
26
+ versionPath: string;
27
+ versionStatus: InstallStatus;
28
+ };
29
+ export declare function writeLocalLoader(loaderDir: string, dryRun?: boolean): InstallStatus;
30
+ export declare function readWavexzoreSandboxManifest(storeDir: string): WavexzoreSandboxManifest | undefined;
31
+ export declare function nextManifest(args: {
32
+ current?: WavexzoreSandboxManifest;
33
+ activeVersion: string;
34
+ configPath: string;
35
+ pluginSpec: string;
36
+ cliPath: string;
37
+ channel?: string;
38
+ now?: number;
39
+ }): WavexzoreSandboxManifest;
40
+ export declare function writeManifest(manifestPath: string, manifest: WavexzoreSandboxManifest, dryRun?: boolean): InstallStatus;
41
+ export declare function writeWavexzoreSandboxBinShims(args: {
42
+ binDir: string;
43
+ storeDir: string;
44
+ dryRun?: boolean;
45
+ enabled?: boolean;
46
+ platform?: string;
47
+ }): {
48
+ binDir: string;
49
+ binStatus: BinStatus;
50
+ };
51
+ export declare function uninstallWavexzoreSandboxBinShims(args: {
52
+ binDir: string;
53
+ dryRun?: boolean;
54
+ keepBin?: boolean;
55
+ }): {
56
+ binDir: string;
57
+ binStatus: UninstallStatus;
58
+ };
59
+ export declare function removePathIfExists(path: string, dryRun?: boolean): UninstallStatus;
60
+ export declare function removeEmptyDirectory(path: string, dryRun?: boolean): void;
61
+ export declare function setupWavexzoreSandboxPath(options?: {
62
+ root?: string;
63
+ scope?: StoreScope;
64
+ homeDir?: string;
65
+ storeDir?: string;
66
+ binDir?: string;
67
+ dryRun?: boolean;
68
+ envPath?: string;
69
+ pathConfigPath?: string;
70
+ platform?: string;
71
+ shell?: string;
72
+ env?: RuntimeEnv;
73
+ }): WavexzoreSandboxPathResult;
74
+ export declare function removeWavexzoreSandboxPath(options?: {
75
+ root?: string;
76
+ scope?: StoreScope;
77
+ homeDir?: string;
78
+ storeDir?: string;
79
+ binDir?: string;
80
+ dryRun?: boolean;
81
+ envPath?: string;
82
+ pathConfigPath?: string;
83
+ platform?: string;
84
+ shell?: string;
85
+ env?: RuntimeEnv;
86
+ }): WavexzoreSandboxPathResult;
87
+ export declare function relativeWithin(root: string, target: string): string;