@ycs77/devbox 0.3.1 → 0.5.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 +2 -3
- package/dist/cli.mjs +135 -37
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# @ycs77/devbox
|
|
4
4
|
|
|
5
|
-
**A
|
|
5
|
+
**A opinionated devbox for AI agents.**
|
|
6
6
|
|
|
7
7
|
[![NPM version][ico-version]][link-npm]
|
|
8
8
|
[![Software License][ico-license]](LICENSE)
|
|
@@ -79,8 +79,7 @@ Run these commands from the initialized project directory:
|
|
|
79
79
|
| `devbox sh` | Open a Bash shell in the running sandbox. |
|
|
80
80
|
| `devbox stop` | Stop the sandbox and preserve it for a later restart. |
|
|
81
81
|
| `devbox down` | Stop and remove the sandbox container. |
|
|
82
|
-
| `devbox config` | Change the current project's Node.js selection. |
|
|
83
|
-
| `devbox config -g` | Change the globally configured Node.js versions or AI agents. |
|
|
82
|
+
| `devbox config` | Change the globally configured or current project's Node.js selection. |
|
|
84
83
|
|
|
85
84
|
## Troubleshooting
|
|
86
85
|
|
package/dist/cli.mjs
CHANGED
|
@@ -45,9 +45,11 @@ const PACKAGED_AGENTS = {
|
|
|
45
45
|
},
|
|
46
46
|
installation: {
|
|
47
47
|
url: "https://claude.ai/install.sh",
|
|
48
|
-
shell: "bash"
|
|
48
|
+
shell: "bash",
|
|
49
|
+
preInstallCommands: ["echo '{}' > /home/devbox/.claude.json"]
|
|
49
50
|
},
|
|
50
51
|
supportsNotifications: true,
|
|
52
|
+
notificationInstallationCommands: ["claude plugin marketplace add ycs77/claude-code-notifications", "claude plugin install notification-basic-wsl@ycs77-notifications"],
|
|
51
53
|
supportsSkillInstallation: true
|
|
52
54
|
},
|
|
53
55
|
codex: {
|
|
@@ -60,7 +62,8 @@ const PACKAGED_AGENTS = {
|
|
|
60
62
|
shell: "sh"
|
|
61
63
|
},
|
|
62
64
|
supportsNotifications: true,
|
|
63
|
-
supportsSkillInstallation: true
|
|
65
|
+
supportsSkillInstallation: true,
|
|
66
|
+
notificationInstallationCommands: ["codex plugin marketplace add ycs77/codex-notifications", "codex plugin add notification-basic-wsl@ycs77-notifications"]
|
|
64
67
|
},
|
|
65
68
|
agy: {
|
|
66
69
|
home: {
|
|
@@ -84,7 +87,8 @@ const PACKAGED_AGENTS = {
|
|
|
84
87
|
shell: "sh"
|
|
85
88
|
},
|
|
86
89
|
supportsNotifications: true,
|
|
87
|
-
supportsSkillInstallation: false
|
|
90
|
+
supportsSkillInstallation: false,
|
|
91
|
+
notificationInstallationCommands: ["omp plugin marketplace add ycs77/omp-notifications", "omp plugin install notification-basic@ycs77-notifications"]
|
|
88
92
|
}
|
|
89
93
|
};
|
|
90
94
|
const PACKAGED_RUNTIME_CATALOG = {
|
|
@@ -205,7 +209,8 @@ function defaultGlobalConfiguration(catalog = PACKAGED_RUNTIME_CATALOG) {
|
|
|
205
209
|
function defaultLocalConfiguration(globalConfiguration) {
|
|
206
210
|
return {
|
|
207
211
|
version: 1,
|
|
208
|
-
node: globalConfiguration.node[0] ?? null
|
|
212
|
+
node: globalConfiguration.node[0] ?? null,
|
|
213
|
+
ports: ["APP_PORT:5173:5173"]
|
|
209
214
|
};
|
|
210
215
|
}
|
|
211
216
|
function parseGlobalConfiguration(source, catalog = PACKAGED_RUNTIME_CATALOG) {
|
|
@@ -245,15 +250,22 @@ function parseLocalConfiguration(source, globalConfiguration, catalog = PACKAGED
|
|
|
245
250
|
const parsed = parseYaml(source);
|
|
246
251
|
if (!parsed.ok) return invalid$1("invalid-local-configuration", parsed.observed);
|
|
247
252
|
const document = parsed.value;
|
|
248
|
-
if (!isRecord(document) || !hasExactKeys$1(document, ["version", "node"])
|
|
253
|
+
if (!isRecord(document) || !hasExactKeys$1(document, ["version", "node"]) && !hasExactKeys$1(document, [
|
|
254
|
+
"version",
|
|
255
|
+
"node",
|
|
256
|
+
"ports"
|
|
257
|
+
])) return invalid$1("invalid-local-configuration", "Local configuration contains unknown or missing fields.");
|
|
249
258
|
if (document.version !== 1) return invalid$1("invalid-local-configuration", "Local configuration must use version: 1.");
|
|
250
259
|
const node = runtimeLineOrNull(document.node);
|
|
251
260
|
if (node === void 0) return invalid$1("invalid-local-configuration", `Node Runtime is invalid: ${String(document.node)}.`);
|
|
252
261
|
if (node !== null && !catalog.runtimes.node?.includes(node)) return invalid$1("invalid-local-configuration", `Node Runtime is not in the packaged catalog: ${String(document.node)}.`);
|
|
253
262
|
if (node !== null && !globalConfiguration.node.includes(node)) return invalid$1("unconfigured-runtime-selection", `Local configuration selects a Node Runtime that is not configured globally: ${node}.`);
|
|
263
|
+
const ports = document.ports === void 0 ? success([]) : normalizePortMappings(document.ports);
|
|
264
|
+
if (!ports.ok) return ports;
|
|
254
265
|
return success({
|
|
255
266
|
version: 1,
|
|
256
|
-
node
|
|
267
|
+
node,
|
|
268
|
+
ports: ports.value
|
|
257
269
|
});
|
|
258
270
|
}
|
|
259
271
|
function serializeGlobalConfiguration(configuration) {
|
|
@@ -267,12 +279,31 @@ function serializeGlobalConfiguration(configuration) {
|
|
|
267
279
|
function serializeLocalConfiguration(configuration) {
|
|
268
280
|
return stringify({
|
|
269
281
|
version: 1,
|
|
270
|
-
node: configuration.node
|
|
282
|
+
node: configuration.node,
|
|
283
|
+
ports: [...configuration.ports]
|
|
271
284
|
});
|
|
272
285
|
}
|
|
273
286
|
function configurationsEqual(left, right) {
|
|
274
287
|
return JSON.stringify(left) === JSON.stringify(right);
|
|
275
288
|
}
|
|
289
|
+
function renderComposePortMapping(mapping) {
|
|
290
|
+
return parsePortMapping(mapping)?.compose ?? mapping;
|
|
291
|
+
}
|
|
292
|
+
function normalizePortMappings(values) {
|
|
293
|
+
if (!Array.isArray(values) || !values.every((value) => typeof value === "string")) return invalid$1("invalid-local-configuration", "Ports must be a list of port mappings.");
|
|
294
|
+
const mappings = [];
|
|
295
|
+
const hostPorts = /* @__PURE__ */ new Set();
|
|
296
|
+
for (const input of values) {
|
|
297
|
+
const mapping = input.trim();
|
|
298
|
+
if (mapping.length === 0) continue;
|
|
299
|
+
const parsed = parsePortMapping(mapping);
|
|
300
|
+
if (parsed === void 0) return invalid$1("invalid-local-configuration", `Port mapping is invalid: ${mapping}.`);
|
|
301
|
+
if (hostPorts.has(parsed.hostPort)) return invalid$1("invalid-local-configuration", `Host port is configured more than once: ${parsed.hostPort}.`);
|
|
302
|
+
hostPorts.add(parsed.hostPort);
|
|
303
|
+
mappings.push(mapping);
|
|
304
|
+
}
|
|
305
|
+
return success(mappings);
|
|
306
|
+
}
|
|
276
307
|
function parseYaml(source) {
|
|
277
308
|
try {
|
|
278
309
|
const document = parseDocument(source, {
|
|
@@ -303,6 +334,22 @@ function runtimeLineOrNull(value) {
|
|
|
303
334
|
if (value === null) return null;
|
|
304
335
|
return runtimeLine(value);
|
|
305
336
|
}
|
|
337
|
+
function parsePortMapping(mapping) {
|
|
338
|
+
const literal = /^(\d+):(\d+)$/.exec(mapping);
|
|
339
|
+
if (literal !== null) return validPort(literal[1]) && validPort(literal[2]) ? {
|
|
340
|
+
hostPort: String(Number(literal[1])),
|
|
341
|
+
compose: mapping
|
|
342
|
+
} : void 0;
|
|
343
|
+
const environment = /^([A-Za-z_][A-Za-z0-9_]*):(\d+):(\d+)$/.exec(mapping);
|
|
344
|
+
if (environment !== null) return validPort(environment[2]) && validPort(environment[3]) ? {
|
|
345
|
+
hostPort: String(Number(environment[2])),
|
|
346
|
+
compose: `\${${environment[1]}:-${environment[2]}}:${environment[3]}`
|
|
347
|
+
} : void 0;
|
|
348
|
+
}
|
|
349
|
+
function validPort(value) {
|
|
350
|
+
const port = Number(value);
|
|
351
|
+
return Number.isInteger(port) && port >= 1 && port <= 65535;
|
|
352
|
+
}
|
|
306
353
|
function isRecord(value) {
|
|
307
354
|
if (typeof value !== "object" || value === null) return false;
|
|
308
355
|
const prototype = Object.getPrototypeOf(value);
|
|
@@ -460,6 +507,7 @@ function selectComposeFragments({ selectedNode, configuredAgents, agentNotificat
|
|
|
460
507
|
function renderProjectCompose(input) {
|
|
461
508
|
const fragments = selectComposeFragments(input);
|
|
462
509
|
const environment = {
|
|
510
|
+
TERM: "xterm-256color",
|
|
463
511
|
...fragments.node?.environment,
|
|
464
512
|
...fragments.notification?.environment
|
|
465
513
|
};
|
|
@@ -496,8 +544,10 @@ function renderProjectCompose(input) {
|
|
|
496
544
|
},
|
|
497
545
|
services: { devbox: {
|
|
498
546
|
image: WORKSPACE_IMAGE,
|
|
499
|
-
|
|
547
|
+
hostname: "devbox",
|
|
548
|
+
container_name: `devbox-${input.sandboxName}`,
|
|
500
549
|
working_dir: `/workspace/${input.sandboxName}`,
|
|
550
|
+
...input.ports.length === 0 ? {} : { ports: input.ports.map((port) => renderComposePortMapping(port)) },
|
|
501
551
|
...Object.keys(environment).length === 0 ? {} : { environment },
|
|
502
552
|
volumes
|
|
503
553
|
} },
|
|
@@ -521,8 +571,9 @@ function validatesProjectCompose(source, input) {
|
|
|
521
571
|
const metadata = definition["x-devbox"];
|
|
522
572
|
if (metadata.version !== 1 || metadata.project_root !== input.projectRoot || metadata.compose_name !== input.sandboxName || metadata.sandbox_service !== "devbox") return false;
|
|
523
573
|
const service = definition.services.devbox;
|
|
524
|
-
if (service.image !== "devbox-workspace:latest" || service.container_name !== input.sandboxName || service.working_dir !== `/workspace/${input.sandboxName}` || "build" in service) return false;
|
|
525
|
-
|
|
574
|
+
if (service.image !== "devbox-workspace:latest" || service.container_name !== `devbox-${input.sandboxName}` || service.working_dir !== `/workspace/${input.sandboxName}` || "build" in service) return false;
|
|
575
|
+
const ports = input.ports.map((port) => renderComposePortMapping(port));
|
|
576
|
+
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);
|
|
526
577
|
} catch {
|
|
527
578
|
return false;
|
|
528
579
|
}
|
|
@@ -915,7 +966,7 @@ async function configureGlobalUnlocked(input = {}, locks) {
|
|
|
915
966
|
for (const [root, localConfiguration] of localConfigurations) {
|
|
916
967
|
if (localConfiguration.node === null || !removedNode.has(localConfiguration.node)) continue;
|
|
917
968
|
let replacement = input.nextLocalConfigurations?.[root];
|
|
918
|
-
if (replacement === void 0 && input.prompt?.editLocal) replacement = await input.prompt.editLocal(localConfiguration, catalog, nextCheck.value);
|
|
969
|
+
if (replacement === void 0 && input.prompt?.editLocal) replacement = await input.prompt.editLocal(localConfiguration, catalog, nextCheck.value, false);
|
|
919
970
|
if (replacement === void 0) return failure({
|
|
920
971
|
kind: "validation",
|
|
921
972
|
code: "node-replacement-required",
|
|
@@ -1324,7 +1375,8 @@ async function composeStateWrite(input) {
|
|
|
1324
1375
|
selectedNode: input.localConfiguration.node,
|
|
1325
1376
|
configuredAgents: input.globalConfiguration.agent,
|
|
1326
1377
|
agentNotifications: input.globalConfiguration.agent_notifications,
|
|
1327
|
-
claudeHostConfiguration: claudeHostConfiguration.value
|
|
1378
|
+
claudeHostConfiguration: claudeHostConfiguration.value,
|
|
1379
|
+
ports: input.localConfiguration.ports
|
|
1328
1380
|
});
|
|
1329
1381
|
if (!rendered.ok) return rendered;
|
|
1330
1382
|
const path = join(input.stateDirectory, "compose.yaml");
|
|
@@ -1515,22 +1567,38 @@ function createConfigurationPrompter({ signal, input, output }) {
|
|
|
1515
1567
|
}))
|
|
1516
1568
|
};
|
|
1517
1569
|
},
|
|
1518
|
-
editLocal: async (configuration, catalog, globalConfiguration) => {
|
|
1519
|
-
p.log.step(`${c.bold(c.cyan("Project configuration"))}\n${c.dim("Choose the runtime for this Project.")}`, common);
|
|
1570
|
+
editLocal: async (configuration, catalog, globalConfiguration, editPorts = true) => {
|
|
1571
|
+
p.log.step(`${c.bold(c.cyan("Project configuration"))}\n${c.dim(editPorts ? "Choose the runtime and published ports for this Project." : "Choose the replacement runtime for this Project.")}`, common);
|
|
1572
|
+
const node = await promptValue(p.select({
|
|
1573
|
+
message: "Selected Node release line",
|
|
1574
|
+
options: [{
|
|
1575
|
+
value: null,
|
|
1576
|
+
label: "None"
|
|
1577
|
+
}, ...(globalConfiguration.node ?? []).map((value) => ({
|
|
1578
|
+
value,
|
|
1579
|
+
label: value
|
|
1580
|
+
}))],
|
|
1581
|
+
initialValue: configuration.node,
|
|
1582
|
+
...common
|
|
1583
|
+
}));
|
|
1584
|
+
if (!editPorts) return {
|
|
1585
|
+
...configuration,
|
|
1586
|
+
node
|
|
1587
|
+
};
|
|
1588
|
+
const ports = normalizePortMappings((await promptValue(p.multiline({
|
|
1589
|
+
message: "Published ports",
|
|
1590
|
+
initialValue: configuration.ports.join("\n"),
|
|
1591
|
+
validate: (value) => {
|
|
1592
|
+
const ports = normalizePortMappings((value ?? "").split("\n"));
|
|
1593
|
+
return ports.ok ? void 0 : ports.error.observed;
|
|
1594
|
+
},
|
|
1595
|
+
...common
|
|
1596
|
+
}))).split("\n"));
|
|
1597
|
+
if (!ports.ok) throw new Error(ports.error.observed);
|
|
1520
1598
|
return {
|
|
1521
1599
|
version: 1,
|
|
1522
|
-
node
|
|
1523
|
-
|
|
1524
|
-
options: [{
|
|
1525
|
-
value: null,
|
|
1526
|
-
label: "None"
|
|
1527
|
-
}, ...(globalConfiguration.node ?? []).map((value) => ({
|
|
1528
|
-
value,
|
|
1529
|
-
label: value
|
|
1530
|
-
}))],
|
|
1531
|
-
initialValue: configuration.node,
|
|
1532
|
-
...common
|
|
1533
|
-
}))
|
|
1600
|
+
node,
|
|
1601
|
+
ports: ports.value
|
|
1534
1602
|
};
|
|
1535
1603
|
}
|
|
1536
1604
|
};
|
|
@@ -1625,6 +1693,8 @@ async function buildWorkspaceUnlocked(input) {
|
|
|
1625
1693
|
const parsedGlobal = parseGlobalConfiguration(globalSource, catalogCheck.value);
|
|
1626
1694
|
if (!parsedGlobal.ok) return parsedGlobal;
|
|
1627
1695
|
const globalConfiguration = parsedGlobal.value;
|
|
1696
|
+
const hostIdentity = currentHostIdentity();
|
|
1697
|
+
if (!hostIdentity.ok) return hostIdentity;
|
|
1628
1698
|
const agents = globalConfiguration.agent.map((name) => ({
|
|
1629
1699
|
name,
|
|
1630
1700
|
recipe: PACKAGED_AGENTS[name]
|
|
@@ -1635,6 +1705,7 @@ async function buildWorkspaceUnlocked(input) {
|
|
|
1635
1705
|
nodeRuntimes,
|
|
1636
1706
|
buildNodeRuntime: buildNodeRuntimeRecipe(globalConfiguration),
|
|
1637
1707
|
agents,
|
|
1708
|
+
agentNotifications: globalConfiguration.agent_notifications,
|
|
1638
1709
|
skillAgents: globalConfiguration.agent.filter((agent) => PACKAGED_AGENTS[agent]?.supportsSkillInstallation === true)
|
|
1639
1710
|
});
|
|
1640
1711
|
await mkdir(paths.buildContext, { recursive: true });
|
|
@@ -1652,7 +1723,8 @@ async function buildWorkspaceUnlocked(input) {
|
|
|
1652
1723
|
const buildResult = await (input.executeDockerBuild ?? runDockerBuild)({
|
|
1653
1724
|
context: paths.buildContext,
|
|
1654
1725
|
image: WORKSPACE_IMAGE,
|
|
1655
|
-
noCache: input.noCache === true
|
|
1726
|
+
noCache: input.noCache === true,
|
|
1727
|
+
buildArgs: hostIdentity.value
|
|
1656
1728
|
});
|
|
1657
1729
|
if (!buildResult.ok) return buildResult;
|
|
1658
1730
|
return success({ image: WORKSPACE_IMAGE });
|
|
@@ -1665,17 +1737,38 @@ function buildNodeRuntimeRecipe(globalConfiguration) {
|
|
|
1665
1737
|
}
|
|
1666
1738
|
return buildNodeRuntime;
|
|
1667
1739
|
}
|
|
1740
|
+
function currentHostIdentity() {
|
|
1741
|
+
const userId = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
1742
|
+
const groupId = typeof process.getgid === "function" ? process.getgid() : void 0;
|
|
1743
|
+
if (typeof userId !== "number" || typeof groupId !== "number" || !Number.isSafeInteger(userId) || !Number.isSafeInteger(groupId) || userId <= 0 || groupId <= 0) return failure({
|
|
1744
|
+
kind: "validation",
|
|
1745
|
+
code: "invalid-host-identity",
|
|
1746
|
+
observed: "The invoking host process must have a non-root POSIX numeric UID and GID.",
|
|
1747
|
+
nextAction: "Run devbox build as the intended non-root host user."
|
|
1748
|
+
});
|
|
1749
|
+
return success({
|
|
1750
|
+
USER_ID: String(userId),
|
|
1751
|
+
GROUP_ID: String(groupId)
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1668
1754
|
function renderBuildDockerfile(input) {
|
|
1669
1755
|
const lines = [
|
|
1670
1756
|
"# Generated by devbox build. Machine-owned Workspace build context.",
|
|
1671
1757
|
"FROM ubuntu:24.04",
|
|
1672
1758
|
"",
|
|
1759
|
+
"ARG USER_ID",
|
|
1760
|
+
"ARG GROUP_ID",
|
|
1761
|
+
"",
|
|
1673
1762
|
"ENV DEBIAN_FRONTEND=noninteractive",
|
|
1674
1763
|
"ENV TZ=UTC",
|
|
1675
1764
|
"ENV LANG=C.UTF-8",
|
|
1676
1765
|
"",
|
|
1677
1766
|
"RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone",
|
|
1678
1767
|
"",
|
|
1768
|
+
"RUN echo \"Acquire::http::Pipeline-Depth 0;\" > /etc/apt/apt.conf.d/99custom && \\",
|
|
1769
|
+
" echo \"Acquire::http::No-Cache true;\" >> /etc/apt/apt.conf.d/99custom && \\",
|
|
1770
|
+
" echo \"Acquire::BrokenProxy true;\" >> /etc/apt/apt.conf.d/99custom",
|
|
1771
|
+
"",
|
|
1679
1772
|
"# Install the Base profile packages and build tools",
|
|
1680
1773
|
"RUN set -eux \\",
|
|
1681
1774
|
" && apt-get update && apt-get upgrade -y \\",
|
|
@@ -1691,8 +1784,8 @@ function renderBuildDockerfile(input) {
|
|
|
1691
1784
|
"",
|
|
1692
1785
|
"# Create the devbox user and group",
|
|
1693
1786
|
"RUN userdel -r ubuntu \\",
|
|
1694
|
-
" && groupadd -
|
|
1695
|
-
" && useradd -m -s /bin/bash -N -g
|
|
1787
|
+
" && groupadd -g \"$GROUP_ID\" devbox \\",
|
|
1788
|
+
" && useradd -m -s /bin/bash -N -g \"$GROUP_ID\" -u \"$USER_ID\" devbox \\",
|
|
1696
1789
|
" && echo \"devbox ALL=(ALL) NOPASSWD:ALL\" > /etc/sudoers.d/devbox \\",
|
|
1697
1790
|
" && chmod 0440 /etc/sudoers.d/devbox",
|
|
1698
1791
|
""
|
|
@@ -1700,7 +1793,10 @@ function renderBuildDockerfile(input) {
|
|
|
1700
1793
|
for (const recipe of input.nodeRuntimes) lines.push(...renderNodeRuntimeStage(recipe));
|
|
1701
1794
|
lines.push("WORKDIR /workspace", "USER devbox", "");
|
|
1702
1795
|
if (input.buildNodeRuntime !== void 0) lines.push("# Configure pnpm for the non-root Sandbox user", "RUN mkdir -p /home/devbox/.config/pnpm /home/devbox/.pnpm-store \\", " && printf 'storeDir: /home/devbox/.pnpm-store\\n' > /home/devbox/.config/pnpm/config.yaml \\", " && chown devbox:devbox /home/devbox/.config/pnpm/config.yaml", "");
|
|
1703
|
-
for (const agent of input.agents)
|
|
1796
|
+
for (const agent of input.agents) {
|
|
1797
|
+
const preInstallCommands = agent.recipe.installation.preInstallCommands ?? [];
|
|
1798
|
+
lines.push(`# Install ${agent.name}`, `RUN mkdir -p ${agent.recipe.home.target} \\`, ...preInstallCommands.map((command) => ` && ${command} \\`), ` && curl -fsSL ${agent.recipe.installation.url} | ${agent.recipe.installation.shell}`, "");
|
|
1799
|
+
}
|
|
1704
1800
|
if (input.agents.length > 0) lines.push("# Set the PATH to include AI tools", "ENV PATH=\"/home/devbox/.local/bin:${PATH}\"", "");
|
|
1705
1801
|
if (input.buildNodeRuntime !== void 0 && input.skillAgents.length > 0) {
|
|
1706
1802
|
const skillAgentArguments = input.skillAgents.map((agent) => `-a ${agent}`).join(" ");
|
|
@@ -1722,6 +1818,8 @@ function renderBuildDockerfile(input) {
|
|
|
1722
1818
|
}
|
|
1723
1819
|
lines.push(`RUN ${ownershipCommands.join(" \\\n && ")}`, "");
|
|
1724
1820
|
}
|
|
1821
|
+
const notificationInstallationCommands = input.agentNotifications ? input.agents.flatMap((agent) => agent.recipe.supportsNotifications ? agent.recipe.notificationInstallationCommands : []) : [];
|
|
1822
|
+
if (notificationInstallationCommands.length > 0) lines.push("# Install Agent Notification Plugins", "USER devbox", "RUN set -eux \\", ...notificationInstallationCommands.map((command, index) => ` && ${command}${index < notificationInstallationCommands.length - 1 ? " \\" : ""}`), "USER root", "");
|
|
1725
1823
|
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\"]");
|
|
1726
1824
|
return `${lines.join("\n")}\n`;
|
|
1727
1825
|
}
|
|
@@ -1756,12 +1854,8 @@ function selectBuildContextAssets(agents) {
|
|
|
1756
1854
|
return assets;
|
|
1757
1855
|
}
|
|
1758
1856
|
function renderEntrypoint(input) {
|
|
1759
|
-
const lines = [
|
|
1760
|
-
|
|
1761
|
-
"set -eu",
|
|
1762
|
-
""
|
|
1763
|
-
];
|
|
1764
|
-
if (input.nodeRuntimes.length > 0) lines.push("# Set the Node.js release line to use", "NODE_VERSION=\"${NODE_VERSION:-}\"", "if [ -n \"$NODE_VERSION\" ]; then", " case \"$NODE_VERSION\" in", " *[!0-9]*)", " echo \"NODE_VERSION must be a numeric release line: $NODE_VERSION\" >&2", " exit 1", " ;;", " esac", "", " NODE_RUNTIME_ROOT=\"/opt/devbox/runtimes/node/$NODE_VERSION\"", " if [ ! -x \"$NODE_RUNTIME_ROOT/bin/node\" ]; then", " echo \"Node.js release line $NODE_VERSION is not installed.\" >&2", " exit 1", " fi", "", " export NODE_RUNTIME_ROOT", " export PATH=\"$NODE_RUNTIME_ROOT/bin:$PATH\"", "fi", "");
|
|
1857
|
+
const lines = ["#!/usr/bin/env bash", ""];
|
|
1858
|
+
if (input.nodeRuntimes.length > 0) lines.push("# Set the Node.js release line to use", "NODE_VERSION=\"${NODE_VERSION:-}\"", "if [ -n \"$NODE_VERSION\" ]; then", " case \"$NODE_VERSION\" in", " *[!0-9]*)", " echo \"NODE_VERSION must be a numeric release line: $NODE_VERSION\" >&2", " exit 1", " ;;", " esac", "", " NODE_RUNTIME_ROOT=\"/opt/devbox/runtimes/node/$NODE_VERSION\"", " if [ ! -x \"$NODE_RUNTIME_ROOT/bin/node\" ]; then", " echo \"Node.js release line $NODE_VERSION is not installed.\" >&2", " exit 1", " fi", "", " if ! grep -q \"# Devbox\" /etc/bash.bashrc; then", " printf \"\\n# Devbox\\nexport PATH=\\\"$NODE_RUNTIME_ROOT/bin:\\$PATH\\\"\\n\" >> /etc/bash.bashrc", " fi", "", " export PATH=\"$NODE_RUNTIME_ROOT/bin:$PATH\"", "fi", "");
|
|
1765
1859
|
if (input.agents.some((agent) => agent.name === "agy")) lines.push("# Link shared Agent Skills", "if [ ! -L \"/home/devbox/.gemini/skills\" ]; then", " ln -sfn /home/devbox/.agents/skills /home/devbox/.gemini/skills", " chown -h devbox:devbox /home/devbox/.gemini/skills", "fi", "");
|
|
1766
1860
|
lines.push("if [ $# -gt 0 ]; then", " exec gosu devbox \"$@\"", "else", " exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf", "fi");
|
|
1767
1861
|
return `${lines.join("\n")}\n`;
|
|
@@ -1823,7 +1917,7 @@ function renderNodeRuntimeStage(recipe) {
|
|
|
1823
1917
|
" && export PATH=\"$NODE_RUNTIME_ROOT/bin:$PATH\" \\",
|
|
1824
1918
|
" && npm install -g npm \\",
|
|
1825
1919
|
" && npm uninstall -g corepack \\",
|
|
1826
|
-
" && npm install -g yarn pnpm @antfu/ni \\",
|
|
1920
|
+
" && npm install -g yarn pnpm @antfu/ni --allow-scripts=pnpm,yarn \\",
|
|
1827
1921
|
" && \"$NODE_RUNTIME_ROOT/bin/node\" --version \\",
|
|
1828
1922
|
" && \"$NODE_RUNTIME_ROOT/bin/npm\" --version \\",
|
|
1829
1923
|
" && rm -rf /tmp/node-source",
|
|
@@ -1834,6 +1928,10 @@ async function runDockerBuild(invocation, signal) {
|
|
|
1834
1928
|
const build = spawn("docker", [
|
|
1835
1929
|
"build",
|
|
1836
1930
|
...invocation.noCache ? ["--no-cache"] : [],
|
|
1931
|
+
"--build-arg",
|
|
1932
|
+
`USER_ID=${invocation.buildArgs.USER_ID}`,
|
|
1933
|
+
"--build-arg",
|
|
1934
|
+
`GROUP_ID=${invocation.buildArgs.GROUP_ID}`,
|
|
1837
1935
|
"--tag",
|
|
1838
1936
|
invocation.image,
|
|
1839
1937
|
"--file",
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ycs77/devbox",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "A
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "A Lucas Yang's opinionated devbox for AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
7
7
|
"agentic",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^26.2.0",
|
|
43
|
+
"actionspack": "^0.1.5",
|
|
43
44
|
"bumpp": "^12.2.0",
|
|
44
45
|
"memfs": "^4.68.1",
|
|
45
46
|
"oxfmt": "^0.63.0",
|
|
@@ -60,7 +61,8 @@
|
|
|
60
61
|
"typecheck": "tsc --noEmit",
|
|
61
62
|
"test": "vitest",
|
|
62
63
|
"check": "pnpm lint && pnpm fmt --check && pnpm typecheck && pnpm test --run",
|
|
63
|
-
"smoke
|
|
64
|
+
"smoke": "node scripts/package-devbox-smoke.mjs",
|
|
65
|
+
"update-actions": "actionspack update",
|
|
64
66
|
"release": "bumpp --commit \"Release v%s\""
|
|
65
67
|
}
|
|
66
68
|
}
|