glaip-sdk 0.0.2__py3-none-any.whl → 0.0.3__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.
Files changed (39) hide show
  1. glaip_sdk/__init__.py +2 -2
  2. glaip_sdk/_version.py +51 -0
  3. glaip_sdk/cli/commands/agents.py +201 -109
  4. glaip_sdk/cli/commands/configure.py +29 -87
  5. glaip_sdk/cli/commands/init.py +16 -7
  6. glaip_sdk/cli/commands/mcps.py +73 -153
  7. glaip_sdk/cli/commands/tools.py +185 -49
  8. glaip_sdk/cli/main.py +30 -27
  9. glaip_sdk/cli/utils.py +126 -13
  10. glaip_sdk/client/__init__.py +54 -2
  11. glaip_sdk/client/agents.py +175 -237
  12. glaip_sdk/client/base.py +62 -2
  13. glaip_sdk/client/mcps.py +63 -20
  14. glaip_sdk/client/tools.py +95 -28
  15. glaip_sdk/config/constants.py +10 -3
  16. glaip_sdk/exceptions.py +13 -0
  17. glaip_sdk/models.py +20 -4
  18. glaip_sdk/utils/__init__.py +116 -18
  19. glaip_sdk/utils/client_utils.py +284 -0
  20. glaip_sdk/utils/rendering/__init__.py +1 -0
  21. glaip_sdk/utils/rendering/formatting.py +211 -0
  22. glaip_sdk/utils/rendering/models.py +53 -0
  23. glaip_sdk/utils/rendering/renderer/__init__.py +38 -0
  24. glaip_sdk/utils/rendering/renderer/base.py +827 -0
  25. glaip_sdk/utils/rendering/renderer/config.py +33 -0
  26. glaip_sdk/utils/rendering/renderer/console.py +54 -0
  27. glaip_sdk/utils/rendering/renderer/debug.py +82 -0
  28. glaip_sdk/utils/rendering/renderer/panels.py +123 -0
  29. glaip_sdk/utils/rendering/renderer/progress.py +118 -0
  30. glaip_sdk/utils/rendering/renderer/stream.py +198 -0
  31. glaip_sdk/utils/rendering/steps.py +168 -0
  32. glaip_sdk/utils/run_renderer.py +22 -1086
  33. {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.3.dist-info}/METADATA +8 -36
  34. glaip_sdk-0.0.3.dist-info/RECORD +40 -0
  35. glaip_sdk/cli/config.py +0 -592
  36. glaip_sdk/utils.py +0 -167
  37. glaip_sdk-0.0.2.dist-info/RECORD +0 -28
  38. {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.3.dist-info}/WHEEL +0 -0
  39. {glaip_sdk-0.0.2.dist-info → glaip_sdk-0.0.3.dist-info}/entry_points.txt +0 -0
glaip_sdk/__init__.py CHANGED
@@ -4,9 +4,9 @@ Authors:
4
4
  Raymond Christopher (raymond.christopher@gdplabs.id)
5
5
  """
6
6
 
7
+ from ._version import __version__
7
8
  from .client import Client
8
9
  from .exceptions import AIPError
9
10
  from .models import MCP, Agent, Tool
10
11
 
11
- __version__ = "0.1.1"
12
- __all__ = ["Client", "Agent", "Tool", "MCP", "AIPError"]
12
+ __all__ = ["Client", "Agent", "Tool", "MCP", "AIPError", "__version__"]
glaip_sdk/_version.py ADDED
@@ -0,0 +1,51 @@
1
+ """Central version definition for glaip_sdk.
2
+
3
+ Resolves from installed package metadata to avoid hardcoding.
4
+ Falls back to a dev marker when running from source without installation.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ try:
10
+ from importlib.metadata import PackageNotFoundError, version # Python 3.8+
11
+ except Exception: # pragma: no cover - extremely unlikely
12
+ PackageNotFoundError = Exception # type: ignore
13
+
14
+ def version(_: str) -> str: # type: ignore
15
+ return "0.0.0.dev0"
16
+
17
+
18
+ try:
19
+ import tomllib as _toml # Python 3.11+
20
+ except Exception: # pragma: no cover
21
+ try:
22
+ import tomli as _toml # type: ignore
23
+ except Exception: # pragma: no cover
24
+ _toml = None # type: ignore
25
+
26
+
27
+ def _get_version() -> str:
28
+ try:
29
+ return version("glaip-sdk")
30
+ except PackageNotFoundError:
31
+ # Not installed; try reading from local pyproject for dev
32
+ if _toml is not None:
33
+ try:
34
+ from pathlib import Path
35
+
36
+ here = Path(__file__).resolve()
37
+ root = here.parent.parent # project root (contains pyproject.toml)
38
+ pyproject = root / "pyproject.toml"
39
+ if pyproject.is_file():
40
+ data = _toml.loads(pyproject.read_text(encoding="utf-8"))
41
+ ver = data.get("project", {}).get("version") or data.get(
42
+ "tool", {}
43
+ ).get("poetry", {}).get("version")
44
+ if isinstance(ver, str) and ver:
45
+ return ver
46
+ except Exception:
47
+ pass
48
+ return "0.0.0.dev0"
49
+
50
+
51
+ __version__ = _get_version()
@@ -1,30 +1,42 @@
1
- """Agent management commands.
1
+ """Agent CLI commands for AIP SDK.
2
2
 
3
3
  Authors:
4
4
  Raymond Christopher (raymond.christopher@gdplabs.id)
5
5
  """
6
6
 
7
7
  import json
8
+ from datetime import datetime
8
9
 
9
10
  import click
10
11
  from rich.console import Console
11
12
  from rich.panel import Panel
12
- from rich.text import Text
13
13
 
14
+ from glaip_sdk.config.constants import DEFAULT_AGENT_RUN_TIMEOUT, DEFAULT_MODEL
15
+ from glaip_sdk.exceptions import AgentTimeoutError
14
16
  from glaip_sdk.utils import is_uuid
15
17
 
16
18
  from ..utils import (
19
+ build_renderer,
20
+ coerce_to_row,
17
21
  get_client,
18
- handle_ambiguous_resource,
19
22
  output_flags,
20
23
  output_list,
21
24
  output_result,
22
- safe_getattr,
25
+ resolve_resource,
23
26
  )
24
27
 
25
28
  console = Console()
26
29
 
27
30
 
31
+ def _format_datetime(dt):
32
+ """Format datetime object to readable string."""
33
+ if isinstance(dt, datetime):
34
+ return dt.strftime("%Y-%m-%d %H:%M:%S UTC")
35
+ elif dt is None:
36
+ return "N/A"
37
+ return dt
38
+
39
+
28
40
  @click.group(name="agents", no_args_is_help=True)
29
41
  def agents_group():
30
42
  """Agent management operations."""
@@ -33,25 +45,14 @@ def agents_group():
33
45
 
34
46
  def _resolve_agent(ctx, client, ref, select=None):
35
47
  """Resolve agent reference (ID or name) with ambiguity handling."""
36
- if is_uuid(ref):
37
- return client.agents.get_agent_by_id(ref)
38
-
39
- # Find agents by name
40
- matches = client.agents.find_agents(name=ref)
41
- if not matches:
42
- raise click.ClickException(f"Agent '{ref}' not found")
43
-
44
- if len(matches) == 1:
45
- return matches[0]
46
-
47
- # Multiple matches - handle ambiguity
48
- if select:
49
- idx = int(select) - 1
50
- if not (0 <= idx < len(matches)):
51
- raise click.ClickException(f"--select must be 1..{len(matches)}")
52
- return matches[idx]
53
-
54
- return handle_ambiguous_resource(ctx, "agent", ref, matches)
48
+ return resolve_resource(
49
+ ctx,
50
+ ref,
51
+ get_by_id=client.agents.get_agent_by_id,
52
+ find_by_name=client.agents.find_agents,
53
+ label="Agent",
54
+ select=select,
55
+ )
55
56
 
56
57
 
57
58
  @agents_group.command(name="list")
@@ -74,13 +75,10 @@ def list_agents(ctx):
74
75
 
75
76
  # Transform function for safe attribute access
76
77
  def transform_agent(agent):
77
- return {
78
- "id": str(agent.id),
79
- "name": agent.name,
80
- "type": safe_getattr(agent, "type") or "N/A",
81
- "framework": safe_getattr(agent, "framework") or "N/A",
82
- "version": safe_getattr(agent, "version") or "N/A",
83
- }
78
+ row = coerce_to_row(agent, ["id", "name", "type", "framework", "version"])
79
+ # Ensure id is always a string
80
+ row["id"] = str(row["id"])
81
+ return row
84
82
 
85
83
  output_list(ctx, agents, "🤖 Available Agents", columns, transform_agent)
86
84
 
@@ -101,6 +99,16 @@ def get(ctx, agent_ref, select):
101
99
  # Resolve agent with ambiguity handling
102
100
  agent = _resolve_agent(ctx, client, agent_ref, select)
103
101
 
102
+ # If resolved by name, it may be a shallow object from list endpoint.
103
+ # Fetch full details by ID to ensure instruction/tools are populated.
104
+ try:
105
+ agent_id = str(getattr(agent, "id", "")).strip()
106
+ if agent_id:
107
+ agent = client.agents.get_agent_by_id(agent_id)
108
+ except Exception:
109
+ # If fetching full details fails, continue with the resolved object.
110
+ pass
111
+
104
112
  # Create result data with all available fields from backend
105
113
  result_data = {
106
114
  "id": str(getattr(agent, "id", "N/A")),
@@ -110,9 +118,12 @@ def get(ctx, agent_ref, select):
110
118
  "version": getattr(agent, "version", "N/A"),
111
119
  "description": getattr(agent, "description", "N/A"),
112
120
  "instruction": getattr(agent, "instruction", "") or "-",
121
+ "created_at": _format_datetime(getattr(agent, "created_at", "N/A")),
122
+ "updated_at": _format_datetime(getattr(agent, "updated_at", "N/A")),
113
123
  "metadata": getattr(agent, "metadata", "N/A"),
114
124
  "language_model_id": getattr(agent, "language_model_id", "N/A"),
115
125
  "agent_config": getattr(agent, "agent_config", "N/A"),
126
+ "tool_configs": agent.tool_configs or {},
116
127
  "tools": getattr(agent, "tools", []),
117
128
  "agents": getattr(agent, "agents", []),
118
129
  "mcps": getattr(agent, "mcps", []),
@@ -128,27 +139,21 @@ def get(ctx, agent_ref, select):
128
139
 
129
140
 
130
141
  @agents_group.command()
131
- @click.argument("agent_id")
142
+ @click.argument("agent_ref")
143
+ @click.option("--select", type=int, help="Choose among ambiguous matches (1-based)")
132
144
  @click.option("--input", "input_text", required=True, help="Input text for the agent")
133
145
  @click.option("--chat-history", help="JSON string of chat history")
134
- @click.option("--timeout", default=600, type=int, help="Request timeout in seconds")
135
146
  @click.option(
136
- "--view",
137
- type=click.Choice(["rich", "plain", "json", "md"]),
138
- default="rich",
139
- help="Output view format",
140
- )
141
- @click.option(
142
- "--compact/--verbose", default=True, help="Collapse tool steps (default: compact)"
147
+ "--timeout",
148
+ default=DEFAULT_AGENT_RUN_TIMEOUT,
149
+ type=int,
150
+ help="Agent execution timeout in seconds (default: 300s)",
143
151
  )
144
152
  @click.option(
145
153
  "--save",
146
154
  type=click.Path(dir_okay=False, writable=True),
147
155
  help="Save transcript to file (md or json)",
148
156
  )
149
- @click.option(
150
- "--theme", type=click.Choice(["dark", "light"]), default="dark", help="Color theme"
151
- )
152
157
  @click.option(
153
158
  "--file",
154
159
  "files",
@@ -156,28 +161,30 @@ def get(ctx, agent_ref, select):
156
161
  type=click.Path(exists=True),
157
162
  help="Attach file(s)",
158
163
  )
164
+ @click.option(
165
+ "--verbose/--no-verbose",
166
+ default=False,
167
+ help="Show detailed SSE events during streaming",
168
+ )
169
+ @output_flags()
159
170
  @click.pass_context
160
171
  def run(
161
172
  ctx,
162
- agent_id,
173
+ agent_ref,
174
+ select,
163
175
  input_text,
164
176
  chat_history,
165
177
  timeout,
166
- view,
167
- compact,
168
178
  save,
169
- theme,
170
179
  files,
180
+ verbose,
171
181
  ):
172
- """Run an agent with input text (ID required)."""
182
+ """Run an agent with input text (ID or name)."""
173
183
  try:
174
184
  client = get_client(ctx)
175
185
 
176
- # Get agent by ID (no ambiguity handling needed)
177
- try:
178
- agent = client.agents.get_agent_by_id(agent_id)
179
- except Exception as e:
180
- raise click.ClickException(f"Agent with ID '{agent_id}' not found: {e}")
186
+ # Resolve agent by ID or name (align with other commands)
187
+ agent = _resolve_agent(ctx, client, agent_ref, select)
181
188
 
182
189
  # Parse chat history if provided
183
190
  parsed_chat_history = None
@@ -187,85 +194,126 @@ def run(
187
194
  except json.JSONDecodeError:
188
195
  raise click.ClickException("Invalid JSON in chat history")
189
196
 
190
- # Always stream (no --no-stream option)
191
- stream = ctx.obj.get("tty", True)
197
+ # Create custom renderer with CLI flags
198
+ tty_enabled = bool((ctx.obj or {}).get("tty", True))
192
199
 
193
- # Create appropriate renderer based on view
194
- renderer = None
195
- if stream:
196
- from ...utils.run_renderer import RichStreamRenderer
200
+ # Build renderer and capturing console
201
+ renderer, working_console = build_renderer(
202
+ ctx,
203
+ save_path=save,
204
+ verbose=verbose,
205
+ tty_enabled=tty_enabled,
206
+ )
197
207
 
198
- # Use RichStreamRenderer for all streaming output
199
- # Different view formats are handled in the output logic below
200
- renderer = RichStreamRenderer(
201
- console, verbose=not compact, theme=theme, use_emoji=True
202
- )
208
+ # Set HTTP timeout to match agent timeout exactly
209
+ # This ensures the agent timeout controls the HTTP timeout
210
+ try:
211
+ client.timeout = float(timeout)
212
+ except Exception:
213
+ pass
214
+
215
+ # Ensure timeout is applied to the root client and subclients share its session
216
+ run_kwargs = {
217
+ "agent_id": agent.id,
218
+ "message": input_text,
219
+ "files": list(files),
220
+ "agent_name": agent.name, # Pass agent name for better display
221
+ "tty": tty_enabled,
222
+ }
203
223
 
204
- # Run agent
205
- result = client.agents.run_agent(
206
- agent_id=agent.id,
207
- message=input_text,
208
- files=list(files),
209
- stream=stream,
210
- agent_name=agent.name, # Pass agent name for better display
211
- **({"chat_history": parsed_chat_history} if parsed_chat_history else {}),
212
- **({"timeout": timeout} if timeout else {}),
213
- )
224
+ # Add optional parameters
225
+ if parsed_chat_history:
226
+ run_kwargs["chat_history"] = parsed_chat_history
227
+
228
+ # Pass custom renderer if available
229
+ if renderer is not None:
230
+ run_kwargs["renderer"] = renderer
231
+
232
+ # Pass timeout to client (verbose mode is handled by the renderer)
233
+ result = client.agents.run_agent(**run_kwargs, timeout=timeout)
214
234
 
215
235
  # Check if renderer already printed output (for streaming renderers)
216
- printed_by_renderer = bool(renderer and stream)
236
+ # Note: Auto-paging is handled by the renderer when view=="rich"
237
+ printed_by_renderer = bool(renderer)
238
+
239
+ # Resolve selected view from context (output_flags() stores it here)
240
+ selected_view = (ctx.obj or {}).get("view", "rich")
217
241
 
218
- # Handle output format for non-streaming or fallback
242
+ # Handle output format for fallback
219
243
  # Only print here if nothing was printed by the renderer
220
244
  if not printed_by_renderer:
221
- if (ctx.obj.get("view") == "json") or (view == "json"):
245
+ if selected_view == "json":
222
246
  click.echo(json.dumps({"output": result}, indent=2))
223
- elif view == "md":
247
+ elif selected_view == "md":
224
248
  click.echo(f"# Assistant\n\n{result}")
225
- elif view == "plain":
249
+ elif selected_view == "plain":
226
250
  click.echo(result)
227
- elif not stream:
228
- # Rich output for non-streaming
229
- panel = Panel(
230
- Text(result, style="green"),
231
- title="Agent Output",
232
- border_style="green",
233
- )
234
- console.print(panel)
235
251
 
236
252
  # Save transcript if requested
237
- if save and result:
253
+ if save:
238
254
  ext = (save.rsplit(".", 1)[-1] or "").lower()
239
255
  if ext == "json":
240
- content = json.dumps({"output": result}, indent=2)
241
- with open(save, "w", encoding="utf-8") as f:
242
- f.write(content)
256
+ # Save both the result and captured output
257
+ save_data = {
258
+ "output": result or "",
259
+ "full_debug_output": getattr(
260
+ working_console, "get_captured_output", lambda: ""
261
+ )(),
262
+ "timestamp": "captured during agent execution",
263
+ }
264
+ content = json.dumps(save_data, indent=2)
243
265
  else:
244
- content = f"# Assistant\n\n{result}\n"
245
- with open(save, "w", encoding="utf-8") as f:
246
- f.write(content)
247
- console.print(f"[green]Transcript saved to: {save}[/green]")
248
-
266
+ # For markdown/text files, save the full captured output if available
267
+ # Get the full captured output including all tool panels and debug info (if available)
268
+ full_output = getattr(
269
+ working_console, "get_captured_output", lambda: ""
270
+ )()
271
+ if full_output:
272
+ content = f"# Agent Debug Log\n\n{full_output}\n\n---\n\n## Final Result\n\n{result or ''}\n"
273
+ else:
274
+ # Fallback to simple format
275
+ content = f"# Assistant\n\n{result or ''}\n"
276
+
277
+ with open(save, "w", encoding="utf-8") as f:
278
+ f.write(content)
279
+ console.print(f"[green]Full debug output saved to: {save}[/green]")
280
+
281
+ except AgentTimeoutError as e:
282
+ # Handle agent timeout errors with specific messages
283
+ error_msg = str(e)
284
+ if ctx.obj.get("view") == "json":
285
+ click.echo(json.dumps({"error": error_msg}, indent=2))
286
+ # Don't print the error message here - Click.ClickException will handle it
287
+ raise click.ClickException(error_msg)
249
288
  except Exception as e:
250
289
  if ctx.obj.get("view") == "json":
251
290
  click.echo(json.dumps({"error": str(e)}, indent=2))
252
- else:
253
- console.print(f"[red]Error running agent: {e}[/red]")
291
+ # Don't print the error message here - Click.ClickException will handle it
254
292
  raise click.ClickException(str(e))
255
293
 
256
294
 
257
295
  @agents_group.command()
258
296
  @click.option("--name", required=True, help="Agent name")
259
297
  @click.option("--instruction", required=True, help="Agent instruction (prompt)")
298
+ @click.option(
299
+ "--model",
300
+ help=f"Language model to use (e.g., {DEFAULT_MODEL}, default: {DEFAULT_MODEL})",
301
+ )
260
302
  @click.option("--tools", multiple=True, help="Tool names or IDs to attach")
261
- @click.option("--agents", multiple=True, help="Sub-agent names to attach")
262
- @click.option("--timeout", default=300, type=int, help="Execution timeout in seconds")
303
+ @click.option("--agents", multiple=True, help="Sub-agent names or IDs to attach")
304
+ @click.option(
305
+ "--timeout",
306
+ default=DEFAULT_AGENT_RUN_TIMEOUT,
307
+ type=int,
308
+ help="Agent execution timeout in seconds (default: 300s)",
309
+ )
263
310
  @output_flags()
264
311
  @click.pass_context
265
312
  def create(
266
313
  ctx,
267
314
  name,
268
315
  instruction,
316
+ model,
269
317
  tools,
270
318
  agents,
271
319
  timeout,
@@ -273,14 +321,57 @@ def create(
273
321
  """Create a new agent."""
274
322
  try:
275
323
  client = get_client(ctx)
276
- # Create agent (uses backend default model)
277
- agent = client.agents.create_agent(
278
- name=name,
279
- instruction=instruction,
280
- tools=list(tools),
281
- agents=list(agents),
282
- timeout=timeout,
283
- )
324
+
325
+ # Resolve tool and agent references: accept names or IDs
326
+ def _resolve_tools(items: tuple[str, ...]) -> list[str]:
327
+ out: list[str] = []
328
+ for ref in list(items or ()): # tuple -> list
329
+ if is_uuid(ref):
330
+ out.append(ref)
331
+ continue
332
+ matches = client.find_tools(name=ref)
333
+ if not matches:
334
+ raise click.ClickException(f"Tool not found: {ref}")
335
+ if len(matches) > 1:
336
+ raise click.ClickException(
337
+ f"Multiple tools named '{ref}'. Use ID instead."
338
+ )
339
+ out.append(str(matches[0].id))
340
+ return out
341
+
342
+ def _resolve_agents(items: tuple[str, ...]) -> list[str]:
343
+ out: list[str] = []
344
+ for ref in list(items or ()): # tuple -> list
345
+ if is_uuid(ref):
346
+ out.append(ref)
347
+ continue
348
+ matches = client.find_agents(name=ref)
349
+ if not matches:
350
+ raise click.ClickException(f"Agent not found: {ref}")
351
+ if len(matches) > 1:
352
+ raise click.ClickException(
353
+ f"Multiple agents named '{ref}'. Use ID instead."
354
+ )
355
+ out.append(str(matches[0].id))
356
+ return out
357
+
358
+ resolved_tools = _resolve_tools(tools)
359
+ resolved_agents = _resolve_agents(agents)
360
+
361
+ # Create agent with optional model specification
362
+ create_kwargs = {
363
+ "name": name,
364
+ "instruction": instruction,
365
+ "tools": resolved_tools or None,
366
+ "agents": resolved_agents or None,
367
+ "timeout": timeout,
368
+ }
369
+
370
+ # Add model if specified
371
+ if model:
372
+ create_kwargs["model"] = model
373
+
374
+ agent = client.agents.create_agent(**create_kwargs)
284
375
 
285
376
  if ctx.obj.get("view") == "json":
286
377
  click.echo(json.dumps(agent.model_dump(), indent=2))
@@ -292,7 +383,8 @@ def create(
292
383
  lm = (
293
384
  cfg.get("lm_name")
294
385
  or cfg.get("model")
295
- or "gpt-4.1 (backend default)"
386
+ or model # Use CLI model if specified
387
+ or f"{DEFAULT_MODEL} (backend default)"
296
388
  )
297
389
 
298
390
  panel = Panel(
@@ -352,7 +444,7 @@ def update(ctx, agent_id, name, instruction, tools, agents, timeout):
352
444
  raise click.ClickException("No update fields specified")
353
445
 
354
446
  # Update agent
355
- updated_agent = client.agents.update_agent(agent.id, update_data)
447
+ updated_agent = client.agents.update_agent(agent.id, **update_data)
356
448
 
357
449
  if ctx.obj.get("view") == "json":
358
450
  click.echo(json.dumps(updated_agent.model_dump(), indent=2))
@@ -14,6 +14,8 @@ from rich.console import Console
14
14
  from rich.panel import Panel
15
15
  from rich.table import Table
16
16
 
17
+ from glaip_sdk import Client
18
+
17
19
  console = Console()
18
20
 
19
21
  CONFIG_DIR = Path.home() / ".aip"
@@ -25,8 +27,11 @@ def load_config():
25
27
  if not CONFIG_FILE.exists():
26
28
  return {}
27
29
 
28
- with open(CONFIG_FILE) as f:
29
- return yaml.safe_load(f) or {}
30
+ try:
31
+ with open(CONFIG_FILE) as f:
32
+ return yaml.safe_load(f) or {}
33
+ except yaml.YAMLError:
34
+ return {}
30
35
 
31
36
 
32
37
  def save_config(config):
@@ -49,82 +54,6 @@ def config_group():
49
54
  pass
50
55
 
51
56
 
52
- @config_group.command()
53
- def configure():
54
- """Configure AIP CLI credentials and settings interactively."""
55
-
56
- console.print(
57
- Panel(
58
- "[bold cyan]AIP Configuration[/bold cyan]\nConfigure your AIP CLI settings.",
59
- title="🔧 Configuration Setup",
60
- border_style="cyan",
61
- )
62
- )
63
-
64
- # Load existing config
65
- config = load_config()
66
-
67
- console.print("\n[bold]Enter your AIP configuration:[/bold]")
68
- console.print("(Leave blank to keep current values)")
69
- console.print("─" * 50)
70
-
71
- # API URL
72
- current_url = config.get("api_url", "")
73
- console.print(
74
- f"\n[cyan]AIP API URL[/cyan] {f'(current: {current_url})' if current_url else ''}:"
75
- )
76
- new_url = input("> ").strip()
77
- if new_url:
78
- config["api_url"] = new_url
79
- elif not current_url:
80
- config["api_url"] = "https://your-aip-instance.com"
81
-
82
- # API Key
83
- current_key_masked = (
84
- "***" + config.get("api_key", "")[-4:] if config.get("api_key") else ""
85
- )
86
- console.print(
87
- f"\n[cyan]AIP API Key[/cyan] {f'(current: {current_key_masked})' if current_key_masked else ''}:"
88
- )
89
- new_key = getpass.getpass("> ")
90
- if new_key:
91
- config["api_key"] = new_key
92
-
93
- # Save configuration
94
- save_config(config)
95
-
96
- console.print(f"\n✅ Configuration saved to: {CONFIG_FILE}")
97
-
98
- # Test the new configuration
99
- console.print("\n🔌 Testing connection...")
100
- try:
101
- from glaip_sdk import Client
102
-
103
- # Create client with new config
104
- client = Client(api_url=config["api_url"], api_key=config["api_key"])
105
-
106
- # Try to list resources to test connection
107
- try:
108
- agents = client.list_agents()
109
- console.print(f"✅ Connection successful! Found {len(agents)} agents")
110
- except Exception as e:
111
- console.print(f"⚠️ Connection established but API call failed: {e}")
112
- console.print(
113
- " You may need to check your API permissions or network access"
114
- )
115
-
116
- client.close()
117
-
118
- except Exception as e:
119
- console.print(f"❌ Connection failed: {e}")
120
- console.print(" Please check your API URL and key")
121
- console.print(" You can run 'aip status' later to test again")
122
-
123
- console.print("\n💡 You can now use AIP CLI commands!")
124
- console.print(" • Run 'aip status' to check connection")
125
- console.print(" • Run 'aip agents list' to see your agents")
126
-
127
-
128
57
  @config_group.command("list")
129
58
  def list_config():
130
59
  """List current configuration."""
@@ -237,13 +166,8 @@ def reset_config(force):
237
166
  console.print("[yellow]No configuration found to reset.[/yellow]")
238
167
 
239
168
 
240
- # Note: The config command group should be registered in main.py
241
-
242
-
243
- @click.command()
244
- def configure_command():
245
- """Configure AIP CLI credentials and settings interactively."""
246
-
169
+ def _configure_interactive():
170
+ """Shared configuration logic for both configure commands."""
247
171
  console.print(
248
172
  Panel(
249
173
  "[bold cyan]AIP Configuration[/bold cyan]\nConfigure your AIP CLI settings.",
@@ -289,8 +213,6 @@ def configure_command():
289
213
  # Test the new configuration
290
214
  console.print("\n🔌 Testing connection...")
291
215
  try:
292
- from glaip_sdk import Client
293
-
294
216
  # Create client with new config
295
217
  client = Client(api_url=config["api_url"], api_key=config["api_key"])
296
218
 
@@ -314,3 +236,23 @@ def configure_command():
314
236
  console.print("\n💡 You can now use AIP CLI commands!")
315
237
  console.print(" • Run 'aip status' to check connection")
316
238
  console.print(" • Run 'aip agents list' to see your agents")
239
+
240
+
241
+ @config_group.command()
242
+ def configure():
243
+ """Configure AIP CLI credentials and settings interactively."""
244
+ _configure_interactive()
245
+
246
+
247
+ # Alias command for backward compatibility
248
+ @click.command()
249
+ def configure_command():
250
+ """Configure AIP CLI credentials and settings interactively.
251
+
252
+ This is an alias for 'aip config configure' for backward compatibility.
253
+ """
254
+ # Delegate to the shared function
255
+ _configure_interactive()
256
+
257
+
258
+ # Note: The config command group should be registered in main.py