yolocage 0.1.1 → 0.1.2
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/lib/config.js +18 -2
- package/lib/docker.js +23 -0
- package/lib/types.js +12 -0
- package/package.json +1 -1
package/lib/config.js
CHANGED
|
@@ -54,8 +54,13 @@ function normalize(raw) {
|
|
|
54
54
|
? path.resolve(raw.config_dir)
|
|
55
55
|
: td.configDirHost;
|
|
56
56
|
|
|
57
|
-
// bind_dirs (replace): if the user set it, use ONLY those
|
|
58
|
-
//
|
|
57
|
+
// bind_dirs (replace): if the user set it, use ONLY those. If unset,
|
|
58
|
+
// generate the type defaults — workspace, config dir, and any
|
|
59
|
+
// type-declared single-file mounts (e.g. claude's ~/.claude.json).
|
|
60
|
+
// The bind_dirs=… semantic is documented as a full replace, so the
|
|
61
|
+
// type-default file mounts get dropped along with the dir defaults
|
|
62
|
+
// if the user opts in to bind_dirs. extra_bind_dirs= remains the
|
|
63
|
+
// append-style escape hatch for adding mounts back on top.
|
|
59
64
|
let bindDirs;
|
|
60
65
|
if (Array.isArray(raw.bind_dirs) && raw.bind_dirs.length > 0) {
|
|
61
66
|
bindDirs = raw.bind_dirs.map(parseBindSpec);
|
|
@@ -64,6 +69,17 @@ function normalize(raw) {
|
|
|
64
69
|
{ host: workspace, container: '/workspace', mode: 'rw' },
|
|
65
70
|
{ host: configDirHost, container: td.configDirContainer, mode: 'rw' },
|
|
66
71
|
];
|
|
72
|
+
// Type-declared single-file binds. Tagged `kind: 'file'` so
|
|
73
|
+
// lib/docker.js can touch them into existence at exec time —
|
|
74
|
+
// docker auto-creates an empty *directory* on the host when the
|
|
75
|
+
// path is missing (long-standing footgun), which breaks the
|
|
76
|
+
// mount on the container side. Side-effect kept out of this
|
|
77
|
+
// pure-data layer so tests don't write to the operator's $HOME.
|
|
78
|
+
for (const f of td.extraHostFiles || []) {
|
|
79
|
+
bindDirs.push({
|
|
80
|
+
host: f.host, container: f.container, mode: 'rw', kind: 'file',
|
|
81
|
+
});
|
|
82
|
+
}
|
|
67
83
|
}
|
|
68
84
|
|
|
69
85
|
const extraBindDirs = (raw.extra_bind_dirs || []).map(parseBindSpec);
|
package/lib/docker.js
CHANGED
|
@@ -10,8 +10,30 @@
|
|
|
10
10
|
|
|
11
11
|
'use strict';
|
|
12
12
|
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
13
15
|
const { spawnSync, execFileSync } = require('child_process');
|
|
14
16
|
|
|
17
|
+
// Touch any bind specs tagged `kind: 'file'` into existence on the host.
|
|
18
|
+
// `docker run -v <host>:<container>` auto-creates an empty DIRECTORY
|
|
19
|
+
// at the host path when it's missing (long-standing footgun); single-
|
|
20
|
+
// file binds (e.g. claude's ~/.claude.json) need an actual file there
|
|
21
|
+
// or the mount goes wrong. No-op for paths that already exist; no-op
|
|
22
|
+
// for entries without the `kind: 'file'` tag. Best-effort: an EACCES
|
|
23
|
+
// or similar isn't fatal — docker will surface its own error later.
|
|
24
|
+
function materializeFileBinds(spec) {
|
|
25
|
+
for (const b of spec.bindDirs || []) {
|
|
26
|
+
if (b.kind !== 'file') continue;
|
|
27
|
+
if (fs.existsSync(b.host)) continue;
|
|
28
|
+
try {
|
|
29
|
+
fs.mkdirSync(path.dirname(b.host), { recursive: true });
|
|
30
|
+
fs.writeFileSync(b.host, '', { mode: 0o600 });
|
|
31
|
+
} catch (_e) {
|
|
32
|
+
// swallow — surface as docker's own error if it matters
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
15
37
|
let _sudoChecked = false;
|
|
16
38
|
let _useSudo = false;
|
|
17
39
|
let _noticePrinted = false;
|
|
@@ -75,6 +97,7 @@ function _printNoticeOnce(stderr) {
|
|
|
75
97
|
// { argv: [...], cmd: [...] } for piping into spawnSync(argv[0], argv.slice(1).concat(cmd))
|
|
76
98
|
function buildRunArgs(spec, opts) {
|
|
77
99
|
opts = opts || {};
|
|
100
|
+
materializeFileBinds(spec);
|
|
78
101
|
const args = ['run'];
|
|
79
102
|
if (opts.rm !== false) args.push('--rm');
|
|
80
103
|
if (opts.interactive !== false) args.push('-it');
|
package/lib/types.js
CHANGED
|
@@ -27,6 +27,18 @@ const TYPE_DEFAULTS = {
|
|
|
27
27
|
image: 'ghcr.io/jlamendo/yolocage-claude:latest',
|
|
28
28
|
configDirHost: path.join(HOME, '.claude'),
|
|
29
29
|
configDirContainer: '/home/agent/.claude',
|
|
30
|
+
// claude-code reads config from BOTH `~/.claude/` (directory, project
|
|
31
|
+
// sessions + backups) AND `~/.claude.json` (file, anchored auth +
|
|
32
|
+
// global config). The dir is the `configDir*` mount above; the
|
|
33
|
+
// file lives at the same level outside the dir, so it needs its
|
|
34
|
+
// own bind. Without this, the container starts with no auth and
|
|
35
|
+
// the CLI prompts the operator to log in fresh every cage.
|
|
36
|
+
// (`extraHostFiles` is touched into existence at normalize time if
|
|
37
|
+
// missing on the host, so a never-logged-in operator gets an empty
|
|
38
|
+
// host file that claude-code populates on first sign-in.)
|
|
39
|
+
extraHostFiles: [
|
|
40
|
+
{ host: path.join(HOME, '.claude.json'), container: '/home/agent/.claude.json' },
|
|
41
|
+
],
|
|
30
42
|
// --continue auto-resumes the most recent in-cwd conversation; harmless on
|
|
31
43
|
// first run (claude falls back to a fresh session). With per-cwd persistent
|
|
32
44
|
// cages, this is what the operator wants by default — `yc claude` always
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "yolocage",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Sandboxed claude-code / codex container with built-in egress credential scrubber. Run AI coding agents in --dangerously-skip-permissions mode with a safety net.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "yolocage contributors",
|