@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.
Files changed (45) hide show
  1. package/README.md +540 -0
  2. package/bin/ai-debug +116 -0
  3. package/bin/ai-network +144 -0
  4. package/bin/ai-run +631 -0
  5. package/bin/cli.js +83 -0
  6. package/bin/setup-ssh-config +328 -0
  7. package/dockerfiles/AGENTS.md +92 -0
  8. package/dockerfiles/aider/Dockerfile +5 -0
  9. package/dockerfiles/amp/Dockerfile +10 -0
  10. package/dockerfiles/auggie/Dockerfile +12 -0
  11. package/dockerfiles/base/Dockerfile +73 -0
  12. package/dockerfiles/claude/Dockerfile +11 -0
  13. package/dockerfiles/codebuddy/Dockerfile +12 -0
  14. package/dockerfiles/codex/Dockerfile +9 -0
  15. package/dockerfiles/droid/Dockerfile +8 -0
  16. package/dockerfiles/gemini/Dockerfile +9 -0
  17. package/dockerfiles/jules/Dockerfile +12 -0
  18. package/dockerfiles/kilo/Dockerfile +25 -0
  19. package/dockerfiles/opencode/Dockerfile +10 -0
  20. package/dockerfiles/qoder/Dockerfile +12 -0
  21. package/dockerfiles/qwen/Dockerfile +10 -0
  22. package/dockerfiles/shai/Dockerfile +9 -0
  23. package/lib/AGENTS.md +58 -0
  24. package/lib/generate-ai-run.sh +19 -0
  25. package/lib/install-aider.sh +30 -0
  26. package/lib/install-amp.sh +39 -0
  27. package/lib/install-auggie.sh +36 -0
  28. package/lib/install-base.sh +139 -0
  29. package/lib/install-claude.sh +42 -0
  30. package/lib/install-codebuddy.sh +36 -0
  31. package/lib/install-codeserver.sh +171 -0
  32. package/lib/install-codex.sh +40 -0
  33. package/lib/install-droid.sh +27 -0
  34. package/lib/install-gemini.sh +39 -0
  35. package/lib/install-jules.sh +36 -0
  36. package/lib/install-kilo.sh +57 -0
  37. package/lib/install-opencode.sh +39 -0
  38. package/lib/install-qoder.sh +37 -0
  39. package/lib/install-qwen.sh +40 -0
  40. package/lib/install-shai.sh +33 -0
  41. package/lib/install-tool.sh +40 -0
  42. package/lib/install-vscode.sh +219 -0
  43. package/lib/ssh-key-selector.sh +189 -0
  44. package/package.json +46 -0
  45. package/setup.sh +530 -0
package/setup.sh ADDED
@@ -0,0 +1,530 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+
4
+ # Interactive multi-select menu
5
+ # Usage: multi_select "title" "comma,separated,options" "comma,separated,descriptions"
6
+ # Returns: SELECTED_ITEMS as an array
7
+ multi_select() {
8
+ local title="$1"
9
+ IFS=',' read -ra options <<< "$2"
10
+ IFS=',' read -ra descriptions <<< "$3"
11
+ local cursor=0
12
+ local selected=()
13
+ for ((i=0; i<${#options[@]}; i++)); do selected[i]=0; done
14
+
15
+ # Use tput for better terminal control
16
+ tput civis # Hide cursor
17
+ trap 'tput cnorm; exit' INT TERM # Show cursor on exit
18
+
19
+ while true; do
20
+ clear
21
+ echo "🚀 $title"
22
+ echo "Use ARROWS to move, SPACE to toggle, ENTER to confirm"
23
+ echo ""
24
+
25
+ for i in "${!options[@]}"; do
26
+ if [ "$i" -eq "$cursor" ]; then
27
+ prefix="➔ "
28
+ tput setaf 6 # Cyan
29
+ else
30
+ prefix=" "
31
+ fi
32
+
33
+ if [ "${selected[$i]}" -eq 1 ]; then
34
+ check="[x]"
35
+ tput setaf 2 # Green
36
+ else
37
+ check="[ ]"
38
+ fi
39
+
40
+ printf "%s %s %-12s - %s\n" "$prefix" "$check" "${options[$i]}" "${descriptions[$i]}"
41
+ tput sgr0 # Reset colors
42
+ done
43
+
44
+ # Handle input
45
+ IFS= read -rsn1 key
46
+
47
+ # Handle escape sequences for arrows
48
+ if [[ "$key" == $'\x1b' ]]; then
49
+ # Read next two chars of the escape sequence
50
+ read -rsn1 -t 1 next1
51
+ read -rsn1 -t 1 next2
52
+ case "$next1$next2" in
53
+ '[A') ((cursor--)) ;; # Up
54
+ '[B') ((cursor++)) ;; # Down
55
+ esac
56
+ else
57
+ case "$key" in
58
+ k) ((cursor--)) ;; # k for Up
59
+ j) ((cursor++)) ;; # j for Down
60
+ " ") # Space (toggle)
61
+ if [ "${selected[$cursor]}" -eq 1 ]; then
62
+ selected[$cursor]=0
63
+ else
64
+ selected[$cursor]=1
65
+ fi
66
+ ;;
67
+ "") # Enter (newline/carriage return/empty string)
68
+ break
69
+ ;;
70
+ $'\n'|$'\r') # Extra safety for different enter signals
71
+ break
72
+ ;;
73
+ esac
74
+ fi
75
+
76
+ # Keep cursor in bounds
77
+ if [ "$cursor" -lt 0 ]; then cursor=$((${#options[@]} - 1)); fi
78
+ if [ "$cursor" -ge "${#options[@]}" ]; then cursor=0; fi
79
+ done
80
+
81
+ tput cnorm # Show cursor
82
+
83
+ # Prepare result
84
+ SELECTED_ITEMS=()
85
+ for i in "${!options[@]}"; do
86
+ if [ "${selected[$i]}" -eq 1 ]; then
87
+ SELECTED_ITEMS+=("${options[$i]}")
88
+ fi
89
+ done
90
+ }
91
+
92
+ # Interactive single-select menu
93
+ # Usage: single_select "title" "comma,separated,options" "comma,separated,descriptions"
94
+ # Returns: SELECTED_ITEM as a string
95
+ single_select() {
96
+ local title="$1"
97
+ IFS=',' read -ra options <<< "$2"
98
+ IFS=',' read -ra descriptions <<< "$3"
99
+ local cursor=0
100
+
101
+ tput civis # Hide cursor
102
+ trap 'tput cnorm; exit' INT TERM
103
+
104
+ while true; do
105
+ clear
106
+ echo "🚀 $title"
107
+ echo "Use ARROWS to move, ENTER to select"
108
+ echo ""
109
+
110
+ for i in "${!options[@]}"; do
111
+ if [ "$i" -eq "$cursor" ]; then
112
+ prefix="➔ "
113
+ tput setaf 6 # Cyan
114
+ else
115
+ prefix=" "
116
+ fi
117
+
118
+ printf "%s %-12s - %s\n" "$prefix" "${options[$i]}" "${descriptions[$i]}"
119
+ tput sgr0
120
+ done
121
+
122
+ IFS= read -rsn1 key
123
+ if [[ "$key" == $'\x1b' ]]; then
124
+ read -rsn1 -t 1 next1
125
+ read -rsn1 -t 1 next2
126
+ case "$next1$next2" in
127
+ '[A') ((cursor--)) ;;
128
+ '[B') ((cursor++)) ;;
129
+ esac
130
+ else
131
+ case "$key" in
132
+ k) ((cursor--)) ;;
133
+ j) ((cursor++)) ;;
134
+ "") break ;;
135
+ $'\n'|$'\r') break ;;
136
+ esac
137
+ fi
138
+
139
+ if [ "$cursor" -lt 0 ]; then cursor=$((${#options[@]} - 1)); fi
140
+ if [ "$cursor" -ge "${#options[@]}" ]; then cursor=0; fi
141
+ done
142
+
143
+ tput cnorm
144
+ SELECTED_ITEM="${options[$cursor]}"
145
+ }
146
+
147
+ # Check and install dependencies
148
+ echo "Checking and installing dependencies..."
149
+
150
+ if ! command -v git &> /dev/null; then
151
+ echo "Installing git..."
152
+ apt-get update && apt-get install -y git
153
+ fi
154
+
155
+ if ! command -v python3 &> /dev/null; then
156
+ echo "Installing python3..."
157
+ apt-get install -y python3 python3-pip
158
+ fi
159
+
160
+ # Check for Docker
161
+ if ! command -v docker &> /dev/null; then
162
+ echo "❌ Docker not found. Please install Docker Desktop first."
163
+ exit 1
164
+ fi
165
+
166
+ echo "🚀 AI Sandbox Setup (Docker Desktop + Node 22 LTS)"
167
+
168
+ WORKSPACES_FILE="$HOME/.ai-workspaces"
169
+
170
+ # Handle whitelisted workspaces
171
+ WORKSPACES=()
172
+
173
+ if [[ -f "$WORKSPACES_FILE" ]]; then
174
+ echo "Existing whitelisted workspaces found:"
175
+ while IFS= read -r line; do
176
+ if [[ -n "$line" ]]; then
177
+ echo " - $line"
178
+ WORKSPACES+=("$line")
179
+ fi
180
+ done < "$WORKSPACES_FILE"
181
+
182
+ echo ""
183
+ single_select "Configure Workspaces" "reuse,add,replace" "Keep existing whitelisted folders,Append new folders to the list,Start fresh with new folders"
184
+
185
+ case "$SELECTED_ITEM" in
186
+ add)
187
+ echo "Enter additional workspace directories (comma-separated):"
188
+ read -p "Add Workspaces: " WORKSPACE_INPUT
189
+ ;;
190
+ replace)
191
+ WORKSPACES=()
192
+ echo "Enter new workspace directories (comma-separated):"
193
+ read -p "Workspaces: " WORKSPACE_INPUT
194
+ ;;
195
+ *)
196
+ WORKSPACE_INPUT=""
197
+ ;;
198
+ esac
199
+ else
200
+ echo "Enter workspace directories to whitelist (comma-separated):"
201
+ echo "Example: $HOME/projects, $HOME/code, /opt/work"
202
+ read -p "Workspaces: " WORKSPACE_INPUT
203
+ fi
204
+
205
+ # Parse and validate new workspaces if provided
206
+ if [[ -n "$WORKSPACE_INPUT" ]]; then
207
+ IFS=',' read -ra WORKSPACE_ARRAY <<< "$WORKSPACE_INPUT"
208
+ for ws in "${WORKSPACE_ARRAY[@]}"; do
209
+ ws=$(echo "$ws" | xargs) # trim whitespace
210
+ ws="${ws/#\~/$HOME}" # expand ~ to $HOME
211
+ if [[ -n "$ws" ]]; then
212
+ mkdir -p "$ws"
213
+ # Avoid duplicates
214
+ if [[ ! " ${WORKSPACES[*]} " =~ " ${ws} " ]]; then
215
+ WORKSPACES+=("$ws")
216
+ fi
217
+ fi
218
+ done
219
+ fi
220
+
221
+ if [[ ${#WORKSPACES[@]} -eq 0 ]]; then
222
+ echo "❌ No valid workspaces provided"
223
+ exit 1
224
+ fi
225
+
226
+ # Save workspaces to config file
227
+ printf "%s\n" "${WORKSPACES[@]}" > "$WORKSPACES_FILE"
228
+ chmod 600 "$WORKSPACES_FILE"
229
+ echo "📁 Whitelisted workspaces saved to: $WORKSPACES_FILE"
230
+
231
+ # Use first workspace as default for backwards compatibility
232
+ WORKSPACE="${WORKSPACES[0]}"
233
+
234
+ # Network configuration for Docker network access
235
+ echo ""
236
+ echo "🔗 Docker Network Configuration"
237
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
238
+ echo "You can configure AI tools to join existing Docker networks"
239
+ echo "(e.g., for MetaMCP services, databases, or other containers)."
240
+ echo ""
241
+
242
+ # Check for existing MetaMCP network
243
+ if docker network inspect "metamcp_metamcp-network" >/dev/null 2>&1; then
244
+ echo "✅ Found existing network: metamcp_metamcp-network"
245
+ echo ""
246
+
247
+ # Use single_select for interactive choice
248
+ single_select "MetaMCP Access Method" "join,host-only" "Join network (container-to-container communication),Use host.docker.internal (host access)"
249
+
250
+ case "$SELECTED_ITEM" in
251
+ join)
252
+ NETWORK_FILE="$HOME/.ai-networks"
253
+ echo "metamcp_metamcp-network" >> "$NETWORK_FILE"
254
+ chmod 600 "$NETWORK_FILE"
255
+ echo ""
256
+ echo "✅ Network joined. Both host.docker.internal and MetaMCP network enabled."
257
+ ;;
258
+ host-only|"")
259
+ echo ""
260
+ echo "ℹ️ Using host.docker.internal only. MetaMCP accessible at localhost:12008 on host."
261
+ ;;
262
+ esac
263
+ else
264
+ echo "No existing MetaMCP network detected."
265
+ echo ""
266
+ echo "You have two options:"
267
+ echo ""
268
+ echo "Option 1: Use host.docker.internal (recommended for most cases)"
269
+ echo " MetaMCP at localhost:12008 on your host machine"
270
+ echo " Already enabled by default"
271
+ echo ""
272
+ echo "Option 2: Join a Docker network"
273
+ echo " For container-to-container communication"
274
+ echo ""
275
+ read -p "Enter a Docker network name to join (leave empty to skip): " network_name
276
+
277
+ if [[ -n "$network_name" ]]; then
278
+ if docker network inspect "$network_name" >/dev/null 2>&1; then
279
+ NETWORK_FILE="$HOME/.ai-networks"
280
+ echo "$network_name" >> "$NETWORK_FILE"
281
+ chmod 600 "$NETWORK_FILE"
282
+ echo "✅ Network '$network_name' saved"
283
+ else
284
+ echo "⚠️ Network '$network_name' not found. Skipping."
285
+ fi
286
+ else
287
+ echo "ℹ️ Skipped. host.docker.internal is enabled for host access."
288
+ fi
289
+ fi
290
+
291
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
292
+ echo ""
293
+
294
+ # Tool definitions
295
+ TOOL_OPTIONS="amp,opencode,droid,claude,gemini,kilo,qwen,codex,qoder,auggie,codebuddy,jules,shai,vscode,codeserver"
296
+ TOOL_DESCS="AI coding assistant from @sourcegraph/amp,Open-source coding tool from opencode-ai,Factory CLI from factory.ai,Claude Code CLI from Anthropic,Google Gemini CLI (free tier),AI pair programmer (Git-native),Kilo Code (500+ models),Alibaba Qwen CLI (1M context),OpenAI Codex terminal agent,Qoder AI CLI assistant,Augment Auggie CLI,Tencent CodeBuddy CLI,Google Jules CLI,OVHcloud SHAI agent,VSCode Desktop in Docker (X11),VSCode in browser (fast)"
297
+
298
+ # Interactive multi-select
299
+ multi_select "Select AI Tools to Install" "$TOOL_OPTIONS" "$TOOL_DESCS"
300
+ TOOLS=("${SELECTED_ITEMS[@]}")
301
+
302
+ if [[ ${#TOOLS[@]} -eq 0 ]]; then
303
+ echo "❌ No tools selected for installation"
304
+ exit 0
305
+ fi
306
+
307
+ echo "Installing tools: ${TOOLS[*]}"
308
+
309
+ CONTAINERIZED_TOOLS=()
310
+ for tool in "${TOOLS[@]}"; do
311
+ if [[ "$tool" =~ ^(amp|opencode|claude|aider|droid|gemini|kilo|qwen|codex|qoder|auggie|codebuddy|jules|shai)$ ]]; then
312
+ CONTAINERIZED_TOOLS+=("$tool")
313
+ fi
314
+ done
315
+
316
+ echo ""
317
+ if [[ ${#CONTAINERIZED_TOOLS[@]} -gt 0 ]]; then
318
+ ADDITIONAL_TOOL_OPTIONS="spec-kit,ux-ui-promax,openspec,playwright,ruby"
319
+ ADDITIONAL_TOOL_DESCS="Spec-driven development toolkit,UI/UX design intelligence tool,OpenSpec - spec-driven development,Playwright browser automation (adds ~500MB),Ruby 3.3.0 + Rails 8.0.2 (adds ~500MB)"
320
+
321
+ multi_select "Select Additional Tools (installed in containers)" "$ADDITIONAL_TOOL_OPTIONS" "$ADDITIONAL_TOOL_DESCS"
322
+ ADDITIONAL_TOOLS=("${SELECTED_ITEMS[@]}")
323
+
324
+ if [[ ${#ADDITIONAL_TOOLS[@]} -gt 0 ]]; then
325
+ echo "Additional tools selected: ${ADDITIONAL_TOOLS[*]}"
326
+ fi
327
+ else
328
+ ADDITIONAL_TOOLS=()
329
+ echo "ℹ️ No containerized AI tools selected. Skipping additional tools."
330
+ fi
331
+
332
+ mkdir -p "$WORKSPACE"
333
+ mkdir -p "$HOME/bin"
334
+
335
+ # Secrets
336
+ ENV_FILE="$HOME/.ai-env"
337
+ if [ ! -f "$ENV_FILE" ]; then
338
+ cat <<EOF > "$ENV_FILE"
339
+ OPENAI_API_KEY=[REDACTED:api-key]
340
+ ANTHROPIC_API_KEY=[REDACTED:api-key]
341
+ EOF
342
+ chmod 600 "$ENV_FILE"
343
+ echo "⚠️ Edit $ENV_FILE with your real API keys"
344
+ fi
345
+
346
+ # Get script directory (supports both direct execution and npx)
347
+ if [[ -n "$AI_SANDBOX_ROOT" ]]; then
348
+ SCRIPT_DIR="$AI_SANDBOX_ROOT"
349
+ else
350
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
351
+ fi
352
+
353
+ # Install base image if any containerized tools selected (vscode doesn't need it)
354
+ NEEDS_BASE_IMAGE=0
355
+ for tool in "${TOOLS[@]}"; do
356
+ if [[ "$tool" =~ ^(amp|opencode|claude|aider)$ ]]; then
357
+ NEEDS_BASE_IMAGE=1
358
+ break
359
+ fi
360
+ done
361
+
362
+ if [[ $NEEDS_BASE_IMAGE -eq 1 ]]; then
363
+ INSTALL_SPEC_KIT=0
364
+ INSTALL_UX_UI_PROMAX=0
365
+ INSTALL_OPENSPEC=0
366
+ INSTALL_PLAYWRIGHT=0
367
+ INSTALL_RUBY=0
368
+
369
+ for addon in "${ADDITIONAL_TOOLS[@]}"; do
370
+ case "$addon" in
371
+ spec-kit)
372
+ INSTALL_SPEC_KIT=1
373
+ ;;
374
+ ux-ui-promax)
375
+ INSTALL_UX_UI_PROMAX=1
376
+ ;;
377
+ openspec)
378
+ INSTALL_OPENSPEC=1
379
+ ;;
380
+ playwright)
381
+ INSTALL_PLAYWRIGHT=1
382
+ ;;
383
+ ruby)
384
+ INSTALL_RUBY=1
385
+ ;;
386
+ esac
387
+ done
388
+
389
+ export INSTALL_SPEC_KIT INSTALL_UX_UI_PROMAX INSTALL_OPENSPEC INSTALL_PLAYWRIGHT INSTALL_RUBY
390
+ bash "$SCRIPT_DIR/lib/install-base.sh"
391
+ fi
392
+
393
+ # Install selected tools
394
+ for tool in "${TOOLS[@]}"; do
395
+ case $tool in
396
+ amp)
397
+ bash "$SCRIPT_DIR/lib/install-amp.sh"
398
+ ;;
399
+ opencode)
400
+ bash "$SCRIPT_DIR/lib/install-opencode.sh"
401
+ ;;
402
+ droid)
403
+ bash "$SCRIPT_DIR/lib/install-droid.sh"
404
+ ;;
405
+ claude)
406
+ bash "$SCRIPT_DIR/lib/install-claude.sh"
407
+ ;;
408
+ gemini)
409
+ bash "$SCRIPT_DIR/lib/install-gemini.sh"
410
+ ;;
411
+ aider)
412
+ bash "$SCRIPT_DIR/lib/install-aider.sh"
413
+ ;;
414
+ kilo)
415
+ bash "$SCRIPT_DIR/lib/install-kilo.sh"
416
+ ;;
417
+ qwen)
418
+ bash "$SCRIPT_DIR/lib/install-qwen.sh"
419
+ ;;
420
+ codex)
421
+ bash "$SCRIPT_DIR/lib/install-codex.sh"
422
+ ;;
423
+ qoder)
424
+ bash "$SCRIPT_DIR/lib/install-qoder.sh"
425
+ ;;
426
+ auggie)
427
+ bash "$SCRIPT_DIR/lib/install-auggie.sh"
428
+ ;;
429
+ codebuddy)
430
+ bash "$SCRIPT_DIR/lib/install-codebuddy.sh"
431
+ ;;
432
+ jules)
433
+ bash "$SCRIPT_DIR/lib/install-jules.sh"
434
+ ;;
435
+ shai)
436
+ bash "$SCRIPT_DIR/lib/install-shai.sh"
437
+ ;;
438
+ vscode)
439
+ bash "$SCRIPT_DIR/lib/install-vscode.sh"
440
+ ;;
441
+ codeserver)
442
+ bash "$SCRIPT_DIR/lib/install-codeserver.sh"
443
+ ;;
444
+ esac
445
+ done
446
+
447
+ # Additional tools are installed in base image (no host installation)
448
+
449
+ # Generate ai-run wrapper
450
+ bash "$SCRIPT_DIR/lib/generate-ai-run.sh"
451
+
452
+ # PATH + aliases
453
+ SHELL_RC="$HOME/.zshrc"
454
+
455
+ # Add PATH if not already present
456
+ if ! grep -q 'export PATH="\$HOME/bin:\$PATH"' "$SHELL_RC" 2>/dev/null; then
457
+ echo "export PATH=\"\$HOME/bin:\$PATH\"" >> "$SHELL_RC"
458
+ fi
459
+
460
+ # Add aliases for each tool (only if not already present)
461
+ for tool in "${TOOLS[@]}"; do
462
+ if [[ "$tool" == "vscode" ]]; then
463
+ if ! grep -q "alias vscode=" "$SHELL_RC" 2>/dev/null; then
464
+ echo "alias vscode='vscode-run'" >> "$SHELL_RC"
465
+ fi
466
+ elif [[ "$tool" == "codeserver" ]]; then
467
+ if ! grep -q "alias codeserver=" "$SHELL_RC" 2>/dev/null; then
468
+ echo "alias codeserver='codeserver-run'" >> "$SHELL_RC"
469
+ fi
470
+ else
471
+ if ! grep -q "alias $tool=" "$SHELL_RC" 2>/dev/null; then
472
+ echo "alias $tool=\"ai-run $tool\"" >> "$SHELL_RC"
473
+ fi
474
+ fi
475
+ done
476
+
477
+ # Additional tools don't need host aliases (only in containers)
478
+
479
+ echo ""
480
+ echo "✅ Setup complete!"
481
+ echo ""
482
+ echo "🛠️ Installed AI tools:"
483
+ for tool in "${TOOLS[@]}"; do
484
+ if [[ "$tool" == "vscode" ]]; then
485
+ echo " vscode-run (or: vscode) - Desktop VSCode via X11"
486
+ elif [[ "$tool" == "codeserver" ]]; then
487
+ echo " codeserver-run (or: codeserver) - Browser VSCode at localhost:8080"
488
+ else
489
+ echo " ai-run $tool (or: $tool)"
490
+ fi
491
+ done
492
+
493
+ if [[ ${#ADDITIONAL_TOOLS[@]} -gt 0 ]]; then
494
+ echo ""
495
+ echo "🔧 Additional tools (available inside all containerized AI tools):"
496
+ for addon in "${ADDITIONAL_TOOLS[@]}"; do
497
+ case $addon in
498
+ spec-kit)
499
+ echo " specify - Spec-driven development toolkit"
500
+ ;;
501
+ ux-ui-promax)
502
+ echo " uipro - UI/UX design intelligence tool"
503
+ ;;
504
+ openspec)
505
+ echo " openspec - OpenSpec CLI for spec-driven development"
506
+ ;;
507
+ esac
508
+ done
509
+ fi
510
+ echo ""
511
+ echo "➡ Restart terminal or run: source ~/.zshrc"
512
+ echo "➡ Add API keys to: $ENV_FILE"
513
+ echo ""
514
+ echo "📁 Whitelisted workspaces:"
515
+ for ws in "${WORKSPACES[@]}"; do
516
+ echo " $ws"
517
+ done
518
+ echo ""
519
+ echo "💡 Manage workspaces in: $WORKSPACES_FILE"
520
+ echo " Add folder: echo '/path/to/folder' >> $WORKSPACES_FILE"
521
+ echo " Remove folder: Edit $WORKSPACES_FILE and delete the line"
522
+ echo " List folders: cat $WORKSPACES_FILE"
523
+ echo ""
524
+ echo "📁 Per-project configs supported:"
525
+ for tool in "${TOOLS[@]}"; do
526
+ if [[ "$tool" =~ ^(vscode|codeserver)$ ]]; then
527
+ continue
528
+ fi
529
+ echo " .$tool.json (overrides global config in $HOME/.config/$tool or $HOME/.$tool)"
530
+ done