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.
- mrstack/__init__.py +4 -0
- mrstack/_data/config/com.mrstack.claude-telegram.plist +25 -0
- mrstack/_data/config/mcp-config.example.json +23 -0
- mrstack/_data/config/start-daemon.sh +53 -0
- mrstack/_data/config/start.sh +29 -0
- mrstack/_data/schedulers/manage-jobs.sh +87 -0
- mrstack/_data/schedulers/morning-briefing.sh +29 -0
- mrstack/_data/schedulers/register-jobs.py +182 -0
- mrstack/_data/schedulers/run-threads-briefing.sh +36 -0
- mrstack/_data/schedulers/weekly-review.sh +26 -0
- mrstack/_data/templates/DESIGN-GUIDE.md +160 -0
- mrstack/_data/templates/alert.md +56 -0
- mrstack/_data/templates/evening-summary.md +73 -0
- mrstack/_data/templates/jarvis-alert.md +64 -0
- mrstack/_data/templates/morning-briefing.md +53 -0
- mrstack/_data/templates/weekly-review.md +79 -0
- mrstack/_overlay/api/dashboard.py +223 -0
- mrstack/_overlay/api/templates/dashboard.html +328 -0
- mrstack/_overlay/bot/handlers/callback.py +1432 -0
- mrstack/_overlay/bot/handlers/command.py +1541 -0
- mrstack/_overlay/bot/utils/keyboards.py +125 -0
- mrstack/_overlay/bot/utils/ui_components.py +166 -0
- mrstack/_overlay/claude/session.py +341 -0
- mrstack/_overlay/jarvis/__init__.py +77 -0
- mrstack/_overlay/jarvis/coach.py +122 -0
- mrstack/_overlay/jarvis/context_engine.py +463 -0
- mrstack/_overlay/jarvis/pattern_learner.py +255 -0
- mrstack/_overlay/jarvis/persona.py +84 -0
- mrstack/_overlay/jarvis/platform.py +182 -0
- mrstack/_overlay/knowledge/__init__.py +6 -0
- mrstack/_overlay/knowledge/manager.py +464 -0
- mrstack/_overlay/knowledge/memory_index.py +180 -0
- mrstack/cli.py +330 -0
- mrstack/constants.py +77 -0
- mrstack/daemon.py +325 -0
- mrstack/patcher.py +169 -0
- mrstack/wizard.py +271 -0
- mrstack-1.1.0.dist-info/METADATA +640 -0
- mrstack-1.1.0.dist-info/RECORD +42 -0
- mrstack-1.1.0.dist-info/WHEEL +4 -0
- mrstack-1.1.0.dist-info/entry_points.txt +2 -0
- 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}")
|