healthcloud-cli 3.6.0__tar.gz → 3.7.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 (92) hide show
  1. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/.gitignore +2 -0
  2. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/PKG-INFO +52 -5
  3. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/README.md +51 -4
  4. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/output.py +56 -0
  5. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/policies/commands.py +29 -5
  6. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/policies/operations.py +37 -1
  7. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/sessions/commands.py +30 -4
  8. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/sessions/operations.py +32 -7
  9. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/contracts/mcp-tools.manifest.json +207 -3
  10. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/pyproject.toml +1 -1
  11. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/hatch_build.py +0 -0
  12. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/__init__.py +0 -0
  13. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/admins/__init__.py +0 -0
  14. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/admins/commands.py +0 -0
  15. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/admins/group.py +0 -0
  16. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/admins/operations.py +0 -0
  17. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/__init__.py +0 -0
  18. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/agents/__init__.py +0 -0
  19. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/agents/commands.py +0 -0
  20. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/agents/group.py +0 -0
  21. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/agents/operations.py +0 -0
  22. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/agents/projections.py +0 -0
  23. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/auth.py +0 -0
  24. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/commands.py +0 -0
  25. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/compatibility.py +0 -0
  26. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/context.py +0 -0
  27. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/__init__.py +0 -0
  28. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/commands.py +0 -0
  29. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/group.py +0 -0
  30. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/operations.py +0 -0
  31. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/projections.py +0 -0
  32. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/departments/__init__.py +0 -0
  33. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/departments/commands.py +0 -0
  34. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/departments/group.py +0 -0
  35. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/departments/operations.py +0 -0
  36. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/departments/projections.py +0 -0
  37. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/domain_errors.py +0 -0
  38. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/errors.py +0 -0
  39. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/group.py +0 -0
  40. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/guardrails/__init__.py +0 -0
  41. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/guardrails/commands.py +0 -0
  42. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/guardrails/group.py +0 -0
  43. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/guardrails/operations.py +0 -0
  44. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/guardrails/projections.py +0 -0
  45. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/__init__.py +0 -0
  46. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/commands.py +0 -0
  47. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/group.py +0 -0
  48. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/operations.py +0 -0
  49. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/projections.py +0 -0
  50. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/policies/__init__.py +0 -0
  51. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/policies/group.py +0 -0
  52. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/policies/projections.py +0 -0
  53. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/pretty.py +0 -0
  54. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/registry.py +0 -0
  55. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/results.py +0 -0
  56. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/session.py +0 -0
  57. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/sessions/__init__.py +0 -0
  58. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/sessions/group.py +0 -0
  59. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/sessions/projections.py +0 -0
  60. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/skills/__init__.py +0 -0
  61. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/skills/commands.py +0 -0
  62. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/skills/group.py +0 -0
  63. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/skills/operations.py +0 -0
  64. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/skills/projections.py +0 -0
  65. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/target.py +0 -0
  66. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/teams/__init__.py +0 -0
  67. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/teams/commands.py +0 -0
  68. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/teams/group.py +0 -0
  69. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/teams/operations.py +0 -0
  70. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/teams/projections.py +0 -0
  71. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/tools/__init__.py +0 -0
  72. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/tools/commands.py +0 -0
  73. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/tools/group.py +0 -0
  74. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/tools/operations.py +0 -0
  75. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/tools/projections.py +0 -0
  76. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/tuning.py +0 -0
  77. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/ui.py +0 -0
  78. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/windows_files.py +0 -0
  79. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/cli.py +0 -0
  80. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/context.py +0 -0
  81. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/contracts/__init__.py +0 -0
  82. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/main.py +0 -0
  83. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/manifest.py +0 -0
  84. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/output.py +0 -0
  85. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/params.py +0 -0
  86. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/tenants/__init__.py +0 -0
  87. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/tenants/commands.py +0 -0
  88. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/tenants/group.py +0 -0
  89. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/tenants/operations.py +0 -0
  90. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/util.py +0 -0
  91. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/scripts/__init__.py +0 -0
  92. {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/scripts/bundle_manifest.py +0 -0
@@ -260,3 +260,5 @@ integration-tests/shared/test-assets/
260
260
  .ralph/
261
261
  /PROMPT.md
262
262
  .blind-peer-review/
263
+ # Terraform CI runner-local diagnostics, inputs and sensitive plan bundles.
264
+ /.kb-ci/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: healthcloud-cli
3
- Version: 3.6.0
3
+ Version: 3.7.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
@@ -897,8 +897,8 @@ Policies have an `active` lifecycle flag, so `activate` and `deactivate`
897
897
  commands exist (they PUT the `active` field):
898
898
 
899
899
  ```bash
900
- uv run --frozen hc ams policies create --environment dev --tenant-id TENANT_ID --name "Appointment Safety" --department-id DEPARTMENT_ID --created-by-user-id ADMIN_USER_ID [--description TEXT] [--content TEXT] [--guardrail-ids GUARDRAIL_ID ...]
901
- uv run --frozen hc ams policies update --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID [--name NAME] [--description TEXT] [--content TEXT] [--guardrail-ids GUARDRAIL_ID ... | --clear-guardrail-ids]
900
+ uv run --frozen hc ams policies create --environment dev --tenant-id TENANT_ID --name "Appointment Safety" --department-id DEPARTMENT_ID --created-by-user-id ADMIN_USER_ID [--description TEXT] [--content TEXT | --content-file PATH] [--guardrail-ids GUARDRAIL_ID ...]
901
+ uv run --frozen hc ams policies update --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID [--name NAME] [--description TEXT] [--content TEXT | --content-file PATH] [--guardrail-ids GUARDRAIL_ID ... | --clear-guardrail-ids]
902
902
  uv run --frozen hc ams policies activate --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID
903
903
  uv run --frozen hc ams policies deactivate --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID
904
904
  uv run --frozen hc ams policies delete --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID
@@ -911,6 +911,29 @@ uv run --frozen hc ams policies delete --environment dev --tenant-id TENANT_ID -
911
911
  server does not validate that `--guardrail-ids` reference real guardrails
912
912
  (blank guardrail IDs are rejected client-side). `--content` is write-only:
913
913
  it is sent but never echoed back.
914
+ - The protected policy content can also arrive from an owner-only file, which
915
+ keeps it out of the command line entirely (argv, the process table under
916
+ `ps`/`/proc/*/cmdline`, shell history and CI logs all see only the path):
917
+
918
+ ```bash
919
+ uv run --frozen hc ams policies create --environment dev --tenant-id TENANT_ID --name "Appointment Safety" --department-id DEPARTMENT_ID --created-by-user-id ADMIN_USER_ID --content-file PATH
920
+ uv run --frozen hc ams policies update --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID --content-file PATH
921
+ ```
922
+
923
+ The file is read locally, before the login check and any request, by the same
924
+ reader the sessions commands use: it must be a readable owner-only regular
925
+ text file (no symlinks, no directories, not group- or world-readable) and
926
+ must hold 1-20000 characters. Its content is never printed, and an invalid,
927
+ empty or oversized file is refused locally with a message that names the
928
+ discipline, never the text. The output is identical to the inline form:
929
+ `policies.create` / `policies.update` with `data.policy`.
930
+ - Exactly one content source is accepted per write: passing `--content` with
931
+ `--content-file` is a usage error (exit 2) that sends nothing. The inline
932
+ `--content` flag remains supported for convenience and remains exposed in
933
+ argv, the process table and shell history — use the file channel for
934
+ anything sensitive. An empty `--content ""` on `update` is still the
935
+ documented clear; an empty `--content-file` is a `validation` refusal, so a
936
+ truncated or misdirected file can never clear protected content by accident.
914
937
  - `update` sends only the options you pass (an empty `--description ""` or
915
938
  `--content ""` clears it; `--clear-guardrail-ids` sends an explicit empty
916
939
  list to clear the policy's guardrail bindings); it refuses with exit 1
@@ -947,9 +970,9 @@ is refused locally:
947
970
  ```bash
948
971
  uv run --frozen hc ams sessions list --environment dev --tenant-id TENANT_ID [--limit N]
949
972
  uv run --frozen hc ams sessions get --environment dev --tenant-id TENANT_ID --session-id SESSION_ID
950
- uv run --frozen hc ams sessions create --environment dev --tenant-id TENANT_ID [--agent-id AGENT_ID] [--mode plan|implement] [--initial-prompt TEXT]
973
+ uv run --frozen hc ams sessions create --environment dev --tenant-id TENANT_ID [--agent-id AGENT_ID] [--mode plan|implement] [--initial-prompt TEXT | --initial-prompt-file PATH]
951
974
  uv run --frozen hc ams sessions messages list --environment dev --tenant-id TENANT_ID --session-id SESSION_ID [--limit N]
952
- uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENANT_ID --session-id SESSION_ID --content TEXT [--mode plan|implement] [--idempotency-key KEY]
975
+ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENANT_ID --session-id SESSION_ID [--content TEXT | --content-file PATH] [--mode plan|implement] [--idempotency-key KEY]
953
976
  ```
954
977
 
955
978
  - `list` emits `operation: "sessions.list"`, `data.sessions` (bounded projections
@@ -966,6 +989,30 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
966
989
  mode, plan ID, model, timestamps) plus `data.idempotency_key` when a key was
967
990
  supplied. Protected free text — `--initial-prompt` and `--content` — is sent to
968
991
  the server but never appears in the envelope, a refusal, a diagnostic or a log.
992
+ - The protected free text can also arrive from an owner-only file, which keeps it
993
+ out of the command line entirely (argv, the process table under
994
+ `ps`/`/proc/*/cmdline`, shell history and CI logs all see only the path):
995
+
996
+ ```bash
997
+ uv run --frozen hc ams sessions create --environment dev --tenant-id TENANT_ID --initial-prompt-file PATH
998
+ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENANT_ID --session-id SESSION_ID --content-file PATH
999
+ ```
1000
+
1001
+ The file is read locally, before the login check and any request, by the same
1002
+ reader `hc ams sessions plans generate --prompt-file` uses: it must be a
1003
+ readable owner-only regular text file (no symlinks, no directories, not
1004
+ group- or world-readable), and must hold 1-20000 characters — the same bound
1005
+ the inline flag is now held to, so neither channel can carry an empty or
1006
+ over-long value past the local check. Its content is never printed, and an
1007
+ invalid, empty or oversized file is refused locally with a message that
1008
+ names the discipline, never the text. The output is identical to the inline form: `sessions.create`
1009
+ with `data.session`, `sessions.messages.add` with `data.message`.
1010
+ - Exactly one source is accepted per field. Passing `--initial-prompt` with
1011
+ `--initial-prompt-file`, or `--content` with `--content-file`, is a usage
1012
+ error (exit 2) that sends nothing; `messages add` with neither is the same
1013
+ usage error. The inline `--initial-prompt` and `--content` flags remain
1014
+ supported for convenience, and remain exposed in argv, the process table and
1015
+ shell history — use the file channel for anything sensitive.
969
1016
  - `--initial-prompt` becomes the session's first `user` message in the same
970
1017
  server transaction; `hc ams sessions messages list` shows it afterwards.
971
1018
  - `--mode implement` is not available from the server yet: `create` reports one
@@ -879,8 +879,8 @@ Policies have an `active` lifecycle flag, so `activate` and `deactivate`
879
879
  commands exist (they PUT the `active` field):
880
880
 
881
881
  ```bash
882
- uv run --frozen hc ams policies create --environment dev --tenant-id TENANT_ID --name "Appointment Safety" --department-id DEPARTMENT_ID --created-by-user-id ADMIN_USER_ID [--description TEXT] [--content TEXT] [--guardrail-ids GUARDRAIL_ID ...]
883
- uv run --frozen hc ams policies update --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID [--name NAME] [--description TEXT] [--content TEXT] [--guardrail-ids GUARDRAIL_ID ... | --clear-guardrail-ids]
882
+ uv run --frozen hc ams policies create --environment dev --tenant-id TENANT_ID --name "Appointment Safety" --department-id DEPARTMENT_ID --created-by-user-id ADMIN_USER_ID [--description TEXT] [--content TEXT | --content-file PATH] [--guardrail-ids GUARDRAIL_ID ...]
883
+ uv run --frozen hc ams policies update --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID [--name NAME] [--description TEXT] [--content TEXT | --content-file PATH] [--guardrail-ids GUARDRAIL_ID ... | --clear-guardrail-ids]
884
884
  uv run --frozen hc ams policies activate --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID
885
885
  uv run --frozen hc ams policies deactivate --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID
886
886
  uv run --frozen hc ams policies delete --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID
@@ -893,6 +893,29 @@ uv run --frozen hc ams policies delete --environment dev --tenant-id TENANT_ID -
893
893
  server does not validate that `--guardrail-ids` reference real guardrails
894
894
  (blank guardrail IDs are rejected client-side). `--content` is write-only:
895
895
  it is sent but never echoed back.
896
+ - The protected policy content can also arrive from an owner-only file, which
897
+ keeps it out of the command line entirely (argv, the process table under
898
+ `ps`/`/proc/*/cmdline`, shell history and CI logs all see only the path):
899
+
900
+ ```bash
901
+ uv run --frozen hc ams policies create --environment dev --tenant-id TENANT_ID --name "Appointment Safety" --department-id DEPARTMENT_ID --created-by-user-id ADMIN_USER_ID --content-file PATH
902
+ uv run --frozen hc ams policies update --environment dev --tenant-id TENANT_ID --policy-id POLICY_ID --content-file PATH
903
+ ```
904
+
905
+ The file is read locally, before the login check and any request, by the same
906
+ reader the sessions commands use: it must be a readable owner-only regular
907
+ text file (no symlinks, no directories, not group- or world-readable) and
908
+ must hold 1-20000 characters. Its content is never printed, and an invalid,
909
+ empty or oversized file is refused locally with a message that names the
910
+ discipline, never the text. The output is identical to the inline form:
911
+ `policies.create` / `policies.update` with `data.policy`.
912
+ - Exactly one content source is accepted per write: passing `--content` with
913
+ `--content-file` is a usage error (exit 2) that sends nothing. The inline
914
+ `--content` flag remains supported for convenience and remains exposed in
915
+ argv, the process table and shell history — use the file channel for
916
+ anything sensitive. An empty `--content ""` on `update` is still the
917
+ documented clear; an empty `--content-file` is a `validation` refusal, so a
918
+ truncated or misdirected file can never clear protected content by accident.
896
919
  - `update` sends only the options you pass (an empty `--description ""` or
897
920
  `--content ""` clears it; `--clear-guardrail-ids` sends an explicit empty
898
921
  list to clear the policy's guardrail bindings); it refuses with exit 1
@@ -929,9 +952,9 @@ is refused locally:
929
952
  ```bash
930
953
  uv run --frozen hc ams sessions list --environment dev --tenant-id TENANT_ID [--limit N]
931
954
  uv run --frozen hc ams sessions get --environment dev --tenant-id TENANT_ID --session-id SESSION_ID
932
- uv run --frozen hc ams sessions create --environment dev --tenant-id TENANT_ID [--agent-id AGENT_ID] [--mode plan|implement] [--initial-prompt TEXT]
955
+ uv run --frozen hc ams sessions create --environment dev --tenant-id TENANT_ID [--agent-id AGENT_ID] [--mode plan|implement] [--initial-prompt TEXT | --initial-prompt-file PATH]
933
956
  uv run --frozen hc ams sessions messages list --environment dev --tenant-id TENANT_ID --session-id SESSION_ID [--limit N]
934
- uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENANT_ID --session-id SESSION_ID --content TEXT [--mode plan|implement] [--idempotency-key KEY]
957
+ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENANT_ID --session-id SESSION_ID [--content TEXT | --content-file PATH] [--mode plan|implement] [--idempotency-key KEY]
935
958
  ```
936
959
 
937
960
  - `list` emits `operation: "sessions.list"`, `data.sessions` (bounded projections
@@ -948,6 +971,30 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
948
971
  mode, plan ID, model, timestamps) plus `data.idempotency_key` when a key was
949
972
  supplied. Protected free text — `--initial-prompt` and `--content` — is sent to
950
973
  the server but never appears in the envelope, a refusal, a diagnostic or a log.
974
+ - The protected free text can also arrive from an owner-only file, which keeps it
975
+ out of the command line entirely (argv, the process table under
976
+ `ps`/`/proc/*/cmdline`, shell history and CI logs all see only the path):
977
+
978
+ ```bash
979
+ uv run --frozen hc ams sessions create --environment dev --tenant-id TENANT_ID --initial-prompt-file PATH
980
+ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENANT_ID --session-id SESSION_ID --content-file PATH
981
+ ```
982
+
983
+ The file is read locally, before the login check and any request, by the same
984
+ reader `hc ams sessions plans generate --prompt-file` uses: it must be a
985
+ readable owner-only regular text file (no symlinks, no directories, not
986
+ group- or world-readable), and must hold 1-20000 characters — the same bound
987
+ the inline flag is now held to, so neither channel can carry an empty or
988
+ over-long value past the local check. Its content is never printed, and an
989
+ invalid, empty or oversized file is refused locally with a message that
990
+ names the discipline, never the text. The output is identical to the inline form: `sessions.create`
991
+ with `data.session`, `sessions.messages.add` with `data.message`.
992
+ - Exactly one source is accepted per field. Passing `--initial-prompt` with
993
+ `--initial-prompt-file`, or `--content` with `--content-file`, is a usage
994
+ error (exit 2) that sends nothing; `messages add` with neither is the same
995
+ usage error. The inline `--initial-prompt` and `--content` flags remain
996
+ supported for convenience, and remain exposed in argv, the process table and
997
+ shell history — use the file channel for anything sensitive.
951
998
  - `--initial-prompt` becomes the session's first `user` message in the same
952
999
  server transaction; `hc ams sessions messages list` shows it afterwards.
953
1000
  - `--mode implement` is not available from the server yet: `create` reports one
@@ -6,11 +6,16 @@ import hashlib
6
6
  import json
7
7
  import os
8
8
  import stat
9
+ from collections.abc import Callable
9
10
  from contextlib import contextmanager
10
11
  from pathlib import Path
11
12
 
12
13
  import click
13
14
 
15
+ # One bound for every protected free-text field the CLI accepts, whichever
16
+ # channel it arrives on (#834): the same value the server enforces.
17
+ PROTECTED_TEXT_MAX_CHARS = 20000
18
+
14
19
 
15
20
  def canonical(value: object) -> str:
16
21
  return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False)
@@ -91,6 +96,57 @@ def read_private_text(path: Path, *, maximum: int) -> str:
91
96
  ) from None
92
97
 
93
98
 
99
+ def protected_text(
100
+ inline: str | None,
101
+ path: str | None,
102
+ *,
103
+ flag: str,
104
+ file_flag: str,
105
+ refuse: Callable[[str, str], BaseException],
106
+ required: bool = False,
107
+ allow_empty_inline: bool = False,
108
+ ) -> str | None:
109
+ """Resolve one protected free-text field from exactly one input channel.
110
+
111
+ ``--<field>`` keeps working for convenience but lands in argv, the process
112
+ table and shell history; ``--<field>-file`` reads the same text from one
113
+ owner-only file instead. Exactly one source is allowed, and both channels
114
+ are bounded identically, so neither can carry an over-long value past the
115
+ local check the other applies.
116
+
117
+ ``required`` makes "no source at all" a refusal here, in the Click-free
118
+ layer, so the invariant does not depend on the adapter. Empty text is
119
+ refused on both channels unless the caller documents an empty inline value
120
+ as a clear (``hc ams policies update --content ""``); an empty file is
121
+ always refused, so a truncated or misdirected file can never clear a
122
+ protected field by accident. ``refuse`` builds the caller's own categorized
123
+ refusal, and no refusal quotes the text itself.
124
+ """
125
+
126
+ if inline is not None and path is not None:
127
+ raise refuse("validation", f"Pass {flag} or {file_flag}, not both; nothing was sent.")
128
+ if inline is None and path is None:
129
+ if not required:
130
+ return None
131
+ raise refuse("validation", f"Pass {flag} or {file_flag}; nothing was sent.")
132
+ if path is not None:
133
+ if not isinstance(path, str) or not path.strip():
134
+ raise refuse("validation", f"Explicit {file_flag} file path required.")
135
+ text = read_private_text(Path(path), maximum=PROTECTED_TEXT_MAX_CHARS)
136
+ if not text:
137
+ raise refuse("validation", f"The file given to {file_flag} is empty; nothing was sent.")
138
+ return text
139
+ if len(inline) > PROTECTED_TEXT_MAX_CHARS:
140
+ raise refuse(
141
+ "validation",
142
+ f"{flag} is longer than {PROTECTED_TEXT_MAX_CHARS} characters; nothing was sent. "
143
+ f"Pass a shorter value, or the same text with {file_flag}.",
144
+ )
145
+ if not inline and not allow_empty_inline:
146
+ raise refuse("validation", f"{flag} is empty; nothing was sent. Pass the text, or {file_flag} holding it.")
147
+ return inline
148
+
149
+
94
150
  @contextmanager
95
151
  def _private_stream(path: Path, newline: str | None = None):
96
152
  """Reserve output before network calls, never overwrite, clean failed work."""
@@ -88,7 +88,15 @@ def create_create_command(dependencies: AmsDependencies) -> click.Command:
88
88
  @click.option(
89
89
  "--content",
90
90
  default=None,
91
- help="Policy content (write-only; never echoed in command output).",
91
+ help="Policy content (write-only; never echoed in command output). Exposed in argv, the process "
92
+ "table and shell history: prefer --content-file.",
93
+ )
94
+ @click.option(
95
+ "--content-file",
96
+ default=None,
97
+ type=click.Path(dir_okay=False),
98
+ help="Owner-only text file holding the policy content, so the text never enters argv or shell "
99
+ "history (1-20000 characters). Cannot be combined with --content.",
92
100
  )
93
101
  @click.option(
94
102
  "--guardrail-ids",
@@ -106,7 +114,10 @@ def create_create_command(dependencies: AmsDependencies) -> click.Command:
106
114
  description: str | None,
107
115
  content: str | None,
108
116
  guardrail_ids: tuple[str, ...],
117
+ content_file: str | None,
109
118
  ) -> None:
119
+ if content is not None and content_file is not None:
120
+ raise click.UsageError("Pass --content or --content-file, not both; nothing was sent.")
110
121
  target_data = {"environment": environment, "tenant_id": tenant_id, "name": name}
111
122
 
112
123
  def execute():
@@ -123,6 +134,7 @@ def create_create_command(dependencies: AmsDependencies) -> click.Command:
123
134
  description=description,
124
135
  content=content,
125
136
  guardrail_ids=list(guardrail_ids) or None,
137
+ content_file=content_file,
126
138
  )
127
139
 
128
140
  run_domain("policies.create", target_data, execute, read_only=False)
@@ -140,7 +152,16 @@ def create_update_command(dependencies: AmsDependencies) -> click.Command:
140
152
  @click.option(
141
153
  "--content",
142
154
  default=None,
143
- help="New policy content; an empty value clears it (write-only; never echoed).",
155
+ help="New policy content; an empty value clears it (write-only; never echoed). Exposed in argv, "
156
+ "the process table and shell history: prefer --content-file.",
157
+ )
158
+ @click.option(
159
+ "--content-file",
160
+ default=None,
161
+ type=click.Path(dir_okay=False),
162
+ help="Owner-only text file holding the new policy content, so the text never enters argv or "
163
+ "shell history (1-20000 characters). An empty file is refused. Cannot be combined with "
164
+ "--content.",
144
165
  )
145
166
  @click.option(
146
167
  "--guardrail-ids",
@@ -163,7 +184,10 @@ def create_update_command(dependencies: AmsDependencies) -> click.Command:
163
184
  content: str | None,
164
185
  guardrail_ids: tuple[str, ...],
165
186
  clear_guardrail_ids: bool,
187
+ content_file: str | None,
166
188
  ) -> None:
189
+ if content is not None and content_file is not None:
190
+ raise click.UsageError("Pass --content or --content-file, not both; nothing was sent.")
167
191
  target_data = {
168
192
  "environment": environment,
169
193
  "tenant_id": tenant_id,
@@ -181,14 +205,14 @@ def create_update_command(dependencies: AmsDependencies) -> click.Command:
181
205
  if description is not None:
182
206
  fields["description"] = description or None
183
207
  if content is not None:
184
- fields["content"] = content or None
208
+ fields["content"] = content
185
209
  if guardrail_ids:
186
210
  fields["guardrail_ids"] = list(guardrail_ids)
187
211
  elif clear_guardrail_ids:
188
212
  fields["guardrail_ids"] = []
189
- if not fields:
213
+ if not fields and content_file is None:
190
214
  raise click.UsageError("Nothing to update; pass at least one field option.")
191
- return update_policy(dependencies, target, policy_id, fields=fields)
215
+ return update_policy(dependencies, target, policy_id, fields=fields, content_file=content_file)
192
216
 
193
217
  run_domain("policies.update", target_data, execute, read_only=False)
194
218
 
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from ..context import AmsDependencies
6
6
  from ..domain_errors import DomainReadRefused
7
+ from ..output import protected_text
7
8
  from ..results import succeeded
8
9
  from ..target import TenantTarget
9
10
  from .projections import policy_projection
@@ -161,13 +162,26 @@ def create_policy(
161
162
  description: str | None,
162
163
  content: str | None,
163
164
  guardrail_ids: list[str] | None,
165
+ content_file: str | None = None,
164
166
  ) -> dict:
165
167
  """Create one policy; the envelope carries the server's authoritative readback.
166
168
 
167
169
  Policy content is never included in the result envelope or error messages.
170
+ It arrives from exactly one of ``content`` (argv, exposed) or
171
+ ``content_file`` (an owner-only file, read here so the text never enters
172
+ argv, the process table or shell history); the file is read, and the
173
+ single-source invariant enforced, before any login or network call, so an
174
+ ambiguous invocation never reaches the server.
168
175
  """
169
176
  from healthcloud.errors import HealthCloudHTTPError, HealthCloudNetworkError
170
177
 
178
+ content = protected_text(
179
+ content,
180
+ content_file,
181
+ flag="--content",
182
+ file_flag="--content-file",
183
+ refuse=PolicyWriteRefused,
184
+ )
171
185
  with dependencies.authenticated_sdk_factory(target) as sdk:
172
186
  resource = _resource(sdk, ("create",), write=True)
173
187
  try:
@@ -199,14 +213,36 @@ def create_policy(
199
213
  return _write_result("policies.create", target, policy.id, {"policy": policy_projection(policy)})
200
214
 
201
215
 
202
- def update_policy(dependencies: AmsDependencies, target: TenantTarget, policy_id: str, *, fields: dict) -> dict:
216
+ def update_policy(
217
+ dependencies: AmsDependencies,
218
+ target: TenantTarget,
219
+ policy_id: str,
220
+ *,
221
+ fields: dict,
222
+ content_file: str | None = None,
223
+ ) -> dict:
203
224
  """Partially update one policy; only explicitly set fields are sent.
204
225
 
205
226
  Policy content is write-only: it may be sent but never appears in the readback.
227
+ It arrives either inline in ``fields['content']`` (argv, exposed) or from a
228
+ single owner-only ``content_file``, resolved before any login or network
229
+ call. An empty inline value is the documented clear (``--content ""``),
230
+ while an empty file is refused so a truncated file cannot clear content.
206
231
  """
207
232
  from healthcloud.errors import HealthCloudHTTPError, HealthCloudNetworkError
208
233
 
209
234
  policy_id = _policy_id(policy_id)
235
+ fields = dict(fields)
236
+ resolved = protected_text(
237
+ fields.pop("content", None),
238
+ content_file,
239
+ flag="--content",
240
+ file_flag="--content-file",
241
+ refuse=PolicyWriteRefused,
242
+ allow_empty_inline=True,
243
+ )
244
+ if resolved is not None:
245
+ fields["content"] = resolved or None
210
246
  with dependencies.authenticated_sdk_factory(target) as sdk:
211
247
  resource = _resource(sdk, ("update",), write=True)
212
248
  try:
@@ -97,7 +97,15 @@ def create_create_command(dependencies: AmsDependencies) -> click.Command:
97
97
  @click.option(
98
98
  "--initial-prompt",
99
99
  default=None,
100
- help="Protected free-text opening prompt; becomes the session's first user message (never echoed).",
100
+ help="Protected free-text opening prompt; becomes the session's first user message (never echoed). "
101
+ "Exposed in argv, the process table and shell history: prefer --initial-prompt-file.",
102
+ )
103
+ @click.option(
104
+ "--initial-prompt-file",
105
+ default=None,
106
+ type=click.Path(dir_okay=False),
107
+ help="Owner-only text file holding the protected opening prompt, so the text never enters argv or "
108
+ "shell history (at most 20000 characters). Cannot be combined with --initial-prompt.",
101
109
  )
102
110
  @pass_ams
103
111
  def command(
@@ -107,7 +115,10 @@ def create_create_command(dependencies: AmsDependencies) -> click.Command:
107
115
  agent_id: str | None,
108
116
  mode: str,
109
117
  initial_prompt: str | None,
118
+ initial_prompt_file: str | None,
110
119
  ) -> None:
120
+ if initial_prompt is not None and initial_prompt_file is not None:
121
+ raise click.UsageError("Pass --initial-prompt or --initial-prompt-file, not both; nothing was sent.")
111
122
  target_data = {"environment": environment, "tenant_id": tenant_id, "agent_id": agent_id, "mode": mode}
112
123
 
113
124
  def execute():
@@ -121,6 +132,7 @@ def create_create_command(dependencies: AmsDependencies) -> click.Command:
121
132
  agent_id=agent_id,
122
133
  mode=mode,
123
134
  initial_prompt=initial_prompt,
135
+ initial_prompt_file=initial_prompt_file,
124
136
  )
125
137
 
126
138
  run_domain("sessions.create", target_data, execute, read_only=False)
@@ -504,8 +516,16 @@ def create_messages_add_command(dependencies: AmsDependencies) -> click.Command:
504
516
  @click.option("--session-id", required=True)
505
517
  @click.option(
506
518
  "--content",
507
- required=True,
508
- help="Protected free-text message content (never echoed); 1-20000 characters.",
519
+ default=None,
520
+ help="Protected free-text message content (never echoed); 1-20000 characters. Exposed in argv, the "
521
+ "process table and shell history: prefer --content-file.",
522
+ )
523
+ @click.option(
524
+ "--content-file",
525
+ default=None,
526
+ type=click.Path(dir_okay=False),
527
+ help="Owner-only text file holding the protected message content, so the text never enters argv or "
528
+ "shell history (1-20000 characters). Cannot be combined with --content.",
509
529
  )
510
530
  @click.option(
511
531
  "--mode", type=MODES, default="plan", show_default=True, help="Message mode; must match the session mode."
@@ -521,10 +541,15 @@ def create_messages_add_command(dependencies: AmsDependencies) -> click.Command:
521
541
  environment: str | None,
522
542
  tenant_id: str | None,
523
543
  session_id: str,
524
- content: str,
544
+ content: str | None,
525
545
  mode: str,
526
546
  idempotency_key: str | None,
547
+ content_file: str | None,
527
548
  ) -> None:
549
+ if content is not None and content_file is not None:
550
+ raise click.UsageError("Pass --content or --content-file, not both; nothing was sent.")
551
+ if content is None and content_file is None:
552
+ raise click.UsageError("Pass --content or --content-file; nothing was sent.")
528
553
  target_data = {
529
554
  "environment": environment,
530
555
  "tenant_id": tenant_id,
@@ -544,6 +569,7 @@ def create_messages_add_command(dependencies: AmsDependencies) -> click.Command:
544
569
  content=content,
545
570
  mode=mode,
546
571
  idempotency_key=idempotency_key,
572
+ content_file=content_file,
547
573
  )
548
574
 
549
575
  run_domain("sessions.messages.add", target_data, execute, read_only=False)
@@ -7,7 +7,7 @@ from pathlib import Path
7
7
 
8
8
  from ..context import AmsDependencies
9
9
  from ..domain_errors import DomainOutcomeUnknown, DomainReadRefused
10
- from ..output import private_output, read_private_text
10
+ from ..output import PROTECTED_TEXT_MAX_CHARS, private_output, protected_text, read_private_text
11
11
  from ..results import succeeded, unknown
12
12
  from ..target import TenantTarget
13
13
  from .projections import (
@@ -179,7 +179,7 @@ def _attachment_extension(filename: str) -> None:
179
179
 
180
180
  PLAN_OPERATIONS = ("modify", "create")
181
181
  PLAN_AGENT_TYPES = ("Application", "Utility")
182
- PROMPT_MAX_CHARS = 20000
182
+ PROMPT_MAX_CHARS = PROTECTED_TEXT_MAX_CHARS
183
183
  # The only fields a configure review endorses. Everything else the plan may try
184
184
  # to change (capability/permission expansion, lifecycle, model, files) is
185
185
  # refused by field name: permission assignment stays a separately reviewed
@@ -646,14 +646,25 @@ def create_session(
646
646
  agent_id: str | None,
647
647
  mode: str,
648
648
  initial_prompt: str | None,
649
+ initial_prompt_file: str | None = None,
649
650
  ) -> dict:
650
651
  """Create one session; the envelope carries the server's authoritative readback.
651
652
 
652
653
  ``initial_prompt`` is protected free text: it is sent once and never
653
- appears in the envelope or in any refusal.
654
+ appears in the envelope or in any refusal. It arrives from exactly one of
655
+ ``initial_prompt`` (argv, exposed) or ``initial_prompt_file`` (an owner-only
656
+ file, read here so the text never enters argv, the process table or shell
657
+ history); the file is read and bounded before any login or network call.
654
658
  """
655
659
  from healthcloud.errors import HealthCloudHTTPError, HealthCloudNetworkError
656
660
 
661
+ initial_prompt = protected_text(
662
+ initial_prompt,
663
+ initial_prompt_file,
664
+ flag="--initial-prompt",
665
+ file_flag="--initial-prompt-file",
666
+ refuse=SessionWriteRefused,
667
+ )
657
668
  with dependencies.authenticated_sdk_factory(target) as sdk:
658
669
  resource = _sessions(sdk, ("create",), write=True)
659
670
  try:
@@ -999,20 +1010,34 @@ def add_message(
999
1010
  target: TenantTarget,
1000
1011
  session_id: str,
1001
1012
  *,
1002
- content: str,
1013
+ content: str | None,
1003
1014
  mode: str,
1004
1015
  idempotency_key: str | None,
1016
+ content_file: str | None = None,
1005
1017
  ) -> dict:
1006
1018
  """Append one user message; the envelope carries the server's authoritative readback.
1007
1019
 
1008
1020
  ``content`` is protected free text: it is sent once and never appears in
1009
- the envelope or in any refusal. An idempotency replay answers 201 with the
1010
- existing message, so the envelope reports that message (with its original
1011
- id) and never a second write.
1021
+ the envelope or in any refusal. It arrives from exactly one of ``content``
1022
+ (argv, exposed) or ``content_file`` (an owner-only file, read here so the
1023
+ text never enters argv, the process table or shell history); the file is
1024
+ read, and its presence enforced, before any login or network call, so the
1025
+ one-source and non-empty invariants hold below the Click layer too. An
1026
+ idempotency replay answers 201 with the existing message, so the envelope
1027
+ reports that message
1028
+ (with its original id) and never a second write.
1012
1029
  """
1013
1030
  from healthcloud.errors import HealthCloudHTTPError, HealthCloudNetworkError
1014
1031
 
1015
1032
  session_id = _session_id(session_id)
1033
+ content = protected_text(
1034
+ content,
1035
+ content_file,
1036
+ flag="--content",
1037
+ file_flag="--content-file",
1038
+ refuse=SessionWriteRefused,
1039
+ required=True,
1040
+ )
1016
1041
  with dependencies.authenticated_sdk_factory(target) as sdk:
1017
1042
  resource = _messages(sdk, ("add",), write=True)
1018
1043
  try:
@@ -30336,7 +30336,7 @@
30336
30336
  "defaultBaseUrl": "patient",
30337
30337
  "name": "patient",
30338
30338
  "path": "/patient",
30339
- "toolCount": 114,
30339
+ "toolCount": 118,
30340
30340
  "tools": [
30341
30341
  {
30342
30342
  "cliCommand": "add-insurance",
@@ -30641,6 +30641,52 @@
30641
30641
  "requiredRoles": [],
30642
30642
  "timeoutSeconds": 25
30643
30643
  },
30644
+ {
30645
+ "cliCommand": "apply-patient-flags-operation",
30646
+ "description": "Apply the exact patient-confirmed boolean flag patch under a stable canonical UUID key. Returns the original stored receipt on replay, not current flags. Requires the patient's Bearer JWT; tenant and patient are resolved server-side. On timeout or unknown outcome, look up the same key before proceeding; never create a fresh key to retry an uncertain write.",
30647
+ "descriptionSource": "mcp-authored",
30648
+ "destructive": false,
30649
+ "fixedArguments": null,
30650
+ "httpMethod": "POST",
30651
+ "httpPath": "/patients/flags/operations",
30652
+ "inputSchema": {
30653
+ "additionalProperties": false,
30654
+ "properties": {
30655
+ "flags": {
30656
+ "additionalProperties": {
30657
+ "type": "boolean"
30658
+ },
30659
+ "minProperties": 1,
30660
+ "propertyNames": {
30661
+ "maxLength": 256,
30662
+ "minLength": 1
30663
+ },
30664
+ "type": "object"
30665
+ },
30666
+ "idempotency_key": {
30667
+ "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
30668
+ "type": "string"
30669
+ }
30670
+ },
30671
+ "required": [
30672
+ "idempotency_key",
30673
+ "flags"
30674
+ ],
30675
+ "type": "object"
30676
+ },
30677
+ "name": "apply_patient_flags_operation",
30678
+ "operationId": "patient.applyPatientFlagsOperation",
30679
+ "parameters": {
30680
+ "body": [
30681
+ "flags",
30682
+ "idempotency_key"
30683
+ ],
30684
+ "path": [],
30685
+ "query": []
30686
+ },
30687
+ "requiredRoles": [],
30688
+ "timeoutSeconds": 25
30689
+ },
30644
30690
  {
30645
30691
  "cliCommand": "ask-assistant",
30646
30692
  "description": "Ask the grounded RAG field-worker assistant a question. Answers are drawn only from the internal knowledge base (PPE, screening, sample collection, field ops).",
@@ -32200,6 +32246,39 @@
32200
32246
  "requiredRoles": [],
32201
32247
  "timeoutSeconds": 25
32202
32248
  },
32249
+ {
32250
+ "cliCommand": "get-patient-flags-operation",
32251
+ "description": "Read the authenticated patient's original flag-operation receipt by canonical UUID key. Never writes or infers an outcome from current flags. A missing receipt does not prove failure: an in-flight write may still commit. Requires the patient's Bearer JWT.",
32252
+ "descriptionSource": "mcp-authored",
32253
+ "destructive": false,
32254
+ "fixedArguments": null,
32255
+ "httpMethod": "GET",
32256
+ "httpPath": "/patients/flags/operations/{idempotency_key}",
32257
+ "inputSchema": {
32258
+ "additionalProperties": false,
32259
+ "properties": {
32260
+ "idempotency_key": {
32261
+ "pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
32262
+ "type": "string"
32263
+ }
32264
+ },
32265
+ "required": [
32266
+ "idempotency_key"
32267
+ ],
32268
+ "type": "object"
32269
+ },
32270
+ "name": "get_patient_flags_operation",
32271
+ "operationId": "patient.getPatientFlagsOperation",
32272
+ "parameters": {
32273
+ "body": [],
32274
+ "path": [
32275
+ "idempotency_key"
32276
+ ],
32277
+ "query": []
32278
+ },
32279
+ "requiredRoles": [],
32280
+ "timeoutSeconds": 25
32281
+ },
32203
32282
  {
32204
32283
  "cliCommand": "get-patient-insurance",
32205
32284
  "description": "List FHIR Coverage resources for the authenticated patient.",
@@ -33504,6 +33583,89 @@
33504
33583
  "requiredRoles": [],
33505
33584
  "timeoutSeconds": 25
33506
33585
  },
33586
+ {
33587
+ "cliCommand": "replace-insurance",
33588
+ "description": "Replace existing primary self insurance only after the patient's explicit confirmation of changed card details. Never call for an OCR rescan before first save, an eligibility retry, or Continue without insurance. Keep request_id and the body identical for retries. A pending result requires reconciliation, not add_insurance.",
33589
+ "descriptionSource": "mcp-authored",
33590
+ "destructive": false,
33591
+ "fixedArguments": null,
33592
+ "httpMethod": "PUT",
33593
+ "httpPath": "/patients/insurance/{coverage_id}",
33594
+ "inputSchema": {
33595
+ "additionalProperties": false,
33596
+ "properties": {
33597
+ "coverage_id": {
33598
+ "pattern": "^[A-Za-z0-9.-]{1,64}$",
33599
+ "type": "string"
33600
+ },
33601
+ "image": {
33602
+ "description": "Reviewed replacement card image, base64 PNG/JPEG. The previous card is not reused.",
33603
+ "maxLength": 2000000,
33604
+ "minLength": 1,
33605
+ "title": "Image",
33606
+ "type": "string"
33607
+ },
33608
+ "member_id": {
33609
+ "maxLength": 50,
33610
+ "minLength": 1,
33611
+ "title": "Member Id",
33612
+ "type": "string"
33613
+ },
33614
+ "payer_id": {
33615
+ "maxLength": 100,
33616
+ "minLength": 1,
33617
+ "title": "Payer Id",
33618
+ "type": "string"
33619
+ },
33620
+ "payer_name": {
33621
+ "maxLength": 200,
33622
+ "minLength": 1,
33623
+ "title": "Payer Name",
33624
+ "type": "string"
33625
+ },
33626
+ "request_id": {
33627
+ "description": "Client-generated UUID retained for every retry of this confirmed change.",
33628
+ "format": "uuid",
33629
+ "title": "Request Id",
33630
+ "type": "string"
33631
+ },
33632
+ "state": {
33633
+ "description": "Reviewed US coverage state used for vendor package matching.",
33634
+ "pattern": "^[A-Z]{2}$",
33635
+ "title": "State",
33636
+ "type": "string"
33637
+ }
33638
+ },
33639
+ "required": [
33640
+ "coverage_id",
33641
+ "request_id",
33642
+ "payer_id",
33643
+ "payer_name",
33644
+ "member_id",
33645
+ "state",
33646
+ "image"
33647
+ ],
33648
+ "type": "object"
33649
+ },
33650
+ "name": "replace_insurance",
33651
+ "operationId": "patient.replaceInsurance",
33652
+ "parameters": {
33653
+ "body": [
33654
+ "image",
33655
+ "member_id",
33656
+ "payer_id",
33657
+ "payer_name",
33658
+ "request_id",
33659
+ "state"
33660
+ ],
33661
+ "path": [
33662
+ "coverage_id"
33663
+ ],
33664
+ "query": []
33665
+ },
33666
+ "requiredRoles": [],
33667
+ "timeoutSeconds": 120
33668
+ },
33507
33669
  {
33508
33670
  "cliCommand": "resend-verification-code",
33509
33671
  "description": "Issue a fresh email-verification OTP for a patient whose email is not yet verified.",
@@ -33578,6 +33740,48 @@
33578
33740
  "requiredRoles": [],
33579
33741
  "timeoutSeconds": 25
33580
33742
  },
33743
+ {
33744
+ "cliCommand": "retire-insurance",
33745
+ "description": "Retire the exact saved primary self insurance only after explicit patient confirmation of removal. History is retained; no replacement is created and the insurer contract is not cancelled. Never call because eligibility is inactive, unknown or unavailable, or for Continue without insurance. Reuse request_id for retries; pending is not completed removal.",
33746
+ "descriptionSource": "mcp-authored",
33747
+ "destructive": false,
33748
+ "fixedArguments": null,
33749
+ "httpMethod": "POST",
33750
+ "httpPath": "/patients/insurance/{coverage_id}/retire",
33751
+ "inputSchema": {
33752
+ "additionalProperties": false,
33753
+ "properties": {
33754
+ "coverage_id": {
33755
+ "pattern": "^[A-Za-z0-9.-]{1,64}$",
33756
+ "type": "string"
33757
+ },
33758
+ "request_id": {
33759
+ "description": "Client-generated UUID retained for retries of this confirmed removal.",
33760
+ "format": "uuid",
33761
+ "title": "Request Id",
33762
+ "type": "string"
33763
+ }
33764
+ },
33765
+ "required": [
33766
+ "coverage_id",
33767
+ "request_id"
33768
+ ],
33769
+ "type": "object"
33770
+ },
33771
+ "name": "retire_insurance",
33772
+ "operationId": "patient.retireInsurance",
33773
+ "parameters": {
33774
+ "body": [
33775
+ "request_id"
33776
+ ],
33777
+ "path": [
33778
+ "coverage_id"
33779
+ ],
33780
+ "query": []
33781
+ },
33782
+ "requiredRoles": [],
33783
+ "timeoutSeconds": 120
33784
+ },
33581
33785
  {
33582
33786
  "cliCommand": "search-assistant-knowledge",
33583
33787
  "description": "Search the assistant knowledge base lexically.",
@@ -41252,7 +41456,7 @@
41252
41456
  "protocolVersion": "2024-11-05",
41253
41457
  "schemaVersion": 1,
41254
41458
  "source": {
41255
- "catalogSha256": "8ee176981250729af9845b7e6dd65fb847f19c9cbbb9ed21429d53e098bbdeab"
41459
+ "catalogSha256": "035606bcc89f84b5a6efbc1ef52a2bcfd0359c1e14eac8e2b60e1f36e2d0c035"
41256
41460
  },
41257
- "toolCount": 1076
41461
+ "toolCount": 1080
41258
41462
  }
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "healthcloud-cli"
7
- version = "3.6.0"
7
+ version = "3.7.0"
8
8
  description = "Command-line interface for the HealthCloud MCP gateway, generated from the shared MCP tool manifest."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.14"