yolocage 0.1.1 → 0.1.3

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 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 + the
58
- // workspace + the config_dir. If unset, generate the type defaults.
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,11 +27,27 @@ 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
- // --continue auto-resumes the most recent in-cwd conversation; harmless on
31
- // first run (claude falls back to a fresh session). With per-cwd persistent
32
- // cages, this is what the operator wants by default — `yc claude` always
33
- // either resumes or starts fresh, never re-introduces itself mid-project.
34
- cmd: ['claude', '--continue', '--dangerously-skip-permissions'],
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
+ ],
42
+ // Default to a fresh interactive session. We used to pass --continue
43
+ // here on the assumption that it was a no-op on the first run; in
44
+ // current claude-code versions --continue exits with "No conversation
45
+ // found to continue" instead of falling back, which made the very
46
+ // first `yc claude` in a fresh cwd kick the operator straight back
47
+ // out. Operators who want resume can pass it via `yc claude --
48
+ // --continue` (everything after `--` is appended to the in-container
49
+ // argv via `passthrough`).
50
+ cmd: ['claude', '--dangerously-skip-permissions'],
35
51
  },
36
52
  codex: {
37
53
  v0: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yolocage",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
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",