@alanbem/dclaude 0.0.10 → 0.0.12

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 (3) hide show
  1. package/README.md +53 -0
  2. package/dclaude +111 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -151,6 +151,21 @@ dclaude chrome # Launch Chrome with DevTools
151
151
  dclaude # Claude can now interact with the browser
152
152
  ```
153
153
 
154
+ ### iTerm2 Shell Integration
155
+
156
+ If you use iTerm2 on macOS, dclaude automatically enables [iTerm2 Shell Integration](https://iterm2.com/documentation-shell-integration.html):
157
+
158
+ - **Click URLs in output** - Opens in your Mac's browser
159
+ - **imgcat** - Display images inline in terminal
160
+ - **it2copy** - Copy to Mac clipboard from inside container
161
+ - **Marks** - Navigate between command prompts
162
+
163
+ This only activates when running in iTerm2. To disable:
164
+
165
+ ```bash
166
+ DCLAUDE_ITERM2=false dclaude
167
+ ```
168
+
154
169
  ### System Context
155
170
 
156
171
  dclaude automatically tells Claude about its container environment so it can give better suggestions:
@@ -175,10 +190,12 @@ DCLAUDE_SYSTEM_CONTEXT=false dclaude
175
190
  | `DCLAUDE_NAMESPACE` | (none) | Namespace for isolated credentials/config |
176
191
  | `DCLAUDE_NETWORK` | `auto` | Network mode: `auto`, `host`, `bridge` |
177
192
  | `DCLAUDE_GIT_AUTH` | `auto` | SSH auth: `auto`, `agent-forwarding`, `key-mount`, `none` |
193
+ | `DCLAUDE_MOUNT_ROOT` | (working dir) | Mount parent directory for sibling access |
178
194
  | `DCLAUDE_DEBUG` | `false` | Enable debug output |
179
195
  | `DCLAUDE_QUIET` | `false` | Suppress info messages |
180
196
  | `DCLAUDE_NO_UPDATE` | `false` | Skip image update check |
181
197
  | `DCLAUDE_SYSTEM_CONTEXT` | `true` | Inform Claude about container environment |
198
+ | `DCLAUDE_ITERM2` | `true` | Enable iTerm2 shell integration (only affects iTerm2) |
182
199
 
183
200
  ## Configuration File
184
201
 
@@ -200,6 +217,7 @@ dclaude walks up the directory tree to find `.dclaude` files. Any dclaude sessio
200
217
  | `NAMESPACE` | Isolate credentials/config (see Namespace Isolation) |
201
218
  | `NETWORK` | Network mode (`host`, `bridge`) |
202
219
  | `GIT_AUTH` | Git auth mode |
220
+ | `MOUNT_ROOT` | Mount directory (relative to config file, or absolute path) |
203
221
  | `DEBUG` | Enable debug output (`true`, `false`) |
204
222
  | `CHROME_PORT` | Chrome DevTools port |
205
223
 
@@ -238,6 +256,41 @@ Each namespace gets its own:
238
256
  - Git configuration
239
257
  - Container instance
240
258
 
259
+ ## Mount Root (Parent Directory Access)
260
+
261
+ By default, dclaude only mounts the current working directory. Use `MOUNT_ROOT` to mount a parent directory, enabling access to sibling directories.
262
+
263
+ **Use case:** You're working in a subdirectory but need access to related projects:
264
+
265
+ ```text
266
+ /Users/alan/projects/mycompany/
267
+ ├── shared-libs/ # Common libraries
268
+ ├── api-service/ # API backend
269
+ ├── web-app/ # Frontend
270
+ └── infrastructure/
271
+ └── terraform/ # ← You're here, but need access to siblings
272
+ ```
273
+
274
+ **Option 1: Using `.dclaude` file (recommended)**
275
+ ```bash
276
+ # Create config at the directory you want as mount root
277
+ echo "MOUNT_ROOT=." > ~/projects/mycompany/.dclaude
278
+
279
+ # Relative paths are resolved from config file's directory
280
+ echo "MOUNT_ROOT=.." > ~/projects/mycompany/subdir/.dclaude # mounts mycompany/
281
+ ```
282
+
283
+ **Option 2: Using environment variable**
284
+ ```bash
285
+ # Relative path (from working directory)
286
+ DCLAUDE_MOUNT_ROOT=../.. dclaude
287
+
288
+ # Absolute path
289
+ DCLAUDE_MOUNT_ROOT=/Users/alan/projects/mycompany dclaude
290
+ ```
291
+
292
+ Now Claude can see and work with all sibling directories while your working directory remains `terraform/`.
293
+
241
294
  ## Networking
242
295
 
243
296
  dclaude auto-detects the best networking mode:
package/dclaude CHANGED
@@ -27,6 +27,7 @@ CHROME_FLAGS="${DCLAUDE_CHROME_FLAGS:-}"
27
27
  # - DCLAUDE_GIT_AUTH (git auth mode)
28
28
  # - DCLAUDE_DEBUG (enable debug output)
29
29
  # - DCLAUDE_CHROME_PORT (chrome devtools port)
30
+ # - DCLAUDE_MOUNT_ROOT (mount a parent directory instead of working directory)
30
31
 
31
32
  # Colors for output (only if terminal supports it)
32
33
  if [[ -t 1 ]]; then
@@ -122,6 +123,18 @@ load_config_file() {
122
123
  CHROME_PORT)
123
124
  [[ -z "${DCLAUDE_CHROME_PORT:-}" ]] && DCLAUDE_CHROME_PORT="$value"
124
125
  ;;
126
+ MOUNT_ROOT)
127
+ if [[ -z "${DCLAUDE_MOUNT_ROOT:-}" ]]; then
128
+ # Resolve relative paths relative to config file's directory
129
+ if [[ "$value" != /* ]]; then
130
+ local config_dir
131
+ config_dir=$(dirname "$config_file")
132
+ DCLAUDE_MOUNT_ROOT=$(cd "$config_dir" && cd "$value" 2>/dev/null && pwd)
133
+ else
134
+ DCLAUDE_MOUNT_ROOT="$value"
135
+ fi
136
+ fi
137
+ ;;
125
138
  esac
126
139
  fi
127
140
  done < "$config_file"
@@ -147,6 +160,7 @@ if [[ -n "$DCLAUDE_CONFIG_FILE" ]]; then
147
160
  [[ -n "${DCLAUDE_NETWORK:-}" ]] && debug " NETWORK=$DCLAUDE_NETWORK"
148
161
  [[ -n "${DCLAUDE_GIT_AUTH:-}" ]] && debug " GIT_AUTH=$DCLAUDE_GIT_AUTH"
149
162
  [[ -n "${DCLAUDE_CHROME_PORT:-}" ]] && debug " CHROME_PORT=$DCLAUDE_CHROME_PORT"
163
+ [[ -n "${DCLAUDE_MOUNT_ROOT:-}" ]] && debug " MOUNT_ROOT=$DCLAUDE_MOUNT_ROOT"
150
164
  fi
151
165
  fi
152
166
 
@@ -577,6 +591,55 @@ get_host_path() {
577
591
  echo "$host_path"
578
592
  }
579
593
 
594
+ # Compute mount root path from DCLAUDE_MOUNT_ROOT
595
+ # Supports: absolute paths, relative paths (../, ../../, etc.)
596
+ # Returns: Absolute path to mount root, or HOST_PATH if not set
597
+ get_mount_root() {
598
+ local host_path="$1"
599
+ local mount_root="${DCLAUDE_MOUNT_ROOT:-}"
600
+
601
+ # If not set, use host path (current behavior)
602
+ if [[ -z "$mount_root" ]]; then
603
+ echo "$host_path"
604
+ return 0
605
+ fi
606
+
607
+ local resolved_root
608
+
609
+ # Handle relative paths (resolve relative to host_path)
610
+ if [[ "$mount_root" != /* ]]; then
611
+ # Relative path - resolve it
612
+ resolved_root=$(cd "$host_path" && cd "$mount_root" 2>/dev/null && pwd) || {
613
+ error "Mount root path not accessible: $mount_root (relative to $host_path)"
614
+ exit 1
615
+ }
616
+ else
617
+ # Absolute path - use directly
618
+ resolved_root="$mount_root"
619
+ fi
620
+
621
+ # Validate mount root exists
622
+ if [[ ! -d "$resolved_root" ]]; then
623
+ error "Mount root directory does not exist: $resolved_root"
624
+ exit 1
625
+ fi
626
+
627
+ # Validate that host_path is under mount_root
628
+ # Use realpath to normalize paths for comparison
629
+ local real_host real_root
630
+ real_host=$(cd "$host_path" && pwd -P)
631
+ real_root=$(cd "$resolved_root" && pwd -P)
632
+
633
+ if [[ "$real_host" != "$real_root" && "$real_host" != "$real_root"/* ]]; then
634
+ error "Working directory is not under mount root"
635
+ error " Working directory: $real_host"
636
+ error " Mount root: $real_root"
637
+ exit 1
638
+ fi
639
+
640
+ echo "$resolved_root"
641
+ }
642
+
580
643
  # Generate deterministic container name from path (and namespace if set)
581
644
  get_container_name() {
582
645
  local path="${1:-$HOST_PATH}"
@@ -955,13 +1018,15 @@ detect_tty_flags() {
955
1018
  echo "$tty_flags"
956
1019
  }
957
1020
 
958
- # Check if Claude is being run in print mode (-p/--print)
959
- # This checks arguments as separate array elements, so -p inside a string won't match
960
- is_print_mode() {
1021
+ # Check if Claude is being run in a mode that should skip tmux
1022
+ # These are flags that just output something and exit (no interactive session)
1023
+ should_skip_tmux() {
961
1024
  for arg in "$@"; do
962
- if [[ "$arg" == "-p" ]] || [[ "$arg" == "--print" ]]; then
963
- return 0
964
- fi
1025
+ case "$arg" in
1026
+ -p|--print|--version|-v|--help|-h)
1027
+ return 0
1028
+ ;;
1029
+ esac
965
1030
  done
966
1031
  return 1
967
1032
  }
@@ -974,6 +1039,9 @@ main() {
974
1039
 
975
1040
  info "Verifying environment..."
976
1041
  debug "Host path: $HOST_PATH"
1042
+ if [[ "$MOUNT_ROOT" != "$HOST_PATH" ]]; then
1043
+ debug "Mount root: $MOUNT_ROOT"
1044
+ fi
977
1045
 
978
1046
  # Check prerequisites
979
1047
  check_docker
@@ -1106,6 +1174,9 @@ main() {
1106
1174
  debug " SYSTEM_CONTEXT=$ENABLE_SYSTEM_CONTEXT"
1107
1175
  debug " IMAGE=$IMAGE"
1108
1176
  debug " HOST_PATH=$HOST_PATH"
1177
+ if [[ "$MOUNT_ROOT" != "$HOST_PATH" ]]; then
1178
+ debug " MOUNT_ROOT=$MOUNT_ROOT"
1179
+ fi
1109
1180
  debug " PLATFORM=$platform"
1110
1181
  fi
1111
1182
 
@@ -1143,8 +1214,18 @@ main() {
1143
1214
  done
1144
1215
  fi
1145
1216
 
1217
+ # Ensure SSH proxy is running if container uses agent forwarding (macOS)
1218
+ if [[ "$platform" == "darwin" ]]; then
1219
+ local container_ssh_sock
1220
+ container_ssh_sock=$(docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' "$container_name" 2>/dev/null | grep "^SSH_AUTH_SOCK=" | cut -d= -f2)
1221
+ if [[ "$container_ssh_sock" == "/tmp/ssh-proxy/agent" ]]; then
1222
+ debug "Container uses SSH agent forwarding, ensuring proxy is running"
1223
+ setup_ssh_proxy_container
1224
+ fi
1225
+ fi
1226
+
1146
1227
  # Check if interactive (TTY available) and not in print mode
1147
- if [[ -n "$tty_flags" ]] && ! is_print_mode "${claude_args[@]}" "$@"; then
1228
+ if [[ -n "$tty_flags" ]] && ! should_skip_tmux "${claude_args[@]}" "$@"; then
1148
1229
  # Interactive and not print mode - use tmux for session management
1149
1230
  local tmux_session
1150
1231
  if [[ -n "${DCLAUDE_TMUX_SESSION:-}" ]]; then
@@ -1161,6 +1242,7 @@ main() {
1161
1242
  [[ -n "${TERM_PROGRAM_VERSION:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
1162
1243
  [[ -n "${TERM_SESSION_ID:-}" ]] && exec_env_args+=(-e "TERM_SESSION_ID=${TERM_SESSION_ID}")
1163
1244
  [[ -n "${COLORTERM:-}" ]] && exec_env_args+=(-e "COLORTERM=${COLORTERM}")
1245
+ [[ -n "${DCLAUDE_ITERM2:-}" ]] && exec_env_args+=(-e "DCLAUDE_ITERM2=${DCLAUDE_ITERM2}")
1164
1246
 
1165
1247
  debug "Creating new tmux session running Claude"
1166
1248
  debug "Claude args count: ${#claude_args[@]}, user args: $*"
@@ -1198,11 +1280,11 @@ main() {
1198
1280
  claude_volume=$(get_volume_name)
1199
1281
 
1200
1282
  DOCKER_ARGS+=(
1201
- # Mount current directory
1202
- -v "${HOST_PATH}:${HOST_PATH}"
1283
+ # Mount directory tree (mount root allows access to parent directories)
1284
+ -v "${MOUNT_ROOT}:${MOUNT_ROOT}"
1203
1285
  # Mount persistent Claude configuration volume
1204
1286
  -v "${claude_volume}:/home/claude/.claude"
1205
- # Set working directory
1287
+ # Set working directory (within the mounted tree)
1206
1288
  -w "${HOST_PATH}"
1207
1289
  # Network mode
1208
1290
  --network="$network_mode"
@@ -1238,6 +1320,11 @@ main() {
1238
1320
  DOCKER_ARGS+=(-e "COLORTERM=${COLORTERM}")
1239
1321
  fi
1240
1322
 
1323
+ # Pass through iTerm2 integration opt-out if set
1324
+ if [[ -n "${DCLAUDE_ITERM2:-}" ]]; then
1325
+ DOCKER_ARGS+=(-e "DCLAUDE_ITERM2=${DCLAUDE_ITERM2}")
1326
+ fi
1327
+
1241
1328
  # Mount Docker socket if detected (detection done earlier for system context)
1242
1329
  if [[ -n "$DOCKER_SOCKET" ]] && [[ -S "$DOCKER_SOCKET" ]]; then
1243
1330
  DOCKER_ARGS+=(-v "${DOCKER_SOCKET}:/var/run/docker.sock")
@@ -1291,7 +1378,7 @@ main() {
1291
1378
  docker exec -u root "$container_name" /usr/local/bin/docker-entrypoint.sh true >/dev/null 2>&1 || true
1292
1379
 
1293
1380
  # Check if interactive (TTY available) and not in print mode
1294
- if [[ -n "$tty_flags" ]] && ! is_print_mode "${claude_args[@]}" "$@"; then
1381
+ if [[ -n "$tty_flags" ]] && ! should_skip_tmux "${claude_args[@]}" "$@"; then
1295
1382
  # Interactive and not print mode - use tmux for session management
1296
1383
  local tmux_session
1297
1384
  if [[ -n "${DCLAUDE_TMUX_SESSION:-}" ]]; then
@@ -1308,6 +1395,7 @@ main() {
1308
1395
  [[ -n "${TERM_PROGRAM_VERSION:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
1309
1396
  [[ -n "${TERM_SESSION_ID:-}" ]] && exec_env_args+=(-e "TERM_SESSION_ID=${TERM_SESSION_ID}")
1310
1397
  [[ -n "${COLORTERM:-}" ]] && exec_env_args+=(-e "COLORTERM=${COLORTERM}")
1398
+ [[ -n "${DCLAUDE_ITERM2:-}" ]] && exec_env_args+=(-e "DCLAUDE_ITERM2=${DCLAUDE_ITERM2}")
1311
1399
 
1312
1400
  debug "Creating new tmux session running Claude"
1313
1401
  debug "Claude args count: ${#claude_args[@]}, user args: $*"
@@ -1456,6 +1544,7 @@ cmd_attach() {
1456
1544
  [[ -n "${TERM_PROGRAM_VERSION:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
1457
1545
  [[ -n "${TERM_SESSION_ID:-}" ]] && exec_env_args+=(-e "TERM_SESSION_ID=${TERM_SESSION_ID}")
1458
1546
  [[ -n "${COLORTERM:-}" ]] && exec_env_args+=(-e "COLORTERM=${COLORTERM}")
1547
+ [[ -n "${DCLAUDE_ITERM2:-}" ]] && exec_env_args+=(-e "DCLAUDE_ITERM2=${DCLAUDE_ITERM2}")
1459
1548
 
1460
1549
  # Attach to existing session
1461
1550
  info "Attaching to session: $session_name"
@@ -1512,7 +1601,7 @@ cmd_chrome() {
1512
1601
  fi
1513
1602
 
1514
1603
  # 2. Setup profile directory
1515
- local profile_dir="$HOST_PATH/.dclaude/chrome/profiles/$CHROME_PROFILE"
1604
+ local profile_dir="$HOST_PATH/.dclaude.d/chrome/profiles/$CHROME_PROFILE"
1516
1605
  mkdir -p "$profile_dir"
1517
1606
  debug "Profile directory: $profile_dir"
1518
1607
 
@@ -1987,8 +2076,9 @@ cmd_git() {
1987
2076
  exit 0
1988
2077
  }
1989
2078
 
1990
- # Initialize HOST_PATH once for all commands
2079
+ # Initialize HOST_PATH and MOUNT_ROOT once for all commands
1991
2080
  HOST_PATH=$(get_host_path)
2081
+ MOUNT_ROOT=$(get_mount_root "$HOST_PATH")
1992
2082
 
1993
2083
  # Handle subcommands
1994
2084
  if [[ $# -gt 0 ]]; then
@@ -2065,6 +2155,7 @@ Environment Variables:
2065
2155
  DCLAUDE_NAMESPACE Namespace for isolated credentials/config
2066
2156
  DCLAUDE_GIT_AUTH SSH auth for Git: auto, agent-forwarding, key-mount, none
2067
2157
  DCLAUDE_NETWORK Network mode: auto, host, bridge
2158
+ DCLAUDE_MOUNT_ROOT Mount parent directory (absolute or relative path)
2068
2159
  DCLAUDE_DOCKER_SOCKET Override Docker socket path
2069
2160
  DCLAUDE_TMUX_SESSION Custom tmux session name (default: claude-TIMESTAMP)
2070
2161
  DCLAUDE_CHROME_BIN Chrome executable path (auto-detected if not set)
@@ -2077,6 +2168,8 @@ Configuration File (.dclaude):
2077
2168
  NAMESPACE=mycompany
2078
2169
  NETWORK=host
2079
2170
  DEBUG=true
2171
+ MOUNT_ROOT=. # Mount config file's directory
2172
+ MOUNT_ROOT=.. # Mount parent of config file's directory
2080
2173
  dclaude walks up the directory tree to find .dclaude files.
2081
2174
  Environment variables override .dclaude settings.
2082
2175
 
@@ -2100,6 +2193,11 @@ Examples:
2100
2193
  DCLAUDE_NAMESPACE=mycompany dclaude
2101
2194
  # Or create .dclaude file: echo "NAMESPACE=mycompany" > ~/projects/mycompany/.dclaude
2102
2195
 
2196
+ # Mount parent directory to access sibling directories
2197
+ DCLAUDE_MOUNT_ROOT=.. dclaude # Relative path
2198
+ DCLAUDE_MOUNT_ROOT=/Users/alan/projects dclaude # Absolute path
2199
+ # Or in .dclaude file: MOUNT_ROOT=..
2200
+
2103
2201
  # Update image and restart container
2104
2202
  dclaude pull # Pull latest image
2105
2203
  dclaude update # Update Claude CLI in container
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alanbem/dclaude",
3
- "version": "0.0.10",
3
+ "version": "0.0.12",
4
4
  "description": "Dockerized Claude Code CLI launcher with MCP support",
5
5
  "main": "dclaude",
6
6
  "bin": {