vibe-remote 2.1.6__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 (52) hide show
  1. config/__init__.py +37 -0
  2. config/paths.py +56 -0
  3. config/v2_compat.py +74 -0
  4. config/v2_config.py +206 -0
  5. config/v2_sessions.py +73 -0
  6. config/v2_settings.py +115 -0
  7. core/__init__.py +0 -0
  8. core/controller.py +736 -0
  9. core/handlers/__init__.py +13 -0
  10. core/handlers/command_handlers.py +342 -0
  11. core/handlers/message_handler.py +365 -0
  12. core/handlers/session_handler.py +233 -0
  13. core/handlers/settings_handler.py +362 -0
  14. modules/__init__.py +0 -0
  15. modules/agent_router.py +58 -0
  16. modules/agents/__init__.py +38 -0
  17. modules/agents/base.py +91 -0
  18. modules/agents/claude_agent.py +344 -0
  19. modules/agents/codex_agent.py +368 -0
  20. modules/agents/opencode_agent.py +2155 -0
  21. modules/agents/service.py +41 -0
  22. modules/agents/subagent_router.py +136 -0
  23. modules/claude_client.py +154 -0
  24. modules/im/__init__.py +63 -0
  25. modules/im/base.py +323 -0
  26. modules/im/factory.py +60 -0
  27. modules/im/formatters/__init__.py +4 -0
  28. modules/im/formatters/base_formatter.py +639 -0
  29. modules/im/formatters/slack_formatter.py +127 -0
  30. modules/im/slack.py +2091 -0
  31. modules/session_manager.py +138 -0
  32. modules/settings_manager.py +587 -0
  33. vibe/__init__.py +6 -0
  34. vibe/__main__.py +12 -0
  35. vibe/_version.py +34 -0
  36. vibe/api.py +412 -0
  37. vibe/cli.py +637 -0
  38. vibe/runtime.py +213 -0
  39. vibe/service_main.py +101 -0
  40. vibe/templates/slack_manifest.json +65 -0
  41. vibe/ui/dist/assets/index-8g3mNwMK.js +35 -0
  42. vibe/ui/dist/assets/index-M55aMB5R.css +1 -0
  43. vibe/ui/dist/assets/logo-BzryTZ7u.png +0 -0
  44. vibe/ui/dist/index.html +17 -0
  45. vibe/ui/dist/logo.png +0 -0
  46. vibe/ui/dist/vite.svg +1 -0
  47. vibe/ui_server.py +346 -0
  48. vibe_remote-2.1.6.dist-info/METADATA +295 -0
  49. vibe_remote-2.1.6.dist-info/RECORD +52 -0
  50. vibe_remote-2.1.6.dist-info/WHEEL +4 -0
  51. vibe_remote-2.1.6.dist-info/entry_points.txt +2 -0
  52. vibe_remote-2.1.6.dist-info/licenses/LICENSE +21 -0
vibe/runtime.py ADDED
@@ -0,0 +1,213 @@
1
+ import json
2
+ import os
3
+ import signal
4
+ import subprocess
5
+ import sys
6
+ import time
7
+ from pathlib import Path
8
+
9
+ from config import paths
10
+ from config.v2_config import (
11
+ AgentsConfig,
12
+ ClaudeConfig,
13
+ CodexConfig,
14
+ OpenCodeConfig,
15
+ RuntimeConfig,
16
+ SlackConfig,
17
+ V2Config,
18
+ )
19
+
20
+
21
+ def get_package_root() -> Path:
22
+ """Get the root directory of the vibe package."""
23
+ return Path(__file__).resolve().parent
24
+
25
+
26
+ def get_project_root() -> Path:
27
+ """Get the project root directory (for development mode)."""
28
+ return Path(__file__).resolve().parents[1]
29
+
30
+
31
+ def get_ui_dist_path() -> Path:
32
+ """Get the path to UI dist directory."""
33
+ # First check if we're in development mode (ui/dist exists at project root)
34
+ project_root = get_project_root()
35
+ dev_ui_path = project_root / "ui" / "dist"
36
+ if dev_ui_path.exists():
37
+ return dev_ui_path
38
+
39
+ # Then check if UI is bundled with the package
40
+ package_ui_path = get_package_root() / "ui" / "dist"
41
+ if package_ui_path.exists():
42
+ return package_ui_path
43
+
44
+ # Fallback to development path
45
+ return dev_ui_path
46
+
47
+
48
+ def get_service_main_path() -> Path:
49
+ """Get the path to the main service entry point."""
50
+ # First check if we're in development mode (main.py exists at project root)
51
+ project_root = get_project_root()
52
+ dev_main_path = project_root / "main.py"
53
+ if dev_main_path.exists():
54
+ return dev_main_path
55
+
56
+ # Then check if service_main.py is bundled with the package
57
+ package_main_path = get_package_root() / "service_main.py"
58
+ if package_main_path.exists():
59
+ return package_main_path
60
+
61
+ # Fallback to development path
62
+ return dev_main_path
63
+
64
+
65
+ def get_working_dir() -> Path:
66
+ """Get the working directory for subprocess execution."""
67
+ # In development mode, use project root
68
+ project_root = get_project_root()
69
+ if (project_root / "main.py").exists():
70
+ return project_root
71
+
72
+ # In installed mode, use package root
73
+ return get_package_root()
74
+
75
+
76
+ ROOT_DIR = get_project_root() # For backward compatibility
77
+ MAIN_PATH = get_service_main_path()
78
+
79
+
80
+ def ensure_dirs():
81
+ paths.ensure_data_dirs()
82
+
83
+
84
+ def default_config():
85
+ return V2Config(
86
+ mode="self_host",
87
+ version="v2",
88
+ slack=SlackConfig(bot_token="", app_token=""),
89
+ runtime=RuntimeConfig(default_cwd=str(Path.cwd())),
90
+ agents=AgentsConfig(
91
+ default_backend="opencode",
92
+ opencode=OpenCodeConfig(enabled=True, cli_path="opencode"),
93
+ claude=ClaudeConfig(enabled=True, cli_path="claude"),
94
+ codex=CodexConfig(enabled=False, cli_path="codex"),
95
+ ),
96
+ )
97
+
98
+
99
+ def ensure_config():
100
+ config_path = paths.get_config_path()
101
+ if not config_path.exists():
102
+ default = default_config()
103
+ default.save(config_path)
104
+ return V2Config.load(config_path)
105
+
106
+
107
+ def write_json(path, payload):
108
+ path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
109
+
110
+
111
+ def read_json(path):
112
+ if not path.exists():
113
+ return None
114
+ return json.loads(path.read_text(encoding="utf-8"))
115
+
116
+
117
+ def pid_alive(pid):
118
+ try:
119
+ os.kill(pid, 0)
120
+ return True
121
+ except OSError:
122
+ return False
123
+
124
+
125
+ def _log_path(name: str) -> Path:
126
+ return paths.get_runtime_dir() / name
127
+
128
+
129
+ def spawn_background(args, pid_path, stdout_name: str, stderr_name: str):
130
+ stdout_path = _log_path(stdout_name)
131
+ stderr_path = _log_path(stderr_name)
132
+ stdout_path.parent.mkdir(parents=True, exist_ok=True)
133
+ stdout = stdout_path.open("ab")
134
+ stderr = stderr_path.open("ab")
135
+ process = subprocess.Popen(
136
+ args,
137
+ stdout=stdout,
138
+ stderr=stderr,
139
+ start_new_session=True,
140
+ cwd=str(get_working_dir()),
141
+ close_fds=True,
142
+ )
143
+ stdout.close()
144
+ stderr.close()
145
+ pid_path.write_text(str(process.pid), encoding="utf-8")
146
+ return process.pid
147
+
148
+
149
+ def stop_process(pid_path):
150
+ if not pid_path.exists():
151
+ return False
152
+ pid = int(pid_path.read_text(encoding="utf-8").strip())
153
+ if not pid_alive(pid):
154
+ pid_path.unlink(missing_ok=True)
155
+ return False
156
+ os.kill(pid, signal.SIGTERM)
157
+ pid_path.unlink(missing_ok=True)
158
+ return True
159
+
160
+
161
+ def write_status(state, detail=None, service_pid=None, ui_pid=None):
162
+ payload = {
163
+ "state": state,
164
+ "detail": detail,
165
+ "service_pid": service_pid,
166
+ "ui_pid": ui_pid,
167
+ "updated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
168
+ }
169
+ write_json(paths.get_runtime_status_path(), payload)
170
+
171
+
172
+ def read_status():
173
+ return read_json(paths.get_runtime_status_path()) or {}
174
+
175
+
176
+ def render_status():
177
+ status = read_status()
178
+ pid_path = paths.get_runtime_pid_path()
179
+ pid = pid_path.read_text(encoding="utf-8").strip() if pid_path.exists() else None
180
+ running = bool(pid and pid.isdigit() and pid_alive(int(pid)))
181
+ status["running"] = running
182
+ status["pid"] = int(pid) if pid and pid.isdigit() else None
183
+ return json.dumps(status, indent=2)
184
+
185
+
186
+ def start_service():
187
+ main_path = get_service_main_path()
188
+ return spawn_background(
189
+ [sys.executable, str(main_path)],
190
+ paths.get_runtime_pid_path(),
191
+ "service_stdout.log",
192
+ "service_stderr.log",
193
+ )
194
+
195
+
196
+ def start_ui(host, port):
197
+ command = "from vibe.ui_server import run_ui_server; run_ui_server('{}', {})".format(
198
+ host, port
199
+ )
200
+ return spawn_background(
201
+ [sys.executable, "-c", command],
202
+ paths.get_runtime_ui_pid_path(),
203
+ "ui_stdout.log",
204
+ "ui_stderr.log",
205
+ )
206
+
207
+
208
+ def stop_service():
209
+ return stop_process(paths.get_runtime_pid_path())
210
+
211
+
212
+ def stop_ui():
213
+ return stop_process(paths.get_runtime_ui_pid_path())
vibe/service_main.py ADDED
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env python3
2
+ import os
3
+ import signal
4
+ import sys
5
+ import logging
6
+ import asyncio
7
+ from config.paths import ensure_data_dirs, get_logs_dir
8
+ from config.v2_config import V2Config
9
+ from core.controller import Controller
10
+
11
+
12
+ def setup_logging(level: str = "INFO"):
13
+ """Setup logging configuration with file location and line numbers"""
14
+ # Create a custom formatter with file location
15
+ log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(funcName)s() - %(message)s'
16
+
17
+ # For development, you can use this more detailed format:
18
+ # log_format = '%(asctime)s - %(name)s - %(levelname)s - [%(pathname)s:%(lineno)d] - %(funcName)s() - %(message)s'
19
+
20
+ ensure_data_dirs()
21
+ logs_dir = str(get_logs_dir())
22
+
23
+ logging.basicConfig(
24
+ level=getattr(logging, level.upper()),
25
+ format=log_format,
26
+ handlers=[
27
+ logging.StreamHandler(sys.stdout),
28
+ logging.FileHandler(f"{logs_dir}/vibe_remote.log"),
29
+ ],
30
+ )
31
+
32
+
33
+ def apply_claude_sdk_patches():
34
+ """Apply runtime patches for third-party SDK limits."""
35
+ logger = logging.getLogger(__name__)
36
+ try:
37
+ from claude_code_sdk._internal.transport import subprocess_cli
38
+ except Exception as exc:
39
+ logger.warning(f"Claude SDK patch skipped: {exc}")
40
+ return
41
+
42
+ buffer_size = 16 * 1024 * 1024
43
+ previous = getattr(subprocess_cli, "_MAX_BUFFER_SIZE", None)
44
+ subprocess_cli._MAX_BUFFER_SIZE = buffer_size
45
+ if previous != buffer_size:
46
+ logger.info(
47
+ "Patched claude_code_sdk _MAX_BUFFER_SIZE from %s to %s bytes",
48
+ previous,
49
+ buffer_size,
50
+ )
51
+
52
+
53
+ def main():
54
+ """Main entry point"""
55
+ try:
56
+ # Load configuration
57
+ config = V2Config.load()
58
+
59
+ # Setup logging
60
+ setup_logging(config.runtime.log_level)
61
+ logger = logging.getLogger(__name__)
62
+
63
+ apply_claude_sdk_patches()
64
+
65
+ logger.info("Starting vibe-remote service...")
66
+ logger.info(f"Working directory: {config.runtime.default_cwd}")
67
+
68
+ # Create and run controller
69
+ from config.v2_compat import to_app_config
70
+
71
+ controller = Controller(to_app_config(config))
72
+
73
+ shutdown_initiated = False
74
+
75
+ def _handle_shutdown(signum, frame):
76
+ nonlocal shutdown_initiated
77
+ if shutdown_initiated:
78
+ return
79
+ shutdown_initiated = True
80
+ try:
81
+ logger.info(f"Received signal {signum}, shutting down...")
82
+ except Exception:
83
+ pass
84
+ try:
85
+ controller.cleanup_sync()
86
+ except Exception as cleanup_err:
87
+ logger.error(f"Cleanup failed: {cleanup_err}")
88
+ raise SystemExit(0)
89
+
90
+ signal.signal(signal.SIGTERM, _handle_shutdown)
91
+ signal.signal(signal.SIGINT, _handle_shutdown)
92
+
93
+ controller.run()
94
+
95
+ except Exception as e:
96
+ logging.error(f"Failed to start: {e}")
97
+ sys.exit(1)
98
+
99
+
100
+ if __name__ == "__main__":
101
+ main()
@@ -0,0 +1,65 @@
1
+ {
2
+ "_metadata": {
3
+ "major_version": 1,
4
+ "minor_version": 1
5
+ },
6
+ "display_information": {
7
+ "name": "Vibe Remote",
8
+ "description": "AI coding agent runtime for Slack",
9
+ "background_color": "#262626"
10
+ },
11
+ "features": {
12
+ "bot_user": {
13
+ "display_name": "Vibe Remote",
14
+ "always_online": true
15
+ },
16
+ "app_home": {
17
+ "home_tab_enabled": true,
18
+ "messages_tab_enabled": true,
19
+ "messages_tab_read_only_enabled": false
20
+ }
21
+ },
22
+ "oauth_config": {
23
+ "scopes": {
24
+ "bot": [
25
+ "channels:history",
26
+ "channels:read",
27
+ "chat:write",
28
+ "app_mentions:read",
29
+ "users:read",
30
+ "commands",
31
+ "groups:read",
32
+ "groups:history",
33
+ "im:history",
34
+ "im:read",
35
+ "im:write",
36
+ "mpim:history",
37
+ "mpim:read",
38
+ "mpim:write",
39
+ "files:read",
40
+ "files:write",
41
+ "reactions:read",
42
+ "reactions:write"
43
+ ]
44
+ }
45
+ },
46
+ "settings": {
47
+ "event_subscriptions": {
48
+ "bot_events": [
49
+ "message.channels",
50
+ "message.groups",
51
+ "message.im",
52
+ "message.mpim",
53
+ "app_mention",
54
+ "reaction_added",
55
+ "reaction_removed"
56
+ ]
57
+ },
58
+ "interactivity": {
59
+ "is_enabled": true
60
+ },
61
+ "org_deploy_enabled": true,
62
+ "socket_mode_enabled": true,
63
+ "token_rotation_enabled": false
64
+ }
65
+ }