@ycs77/devbox 0.6.0 → 0.7.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/README.md +9 -4
- package/dist/cli.mjs +58 -167
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,11 +47,16 @@ From your project directory, initialize Devbox:
|
|
|
47
47
|
devbox init
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
**Global configuration** selects the Node.js versions and AI agents installed in the shared Workspace image. These choices apply to every Project; adding more makes the image larger and uses more disk space.
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
**Local configuration** selects the Node.js version for the current Project. A Project can use only a version included in the Global configuration.
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
Published ports accept one mapping per line. The following are the supported port mapping formats:
|
|
55
|
+
|
|
56
|
+
- `5173:5173` maps host port `5173` to container port `5173`.
|
|
57
|
+
- `APP_PORT:5173:5173` maps the host port from the `APP_PORT` environment variable (default port: `5173`) to container port `5173`.
|
|
58
|
+
|
|
59
|
+
Build the shared Workspace image once after setup or whenever you change global configuration:
|
|
55
60
|
|
|
56
61
|
```bash
|
|
57
62
|
devbox build
|
|
@@ -81,8 +86,8 @@ Run these commands from the initialized project directory:
|
|
|
81
86
|
| `devbox sh` | Open a Bash shell in the running sandbox. |
|
|
82
87
|
| `devbox stop` | Stop the sandbox and preserve it for a later restart. |
|
|
83
88
|
| `devbox down` | Stop and remove the sandbox container. |
|
|
89
|
+
| `devbox rm` | Stop and remove the current Project's Sandbox, then remove its Devbox state. |
|
|
84
90
|
| `devbox config` | Change global settings or this project's configuration. |
|
|
85
|
-
| `devbox cleanup --missing-projects` | Remove registrations for projects whose root directory no longer exists. |
|
|
86
91
|
|
|
87
92
|
## Troubleshooting
|
|
88
93
|
|
package/dist/cli.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { promisify } from "node:util";
|
|
|
11
11
|
import { parse as parse$1, parseDocument, stringify } from "yaml";
|
|
12
12
|
import { once } from "node:events";
|
|
13
13
|
//#region package.json
|
|
14
|
-
var version = "0.
|
|
14
|
+
var version = "0.7.0";
|
|
15
15
|
//#endregion
|
|
16
16
|
//#region src/catalog/index.ts
|
|
17
17
|
const NODE_RELEASE_KEYS = [
|
|
@@ -55,7 +55,7 @@ const PACKAGED_AGENTS = {
|
|
|
55
55
|
},
|
|
56
56
|
supportsNotifications: true,
|
|
57
57
|
notificationInstallationCommands: ["claude plugin marketplace add ycs77/claude-code-notifications", "claude plugin install notification-basic-wsl@ycs77-notifications"],
|
|
58
|
-
|
|
58
|
+
skillInstallationTarget: "claude-code"
|
|
59
59
|
},
|
|
60
60
|
codex: {
|
|
61
61
|
home: {
|
|
@@ -67,7 +67,7 @@ const PACKAGED_AGENTS = {
|
|
|
67
67
|
shell: "sh"
|
|
68
68
|
},
|
|
69
69
|
supportsNotifications: true,
|
|
70
|
-
|
|
70
|
+
skillInstallationTarget: "codex",
|
|
71
71
|
notificationInstallationCommands: ["codex plugin marketplace add ycs77/codex-notifications", "codex plugin add notification-basic-wsl@ycs77-notifications"]
|
|
72
72
|
},
|
|
73
73
|
agy: {
|
|
@@ -79,8 +79,7 @@ const PACKAGED_AGENTS = {
|
|
|
79
79
|
url: "https://antigravity.google/cli/install.sh",
|
|
80
80
|
shell: "bash"
|
|
81
81
|
},
|
|
82
|
-
supportsNotifications: false
|
|
83
|
-
supportsSkillInstallation: false
|
|
82
|
+
supportsNotifications: false
|
|
84
83
|
},
|
|
85
84
|
omp: {
|
|
86
85
|
home: {
|
|
@@ -92,7 +91,7 @@ const PACKAGED_AGENTS = {
|
|
|
92
91
|
shell: "sh"
|
|
93
92
|
},
|
|
94
93
|
supportsNotifications: true,
|
|
95
|
-
|
|
94
|
+
skillInstallationTarget: "codex",
|
|
96
95
|
notificationInstallationCommands: ["omp plugin marketplace add ycs77/omp-notifications", "omp plugin install notification-basic@ycs77-notifications"]
|
|
97
96
|
}
|
|
98
97
|
};
|
|
@@ -514,6 +513,7 @@ const WORKSPACE_IMAGE = "devbox-workspace:latest";
|
|
|
514
513
|
//#endregion
|
|
515
514
|
//#region src/project/compose.ts
|
|
516
515
|
function renderProjectCompose(input) {
|
|
516
|
+
const containerName = `devbox-${input.sandboxName}`;
|
|
517
517
|
const node = selectNodeComposeFragment(input.selectedNode);
|
|
518
518
|
const agent = selectAgentComposeFragments(input.configuredAgents, input.agentNotifications);
|
|
519
519
|
const environment = {
|
|
@@ -544,17 +544,17 @@ function renderProjectCompose(input) {
|
|
|
544
544
|
}]
|
|
545
545
|
];
|
|
546
546
|
const definition = {
|
|
547
|
-
name:
|
|
547
|
+
name: containerName,
|
|
548
548
|
"x-devbox": {
|
|
549
549
|
version: 1,
|
|
550
550
|
project_root: input.projectRoot,
|
|
551
|
-
|
|
551
|
+
container_name: containerName,
|
|
552
552
|
sandbox_service: "devbox"
|
|
553
553
|
},
|
|
554
554
|
services: { devbox: {
|
|
555
555
|
image: WORKSPACE_IMAGE,
|
|
556
556
|
hostname: "devbox",
|
|
557
|
-
container_name:
|
|
557
|
+
container_name: containerName,
|
|
558
558
|
working_dir: `/workspace/${input.sandboxName}`,
|
|
559
559
|
...input.ports.length === 0 ? {} : { ports: input.ports.map((port) => renderComposePortMapping(port)) },
|
|
560
560
|
...Object.keys(environment).length === 0 ? {} : { environment },
|
|
@@ -566,21 +566,21 @@ function renderProjectCompose(input) {
|
|
|
566
566
|
}])) }
|
|
567
567
|
};
|
|
568
568
|
const source = stringify(definition);
|
|
569
|
-
return validatesProjectCompose(source, input) ? success(source) : failure({
|
|
569
|
+
return validatesProjectCompose(source, input, containerName) ? success(source) : failure({
|
|
570
570
|
kind: "operational",
|
|
571
571
|
code: "invalid-project-compose",
|
|
572
572
|
observed: `Devbox generated an invalid Compose definition for Project ${input.projectRoot}.`,
|
|
573
573
|
nextAction: "Run the command again. If the problem continues, report it as a Devbox bug."
|
|
574
574
|
});
|
|
575
575
|
}
|
|
576
|
-
function validatesProjectCompose(source, input) {
|
|
576
|
+
function validatesProjectCompose(source, input, containerName) {
|
|
577
577
|
try {
|
|
578
578
|
const definition = parse$1(source);
|
|
579
|
-
if (definition.name !==
|
|
579
|
+
if (definition.name !== containerName) return false;
|
|
580
580
|
const metadata = definition["x-devbox"];
|
|
581
|
-
if (metadata.version !== 1 || metadata.project_root !== input.projectRoot || metadata.
|
|
581
|
+
if (metadata.version !== 1 || metadata.project_root !== input.projectRoot || metadata.container_name !== containerName || metadata.sandbox_service !== "devbox") return false;
|
|
582
582
|
const service = definition.services.devbox;
|
|
583
|
-
if (service.image !== "devbox-workspace:latest" || service.container_name !==
|
|
583
|
+
if (service.image !== "devbox-workspace:latest" || service.container_name !== containerName || service.working_dir !== `/workspace/${input.sandboxName}` || "build" in service) return false;
|
|
584
584
|
const ports = input.ports.map((port) => renderComposePortMapping(port));
|
|
585
585
|
return JSON.stringify(service.ports ?? []) === JSON.stringify(ports) && (input.selectedNode === null && service.environment?.NODE_VERSION === void 0 || input.selectedNode !== null && service.environment?.NODE_VERSION === input.selectedNode);
|
|
586
586
|
} catch {
|
|
@@ -1060,13 +1060,20 @@ async function removeProjectUnlocked(input = {}) {
|
|
|
1060
1060
|
const registration = registryCheck.value.projects[projectRoot];
|
|
1061
1061
|
if (registration === void 0) return notRegistered(projectRoot);
|
|
1062
1062
|
if (!input.yes) {
|
|
1063
|
-
if (!await (input.confirm ?? (async () => false))(`Remove Project ${projectRoot} and
|
|
1063
|
+
if (!await (input.confirm ?? (async () => false))(`Remove Project ${projectRoot}, its Sandbox, and Devbox state?`)) return success({
|
|
1064
1064
|
root: projectRoot,
|
|
1065
1065
|
removed: false
|
|
1066
1066
|
});
|
|
1067
1067
|
}
|
|
1068
1068
|
if (input.signal?.aborted) throw new InterruptedError();
|
|
1069
1069
|
const stateDirectory = projectStateDirectory(registration.identity, paths.home);
|
|
1070
|
+
const environment = input.environment ?? currentHostEnvironment();
|
|
1071
|
+
await (environment.runDirect ?? environment.run)("docker", [
|
|
1072
|
+
"compose",
|
|
1073
|
+
"--file",
|
|
1074
|
+
join(stateDirectory, "compose.yaml"),
|
|
1075
|
+
"down"
|
|
1076
|
+
]);
|
|
1070
1077
|
try {
|
|
1071
1078
|
await rm(stateDirectory, {
|
|
1072
1079
|
recursive: true,
|
|
@@ -1092,107 +1099,6 @@ async function removeProjectUnlocked(input = {}) {
|
|
|
1092
1099
|
removed: true
|
|
1093
1100
|
});
|
|
1094
1101
|
}
|
|
1095
|
-
async function cleanupMissingProjects(input = {}) {
|
|
1096
|
-
return withStateLocks({
|
|
1097
|
-
devboxHome: input.devboxHome ?? join(homedir(), ".devbox"),
|
|
1098
|
-
global: true,
|
|
1099
|
-
projectRoots: [],
|
|
1100
|
-
signal: input.signal
|
|
1101
|
-
}, async (locks) => {
|
|
1102
|
-
const discovery = await discoverMissingProjects(input);
|
|
1103
|
-
if (!discovery.ok) return discovery;
|
|
1104
|
-
await locks.acquireProjectScopes(discovery.value.missingRoots);
|
|
1105
|
-
return cleanupMissingProjectsUnlocked(input, discovery.value);
|
|
1106
|
-
});
|
|
1107
|
-
}
|
|
1108
|
-
async function discoverMissingProjects(input) {
|
|
1109
|
-
if (input.signal?.aborted) throw new InterruptedError();
|
|
1110
|
-
const paths = devboxPaths(input.devboxHome);
|
|
1111
|
-
const registryState = await readOptionalFile(paths.projectRegistry);
|
|
1112
|
-
if (!registryState.ok) return registryState;
|
|
1113
|
-
if (!registryState.value.exists) return success({
|
|
1114
|
-
paths,
|
|
1115
|
-
registry: {
|
|
1116
|
-
version: 1,
|
|
1117
|
-
projects: {}
|
|
1118
|
-
},
|
|
1119
|
-
missingRoots: []
|
|
1120
|
-
});
|
|
1121
|
-
const registryCheck = parseProjectRegistry(registryState.value.content);
|
|
1122
|
-
if (!registryCheck.ok) return registryCheck;
|
|
1123
|
-
const missingRoots = [];
|
|
1124
|
-
for (const root of Object.keys(registryCheck.value.projects)) {
|
|
1125
|
-
if (input.signal?.aborted) throw new InterruptedError();
|
|
1126
|
-
const exists = await projectRootExists(root);
|
|
1127
|
-
if (exists === void 0) return failure({
|
|
1128
|
-
kind: "operational",
|
|
1129
|
-
code: "project-root-observation-failed",
|
|
1130
|
-
observed: `Devbox could not inspect Project root: ${root}.`,
|
|
1131
|
-
nextAction: "Check access to the registered Project root and try cleanup again."
|
|
1132
|
-
});
|
|
1133
|
-
if (!exists) missingRoots.push(root);
|
|
1134
|
-
}
|
|
1135
|
-
return success({
|
|
1136
|
-
paths,
|
|
1137
|
-
registry: registryCheck.value,
|
|
1138
|
-
missingRoots
|
|
1139
|
-
});
|
|
1140
|
-
}
|
|
1141
|
-
async function cleanupMissingProjectsUnlocked(input, discovery) {
|
|
1142
|
-
if (input.signal?.aborted) throw new InterruptedError();
|
|
1143
|
-
if (discovery.missingRoots.length === 0) return success({
|
|
1144
|
-
roots: [],
|
|
1145
|
-
removed: false
|
|
1146
|
-
});
|
|
1147
|
-
if (!input.yes) {
|
|
1148
|
-
if (!await (input.confirm ?? (async () => false))(`Remove Missing-root Project registrations?\n${discovery.missingRoots.map((root) => `- ${root}`).join("\n")}`)) return success({
|
|
1149
|
-
roots: [],
|
|
1150
|
-
removed: false
|
|
1151
|
-
});
|
|
1152
|
-
}
|
|
1153
|
-
const removedRoots = [];
|
|
1154
|
-
const projects = { ...discovery.registry.projects };
|
|
1155
|
-
for (const root of discovery.missingRoots) {
|
|
1156
|
-
const rootExists = await projectRootExists(root);
|
|
1157
|
-
if (rootExists === void 0) return failure({
|
|
1158
|
-
kind: "operational",
|
|
1159
|
-
code: "project-root-observation-failed",
|
|
1160
|
-
observed: `Devbox could not recheck Project root: ${root}.`,
|
|
1161
|
-
nextAction: "Check access to the registered Project root and try cleanup again."
|
|
1162
|
-
});
|
|
1163
|
-
if (rootExists) continue;
|
|
1164
|
-
const registration = projects[root];
|
|
1165
|
-
if (registration === void 0) continue;
|
|
1166
|
-
try {
|
|
1167
|
-
await rm(projectStateDirectory(registration.identity, discovery.paths.home), {
|
|
1168
|
-
recursive: true,
|
|
1169
|
-
force: true
|
|
1170
|
-
});
|
|
1171
|
-
} catch {
|
|
1172
|
-
return failure({
|
|
1173
|
-
kind: "operational",
|
|
1174
|
-
code: "missing-project-state-removal-failed",
|
|
1175
|
-
observed: `Devbox could not remove Missing-root Project state for ${root}.`,
|
|
1176
|
-
nextAction: "Check write access to ~/.devbox and try cleanup again."
|
|
1177
|
-
});
|
|
1178
|
-
}
|
|
1179
|
-
delete projects[root];
|
|
1180
|
-
removedRoots.push(root);
|
|
1181
|
-
}
|
|
1182
|
-
if (removedRoots.length === 0) return success({
|
|
1183
|
-
roots: [],
|
|
1184
|
-
removed: false
|
|
1185
|
-
});
|
|
1186
|
-
const written = await writeAtomically(discovery.paths.projectRegistry, serializeProjectRegistry({
|
|
1187
|
-
version: 1,
|
|
1188
|
-
projects
|
|
1189
|
-
}));
|
|
1190
|
-
if (!written.ok) return written;
|
|
1191
|
-
return success({
|
|
1192
|
-
roots: removedRoots,
|
|
1193
|
-
removed: true
|
|
1194
|
-
});
|
|
1195
|
-
}
|
|
1196
1102
|
async function runSandboxLifecycle(command, input = {}) {
|
|
1197
1103
|
const projectRoot = input.root ?? process.cwd();
|
|
1198
1104
|
return withStateLocks({
|
|
@@ -1311,14 +1217,6 @@ function allocateSandboxName(projectRoot, registry) {
|
|
|
1311
1217
|
nextAction: "Remove stale Devbox state only through its supported command and try again."
|
|
1312
1218
|
});
|
|
1313
1219
|
}
|
|
1314
|
-
async function projectRootExists(projectRoot) {
|
|
1315
|
-
try {
|
|
1316
|
-
return (await lstat(projectRoot)).isDirectory();
|
|
1317
|
-
} catch (error) {
|
|
1318
|
-
if (isMissingFileError(error)) return false;
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
|
-
}
|
|
1322
1220
|
async function readOptionalFile(path) {
|
|
1323
1221
|
try {
|
|
1324
1222
|
return success({
|
|
@@ -1547,17 +1445,15 @@ function createConfigurationPrompter({ signal, input, output }) {
|
|
|
1547
1445
|
message: "Where should this configuration apply?",
|
|
1548
1446
|
options: [{
|
|
1549
1447
|
value: "local",
|
|
1550
|
-
label: "Current project"
|
|
1551
|
-
hint: "Enabled when this project’s container starts."
|
|
1448
|
+
label: "Current project"
|
|
1552
1449
|
}, {
|
|
1553
1450
|
value: "global",
|
|
1554
|
-
label: "Workspace Docker image"
|
|
1555
|
-
hint: "Built into the shared image. It affects image size and disk usage."
|
|
1451
|
+
label: "Workspace Docker image"
|
|
1556
1452
|
}],
|
|
1557
1453
|
...common
|
|
1558
1454
|
})),
|
|
1559
1455
|
editGlobal: async (configuration, catalog) => {
|
|
1560
|
-
p.log.step(
|
|
1456
|
+
p.log.step(c.bold(c.cyan("Workspace Docker image")), common);
|
|
1561
1457
|
return {
|
|
1562
1458
|
version: 1,
|
|
1563
1459
|
node: await promptValue(p.multiselect({
|
|
@@ -1582,7 +1478,7 @@ function createConfigurationPrompter({ signal, input, output }) {
|
|
|
1582
1478
|
};
|
|
1583
1479
|
},
|
|
1584
1480
|
editLocal: async (configuration, catalog, globalConfiguration, editPorts = true) => {
|
|
1585
|
-
p.log.step(
|
|
1481
|
+
p.log.step(c.bold(c.cyan("Project configuration")), common);
|
|
1586
1482
|
const node = await promptValue(p.select({
|
|
1587
1483
|
message: "Selected Node release line",
|
|
1588
1484
|
options: [{
|
|
@@ -1600,7 +1496,7 @@ function createConfigurationPrompter({ signal, input, output }) {
|
|
|
1600
1496
|
node
|
|
1601
1497
|
};
|
|
1602
1498
|
const ports = normalizePortMappings((await promptValue(p.multiline({
|
|
1603
|
-
message: "Published ports",
|
|
1499
|
+
message: "Published ports" + c.dim(" (multiline; one mapping per line; press Enter twice to submit)"),
|
|
1604
1500
|
initialValue: configuration.ports.join("\n"),
|
|
1605
1501
|
validate: (value) => {
|
|
1606
1502
|
const ports = normalizePortMappings((value ?? "").split("\n"));
|
|
@@ -1658,6 +1554,9 @@ var settings_json_default = "{\n \"$schema\": \"https://json.schemastore.org/cl
|
|
|
1658
1554
|
//#region \0devbox-image-default:/home/runner/work/devbox/devbox/src/agent/image-defaults/.omp/agent/config.yml.mjs
|
|
1659
1555
|
var config_yml_default = "setupVersion: 2\n\n# Basic\nmodelRoles:\n default: openai-codex/gpt-5.6-terra:high\n smol: openai-codex/gpt-5.6-luna:high\n slow: openai-codex/gpt-5.6-sol:max\n plan: openai-codex/gpt-5.6-sol:max\nsymbolPreset: unicode\n\n# Theme\ntheme:\n dark: titanium\n light: light\ncomposer:\n shape: claude\n\n# Auto QA\ndev:\n autoqa: false\n autoqaConsent: denied\n";
|
|
1660
1556
|
//#endregion
|
|
1557
|
+
//#region \0devbox-image-default:/home/runner/work/devbox/devbox/src/agent/image-defaults/AGENTS.md.mjs
|
|
1558
|
+
var AGENTS_md_default = "# 協作規範\n\n## 語言\n\n- 一律使用繁體中文台灣用語產生對話及結果\n- 嚴禁使用簡體字,或是繁體字但中國大陸用語\n- 用詞要口語、常見,避免生硬或翻譯腔的詞彙;例如「根因」改用「原因」、「根本原因」、「主要原因」\n\n## 開發規範\n\n### 共用原則\n\n- 優先遵循專案既有的 `AGENTS.md`、文件、設定檔、lockfile、指令與程式碼慣例;專案沒有既有規範時,才使用本文件的預設選擇\n- 優先使用目前環境提供的專用工具;只有在專用工具無法完成工作或需要執行專案指令時,才使用適合的 CLI 工具\n- 執行會修改檔案、安裝套件或變更環境的指令前,確認目前位於預期的工作目錄\n- 可以在目前 workspace 內存取所需的上層目錄;存取 workspace 以外的檔案或目錄前,必須先取得使用者同意\n- 嚴禁執行任何會遞迴刪除根目錄的指令(例如 `rm -rf /`),也不得改寫或規避為效果相同的形式\n- 執行其他可能造成不可逆資料遺失的操作前,必須說明影響並取得使用者明確同意\n\n### JavaScript / TypeScript\n\n- 優先使用專案既有的 Node.js 版本設定與套件管理器\n- 專案未指定套件管理器且不存在 lockfile 時,使用 npm\n- 需要執行專案套件提供的 CLI 時,使用專案既有套件管理器對應的執行方式;使用 npm 時使用 npx\n- 不得擅自更換套件管理器、刪除 lockfile 或產生不同格式的 lockfile\n- TypeScript 格式規則\n - 陣列型別優先使用 `T[]`,避免使用 `Array<T>`\n - 字典型物件優先使用 `Record<string, T>`,避免使用 `{ [key: string]: T }`\n- Vue 3 程式碼變更前,先讀取 `/ycs77-vue` skill\n\n### PHP\n\n- 優先使用專案既有的 Composer scripts\n\n### 常見 CLI 工具\n\n- 版本控制操作使用 `git`\n- 所有 Git 操作都必須直接執行 `git` CLI\n- 禁止透過 Eval、JavaScript、Bun、Node.js、Python 或臨時程式碼間接包裝或呼叫 Git 指令\n- GitHub 相關操作優先使用 `gh` CLI\n\n## 安全\n\n- 在任何對話、指令輸出或程式碼中,不得揭露密碼、金鑰、Access Token、API Key 等機密資料\n- 不得讀取或輸出 `.env`、`.env.*` 等可能包含機密資料的檔案;不含真實機密的範本檔案(例如 `.env.example`)除外\n- 開發時需要使用機密資料,應以環境變數名稱或假值代稱,不得嘗試取得實際值\n";
|
|
1559
|
+
//#endregion
|
|
1661
1560
|
//#region src/agent/build.ts
|
|
1662
1561
|
function createAgentWorkspaceContribution(input) {
|
|
1663
1562
|
const agents = input.configuredAgents.map((name) => ({
|
|
@@ -1665,7 +1564,7 @@ function createAgentWorkspaceContribution(input) {
|
|
|
1665
1564
|
recipe: PACKAGED_AGENTS[name]
|
|
1666
1565
|
}));
|
|
1667
1566
|
const buildContextAssets = selectBuildContextAssets(agents);
|
|
1668
|
-
const skillAgents = agents.
|
|
1567
|
+
const skillAgents = [...new Set(agents.flatMap((agent) => agent.recipe.skillInstallationTarget === void 0 ? [] : [agent.recipe.skillInstallationTarget]))];
|
|
1669
1568
|
return {
|
|
1670
1569
|
buildContextAssets,
|
|
1671
1570
|
dockerignoreEntries: selectDockerignoreEntries(buildContextAssets),
|
|
@@ -1690,12 +1589,17 @@ function renderAgentInstallations(agents, buildNodeRuntimeRoot, skillAgents) {
|
|
|
1690
1589
|
}
|
|
1691
1590
|
function selectDockerignoreEntries(assets) {
|
|
1692
1591
|
const entries = [];
|
|
1693
|
-
if (assets.some((asset) => asset.relativePath === ".
|
|
1694
|
-
if (assets.some((asset) => asset.relativePath
|
|
1592
|
+
if (assets.some((asset) => asset.relativePath === "AGENTS.md")) entries.push("!AGENTS.md");
|
|
1593
|
+
if (assets.some((asset) => asset.relativePath.startsWith(".claude/"))) entries.push("!.claude", "!.claude/settings.json");
|
|
1594
|
+
if (assets.some((asset) => asset.relativePath.startsWith(".omp/agent/"))) entries.push("!.omp", "!.omp/agent", "!.omp/agent/config.yml");
|
|
1695
1595
|
return entries;
|
|
1696
1596
|
}
|
|
1697
1597
|
function selectBuildContextAssets(agents) {
|
|
1698
1598
|
const assets = [];
|
|
1599
|
+
if (agents.length > 0) assets.push({
|
|
1600
|
+
relativePath: "AGENTS.md",
|
|
1601
|
+
content: AGENTS_md_default
|
|
1602
|
+
});
|
|
1699
1603
|
if (agents.some((agent) => agent.name === "claude-code")) assets.push({
|
|
1700
1604
|
relativePath: ".claude/settings.json",
|
|
1701
1605
|
content: settings_json_default
|
|
@@ -1708,19 +1612,16 @@ function selectBuildContextAssets(agents) {
|
|
|
1708
1612
|
}
|
|
1709
1613
|
function renderAgentAssets(agents) {
|
|
1710
1614
|
const hasClaudeCode = agents.some((agent) => agent.name === "claude-code");
|
|
1615
|
+
const hasCodex = agents.some((agent) => agent.name === "codex");
|
|
1616
|
+
const hasAgy = agents.some((agent) => agent.name === "agy");
|
|
1711
1617
|
const hasOmp = agents.some((agent) => agent.name === "omp");
|
|
1712
|
-
if (!hasClaudeCode && !hasOmp) return [];
|
|
1713
|
-
const ownershipCommands = [];
|
|
1618
|
+
if (!hasClaudeCode && !hasCodex && !hasAgy && !hasOmp) return [];
|
|
1714
1619
|
const lines = ["# Copy AI dotfiles"];
|
|
1715
|
-
if (hasClaudeCode)
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
lines.push("COPY .omp/agent/config.yml /home/devbox/.omp/agent/config.yml");
|
|
1721
|
-
ownershipCommands.push("chown devbox:devbox /home/devbox/.omp/agent", "chown devbox:devbox /home/devbox/.omp/agent/config.yml");
|
|
1722
|
-
}
|
|
1723
|
-
lines.push(`RUN ${ownershipCommands.join(" \\\n && ")}`, "");
|
|
1620
|
+
if (hasClaudeCode) lines.push("COPY --chown=devbox:devbox .claude/settings.json /home/devbox/.claude/settings.json", "COPY --chown=devbox:devbox AGENTS.md /home/devbox/.claude/CLAUDE.md");
|
|
1621
|
+
if (hasCodex) lines.push("COPY --chown=devbox:devbox AGENTS.md /home/devbox/.codex/AGENTS.md");
|
|
1622
|
+
if (hasAgy) lines.push("COPY --chown=devbox:devbox AGENTS.md /home/devbox/.gemini/GEMINI.md");
|
|
1623
|
+
if (hasOmp) lines.push("COPY --chown=devbox:devbox .omp/agent/config.yml /home/devbox/.omp/agent/config.yml", "COPY --chown=devbox:devbox AGENTS.md /home/devbox/.omp/agent/AGENTS.md");
|
|
1624
|
+
lines.push("");
|
|
1724
1625
|
return lines;
|
|
1725
1626
|
}
|
|
1726
1627
|
function renderAgentNotifications(agents, notificationsEnabled) {
|
|
@@ -1872,7 +1773,7 @@ async function buildWorkspaceUnlocked(input) {
|
|
|
1872
1773
|
kind: "operational",
|
|
1873
1774
|
code: "global-configuration-missing",
|
|
1874
1775
|
observed: "Global configuration is required before building the Workspace image.",
|
|
1875
|
-
nextAction: "Run devbox
|
|
1776
|
+
nextAction: "Run devbox init to commit Global configuration before devbox build."
|
|
1876
1777
|
});
|
|
1877
1778
|
}
|
|
1878
1779
|
const parsedGlobal = parseGlobalConfiguration(globalSource, catalogCheck.value);
|
|
@@ -1972,7 +1873,7 @@ function renderBuildDockerfile(input) {
|
|
|
1972
1873
|
lines.push("WORKDIR /workspace", "USER devbox", "");
|
|
1973
1874
|
lines.push(...input.node.sandboxUserSetup);
|
|
1974
1875
|
lines.push(...input.agent.installationDockerfileLines);
|
|
1975
|
-
lines.push("USER root", "", "# Copy dotfiles", "COPY .bash_aliases /home/devbox/.bash_aliases", "COPY
|
|
1876
|
+
lines.push("USER root", "", "# Copy dotfiles", "COPY --chown=devbox:devbox .bash_aliases /home/devbox/.bash_aliases", "COPY --chown=devbox:devbox .gitconfig /home/devbox/.gitconfig", "");
|
|
1976
1877
|
lines.push(...input.agent.assetDockerfileLines);
|
|
1977
1878
|
lines.push(...input.agent.notificationDockerfileLines);
|
|
1978
1879
|
lines.push("COPY entrypoint.sh /usr/local/bin/entrypoint.sh", "COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf", "RUN chmod +x /usr/local/bin/entrypoint.sh", "", "ENTRYPOINT [\"/usr/local/bin/entrypoint.sh\"]");
|
|
@@ -2088,7 +1989,7 @@ function createCli(signal, interactive) {
|
|
|
2088
1989
|
outro("Project is already registered.");
|
|
2089
1990
|
return success({});
|
|
2090
1991
|
}
|
|
2091
|
-
outro(`Next: run ${
|
|
1992
|
+
outro(`Next: run ${command("devbox build")} to prepare your workspace, then ${command("devbox up")} to start it.`);
|
|
2092
1993
|
return success({});
|
|
2093
1994
|
});
|
|
2094
1995
|
cli.command("config", "Edit Global or Local configuration for the current Project.").action(async () => {
|
|
@@ -2100,7 +2001,7 @@ function createCli(signal, interactive) {
|
|
|
2100
2001
|
prompt
|
|
2101
2002
|
});
|
|
2102
2003
|
if (!result.ok) return result;
|
|
2103
|
-
outro(result.value.changed ?
|
|
2004
|
+
outro(result.value.changed ? `Global configuration updated. Run ${command("devbox build")} to rebuild the Workspace image.` : "Global configuration was not changed.");
|
|
2104
2005
|
return success({});
|
|
2105
2006
|
}
|
|
2106
2007
|
const result = await configureLocalProject({
|
|
@@ -2119,7 +2020,7 @@ function createCli(signal, interactive) {
|
|
|
2119
2020
|
if (!result.ok) return result;
|
|
2120
2021
|
return success({ message: `Built Workspace image: ${result.value.image}` });
|
|
2121
2022
|
});
|
|
2122
|
-
cli.command("rm", "Remove the current
|
|
2023
|
+
cli.command("rm", "Remove the current Project Sandbox and Devbox state.").option("--yes", "Skip the confirmation prompt.").action(async (options = {}) => {
|
|
2123
2024
|
if (!interactive && !options.yes) return interactiveFailure("devbox rm requires a TTY or --yes.", "Run devbox rm from a TTY, or pass --yes to confirm removal.");
|
|
2124
2025
|
const result = await removeProject({
|
|
2125
2026
|
signal,
|
|
@@ -2129,22 +2030,6 @@ function createCli(signal, interactive) {
|
|
|
2129
2030
|
if (!result.ok) return result;
|
|
2130
2031
|
return success({ message: result.value.removed ? `Removed Project: ${result.value.root}` : "Project removal was not changed." });
|
|
2131
2032
|
});
|
|
2132
|
-
cli.command("cleanup", "Remove explicitly selected disposable Devbox state.").option("--missing-projects", "Remove Missing-root Project registrations.").option("--yes", "Skip the confirmation prompt.").action(async (options = {}) => {
|
|
2133
|
-
if (!options.missingProjects) return failure({
|
|
2134
|
-
kind: "usage",
|
|
2135
|
-
code: "cleanup-mode-required",
|
|
2136
|
-
observed: "cleanup requires --missing-projects.",
|
|
2137
|
-
nextAction: "Run devbox cleanup --missing-projects."
|
|
2138
|
-
});
|
|
2139
|
-
if (!interactive && !options.yes) return interactiveFailure("devbox cleanup --missing-projects requires a TTY or --yes.", "Run devbox cleanup --missing-projects from a TTY, or pass --yes to confirm removal.");
|
|
2140
|
-
const result = await cleanupMissingProjects({
|
|
2141
|
-
signal,
|
|
2142
|
-
confirm: prompt.confirm,
|
|
2143
|
-
yes: options.yes
|
|
2144
|
-
});
|
|
2145
|
-
if (!result.ok) return result;
|
|
2146
|
-
return success({ message: result.value.removed ? `Removed Missing-root Projects: ${result.value.roots.join(", ")}` : "No Missing-root Projects were removed." });
|
|
2147
|
-
});
|
|
2148
2033
|
for (const [command, description] of [
|
|
2149
2034
|
["up", "Start the current Project Sandbox."],
|
|
2150
2035
|
["down", "Stop and remove the current Project Sandbox."],
|
|
@@ -2163,7 +2048,7 @@ function createCli(signal, interactive) {
|
|
|
2163
2048
|
}
|
|
2164
2049
|
function present(result) {
|
|
2165
2050
|
if (!result.ok) {
|
|
2166
|
-
process.stderr.write(`${result.error.observed}\n${result.error.nextAction}\n`);
|
|
2051
|
+
process.stderr.write(`${highlightCommands(result.error.observed)}\n${highlightCommands(result.error.nextAction)}\n`);
|
|
2167
2052
|
return result.error.kind === "usage" ? 2 : 1;
|
|
2168
2053
|
}
|
|
2169
2054
|
if (result.value.message !== void 0) process.stdout.write(`${result.value.message}\n`);
|
|
@@ -2196,6 +2081,12 @@ async function main() {
|
|
|
2196
2081
|
function isCacError(error) {
|
|
2197
2082
|
return error instanceof Error && error.name === "CACError";
|
|
2198
2083
|
}
|
|
2084
|
+
function command(value) {
|
|
2085
|
+
return c.cyan(c.bold(value));
|
|
2086
|
+
}
|
|
2087
|
+
function highlightCommands(message) {
|
|
2088
|
+
return message.replace(/\bdevbox (?:init|build|config|up|down|stop|sh|rm)\b/g, command);
|
|
2089
|
+
}
|
|
2199
2090
|
function interactiveFailure(observed, nextAction) {
|
|
2200
2091
|
return failure({
|
|
2201
2092
|
kind: "usage",
|