agentstack-cli 0.4.0rc1__tar.gz → 0.4.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.
Files changed (25) hide show
  1. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/PKG-INFO +1 -1
  2. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/pyproject.toml +1 -1
  3. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/auth_manager.py +14 -3
  4. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/commands/agent.py +1 -67
  5. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/commands/platform/base_driver.py +1 -1
  6. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/commands/server.py +45 -5
  7. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/configuration.py +4 -2
  8. agentstack_cli-0.4.1/src/agentstack_cli/data/helm-chart.tgz +0 -0
  9. agentstack_cli-0.4.0rc1/src/agentstack_cli/data/helm-chart.tgz +0 -0
  10. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/README.md +0 -0
  11. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/__init__.py +0 -0
  12. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/api.py +0 -0
  13. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/async_typer.py +0 -0
  14. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/commands/__init__.py +0 -0
  15. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/commands/build.py +0 -0
  16. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/commands/mcp.py +0 -0
  17. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/commands/model.py +0 -0
  18. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/commands/platform/__init__.py +0 -0
  19. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/commands/platform/istio.py +0 -0
  20. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/commands/platform/lima_driver.py +0 -0
  21. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/commands/platform/wsl_driver.py +0 -0
  22. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/commands/self.py +0 -0
  23. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/console.py +0 -0
  24. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/data/.gitignore +0 -0
  25. {agentstack_cli-0.4.0rc1 → agentstack_cli-0.4.1}/src/agentstack_cli/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: agentstack-cli
3
- Version: 0.4.0rc1
3
+ Version: 0.4.1
4
4
  Summary: Agent Stack CLI
5
5
  Author: IBM Corp.
6
6
  Requires-Dist: anyio~=4.10.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "agentstack-cli"
3
- version = "0.4.0-rc1"
3
+ version = "0.4.1"
4
4
  description = "Agent Stack CLI"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "IBM Corp." }]
@@ -18,6 +18,8 @@ class AuthToken(BaseModel):
18
18
 
19
19
 
20
20
  class AuthServer(BaseModel):
21
+ client_id: str = "df82a687-d647-4247-838b-7080d7d83f6c" # Backwards compatibility default
22
+ client_secret: str | None = None
21
23
  token: AuthToken | None = None
22
24
 
23
25
 
@@ -48,9 +50,18 @@ class AuthManager:
48
50
  def _save(self) -> None:
49
51
  self._auth_path.write_text(self._auth.model_dump_json(indent=2))
50
52
 
51
- def save_auth_token(self, server: str, auth_server: str | None = None, token: dict[str, Any] | None = None) -> None:
52
- if auth_server is not None and token is not None:
53
- self._auth.servers[server].authorization_servers[auth_server] = AuthServer(token=AuthToken(**token))
53
+ def save_auth_token(
54
+ self,
55
+ server: str,
56
+ auth_server: str | None = None,
57
+ client_id: str | None = None,
58
+ client_secret: str | None = None,
59
+ token: dict[str, Any] | None = None,
60
+ ) -> None:
61
+ if auth_server is not None and client_id is not None and token is not None:
62
+ self._auth.servers[server].authorization_servers[auth_server] = AuthServer(
63
+ client_id=client_id, client_secret=client_secret, token=AuthToken(**token)
64
+ )
54
65
  else:
55
66
  self._auth.servers[server] # touch
56
67
  self._save()
@@ -68,7 +68,6 @@ from pydantic import BaseModel
68
68
  from rich.box import HORIZONTALS
69
69
  from rich.console import ConsoleRenderable, Group, NewLine
70
70
  from rich.panel import Panel
71
- from rich.rule import Rule
72
71
  from rich.text import Text
73
72
 
74
73
  from agentstack_cli.commands.build import build
@@ -721,54 +720,6 @@ def _create_input_handler(
721
720
  return handler
722
721
 
723
722
 
724
- def _setup_sequential_workflow(providers: list[Provider], splash_screen: ConsoleRenderable | None = None):
725
- prompt_agents = {
726
- provider.agent_card.name: provider
727
- for provider in providers
728
- if (ProviderUtils.detail(provider) or {}).get("interaction_mode") == InteractionMode.SINGLE_TURN
729
- }
730
- steps = []
731
-
732
- console.print(Rule(title="Configure Workflow", style="white"))
733
-
734
- handle_input = _create_input_handler(
735
- [], prompt="Agent: ", choice=list(prompt_agents), placeholder="Select agent", splash_screen=splash_screen
736
- )
737
- handle_instruction_input = _create_input_handler(
738
- [], prompt="Instruction: ", placeholder="Enter agent instruction", splash_screen=splash_screen
739
- )
740
- i = 0
741
-
742
- while True:
743
- if not (agent := handle_input()):
744
- console.print(Rule(style="white"))
745
- break
746
- instruction = handle_instruction_input()
747
-
748
- if not steps:
749
- # change prompt for other passes
750
- handle_input = _create_input_handler(
751
- [],
752
- prompt="Agent: ",
753
- placeholder="Select agent (Leave empty to execute)",
754
- choice=list(prompt_agents),
755
- optional=True,
756
- splash_screen=splash_screen,
757
- )
758
- handle_instruction_input = _create_input_handler(
759
- [],
760
- prompt="Instruction: ",
761
- placeholder="Enter agent instruction (leave empty to pass raw output from previous agent)",
762
- optional=True,
763
- splash_screen=splash_screen,
764
- )
765
- console.print(Rule(style="dim", characters="·"))
766
- i += 1
767
- steps.append({"provider_id": prompt_agents[agent].id, "instruction": instruction})
768
-
769
- return steps
770
-
771
-
772
723
  @app.command("run")
773
724
  async def run_agent(
774
725
  search_path: typing.Annotated[
@@ -810,7 +761,6 @@ async def run_agent(
810
761
 
811
762
  ui_annotations = ProviderUtils.detail(provider) or {}
812
763
  interaction_mode = ui_annotations.get("interaction_mode")
813
- is_sequential_workflow = agent.name in {"sequential_workflow"}
814
764
 
815
765
  user_greeting = ui_annotations.get("user_greeting", None) or "How can I help you?"
816
766
 
@@ -818,10 +768,7 @@ async def run_agent(
818
768
  handle_input = _create_input_handler([], splash_screen=splash_screen)
819
769
 
820
770
  if not input:
821
- if (
822
- interaction_mode not in {InteractionMode.MULTI_TURN, InteractionMode.SINGLE_TURN}
823
- and not is_sequential_workflow
824
- ):
771
+ if interaction_mode not in {InteractionMode.MULTI_TURN, InteractionMode.SINGLE_TURN}:
825
772
  err_console.error(
826
773
  f"Agent {agent.name} does not use any supported UIs.\n"
827
774
  + "Please use the agent according to the following examples and schema:"
@@ -867,19 +814,6 @@ async def run_agent(
867
814
  dump_files_path=dump_files,
868
815
  handle_input=handle_input,
869
816
  )
870
- elif is_sequential_workflow:
871
- workflow_steps = _setup_sequential_workflow(providers, splash_screen=splash_screen)
872
- console.print()
873
- message_part = DataPart(data={"steps": workflow_steps}, metadata={"kind": "configuration"})
874
- async with a2a_client(provider.agent_card) as client:
875
- await _run_agent(
876
- client,
877
- message_part,
878
- agent_card=agent,
879
- context_token=context_token,
880
- dump_files_path=dump_files,
881
- handle_input=handle_input,
882
- )
883
817
 
884
818
  else:
885
819
  async with a2a_client(provider.agent_card) as client:
@@ -183,7 +183,7 @@ class BaseDriver(abc.ABC):
183
183
  "--namespace=default",
184
184
  "--create-namespace",
185
185
  "--values=/tmp/agentstack/values.yaml",
186
- "--timeout=5m",
186
+ "--timeout=20m",
187
187
  "--wait",
188
188
  "--kubeconfig=/etc/rancher/k3s/k3s.yaml",
189
189
  *(f"--set={value}" for value in set_values_list),
@@ -91,7 +91,7 @@ async def server_login(server: typing.Annotated[str | None, typer.Argument()] =
91
91
  )
92
92
  server = server or await inquirer.text(message="Enter server URL:").execute_async() # type: ignore
93
93
 
94
- if server is None:
94
+ if not server:
95
95
  raise RuntimeError("No server selected. Action cancelled.")
96
96
 
97
97
  if "://" not in server:
@@ -134,6 +134,10 @@ async def server_login(server: typing.Annotated[str | None, typer.Argument()] =
134
134
  auth_servers = metadata.get("authorization_servers", [])
135
135
  auth_server = None
136
136
  token = None
137
+
138
+ client_id = config.client_id
139
+ client_secret = config.client_secret
140
+
137
141
  if auth_servers:
138
142
  if len(auth_servers) == 1:
139
143
  auth_server = auth_servers[0]
@@ -154,15 +158,44 @@ async def server_login(server: typing.Annotated[str | None, typer.Argument()] =
154
158
  except Exception as e:
155
159
  raise RuntimeError(f"OIDC discovery failed: {e}") from e
156
160
 
161
+ registration_endpoint = oidc["registration_endpoint"]
162
+ if not client_id and registration_endpoint:
163
+ async with httpx.AsyncClient() as client:
164
+ try:
165
+ resp = await client.post(
166
+ registration_endpoint,
167
+ json={"client_name": "Agent Stack CLI", "redirect_uris": [REDIRECT_URI]},
168
+ )
169
+ resp.raise_for_status()
170
+ data = resp.json()
171
+ client_id = data["client_id"]
172
+ client_secret = data["client_secret"]
173
+ except Exception:
174
+ console.warning("Dynamic client registration failed. Proceed with manual input.")
175
+
176
+ if not client_id:
177
+ client_id = await inquirer.text( # type: ignore
178
+ message="Enter Client ID:",
179
+ instruction=f"(Redirect URI: {REDIRECT_URI})",
180
+ ).execute_async()
181
+ if not client_id:
182
+ raise RuntimeError("Client ID is mandatory. Action cancelled.")
183
+ client_secret = (
184
+ await inquirer.text( # type: ignore
185
+ message="Enter Client Secret (optional):"
186
+ ).execute_async()
187
+ or None
188
+ )
189
+
157
190
  code_verifier = generate_token(64)
158
191
 
159
192
  auth_url = f"{oidc['authorization_endpoint']}?{
160
193
  urlencode(
161
194
  {
162
- 'client_id': config.client_id,
195
+ 'client_id': client_id,
163
196
  'response_type': 'code',
164
197
  'redirect_uri': REDIRECT_URI,
165
- 'scope': ' '.join(metadata.get('scopes_supported', ['openid'])),
198
+ 'scope': ' '.join(metadata.get('scopes_supported', ['openid', 'email', 'profile'])),
166
199
  'code_challenge': typing.cast(str, create_s256_code_challenge(code_verifier)),
167
200
  'code_challenge_method': 'S256',
168
201
  }
@@ -182,7 +215,8 @@ async def server_login(server: typing.Annotated[str | None, typer.Argument()] =
182
215
  "grant_type": "authorization_code",
183
216
  "code": code,
184
217
  "redirect_uri": REDIRECT_URI,
185
- "client_id": config.client_id,
218
+ "client_id": client_id,
219
+ "client_secret": client_secret,
186
220
  "code_verifier": code_verifier,
187
221
  },
188
222
  )
@@ -194,7 +228,13 @@ async def server_login(server: typing.Annotated[str | None, typer.Argument()] =
194
228
  if not token:
195
229
  raise RuntimeError("Login timed out or not successful.")
196
230
 
197
- config.auth_manager.save_auth_token(server, auth_server, token)
231
+ config.auth_manager.save_auth_token(
232
+ server=server,
233
+ auth_server=auth_server,
234
+ client_id=client_id,
235
+ client_secret=client_secret,
236
+ token=token,
237
+ )
198
238
 
199
239
  config.auth_manager.active_server = server
200
240
  config.auth_manager.active_auth_server = auth_server
@@ -36,9 +36,11 @@ class Configuration(pydantic_settings.BaseSettings):
36
36
  f"https://github.com/i-am-bee/agentstack@v{version()}#path=agent-registry.yaml"
37
37
  )
38
38
  admin_password: SecretStr | None = None
39
- oidc_enabled: bool = False
40
39
  server_metadata_ttl: int = 86400
41
- client_id: str = "df82a687-d647-4247-838b-7080d7d83f6c" # pre-registered with AS
40
+
41
+ oidc_enabled: bool = False
42
+ client_id: str | None = None
43
+ client_secret: str | None = None
42
44
 
43
45
  @property
44
46
  def lima_home(self) -> pathlib.Path: