albus-cli 0.5.0__tar.gz → 0.7.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.
- {albus_cli-0.5.0 → albus_cli-0.7.0}/PKG-INFO +45 -6
- {albus_cli-0.5.0 → albus_cli-0.7.0}/README.md +43 -4
- {albus_cli-0.5.0 → albus_cli-0.7.0}/pyproject.toml +2 -2
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/client.py +35 -10
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/commands/auth.py +18 -3
- albus_cli-0.7.0/src/albus_cli/commands/billing.py +67 -0
- albus_cli-0.7.0/src/albus_cli/commands/invites.py +54 -0
- albus_cli-0.7.0/src/albus_cli/commands/memories.py +56 -0
- albus_cli-0.7.0/src/albus_cli/commands/organization.py +64 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/commands/sessions.py +8 -2
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/commands/status.py +2 -2
- albus_cli-0.7.0/src/albus_cli/commands/traces.py +131 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/context.py +9 -2
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/main.py +35 -12
- albus_cli-0.7.0/tests/conftest.py +366 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tests/test_auth.py +5 -8
- albus_cli-0.7.0/tests/test_billing.py +84 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tests/test_cli.py +49 -0
- albus_cli-0.7.0/tests/test_invites.py +98 -0
- albus_cli-0.7.0/tests/test_memories.py +74 -0
- albus_cli-0.7.0/tests/test_organization.py +59 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tests/test_status.py +3 -1
- albus_cli-0.7.0/tests/test_traces.py +128 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/uv.lock +5 -5
- albus_cli-0.5.0/src/albus_cli/commands/invites.py +0 -33
- albus_cli-0.5.0/tests/conftest.py +0 -194
- albus_cli-0.5.0/tests/test_invites.py +0 -62
- {albus_cli-0.5.0 → albus_cli-0.7.0}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/.github/pull_request_template.md +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/.github/workflows/close-pull-requests.yml +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/.gitignore +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/AGENTS.md +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/CONTRIBUTING.md +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/LICENSE +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/Makefile +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/RELEASING.md +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/SECURITY.md +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/install.ps1 +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/install.sh +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/pyrightconfig.json +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/setup.sh +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/__init__.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/__main__.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/commands/__init__.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/commands/agents.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/commands/models.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/commands/secrets.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/commands/tokens.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/credentials.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/docs.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/oauth.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/src/albus_cli/output.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tests/__init__.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tests/test_bump_sdk.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tests/test_credentials.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tests/test_health.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tests/test_models.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tests/test_oauth.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tests/test_release_scripts.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tests/test_tokens.py +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tools/bump-sdk +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tools/check +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tools/mirror-release +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tools/publish +0 -0
- {albus_cli-0.5.0 → albus_cli-0.7.0}/tools/release_state.sh +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: albus-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Command-line client for the Albus REST API
|
|
5
5
|
Project-URL: Homepage, https://github.com/albusgroup/albus-cli
|
|
6
6
|
Project-URL: Repository, https://github.com/albusgroup/albus-cli
|
|
@@ -9,7 +9,7 @@ Author: Albus
|
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Python: >=3.11
|
|
12
|
-
Requires-Dist: albus-sdk==0.
|
|
12
|
+
Requires-Dist: albus-sdk==0.14.0
|
|
13
13
|
Requires-Dist: typer<0.28,>=0.26.8
|
|
14
14
|
Description-Content-Type: text/markdown
|
|
15
15
|
|
|
@@ -78,7 +78,11 @@ export ALBUS_BASE_URL=http://localhost:8080/api # optional; defaults to prod
|
|
|
78
78
|
```
|
|
79
79
|
|
|
80
80
|
`--base-url` overrides `ALBUS_BASE_URL`, and `--timeout` bounds each request
|
|
81
|
-
(a waiting `sessions run` long-polls and is exempt).
|
|
81
|
+
(a waiting `sessions run` long-polls and is exempt). A user who belongs to
|
|
82
|
+
several organizations acts in the one joined first; `--org <organization id>`
|
|
83
|
+
(or `ALBUS_ORG`) selects another, and `albus whoami` lists them under
|
|
84
|
+
`organizations` with the selected one as `active_organization`. An API key is
|
|
85
|
+
bound to its organization and ignores `--org`.
|
|
82
86
|
|
|
83
87
|
`albus login` opens a browser on this machine and listens on
|
|
84
88
|
`127.0.0.1:8484-8487`; on a headless host, forward those ports or export
|
|
@@ -115,8 +119,12 @@ albus sessions run my-session -p "and the follow-up?" \
|
|
|
115
119
|
albus sessions list
|
|
116
120
|
albus sessions get my-session --limit 20
|
|
117
121
|
albus sessions audit my-session --after "$cursor"
|
|
122
|
+
albus sessions cancel my-session
|
|
118
123
|
albus sessions delete my-session
|
|
119
124
|
|
|
125
|
+
albus traces list --agent-name support-triage --status FAILED
|
|
126
|
+
albus traces get "$invocation_key" --no-payloads --attempts all
|
|
127
|
+
|
|
120
128
|
albus secrets list
|
|
121
129
|
albus secrets create gemini-key --value ... # or pipe the value on stdin
|
|
122
130
|
albus secrets get gemini-key
|
|
@@ -132,11 +140,32 @@ albus tokens create ci
|
|
|
132
140
|
albus tokens get "$id"
|
|
133
141
|
albus tokens delete "$id"
|
|
134
142
|
|
|
143
|
+
albus invites list
|
|
135
144
|
albus invites create teammate@example.com
|
|
145
|
+
albus invites revoke "$id"
|
|
146
|
+
|
|
147
|
+
albus organization get
|
|
148
|
+
albus organization rename "Albus Labs"
|
|
149
|
+
albus organization members
|
|
150
|
+
albus organization set-role "$user_id" admin
|
|
151
|
+
albus organization remove-member "$user_id"
|
|
152
|
+
|
|
153
|
+
albus billing balance
|
|
154
|
+
albus billing ledger --after "$cursor"
|
|
155
|
+
albus billing checkout 20 --success-url https://... --cancel-url https://...
|
|
136
156
|
|
|
137
157
|
albus models list
|
|
158
|
+
|
|
159
|
+
albus memories groups
|
|
160
|
+
albus memories list --group team-a
|
|
161
|
+
albus memories delete "$id" --group team-a
|
|
162
|
+
albus memories delete-group --group team-a
|
|
138
163
|
```
|
|
139
164
|
|
|
165
|
+
`memories groups` lists the organization's memory groups with their active
|
|
166
|
+
counts; `memories list --group` reads one group's memories, newest first. Both
|
|
167
|
+
page with `--after` and `--limit`.
|
|
168
|
+
|
|
140
169
|
`models list` gives the model names `sessions run --model` takes, each with
|
|
141
170
|
the provider it runs on — which is the provider the `--credential` secret has
|
|
142
171
|
to be for.
|
|
@@ -144,9 +173,19 @@ to be for.
|
|
|
144
173
|
`tokens create` prints the only copy of the key value the API ever returns;
|
|
145
174
|
`list` and `get` return metadata alone.
|
|
146
175
|
|
|
147
|
-
`invites create` invites a person by email
|
|
148
|
-
|
|
149
|
-
`
|
|
176
|
+
`invites create` invites a person by email to join your active organization,
|
|
177
|
+
as a `member` unless `--role admin` says otherwise. `invites list` and
|
|
178
|
+
`invites revoke`, like the `organization` member commands, need the admin
|
|
179
|
+
role.
|
|
180
|
+
|
|
181
|
+
`billing checkout` starts a credit purchase and prints the payment page URL
|
|
182
|
+
to send the buyer to; like `tokens` and `invites`, it needs `albus login`
|
|
183
|
+
rather than an API key. `billing balance` and `billing ledger` take either.
|
|
184
|
+
|
|
185
|
+
`traces list` searches the last 31 days of invocations unless `--since` and
|
|
186
|
+
`--until` say otherwise; `traces get` reads one with a page of its spans, and
|
|
187
|
+
`--no-payloads` returns the shape without what each span was given and
|
|
188
|
+
produced.
|
|
150
189
|
|
|
151
190
|
`sessions run` waits for the assistant response by default; pass `--no-wait`
|
|
152
191
|
to return as soon as the invocation is accepted, or `--wait-timeout` to bound
|
|
@@ -63,7 +63,11 @@ export ALBUS_BASE_URL=http://localhost:8080/api # optional; defaults to prod
|
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
`--base-url` overrides `ALBUS_BASE_URL`, and `--timeout` bounds each request
|
|
66
|
-
(a waiting `sessions run` long-polls and is exempt).
|
|
66
|
+
(a waiting `sessions run` long-polls and is exempt). A user who belongs to
|
|
67
|
+
several organizations acts in the one joined first; `--org <organization id>`
|
|
68
|
+
(or `ALBUS_ORG`) selects another, and `albus whoami` lists them under
|
|
69
|
+
`organizations` with the selected one as `active_organization`. An API key is
|
|
70
|
+
bound to its organization and ignores `--org`.
|
|
67
71
|
|
|
68
72
|
`albus login` opens a browser on this machine and listens on
|
|
69
73
|
`127.0.0.1:8484-8487`; on a headless host, forward those ports or export
|
|
@@ -100,8 +104,12 @@ albus sessions run my-session -p "and the follow-up?" \
|
|
|
100
104
|
albus sessions list
|
|
101
105
|
albus sessions get my-session --limit 20
|
|
102
106
|
albus sessions audit my-session --after "$cursor"
|
|
107
|
+
albus sessions cancel my-session
|
|
103
108
|
albus sessions delete my-session
|
|
104
109
|
|
|
110
|
+
albus traces list --agent-name support-triage --status FAILED
|
|
111
|
+
albus traces get "$invocation_key" --no-payloads --attempts all
|
|
112
|
+
|
|
105
113
|
albus secrets list
|
|
106
114
|
albus secrets create gemini-key --value ... # or pipe the value on stdin
|
|
107
115
|
albus secrets get gemini-key
|
|
@@ -117,11 +125,32 @@ albus tokens create ci
|
|
|
117
125
|
albus tokens get "$id"
|
|
118
126
|
albus tokens delete "$id"
|
|
119
127
|
|
|
128
|
+
albus invites list
|
|
120
129
|
albus invites create teammate@example.com
|
|
130
|
+
albus invites revoke "$id"
|
|
131
|
+
|
|
132
|
+
albus organization get
|
|
133
|
+
albus organization rename "Albus Labs"
|
|
134
|
+
albus organization members
|
|
135
|
+
albus organization set-role "$user_id" admin
|
|
136
|
+
albus organization remove-member "$user_id"
|
|
137
|
+
|
|
138
|
+
albus billing balance
|
|
139
|
+
albus billing ledger --after "$cursor"
|
|
140
|
+
albus billing checkout 20 --success-url https://... --cancel-url https://...
|
|
121
141
|
|
|
122
142
|
albus models list
|
|
143
|
+
|
|
144
|
+
albus memories groups
|
|
145
|
+
albus memories list --group team-a
|
|
146
|
+
albus memories delete "$id" --group team-a
|
|
147
|
+
albus memories delete-group --group team-a
|
|
123
148
|
```
|
|
124
149
|
|
|
150
|
+
`memories groups` lists the organization's memory groups with their active
|
|
151
|
+
counts; `memories list --group` reads one group's memories, newest first. Both
|
|
152
|
+
page with `--after` and `--limit`.
|
|
153
|
+
|
|
125
154
|
`models list` gives the model names `sessions run --model` takes, each with
|
|
126
155
|
the provider it runs on — which is the provider the `--credential` secret has
|
|
127
156
|
to be for.
|
|
@@ -129,9 +158,19 @@ to be for.
|
|
|
129
158
|
`tokens create` prints the only copy of the key value the API ever returns;
|
|
130
159
|
`list` and `get` return metadata alone.
|
|
131
160
|
|
|
132
|
-
`invites create` invites a person by email
|
|
133
|
-
|
|
134
|
-
`
|
|
161
|
+
`invites create` invites a person by email to join your active organization,
|
|
162
|
+
as a `member` unless `--role admin` says otherwise. `invites list` and
|
|
163
|
+
`invites revoke`, like the `organization` member commands, need the admin
|
|
164
|
+
role.
|
|
165
|
+
|
|
166
|
+
`billing checkout` starts a credit purchase and prints the payment page URL
|
|
167
|
+
to send the buyer to; like `tokens` and `invites`, it needs `albus login`
|
|
168
|
+
rather than an API key. `billing balance` and `billing ledger` take either.
|
|
169
|
+
|
|
170
|
+
`traces list` searches the last 31 days of invocations unless `--since` and
|
|
171
|
+
`--until` say otherwise; `traces get` reads one with a page of its spans, and
|
|
172
|
+
`--no-payloads` returns the shape without what each span was given and
|
|
173
|
+
produced.
|
|
135
174
|
|
|
136
175
|
`sessions run` waits for the assistant response by default; pass `--no-wait`
|
|
137
176
|
to return as soon as the invocation is accepted, or `--wait-timeout` to bound
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "albus-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.7.0"
|
|
8
8
|
description = "Command-line client for the Albus REST API"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -13,7 +13,7 @@ authors = [{ name = "Albus" }]
|
|
|
13
13
|
dependencies = [
|
|
14
14
|
# Pinned, not floated: the published CLI states the one SDK version it was
|
|
15
15
|
# tested against, and bumping it is a reviewed edit made when releasing.
|
|
16
|
-
"albus-sdk==0.
|
|
16
|
+
"albus-sdk==0.14.0",
|
|
17
17
|
"typer>=0.26.8,<0.28",
|
|
18
18
|
]
|
|
19
19
|
|
|
@@ -18,6 +18,11 @@ from albus_cli.credentials import Credential
|
|
|
18
18
|
|
|
19
19
|
API_KEY_ENV = "ALBUS_API_KEY"
|
|
20
20
|
BASE_URL_ENV = "ALBUS_BASE_URL"
|
|
21
|
+
ORGANIZATION_ENV = "ALBUS_ORG"
|
|
22
|
+
|
|
23
|
+
# Selects among the signed-in user's organizations. An API key is bound to
|
|
24
|
+
# one already, and the server ignores the header for it.
|
|
25
|
+
ORGANIZATION_HEADER = "X-Albus-Organization"
|
|
21
26
|
|
|
22
27
|
# Renew slightly early: an access token that outlives the request it is
|
|
23
28
|
# sent on is not worth the 401.
|
|
@@ -53,22 +58,29 @@ def api_key() -> str | None:
|
|
|
53
58
|
return os.environ.get(API_KEY_ENV)
|
|
54
59
|
|
|
55
60
|
|
|
56
|
-
def client(
|
|
57
|
-
|
|
61
|
+
def client(
|
|
62
|
+
base_url: str, timeout: float | None, organization: str | None
|
|
63
|
+
) -> Albus:
|
|
64
|
+
"""Build an SDK client. A timeout of None disables the read timeout;
|
|
65
|
+
an organization selects which of the user's the requests act in."""
|
|
58
66
|
key = api_key()
|
|
59
67
|
if key:
|
|
60
68
|
# The environment wins over a stored session deliberately, so CI
|
|
61
69
|
# and agent harnesses inject a credential without touching disk.
|
|
62
|
-
return _client(base_url, timeout, api_key=key)
|
|
70
|
+
return _client(base_url, timeout, organization, api_key=key)
|
|
63
71
|
|
|
64
72
|
stored = credentials.load(base_url)
|
|
65
73
|
if stored is None:
|
|
66
74
|
raise _signed_out(base_url)
|
|
67
75
|
|
|
68
|
-
return bearer_client(
|
|
76
|
+
return bearer_client(
|
|
77
|
+
base_url, timeout, organization, _current(base_url, stored)
|
|
78
|
+
)
|
|
69
79
|
|
|
70
80
|
|
|
71
|
-
def signed_in_client(
|
|
81
|
+
def signed_in_client(
|
|
82
|
+
base_url: str, timeout: float | None, organization: str | None
|
|
83
|
+
) -> Albus:
|
|
72
84
|
"""A client on the browser session. The operations Albus accepts
|
|
73
85
|
only a human bearer token for use it directly: an API key is a 401
|
|
74
86
|
there, and the environment winning would be a 401 nobody asked
|
|
@@ -81,7 +93,9 @@ def signed_in_client(base_url: str, timeout: float | None) -> Albus:
|
|
|
81
93
|
"`albus login`."
|
|
82
94
|
)
|
|
83
95
|
|
|
84
|
-
return bearer_client(
|
|
96
|
+
return bearer_client(
|
|
97
|
+
base_url, timeout, organization, _current(base_url, stored)
|
|
98
|
+
)
|
|
85
99
|
|
|
86
100
|
|
|
87
101
|
def public_client(base_url: str, timeout: float | None) -> Albus:
|
|
@@ -94,15 +108,20 @@ def public_client(base_url: str, timeout: float | None) -> Albus:
|
|
|
94
108
|
sign-in changes."""
|
|
95
109
|
global _used
|
|
96
110
|
_used = None
|
|
97
|
-
return _built(base_url, timeout)
|
|
111
|
+
return _built(base_url, timeout, None)
|
|
98
112
|
|
|
99
113
|
|
|
100
114
|
def bearer_client(
|
|
101
|
-
base_url: str,
|
|
115
|
+
base_url: str,
|
|
116
|
+
timeout: float | None,
|
|
117
|
+
organization: str | None,
|
|
118
|
+
credential: Credential,
|
|
102
119
|
) -> Albus:
|
|
103
120
|
"""A client on one credential, for `login` reading back the session
|
|
104
121
|
it just stored rather than whatever precedence would pick."""
|
|
105
|
-
return _client(
|
|
122
|
+
return _client(
|
|
123
|
+
base_url, timeout, organization, access_token=credential.access_token
|
|
124
|
+
)
|
|
106
125
|
|
|
107
126
|
|
|
108
127
|
def credential(tokens: oauth.Tokens, replacing: Credential | None) -> Credential:
|
|
@@ -206,6 +225,7 @@ def _shadowing(consequence: str) -> str | None:
|
|
|
206
225
|
def _client(
|
|
207
226
|
base_url: str,
|
|
208
227
|
timeout: float | None,
|
|
228
|
+
organization: str | None,
|
|
209
229
|
*,
|
|
210
230
|
api_key: str | None = None,
|
|
211
231
|
access_token: str | None = None,
|
|
@@ -215,6 +235,7 @@ def _client(
|
|
|
215
235
|
return _built(
|
|
216
236
|
base_url,
|
|
217
237
|
timeout,
|
|
238
|
+
organization,
|
|
218
239
|
api_key=api_key,
|
|
219
240
|
access_token=access_token,
|
|
220
241
|
)
|
|
@@ -223,15 +244,19 @@ def _client(
|
|
|
223
244
|
def _built(
|
|
224
245
|
base_url: str,
|
|
225
246
|
timeout: float | None,
|
|
247
|
+
organization: str | None,
|
|
226
248
|
*,
|
|
227
249
|
api_key: str | None = None,
|
|
228
250
|
access_token: str | None = None,
|
|
229
251
|
) -> Albus:
|
|
252
|
+
headers = {ORGANIZATION_HEADER: organization} if organization else {}
|
|
230
253
|
return Albus(
|
|
231
254
|
api_key=api_key,
|
|
232
255
|
access_token=access_token,
|
|
233
256
|
server_url=base_url,
|
|
234
|
-
client=httpx.Client(
|
|
257
|
+
client=httpx.Client(
|
|
258
|
+
follow_redirects=True, timeout=timeout, headers=headers
|
|
259
|
+
),
|
|
235
260
|
)
|
|
236
261
|
|
|
237
262
|
|
|
@@ -8,9 +8,10 @@ SDK operation: they talk to Auth0 rather than to Albus. See
|
|
|
8
8
|
from typing import Annotated
|
|
9
9
|
|
|
10
10
|
import typer
|
|
11
|
+
from albus_sdk import models
|
|
11
12
|
|
|
12
13
|
from albus_cli import client, credentials, docs, oauth, output
|
|
13
|
-
from albus_cli.context import base_url, sdk, timeout
|
|
14
|
+
from albus_cli.context import base_url, organization, sdk, timeout
|
|
14
15
|
|
|
15
16
|
NoBrowser = Annotated[
|
|
16
17
|
bool,
|
|
@@ -39,11 +40,13 @@ def login(ctx: typer.Context, no_browser: NoBrowser = False) -> None:
|
|
|
39
40
|
|
|
40
41
|
# Read the account back through the session just stored, not through
|
|
41
42
|
# precedence: naming the API key's account would be a false report.
|
|
42
|
-
signed_in = client.bearer_client(
|
|
43
|
+
signed_in = client.bearer_client(
|
|
44
|
+
api, timeout(ctx), organization(ctx), session
|
|
45
|
+
)
|
|
43
46
|
# A browser session authenticates a user, so `/whoami` names one;
|
|
44
47
|
# an account it did not name is not one to claim under "Signed in".
|
|
45
48
|
user = signed_in.auth.whoami().user
|
|
46
|
-
output.done(
|
|
49
|
+
output.done(_signed_in(user))
|
|
47
50
|
output.field("API", api)
|
|
48
51
|
output.field("Credential", output.abbreviated(credentials.path()))
|
|
49
52
|
if session.refresh_token is None:
|
|
@@ -69,6 +72,18 @@ def whoami(ctx: typer.Context) -> None:
|
|
|
69
72
|
output.emit(sdk(ctx).auth.whoami())
|
|
70
73
|
|
|
71
74
|
|
|
75
|
+
def _signed_in(user: models.AuthenticatedUser | None) -> str:
|
|
76
|
+
"""The account and the organization its commands act in, as Albus
|
|
77
|
+
reports them for the session just stored."""
|
|
78
|
+
if user is None:
|
|
79
|
+
return "Signed in"
|
|
80
|
+
|
|
81
|
+
if user.active_organization is None:
|
|
82
|
+
return f"Signed in as {user.email}"
|
|
83
|
+
|
|
84
|
+
return f"Signed in as {user.email} in {user.active_organization.name}"
|
|
85
|
+
|
|
86
|
+
|
|
72
87
|
def _next_steps() -> None:
|
|
73
88
|
"""The last lines a successful sign-in prints, where attention is
|
|
74
89
|
highest: the one command that runs an agent, and the sentence to
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""`albus billing` — the organization's prepaid credits.
|
|
2
|
+
|
|
3
|
+
`/billing/checkout` is `bearerAuth`-only: an API key cannot spend money, so
|
|
4
|
+
`checkout` takes the browser session with `signed_in_sdk`. Reading the
|
|
5
|
+
balance and the ledger accepts either credential.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Annotated
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
from albus_cli.context import sdk, signed_in_sdk
|
|
13
|
+
from albus_cli.output import emit
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(no_args_is_help=True, help="Read and top up prepaid credits.")
|
|
16
|
+
|
|
17
|
+
After = Annotated[
|
|
18
|
+
str | None,
|
|
19
|
+
typer.Option("--after", help="Pagination cursor from a previous page."),
|
|
20
|
+
]
|
|
21
|
+
Limit = Annotated[int, typer.Option("--limit", help="Page size.")]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command("balance")
|
|
25
|
+
def balance(ctx: typer.Context) -> None:
|
|
26
|
+
"""Read the organization's credit balance in USD."""
|
|
27
|
+
emit(sdk(ctx).billing.get_credit_balance())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command("ledger")
|
|
31
|
+
def ledger(ctx: typer.Context, after: After = None, limit: Limit = 100) -> None:
|
|
32
|
+
"""List the credit ledger, newest first."""
|
|
33
|
+
emit(sdk(ctx).billing.list_credit_ledger(after=after, limit=limit))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@app.command("checkout")
|
|
37
|
+
def checkout(
|
|
38
|
+
ctx: typer.Context,
|
|
39
|
+
amount_usd: Annotated[
|
|
40
|
+
int,
|
|
41
|
+
typer.Argument(
|
|
42
|
+
metavar="AMOUNT_USD", help="Whole US dollars of credit to buy."
|
|
43
|
+
),
|
|
44
|
+
],
|
|
45
|
+
success_url: Annotated[
|
|
46
|
+
str,
|
|
47
|
+
typer.Option(
|
|
48
|
+
"--success-url",
|
|
49
|
+
help="Where the payment page sends the buyer after paying.",
|
|
50
|
+
),
|
|
51
|
+
],
|
|
52
|
+
cancel_url: Annotated[
|
|
53
|
+
str,
|
|
54
|
+
typer.Option(
|
|
55
|
+
"--cancel-url",
|
|
56
|
+
help="Where the payment page sends the buyer who backs out.",
|
|
57
|
+
),
|
|
58
|
+
],
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Start a credit purchase and print the payment page URL."""
|
|
61
|
+
emit(
|
|
62
|
+
signed_in_sdk(ctx).billing.create_checkout(
|
|
63
|
+
amount_usd=amount_usd,
|
|
64
|
+
success_url=success_url,
|
|
65
|
+
cancel_url=cancel_url,
|
|
66
|
+
)
|
|
67
|
+
)
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""`albus invites` — the pending invitations into the active organization.
|
|
2
|
+
|
|
3
|
+
`/organization/invites` is `bearerAuth`-only, like `/tokens`: an API key
|
|
4
|
+
cannot invite a user, so these commands take the browser session with
|
|
5
|
+
`signed_in_sdk`.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Annotated, Literal
|
|
9
|
+
|
|
10
|
+
import typer
|
|
11
|
+
|
|
12
|
+
from albus_cli.context import signed_in_sdk
|
|
13
|
+
from albus_cli.output import emit
|
|
14
|
+
|
|
15
|
+
app = typer.Typer(no_args_is_help=True, help="Invite users by email.")
|
|
16
|
+
|
|
17
|
+
Role = Literal["admin", "member"]
|
|
18
|
+
|
|
19
|
+
InviteID = Annotated[
|
|
20
|
+
str, typer.Argument(metavar="ID", help="The invitation ID.")
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command("list")
|
|
25
|
+
def list_invites(ctx: typer.Context) -> None:
|
|
26
|
+
"""List pending invitations. Requires the admin role."""
|
|
27
|
+
emit(signed_in_sdk(ctx).invites.list_invites())
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@app.command("create")
|
|
31
|
+
def create(
|
|
32
|
+
ctx: typer.Context,
|
|
33
|
+
email: Annotated[
|
|
34
|
+
str,
|
|
35
|
+
typer.Argument(
|
|
36
|
+
metavar="EMAIL", help="Email address of the person to invite."
|
|
37
|
+
),
|
|
38
|
+
],
|
|
39
|
+
role: Annotated[
|
|
40
|
+
Role | None,
|
|
41
|
+
typer.Option(
|
|
42
|
+
"--role",
|
|
43
|
+
help="Role the invitee joins with. Defaults to member.",
|
|
44
|
+
),
|
|
45
|
+
] = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""Invite a user to join your active organization."""
|
|
48
|
+
emit(signed_in_sdk(ctx).invites.create_invite(email=email, role=role))
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@app.command("revoke")
|
|
52
|
+
def revoke(ctx: typer.Context, invite_id: InviteID) -> None:
|
|
53
|
+
"""Revoke a pending invitation. Requires the admin role."""
|
|
54
|
+
signed_in_sdk(ctx).invites.revoke_invite(id=invite_id)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""`albus memories` — the memory groups agents share, and what they hold."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from albus_cli.context import sdk
|
|
8
|
+
from albus_cli.output import emit
|
|
9
|
+
|
|
10
|
+
app = typer.Typer(
|
|
11
|
+
no_args_is_help=True, help="List and delete memory groups and memories."
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
Group = Annotated[
|
|
15
|
+
str,
|
|
16
|
+
typer.Option("--group", help="Memory group key."),
|
|
17
|
+
]
|
|
18
|
+
After = Annotated[
|
|
19
|
+
str | None,
|
|
20
|
+
typer.Option("--after", help="Pagination cursor from a previous page."),
|
|
21
|
+
]
|
|
22
|
+
Limit = Annotated[int, typer.Option("--limit", help="Page size.")]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.command("groups")
|
|
26
|
+
def list_groups(
|
|
27
|
+
ctx: typer.Context, after: After = None, limit: Limit = 100
|
|
28
|
+
) -> None:
|
|
29
|
+
"""List the organization's memory groups."""
|
|
30
|
+
emit(sdk(ctx).memories.list_memory_groups(after=after, limit=limit))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command("list")
|
|
34
|
+
def list_memories(
|
|
35
|
+
ctx: typer.Context, group: Group, after: After = None, limit: Limit = 100
|
|
36
|
+
) -> None:
|
|
37
|
+
"""List a group's memories, newest first."""
|
|
38
|
+
emit(sdk(ctx).memories.list_memories(group=group, after=after, limit=limit))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@app.command("delete")
|
|
42
|
+
def delete(
|
|
43
|
+
ctx: typer.Context,
|
|
44
|
+
memory_id: Annotated[
|
|
45
|
+
str, typer.Argument(metavar="ID", help="The memory ID.")
|
|
46
|
+
],
|
|
47
|
+
group: Group,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Delete one memory of a group, permanently."""
|
|
50
|
+
sdk(ctx).memories.delete_memory(group=group, id=memory_id)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.command("delete-group")
|
|
54
|
+
def delete_group(ctx: typer.Context, group: Group) -> None:
|
|
55
|
+
"""Delete every memory of a group."""
|
|
56
|
+
sdk(ctx).memories.delete_memory_group(group=group)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""`albus organization` — the organization the commands act in, and its
|
|
2
|
+
members."""
|
|
3
|
+
|
|
4
|
+
from typing import Annotated, Literal
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from albus_cli.context import sdk
|
|
9
|
+
from albus_cli.output import emit
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(
|
|
12
|
+
no_args_is_help=True, help="Manage the organization and its members."
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
Role = Literal["admin", "member"]
|
|
16
|
+
|
|
17
|
+
UserID = Annotated[
|
|
18
|
+
str, typer.Argument(metavar="USER_ID", help="The member's user ID.")
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@app.command("get")
|
|
23
|
+
def get(ctx: typer.Context) -> None:
|
|
24
|
+
"""Get the organization."""
|
|
25
|
+
emit(sdk(ctx).organization.get_organization())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@app.command("rename")
|
|
29
|
+
def rename(
|
|
30
|
+
ctx: typer.Context,
|
|
31
|
+
name: Annotated[
|
|
32
|
+
str, typer.Argument(metavar="NAME", help="New display name.")
|
|
33
|
+
],
|
|
34
|
+
) -> None:
|
|
35
|
+
"""Rename the organization. Requires the admin role."""
|
|
36
|
+
emit(sdk(ctx).organization.update_organization(name=name))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@app.command("members")
|
|
40
|
+
def members(ctx: typer.Context) -> None:
|
|
41
|
+
"""List the organization's members. Requires the admin role."""
|
|
42
|
+
emit(sdk(ctx).organization.list_organization_members())
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command("set-role")
|
|
46
|
+
def set_role(
|
|
47
|
+
ctx: typer.Context,
|
|
48
|
+
user_id: UserID,
|
|
49
|
+
role: Annotated[
|
|
50
|
+
Role, typer.Argument(metavar="ROLE", help="admin or member.")
|
|
51
|
+
],
|
|
52
|
+
) -> None:
|
|
53
|
+
"""Set a member's role. Requires the admin role."""
|
|
54
|
+
emit(
|
|
55
|
+
sdk(ctx).organization.set_organization_member_role(
|
|
56
|
+
user_id=user_id, role=role
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.command("remove-member")
|
|
62
|
+
def remove_member(ctx: typer.Context, user_id: UserID) -> None:
|
|
63
|
+
"""Remove a member from the organization. Requires the admin role."""
|
|
64
|
+
sdk(ctx).organization.remove_organization_member(user_id=user_id)
|
|
@@ -11,7 +11,7 @@ from albus_sdk import models
|
|
|
11
11
|
from pydantic import ValidationError
|
|
12
12
|
|
|
13
13
|
from albus_cli.client import client
|
|
14
|
-
from albus_cli.context import base_url, options, sdk
|
|
14
|
+
from albus_cli.context import base_url, options, organization, sdk
|
|
15
15
|
from albus_cli.output import emit
|
|
16
16
|
|
|
17
17
|
app = typer.Typer(no_args_is_help=True, help="Run and inspect sessions.")
|
|
@@ -215,7 +215,7 @@ def run(
|
|
|
215
215
|
)
|
|
216
216
|
timeout = options(ctx).timeout
|
|
217
217
|
# A waiting invocation long-polls, so it outlives the request timeout.
|
|
218
|
-
albus = client(base_url(ctx), None if wait else timeout)
|
|
218
|
+
albus = client(base_url(ctx), None if wait else timeout, organization(ctx))
|
|
219
219
|
response = albus.sessions.run_session(
|
|
220
220
|
id=session_id,
|
|
221
221
|
user_prompt=prompt,
|
|
@@ -270,6 +270,12 @@ def audit(
|
|
|
270
270
|
)
|
|
271
271
|
|
|
272
272
|
|
|
273
|
+
@app.command("cancel")
|
|
274
|
+
def cancel(ctx: typer.Context, session_id: SessionID) -> None:
|
|
275
|
+
"""Cancel the invocation running for a session."""
|
|
276
|
+
emit(sdk(ctx).sessions.cancel_session(id=session_id))
|
|
277
|
+
|
|
278
|
+
|
|
273
279
|
@app.command("delete")
|
|
274
280
|
def delete(ctx: typer.Context, session_id: SessionID) -> None:
|
|
275
281
|
"""Delete a session."""
|
|
@@ -15,7 +15,7 @@ import typer
|
|
|
15
15
|
from albus_sdk import errors
|
|
16
16
|
|
|
17
17
|
from albus_cli import client, credentials, oauth, output
|
|
18
|
-
from albus_cli.context import base_url, timeout
|
|
18
|
+
from albus_cli.context import base_url, organization, timeout
|
|
19
19
|
|
|
20
20
|
# Everything that answers "the credential does not work": Albus refusing it, a
|
|
21
21
|
# stored session that cannot be renewed, an Auth0 tenant that is unconfigured
|
|
@@ -77,7 +77,7 @@ def _accepted(ctx: typer.Context, api: str) -> dict[str, Any]:
|
|
|
77
77
|
principal — the signed-in user, or the API key and the organization
|
|
78
78
|
it acts in — so one operation both verifies the credential the next
|
|
79
79
|
command sends and reports who it belongs to."""
|
|
80
|
-
caller = client.client(api, timeout(ctx)).auth.whoami()
|
|
80
|
+
caller = client.client(api, timeout(ctx), organization(ctx)).auth.whoami()
|
|
81
81
|
return {
|
|
82
82
|
"authenticated": True,
|
|
83
83
|
"caller": caller.model_dump(mode="json", exclude_none=True),
|