@alanbem/dclaude 0.0.8 → 0.0.10
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 +178 -48
- 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"
|
|
@@ -858,6 +966,7 @@ is_print_mode() {
|
|
|
858
966
|
return 1
|
|
859
967
|
}
|
|
860
968
|
|
|
969
|
+
|
|
861
970
|
# Main execution
|
|
862
971
|
main() {
|
|
863
972
|
# No argument parsing - pass everything through to Claude
|
|
@@ -986,12 +1095,30 @@ main() {
|
|
|
986
1095
|
debug "System context disabled (DCLAUDE_SYSTEM_CONTEXT=$ENABLE_SYSTEM_CONTEXT)"
|
|
987
1096
|
fi
|
|
988
1097
|
|
|
1098
|
+
# Final environment summary (all variables now resolved)
|
|
1099
|
+
if [[ "$DEBUG" == "true" ]]; then
|
|
1100
|
+
debug "Resolved environment:"
|
|
1101
|
+
debug " NAMESPACE=${NAMESPACE:-<not set>}"
|
|
1102
|
+
debug " NETWORK_MODE=$network_mode"
|
|
1103
|
+
debug " GIT_AUTH=$resolved_git_auth"
|
|
1104
|
+
debug " DOCKER_SOCKET=${DOCKER_SOCKET:-<not found>}"
|
|
1105
|
+
debug " CHROME_PORT=${CHROME_PORT:-9222}"
|
|
1106
|
+
debug " SYSTEM_CONTEXT=$ENABLE_SYSTEM_CONTEXT"
|
|
1107
|
+
debug " IMAGE=$IMAGE"
|
|
1108
|
+
debug " HOST_PATH=$HOST_PATH"
|
|
1109
|
+
debug " PLATFORM=$platform"
|
|
1110
|
+
fi
|
|
1111
|
+
|
|
989
1112
|
# Generate container name based on path (for reuse when DCLAUDE_RM=false)
|
|
990
1113
|
local container_name=""
|
|
991
1114
|
if [[ "$REMOVE_CONTAINER" == "false" ]]; then
|
|
992
1115
|
# Create deterministic name from path hash
|
|
993
1116
|
container_name=$(get_container_name "$HOST_PATH")
|
|
994
|
-
|
|
1117
|
+
if [[ -n "$NAMESPACE" ]]; then
|
|
1118
|
+
debug "Container name: $container_name (namespace: $NAMESPACE, path: $HOST_PATH)"
|
|
1119
|
+
else
|
|
1120
|
+
debug "Container name: $container_name (path: $HOST_PATH)"
|
|
1121
|
+
fi
|
|
995
1122
|
|
|
996
1123
|
# Check if container already exists
|
|
997
1124
|
if docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
|
|
@@ -1034,18 +1161,12 @@ main() {
|
|
|
1034
1161
|
[[ -n "${TERM_PROGRAM_VERSION:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
|
|
1035
1162
|
[[ -n "${TERM_SESSION_ID:-}" ]] && exec_env_args+=(-e "TERM_SESSION_ID=${TERM_SESSION_ID}")
|
|
1036
1163
|
[[ -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
1164
|
|
|
1042
1165
|
debug "Creating new tmux session running Claude"
|
|
1043
1166
|
debug "Claude args count: ${#claude_args[@]}, user args: $*"
|
|
1044
1167
|
info "Starting new Claude session..."
|
|
1045
1168
|
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
|
|
1169
|
+
exit $?
|
|
1049
1170
|
else
|
|
1050
1171
|
# Non-interactive or print mode - run claude directly without tmux
|
|
1051
1172
|
debug "Non-interactive or print mode, running Claude directly (no tmux)"
|
|
@@ -1072,11 +1193,15 @@ main() {
|
|
|
1072
1193
|
# Persistent containers run in background and shouldn't have TTY/stdin attached
|
|
1073
1194
|
|
|
1074
1195
|
|
|
1196
|
+
# Get namespaced volume name
|
|
1197
|
+
local claude_volume
|
|
1198
|
+
claude_volume=$(get_volume_name)
|
|
1199
|
+
|
|
1075
1200
|
DOCKER_ARGS+=(
|
|
1076
1201
|
# Mount current directory
|
|
1077
1202
|
-v "${HOST_PATH}:${HOST_PATH}"
|
|
1078
1203
|
# Mount persistent Claude configuration volume
|
|
1079
|
-
-v "${
|
|
1204
|
+
-v "${claude_volume}:/home/claude/.claude"
|
|
1080
1205
|
# Set working directory
|
|
1081
1206
|
-w "${HOST_PATH}"
|
|
1082
1207
|
# Network mode
|
|
@@ -1183,18 +1308,12 @@ main() {
|
|
|
1183
1308
|
[[ -n "${TERM_PROGRAM_VERSION:-}" ]] && exec_env_args+=(-e "TERM_PROGRAM_VERSION=${TERM_PROGRAM_VERSION}")
|
|
1184
1309
|
[[ -n "${TERM_SESSION_ID:-}" ]] && exec_env_args+=(-e "TERM_SESSION_ID=${TERM_SESSION_ID}")
|
|
1185
1310
|
[[ -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
1311
|
|
|
1191
1312
|
debug "Creating new tmux session running Claude"
|
|
1192
1313
|
debug "Claude args count: ${#claude_args[@]}, user args: $*"
|
|
1193
1314
|
info "Starting new Claude session..."
|
|
1194
1315
|
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
|
|
1316
|
+
exit $?
|
|
1198
1317
|
else
|
|
1199
1318
|
# Non-interactive or print mode - run claude directly without tmux
|
|
1200
1319
|
debug "Non-interactive or print mode, running Claude directly (no tmux)"
|
|
@@ -1341,9 +1460,7 @@ cmd_attach() {
|
|
|
1341
1460
|
# Attach to existing session
|
|
1342
1461
|
info "Attaching to session: $session_name"
|
|
1343
1462
|
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
|
|
1463
|
+
exit $?
|
|
1347
1464
|
}
|
|
1348
1465
|
|
|
1349
1466
|
# Subcommand: launch Chrome with DevTools and ensure MCP configured
|
|
@@ -1532,16 +1649,16 @@ cmd_update() {
|
|
|
1532
1649
|
fi
|
|
1533
1650
|
|
|
1534
1651
|
info "Updating Claude CLI..."
|
|
1535
|
-
local
|
|
1536
|
-
if
|
|
1652
|
+
local update_output
|
|
1653
|
+
if update_output=$(docker exec -u claude "$container_name" claude update 2>&1); then
|
|
1537
1654
|
# Show command and output together, indented to align with "Debug: " prefix
|
|
1538
|
-
debug "
|
|
1539
|
-
$(echo "$
|
|
1655
|
+
debug "claude update
|
|
1656
|
+
$(echo "$update_output" | sed 's/^/ /')"
|
|
1540
1657
|
local new_version=$(docker exec -u claude "$container_name" claude --version 2>/dev/null | head -1)
|
|
1541
1658
|
success "Claude CLI updated: $new_version"
|
|
1542
1659
|
else
|
|
1543
|
-
debug "
|
|
1544
|
-
$(echo "$
|
|
1660
|
+
debug "claude update
|
|
1661
|
+
$(echo "$update_output" | sed 's/^/ /')"
|
|
1545
1662
|
error "Failed to update Claude CLI"
|
|
1546
1663
|
exit 1
|
|
1547
1664
|
fi
|
|
@@ -1945,6 +2062,7 @@ Environment Variables:
|
|
|
1945
2062
|
DCLAUDE_TAG Docker image tag (default: latest)
|
|
1946
2063
|
DCLAUDE_RM Remove container on exit (default: false)
|
|
1947
2064
|
DCLAUDE_DEBUG Enable debug output (default: false)
|
|
2065
|
+
DCLAUDE_NAMESPACE Namespace for isolated credentials/config
|
|
1948
2066
|
DCLAUDE_GIT_AUTH SSH auth for Git: auto, agent-forwarding, key-mount, none
|
|
1949
2067
|
DCLAUDE_NETWORK Network mode: auto, host, bridge
|
|
1950
2068
|
DCLAUDE_DOCKER_SOCKET Override Docker socket path
|
|
@@ -1954,6 +2072,14 @@ Environment Variables:
|
|
|
1954
2072
|
DCLAUDE_CHROME_PORT Chrome debugging port (default: 9222)
|
|
1955
2073
|
DCLAUDE_CHROME_FLAGS Additional Chrome flags (default: empty)
|
|
1956
2074
|
|
|
2075
|
+
Configuration File (.dclaude):
|
|
2076
|
+
Create a .dclaude file at project root to configure dclaude for that tree:
|
|
2077
|
+
NAMESPACE=mycompany
|
|
2078
|
+
NETWORK=host
|
|
2079
|
+
DEBUG=true
|
|
2080
|
+
dclaude walks up the directory tree to find .dclaude files.
|
|
2081
|
+
Environment variables override .dclaude settings.
|
|
2082
|
+
|
|
1957
2083
|
Examples:
|
|
1958
2084
|
# Start new Claude session (auto-generated session name)
|
|
1959
2085
|
dclaude
|
|
@@ -1970,6 +2096,10 @@ Examples:
|
|
|
1970
2096
|
# Start with ephemeral container (removed on exit)
|
|
1971
2097
|
DCLAUDE_RM=true dclaude
|
|
1972
2098
|
|
|
2099
|
+
# Use namespace for isolated credentials (personal vs company)
|
|
2100
|
+
DCLAUDE_NAMESPACE=mycompany dclaude
|
|
2101
|
+
# Or create .dclaude file: echo "NAMESPACE=mycompany" > ~/projects/mycompany/.dclaude
|
|
2102
|
+
|
|
1973
2103
|
# Update image and restart container
|
|
1974
2104
|
dclaude pull # Pull latest image
|
|
1975
2105
|
dclaude update # Update Claude CLI in container
|