@alanbem/dclaude 0.0.12 → 0.0.14
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 +36 -3
- package/dclaude +885 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -132,12 +132,15 @@ dclaude gh # Interactive GitHub login
|
|
|
132
132
|
dclaude exec gh pr list # Use gh commands
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
### SSH Server
|
|
135
|
+
### SSH Key and Server Management
|
|
136
136
|
|
|
137
|
-
|
|
137
|
+
Load SSH keys and start SSH server for JetBrains Gateway, VS Code Remote, or any SSH client:
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
|
-
dclaude ssh #
|
|
140
|
+
dclaude ssh # Load keys + start SSH server
|
|
141
|
+
dclaude ssh keys # Load SSH keys into agent
|
|
142
|
+
dclaude ssh server # Start SSH server, shows port
|
|
143
|
+
dclaude ssh server --stop # Stop SSH server
|
|
141
144
|
# Connect: ssh claude@localhost -p <port>
|
|
142
145
|
# Password: claude
|
|
143
146
|
```
|
|
@@ -151,6 +154,32 @@ dclaude chrome # Launch Chrome with DevTools
|
|
|
151
154
|
dclaude # Claude can now interact with the browser
|
|
152
155
|
```
|
|
153
156
|
|
|
157
|
+
### AWS CLI Integration
|
|
158
|
+
|
|
159
|
+
AWS CLI v2 is pre-installed in the container. Configure how AWS credentials are provided:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
# Auto (default): mounts ~/.aws from host if it exists, otherwise no config
|
|
163
|
+
dclaude
|
|
164
|
+
|
|
165
|
+
# Mount host's ~/.aws directory (read-write, shared with host)
|
|
166
|
+
DCLAUDE_AWS_CLI=mount dclaude
|
|
167
|
+
|
|
168
|
+
# Use isolated Docker volume (persists across container recreations)
|
|
169
|
+
DCLAUDE_AWS_CLI=volume dclaude
|
|
170
|
+
|
|
171
|
+
# No AWS config mounting
|
|
172
|
+
DCLAUDE_AWS_CLI=none dclaude
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Volume mode** provides isolated AWS config per namespace:
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
dclaude aws configure # Copy ~/.aws/config (profiles, regions) into container
|
|
179
|
+
dclaude aws login # Run 'aws login' in container
|
|
180
|
+
dclaude aws login --profile staging # Login with specific profile
|
|
181
|
+
```
|
|
182
|
+
|
|
154
183
|
### iTerm2 Shell Integration
|
|
155
184
|
|
|
156
185
|
If you use iTerm2 on macOS, dclaude automatically enables [iTerm2 Shell Integration](https://iterm2.com/documentation-shell-integration.html):
|
|
@@ -173,6 +202,7 @@ dclaude automatically tells Claude about its container environment so it can giv
|
|
|
173
202
|
- **Network mode** - Whether `localhost` works or needs `host.docker.internal`
|
|
174
203
|
- **Docker access** - Whether Docker commands are available
|
|
175
204
|
- **SSH auth method** - How git authentication is configured
|
|
205
|
+
- **AWS CLI** - Whether and how AWS credentials are configured
|
|
176
206
|
- **Path mirroring** - That file paths match the host
|
|
177
207
|
|
|
178
208
|
This helps Claude understand its environment without you explaining it. Disable if needed:
|
|
@@ -195,6 +225,7 @@ DCLAUDE_SYSTEM_CONTEXT=false dclaude
|
|
|
195
225
|
| `DCLAUDE_QUIET` | `false` | Suppress info messages |
|
|
196
226
|
| `DCLAUDE_NO_UPDATE` | `false` | Skip image update check |
|
|
197
227
|
| `DCLAUDE_SYSTEM_CONTEXT` | `true` | Inform Claude about container environment |
|
|
228
|
+
| `DCLAUDE_AWS_CLI` | `auto` | AWS config: `auto`, `mount`, `volume`, `none` |
|
|
198
229
|
| `DCLAUDE_ITERM2` | `true` | Enable iTerm2 shell integration (only affects iTerm2) |
|
|
199
230
|
|
|
200
231
|
## Configuration File
|
|
@@ -220,6 +251,7 @@ dclaude walks up the directory tree to find `.dclaude` files. Any dclaude sessio
|
|
|
220
251
|
| `MOUNT_ROOT` | Mount directory (relative to config file, or absolute path) |
|
|
221
252
|
| `DEBUG` | Enable debug output (`true`, `false`) |
|
|
222
253
|
| `CHROME_PORT` | Chrome DevTools port |
|
|
254
|
+
| `AWS_CLI` | AWS config mode (`mount`, `volume`, `none`) |
|
|
223
255
|
|
|
224
256
|
**Precedence:** Environment variables override `.dclaude` file settings.
|
|
225
257
|
|
|
@@ -327,6 +359,7 @@ The container includes:
|
|
|
327
359
|
- **Docker CLI** and **Docker Compose**
|
|
328
360
|
- **Git**, **GitHub CLI** (`gh`), common dev tools
|
|
329
361
|
- **tmux** for session management
|
|
362
|
+
- **AWS CLI v2** for cloud operations
|
|
330
363
|
- **SSH server** for IDE integration
|
|
331
364
|
|
|
332
365
|
## Troubleshooting
|
package/dclaude
CHANGED
|
@@ -135,6 +135,9 @@ load_config_file() {
|
|
|
135
135
|
fi
|
|
136
136
|
fi
|
|
137
137
|
;;
|
|
138
|
+
AWS_CLI)
|
|
139
|
+
[[ -z "${DCLAUDE_AWS_CLI:-}" ]] && DCLAUDE_AWS_CLI="$value"
|
|
140
|
+
;;
|
|
138
141
|
esac
|
|
139
142
|
fi
|
|
140
143
|
done < "$config_file"
|
|
@@ -151,6 +154,7 @@ readonly DEBUG="${DCLAUDE_DEBUG:-false}"
|
|
|
151
154
|
readonly GIT_AUTH_MODE="${DCLAUDE_GIT_AUTH:-auto}" # auto, agent-forwarding, key-mount, none
|
|
152
155
|
readonly NAMESPACE="${DCLAUDE_NAMESPACE:-}"
|
|
153
156
|
CHROME_PORT="${DCLAUDE_CHROME_PORT:-9222}"
|
|
157
|
+
readonly AWS_CLI_MODE="${DCLAUDE_AWS_CLI:-auto}" # auto, mount, volume, none
|
|
154
158
|
|
|
155
159
|
# Show config file if loaded (always at info level, details at debug)
|
|
156
160
|
if [[ -n "$DCLAUDE_CONFIG_FILE" ]]; then
|
|
@@ -161,6 +165,7 @@ if [[ -n "$DCLAUDE_CONFIG_FILE" ]]; then
|
|
|
161
165
|
[[ -n "${DCLAUDE_GIT_AUTH:-}" ]] && debug " GIT_AUTH=$DCLAUDE_GIT_AUTH"
|
|
162
166
|
[[ -n "${DCLAUDE_CHROME_PORT:-}" ]] && debug " CHROME_PORT=$DCLAUDE_CHROME_PORT"
|
|
163
167
|
[[ -n "${DCLAUDE_MOUNT_ROOT:-}" ]] && debug " MOUNT_ROOT=$DCLAUDE_MOUNT_ROOT"
|
|
168
|
+
[[ -n "${DCLAUDE_AWS_CLI:-}" ]] && debug " AWS_CLI=$DCLAUDE_AWS_CLI"
|
|
164
169
|
fi
|
|
165
170
|
fi
|
|
166
171
|
|
|
@@ -510,6 +515,36 @@ get_volume_name() {
|
|
|
510
515
|
fi
|
|
511
516
|
}
|
|
512
517
|
|
|
518
|
+
# Get AWS volume name (includes namespace if set)
|
|
519
|
+
get_aws_volume_name() {
|
|
520
|
+
if [[ -n "$NAMESPACE" ]]; then
|
|
521
|
+
echo "${VOLUME_PREFIX}-${NAMESPACE}-aws"
|
|
522
|
+
else
|
|
523
|
+
echo "${VOLUME_PREFIX}-aws"
|
|
524
|
+
fi
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
# Resolve AWS CLI mode from configured value
|
|
528
|
+
# auto → mount (if ~/.aws exists) or none
|
|
529
|
+
resolve_aws_cli_mode() {
|
|
530
|
+
case "$AWS_CLI_MODE" in
|
|
531
|
+
mount|volume|none)
|
|
532
|
+
echo "$AWS_CLI_MODE"
|
|
533
|
+
;;
|
|
534
|
+
auto)
|
|
535
|
+
if [[ -d "${HOME}/.aws" ]]; then
|
|
536
|
+
echo "mount"
|
|
537
|
+
else
|
|
538
|
+
echo "none"
|
|
539
|
+
fi
|
|
540
|
+
;;
|
|
541
|
+
*)
|
|
542
|
+
warning "Unknown AWS_CLI mode: $AWS_CLI_MODE (using none)"
|
|
543
|
+
echo "none"
|
|
544
|
+
;;
|
|
545
|
+
esac
|
|
546
|
+
}
|
|
547
|
+
|
|
513
548
|
# Create Docker volumes if they don't exist
|
|
514
549
|
create_volumes() {
|
|
515
550
|
# Create essential volume for Claude CLI persistence
|
|
@@ -526,6 +561,24 @@ create_volumes() {
|
|
|
526
561
|
else
|
|
527
562
|
debug "Volume exists: $volume"
|
|
528
563
|
fi
|
|
564
|
+
|
|
565
|
+
# Create AWS volume if volume mode is active
|
|
566
|
+
local aws_mode
|
|
567
|
+
aws_mode=$(resolve_aws_cli_mode)
|
|
568
|
+
if [[ "$aws_mode" == "volume" ]]; then
|
|
569
|
+
local aws_volume
|
|
570
|
+
aws_volume=$(get_aws_volume_name)
|
|
571
|
+
if ! docker volume inspect "$aws_volume" &> /dev/null; then
|
|
572
|
+
info "Creating AWS volume: $aws_volume"
|
|
573
|
+
if ! docker volume create "$aws_volume" > /dev/null; then
|
|
574
|
+
error "Failed to create volume: $aws_volume"
|
|
575
|
+
exit 1
|
|
576
|
+
fi
|
|
577
|
+
debug "Volume created successfully: $aws_volume"
|
|
578
|
+
else
|
|
579
|
+
debug "Volume exists: $aws_volume"
|
|
580
|
+
fi
|
|
581
|
+
fi
|
|
529
582
|
}
|
|
530
583
|
|
|
531
584
|
# Pull or update the Docker image
|
|
@@ -660,6 +713,7 @@ generate_system_context() {
|
|
|
660
713
|
local has_docker="${3:-false}"
|
|
661
714
|
local platform="${4:-unknown}"
|
|
662
715
|
local docker_socket="${5:-}"
|
|
716
|
+
local aws_cli_mode="${6:-none}"
|
|
663
717
|
|
|
664
718
|
cat <<'EOF'
|
|
665
719
|
|
|
@@ -806,8 +860,36 @@ EOF
|
|
|
806
860
|
## Development Tools Available
|
|
807
861
|
- **Languages**: Node.js 20+, Python 3
|
|
808
862
|
- **Package Managers**: npm, pip, Homebrew/Linuxbrew
|
|
809
|
-
- **Tools**: git, gh (GitHub CLI), docker, docker-compose, curl, tmux, nano
|
|
863
|
+
- **Tools**: git, gh (GitHub CLI), docker, docker-compose, aws (AWS CLI v2), curl, tmux, nano
|
|
810
864
|
- **Shell**: bash (your commands execute in bash shell)
|
|
865
|
+
EOF
|
|
866
|
+
|
|
867
|
+
# AWS CLI context
|
|
868
|
+
case "$aws_cli_mode" in
|
|
869
|
+
mount)
|
|
870
|
+
cat <<'EOF'
|
|
871
|
+
- **AWS CLI**: Configured — credentials mounted from host's `~/.aws` directory
|
|
872
|
+
- All host AWS profiles, SSO config, and credentials are available
|
|
873
|
+
- Changes to AWS config (e.g., `aws sso login`) are shared with host
|
|
874
|
+
EOF
|
|
875
|
+
;;
|
|
876
|
+
volume)
|
|
877
|
+
cat <<'EOF'
|
|
878
|
+
- **AWS CLI**: Configured — credentials stored in persistent Docker volume
|
|
879
|
+
- AWS config is isolated from host and persists across container recreations
|
|
880
|
+
- Run `aws configure` or `aws configure sso` to set up credentials
|
|
881
|
+
EOF
|
|
882
|
+
;;
|
|
883
|
+
*)
|
|
884
|
+
cat <<'EOF'
|
|
885
|
+
- **AWS CLI**: Installed but no credentials configured
|
|
886
|
+
- User can set `DCLAUDE_AWS_CLI=mount` when launching dclaude to use host's `~/.aws` config
|
|
887
|
+
- User can set `DCLAUDE_AWS_CLI=volume` when launching dclaude for isolated persistent config
|
|
888
|
+
EOF
|
|
889
|
+
;;
|
|
890
|
+
esac
|
|
891
|
+
|
|
892
|
+
cat <<'EOF'
|
|
811
893
|
|
|
812
894
|
## Git Configuration Requirements
|
|
813
895
|
**IMPORTANT**: Before performing any git operations (commit, push, etc.), you MUST:
|
|
@@ -987,6 +1069,423 @@ handle_git_auth() {
|
|
|
987
1069
|
fi
|
|
988
1070
|
}
|
|
989
1071
|
|
|
1072
|
+
# ============================================================================
|
|
1073
|
+
# Agent Teams Tmux Passthrough — Relay Functions
|
|
1074
|
+
# ============================================================================
|
|
1075
|
+
# When the user runs dclaude from inside a host tmux session, these functions
|
|
1076
|
+
# set up a TCP relay that forwards Claude Code's tmux commands to the host.
|
|
1077
|
+
# This makes Agent Teams sub-agent panes visible in the host's tmux session.
|
|
1078
|
+
|
|
1079
|
+
# Check if user is in a host tmux session
|
|
1080
|
+
detect_host_tmux() {
|
|
1081
|
+
[[ -n "${TMUX:-}" ]]
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
# Check if host has required tools for relay (jq + socat or ncat)
|
|
1085
|
+
check_relay_deps() {
|
|
1086
|
+
if ! command -v jq &>/dev/null; then
|
|
1087
|
+
debug "Relay dep missing: jq"
|
|
1088
|
+
return 1
|
|
1089
|
+
fi
|
|
1090
|
+
if command -v socat &>/dev/null; then
|
|
1091
|
+
debug "Relay listener: socat"
|
|
1092
|
+
return 0
|
|
1093
|
+
fi
|
|
1094
|
+
if command -v ncat &>/dev/null; then
|
|
1095
|
+
debug "Relay listener: ncat"
|
|
1096
|
+
return 0
|
|
1097
|
+
fi
|
|
1098
|
+
debug "Relay dep missing: socat or ncat"
|
|
1099
|
+
return 1
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
# Get the address the container uses to reach the host relay
|
|
1103
|
+
get_relay_host() {
|
|
1104
|
+
local platform="$1"
|
|
1105
|
+
local network_mode="$2"
|
|
1106
|
+
# Linux host mode: container shares host network, use loopback
|
|
1107
|
+
if [[ "$platform" == "linux" && "$network_mode" == "host" ]]; then
|
|
1108
|
+
echo "127.0.0.1"
|
|
1109
|
+
else
|
|
1110
|
+
# macOS (any mode) or Linux bridge: use Docker's host gateway
|
|
1111
|
+
echo "host.docker.internal"
|
|
1112
|
+
fi
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
# Get the address the relay binds to on the host
|
|
1116
|
+
get_relay_bind_addr() {
|
|
1117
|
+
local platform="$1"
|
|
1118
|
+
local network_mode="$2"
|
|
1119
|
+
# Linux bridge: container reaches host via bridge gateway, not loopback
|
|
1120
|
+
if [[ "$platform" == "linux" && "$network_mode" == "bridge" ]]; then
|
|
1121
|
+
echo "0.0.0.0"
|
|
1122
|
+
else
|
|
1123
|
+
echo "127.0.0.1"
|
|
1124
|
+
fi
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
# Get or create relay nonce for a container (reused across reattachments)
|
|
1128
|
+
get_or_create_relay_nonce() {
|
|
1129
|
+
local container_name="$1"
|
|
1130
|
+
local nonce_file="$HOME/.dclaude/tmux-relay-nonce-${container_name}"
|
|
1131
|
+
|
|
1132
|
+
mkdir -p "$HOME/.dclaude"
|
|
1133
|
+
|
|
1134
|
+
if [[ -f "$nonce_file" ]]; then
|
|
1135
|
+
cat "$nonce_file"
|
|
1136
|
+
return 0
|
|
1137
|
+
fi
|
|
1138
|
+
|
|
1139
|
+
local nonce
|
|
1140
|
+
nonce=$(head -c 32 /dev/urandom | base64 | tr -d '=/+' | head -c 32)
|
|
1141
|
+
echo "$nonce" > "$nonce_file"
|
|
1142
|
+
echo "$nonce"
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
# Generate the relay handler script (executed by socat/ncat for each connection)
|
|
1146
|
+
# Uses a non-quoted heredoc for baked-in values, then a single-quoted heredoc for logic
|
|
1147
|
+
generate_relay_handler() {
|
|
1148
|
+
local container_name="$1"
|
|
1149
|
+
local relay_port="$2"
|
|
1150
|
+
local nonce="$3"
|
|
1151
|
+
local handler_path="$4"
|
|
1152
|
+
local tracking_file="$5"
|
|
1153
|
+
local relay_host="$6"
|
|
1154
|
+
|
|
1155
|
+
# Header with baked-in values (variable expansion)
|
|
1156
|
+
cat > "$handler_path" <<HANDLER_HEADER
|
|
1157
|
+
#!/bin/bash
|
|
1158
|
+
CONTAINER="$container_name"
|
|
1159
|
+
NONCE="$nonce"
|
|
1160
|
+
RELAY_PORT="$relay_port"
|
|
1161
|
+
RELAY_HOST="$relay_host"
|
|
1162
|
+
TRACKING_FILE="$tracking_file"
|
|
1163
|
+
HANDLER_HEADER
|
|
1164
|
+
|
|
1165
|
+
# Logic body (single-quoted heredoc — no escaping needed)
|
|
1166
|
+
cat >> "$handler_path" <<'HANDLER_BODY'
|
|
1167
|
+
|
|
1168
|
+
# Read one JSON line (max 65536 bytes to prevent memory exhaustion)
|
|
1169
|
+
read -r -n 65536 request
|
|
1170
|
+
if [[ -z "$request" ]]; then
|
|
1171
|
+
printf '{"code":1,"stdout":"","stderr":"empty request"}\n'
|
|
1172
|
+
exit 1
|
|
1173
|
+
fi
|
|
1174
|
+
|
|
1175
|
+
# Validate nonce
|
|
1176
|
+
req_nonce=$(printf '%s' "$request" | jq -r '.nonce // empty')
|
|
1177
|
+
if [[ "$req_nonce" != "$NONCE" ]]; then
|
|
1178
|
+
printf '{"code":1,"stdout":"","stderr":"authentication failed"}\n'
|
|
1179
|
+
exit 1
|
|
1180
|
+
fi
|
|
1181
|
+
|
|
1182
|
+
# Extract command array (bash 3.2 compatible — no mapfile)
|
|
1183
|
+
cmd=()
|
|
1184
|
+
while IFS= read -r line; do
|
|
1185
|
+
cmd+=("$line")
|
|
1186
|
+
done < <(printf '%s' "$request" | jq -r '.cmd[]')
|
|
1187
|
+
subcmd="${cmd[0]}"
|
|
1188
|
+
cwd=$(printf '%s' "$request" | jq -r '.cwd // empty')
|
|
1189
|
+
req_pane=$(printf '%s' "$request" | jq -r '.pane // empty')
|
|
1190
|
+
|
|
1191
|
+
# Allowlist tmux subcommands
|
|
1192
|
+
case "$subcmd" in
|
|
1193
|
+
split-window|send-keys|list-panes|select-layout|resize-pane|\
|
|
1194
|
+
select-pane|kill-pane|has-session|list-windows|display-message|list-sessions)
|
|
1195
|
+
;;
|
|
1196
|
+
*)
|
|
1197
|
+
printf '{"code":1,"stdout":"","stderr":"disallowed: %s"}\n' "$subcmd"
|
|
1198
|
+
exit 1
|
|
1199
|
+
;;
|
|
1200
|
+
esac
|
|
1201
|
+
|
|
1202
|
+
# Build tmux args based on subcommand
|
|
1203
|
+
args=()
|
|
1204
|
+
|
|
1205
|
+
if [[ "$subcmd" == "split-window" ]]; then
|
|
1206
|
+
args+=("split-window")
|
|
1207
|
+
|
|
1208
|
+
# Parse split-window flags, strip -c (handled by docker exec -w)
|
|
1209
|
+
# and strip trailing shell command (replaced by docker exec)
|
|
1210
|
+
i=1
|
|
1211
|
+
while (( i < ${#cmd[@]} )); do
|
|
1212
|
+
case "${cmd[$i]}" in
|
|
1213
|
+
-b|-d|-f|-h|-I|-v|-P|-Z)
|
|
1214
|
+
args+=("${cmd[$i]}")
|
|
1215
|
+
;;
|
|
1216
|
+
-c)
|
|
1217
|
+
# Capture directory, use for docker exec -w
|
|
1218
|
+
(( i++ ))
|
|
1219
|
+
cwd="${cmd[$i]}"
|
|
1220
|
+
;;
|
|
1221
|
+
-e|-l|-t|-F)
|
|
1222
|
+
# Flags with a value argument
|
|
1223
|
+
args+=("${cmd[$i]}")
|
|
1224
|
+
(( i++ ))
|
|
1225
|
+
args+=("${cmd[$i]}")
|
|
1226
|
+
;;
|
|
1227
|
+
-*)
|
|
1228
|
+
# Unknown flag, pass through
|
|
1229
|
+
args+=("${cmd[$i]}")
|
|
1230
|
+
;;
|
|
1231
|
+
*)
|
|
1232
|
+
# Shell command — skip (replaced by docker exec)
|
|
1233
|
+
break
|
|
1234
|
+
;;
|
|
1235
|
+
esac
|
|
1236
|
+
(( i++ ))
|
|
1237
|
+
done
|
|
1238
|
+
|
|
1239
|
+
# Validate cwd: absolute path, safe characters only
|
|
1240
|
+
if [[ -n "$cwd" && ! "$cwd" =~ ^[a-zA-Z0-9/._-]+$ ]]; then
|
|
1241
|
+
printf '{"code":1,"stdout":"","stderr":"invalid cwd"}\n'
|
|
1242
|
+
exit 1
|
|
1243
|
+
fi
|
|
1244
|
+
|
|
1245
|
+
# Append docker exec as the pane command (no sh -c, args as array)
|
|
1246
|
+
args+=(
|
|
1247
|
+
"--"
|
|
1248
|
+
"docker" "exec" "-it"
|
|
1249
|
+
"-e" "DCLAUDE_TMUX_RELAY_PORT=$RELAY_PORT"
|
|
1250
|
+
"-e" "DCLAUDE_TMUX_RELAY_HOST=$RELAY_HOST"
|
|
1251
|
+
"-e" "DCLAUDE_TMUX_RELAY_NONCE=$NONCE"
|
|
1252
|
+
"-e" "DCLAUDE_CONTAINER=$CONTAINER"
|
|
1253
|
+
"-u" "claude"
|
|
1254
|
+
"-w" "${cwd:-/home/claude}"
|
|
1255
|
+
"$CONTAINER"
|
|
1256
|
+
"bash"
|
|
1257
|
+
)
|
|
1258
|
+
|
|
1259
|
+
elif [[ "$subcmd" == "send-keys" ]]; then
|
|
1260
|
+
args+=("send-keys")
|
|
1261
|
+
|
|
1262
|
+
# Validate target pane is one we created
|
|
1263
|
+
for (( i=1; i < ${#cmd[@]}; i++ )); do
|
|
1264
|
+
if [[ "${cmd[$i]}" == "-t" && $(( i + 1 )) -lt ${#cmd[@]} ]]; then
|
|
1265
|
+
target="${cmd[$(( i + 1 ))]}"
|
|
1266
|
+
if [[ -f "$TRACKING_FILE" ]] && ! grep -qxF "$target" "$TRACKING_FILE"; then
|
|
1267
|
+
printf '{"code":1,"stdout":"","stderr":"pane not tracked: %s"}\n' "$target"
|
|
1268
|
+
exit 1
|
|
1269
|
+
fi
|
|
1270
|
+
break
|
|
1271
|
+
fi
|
|
1272
|
+
done
|
|
1273
|
+
|
|
1274
|
+
# Pass through all args
|
|
1275
|
+
for (( i=1; i < ${#cmd[@]}; i++ )); do
|
|
1276
|
+
args+=("${cmd[$i]}")
|
|
1277
|
+
done
|
|
1278
|
+
|
|
1279
|
+
elif [[ "$subcmd" == "kill-pane" ]]; then
|
|
1280
|
+
args+=("kill-pane")
|
|
1281
|
+
for (( i=1; i < ${#cmd[@]}; i++ )); do
|
|
1282
|
+
args+=("${cmd[$i]}")
|
|
1283
|
+
done
|
|
1284
|
+
else
|
|
1285
|
+
# All other allowed commands: pass through args
|
|
1286
|
+
# Inject -t $req_pane for commands that need pane context
|
|
1287
|
+
# (relay runs outside tmux pane context, so tmux needs explicit targeting)
|
|
1288
|
+
args+=("$subcmd")
|
|
1289
|
+
has_target=false
|
|
1290
|
+
for (( i=1; i < ${#cmd[@]}; i++ )); do
|
|
1291
|
+
[[ "${cmd[$i]}" == "-t" ]] && has_target=true
|
|
1292
|
+
[[ "${cmd[$i]}" =~ ^-t.+ ]] && has_target=true
|
|
1293
|
+
done
|
|
1294
|
+
# Only inject -t for commands that accept it (not list-sessions, has-session)
|
|
1295
|
+
case "$subcmd" in
|
|
1296
|
+
display-message|list-panes|list-windows|select-layout|resize-pane|select-pane)
|
|
1297
|
+
if [[ "$has_target" == "false" && -n "$req_pane" ]]; then
|
|
1298
|
+
args+=("-t" "$req_pane")
|
|
1299
|
+
fi
|
|
1300
|
+
;;
|
|
1301
|
+
esac
|
|
1302
|
+
for (( i=1; i < ${#cmd[@]}; i++ )); do
|
|
1303
|
+
args+=("${cmd[$i]}")
|
|
1304
|
+
done
|
|
1305
|
+
fi
|
|
1306
|
+
|
|
1307
|
+
# Execute tmux command, capture output
|
|
1308
|
+
stdout_file=$(mktemp)
|
|
1309
|
+
stderr_file=$(mktemp)
|
|
1310
|
+
tmux "${args[@]}" >"$stdout_file" 2>"$stderr_file"
|
|
1311
|
+
code=$?
|
|
1312
|
+
stdout=$(cat "$stdout_file")
|
|
1313
|
+
stderr=$(cat "$stderr_file")
|
|
1314
|
+
rm -f "$stdout_file" "$stderr_file"
|
|
1315
|
+
|
|
1316
|
+
# Post-processing for split-window: track new pane ID
|
|
1317
|
+
if [[ "$subcmd" == "split-window" && $code -eq 0 ]]; then
|
|
1318
|
+
pane_id=$(echo "$stdout" | head -1 | tr -d '[:space:]')
|
|
1319
|
+
if [[ "$pane_id" =~ ^%[0-9]+$ ]]; then
|
|
1320
|
+
echo "$pane_id" >> "$TRACKING_FILE"
|
|
1321
|
+
fi
|
|
1322
|
+
fi
|
|
1323
|
+
|
|
1324
|
+
# Post-processing for kill-pane: clean up orphaned processes after delay
|
|
1325
|
+
if [[ "$subcmd" == "kill-pane" && $code -eq 0 ]]; then
|
|
1326
|
+
(
|
|
1327
|
+
sleep 2
|
|
1328
|
+
docker exec "$CONTAINER" bash -c '
|
|
1329
|
+
ps -eo pid,ppid,tty,comm --no-headers 2>/dev/null | while read pid ppid tty comm; do
|
|
1330
|
+
[[ "$ppid" == "0" && "$comm" == "bash" ]] || continue
|
|
1331
|
+
[[ "$tty" =~ ^pts/([0-9]+)$ ]] || continue
|
|
1332
|
+
(( ${BASH_REMATCH[1]} >= 2 )) || continue
|
|
1333
|
+
children=$(ps --ppid "$pid" --no-headers 2>/dev/null | wc -l)
|
|
1334
|
+
if [[ "$children" -eq 0 ]]; then
|
|
1335
|
+
kill -9 "$pid" 2>/dev/null
|
|
1336
|
+
fi
|
|
1337
|
+
done
|
|
1338
|
+
'
|
|
1339
|
+
) &>/dev/null &
|
|
1340
|
+
fi
|
|
1341
|
+
|
|
1342
|
+
# Return JSON response
|
|
1343
|
+
jq -nc --arg code "$code" --arg stdout "$stdout" --arg stderr "$stderr" \
|
|
1344
|
+
'{code: ($code | tonumber), stdout: $stdout, stderr: $stderr}'
|
|
1345
|
+
HANDLER_BODY
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
# Start the relay listener on the host
|
|
1349
|
+
start_relay() {
|
|
1350
|
+
local container_name="$1"
|
|
1351
|
+
local relay_port="$2"
|
|
1352
|
+
local nonce="$3"
|
|
1353
|
+
local bind_addr="$4"
|
|
1354
|
+
local relay_host="$5"
|
|
1355
|
+
|
|
1356
|
+
local handler_path="$HOME/.dclaude/tmux-relay-handler-${container_name}.sh"
|
|
1357
|
+
local pid_file="$HOME/.dclaude/tmux-relay-pid-${container_name}"
|
|
1358
|
+
local tracking_file="$HOME/.dclaude/tmux-relay-panes-${container_name}"
|
|
1359
|
+
|
|
1360
|
+
mkdir -p "$HOME/.dclaude"
|
|
1361
|
+
|
|
1362
|
+
# Generate handler script
|
|
1363
|
+
generate_relay_handler "$container_name" "$relay_port" "$nonce" \
|
|
1364
|
+
"$handler_path" "$tracking_file" "$relay_host"
|
|
1365
|
+
chmod +x "$handler_path"
|
|
1366
|
+
|
|
1367
|
+
# Clear pane tracking
|
|
1368
|
+
> "$tracking_file"
|
|
1369
|
+
|
|
1370
|
+
# Start listener
|
|
1371
|
+
if command -v socat &>/dev/null; then
|
|
1372
|
+
debug "Starting socat relay on $bind_addr:$relay_port"
|
|
1373
|
+
socat "TCP-LISTEN:$relay_port,bind=$bind_addr,reuseaddr,fork,max-children=10" \
|
|
1374
|
+
"EXEC:bash $handler_path" 2>/dev/null &
|
|
1375
|
+
elif command -v ncat &>/dev/null; then
|
|
1376
|
+
debug "Starting ncat relay on $bind_addr:$relay_port"
|
|
1377
|
+
ncat -l -k "$bind_addr" "$relay_port" --sh-exec "$handler_path" &
|
|
1378
|
+
else
|
|
1379
|
+
error "No relay listener available (need socat or ncat)"
|
|
1380
|
+
return 1
|
|
1381
|
+
fi
|
|
1382
|
+
|
|
1383
|
+
echo $! > "$pid_file"
|
|
1384
|
+
debug "Relay PID: $(cat "$pid_file")"
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
# Stop the relay and clean up
|
|
1388
|
+
stop_relay() {
|
|
1389
|
+
local container_name="$1"
|
|
1390
|
+
|
|
1391
|
+
local pid_file="$HOME/.dclaude/tmux-relay-pid-${container_name}"
|
|
1392
|
+
local handler_path="$HOME/.dclaude/tmux-relay-handler-${container_name}.sh"
|
|
1393
|
+
local tracking_file="$HOME/.dclaude/tmux-relay-panes-${container_name}"
|
|
1394
|
+
|
|
1395
|
+
if [[ -f "$pid_file" ]]; then
|
|
1396
|
+
local pid
|
|
1397
|
+
pid=$(cat "$pid_file")
|
|
1398
|
+
if [[ -n "$pid" ]]; then
|
|
1399
|
+
# Kill forked handler processes first
|
|
1400
|
+
pkill -P "$pid" 2>/dev/null || true
|
|
1401
|
+
# Verify process identity before killing (guard against PID recycling)
|
|
1402
|
+
local comm
|
|
1403
|
+
comm=$(ps -p "$pid" -o comm= 2>/dev/null || true)
|
|
1404
|
+
if [[ "$comm" =~ (socat|ncat) ]]; then
|
|
1405
|
+
kill "$pid" 2>/dev/null || true
|
|
1406
|
+
fi
|
|
1407
|
+
fi
|
|
1408
|
+
fi
|
|
1409
|
+
|
|
1410
|
+
# Clean up orphaned bash processes in container
|
|
1411
|
+
cleanup_container_orphans "$container_name"
|
|
1412
|
+
|
|
1413
|
+
# Remove relay files (but keep nonce for reattachment)
|
|
1414
|
+
rm -f "$pid_file" "$handler_path" "$tracking_file"
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
# Kill orphaned bash sessions in container (from dead docker exec panes)
|
|
1418
|
+
cleanup_container_orphans() {
|
|
1419
|
+
local container_name="$1"
|
|
1420
|
+
|
|
1421
|
+
# Check container is running before trying
|
|
1422
|
+
if ! docker ps -q -f name="^${container_name}$" 2>/dev/null | grep -q .; then
|
|
1423
|
+
return 0
|
|
1424
|
+
fi
|
|
1425
|
+
|
|
1426
|
+
docker exec "$container_name" bash -c '
|
|
1427
|
+
ps -eo pid,ppid,tty,comm --no-headers 2>/dev/null | while read pid ppid tty comm; do
|
|
1428
|
+
[[ "$ppid" == "0" && "$comm" == "bash" ]] || continue
|
|
1429
|
+
[[ "$tty" =~ ^pts/([0-9]+)$ ]] || continue
|
|
1430
|
+
(( ${BASH_REMATCH[1]} >= 2 )) || continue
|
|
1431
|
+
children=$(ps --ppid "$pid" --no-headers 2>/dev/null | wc -l)
|
|
1432
|
+
if [[ "$children" -eq 0 ]]; then
|
|
1433
|
+
kill -9 "$pid" 2>/dev/null
|
|
1434
|
+
fi
|
|
1435
|
+
done
|
|
1436
|
+
' 2>/dev/null || true
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
# Set up Agent Teams relay if conditions are met
|
|
1440
|
+
# Sets globals: RELAY_STARTED, TMUX_SESSION_ENV_ARGS
|
|
1441
|
+
setup_agent_teams_relay() {
|
|
1442
|
+
local container_name="$1"
|
|
1443
|
+
local relay_port="$2"
|
|
1444
|
+
local platform="$3"
|
|
1445
|
+
local network_mode="$4"
|
|
1446
|
+
|
|
1447
|
+
RELAY_STARTED=false
|
|
1448
|
+
TMUX_SESSION_ENV_ARGS=()
|
|
1449
|
+
|
|
1450
|
+
if [[ -z "$relay_port" ]]; then
|
|
1451
|
+
debug "No relay port configured for this container"
|
|
1452
|
+
TMUX_SESSION_ENV_ARGS=(-e "DCLAUDE_HIDE_TMUX=1")
|
|
1453
|
+
return
|
|
1454
|
+
fi
|
|
1455
|
+
|
|
1456
|
+
if ! detect_host_tmux; then
|
|
1457
|
+
debug "Not in host tmux - Agent Teams will use in-process mode"
|
|
1458
|
+
TMUX_SESSION_ENV_ARGS=(-e "DCLAUDE_HIDE_TMUX=1")
|
|
1459
|
+
return
|
|
1460
|
+
fi
|
|
1461
|
+
|
|
1462
|
+
if ! check_relay_deps; then
|
|
1463
|
+
info "Tip: Install jq + socat (or ncat) for Agent Teams pane mode in tmux"
|
|
1464
|
+
TMUX_SESSION_ENV_ARGS=(-e "DCLAUDE_HIDE_TMUX=1")
|
|
1465
|
+
return
|
|
1466
|
+
fi
|
|
1467
|
+
|
|
1468
|
+
local bind_addr relay_host relay_nonce host_pane
|
|
1469
|
+
bind_addr=$(get_relay_bind_addr "$platform" "$network_mode")
|
|
1470
|
+
relay_host=$(get_relay_host "$platform" "$network_mode")
|
|
1471
|
+
relay_nonce=$(get_or_create_relay_nonce "$container_name")
|
|
1472
|
+
host_pane=$(tmux display-message -p '#{pane_id}')
|
|
1473
|
+
|
|
1474
|
+
start_relay "$container_name" "$relay_port" "$relay_nonce" "$bind_addr" "$relay_host"
|
|
1475
|
+
RELAY_STARTED=true
|
|
1476
|
+
|
|
1477
|
+
TMUX_SESSION_ENV_ARGS=(
|
|
1478
|
+
-e "DCLAUDE_TMUX_RELAY_PORT=$relay_port"
|
|
1479
|
+
-e "DCLAUDE_TMUX_RELAY_HOST=$relay_host"
|
|
1480
|
+
-e "DCLAUDE_TMUX_RELAY_NONCE=$relay_nonce"
|
|
1481
|
+
-e "DCLAUDE_HOST_PANE=$host_pane"
|
|
1482
|
+
-e "DCLAUDE_CONTAINER=$container_name"
|
|
1483
|
+
)
|
|
1484
|
+
|
|
1485
|
+
debug "Agent Teams relay: $bind_addr:$relay_port -> $container_name (pane $host_pane)"
|
|
1486
|
+
}
|
|
1487
|
+
# ============================================================================
|
|
1488
|
+
|
|
990
1489
|
# Detect TTY availability and return appropriate Docker flags
|
|
991
1490
|
# Detect TTY status early (before any subshells)
|
|
992
1491
|
# Must be done at script top-level since $() subshells don't inherit TTY
|
|
@@ -1031,6 +1530,17 @@ should_skip_tmux() {
|
|
|
1031
1530
|
return 1
|
|
1032
1531
|
}
|
|
1033
1532
|
|
|
1533
|
+
# Debug countdown before launching Claude (gives time to read debug output)
|
|
1534
|
+
# Press Enter to skip the countdown and launch immediately
|
|
1535
|
+
debug_countdown() {
|
|
1536
|
+
if [[ "$DEBUG" == "true" ]]; then
|
|
1537
|
+
for i in 5 4 3 2 1; do
|
|
1538
|
+
printf '\r%b' "${CYAN}Debug: Launching in ${i}... (press Enter to skip)${NC}" >&2
|
|
1539
|
+
read -t 1 -s 2>/dev/null && break
|
|
1540
|
+
done
|
|
1541
|
+
printf '\r%b\n' "${CYAN}Debug: Launching... ${NC}" >&2
|
|
1542
|
+
fi
|
|
1543
|
+
}
|
|
1034
1544
|
|
|
1035
1545
|
# Main execution
|
|
1036
1546
|
main() {
|
|
@@ -1145,14 +1655,27 @@ main() {
|
|
|
1145
1655
|
debug "Git auth: $resolved_git_auth (user-specified)"
|
|
1146
1656
|
fi
|
|
1147
1657
|
|
|
1658
|
+
# Warn if agent-forwarding is active but no keys are loaded
|
|
1659
|
+
if [[ "$resolved_git_auth" == "agent-forwarding" ]]; then
|
|
1660
|
+
local ssh_add_rc=0
|
|
1661
|
+
ssh-add -l >/dev/null 2>&1 || ssh_add_rc=$?
|
|
1662
|
+
if [[ $ssh_add_rc -eq 1 ]]; then
|
|
1663
|
+
warning "SSH agent has no keys loaded — SSH won't work inside container."
|
|
1664
|
+
warning "Run 'dclaude ssh keys' to load your keys."
|
|
1665
|
+
fi
|
|
1666
|
+
fi
|
|
1667
|
+
|
|
1148
1668
|
# Generate system context for Claude (if enabled) - must be done early for all code paths
|
|
1149
1669
|
local claude_args=()
|
|
1150
1670
|
if [[ "$ENABLE_SYSTEM_CONTEXT" == "true" ]]; then
|
|
1151
1671
|
local has_docker="false"
|
|
1152
1672
|
[[ -n "$DOCKER_SOCKET" ]] && [[ -S "$DOCKER_SOCKET" ]] && has_docker="true"
|
|
1153
1673
|
|
|
1674
|
+
local resolved_aws_cli_mode
|
|
1675
|
+
resolved_aws_cli_mode=$(resolve_aws_cli_mode)
|
|
1676
|
+
|
|
1154
1677
|
local system_context
|
|
1155
|
-
system_context=$(generate_system_context "$network_mode" "$resolved_git_auth" "$has_docker" "$platform" "$DOCKER_SOCKET")
|
|
1678
|
+
system_context=$(generate_system_context "$network_mode" "$resolved_git_auth" "$has_docker" "$platform" "$DOCKER_SOCKET" "$resolved_aws_cli_mode")
|
|
1156
1679
|
|
|
1157
1680
|
claude_args+=("--append-system-prompt" "$system_context")
|
|
1158
1681
|
debug "System context enabled (${#system_context} chars, has_docker=$has_docker, git_auth=$resolved_git_auth, platform=$platform)"
|
|
@@ -1244,10 +1767,23 @@ main() {
|
|
|
1244
1767
|
[[ -n "${COLORTERM:-}" ]] && exec_env_args+=(-e "COLORTERM=${COLORTERM}")
|
|
1245
1768
|
[[ -n "${DCLAUDE_ITERM2:-}" ]] && exec_env_args+=(-e "DCLAUDE_ITERM2=${DCLAUDE_ITERM2}")
|
|
1246
1769
|
|
|
1770
|
+
# Set up Agent Teams relay (or hide $TMUX for in-process mode)
|
|
1771
|
+
local relay_port
|
|
1772
|
+
relay_port=$(docker inspect --format='{{index .Config.Labels "dclaude.relay.port"}}' "$container_name" 2>/dev/null || true)
|
|
1773
|
+
setup_agent_teams_relay "$container_name" "$relay_port" "$platform" "$network_mode"
|
|
1774
|
+
if [[ "$RELAY_STARTED" == "true" ]]; then
|
|
1775
|
+
trap "stop_relay '$container_name'" EXIT
|
|
1776
|
+
fi
|
|
1777
|
+
|
|
1247
1778
|
debug "Creating new tmux session running Claude"
|
|
1248
1779
|
debug "Claude args count: ${#claude_args[@]}, user args: $*"
|
|
1249
1780
|
info "Starting new Claude session..."
|
|
1250
|
-
|
|
1781
|
+
debug_countdown
|
|
1782
|
+
docker exec -it -u claude "${exec_env_args[@]}" "$container_name" \
|
|
1783
|
+
/usr/bin/tmux -L dclaude-inner -f /home/claude/.tmux.conf \
|
|
1784
|
+
new-session -s "$tmux_session" \
|
|
1785
|
+
"${TMUX_SESSION_ENV_ARGS[@]}" \
|
|
1786
|
+
claude-launcher.sh "${claude_args[@]}" "$@"
|
|
1251
1787
|
exit $?
|
|
1252
1788
|
else
|
|
1253
1789
|
# Non-interactive or print mode - run claude directly without tmux
|
|
@@ -1298,6 +1834,17 @@ main() {
|
|
|
1298
1834
|
debug "SSH port reserved: $ssh_port"
|
|
1299
1835
|
DOCKER_ARGS+=(--label "dclaude.ssh.port=${ssh_port}")
|
|
1300
1836
|
|
|
1837
|
+
# Reserve relay port for Agent Teams tmux passthrough
|
|
1838
|
+
local relay_port
|
|
1839
|
+
relay_port=$(find_available_port 30000 60000)
|
|
1840
|
+
debug "Relay port reserved: $relay_port"
|
|
1841
|
+
DOCKER_ARGS+=(--label "dclaude.relay.port=${relay_port}")
|
|
1842
|
+
|
|
1843
|
+
# Add host.docker.internal for Linux bridge mode (needed for relay connectivity)
|
|
1844
|
+
if [[ "$platform" == "linux" && "$network_mode" == "bridge" ]]; then
|
|
1845
|
+
DOCKER_ARGS+=(--add-host "host.docker.internal:host-gateway")
|
|
1846
|
+
fi
|
|
1847
|
+
|
|
1301
1848
|
# Port mapping only needed for bridge mode (host mode shares network stack)
|
|
1302
1849
|
if [[ "$network_mode" != "host" ]]; then
|
|
1303
1850
|
DOCKER_ARGS+=(-p "${ssh_port}:${ssh_port}")
|
|
@@ -1341,6 +1888,32 @@ main() {
|
|
|
1341
1888
|
[[ -n "$ssh_arg" ]] && DOCKER_ARGS+=("$ssh_arg")
|
|
1342
1889
|
done < <(handle_git_auth)
|
|
1343
1890
|
|
|
1891
|
+
# Handle AWS CLI configuration mounting
|
|
1892
|
+
local resolved_aws_mode
|
|
1893
|
+
resolved_aws_mode=$(resolve_aws_cli_mode)
|
|
1894
|
+
# Pass mode to entrypoint so it knows whether to chown .aws (volume only)
|
|
1895
|
+
DOCKER_ARGS+=(-e "DCLAUDE_AWS_CLI_MODE=${resolved_aws_mode}")
|
|
1896
|
+
case "$resolved_aws_mode" in
|
|
1897
|
+
mount)
|
|
1898
|
+
if [[ -d "${HOME}/.aws" ]]; then
|
|
1899
|
+
DOCKER_ARGS+=(-v "${HOME}/.aws:/home/claude/.aws")
|
|
1900
|
+
info "AWS CLI: mounting host ~/.aws"
|
|
1901
|
+
debug "AWS config mounted from host: ${HOME}/.aws"
|
|
1902
|
+
else
|
|
1903
|
+
warning "AWS_CLI=mount but ~/.aws not found, skipping"
|
|
1904
|
+
fi
|
|
1905
|
+
;;
|
|
1906
|
+
volume)
|
|
1907
|
+
local aws_volume
|
|
1908
|
+
aws_volume=$(get_aws_volume_name)
|
|
1909
|
+
DOCKER_ARGS+=(-v "${aws_volume}:/home/claude/.aws")
|
|
1910
|
+
debug "AWS config using volume: $aws_volume"
|
|
1911
|
+
;;
|
|
1912
|
+
none)
|
|
1913
|
+
debug "AWS CLI config mounting disabled"
|
|
1914
|
+
;;
|
|
1915
|
+
esac
|
|
1916
|
+
|
|
1344
1917
|
# Add any additional environment variables
|
|
1345
1918
|
if [[ -n "${CLAUDE_MODEL:-}" ]]; then
|
|
1346
1919
|
DOCKER_ARGS+=(-e "CLAUDE_MODEL=${CLAUDE_MODEL}")
|
|
@@ -1397,10 +1970,21 @@ main() {
|
|
|
1397
1970
|
[[ -n "${COLORTERM:-}" ]] && exec_env_args+=(-e "COLORTERM=${COLORTERM}")
|
|
1398
1971
|
[[ -n "${DCLAUDE_ITERM2:-}" ]] && exec_env_args+=(-e "DCLAUDE_ITERM2=${DCLAUDE_ITERM2}")
|
|
1399
1972
|
|
|
1973
|
+
# Set up Agent Teams relay (or hide $TMUX for in-process mode)
|
|
1974
|
+
setup_agent_teams_relay "$container_name" "$relay_port" "$platform" "$network_mode"
|
|
1975
|
+
if [[ "$RELAY_STARTED" == "true" ]]; then
|
|
1976
|
+
trap "stop_relay '$container_name'" EXIT
|
|
1977
|
+
fi
|
|
1978
|
+
|
|
1400
1979
|
debug "Creating new tmux session running Claude"
|
|
1401
1980
|
debug "Claude args count: ${#claude_args[@]}, user args: $*"
|
|
1402
1981
|
info "Starting new Claude session..."
|
|
1403
|
-
|
|
1982
|
+
debug_countdown
|
|
1983
|
+
docker exec -it -u claude "${exec_env_args[@]}" "$container_name" \
|
|
1984
|
+
/usr/bin/tmux -L dclaude-inner -f /home/claude/.tmux.conf \
|
|
1985
|
+
new-session -s "$tmux_session" \
|
|
1986
|
+
"${TMUX_SESSION_ENV_ARGS[@]}" \
|
|
1987
|
+
claude-launcher.sh "${claude_args[@]}" "$@"
|
|
1404
1988
|
exit $?
|
|
1405
1989
|
else
|
|
1406
1990
|
# Non-interactive or print mode - run claude directly without tmux
|
|
@@ -1530,11 +2114,11 @@ cmd_attach() {
|
|
|
1530
2114
|
fi
|
|
1531
2115
|
fi
|
|
1532
2116
|
|
|
1533
|
-
# Check if session exists
|
|
1534
|
-
if ! docker exec -u claude "$container_name" tmux has-session -t "$session_name" 2>/dev/null; then
|
|
2117
|
+
# Check if session exists (use -L dclaude-inner for namespaced socket)
|
|
2118
|
+
if ! docker exec -u claude "$container_name" /usr/bin/tmux -L dclaude-inner has-session -t "$session_name" 2>/dev/null; then
|
|
1535
2119
|
error "Session '$session_name' not found in container"
|
|
1536
2120
|
info "Available sessions:"
|
|
1537
|
-
docker exec -u claude "$container_name" tmux list-sessions 2>/dev/null || echo " (no sessions running)"
|
|
2121
|
+
docker exec -u claude "$container_name" /usr/bin/tmux -L dclaude-inner list-sessions 2>/dev/null || echo " (no sessions running)"
|
|
1538
2122
|
exit 1
|
|
1539
2123
|
fi
|
|
1540
2124
|
|
|
@@ -1546,9 +2130,22 @@ cmd_attach() {
|
|
|
1546
2130
|
[[ -n "${COLORTERM:-}" ]] && exec_env_args+=(-e "COLORTERM=${COLORTERM}")
|
|
1547
2131
|
[[ -n "${DCLAUDE_ITERM2:-}" ]] && exec_env_args+=(-e "DCLAUDE_ITERM2=${DCLAUDE_ITERM2}")
|
|
1548
2132
|
|
|
2133
|
+
# Restart relay if the session uses it (relay was stopped when previous dclaude exited)
|
|
2134
|
+
local relay_port
|
|
2135
|
+
relay_port=$(docker inspect --format='{{index .Config.Labels "dclaude.relay.port"}}' "$container_name" 2>/dev/null || true)
|
|
2136
|
+
local platform
|
|
2137
|
+
platform=$(detect_platform)
|
|
2138
|
+
local network_mode
|
|
2139
|
+
network_mode=$(docker inspect --format='{{.HostConfig.NetworkMode}}' "$container_name" 2>/dev/null || echo "bridge")
|
|
2140
|
+
setup_agent_teams_relay "$container_name" "$relay_port" "$platform" "$network_mode"
|
|
2141
|
+
if [[ "$RELAY_STARTED" == "true" ]]; then
|
|
2142
|
+
trap "stop_relay '$container_name'" EXIT
|
|
2143
|
+
fi
|
|
2144
|
+
|
|
1549
2145
|
# Attach to existing session
|
|
1550
2146
|
info "Attaching to session: $session_name"
|
|
1551
|
-
docker exec -it -u claude "${exec_env_args[@]}" "$container_name"
|
|
2147
|
+
docker exec -it -u claude "${exec_env_args[@]}" "$container_name" \
|
|
2148
|
+
/usr/bin/tmux -L dclaude-inner -f /home/claude/.tmux.conf attach-session -t "$session_name"
|
|
1552
2149
|
exit $?
|
|
1553
2150
|
}
|
|
1554
2151
|
|
|
@@ -1770,7 +2367,7 @@ cmd_stop() {
|
|
|
1770
2367
|
if [[ "$container_status" == "running" ]]; then
|
|
1771
2368
|
# Count active tmux sessions
|
|
1772
2369
|
local session_count
|
|
1773
|
-
session_count=$(docker exec -u claude "$container_name" tmux list-sessions 2>/dev/null | wc -l | tr -d '[:space:]') || session_count=0
|
|
2370
|
+
session_count=$(docker exec -u claude "$container_name" /usr/bin/tmux -L dclaude-inner list-sessions 2>/dev/null | wc -l | tr -d '[:space:]') || session_count=0
|
|
1774
2371
|
|
|
1775
2372
|
if [[ "$session_count" =~ ^[0-9]+$ ]] && [[ "$session_count" -gt 0 ]]; then
|
|
1776
2373
|
info "Stopping container $container_name ($session_count active session(s))..."
|
|
@@ -1778,6 +2375,9 @@ cmd_stop() {
|
|
|
1778
2375
|
info "Stopping container $container_name..."
|
|
1779
2376
|
fi
|
|
1780
2377
|
|
|
2378
|
+
# Stop relay if running
|
|
2379
|
+
stop_relay "$container_name"
|
|
2380
|
+
|
|
1781
2381
|
if docker stop "$container_name" >/dev/null; then
|
|
1782
2382
|
success "Container stopped"
|
|
1783
2383
|
else
|
|
@@ -1790,8 +2390,117 @@ cmd_stop() {
|
|
|
1790
2390
|
exit 0
|
|
1791
2391
|
}
|
|
1792
2392
|
|
|
1793
|
-
# Subcommand: SSH
|
|
2393
|
+
# Subcommand: SSH — dispatches to 'keys', 'server', or both
|
|
1794
2394
|
cmd_ssh() {
|
|
2395
|
+
local rc=0
|
|
2396
|
+
case "${1:-}" in
|
|
2397
|
+
keys)
|
|
2398
|
+
shift
|
|
2399
|
+
cmd_ssh_keys "$@" || rc=$?
|
|
2400
|
+
;;
|
|
2401
|
+
server)
|
|
2402
|
+
shift
|
|
2403
|
+
cmd_ssh_server "$@" || rc=$?
|
|
2404
|
+
;;
|
|
2405
|
+
--help|-h)
|
|
2406
|
+
cat << 'SSH_HELP'
|
|
2407
|
+
dclaude ssh - SSH Key and Server Management
|
|
2408
|
+
|
|
2409
|
+
Usage:
|
|
2410
|
+
dclaude ssh Load SSH keys into agent and start SSH server
|
|
2411
|
+
dclaude ssh keys Load SSH keys into agent
|
|
2412
|
+
dclaude ssh server Start SSH server and show connection info
|
|
2413
|
+
dclaude ssh server --stop Stop SSH server
|
|
2414
|
+
|
|
2415
|
+
SSH_HELP
|
|
2416
|
+
exit 0
|
|
2417
|
+
;;
|
|
2418
|
+
"")
|
|
2419
|
+
# Bare 'dclaude ssh': load keys (non-fatal), then start server
|
|
2420
|
+
cmd_ssh_keys "$@" || true
|
|
2421
|
+
echo ""
|
|
2422
|
+
cmd_ssh_server "$@" || rc=$?
|
|
2423
|
+
;;
|
|
2424
|
+
*)
|
|
2425
|
+
error "Unknown subcommand: $1"
|
|
2426
|
+
info "Usage: dclaude ssh [keys | server]"
|
|
2427
|
+
exit 1
|
|
2428
|
+
;;
|
|
2429
|
+
esac
|
|
2430
|
+
exit "$rc"
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
# Subcommand: SSH key loading
|
|
2434
|
+
cmd_ssh_keys() {
|
|
2435
|
+
# Parse flags
|
|
2436
|
+
while [[ $# -gt 0 ]]; do
|
|
2437
|
+
case "$1" in
|
|
2438
|
+
--help|-h)
|
|
2439
|
+
cat << 'SSH_KEYS_HELP'
|
|
2440
|
+
dclaude ssh keys - Load SSH Keys into Agent
|
|
2441
|
+
|
|
2442
|
+
Usage:
|
|
2443
|
+
dclaude ssh keys Detect and load SSH keys from ~/.ssh/
|
|
2444
|
+
|
|
2445
|
+
Detects private keys (id_rsa, id_ed25519, etc.) in ~/.ssh/ and loads
|
|
2446
|
+
them into the SSH agent via ssh-add. May prompt for passphrases.
|
|
2447
|
+
|
|
2448
|
+
SSH_KEYS_HELP
|
|
2449
|
+
return 0
|
|
2450
|
+
;;
|
|
2451
|
+
*)
|
|
2452
|
+
error "Unknown option: $1"
|
|
2453
|
+
info "Usage: dclaude ssh keys"
|
|
2454
|
+
return 1
|
|
2455
|
+
;;
|
|
2456
|
+
esac
|
|
2457
|
+
done
|
|
2458
|
+
|
|
2459
|
+
local ssh_dir="${HOME}/.ssh"
|
|
2460
|
+
if [[ ! -d "$ssh_dir" ]]; then
|
|
2461
|
+
error "No ~/.ssh directory found"
|
|
2462
|
+
return 1
|
|
2463
|
+
fi
|
|
2464
|
+
|
|
2465
|
+
# Detect private key files
|
|
2466
|
+
local keys=()
|
|
2467
|
+
for key_file in "$ssh_dir"/id_*; do
|
|
2468
|
+
[[ -f "$key_file" ]] || continue
|
|
2469
|
+
# Skip public keys and certificates
|
|
2470
|
+
[[ "$key_file" == *.pub ]] && continue
|
|
2471
|
+
[[ "$key_file" == *-cert.pub ]] && continue
|
|
2472
|
+
keys+=("$key_file")
|
|
2473
|
+
done
|
|
2474
|
+
|
|
2475
|
+
if [[ ${#keys[@]} -eq 0 ]]; then
|
|
2476
|
+
warning "No SSH keys found in ~/.ssh/"
|
|
2477
|
+
info "Generate a key with: ssh-keygen -t ed25519"
|
|
2478
|
+
return 1
|
|
2479
|
+
fi
|
|
2480
|
+
|
|
2481
|
+
info "Loading SSH keys..."
|
|
2482
|
+
local loaded=0
|
|
2483
|
+
for key_file in "${keys[@]}"; do
|
|
2484
|
+
local display_path="~/.ssh/$(basename "$key_file")"
|
|
2485
|
+
if ssh-add "$key_file" 2>/dev/null; then
|
|
2486
|
+
success " $display_path"
|
|
2487
|
+
loaded=$((loaded + 1))
|
|
2488
|
+
else
|
|
2489
|
+
warning " $display_path (failed — wrong passphrase or unsupported key)"
|
|
2490
|
+
fi
|
|
2491
|
+
done
|
|
2492
|
+
|
|
2493
|
+
echo ""
|
|
2494
|
+
if [[ $loaded -gt 0 ]]; then
|
|
2495
|
+
success "$loaded key(s) loaded into SSH agent."
|
|
2496
|
+
else
|
|
2497
|
+
error "No keys were loaded"
|
|
2498
|
+
return 1
|
|
2499
|
+
fi
|
|
2500
|
+
}
|
|
2501
|
+
|
|
2502
|
+
# Subcommand: SSH server for remote access (JetBrains Gateway, debugging, etc.)
|
|
2503
|
+
cmd_ssh_server() {
|
|
1795
2504
|
local action=""
|
|
1796
2505
|
|
|
1797
2506
|
# Parse flags
|
|
@@ -1802,12 +2511,12 @@ cmd_ssh() {
|
|
|
1802
2511
|
shift
|
|
1803
2512
|
;;
|
|
1804
2513
|
--help|-h)
|
|
1805
|
-
cat << '
|
|
1806
|
-
dclaude ssh - SSH Server for Remote Access
|
|
2514
|
+
cat << 'SSH_SERVER_HELP'
|
|
2515
|
+
dclaude ssh server - SSH Server for Remote Access
|
|
1807
2516
|
|
|
1808
2517
|
Usage:
|
|
1809
|
-
dclaude ssh
|
|
1810
|
-
dclaude ssh --stop
|
|
2518
|
+
dclaude ssh server Start SSH server and show connection info
|
|
2519
|
+
dclaude ssh server --stop Stop SSH server
|
|
1811
2520
|
|
|
1812
2521
|
Connection:
|
|
1813
2522
|
ssh claude@localhost -p <port>
|
|
@@ -1822,20 +2531,20 @@ Use Cases:
|
|
|
1822
2531
|
|
|
1823
2532
|
JetBrains Gateway Setup:
|
|
1824
2533
|
1. Start container: dclaude
|
|
1825
|
-
2. Start SSH: dclaude ssh
|
|
2534
|
+
2. Start SSH: dclaude ssh server
|
|
1826
2535
|
3. Open JetBrains Gateway
|
|
1827
2536
|
4. New Connection → SSH → localhost:<port shown above>
|
|
1828
2537
|
5. Username: claude, Password: claude
|
|
1829
2538
|
6. Gateway will download and install IDE backend automatically
|
|
1830
2539
|
7. Select your project directory
|
|
1831
2540
|
|
|
1832
|
-
|
|
1833
|
-
|
|
2541
|
+
SSH_SERVER_HELP
|
|
2542
|
+
return 0
|
|
1834
2543
|
;;
|
|
1835
2544
|
*)
|
|
1836
2545
|
error "Unknown option: $1"
|
|
1837
|
-
info "Usage: dclaude ssh [--stop]"
|
|
1838
|
-
|
|
2546
|
+
info "Usage: dclaude ssh server [--stop]"
|
|
2547
|
+
return 1
|
|
1839
2548
|
;;
|
|
1840
2549
|
esac
|
|
1841
2550
|
done
|
|
@@ -1846,7 +2555,7 @@ SSH_HELP
|
|
|
1846
2555
|
if ! docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
|
|
1847
2556
|
error "No container found for this directory"
|
|
1848
2557
|
info "Run 'dclaude' first to create a persistent container"
|
|
1849
|
-
|
|
2558
|
+
return 1
|
|
1850
2559
|
fi
|
|
1851
2560
|
|
|
1852
2561
|
local container_status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)
|
|
@@ -1858,7 +2567,7 @@ SSH_HELP
|
|
|
1858
2567
|
sleep 1
|
|
1859
2568
|
else
|
|
1860
2569
|
error "Container $container_name is in unexpected state: $container_status"
|
|
1861
|
-
|
|
2570
|
+
return 1
|
|
1862
2571
|
fi
|
|
1863
2572
|
fi
|
|
1864
2573
|
|
|
@@ -1874,9 +2583,9 @@ SSH_HELP
|
|
|
1874
2583
|
echo ""
|
|
1875
2584
|
echo " dclaude rm -f"
|
|
1876
2585
|
echo " dclaude"
|
|
1877
|
-
echo " dclaude ssh"
|
|
2586
|
+
echo " dclaude ssh server"
|
|
1878
2587
|
echo ""
|
|
1879
|
-
|
|
2588
|
+
return 1
|
|
1880
2589
|
fi
|
|
1881
2590
|
|
|
1882
2591
|
case "$action" in
|
|
@@ -1918,8 +2627,6 @@ SSH_HELP
|
|
|
1918
2627
|
echo ""
|
|
1919
2628
|
;;
|
|
1920
2629
|
esac
|
|
1921
|
-
|
|
1922
|
-
exit 0
|
|
1923
2630
|
}
|
|
1924
2631
|
|
|
1925
2632
|
# Subcommand: remove container for current directory
|
|
@@ -1956,7 +2663,7 @@ cmd_rm() {
|
|
|
1956
2663
|
if [[ "$force" == "true" ]]; then
|
|
1957
2664
|
# Count active tmux sessions
|
|
1958
2665
|
local session_count
|
|
1959
|
-
session_count=$(docker exec -u claude "$container_name" tmux list-sessions 2>/dev/null | wc -l | tr -d '[:space:]') || session_count=0
|
|
2666
|
+
session_count=$(docker exec -u claude "$container_name" /usr/bin/tmux -L dclaude-inner list-sessions 2>/dev/null | wc -l | tr -d '[:space:]') || session_count=0
|
|
1960
2667
|
|
|
1961
2668
|
if [[ "$session_count" =~ ^[0-9]+$ ]] && [[ "$session_count" -gt 0 ]]; then
|
|
1962
2669
|
info "Removing running container $container_name ($session_count active session(s))..."
|
|
@@ -1964,6 +2671,10 @@ cmd_rm() {
|
|
|
1964
2671
|
info "Removing running container $container_name..."
|
|
1965
2672
|
fi
|
|
1966
2673
|
|
|
2674
|
+
# Clean up relay (including nonce file since container is being removed)
|
|
2675
|
+
stop_relay "$container_name"
|
|
2676
|
+
rm -f "$HOME/.dclaude/tmux-relay-nonce-${container_name}"
|
|
2677
|
+
|
|
1967
2678
|
if docker rm -f "$container_name" >/dev/null; then
|
|
1968
2679
|
success "Container removed"
|
|
1969
2680
|
else
|
|
@@ -1978,6 +2689,11 @@ cmd_rm() {
|
|
|
1978
2689
|
fi
|
|
1979
2690
|
else
|
|
1980
2691
|
info "Removing container $container_name..."
|
|
2692
|
+
|
|
2693
|
+
# Clean up relay files (including nonce since container is being removed)
|
|
2694
|
+
stop_relay "$container_name"
|
|
2695
|
+
rm -f "$HOME/.dclaude/tmux-relay-nonce-${container_name}"
|
|
2696
|
+
|
|
1981
2697
|
if docker rm "$container_name" >/dev/null; then
|
|
1982
2698
|
success "Container removed"
|
|
1983
2699
|
else
|
|
@@ -1989,6 +2705,133 @@ cmd_rm() {
|
|
|
1989
2705
|
}
|
|
1990
2706
|
|
|
1991
2707
|
# Subcommand: configure git identity
|
|
2708
|
+
cmd_aws_configure() {
|
|
2709
|
+
local container_name="$1"
|
|
2710
|
+
|
|
2711
|
+
# This subcommand only makes sense for volume mode
|
|
2712
|
+
local resolved_mode
|
|
2713
|
+
resolved_mode=$(resolve_aws_cli_mode)
|
|
2714
|
+
if [[ "$resolved_mode" != "volume" ]]; then
|
|
2715
|
+
error "dclaude aws configure is only for AWS_CLI=volume mode"
|
|
2716
|
+
if [[ "$resolved_mode" == "mount" ]]; then
|
|
2717
|
+
info "Current mode is 'mount' — host's ~/.aws is already available in the container"
|
|
2718
|
+
else
|
|
2719
|
+
info "Set DCLAUDE_AWS_CLI=volume to use persistent AWS volume"
|
|
2720
|
+
fi
|
|
2721
|
+
exit 1
|
|
2722
|
+
fi
|
|
2723
|
+
|
|
2724
|
+
echo ""
|
|
2725
|
+
echo "AWS CLI Configuration"
|
|
2726
|
+
echo "─────────────────────"
|
|
2727
|
+
|
|
2728
|
+
# Check existing config in container
|
|
2729
|
+
local existing_config
|
|
2730
|
+
existing_config=$(docker exec -u claude "$container_name" cat /home/claude/.aws/config 2>/dev/null || echo "")
|
|
2731
|
+
|
|
2732
|
+
if [[ -n "$existing_config" ]]; then
|
|
2733
|
+
echo "Current config in container:"
|
|
2734
|
+
echo "$existing_config" | sed 's/^/ /'
|
|
2735
|
+
echo ""
|
|
2736
|
+
read -p "Overwrite with host config? [y/N]: " overwrite
|
|
2737
|
+
if [[ "$overwrite" != "y" && "$overwrite" != "Y" ]]; then
|
|
2738
|
+
exit 0
|
|
2739
|
+
fi
|
|
2740
|
+
fi
|
|
2741
|
+
|
|
2742
|
+
# Check host config
|
|
2743
|
+
if [[ ! -f "${HOME}/.aws/config" ]]; then
|
|
2744
|
+
error "No ~/.aws/config found on host"
|
|
2745
|
+
info "Run 'aws configure sso' on your host first, then re-run this command"
|
|
2746
|
+
exit 1
|
|
2747
|
+
fi
|
|
2748
|
+
|
|
2749
|
+
echo "Found on host:"
|
|
2750
|
+
sed 's/^/ /' "${HOME}/.aws/config"
|
|
2751
|
+
echo ""
|
|
2752
|
+
info "Only config (profiles, regions, SSO URLs) will be copied — no credentials or tokens"
|
|
2753
|
+
echo ""
|
|
2754
|
+
read -p "Copy to container? [Y/n]: " copy
|
|
2755
|
+
if [[ "$copy" == "n" || "$copy" == "N" ]]; then
|
|
2756
|
+
exit 0
|
|
2757
|
+
fi
|
|
2758
|
+
|
|
2759
|
+
# Copy config file into container (docker cp copies as root, fix ownership after)
|
|
2760
|
+
docker exec -u claude "$container_name" mkdir -p /home/claude/.aws
|
|
2761
|
+
docker cp "${HOME}/.aws/config" "${container_name}:/home/claude/.aws/config"
|
|
2762
|
+
docker exec "$container_name" chown claude:claude /home/claude/.aws /home/claude/.aws/config
|
|
2763
|
+
docker exec "$container_name" chmod 600 /home/claude/.aws/config
|
|
2764
|
+
|
|
2765
|
+
echo ""
|
|
2766
|
+
success "AWS config copied to container"
|
|
2767
|
+
info "Run 'dclaude aws login' or 'aws login' inside dclaude to authenticate"
|
|
2768
|
+
exit 0
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
cmd_aws_login() {
|
|
2772
|
+
local container_name="$1"
|
|
2773
|
+
shift
|
|
2774
|
+
|
|
2775
|
+
local tty_flags
|
|
2776
|
+
tty_flags=$(detect_tty_flags)
|
|
2777
|
+
|
|
2778
|
+
info "Starting AWS login..."
|
|
2779
|
+
# shellcheck disable=SC2086
|
|
2780
|
+
exec docker exec $tty_flags -u claude "$container_name" aws login "$@"
|
|
2781
|
+
}
|
|
2782
|
+
|
|
2783
|
+
cmd_aws() {
|
|
2784
|
+
local container_name=$(get_container_name "$HOST_PATH")
|
|
2785
|
+
|
|
2786
|
+
# Check if container exists
|
|
2787
|
+
if ! docker ps -a --format '{{.Names}}' | grep -q "^${container_name}$"; then
|
|
2788
|
+
error "No container found for this directory"
|
|
2789
|
+
info "Run 'dclaude' first to create a container"
|
|
2790
|
+
exit 1
|
|
2791
|
+
fi
|
|
2792
|
+
|
|
2793
|
+
# Check if container is running
|
|
2794
|
+
local container_status=$(docker inspect --format='{{.State.Status}}' "$container_name" 2>/dev/null)
|
|
2795
|
+
if [[ "$container_status" != "running" ]]; then
|
|
2796
|
+
if [[ "$container_status" == "exited" ]]; then
|
|
2797
|
+
info "Starting container: $container_name"
|
|
2798
|
+
docker start "$container_name" >/dev/null
|
|
2799
|
+
sleep 1
|
|
2800
|
+
else
|
|
2801
|
+
error "Container $container_name is in unexpected state: $container_status"
|
|
2802
|
+
exit 1
|
|
2803
|
+
fi
|
|
2804
|
+
fi
|
|
2805
|
+
|
|
2806
|
+
# Dispatch subcommands
|
|
2807
|
+
local subcmd="${1:-}"
|
|
2808
|
+
case "$subcmd" in
|
|
2809
|
+
configure)
|
|
2810
|
+
shift
|
|
2811
|
+
cmd_aws_configure "$container_name" "$@"
|
|
2812
|
+
;;
|
|
2813
|
+
login)
|
|
2814
|
+
shift
|
|
2815
|
+
cmd_aws_login "$container_name" "$@"
|
|
2816
|
+
;;
|
|
2817
|
+
--help|-h|"")
|
|
2818
|
+
echo "Usage:"
|
|
2819
|
+
echo " dclaude aws configure Copy AWS config from host (volume mode)"
|
|
2820
|
+
echo " dclaude aws login Run AWS login in container"
|
|
2821
|
+
echo " dclaude aws login --profile <name> Login with specific profile"
|
|
2822
|
+
exit 0
|
|
2823
|
+
;;
|
|
2824
|
+
*)
|
|
2825
|
+
error "Unknown subcommand: $subcmd"
|
|
2826
|
+
echo "Usage:"
|
|
2827
|
+
echo " dclaude aws configure Copy AWS config from host (volume mode)"
|
|
2828
|
+
echo " dclaude aws login Run AWS login in container"
|
|
2829
|
+
echo " dclaude aws login --profile <name> Login with specific profile"
|
|
2830
|
+
exit 1
|
|
2831
|
+
;;
|
|
2832
|
+
esac
|
|
2833
|
+
}
|
|
2834
|
+
|
|
1992
2835
|
cmd_git() {
|
|
1993
2836
|
local container_name=$(get_container_name "$HOST_PATH")
|
|
1994
2837
|
|
|
@@ -2128,6 +2971,10 @@ if [[ $# -gt 0 ]]; then
|
|
|
2128
2971
|
shift
|
|
2129
2972
|
cmd_git "$@"
|
|
2130
2973
|
;;
|
|
2974
|
+
aws)
|
|
2975
|
+
shift
|
|
2976
|
+
cmd_aws "$@"
|
|
2977
|
+
;;
|
|
2131
2978
|
--help|-h|help)
|
|
2132
2979
|
cat << 'EOF'
|
|
2133
2980
|
dclaude - Dockerized Claude Code Launcher
|
|
@@ -2140,8 +2987,12 @@ Usage:
|
|
|
2140
2987
|
dclaude update Update Claude CLI inside container
|
|
2141
2988
|
dclaude stop Stop container for current directory
|
|
2142
2989
|
dclaude rm [-f] Remove container for current directory
|
|
2143
|
-
dclaude ssh
|
|
2990
|
+
dclaude ssh Load SSH keys and start SSH server
|
|
2991
|
+
dclaude ssh keys Load SSH keys into agent
|
|
2992
|
+
dclaude ssh server Start SSH server for remote access
|
|
2144
2993
|
dclaude git Configure git identity (name/email)
|
|
2994
|
+
dclaude aws configure Copy AWS config from host to container (volume mode)
|
|
2995
|
+
dclaude aws login Run AWS login in container
|
|
2145
2996
|
dclaude chrome [options] Launch Chrome with DevTools and MCP integration
|
|
2146
2997
|
dclaude gh Authenticate GitHub CLI (runs gh auth login)
|
|
2147
2998
|
dclaude exec [command] Execute command in container (default: bash)
|
|
@@ -2156,6 +3007,7 @@ Environment Variables:
|
|
|
2156
3007
|
DCLAUDE_GIT_AUTH SSH auth for Git: auto, agent-forwarding, key-mount, none
|
|
2157
3008
|
DCLAUDE_NETWORK Network mode: auto, host, bridge
|
|
2158
3009
|
DCLAUDE_MOUNT_ROOT Mount parent directory (absolute or relative path)
|
|
3010
|
+
DCLAUDE_AWS_CLI AWS config mode: auto, mount, volume, none
|
|
2159
3011
|
DCLAUDE_DOCKER_SOCKET Override Docker socket path
|
|
2160
3012
|
DCLAUDE_TMUX_SESSION Custom tmux session name (default: claude-TIMESTAMP)
|
|
2161
3013
|
DCLAUDE_CHROME_BIN Chrome executable path (auto-detected if not set)
|
|
@@ -2167,6 +3019,7 @@ Configuration File (.dclaude):
|
|
|
2167
3019
|
Create a .dclaude file at project root to configure dclaude for that tree:
|
|
2168
3020
|
NAMESPACE=mycompany
|
|
2169
3021
|
NETWORK=host
|
|
3022
|
+
AWS_CLI=mount
|
|
2170
3023
|
DEBUG=true
|
|
2171
3024
|
MOUNT_ROOT=. # Mount config file's directory
|
|
2172
3025
|
MOUNT_ROOT=.. # Mount parent of config file's directory
|
|
@@ -2205,9 +3058,11 @@ Examples:
|
|
|
2205
3058
|
dclaude rm # Remove stopped container
|
|
2206
3059
|
dclaude rm -f # Force remove running container
|
|
2207
3060
|
|
|
2208
|
-
# SSH
|
|
2209
|
-
dclaude ssh #
|
|
2210
|
-
dclaude ssh
|
|
3061
|
+
# SSH key and server management
|
|
3062
|
+
dclaude ssh # Load keys + start SSH server
|
|
3063
|
+
dclaude ssh keys # Load SSH keys into agent
|
|
3064
|
+
dclaude ssh server # Start SSH server, show connection info
|
|
3065
|
+
dclaude ssh server --stop # Stop SSH server
|
|
2211
3066
|
|
|
2212
3067
|
# Launch Chrome with DevTools for MCP integration
|
|
2213
3068
|
dclaude chrome
|