Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions apps/agentstack-cli/src/agentstack_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,10 @@
╰────────────────────────────────────────────────────────────────────────────╯

╭─ Agent Management [Admin only] ────────────────────────────────────────────╮
│ add Install an agent (Docker, GitHub)
│ add Install an agent
│ remove Uninstall an agent │
│ update Update an agent │
│ logs Stream agent execution logs │
│ env Manage agent environment variables │
│ build Build an agent remotely │
│ client-side-build Build an agent container image locally │
│ build Build an agent image locally │
╰────────────────────────────────────────────────────────────────────────────╯

╭─ Platform & Configuration ─────────────────────────────────────────────────╮
Expand Down Expand Up @@ -100,7 +97,6 @@ def main(
no_args_is_help=True,
help="Manage Agent Stack platform. [Local only]",
)
app.add_typer(agentstack_cli.commands.build.app, name="", no_args_is_help=True, help="Build agent images.")
app.add_typer(
agentstack_cli.commands.server.app,
name="server",
Expand All @@ -123,6 +119,8 @@ def main(
# )


app.add_typer(agentstack_cli.commands.build.app, name="", no_args_is_help=True, help="Build agent images.")

agent_alias = deepcopy(agentstack_cli.commands.agent.app)
for cmd in agent_alias.registered_commands:
cmd.rich_help_panel = "Agent commands"
Expand Down Expand Up @@ -153,12 +151,14 @@ async def ui():
active_server = config.auth_manager.active_server

if active_server:
if re.search(r"(localhost|127\.0\.0\.1):8333", active_server):
if "agentstack-api.localtest.me" in active_server:
ui_url = active_server.replace("agentstack-api.localtest.me", "agentstack.localtest.me")
elif re.search(r"(localhost|127\.0\.0\.1):8333", active_server):
ui_url = re.sub(r":8333", ":8334", active_server)
else:
ui_url = active_server
else:
ui_url = "http://localhost:8334"
ui_url = "http://agentstack.localtest.me:8080"

webbrowser.open(ui_url)

Expand Down
4 changes: 3 additions & 1 deletion apps/agentstack-cli/src/agentstack_cli/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,10 @@ async def a2a_client(agent_card: AgentCard, context_token: ContextToken) -> Asyn
@asynccontextmanager
async def openai_client() -> AsyncIterator[openai.AsyncOpenAI]:
async with Configuration().use_platform_client() as platform_client:
headers = platform_client.headers.copy()
headers.pop("Authorization", None)
yield openai.AsyncOpenAI(
api_key=platform_client.headers.get("Authorization", "").removeprefix("Bearer ") or "dummy",
base_url=urllib.parse.urljoin(str(platform_client.base_url), urllib.parse.urljoin(API_BASE_URL, "openai")),
default_headers=platform_client.headers,
default_headers=headers,
)
45 changes: 45 additions & 0 deletions apps/agentstack-cli/src/agentstack_cli/auth_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,51 @@ def save_auth_info(
self._auth.servers[server] # touch
self._save()

async def login_with_password(
self,
server: str,
username: str,
password: str,
client_id: str = "agentstack-cli",
) -> AuthToken:
"""Authenticate using resource owner password grant (direct access)."""
oauth_metadata = await self.fetch_oauth_protected_resource_metadata(server)
auth_servers = oauth_metadata.get("authorization_servers", [])
if not auth_servers:
raise RuntimeError(f"No authorization servers found for {server}")

auth_server_url = auth_servers[0]
oidc = await self.get_oidc_metadata(auth_server_url)
token_endpoint = oidc["token_endpoint"]

async with httpx.AsyncClient() as client:
resp = await client.post(
token_endpoint,
data={
"grant_type": "password",
"client_id": client_id,
"username": username,
"password": password,
"scope": " ".join(oauth_metadata.get("scopes_supported", ["openid", "email", "profile"])),
},
)
resp.raise_for_status()
token_data = resp.json()

auth_token = AuthToken(**token_data)

self.save_auth_info(
server=server,
auth_server=auth_server_url,
client_id=client_id,
token=token_data,
)
self._auth.active_server = server
self._auth.active_auth_server = auth_server_url
self._save()

return auth_token

async def _exchange_refresh_token(self, auth_server: str, token: AuthToken) -> AuthToken:
if not self._auth.active_server:
raise ValueError("No active server configured")
Expand Down
Loading
Loading