godfather-cli 1.0.1__tar.gz
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.
- godfather_cli-1.0.1/MANIFEST.in +3 -0
- godfather_cli-1.0.1/PKG-INFO +212 -0
- godfather_cli-1.0.1/README.md +189 -0
- godfather_cli-1.0.1/godfather_cli/__init__.py +7 -0
- godfather_cli-1.0.1/godfather_cli/auth.py +135 -0
- godfather_cli-1.0.1/godfather_cli/cli.py +237 -0
- godfather_cli-1.0.1/godfather_cli/pod_manager.py +132 -0
- godfather_cli-1.0.1/godfather_cli/ssh_connector.py +145 -0
- godfather_cli-1.0.1/godfather_cli.egg-info/PKG-INFO +212 -0
- godfather_cli-1.0.1/godfather_cli.egg-info/SOURCES.txt +14 -0
- godfather_cli-1.0.1/godfather_cli.egg-info/dependency_links.txt +1 -0
- godfather_cli-1.0.1/godfather_cli.egg-info/entry_points.txt +2 -0
- godfather_cli-1.0.1/godfather_cli.egg-info/requires.txt +2 -0
- godfather_cli-1.0.1/godfather_cli.egg-info/top_level.txt +1 -0
- godfather_cli-1.0.1/pyproject.toml +37 -0
- godfather_cli-1.0.1/setup.cfg +4 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: godfather-cli
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: AI Society Godfather CLI - RunPod Environment Manager
|
|
5
|
+
Author-email: AI Society ASU <theaisocietyasu@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/theaisocietyasu/godfather
|
|
8
|
+
Project-URL: Repository, https://github.com/theaisocietyasu/godfather
|
|
9
|
+
Project-URL: Issues, https://github.com/theaisocietyasu/godfather/issues
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Requires-Python: >=3.7
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: requests>=2.25.0
|
|
22
|
+
Requires-Dist: rich>=13.0.0
|
|
23
|
+
|
|
24
|
+
# Godfather CLI
|
|
25
|
+
|
|
26
|
+
Beautiful command-line interface for managing AI Society ASU RunPod environments.
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
## ✨ Features
|
|
32
|
+
|
|
33
|
+
- 🎨 **Beautiful Interface** - Rich terminal UI with colors, tables, and panels
|
|
34
|
+
- 🔐 **Secure Authentication** - Discord-based authentication
|
|
35
|
+
- 🚀 **Pod Management** - List and connect to available RunPod environments
|
|
36
|
+
- 🔌 **SSH Integration** - Automatic SSH key setup and connection
|
|
37
|
+
- 📊 **Status Monitoring** - Check authentication and configuration status
|
|
38
|
+
|
|
39
|
+
## 📦 Installation
|
|
40
|
+
|
|
41
|
+
### Option 1: Install from PyPI (Recommended)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install godfather-cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Option 2: Install from GitHub
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install git+https://github.com/theaisocietyasu/godfather.git#subdirectory=cli
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Option 3: Install for Development
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/theaisocietyasu/godfather.git
|
|
57
|
+
cd godfather/cli
|
|
58
|
+
pip install -e .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 🚀 Quick Start
|
|
62
|
+
|
|
63
|
+
1. **Run the CLI**:
|
|
64
|
+
```bash
|
|
65
|
+
godfather
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
2. **Authenticate**:
|
|
69
|
+
- Visit the admin portal to get your authentication token
|
|
70
|
+
- Paste the token when prompted
|
|
71
|
+
|
|
72
|
+
3. **Connect to a Pod**:
|
|
73
|
+
- Select option `2` to connect to a pod
|
|
74
|
+
- Choose from available pods
|
|
75
|
+
- Automatically SSH into your isolated workspace
|
|
76
|
+
|
|
77
|
+
## 📖 Usage
|
|
78
|
+
|
|
79
|
+
### Interactive Menu
|
|
80
|
+
|
|
81
|
+
Simply run:
|
|
82
|
+
```bash
|
|
83
|
+
godfather
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
You'll see a beautiful menu:
|
|
87
|
+
```
|
|
88
|
+
╔═══════════════════════════════════════╗
|
|
89
|
+
║ Godfather CLI ║
|
|
90
|
+
║ AI Society RunPod Environment Manager ║
|
|
91
|
+
╚═══════════════════════════════════════╝
|
|
92
|
+
|
|
93
|
+
╭──────── What would you like to do? ──────────╮
|
|
94
|
+
│ 1. 📋 List available pods │
|
|
95
|
+
│ 2. 🔌 Connect to a pod │
|
|
96
|
+
│ 3. 📊 Show status │
|
|
97
|
+
│ 4. 🚪 Logout │
|
|
98
|
+
│ 5. 👋 Exit │
|
|
99
|
+
╰──────────────────────────────────────────────╯
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Command-Line Interface
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# List available pods
|
|
106
|
+
godfather list
|
|
107
|
+
|
|
108
|
+
# Connect to a specific pod
|
|
109
|
+
godfather connect <pod-id>
|
|
110
|
+
|
|
111
|
+
# Connect interactively
|
|
112
|
+
godfather connect
|
|
113
|
+
|
|
114
|
+
# Show CLI status
|
|
115
|
+
godfather status
|
|
116
|
+
|
|
117
|
+
# Logout
|
|
118
|
+
godfather logout
|
|
119
|
+
|
|
120
|
+
# Re-authenticate
|
|
121
|
+
godfather auth
|
|
122
|
+
|
|
123
|
+
# Use custom API URL
|
|
124
|
+
godfather --api-url https://your-api.com list
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 🔑 Authentication
|
|
128
|
+
|
|
129
|
+
1. Get your authentication token from: `https://your-godfather-instance.com/cli-auth`
|
|
130
|
+
2. Run `godfather` or `godfather auth`
|
|
131
|
+
3. Paste your token when prompted
|
|
132
|
+
4. Token is securely stored in `~/.godfather/config.json`
|
|
133
|
+
|
|
134
|
+
## 🛠️ Configuration
|
|
135
|
+
|
|
136
|
+
Configuration is stored in `~/.godfather/config.json`:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"token": "discord_<user_id>_<timestamp>",
|
|
141
|
+
"discord_user_id": "<your_discord_id>"
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Environment Variables
|
|
146
|
+
|
|
147
|
+
You can override the API URL with:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
export GODFATHER_API_URL="https://your-api.com"
|
|
151
|
+
# or
|
|
152
|
+
export BACKEND_URL="https://your-api.com"
|
|
153
|
+
# or
|
|
154
|
+
export NEXT_PUBLIC_BACKEND_URL="https://your-api.com"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 🎨 Features in Detail
|
|
158
|
+
|
|
159
|
+
### Beautiful Tables
|
|
160
|
+
|
|
161
|
+
Pod listings display in rich, colorful tables:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
🚀 Available Pods (3)
|
|
165
|
+
╭───┬────────┬─────────────┬──────────────┬───────────╮
|
|
166
|
+
│ # │ Status │ Name │ ID │ Created │
|
|
167
|
+
├───┼────────┼─────────────┼──────────────┼───────────┤
|
|
168
|
+
│ 1 │ 🟢 RUN │ ml-training │ abc123def... │ 2 days ago│
|
|
169
|
+
│ 2 │ 🟢 RUN │ gpu-dev │ xyz789ghi... │ 1 week ago│
|
|
170
|
+
│ 3 │ 🔴 OFF │ testing │ jkl456mno... │ 3 days ago│
|
|
171
|
+
╰───┴────────┴─────────────┴──────────────┴───────────╯
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Status Dashboard
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
Godfather CLI Status
|
|
178
|
+
╭─────────────────────┬─────────────────────────────╮
|
|
179
|
+
│ 🔐 Authentication │ ✓ Authenticated │
|
|
180
|
+
│ 🌐 API Connection │ ✓ Connected │
|
|
181
|
+
│ 🏠 Config Directory │ /home/user/.godfather │
|
|
182
|
+
│ 🔗 API Endpoint │ https://api.example.com │
|
|
183
|
+
╰─────────────────────┴─────────────────────────────╯
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## 🤝 Contributing
|
|
187
|
+
|
|
188
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
189
|
+
|
|
190
|
+
1. Fork the repository
|
|
191
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
192
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
193
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
194
|
+
5. Open a Pull Request
|
|
195
|
+
|
|
196
|
+
## 📝 License
|
|
197
|
+
|
|
198
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
199
|
+
|
|
200
|
+
## 🆘 Support
|
|
201
|
+
|
|
202
|
+
- **Issues**: [GitHub Issues](https://github.com/theaisocietyasu/godfather/issues)
|
|
203
|
+
- **Discord**: Contact AI Society ASU administrators
|
|
204
|
+
- **Email**: admin@ais-asu.com
|
|
205
|
+
|
|
206
|
+
## 🎓 About
|
|
207
|
+
|
|
208
|
+
Built with ❤️ by [AI Society at Arizona State University](https://github.com/theaisocietyasu)
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
**Note**: You need to be a member of the AI Society Discord server with appropriate permissions to use this CLI.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# Godfather CLI
|
|
2
|
+
|
|
3
|
+
Beautiful command-line interface for managing AI Society ASU RunPod environments.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
## ✨ Features
|
|
9
|
+
|
|
10
|
+
- 🎨 **Beautiful Interface** - Rich terminal UI with colors, tables, and panels
|
|
11
|
+
- 🔐 **Secure Authentication** - Discord-based authentication
|
|
12
|
+
- 🚀 **Pod Management** - List and connect to available RunPod environments
|
|
13
|
+
- 🔌 **SSH Integration** - Automatic SSH key setup and connection
|
|
14
|
+
- 📊 **Status Monitoring** - Check authentication and configuration status
|
|
15
|
+
|
|
16
|
+
## 📦 Installation
|
|
17
|
+
|
|
18
|
+
### Option 1: Install from PyPI (Recommended)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install godfather-cli
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Option 2: Install from GitHub
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install git+https://github.com/theaisocietyasu/godfather.git#subdirectory=cli
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Option 3: Install for Development
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
git clone https://github.com/theaisocietyasu/godfather.git
|
|
34
|
+
cd godfather/cli
|
|
35
|
+
pip install -e .
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## 🚀 Quick Start
|
|
39
|
+
|
|
40
|
+
1. **Run the CLI**:
|
|
41
|
+
```bash
|
|
42
|
+
godfather
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. **Authenticate**:
|
|
46
|
+
- Visit the admin portal to get your authentication token
|
|
47
|
+
- Paste the token when prompted
|
|
48
|
+
|
|
49
|
+
3. **Connect to a Pod**:
|
|
50
|
+
- Select option `2` to connect to a pod
|
|
51
|
+
- Choose from available pods
|
|
52
|
+
- Automatically SSH into your isolated workspace
|
|
53
|
+
|
|
54
|
+
## 📖 Usage
|
|
55
|
+
|
|
56
|
+
### Interactive Menu
|
|
57
|
+
|
|
58
|
+
Simply run:
|
|
59
|
+
```bash
|
|
60
|
+
godfather
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
You'll see a beautiful menu:
|
|
64
|
+
```
|
|
65
|
+
╔═══════════════════════════════════════╗
|
|
66
|
+
║ Godfather CLI ║
|
|
67
|
+
║ AI Society RunPod Environment Manager ║
|
|
68
|
+
╚═══════════════════════════════════════╝
|
|
69
|
+
|
|
70
|
+
╭──────── What would you like to do? ──────────╮
|
|
71
|
+
│ 1. 📋 List available pods │
|
|
72
|
+
│ 2. 🔌 Connect to a pod │
|
|
73
|
+
│ 3. 📊 Show status │
|
|
74
|
+
│ 4. 🚪 Logout │
|
|
75
|
+
│ 5. 👋 Exit │
|
|
76
|
+
╰──────────────────────────────────────────────╯
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Command-Line Interface
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# List available pods
|
|
83
|
+
godfather list
|
|
84
|
+
|
|
85
|
+
# Connect to a specific pod
|
|
86
|
+
godfather connect <pod-id>
|
|
87
|
+
|
|
88
|
+
# Connect interactively
|
|
89
|
+
godfather connect
|
|
90
|
+
|
|
91
|
+
# Show CLI status
|
|
92
|
+
godfather status
|
|
93
|
+
|
|
94
|
+
# Logout
|
|
95
|
+
godfather logout
|
|
96
|
+
|
|
97
|
+
# Re-authenticate
|
|
98
|
+
godfather auth
|
|
99
|
+
|
|
100
|
+
# Use custom API URL
|
|
101
|
+
godfather --api-url https://your-api.com list
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 🔑 Authentication
|
|
105
|
+
|
|
106
|
+
1. Get your authentication token from: `https://your-godfather-instance.com/cli-auth`
|
|
107
|
+
2. Run `godfather` or `godfather auth`
|
|
108
|
+
3. Paste your token when prompted
|
|
109
|
+
4. Token is securely stored in `~/.godfather/config.json`
|
|
110
|
+
|
|
111
|
+
## 🛠️ Configuration
|
|
112
|
+
|
|
113
|
+
Configuration is stored in `~/.godfather/config.json`:
|
|
114
|
+
|
|
115
|
+
```json
|
|
116
|
+
{
|
|
117
|
+
"token": "discord_<user_id>_<timestamp>",
|
|
118
|
+
"discord_user_id": "<your_discord_id>"
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Environment Variables
|
|
123
|
+
|
|
124
|
+
You can override the API URL with:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
export GODFATHER_API_URL="https://your-api.com"
|
|
128
|
+
# or
|
|
129
|
+
export BACKEND_URL="https://your-api.com"
|
|
130
|
+
# or
|
|
131
|
+
export NEXT_PUBLIC_BACKEND_URL="https://your-api.com"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 🎨 Features in Detail
|
|
135
|
+
|
|
136
|
+
### Beautiful Tables
|
|
137
|
+
|
|
138
|
+
Pod listings display in rich, colorful tables:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
🚀 Available Pods (3)
|
|
142
|
+
╭───┬────────┬─────────────┬──────────────┬───────────╮
|
|
143
|
+
│ # │ Status │ Name │ ID │ Created │
|
|
144
|
+
├───┼────────┼─────────────┼──────────────┼───────────┤
|
|
145
|
+
│ 1 │ 🟢 RUN │ ml-training │ abc123def... │ 2 days ago│
|
|
146
|
+
│ 2 │ 🟢 RUN │ gpu-dev │ xyz789ghi... │ 1 week ago│
|
|
147
|
+
│ 3 │ 🔴 OFF │ testing │ jkl456mno... │ 3 days ago│
|
|
148
|
+
╰───┴────────┴─────────────┴──────────────┴───────────╯
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Status Dashboard
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
Godfather CLI Status
|
|
155
|
+
╭─────────────────────┬─────────────────────────────╮
|
|
156
|
+
│ 🔐 Authentication │ ✓ Authenticated │
|
|
157
|
+
│ 🌐 API Connection │ ✓ Connected │
|
|
158
|
+
│ 🏠 Config Directory │ /home/user/.godfather │
|
|
159
|
+
│ 🔗 API Endpoint │ https://api.example.com │
|
|
160
|
+
╰─────────────────────┴─────────────────────────────╯
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## 🤝 Contributing
|
|
164
|
+
|
|
165
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
166
|
+
|
|
167
|
+
1. Fork the repository
|
|
168
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
169
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
170
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
171
|
+
5. Open a Pull Request
|
|
172
|
+
|
|
173
|
+
## 📝 License
|
|
174
|
+
|
|
175
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
176
|
+
|
|
177
|
+
## 🆘 Support
|
|
178
|
+
|
|
179
|
+
- **Issues**: [GitHub Issues](https://github.com/theaisocietyasu/godfather/issues)
|
|
180
|
+
- **Discord**: Contact AI Society ASU administrators
|
|
181
|
+
- **Email**: admin@ais-asu.com
|
|
182
|
+
|
|
183
|
+
## 🎓 About
|
|
184
|
+
|
|
185
|
+
Built with ❤️ by [AI Society at Arizona State University](https://github.com/theaisocietyasu)
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
**Note**: You need to be a member of the AI Society Discord server with appropriate permissions to use this CLI.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""CLI Authentication module"""
|
|
2
|
+
import os
|
|
3
|
+
import json
|
|
4
|
+
import requests
|
|
5
|
+
import getpass
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Optional
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich import box
|
|
11
|
+
from rich.prompt import Prompt
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class CLIAuthenticator:
|
|
17
|
+
"""Handle CLI authentication"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, api_base: str, config_dir: Path):
|
|
20
|
+
self.api_base = api_base
|
|
21
|
+
self.config_dir = config_dir
|
|
22
|
+
self.config_file = config_dir / 'config.json'
|
|
23
|
+
self.config = self.load_config()
|
|
24
|
+
|
|
25
|
+
def load_config(self) -> Dict:
|
|
26
|
+
"""Load configuration from file"""
|
|
27
|
+
if self.config_file.exists():
|
|
28
|
+
try:
|
|
29
|
+
with open(self.config_file, 'r') as f:
|
|
30
|
+
return json.load(f)
|
|
31
|
+
except (json.JSONDecodeError, IOError):
|
|
32
|
+
pass
|
|
33
|
+
return {}
|
|
34
|
+
|
|
35
|
+
def save_config(self):
|
|
36
|
+
"""Save configuration to file"""
|
|
37
|
+
self.config_dir.mkdir(exist_ok=True)
|
|
38
|
+
with open(self.config_file, 'w') as f:
|
|
39
|
+
json.dump(self.config, f, indent=2)
|
|
40
|
+
|
|
41
|
+
def authenticate(self) -> bool:
|
|
42
|
+
"""Authenticate user via CLI token"""
|
|
43
|
+
panel = Panel(
|
|
44
|
+
f"[cyan]Visit the admin portal to get your authentication token:[/cyan]\n"
|
|
45
|
+
f"[bold white]{self.api_base}/cli-auth[/bold white]",
|
|
46
|
+
title="[bold magenta]🔐 Authentication Required[/bold magenta]",
|
|
47
|
+
border_style="magenta",
|
|
48
|
+
box=box.ROUNDED
|
|
49
|
+
)
|
|
50
|
+
console.print(panel)
|
|
51
|
+
console.print()
|
|
52
|
+
|
|
53
|
+
token = Prompt.ask("[cyan]Enter your authentication token[/cyan]", password=True).strip()
|
|
54
|
+
if not token:
|
|
55
|
+
console.print("[red]✗[/red] No token provided")
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
# Extract Discord ID from token (format: discord_<ID>_<timestamp>)
|
|
59
|
+
try:
|
|
60
|
+
if token.startswith('discord_'):
|
|
61
|
+
parts = token.split('_')
|
|
62
|
+
if len(parts) >= 2:
|
|
63
|
+
discord_user_id = parts[1]
|
|
64
|
+
else:
|
|
65
|
+
console.print("[red]✗[/red] Invalid token format")
|
|
66
|
+
return False
|
|
67
|
+
else:
|
|
68
|
+
console.print("[red]✗[/red] Invalid token format")
|
|
69
|
+
return False
|
|
70
|
+
except:
|
|
71
|
+
console.print("[red]✗[/red] Invalid token format")
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
# Verify token with backend
|
|
75
|
+
try:
|
|
76
|
+
response = requests.post(
|
|
77
|
+
f'{self.api_base}/api/auth/verify',
|
|
78
|
+
json={'discord_user_id': discord_user_id},
|
|
79
|
+
timeout=10
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if response.status_code == 200:
|
|
83
|
+
self.config['token'] = token
|
|
84
|
+
self.config['discord_user_id'] = discord_user_id
|
|
85
|
+
self.save_config()
|
|
86
|
+
console.print("[green]✓[/green] Authentication successful!")
|
|
87
|
+
return True
|
|
88
|
+
else:
|
|
89
|
+
error = response.json().get('error', 'Authentication failed')
|
|
90
|
+
console.print(f"[red]✗[/red] Authentication failed: {error}")
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
except requests.RequestException as e:
|
|
94
|
+
console.print(f"[red]✗[/red] Connection error: {e}")
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
def get_token(self) -> Optional[str]:
|
|
98
|
+
"""Get authentication token"""
|
|
99
|
+
return self.config.get('token')
|
|
100
|
+
|
|
101
|
+
def get_discord_user_id(self) -> Optional[str]:
|
|
102
|
+
"""Get Discord user ID"""
|
|
103
|
+
return self.config.get('discord_user_id')
|
|
104
|
+
|
|
105
|
+
def is_authenticated(self) -> bool:
|
|
106
|
+
"""Check if user is authenticated"""
|
|
107
|
+
return 'token' in self.config
|
|
108
|
+
|
|
109
|
+
def logout(self):
|
|
110
|
+
"""Clear authentication token"""
|
|
111
|
+
if 'token' in self.config:
|
|
112
|
+
del self.config['token']
|
|
113
|
+
self.save_config()
|
|
114
|
+
console.print("[green]✓[/green] Logged out successfully")
|
|
115
|
+
else:
|
|
116
|
+
console.print("[yellow]ℹ[/yellow] You were not logged in")
|
|
117
|
+
|
|
118
|
+
def verify_token(self) -> bool:
|
|
119
|
+
"""Verify current token is still valid"""
|
|
120
|
+
if not self.is_authenticated():
|
|
121
|
+
return False
|
|
122
|
+
|
|
123
|
+
discord_user_id = self.config.get('discord_user_id')
|
|
124
|
+
if not discord_user_id:
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
response = requests.post(
|
|
129
|
+
f'{self.api_base}/api/auth/verify',
|
|
130
|
+
json={'discord_user_id': discord_user_id},
|
|
131
|
+
timeout=5
|
|
132
|
+
)
|
|
133
|
+
return response.status_code == 200
|
|
134
|
+
except:
|
|
135
|
+
return False
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
AI Society Godfather CLI - Modular version
|
|
4
|
+
Command line interface for connecting to RunPod environments
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import argparse
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
from rich import box
|
|
15
|
+
from rich.prompt import Prompt, Confirm
|
|
16
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
17
|
+
|
|
18
|
+
from .auth import CLIAuthenticator
|
|
19
|
+
from .pod_manager import PodManager
|
|
20
|
+
from .ssh_connector import SSHConnector
|
|
21
|
+
|
|
22
|
+
console = Console()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GodfatherCLI:
|
|
26
|
+
"""Main CLI application"""
|
|
27
|
+
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.config_dir = Path.home() / '.godfather'
|
|
30
|
+
|
|
31
|
+
# Auto-detect API URL from multiple possible environment variables
|
|
32
|
+
# Priority: GODFATHER_API_URL > BACKEND_URL > NEXT_PUBLIC_BACKEND_URL > NEXT_PUBLIC_API_URL
|
|
33
|
+
self.api_base = (
|
|
34
|
+
os.getenv('GODFATHER_API_URL') or
|
|
35
|
+
os.getenv('BACKEND_URL') or
|
|
36
|
+
os.getenv('NEXT_PUBLIC_BACKEND_URL') or
|
|
37
|
+
(os.getenv('NEXT_PUBLIC_API_URL', '').replace('/api', '')) or
|
|
38
|
+
'https://8bzhwve1ri5cw2-80.proxy.runpod.net' # Fallback to RunPod proxy
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Initialize components
|
|
42
|
+
self.authenticator = CLIAuthenticator(self.api_base, self.config_dir)
|
|
43
|
+
self.pod_manager = PodManager(self.api_base)
|
|
44
|
+
self.ssh_connector = SSHConnector(self.api_base, self.config_dir)
|
|
45
|
+
|
|
46
|
+
def print_banner(self):
|
|
47
|
+
"""Print CLI banner"""
|
|
48
|
+
banner = Panel.fit(
|
|
49
|
+
"[bold magenta]Godfather CLI[/bold magenta]\n"
|
|
50
|
+
"[dim]AI Society RunPod Environment Manager[/dim]",
|
|
51
|
+
border_style="magenta",
|
|
52
|
+
box=box.DOUBLE
|
|
53
|
+
)
|
|
54
|
+
console.print(banner)
|
|
55
|
+
console.print()
|
|
56
|
+
|
|
57
|
+
def ensure_authenticated(self) -> bool:
|
|
58
|
+
"""Ensure user is authenticated"""
|
|
59
|
+
if not self.authenticator.is_authenticated():
|
|
60
|
+
return self.authenticator.authenticate()
|
|
61
|
+
|
|
62
|
+
# Verify token is still valid
|
|
63
|
+
if not self.authenticator.verify_token():
|
|
64
|
+
console.print("[yellow]⚠[/yellow] Token expired. Please re-authenticate.")
|
|
65
|
+
return self.authenticator.authenticate()
|
|
66
|
+
|
|
67
|
+
return True
|
|
68
|
+
|
|
69
|
+
def list_pods(self):
|
|
70
|
+
"""List available public pods"""
|
|
71
|
+
if not self.ensure_authenticated():
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
discord_user_id = self.authenticator.get_discord_user_id()
|
|
75
|
+
self.pod_manager.list_pods(discord_user_id)
|
|
76
|
+
|
|
77
|
+
def connect_to_pod(self, pod_id: str = None):
|
|
78
|
+
"""Connect to a specific pod"""
|
|
79
|
+
if not self.ensure_authenticated():
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
discord_user_id = self.authenticator.get_discord_user_id()
|
|
83
|
+
|
|
84
|
+
# Select pod if not provided
|
|
85
|
+
if not pod_id:
|
|
86
|
+
pod_id = self.pod_manager.select_pod(discord_user_id)
|
|
87
|
+
if not pod_id:
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
console.print(f"[cyan]🔌 Connecting to pod {pod_id[:8]}...[/cyan]")
|
|
91
|
+
|
|
92
|
+
# Get connection details
|
|
93
|
+
ssh_info = self.pod_manager.get_connection_info(pod_id, discord_user_id)
|
|
94
|
+
if not ssh_info:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
# Fetch SSH key
|
|
98
|
+
if not self.ssh_connector.fetch_ssh_key(discord_user_id):
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
# Establish SSH connection
|
|
102
|
+
self.ssh_connector.connect(ssh_info)
|
|
103
|
+
|
|
104
|
+
def status(self):
|
|
105
|
+
"""Show CLI status and configuration"""
|
|
106
|
+
table = Table(title="[bold cyan]Godfather CLI Status[/bold cyan]", box=box.ROUNDED, border_style="cyan")
|
|
107
|
+
table.add_column("Setting", style="cyan bold", no_wrap=True)
|
|
108
|
+
table.add_column("Value", style="white")
|
|
109
|
+
|
|
110
|
+
if self.authenticator.is_authenticated():
|
|
111
|
+
table.add_row("🔐 Authentication", "[green]✓ Authenticated[/green]")
|
|
112
|
+
if self.authenticator.verify_token():
|
|
113
|
+
table.add_row("🌐 API Connection", "[green]✓ Connected[/green]")
|
|
114
|
+
else:
|
|
115
|
+
table.add_row("🌐 API Connection", "[yellow]⚠ Token expired[/yellow]")
|
|
116
|
+
else:
|
|
117
|
+
table.add_row("🔐 Authentication", "[red]✗ Not authenticated[/red]")
|
|
118
|
+
|
|
119
|
+
table.add_row("🏠 Config Directory", str(self.config_dir))
|
|
120
|
+
table.add_row("🔗 API Endpoint", self.api_base)
|
|
121
|
+
|
|
122
|
+
console.print(table)
|
|
123
|
+
|
|
124
|
+
def logout(self):
|
|
125
|
+
"""Clear authentication token"""
|
|
126
|
+
self.authenticator.logout()
|
|
127
|
+
|
|
128
|
+
def authenticate(self):
|
|
129
|
+
"""Trigger authentication"""
|
|
130
|
+
self.authenticator.authenticate()
|
|
131
|
+
|
|
132
|
+
def interactive_menu(self):
|
|
133
|
+
"""Show interactive menu"""
|
|
134
|
+
self.print_banner()
|
|
135
|
+
|
|
136
|
+
while True:
|
|
137
|
+
console.print()
|
|
138
|
+
menu = Table.grid(padding=(0, 2))
|
|
139
|
+
menu.add_column(style="cyan bold", justify="right")
|
|
140
|
+
menu.add_column(style="white")
|
|
141
|
+
|
|
142
|
+
menu.add_row("1.", "📋 List available pods")
|
|
143
|
+
menu.add_row("2.", "🔌 Connect to a pod")
|
|
144
|
+
menu.add_row("3.", "📊 Show status")
|
|
145
|
+
menu.add_row("4.", "🚪 Logout")
|
|
146
|
+
menu.add_row("5.", "👋 Exit")
|
|
147
|
+
|
|
148
|
+
panel = Panel(
|
|
149
|
+
menu,
|
|
150
|
+
title="[bold cyan]What would you like to do?[/bold cyan]",
|
|
151
|
+
border_style="cyan",
|
|
152
|
+
box=box.ROUNDED
|
|
153
|
+
)
|
|
154
|
+
console.print(panel)
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
choice = Prompt.ask("\n[cyan]Enter your choice[/cyan]", choices=["1", "2", "3", "4", "5"], default="1")
|
|
158
|
+
console.print()
|
|
159
|
+
|
|
160
|
+
if choice == '1':
|
|
161
|
+
self.list_pods()
|
|
162
|
+
elif choice == '2':
|
|
163
|
+
self.connect_to_pod()
|
|
164
|
+
elif choice == '3':
|
|
165
|
+
self.status()
|
|
166
|
+
elif choice == '4':
|
|
167
|
+
self.logout()
|
|
168
|
+
elif choice == '5':
|
|
169
|
+
console.print("[bold magenta]👋 Goodbye![/bold magenta]")
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
except KeyboardInterrupt:
|
|
173
|
+
console.print("\n[bold magenta]👋 Goodbye![/bold magenta]")
|
|
174
|
+
break
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def main():
|
|
178
|
+
"""Main CLI entry point"""
|
|
179
|
+
parser = argparse.ArgumentParser(
|
|
180
|
+
description='AI Society Godfather CLI - RunPod Environment Manager',
|
|
181
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
182
|
+
epilog="""
|
|
183
|
+
Examples:
|
|
184
|
+
godfather list # List available pods
|
|
185
|
+
godfather connect # Interactive pod connection
|
|
186
|
+
godfather connect <pod-id> # Connect to specific pod
|
|
187
|
+
godfather status # Show CLI status
|
|
188
|
+
godfather logout # Clear authentication
|
|
189
|
+
|
|
190
|
+
For support, contact AI Society administrators.
|
|
191
|
+
"""
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
parser.add_argument(
|
|
195
|
+
'command',
|
|
196
|
+
nargs='?',
|
|
197
|
+
choices=['list', 'connect', 'status', 'logout', 'auth'],
|
|
198
|
+
help='Command to execute'
|
|
199
|
+
)
|
|
200
|
+
parser.add_argument(
|
|
201
|
+
'pod_id',
|
|
202
|
+
nargs='?',
|
|
203
|
+
help='Pod ID for connect command'
|
|
204
|
+
)
|
|
205
|
+
parser.add_argument(
|
|
206
|
+
'--api-url',
|
|
207
|
+
help='Override API base URL'
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
args = parser.parse_args()
|
|
211
|
+
|
|
212
|
+
# Override API URL if provided
|
|
213
|
+
if args.api_url:
|
|
214
|
+
os.environ['GODFATHER_API_URL'] = args.api_url
|
|
215
|
+
|
|
216
|
+
cli = GodfatherCLI()
|
|
217
|
+
|
|
218
|
+
# If no command provided, show interactive menu
|
|
219
|
+
if not args.command:
|
|
220
|
+
cli.interactive_menu()
|
|
221
|
+
return
|
|
222
|
+
|
|
223
|
+
# Execute command
|
|
224
|
+
if args.command == 'list':
|
|
225
|
+
cli.list_pods()
|
|
226
|
+
elif args.command == 'connect':
|
|
227
|
+
cli.connect_to_pod(args.pod_id)
|
|
228
|
+
elif args.command == 'status':
|
|
229
|
+
cli.status()
|
|
230
|
+
elif args.command == 'logout':
|
|
231
|
+
cli.logout()
|
|
232
|
+
elif args.command == 'auth':
|
|
233
|
+
cli.authenticate()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
if __name__ == '__main__':
|
|
237
|
+
main()
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""CLI Pod Operations module"""
|
|
2
|
+
import requests
|
|
3
|
+
from typing import List, Dict, Optional
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich import box
|
|
7
|
+
from rich.prompt import IntPrompt
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PodManager:
|
|
13
|
+
"""Handle pod operations"""
|
|
14
|
+
|
|
15
|
+
def __init__(self, api_base: str):
|
|
16
|
+
self.api_base = api_base
|
|
17
|
+
|
|
18
|
+
def get_public_pods(self, discord_user_id: str) -> List[Dict]:
|
|
19
|
+
"""Get list of public pods available for connection"""
|
|
20
|
+
try:
|
|
21
|
+
headers = {'X-Discord-User-ID': discord_user_id}
|
|
22
|
+
response = requests.get(
|
|
23
|
+
f'{self.api_base}/api/pods/public',
|
|
24
|
+
headers=headers,
|
|
25
|
+
timeout=10
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
if response.status_code == 200:
|
|
29
|
+
return response.json().get('pods', [])
|
|
30
|
+
elif response.status_code == 401:
|
|
31
|
+
console.print("[red]✗[/red] Authentication failed. Please re-authenticate.")
|
|
32
|
+
return []
|
|
33
|
+
else:
|
|
34
|
+
error = response.json().get('error', 'Failed to fetch pods')
|
|
35
|
+
console.print(f"[red]✗[/red] Error: {error}")
|
|
36
|
+
return []
|
|
37
|
+
|
|
38
|
+
except requests.RequestException as e:
|
|
39
|
+
console.print(f"[red]✗[/red] Connection error: {e}")
|
|
40
|
+
return []
|
|
41
|
+
|
|
42
|
+
def get_connection_info(self, pod_id: str, discord_user_id: str) -> Optional[Dict]:
|
|
43
|
+
"""Get SSH connection information for a pod"""
|
|
44
|
+
try:
|
|
45
|
+
headers = {'X-Discord-User-ID': discord_user_id}
|
|
46
|
+
response = requests.post(
|
|
47
|
+
f'{self.api_base}/api/pods/{pod_id}/connect',
|
|
48
|
+
headers=headers,
|
|
49
|
+
timeout=10
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
if response.status_code == 200:
|
|
53
|
+
return response.json().get('ssh_info')
|
|
54
|
+
else:
|
|
55
|
+
error = response.json().get('error', 'Connection failed')
|
|
56
|
+
console.print(f"[red]✗[/red] Connection failed: {error}")
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
except requests.RequestException as e:
|
|
60
|
+
console.print(f"[red]✗[/red] Connection error: {e}")
|
|
61
|
+
return None
|
|
62
|
+
|
|
63
|
+
def list_pods(self, discord_user_id: str):
|
|
64
|
+
"""List available public pods"""
|
|
65
|
+
console.print("[cyan]📡 Fetching available pods...[/cyan]")
|
|
66
|
+
pods = self.get_public_pods(discord_user_id)
|
|
67
|
+
|
|
68
|
+
if not pods:
|
|
69
|
+
console.print("[yellow]😔 No public pods available at the moment.[/yellow]")
|
|
70
|
+
console.print(" [dim]Ask a godfather to create and make pods public.[/dim]")
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
table = Table(title=f"[bold cyan]🚀 Available Pods ({len(pods)})[/bold cyan]", box=box.ROUNDED, border_style="cyan")
|
|
74
|
+
table.add_column("#", style="dim", width=3)
|
|
75
|
+
table.add_column("Status", justify="center", width=8)
|
|
76
|
+
table.add_column("Name", style="cyan bold")
|
|
77
|
+
table.add_column("ID", style="dim")
|
|
78
|
+
table.add_column("Created", style="white")
|
|
79
|
+
|
|
80
|
+
for i, pod in enumerate(pods, 1):
|
|
81
|
+
status = pod.get('status', 'Unknown')
|
|
82
|
+
if status == 'RUNNING':
|
|
83
|
+
status_display = "[green]� RUN[/green]"
|
|
84
|
+
else:
|
|
85
|
+
status_display = "[red]🔴 OFF[/red]"
|
|
86
|
+
|
|
87
|
+
table.add_row(
|
|
88
|
+
str(i),
|
|
89
|
+
status_display,
|
|
90
|
+
pod['name'],
|
|
91
|
+
pod['id'][:12] + "...",
|
|
92
|
+
pod.get('created_at', 'Unknown')
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
console.print()
|
|
96
|
+
console.print(table)
|
|
97
|
+
|
|
98
|
+
def select_pod(self, discord_user_id: str) -> Optional[str]:
|
|
99
|
+
"""Interactive pod selection"""
|
|
100
|
+
pods = self.get_public_pods(discord_user_id)
|
|
101
|
+
|
|
102
|
+
if not pods:
|
|
103
|
+
console.print("[yellow]😔 No public pods available.[/yellow]")
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
table = Table(title="[bold cyan]🚀 Select a Pod[/bold cyan]", box=box.SIMPLE, border_style="cyan")
|
|
107
|
+
table.add_column("#", style="cyan bold", width=3)
|
|
108
|
+
table.add_column("Status", justify="center", width=8)
|
|
109
|
+
table.add_column("Name", style="white")
|
|
110
|
+
table.add_column("ID", style="dim")
|
|
111
|
+
|
|
112
|
+
for i, pod in enumerate(pods, 1):
|
|
113
|
+
status = pod.get('status', 'Unknown')
|
|
114
|
+
status_display = "[green]🟢 RUN[/green]" if status == 'RUNNING' else "[red]🔴 OFF[/red]"
|
|
115
|
+
|
|
116
|
+
table.add_row(
|
|
117
|
+
str(i),
|
|
118
|
+
status_display,
|
|
119
|
+
pod['name'],
|
|
120
|
+
pod['id'][:12] + "..."
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
console.print()
|
|
124
|
+
console.print(table)
|
|
125
|
+
console.print()
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
choice = IntPrompt.ask("[cyan]Select a pod number[/cyan]", choices=[str(i) for i in range(1, len(pods) + 1)])
|
|
129
|
+
return pods[choice - 1]['id']
|
|
130
|
+
except (KeyboardInterrupt, EOFError):
|
|
131
|
+
console.print("\n[yellow]Selection cancelled[/yellow]")
|
|
132
|
+
return None
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""CLI SSH Connection module"""
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import requests
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SSHConnector:
|
|
10
|
+
"""Handle SSH connections to pods"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, api_base: str, config_dir: Path):
|
|
13
|
+
self.api_base = api_base
|
|
14
|
+
self.ssh_key_dir = config_dir / 'ssh'
|
|
15
|
+
self.ssh_key_file = self.ssh_key_dir / 'godfather_key'
|
|
16
|
+
|
|
17
|
+
def fetch_ssh_key(self, discord_user_id: str) -> bool:
|
|
18
|
+
"""Fetch SSH private key from API and save it"""
|
|
19
|
+
print("🔑 Fetching SSH key...")
|
|
20
|
+
try:
|
|
21
|
+
headers = {'X-Discord-User-ID': discord_user_id}
|
|
22
|
+
response = requests.get(
|
|
23
|
+
f'{self.api_base}/api/ssh-key',
|
|
24
|
+
headers=headers,
|
|
25
|
+
timeout=10
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
if response.status_code != 200:
|
|
29
|
+
error = response.json().get('error', 'Unknown error')
|
|
30
|
+
print(f"❌ Failed to fetch SSH key: {error}")
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
private_key = response.json().get('private_key')
|
|
34
|
+
if not private_key:
|
|
35
|
+
print("❌ No SSH key returned from API")
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
# Ensure proper formatting
|
|
39
|
+
if '\\n' in private_key:
|
|
40
|
+
private_key = private_key.replace('\\n', '\n')
|
|
41
|
+
|
|
42
|
+
if not private_key.endswith('\n'):
|
|
43
|
+
private_key += '\n'
|
|
44
|
+
|
|
45
|
+
# Save SSH key
|
|
46
|
+
self.ssh_key_dir.mkdir(exist_ok=True)
|
|
47
|
+
|
|
48
|
+
with open(self.ssh_key_file, 'w') as f:
|
|
49
|
+
f.write(private_key)
|
|
50
|
+
|
|
51
|
+
# Set correct permissions
|
|
52
|
+
os.chmod(self.ssh_key_file, 0o600)
|
|
53
|
+
print("✅ SSH key ready")
|
|
54
|
+
|
|
55
|
+
return True
|
|
56
|
+
|
|
57
|
+
except requests.RequestException as e:
|
|
58
|
+
print(f"❌ Failed to fetch SSH key: {e}")
|
|
59
|
+
return False
|
|
60
|
+
except IOError as e:
|
|
61
|
+
print(f"❌ Failed to save SSH key: {e}")
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
def connect(self, ssh_info: Dict) -> int:
|
|
65
|
+
"""Establish SSH connection to pod"""
|
|
66
|
+
host = ssh_info.get('host')
|
|
67
|
+
port = ssh_info.get('port', 22)
|
|
68
|
+
username = ssh_info.get('username', 'root')
|
|
69
|
+
user_folder = ssh_info.get('user_folder', 'user')
|
|
70
|
+
is_admin = ssh_info.get('is_admin', False)
|
|
71
|
+
|
|
72
|
+
if not host:
|
|
73
|
+
print("❌ No host information available")
|
|
74
|
+
return 1
|
|
75
|
+
|
|
76
|
+
print(f"🔗 Connecting to {host}:{port}")
|
|
77
|
+
print(f"👤 User workspace: /workspace/users/{user_folder}")
|
|
78
|
+
|
|
79
|
+
if is_admin:
|
|
80
|
+
print("👑 Admin mode: Full system access")
|
|
81
|
+
else:
|
|
82
|
+
print("🔒 Restricted mode: Limited to your workspace")
|
|
83
|
+
|
|
84
|
+
print("\n📁 Setting up your personal workspace...")
|
|
85
|
+
|
|
86
|
+
# Setup user workspace
|
|
87
|
+
admin_flag = "true" if is_admin else "false"
|
|
88
|
+
setup_command = f"/usr/local/bin/godfather-user-setup.sh {user_folder} {admin_flag}"
|
|
89
|
+
|
|
90
|
+
# Build SSH connection command
|
|
91
|
+
ssh_command = [
|
|
92
|
+
'ssh',
|
|
93
|
+
'-t',
|
|
94
|
+
'-i', str(self.ssh_key_file),
|
|
95
|
+
'-o', 'StrictHostKeyChecking=no',
|
|
96
|
+
'-o', 'UserKnownHostsFile=/dev/null',
|
|
97
|
+
'-p', str(port),
|
|
98
|
+
f'{username}@{host}',
|
|
99
|
+
f'PROFILE=$({setup_command}) && source $PROFILE && exec bash'
|
|
100
|
+
]
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
print("🚪 Opening SSH session...")
|
|
104
|
+
|
|
105
|
+
if is_admin:
|
|
106
|
+
print("✅ You have full admin access (sudo available)")
|
|
107
|
+
else:
|
|
108
|
+
print("💡 You're in your personal workspace")
|
|
109
|
+
print("🤝 You can also access /workspace/shared for collaboration")
|
|
110
|
+
print("⚠️ Restricted mode: No sudo access")
|
|
111
|
+
|
|
112
|
+
print("📝 Type 'exit' to disconnect\n")
|
|
113
|
+
|
|
114
|
+
# Execute SSH connection
|
|
115
|
+
result = subprocess.run(ssh_command)
|
|
116
|
+
|
|
117
|
+
if result.returncode != 0:
|
|
118
|
+
print("\n❌ SSH connection failed!")
|
|
119
|
+
print("\n⚠️ SSH Key Setup Required")
|
|
120
|
+
print("=" * 60)
|
|
121
|
+
print("The pod needs to have the SSH key configured first.")
|
|
122
|
+
print("\nOption 1: Use the godfather-base Docker image (recommended)")
|
|
123
|
+
print(" - The image automatically sets up SSH on startup")
|
|
124
|
+
print(" - Image: theaisocietyasu/godfather-base:latest")
|
|
125
|
+
print("\nOption 2: Manual setup in RunPod web terminal:")
|
|
126
|
+
print("\n mkdir -p /root/.ssh && \\")
|
|
127
|
+
print(f' echo "$GODFATHER_SSH_PUBLIC_KEY" >> /root/.ssh/authorized_keys && \\')
|
|
128
|
+
print(" chmod 700 /root/.ssh && \\")
|
|
129
|
+
print(" chmod 600 /root/.ssh/authorized_keys")
|
|
130
|
+
print("\n💡 The GODFATHER_SSH_PUBLIC_KEY environment variable is already set in your pod.")
|
|
131
|
+
print("=" * 60)
|
|
132
|
+
return result.returncode
|
|
133
|
+
else:
|
|
134
|
+
print("\n👋 Disconnected from pod")
|
|
135
|
+
return 0
|
|
136
|
+
|
|
137
|
+
except KeyboardInterrupt:
|
|
138
|
+
print("\n👋 Connection cancelled")
|
|
139
|
+
return 1
|
|
140
|
+
except FileNotFoundError:
|
|
141
|
+
print("❌ SSH client not found. Please install OpenSSH client.")
|
|
142
|
+
return 1
|
|
143
|
+
except subprocess.SubprocessError as e:
|
|
144
|
+
print(f"❌ SSH connection failed: {e}")
|
|
145
|
+
return 1
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: godfather-cli
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: AI Society Godfather CLI - RunPod Environment Manager
|
|
5
|
+
Author-email: AI Society ASU <theaisocietyasu@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/theaisocietyasu/godfather
|
|
8
|
+
Project-URL: Repository, https://github.com/theaisocietyasu/godfather
|
|
9
|
+
Project-URL: Issues, https://github.com/theaisocietyasu/godfather/issues
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Requires-Python: >=3.7
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: requests>=2.25.0
|
|
22
|
+
Requires-Dist: rich>=13.0.0
|
|
23
|
+
|
|
24
|
+
# Godfather CLI
|
|
25
|
+
|
|
26
|
+
Beautiful command-line interface for managing AI Society ASU RunPod environments.
|
|
27
|
+
|
|
28
|
+

|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
## ✨ Features
|
|
32
|
+
|
|
33
|
+
- 🎨 **Beautiful Interface** - Rich terminal UI with colors, tables, and panels
|
|
34
|
+
- 🔐 **Secure Authentication** - Discord-based authentication
|
|
35
|
+
- 🚀 **Pod Management** - List and connect to available RunPod environments
|
|
36
|
+
- 🔌 **SSH Integration** - Automatic SSH key setup and connection
|
|
37
|
+
- 📊 **Status Monitoring** - Check authentication and configuration status
|
|
38
|
+
|
|
39
|
+
## 📦 Installation
|
|
40
|
+
|
|
41
|
+
### Option 1: Install from PyPI (Recommended)
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install godfather-cli
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Option 2: Install from GitHub
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install git+https://github.com/theaisocietyasu/godfather.git#subdirectory=cli
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Option 3: Install for Development
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git clone https://github.com/theaisocietyasu/godfather.git
|
|
57
|
+
cd godfather/cli
|
|
58
|
+
pip install -e .
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## 🚀 Quick Start
|
|
62
|
+
|
|
63
|
+
1. **Run the CLI**:
|
|
64
|
+
```bash
|
|
65
|
+
godfather
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
2. **Authenticate**:
|
|
69
|
+
- Visit the admin portal to get your authentication token
|
|
70
|
+
- Paste the token when prompted
|
|
71
|
+
|
|
72
|
+
3. **Connect to a Pod**:
|
|
73
|
+
- Select option `2` to connect to a pod
|
|
74
|
+
- Choose from available pods
|
|
75
|
+
- Automatically SSH into your isolated workspace
|
|
76
|
+
|
|
77
|
+
## 📖 Usage
|
|
78
|
+
|
|
79
|
+
### Interactive Menu
|
|
80
|
+
|
|
81
|
+
Simply run:
|
|
82
|
+
```bash
|
|
83
|
+
godfather
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
You'll see a beautiful menu:
|
|
87
|
+
```
|
|
88
|
+
╔═══════════════════════════════════════╗
|
|
89
|
+
║ Godfather CLI ║
|
|
90
|
+
║ AI Society RunPod Environment Manager ║
|
|
91
|
+
╚═══════════════════════════════════════╝
|
|
92
|
+
|
|
93
|
+
╭──────── What would you like to do? ──────────╮
|
|
94
|
+
│ 1. 📋 List available pods │
|
|
95
|
+
│ 2. 🔌 Connect to a pod │
|
|
96
|
+
│ 3. 📊 Show status │
|
|
97
|
+
│ 4. 🚪 Logout │
|
|
98
|
+
│ 5. 👋 Exit │
|
|
99
|
+
╰──────────────────────────────────────────────╯
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Command-Line Interface
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# List available pods
|
|
106
|
+
godfather list
|
|
107
|
+
|
|
108
|
+
# Connect to a specific pod
|
|
109
|
+
godfather connect <pod-id>
|
|
110
|
+
|
|
111
|
+
# Connect interactively
|
|
112
|
+
godfather connect
|
|
113
|
+
|
|
114
|
+
# Show CLI status
|
|
115
|
+
godfather status
|
|
116
|
+
|
|
117
|
+
# Logout
|
|
118
|
+
godfather logout
|
|
119
|
+
|
|
120
|
+
# Re-authenticate
|
|
121
|
+
godfather auth
|
|
122
|
+
|
|
123
|
+
# Use custom API URL
|
|
124
|
+
godfather --api-url https://your-api.com list
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## 🔑 Authentication
|
|
128
|
+
|
|
129
|
+
1. Get your authentication token from: `https://your-godfather-instance.com/cli-auth`
|
|
130
|
+
2. Run `godfather` or `godfather auth`
|
|
131
|
+
3. Paste your token when prompted
|
|
132
|
+
4. Token is securely stored in `~/.godfather/config.json`
|
|
133
|
+
|
|
134
|
+
## 🛠️ Configuration
|
|
135
|
+
|
|
136
|
+
Configuration is stored in `~/.godfather/config.json`:
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"token": "discord_<user_id>_<timestamp>",
|
|
141
|
+
"discord_user_id": "<your_discord_id>"
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Environment Variables
|
|
146
|
+
|
|
147
|
+
You can override the API URL with:
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
export GODFATHER_API_URL="https://your-api.com"
|
|
151
|
+
# or
|
|
152
|
+
export BACKEND_URL="https://your-api.com"
|
|
153
|
+
# or
|
|
154
|
+
export NEXT_PUBLIC_BACKEND_URL="https://your-api.com"
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 🎨 Features in Detail
|
|
158
|
+
|
|
159
|
+
### Beautiful Tables
|
|
160
|
+
|
|
161
|
+
Pod listings display in rich, colorful tables:
|
|
162
|
+
|
|
163
|
+
```
|
|
164
|
+
🚀 Available Pods (3)
|
|
165
|
+
╭───┬────────┬─────────────┬──────────────┬───────────╮
|
|
166
|
+
│ # │ Status │ Name │ ID │ Created │
|
|
167
|
+
├───┼────────┼─────────────┼──────────────┼───────────┤
|
|
168
|
+
│ 1 │ 🟢 RUN │ ml-training │ abc123def... │ 2 days ago│
|
|
169
|
+
│ 2 │ 🟢 RUN │ gpu-dev │ xyz789ghi... │ 1 week ago│
|
|
170
|
+
│ 3 │ 🔴 OFF │ testing │ jkl456mno... │ 3 days ago│
|
|
171
|
+
╰───┴────────┴─────────────┴──────────────┴───────────╯
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Status Dashboard
|
|
175
|
+
|
|
176
|
+
```
|
|
177
|
+
Godfather CLI Status
|
|
178
|
+
╭─────────────────────┬─────────────────────────────╮
|
|
179
|
+
│ 🔐 Authentication │ ✓ Authenticated │
|
|
180
|
+
│ 🌐 API Connection │ ✓ Connected │
|
|
181
|
+
│ 🏠 Config Directory │ /home/user/.godfather │
|
|
182
|
+
│ 🔗 API Endpoint │ https://api.example.com │
|
|
183
|
+
╰─────────────────────┴─────────────────────────────╯
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
## 🤝 Contributing
|
|
187
|
+
|
|
188
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
189
|
+
|
|
190
|
+
1. Fork the repository
|
|
191
|
+
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
|
|
192
|
+
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
193
|
+
4. Push to the branch (`git push origin feature/amazing-feature`)
|
|
194
|
+
5. Open a Pull Request
|
|
195
|
+
|
|
196
|
+
## 📝 License
|
|
197
|
+
|
|
198
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
199
|
+
|
|
200
|
+
## 🆘 Support
|
|
201
|
+
|
|
202
|
+
- **Issues**: [GitHub Issues](https://github.com/theaisocietyasu/godfather/issues)
|
|
203
|
+
- **Discord**: Contact AI Society ASU administrators
|
|
204
|
+
- **Email**: admin@ais-asu.com
|
|
205
|
+
|
|
206
|
+
## 🎓 About
|
|
207
|
+
|
|
208
|
+
Built with ❤️ by [AI Society at Arizona State University](https://github.com/theaisocietyasu)
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
**Note**: You need to be a member of the AI Society Discord server with appropriate permissions to use this CLI.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
godfather_cli/__init__.py
|
|
5
|
+
godfather_cli/auth.py
|
|
6
|
+
godfather_cli/cli.py
|
|
7
|
+
godfather_cli/pod_manager.py
|
|
8
|
+
godfather_cli/ssh_connector.py
|
|
9
|
+
godfather_cli.egg-info/PKG-INFO
|
|
10
|
+
godfather_cli.egg-info/SOURCES.txt
|
|
11
|
+
godfather_cli.egg-info/dependency_links.txt
|
|
12
|
+
godfather_cli.egg-info/entry_points.txt
|
|
13
|
+
godfather_cli.egg-info/requires.txt
|
|
14
|
+
godfather_cli.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
godfather_cli
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=45", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "godfather-cli"
|
|
7
|
+
version = "1.0.1"
|
|
8
|
+
description = "AI Society Godfather CLI - RunPod Environment Manager"
|
|
9
|
+
authors = [
|
|
10
|
+
{name = "AI Society ASU", email = "theaisocietyasu@gmail.com"}
|
|
11
|
+
]
|
|
12
|
+
license = {text = "MIT"}
|
|
13
|
+
readme = "README.md"
|
|
14
|
+
requires-python = ">=3.7"
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.7",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
]
|
|
26
|
+
dependencies = [
|
|
27
|
+
"requests>=2.25.0",
|
|
28
|
+
"rich>=13.0.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
godfather = "godfather_cli.cli:main"
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/theaisocietyasu/godfather"
|
|
36
|
+
Repository = "https://github.com/theaisocietyasu/godfather"
|
|
37
|
+
Issues = "https://github.com/theaisocietyasu/godfather/issues"
|