abstractassistant 0.3.2__py3-none-any.whl → 0.3.3__py3-none-any.whl

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.
@@ -6,21 +6,363 @@ This script can be run after installation to create a macOS app bundle
6
6
  that allows launching AbstractAssistant from the Dock.
7
7
  """
8
8
 
9
+ import os
9
10
  import sys
11
+ import shutil
12
+ import subprocess
10
13
  from pathlib import Path
11
14
 
15
+ try:
16
+ from PIL import Image
17
+ PIL_AVAILABLE = True
18
+ except ImportError:
19
+ PIL_AVAILABLE = False
20
+
21
+
22
+ class MacOSAppBundleGenerator:
23
+ """Generates macOS app bundles for AbstractAssistant."""
24
+
25
+ def __init__(self, package_dir: Path):
26
+ """Initialize the app bundle generator.
27
+
28
+ Args:
29
+ package_dir: Path to the abstractassistant package directory
30
+ """
31
+ self.package_dir = package_dir
32
+ self.app_name = "AbstractAssistant"
33
+ self.app_bundle_path = Path("/Applications") / f"{self.app_name}.app"
34
+
35
+ def is_macos(self) -> bool:
36
+ """Check if running on macOS."""
37
+ return sys.platform == "darwin"
38
+
39
+ def has_permissions(self) -> bool:
40
+ """Check if we have permissions to write to /Applications."""
41
+ try:
42
+ test_file = Path("/Applications") / ".test_write_permission"
43
+ test_file.touch()
44
+ test_file.unlink()
45
+ return True
46
+ except (PermissionError, OSError):
47
+ return False
48
+
49
+ def create_app_bundle_structure(self) -> bool:
50
+ """Create the basic app bundle directory structure."""
51
+ try:
52
+ # Create main directories
53
+ contents_dir = self.app_bundle_path / "Contents"
54
+ macos_dir = contents_dir / "MacOS"
55
+ resources_dir = contents_dir / "Resources"
56
+
57
+ for directory in [contents_dir, macos_dir, resources_dir]:
58
+ directory.mkdir(parents=True, exist_ok=True)
59
+
60
+ return True
61
+ except Exception as e:
62
+ print(f"Error creating app bundle structure: {e}")
63
+ return False
64
+
65
+ def generate_app_icon(self) -> bool:
66
+ """Generate or preserve the app icon."""
67
+ try:
68
+ icon_path = self.app_bundle_path / "Contents" / "Resources" / "icon.png"
69
+
70
+ # Look for custom icons in multiple locations
71
+ custom_icon_paths = [
72
+ # Project directory bundle (development) - inside package directory
73
+ Path(self.package_dir).parent / "AbstractAssistant.app" / "Contents" / "Resources" / "icon.png",
74
+ # Also check if it's in the package directory itself
75
+ Path(self.package_dir) / "AbstractAssistant.app" / "Contents" / "Resources" / "icon.png",
76
+ # Any icon.png in the project root
77
+ Path(self.package_dir).parent / "icon.png",
78
+ # Any icon.png in the package directory
79
+ Path(self.package_dir) / "icon.png",
80
+ ]
81
+
82
+ custom_icon_found = False
83
+
84
+ # Try each custom icon location
85
+ for custom_path in custom_icon_paths:
86
+ if custom_path and custom_path.exists():
87
+ print(f"Using existing custom icon from {custom_path}")
88
+ shutil.copy2(str(custom_path), str(icon_path))
89
+ custom_icon_found = True
90
+ break
91
+
92
+ # If no custom icon found, try to restore from git
93
+ if not custom_icon_found:
94
+ try:
95
+ git_icon_path = self.package_dir.parent / "AbstractAssistant.app" / "Contents" / "Resources" / "icon.png"
96
+ if git_icon_path.parent.parent.parent.exists(): # Check if AbstractAssistant.app exists
97
+ # Try to get the icon from git
98
+ result = subprocess.run([
99
+ 'git', 'show', 'HEAD:AbstractAssistant.app/Contents/Resources/icon.png'
100
+ ], cwd=str(self.package_dir.parent), capture_output=True)
101
+
102
+ if result.returncode == 0:
103
+ print("Restoring custom icon from git history")
104
+ with open(str(icon_path), 'wb') as f:
105
+ f.write(result.stdout)
106
+ custom_icon_found = True
107
+ except Exception as git_error:
108
+ print(f"Could not restore icon from git: {git_error}")
109
+
110
+ # If still no custom icon, generate one
111
+ if not custom_icon_found:
112
+ print("Generating new icon using IconGenerator")
113
+ # Import the icon generator
114
+ sys.path.insert(0, str(self.package_dir))
115
+ from abstractassistant.utils.icon_generator import IconGenerator
116
+
117
+ # Generate high-resolution icon
118
+ generator = IconGenerator(size=512)
119
+ icon = generator.create_app_icon('blue', animated=False)
120
+
121
+ # Save as PNG
122
+ icon.save(str(icon_path))
123
+
124
+ # Create ICNS file
125
+ return self._create_icns_file(icon_path)
126
+
127
+ except Exception as e:
128
+ print(f"Error generating app icon: {e}")
129
+ return False
130
+
131
+ def _create_icns_file(self, png_path: Path) -> bool:
132
+ """Create ICNS file from PNG using macOS iconutil."""
133
+ try:
134
+ # Create iconset directory
135
+ iconset_dir = png_path.parent / "temp_icons.iconset"
136
+ iconset_dir.mkdir(exist_ok=True)
137
+
138
+ # Load the PNG and create different sizes
139
+ icon = Image.open(png_path)
140
+ sizes = [
141
+ (16, 'icon_16x16.png'),
142
+ (32, 'icon_16x16@2x.png'),
143
+ (32, 'icon_32x32.png'),
144
+ (64, 'icon_32x32@2x.png'),
145
+ (128, 'icon_128x128.png'),
146
+ (256, 'icon_128x128@2x.png'),
147
+ (256, 'icon_256x256.png'),
148
+ (512, 'icon_256x256@2x.png'),
149
+ (512, 'icon_512x512.png'),
150
+ (1024, 'icon_512x512@2x.png')
151
+ ]
152
+
153
+ for size, filename in sizes:
154
+ resized = icon.resize((size, size), Image.Resampling.LANCZOS)
155
+ resized.save(iconset_dir / filename)
156
+
157
+ # Convert to ICNS
158
+ icns_path = png_path.parent / "icon.icns"
159
+ result = subprocess.run([
160
+ 'iconutil', '-c', 'icns', str(iconset_dir),
161
+ '-o', str(icns_path)
162
+ ], capture_output=True, text=True)
163
+
164
+ # Clean up
165
+ shutil.rmtree(iconset_dir)
166
+
167
+ return result.returncode == 0
168
+
169
+ except Exception as e:
170
+ print(f"Error creating ICNS file: {e}")
171
+ return False
172
+
173
+ def create_info_plist(self) -> bool:
174
+ """Create the Info.plist file."""
175
+ try:
176
+ plist_content = '''<?xml version="1.0" encoding="UTF-8"?>
177
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
178
+ <plist version="1.0">
179
+ <dict>
180
+ <key>CFBundleExecutable</key>
181
+ <string>AbstractAssistant</string>
182
+ <key>CFBundleIdentifier</key>
183
+ <string>ai.abstractcore.abstractassistant</string>
184
+ <key>CFBundleName</key>
185
+ <string>AbstractAssistant</string>
186
+ <key>CFBundleDisplayName</key>
187
+ <string>AbstractAssistant</string>
188
+ <key>CFBundleVersion</key>
189
+ <string>0.3.2</string>
190
+ <key>CFBundleShortVersionString</key>
191
+ <string>0.3.2</string>
192
+ <key>CFBundlePackageType</key>
193
+ <string>APPL</string>
194
+ <key>CFBundleSignature</key>
195
+ <string>????</string>
196
+ <key>CFBundleIconFile</key>
197
+ <string>icon.icns</string>
198
+ <key>LSMinimumSystemVersion</key>
199
+ <string>10.15</string>
200
+ <key>NSHighResolutionCapable</key>
201
+ <true/>
202
+ <key>NSRequiresAquaSystemAppearance</key>
203
+ <false/>
204
+ <key>LSUIElement</key>
205
+ <true/>
206
+ <key>NSAppleScriptEnabled</key>
207
+ <false/>
208
+ <key>CFBundleDocumentTypes</key>
209
+ <array/>
210
+ <key>NSPrincipalClass</key>
211
+ <string>NSApplication</string>
212
+ </dict>
213
+ </plist>'''
214
+
215
+ plist_path = self.app_bundle_path / "Contents" / "Info.plist"
216
+ plist_path.write_text(plist_content)
217
+ return True
218
+
219
+ except Exception as e:
220
+ print(f"Error creating Info.plist: {e}")
221
+ return False
222
+
223
+ def create_launch_script(self) -> bool:
224
+ """Create the executable launch script."""
225
+ try:
226
+ script_content = '''#!/bin/bash
227
+
228
+ # AbstractAssistant macOS App Launcher
229
+ # This script launches the AbstractAssistant application
230
+
231
+ # Set up environment paths for GUI launch (common locations)
232
+ export PATH="/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:$PATH"
233
+
234
+ # Add user-specific Python paths if they exist
235
+ if [ -d "$HOME/.pyenv/shims" ]; then
236
+ export PATH="$HOME/.pyenv/shims:$PATH"
237
+ fi
238
+
239
+ if [ -d "$HOME/.local/bin" ]; then
240
+ export PATH="$HOME/.local/bin:$PATH"
241
+ fi
242
+
243
+ if [ -d "/opt/anaconda3/bin" ]; then
244
+ export PATH="/opt/anaconda3/bin:$PATH"
245
+ fi
246
+
247
+ if [ -d "$HOME/anaconda3/bin" ]; then
248
+ export PATH="$HOME/anaconda3/bin:$PATH"
249
+ fi
250
+
251
+ # Function to find Python with abstractassistant installed
252
+ find_python_with_abstractassistant() {
253
+ # First try pyenv's active Python (most reliable)
254
+ if [ -x "$HOME/.pyenv/shims/python3" ]; then
255
+ if "$HOME/.pyenv/shims/python3" -c "import abstractassistant" 2>/dev/null; then
256
+ echo "$HOME/.pyenv/shims/python3"
257
+ return 0
258
+ fi
259
+ fi
260
+
261
+ # Try PATH-based search (respects current environment)
262
+ for python_cmd in python3 python python3.13 python3.12 python3.11 python3.10 python3.9; do
263
+ if command -v "$python_cmd" >/dev/null 2>&1; then
264
+ if "$python_cmd" -c "import abstractassistant" 2>/dev/null; then
265
+ echo "$python_cmd"
266
+ return 0
267
+ fi
268
+ fi
269
+ done
270
+
271
+ # Try specific pyenv versions (sorted by version number, newest first)
272
+ for version_dir in $(ls -1v "$HOME/.pyenv/versions/" 2>/dev/null | sort -V -r); do
273
+ py="$HOME/.pyenv/versions/$version_dir/bin/python3"
274
+ if [ -x "$py" ] && "$py" -c "import abstractassistant" 2>/dev/null; then
275
+ echo "$py"
276
+ return 0
277
+ fi
278
+ done
279
+
280
+ # Try other common locations
281
+ for python_path in \\
282
+ "/usr/local/bin/python3" \\
283
+ "/opt/homebrew/bin/python3" \\
284
+ "/usr/bin/python3" \\
285
+ "/opt/anaconda3/bin/python" \\
286
+ "$HOME/anaconda3/bin/python" \\
287
+ "/usr/local/anaconda3/bin/python"; do
288
+
289
+ if [ -x "$python_path" ] && "$python_path" -c "import abstractassistant" 2>/dev/null; then
290
+ echo "$python_path"
291
+ return 0
292
+ fi
293
+ done
294
+
295
+ return 1
296
+ }
297
+
298
+ # Find Python with AbstractAssistant
299
+ PYTHON_EXEC=$(find_python_with_abstractassistant)
300
+
301
+ if [ -z "$PYTHON_EXEC" ]; then
302
+ osascript -e 'display dialog "AbstractAssistant not found in any Python installation.\\n\\nPlease install it with:\\npip install abstractassistant\\n\\nOr run the create-app-bundle command after installation." with title "AbstractAssistant" buttons {"OK"} default button "OK" with icon caution'
303
+ exit 1
304
+ fi
305
+
306
+ # Change to a neutral directory to avoid importing development versions
307
+ cd /tmp
308
+
309
+ # Launch the assistant
310
+ exec "$PYTHON_EXEC" -m abstractassistant.cli "$@"'''
311
+
312
+ script_path = self.app_bundle_path / "Contents" / "MacOS" / "AbstractAssistant"
313
+ script_path.write_text(script_content)
314
+
315
+ # Make executable
316
+ os.chmod(script_path, 0o755)
317
+ return True
318
+
319
+ except Exception as e:
320
+ print(f"Error creating launch script: {e}")
321
+ return False
322
+
323
+ def generate_app_bundle(self) -> bool:
324
+ """Generate the complete macOS app bundle."""
325
+ if not self.is_macos():
326
+ print("macOS app bundle generation is only available on macOS")
327
+ return False
328
+
329
+ if not self.has_permissions():
330
+ print("Insufficient permissions to create app bundle in /Applications")
331
+ print("Please run with sudo or manually copy the app bundle")
332
+ return False
333
+
334
+ print("Creating macOS app bundle...")
335
+
336
+ # Remove existing bundle if it exists
337
+ if self.app_bundle_path.exists():
338
+ shutil.rmtree(self.app_bundle_path)
339
+
340
+ # Create bundle structure
341
+ if not self.create_app_bundle_structure():
342
+ return False
343
+
344
+ # Generate icon
345
+ if not self.generate_app_icon():
346
+ return False
347
+
348
+ # Create Info.plist
349
+ if not self.create_info_plist():
350
+ return False
351
+
352
+ # Create launch script
353
+ if not self.create_launch_script():
354
+ return False
355
+
356
+ print(f"✅ macOS app bundle created successfully!")
357
+ print(f" Location: {self.app_bundle_path}")
358
+ print(f" You can now launch AbstractAssistant from the Dock!")
359
+
360
+ return True
361
+
12
362
 
13
363
  def main():
14
364
  """Create macOS app bundle for AbstractAssistant."""
15
365
  try:
16
- # Import the app bundle generator
17
- try:
18
- import setup_macos_app
19
- MacOSAppBundleGenerator = setup_macos_app.MacOSAppBundleGenerator
20
- except ImportError:
21
- # Fallback: try importing from abstractassistant package
22
- from abstractassistant.setup_macos_app import MacOSAppBundleGenerator
23
-
24
366
  # Find the package directory
25
367
  import abstractassistant
26
368
  package_dir = Path(abstractassistant.__file__).parent
@@ -121,8 +121,10 @@ class ChatBubble:
121
121
  )
122
122
  self.text_input.pack(fill="both", expand=True)
123
123
 
124
- # Bind Enter key for sending (Cmd+Enter on macOS)
125
- self.text_input.bind("<Command-Return>", lambda e: self._send_message())
124
+ # Bind keyboard shortcuts for message sending
125
+ # Enter = send message, Shift+Enter = new line
126
+ self.text_input.bind("<Return>", self._handle_enter_key)
127
+ self.text_input.bind("<KP_Enter>", self._handle_enter_key) # Numpad Enter
126
128
 
127
129
  # Controls frame
128
130
  controls_frame = ctk.CTkFrame(main_frame, fg_color="transparent")
@@ -271,6 +273,17 @@ class ChatBubble:
271
273
  text_color=status_colors.get(status, "gray")
272
274
  )
273
275
 
276
+ def _handle_enter_key(self, event):
277
+ """Handle Enter key press in text input."""
278
+ # Check if Shift is held down
279
+ if event.state & 0x1: # Shift modifier
280
+ # Shift+Enter: Allow default behavior (new line)
281
+ return None
282
+ else:
283
+ # Plain Enter: Send message
284
+ self._send_message()
285
+ return "break" # Prevent default behavior
286
+
274
287
  def _send_message(self):
275
288
  """Send the current message."""
276
289
  if self.is_sending or self.text_input is None:
@@ -1070,13 +1070,15 @@ class QtChatBubble(QWidget):
1070
1070
 
1071
1071
  # Check for Enter/Return key
1072
1072
  if event.key() == Qt.Key.Key_Return or event.key() == Qt.Key.Key_Enter:
1073
- # Shift+Enter or Ctrl+Enter or Cmd+Enter should send message
1074
- if (event.modifiers() & Qt.KeyboardModifier.ShiftModifier or
1075
- event.modifiers() & Qt.KeyboardModifier.ControlModifier or
1076
- event.modifiers() & Qt.KeyboardModifier.MetaModifier):
1073
+ # Shift+Enter should add a new line
1074
+ if event.modifiers() & Qt.KeyboardModifier.ShiftModifier:
1075
+ # Allow default behavior (new line)
1076
+ QTextEdit.keyPressEvent(self.input_text, event)
1077
+ return
1078
+ # Plain Enter should send message
1079
+ else:
1077
1080
  self.send_message()
1078
1081
  return
1079
- # Plain Enter should add a new line (default behavior)
1080
1082
 
1081
1083
  # Call original keyPressEvent for all other keys
1082
1084
  QTextEdit.keyPressEvent(self.input_text, event)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: abstractassistant
3
- Version: 0.3.2
3
+ Version: 0.3.3
4
4
  Summary: A sleek (macOS) system tray application providing instant access to LLMs
5
5
  Author-email: Laurent-Philippe Albou <contact@abstractcore.ai>
6
6
  License-Expression: MIT
@@ -211,26 +211,26 @@ assistant --debug
211
211
 
212
212
  ```
213
213
  abstractassistant/
214
- ├── pyproject.toml # Package configuration
215
- ├── requirements.txt # Dependencies
216
- ├── config.toml # Default configuration
217
- ├── abstractassistant/ # Main package
218
- │ ├── cli.py # CLI entry point
219
- │ ├── app.py # Main application
220
- │ ├── config.py # Configuration management
221
- │ ├── core/ # Business logic
222
- │ │ ├── llm_manager.py # LLM provider management
223
- │ │ └── tts_manager.py # Voice/TTS integration
224
- │ ├── ui/ # User interface
225
- │ │ ├── qt_bubble.py # Main Qt chat interface
226
- │ │ └── toast_window.py # Notification system
227
- │ └── utils/ # Utilities
228
- │ ├── icon_generator.py # Dynamic icon creation
229
- │ └── markdown_renderer.py # Markdown processing
230
- └── docs/ # Documentation
231
- ├── ARCHITECTURE.md # Technical documentation
232
- ├── INSTALLATION.md # Installation guide
233
- └── USAGE.md # Usage guide
214
+ ├── pyproject.toml # Package configuration
215
+ ├── requirements.txt # Dependencies
216
+ ├── config.toml # Default configuration
217
+ ├── abstractassistant/ # Main package
218
+ │ ├── cli.py # CLI entry point
219
+ │ ├── app.py # Main application
220
+ │ ├── config.py # Configuration management
221
+ │ ├── core/ # Business logic
222
+ │ │ ├── llm_manager.py # LLM provider management
223
+ │ │ └── tts_manager.py # Voice/TTS integration
224
+ │ ├── ui/ # User interface
225
+ │ │ ├── qt_bubble.py # Main Qt chat interface
226
+ │ │ └── toast_window.py # Notification system
227
+ │ └── utils/ # Utilities
228
+ │ ├── icon_generator.py # Dynamic icon creation
229
+ │ └── markdown_renderer.py # Markdown processing
230
+ └── docs/ # Documentation
231
+ ├── architecture.md # Technical documentation
232
+ ├── installation.md # Installation guide
233
+ └── getting-started.md # Usage guide
234
234
  ```
235
235
 
236
236
  ## 🌟 Why AbstractAssistant?
@@ -1,18 +1,17 @@
1
- setup_macos_app.py,sha256=9dIPr9TipjtgdIhd0MnR2syRNoFyBVMnRsWDW0UCT3A,10736
2
1
  abstractassistant/__init__.py,sha256=homfqMDh6sX2nBROtk6-y72jnrStPph8gEOeT0OjKyU,35
3
2
  abstractassistant/app.py,sha256=yGFszbaqja_Y1ejSMcVYIcq8f1qdeZpVTb032geI-ZE,40374
4
3
  abstractassistant/cli.py,sha256=SQPxQCLjX-LOlhSEvG302D0AOyxlxo5QM2imxr9wxmc,4385
5
4
  abstractassistant/config.py,sha256=KodfPYTpHtavJyne-h-B-r3kbEt1uusSY8GknGLtDL8,5809
6
- abstractassistant/create_app_bundle.py,sha256=LAZdp2C90ikMVd3KPdwNYBYUASbHpypOJIwvx6fQyXM,1698
5
+ abstractassistant/create_app_bundle.py,sha256=lJJsdnjl-WSdQVn8uFp7c_3cJkx8liZbK2C7elOCG1A,14215
7
6
  abstractassistant/web_server.py,sha256=_pqMzy13qfim9BMBqQJQifWyX7UQXFD_sZeiu4ZBt40,12816
8
7
  abstractassistant/core/__init__.py,sha256=TETStgToTe7QSsCZgRHDk2oSErlLJoeGN0sFg4Yx2_c,15
9
8
  abstractassistant/core/llm_manager.py,sha256=hJun-nDfRv9zxv_3tfrHAmVYSYT96E-0zDJB2TiaSeQ,19226
10
9
  abstractassistant/core/tts_manager.py,sha256=Cxh302EgIycwkWxe7XntmLW-j_WusbJOYRCs3Jms3CU,9892
11
10
  abstractassistant/ui/__init__.py,sha256=aRNE2pS50nFAX6y--rSGMNYwhz905g14gRd6g4BolYU,13
12
- abstractassistant/ui/chat_bubble.py,sha256=TE6zPtQ46I9grKGAb744wHqk4yO6-und3iif8_33XGk,11357
11
+ abstractassistant/ui/chat_bubble.py,sha256=bY48b4IeQzOrRN2_sJ5OazhZcJ8IMaBM6R3EexvU30Q,11885
13
12
  abstractassistant/ui/history_dialog.py,sha256=949l8fVgRpUfRGcoOVRutERVXrShYgpKkFJCxo2JG-4,19323
14
13
  abstractassistant/ui/provider_manager.py,sha256=9IM-BxIs6lUlk6cDCBi7oZFMXmn4CFMlxh0s-_vhzXY,8403
15
- abstractassistant/ui/qt_bubble.py,sha256=cBjViRyusWWEmX5bHz4k-W8mUFRY6RPOuUq5AyN7ciI,97030
14
+ abstractassistant/ui/qt_bubble.py,sha256=ujJtoDuVuXthxfXsvHX-YbqV4GuUgNp2FGy_wT3t4UY,96990
16
15
  abstractassistant/ui/toast_manager.py,sha256=1aU4DPo-J45bC61gTEctHq98ZrHIFxRfZa_9Q8KF588,13721
17
16
  abstractassistant/ui/toast_window.py,sha256=BRSwEBlaND5LLipn1HOX0ISWxVH-zOHsYplFkiPaj_g,21727
18
17
  abstractassistant/ui/tts_state_manager.py,sha256=UF_zrfl9wf0hNHBGxevcoKxW5Dh7zXibUSVoSSjGP4o,10565
@@ -20,9 +19,9 @@ abstractassistant/ui/ui_styles.py,sha256=FvE2CVUbHmHu1PKVTBBGyhbt781qh4WjLMrHvil
20
19
  abstractassistant/utils/__init__.py,sha256=7Q3BxyXETkt3tm5trhuLTyL8PoECOK0QiK-0KUVAR2Q,16
21
20
  abstractassistant/utils/icon_generator.py,sha256=SWPgi1V6_8544Zbc2vAfFXAy15H35neyUGCYt2eKoic,16475
22
21
  abstractassistant/utils/markdown_renderer.py,sha256=u5tVIhulSwRYADiqJcZNoHhU8e6pJVgzrwZRd61Bov0,12585
23
- abstractassistant-0.3.2.dist-info/licenses/LICENSE,sha256=QUjFNAE-0yOkW9-Rle2axkpkt9H7xiZ2VbN-VeONhxc,1106
24
- abstractassistant-0.3.2.dist-info/METADATA,sha256=Z0vaLS5-e5Mm9urL8lLq7Rh-wvom7AtLdcCPdwSG-Uc,11320
25
- abstractassistant-0.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
26
- abstractassistant-0.3.2.dist-info/entry_points.txt,sha256=MIzeCh0XG6MbhIzBHtkdEjmjxYBsQrGFevq8Y1L8Jkc,118
27
- abstractassistant-0.3.2.dist-info/top_level.txt,sha256=oEcSXZAqbflTfZRfF4dogUq6TC1Nqyplq4JgC0CZnLI,34
28
- abstractassistant-0.3.2.dist-info/RECORD,,
22
+ abstractassistant-0.3.3.dist-info/licenses/LICENSE,sha256=QUjFNAE-0yOkW9-Rle2axkpkt9H7xiZ2VbN-VeONhxc,1106
23
+ abstractassistant-0.3.3.dist-info/METADATA,sha256=gHXdK078vCVh3vMAdjAxEd32JPShNpV3DCkNWLlUjZ0,11564
24
+ abstractassistant-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
25
+ abstractassistant-0.3.3.dist-info/entry_points.txt,sha256=MIzeCh0XG6MbhIzBHtkdEjmjxYBsQrGFevq8Y1L8Jkc,118
26
+ abstractassistant-0.3.3.dist-info/top_level.txt,sha256=qZc_LQH3CBxLq2P4B1aHayzkj8hn0euR31edkXQVzDA,18
27
+ abstractassistant-0.3.3.dist-info/RECORD,,
@@ -1,2 +1 @@
1
1
  abstractassistant
2
- setup_macos_app
setup_macos_app.py DELETED
@@ -1,323 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- macOS App Bundle Generator for AbstractAssistant.
4
-
5
- Creates a native macOS .app bundle with Dock integration and system tray support.
6
- Usage: create-app-bundle (after pip install abstractassistant)
7
- """
8
-
9
- import os
10
- import sys
11
- import shutil
12
- import subprocess
13
- from pathlib import Path
14
-
15
- try:
16
- from PIL import Image
17
- PIL_AVAILABLE = True
18
- except ImportError:
19
- PIL_AVAILABLE = False
20
-
21
-
22
- class MacOSAppBundleGenerator:
23
- """Generates macOS app bundles for AbstractAssistant."""
24
-
25
- def __init__(self, package_dir: Path):
26
- """Initialize the app bundle generator.
27
-
28
- Args:
29
- package_dir: Path to the abstractassistant package directory
30
- """
31
- self.package_dir = package_dir
32
- self.app_name = "AbstractAssistant"
33
- self.app_bundle_path = Path("/Applications") / f"{self.app_name}.app"
34
-
35
- def is_macos(self) -> bool:
36
- """Check if running on macOS."""
37
- return sys.platform == "darwin"
38
-
39
- def has_permissions(self) -> bool:
40
- """Check if we have permissions to write to /Applications."""
41
- try:
42
- test_file = Path("/Applications") / ".test_write_permission"
43
- test_file.touch()
44
- test_file.unlink()
45
- return True
46
- except (PermissionError, OSError):
47
- return False
48
-
49
- def create_app_bundle_structure(self) -> bool:
50
- """Create the basic app bundle directory structure."""
51
- try:
52
- # Create main directories
53
- contents_dir = self.app_bundle_path / "Contents"
54
- macos_dir = contents_dir / "MacOS"
55
- resources_dir = contents_dir / "Resources"
56
-
57
- for directory in [contents_dir, macos_dir, resources_dir]:
58
- directory.mkdir(parents=True, exist_ok=True)
59
-
60
- return True
61
- except Exception as e:
62
- print(f"Error creating app bundle structure: {e}")
63
- return False
64
-
65
- def generate_app_icon(self) -> bool:
66
- """Generate the app icon using the existing icon generator."""
67
- try:
68
- # Import the icon generator
69
- sys.path.insert(0, str(self.package_dir))
70
- from abstractassistant.utils.icon_generator import IconGenerator
71
-
72
- # Generate high-resolution icon
73
- generator = IconGenerator(size=512)
74
- icon = generator.create_app_icon('blue', animated=False)
75
-
76
- # Save as PNG
77
- icon_path = self.app_bundle_path / "Contents" / "Resources" / "icon.png"
78
- icon.save(str(icon_path))
79
-
80
- # Create ICNS file
81
- return self._create_icns_file(icon_path)
82
-
83
- except Exception as e:
84
- print(f"Error generating app icon: {e}")
85
- return False
86
-
87
- def _create_icns_file(self, png_path: Path) -> bool:
88
- """Create ICNS file from PNG using macOS iconutil."""
89
- try:
90
- # Create iconset directory
91
- iconset_dir = png_path.parent / "temp_icons.iconset"
92
- iconset_dir.mkdir(exist_ok=True)
93
-
94
- # Load the PNG and create different sizes
95
- icon = Image.open(png_path)
96
- sizes = [
97
- (16, 'icon_16x16.png'),
98
- (32, 'icon_16x16@2x.png'),
99
- (32, 'icon_32x32.png'),
100
- (64, 'icon_32x32@2x.png'),
101
- (128, 'icon_128x128.png'),
102
- (256, 'icon_128x128@2x.png'),
103
- (256, 'icon_256x256.png'),
104
- (512, 'icon_256x256@2x.png'),
105
- (512, 'icon_512x512.png'),
106
- (1024, 'icon_512x512@2x.png')
107
- ]
108
-
109
- for size, filename in sizes:
110
- resized = icon.resize((size, size), Image.Resampling.LANCZOS)
111
- resized.save(iconset_dir / filename)
112
-
113
- # Convert to ICNS
114
- icns_path = png_path.parent / "icon.icns"
115
- result = subprocess.run([
116
- 'iconutil', '-c', 'icns', str(iconset_dir),
117
- '-o', str(icns_path)
118
- ], capture_output=True, text=True)
119
-
120
- # Clean up
121
- shutil.rmtree(iconset_dir)
122
-
123
- return result.returncode == 0
124
-
125
- except Exception as e:
126
- print(f"Error creating ICNS file: {e}")
127
- return False
128
-
129
- def create_info_plist(self) -> bool:
130
- """Create the Info.plist file."""
131
- try:
132
- plist_content = '''<?xml version="1.0" encoding="UTF-8"?>
133
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
134
- <plist version="1.0">
135
- <dict>
136
- <key>CFBundleExecutable</key>
137
- <string>AbstractAssistant</string>
138
- <key>CFBundleIdentifier</key>
139
- <string>ai.abstractcore.abstractassistant</string>
140
- <key>CFBundleName</key>
141
- <string>AbstractAssistant</string>
142
- <key>CFBundleDisplayName</key>
143
- <string>AbstractAssistant</string>
144
- <key>CFBundleVersion</key>
145
- <string>0.2.8</string>
146
- <key>CFBundleShortVersionString</key>
147
- <string>0.2.8</string>
148
- <key>CFBundlePackageType</key>
149
- <string>APPL</string>
150
- <key>CFBundleSignature</key>
151
- <string>????</string>
152
- <key>CFBundleIconFile</key>
153
- <string>icon.icns</string>
154
- <key>LSMinimumSystemVersion</key>
155
- <string>10.15</string>
156
- <key>NSHighResolutionCapable</key>
157
- <true/>
158
- <key>NSRequiresAquaSystemAppearance</key>
159
- <false/>
160
- <key>LSUIElement</key>
161
- <true/>
162
- <key>NSAppleScriptEnabled</key>
163
- <false/>
164
- <key>CFBundleDocumentTypes</key>
165
- <array/>
166
- <key>NSPrincipalClass</key>
167
- <string>NSApplication</string>
168
- </dict>
169
- </plist>'''
170
-
171
- plist_path = self.app_bundle_path / "Contents" / "Info.plist"
172
- plist_path.write_text(plist_content)
173
- return True
174
-
175
- except Exception as e:
176
- print(f"Error creating Info.plist: {e}")
177
- return False
178
-
179
- def create_launch_script(self) -> bool:
180
- """Create the executable launch script."""
181
- try:
182
- script_content = '''#!/bin/bash
183
-
184
- # AbstractAssistant macOS App Launcher
185
- # This script launches the AbstractAssistant application
186
-
187
- # Set up environment paths for GUI launch (common locations)
188
- export PATH="/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:$PATH"
189
-
190
- # Add user-specific Python paths if they exist
191
- if [ -d "$HOME/.pyenv/shims" ]; then
192
- export PATH="$HOME/.pyenv/shims:$PATH"
193
- fi
194
-
195
- if [ -d "$HOME/.local/bin" ]; then
196
- export PATH="$HOME/.local/bin:$PATH"
197
- fi
198
-
199
- if [ -d "/opt/anaconda3/bin" ]; then
200
- export PATH="/opt/anaconda3/bin:$PATH"
201
- fi
202
-
203
- if [ -d "$HOME/anaconda3/bin" ]; then
204
- export PATH="$HOME/anaconda3/bin:$PATH"
205
- fi
206
-
207
- # Function to find Python with abstractassistant installed
208
- find_python_with_abstractassistant() {
209
- # Try specific paths first (more reliable than PATH-based search)
210
- for python_path in \\
211
- "$HOME/.pyenv/versions/*/bin/python3" \\
212
- "$HOME/.pyenv/shims/python3" \\
213
- "/usr/local/bin/python3" \\
214
- "/opt/homebrew/bin/python3" \\
215
- "/usr/bin/python3" \\
216
- "/opt/anaconda3/bin/python" \\
217
- "$HOME/anaconda3/bin/python" \\
218
- "/usr/local/anaconda3/bin/python"; do
219
-
220
- # Expand glob patterns
221
- for py in $python_path; do
222
- if [ -x "$py" ] && "$py" -c "import abstractassistant" 2>/dev/null; then
223
- echo "$py"
224
- return 0
225
- fi
226
- done
227
- done
228
-
229
- # Fallback to PATH-based search
230
- for python_cmd in python3 python python3.12 python3.11 python3.10 python3.9; do
231
- if command -v "$python_cmd" >/dev/null 2>&1; then
232
- if "$python_cmd" -c "import abstractassistant" 2>/dev/null; then
233
- echo "$python_cmd"
234
- return 0
235
- fi
236
- fi
237
- done
238
-
239
- return 1
240
- }
241
-
242
- # Find Python with AbstractAssistant
243
- PYTHON_EXEC=$(find_python_with_abstractassistant)
244
-
245
- if [ -z "$PYTHON_EXEC" ]; then
246
- osascript -e 'display dialog "AbstractAssistant not found in any Python installation.\\n\\nPlease install it with:\\npip install abstractassistant\\n\\nOr run the create-app-bundle command after installation." with title "AbstractAssistant" buttons {"OK"} default button "OK" with icon caution'
247
- exit 1
248
- fi
249
-
250
- # Change to a neutral directory to avoid importing development versions
251
- cd /tmp
252
-
253
- # Launch the assistant
254
- exec "$PYTHON_EXEC" -m abstractassistant.cli "$@"'''
255
-
256
- script_path = self.app_bundle_path / "Contents" / "MacOS" / "AbstractAssistant"
257
- script_path.write_text(script_content)
258
-
259
- # Make executable
260
- os.chmod(script_path, 0o755)
261
- return True
262
-
263
- except Exception as e:
264
- print(f"Error creating launch script: {e}")
265
- return False
266
-
267
- def generate_app_bundle(self) -> bool:
268
- """Generate the complete macOS app bundle."""
269
- if not self.is_macos():
270
- print("macOS app bundle generation is only available on macOS")
271
- return False
272
-
273
- if not self.has_permissions():
274
- print("Insufficient permissions to create app bundle in /Applications")
275
- print("Please run with sudo or manually copy the app bundle")
276
- return False
277
-
278
- print("Creating macOS app bundle...")
279
-
280
- # Remove existing bundle if it exists
281
- if self.app_bundle_path.exists():
282
- shutil.rmtree(self.app_bundle_path)
283
-
284
- # Create bundle structure
285
- if not self.create_app_bundle_structure():
286
- return False
287
-
288
- # Generate icon
289
- if not self.generate_app_icon():
290
- return False
291
-
292
- # Create Info.plist
293
- if not self.create_info_plist():
294
- return False
295
-
296
- # Create launch script
297
- if not self.create_launch_script():
298
- return False
299
-
300
- print(f"✅ macOS app bundle created successfully!")
301
- print(f" Location: {self.app_bundle_path}")
302
- print(f" You can now launch AbstractAssistant from the Dock!")
303
-
304
- return True
305
-
306
-
307
- def create_macos_app_bundle():
308
- """Main function to create macOS app bundle during installation."""
309
- try:
310
- # Find the package directory
311
- package_dir = Path(__file__).parent
312
-
313
- generator = MacOSAppBundleGenerator(package_dir)
314
- return generator.generate_app_bundle()
315
-
316
- except Exception as e:
317
- print(f"Error creating macOS app bundle: {e}")
318
- return False
319
-
320
-
321
- if __name__ == "__main__":
322
- success = create_macos_app_bundle()
323
- sys.exit(0 if success else 1)