@kokorolx/ai-sandbox-wrapper 1.0.0
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 +540 -0
- package/bin/ai-debug +116 -0
- package/bin/ai-network +144 -0
- package/bin/ai-run +631 -0
- package/bin/cli.js +83 -0
- package/bin/setup-ssh-config +328 -0
- package/dockerfiles/AGENTS.md +92 -0
- package/dockerfiles/aider/Dockerfile +5 -0
- package/dockerfiles/amp/Dockerfile +10 -0
- package/dockerfiles/auggie/Dockerfile +12 -0
- package/dockerfiles/base/Dockerfile +73 -0
- package/dockerfiles/claude/Dockerfile +11 -0
- package/dockerfiles/codebuddy/Dockerfile +12 -0
- package/dockerfiles/codex/Dockerfile +9 -0
- package/dockerfiles/droid/Dockerfile +8 -0
- package/dockerfiles/gemini/Dockerfile +9 -0
- package/dockerfiles/jules/Dockerfile +12 -0
- package/dockerfiles/kilo/Dockerfile +25 -0
- package/dockerfiles/opencode/Dockerfile +10 -0
- package/dockerfiles/qoder/Dockerfile +12 -0
- package/dockerfiles/qwen/Dockerfile +10 -0
- package/dockerfiles/shai/Dockerfile +9 -0
- package/lib/AGENTS.md +58 -0
- package/lib/generate-ai-run.sh +19 -0
- package/lib/install-aider.sh +30 -0
- package/lib/install-amp.sh +39 -0
- package/lib/install-auggie.sh +36 -0
- package/lib/install-base.sh +139 -0
- package/lib/install-claude.sh +42 -0
- package/lib/install-codebuddy.sh +36 -0
- package/lib/install-codeserver.sh +171 -0
- package/lib/install-codex.sh +40 -0
- package/lib/install-droid.sh +27 -0
- package/lib/install-gemini.sh +39 -0
- package/lib/install-jules.sh +36 -0
- package/lib/install-kilo.sh +57 -0
- package/lib/install-opencode.sh +39 -0
- package/lib/install-qoder.sh +37 -0
- package/lib/install-qwen.sh +40 -0
- package/lib/install-shai.sh +33 -0
- package/lib/install-tool.sh +40 -0
- package/lib/install-vscode.sh +219 -0
- package/lib/ssh-key-selector.sh +189 -0
- package/package.json +46 -0
- package/setup.sh +530 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const command = args[0];
|
|
9
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
10
|
+
|
|
11
|
+
function showHelp() {
|
|
12
|
+
console.log(`
|
|
13
|
+
🔒 AI Sandbox Wrapper
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
npx @kokorolx/ai-sandbox-wrapper <command>
|
|
17
|
+
|
|
18
|
+
Commands:
|
|
19
|
+
setup Run interactive setup (configure workspaces, select tools)
|
|
20
|
+
help Show this help message
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
npx @kokorolx/ai-sandbox-wrapper setup
|
|
24
|
+
|
|
25
|
+
Documentation: https://github.com/kokorolx/ai-sandbox-wrapper
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function runSetup() {
|
|
30
|
+
const setupScript = path.join(packageRoot, 'setup.sh');
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(setupScript)) {
|
|
33
|
+
console.error('❌ Error: setup.sh not found at', setupScript);
|
|
34
|
+
console.error('This may indicate a corrupted installation.');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
fs.chmodSync(setupScript, '755');
|
|
40
|
+
} catch (err) {
|
|
41
|
+
/* Windows doesn't support chmod */
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const child = spawn('bash', [setupScript], {
|
|
45
|
+
cwd: packageRoot,
|
|
46
|
+
stdio: 'inherit',
|
|
47
|
+
env: {
|
|
48
|
+
...process.env,
|
|
49
|
+
AI_SANDBOX_ROOT: packageRoot
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
child.on('error', (err) => {
|
|
54
|
+
if (err.code === 'ENOENT') {
|
|
55
|
+
console.error('❌ Error: bash not found. Please install bash to run setup.');
|
|
56
|
+
console.error(' macOS/Linux: bash is usually pre-installed');
|
|
57
|
+
console.error(' Windows: Use WSL2 or Git Bash');
|
|
58
|
+
} else {
|
|
59
|
+
console.error('❌ Error running setup:', err.message);
|
|
60
|
+
}
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
child.on('close', (code) => {
|
|
65
|
+
process.exit(code || 0);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
switch (command) {
|
|
70
|
+
case 'setup':
|
|
71
|
+
case undefined:
|
|
72
|
+
runSetup();
|
|
73
|
+
break;
|
|
74
|
+
case 'help':
|
|
75
|
+
case '--help':
|
|
76
|
+
case '-h':
|
|
77
|
+
showHelp();
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
console.error(`❌ Unknown command: ${command}`);
|
|
81
|
+
showHelp();
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Extract SSH config entries needed for this repo's git remotes
|
|
3
|
+
# Also collects and fixes SSH keys for use in containers
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
SSH_CONFIG_FILE="${HOME}/.ssh/config"
|
|
8
|
+
SSH_DIR="${HOME}/.ssh"
|
|
9
|
+
TEMP_CONFIG="/tmp/ssh_config_filtered"
|
|
10
|
+
TEMP_KEYS_DIR="/tmp/ssh_keys_filtered"
|
|
11
|
+
COLLECTED_KEYS=()
|
|
12
|
+
STRICT_MODE=0
|
|
13
|
+
HOSTS=""
|
|
14
|
+
|
|
15
|
+
# Show usage
|
|
16
|
+
usage() {
|
|
17
|
+
cat << EOF
|
|
18
|
+
Usage: $(basename "$0") [OPTIONS]
|
|
19
|
+
|
|
20
|
+
Extract SSH config entries and keys needed for git remotes (or custom hosts).
|
|
21
|
+
|
|
22
|
+
OPTIONS:
|
|
23
|
+
--hosts HOSTS Comma/space-separated list of SSH host aliases to extract
|
|
24
|
+
(default: auto-detect from git remotes)
|
|
25
|
+
--keys KEYS Comma/space-separated list of SSH key filenames (e.g. id_rsa)
|
|
26
|
+
to filter by. Only hosts using these keys will be included.
|
|
27
|
+
--strict Fail if any key file is missing (default: warn and continue)
|
|
28
|
+
--help Show this help message
|
|
29
|
+
|
|
30
|
+
EXAMPLES:
|
|
31
|
+
# Use git remotes (default)
|
|
32
|
+
$(basename "$0")
|
|
33
|
+
|
|
34
|
+
# Extract specific hosts
|
|
35
|
+
$(basename "$0") --hosts github.com-kokorolx,gitlab.com-kokorolee
|
|
36
|
+
|
|
37
|
+
# Filter by specific keys
|
|
38
|
+
$(basename "$0") --keys id_rsa_work,id_ed25519_personal
|
|
39
|
+
|
|
40
|
+
# Fail on missing keys
|
|
41
|
+
$(basename "$0") --strict
|
|
42
|
+
|
|
43
|
+
EOF
|
|
44
|
+
exit "${1:-0}"
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Parse arguments
|
|
48
|
+
ALLOWED_KEYS_INPUT=""
|
|
49
|
+
while [[ $# -gt 0 ]]; do
|
|
50
|
+
case "$1" in
|
|
51
|
+
--hosts)
|
|
52
|
+
HOSTS="$2"
|
|
53
|
+
shift 2
|
|
54
|
+
;;
|
|
55
|
+
--keys)
|
|
56
|
+
ALLOWED_KEYS_INPUT="$2"
|
|
57
|
+
shift 2
|
|
58
|
+
;;
|
|
59
|
+
--strict)
|
|
60
|
+
STRICT_MODE=1
|
|
61
|
+
shift
|
|
62
|
+
;;
|
|
63
|
+
--help|-h)
|
|
64
|
+
usage 0
|
|
65
|
+
;;
|
|
66
|
+
*)
|
|
67
|
+
echo "❌ Unknown option: $1"
|
|
68
|
+
usage 1
|
|
69
|
+
;;
|
|
70
|
+
esac
|
|
71
|
+
done
|
|
72
|
+
|
|
73
|
+
# Validate SSH files exist
|
|
74
|
+
if [ ! -f "$SSH_CONFIG_FILE" ]; then
|
|
75
|
+
echo "❌ Error: SSH config not found at $SSH_CONFIG_FILE"
|
|
76
|
+
exit 1
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
if [ ! -d "$SSH_DIR" ]; then
|
|
80
|
+
echo "❌ Error: SSH directory not found at $SSH_DIR"
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# Normalize allowed keys to array of basenames
|
|
85
|
+
ALLOWED_KEYS=()
|
|
86
|
+
if [ -n "$ALLOWED_KEYS_INPUT" ]; then
|
|
87
|
+
IFS=', ' read -r -a keys_array <<< "$ALLOWED_KEYS_INPUT"
|
|
88
|
+
for k in "${keys_array[@]}"; do
|
|
89
|
+
[ -z "$k" ] && continue
|
|
90
|
+
ALLOWED_KEYS+=("$(basename "$k")")
|
|
91
|
+
done
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Get hosts from keys if provided, otherwise from git remotes if --hosts not specified
|
|
95
|
+
if [ ${#ALLOWED_KEYS[@]} -gt 0 ]; then
|
|
96
|
+
echo "🔑 Filtering by keys: ${ALLOWED_KEYS[*]}"
|
|
97
|
+
|
|
98
|
+
# Find all Host entries that use any of the allowed keys
|
|
99
|
+
# This awk script finds Host blocks and checks if any IdentityFile matches our allowed keys
|
|
100
|
+
MATCHING_HOSTS=$(awk -v keys="${ALLOWED_KEYS[*]}" '
|
|
101
|
+
BEGIN {
|
|
102
|
+
split(keys, k_arr, " ");
|
|
103
|
+
for (i in k_arr) allowed_keys[k_arr[i]] = 1;
|
|
104
|
+
}
|
|
105
|
+
/^Host / {
|
|
106
|
+
if (current_host != "" && host_matches) {
|
|
107
|
+
print current_host
|
|
108
|
+
}
|
|
109
|
+
current_host = $2;
|
|
110
|
+
host_matches = 0;
|
|
111
|
+
}
|
|
112
|
+
/^[[:space:]]*IdentityFile[[:space:]]+/ {
|
|
113
|
+
split($2, path_parts, "/");
|
|
114
|
+
filename = path_parts[length(path_parts)];
|
|
115
|
+
# Remove any trailing comments or quotes
|
|
116
|
+
gsub(/[[:space:]].*$/, "", filename);
|
|
117
|
+
gsub(/^"|"$/, "", filename);
|
|
118
|
+
|
|
119
|
+
if (allowed_keys[filename]) {
|
|
120
|
+
host_matches = 1;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
END {
|
|
124
|
+
if (current_host != "" && host_matches) {
|
|
125
|
+
print current_host
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
' "$SSH_CONFIG_FILE")
|
|
129
|
+
|
|
130
|
+
if [ -z "$HOSTS" ]; then
|
|
131
|
+
HOSTS="$MATCHING_HOSTS"
|
|
132
|
+
fi
|
|
133
|
+
|
|
134
|
+
if [ -z "$HOSTS" ]; then
|
|
135
|
+
echo "⚠️ No Host entries found in SSH config matching the provided keys."
|
|
136
|
+
# We allow it to continue if hosts were explicitly provided, otherwise exit
|
|
137
|
+
if [ -z "$ALLOWED_KEYS_INPUT" ]; then exit 1; fi
|
|
138
|
+
fi
|
|
139
|
+
elif [ -z "$HOSTS" ]; then
|
|
140
|
+
HOSTS=$(git config --get-regexp 'remote\..*\.url' 2>/dev/null | sed -E 's/.*@([^:]+):.*/\1/' | sort -u)
|
|
141
|
+
|
|
142
|
+
if [ -z "$HOSTS" ]; then
|
|
143
|
+
echo "⚠️ No git remotes found with SSH URLs and no --hosts/--keys specified"
|
|
144
|
+
usage 1
|
|
145
|
+
fi
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
# Normalize hosts to newline-separated
|
|
149
|
+
HOSTS=$(echo "$HOSTS" | tr ',' '\n' | tr ' ' '\n' | sort -u | grep -v '^$')
|
|
150
|
+
|
|
151
|
+
echo "📋 Processing hosts:"
|
|
152
|
+
echo "$HOSTS" | sed 's/^/ - /'
|
|
153
|
+
echo ""
|
|
154
|
+
|
|
155
|
+
# Create temp directories
|
|
156
|
+
rm -rf "$TEMP_CONFIG" "$TEMP_KEYS_DIR"
|
|
157
|
+
mkdir -p "$TEMP_KEYS_DIR"
|
|
158
|
+
|
|
159
|
+
# Start with Host * (global settings)
|
|
160
|
+
{
|
|
161
|
+
awk '/^Host \*$/,/^Host [a-zA-Z0-9]/ { if (/^Host [a-zA-Z0-9]/ && !/^Host \*$/) exit; print }' "$SSH_CONFIG_FILE"
|
|
162
|
+
echo ""
|
|
163
|
+
} >> "$TEMP_CONFIG"
|
|
164
|
+
|
|
165
|
+
# Extract Host blocks for each needed host
|
|
166
|
+
failed_keys=0
|
|
167
|
+
while IFS= read -r host; do
|
|
168
|
+
[ -z "$host" ] && continue
|
|
169
|
+
|
|
170
|
+
echo "Processing SSH config for host: $host"
|
|
171
|
+
|
|
172
|
+
# Extract the Host block and filter IdentityFile entries if keys were provided
|
|
173
|
+
# We use awk to:
|
|
174
|
+
# 1. Find the Host block
|
|
175
|
+
# 2. Within that block, if --keys were provided, only keep IdentityFile lines that match
|
|
176
|
+
# 3. If no --keys provided, keep all IdentityFile lines
|
|
177
|
+
awk -v target="$host" -v keys="${ALLOWED_KEYS[*]}" '
|
|
178
|
+
BEGIN {
|
|
179
|
+
use_key_filter = (keys != "");
|
|
180
|
+
split(keys, k_arr, " ");
|
|
181
|
+
for (i in k_arr) allowed_keys[k_arr[i]] = 1;
|
|
182
|
+
}
|
|
183
|
+
/^Host / && $2 == target {
|
|
184
|
+
found=1;
|
|
185
|
+
print;
|
|
186
|
+
next;
|
|
187
|
+
}
|
|
188
|
+
found {
|
|
189
|
+
if (/^Host / && $2 != target) {
|
|
190
|
+
exit;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (/^[[:space:]]*IdentityFile[[:space:]]+/) {
|
|
194
|
+
if (!use_key_filter) {
|
|
195
|
+
print;
|
|
196
|
+
} else {
|
|
197
|
+
# Check if this IdentityFile is allowed
|
|
198
|
+
line = $0;
|
|
199
|
+
split($2, path_parts, "/");
|
|
200
|
+
filename = path_parts[length(path_parts)];
|
|
201
|
+
gsub(/[[:space:]].*$/, "", filename);
|
|
202
|
+
gsub(/^"|"$/, "", filename);
|
|
203
|
+
|
|
204
|
+
if (allowed_keys[filename]) {
|
|
205
|
+
print line;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
print;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
' "$SSH_CONFIG_FILE" >> "$TEMP_CONFIG"
|
|
213
|
+
|
|
214
|
+
# Extract IdentityFile paths to copy
|
|
215
|
+
keys_to_copy=$(awk -v target="$host" -v keys="${ALLOWED_KEYS[*]}" '
|
|
216
|
+
BEGIN {
|
|
217
|
+
use_key_filter = (keys != "");
|
|
218
|
+
split(keys, k_arr, " ");
|
|
219
|
+
for (i in k_arr) allowed_keys[k_arr[i]] = 1;
|
|
220
|
+
}
|
|
221
|
+
/^Host / && $2 == target {
|
|
222
|
+
found=1
|
|
223
|
+
}
|
|
224
|
+
found {
|
|
225
|
+
if (/^Host / && $2 != target) {
|
|
226
|
+
exit
|
|
227
|
+
}
|
|
228
|
+
if (/^[[:space:]]*IdentityFile[[:space:]]+/) {
|
|
229
|
+
orig_line = $0;
|
|
230
|
+
gsub(/^[[:space:]]*IdentityFile[[:space:]]+/, "")
|
|
231
|
+
path = $0
|
|
232
|
+
gsub(/[[:space:]].*$/, "", path); # Remove comments
|
|
233
|
+
gsub(/^"|"$/, "", path); # Remove quotes
|
|
234
|
+
|
|
235
|
+
# Check if allowed if filter is active
|
|
236
|
+
split(path, path_parts, "/");
|
|
237
|
+
filename = path_parts[length(path_parts)];
|
|
238
|
+
|
|
239
|
+
if (!use_key_filter || allowed_keys[filename]) {
|
|
240
|
+
# Expand tilde using shell variable
|
|
241
|
+
if (path ~ /^~/) {
|
|
242
|
+
sub(/^~/, "'"$HOME"'", path)
|
|
243
|
+
}
|
|
244
|
+
print path
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
' "$SSH_CONFIG_FILE")
|
|
249
|
+
|
|
250
|
+
if [ -z "$keys_to_copy" ]; then
|
|
251
|
+
echo " ⚠️ No matching IdentityFile entries found for this host"
|
|
252
|
+
else
|
|
253
|
+
while IFS= read -r keypath; do
|
|
254
|
+
[ -z "$keypath" ] && continue
|
|
255
|
+
|
|
256
|
+
if [ -f "$keypath" ]; then
|
|
257
|
+
COLLECTED_KEYS+=("$keypath")
|
|
258
|
+
echo " ✓ Found key: $keypath"
|
|
259
|
+
else
|
|
260
|
+
# Try relative to SSH_DIR if not absolute and not found
|
|
261
|
+
if [[ "$keypath" != /* ]] && [ -f "$SSH_DIR/$keypath" ]; then
|
|
262
|
+
COLLECTED_KEYS+=("$SSH_DIR/$keypath")
|
|
263
|
+
echo " ✓ Found key: $SSH_DIR/$keypath"
|
|
264
|
+
else
|
|
265
|
+
echo " ✗ Key not found: $keypath"
|
|
266
|
+
((failed_keys++))
|
|
267
|
+
if [ $STRICT_MODE -eq 1 ]; then
|
|
268
|
+
echo "❌ Strict mode: aborting due to missing key"
|
|
269
|
+
exit 1
|
|
270
|
+
fi
|
|
271
|
+
fi
|
|
272
|
+
fi
|
|
273
|
+
done <<< "$keys_to_copy"
|
|
274
|
+
fi
|
|
275
|
+
|
|
276
|
+
echo ""
|
|
277
|
+
done <<< "$HOSTS"
|
|
278
|
+
|
|
279
|
+
# Fix paths in config: change /Users/tamlh or ~ to ~/.ssh for portability
|
|
280
|
+
echo "🔧 Fixing paths in SSH config..."
|
|
281
|
+
# Also normalize any absolute paths to ~/.ssh/ for the container
|
|
282
|
+
sed -i.bak \
|
|
283
|
+
-e "s|IdentityFile /Users/[^/]*/\.ssh/|IdentityFile ~/.ssh/|g" \
|
|
284
|
+
-e "s|IdentityFile /home/[^/]*/\.ssh/|IdentityFile ~/.ssh/|g" \
|
|
285
|
+
-e "s|IdentityFile \./|IdentityFile ~/.ssh/|g" \
|
|
286
|
+
"$TEMP_CONFIG"
|
|
287
|
+
rm -f "$TEMP_CONFIG.bak"
|
|
288
|
+
|
|
289
|
+
echo ""
|
|
290
|
+
echo "📋 Filtered SSH config:"
|
|
291
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
292
|
+
if [ -f "$TEMP_CONFIG" ]; then
|
|
293
|
+
cat "$TEMP_CONFIG"
|
|
294
|
+
else
|
|
295
|
+
echo "(Empty config)"
|
|
296
|
+
fi
|
|
297
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
298
|
+
|
|
299
|
+
if [ ${#COLLECTED_KEYS[@]} -gt 0 ]; then
|
|
300
|
+
# Remove duplicates (bash 3.2 compatible)
|
|
301
|
+
UNIQUE_KEYS=()
|
|
302
|
+
while IFS= read -r key; do
|
|
303
|
+
if [[ -n "$key" ]]; then
|
|
304
|
+
UNIQUE_KEYS+=("$key")
|
|
305
|
+
fi
|
|
306
|
+
done < <(printf '%s\n' "${COLLECTED_KEYS[@]}" | sort -u)
|
|
307
|
+
|
|
308
|
+
echo ""
|
|
309
|
+
echo "🔑 Collected SSH keys (${#UNIQUE_KEYS[@]} total):"
|
|
310
|
+
|
|
311
|
+
for keypath in "${UNIQUE_KEYS[@]}"; do
|
|
312
|
+
filename=$(basename "$keypath")
|
|
313
|
+
cp "$keypath" "$TEMP_KEYS_DIR/$filename"
|
|
314
|
+
echo " ✓ $filename"
|
|
315
|
+
done
|
|
316
|
+
|
|
317
|
+
echo ""
|
|
318
|
+
echo "🚀 Info:"
|
|
319
|
+
echo " Config: $TEMP_CONFIG"
|
|
320
|
+
echo " Keys: $TEMP_KEYS_DIR"
|
|
321
|
+
else
|
|
322
|
+
echo "⚠️ No SSH keys were found!"
|
|
323
|
+
fi
|
|
324
|
+
|
|
325
|
+
if [ $failed_keys -gt 0 ]; then
|
|
326
|
+
echo ""
|
|
327
|
+
echo "⚠️ $failed_keys key(s) were missing (use --strict to fail on these)"
|
|
328
|
+
fi
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Dockerfiles/ - Container Images
|
|
2
|
+
|
|
3
|
+
**Purpose:** Docker container definitions for AI coding tool isolation
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
One Dockerfile per supported AI tool. Each creates a secure, sandboxed environment with only the necessary runtime and access to whitelisted workspaces.
|
|
8
|
+
|
|
9
|
+
## Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
dockerfiles/
|
|
13
|
+
├── base/ # Bun runtime, shared by most tools
|
|
14
|
+
├── claude/ # Claude Code CLI
|
|
15
|
+
├── gemini/ # Google Gemini CLI
|
|
16
|
+
├── aider/ # Aider pair programmer
|
|
17
|
+
├── opencode/ # Open-source AI coding
|
|
18
|
+
├── kilo/ # 500+ model support
|
|
19
|
+
├── codex/ # OpenAI Codex
|
|
20
|
+
├── amp/ # Sourcegraph Amp
|
|
21
|
+
├── qwen/ # Alibaba Qwen (1M context)
|
|
22
|
+
├── droid/ # Factory CLI
|
|
23
|
+
├── jules/ # Google Jules
|
|
24
|
+
├── qoder/ # Qoder AI assistant
|
|
25
|
+
├── auggie/ # Augment Auggie
|
|
26
|
+
├── codebuddy/ # Tencent CodeBuddy
|
|
27
|
+
└── shai/ # OVHcloud SHAI
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Where to Look
|
|
31
|
+
|
|
32
|
+
| Task | Location | Notes |
|
|
33
|
+
|------|----------|-------|
|
|
34
|
+
| Modify base image | `base/Dockerfile` | Bun runtime, shared deps |
|
|
35
|
+
| Tool-specific config | `{tool}/Dockerfile` | Tool install, env setup |
|
|
36
|
+
| Security settings | All Dockerfiles | User, permissions, caps |
|
|
37
|
+
|
|
38
|
+
## Image Patterns
|
|
39
|
+
|
|
40
|
+
**Base Image (base/):**
|
|
41
|
+
- Bun runtime installation
|
|
42
|
+
- Non-root `agent` user
|
|
43
|
+
- Workspace mount point at `/workspace`
|
|
44
|
+
|
|
45
|
+
**Tool Images:**
|
|
46
|
+
- FROM `ai-base:latest`
|
|
47
|
+
- Tool-specific installation
|
|
48
|
+
- Environment variables for API keys
|
|
49
|
+
- Entry point configuration
|
|
50
|
+
|
|
51
|
+
## Security Settings (All Images)
|
|
52
|
+
|
|
53
|
+
```dockerfile
|
|
54
|
+
# Non-root user
|
|
55
|
+
RUN adduser -D agent
|
|
56
|
+
|
|
57
|
+
# Workspace mount
|
|
58
|
+
VOLUME ["/workspace"]
|
|
59
|
+
|
|
60
|
+
# Entry point
|
|
61
|
+
USER agent
|
|
62
|
+
WORKDIR /home/agent
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Image Source Options
|
|
66
|
+
|
|
67
|
+
- **Local build:** `AI_IMAGE_SOURCE=local` (default)
|
|
68
|
+
- **Registry pull:** `AI_IMAGE_SOURCE=registry` (faster)
|
|
69
|
+
|
|
70
|
+
Registry: `registry.gitlab.com/kokorolee/ai-sandbox-wrapper/ai-{tool}:latest`
|
|
71
|
+
|
|
72
|
+
## Platform Support
|
|
73
|
+
|
|
74
|
+
- linux/amd64 (x64)
|
|
75
|
+
- linux/arm64 (Apple Silicon)
|
|
76
|
+
- Configurable via `AI_RUN_PLATFORM` env var
|
|
77
|
+
|
|
78
|
+
## Host Access
|
|
79
|
+
|
|
80
|
+
**Accessing host services from containers:**
|
|
81
|
+
|
|
82
|
+
When joining Docker networks (e.g., MetaMCP), containers can access host services via `host.docker.internal`:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Inside container, access MetaMCP at port 12008
|
|
86
|
+
curl http://host.docker.internal:12008/metamcp/default/sse
|
|
87
|
+
|
|
88
|
+
# Access PostgreSQL on host at port 5432
|
|
89
|
+
psql -h host.docker.internal -p 5432 -U myuser mydb
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Configuration:** See [METAMCP_GUIDE.md](../METAMCP_GUIDE.md) for detailed examples.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
FROM ai-base:latest
|
|
2
|
+
USER root
|
|
3
|
+
# Install Amp globally into a persistent directory (not shadowed by home)
|
|
4
|
+
RUN mkdir -p /usr/local/lib/amp && \
|
|
5
|
+
cd /usr/local/lib/amp && \
|
|
6
|
+
bun init -y && \
|
|
7
|
+
bun add @sourcegraph/amp && \
|
|
8
|
+
ln -s /usr/local/lib/amp/node_modules/.bin/amp /usr/local/bin/amp
|
|
9
|
+
USER agent
|
|
10
|
+
ENTRYPOINT ["amp"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
FROM ai-base:latest
|
|
2
|
+
USER root
|
|
3
|
+
|
|
4
|
+
# Install Auggie CLI to a non-shadowed path
|
|
5
|
+
RUN mkdir -p /usr/local/lib/auggie && \
|
|
6
|
+
cd /usr/local/lib/auggie && \
|
|
7
|
+
bun init -y && \
|
|
8
|
+
bun add @augmentcode/auggie && \
|
|
9
|
+
ln -s /usr/local/lib/auggie/node_modules/.bin/auggie /usr/local/bin/auggie
|
|
10
|
+
|
|
11
|
+
USER agent
|
|
12
|
+
ENTRYPOINT ["auggie"]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
FROM oven/bun:latest
|
|
2
|
+
|
|
3
|
+
# Install common dependencies (Bun + Node.js + Python for full package manager support)
|
|
4
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
5
|
+
git \
|
|
6
|
+
curl \
|
|
7
|
+
ssh \
|
|
8
|
+
ca-certificates \
|
|
9
|
+
python3 \
|
|
10
|
+
python3-pip \
|
|
11
|
+
python3-venv \
|
|
12
|
+
python3-dev \
|
|
13
|
+
python3-setuptools \
|
|
14
|
+
build-essential \
|
|
15
|
+
libopenblas-dev \
|
|
16
|
+
pipx \
|
|
17
|
+
&& curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh \
|
|
18
|
+
&& rm -rf /var/lib/apt/lists/* \
|
|
19
|
+
&& pipx ensurepath
|
|
20
|
+
|
|
21
|
+
# Install Node.js LTS (includes npm) via NodeSource
|
|
22
|
+
RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \
|
|
23
|
+
&& apt-get install -y nodejs \
|
|
24
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
25
|
+
|
|
26
|
+
# Install pnpm globally via npm
|
|
27
|
+
RUN npm install -g pnpm
|
|
28
|
+
|
|
29
|
+
# Verify installations
|
|
30
|
+
RUN node --version && npm --version && pnpm --version && bun --version
|
|
31
|
+
|
|
32
|
+
# Install additional tools (if selected)
|
|
33
|
+
RUN pipx install specify-cli --pip-args="git+https://github.com/github/spec-kit.git"
|
|
34
|
+
RUN bun install -g uipro-cli
|
|
35
|
+
RUN mkdir -p /usr/local/lib/openspec && \
|
|
36
|
+
cd /usr/local/lib/openspec && \
|
|
37
|
+
bun init -y && \
|
|
38
|
+
bun add @fission-ai/openspec && \
|
|
39
|
+
ln -sf /usr/local/lib/openspec/node_modules/.bin/openspec /usr/local/bin/openspec
|
|
40
|
+
# Install Playwright system dependencies
|
|
41
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
42
|
+
libglib2.0-0 \
|
|
43
|
+
libnspr4 \
|
|
44
|
+
libnss3 \
|
|
45
|
+
libdbus-1-3 \
|
|
46
|
+
libatk1.0-0 \
|
|
47
|
+
libatk-bridge2.0-0 \
|
|
48
|
+
libcups2 \
|
|
49
|
+
libxcb1 \
|
|
50
|
+
libxkbcommon0 \
|
|
51
|
+
libatspi2.0-0 \
|
|
52
|
+
libx11-6 \
|
|
53
|
+
libxcomposite1 \
|
|
54
|
+
libxdamage1 \
|
|
55
|
+
libxext6 \
|
|
56
|
+
libxfixes3 \
|
|
57
|
+
libxrandr2 \
|
|
58
|
+
libgbm1 \
|
|
59
|
+
libcairo2 \
|
|
60
|
+
libpango-1.0-0 \
|
|
61
|
+
libasound2 \
|
|
62
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
63
|
+
# Install Playwright and browsers via npm (avoids pnpm global bin issues)
|
|
64
|
+
RUN npm install -g playwright && npx playwright install
|
|
65
|
+
|
|
66
|
+
# Create workspace
|
|
67
|
+
WORKDIR /workspace
|
|
68
|
+
|
|
69
|
+
# Non-root user for security
|
|
70
|
+
RUN useradd -m -u 1001 -d /home/agent agent && \
|
|
71
|
+
chown -R agent:agent /workspace
|
|
72
|
+
USER agent
|
|
73
|
+
ENV HOME=/home/agent
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
FROM ai-base:latest
|
|
2
|
+
|
|
3
|
+
USER root
|
|
4
|
+
# Install Claude Code using official native installer
|
|
5
|
+
RUN curl -fsSL https://claude.ai/install.sh | bash && \
|
|
6
|
+
mkdir -p /usr/local/share && \
|
|
7
|
+
mv /home/agent/.local/share/claude /usr/local/share/claude && \
|
|
8
|
+
ln -sf /usr/local/share/claude/versions/$(ls /usr/local/share/claude/versions | head -1) /usr/local/bin/claude
|
|
9
|
+
|
|
10
|
+
USER agent
|
|
11
|
+
ENTRYPOINT ["claude"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
FROM ai-base:latest
|
|
2
|
+
USER root
|
|
3
|
+
|
|
4
|
+
# Install CodeBuddy CLI to a non-shadowed path
|
|
5
|
+
RUN mkdir -p /usr/local/lib/codebuddy && \
|
|
6
|
+
cd /usr/local/lib/codebuddy && \
|
|
7
|
+
bun init -y && \
|
|
8
|
+
bun add @tencent-ai/codebuddy-code && \
|
|
9
|
+
ln -s /usr/local/lib/codebuddy/node_modules/.bin/codebuddy /usr/local/bin/codebuddy
|
|
10
|
+
|
|
11
|
+
USER agent
|
|
12
|
+
ENTRYPOINT ["codebuddy"]
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
FROM ai-base:latest
|
|
2
|
+
USER root
|
|
3
|
+
RUN mkdir -p /home/agent/.factory && \
|
|
4
|
+
bash -c "curl -fsSL https://app.factory.ai/cli | sh" && \
|
|
5
|
+
mv /home/agent/.local/bin/droid /usr/local/bin/droid && \
|
|
6
|
+
chown -R agent:agent /home/agent/.factory
|
|
7
|
+
USER agent
|
|
8
|
+
ENTRYPOINT ["bash", "-c", "exec droid \"$@\"", "--"]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
FROM ai-base:latest
|
|
2
|
+
USER root
|
|
3
|
+
RUN mkdir -p /usr/local/lib/gemini && \
|
|
4
|
+
cd /usr/local/lib/gemini && \
|
|
5
|
+
bun init -y && \
|
|
6
|
+
bun add @google/gemini-cli && \
|
|
7
|
+
ln -s /usr/local/lib/gemini/node_modules/.bin/gemini /usr/local/bin/gemini
|
|
8
|
+
USER agent
|
|
9
|
+
ENTRYPOINT ["gemini"]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
FROM ai-base:latest
|
|
2
|
+
USER root
|
|
3
|
+
|
|
4
|
+
# Install Jules CLI to a non-shadowed path
|
|
5
|
+
RUN mkdir -p /usr/local/lib/jules && \
|
|
6
|
+
cd /usr/local/lib/jules && \
|
|
7
|
+
bun init -y && \
|
|
8
|
+
bun add @google/jules && \
|
|
9
|
+
ln -s /usr/local/lib/jules/node_modules/.bin/jules /usr/local/bin/jules
|
|
10
|
+
|
|
11
|
+
USER agent
|
|
12
|
+
ENTRYPOINT ["jules"]
|