albus-cli 0.1.0__py3-none-any.whl
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/__init__.py +0 -0
- albus_cli/__main__.py +6 -0
- albus_cli/client.py +30 -0
- albus_cli/commands/__init__.py +0 -0
- albus_cli/commands/agents.py +39 -0
- albus_cli/commands/secrets.py +62 -0
- albus_cli/commands/sessions.py +223 -0
- albus_cli/context.py +24 -0
- albus_cli/main.py +63 -0
- albus_cli/output.py +16 -0
- albus_cli-0.1.0.dist-info/METADATA +98 -0
- albus_cli-0.1.0.dist-info/RECORD +15 -0
- albus_cli-0.1.0.dist-info/WHEEL +4 -0
- albus_cli-0.1.0.dist-info/entry_points.txt +2 -0
- albus_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
albus_cli/__init__.py
ADDED
|
File without changes
|
albus_cli/__main__.py
ADDED
albus_cli/client.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Albus SDK client construction from the shell environment."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
from albus_sdk import Albus, models
|
|
7
|
+
|
|
8
|
+
API_KEY_ENV = "ALBUS_API_KEY"
|
|
9
|
+
BASE_URL_ENV = "ALBUS_BASE_URL"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class MissingAPIKey(Exception):
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
super().__init__(
|
|
15
|
+
f"{API_KEY_ENV} is not set. Export an Albus API key, e.g. "
|
|
16
|
+
f"`export {API_KEY_ENV}=...`."
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def client(base_url: str | None, timeout: float | None) -> Albus:
|
|
21
|
+
"""Build an SDK client. A timeout of None disables the read timeout."""
|
|
22
|
+
api_key = os.environ.get(API_KEY_ENV)
|
|
23
|
+
if not api_key:
|
|
24
|
+
raise MissingAPIKey
|
|
25
|
+
|
|
26
|
+
return Albus(
|
|
27
|
+
security=models.Security(api_key_auth=api_key),
|
|
28
|
+
server_url=base_url,
|
|
29
|
+
client=httpx.Client(follow_redirects=True, timeout=timeout),
|
|
30
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""`albus agents` — inspect the agents that have run in the org."""
|
|
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(no_args_is_help=True, help="Inspect agents.")
|
|
11
|
+
|
|
12
|
+
AgentName = Annotated[
|
|
13
|
+
str, typer.Argument(metavar="AGENT_NAME", help="Agent name.")
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@app.command("list")
|
|
18
|
+
def list_agents(ctx: typer.Context) -> None:
|
|
19
|
+
"""List agents."""
|
|
20
|
+
emit(sdk(ctx).agents.list_agents())
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@app.command("get")
|
|
24
|
+
def get(ctx: typer.Context, name: AgentName) -> None:
|
|
25
|
+
"""Get an agent with its current revision."""
|
|
26
|
+
emit(sdk(ctx).agents.get_agent(name=name))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.command("revision")
|
|
30
|
+
def revision(
|
|
31
|
+
ctx: typer.Context,
|
|
32
|
+
name: AgentName,
|
|
33
|
+
revision: Annotated[
|
|
34
|
+
str,
|
|
35
|
+
typer.Argument(metavar="REVISION", help="Agent revision identifier."),
|
|
36
|
+
],
|
|
37
|
+
) -> None:
|
|
38
|
+
"""Get one revision of an agent."""
|
|
39
|
+
emit(sdk(ctx).agents.get_agent_revision(name=name, revision=revision))
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""`albus secrets` — manage secrets available to agent sessions."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
|
|
8
|
+
from albus_cli.context import sdk
|
|
9
|
+
from albus_cli.output import emit
|
|
10
|
+
|
|
11
|
+
app = typer.Typer(no_args_is_help=True, help="Manage secrets.")
|
|
12
|
+
|
|
13
|
+
Name = Annotated[str, typer.Argument(metavar="NAME", help="Secret name.")]
|
|
14
|
+
Value = Annotated[
|
|
15
|
+
str | None,
|
|
16
|
+
typer.Option(
|
|
17
|
+
"--value",
|
|
18
|
+
help="Secret value. Omit to read it from stdin, keeping it out "
|
|
19
|
+
"of the shell history.",
|
|
20
|
+
),
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def secret_value(value: str | None) -> str:
|
|
25
|
+
if value is not None:
|
|
26
|
+
return value
|
|
27
|
+
|
|
28
|
+
read = sys.stdin.read().strip()
|
|
29
|
+
if not read:
|
|
30
|
+
raise typer.BadParameter("no secret value on stdin")
|
|
31
|
+
|
|
32
|
+
return read
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@app.command("list")
|
|
36
|
+
def list_secrets(ctx: typer.Context) -> None:
|
|
37
|
+
"""List all secrets with masked values."""
|
|
38
|
+
emit(sdk(ctx).secrets.list_secrets())
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@app.command("create")
|
|
42
|
+
def create(ctx: typer.Context, name: Name, value: Value = None) -> None:
|
|
43
|
+
"""Create a secret."""
|
|
44
|
+
emit(sdk(ctx).secrets.create_secret(name=name, value=secret_value(value)))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.command("get")
|
|
48
|
+
def get(ctx: typer.Context, name: Name) -> None:
|
|
49
|
+
"""Get a secret's masked value."""
|
|
50
|
+
emit(sdk(ctx).secrets.get_secret(name=name))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.command("update")
|
|
54
|
+
def update(ctx: typer.Context, name: Name, value: Value = None) -> None:
|
|
55
|
+
"""Replace a secret's value."""
|
|
56
|
+
emit(sdk(ctx).secrets.update_secret(name=name, value=secret_value(value)))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@app.command("delete")
|
|
60
|
+
def delete(ctx: typer.Context, name: Name) -> None:
|
|
61
|
+
"""Delete a secret."""
|
|
62
|
+
sdk(ctx).secrets.delete_secret(name=name)
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""`albus sessions` — run and inspect agent sessions."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Annotated
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from albus_sdk import models
|
|
8
|
+
|
|
9
|
+
from albus_cli.client import client
|
|
10
|
+
from albus_cli.context import options, sdk
|
|
11
|
+
from albus_cli.output import emit
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(no_args_is_help=True, help="Run and inspect sessions.")
|
|
14
|
+
|
|
15
|
+
SessionID = Annotated[
|
|
16
|
+
str,
|
|
17
|
+
typer.Argument(
|
|
18
|
+
metavar="SESSION_ID",
|
|
19
|
+
help="Client-provided session identifier. Reuse it to continue "
|
|
20
|
+
"the same session.",
|
|
21
|
+
),
|
|
22
|
+
]
|
|
23
|
+
After = Annotated[
|
|
24
|
+
str | None,
|
|
25
|
+
typer.Option("--after", help="Pagination cursor from a previous page."),
|
|
26
|
+
]
|
|
27
|
+
Limit = Annotated[int, typer.Option("--limit", help="Page size.")]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def agent_config(
|
|
31
|
+
agent_file: Path | None,
|
|
32
|
+
model: str | None,
|
|
33
|
+
provider: str | None,
|
|
34
|
+
credential: str | None,
|
|
35
|
+
system_prompt: str | None,
|
|
36
|
+
tools: list[str],
|
|
37
|
+
max_steps: int | None,
|
|
38
|
+
) -> models.AgentConfig:
|
|
39
|
+
if agent_file is not None:
|
|
40
|
+
flags = (model, provider, credential, system_prompt, max_steps)
|
|
41
|
+
if any(flag is not None for flag in flags) or tools:
|
|
42
|
+
raise typer.BadParameter(
|
|
43
|
+
"--agent-file holds the whole agent configuration and "
|
|
44
|
+
"cannot be combined with the other agent options"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return models.AgentConfig.model_validate_json(agent_file.read_text())
|
|
48
|
+
|
|
49
|
+
if model is None:
|
|
50
|
+
raise typer.BadParameter(
|
|
51
|
+
"--model is required unless --agent-file is given"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
if (provider is None) != (credential is None):
|
|
55
|
+
raise typer.BadParameter(
|
|
56
|
+
"--provider and --credential must be given together"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
provider_config = (
|
|
60
|
+
models.Provider(name=provider, credential=credential)
|
|
61
|
+
if provider is not None and credential is not None
|
|
62
|
+
else None
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return models.AgentConfig(
|
|
66
|
+
model=models.Model(name=model, provider=provider_config),
|
|
67
|
+
tools=tools or None,
|
|
68
|
+
system_prompt=system_prompt,
|
|
69
|
+
max_steps=max_steps,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@app.command("run")
|
|
74
|
+
def run(
|
|
75
|
+
ctx: typer.Context,
|
|
76
|
+
session_id: SessionID,
|
|
77
|
+
prompt: Annotated[
|
|
78
|
+
str,
|
|
79
|
+
typer.Option(
|
|
80
|
+
"--prompt",
|
|
81
|
+
"-p",
|
|
82
|
+
help="The user prompt driving this invocation.",
|
|
83
|
+
),
|
|
84
|
+
],
|
|
85
|
+
agent_name: Annotated[
|
|
86
|
+
str,
|
|
87
|
+
typer.Option(
|
|
88
|
+
"--agent-name",
|
|
89
|
+
help='Name identifying the agent (e.g. "support-triage").',
|
|
90
|
+
),
|
|
91
|
+
],
|
|
92
|
+
model: Annotated[
|
|
93
|
+
str | None,
|
|
94
|
+
typer.Option(
|
|
95
|
+
"--model", help='Model identifier (e.g. "gemini-3.6-flash").'
|
|
96
|
+
),
|
|
97
|
+
] = None,
|
|
98
|
+
provider: Annotated[
|
|
99
|
+
str | None,
|
|
100
|
+
typer.Option("--provider", help='Provider name (e.g. "gemini").'),
|
|
101
|
+
] = None,
|
|
102
|
+
credential: Annotated[
|
|
103
|
+
str | None,
|
|
104
|
+
typer.Option(
|
|
105
|
+
"--credential",
|
|
106
|
+
help="Secret reference the provider authenticates with "
|
|
107
|
+
'(e.g. "albus.sh/secrets/my-key").',
|
|
108
|
+
),
|
|
109
|
+
] = None,
|
|
110
|
+
system_prompt: Annotated[
|
|
111
|
+
str | None,
|
|
112
|
+
typer.Option(
|
|
113
|
+
"--system-prompt", help="System instructions for the model."
|
|
114
|
+
),
|
|
115
|
+
] = None,
|
|
116
|
+
tools: Annotated[
|
|
117
|
+
list[str] | None,
|
|
118
|
+
typer.Option(
|
|
119
|
+
"--tool",
|
|
120
|
+
help="Tool the model may call. Repeat to allow several.",
|
|
121
|
+
),
|
|
122
|
+
] = None,
|
|
123
|
+
max_steps: Annotated[
|
|
124
|
+
int | None,
|
|
125
|
+
typer.Option(
|
|
126
|
+
"--max-steps", help="Max model steps before the run stops."
|
|
127
|
+
),
|
|
128
|
+
] = None,
|
|
129
|
+
agent_file: Annotated[
|
|
130
|
+
Path | None,
|
|
131
|
+
typer.Option(
|
|
132
|
+
"--agent-file",
|
|
133
|
+
exists=True,
|
|
134
|
+
dir_okay=False,
|
|
135
|
+
help="JSON file holding the whole agent configuration, for "
|
|
136
|
+
"configurations the flags do not cover (e.g. MCP servers).",
|
|
137
|
+
),
|
|
138
|
+
] = None,
|
|
139
|
+
idempotency_key: Annotated[
|
|
140
|
+
str | None,
|
|
141
|
+
typer.Option(
|
|
142
|
+
"--idempotency-key",
|
|
143
|
+
help="Identifies this invocation so the call is retry-safe.",
|
|
144
|
+
),
|
|
145
|
+
] = None,
|
|
146
|
+
wait: Annotated[
|
|
147
|
+
bool,
|
|
148
|
+
typer.Option(
|
|
149
|
+
"--wait/--no-wait",
|
|
150
|
+
help="Block until the assistant response is available.",
|
|
151
|
+
),
|
|
152
|
+
] = True,
|
|
153
|
+
wait_timeout: Annotated[
|
|
154
|
+
int | None,
|
|
155
|
+
typer.Option(
|
|
156
|
+
"--wait-timeout",
|
|
157
|
+
help="Seconds to block while waiting. Omit to wait indefinitely.",
|
|
158
|
+
),
|
|
159
|
+
] = None,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""Run or resume a session."""
|
|
162
|
+
agent = agent_config(
|
|
163
|
+
agent_file,
|
|
164
|
+
model,
|
|
165
|
+
provider,
|
|
166
|
+
credential,
|
|
167
|
+
system_prompt,
|
|
168
|
+
tools or [],
|
|
169
|
+
max_steps,
|
|
170
|
+
)
|
|
171
|
+
opts = options(ctx)
|
|
172
|
+
# A waiting run long-polls, so it outlives the request timeout.
|
|
173
|
+
albus = client(opts.base_url, None if wait else opts.timeout)
|
|
174
|
+
response = albus.sessions.run_session(
|
|
175
|
+
id=session_id,
|
|
176
|
+
user_prompt=prompt,
|
|
177
|
+
agent_name=agent_name,
|
|
178
|
+
agent=agent,
|
|
179
|
+
idempotency_key=idempotency_key,
|
|
180
|
+
wait=wait,
|
|
181
|
+
wait_timeout=wait_timeout,
|
|
182
|
+
)
|
|
183
|
+
result = response.result.model_dump(mode="json", exclude_none=True)
|
|
184
|
+
result["idempotency_key"] = response.headers["idempotency-key"][0]
|
|
185
|
+
emit(result)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@app.command("list")
|
|
189
|
+
def list_sessions(ctx: typer.Context) -> None:
|
|
190
|
+
"""List all sessions."""
|
|
191
|
+
emit(sdk(ctx).sessions.list_sessions())
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@app.command("get")
|
|
195
|
+
def get(
|
|
196
|
+
ctx: typer.Context,
|
|
197
|
+
session_id: SessionID,
|
|
198
|
+
after: After = None,
|
|
199
|
+
limit: Limit = 100,
|
|
200
|
+
) -> None:
|
|
201
|
+
"""Get a session with a page of its messages."""
|
|
202
|
+
emit(sdk(ctx).sessions.get_session(id=session_id, after=after, limit=limit))
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@app.command("audit")
|
|
206
|
+
def audit(
|
|
207
|
+
ctx: typer.Context,
|
|
208
|
+
session_id: SessionID,
|
|
209
|
+
after: After = None,
|
|
210
|
+
limit: Limit = 100,
|
|
211
|
+
) -> None:
|
|
212
|
+
"""List a page of a session's audit log."""
|
|
213
|
+
emit(
|
|
214
|
+
sdk(ctx).sessions.get_session_audit(
|
|
215
|
+
id=session_id, after=after, limit=limit
|
|
216
|
+
)
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
@app.command("delete")
|
|
221
|
+
def delete(ctx: typer.Context, session_id: SessionID) -> None:
|
|
222
|
+
"""Delete a session."""
|
|
223
|
+
sdk(ctx).sessions.delete_session(id=session_id)
|
albus_cli/context.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Global options shared by every command."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import cast
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from albus_sdk import Albus
|
|
8
|
+
|
|
9
|
+
from albus_cli.client import client
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass(frozen=True)
|
|
13
|
+
class Options:
|
|
14
|
+
base_url: str | None
|
|
15
|
+
timeout: float
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def options(ctx: typer.Context) -> Options:
|
|
19
|
+
return cast(Options, ctx.obj)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def sdk(ctx: typer.Context) -> Albus:
|
|
23
|
+
opts = options(ctx)
|
|
24
|
+
return client(opts.base_url, opts.timeout)
|
albus_cli/main.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""`albus` — command-line client for the Albus REST API."""
|
|
2
|
+
|
|
3
|
+
from typing import Annotated
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import typer
|
|
7
|
+
from albus_sdk import errors
|
|
8
|
+
|
|
9
|
+
from albus_cli.client import BASE_URL_ENV, MissingAPIKey
|
|
10
|
+
from albus_cli.commands import agents, secrets, sessions
|
|
11
|
+
from albus_cli.context import Options, sdk
|
|
12
|
+
from albus_cli.output import emit
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(
|
|
15
|
+
no_args_is_help=True,
|
|
16
|
+
add_completion=False,
|
|
17
|
+
help="Command-line client for the Albus REST API. Authenticates with "
|
|
18
|
+
"the API key in ALBUS_API_KEY and prints JSON responses.",
|
|
19
|
+
)
|
|
20
|
+
app.add_typer(sessions.app, name="sessions")
|
|
21
|
+
app.add_typer(secrets.app, name="secrets")
|
|
22
|
+
app.add_typer(agents.app, name="agents")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.callback()
|
|
26
|
+
def configure(
|
|
27
|
+
ctx: typer.Context,
|
|
28
|
+
base_url: Annotated[
|
|
29
|
+
str | None,
|
|
30
|
+
typer.Option(
|
|
31
|
+
"--base-url",
|
|
32
|
+
envvar=BASE_URL_ENV,
|
|
33
|
+
help="Albus API base URL. Defaults to production.",
|
|
34
|
+
),
|
|
35
|
+
] = None,
|
|
36
|
+
timeout: Annotated[
|
|
37
|
+
float,
|
|
38
|
+
typer.Option("--timeout", help="Request timeout in seconds."),
|
|
39
|
+
] = 30.0,
|
|
40
|
+
) -> None:
|
|
41
|
+
ctx.obj = Options(base_url=base_url, timeout=timeout)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@app.command("health")
|
|
45
|
+
def health(ctx: typer.Context) -> None:
|
|
46
|
+
"""Check service availability."""
|
|
47
|
+
emit(sdk(ctx).health.health())
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def main() -> None:
|
|
51
|
+
try:
|
|
52
|
+
app()
|
|
53
|
+
except MissingAPIKey as missing:
|
|
54
|
+
fail(str(missing))
|
|
55
|
+
except errors.AlbusError as error:
|
|
56
|
+
fail(f"{error.status_code}: {error.message}")
|
|
57
|
+
except httpx.HTTPError as transport:
|
|
58
|
+
fail(str(transport))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def fail(message: str) -> None:
|
|
62
|
+
typer.secho(f"albus: {message}", fg=typer.colors.RED, err=True)
|
|
63
|
+
raise SystemExit(1)
|
albus_cli/output.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Command output. Every command prints one pretty-printed JSON value."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def emit(value: BaseModel | list[Any] | dict[str, Any] | None) -> None:
|
|
10
|
+
if value is None:
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
if isinstance(value, BaseModel):
|
|
14
|
+
value = value.model_dump(mode="json", exclude_none=True)
|
|
15
|
+
|
|
16
|
+
print(json.dumps(value, indent=2))
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: albus-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Command-line client for the Albus REST API
|
|
5
|
+
Project-URL: Homepage, https://github.com/albusgroup/albus-cli
|
|
6
|
+
Project-URL: Repository, https://github.com/albusgroup/albus-cli
|
|
7
|
+
Project-URL: Issues, https://github.com/albusgroup/albus-cli/issues
|
|
8
|
+
Author: Albus
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Requires-Python: >=3.11
|
|
12
|
+
Requires-Dist: albus-sdk==0.4.0
|
|
13
|
+
Requires-Dist: typer<0.28,>=0.26.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# albus CLI
|
|
17
|
+
|
|
18
|
+
Command-line client for the Albus REST API. Thin shell over the public
|
|
19
|
+
[`albus-sdk`](https://pypi.org/project/albus-sdk/) Python package: every
|
|
20
|
+
command maps to one API operation and prints JSON (pipe into `jq`).
|
|
21
|
+
|
|
22
|
+
## Install
|
|
23
|
+
|
|
24
|
+
macOS / Linux:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
curl -fsSL https://raw.githubusercontent.com/albusgroup/albus-cli/master/install.sh | sh
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Windows (PowerShell):
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
irm https://raw.githubusercontent.com/albusgroup/albus-cli/master/install.ps1 | iex
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Pin a version:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
ALBUS_CLI_VERSION=0.1.0 curl -fsSL https://raw.githubusercontent.com/albusgroup/albus-cli/master/install.sh | sh
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The installer uses, in order: `uv tool install`, `pip install --user`, or
|
|
43
|
+
`pip` inside a conda environment. It does not download OS-specific Albus
|
|
44
|
+
binaries and does not check OS versions.
|
|
45
|
+
|
|
46
|
+
You can also install directly:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
uv tool install albus-cli
|
|
50
|
+
# or
|
|
51
|
+
pip install --user albus-cli
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Authentication
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
export ALBUS_API_KEY=... # organization API key
|
|
58
|
+
export ALBUS_BASE_URL=... # optional; defaults to production
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`--base-url` overrides `ALBUS_BASE_URL`, and `--timeout` bounds each request
|
|
62
|
+
(a waiting `sessions run` long-polls and is exempt).
|
|
63
|
+
|
|
64
|
+
The API key is the only credential, so the CLI covers only the operations that
|
|
65
|
+
accept `apiKeyAuth`.
|
|
66
|
+
|
|
67
|
+
## Commands
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
albus health
|
|
71
|
+
|
|
72
|
+
albus sessions run my-session -p "summarize the incident" \
|
|
73
|
+
--agent-name support-triage --model gemini-3.6-flash \
|
|
74
|
+
--provider gemini --credential albus.sh/secrets/gemini-key
|
|
75
|
+
albus sessions list
|
|
76
|
+
albus sessions get my-session --limit 20
|
|
77
|
+
albus sessions audit my-session --after "$cursor"
|
|
78
|
+
albus sessions delete my-session
|
|
79
|
+
|
|
80
|
+
albus secrets list
|
|
81
|
+
albus secrets create gemini-key --value ...
|
|
82
|
+
albus secrets get gemini-key
|
|
83
|
+
albus secrets update gemini-key < value.txt
|
|
84
|
+
albus secrets delete gemini-key
|
|
85
|
+
|
|
86
|
+
albus agents list
|
|
87
|
+
albus agents get support-triage
|
|
88
|
+
albus agents revision support-triage "$revision"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Development
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
make install
|
|
95
|
+
make check # ruff, mypy --strict, pytest
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
See [RELEASING.md](RELEASING.md) to publish a PyPI version.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
albus_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
albus_cli/__main__.py,sha256=oJAekektwsWMaeZVHcR7YcFSHlrCloXQHP_j5AcfsH0,117
|
|
3
|
+
albus_cli/client.py,sha256=Q18fv9CvCK99VgNINHmkwrDypAlePMovtzUPuIDyLlc,818
|
|
4
|
+
albus_cli/context.py,sha256=xgIgwQ8-tkNxWEEVhcjplPgNgim5P3u91e_ur2s5UEE,460
|
|
5
|
+
albus_cli/main.py,sha256=Wlpj1Fn816eQ4HfhrOYv9WeIQk5c6fuEih2_YHRJ2OM,1682
|
|
6
|
+
albus_cli/output.py,sha256=ECRO5OPqF7PjgnvdLyv8amTKBrxMiS3SksGmmzhuIuc,396
|
|
7
|
+
albus_cli/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
albus_cli/commands/agents.py,sha256=WuSXfbqyECvxikne60b9U7XBFcCcD-QNaS95MWSyA8o,980
|
|
9
|
+
albus_cli/commands/secrets.py,sha256=BJLt2bn_2-LWi-Xh5klhO2q1RHx6_PMMezIuUW0SfBY,1631
|
|
10
|
+
albus_cli/commands/sessions.py,sha256=IQZmKppSRAhcHUCvNboYOkw2L7vWAvl0imWjnsxvBlQ,6144
|
|
11
|
+
albus_cli-0.1.0.dist-info/METADATA,sha256=2dD0Oj8n-UszHSeanmDc_H4L1x6GFOx9b4besWDrj-c,2570
|
|
12
|
+
albus_cli-0.1.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
13
|
+
albus_cli-0.1.0.dist-info/entry_points.txt,sha256=9g6sVxZtR__IY9Nvk7aaLh8S6cxUWSxwyKHiQMEweG4,46
|
|
14
|
+
albus_cli-0.1.0.dist-info/licenses/LICENSE,sha256=DNMiLr-P9bczooicuwDQOU04-9XSjT0NWwAJhBmE1Bk,1062
|
|
15
|
+
albus_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Albus
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|