healthcloud-cli 3.7.1__tar.gz → 3.7.3__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 (92) hide show
  1. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/PKG-INFO +107 -18
  2. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/README.md +106 -17
  3. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/agents/commands.py +79 -10
  4. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/agents/operations.py +149 -24
  5. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/errors.py +43 -33
  6. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/pretty.py +128 -1
  7. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/cli.py +17 -9
  8. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/contracts/mcp-tools.manifest.json +533 -3
  9. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/output.py +13 -0
  10. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/pyproject.toml +1 -1
  11. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/.gitignore +0 -0
  12. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/hatch_build.py +0 -0
  13. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/__init__.py +0 -0
  14. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/admins/__init__.py +0 -0
  15. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/admins/commands.py +0 -0
  16. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/admins/group.py +0 -0
  17. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/admins/operations.py +0 -0
  18. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/__init__.py +0 -0
  19. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/agents/__init__.py +0 -0
  20. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/agents/group.py +0 -0
  21. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/agents/projections.py +0 -0
  22. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/auth.py +0 -0
  23. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/commands.py +0 -0
  24. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/compatibility.py +0 -0
  25. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/context.py +0 -0
  26. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/data_sources/__init__.py +0 -0
  27. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/data_sources/commands.py +0 -0
  28. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/data_sources/group.py +0 -0
  29. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/data_sources/operations.py +0 -0
  30. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/data_sources/projections.py +0 -0
  31. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/departments/__init__.py +0 -0
  32. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/departments/commands.py +0 -0
  33. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/departments/group.py +0 -0
  34. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/departments/operations.py +0 -0
  35. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/departments/projections.py +0 -0
  36. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/domain_errors.py +0 -0
  37. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/group.py +0 -0
  38. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/guardrails/__init__.py +0 -0
  39. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/guardrails/commands.py +0 -0
  40. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/guardrails/group.py +0 -0
  41. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/guardrails/operations.py +0 -0
  42. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/guardrails/projections.py +0 -0
  43. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/organization/__init__.py +0 -0
  44. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/organization/commands.py +0 -0
  45. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/organization/group.py +0 -0
  46. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/organization/operations.py +0 -0
  47. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/organization/projections.py +0 -0
  48. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/output.py +0 -0
  49. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/policies/__init__.py +0 -0
  50. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/policies/commands.py +0 -0
  51. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/policies/group.py +0 -0
  52. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/policies/operations.py +0 -0
  53. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/policies/projections.py +0 -0
  54. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/registry.py +0 -0
  55. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/results.py +0 -0
  56. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/session.py +0 -0
  57. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/sessions/__init__.py +0 -0
  58. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/sessions/commands.py +0 -0
  59. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/sessions/group.py +0 -0
  60. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/sessions/operations.py +0 -0
  61. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/sessions/projections.py +0 -0
  62. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/skills/__init__.py +0 -0
  63. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/skills/commands.py +0 -0
  64. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/skills/group.py +0 -0
  65. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/skills/operations.py +0 -0
  66. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/skills/projections.py +0 -0
  67. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/target.py +0 -0
  68. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/teams/__init__.py +0 -0
  69. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/teams/commands.py +0 -0
  70. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/teams/group.py +0 -0
  71. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/teams/operations.py +0 -0
  72. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/teams/projections.py +0 -0
  73. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/tools/__init__.py +0 -0
  74. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/tools/commands.py +0 -0
  75. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/tools/group.py +0 -0
  76. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/tools/operations.py +0 -0
  77. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/tools/projections.py +0 -0
  78. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/tuning.py +0 -0
  79. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/ui.py +0 -0
  80. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/ams/windows_files.py +0 -0
  81. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/context.py +0 -0
  82. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/contracts/__init__.py +0 -0
  83. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/main.py +0 -0
  84. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/manifest.py +0 -0
  85. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/params.py +0 -0
  86. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/tenants/__init__.py +0 -0
  87. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/tenants/commands.py +0 -0
  88. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/tenants/group.py +0 -0
  89. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/tenants/operations.py +0 -0
  90. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/healthcloud_cli/util.py +0 -0
  91. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/scripts/__init__.py +0 -0
  92. {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.3}/scripts/bundle_manifest.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: healthcloud-cli
3
- Version: 3.7.1
3
+ Version: 3.7.3
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
@@ -316,6 +316,27 @@ otherwise it prompts (with hidden password input). It stores only the email,
316
316
  tenant/environment binding, access/refresh tokens and expiry in the strict
317
317
  owner-only `~/.healthcloud/cli/ams-session.json`; it never stores the password.
318
318
 
319
+ By default (`pretty`) these three commands print readable lines — never a raw
320
+ JSON blob and never a token:
321
+
322
+ ```text
323
+ $ hc login --environment dev --tenant-id TENANT_ID
324
+ saved session: dev / TENANT_ID
325
+ email: admin@example.test
326
+ expires_at: 2026-11-02T01:42:29Z
327
+ $ hc logout
328
+ saved session removed: true
329
+ server session revoked: false
330
+ ```
331
+
332
+ `hc login --status` prints the same three lines for the saved session, and a
333
+ second `hc logout` reports `saved session removed: false`. `--output json` (and
334
+ `--output raw`, which these commands treat as JSON) prints exactly the objects
335
+ they have always printed: `{"schema_version": 1, "ok": true, "session":
336
+ {"schema_version": 1, "environment", "tenant_id", "email", "expires_at"}}`
337
+ for `login`/`--status`, and `{"schema_version": 1, "ok": true, "removed":
338
+ true|false, "server_session_revoked": false}` for `logout`.
339
+
319
340
  For CI and agents, log in non-interactively: pass `--email` and pipe the password
320
341
  to `--password-stdin` (read as exactly one stdin line, never echoed, never a
321
342
  flag value — there is no `--password` option):
@@ -545,11 +566,33 @@ explicitly that
545
566
  fields — a disabled agent can still be selected by those paths — and that
546
567
  in-flight runs, WSS sessions and schedules are untouched by this route.
547
568
 
548
- Pre-checks refuse with exit 1 and **no write**: a missing `--confirm` (exit 2
549
- usage error, before login), an agent outside the explicit tenant, an already
550
- in-effect state, an unsupported `archived`/`draft` target (named as having no
551
- supported reversible transition here), or a `config_version` that no longer
552
- matches `--expected-config-version`. That version check is a local best-effort
569
+ Default `pretty` output prints those facts as labelled lines — `transition`,
570
+ `requested`, `evidence` (`saved`, `side_effects`, `runtime`), `eligibility`,
571
+ `mutation_replayed: false` — and closes with a `verification:` block, so a
572
+ deactivate reads `transition: deactivate`, then
573
+ `evidence: / saved: / status: confirmed`, then `verification: / saved_state:
574
+ confirmed`, `side_effects: unverified`, `runtime: unverified`; it is never one
575
+ raw JSON line. `--output json` prints the full envelope above, byte-identical to
576
+ before. An unknown outcome still prints its one-line `error:` plus the
577
+ reconciliation on stderr.
578
+
579
+ Without `--confirm` the command is a **preview**: it re-reads the agent exactly
580
+ once (the same one bounded `GET`), runs the same local pre-checks, prints
581
+ `preview: --confirm was not passed, so nothing has been sent.` and then the
582
+ resolved change to stderr — the target (`agent_id`, `name`, `type`,
583
+ `config_version`), `status` and `active` before -> after, and
584
+ `one PUT /agents/{agent_id} with body keys: status, active` — and exits 2 having
585
+ sent nothing. With `--confirm` the write path is unchanged: it re-reads before
586
+ the single `PUT`, so a preview is never an approval token. The preview text is
587
+ suppressed under `--output json`/`raw` (the same rule the confirmed path's
588
+ review follows) so those modes keep stderr machine-parsable.
589
+
590
+ Pre-checks refuse with exit 1 and **no write**: an agent outside the explicit
591
+ tenant, an already in-effect state, an unsupported `archived`/`draft` target
592
+ (named as having no supported reversible transition here), or a
593
+ `config_version` that no longer matches `--expected-config-version` — all of
594
+ these are also enforced by the preview, which still sends nothing. A missing
595
+ `--confirm` exits 2. That version check is a local best-effort
553
596
  guard only: the route has no `If-Match` and the server never compares the
554
597
  version, so it cannot prove atomicity and a request could still race between
555
598
  the read and the PUT.
@@ -615,20 +658,34 @@ server audit log records an `UPDATE` with the payload. Runtime enforcement of
615
658
  the new references is unobservable: no serving readback route exists, so this
616
659
  command never claims it.
617
660
 
661
+ Default `pretty` output prints the same facts as labelled lines: `agent_id`,
662
+ `changed` (each field with `before`/`after`/`added`/`removed`),
663
+ `config_version: {before, after}`, `evidence` (`saved`, `side_effects`,
664
+ `runtime`), `mutation_replayed: false`, and a closing `verification:` block.
665
+ `--output json` prints the full envelope described here, unchanged.
666
+
618
667
  `tool_ids` and `data_source_ids` address **unscoped global catalogs** by design;
619
668
  the server checks tenant scope only for the department, team, skill, policy and
620
669
  guardrail references. This command does not imply otherwise, and it does not
621
670
  re-implement the server's reference checks.
622
671
 
623
- Pre-checks refuse with exit 1 and **no write**: a missing `--confirm` (exit 2
624
- usage error, before login and before any request), no assignment option at all
672
+ Without `--confirm` the command is a **preview**: it reads the agent exactly once
673
+ (the same one bounded `GET`), computes the same reviewed diff and runs the same
674
+ local pre-checks, prints `preview: --confirm was not passed, so nothing has been
675
+ sent.` followed by the reviewed diff, and exits 2 having sent no `PUT`. With
676
+ `--confirm` the write path is unchanged: it re-reads before the single `PUT`, so
677
+ a preview is never an approval token. The preview text is suppressed under
678
+ `--output json`/`raw`, like the confirmed path's review.
679
+
680
+ Pre-checks refuse with exit 1 and **no write**: no assignment option at all
625
681
  (exit 2), setting and clearing the same family together (exit 2), an agent
626
682
  outside the explicit tenant, a request where every reference already equals the
627
683
  read state ("already in effect"), or a `config_version` that no longer matches
628
- `--expected-config-version`. That version check is a local best-effort guard
629
- only: the route has no `If-Match` and the server never compares the version, so
630
- it cannot prove atomicity and a request could still race between the read and
631
- the PUT. Operation-specific approval here is the CLI's own `--confirm` plus the
684
+ `--expected-config-version` — all of these are also enforced by the preview,
685
+ which still sends nothing. A missing `--confirm` exits 2. That version check is
686
+ a local best-effort guard only: the route has no `If-Match` and the server never
687
+ compares the version, so it cannot prove atomicity and a request could still
688
+ race between the read and the PUT. Operation-specific approval here is the CLI's own `--confirm` plus the
632
689
  reviewed diff, deliberately separate from the tuning/plan-apply approval path;
633
690
  the server enforces tenant isolation, reference existence and the audit log, and
634
691
  it has **no** assignment approval or consent entity.
@@ -689,10 +746,24 @@ of restore and has no `--force`, no `--dry-run`, no interactive prompt and no
689
746
  `--expected-config-version` — the route has no `If-Match` or version check, so a
690
747
  version flag would advertise protection the server does not offer.
691
748
 
692
- Refusals claim nothing and send nothing extra: a missing `--confirm` is an exit
693
- 2 usage error before login or any request; an agent outside the explicit tenant,
694
- the tenant identity mismatch and a delete target that no longer exists are
695
- refused before the `DELETE`; a `409` is a `conflict` refusal naming the referring
749
+ Default `pretty` output prints the result as labelled lines next to the stderr
750
+ disclosure: `agent_id`, `deleted: true`, `target`, `disclosure`, `evidence`
751
+ (`deleted`/`side_effects`/`runtime`), `mutation_replayed: false`, and a closing
752
+ `verification:` block. `--output json` prints the full envelope, unchanged. The
753
+ reviewed target and disclosure above are still printed to stderr in `pretty`
754
+ mode only; `--output json`/`raw` keep stderr machine-parsable.
755
+
756
+ Without `--confirm` the command is a **preview**: it reads the agent exactly once
757
+ (the same one bounded `GET`), prints `preview: --confirm was not passed, so
758
+ nothing has been sent.` plus the resolved destructive target and the whole
759
+ disclosure block to stderr, and exits 2 having sent no `DELETE`. With `--confirm`
760
+ the sequence above is unchanged: it re-reads before the single `DELETE`, so a
761
+ preview is never an approval token. The preview text is suppressed under
762
+ `--output json`/`raw`, like the confirmed path's review.
763
+
764
+ Refusals claim nothing and send nothing extra: an agent outside the explicit
765
+ tenant, the tenant identity mismatch and a delete target that no longer exists
766
+ are refused before the `DELETE` (the preview reports the same `not_found`); a `409` is a `conflict` refusal naming the referring
696
767
  schedules; a `404` from the `DELETE` is `not_found`; and a `405` is the server's
697
768
  refusal (`unsupported`). None of them is retried.
698
769
 
@@ -997,6 +1068,14 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
997
1068
  "continuation": null}`). `get` emits `operation: "sessions.get"` and
998
1069
  `data.session`. The projections never include the session's protected
999
1070
  `initial_prompt`, tenant or user identity, or arbitrary response fields.
1071
+ - Default `pretty` output prints `list` as a table with honest headers —
1072
+ `ID`, `AGENT ID`, `OPERATION`, `STATUS`, `EXPIRES AT` for sessions and `ID`,
1073
+ `ROLE`, `MODE`, `PLAN ID`, `CREATED AT` for messages — followed by an entry
1074
+ count (`1 entry` / `N entries`), and `(no entries)` for an empty list. These
1075
+ projections carry no `name`, so no `NAME` column is invented and the `id` is
1076
+ always shown. `get`, `create` and `messages add` print `key: value` lines.
1077
+ `--output json` prints the full envelope exactly as before; pretty output is
1078
+ additive and never changes the envelope.
1000
1079
  - `create` and `messages add` require a saved `hc login` session (env-var
1001
1080
  credentials are not accepted for writes). Each write is sent exactly once and
1002
1081
  the envelope carries the server's authoritative readback:
@@ -1169,6 +1248,12 @@ uv run --frozen hc ams sessions plans reconcile --environment dev --tenant-id TE
1169
1248
  (create: reference counts by kind) and finding counts. No prompt,
1170
1249
  instructions, greeting, summary, message content or plan value is printed to
1171
1250
  stdout, stderr, a diagnostic or an error string.
1251
+ - Default `pretty` output renders `generate`, `get`, `apply`, `rollback` and
1252
+ `reconcile` as labelled lines — `plan:` with the bounded metadata, then
1253
+ `evidence:` (`saved`/`side_effects`/`runtime`) and `mutation_replayed: false`
1254
+ where the command reports them, and a closing `verification:` block for the
1255
+ writes. A write is never printed as a raw JSON blob in the default mode.
1256
+ `--output json` prints the full envelope, unchanged.
1172
1257
  - The prompt is protected free text and never comes from argv: `--prompt-file`
1173
1258
  reads one owner-only regular text file (no symlinks, at most 20000
1174
1259
  characters) and sends it once. `--prompt-file` is optional — the server falls
@@ -1423,8 +1508,12 @@ and `hc ams organization hierarchy`) render in two modes (`--output` on the root
1423
1508
  `hc` command; `pretty` is the default):
1424
1509
 
1425
1510
  - **`pretty`** (default): a compact human view. Lists print an `ID`/`NAME`
1426
- table with an entry count; a single record prints `key: value` lines; the
1427
- organization hierarchy prints an indented tree. Each failed AMS command
1511
+ table with an entry count; a collection whose rows have no `name` (sessions,
1512
+ messages, attachments) prints a table of that command's own columns plus the
1513
+ count, and `(no entries)` when empty. A single record prints `key: value`
1514
+ lines, the organization hierarchy prints an indented tree, and write or
1515
+ reconciliation evidence prints labelled `key: value` lines (the write's
1516
+ `verification:` block included) instead of JSON. Each failed AMS command
1428
1517
  prints one stderr line, `error: <message> (<category>)` (flag/argument
1429
1518
  usage errors from Click print Click's standard usage message instead).
1430
1519
  - **`json`**: the stable result envelope (below) on stdout. When stdout is not
@@ -298,6 +298,27 @@ otherwise it prompts (with hidden password input). It stores only the email,
298
298
  tenant/environment binding, access/refresh tokens and expiry in the strict
299
299
  owner-only `~/.healthcloud/cli/ams-session.json`; it never stores the password.
300
300
 
301
+ By default (`pretty`) these three commands print readable lines — never a raw
302
+ JSON blob and never a token:
303
+
304
+ ```text
305
+ $ hc login --environment dev --tenant-id TENANT_ID
306
+ saved session: dev / TENANT_ID
307
+ email: admin@example.test
308
+ expires_at: 2026-11-02T01:42:29Z
309
+ $ hc logout
310
+ saved session removed: true
311
+ server session revoked: false
312
+ ```
313
+
314
+ `hc login --status` prints the same three lines for the saved session, and a
315
+ second `hc logout` reports `saved session removed: false`. `--output json` (and
316
+ `--output raw`, which these commands treat as JSON) prints exactly the objects
317
+ they have always printed: `{"schema_version": 1, "ok": true, "session":
318
+ {"schema_version": 1, "environment", "tenant_id", "email", "expires_at"}}`
319
+ for `login`/`--status`, and `{"schema_version": 1, "ok": true, "removed":
320
+ true|false, "server_session_revoked": false}` for `logout`.
321
+
301
322
  For CI and agents, log in non-interactively: pass `--email` and pipe the password
302
323
  to `--password-stdin` (read as exactly one stdin line, never echoed, never a
303
324
  flag value — there is no `--password` option):
@@ -527,11 +548,33 @@ explicitly that
527
548
  fields — a disabled agent can still be selected by those paths — and that
528
549
  in-flight runs, WSS sessions and schedules are untouched by this route.
529
550
 
530
- Pre-checks refuse with exit 1 and **no write**: a missing `--confirm` (exit 2
531
- usage error, before login), an agent outside the explicit tenant, an already
532
- in-effect state, an unsupported `archived`/`draft` target (named as having no
533
- supported reversible transition here), or a `config_version` that no longer
534
- matches `--expected-config-version`. That version check is a local best-effort
551
+ Default `pretty` output prints those facts as labelled lines — `transition`,
552
+ `requested`, `evidence` (`saved`, `side_effects`, `runtime`), `eligibility`,
553
+ `mutation_replayed: false` — and closes with a `verification:` block, so a
554
+ deactivate reads `transition: deactivate`, then
555
+ `evidence: / saved: / status: confirmed`, then `verification: / saved_state:
556
+ confirmed`, `side_effects: unverified`, `runtime: unverified`; it is never one
557
+ raw JSON line. `--output json` prints the full envelope above, byte-identical to
558
+ before. An unknown outcome still prints its one-line `error:` plus the
559
+ reconciliation on stderr.
560
+
561
+ Without `--confirm` the command is a **preview**: it re-reads the agent exactly
562
+ once (the same one bounded `GET`), runs the same local pre-checks, prints
563
+ `preview: --confirm was not passed, so nothing has been sent.` and then the
564
+ resolved change to stderr — the target (`agent_id`, `name`, `type`,
565
+ `config_version`), `status` and `active` before -> after, and
566
+ `one PUT /agents/{agent_id} with body keys: status, active` — and exits 2 having
567
+ sent nothing. With `--confirm` the write path is unchanged: it re-reads before
568
+ the single `PUT`, so a preview is never an approval token. The preview text is
569
+ suppressed under `--output json`/`raw` (the same rule the confirmed path's
570
+ review follows) so those modes keep stderr machine-parsable.
571
+
572
+ Pre-checks refuse with exit 1 and **no write**: an agent outside the explicit
573
+ tenant, an already in-effect state, an unsupported `archived`/`draft` target
574
+ (named as having no supported reversible transition here), or a
575
+ `config_version` that no longer matches `--expected-config-version` — all of
576
+ these are also enforced by the preview, which still sends nothing. A missing
577
+ `--confirm` exits 2. That version check is a local best-effort
535
578
  guard only: the route has no `If-Match` and the server never compares the
536
579
  version, so it cannot prove atomicity and a request could still race between
537
580
  the read and the PUT.
@@ -597,20 +640,34 @@ server audit log records an `UPDATE` with the payload. Runtime enforcement of
597
640
  the new references is unobservable: no serving readback route exists, so this
598
641
  command never claims it.
599
642
 
643
+ Default `pretty` output prints the same facts as labelled lines: `agent_id`,
644
+ `changed` (each field with `before`/`after`/`added`/`removed`),
645
+ `config_version: {before, after}`, `evidence` (`saved`, `side_effects`,
646
+ `runtime`), `mutation_replayed: false`, and a closing `verification:` block.
647
+ `--output json` prints the full envelope described here, unchanged.
648
+
600
649
  `tool_ids` and `data_source_ids` address **unscoped global catalogs** by design;
601
650
  the server checks tenant scope only for the department, team, skill, policy and
602
651
  guardrail references. This command does not imply otherwise, and it does not
603
652
  re-implement the server's reference checks.
604
653
 
605
- Pre-checks refuse with exit 1 and **no write**: a missing `--confirm` (exit 2
606
- usage error, before login and before any request), no assignment option at all
654
+ Without `--confirm` the command is a **preview**: it reads the agent exactly once
655
+ (the same one bounded `GET`), computes the same reviewed diff and runs the same
656
+ local pre-checks, prints `preview: --confirm was not passed, so nothing has been
657
+ sent.` followed by the reviewed diff, and exits 2 having sent no `PUT`. With
658
+ `--confirm` the write path is unchanged: it re-reads before the single `PUT`, so
659
+ a preview is never an approval token. The preview text is suppressed under
660
+ `--output json`/`raw`, like the confirmed path's review.
661
+
662
+ Pre-checks refuse with exit 1 and **no write**: no assignment option at all
607
663
  (exit 2), setting and clearing the same family together (exit 2), an agent
608
664
  outside the explicit tenant, a request where every reference already equals the
609
665
  read state ("already in effect"), or a `config_version` that no longer matches
610
- `--expected-config-version`. That version check is a local best-effort guard
611
- only: the route has no `If-Match` and the server never compares the version, so
612
- it cannot prove atomicity and a request could still race between the read and
613
- the PUT. Operation-specific approval here is the CLI's own `--confirm` plus the
666
+ `--expected-config-version` — all of these are also enforced by the preview,
667
+ which still sends nothing. A missing `--confirm` exits 2. That version check is
668
+ a local best-effort guard only: the route has no `If-Match` and the server never
669
+ compares the version, so it cannot prove atomicity and a request could still
670
+ race between the read and the PUT. Operation-specific approval here is the CLI's own `--confirm` plus the
614
671
  reviewed diff, deliberately separate from the tuning/plan-apply approval path;
615
672
  the server enforces tenant isolation, reference existence and the audit log, and
616
673
  it has **no** assignment approval or consent entity.
@@ -671,10 +728,24 @@ of restore and has no `--force`, no `--dry-run`, no interactive prompt and no
671
728
  `--expected-config-version` — the route has no `If-Match` or version check, so a
672
729
  version flag would advertise protection the server does not offer.
673
730
 
674
- Refusals claim nothing and send nothing extra: a missing `--confirm` is an exit
675
- 2 usage error before login or any request; an agent outside the explicit tenant,
676
- the tenant identity mismatch and a delete target that no longer exists are
677
- refused before the `DELETE`; a `409` is a `conflict` refusal naming the referring
731
+ Default `pretty` output prints the result as labelled lines next to the stderr
732
+ disclosure: `agent_id`, `deleted: true`, `target`, `disclosure`, `evidence`
733
+ (`deleted`/`side_effects`/`runtime`), `mutation_replayed: false`, and a closing
734
+ `verification:` block. `--output json` prints the full envelope, unchanged. The
735
+ reviewed target and disclosure above are still printed to stderr in `pretty`
736
+ mode only; `--output json`/`raw` keep stderr machine-parsable.
737
+
738
+ Without `--confirm` the command is a **preview**: it reads the agent exactly once
739
+ (the same one bounded `GET`), prints `preview: --confirm was not passed, so
740
+ nothing has been sent.` plus the resolved destructive target and the whole
741
+ disclosure block to stderr, and exits 2 having sent no `DELETE`. With `--confirm`
742
+ the sequence above is unchanged: it re-reads before the single `DELETE`, so a
743
+ preview is never an approval token. The preview text is suppressed under
744
+ `--output json`/`raw`, like the confirmed path's review.
745
+
746
+ Refusals claim nothing and send nothing extra: an agent outside the explicit
747
+ tenant, the tenant identity mismatch and a delete target that no longer exists
748
+ are refused before the `DELETE` (the preview reports the same `not_found`); a `409` is a `conflict` refusal naming the referring
678
749
  schedules; a `404` from the `DELETE` is `not_found`; and a `405` is the server's
679
750
  refusal (`unsupported`). None of them is retried.
680
751
 
@@ -979,6 +1050,14 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
979
1050
  "continuation": null}`). `get` emits `operation: "sessions.get"` and
980
1051
  `data.session`. The projections never include the session's protected
981
1052
  `initial_prompt`, tenant or user identity, or arbitrary response fields.
1053
+ - Default `pretty` output prints `list` as a table with honest headers —
1054
+ `ID`, `AGENT ID`, `OPERATION`, `STATUS`, `EXPIRES AT` for sessions and `ID`,
1055
+ `ROLE`, `MODE`, `PLAN ID`, `CREATED AT` for messages — followed by an entry
1056
+ count (`1 entry` / `N entries`), and `(no entries)` for an empty list. These
1057
+ projections carry no `name`, so no `NAME` column is invented and the `id` is
1058
+ always shown. `get`, `create` and `messages add` print `key: value` lines.
1059
+ `--output json` prints the full envelope exactly as before; pretty output is
1060
+ additive and never changes the envelope.
982
1061
  - `create` and `messages add` require a saved `hc login` session (env-var
983
1062
  credentials are not accepted for writes). Each write is sent exactly once and
984
1063
  the envelope carries the server's authoritative readback:
@@ -1151,6 +1230,12 @@ uv run --frozen hc ams sessions plans reconcile --environment dev --tenant-id TE
1151
1230
  (create: reference counts by kind) and finding counts. No prompt,
1152
1231
  instructions, greeting, summary, message content or plan value is printed to
1153
1232
  stdout, stderr, a diagnostic or an error string.
1233
+ - Default `pretty` output renders `generate`, `get`, `apply`, `rollback` and
1234
+ `reconcile` as labelled lines — `plan:` with the bounded metadata, then
1235
+ `evidence:` (`saved`/`side_effects`/`runtime`) and `mutation_replayed: false`
1236
+ where the command reports them, and a closing `verification:` block for the
1237
+ writes. A write is never printed as a raw JSON blob in the default mode.
1238
+ `--output json` prints the full envelope, unchanged.
1154
1239
  - The prompt is protected free text and never comes from argv: `--prompt-file`
1155
1240
  reads one owner-only regular text file (no symlinks, at most 20000
1156
1241
  characters) and sends it once. `--prompt-file` is optional — the server falls
@@ -1405,8 +1490,12 @@ and `hc ams organization hierarchy`) render in two modes (`--output` on the root
1405
1490
  `hc` command; `pretty` is the default):
1406
1491
 
1407
1492
  - **`pretty`** (default): a compact human view. Lists print an `ID`/`NAME`
1408
- table with an entry count; a single record prints `key: value` lines; the
1409
- organization hierarchy prints an indented tree. Each failed AMS command
1493
+ table with an entry count; a collection whose rows have no `name` (sessions,
1494
+ messages, attachments) prints a table of that command's own columns plus the
1495
+ count, and `(no entries)` when empty. A single record prints `key: value`
1496
+ lines, the organization hierarchy prints an indented tree, and write or
1497
+ reconciliation evidence prints labelled `key: value` lines (the write's
1498
+ `verification:` block included) instead of JSON. Each failed AMS command
1410
1499
  prints one stderr line, `error: <message> (<category>)` (flag/argument
1411
1500
  usage errors from Click print Click's standard usage message instead).
1412
1501
  - **`json`**: the stable result envelope (below) on stdout. When stdout is not
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
  import click
8
8
 
9
9
  from ..context import AmsDependencies, pass_ams, preflight_agents
10
- from ..errors import run_domain
10
+ from ..errors import run_domain, run_preview
11
11
  from ..session import resolve_tenant_target
12
12
  from .operations import (
13
13
  ASSIGNMENT_FAMILIES,
@@ -16,6 +16,9 @@ from .operations import (
16
16
  export_agent,
17
17
  get_agent,
18
18
  list_agents,
19
+ preview_assignments,
20
+ preview_delete,
21
+ preview_lifecycle,
19
22
  set_assignments,
20
23
  set_lifecycle,
21
24
  )
@@ -37,6 +40,34 @@ def _agent(function):
37
40
  return click.option("--agent-id", required=True)(function)
38
41
 
39
42
 
43
+ def _preview_then_refuse(
44
+ invocation,
45
+ operation: str,
46
+ target_data: dict,
47
+ preview,
48
+ refusal: str,
49
+ *,
50
+ login_hint: str,
51
+ ) -> None:
52
+ """Read the target once, print the reviewed change, then refuse with exit 2.
53
+
54
+ Nothing is written before the refusal, and the preview is still a read: a
55
+ failed read (not logged in, wrong tenant, 404, transport) keeps the bounded
56
+ error envelope instead of escaping as a traceback. The confirmed run
57
+ re-reads before its single write, so a preview is never an approval token.
58
+ """
59
+
60
+ def read() -> None:
61
+ _require_login(login_hint)
62
+ target = resolve_tenant_target(target_data["environment"], target_data["tenant_id"])
63
+ target_data.update(environment=target.environment, tenant_id=target.tenant_id)
64
+ preflight_agents(invocation, target, methods=("get_agent",))
65
+ preview(target)
66
+
67
+ run_preview(operation, target_data, read)
68
+ raise click.UsageError(refusal)
69
+
70
+
40
71
  def _require_login(hint: str = LOGIN_HINT) -> None:
41
72
  """Agent writes need a saved `hc login` session, not env-var credentials."""
42
73
 
@@ -71,7 +102,8 @@ def create_lifecycle_command(dependencies: AmsDependencies, transition: str) ->
71
102
  "--confirm",
72
103
  is_flag=True,
73
104
  default=False,
74
- help="Required acknowledgement of the exact target and state; without it nothing is sent.",
105
+ help="Required acknowledgement of the exact target and state; without it the command reads the "
106
+ "target, prints the change and sends nothing.",
75
107
  )
76
108
  @pass_ams
77
109
  def command(
@@ -82,14 +114,27 @@ def create_lifecycle_command(dependencies: AmsDependencies, transition: str) ->
82
114
  expected_config_version: int,
83
115
  confirm: bool,
84
116
  ) -> None:
85
- if not confirm:
86
- raise click.UsageError("--confirm is required; no lifecycle write was sent.")
87
117
  target_data = {
88
118
  "environment": environment,
89
119
  "tenant_id": tenant_id,
90
120
  "agent_id": agent_id,
91
121
  "expected_config_version": expected_config_version,
92
122
  }
123
+ if not confirm:
124
+ _preview_then_refuse(
125
+ invocation,
126
+ f"agents.{transition}",
127
+ target_data,
128
+ lambda target: preview_lifecycle(
129
+ dependencies,
130
+ target,
131
+ agent_id,
132
+ transition=transition,
133
+ expected_config_version=expected_config_version,
134
+ ),
135
+ "--confirm is required; no lifecycle write was sent. Re-run with --confirm to send it.",
136
+ login_hint=LOGIN_HINT,
137
+ )
93
138
 
94
139
  def execute():
95
140
  _require_login()
@@ -123,13 +168,21 @@ def create_delete_command(dependencies: AmsDependencies) -> click.Command:
123
168
  "--confirm",
124
169
  is_flag=True,
125
170
  default=False,
126
- help="Required acknowledgement of the printed target and disclosure block; without it nothing is sent.",
171
+ help="Required acknowledgement of the printed target and disclosure block; without it the command "
172
+ "prints both and sends nothing.",
127
173
  )
128
174
  @pass_ams
129
175
  def command(invocation, environment: str | None, tenant_id: str | None, agent_id: str, confirm: bool) -> None:
130
- if not confirm:
131
- raise click.UsageError("--confirm is required; no delete was sent.")
132
176
  target_data = {"environment": environment, "tenant_id": tenant_id, "agent_id": agent_id}
177
+ if not confirm:
178
+ _preview_then_refuse(
179
+ invocation,
180
+ "agents.delete",
181
+ target_data,
182
+ lambda target: preview_delete(dependencies, target, agent_id),
183
+ "--confirm is required; no delete was sent. Re-run with --confirm to send it.",
184
+ login_hint=DELETE_LOGIN_HINT,
185
+ )
133
186
 
134
187
  def execute():
135
188
  _require_login(DELETE_LOGIN_HINT)
@@ -221,7 +274,10 @@ CONFIG_VERSION_HELP = (
221
274
  "The config_version read before the write. A local best-effort guard only: the route has no "
222
275
  "If-Match, so it never proves atomicity."
223
276
  )
224
- CONFIRM_HELP = "Required acknowledgement of the target and the printed reviewed diff; without it nothing is sent."
277
+ CONFIRM_HELP = (
278
+ "Required acknowledgement of the target and the printed reviewed diff; without it the command prints "
279
+ "the diff and sends nothing."
280
+ )
225
281
 
226
282
 
227
283
  def _assignment_changes(values: dict, cleared: dict) -> dict:
@@ -320,8 +376,6 @@ def create_assignments_set_command(dependencies: AmsDependencies) -> click.Comma
320
376
  clear_data_source_ids: bool,
321
377
  confirm: bool,
322
378
  ) -> None:
323
- if not confirm:
324
- raise click.UsageError("--confirm is required; no assignment write was sent.")
325
379
  changes = _assignment_changes(
326
380
  {
327
381
  "department_id": department_id,
@@ -351,6 +405,21 @@ def create_assignments_set_command(dependencies: AmsDependencies) -> click.Comma
351
405
  "expected_config_version": expected_config_version,
352
406
  "changed_fields": sorted(changes),
353
407
  }
408
+ if not confirm:
409
+ _preview_then_refuse(
410
+ invocation,
411
+ "agents.assignments.set",
412
+ target_data,
413
+ lambda target: preview_assignments(
414
+ dependencies,
415
+ target,
416
+ agent_id,
417
+ changes,
418
+ expected_config_version=expected_config_version,
419
+ ),
420
+ "--confirm is required; no assignment write was sent. Re-run with --confirm to send it.",
421
+ login_hint=ASSIGNMENT_LOGIN_HINT,
422
+ )
354
423
 
355
424
  def execute():
356
425
  _require_login(ASSIGNMENT_LOGIN_HINT)