godfather-cli 2.1.0__tar.gz → 2.3.0__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-2.1.0/godfather_cli.egg-info → godfather_cli-2.3.0}/PKG-INFO +35 -14
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/README.md +34 -13
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli/__init__.py +1 -1
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli/auth.py +105 -12
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli/cli.py +148 -42
- godfather_cli-2.3.0/godfather_cli/login_server.py +72 -0
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli/picker.py +20 -13
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli/pod_manager.py +111 -14
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli/ssh_connector.py +96 -12
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli/update_checker.py +18 -20
- {godfather_cli-2.1.0 → godfather_cli-2.3.0/godfather_cli.egg-info}/PKG-INFO +35 -14
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli.egg-info/SOURCES.txt +5 -1
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/pyproject.toml +14 -2
- godfather_cli-2.3.0/tests/test_cli.py +262 -0
- godfather_cli-2.3.0/tests/test_login.py +151 -0
- godfather_cli-2.3.0/tests/test_sessions.py +109 -0
- godfather_cli-2.3.0/tests/test_vscode.py +98 -0
- godfather_cli-2.1.0/tests/test_cli.py +0 -102
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/LICENSE +0 -0
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/MANIFEST.in +0 -0
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli/ui.py +1 -1
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli.egg-info/dependency_links.txt +0 -0
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli.egg-info/entry_points.txt +0 -0
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli.egg-info/requires.txt +0 -0
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/godfather_cli.egg-info/top_level.txt +0 -0
- {godfather_cli-2.1.0 → godfather_cli-2.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: godfather-cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Free compute for ASU students: connect to AI Society at ASU GPU pods
|
|
5
5
|
Author-email: AI Society ASU <theaisocietyasu@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -62,10 +62,13 @@ pip install -e .
|
|
|
62
62
|
|
|
63
63
|
1. Run `godfather` with no arguments. On first run you won't be logged in
|
|
64
64
|
yet, so it'll walk you into the login flow.
|
|
65
|
-
2. It opens the
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
2. It opens the sign-in page in your browser and prints the link. Sign in
|
|
66
|
+
with Discord. The browser sends the login back to the CLI, which checks it
|
|
67
|
+
and stores the token in `~/.godfather/config.json`.
|
|
68
|
+
3. If the browser cannot reach the CLI (no browser on this computer, an
|
|
69
|
+
older server, or no sign-in within 5 minutes), the CLI asks you to paste
|
|
70
|
+
the token that the sign-in page shows. `godfather auth --paste` goes
|
|
71
|
+
straight to that, for a browser on another computer.
|
|
69
72
|
4. From the menu (or `godfather connect`), pick a pod. The CLI creates an SSH
|
|
70
73
|
key on your machine the first time, asks the server for a 12-hour
|
|
71
74
|
certificate for that pod, and opens the connection.
|
|
@@ -81,12 +84,14 @@ Windows 10+ ship it.
|
|
|
81
84
|
### Interactive menu
|
|
82
85
|
|
|
83
86
|
Running `godfather` with no arguments opens a menu. Use the arrow keys to
|
|
84
|
-
choose Connect to a pod, List pods, Status, Log out or Exit, and
|
|
85
|
-
Ctrl-C quits.
|
|
87
|
+
choose Connect to a pod, List pods, Sessions, Set up VS Code, Status, Log out or Exit, and
|
|
88
|
+
press Enter. Ctrl-C quits.
|
|
86
89
|
|
|
87
90
|
Connect to a pod shows a list of your pods with their status, access and
|
|
88
91
|
pod ID. A pod that is not running shows with the reason and cannot be
|
|
89
92
|
chosen. If only one pod is running, the CLI asks for a yes instead.
|
|
93
|
+
If none of your pods is running, the CLI shows the next session on your
|
|
94
|
+
pods. A pod starts 10 minutes before its session.
|
|
90
95
|
|
|
91
96
|
When input is not a terminal (for example in a script), the menu and the pod
|
|
92
97
|
list use numbered prompts.
|
|
@@ -97,8 +102,13 @@ list use numbered prompts.
|
|
|
97
102
|
godfather list # List pods you can connect to
|
|
98
103
|
godfather connect # Connect to a pod, picking from a list
|
|
99
104
|
godfather connect <pod-id> # Connect to a specific pod
|
|
105
|
+
godfather connect --admin # Ask for a root shell (officers, when pod admin is on)
|
|
106
|
+
godfather sessions # Upcoming sessions on your pods, in local time
|
|
107
|
+
godfather vscode # Set up VS Code Remote-SSH for a pod, picking from a list
|
|
108
|
+
godfather vscode <pod-id> # The same for one pod; run it again to renew the certificate
|
|
100
109
|
godfather status # Show server, org, login, token expiry and version
|
|
101
|
-
godfather auth # Log in
|
|
110
|
+
godfather auth # Log in with Discord in your browser
|
|
111
|
+
godfather auth --paste # Log in by pasting the token
|
|
102
112
|
godfather logout # Clear the stored session
|
|
103
113
|
godfather update # Update the CLI to the latest version
|
|
104
114
|
|
|
@@ -106,6 +116,16 @@ godfather update # Update the CLI to the latest version
|
|
|
106
116
|
godfather --api-url https://platform.example.com --org soda list
|
|
107
117
|
```
|
|
108
118
|
|
|
119
|
+
### VS Code
|
|
120
|
+
|
|
121
|
+
`godfather vscode` sets up a pod for the VS Code Remote - SSH extension:
|
|
122
|
+
|
|
123
|
+
1. It gets a 12-hour certificate for the pod and saves it as `~/.godfather/ssh/<pod-id>-cert.pub`.
|
|
124
|
+
2. It writes a `Host godfather-<pod-id>` block to `~/.godfather/ssh/config` and adds one `Include` line for that file at the top of `~/.ssh/config`.
|
|
125
|
+
3. If the `code` command is on your PATH, it opens VS Code on `/workspace/users/<you>` on the pod. Otherwise run **Remote-SSH: Connect to Host...** in VS Code and pick `godfather-<pod-id>`.
|
|
126
|
+
|
|
127
|
+
VS Code runs as your pod account (`godfather_<you>`), with the same files and limits as `godfather connect`. After 12 hours VS Code cannot reconnect; run `godfather vscode <pod-id>` again. The pod must run image 2.3.0 or later. `godfather vscode --admin` gives officers a root login on a pod with pod admin on.
|
|
128
|
+
|
|
109
129
|
## Files it keeps
|
|
110
130
|
|
|
111
131
|
Everything lives in `~/.godfather/`:
|
|
@@ -113,6 +133,7 @@ Everything lives in `~/.godfather/`:
|
|
|
113
133
|
- `config.json`: your token (mode 600). Treat it like a password.
|
|
114
134
|
- `ssh/id_ed25519`, `ssh/id_ed25519.pub`: your SSH key, created on first connect. The private key never leaves your machine.
|
|
115
135
|
- `ssh/id_ed25519-cert.pub`: the certificate for the last pod you connected to.
|
|
136
|
+
- `ssh/<pod-id>-cert.pub`, `ssh/config`: the certificate and Host block of each pod set up for VS Code.
|
|
116
137
|
|
|
117
138
|
Delete the folder to reset everything.
|
|
118
139
|
|
|
@@ -141,23 +162,23 @@ another server or org; the ones you last logged in with are remembered.
|
|
|
141
162
|
## Development
|
|
142
163
|
|
|
143
164
|
The code is in `godfather_cli/`: `cli.py` (commands and menu), `picker.py` (arrow-key menu and pod picker), `auth.py`
|
|
144
|
-
(platform sign-in), `pod_manager.py` (API calls), `ssh_connector.py` (keys,
|
|
165
|
+
(platform sign-in), `login_server.py` (the sign-in callback), `pod_manager.py` (API calls), `ssh_connector.py` (keys,
|
|
145
166
|
certificate, running ssh), `update_checker.py` (PyPI version check), `ui.py`
|
|
146
167
|
(shared console styling). Tests are in `tests/`:
|
|
147
168
|
|
|
148
169
|
```bash
|
|
149
|
-
|
|
150
|
-
pytest -q tests
|
|
170
|
+
uv sync --group dev
|
|
171
|
+
uv run pytest -q tests
|
|
151
172
|
```
|
|
152
173
|
|
|
174
|
+
`make check-cli` at the repo root runs ruff, the tests and a build, as CI does.
|
|
175
|
+
|
|
153
176
|
Releases go to PyPI when a version bump is merged to main; see the
|
|
154
177
|
repo README.
|
|
155
178
|
|
|
156
179
|
## Contributing
|
|
157
180
|
|
|
158
|
-
|
|
159
|
-
2. Create a feature branch (`git checkout -b feature/your-feature`).
|
|
160
|
-
3. Commit your changes and open a Pull Request.
|
|
181
|
+
See [CONTRIBUTING.md](../CONTRIBUTING.md).
|
|
161
182
|
|
|
162
183
|
## License
|
|
163
184
|
|
|
@@ -33,10 +33,13 @@ pip install -e .
|
|
|
33
33
|
|
|
34
34
|
1. Run `godfather` with no arguments. On first run you won't be logged in
|
|
35
35
|
yet, so it'll walk you into the login flow.
|
|
36
|
-
2. It opens the
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
2. It opens the sign-in page in your browser and prints the link. Sign in
|
|
37
|
+
with Discord. The browser sends the login back to the CLI, which checks it
|
|
38
|
+
and stores the token in `~/.godfather/config.json`.
|
|
39
|
+
3. If the browser cannot reach the CLI (no browser on this computer, an
|
|
40
|
+
older server, or no sign-in within 5 minutes), the CLI asks you to paste
|
|
41
|
+
the token that the sign-in page shows. `godfather auth --paste` goes
|
|
42
|
+
straight to that, for a browser on another computer.
|
|
40
43
|
4. From the menu (or `godfather connect`), pick a pod. The CLI creates an SSH
|
|
41
44
|
key on your machine the first time, asks the server for a 12-hour
|
|
42
45
|
certificate for that pod, and opens the connection.
|
|
@@ -52,12 +55,14 @@ Windows 10+ ship it.
|
|
|
52
55
|
### Interactive menu
|
|
53
56
|
|
|
54
57
|
Running `godfather` with no arguments opens a menu. Use the arrow keys to
|
|
55
|
-
choose Connect to a pod, List pods, Status, Log out or Exit, and
|
|
56
|
-
Ctrl-C quits.
|
|
58
|
+
choose Connect to a pod, List pods, Sessions, Set up VS Code, Status, Log out or Exit, and
|
|
59
|
+
press Enter. Ctrl-C quits.
|
|
57
60
|
|
|
58
61
|
Connect to a pod shows a list of your pods with their status, access and
|
|
59
62
|
pod ID. A pod that is not running shows with the reason and cannot be
|
|
60
63
|
chosen. If only one pod is running, the CLI asks for a yes instead.
|
|
64
|
+
If none of your pods is running, the CLI shows the next session on your
|
|
65
|
+
pods. A pod starts 10 minutes before its session.
|
|
61
66
|
|
|
62
67
|
When input is not a terminal (for example in a script), the menu and the pod
|
|
63
68
|
list use numbered prompts.
|
|
@@ -68,8 +73,13 @@ list use numbered prompts.
|
|
|
68
73
|
godfather list # List pods you can connect to
|
|
69
74
|
godfather connect # Connect to a pod, picking from a list
|
|
70
75
|
godfather connect <pod-id> # Connect to a specific pod
|
|
76
|
+
godfather connect --admin # Ask for a root shell (officers, when pod admin is on)
|
|
77
|
+
godfather sessions # Upcoming sessions on your pods, in local time
|
|
78
|
+
godfather vscode # Set up VS Code Remote-SSH for a pod, picking from a list
|
|
79
|
+
godfather vscode <pod-id> # The same for one pod; run it again to renew the certificate
|
|
71
80
|
godfather status # Show server, org, login, token expiry and version
|
|
72
|
-
godfather auth # Log in
|
|
81
|
+
godfather auth # Log in with Discord in your browser
|
|
82
|
+
godfather auth --paste # Log in by pasting the token
|
|
73
83
|
godfather logout # Clear the stored session
|
|
74
84
|
godfather update # Update the CLI to the latest version
|
|
75
85
|
|
|
@@ -77,6 +87,16 @@ godfather update # Update the CLI to the latest version
|
|
|
77
87
|
godfather --api-url https://platform.example.com --org soda list
|
|
78
88
|
```
|
|
79
89
|
|
|
90
|
+
### VS Code
|
|
91
|
+
|
|
92
|
+
`godfather vscode` sets up a pod for the VS Code Remote - SSH extension:
|
|
93
|
+
|
|
94
|
+
1. It gets a 12-hour certificate for the pod and saves it as `~/.godfather/ssh/<pod-id>-cert.pub`.
|
|
95
|
+
2. It writes a `Host godfather-<pod-id>` block to `~/.godfather/ssh/config` and adds one `Include` line for that file at the top of `~/.ssh/config`.
|
|
96
|
+
3. If the `code` command is on your PATH, it opens VS Code on `/workspace/users/<you>` on the pod. Otherwise run **Remote-SSH: Connect to Host...** in VS Code and pick `godfather-<pod-id>`.
|
|
97
|
+
|
|
98
|
+
VS Code runs as your pod account (`godfather_<you>`), with the same files and limits as `godfather connect`. After 12 hours VS Code cannot reconnect; run `godfather vscode <pod-id>` again. The pod must run image 2.3.0 or later. `godfather vscode --admin` gives officers a root login on a pod with pod admin on.
|
|
99
|
+
|
|
80
100
|
## Files it keeps
|
|
81
101
|
|
|
82
102
|
Everything lives in `~/.godfather/`:
|
|
@@ -84,6 +104,7 @@ Everything lives in `~/.godfather/`:
|
|
|
84
104
|
- `config.json`: your token (mode 600). Treat it like a password.
|
|
85
105
|
- `ssh/id_ed25519`, `ssh/id_ed25519.pub`: your SSH key, created on first connect. The private key never leaves your machine.
|
|
86
106
|
- `ssh/id_ed25519-cert.pub`: the certificate for the last pod you connected to.
|
|
107
|
+
- `ssh/<pod-id>-cert.pub`, `ssh/config`: the certificate and Host block of each pod set up for VS Code.
|
|
87
108
|
|
|
88
109
|
Delete the folder to reset everything.
|
|
89
110
|
|
|
@@ -112,23 +133,23 @@ another server or org; the ones you last logged in with are remembered.
|
|
|
112
133
|
## Development
|
|
113
134
|
|
|
114
135
|
The code is in `godfather_cli/`: `cli.py` (commands and menu), `picker.py` (arrow-key menu and pod picker), `auth.py`
|
|
115
|
-
(platform sign-in), `pod_manager.py` (API calls), `ssh_connector.py` (keys,
|
|
136
|
+
(platform sign-in), `login_server.py` (the sign-in callback), `pod_manager.py` (API calls), `ssh_connector.py` (keys,
|
|
116
137
|
certificate, running ssh), `update_checker.py` (PyPI version check), `ui.py`
|
|
117
138
|
(shared console styling). Tests are in `tests/`:
|
|
118
139
|
|
|
119
140
|
```bash
|
|
120
|
-
|
|
121
|
-
pytest -q tests
|
|
141
|
+
uv sync --group dev
|
|
142
|
+
uv run pytest -q tests
|
|
122
143
|
```
|
|
123
144
|
|
|
145
|
+
`make check-cli` at the repo root runs ruff, the tests and a build, as CI does.
|
|
146
|
+
|
|
124
147
|
Releases go to PyPI when a version bump is merged to main; see the
|
|
125
148
|
repo README.
|
|
126
149
|
|
|
127
150
|
## Contributing
|
|
128
151
|
|
|
129
|
-
|
|
130
|
-
2. Create a feature branch (`git checkout -b feature/your-feature`).
|
|
131
|
-
3. Commit your changes and open a Pull Request.
|
|
152
|
+
See [CONTRIBUTING.md](../CONTRIBUTING.md).
|
|
132
153
|
|
|
133
154
|
## License
|
|
134
155
|
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
"""CLI authentication: token storage, login flow, and token verification."""
|
|
2
2
|
|
|
3
|
+
import base64
|
|
3
4
|
import datetime
|
|
5
|
+
import hashlib
|
|
4
6
|
import json
|
|
5
7
|
import os
|
|
6
|
-
import
|
|
8
|
+
import secrets
|
|
7
9
|
import webbrowser
|
|
8
10
|
from pathlib import Path
|
|
9
11
|
from typing import Dict, Optional
|
|
12
|
+
from urllib.parse import urlencode
|
|
13
|
+
|
|
14
|
+
import requests
|
|
10
15
|
from rich.panel import Panel
|
|
11
16
|
from rich.prompt import Prompt
|
|
12
17
|
|
|
13
|
-
from .
|
|
18
|
+
from .login_server import LoopbackReceiver
|
|
19
|
+
from .ui import BORDER, BOX, console, error, spinner, success, warning
|
|
14
20
|
|
|
15
21
|
TOKEN_PREFIX = 'plat_'
|
|
16
22
|
# The platform issues CLI tokens for 90 days.
|
|
17
23
|
TOKEN_DAYS = 90
|
|
18
24
|
|
|
25
|
+
# How long the CLI waits for the browser sign-in.
|
|
26
|
+
LOGIN_WAIT_SECONDS = 300
|
|
27
|
+
|
|
19
28
|
# Token states that check_token returns.
|
|
20
29
|
TOKEN_OK = 'ok'
|
|
21
30
|
TOKEN_MISSING = 'missing'
|
|
@@ -24,6 +33,17 @@ TOKEN_UNREACHABLE = 'unreachable'
|
|
|
24
33
|
TOKEN_REFUSED = 'refused'
|
|
25
34
|
|
|
26
35
|
|
|
36
|
+
def make_verifier() -> str:
|
|
37
|
+
"""A PKCE code verifier (RFC 7636): 64 URL-safe characters."""
|
|
38
|
+
return secrets.token_urlsafe(48)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def challenge_for(verifier: str) -> str:
|
|
42
|
+
"""The S256 code challenge of a verifier: base64url SHA-256 without padding."""
|
|
43
|
+
digest = hashlib.sha256(verifier.encode('ascii')).digest()
|
|
44
|
+
return base64.urlsafe_b64encode(digest).rstrip(b'=').decode('ascii')
|
|
45
|
+
|
|
46
|
+
|
|
27
47
|
class CLIAuthenticator:
|
|
28
48
|
"""Handle CLI authentication."""
|
|
29
49
|
|
|
@@ -61,8 +81,84 @@ class CLIAuthenticator:
|
|
|
61
81
|
"""URL of a platform compute route for the configured org."""
|
|
62
82
|
return f"{self.api_base}/api/compute/{self.org}{path}"
|
|
63
83
|
|
|
64
|
-
def authenticate(self) -> bool:
|
|
65
|
-
"""
|
|
84
|
+
def authenticate(self, paste: bool = False) -> bool:
|
|
85
|
+
"""Sign in with Discord. The browser hands the token back; the paste flow is the fallback."""
|
|
86
|
+
if not paste:
|
|
87
|
+
if self.supports_loopback():
|
|
88
|
+
token = self._browser_login()
|
|
89
|
+
if token:
|
|
90
|
+
return self._verify_and_save(token)
|
|
91
|
+
else:
|
|
92
|
+
console.print("[dim]This server does not support browser sign-in yet. Paste the token instead.[/dim]")
|
|
93
|
+
return self._paste_login()
|
|
94
|
+
|
|
95
|
+
def supports_loopback(self) -> bool:
|
|
96
|
+
"""True when the server has the cli/token route. It answers 400 to an empty body."""
|
|
97
|
+
try:
|
|
98
|
+
response = requests.post(self.compute_url('/cli/token'), json={}, timeout=10)
|
|
99
|
+
except requests.RequestException:
|
|
100
|
+
return False
|
|
101
|
+
return response.status_code == 400
|
|
102
|
+
|
|
103
|
+
def _browser_login(self) -> Optional[str]:
|
|
104
|
+
"""The token from the localhost callback with PKCE, or None after printing why not."""
|
|
105
|
+
state = secrets.token_urlsafe(16)
|
|
106
|
+
verifier = make_verifier()
|
|
107
|
+
try:
|
|
108
|
+
receiver = LoopbackReceiver(state)
|
|
109
|
+
except OSError:
|
|
110
|
+
console.print("[dim]Could not listen on 127.0.0.1. Paste the token instead.[/dim]")
|
|
111
|
+
return None
|
|
112
|
+
try:
|
|
113
|
+
query = urlencode(
|
|
114
|
+
{
|
|
115
|
+
'port': receiver.port,
|
|
116
|
+
'state': state,
|
|
117
|
+
'code_challenge': challenge_for(verifier),
|
|
118
|
+
'code_challenge_method': 'S256',
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
login_url = f"{self.compute_url('/cli/login')}?{query}"
|
|
122
|
+
try:
|
|
123
|
+
opened = webbrowser.open(login_url)
|
|
124
|
+
except webbrowser.Error:
|
|
125
|
+
opened = False
|
|
126
|
+
if not opened:
|
|
127
|
+
console.print("[dim]No browser opened on this computer. Paste the token instead.[/dim]")
|
|
128
|
+
return None
|
|
129
|
+
console.print(f"Sign in with Discord in your browser. If it did not open, go to:\n{login_url}")
|
|
130
|
+
try:
|
|
131
|
+
with spinner("Waiting for the browser sign-in..."):
|
|
132
|
+
code = receiver.wait(LOGIN_WAIT_SECONDS)
|
|
133
|
+
except KeyboardInterrupt:
|
|
134
|
+
console.print("[dim]Cancelled[/dim]")
|
|
135
|
+
return None
|
|
136
|
+
finally:
|
|
137
|
+
receiver.close()
|
|
138
|
+
if not code:
|
|
139
|
+
console.print("[dim]The browser sign-in did not finish in time. Paste the token instead.[/dim]")
|
|
140
|
+
return None
|
|
141
|
+
return self._redeem(code, verifier)
|
|
142
|
+
|
|
143
|
+
def _redeem(self, code: str, verifier: str) -> Optional[str]:
|
|
144
|
+
"""Exchange the one-time code and the PKCE verifier for a token."""
|
|
145
|
+
try:
|
|
146
|
+
with spinner("Getting your token..."):
|
|
147
|
+
response = requests.post(
|
|
148
|
+
self.compute_url('/cli/token'),
|
|
149
|
+
json={'code': code, 'code_verifier': verifier},
|
|
150
|
+
timeout=10,
|
|
151
|
+
)
|
|
152
|
+
token = str(response.json().get('token') or '') if response.status_code == 200 else ''
|
|
153
|
+
except (requests.RequestException, ValueError, AttributeError):
|
|
154
|
+
token = ''
|
|
155
|
+
if not token.startswith(TOKEN_PREFIX):
|
|
156
|
+
console.print("[dim]The server did not return a token. Paste the token instead.[/dim]")
|
|
157
|
+
return None
|
|
158
|
+
return token
|
|
159
|
+
|
|
160
|
+
def _paste_login(self) -> bool:
|
|
161
|
+
"""Open the sign-in page and take the token the member pastes."""
|
|
66
162
|
login_url = self.compute_url('/cli/login')
|
|
67
163
|
panel = Panel(
|
|
68
164
|
f"[bold]1.[/bold] Open [bold]{login_url}[/bold] and sign in with Discord\n"
|
|
@@ -91,13 +187,14 @@ class CLIAuthenticator:
|
|
|
91
187
|
error("That doesn't look like a valid Godfather token")
|
|
92
188
|
console.print(f"[dim]Tokens start with {TOKEN_PREFIX}. Get a fresh one from {login_url}[/dim]")
|
|
93
189
|
return False
|
|
190
|
+
return self._verify_and_save(token)
|
|
94
191
|
|
|
192
|
+
def _verify_and_save(self, token: str) -> bool:
|
|
193
|
+
"""Check the token with GET /me/pods, then save it with the server and org."""
|
|
95
194
|
try:
|
|
96
195
|
with spinner("Verifying token..."):
|
|
97
196
|
response = requests.get(
|
|
98
|
-
self.compute_url('/me/pods'),
|
|
99
|
-
headers={'Authorization': f'Bearer {token}'},
|
|
100
|
-
timeout=10
|
|
197
|
+
self.compute_url('/me/pods'), headers={'Authorization': f'Bearer {token}'}, timeout=10
|
|
101
198
|
)
|
|
102
199
|
except requests.ConnectionError:
|
|
103
200
|
error(f"Couldn't reach {self.api_base}")
|
|
@@ -153,11 +250,7 @@ class CLIAuthenticator:
|
|
|
153
250
|
return TOKEN_MISSING
|
|
154
251
|
|
|
155
252
|
try:
|
|
156
|
-
response = requests.get(
|
|
157
|
-
self.compute_url('/me/pods'),
|
|
158
|
-
headers=self.auth_headers(),
|
|
159
|
-
timeout=5
|
|
160
|
-
)
|
|
253
|
+
response = requests.get(self.compute_url('/me/pods'), headers=self.auth_headers(), timeout=5)
|
|
161
254
|
except requests.RequestException:
|
|
162
255
|
return TOKEN_UNREACHABLE
|
|
163
256
|
if response.status_code == 200:
|