-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathterminal.py
More file actions
312 lines (271 loc) · 11.3 KB
/
Copy pathterminal.py
File metadata and controls
312 lines (271 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
import os
import sys
import asyncio
import logging
import time
# Ensure package directory is in sys.path
sys.path.append(os.path.abspath(os.path.dirname(__file__)))
# Configure minimal/clean logging for terminal interaction
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.FileHandler("terminal.log", encoding="utf-8"),
logging.StreamHandler(sys.stdout)
]
)
# Disable console logging for logger objects to keep terminal output clean, except custom prints
logging.getLogger().handlers[1].setLevel(logging.WARNING)
BANNER = r"""
============================================================
__ ___
/ /_ ____ __ ______ ____/ (_)__ _____
/ __ \/ __ \/ / / / __ \/ __ / / _ \/ ___/
/ /_/ / /_/ / /_/ / / / / /_/ / / __/ /
/_.___/\____/\__,_/_/ /_/\__,_/_/\___/_/
SETUP TERMINAL
============================================================
"""
def print_banner():
print(BANNER)
def load_env_defaults():
defaults = {"DISCORD_TOKEN": "", "GITHUB_PAT": "", "ENCRYPTION_KEY": ""}
if os.path.exists(".env"):
with open(".env", "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, v = line.split("=", 1)
defaults[k.strip()] = v.strip().strip('"').strip("'")
return defaults
def load_config_defaults():
defaults = {"admin_channel_id": "", "max_users": "5"}
if os.path.exists("config.yaml"):
try:
import yaml
with open("config.yaml", "r", encoding="utf-8") as f:
cfg = yaml.safe_load(f) or {}
defaults["admin_channel_id"] = str(cfg.get("discord", {}).get("admin_channel_id", ""))
defaults["max_users"] = str(cfg.get("max_users", 5))
except Exception:
pass
return defaults
def configure_interactive():
print("\n--- [1] INTERACTIVE CONFIGURATION ---")
env_defs = load_env_defaults()
cfg_defs = load_config_defaults()
print("Press Enter to keep existing values in brackets.")
# 1. Discord Bot Token
token = input(f"Discord Bot Token [{env_defs['DISCORD_TOKEN'][:15]}...]: ").strip()
if not token:
token = env_defs["DISCORD_TOKEN"]
# 2. Admin Channel ID
channel_str = input(f"Discord Admin Channel ID [{cfg_defs['admin_channel_id']}]: ").strip()
if not channel_str:
channel_str = cfg_defs["admin_channel_id"]
try:
admin_channel_id = int(channel_str) if channel_str else 0
except ValueError:
print("Error: Admin Channel ID must be an integer.")
return
# 3. Max Users (1-5)
print()
print("How many people can use this bot?")
print(" 1 = Only you")
print(" 2 = You + 1 other")
print(" 3 = You + 2 others")
print(" 4 = You + 3 others")
print(" 5 = You + 4 others (default)")
max_users_str = input(f"Enter a number (1-5) [{cfg_defs['max_users']}]: ").strip()
if not max_users_str:
max_users_str = cfg_defs["max_users"]
try:
max_users = int(max_users_str)
if max_users < 1 or max_users > 5:
print("Error: Please enter a number between 1 and 5.")
return
except ValueError:
print("Error: Please enter a number between 1 and 5.")
return
# Write to .env
env_content = {}
if os.path.exists(".env"):
with open(".env", "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, v = line.split("=", 1)
env_content[k.strip()] = v.strip().strip('"').strip("'")
env_content["DISCORD_TOKEN"] = token
with open(".env", "w", encoding="utf-8") as f:
for k, v in env_content.items():
f.write(f'{k}="{v}"\n')
# Write/update config.yaml
config_content = {}
if os.path.exists("config.yaml"):
try:
import yaml
with open("config.yaml", "r", encoding="utf-8") as f:
config_content = yaml.safe_load(f) or {}
except Exception:
pass
if "discord" not in config_content:
config_content["discord"] = {}
config_content["discord"]["admin_channel_id"] = admin_channel_id
# Save token for compatibility if present
config_content["discord"]["token"] = token
# Write max_users at top level
config_content["max_users"] = max_users
# Make sure other keys exist
if "playwright" not in config_content:
config_content["playwright"] = {
"headless": True,
"user_data_dir": "browser_profile/",
"timeout_ms": 90000,
"max_pages": 3,
"viewport": {"width": 1280, "height": 720}
}
if "memory" not in config_content:
config_content["memory"] = {
"max_thread_messages": 15,
"channel_summary_limit": 500,
"system_instructions": "You are Boundier, a helpful coding assistant."
}
try:
import yaml
with open("config.yaml", "w", encoding="utf-8") as f:
yaml.safe_dump(config_content, f, default_flow_style=False)
print("\n[SUCCESS] Configuration saved to .env and config.yaml!")
except Exception as err:
print(f"Error saving config.yaml: {err}")
async def run_headed_login():
print("\n--- [2] AUTHORIZE CHATGPT (HEADED LOGIN) ---")
if not os.path.exists("config.yaml"):
print("Error: Configuration files not found. Please run Option [1] first.")
return
from boundier.config import load_config
from boundier.chatgpt.driver import PlaywrightDriver
config = load_config("config.yaml")
# Force headed mode and browser context parameters
config.playwright.headless = False
config.playwright.user_data_dir = "./browser_profile"
driver = PlaywrightDriver(config)
await driver.start()
try:
print("\n" + "="*80)
print("Launching headed Chromium browser tab...")
print("Please log in manually on the browser page.")
print("We will automatically detect when the session is authenticated.")
print("="*80 + "\n")
# Navigate to ChatGPT home page
await driver.page.goto("https://chatgpt.com", wait_until="domcontentloaded")
# Wait for authentication up to 5 minutes
authenticated = await driver.wait_for_manual_login(timeout_seconds=300)
if authenticated:
print("\n[SUCCESS] Login verified successfully!")
await driver.save_session_state()
print("[SUCCESS] Session cookies saved locally to 'storage_state.json' and synced to backup.")
else:
print("\n[ERROR] Login wait period timed out or failed.")
except Exception as e:
print(f"\n[ERROR] An error occurred during browser login: {e}")
finally:
await driver.stop()
def bootstrap_database():
print("\n--- [3] BOOTSTRAP SQLITE DATABASE & MEMORIES ---")
try:
from boundier.storage.sqlite_store import SQLiteStore
print("Initializing SQLite database ('boundier.db')...")
store = SQLiteStore(db_path="boundier.db", schema_path="schema.sql", memory_dir="memory")
print("Syncing markdown memory files to database...")
store.sync_markdown_files()
print("\n[SUCCESS] SQLite database bootstrapped and synced successfully!")
except Exception as e:
print(f"\n[ERROR] Failed to bootstrap database: {e}")
async def run_diagnostics():
print("\n--- [4] RUN SELF-DIAGNOSTICS ---")
if not os.path.exists("config.yaml"):
print("[FAIL] config.yaml not found. Please run Option [1] first.")
return
from boundier.config import load_config
from boundier.chatgpt.driver import PlaywrightDriver
config = load_config("config.yaml")
# 1. Test Discord Token
print("\n1. Testing Discord Bot Token connection...")
discord_token = os.environ.get("DISCORD_TOKEN") or load_env_defaults().get("DISCORD_TOKEN")
if discord_token:
import urllib.request
import json
url = "https://discord.com/api/v10/users/@me"
headers = {
"Authorization": f"Bot {discord_token}",
"User-Agent": "DiscordBot (https://github.com/keepsloading/Boundier, 1.0.0)"
}
req = urllib.request.Request(url, headers=headers)
try:
with urllib.request.urlopen(req, timeout=8) as res:
user_info = json.loads(res.read().decode("utf-8"))
print(f" [PASS] Discord Token validated. Logged in as: {user_info['username']}")
except Exception as e:
print(f" [FAIL] Discord API validation failed: {e}")
else:
print(" [FAIL] DISCORD_TOKEN is not set. Discord Bot will not launch.")
# 2. Test ChatGPT Session Cookie status
print("\n2. Testing ChatGPT session status (Headless browser check)...")
driver = PlaywrightDriver(config)
await driver.start()
try:
is_active = await driver.check_session_active(navigate=True)
if is_active:
print(" [PASS] ChatGPT browser session is authenticated and active!")
else:
print(" [FAIL] ChatGPT browser session is unauthenticated. Please run Option [2].")
except Exception as e:
print(f" [FAIL] Error validating ChatGPT session: {e}")
finally:
await driver.stop()
def run_discord_bot():
print("\n--- [5] LAUNCH BOUNDIER DISCORD BOT ---")
try:
print("Initializing bootstrap pipeline...")
from boundier.main import async_main
# We run async_main using asyncio.run
asyncio.run(async_main())
except (KeyboardInterrupt, SystemExit):
print("\n[INFO] Bot terminated.")
except Exception as e:
print(f"\n[ERROR] Failed to run bot: {e}")
def main():
while True:
print_banner()
print("[1] Interactive Configuration (Set Token, Admin Channel)")
print("[2] Authorize ChatGPT (Headed Browser Login)")
print("[3] Bootstrap SQLite Database & Memories")
print("[4] Run Self-Diagnostics (Session / Discord Checks)")
print("[5] Launch Boundier Discord Bot")
print("[6] Exit")
print("="*60)
choice = input("Choose an option [1-6]: ").strip()
if choice == "1":
configure_interactive()
elif choice == "2":
asyncio.run(run_headed_login())
elif choice == "3":
bootstrap_database()
elif choice == "4":
asyncio.run(run_diagnostics())
elif choice == "5":
run_discord_bot()
elif choice == "6":
print("\nGoodbye!")
break
else:
print("\nInvalid choice or not implemented yet.")
input("\nPress Enter to return to menu...")
# Clear screen for next iteration
if sys.platform == "win32":
os.system("cls")
else:
os.system("clear")
if __name__ == "__main__":
main()