@sarkar-ai/deskmate 0.2.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/install.sh ADDED
@@ -0,0 +1,817 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ # Colors for output
6
+ RED='\033[0;31m'
7
+ GREEN='\033[0;32m'
8
+ YELLOW='\033[1;33m'
9
+ BLUE='\033[0;34m'
10
+ NC='\033[0m' # No Color
11
+
12
+ # =============================================================================
13
+ # OS Detection
14
+ # =============================================================================
15
+ OS_TYPE="$(uname -s)"
16
+ case "$OS_TYPE" in
17
+ Darwin) PLATFORM="macos" ;;
18
+ Linux) PLATFORM="linux" ;;
19
+ CYGWIN*|MINGW*|MSYS*) PLATFORM="windows" ;;
20
+ *)
21
+ echo -e "${RED}Unsupported platform: $OS_TYPE${NC}"
22
+ echo "Deskmate supports macOS and Linux. On Windows, use WSL2."
23
+ exit 1
24
+ ;;
25
+ esac
26
+
27
+ # Configuration
28
+ PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"
29
+ LOGS_DIR="$PROJECT_DIR/logs"
30
+ NODE_PATH=$(which node)
31
+
32
+ # Platform-specific paths
33
+ if [ "$PLATFORM" = "macos" ]; then
34
+ PLIST_NAME="com.deskmate.service"
35
+ PLIST_PATH="$HOME/Library/LaunchAgents/$PLIST_NAME.plist"
36
+ CLAUDE_DESKTOP_CONFIG="$HOME/Library/Application Support/Claude/claude_desktop_config.json"
37
+ elif [ "$PLATFORM" = "linux" ]; then
38
+ SYSTEMD_SERVICE="deskmate.service"
39
+ SYSTEMD_DIR="$HOME/.config/systemd/user"
40
+ SYSTEMD_PATH="$SYSTEMD_DIR/$SYSTEMD_SERVICE"
41
+ CLAUDE_DESKTOP_CONFIG="$HOME/.config/Claude/claude_desktop_config.json"
42
+ fi
43
+
44
+ # Helper: cross-platform sed -i
45
+ sed_inplace() {
46
+ if [ "$PLATFORM" = "macos" ]; then
47
+ sed -i '' "$@"
48
+ else
49
+ sed -i "$@"
50
+ fi
51
+ }
52
+
53
+ echo -e "${BLUE}"
54
+ echo "╔════════════════════════════════════════╗"
55
+ echo "║ Deskmate Installer ║"
56
+ echo "╚════════════════════════════════════════╝"
57
+ echo -e "${NC}"
58
+ echo -e "Detected platform: ${GREEN}$PLATFORM${NC}"
59
+
60
+ if [ "$PLATFORM" = "windows" ]; then
61
+ echo -e "${RED}Native Windows is not supported.${NC}"
62
+ echo "Please use WSL2 (Windows Subsystem for Linux) to run Deskmate."
63
+ echo ""
64
+ echo " 1. Install WSL2: wsl --install"
65
+ echo " 2. Open a WSL2 terminal"
66
+ echo " 3. Re-run this installer from inside WSL2"
67
+ exit 1
68
+ fi
69
+
70
+ # =============================================================================
71
+ # 1. Prerequisites
72
+ # =============================================================================
73
+ echo -e "${YELLOW}Checking prerequisites...${NC}"
74
+
75
+ if [ -z "$NODE_PATH" ]; then
76
+ echo -e "${RED}Error: Node.js not found. Please install Node.js first.${NC}"
77
+ exit 1
78
+ fi
79
+
80
+ echo -e "${GREEN}✓ Node.js found: $NODE_PATH${NC}"
81
+
82
+ # Check if Claude Code is installed (required for Agent SDK)
83
+ CLAUDE_PATH=$(which claude 2>/dev/null || echo "")
84
+ if [ -z "$CLAUDE_PATH" ]; then
85
+ echo -e "${RED}Error: Claude Code CLI not found. Please install it first:${NC}"
86
+ echo -e "${YELLOW} curl -fsSL https://claude.ai/install.sh | bash${NC}"
87
+ exit 1
88
+ fi
89
+ echo -e "${GREEN}✓ Claude Code found: $CLAUDE_PATH${NC}"
90
+
91
+ # Linux: check for screenshot tool
92
+ if [ "$PLATFORM" = "linux" ]; then
93
+ SCREENSHOT_TOOL=""
94
+ if command -v import &>/dev/null; then
95
+ SCREENSHOT_TOOL="import (ImageMagick)"
96
+ elif command -v gnome-screenshot &>/dev/null; then
97
+ SCREENSHOT_TOOL="gnome-screenshot"
98
+ elif command -v scrot &>/dev/null; then
99
+ SCREENSHOT_TOOL="scrot"
100
+ fi
101
+
102
+ if [ -n "$SCREENSHOT_TOOL" ]; then
103
+ echo -e "${GREEN}✓ Screenshot tool found: $SCREENSHOT_TOOL${NC}"
104
+ else
105
+ echo -e "${YELLOW}Warning: No screenshot tool found. Install ImageMagick for screenshot support:${NC}"
106
+ echo -e "${YELLOW} sudo apt install imagemagick # Debian/Ubuntu${NC}"
107
+ echo -e "${YELLOW} sudo dnf install ImageMagick # Fedora${NC}"
108
+ echo -e "${YELLOW} sudo pacman -S imagemagick # Arch${NC}"
109
+ fi
110
+ fi
111
+
112
+ # =============================================================================
113
+ # 2. .env Configuration
114
+ # =============================================================================
115
+ echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
116
+ echo -e "${YELLOW}Configuring environment...${NC}"
117
+ echo ""
118
+
119
+ CONFIGURE_ENV=true
120
+
121
+ if [ -f "$PROJECT_DIR/.env" ]; then
122
+ echo -e "${GREEN}Found existing .env file.${NC}"
123
+ read -p "Reconfigure? [y/N]: " RECONFIGURE
124
+ if [ "$RECONFIGURE" != "y" ] && [ "$RECONFIGURE" != "Y" ]; then
125
+ CONFIGURE_ENV=false
126
+ echo -e "${GREEN}✓ Keeping existing .env${NC}"
127
+ fi
128
+ fi
129
+
130
+ if [ "$CONFIGURE_ENV" = true ]; then
131
+ echo ""
132
+ echo -e "${YELLOW}Enter your credentials (press Enter to skip optional fields):${NC}"
133
+ echo ""
134
+
135
+ read -p " Anthropic API Key: " ANTHROPIC_API_KEY
136
+ if [ -z "$ANTHROPIC_API_KEY" ]; then
137
+ echo -e " ${RED}Warning: No API key entered. You'll need to set ANTHROPIC_API_KEY in .env later.${NC}"
138
+ fi
139
+
140
+ echo ""
141
+ echo -e " ${BLUE}Telegram setup — get these from Telegram:${NC}"
142
+ echo -e " Bot token → message @BotFather, send /newbot"
143
+ echo -e " User ID → message @userinfobot, copy the number"
144
+ echo ""
145
+
146
+ read -p " Telegram Bot Token (from @BotFather): " TELEGRAM_BOT_TOKEN
147
+ read -p " Telegram User ID (from @userinfobot): " TELEGRAM_USER_ID
148
+
149
+ echo ""
150
+ read -p " Working directory (default: $HOME): " WORKING_DIR
151
+ WORKING_DIR=${WORKING_DIR:-$HOME}
152
+
153
+ read -p " Bot name (default: Deskmate): " BOT_NAME
154
+ BOT_NAME=${BOT_NAME:-Deskmate}
155
+
156
+ # Write .env
157
+ cat > "$PROJECT_DIR/.env" << EOF
158
+ # Deskmate Configuration (generated by install.sh)
159
+
160
+ TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
161
+ ALLOWED_USER_ID=${TELEGRAM_USER_ID}
162
+ ALLOWED_USERS=telegram:${TELEGRAM_USER_ID}
163
+ ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
164
+ AGENT_PROVIDER=claude-code
165
+ WORKING_DIR=${WORKING_DIR}
166
+ BOT_NAME=${BOT_NAME}
167
+ LOG_LEVEL=info
168
+ REQUIRE_APPROVAL_FOR_ALL=false
169
+ EOF
170
+
171
+ echo -e "\n${GREEN}✓ .env file written${NC}"
172
+ fi
173
+
174
+ # =============================================================================
175
+ # 3. Build
176
+ # =============================================================================
177
+ echo -e "\n${YELLOW}Building project...${NC}"
178
+ cd "$PROJECT_DIR"
179
+ npm install --legacy-peer-deps
180
+ npm run build
181
+ echo -e "${GREEN}✓ Build complete${NC}"
182
+
183
+ # Create logs directory
184
+ mkdir -p "$LOGS_DIR"
185
+ echo -e "${GREEN}✓ Logs directory created: $LOGS_DIR${NC}"
186
+
187
+ # =============================================================================
188
+ # 4. macOS Permissions Setup (macOS only)
189
+ # =============================================================================
190
+ if [ "$PLATFORM" = "macos" ]; then
191
+ echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
192
+ echo -e "${YELLOW}Setting up macOS permissions...${NC}"
193
+ echo ""
194
+ echo -e "Deskmate needs the following permissions to work properly:"
195
+ echo -e " • ${GREEN}Screen Recording${NC} - To take screenshots when requested"
196
+ echo -e " • ${GREEN}Accessibility${NC} - To control system functions"
197
+ echo -e " • ${GREEN}Full Disk Access${NC} - To read/write files anywhere"
198
+ echo -e " • ${GREEN}Automation${NC} - To control other applications"
199
+ echo ""
200
+ read -p "Would you like to configure permissions now? [Y/n]: " SETUP_PERMISSIONS
201
+ SETUP_PERMISSIONS=${SETUP_PERMISSIONS:-y}
202
+
203
+ if [ "$SETUP_PERMISSIONS" = "y" ] || [ "$SETUP_PERMISSIONS" = "Y" ]; then
204
+
205
+ # Get the terminal app being used
206
+ TERMINAL_APP=$(osascript -e 'tell application "System Events" to get name of first process whose frontmost is true' 2>/dev/null || echo "Terminal")
207
+
208
+ echo -e "\n${YELLOW}1. Screen Recording Permission${NC}"
209
+ echo " This allows taking screenshots when you request them."
210
+ read -p " Trigger Screen Recording permission dialog? [Y/n]: " TRIGGER_SCREEN
211
+ TRIGGER_SCREEN=${TRIGGER_SCREEN:-y}
212
+
213
+ if [ "$TRIGGER_SCREEN" = "y" ] || [ "$TRIGGER_SCREEN" = "Y" ]; then
214
+ echo -e " ${YELLOW}Triggering permission dialog...${NC}"
215
+ # Attempt to capture screen to trigger the permission dialog
216
+ screencapture -x /tmp/sarkar-test-screenshot.png 2>/dev/null || true
217
+ rm -f /tmp/sarkar-test-screenshot.png 2>/dev/null || true
218
+ echo -e " ${GREEN}✓ If a dialog appeared, please click 'Allow'${NC}"
219
+ echo -e " ${YELLOW} If no dialog appeared, the permission may already be granted${NC}"
220
+ sleep 1
221
+ fi
222
+
223
+ echo -e "\n${YELLOW}2. Accessibility Permission${NC}"
224
+ echo " This allows controlling system functions."
225
+ read -p " Open Accessibility settings? [Y/n]: " OPEN_ACCESSIBILITY
226
+ OPEN_ACCESSIBILITY=${OPEN_ACCESSIBILITY:-y}
227
+
228
+ if [ "$OPEN_ACCESSIBILITY" = "y" ] || [ "$OPEN_ACCESSIBILITY" = "Y" ]; then
229
+ echo -e " ${YELLOW}Opening System Settings > Privacy & Security > Accessibility...${NC}"
230
+ open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"
231
+ echo -e " ${GREEN}Please add '$TERMINAL_APP' and/or 'deskmate' to the list${NC}"
232
+ read -p " Press Enter when done..."
233
+ fi
234
+
235
+ echo -e "\n${YELLOW}3. Full Disk Access Permission${NC}"
236
+ echo " This allows reading and writing files in protected locations."
237
+ read -p " Open Full Disk Access settings? [Y/n]: " OPEN_DISK
238
+ OPEN_DISK=${OPEN_DISK:-y}
239
+
240
+ if [ "$OPEN_DISK" = "y" ] || [ "$OPEN_DISK" = "Y" ]; then
241
+ echo -e " ${YELLOW}Opening System Settings > Privacy & Security > Full Disk Access...${NC}"
242
+ open "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"
243
+ echo -e " ${GREEN}Please add '$TERMINAL_APP' and/or 'deskmate' to the list${NC}"
244
+ read -p " Press Enter when done..."
245
+ fi
246
+
247
+ echo -e "\n${YELLOW}4. Automation Permission${NC}"
248
+ echo " This allows controlling other applications via AppleScript."
249
+ read -p " Open Automation settings? [Y/n]: " OPEN_AUTOMATION
250
+ OPEN_AUTOMATION=${OPEN_AUTOMATION:-y}
251
+
252
+ if [ "$OPEN_AUTOMATION" = "y" ] || [ "$OPEN_AUTOMATION" = "Y" ]; then
253
+ echo -e " ${YELLOW}Opening System Settings > Privacy & Security > Automation...${NC}"
254
+ open "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation"
255
+ echo -e " ${GREEN}Please enable automation for '$TERMINAL_APP' if listed${NC}"
256
+ read -p " Press Enter when done..."
257
+ fi
258
+
259
+ echo -e "\n${YELLOW}5. Background Items (Login Items)${NC}"
260
+ echo " This allows the service to run in the background and start at login."
261
+ read -p " Open Login Items settings? [Y/n]: " OPEN_LOGIN
262
+ OPEN_LOGIN=${OPEN_LOGIN:-y}
263
+
264
+ if [ "$OPEN_LOGIN" = "y" ] || [ "$OPEN_LOGIN" = "Y" ]; then
265
+ echo -e " ${YELLOW}Opening System Settings > General > Login Items...${NC}"
266
+ open "x-apple.systempreferences:com.apple.LoginItems-Settings.extension"
267
+ echo -e " ${GREEN}Ensure 'node' is enabled under 'Allow in the Background'${NC}"
268
+ read -p " Press Enter when done..."
269
+ fi
270
+
271
+ echo -e "\n${GREEN}✓ Permissions setup complete${NC}"
272
+ echo -e "${YELLOW}Note: Some permissions may require restarting the terminal or service${NC}"
273
+ else
274
+ echo -e "${YELLOW}Skipping permissions setup. You can configure them later in:${NC}"
275
+ echo -e " System Settings > Privacy & Security"
276
+ fi
277
+ fi
278
+
279
+ # =============================================================================
280
+ # 5. Folder Access Configuration (macOS only — Linux doesn't have TCC dialogs)
281
+ # =============================================================================
282
+ if [ "$PLATFORM" = "macos" ]; then
283
+ echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
284
+ echo -e "${YELLOW}Configuring folder access...${NC}"
285
+ echo ""
286
+ echo -e "macOS will ask for permission when the agent accesses protected folders."
287
+ echo -e "Let's configure which folders the agent should have access to."
288
+ echo ""
289
+
290
+ # Common protected folders
291
+ FOLDERS_TO_CONFIGURE=""
292
+
293
+ echo -e "${YELLOW}Select folders to grant access (this will trigger macOS permission dialogs):${NC}"
294
+ echo ""
295
+
296
+ # Desktop
297
+ read -p " Grant access to Desktop? [Y/n]: " ACCESS_DESKTOP
298
+ ACCESS_DESKTOP=${ACCESS_DESKTOP:-y}
299
+ if [ "$ACCESS_DESKTOP" = "y" ] || [ "$ACCESS_DESKTOP" = "Y" ]; then
300
+ echo -e " ${YELLOW}Triggering Desktop access...${NC}"
301
+ ls "$HOME/Desktop" > /dev/null 2>&1 || true
302
+ FOLDERS_TO_CONFIGURE="$HOME/Desktop"
303
+ echo -e " ${GREEN}✓ Desktop access requested${NC}"
304
+ fi
305
+
306
+ # Documents
307
+ read -p " Grant access to Documents? [Y/n]: " ACCESS_DOCUMENTS
308
+ ACCESS_DOCUMENTS=${ACCESS_DOCUMENTS:-y}
309
+ if [ "$ACCESS_DOCUMENTS" = "y" ] || [ "$ACCESS_DOCUMENTS" = "Y" ]; then
310
+ echo -e " ${YELLOW}Triggering Documents access...${NC}"
311
+ ls "$HOME/Documents" > /dev/null 2>&1 || true
312
+ FOLDERS_TO_CONFIGURE="$FOLDERS_TO_CONFIGURE:$HOME/Documents"
313
+ echo -e " ${GREEN}✓ Documents access requested${NC}"
314
+ fi
315
+
316
+ # Downloads
317
+ read -p " Grant access to Downloads? [Y/n]: " ACCESS_DOWNLOADS
318
+ ACCESS_DOWNLOADS=${ACCESS_DOWNLOADS:-y}
319
+ if [ "$ACCESS_DOWNLOADS" = "y" ] || [ "$ACCESS_DOWNLOADS" = "Y" ]; then
320
+ echo -e " ${YELLOW}Triggering Downloads access...${NC}"
321
+ ls "$HOME/Downloads" > /dev/null 2>&1 || true
322
+ FOLDERS_TO_CONFIGURE="$FOLDERS_TO_CONFIGURE:$HOME/Downloads"
323
+ echo -e " ${GREEN}✓ Downloads access requested${NC}"
324
+ fi
325
+
326
+ # Pictures
327
+ read -p " Grant access to Pictures? [y/N]: " ACCESS_PICTURES
328
+ if [ "$ACCESS_PICTURES" = "y" ] || [ "$ACCESS_PICTURES" = "Y" ]; then
329
+ echo -e " ${YELLOW}Triggering Pictures access...${NC}"
330
+ ls "$HOME/Pictures" > /dev/null 2>&1 || true
331
+ FOLDERS_TO_CONFIGURE="$FOLDERS_TO_CONFIGURE:$HOME/Pictures"
332
+ echo -e " ${GREEN}✓ Pictures access requested${NC}"
333
+ fi
334
+
335
+ # Movies
336
+ read -p " Grant access to Movies? [y/N]: " ACCESS_MOVIES
337
+ if [ "$ACCESS_MOVIES" = "y" ] || [ "$ACCESS_MOVIES" = "Y" ]; then
338
+ echo -e " ${YELLOW}Triggering Movies access...${NC}"
339
+ ls "$HOME/Movies" > /dev/null 2>&1 || true
340
+ FOLDERS_TO_CONFIGURE="$FOLDERS_TO_CONFIGURE:$HOME/Movies"
341
+ echo -e " ${GREEN}✓ Movies access requested${NC}"
342
+ fi
343
+
344
+ # Music
345
+ read -p " Grant access to Music? [y/N]: " ACCESS_MUSIC
346
+ if [ "$ACCESS_MUSIC" = "y" ] || [ "$ACCESS_MUSIC" = "Y" ]; then
347
+ echo -e " ${YELLOW}Triggering Music access...${NC}"
348
+ ls "$HOME/Music" > /dev/null 2>&1 || true
349
+ FOLDERS_TO_CONFIGURE="$FOLDERS_TO_CONFIGURE:$HOME/Music"
350
+ echo -e " ${GREEN}✓ Music access requested${NC}"
351
+ fi
352
+
353
+ # iCloud Drive
354
+ if [ -d "$HOME/Library/Mobile Documents/com~apple~CloudDocs" ]; then
355
+ read -p " Grant access to iCloud Drive? [y/N]: " ACCESS_ICLOUD
356
+ if [ "$ACCESS_ICLOUD" = "y" ] || [ "$ACCESS_ICLOUD" = "Y" ]; then
357
+ echo -e " ${YELLOW}Triggering iCloud Drive access...${NC}"
358
+ ls "$HOME/Library/Mobile Documents/com~apple~CloudDocs" > /dev/null 2>&1 || true
359
+ FOLDERS_TO_CONFIGURE="$FOLDERS_TO_CONFIGURE:$HOME/Library/Mobile Documents/com~apple~CloudDocs"
360
+ echo -e " ${GREEN}✓ iCloud Drive access requested${NC}"
361
+ fi
362
+ fi
363
+
364
+ # Custom folder
365
+ echo ""
366
+ read -p " Add a custom folder path? [y/N]: " ADD_CUSTOM
367
+ if [ "$ADD_CUSTOM" = "y" ] || [ "$ADD_CUSTOM" = "Y" ]; then
368
+ read -p " Enter folder path: " CUSTOM_FOLDER
369
+ if [ -d "$CUSTOM_FOLDER" ]; then
370
+ echo -e " ${YELLOW}Triggering access to $CUSTOM_FOLDER...${NC}"
371
+ ls "$CUSTOM_FOLDER" > /dev/null 2>&1 || true
372
+ FOLDERS_TO_CONFIGURE="$FOLDERS_TO_CONFIGURE:$CUSTOM_FOLDER"
373
+ echo -e " ${GREEN}✓ Custom folder access requested${NC}"
374
+ else
375
+ echo -e " ${RED}✗ Folder not found: $CUSTOM_FOLDER${NC}"
376
+ fi
377
+ fi
378
+
379
+ # Clean up the folders string (remove leading colon)
380
+ FOLDERS_TO_CONFIGURE=$(echo "$FOLDERS_TO_CONFIGURE" | sed 's/^://')
381
+
382
+ # Save to .env if not already there
383
+ if [ -n "$FOLDERS_TO_CONFIGURE" ]; then
384
+ # Check if ALLOWED_FOLDERS exists in .env
385
+ if grep -q "^ALLOWED_FOLDERS=" "$PROJECT_DIR/.env" 2>/dev/null; then
386
+ # Update existing
387
+ sed_inplace "s|^ALLOWED_FOLDERS=.*|ALLOWED_FOLDERS=$FOLDERS_TO_CONFIGURE|" "$PROJECT_DIR/.env"
388
+ else
389
+ # Add new
390
+ echo "" >> "$PROJECT_DIR/.env"
391
+ echo "# Folders the agent is allowed to access" >> "$PROJECT_DIR/.env"
392
+ echo "ALLOWED_FOLDERS=$FOLDERS_TO_CONFIGURE" >> "$PROJECT_DIR/.env"
393
+ fi
394
+ echo -e "\n${GREEN}✓ Folder access configuration saved to .env${NC}"
395
+ fi
396
+
397
+ echo -e "${YELLOW}Note: If permission dialogs appeared, make sure to click 'Allow'${NC}"
398
+ echo -e "${YELLOW} You may need to restart the service for changes to take effect${NC}"
399
+ fi
400
+
401
+ # =============================================================================
402
+ # 6. Mode Selection
403
+ # =============================================================================
404
+ echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
405
+ echo -e "${YELLOW}Which mode would you like to install?${NC}"
406
+ echo ""
407
+ echo -e " ${GREEN}1)${NC} Gateway (recommended)"
408
+ echo " Multi-client gateway with Telegram"
409
+ echo ""
410
+ echo -e " ${GREEN}2)${NC} MCP only"
411
+ echo " Expose as MCP server for Claude Desktop"
412
+ echo ""
413
+ echo -e " ${GREEN}3)${NC} Both"
414
+ echo " Gateway + MCP server together"
415
+ echo ""
416
+ read -p "Choose mode [1/2/3] (default: 1): " MODE_CHOICE
417
+ MODE_CHOICE=${MODE_CHOICE:-1}
418
+
419
+ case $MODE_CHOICE in
420
+ 2)
421
+ RUN_MODE="mcp"
422
+ echo -e "${GREEN}✓ MCP mode selected${NC}"
423
+ ;;
424
+ 3)
425
+ RUN_MODE="both"
426
+ echo -e "${GREEN}✓ Both modes selected${NC}"
427
+ ;;
428
+ *)
429
+ RUN_MODE="gateway"
430
+ echo -e "${GREEN}✓ Gateway mode selected${NC}"
431
+ ;;
432
+ esac
433
+
434
+ # MCP Configuration for Claude Desktop - auto-configure when MCP mode is selected
435
+ CONFIGURE_CLAUDE_DESKTOP=false
436
+ if [ "$RUN_MODE" = "mcp" ] || [ "$RUN_MODE" = "both" ]; then
437
+ CONFIGURE_CLAUDE_DESKTOP=true
438
+ echo -e "${GREEN}✓ Will configure Claude Desktop for MCP${NC}"
439
+ fi
440
+
441
+ # =============================================================================
442
+ # 7. Sleep Prevention (for gateway/both modes)
443
+ # =============================================================================
444
+ if [ "$RUN_MODE" = "gateway" ] || [ "$RUN_MODE" = "both" ]; then
445
+ echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
446
+ echo -e "${YELLOW}Configuring system sleep settings...${NC}"
447
+
448
+ USE_CAFFEINATE=false
449
+
450
+ if [ "$PLATFORM" = "macos" ]; then
451
+ echo -e "${YELLOW}This requires sudo access to modify power management settings.${NC}"
452
+ sudo pmset -c sleep 0 displaysleep 10
453
+ echo -e "${GREEN}✓ System configured: sleep disabled when plugged in, display sleeps after 10 min${NC}"
454
+
455
+ # Ask if user also wants caffeinate as extra protection
456
+ echo ""
457
+ echo -e "${YELLOW}Optional: Also use caffeinate for extra protection?${NC}"
458
+ echo " This adds an extra layer - prevents sleep specifically while the service runs."
459
+ echo ""
460
+ read -p "Enable caffeinate? [y/N]: " ENABLE_CAFFEINATE
461
+
462
+ if [ "$ENABLE_CAFFEINATE" = "y" ] || [ "$ENABLE_CAFFEINATE" = "Y" ]; then
463
+ USE_CAFFEINATE=true
464
+ echo -e "${GREEN}✓ Caffeinate enabled${NC}"
465
+ else
466
+ echo -e "${GREEN}✓ Using system settings only${NC}"
467
+ fi
468
+ elif [ "$PLATFORM" = "linux" ]; then
469
+ echo -e "${YELLOW}On Linux, sleep prevention depends on your desktop environment.${NC}"
470
+ echo -e " You can use ${GREEN}systemd-inhibit${NC} to prevent sleep while the service runs."
471
+ echo -e " The systemd service will be configured with the Idle inhibitor."
472
+ echo -e "${GREEN}✓ Sleep prevention will be handled by the systemd service${NC}"
473
+ fi
474
+ fi
475
+
476
+ # =============================================================================
477
+ # 8. Service Installation (platform-specific)
478
+ # =============================================================================
479
+
480
+ if [ "$PLATFORM" = "macos" ]; then
481
+ # ── macOS: launchd ──────────────────────────────────────────────────
482
+
483
+ # Unload existing service if present
484
+ if launchctl list 2>/dev/null | grep -q "$PLIST_NAME"; then
485
+ echo -e "\n${YELLOW}Stopping existing service...${NC}"
486
+ launchctl unload "$PLIST_PATH" 2>/dev/null || true
487
+ echo -e "${GREEN}✓ Existing service stopped${NC}"
488
+ fi
489
+
490
+ # Create the launchd plist file (for gateway or both modes)
491
+ if [ "$RUN_MODE" = "gateway" ] || [ "$RUN_MODE" = "both" ]; then
492
+ echo -e "\n${YELLOW}Creating launchd service...${NC}"
493
+
494
+ if [ "$USE_CAFFEINATE" = true ]; then
495
+ cat > "$PLIST_PATH" << EOF
496
+ <?xml version="1.0" encoding="UTF-8"?>
497
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
498
+ <plist version="1.0">
499
+ <dict>
500
+ <key>Label</key>
501
+ <string>$PLIST_NAME</string>
502
+
503
+ <key>ProgramArguments</key>
504
+ <array>
505
+ <string>/usr/bin/caffeinate</string>
506
+ <string>-i</string>
507
+ <string>$NODE_PATH</string>
508
+ <string>$PROJECT_DIR/dist/index.js</string>
509
+ <string>$RUN_MODE</string>
510
+ </array>
511
+
512
+ <key>WorkingDirectory</key>
513
+ <string>$PROJECT_DIR</string>
514
+
515
+ <key>EnvironmentVariables</key>
516
+ <dict>
517
+ <key>PATH</key>
518
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/.local/bin</string>
519
+ </dict>
520
+
521
+ <key>RunAtLoad</key>
522
+ <true/>
523
+
524
+ <key>KeepAlive</key>
525
+ <true/>
526
+
527
+ <key>StandardOutPath</key>
528
+ <string>$LOGS_DIR/stdout.log</string>
529
+
530
+ <key>StandardErrorPath</key>
531
+ <string>$LOGS_DIR/stderr.log</string>
532
+ </dict>
533
+ </plist>
534
+ EOF
535
+ else
536
+ cat > "$PLIST_PATH" << EOF
537
+ <?xml version="1.0" encoding="UTF-8"?>
538
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
539
+ <plist version="1.0">
540
+ <dict>
541
+ <key>Label</key>
542
+ <string>$PLIST_NAME</string>
543
+
544
+ <key>ProgramArguments</key>
545
+ <array>
546
+ <string>$NODE_PATH</string>
547
+ <string>$PROJECT_DIR/dist/index.js</string>
548
+ <string>$RUN_MODE</string>
549
+ </array>
550
+
551
+ <key>WorkingDirectory</key>
552
+ <string>$PROJECT_DIR</string>
553
+
554
+ <key>EnvironmentVariables</key>
555
+ <dict>
556
+ <key>PATH</key>
557
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$HOME/.local/bin</string>
558
+ </dict>
559
+
560
+ <key>RunAtLoad</key>
561
+ <true/>
562
+
563
+ <key>KeepAlive</key>
564
+ <true/>
565
+
566
+ <key>StandardOutPath</key>
567
+ <string>$LOGS_DIR/stdout.log</string>
568
+
569
+ <key>StandardErrorPath</key>
570
+ <string>$LOGS_DIR/stderr.log</string>
571
+ </dict>
572
+ </plist>
573
+ EOF
574
+ fi
575
+
576
+ echo -e "${GREEN}✓ Service configuration created${NC}"
577
+
578
+ # Load the service
579
+ echo -e "\n${YELLOW}Starting service...${NC}"
580
+ launchctl load "$PLIST_PATH"
581
+ sleep 2
582
+
583
+ # Check if service is running
584
+ if launchctl list | grep -q "$PLIST_NAME"; then
585
+ echo -e "${GREEN}✓ Service started successfully${NC}"
586
+ else
587
+ echo -e "${RED}✗ Service failed to start. Check logs:${NC}"
588
+ echo -e " tail -f $LOGS_DIR/stderr.log"
589
+ exit 1
590
+ fi
591
+ fi
592
+
593
+ elif [ "$PLATFORM" = "linux" ]; then
594
+ # ── Linux: systemd user service ─────────────────────────────────────
595
+
596
+ # Stop existing service if present
597
+ if systemctl --user is-active "$SYSTEMD_SERVICE" &>/dev/null; then
598
+ echo -e "\n${YELLOW}Stopping existing service...${NC}"
599
+ systemctl --user stop "$SYSTEMD_SERVICE" 2>/dev/null || true
600
+ echo -e "${GREEN}✓ Existing service stopped${NC}"
601
+ fi
602
+
603
+ # Create the systemd user service (for gateway or both modes)
604
+ if [ "$RUN_MODE" = "gateway" ] || [ "$RUN_MODE" = "both" ]; then
605
+ echo -e "\n${YELLOW}Creating systemd user service...${NC}"
606
+
607
+ mkdir -p "$SYSTEMD_DIR"
608
+ cat > "$SYSTEMD_PATH" << EOF
609
+ [Unit]
610
+ Description=Deskmate - Local Machine Assistant
611
+ After=network-online.target
612
+ Wants=network-online.target
613
+
614
+ [Service]
615
+ Type=simple
616
+ ExecStart=$NODE_PATH $PROJECT_DIR/dist/index.js $RUN_MODE
617
+ WorkingDirectory=$PROJECT_DIR
618
+ Environment=PATH=/usr/local/bin:/usr/bin:/bin:$HOME/.local/bin
619
+ Restart=always
620
+ RestartSec=5
621
+
622
+ # Prevent system sleep while running
623
+ InhibitDelayMaxSec=5
624
+
625
+ StandardOutput=append:$LOGS_DIR/stdout.log
626
+ StandardError=append:$LOGS_DIR/stderr.log
627
+
628
+ [Install]
629
+ WantedBy=default.target
630
+ EOF
631
+
632
+ echo -e "${GREEN}✓ Service configuration created${NC}"
633
+
634
+ # Reload and start the service
635
+ echo -e "\n${YELLOW}Starting service...${NC}"
636
+ systemctl --user daemon-reload
637
+ systemctl --user enable "$SYSTEMD_SERVICE"
638
+ systemctl --user start "$SYSTEMD_SERVICE"
639
+ sleep 2
640
+
641
+ # Check if service is running
642
+ if systemctl --user is-active "$SYSTEMD_SERVICE" &>/dev/null; then
643
+ echo -e "${GREEN}✓ Service started successfully${NC}"
644
+ else
645
+ echo -e "${RED}✗ Service failed to start. Check logs:${NC}"
646
+ echo -e " journalctl --user -u $SYSTEMD_SERVICE -f"
647
+ echo -e " tail -f $LOGS_DIR/stderr.log"
648
+ exit 1
649
+ fi
650
+
651
+ # Enable lingering so the user service runs without an active login session
652
+ if command -v loginctl &>/dev/null; then
653
+ loginctl enable-linger "$(whoami)" 2>/dev/null || true
654
+ echo -e "${GREEN}✓ Lingering enabled (service runs without active login)${NC}"
655
+ fi
656
+ fi
657
+ fi
658
+
659
+ # =============================================================================
660
+ # 9. Claude Desktop MCP Config
661
+ # =============================================================================
662
+ if [ "$CONFIGURE_CLAUDE_DESKTOP" = true ]; then
663
+ echo -e "\n${YELLOW}Configuring Claude Desktop...${NC}"
664
+
665
+ # Create config directory if it doesn't exist
666
+ mkdir -p "$(dirname "$CLAUDE_DESKTOP_CONFIG")"
667
+
668
+ # Get WORKING_DIR from .env or use HOME
669
+ WORKING_DIR=$(grep -E "^WORKING_DIR=" "$PROJECT_DIR/.env" | cut -d'=' -f2 || echo "$HOME")
670
+ WORKING_DIR=${WORKING_DIR:-$HOME}
671
+
672
+ # Check if config file exists and has content
673
+ if [ -f "$CLAUDE_DESKTOP_CONFIG" ] && [ -s "$CLAUDE_DESKTOP_CONFIG" ]; then
674
+ # Backup existing config
675
+ cp "$CLAUDE_DESKTOP_CONFIG" "$CLAUDE_DESKTOP_CONFIG.backup"
676
+ echo -e "${GREEN}✓ Backed up existing config${NC}"
677
+
678
+ # Check if deskmate already configured
679
+ if grep -q '"deskmate"' "$CLAUDE_DESKTOP_CONFIG"; then
680
+ echo -e "${YELLOW}⚠ deskmate already configured in Claude Desktop${NC}"
681
+ echo " Please manually update the config if needed."
682
+ else
683
+ # Add to existing config using Python (more reliable JSON handling)
684
+ python3 << PYTHON_EOF
685
+ import json
686
+
687
+ config_path = "$CLAUDE_DESKTOP_CONFIG"
688
+ with open(config_path, 'r') as f:
689
+ config = json.load(f)
690
+
691
+ if 'mcpServers' not in config:
692
+ config['mcpServers'] = {}
693
+
694
+ config['mcpServers']['deskmate'] = {
695
+ "command": "$NODE_PATH",
696
+ "args": ["$PROJECT_DIR/dist/index.js", "mcp"],
697
+ "env": {
698
+ "WORKING_DIR": "$WORKING_DIR"
699
+ }
700
+ }
701
+
702
+ with open(config_path, 'w') as f:
703
+ json.dump(config, f, indent=2)
704
+
705
+ print("Config updated successfully")
706
+ PYTHON_EOF
707
+ echo -e "${GREEN}✓ Added deskmate to Claude Desktop config${NC}"
708
+ fi
709
+ else
710
+ # Create new config file
711
+ cat > "$CLAUDE_DESKTOP_CONFIG" << EOF
712
+ {
713
+ "mcpServers": {
714
+ "deskmate": {
715
+ "command": "$NODE_PATH",
716
+ "args": ["$PROJECT_DIR/dist/index.js", "mcp"],
717
+ "env": {
718
+ "WORKING_DIR": "$WORKING_DIR"
719
+ }
720
+ }
721
+ }
722
+ }
723
+ EOF
724
+ echo -e "${GREEN}✓ Created Claude Desktop config${NC}"
725
+ fi
726
+
727
+ echo -e "${YELLOW}Note: Restart Claude Desktop for changes to take effect${NC}"
728
+ fi
729
+
730
+ # =============================================================================
731
+ # 10. Summary
732
+ # =============================================================================
733
+ echo -e "\n${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
734
+ echo -e "${GREEN}Installation complete!${NC}"
735
+ echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
736
+ echo ""
737
+
738
+ case $RUN_MODE in
739
+ gateway)
740
+ echo -e "Your gateway is now running as a background service."
741
+ echo -e "Telegram client is active — open Telegram and message your bot."
742
+ ;;
743
+ mcp)
744
+ echo -e "MCP server configured for Claude Desktop."
745
+ echo -e "Claude Desktop will start the MCP server on demand."
746
+ ;;
747
+ both)
748
+ echo -e "Both gateway and MCP server are configured!"
749
+ echo -e "• Gateway: Running as background service (Telegram active)"
750
+ echo -e "• MCP server: Available to Claude Desktop"
751
+ ;;
752
+ esac
753
+
754
+ echo ""
755
+ echo -e "${YELLOW}Useful commands:${NC}"
756
+
757
+ if [ "$RUN_MODE" = "gateway" ] || [ "$RUN_MODE" = "both" ]; then
758
+ echo -e " ${GREEN}View logs:${NC}"
759
+ echo " tail -f $LOGS_DIR/stdout.log"
760
+ echo " tail -f $LOGS_DIR/stderr.log"
761
+ echo ""
762
+
763
+ if [ "$PLATFORM" = "macos" ]; then
764
+ echo -e " ${GREEN}Stop service:${NC}"
765
+ echo " launchctl unload $PLIST_PATH"
766
+ echo ""
767
+ echo -e " ${GREEN}Start service:${NC}"
768
+ echo " launchctl load $PLIST_PATH"
769
+ echo ""
770
+ echo -e " ${GREEN}Restart service:${NC}"
771
+ echo " launchctl unload $PLIST_PATH && launchctl load $PLIST_PATH"
772
+ echo ""
773
+ echo -e " ${GREEN}Check status:${NC}"
774
+ echo " launchctl list | grep deskmate"
775
+ echo ""
776
+ echo -e " ${GREEN}Restore default sleep settings:${NC}"
777
+ echo " sudo pmset -c sleep 1"
778
+ elif [ "$PLATFORM" = "linux" ]; then
779
+ echo -e " ${GREEN}Stop service:${NC}"
780
+ echo " systemctl --user stop $SYSTEMD_SERVICE"
781
+ echo ""
782
+ echo -e " ${GREEN}Start service:${NC}"
783
+ echo " systemctl --user start $SYSTEMD_SERVICE"
784
+ echo ""
785
+ echo -e " ${GREEN}Restart service:${NC}"
786
+ echo " systemctl --user restart $SYSTEMD_SERVICE"
787
+ echo ""
788
+ echo -e " ${GREEN}Check status:${NC}"
789
+ echo " systemctl --user status $SYSTEMD_SERVICE"
790
+ echo ""
791
+ echo -e " ${GREEN}View journal logs:${NC}"
792
+ echo " journalctl --user -u $SYSTEMD_SERVICE -f"
793
+ fi
794
+ fi
795
+
796
+ if [ "$RUN_MODE" = "mcp" ] || [ "$RUN_MODE" = "both" ]; then
797
+ echo ""
798
+ echo -e " ${GREEN}Test MCP server manually:${NC}"
799
+ echo " npm run start:mcp"
800
+ echo ""
801
+ echo -e " ${GREEN}Claude Desktop config location:${NC}"
802
+ echo " $CLAUDE_DESKTOP_CONFIG"
803
+ fi
804
+
805
+ echo ""
806
+ echo -e " ${GREEN}Reconfigure:${NC}"
807
+ echo " deskmate init"
808
+ echo ""
809
+
810
+ if [ "$PLATFORM" = "macos" ]; then
811
+ echo -e "${YELLOW}Permissions reminder:${NC}"
812
+ echo " If you skipped permissions setup or need to reconfigure:"
813
+ echo " System Settings > Privacy & Security > [Screen Recording/Accessibility/Full Disk Access]"
814
+ echo ""
815
+ fi
816
+
817
+ echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"