treadstone-cli 0.2.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.
- treadstone_cli-0.2.1/.gitignore +34 -0
- treadstone_cli-0.2.1/PKG-INFO +222 -0
- treadstone_cli-0.2.1/README.md +212 -0
- treadstone_cli-0.2.1/pyproject.toml +25 -0
- treadstone_cli-0.2.1/treadstone_cli/__init__.py +0 -0
- treadstone_cli-0.2.1/treadstone_cli/__main__.py +6 -0
- treadstone_cli-0.2.1/treadstone_cli/_client.py +77 -0
- treadstone_cli-0.2.1/treadstone_cli/_output.py +102 -0
- treadstone_cli-0.2.1/treadstone_cli/api_keys.py +73 -0
- treadstone_cli-0.2.1/treadstone_cli/auth.py +153 -0
- treadstone_cli-0.2.1/treadstone_cli/config_cmd.py +157 -0
- treadstone_cli-0.2.1/treadstone_cli/main.py +104 -0
- treadstone_cli-0.2.1/treadstone_cli/sandboxes.py +170 -0
- treadstone_cli-0.2.1/treadstone_cli/templates.py +39 -0
- treadstone_cli-0.2.1/uv.lock +161 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
*.egg
|
|
8
|
+
|
|
9
|
+
.venv/
|
|
10
|
+
.env
|
|
11
|
+
.env.test
|
|
12
|
+
.env.local
|
|
13
|
+
.env.demo
|
|
14
|
+
.env.prod
|
|
15
|
+
|
|
16
|
+
.mypy_cache/
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.ruff_cache/
|
|
19
|
+
.coverage
|
|
20
|
+
htmlcov/
|
|
21
|
+
|
|
22
|
+
*.db
|
|
23
|
+
*.sqlite3
|
|
24
|
+
|
|
25
|
+
openapi.json
|
|
26
|
+
reports/
|
|
27
|
+
|
|
28
|
+
.DS_Store
|
|
29
|
+
Thumbs.db
|
|
30
|
+
|
|
31
|
+
.idea/
|
|
32
|
+
.vscode/
|
|
33
|
+
*.swp
|
|
34
|
+
*.swo
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: treadstone-cli
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: CLI for the Treadstone sandbox service.
|
|
5
|
+
Requires-Python: >=3.12
|
|
6
|
+
Requires-Dist: click>=8.1
|
|
7
|
+
Requires-Dist: httpx>=0.28.1
|
|
8
|
+
Requires-Dist: rich>=14.0
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# Treadstone CLI
|
|
12
|
+
|
|
13
|
+
Command-line interface for the Treadstone sandbox service.
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### From PyPI
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install treadstone
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Pre-built binary
|
|
24
|
+
|
|
25
|
+
Download the latest release from [GitHub Releases](https://github.com/earayu/treadstone/releases). The binary is self-contained and does not require Python.
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# macOS / Linux
|
|
29
|
+
chmod +x treadstone
|
|
30
|
+
sudo mv treadstone /usr/local/bin/
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Quick start
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# Check that the server is reachable
|
|
37
|
+
treadstone health
|
|
38
|
+
|
|
39
|
+
# Register an account (first time only)
|
|
40
|
+
treadstone auth register
|
|
41
|
+
|
|
42
|
+
# Log in
|
|
43
|
+
treadstone auth login
|
|
44
|
+
|
|
45
|
+
# Create an API key for non-interactive use
|
|
46
|
+
treadstone api-keys create --name my-key
|
|
47
|
+
# Save the key — it is shown only once
|
|
48
|
+
|
|
49
|
+
# Store the key in config so you don't have to pass it every time
|
|
50
|
+
treadstone config set api_key ts_live_xxxxxxxxxxxx
|
|
51
|
+
|
|
52
|
+
# Create a sandbox
|
|
53
|
+
treadstone sandboxes create --template default --name my-sandbox
|
|
54
|
+
|
|
55
|
+
# List sandboxes
|
|
56
|
+
treadstone sb list
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
The CLI reads configuration from three sources, in order of priority:
|
|
62
|
+
|
|
63
|
+
| Priority | Source | Example |
|
|
64
|
+
|----------|--------|---------|
|
|
65
|
+
| 1 (highest) | CLI flags | `--base-url https://... --api-key ts_...` |
|
|
66
|
+
| 2 | Environment variables | `TREADSTONE_BASE_URL`, `TREADSTONE_API_KEY` |
|
|
67
|
+
| 3 (lowest) | Config file | `~/.config/treadstone/config.toml` |
|
|
68
|
+
|
|
69
|
+
### Config file
|
|
70
|
+
|
|
71
|
+
Location: `~/.config/treadstone/config.toml`
|
|
72
|
+
|
|
73
|
+
```toml
|
|
74
|
+
[default]
|
|
75
|
+
base_url = "https://your-server.example.com"
|
|
76
|
+
api_key = "ts_live_xxxxxxxxxxxx"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
You can manage this file with the `config` subcommand:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Set a value
|
|
83
|
+
treadstone config set base_url https://your-server.example.com
|
|
84
|
+
treadstone config set api_key ts_live_xxxxxxxxxxxx
|
|
85
|
+
|
|
86
|
+
# View current settings (api_key is partially masked)
|
|
87
|
+
treadstone config get
|
|
88
|
+
|
|
89
|
+
# View a single key
|
|
90
|
+
treadstone config get base_url
|
|
91
|
+
|
|
92
|
+
# Remove a value
|
|
93
|
+
treadstone config unset api_key
|
|
94
|
+
|
|
95
|
+
# Show config file path
|
|
96
|
+
treadstone config path
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Environment variables
|
|
100
|
+
|
|
101
|
+
| Variable | Description | Default |
|
|
102
|
+
|----------|-------------|---------|
|
|
103
|
+
| `TREADSTONE_BASE_URL` | Base URL of the Treadstone server | `https://api.treadstone-ai.dev` |
|
|
104
|
+
| `TREADSTONE_API_KEY` | API key for authentication | (none) |
|
|
105
|
+
|
|
106
|
+
### Global flags
|
|
107
|
+
|
|
108
|
+
| Flag | Description |
|
|
109
|
+
|------|-------------|
|
|
110
|
+
| `--base-url URL` | Override the server URL |
|
|
111
|
+
| `--api-key KEY` | Override the API key |
|
|
112
|
+
| `--json` | Output responses as JSON (useful for scripting) |
|
|
113
|
+
| `--version` | Show CLI version |
|
|
114
|
+
| `--help` | Show help message |
|
|
115
|
+
|
|
116
|
+
## Commands
|
|
117
|
+
|
|
118
|
+
### `health`
|
|
119
|
+
|
|
120
|
+
Check if the server is reachable and healthy.
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
treadstone health
|
|
124
|
+
# Server is healthy
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### `auth`
|
|
128
|
+
|
|
129
|
+
Authentication and user management.
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
treadstone auth register # Create a new account
|
|
133
|
+
treadstone auth login # Log in interactively
|
|
134
|
+
treadstone auth logout # Log out
|
|
135
|
+
treadstone auth whoami # Show current user info
|
|
136
|
+
treadstone auth change-password # Change your password
|
|
137
|
+
treadstone auth invite --email user@example.com # Invite a user (admin)
|
|
138
|
+
treadstone auth users # List all users (admin)
|
|
139
|
+
treadstone auth delete-user <user-id> # Delete a user (admin)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `sandboxes` (alias: `sb`)
|
|
143
|
+
|
|
144
|
+
Create and manage sandboxes.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
treadstone sb create --template default --name my-box
|
|
148
|
+
treadstone sb create --template python --label env:dev --persist
|
|
149
|
+
treadstone sb list
|
|
150
|
+
treadstone sb list --label env:prod --limit 10
|
|
151
|
+
treadstone sb get <sandbox-id>
|
|
152
|
+
treadstone sb start <sandbox-id>
|
|
153
|
+
treadstone sb stop <sandbox-id>
|
|
154
|
+
treadstone sb delete <sandbox-id>
|
|
155
|
+
treadstone sb token <sandbox-id> # Get an access token
|
|
156
|
+
treadstone sb token <sandbox-id> --expires-in 7200 # Custom TTL
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### `templates`
|
|
160
|
+
|
|
161
|
+
List available sandbox templates.
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
treadstone templates list
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### `api-keys`
|
|
168
|
+
|
|
169
|
+
Manage long-lived API keys.
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
treadstone api-keys create --name ci-bot
|
|
173
|
+
treadstone api-keys create --name temp --expires-in 86400 # 24h
|
|
174
|
+
treadstone api-keys list
|
|
175
|
+
treadstone api-keys delete <key-id>
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### `config`
|
|
179
|
+
|
|
180
|
+
Manage local CLI configuration.
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
treadstone config set base_url https://...
|
|
184
|
+
treadstone config set api_key ts_...
|
|
185
|
+
treadstone config get
|
|
186
|
+
treadstone config get base_url
|
|
187
|
+
treadstone config unset api_key
|
|
188
|
+
treadstone config path
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## JSON output
|
|
192
|
+
|
|
193
|
+
Pass `--json` before any command to get machine-readable JSON output:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
treadstone --json sb list
|
|
197
|
+
treadstone --json health
|
|
198
|
+
treadstone --json api-keys list
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Error handling
|
|
202
|
+
|
|
203
|
+
The CLI displays user-friendly error messages instead of stack traces:
|
|
204
|
+
|
|
205
|
+
```
|
|
206
|
+
Error: Connection refused.
|
|
207
|
+
Possible causes:
|
|
208
|
+
- The Treadstone server is not running
|
|
209
|
+
- The --base-url or TREADSTONE_BASE_URL is incorrect
|
|
210
|
+
- A firewall or proxy is blocking the connection
|
|
211
|
+
Detail: ...
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Common errors:
|
|
215
|
+
|
|
216
|
+
| Error | Cause | Fix |
|
|
217
|
+
|-------|-------|-----|
|
|
218
|
+
| Connection refused | Server not running or wrong URL | Check `treadstone config get base_url` |
|
|
219
|
+
| Request timed out | Server slow or unreachable | Retry, or check network |
|
|
220
|
+
| No API key configured | Missing authentication | `treadstone config set api_key <key>` |
|
|
221
|
+
| HTTP 401 | Invalid or expired API key | Create a new key with `treadstone api-keys create` |
|
|
222
|
+
| HTTP 404 | Resource not found | Verify the ID is correct |
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Treadstone CLI
|
|
2
|
+
|
|
3
|
+
Command-line interface for the Treadstone sandbox service.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### From PyPI
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install treadstone
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Pre-built binary
|
|
14
|
+
|
|
15
|
+
Download the latest release from [GitHub Releases](https://github.com/earayu/treadstone/releases). The binary is self-contained and does not require Python.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# macOS / Linux
|
|
19
|
+
chmod +x treadstone
|
|
20
|
+
sudo mv treadstone /usr/local/bin/
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Check that the server is reachable
|
|
27
|
+
treadstone health
|
|
28
|
+
|
|
29
|
+
# Register an account (first time only)
|
|
30
|
+
treadstone auth register
|
|
31
|
+
|
|
32
|
+
# Log in
|
|
33
|
+
treadstone auth login
|
|
34
|
+
|
|
35
|
+
# Create an API key for non-interactive use
|
|
36
|
+
treadstone api-keys create --name my-key
|
|
37
|
+
# Save the key — it is shown only once
|
|
38
|
+
|
|
39
|
+
# Store the key in config so you don't have to pass it every time
|
|
40
|
+
treadstone config set api_key ts_live_xxxxxxxxxxxx
|
|
41
|
+
|
|
42
|
+
# Create a sandbox
|
|
43
|
+
treadstone sandboxes create --template default --name my-sandbox
|
|
44
|
+
|
|
45
|
+
# List sandboxes
|
|
46
|
+
treadstone sb list
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
The CLI reads configuration from three sources, in order of priority:
|
|
52
|
+
|
|
53
|
+
| Priority | Source | Example |
|
|
54
|
+
|----------|--------|---------|
|
|
55
|
+
| 1 (highest) | CLI flags | `--base-url https://... --api-key ts_...` |
|
|
56
|
+
| 2 | Environment variables | `TREADSTONE_BASE_URL`, `TREADSTONE_API_KEY` |
|
|
57
|
+
| 3 (lowest) | Config file | `~/.config/treadstone/config.toml` |
|
|
58
|
+
|
|
59
|
+
### Config file
|
|
60
|
+
|
|
61
|
+
Location: `~/.config/treadstone/config.toml`
|
|
62
|
+
|
|
63
|
+
```toml
|
|
64
|
+
[default]
|
|
65
|
+
base_url = "https://your-server.example.com"
|
|
66
|
+
api_key = "ts_live_xxxxxxxxxxxx"
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
You can manage this file with the `config` subcommand:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Set a value
|
|
73
|
+
treadstone config set base_url https://your-server.example.com
|
|
74
|
+
treadstone config set api_key ts_live_xxxxxxxxxxxx
|
|
75
|
+
|
|
76
|
+
# View current settings (api_key is partially masked)
|
|
77
|
+
treadstone config get
|
|
78
|
+
|
|
79
|
+
# View a single key
|
|
80
|
+
treadstone config get base_url
|
|
81
|
+
|
|
82
|
+
# Remove a value
|
|
83
|
+
treadstone config unset api_key
|
|
84
|
+
|
|
85
|
+
# Show config file path
|
|
86
|
+
treadstone config path
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Environment variables
|
|
90
|
+
|
|
91
|
+
| Variable | Description | Default |
|
|
92
|
+
|----------|-------------|---------|
|
|
93
|
+
| `TREADSTONE_BASE_URL` | Base URL of the Treadstone server | `https://api.treadstone-ai.dev` |
|
|
94
|
+
| `TREADSTONE_API_KEY` | API key for authentication | (none) |
|
|
95
|
+
|
|
96
|
+
### Global flags
|
|
97
|
+
|
|
98
|
+
| Flag | Description |
|
|
99
|
+
|------|-------------|
|
|
100
|
+
| `--base-url URL` | Override the server URL |
|
|
101
|
+
| `--api-key KEY` | Override the API key |
|
|
102
|
+
| `--json` | Output responses as JSON (useful for scripting) |
|
|
103
|
+
| `--version` | Show CLI version |
|
|
104
|
+
| `--help` | Show help message |
|
|
105
|
+
|
|
106
|
+
## Commands
|
|
107
|
+
|
|
108
|
+
### `health`
|
|
109
|
+
|
|
110
|
+
Check if the server is reachable and healthy.
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
treadstone health
|
|
114
|
+
# Server is healthy
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### `auth`
|
|
118
|
+
|
|
119
|
+
Authentication and user management.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
treadstone auth register # Create a new account
|
|
123
|
+
treadstone auth login # Log in interactively
|
|
124
|
+
treadstone auth logout # Log out
|
|
125
|
+
treadstone auth whoami # Show current user info
|
|
126
|
+
treadstone auth change-password # Change your password
|
|
127
|
+
treadstone auth invite --email user@example.com # Invite a user (admin)
|
|
128
|
+
treadstone auth users # List all users (admin)
|
|
129
|
+
treadstone auth delete-user <user-id> # Delete a user (admin)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### `sandboxes` (alias: `sb`)
|
|
133
|
+
|
|
134
|
+
Create and manage sandboxes.
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
treadstone sb create --template default --name my-box
|
|
138
|
+
treadstone sb create --template python --label env:dev --persist
|
|
139
|
+
treadstone sb list
|
|
140
|
+
treadstone sb list --label env:prod --limit 10
|
|
141
|
+
treadstone sb get <sandbox-id>
|
|
142
|
+
treadstone sb start <sandbox-id>
|
|
143
|
+
treadstone sb stop <sandbox-id>
|
|
144
|
+
treadstone sb delete <sandbox-id>
|
|
145
|
+
treadstone sb token <sandbox-id> # Get an access token
|
|
146
|
+
treadstone sb token <sandbox-id> --expires-in 7200 # Custom TTL
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### `templates`
|
|
150
|
+
|
|
151
|
+
List available sandbox templates.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
treadstone templates list
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### `api-keys`
|
|
158
|
+
|
|
159
|
+
Manage long-lived API keys.
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
treadstone api-keys create --name ci-bot
|
|
163
|
+
treadstone api-keys create --name temp --expires-in 86400 # 24h
|
|
164
|
+
treadstone api-keys list
|
|
165
|
+
treadstone api-keys delete <key-id>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### `config`
|
|
169
|
+
|
|
170
|
+
Manage local CLI configuration.
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
treadstone config set base_url https://...
|
|
174
|
+
treadstone config set api_key ts_...
|
|
175
|
+
treadstone config get
|
|
176
|
+
treadstone config get base_url
|
|
177
|
+
treadstone config unset api_key
|
|
178
|
+
treadstone config path
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## JSON output
|
|
182
|
+
|
|
183
|
+
Pass `--json` before any command to get machine-readable JSON output:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
treadstone --json sb list
|
|
187
|
+
treadstone --json health
|
|
188
|
+
treadstone --json api-keys list
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Error handling
|
|
192
|
+
|
|
193
|
+
The CLI displays user-friendly error messages instead of stack traces:
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
Error: Connection refused.
|
|
197
|
+
Possible causes:
|
|
198
|
+
- The Treadstone server is not running
|
|
199
|
+
- The --base-url or TREADSTONE_BASE_URL is incorrect
|
|
200
|
+
- A firewall or proxy is blocking the connection
|
|
201
|
+
Detail: ...
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Common errors:
|
|
205
|
+
|
|
206
|
+
| Error | Cause | Fix |
|
|
207
|
+
|-------|-------|-----|
|
|
208
|
+
| Connection refused | Server not running or wrong URL | Check `treadstone config get base_url` |
|
|
209
|
+
| Request timed out | Server slow or unreachable | Retry, or check network |
|
|
210
|
+
| No API key configured | Missing authentication | `treadstone config set api_key <key>` |
|
|
211
|
+
| HTTP 401 | Invalid or expired API key | Create a new key with `treadstone api-keys create` |
|
|
212
|
+
| HTTP 404 | Resource not found | Verify the ID is correct |
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "treadstone-cli"
|
|
7
|
+
version = "0.2.1"
|
|
8
|
+
description = "CLI for the Treadstone sandbox service."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.12"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"click>=8.1",
|
|
13
|
+
"httpx>=0.28.1",
|
|
14
|
+
"rich>=14.0",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.scripts]
|
|
18
|
+
treadstone = "treadstone_cli.main:cli"
|
|
19
|
+
|
|
20
|
+
[tool.ruff]
|
|
21
|
+
target-version = "py312"
|
|
22
|
+
line-length = 120
|
|
23
|
+
|
|
24
|
+
[tool.ruff.lint]
|
|
25
|
+
select = ["E", "F", "I", "UP"]
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""HTTP client factory for CLI commands.
|
|
2
|
+
|
|
3
|
+
Priority: CLI flags > environment variables > config file.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
CONFIG_DIR = Path.home() / ".config" / "treadstone"
|
|
16
|
+
CONFIG_FILE = CONFIG_DIR / "config.toml"
|
|
17
|
+
|
|
18
|
+
_DEFAULT_BASE_URL = "https://api.treadstone-ai.dev"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _read_config() -> dict[str, str]:
|
|
22
|
+
if not CONFIG_FILE.exists():
|
|
23
|
+
return {}
|
|
24
|
+
try:
|
|
25
|
+
import tomllib
|
|
26
|
+
except ModuleNotFoundError:
|
|
27
|
+
import tomli as tomllib # type: ignore[no-redef]
|
|
28
|
+
with open(CONFIG_FILE, "rb") as f:
|
|
29
|
+
data = tomllib.load(f)
|
|
30
|
+
return data.get("default", {})
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_base_url(ctx: click.Context) -> str:
|
|
34
|
+
url = ctx.obj.get("base_url") or _read_config().get("base_url") or _DEFAULT_BASE_URL
|
|
35
|
+
return url.rstrip("/")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_api_key(ctx: click.Context) -> str | None:
|
|
39
|
+
return ctx.obj.get("api_key") or _read_config().get("api_key")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def effective_base_url() -> tuple[str, str]:
|
|
43
|
+
"""Return (url, source) without requiring a Click context.
|
|
44
|
+
|
|
45
|
+
Source is one of: 'env', 'file', 'default'.
|
|
46
|
+
Used for displaying configuration in help text.
|
|
47
|
+
"""
|
|
48
|
+
env = os.environ.get("TREADSTONE_BASE_URL")
|
|
49
|
+
if env:
|
|
50
|
+
return env.rstrip("/"), "env"
|
|
51
|
+
cfg = _read_config().get("base_url")
|
|
52
|
+
if cfg:
|
|
53
|
+
return cfg.rstrip("/"), "file"
|
|
54
|
+
return _DEFAULT_BASE_URL, "default"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def effective_api_key() -> str | None:
|
|
58
|
+
"""Return API key without requiring a Click context."""
|
|
59
|
+
return os.environ.get("TREADSTONE_API_KEY") or _read_config().get("api_key")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def build_client(ctx: click.Context) -> httpx.Client:
|
|
63
|
+
base_url = get_base_url(ctx)
|
|
64
|
+
api_key = get_api_key(ctx)
|
|
65
|
+
headers: dict[str, str] = {}
|
|
66
|
+
if api_key:
|
|
67
|
+
headers["Authorization"] = f"Bearer {api_key}"
|
|
68
|
+
return httpx.Client(base_url=base_url, headers=headers, timeout=30.0)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def require_auth(ctx: click.Context) -> httpx.Client:
|
|
72
|
+
"""Build client and abort if no API key is configured."""
|
|
73
|
+
client = build_client(ctx)
|
|
74
|
+
if "Authorization" not in client.headers:
|
|
75
|
+
click.echo("Error: No API key configured. Set TREADSTONE_API_KEY or use --api-key.", err=True)
|
|
76
|
+
sys.exit(1)
|
|
77
|
+
return client
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Output formatting utilities for CLI — table (Rich) or JSON mode."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
import click
|
|
10
|
+
import httpx
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.table import Table
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def friendly_exception_handler(cli_main: click.Group) -> click.Group:
|
|
18
|
+
"""Wrap a Click group so unhandled exceptions print user-friendly messages instead of tracebacks."""
|
|
19
|
+
original_main = cli_main.main
|
|
20
|
+
|
|
21
|
+
def _patched_main(*args: Any, **kwargs: Any) -> Any:
|
|
22
|
+
try:
|
|
23
|
+
return original_main(*args, standalone_mode=False, **kwargs)
|
|
24
|
+
except click.exceptions.Abort:
|
|
25
|
+
click.echo("\nAborted.", err=True)
|
|
26
|
+
sys.exit(130)
|
|
27
|
+
except click.exceptions.Exit as exc:
|
|
28
|
+
sys.exit(exc.exit_code)
|
|
29
|
+
except click.UsageError as exc:
|
|
30
|
+
exc.show()
|
|
31
|
+
sys.exit(exc.exit_code if exc.exit_code is not None else 2)
|
|
32
|
+
except SystemExit:
|
|
33
|
+
raise
|
|
34
|
+
except KeyboardInterrupt:
|
|
35
|
+
click.echo("\nInterrupted.", err=True)
|
|
36
|
+
sys.exit(130)
|
|
37
|
+
except httpx.ConnectError as exc:
|
|
38
|
+
_print_network_hint(str(exc), "Connection refused")
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
except httpx.TimeoutException:
|
|
41
|
+
click.echo("Error: Request timed out. The server may be slow or unreachable.", err=True)
|
|
42
|
+
sys.exit(1)
|
|
43
|
+
except httpx.HTTPStatusError as exc:
|
|
44
|
+
click.echo(f"Error: HTTP {exc.response.status_code} — {exc.response.text[:200]}", err=True)
|
|
45
|
+
sys.exit(1)
|
|
46
|
+
except httpx.HTTPError as exc:
|
|
47
|
+
click.echo(f"Error: {type(exc).__name__} — {exc}", err=True)
|
|
48
|
+
sys.exit(1)
|
|
49
|
+
except Exception as exc:
|
|
50
|
+
click.echo(f"Error: {exc}", err=True)
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
cli_main.main = _patched_main # type: ignore[assignment]
|
|
54
|
+
return cli_main
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _print_network_hint(detail: str, summary: str) -> None:
|
|
58
|
+
click.echo(f"Error: {summary}.", err=True)
|
|
59
|
+
click.echo(" Possible causes:", err=True)
|
|
60
|
+
click.echo(" - The Treadstone server is not running", err=True)
|
|
61
|
+
click.echo(" - The --base-url or TREADSTONE_BASE_URL is incorrect", err=True)
|
|
62
|
+
click.echo(" - A firewall or proxy is blocking the connection", err=True)
|
|
63
|
+
click.echo(f" Detail: {detail}", err=True)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def is_json_mode(ctx: click.Context) -> bool:
|
|
67
|
+
return ctx.obj.get("json_output", False)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def print_json(data: Any) -> None:
|
|
71
|
+
click.echo(json.dumps(data, indent=2, default=str))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def print_table(columns: list[str], rows: list[list[Any]], title: str | None = None) -> None:
|
|
75
|
+
table = Table(title=title, show_header=True, header_style="bold cyan")
|
|
76
|
+
for col in columns:
|
|
77
|
+
table.add_column(col)
|
|
78
|
+
for row in rows:
|
|
79
|
+
table.add_row(*[str(v) if v is not None else "" for v in row])
|
|
80
|
+
console.print(table)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def print_detail(data: dict[str, Any], title: str | None = None) -> None:
|
|
84
|
+
table = Table(title=title, show_header=False)
|
|
85
|
+
table.add_column("Field", style="bold")
|
|
86
|
+
table.add_column("Value")
|
|
87
|
+
for k, v in data.items():
|
|
88
|
+
table.add_row(k, str(v) if v is not None else "")
|
|
89
|
+
console.print(table)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def handle_error(resp: Any) -> None:
|
|
93
|
+
"""Print error and exit if response is not 2xx."""
|
|
94
|
+
if resp.status_code >= 400:
|
|
95
|
+
try:
|
|
96
|
+
body = resp.json()
|
|
97
|
+
err = body.get("error", body)
|
|
98
|
+
msg = err.get("message", str(err)) if isinstance(err, dict) else str(err)
|
|
99
|
+
except Exception:
|
|
100
|
+
msg = resp.text
|
|
101
|
+
click.echo(f"Error ({resp.status_code}): {msg}", err=True)
|
|
102
|
+
sys.exit(1)
|