mcp-rubber-duck 1.1.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 (184) hide show
  1. package/.dockerignore +19 -0
  2. package/.env.desktop.example +145 -0
  3. package/.env.example +45 -0
  4. package/.env.pi.example +106 -0
  5. package/.env.template +165 -0
  6. package/.eslintrc.json +40 -0
  7. package/.github/ISSUE_TEMPLATE/bug_report.md +65 -0
  8. package/.github/ISSUE_TEMPLATE/feature_request.md +58 -0
  9. package/.github/ISSUE_TEMPLATE/question.md +67 -0
  10. package/.github/pull_request_template.md +111 -0
  11. package/.github/workflows/docker-build.yml +138 -0
  12. package/.github/workflows/release.yml +182 -0
  13. package/.github/workflows/security.yml +141 -0
  14. package/.github/workflows/semantic-release.yml +89 -0
  15. package/.prettierrc +10 -0
  16. package/.releaserc.json +66 -0
  17. package/CHANGELOG.md +95 -0
  18. package/CONTRIBUTING.md +242 -0
  19. package/Dockerfile +62 -0
  20. package/LICENSE +21 -0
  21. package/README.md +803 -0
  22. package/audit-ci.json +8 -0
  23. package/config/claude_desktop.json +14 -0
  24. package/config/config.example.json +91 -0
  25. package/dist/config/config.d.ts +51 -0
  26. package/dist/config/config.d.ts.map +1 -0
  27. package/dist/config/config.js +301 -0
  28. package/dist/config/config.js.map +1 -0
  29. package/dist/config/types.d.ts +356 -0
  30. package/dist/config/types.d.ts.map +1 -0
  31. package/dist/config/types.js +41 -0
  32. package/dist/config/types.js.map +1 -0
  33. package/dist/index.d.ts +3 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +109 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/providers/duck-provider-enhanced.d.ts +29 -0
  38. package/dist/providers/duck-provider-enhanced.d.ts.map +1 -0
  39. package/dist/providers/duck-provider-enhanced.js +230 -0
  40. package/dist/providers/duck-provider-enhanced.js.map +1 -0
  41. package/dist/providers/enhanced-manager.d.ts +54 -0
  42. package/dist/providers/enhanced-manager.d.ts.map +1 -0
  43. package/dist/providers/enhanced-manager.js +217 -0
  44. package/dist/providers/enhanced-manager.js.map +1 -0
  45. package/dist/providers/manager.d.ts +28 -0
  46. package/dist/providers/manager.d.ts.map +1 -0
  47. package/dist/providers/manager.js +204 -0
  48. package/dist/providers/manager.js.map +1 -0
  49. package/dist/providers/provider.d.ts +29 -0
  50. package/dist/providers/provider.d.ts.map +1 -0
  51. package/dist/providers/provider.js +179 -0
  52. package/dist/providers/provider.js.map +1 -0
  53. package/dist/providers/types.d.ts +69 -0
  54. package/dist/providers/types.d.ts.map +1 -0
  55. package/dist/providers/types.js +2 -0
  56. package/dist/providers/types.js.map +1 -0
  57. package/dist/server.d.ts +24 -0
  58. package/dist/server.d.ts.map +1 -0
  59. package/dist/server.js +501 -0
  60. package/dist/server.js.map +1 -0
  61. package/dist/services/approval.d.ts +44 -0
  62. package/dist/services/approval.d.ts.map +1 -0
  63. package/dist/services/approval.js +159 -0
  64. package/dist/services/approval.js.map +1 -0
  65. package/dist/services/cache.d.ts +21 -0
  66. package/dist/services/cache.d.ts.map +1 -0
  67. package/dist/services/cache.js +63 -0
  68. package/dist/services/cache.js.map +1 -0
  69. package/dist/services/conversation.d.ts +24 -0
  70. package/dist/services/conversation.d.ts.map +1 -0
  71. package/dist/services/conversation.js +108 -0
  72. package/dist/services/conversation.js.map +1 -0
  73. package/dist/services/function-bridge.d.ts +41 -0
  74. package/dist/services/function-bridge.d.ts.map +1 -0
  75. package/dist/services/function-bridge.js +259 -0
  76. package/dist/services/function-bridge.js.map +1 -0
  77. package/dist/services/health.d.ts +17 -0
  78. package/dist/services/health.d.ts.map +1 -0
  79. package/dist/services/health.js +77 -0
  80. package/dist/services/health.js.map +1 -0
  81. package/dist/services/mcp-client-manager.d.ts +49 -0
  82. package/dist/services/mcp-client-manager.d.ts.map +1 -0
  83. package/dist/services/mcp-client-manager.js +279 -0
  84. package/dist/services/mcp-client-manager.js.map +1 -0
  85. package/dist/tools/approve-mcp-request.d.ts +9 -0
  86. package/dist/tools/approve-mcp-request.d.ts.map +1 -0
  87. package/dist/tools/approve-mcp-request.js +111 -0
  88. package/dist/tools/approve-mcp-request.js.map +1 -0
  89. package/dist/tools/ask-duck.d.ts +9 -0
  90. package/dist/tools/ask-duck.d.ts.map +1 -0
  91. package/dist/tools/ask-duck.js +43 -0
  92. package/dist/tools/ask-duck.js.map +1 -0
  93. package/dist/tools/chat-duck.d.ts +9 -0
  94. package/dist/tools/chat-duck.d.ts.map +1 -0
  95. package/dist/tools/chat-duck.js +57 -0
  96. package/dist/tools/chat-duck.js.map +1 -0
  97. package/dist/tools/clear-conversations.d.ts +8 -0
  98. package/dist/tools/clear-conversations.d.ts.map +1 -0
  99. package/dist/tools/clear-conversations.js +17 -0
  100. package/dist/tools/clear-conversations.js.map +1 -0
  101. package/dist/tools/compare-ducks.d.ts +8 -0
  102. package/dist/tools/compare-ducks.d.ts.map +1 -0
  103. package/dist/tools/compare-ducks.js +49 -0
  104. package/dist/tools/compare-ducks.js.map +1 -0
  105. package/dist/tools/duck-council.d.ts +8 -0
  106. package/dist/tools/duck-council.d.ts.map +1 -0
  107. package/dist/tools/duck-council.js +69 -0
  108. package/dist/tools/duck-council.js.map +1 -0
  109. package/dist/tools/get-pending-approvals.d.ts +15 -0
  110. package/dist/tools/get-pending-approvals.d.ts.map +1 -0
  111. package/dist/tools/get-pending-approvals.js +74 -0
  112. package/dist/tools/get-pending-approvals.js.map +1 -0
  113. package/dist/tools/list-ducks.d.ts +9 -0
  114. package/dist/tools/list-ducks.d.ts.map +1 -0
  115. package/dist/tools/list-ducks.js +47 -0
  116. package/dist/tools/list-ducks.js.map +1 -0
  117. package/dist/tools/list-models.d.ts +8 -0
  118. package/dist/tools/list-models.d.ts.map +1 -0
  119. package/dist/tools/list-models.js +72 -0
  120. package/dist/tools/list-models.js.map +1 -0
  121. package/dist/tools/mcp-status.d.ts +17 -0
  122. package/dist/tools/mcp-status.d.ts.map +1 -0
  123. package/dist/tools/mcp-status.js +100 -0
  124. package/dist/tools/mcp-status.js.map +1 -0
  125. package/dist/utils/ascii-art.d.ts +19 -0
  126. package/dist/utils/ascii-art.d.ts.map +1 -0
  127. package/dist/utils/ascii-art.js +73 -0
  128. package/dist/utils/ascii-art.js.map +1 -0
  129. package/dist/utils/logger.d.ts +3 -0
  130. package/dist/utils/logger.d.ts.map +1 -0
  131. package/dist/utils/logger.js +86 -0
  132. package/dist/utils/logger.js.map +1 -0
  133. package/dist/utils/safe-logger.d.ts +23 -0
  134. package/dist/utils/safe-logger.d.ts.map +1 -0
  135. package/dist/utils/safe-logger.js +145 -0
  136. package/dist/utils/safe-logger.js.map +1 -0
  137. package/docker-compose.yml +161 -0
  138. package/jest.config.js +26 -0
  139. package/package.json +65 -0
  140. package/scripts/build-multiarch.sh +290 -0
  141. package/scripts/deploy-raspbian.sh +410 -0
  142. package/scripts/deploy.sh +322 -0
  143. package/scripts/gh-deploy.sh +343 -0
  144. package/scripts/setup-docker-raspbian.sh +530 -0
  145. package/server.json +8 -0
  146. package/src/config/config.ts +357 -0
  147. package/src/config/types.ts +89 -0
  148. package/src/index.ts +114 -0
  149. package/src/providers/duck-provider-enhanced.ts +294 -0
  150. package/src/providers/enhanced-manager.ts +290 -0
  151. package/src/providers/manager.ts +257 -0
  152. package/src/providers/provider.ts +207 -0
  153. package/src/providers/types.ts +78 -0
  154. package/src/server.ts +603 -0
  155. package/src/services/approval.ts +225 -0
  156. package/src/services/cache.ts +79 -0
  157. package/src/services/conversation.ts +146 -0
  158. package/src/services/function-bridge.ts +329 -0
  159. package/src/services/health.ts +107 -0
  160. package/src/services/mcp-client-manager.ts +362 -0
  161. package/src/tools/approve-mcp-request.ts +126 -0
  162. package/src/tools/ask-duck.ts +74 -0
  163. package/src/tools/chat-duck.ts +82 -0
  164. package/src/tools/clear-conversations.ts +24 -0
  165. package/src/tools/compare-ducks.ts +67 -0
  166. package/src/tools/duck-council.ts +88 -0
  167. package/src/tools/get-pending-approvals.ts +90 -0
  168. package/src/tools/list-ducks.ts +65 -0
  169. package/src/tools/list-models.ts +101 -0
  170. package/src/tools/mcp-status.ts +117 -0
  171. package/src/utils/ascii-art.ts +85 -0
  172. package/src/utils/logger.ts +116 -0
  173. package/src/utils/safe-logger.ts +165 -0
  174. package/systemd/mcp-rubber-duck-with-ollama.service +55 -0
  175. package/systemd/mcp-rubber-duck.service +58 -0
  176. package/test-functionality.js +147 -0
  177. package/test-mcp-interface.js +221 -0
  178. package/tests/ascii-art.test.ts +36 -0
  179. package/tests/config.test.ts +239 -0
  180. package/tests/conversation.test.ts +308 -0
  181. package/tests/mcp-bridge.test.ts +291 -0
  182. package/tests/providers.test.ts +269 -0
  183. package/tests/tools/clear-conversations.test.ts +163 -0
  184. package/tsconfig.json +26 -0
@@ -0,0 +1,322 @@
1
+ #!/bin/bash
2
+
3
+ # MCP Rubber Duck - Universal Deployment Script
4
+ # Works on macOS, Linux, Windows (WSL), and Raspberry Pi
5
+
6
+ set -euo pipefail
7
+
8
+ # Colors for output
9
+ RED='\033[0;31m'
10
+ GREEN='\033[0;32m'
11
+ YELLOW='\033[1;33m'
12
+ BLUE='\033[0;34m'
13
+ NC='\033[0m' # No Color
14
+
15
+ # Configuration
16
+ DEPLOYMENT_MODE="${DEPLOYMENT_MODE:-auto}"
17
+ PLATFORM="${PLATFORM:-auto}"
18
+
19
+ # Functions
20
+ log() {
21
+ echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
22
+ }
23
+
24
+ warn() {
25
+ echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
26
+ }
27
+
28
+ error() {
29
+ echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
30
+ exit 1
31
+ }
32
+
33
+ info() {
34
+ echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $1${NC}"
35
+ }
36
+
37
+ print_usage() {
38
+ cat << EOF
39
+ Usage: $0 [OPTIONS]
40
+
41
+ Universal deployment script for MCP Rubber Duck
42
+ Works on: macOS, Linux, Windows (WSL), Raspberry Pi
43
+
44
+ OPTIONS:
45
+ -m, --mode MODE Deployment mode: local, docker, ssh
46
+ -p, --platform PLATFORM Target platform: pi, desktop, auto
47
+ --profile PROFILE Docker compose profile: lightweight, desktop, with-ollama
48
+ --ssh-host HOST SSH host for remote deployment
49
+ --dry-run Show what would be done
50
+ -h, --help Show this help message
51
+
52
+ DEPLOYMENT MODES:
53
+ local - Run from source code (npm start)
54
+ docker - Use Docker containers (recommended)
55
+ ssh - Deploy via SSH to remote host
56
+
57
+ PLATFORMS:
58
+ pi - Raspberry Pi (optimized for low memory)
59
+ desktop - Desktop/server (higher resource limits)
60
+ auto - Auto-detect platform
61
+
62
+ EXAMPLES:
63
+ # Auto-detect platform and deploy with Docker
64
+ $0
65
+
66
+ # Deploy to Raspberry Pi with lightweight profile
67
+ $0 --platform pi --profile lightweight
68
+
69
+ # Deploy to desktop with Ollama support
70
+ $0 --platform desktop --profile with-ollama
71
+
72
+ # Deploy to remote Raspberry Pi via SSH
73
+ $0 --mode ssh --ssh-host pi@192.168.1.100
74
+
75
+ EOF
76
+ }
77
+
78
+ # Detect platform
79
+ detect_platform() {
80
+ if [[ "$PLATFORM" != "auto" ]]; then
81
+ echo "$PLATFORM"
82
+ return
83
+ fi
84
+
85
+ # Check system info
86
+ local arch=$(uname -m)
87
+ local system=$(uname -s)
88
+ local mem_kb=$(grep MemTotal /proc/meminfo 2>/dev/null | awk '{print $2}' || echo "8000000")
89
+ local mem_gb=$((mem_kb / 1024 / 1024))
90
+
91
+ # Raspberry Pi detection
92
+ if [[ -f /proc/device-tree/model ]] && grep -q "Raspberry Pi" /proc/device-tree/model 2>/dev/null; then
93
+ echo "pi"
94
+ return
95
+ fi
96
+
97
+ # Low memory systems (< 2GB)
98
+ if [[ $mem_gb -lt 2 ]]; then
99
+ echo "pi"
100
+ return
101
+ fi
102
+
103
+ echo "desktop"
104
+ }
105
+
106
+ # Create environment file based on platform
107
+ create_env_file() {
108
+ local platform=$1
109
+ local env_file=".env"
110
+
111
+ if [[ -f "$env_file" ]]; then
112
+ info "Using existing $env_file file"
113
+ return
114
+ fi
115
+
116
+ log "Creating environment file for $platform platform..."
117
+
118
+ if [[ "$platform" == "pi" ]]; then
119
+ cp .env.pi.example "$env_file" 2>/dev/null || cp .env.template "$env_file"
120
+ else
121
+ cp .env.desktop.example "$env_file" 2>/dev/null || cp .env.template "$env_file"
122
+ fi
123
+
124
+ warn "Please edit $env_file and add your API keys"
125
+ }
126
+
127
+ # Deploy with Docker
128
+ deploy_docker() {
129
+ local platform=$1
130
+ local profile=${COMPOSE_PROFILE:-}
131
+
132
+ log "Deploying with Docker for $platform platform..."
133
+
134
+ # Set platform-specific defaults
135
+ if [[ "$platform" == "pi" ]]; then
136
+ export DOCKER_CPU_LIMIT="${DOCKER_CPU_LIMIT:-1.5}"
137
+ export DOCKER_MEMORY_LIMIT="${DOCKER_MEMORY_LIMIT:-512M}"
138
+ export DOCKER_MEMORY_RESERVATION="${DOCKER_MEMORY_RESERVATION:-256M}"
139
+ export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=256}"
140
+ profile="${profile:-lightweight}"
141
+ else
142
+ export DOCKER_CPU_LIMIT="${DOCKER_CPU_LIMIT:-4.0}"
143
+ export DOCKER_MEMORY_LIMIT="${DOCKER_MEMORY_LIMIT:-2G}"
144
+ export DOCKER_MEMORY_RESERVATION="${DOCKER_MEMORY_RESERVATION:-1G}"
145
+ export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=1024}"
146
+ profile="${profile:-desktop}"
147
+ fi
148
+
149
+ # Create environment file
150
+ create_env_file "$platform"
151
+
152
+ # Docker commands
153
+ local compose_cmd="docker compose"
154
+ if [[ -n "$profile" && "$profile" != "default" ]]; then
155
+ compose_cmd="$compose_cmd --profile $profile"
156
+ fi
157
+
158
+ if [[ "${DRY_RUN:-false}" == "true" ]]; then
159
+ info "DRY RUN: Would run:"
160
+ info " $compose_cmd pull"
161
+ info " $compose_cmd up -d"
162
+ return
163
+ fi
164
+
165
+ # Pull and start
166
+ log "Pulling Docker images..."
167
+ $compose_cmd pull
168
+
169
+ log "Starting containers..."
170
+ $compose_cmd up -d
171
+
172
+ # Show status
173
+ log "Checking container status..."
174
+ docker ps --filter name=mcp-rubber-duck
175
+
176
+ log "✅ Deployment completed!"
177
+ info "Check logs with: docker logs mcp-rubber-duck"
178
+ info "Stop with: $compose_cmd down"
179
+ }
180
+
181
+ # Deploy via SSH
182
+ deploy_ssh() {
183
+ local ssh_host=$1
184
+
185
+ if [[ -z "$ssh_host" ]]; then
186
+ error "SSH host not specified. Use --ssh-host option."
187
+ fi
188
+
189
+ log "Deploying to remote host: $ssh_host"
190
+
191
+ if [[ "${DRY_RUN:-false}" == "true" ]]; then
192
+ info "DRY RUN: Would deploy to $ssh_host"
193
+ return
194
+ fi
195
+
196
+ # Copy files to remote host
197
+ log "Copying files to remote host..."
198
+ ssh "$ssh_host" "mkdir -p ~/mcp-rubber-duck"
199
+ scp docker-compose.yml "$ssh_host:~/mcp-rubber-duck/"
200
+ scp .env.template "$ssh_host:~/mcp-rubber-duck/"
201
+ scp scripts/deploy.sh "$ssh_host:~/mcp-rubber-duck/"
202
+
203
+ # Run deployment on remote host
204
+ log "Running deployment on remote host..."
205
+ ssh "$ssh_host" "cd ~/mcp-rubber-duck && bash deploy.sh --mode docker --platform pi"
206
+
207
+ log "✅ Remote deployment completed!"
208
+ }
209
+
210
+ # Deploy from source
211
+ deploy_local() {
212
+ local platform=$1
213
+
214
+ log "Deploying from source for $platform platform..."
215
+
216
+ # Check prerequisites
217
+ if ! command -v node &> /dev/null; then
218
+ error "Node.js is not installed"
219
+ fi
220
+
221
+ if ! command -v npm &> /dev/null; then
222
+ error "npm is not installed"
223
+ fi
224
+
225
+ # Set platform-specific environment
226
+ create_env_file "$platform"
227
+
228
+ if [[ "$platform" == "pi" ]]; then
229
+ export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=256}"
230
+ else
231
+ export NODE_OPTIONS="${NODE_OPTIONS:---max-old-space-size=1024}"
232
+ fi
233
+
234
+ if [[ "${DRY_RUN:-false}" == "true" ]]; then
235
+ info "DRY RUN: Would run:"
236
+ info " npm install"
237
+ info " npm run build"
238
+ info " npm start"
239
+ return
240
+ fi
241
+
242
+ # Build and run
243
+ log "Installing dependencies..."
244
+ npm install
245
+
246
+ log "Building project..."
247
+ npm run build
248
+
249
+ log "Starting MCP server..."
250
+ info "MCP Rubber Duck is now running from source"
251
+ npm start
252
+ }
253
+
254
+ # Parse command line arguments
255
+ while [[ $# -gt 0 ]]; do
256
+ case $1 in
257
+ -m|--mode)
258
+ DEPLOYMENT_MODE="$2"
259
+ shift 2
260
+ ;;
261
+ -p|--platform)
262
+ PLATFORM="$2"
263
+ shift 2
264
+ ;;
265
+ --profile)
266
+ COMPOSE_PROFILE="$2"
267
+ shift 2
268
+ ;;
269
+ --ssh-host)
270
+ SSH_HOST="$2"
271
+ shift 2
272
+ ;;
273
+ --dry-run)
274
+ DRY_RUN="true"
275
+ shift
276
+ ;;
277
+ -h|--help)
278
+ print_usage
279
+ exit 0
280
+ ;;
281
+ *)
282
+ error "Unknown option: $1"
283
+ ;;
284
+ esac
285
+ done
286
+
287
+ # Main execution
288
+ log "🦆 Starting MCP Rubber Duck deployment"
289
+
290
+ # Detect platform
291
+ DETECTED_PLATFORM=$(detect_platform)
292
+ log "Platform: $DETECTED_PLATFORM"
293
+
294
+ # Auto-detect deployment mode if needed
295
+ if [[ "$DEPLOYMENT_MODE" == "auto" ]]; then
296
+ if command -v docker &> /dev/null; then
297
+ DEPLOYMENT_MODE="docker"
298
+ else
299
+ DEPLOYMENT_MODE="local"
300
+ fi
301
+ fi
302
+
303
+ info "Deployment mode: $DEPLOYMENT_MODE"
304
+ info "Target platform: $DETECTED_PLATFORM"
305
+
306
+ # Deploy based on mode
307
+ case $DEPLOYMENT_MODE in
308
+ docker)
309
+ deploy_docker "$DETECTED_PLATFORM"
310
+ ;;
311
+ ssh)
312
+ deploy_ssh "${SSH_HOST:-}"
313
+ ;;
314
+ local)
315
+ deploy_local "$DETECTED_PLATFORM"
316
+ ;;
317
+ *)
318
+ error "Unknown deployment mode: $DEPLOYMENT_MODE"
319
+ ;;
320
+ esac
321
+
322
+ log "🎉 Deployment completed successfully!"
@@ -0,0 +1,343 @@
1
+ #!/bin/bash
2
+
3
+ # MCP Rubber Duck - GitHub Container Registry Deployment Script
4
+ # This script simplifies deployment using GitHub CLI and GitHub Container Registry
5
+
6
+ set -euo pipefail
7
+
8
+ # Colors for output
9
+ RED='\033[0;31m'
10
+ GREEN='\033[0;32m'
11
+ YELLOW='\033[1;33m'
12
+ BLUE='\033[0;34m'
13
+ NC='\033[0m' # No Color
14
+
15
+ # Configuration
16
+ IMAGE_TAG="${IMAGE_TAG:-latest}"
17
+ MAKE_PUBLIC="${MAKE_PUBLIC:-false}"
18
+ BUILD_PLATFORMS="${BUILD_PLATFORMS:-linux/amd64,linux/arm64,linux/arm/v7}"
19
+
20
+ # Functions
21
+ log() {
22
+ echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')] $1${NC}"
23
+ }
24
+
25
+ warn() {
26
+ echo -e "${YELLOW}[$(date +'%Y-%m-%d %H:%M:%S')] WARNING: $1${NC}"
27
+ }
28
+
29
+ error() {
30
+ echo -e "${RED}[$(date +'%Y-%m-%d %H:%M:%S')] ERROR: $1${NC}"
31
+ exit 1
32
+ }
33
+
34
+ info() {
35
+ echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')] INFO: $1${NC}"
36
+ }
37
+
38
+ print_usage() {
39
+ cat << EOF
40
+ Usage: $0 [OPTIONS]
41
+
42
+ Deploy MCP Rubber Duck to GitHub Container Registry using GitHub CLI
43
+
44
+ OPTIONS:
45
+ -t, --tag TAG Docker image tag (default: latest)
46
+ --public Make package public after push
47
+ --platforms PLATFORMS Target platforms (default: linux/amd64,linux/arm64,linux/arm/v7)
48
+ --local-only Build for local architecture only (faster testing)
49
+ --dry-run Show what would be done without executing
50
+ -v, --verbose Verbose output
51
+ -h, --help Show this help message
52
+
53
+ EXAMPLES:
54
+ # Deploy latest version to GitHub Container Registry
55
+ $0
56
+
57
+ # Deploy with specific tag
58
+ $0 --tag v1.2.0
59
+
60
+ # Deploy and make package public
61
+ $0 --public
62
+
63
+ # Local development build
64
+ $0 --local-only --tag dev
65
+
66
+ # See what would happen
67
+ $0 --dry-run
68
+
69
+ REQUIREMENTS:
70
+ - GitHub CLI (gh) installed and authenticated
71
+ - Docker with buildx support
72
+ - Repository must be initialized as git repo
73
+ - Must have push access to repository
74
+
75
+ EOF
76
+ }
77
+
78
+ # Check prerequisites
79
+ check_prerequisites() {
80
+ log "Checking prerequisites..."
81
+
82
+ # Check if gh is installed
83
+ if ! command -v gh &> /dev/null; then
84
+ error "GitHub CLI (gh) is not installed. Install from: https://cli.github.com/"
85
+ fi
86
+
87
+ # Check if authenticated with GitHub
88
+ if ! gh auth status &> /dev/null; then
89
+ error "Not authenticated with GitHub. Run: gh auth login"
90
+ fi
91
+
92
+ # Check if Docker is installed
93
+ if ! command -v docker &> /dev/null; then
94
+ error "Docker is not installed or not in PATH"
95
+ fi
96
+
97
+ # Check if buildx is available
98
+ if ! docker buildx version &> /dev/null; then
99
+ error "Docker buildx is not available. Please update Docker to a newer version."
100
+ fi
101
+
102
+ # Check if we're in a git repository
103
+ if ! git rev-parse --git-dir &> /dev/null; then
104
+ error "This script must be run from within a git repository"
105
+ fi
106
+
107
+ log "✅ All prerequisites satisfied"
108
+ }
109
+
110
+ # Get GitHub and repository information
111
+ get_github_info() {
112
+ log "Getting GitHub repository information..."
113
+
114
+ # Get GitHub username
115
+ GITHUB_USER=$(gh api user --jq .login 2>/dev/null)
116
+ if [[ -z "$GITHUB_USER" ]]; then
117
+ error "Could not get GitHub username. Check your gh authentication."
118
+ fi
119
+
120
+ # Try to get repository info from current directory
121
+ if gh repo view &> /dev/null; then
122
+ REPO_NAME=$(gh repo view --json name --jq .name)
123
+ REPO_OWNER=$(gh repo view --json owner --jq .owner.login)
124
+ REPO_URL=$(gh repo view --json url --jq .url)
125
+
126
+ # Check if we have push access
127
+ if ! gh api "repos/$REPO_OWNER/$REPO_NAME" --jq .permissions.push | grep -q true; then
128
+ warn "You may not have push access to $REPO_OWNER/$REPO_NAME"
129
+ fi
130
+ else
131
+ # Fallback to git remote
132
+ REPO_NAME=$(basename "$(git rev-parse --show-toplevel)" 2>/dev/null || echo "mcp-rubber-duck")
133
+ REPO_OWNER="$GITHUB_USER"
134
+ REPO_URL="https://github.com/$REPO_OWNER/$REPO_NAME"
135
+ warn "Could not get repository info from gh. Using fallback: $REPO_OWNER/$REPO_NAME"
136
+ fi
137
+
138
+ # Build image name
139
+ IMAGE_NAME="ghcr.io/$REPO_OWNER/$REPO_NAME"
140
+
141
+ info "GitHub User: $GITHUB_USER"
142
+ info "Repository: $REPO_OWNER/$REPO_NAME"
143
+ info "Image name: $IMAGE_NAME:$IMAGE_TAG"
144
+ info "Repository URL: $REPO_URL"
145
+
146
+ log "✅ GitHub information gathered"
147
+ }
148
+
149
+ # Authenticate Docker with GitHub Container Registry
150
+ authenticate_docker() {
151
+ log "Authenticating Docker with GitHub Container Registry..."
152
+
153
+ if [[ "${DRY_RUN:-false}" == "true" ]]; then
154
+ info "DRY RUN: Would authenticate Docker with ghcr.io"
155
+ return
156
+ fi
157
+
158
+ if ! gh auth token | docker login ghcr.io -u "$GITHUB_USER" --password-stdin; then
159
+ error "Failed to authenticate Docker with ghcr.io"
160
+ fi
161
+
162
+ log "✅ Docker authenticated with ghcr.io"
163
+ }
164
+
165
+ # Build and push Docker image
166
+ build_and_push() {
167
+ log "Building and pushing Docker image..."
168
+
169
+ # Determine platforms
170
+ local platforms="$BUILD_PLATFORMS"
171
+ if [[ "${LOCAL_ONLY:-false}" == "true" ]]; then
172
+ platforms=""
173
+ info "Building for local architecture only"
174
+ fi
175
+
176
+ if [[ "${DRY_RUN:-false}" == "true" ]]; then
177
+ info "DRY RUN: Would build and push:"
178
+ info " Image: $IMAGE_NAME:$IMAGE_TAG"
179
+ info " Platforms: ${platforms:-$(uname -m)}"
180
+ return
181
+ fi
182
+
183
+ # Use the enhanced build script with GitHub support
184
+ local build_args=(
185
+ "./scripts/build-multiarch.sh"
186
+ "--github"
187
+ "--tag" "$IMAGE_TAG"
188
+ )
189
+
190
+ if [[ "${LOCAL_ONLY:-false}" == "true" ]]; then
191
+ build_args+=("--local")
192
+ fi
193
+
194
+ if [[ "${VERBOSE:-false}" == "true" ]]; then
195
+ build_args+=("--verbose")
196
+ fi
197
+
198
+ log "Running: ${build_args[*]}"
199
+ if "${build_args[@]}"; then
200
+ log "✅ Build and push completed successfully"
201
+ else
202
+ error "❌ Build and push failed"
203
+ fi
204
+ }
205
+
206
+ # Manage package visibility and settings
207
+ manage_package() {
208
+ log "Managing GitHub package settings..."
209
+
210
+ if [[ "${DRY_RUN:-false}" == "true" ]]; then
211
+ info "DRY RUN: Would manage package settings"
212
+ return
213
+ fi
214
+
215
+ # Wait a moment for package to appear
216
+ sleep 5
217
+
218
+ # Check if package exists
219
+ if ! gh api "user/packages/container/$REPO_NAME" &> /dev/null; then
220
+ warn "Package not yet available. It may take a moment to appear in GitHub."
221
+ return
222
+ fi
223
+
224
+ # Make package public if requested
225
+ if [[ "$MAKE_PUBLIC" == "true" ]]; then
226
+ log "Making package public..."
227
+ if gh api --method PATCH "user/packages/container/$REPO_NAME" --field visibility=public; then
228
+ log "✅ Package is now public"
229
+ else
230
+ warn "Failed to make package public (may require manual action)"
231
+ fi
232
+ fi
233
+
234
+ # Link package to repository
235
+ log "Linking package to repository..."
236
+ if gh api --method PUT "user/packages/container/$REPO_NAME/restore" --field token="$(gh auth token)" &> /dev/null; then
237
+ log "✅ Package linked to repository"
238
+ else
239
+ info "Package repository linking may require manual action"
240
+ fi
241
+
242
+ log "✅ Package management completed"
243
+ }
244
+
245
+ # Show package information and next steps
246
+ show_package_info() {
247
+ log "📦 Package Information:"
248
+
249
+ if [[ "${DRY_RUN:-false}" == "true" ]]; then
250
+ info "DRY RUN: Package would be available at:"
251
+ info " URL: https://github.com/$REPO_OWNER/packages/container/package/$REPO_NAME"
252
+ info " Pull command: docker pull ghcr.io/$REPO_OWNER/$REPO_NAME:$IMAGE_TAG"
253
+ return
254
+ fi
255
+
256
+ # Package URL
257
+ echo " 📍 Package URL: https://github.com/$REPO_OWNER/packages/container/package/$REPO_NAME"
258
+
259
+ # Pull command
260
+ echo " 🐳 Pull command: docker pull $IMAGE_NAME:$IMAGE_TAG"
261
+
262
+ # Try to get package info
263
+ if gh api "user/packages/container/$REPO_NAME" &> /dev/null; then
264
+ local visibility
265
+ local updated
266
+ visibility=$(gh api "user/packages/container/$REPO_NAME" --jq .visibility 2>/dev/null || echo "unknown")
267
+ updated=$(gh api "user/packages/container/$REPO_NAME" --jq .updated_at 2>/dev/null || echo "unknown")
268
+
269
+ echo " 👁️ Visibility: $visibility"
270
+ echo " 📅 Updated: $updated"
271
+
272
+ # Show versions
273
+ local versions
274
+ versions=$(gh api "user/packages/container/$REPO_NAME/versions" --jq 'length' 2>/dev/null || echo "0")
275
+ echo " 🏷️ Versions: $versions"
276
+
277
+ else
278
+ warn "Package information not yet available"
279
+ fi
280
+
281
+ echo
282
+ log "🚀 Multi-Platform Deployment:"
283
+ echo " 1. On your deployment target, update docker-compose.yml (or create .env):"
284
+ echo " DOCKER_IMAGE=$IMAGE_NAME:$IMAGE_TAG"
285
+ echo " 2. Pull and run (works on all platforms):"
286
+ echo " docker compose pull"
287
+ echo " docker compose up -d"
288
+
289
+ if [[ "$MAKE_PUBLIC" != "true" ]]; then
290
+ echo
291
+ info "💡 To make package public for easier access:"
292
+ echo " gh api --method PATCH user/packages/container/$REPO_NAME --field visibility=public"
293
+ fi
294
+ }
295
+
296
+ # Parse command line arguments
297
+ while [[ $# -gt 0 ]]; do
298
+ case $1 in
299
+ -t|--tag)
300
+ IMAGE_TAG="$2"
301
+ shift 2
302
+ ;;
303
+ --public)
304
+ MAKE_PUBLIC="true"
305
+ shift
306
+ ;;
307
+ --platforms)
308
+ BUILD_PLATFORMS="$2"
309
+ shift 2
310
+ ;;
311
+ --local-only)
312
+ LOCAL_ONLY="true"
313
+ shift
314
+ ;;
315
+ --dry-run)
316
+ DRY_RUN="true"
317
+ shift
318
+ ;;
319
+ -v|--verbose)
320
+ VERBOSE="true"
321
+ shift
322
+ ;;
323
+ -h|--help)
324
+ print_usage
325
+ exit 0
326
+ ;;
327
+ *)
328
+ error "Unknown option: $1"
329
+ ;;
330
+ esac
331
+ done
332
+
333
+ # Main execution
334
+ log "🦆 Starting GitHub Container Registry deployment for MCP Rubber Duck"
335
+
336
+ check_prerequisites
337
+ get_github_info
338
+ authenticate_docker
339
+ build_and_push
340
+ manage_package
341
+ show_package_info
342
+
343
+ log "🎉 GitHub deployment completed successfully!"