mrstack 1.1.0__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.
Files changed (42) hide show
  1. mrstack/__init__.py +4 -0
  2. mrstack/_data/config/com.mrstack.claude-telegram.plist +25 -0
  3. mrstack/_data/config/mcp-config.example.json +23 -0
  4. mrstack/_data/config/start-daemon.sh +53 -0
  5. mrstack/_data/config/start.sh +29 -0
  6. mrstack/_data/schedulers/manage-jobs.sh +87 -0
  7. mrstack/_data/schedulers/morning-briefing.sh +29 -0
  8. mrstack/_data/schedulers/register-jobs.py +182 -0
  9. mrstack/_data/schedulers/run-threads-briefing.sh +36 -0
  10. mrstack/_data/schedulers/weekly-review.sh +26 -0
  11. mrstack/_data/templates/DESIGN-GUIDE.md +160 -0
  12. mrstack/_data/templates/alert.md +56 -0
  13. mrstack/_data/templates/evening-summary.md +73 -0
  14. mrstack/_data/templates/jarvis-alert.md +64 -0
  15. mrstack/_data/templates/morning-briefing.md +53 -0
  16. mrstack/_data/templates/weekly-review.md +79 -0
  17. mrstack/_overlay/api/dashboard.py +223 -0
  18. mrstack/_overlay/api/templates/dashboard.html +328 -0
  19. mrstack/_overlay/bot/handlers/callback.py +1432 -0
  20. mrstack/_overlay/bot/handlers/command.py +1541 -0
  21. mrstack/_overlay/bot/utils/keyboards.py +125 -0
  22. mrstack/_overlay/bot/utils/ui_components.py +166 -0
  23. mrstack/_overlay/claude/session.py +341 -0
  24. mrstack/_overlay/jarvis/__init__.py +77 -0
  25. mrstack/_overlay/jarvis/coach.py +122 -0
  26. mrstack/_overlay/jarvis/context_engine.py +463 -0
  27. mrstack/_overlay/jarvis/pattern_learner.py +255 -0
  28. mrstack/_overlay/jarvis/persona.py +84 -0
  29. mrstack/_overlay/jarvis/platform.py +182 -0
  30. mrstack/_overlay/knowledge/__init__.py +6 -0
  31. mrstack/_overlay/knowledge/manager.py +464 -0
  32. mrstack/_overlay/knowledge/memory_index.py +180 -0
  33. mrstack/cli.py +330 -0
  34. mrstack/constants.py +77 -0
  35. mrstack/daemon.py +325 -0
  36. mrstack/patcher.py +169 -0
  37. mrstack/wizard.py +271 -0
  38. mrstack-1.1.0.dist-info/METADATA +640 -0
  39. mrstack-1.1.0.dist-info/RECORD +42 -0
  40. mrstack-1.1.0.dist-info/WHEEL +4 -0
  41. mrstack-1.1.0.dist-info/entry_points.txt +2 -0
  42. mrstack-1.1.0.dist-info/licenses/LICENSE +21 -0
mrstack/wizard.py ADDED
@@ -0,0 +1,271 @@
1
+ """Interactive setup wizard for first-time configuration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import shutil
8
+ import subprocess
9
+ import textwrap
10
+ from pathlib import Path
11
+
12
+ from rich.console import Console
13
+ from rich.panel import Panel
14
+ from rich.prompt import Confirm, Prompt
15
+
16
+ from .constants import (
17
+ BOT_COMMAND,
18
+ CLAUDE_TELEGRAM_PKG,
19
+ DATA_DIR,
20
+ ENV_FILE,
21
+ IS_MACOS,
22
+ LOG_DIR,
23
+ MEMORY_DIR,
24
+ TEMPLATES_DIR,
25
+ find_site_packages,
26
+ )
27
+ from .patcher import patch_install
28
+
29
+ console = Console()
30
+
31
+
32
+ def run_wizard() -> None:
33
+ """Run the interactive setup wizard."""
34
+ console.print(
35
+ Panel(
36
+ "[bold]Mr.Stack Setup Wizard[/]\n"
37
+ "This will configure your AI butler.",
38
+ border_style="cyan",
39
+ )
40
+ )
41
+
42
+ # Step 1: Check prerequisites
43
+ _step("Checking prerequisites")
44
+ _check_claude_code()
45
+ _check_claude_telegram()
46
+
47
+ # Step 2: Telegram bot token
48
+ _step("Telegram Bot Configuration")
49
+ token = _ask_bot_token()
50
+ user_id = _ask_user_id()
51
+
52
+ # Step 3: Working directory setup
53
+ _step("Setting up working directory")
54
+ _setup_directories()
55
+
56
+ # Step 4: Create .env
57
+ _step("Creating configuration")
58
+ jarvis_enabled = False
59
+ if IS_MACOS:
60
+ jarvis_enabled = Confirm.ask(
61
+ "Enable Jarvis mode? (proactive context engine, macOS only)",
62
+ default=True,
63
+ )
64
+
65
+ _write_env(token, user_id, jarvis_enabled)
66
+
67
+ # Step 5: Install overlay modules
68
+ _step("Installing Mr.Stack modules")
69
+ site_pkg = find_site_packages()
70
+ if site_pkg:
71
+ patch_install(site_pkg, force=True)
72
+ else:
73
+ console.print(
74
+ "[yellow]claude-code-telegram not found. "
75
+ "Overlay installation skipped.[/]"
76
+ )
77
+ console.print(
78
+ " After installing claude-code-telegram, run: "
79
+ "[bold]mrstack patch[/]"
80
+ )
81
+
82
+ # Step 6: Copy data files
83
+ _step("Setting up templates and schedulers")
84
+ _copy_data_files()
85
+
86
+ # Done
87
+ console.print()
88
+ console.print(
89
+ Panel(
90
+ "[bold green]Setup complete![/]\n\n"
91
+ " [bold]mrstack start[/] — Start the bot (foreground)\n"
92
+ " [bold]mrstack daemon[/] — Start as background service\n"
93
+ " [bold]mrstack status[/] — Check status\n"
94
+ " [bold]mrstack logs[/] — View logs\n",
95
+ border_style="green",
96
+ title="Next Steps",
97
+ )
98
+ )
99
+
100
+
101
+ def _step(msg: str) -> None:
102
+ console.print(f"\n[bold cyan]>[/] {msg}")
103
+
104
+
105
+ def _check_claude_code() -> None:
106
+ if shutil.which("claude"):
107
+ console.print(" [green]Claude Code[/] found")
108
+ else:
109
+ console.print(
110
+ " [yellow]Claude Code not found in PATH.[/]\n"
111
+ " Install: [bold]npm install -g @anthropic-ai/claude-code[/]"
112
+ )
113
+ if not Confirm.ask("Continue anyway?", default=True):
114
+ raise SystemExit(0)
115
+
116
+
117
+ def _check_claude_telegram() -> None:
118
+ if shutil.which(BOT_COMMAND):
119
+ console.print(f" [green]{CLAUDE_TELEGRAM_PKG}[/] found")
120
+ return
121
+
122
+ console.print(f" [yellow]{CLAUDE_TELEGRAM_PKG} not found.[/]")
123
+ if Confirm.ask("Install it now?", default=True):
124
+ # Prefer uv, fallback to pip
125
+ if shutil.which("uv"):
126
+ subprocess.run(
127
+ ["uv", "tool", "install", CLAUDE_TELEGRAM_PKG],
128
+ check=True,
129
+ )
130
+ elif shutil.which("pipx"):
131
+ subprocess.run(
132
+ ["pipx", "install", CLAUDE_TELEGRAM_PKG],
133
+ check=True,
134
+ )
135
+ else:
136
+ subprocess.run(
137
+ ["pip", "install", CLAUDE_TELEGRAM_PKG],
138
+ check=True,
139
+ )
140
+ console.print(f" [green]{CLAUDE_TELEGRAM_PKG} installed[/]")
141
+ else:
142
+ console.print(" Skipping — you can install it later.")
143
+
144
+
145
+ def _ask_bot_token() -> str:
146
+ console.print(
147
+ " Create a bot via [bold]@BotFather[/] on Telegram and paste the token."
148
+ )
149
+ while True:
150
+ token = Prompt.ask(" Bot token").strip()
151
+ if not token:
152
+ continue
153
+ if ":" not in token:
154
+ console.print(" [red]Invalid token format. Expected: 123456:ABC-DEF...[/]")
155
+ continue
156
+ # Validate via Telegram API
157
+ if _validate_token(token):
158
+ return token
159
+ console.print(" [red]Token validation failed. Check the token.[/]")
160
+
161
+
162
+ def _validate_token(token: str) -> bool:
163
+ try:
164
+ import urllib.request
165
+
166
+ url = f"https://api.telegram.org/bot{token}/getMe"
167
+ req = urllib.request.Request(url, method="GET")
168
+ with urllib.request.urlopen(req, timeout=10) as resp:
169
+ data = json.loads(resp.read())
170
+ if data.get("ok"):
171
+ bot_name = data["result"].get("username", "unknown")
172
+ console.print(f" [green]Verified: @{bot_name}[/]")
173
+ return True
174
+ except Exception:
175
+ pass
176
+ return False
177
+
178
+
179
+ def _ask_user_id() -> str:
180
+ console.print(
181
+ " Your Telegram user ID (send /start to @userinfobot to find it)."
182
+ )
183
+ while True:
184
+ uid = Prompt.ask(" User ID").strip()
185
+ if uid.isdigit():
186
+ return uid
187
+ console.print(" [red]User ID must be a number.[/]")
188
+
189
+
190
+ def _setup_directories() -> None:
191
+ for d in [DATA_DIR, DATA_DIR / "data", LOG_DIR, MEMORY_DIR]:
192
+ d.mkdir(parents=True, exist_ok=True)
193
+ console.print(f" [green]Data directory:[/] {DATA_DIR}")
194
+
195
+
196
+ def _write_env(token: str, user_id: str, jarvis: bool) -> None:
197
+ import secrets
198
+
199
+ webhook_secret = secrets.token_hex(32)
200
+
201
+ env_content = textwrap.dedent(f"""\
202
+ # Mr.Stack Configuration
203
+ # Generated by mrstack init
204
+
205
+ # Telegram
206
+ TELEGRAM_BOT_TOKEN={token}
207
+ TELEGRAM_BOT_USERNAME=
208
+ APPROVED_DIRECTORY={Path.home()}
209
+ ALLOWED_USERS={user_id}
210
+ NOTIFICATION_CHAT_IDS={user_id}
211
+
212
+ # Claude
213
+ CLAUDE_MAX_TURNS=30
214
+ CLAUDE_TIMEOUT_SECONDS=600
215
+ AGENTIC_MODE=true
216
+
217
+ # Features
218
+ ENABLE_JARVIS={'true' if jarvis else 'false'}
219
+ ENABLE_GIT_INTEGRATION=true
220
+ ENABLE_FILE_UPLOADS=true
221
+ ENABLE_IMAGE_UPLOADS=true
222
+ ENABLE_QUICK_ACTIONS=true
223
+ ENABLE_CLIPBOARD_MONITOR={'true' if IS_MACOS else 'false'}
224
+ ENABLE_SCHEDULER=true
225
+ ENABLE_API_SERVER=true
226
+ ENABLE_MCP=false
227
+
228
+ # API Server
229
+ API_SERVER_PORT=8080
230
+ WEBHOOK_API_SECRET={webhook_secret}
231
+
232
+ # Database
233
+ DATABASE_URL=sqlite:///{DATA_DIR}/data/bot.db
234
+ """)
235
+
236
+ if ENV_FILE.is_file():
237
+ if not Confirm.ask(
238
+ f" .env already exists at {ENV_FILE}. Overwrite?", default=False
239
+ ):
240
+ console.print(" [yellow]Keeping existing .env[/]")
241
+ return
242
+
243
+ fd = os.open(str(ENV_FILE), os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
244
+ with os.fdopen(fd, "w") as f:
245
+ f.write(env_content)
246
+ console.print(f" [green].env created[/] ({ENV_FILE})")
247
+
248
+
249
+ def _copy_data_files() -> None:
250
+ """Copy templates and scheduler scripts to the data directory."""
251
+ from .constants import DATA_TEMPLATES_DIR, DATA_SCHEDULERS_DIR
252
+
253
+ # Templates
254
+ src_templates = DATA_TEMPLATES_DIR
255
+ if not src_templates.is_dir():
256
+ # Fallback: repo-local
257
+ src_templates = Path(__file__).parent.parent / "templates"
258
+ if src_templates.is_dir():
259
+ dst = TEMPLATES_DIR
260
+ dst.mkdir(parents=True, exist_ok=True)
261
+ for f in src_templates.iterdir():
262
+ if f.is_file():
263
+ target = dst / f.name
264
+ if not target.exists():
265
+ shutil.copy2(f, target)
266
+ console.print(f" [green]Templates[/] -> {dst}")
267
+
268
+ # Memory subdirs
269
+ for sub in ["daily", "decisions", "knowledge", "patterns", "people", "projects", "preferences"]:
270
+ (MEMORY_DIR / sub).mkdir(parents=True, exist_ok=True)
271
+ console.print(f" [green]Memory dirs[/] -> {MEMORY_DIR}")