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.
- abstractassistant/create_app_bundle.py +350 -8
- abstractassistant/ui/chat_bubble.py +15 -2
- abstractassistant/ui/qt_bubble.py +7 -5
- {abstractassistant-0.3.2.dist-info → abstractassistant-0.3.3.dist-info}/METADATA +21 -21
- {abstractassistant-0.3.2.dist-info → abstractassistant-0.3.3.dist-info}/RECORD +9 -10
- {abstractassistant-0.3.2.dist-info → abstractassistant-0.3.3.dist-info}/top_level.txt +0 -1
- setup_macos_app.py +0 -323
- {abstractassistant-0.3.2.dist-info → abstractassistant-0.3.3.dist-info}/WHEEL +0 -0
- {abstractassistant-0.3.2.dist-info → abstractassistant-0.3.3.dist-info}/entry_points.txt +0 -0
- {abstractassistant-0.3.2.dist-info → abstractassistant-0.3.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
|
125
|
-
|
|
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
|
|
1074
|
-
if
|
|
1075
|
-
|
|
1076
|
-
|
|
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.
|
|
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
|
|
215
|
-
├── requirements.txt
|
|
216
|
-
├── config.toml
|
|
217
|
-
├── abstractassistant/
|
|
218
|
-
│ ├── cli.py
|
|
219
|
-
│ ├── app.py
|
|
220
|
-
│ ├── config.py
|
|
221
|
-
│ ├── core/
|
|
222
|
-
│ │ ├── llm_manager.py
|
|
223
|
-
│ │ └── tts_manager.py
|
|
224
|
-
│ ├── ui/
|
|
225
|
-
│ │ ├── qt_bubble.py
|
|
226
|
-
│ │ └── toast_window.py
|
|
227
|
-
│ └── utils/
|
|
228
|
-
│ ├── icon_generator.py
|
|
229
|
-
│ └── markdown_renderer.py
|
|
230
|
-
└── docs/
|
|
231
|
-
├──
|
|
232
|
-
├──
|
|
233
|
-
└──
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
24
|
-
abstractassistant-0.3.
|
|
25
|
-
abstractassistant-0.3.
|
|
26
|
-
abstractassistant-0.3.
|
|
27
|
-
abstractassistant-0.3.
|
|
28
|
-
abstractassistant-0.3.
|
|
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,,
|
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)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|