healthcloud-cli 3.7.0__tar.gz → 3.7.2__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.0 → healthcloud_cli-3.7.2}/PKG-INFO +97 -19
  2. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/README.md +96 -18
  3. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/departments/operations.py +74 -5
  4. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/errors.py +2 -15
  5. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/policies/operations.py +73 -5
  6. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/pretty.py +128 -1
  7. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/results.py +53 -0
  8. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/skills/operations.py +58 -4
  9. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/teams/operations.py +58 -4
  10. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/cli.py +17 -9
  11. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/contracts/mcp-tools.manifest.json +533 -3
  12. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/output.py +13 -0
  13. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/pyproject.toml +1 -1
  14. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/.gitignore +0 -0
  15. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/hatch_build.py +0 -0
  16. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/__init__.py +0 -0
  17. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/admins/__init__.py +0 -0
  18. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/admins/commands.py +0 -0
  19. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/admins/group.py +0 -0
  20. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/admins/operations.py +0 -0
  21. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/__init__.py +0 -0
  22. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/agents/__init__.py +0 -0
  23. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/agents/commands.py +0 -0
  24. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/agents/group.py +0 -0
  25. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/agents/operations.py +0 -0
  26. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/agents/projections.py +0 -0
  27. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/auth.py +0 -0
  28. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/commands.py +0 -0
  29. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/compatibility.py +0 -0
  30. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/context.py +0 -0
  31. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/data_sources/__init__.py +0 -0
  32. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/data_sources/commands.py +0 -0
  33. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/data_sources/group.py +0 -0
  34. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/data_sources/operations.py +0 -0
  35. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/data_sources/projections.py +0 -0
  36. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/departments/__init__.py +0 -0
  37. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/departments/commands.py +0 -0
  38. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/departments/group.py +0 -0
  39. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/departments/projections.py +0 -0
  40. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/domain_errors.py +0 -0
  41. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/group.py +0 -0
  42. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/guardrails/__init__.py +0 -0
  43. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/guardrails/commands.py +0 -0
  44. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/guardrails/group.py +0 -0
  45. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/guardrails/operations.py +0 -0
  46. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/guardrails/projections.py +0 -0
  47. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/organization/__init__.py +0 -0
  48. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/organization/commands.py +0 -0
  49. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/organization/group.py +0 -0
  50. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/organization/operations.py +0 -0
  51. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/organization/projections.py +0 -0
  52. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/output.py +0 -0
  53. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/policies/__init__.py +0 -0
  54. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/policies/commands.py +0 -0
  55. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/policies/group.py +0 -0
  56. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/policies/projections.py +0 -0
  57. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/registry.py +0 -0
  58. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/session.py +0 -0
  59. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/sessions/__init__.py +0 -0
  60. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/sessions/commands.py +0 -0
  61. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/sessions/group.py +0 -0
  62. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/sessions/operations.py +0 -0
  63. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/sessions/projections.py +0 -0
  64. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/skills/__init__.py +0 -0
  65. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/skills/commands.py +0 -0
  66. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/skills/group.py +0 -0
  67. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/skills/projections.py +0 -0
  68. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/target.py +0 -0
  69. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/teams/__init__.py +0 -0
  70. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/teams/commands.py +0 -0
  71. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/teams/group.py +0 -0
  72. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/teams/projections.py +0 -0
  73. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/tools/__init__.py +0 -0
  74. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/tools/commands.py +0 -0
  75. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/tools/group.py +0 -0
  76. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/tools/operations.py +0 -0
  77. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/tools/projections.py +0 -0
  78. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/tuning.py +0 -0
  79. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/ui.py +0 -0
  80. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/windows_files.py +0 -0
  81. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/context.py +0 -0
  82. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/contracts/__init__.py +0 -0
  83. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/main.py +0 -0
  84. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/manifest.py +0 -0
  85. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/params.py +0 -0
  86. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/tenants/__init__.py +0 -0
  87. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/tenants/commands.py +0 -0
  88. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/tenants/group.py +0 -0
  89. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/tenants/operations.py +0 -0
  90. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/healthcloud_cli/util.py +0 -0
  91. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/scripts/__init__.py +0 -0
  92. {healthcloud_cli-3.7.0 → healthcloud_cli-3.7.2}/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.0
3
+ Version: 3.7.2
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,6 +566,16 @@ 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
 
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
+
548
579
  Pre-checks refuse with exit 1 and **no write**: a missing `--confirm` (exit 2
549
580
  usage error, before login), an agent outside the explicit tenant, an already
550
581
  in-effect state, an unsupported `archived`/`draft` target (named as having no
@@ -615,6 +646,12 @@ server audit log records an `UPDATE` with the payload. Runtime enforcement of
615
646
  the new references is unobservable: no serving readback route exists, so this
616
647
  command never claims it.
617
648
 
649
+ Default `pretty` output prints the same facts as labelled lines: `agent_id`,
650
+ `changed` (each field with `before`/`after`/`added`/`removed`),
651
+ `config_version: {before, after}`, `evidence` (`saved`, `side_effects`,
652
+ `runtime`), `mutation_replayed: false`, and a closing `verification:` block.
653
+ `--output json` prints the full envelope described here, unchanged.
654
+
618
655
  `tool_ids` and `data_source_ids` address **unscoped global catalogs** by design;
619
656
  the server checks tenant scope only for the department, team, skill, policy and
620
657
  guardrail references. This command does not imply otherwise, and it does not
@@ -689,6 +726,13 @@ of restore and has no `--force`, no `--dry-run`, no interactive prompt and no
689
726
  `--expected-config-version` — the route has no `If-Match` or version check, so a
690
727
  version flag would advertise protection the server does not offer.
691
728
 
729
+ Default `pretty` output prints the result as labelled lines next to the stderr
730
+ disclosure: `agent_id`, `deleted: true`, `target`, `disclosure`, `evidence`
731
+ (`deleted`/`side_effects`/`runtime`), `mutation_replayed: false`, and a closing
732
+ `verification:` block. `--output json` prints the full envelope, unchanged. The
733
+ reviewed target and disclosure above are still printed to stderr in `pretty`
734
+ mode only; `--output json`/`raw` keep stderr machine-parsable.
735
+
692
736
  Refusals claim nothing and send nothing extra: a missing `--confirm` is an exit
693
737
  2 usage error before login or any request; an agent outside the explicit tenant,
694
738
  the tenant identity mismatch and a delete target that no longer exists are
@@ -751,10 +795,14 @@ uv run --frozen hc ams departments delete --environment dev --tenant-id TENANT_I
751
795
  reports a `validation` refusal that names the department, the tenant that
752
796
  received it, and tells you to run `hc ams departments get` — it never claims
753
797
  a landed write was a stopped no-op.
754
- - If the transport times out or the connection drops mid-write, the write's
755
- outcome is unknown: the CLI reports one `transport` error whose message says
756
- the outcome is unknown and to check `hc ams departments get` before
757
- retrying. It never re-sends the write on its own.
798
+ - If the transport times out, the connection drops mid-write, or the server
799
+ answers an HTTP 5xx/408 before a readable result, the write's outcome is
800
+ unknown: the CLI exits 1 with an `unknown` envelope
801
+ (`verification.saved_state: unknown`, `verification.side_effects:
802
+ unverified`, `data.mutation_replayed: false`, and
803
+ `data.reconciliation.status: not_read`) whose message says the outcome is
804
+ unknown and to check `hc ams departments get` before retrying. It never
805
+ re-sends the write and never re-reads on its own.
758
806
 
759
807
  Read-only team listing and inspection are also complete and unpaged. The bounded
760
808
  projection omits descriptions and creator identities; it includes evaluation-suite
@@ -797,10 +845,14 @@ uv run --frozen hc ams teams delete --environment dev --tenant-id TENANT_ID --
797
845
  `validation` refusal that reports the landed write truthfully: it names the
798
846
  team and the tenant that actually received it and points to `hc ams teams
799
847
  get` — it never claims a landed write was stopped.
800
- - If the transport times out or the connection drops mid-write, the write's
801
- outcome is unknown: the CLI reports one `transport` error whose message says
802
- the outcome is unknown and to check `hc ams teams get` before retrying. It
803
- never re-sends the write on its own.
848
+ - If the transport times out, the connection drops mid-write, or the server
849
+ answers an HTTP 5xx/408 before a readable result, the write's outcome is
850
+ unknown: the CLI exits 1 with an `unknown` envelope
851
+ (`verification.saved_state: unknown`, `verification.side_effects:
852
+ unverified`, `data.mutation_replayed: false`, and
853
+ `data.reconciliation.status: not_read`) whose message says the outcome is
854
+ unknown and to check `hc ams teams get` before retrying. It never re-sends
855
+ the write and never re-reads on its own.
804
856
 
805
857
  The organization hierarchy is the backend-built department/team/agent tree. The
806
858
  CLI preserves its order and nesting without issuing entity reads, joining records,
@@ -862,10 +914,14 @@ uv run --frozen hc ams skills delete --environment dev --tenant-id TENANT_ID --s
862
914
  landed write truthfully: it names the skill and the tenant that actually
863
915
  received it and points to `hc ams skills get` — it never claims a landed
864
916
  write was stopped.
865
- - If the transport times out or the connection drops mid-write, the write's
866
- outcome is unknown: the CLI reports one `transport` error whose message says
867
- the outcome is unknown and to check `hc ams skills get` before retrying. It
868
- never re-sends the write on its own.
917
+ - If the transport times out, the connection drops mid-write, or the server
918
+ answers an HTTP 5xx/408 before a readable result, the write's outcome is
919
+ unknown: the CLI exits 1 with an `unknown` envelope
920
+ (`verification.saved_state: unknown`, `verification.side_effects:
921
+ unverified`, `data.mutation_replayed: false`, and
922
+ `data.reconciliation.status: not_read`) whose message says the outcome is
923
+ unknown and to check `hc ams skills get` before retrying. It never re-sends
924
+ the write and never re-reads on its own.
869
925
 
870
926
  Read-only policy catalog listing and inspection are complete and unpaged. The
871
927
  bounded projection includes only policy IDs, names, department IDs, guardrail
@@ -954,10 +1010,14 @@ uv run --frozen hc ams policies delete --environment dev --tenant-id TENANT_ID -
954
1010
  landed write truthfully: it names the policy and the tenant that actually
955
1011
  received it and points to `hc ams policies get` — it never claims a landed
956
1012
  write was stopped.
957
- - If the transport times out or the connection drops mid-write, the write's
958
- outcome is unknown: the CLI reports one `transport` error whose message says
959
- the outcome is unknown and to check `hc ams policies get` before retrying.
960
- It never re-sends the write on its own.
1013
+ - If the transport times out, the connection drops mid-write, or the server
1014
+ answers an HTTP 5xx/408 before a readable result, the write's outcome is
1015
+ unknown: the CLI exits 1 with an `unknown` envelope
1016
+ (`verification.saved_state: unknown`, `verification.side_effects:
1017
+ unverified`, `data.mutation_replayed: false`, and
1018
+ `data.reconciliation.status: not_read`) whose message says the outcome is
1019
+ unknown and to check `hc ams policies get` before retrying. It never
1020
+ re-sends the write and never re-reads on its own.
961
1021
 
962
1022
  Governed authoring sessions and their messages are first-class commands over the
963
1023
  VibeCoder session routes. Creating a session and appending a message make no model
@@ -981,6 +1041,14 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
981
1041
  "continuation": null}`). `get` emits `operation: "sessions.get"` and
982
1042
  `data.session`. The projections never include the session's protected
983
1043
  `initial_prompt`, tenant or user identity, or arbitrary response fields.
1044
+ - Default `pretty` output prints `list` as a table with honest headers —
1045
+ `ID`, `AGENT ID`, `OPERATION`, `STATUS`, `EXPIRES AT` for sessions and `ID`,
1046
+ `ROLE`, `MODE`, `PLAN ID`, `CREATED AT` for messages — followed by an entry
1047
+ count (`1 entry` / `N entries`), and `(no entries)` for an empty list. These
1048
+ projections carry no `name`, so no `NAME` column is invented and the `id` is
1049
+ always shown. `get`, `create` and `messages add` print `key: value` lines.
1050
+ `--output json` prints the full envelope exactly as before; pretty output is
1051
+ additive and never changes the envelope.
984
1052
  - `create` and `messages add` require a saved `hc login` session (env-var
985
1053
  credentials are not accepted for writes). Each write is sent exactly once and
986
1054
  the envelope carries the server's authoritative readback:
@@ -1153,6 +1221,12 @@ uv run --frozen hc ams sessions plans reconcile --environment dev --tenant-id TE
1153
1221
  (create: reference counts by kind) and finding counts. No prompt,
1154
1222
  instructions, greeting, summary, message content or plan value is printed to
1155
1223
  stdout, stderr, a diagnostic or an error string.
1224
+ - Default `pretty` output renders `generate`, `get`, `apply`, `rollback` and
1225
+ `reconcile` as labelled lines — `plan:` with the bounded metadata, then
1226
+ `evidence:` (`saved`/`side_effects`/`runtime`) and `mutation_replayed: false`
1227
+ where the command reports them, and a closing `verification:` block for the
1228
+ writes. A write is never printed as a raw JSON blob in the default mode.
1229
+ `--output json` prints the full envelope, unchanged.
1156
1230
  - The prompt is protected free text and never comes from argv: `--prompt-file`
1157
1231
  reads one owner-only regular text file (no symlinks, at most 20000
1158
1232
  characters) and sends it once. `--prompt-file` is optional — the server falls
@@ -1407,8 +1481,12 @@ and `hc ams organization hierarchy`) render in two modes (`--output` on the root
1407
1481
  `hc` command; `pretty` is the default):
1408
1482
 
1409
1483
  - **`pretty`** (default): a compact human view. Lists print an `ID`/`NAME`
1410
- table with an entry count; a single record prints `key: value` lines; the
1411
- organization hierarchy prints an indented tree. Each failed AMS command
1484
+ table with an entry count; a collection whose rows have no `name` (sessions,
1485
+ messages, attachments) prints a table of that command's own columns plus the
1486
+ count, and `(no entries)` when empty. A single record prints `key: value`
1487
+ lines, the organization hierarchy prints an indented tree, and write or
1488
+ reconciliation evidence prints labelled `key: value` lines (the write's
1489
+ `verification:` block included) instead of JSON. Each failed AMS command
1412
1490
  prints one stderr line, `error: <message> (<category>)` (flag/argument
1413
1491
  usage errors from Click print Click's standard usage message instead).
1414
1492
  - **`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,6 +548,16 @@ 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
 
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
+
530
561
  Pre-checks refuse with exit 1 and **no write**: a missing `--confirm` (exit 2
531
562
  usage error, before login), an agent outside the explicit tenant, an already
532
563
  in-effect state, an unsupported `archived`/`draft` target (named as having no
@@ -597,6 +628,12 @@ server audit log records an `UPDATE` with the payload. Runtime enforcement of
597
628
  the new references is unobservable: no serving readback route exists, so this
598
629
  command never claims it.
599
630
 
631
+ Default `pretty` output prints the same facts as labelled lines: `agent_id`,
632
+ `changed` (each field with `before`/`after`/`added`/`removed`),
633
+ `config_version: {before, after}`, `evidence` (`saved`, `side_effects`,
634
+ `runtime`), `mutation_replayed: false`, and a closing `verification:` block.
635
+ `--output json` prints the full envelope described here, unchanged.
636
+
600
637
  `tool_ids` and `data_source_ids` address **unscoped global catalogs** by design;
601
638
  the server checks tenant scope only for the department, team, skill, policy and
602
639
  guardrail references. This command does not imply otherwise, and it does not
@@ -671,6 +708,13 @@ of restore and has no `--force`, no `--dry-run`, no interactive prompt and no
671
708
  `--expected-config-version` — the route has no `If-Match` or version check, so a
672
709
  version flag would advertise protection the server does not offer.
673
710
 
711
+ Default `pretty` output prints the result as labelled lines next to the stderr
712
+ disclosure: `agent_id`, `deleted: true`, `target`, `disclosure`, `evidence`
713
+ (`deleted`/`side_effects`/`runtime`), `mutation_replayed: false`, and a closing
714
+ `verification:` block. `--output json` prints the full envelope, unchanged. The
715
+ reviewed target and disclosure above are still printed to stderr in `pretty`
716
+ mode only; `--output json`/`raw` keep stderr machine-parsable.
717
+
674
718
  Refusals claim nothing and send nothing extra: a missing `--confirm` is an exit
675
719
  2 usage error before login or any request; an agent outside the explicit tenant,
676
720
  the tenant identity mismatch and a delete target that no longer exists are
@@ -733,10 +777,14 @@ uv run --frozen hc ams departments delete --environment dev --tenant-id TENANT_I
733
777
  reports a `validation` refusal that names the department, the tenant that
734
778
  received it, and tells you to run `hc ams departments get` — it never claims
735
779
  a landed write was a stopped no-op.
736
- - If the transport times out or the connection drops mid-write, the write's
737
- outcome is unknown: the CLI reports one `transport` error whose message says
738
- the outcome is unknown and to check `hc ams departments get` before
739
- retrying. It never re-sends the write on its own.
780
+ - If the transport times out, the connection drops mid-write, or the server
781
+ answers an HTTP 5xx/408 before a readable result, the write's outcome is
782
+ unknown: the CLI exits 1 with an `unknown` envelope
783
+ (`verification.saved_state: unknown`, `verification.side_effects:
784
+ unverified`, `data.mutation_replayed: false`, and
785
+ `data.reconciliation.status: not_read`) whose message says the outcome is
786
+ unknown and to check `hc ams departments get` before retrying. It never
787
+ re-sends the write and never re-reads on its own.
740
788
 
741
789
  Read-only team listing and inspection are also complete and unpaged. The bounded
742
790
  projection omits descriptions and creator identities; it includes evaluation-suite
@@ -779,10 +827,14 @@ uv run --frozen hc ams teams delete --environment dev --tenant-id TENANT_ID --
779
827
  `validation` refusal that reports the landed write truthfully: it names the
780
828
  team and the tenant that actually received it and points to `hc ams teams
781
829
  get` — it never claims a landed write was stopped.
782
- - If the transport times out or the connection drops mid-write, the write's
783
- outcome is unknown: the CLI reports one `transport` error whose message says
784
- the outcome is unknown and to check `hc ams teams get` before retrying. It
785
- never re-sends the write on its own.
830
+ - If the transport times out, the connection drops mid-write, or the server
831
+ answers an HTTP 5xx/408 before a readable result, the write's outcome is
832
+ unknown: the CLI exits 1 with an `unknown` envelope
833
+ (`verification.saved_state: unknown`, `verification.side_effects:
834
+ unverified`, `data.mutation_replayed: false`, and
835
+ `data.reconciliation.status: not_read`) whose message says the outcome is
836
+ unknown and to check `hc ams teams get` before retrying. It never re-sends
837
+ the write and never re-reads on its own.
786
838
 
787
839
  The organization hierarchy is the backend-built department/team/agent tree. The
788
840
  CLI preserves its order and nesting without issuing entity reads, joining records,
@@ -844,10 +896,14 @@ uv run --frozen hc ams skills delete --environment dev --tenant-id TENANT_ID --s
844
896
  landed write truthfully: it names the skill and the tenant that actually
845
897
  received it and points to `hc ams skills get` — it never claims a landed
846
898
  write was stopped.
847
- - If the transport times out or the connection drops mid-write, the write's
848
- outcome is unknown: the CLI reports one `transport` error whose message says
849
- the outcome is unknown and to check `hc ams skills get` before retrying. It
850
- never re-sends the write on its own.
899
+ - If the transport times out, the connection drops mid-write, or the server
900
+ answers an HTTP 5xx/408 before a readable result, the write's outcome is
901
+ unknown: the CLI exits 1 with an `unknown` envelope
902
+ (`verification.saved_state: unknown`, `verification.side_effects:
903
+ unverified`, `data.mutation_replayed: false`, and
904
+ `data.reconciliation.status: not_read`) whose message says the outcome is
905
+ unknown and to check `hc ams skills get` before retrying. It never re-sends
906
+ the write and never re-reads on its own.
851
907
 
852
908
  Read-only policy catalog listing and inspection are complete and unpaged. The
853
909
  bounded projection includes only policy IDs, names, department IDs, guardrail
@@ -936,10 +992,14 @@ uv run --frozen hc ams policies delete --environment dev --tenant-id TENANT_ID -
936
992
  landed write truthfully: it names the policy and the tenant that actually
937
993
  received it and points to `hc ams policies get` — it never claims a landed
938
994
  write was stopped.
939
- - If the transport times out or the connection drops mid-write, the write's
940
- outcome is unknown: the CLI reports one `transport` error whose message says
941
- the outcome is unknown and to check `hc ams policies get` before retrying.
942
- It never re-sends the write on its own.
995
+ - If the transport times out, the connection drops mid-write, or the server
996
+ answers an HTTP 5xx/408 before a readable result, the write's outcome is
997
+ unknown: the CLI exits 1 with an `unknown` envelope
998
+ (`verification.saved_state: unknown`, `verification.side_effects:
999
+ unverified`, `data.mutation_replayed: false`, and
1000
+ `data.reconciliation.status: not_read`) whose message says the outcome is
1001
+ unknown and to check `hc ams policies get` before retrying. It never
1002
+ re-sends the write and never re-reads on its own.
943
1003
 
944
1004
  Governed authoring sessions and their messages are first-class commands over the
945
1005
  VibeCoder session routes. Creating a session and appending a message make no model
@@ -963,6 +1023,14 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
963
1023
  "continuation": null}`). `get` emits `operation: "sessions.get"` and
964
1024
  `data.session`. The projections never include the session's protected
965
1025
  `initial_prompt`, tenant or user identity, or arbitrary response fields.
1026
+ - Default `pretty` output prints `list` as a table with honest headers —
1027
+ `ID`, `AGENT ID`, `OPERATION`, `STATUS`, `EXPIRES AT` for sessions and `ID`,
1028
+ `ROLE`, `MODE`, `PLAN ID`, `CREATED AT` for messages — followed by an entry
1029
+ count (`1 entry` / `N entries`), and `(no entries)` for an empty list. These
1030
+ projections carry no `name`, so no `NAME` column is invented and the `id` is
1031
+ always shown. `get`, `create` and `messages add` print `key: value` lines.
1032
+ `--output json` prints the full envelope exactly as before; pretty output is
1033
+ additive and never changes the envelope.
966
1034
  - `create` and `messages add` require a saved `hc login` session (env-var
967
1035
  credentials are not accepted for writes). Each write is sent exactly once and
968
1036
  the envelope carries the server's authoritative readback:
@@ -1135,6 +1203,12 @@ uv run --frozen hc ams sessions plans reconcile --environment dev --tenant-id TE
1135
1203
  (create: reference counts by kind) and finding counts. No prompt,
1136
1204
  instructions, greeting, summary, message content or plan value is printed to
1137
1205
  stdout, stderr, a diagnostic or an error string.
1206
+ - Default `pretty` output renders `generate`, `get`, `apply`, `rollback` and
1207
+ `reconcile` as labelled lines — `plan:` with the bounded metadata, then
1208
+ `evidence:` (`saved`/`side_effects`/`runtime`) and `mutation_replayed: false`
1209
+ where the command reports them, and a closing `verification:` block for the
1210
+ writes. A write is never printed as a raw JSON blob in the default mode.
1211
+ `--output json` prints the full envelope, unchanged.
1138
1212
  - The prompt is protected free text and never comes from argv: `--prompt-file`
1139
1213
  reads one owner-only regular text file (no symlinks, at most 20000
1140
1214
  characters) and sends it once. `--prompt-file` is optional — the server falls
@@ -1389,8 +1463,12 @@ and `hc ams organization hierarchy`) render in two modes (`--output` on the root
1389
1463
  `hc` command; `pretty` is the default):
1390
1464
 
1391
1465
  - **`pretty`** (default): a compact human view. Lists print an `ID`/`NAME`
1392
- table with an entry count; a single record prints `key: value` lines; the
1393
- organization hierarchy prints an indented tree. Each failed AMS command
1466
+ table with an entry count; a collection whose rows have no `name` (sessions,
1467
+ messages, attachments) prints a table of that command's own columns plus the
1468
+ count, and `(no entries)` when empty. A single record prints `key: value`
1469
+ lines, the organization hierarchy prints an indented tree, and write or
1470
+ reconciliation evidence prints labelled `key: value` lines (the write's
1471
+ `verification:` block included) instead of JSON. Each failed AMS command
1394
1472
  prints one stderr line, `error: <message> (<category>)` (flag/argument
1395
1473
  usage errors from Click print Click's standard usage message instead).
1396
1474
  - **`json`**: the stable result envelope (below) on stdout. When stdout is not
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from ..context import AmsDependencies
6
6
  from ..domain_errors import DomainReadRefused
7
- from ..results import succeeded
7
+ from ..results import is_unknown_write_status, succeeded, unknown_write
8
8
  from ..target import TenantTarget
9
9
  from .projections import department_projection
10
10
 
@@ -27,6 +27,18 @@ UNKNOWN_OUTCOME = (
27
27
  "department's current server state before deciding whether to retry."
28
28
  )
29
29
 
30
+ UNKNOWN_HTTP_OUTCOME = (
31
+ "The department write was sent once and its outcome is now unknown: the server answered with an "
32
+ "error status before a readable result, so the request may have been processed. Run "
33
+ "`hc ams departments get` to check the department's current server state before deciding whether "
34
+ "to retry."
35
+ )
36
+
37
+ UNKNOWN_RECONCILIATION = (
38
+ "This command did not re-read the department after the uncertain write; run "
39
+ "`hc ams departments list` before doing anything else."
40
+ )
41
+
30
42
 
31
43
  def _department_id(value: str) -> str:
32
44
  if not isinstance(value, str) or not value.strip():
@@ -140,11 +152,26 @@ def create_department(
140
152
  )
141
153
  except HealthCloudNetworkError:
142
154
  # Sent once: the request may have landed; the outcome is unknown, never retried here.
143
- raise DepartmentWriteRefused("transport", UNKNOWN_OUTCOME) from None
155
+ unknown_write(
156
+ "departments.create",
157
+ _target(target),
158
+ UNKNOWN_OUTCOME,
159
+ {"department": None},
160
+ reconciliation_detail=UNKNOWN_RECONCILIATION,
161
+ )
144
162
  except HealthCloudHTTPError as error:
145
163
  refusal = _create_not_found(error, parent_department_id)
146
164
  if refusal is not None:
147
165
  raise refusal from error
166
+ if is_unknown_write_status(getattr(error, "status_code", None)):
167
+ # A 5xx/408 may have committed server-side: report it as unknown, never a retry shape.
168
+ unknown_write(
169
+ "departments.create",
170
+ _target(target),
171
+ UNKNOWN_HTTP_OUTCOME,
172
+ {"department": None},
173
+ reconciliation_detail=UNKNOWN_RECONCILIATION,
174
+ )
148
175
  raise
149
176
  # The write has already landed server-side; a mismatching readback must never be
150
177
  # reported as a stopped no-op. Name the tenant that actually received it.
@@ -174,11 +201,25 @@ def update_department(
174
201
  try:
175
202
  department = resource.update(department_id, **fields)
176
203
  except HealthCloudNetworkError:
177
- raise DepartmentWriteRefused("transport", UNKNOWN_OUTCOME) from None
204
+ unknown_write(
205
+ "departments.update",
206
+ _target(target, department_id),
207
+ UNKNOWN_OUTCOME,
208
+ {"department": None},
209
+ reconciliation_detail=UNKNOWN_RECONCILIATION,
210
+ )
178
211
  except HealthCloudHTTPError as error:
179
212
  refusal = _not_found(error, department_id)
180
213
  if refusal is not None:
181
214
  raise refusal from error
215
+ if is_unknown_write_status(getattr(error, "status_code", None)):
216
+ unknown_write(
217
+ "departments.update",
218
+ _target(target, department_id),
219
+ UNKNOWN_HTTP_OUTCOME,
220
+ {"department": None},
221
+ reconciliation_detail=UNKNOWN_RECONCILIATION,
222
+ )
182
223
  raise
183
224
  _identity(department, target, department_id)
184
225
  return _write_result("departments.update", target, department_id, {"department": department_projection(department)})
@@ -193,11 +234,25 @@ def _lifecycle(dependencies: AmsDependencies, target: TenantTarget, department_i
193
234
  try:
194
235
  department = getattr(resource, method)(department_id)
195
236
  except HealthCloudNetworkError:
196
- raise DepartmentWriteRefused("transport", UNKNOWN_OUTCOME) from None
237
+ unknown_write(
238
+ f"departments.{method}",
239
+ _target(target, department_id),
240
+ UNKNOWN_OUTCOME,
241
+ {"department": None},
242
+ reconciliation_detail=UNKNOWN_RECONCILIATION,
243
+ )
197
244
  except HealthCloudHTTPError as error:
198
245
  refusal = _not_found(error, department_id)
199
246
  if refusal is not None:
200
247
  raise refusal from error
248
+ if is_unknown_write_status(getattr(error, "status_code", None)):
249
+ unknown_write(
250
+ f"departments.{method}",
251
+ _target(target, department_id),
252
+ UNKNOWN_HTTP_OUTCOME,
253
+ {"department": None},
254
+ reconciliation_detail=UNKNOWN_RECONCILIATION,
255
+ )
201
256
  raise
202
257
  _identity(department, target, department_id)
203
258
  return _write_result(
@@ -225,10 +280,24 @@ def delete_department(dependencies: AmsDependencies, target: TenantTarget, depar
225
280
  try:
226
281
  resource.delete(department_id)
227
282
  except HealthCloudNetworkError:
228
- raise DepartmentWriteRefused("transport", UNKNOWN_OUTCOME) from None
283
+ unknown_write(
284
+ "departments.delete",
285
+ _target(target, department_id),
286
+ UNKNOWN_OUTCOME,
287
+ {"deleted": None},
288
+ reconciliation_detail=UNKNOWN_RECONCILIATION,
289
+ )
229
290
  except HealthCloudHTTPError as error:
230
291
  refusal = _not_found(error, department_id)
231
292
  if refusal is not None:
232
293
  raise refusal from error
294
+ if is_unknown_write_status(getattr(error, "status_code", None)):
295
+ unknown_write(
296
+ "departments.delete",
297
+ _target(target, department_id),
298
+ UNKNOWN_HTTP_OUTCOME,
299
+ {"deleted": None},
300
+ reconciliation_detail=UNKNOWN_RECONCILIATION,
301
+ )
233
302
  raise
234
303
  return _write_result("departments.delete", target, department_id, {"deleted": True})
@@ -10,7 +10,7 @@ from typing import Any
10
10
  import click
11
11
  from pydantic import ValidationError
12
12
 
13
- from ..context import CliConfig
13
+ from ..output import output_mode
14
14
  from .domain_errors import DomainReadRefused
15
15
  from .pretty import error_line, success_text, unknown_details
16
16
  from .results import failed
@@ -135,19 +135,6 @@ def emit_compact_json(value: dict, *, err: bool = False) -> None:
135
135
  click.echo(json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True), err=err)
136
136
 
137
137
 
138
- def _output_mode() -> str:
139
- """The root ``--output`` mode for the running command; pretty is the default.
140
-
141
- Raw is refused for AMS before any read runs, but its refusals keep the
142
- structured JSON error envelope, so raw renders like json.
143
- """
144
-
145
- ctx = click.get_current_context(silent=True)
146
- cfg = ctx.find_object(CliConfig) if ctx is not None else None
147
- mode = getattr(cfg, "output", None)
148
- return "json" if mode in ("json", "raw") else "pretty"
149
-
150
-
151
138
  def _render_success(envelope: dict, mode: str) -> None:
152
139
  if mode != "json":
153
140
  click.echo(success_text(envelope))
@@ -190,7 +177,7 @@ def run_domain(operation: str, target: dict, callback: Callable[[], Any], *, rea
190
177
  ``verification`` block in the error envelope.
191
178
  """
192
179
 
193
- mode = _output_mode()
180
+ mode = output_mode()
194
181
 
195
182
  def _fail(error: BaseException) -> None:
196
183
  """Classify ``error``, render the error envelope, and exit bounded."""