healthcloud-cli 3.2.4__tar.gz → 3.3.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.
Files changed (53) hide show
  1. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/PKG-INFO +108 -1
  2. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/README.md +107 -0
  3. healthcloud_cli-3.3.0/healthcloud_cli/ams/agents/__init__.py +5 -0
  4. healthcloud_cli-3.3.0/healthcloud_cli/ams/agents/commands.py +83 -0
  5. healthcloud_cli-3.3.0/healthcloud_cli/ams/agents/group.py +18 -0
  6. healthcloud_cli-3.3.0/healthcloud_cli/ams/agents/operations.py +72 -0
  7. healthcloud_cli-3.3.0/healthcloud_cli/ams/agents/projections.py +37 -0
  8. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/ams/auth.py +6 -2
  9. healthcloud_cli-3.3.0/healthcloud_cli/ams/commands.py +28 -0
  10. healthcloud_cli-3.3.0/healthcloud_cli/ams/compatibility.py +264 -0
  11. healthcloud_cli-3.3.0/healthcloud_cli/ams/context.py +143 -0
  12. healthcloud_cli-3.3.0/healthcloud_cli/ams/departments/__init__.py +5 -0
  13. healthcloud_cli-3.3.0/healthcloud_cli/ams/departments/commands.py +59 -0
  14. healthcloud_cli-3.3.0/healthcloud_cli/ams/departments/group.py +18 -0
  15. healthcloud_cli-3.3.0/healthcloud_cli/ams/departments/operations.py +71 -0
  16. healthcloud_cli-3.3.0/healthcloud_cli/ams/departments/projections.py +19 -0
  17. healthcloud_cli-3.3.0/healthcloud_cli/ams/domain_errors.py +9 -0
  18. healthcloud_cli-3.3.0/healthcloud_cli/ams/errors.py +87 -0
  19. healthcloud_cli-3.3.0/healthcloud_cli/ams/group.py +30 -0
  20. healthcloud_cli-3.3.0/healthcloud_cli/ams/organization/__init__.py +5 -0
  21. healthcloud_cli-3.3.0/healthcloud_cli/ams/organization/commands.py +28 -0
  22. healthcloud_cli-3.3.0/healthcloud_cli/ams/organization/group.py +17 -0
  23. healthcloud_cli-3.3.0/healthcloud_cli/ams/organization/operations.py +33 -0
  24. healthcloud_cli-3.3.0/healthcloud_cli/ams/organization/projections.py +29 -0
  25. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/ams/output.py +17 -3
  26. healthcloud_cli-3.3.0/healthcloud_cli/ams/registry.py +71 -0
  27. healthcloud_cli-3.3.0/healthcloud_cli/ams/results.py +31 -0
  28. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/ams/target.py +14 -0
  29. healthcloud_cli-3.3.0/healthcloud_cli/ams/teams/__init__.py +5 -0
  30. healthcloud_cli-3.3.0/healthcloud_cli/ams/teams/commands.py +55 -0
  31. healthcloud_cli-3.3.0/healthcloud_cli/ams/teams/group.py +18 -0
  32. healthcloud_cli-3.3.0/healthcloud_cli/ams/teams/operations.py +67 -0
  33. healthcloud_cli-3.3.0/healthcloud_cli/ams/teams/projections.py +21 -0
  34. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/cli.py +10 -9
  35. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/contracts/mcp-tools.manifest.json +690 -237
  36. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/pyproject.toml +1 -1
  37. healthcloud_cli-3.2.4/healthcloud_cli/ams/commands.py +0 -242
  38. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/.gitignore +0 -0
  39. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/hatch_build.py +0 -0
  40. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/__init__.py +0 -0
  41. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/ams/__init__.py +0 -0
  42. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/ams/tuning.py +0 -0
  43. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/ams/ui.py +0 -0
  44. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/ams/windows_files.py +0 -0
  45. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/context.py +0 -0
  46. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/contracts/__init__.py +0 -0
  47. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/main.py +0 -0
  48. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/manifest.py +0 -0
  49. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/output.py +0 -0
  50. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/params.py +0 -0
  51. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/healthcloud_cli/util.py +0 -0
  52. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/scripts/__init__.py +0 -0
  53. {healthcloud_cli-3.2.4 → healthcloud_cli-3.3.0}/scripts/bundle_manifest.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: healthcloud-cli
3
- Version: 3.2.4
3
+ Version: 3.3.0
4
4
  Summary: Command-line interface for the HealthCloud MCP gateway, generated from the shared MCP tool manifest.
5
5
  Project-URL: Issues, https://github.com/healthcloudservices/healthcloud-services/issues
6
6
  Project-URL: Repository, https://github.com/healthcloudservices/healthcloud-services
@@ -56,6 +56,24 @@ uv run --frozen hc mcp tools list --audience patient
56
56
  uv run --frozen hc mcp patient get-patient --help
57
57
  ```
58
58
 
59
+ AMS help and shell-completion discovery are credential-free and offline: they
60
+ do not resolve root profiles, read target files, create SDK clients, or log in.
61
+ You can inspect the group, a leaf command, and completion candidates before
62
+ configuring credentials or a target:
63
+
64
+ ```bash
65
+ uv run --frozen hc ams --help
66
+ uv run --frozen hc ams inspect --help
67
+ _HC_COMPLETE=bash_complete COMP_WORDS="hc ams in" COMP_CWORD=2 uv run --frozen hc
68
+ _HC_COMPLETE=bash_complete COMP_WORDS="hc ams inspect --o" COMP_CWORD=3 uv run --frozen hc
69
+ ```
70
+
71
+ Operational validation starts only when Click invokes an AMS leaf callback.
72
+ `discover` then validates its explicit environment and tenant arguments; the
73
+ other leaves require `--target-file` and apply the existing root, target, and
74
+ authentication checks described below. Deferring preflight does not relax
75
+ those safety requirements.
76
+
59
77
  The bundled tool manifest (`healthcloud_cli/contracts/mcp-tools.manifest.json`)
60
78
  is a copy of `packages/cli/contracts/mcp-tools.manifest.json`. Re-sync it
61
79
  after pulling manifest changes with:
@@ -269,6 +287,54 @@ required; no MCP defaults, admin login, or automatic candidate selection are use
269
287
  uv run --frozen hc ams discover --environment dev --tenant-id TENANT_ID
270
288
  ```
271
289
 
290
+ Read-only agent listing, inspection and export also take explicit flags instead
291
+ of a target file, and log in as tenant admin (`HC_AMS_ADMIN_EMAIL` /
292
+ `HC_AMS_ADMIN_PASSWORD`). Stdout carries bounded metadata only; `adapter` shows
293
+ whether an agent runs from the `file` or `db` config adapter (`unknown` when the
294
+ server does not say). `export` first re-checks the agent/tenant identity, then
295
+ saves the server's own JSON/YAML export byte-for-byte to a new owner-only `--out`
296
+ file, refusing (and leaving no file) when the body is not valid UTF-8. `export`
297
+ needs an AMS build that serves `/agents/{agent_id}/configuration/export`; that
298
+ route is not yet deployed to dev:
299
+
300
+ ```bash
301
+ uv run --frozen hc ams agents list --environment dev --tenant-id TENANT_ID
302
+ uv run --frozen hc ams agents get --environment dev --tenant-id TENANT_ID --agent-id AGENT_ID
303
+ uv run --frozen hc ams agents export --environment dev --tenant-id TENANT_ID --agent-id AGENT_ID \
304
+ --format yaml --out agent.yaml
305
+ ```
306
+
307
+ Read-only department listing and inspection use the same explicit tenant-admin
308
+ targeting. They emit one versioned JSON result on stdout; failures emit one
309
+ categorized JSON result on stderr. Department descriptions, creator identities,
310
+ and evaluation-suite content are never included in the projection:
311
+
312
+ ```bash
313
+ uv run --frozen hc ams departments list --environment dev --tenant-id TENANT_ID
314
+ uv run --frozen hc ams departments get --environment dev --tenant-id TENANT_ID \
315
+ --department-id DEPARTMENT_ID
316
+ ```
317
+
318
+ Department reads are complete and unpaged. The CLI does not expose department
319
+ create, update, delete, activate, or deactivate operations.
320
+
321
+ Read-only team listing and inspection are also complete and unpaged. The bounded
322
+ projection omits descriptions and creator identities; it includes evaluation-suite
323
+ IDs but never suite content. The CLI does not expose team mutations:
324
+
325
+ ```bash
326
+ uv run --frozen hc ams teams list --environment dev --tenant-id TENANT_ID
327
+ uv run --frozen hc ams teams get --environment dev --tenant-id TENANT_ID --team-id TEAM_ID
328
+ ```
329
+
330
+ The organization hierarchy is the backend-built department/team/agent tree. The
331
+ CLI preserves its order and nesting without issuing entity reads, joining records,
332
+ or inferring missing relationships:
333
+
334
+ ```bash
335
+ uv run --frozen hc ams organization hierarchy --environment dev --tenant-id TENANT_ID
336
+ ```
337
+
272
338
  Every other command requires an explicit local, non-secret target JSON file.
273
339
  Only the canonical HTTPS AMS origin for that environment is accepted; custom
274
340
  origins require separately verified support. Target and nested qualification keys
@@ -441,3 +507,44 @@ then GET plan and GET agent. Restart the stateful simulation before repeating;
441
507
  stop it with Ctrl+C afterward. This is a source-backed synthetic failure fixture,
442
508
  not the deployed service. The required CI tests exercise both pre-commit and
443
509
  post-commit 503 over loopback HTTP without requiring a Postman login or installation.
510
+
511
+
512
+ ### Installed CLI integration
513
+
514
+ From the repository root, with Python 3.14, uv 0.12.5 and Node.js available:
515
+
516
+ ```bash
517
+ python -m unittest discover -s tooling/cli-integration -p 'test_*.py' -v
518
+ python tooling/cli-integration/run.py --output /tmp/hc-cli-integration
519
+ ```
520
+
521
+ The runner installs the SDK, tenant-admin SDK and CLI from the frozen CLI lock
522
+ with `uv sync --no-editable` into a fresh temporary environment outside the
523
+ checkout. It verifies installed module ownership, version, help for every AMS
524
+ command, native shell completion, discovery and the
525
+ inspect/plan/show/apply/restore journey. Negative
526
+ journeys exercise denied login, identity/qualification/plan rejection, malformed
527
+ responses, failed readback and uncertain writes without mutation replay.
528
+
529
+ The `Installed CLI integration` workflow selects its uv-installed Linux job at
530
+ job level for `packages/cli/**`, `packages/pip/**`,
531
+ `packages/pip-tenantadmin/**`, its own tooling/workflow, docs compiler inputs
532
+ (`tooling/docs-compiler/**`, `docs-src/**`, `docs/sdk/**`), MCP contracts
533
+ (`contracts/mcp/**`), and root package/lock, Python/lock and Ruff inputs. Backend-only changes skip the install job;
534
+ the small `CLI integration` result check still runs and verifies applicability.
535
+ Invalid selection or an applicable failed/cancelled/skipped job fails it.
536
+ The job retains only sanitized consumer/JUnit results for seven days.
537
+
538
+ All scenario records and credentials are synthetic. A test-only Python startup
539
+ adapter preserves the canonical DEV AMS/tenant-admin origins for product
540
+ validation while routing only those exact origins over real HTTP to an owned
541
+ loopback fixture; other origins are refused before network access. Child
542
+ processes use an isolated home and an allowlisted environment. The adapter is
543
+ outside all shipped distributions. This boundary does not verify TLS or deployed
544
+ service conformance. A verified save still exits 1 with `outcome="saved"` and
545
+ `publication="unknown"`; publication and runtime acceptance remain unverified.
546
+
547
+ Required-check activation is tracked separately in
548
+ [services #724](https://github.com/healthcloudservices/healthcloud-services/issues/724).
549
+ Require `CLI integration` only after the workflow is merged and both relevant
550
+ and unrelated-change Actions results and effective settings are verified.
@@ -38,6 +38,24 @@ uv run --frozen hc mcp tools list --audience patient
38
38
  uv run --frozen hc mcp patient get-patient --help
39
39
  ```
40
40
 
41
+ AMS help and shell-completion discovery are credential-free and offline: they
42
+ do not resolve root profiles, read target files, create SDK clients, or log in.
43
+ You can inspect the group, a leaf command, and completion candidates before
44
+ configuring credentials or a target:
45
+
46
+ ```bash
47
+ uv run --frozen hc ams --help
48
+ uv run --frozen hc ams inspect --help
49
+ _HC_COMPLETE=bash_complete COMP_WORDS="hc ams in" COMP_CWORD=2 uv run --frozen hc
50
+ _HC_COMPLETE=bash_complete COMP_WORDS="hc ams inspect --o" COMP_CWORD=3 uv run --frozen hc
51
+ ```
52
+
53
+ Operational validation starts only when Click invokes an AMS leaf callback.
54
+ `discover` then validates its explicit environment and tenant arguments; the
55
+ other leaves require `--target-file` and apply the existing root, target, and
56
+ authentication checks described below. Deferring preflight does not relax
57
+ those safety requirements.
58
+
41
59
  The bundled tool manifest (`healthcloud_cli/contracts/mcp-tools.manifest.json`)
42
60
  is a copy of `packages/cli/contracts/mcp-tools.manifest.json`. Re-sync it
43
61
  after pulling manifest changes with:
@@ -251,6 +269,54 @@ required; no MCP defaults, admin login, or automatic candidate selection are use
251
269
  uv run --frozen hc ams discover --environment dev --tenant-id TENANT_ID
252
270
  ```
253
271
 
272
+ Read-only agent listing, inspection and export also take explicit flags instead
273
+ of a target file, and log in as tenant admin (`HC_AMS_ADMIN_EMAIL` /
274
+ `HC_AMS_ADMIN_PASSWORD`). Stdout carries bounded metadata only; `adapter` shows
275
+ whether an agent runs from the `file` or `db` config adapter (`unknown` when the
276
+ server does not say). `export` first re-checks the agent/tenant identity, then
277
+ saves the server's own JSON/YAML export byte-for-byte to a new owner-only `--out`
278
+ file, refusing (and leaving no file) when the body is not valid UTF-8. `export`
279
+ needs an AMS build that serves `/agents/{agent_id}/configuration/export`; that
280
+ route is not yet deployed to dev:
281
+
282
+ ```bash
283
+ uv run --frozen hc ams agents list --environment dev --tenant-id TENANT_ID
284
+ uv run --frozen hc ams agents get --environment dev --tenant-id TENANT_ID --agent-id AGENT_ID
285
+ uv run --frozen hc ams agents export --environment dev --tenant-id TENANT_ID --agent-id AGENT_ID \
286
+ --format yaml --out agent.yaml
287
+ ```
288
+
289
+ Read-only department listing and inspection use the same explicit tenant-admin
290
+ targeting. They emit one versioned JSON result on stdout; failures emit one
291
+ categorized JSON result on stderr. Department descriptions, creator identities,
292
+ and evaluation-suite content are never included in the projection:
293
+
294
+ ```bash
295
+ uv run --frozen hc ams departments list --environment dev --tenant-id TENANT_ID
296
+ uv run --frozen hc ams departments get --environment dev --tenant-id TENANT_ID \
297
+ --department-id DEPARTMENT_ID
298
+ ```
299
+
300
+ Department reads are complete and unpaged. The CLI does not expose department
301
+ create, update, delete, activate, or deactivate operations.
302
+
303
+ Read-only team listing and inspection are also complete and unpaged. The bounded
304
+ projection omits descriptions and creator identities; it includes evaluation-suite
305
+ IDs but never suite content. The CLI does not expose team mutations:
306
+
307
+ ```bash
308
+ uv run --frozen hc ams teams list --environment dev --tenant-id TENANT_ID
309
+ uv run --frozen hc ams teams get --environment dev --tenant-id TENANT_ID --team-id TEAM_ID
310
+ ```
311
+
312
+ The organization hierarchy is the backend-built department/team/agent tree. The
313
+ CLI preserves its order and nesting without issuing entity reads, joining records,
314
+ or inferring missing relationships:
315
+
316
+ ```bash
317
+ uv run --frozen hc ams organization hierarchy --environment dev --tenant-id TENANT_ID
318
+ ```
319
+
254
320
  Every other command requires an explicit local, non-secret target JSON file.
255
321
  Only the canonical HTTPS AMS origin for that environment is accepted; custom
256
322
  origins require separately verified support. Target and nested qualification keys
@@ -423,3 +489,44 @@ then GET plan and GET agent. Restart the stateful simulation before repeating;
423
489
  stop it with Ctrl+C afterward. This is a source-backed synthetic failure fixture,
424
490
  not the deployed service. The required CI tests exercise both pre-commit and
425
491
  post-commit 503 over loopback HTTP without requiring a Postman login or installation.
492
+
493
+
494
+ ### Installed CLI integration
495
+
496
+ From the repository root, with Python 3.14, uv 0.12.5 and Node.js available:
497
+
498
+ ```bash
499
+ python -m unittest discover -s tooling/cli-integration -p 'test_*.py' -v
500
+ python tooling/cli-integration/run.py --output /tmp/hc-cli-integration
501
+ ```
502
+
503
+ The runner installs the SDK, tenant-admin SDK and CLI from the frozen CLI lock
504
+ with `uv sync --no-editable` into a fresh temporary environment outside the
505
+ checkout. It verifies installed module ownership, version, help for every AMS
506
+ command, native shell completion, discovery and the
507
+ inspect/plan/show/apply/restore journey. Negative
508
+ journeys exercise denied login, identity/qualification/plan rejection, malformed
509
+ responses, failed readback and uncertain writes without mutation replay.
510
+
511
+ The `Installed CLI integration` workflow selects its uv-installed Linux job at
512
+ job level for `packages/cli/**`, `packages/pip/**`,
513
+ `packages/pip-tenantadmin/**`, its own tooling/workflow, docs compiler inputs
514
+ (`tooling/docs-compiler/**`, `docs-src/**`, `docs/sdk/**`), MCP contracts
515
+ (`contracts/mcp/**`), and root package/lock, Python/lock and Ruff inputs. Backend-only changes skip the install job;
516
+ the small `CLI integration` result check still runs and verifies applicability.
517
+ Invalid selection or an applicable failed/cancelled/skipped job fails it.
518
+ The job retains only sanitized consumer/JUnit results for seven days.
519
+
520
+ All scenario records and credentials are synthetic. A test-only Python startup
521
+ adapter preserves the canonical DEV AMS/tenant-admin origins for product
522
+ validation while routing only those exact origins over real HTTP to an owned
523
+ loopback fixture; other origins are refused before network access. Child
524
+ processes use an isolated home and an allowlisted environment. The adapter is
525
+ outside all shipped distributions. This boundary does not verify TLS or deployed
526
+ service conformance. A verified save still exits 1 with `outcome="saved"` and
527
+ `publication="unknown"`; publication and runtime acceptance remain unverified.
528
+
529
+ Required-check activation is tracked separately in
530
+ [services #724](https://github.com/healthcloudservices/healthcloud-services/issues/724).
531
+ Require `CLI integration` only after the workflow is merged and both relevant
532
+ and unrelated-change Actions results and effective settings are verified.
@@ -0,0 +1,5 @@
1
+ """Read-only AMS agent command domain."""
2
+
3
+ from .group import create_agents_group
4
+
5
+ __all__ = ["create_agents_group"]
@@ -0,0 +1,83 @@
1
+ """Fresh Click adapters for read-only agent operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import click
8
+
9
+ from ..compatibility import safe_errors
10
+ from ..context import AmsDependencies, pass_ams, preflight_agents
11
+ from ..output import metadata
12
+ from ..target import TenantTarget
13
+ from .operations import AgentReadRefused, export_agent, get_agent, list_agents
14
+
15
+
16
+ def _environment(function):
17
+ return click.option("--environment", type=click.Choice(["dev", "uat", "prod"]), required=True)(function)
18
+
19
+
20
+ def _tenant(function):
21
+ return click.option("--tenant-id", required=True)(function)
22
+
23
+
24
+ def _agent(function):
25
+ return click.option("--agent-id", required=True)(function)
26
+
27
+
28
+ def _run(operation, *args):
29
+ try:
30
+ return operation(*args)
31
+ except AgentReadRefused as exc:
32
+ raise click.ClickException(str(exc)) from None
33
+
34
+
35
+ def create_list_command(dependencies: AmsDependencies) -> click.Command:
36
+ @click.command("list")
37
+ @_environment
38
+ @_tenant
39
+ @pass_ams
40
+ @safe_errors
41
+ def command(invocation, environment: str, tenant_id: str) -> None:
42
+ preflight_agents(invocation)
43
+ metadata(_run(list_agents, dependencies, TenantTarget(environment, tenant_id)))
44
+
45
+ return command
46
+
47
+
48
+ def create_get_command(dependencies: AmsDependencies) -> click.Command:
49
+ @click.command("get")
50
+ @_environment
51
+ @_tenant
52
+ @_agent
53
+ @pass_ams
54
+ @safe_errors
55
+ def command(invocation, environment: str, tenant_id: str, agent_id: str) -> None:
56
+ preflight_agents(invocation)
57
+ metadata(_run(get_agent, dependencies, TenantTarget(environment, tenant_id), agent_id))
58
+
59
+ return command
60
+
61
+
62
+ def create_export_command(dependencies: AmsDependencies) -> click.Command:
63
+ @click.command("export")
64
+ @_environment
65
+ @_tenant
66
+ @_agent
67
+ @click.option("--out", required=True, type=click.Path(path_type=Path, dir_okay=False))
68
+ @click.option("--format", "export_format", type=click.Choice(["json", "yaml"]), default="json", show_default=True)
69
+ @pass_ams
70
+ @safe_errors
71
+ def command(
72
+ invocation,
73
+ environment: str,
74
+ tenant_id: str,
75
+ agent_id: str,
76
+ out: Path,
77
+ export_format: str,
78
+ ) -> None:
79
+ preflight_agents(invocation)
80
+ target = TenantTarget(environment, tenant_id)
81
+ metadata(_run(export_agent, dependencies, target, agent_id, out, export_format))
82
+
83
+ return command
@@ -0,0 +1,18 @@
1
+ """Fresh agent-domain group assembly."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+
7
+ from ..context import AmsDependencies
8
+ from .commands import create_export_command, create_get_command, create_list_command
9
+
10
+
11
+ def create_agents_group(dependencies: AmsDependencies) -> click.Group:
12
+ @click.group("agents")
13
+ def group() -> None:
14
+ """Read-only agent listing, inspection and export for one explicit tenant."""
15
+
16
+ for factory in (create_list_command, create_get_command, create_export_command):
17
+ group.add_command(factory(dependencies))
18
+ return group
@@ -0,0 +1,72 @@
1
+ """Click-free orchestration for agent list, get, and exact-text export."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ from pathlib import Path
7
+ from typing import Literal
8
+
9
+ from ..context import AmsDependencies
10
+ from ..output import private_text_output
11
+ from ..target import TenantTarget
12
+ from .projections import detail, summary
13
+
14
+
15
+ class AgentReadRefused(ValueError):
16
+ """A stable refusal safe for the CLI adapter to expose."""
17
+
18
+
19
+ def _agent_id(value: str) -> str:
20
+ if not isinstance(value, str) or not value.strip():
21
+ raise AgentReadRefused("Explicit agent ID required.")
22
+ return value
23
+
24
+
25
+ def list_agents(dependencies: AmsDependencies, target: TenantTarget) -> dict:
26
+ with dependencies.authenticated_sdk_factory(target) as sdk:
27
+ agents = sdk.ams_admin.list_agents()
28
+ if any(agent.tenant_id != target.tenant_id for agent in agents):
29
+ raise AgentReadRefused("Agent/tenant identity mismatch; stopped.")
30
+ return {
31
+ "environment": target.environment,
32
+ "tenant_id": target.tenant_id,
33
+ "agents": [summary(agent) for agent in agents],
34
+ }
35
+
36
+
37
+ def _verified_agent(sdk, target: TenantTarget, agent_id: str):
38
+ agent = sdk.ams_admin.get_agent(agent_id)
39
+ if agent is None or agent.id != agent_id or agent.tenant_id != target.tenant_id:
40
+ raise AgentReadRefused("Agent/tenant identity mismatch; stopped.")
41
+ return agent
42
+
43
+
44
+ def get_agent(dependencies: AmsDependencies, target: TenantTarget, agent_id: str) -> dict:
45
+ agent_id = _agent_id(agent_id)
46
+ with dependencies.authenticated_sdk_factory(target) as sdk:
47
+ agent = _verified_agent(sdk, target, agent_id)
48
+ return {"environment": target.environment, "tenant_id": target.tenant_id} | detail(agent)
49
+
50
+
51
+ def export_agent(
52
+ dependencies: AmsDependencies,
53
+ target: TenantTarget,
54
+ agent_id: str,
55
+ output: Path,
56
+ export_format: Literal["json", "yaml"],
57
+ ) -> dict:
58
+ agent_id = _agent_id(agent_id)
59
+ if export_format not in ("json", "yaml"):
60
+ raise AgentReadRefused("Export format must be json or yaml.")
61
+ with private_text_output(output) as write, dependencies.authenticated_sdk_factory(target) as sdk:
62
+ _verified_agent(sdk, target, agent_id)
63
+ text = sdk.ams_admin.export_agent_configuration(agent_id, format=export_format)
64
+ write(text)
65
+ return {
66
+ "environment": target.environment,
67
+ "tenant_id": target.tenant_id,
68
+ "agent_id": agent_id,
69
+ "path": str(output),
70
+ "format": export_format,
71
+ "sha256": hashlib.sha256(text.encode("utf-8")).hexdigest(),
72
+ }
@@ -0,0 +1,37 @@
1
+ """Explicit bounded projections for agent reads."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from healthcloud.models.ams_tuning import AgentConfig
6
+
7
+ from ..output import digest
8
+
9
+ ASSIGNED_REFERENCES = ("tool_ids", "skill_ids", "guardrail_ids", "policy_ids", "data_source_ids")
10
+
11
+
12
+ def _metadata_string(value: object) -> str:
13
+ return value if isinstance(value, str) and value.strip() else "unknown"
14
+
15
+
16
+ def summary(agent: AgentConfig) -> dict:
17
+ runtime = agent.runtime_configuration if isinstance(agent.runtime_configuration, dict) else {}
18
+ return {
19
+ "id": agent.id,
20
+ "name": agent.name,
21
+ "key": agent.key,
22
+ "type": agent.type,
23
+ "status": agent.status,
24
+ "active": agent.active,
25
+ "config_version": agent.config_version,
26
+ "adapter": _metadata_string(runtime.get("adapter")),
27
+ "runtime": _metadata_string(runtime.get("runtime")),
28
+ }
29
+
30
+
31
+ def detail(agent: AgentConfig) -> dict:
32
+ values = {"instructions": agent.instructions, "greetings": agent.greetings}
33
+ return summary(agent) | {
34
+ "fields": sorted(values),
35
+ "field_hashes": {key: digest(value) for key, value in values.items()},
36
+ "assigned_references": {key: getattr(agent, key) for key in ASSIGNED_REFERENCES},
37
+ }
@@ -8,17 +8,21 @@ import click
8
8
  from healthcloud import HCSDK
9
9
  from healthcloud_tenantadmin import HCTenantAdminSDK
10
10
 
11
- from .target import AmsTarget
11
+ from .target import AmsTarget, TenantTarget
12
12
 
13
13
 
14
14
  @contextmanager
15
- def authenticated_sdk(target: AmsTarget):
15
+ def authenticated_sdk(target: AmsTarget | TenantTarget):
16
16
  sdk = HCSDK(environment=target.environment, tenant_id=target.tenant_id)
17
17
  try:
18
18
  if not hasattr(sdk, "ams_admin"):
19
19
  raise click.ClickException(
20
20
  "SDK missing ams_admin; use the reviewed local editable SDK checkout (see CLI README)."
21
21
  )
22
+ if isinstance(target, TenantTarget) and not hasattr(sdk.ams_admin, "list_agents"):
23
+ raise click.ClickException(
24
+ "SDK missing ams_admin agent listing; use the reviewed local editable SDK checkout (see CLI README)."
25
+ )
22
26
  email = os.environ.get("HC_AMS_ADMIN_EMAIL")
23
27
  password = os.environ.get("HC_AMS_ADMIN_PASSWORD")
24
28
  if not email or not password:
@@ -0,0 +1,28 @@
1
+ """Legacy AMS command imports backed by one compatibility assembly."""
2
+
3
+ from .compatibility import safe_errors
4
+ from .group import create_ams_group
5
+
6
+ ams = create_ams_group()
7
+ agents = ams.commands["agents"]
8
+ discover = ams.commands["discover"]
9
+ inspect = ams.commands["inspect"]
10
+ plan = ams.commands["plan"]
11
+ show = ams.commands["show"]
12
+ apply = ams.commands["apply"]
13
+ restore = ams.commands["restore"]
14
+ ui = ams.commands["ui"]
15
+
16
+ __all__ = [
17
+ "ams",
18
+ "agents",
19
+ "apply",
20
+ "create_ams_group",
21
+ "discover",
22
+ "inspect",
23
+ "plan",
24
+ "restore",
25
+ "safe_errors",
26
+ "show",
27
+ "ui",
28
+ ]