@alanbem/dclaude 0.0.9 → 0.0.11
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 +72 -0
- package/dclaude +188 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -172,6 +172,7 @@ DCLAUDE_SYSTEM_CONTEXT=false dclaude
|
|
|
172
172
|
|----------|---------|-------------|
|
|
173
173
|
| `DCLAUDE_RM` | `false` | Remove container on exit (ephemeral mode) |
|
|
174
174
|
| `DCLAUDE_TAG` | `latest` | Docker image tag |
|
|
175
|
+
| `DCLAUDE_NAMESPACE` | (none) | Namespace for isolated credentials/config |
|
|
175
176
|
| `DCLAUDE_NETWORK` | `auto` | Network mode: `auto`, `host`, `bridge` |
|
|
176
177
|
| `DCLAUDE_GIT_AUTH` | `auto` | SSH auth: `auto`, `agent-forwarding`, `key-mount`, `none` |
|
|
177
178
|
| `DCLAUDE_DEBUG` | `false` | Enable debug output |
|
|
@@ -179,6 +180,64 @@ DCLAUDE_SYSTEM_CONTEXT=false dclaude
|
|
|
179
180
|
| `DCLAUDE_NO_UPDATE` | `false` | Skip image update check |
|
|
180
181
|
| `DCLAUDE_SYSTEM_CONTEXT` | `true` | Inform Claude about container environment |
|
|
181
182
|
|
|
183
|
+
## Configuration File
|
|
184
|
+
|
|
185
|
+
Create a `.dclaude` file at your project root to configure dclaude for that directory tree:
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# ~/projects/mycompany/.dclaude
|
|
189
|
+
NAMESPACE=mycompany
|
|
190
|
+
NETWORK=host
|
|
191
|
+
DEBUG=true
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
dclaude walks up the directory tree to find `.dclaude` files. Any dclaude session started from that directory or any subdirectory will use these settings.
|
|
195
|
+
|
|
196
|
+
**Supported variables:**
|
|
197
|
+
|
|
198
|
+
| Variable | Description |
|
|
199
|
+
|----------|-------------|
|
|
200
|
+
| `NAMESPACE` | Isolate credentials/config (see Namespace Isolation) |
|
|
201
|
+
| `NETWORK` | Network mode (`host`, `bridge`) |
|
|
202
|
+
| `GIT_AUTH` | Git auth mode |
|
|
203
|
+
| `DEBUG` | Enable debug output (`true`, `false`) |
|
|
204
|
+
| `CHROME_PORT` | Chrome DevTools port |
|
|
205
|
+
|
|
206
|
+
**Precedence:** Environment variables override `.dclaude` file settings.
|
|
207
|
+
|
|
208
|
+
## Namespace Isolation
|
|
209
|
+
|
|
210
|
+
Use namespaces to maintain completely separate environments with different credentials and settings.
|
|
211
|
+
|
|
212
|
+
**Use case:** You have both a personal Anthropic subscription and a company subscription. You want to keep them completely separate.
|
|
213
|
+
|
|
214
|
+
**Option 1: Using `.dclaude` file (recommended)**
|
|
215
|
+
```bash
|
|
216
|
+
# Create config at company project root
|
|
217
|
+
echo "NAMESPACE=mycompany" > ~/projects/mycompany/.dclaude
|
|
218
|
+
|
|
219
|
+
# Now any dclaude in that tree uses company credentials
|
|
220
|
+
cd ~/projects/mycompany/api/src
|
|
221
|
+
dclaude # Uses mycompany namespace automatically
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Option 2: Using environment variable**
|
|
225
|
+
```bash
|
|
226
|
+
DCLAUDE_NAMESPACE=mycompany dclaude
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
**Option 3: Shell alias**
|
|
230
|
+
```bash
|
|
231
|
+
# Add to ~/.bashrc or ~/.zshrc
|
|
232
|
+
alias dclaude-work='DCLAUDE_NAMESPACE=mycompany dclaude'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Each namespace gets its own:
|
|
236
|
+
- Claude credentials and API key
|
|
237
|
+
- Claude settings and preferences
|
|
238
|
+
- Git configuration
|
|
239
|
+
- Container instance
|
|
240
|
+
|
|
182
241
|
## Networking
|
|
183
242
|
|
|
184
243
|
dclaude auto-detects the best networking mode:
|
|
@@ -259,6 +318,19 @@ ssh-add ~/.ssh/id_ed25519
|
|
|
259
318
|
dclaude exec brew install <tool> # This persists
|
|
260
319
|
```
|
|
261
320
|
|
|
321
|
+
**Directories appear empty inside container? (OrbStack)**
|
|
322
|
+
|
|
323
|
+
This is a [known OrbStack 2.0.x bug](https://github.com/orbstack/orbstack/issues/2103) where VirtioFS caches stale directory entries. Directories that existed before upgrading to OrbStack 2.0 may appear empty inside the container.
|
|
324
|
+
|
|
325
|
+
```bash
|
|
326
|
+
# Fix: rename the directory to clear the cache
|
|
327
|
+
mv problematic-dir problematic-dir.tmp && mv problematic-dir.tmp problematic-dir
|
|
328
|
+
|
|
329
|
+
# Or restart OrbStack completely (clears all caches)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Upgrading to the latest OrbStack version may also help.
|
|
333
|
+
|
|
262
334
|
## Project Structure
|
|
263
335
|
|
|
264
336
|
```text
|
package/dclaude
CHANGED
|
@@ -4,25 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
set -euo pipefail
|
|
6
6
|
|
|
7
|
-
#
|
|
7
|
+
# Early configuration (before config file loading)
|
|
8
8
|
readonly IMAGE_NAME="${DCLAUDE_REGISTRY:-docker.io}/alanbem/dclaude"
|
|
9
9
|
readonly IMAGE_TAG="${DCLAUDE_TAG:-latest}"
|
|
10
10
|
readonly IMAGE="${IMAGE_NAME}:${IMAGE_TAG}"
|
|
11
11
|
readonly VOLUME_PREFIX="dclaude"
|
|
12
|
-
readonly DEBUG="${DCLAUDE_DEBUG:-false}"
|
|
13
12
|
readonly QUIET="${DCLAUDE_QUIET:-false}"
|
|
14
13
|
readonly REMOVE_CONTAINER="${DCLAUDE_RM:-false}"
|
|
14
|
+
readonly ENABLE_SYSTEM_CONTEXT="${DCLAUDE_SYSTEM_CONTEXT:-true}" # Inform Claude about dclaude environment
|
|
15
|
+
|
|
15
16
|
# Docker socket will be detected dynamically unless overridden
|
|
16
17
|
DOCKER_SOCKET="${DCLAUDE_DOCKER_SOCKET:-}"
|
|
17
|
-
readonly GIT_AUTH_MODE="${DCLAUDE_GIT_AUTH:-auto}" # auto, agent-forwarding, key-mount, none
|
|
18
|
-
readonly ENABLE_SYSTEM_CONTEXT="${DCLAUDE_SYSTEM_CONTEXT:-true}" # Inform Claude about dclaude environment
|
|
19
18
|
|
|
20
|
-
# Chrome configuration (not readonly to allow --port flag override)
|
|
19
|
+
# Chrome configuration (not readonly to allow --port flag and config file override)
|
|
21
20
|
CHROME_PROFILE="${DCLAUDE_CHROME_PROFILE:-claude}"
|
|
22
|
-
CHROME_PORT="${DCLAUDE_CHROME_PORT:-9222}"
|
|
23
21
|
CHROME_BIN="${DCLAUDE_CHROME_BIN:-}"
|
|
24
22
|
CHROME_FLAGS="${DCLAUDE_CHROME_FLAGS:-}"
|
|
25
23
|
|
|
24
|
+
# Variables that can be set via .dclaude config file (declared after config loading)
|
|
25
|
+
# - DCLAUDE_NAMESPACE (namespace for volume/container isolation)
|
|
26
|
+
# - DCLAUDE_NETWORK (network mode: host/bridge)
|
|
27
|
+
# - DCLAUDE_GIT_AUTH (git auth mode)
|
|
28
|
+
# - DCLAUDE_DEBUG (enable debug output)
|
|
29
|
+
# - DCLAUDE_CHROME_PORT (chrome devtools port)
|
|
30
|
+
|
|
26
31
|
# Colors for output (only if terminal supports it)
|
|
27
32
|
if [[ -t 1 ]]; then
|
|
28
33
|
readonly RED='\033[0;31m'
|
|
@@ -68,17 +73,83 @@ debug() {
|
|
|
68
73
|
fi
|
|
69
74
|
}
|
|
70
75
|
|
|
71
|
-
#
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
# Find .dclaude config file by walking up directory tree
|
|
77
|
+
find_config_file() {
|
|
78
|
+
local dir="$PWD"
|
|
79
|
+
while [[ "$dir" != "/" ]]; do
|
|
80
|
+
if [[ -f "$dir/.dclaude" ]]; then
|
|
81
|
+
echo "$dir/.dclaude"
|
|
82
|
+
return 0
|
|
83
|
+
fi
|
|
84
|
+
dir=$(dirname "$dir")
|
|
85
|
+
done
|
|
86
|
+
return 1
|
|
80
87
|
}
|
|
81
88
|
|
|
89
|
+
# Load .dclaude config file (env vars take precedence)
|
|
90
|
+
load_config_file() {
|
|
91
|
+
local config_file="$1"
|
|
92
|
+
|
|
93
|
+
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
94
|
+
# Skip comments and empty lines
|
|
95
|
+
[[ "$line" =~ ^[[:space:]]*# ]] && continue
|
|
96
|
+
[[ -z "${line// }" ]] && continue
|
|
97
|
+
|
|
98
|
+
# Parse key=value
|
|
99
|
+
if [[ "$line" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=[[:space:]]*(.*)$ ]]; then
|
|
100
|
+
local key="${BASH_REMATCH[1]}"
|
|
101
|
+
local value="${BASH_REMATCH[2]}"
|
|
102
|
+
# Trim quotes if present
|
|
103
|
+
value="${value#\"}"
|
|
104
|
+
value="${value%\"}"
|
|
105
|
+
value="${value#\'}"
|
|
106
|
+
value="${value%\'}"
|
|
107
|
+
|
|
108
|
+
# Only set if env var not already set (env var takes precedence)
|
|
109
|
+
case "$key" in
|
|
110
|
+
NAMESPACE)
|
|
111
|
+
[[ -z "${DCLAUDE_NAMESPACE:-}" ]] && DCLAUDE_NAMESPACE="$value"
|
|
112
|
+
;;
|
|
113
|
+
NETWORK)
|
|
114
|
+
[[ -z "${DCLAUDE_NETWORK:-}" ]] && DCLAUDE_NETWORK="$value"
|
|
115
|
+
;;
|
|
116
|
+
GIT_AUTH)
|
|
117
|
+
[[ -z "${DCLAUDE_GIT_AUTH:-}" ]] && DCLAUDE_GIT_AUTH="$value"
|
|
118
|
+
;;
|
|
119
|
+
DEBUG)
|
|
120
|
+
[[ -z "${DCLAUDE_DEBUG:-}" ]] && DCLAUDE_DEBUG="$value"
|
|
121
|
+
;;
|
|
122
|
+
CHROME_PORT)
|
|
123
|
+
[[ -z "${DCLAUDE_CHROME_PORT:-}" ]] && DCLAUDE_CHROME_PORT="$value"
|
|
124
|
+
;;
|
|
125
|
+
esac
|
|
126
|
+
fi
|
|
127
|
+
done < "$config_file"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# Load config file if found (must happen early, before readonly declarations)
|
|
131
|
+
DCLAUDE_CONFIG_FILE=""
|
|
132
|
+
if DCLAUDE_CONFIG_FILE=$(find_config_file); then
|
|
133
|
+
load_config_file "$DCLAUDE_CONFIG_FILE"
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
# Now set readonly variables that may have been loaded from config file
|
|
137
|
+
readonly DEBUG="${DCLAUDE_DEBUG:-false}"
|
|
138
|
+
readonly GIT_AUTH_MODE="${DCLAUDE_GIT_AUTH:-auto}" # auto, agent-forwarding, key-mount, none
|
|
139
|
+
readonly NAMESPACE="${DCLAUDE_NAMESPACE:-}"
|
|
140
|
+
CHROME_PORT="${DCLAUDE_CHROME_PORT:-9222}"
|
|
141
|
+
|
|
142
|
+
# Show config file if loaded (always at info level, details at debug)
|
|
143
|
+
if [[ -n "$DCLAUDE_CONFIG_FILE" ]]; then
|
|
144
|
+
info "Using config: $DCLAUDE_CONFIG_FILE"
|
|
145
|
+
if [[ "$DEBUG" == "true" ]]; then
|
|
146
|
+
[[ -n "$NAMESPACE" ]] && debug " NAMESPACE=$NAMESPACE"
|
|
147
|
+
[[ -n "${DCLAUDE_NETWORK:-}" ]] && debug " NETWORK=$DCLAUDE_NETWORK"
|
|
148
|
+
[[ -n "${DCLAUDE_GIT_AUTH:-}" ]] && debug " GIT_AUTH=$DCLAUDE_GIT_AUTH"
|
|
149
|
+
[[ -n "${DCLAUDE_CHROME_PORT:-}" ]] && debug " CHROME_PORT=$DCLAUDE_CHROME_PORT"
|
|
150
|
+
fi
|
|
151
|
+
fi
|
|
152
|
+
|
|
82
153
|
# Detect platform
|
|
83
154
|
detect_platform() {
|
|
84
155
|
case "$(uname -s)" in
|
|
@@ -416,10 +487,20 @@ detect_network_capability() {
|
|
|
416
487
|
echo "$test_result"
|
|
417
488
|
}
|
|
418
489
|
|
|
490
|
+
# Get volume name (includes namespace if set)
|
|
491
|
+
get_volume_name() {
|
|
492
|
+
if [[ -n "$NAMESPACE" ]]; then
|
|
493
|
+
echo "${VOLUME_PREFIX}-${NAMESPACE}-claude"
|
|
494
|
+
else
|
|
495
|
+
echo "${VOLUME_PREFIX}-claude"
|
|
496
|
+
fi
|
|
497
|
+
}
|
|
498
|
+
|
|
419
499
|
# Create Docker volumes if they don't exist
|
|
420
500
|
create_volumes() {
|
|
421
501
|
# Create essential volume for Claude CLI persistence
|
|
422
|
-
local volume
|
|
502
|
+
local volume
|
|
503
|
+
volume=$(get_volume_name)
|
|
423
504
|
|
|
424
505
|
if ! docker volume inspect "$volume" &> /dev/null; then
|
|
425
506
|
info "Creating volume: $volume"
|
|
@@ -496,12 +577,17 @@ get_host_path() {
|
|
|
496
577
|
echo "$host_path"
|
|
497
578
|
}
|
|
498
579
|
|
|
499
|
-
# Generate deterministic container name from path
|
|
580
|
+
# Generate deterministic container name from path (and namespace if set)
|
|
500
581
|
get_container_name() {
|
|
501
582
|
local path="${1:-$HOST_PATH}"
|
|
583
|
+
local hash_input="${NAMESPACE}:${path}"
|
|
502
584
|
local path_hash
|
|
503
|
-
path_hash=$(echo -n "$
|
|
504
|
-
|
|
585
|
+
path_hash=$(echo -n "$hash_input" | md5sum 2>/dev/null | cut -d' ' -f1 || echo -n "$hash_input" | md5 | cut -d' ' -f1)
|
|
586
|
+
if [[ -n "$NAMESPACE" ]]; then
|
|
587
|
+
echo "dclaude-${NAMESPACE}-${path_hash:0:8}"
|
|
588
|
+
else
|
|
589
|
+
echo "dclaude-${path_hash:0:12}"
|
|
590
|
+
fi
|
|
505
591
|
}
|
|
506
592
|
|
|
507
593
|
# Generate system context prompt for Claude
|
|
@@ -678,10 +764,30 @@ When suggesting commands or file operations, you can treat this environment as i
|
|
|
678
764
|
EOF
|
|
679
765
|
}
|
|
680
766
|
|
|
767
|
+
# Get SSH proxy container/volume names (includes namespace if set)
|
|
768
|
+
get_ssh_proxy_container_name() {
|
|
769
|
+
if [[ -n "$NAMESPACE" ]]; then
|
|
770
|
+
echo "dclaude-${NAMESPACE}-ssh-proxy-$(id -u)"
|
|
771
|
+
else
|
|
772
|
+
echo "dclaude-ssh-proxy-$(id -u)"
|
|
773
|
+
fi
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
get_ssh_proxy_volume_name() {
|
|
777
|
+
if [[ -n "$NAMESPACE" ]]; then
|
|
778
|
+
echo "dclaude-${NAMESPACE}-ssh-proxy"
|
|
779
|
+
else
|
|
780
|
+
echo "dclaude-ssh-proxy"
|
|
781
|
+
fi
|
|
782
|
+
}
|
|
783
|
+
|
|
681
784
|
# Handle SSH authentication based on DCLAUDE_GIT_AUTH mode
|
|
682
785
|
# Setup SSH proxy container for macOS
|
|
683
786
|
setup_ssh_proxy_container() {
|
|
684
|
-
local proxy_container
|
|
787
|
+
local proxy_container
|
|
788
|
+
local proxy_volume
|
|
789
|
+
proxy_container=$(get_ssh_proxy_container_name)
|
|
790
|
+
proxy_volume=$(get_ssh_proxy_volume_name)
|
|
685
791
|
|
|
686
792
|
# Check if proxy container already exists and is running
|
|
687
793
|
if docker ps -q -f name="^${proxy_container}$" 2>/dev/null | grep -q .; then
|
|
@@ -704,7 +810,7 @@ setup_ssh_proxy_container() {
|
|
|
704
810
|
docker run -d \
|
|
705
811
|
--name "$proxy_container" \
|
|
706
812
|
-v "/run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock:ro" \
|
|
707
|
-
-v "
|
|
813
|
+
-v "${proxy_volume}:/tmp/ssh-proxy" \
|
|
708
814
|
--rm \
|
|
709
815
|
alpine:3.19 sh -c '
|
|
710
816
|
# Install socat
|
|
@@ -775,10 +881,12 @@ handle_git_auth() {
|
|
|
775
881
|
setup_ssh_proxy_container
|
|
776
882
|
|
|
777
883
|
# Now mount the proxied socket from the shared volume
|
|
778
|
-
|
|
884
|
+
local ssh_proxy_vol
|
|
885
|
+
ssh_proxy_vol=$(get_ssh_proxy_volume_name)
|
|
886
|
+
docker_args+=(-v "${ssh_proxy_vol}:/tmp/ssh-proxy:ro"
|
|
779
887
|
-e "SSH_AUTH_SOCK=/tmp/ssh-proxy/agent")
|
|
780
888
|
info "SSH agent forwarding enabled via proxy container"
|
|
781
|
-
debug "Mounted SSH proxy volume:
|
|
889
|
+
debug "Mounted SSH proxy volume: ${ssh_proxy_vol}:/tmp/ssh-proxy"
|
|
782
890
|
;;
|
|
783
891
|
windows)
|
|
784
892
|
warning "SSH agent forwarding not fully supported on Windows"
|
|
@@ -847,17 +955,20 @@ detect_tty_flags() {
|
|
|
847
955
|
echo "$tty_flags"
|
|
848
956
|
}
|
|
849
957
|
|
|
850
|
-
# Check if Claude is being run in
|
|
851
|
-
#
|
|
852
|
-
|
|
958
|
+
# Check if Claude is being run in a mode that should skip tmux
|
|
959
|
+
# These are flags that just output something and exit (no interactive session)
|
|
960
|
+
should_skip_tmux() {
|
|
853
961
|
for arg in "$@"; do
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
962
|
+
case "$arg" in
|
|
963
|
+
-p|--print|--version|-v|--help|-h)
|
|
964
|
+
return 0
|
|
965
|
+
;;
|
|
966
|
+
esac
|
|
857
967
|
done
|
|
858
968
|
return 1
|
|
859
969
|
}
|
|
860
970
|
|
|
971
|
+
|
|
861
972
|
# Main execution
|
|
862
973
|
main() {
|
|
863
974
|
# No argument parsing - pass everything through to Claude
|
|
@@ -986,12 +1097,30 @@ main() {
|
|
|
986
1097
|
debug "System context disabled (DCLAUDE_SYSTEM_CONTEXT=$ENABLE_SYSTEM_CONTEXT)"
|
|
987
1098
|
fi
|
|
988
1099
|
|
|
1100
|
+
# Final environment summary (all variables now resolved)
|
|
1101
|
+
if [[ "$DEBUG" == "true" ]]; then
|
|
1102
|
+
debug "Resolved environment:"
|
|
1103
|
+
debug " NAMESPACE=${NAMESPACE:-<not set>}"
|
|
1104
|
+
debug " NETWORK_MODE=$network_mode"
|
|
1105
|
+
debug " GIT_AUTH=$resolved_git_auth"
|
|
1106
|
+
debug " DOCKER_SOCKET=${DOCKER_SOCKET:-<not found>}"
|
|
1107
|
+
debug " CHROME_PORT=${CHROME_PORT:-9222}"
|
|
1108
|
+
debug " SYSTEM_CONTEXT=$ENABLE_SYSTEM_CONTEXT"
|
|
1109
|
+
debug " IMAGE=$IMAGE"
|
|
1110
|
+
debug " HOST_PATH=$HOST_PATH"
|
|
1111
|
+
debug " PLATFORM=$platform"
|
|
1112
|
+
fi
|
|
1113
|
+
|
|
989
1114
|
# Generate container name based on path (for reuse when DCLAUDE_RM=false)
|
|
990
1115
|
local container_name=""
|
|
991
1116
|
if [[ "$REMOVE_CONTAINER" == "false" ]]; then
|
|
992
1117
|
# Create deterministic name from path hash
|
|
993
1118
|
container_name=$(get_container_name "$HOST_PATH")
|
|
994
|
-
|
|
1119
|
+
if [[ -n "$NAMESPACE" ]]; then
|
|
1120
|
+
debug "Container name: $container_name (namespace: $NAMESPACE, path: $HOST_PATH)"
|
|
1121
|
+
else
|
|
1122
|
+
debug "Container name: $container_name (path: $HOST_PATH)"
|
|
1123
|
+
fi
|
|
995
1124
|
|
|
996
1125
|
# Check if container already exists
|
|
997
1126
|
if docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
|
|
@@ -1017,7 +1146,7 @@ main() {
|
|
|
1017
1146
|
fi
|
|
1018
1147
|
|
|
1019
1148
|
# Check if interactive (TTY available) and not in print mode
|
|
1020
|
-
if [[ -n "$tty_flags" ]] && !
|
|
1149
|
+
if [[ -n "$tty_flags" ]] && ! should_skip_tmux "${claude_args[@]}" "$@"; then
|
|
1021
1150
|
# Interactive and not print mode - use tmux for session management
|
|
1022
1151
|
local tmux_session
|
|
1023
1152
|
if [[ -n "${DCLAUDE_TMUX_SESSION:-}" ]]; then
|
|
@@ -1034,18 +1163,12 @@ main() {
|
|
|
1034
1163
|
[[ -n "${TERM_PROGRAM_VERSION:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
|
|
1035
1164
|
[[ -n "${TERM_SESSION_ID:-}" ]] && exec_env_args+=(-e "TERM_SESSION_ID=${TERM_SESSION_ID}")
|
|
1036
1165
|
[[ -n "${COLORTERM:-}" ]] && exec_env_args+=(-e "COLORTERM=${COLORTERM}")
|
|
1037
|
-
# Internal env vars for tmux status bar display
|
|
1038
|
-
exec_env_args+=(-e "_DCLAUDE_NET=${network_mode}")
|
|
1039
|
-
exec_env_args+=(-e "_DCLAUDE_TAG=${DCLAUDE_TAG:-latest}")
|
|
1040
|
-
exec_env_args+=(-e "_DCLAUDE_SESSION=${DCLAUDE_TMUX_SESSION:-auto}")
|
|
1041
1166
|
|
|
1042
1167
|
debug "Creating new tmux session running Claude"
|
|
1043
1168
|
debug "Claude args count: ${#claude_args[@]}, user args: $*"
|
|
1044
1169
|
info "Starting new Claude session..."
|
|
1045
1170
|
docker exec -it -u claude "${exec_env_args[@]}" "$container_name" tmux -f /home/claude/.tmux.conf new-session -s "$tmux_session" claude "${claude_args[@]}" "$@"
|
|
1046
|
-
|
|
1047
|
-
reset_terminal_mouse
|
|
1048
|
-
exit $tmux_exit
|
|
1171
|
+
exit $?
|
|
1049
1172
|
else
|
|
1050
1173
|
# Non-interactive or print mode - run claude directly without tmux
|
|
1051
1174
|
debug "Non-interactive or print mode, running Claude directly (no tmux)"
|
|
@@ -1072,11 +1195,15 @@ main() {
|
|
|
1072
1195
|
# Persistent containers run in background and shouldn't have TTY/stdin attached
|
|
1073
1196
|
|
|
1074
1197
|
|
|
1198
|
+
# Get namespaced volume name
|
|
1199
|
+
local claude_volume
|
|
1200
|
+
claude_volume=$(get_volume_name)
|
|
1201
|
+
|
|
1075
1202
|
DOCKER_ARGS+=(
|
|
1076
1203
|
# Mount current directory
|
|
1077
1204
|
-v "${HOST_PATH}:${HOST_PATH}"
|
|
1078
1205
|
# Mount persistent Claude configuration volume
|
|
1079
|
-
-v "${
|
|
1206
|
+
-v "${claude_volume}:/home/claude/.claude"
|
|
1080
1207
|
# Set working directory
|
|
1081
1208
|
-w "${HOST_PATH}"
|
|
1082
1209
|
# Network mode
|
|
@@ -1166,7 +1293,7 @@ main() {
|
|
|
1166
1293
|
docker exec -u root "$container_name" /usr/local/bin/docker-entrypoint.sh true >/dev/null 2>&1 || true
|
|
1167
1294
|
|
|
1168
1295
|
# Check if interactive (TTY available) and not in print mode
|
|
1169
|
-
if [[ -n "$tty_flags" ]] && !
|
|
1296
|
+
if [[ -n "$tty_flags" ]] && ! should_skip_tmux "${claude_args[@]}" "$@"; then
|
|
1170
1297
|
# Interactive and not print mode - use tmux for session management
|
|
1171
1298
|
local tmux_session
|
|
1172
1299
|
if [[ -n "${DCLAUDE_TMUX_SESSION:-}" ]]; then
|
|
@@ -1183,18 +1310,12 @@ main() {
|
|
|
1183
1310
|
[[ -n "${TERM_PROGRAM_VERSION:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
|
|
1184
1311
|
[[ -n "${TERM_SESSION_ID:-}" ]] && exec_env_args+=(-e "TERM_SESSION_ID=${TERM_SESSION_ID}")
|
|
1185
1312
|
[[ -n "${COLORTERM:-}" ]] && exec_env_args+=(-e "COLORTERM=${COLORTERM}")
|
|
1186
|
-
# Internal env vars for tmux status bar display
|
|
1187
|
-
exec_env_args+=(-e "_DCLAUDE_NET=${network_mode}")
|
|
1188
|
-
exec_env_args+=(-e "_DCLAUDE_TAG=${DCLAUDE_TAG:-latest}")
|
|
1189
|
-
exec_env_args+=(-e "_DCLAUDE_SESSION=${DCLAUDE_TMUX_SESSION:-auto}")
|
|
1190
1313
|
|
|
1191
1314
|
debug "Creating new tmux session running Claude"
|
|
1192
1315
|
debug "Claude args count: ${#claude_args[@]}, user args: $*"
|
|
1193
1316
|
info "Starting new Claude session..."
|
|
1194
1317
|
docker exec -it -u claude "${exec_env_args[@]}" "$container_name" tmux -f /home/claude/.tmux.conf new-session -s "$tmux_session" claude "${claude_args[@]}" "$@"
|
|
1195
|
-
|
|
1196
|
-
reset_terminal_mouse
|
|
1197
|
-
exit $tmux_exit
|
|
1318
|
+
exit $?
|
|
1198
1319
|
else
|
|
1199
1320
|
# Non-interactive or print mode - run claude directly without tmux
|
|
1200
1321
|
debug "Non-interactive or print mode, running Claude directly (no tmux)"
|
|
@@ -1341,9 +1462,7 @@ cmd_attach() {
|
|
|
1341
1462
|
# Attach to existing session
|
|
1342
1463
|
info "Attaching to session: $session_name"
|
|
1343
1464
|
docker exec -it -u claude "${exec_env_args[@]}" "$container_name" tmux -f /home/claude/.tmux.conf attach-session -t "$session_name"
|
|
1344
|
-
|
|
1345
|
-
reset_terminal_mouse
|
|
1346
|
-
exit $tmux_exit
|
|
1465
|
+
exit $?
|
|
1347
1466
|
}
|
|
1348
1467
|
|
|
1349
1468
|
# Subcommand: launch Chrome with DevTools and ensure MCP configured
|
|
@@ -1532,16 +1651,16 @@ cmd_update() {
|
|
|
1532
1651
|
fi
|
|
1533
1652
|
|
|
1534
1653
|
info "Updating Claude CLI..."
|
|
1535
|
-
local
|
|
1536
|
-
if
|
|
1654
|
+
local update_output
|
|
1655
|
+
if update_output=$(docker exec -u claude "$container_name" claude update 2>&1); then
|
|
1537
1656
|
# Show command and output together, indented to align with "Debug: " prefix
|
|
1538
|
-
debug "
|
|
1539
|
-
$(echo "$
|
|
1657
|
+
debug "claude update
|
|
1658
|
+
$(echo "$update_output" | sed 's/^/ /')"
|
|
1540
1659
|
local new_version=$(docker exec -u claude "$container_name" claude --version 2>/dev/null | head -1)
|
|
1541
1660
|
success "Claude CLI updated: $new_version"
|
|
1542
1661
|
else
|
|
1543
|
-
debug "
|
|
1544
|
-
$(echo "$
|
|
1662
|
+
debug "claude update
|
|
1663
|
+
$(echo "$update_output" | sed 's/^/ /')"
|
|
1545
1664
|
error "Failed to update Claude CLI"
|
|
1546
1665
|
exit 1
|
|
1547
1666
|
fi
|
|
@@ -1945,6 +2064,7 @@ Environment Variables:
|
|
|
1945
2064
|
DCLAUDE_TAG Docker image tag (default: latest)
|
|
1946
2065
|
DCLAUDE_RM Remove container on exit (default: false)
|
|
1947
2066
|
DCLAUDE_DEBUG Enable debug output (default: false)
|
|
2067
|
+
DCLAUDE_NAMESPACE Namespace for isolated credentials/config
|
|
1948
2068
|
DCLAUDE_GIT_AUTH SSH auth for Git: auto, agent-forwarding, key-mount, none
|
|
1949
2069
|
DCLAUDE_NETWORK Network mode: auto, host, bridge
|
|
1950
2070
|
DCLAUDE_DOCKER_SOCKET Override Docker socket path
|
|
@@ -1954,6 +2074,14 @@ Environment Variables:
|
|
|
1954
2074
|
DCLAUDE_CHROME_PORT Chrome debugging port (default: 9222)
|
|
1955
2075
|
DCLAUDE_CHROME_FLAGS Additional Chrome flags (default: empty)
|
|
1956
2076
|
|
|
2077
|
+
Configuration File (.dclaude):
|
|
2078
|
+
Create a .dclaude file at project root to configure dclaude for that tree:
|
|
2079
|
+
NAMESPACE=mycompany
|
|
2080
|
+
NETWORK=host
|
|
2081
|
+
DEBUG=true
|
|
2082
|
+
dclaude walks up the directory tree to find .dclaude files.
|
|
2083
|
+
Environment variables override .dclaude settings.
|
|
2084
|
+
|
|
1957
2085
|
Examples:
|
|
1958
2086
|
# Start new Claude session (auto-generated session name)
|
|
1959
2087
|
dclaude
|
|
@@ -1970,6 +2098,10 @@ Examples:
|
|
|
1970
2098
|
# Start with ephemeral container (removed on exit)
|
|
1971
2099
|
DCLAUDE_RM=true dclaude
|
|
1972
2100
|
|
|
2101
|
+
# Use namespace for isolated credentials (personal vs company)
|
|
2102
|
+
DCLAUDE_NAMESPACE=mycompany dclaude
|
|
2103
|
+
# Or create .dclaude file: echo "NAMESPACE=mycompany" > ~/projects/mycompany/.dclaude
|
|
2104
|
+
|
|
1973
2105
|
# Update image and restart container
|
|
1974
2106
|
dclaude pull # Pull latest image
|
|
1975
2107
|
dclaude update # Update Claude CLI in container
|