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.
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/.gitignore +2 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/PKG-INFO +52 -5
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/README.md +51 -4
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/output.py +56 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/policies/commands.py +29 -5
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/policies/operations.py +37 -1
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/sessions/commands.py +30 -4
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/sessions/operations.py +32 -7
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/contracts/mcp-tools.manifest.json +207 -3
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/pyproject.toml +1 -1
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/hatch_build.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/admins/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/admins/commands.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/admins/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/admins/operations.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/agents/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/agents/commands.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/agents/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/agents/operations.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/agents/projections.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/auth.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/commands.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/compatibility.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/context.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/commands.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/operations.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/projections.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/departments/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/departments/commands.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/departments/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/departments/operations.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/departments/projections.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/domain_errors.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/errors.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/guardrails/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/guardrails/commands.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/guardrails/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/guardrails/operations.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/guardrails/projections.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/commands.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/operations.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/projections.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/policies/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/policies/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/policies/projections.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/pretty.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/registry.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/results.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/session.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/sessions/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/sessions/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/sessions/projections.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/skills/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/skills/commands.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/skills/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/skills/operations.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/skills/projections.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/target.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/teams/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/teams/commands.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/teams/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/teams/operations.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/teams/projections.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/tools/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/tools/commands.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/tools/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/tools/operations.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/tools/projections.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/tuning.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/ui.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/windows_files.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/cli.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/context.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/contracts/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/main.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/manifest.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/output.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/params.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/tenants/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/tenants/commands.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/tenants/group.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/tenants/operations.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/util.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/scripts/__init__.py +0 -0
- {healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/scripts/bundle_manifest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: healthcloud-cli
|
|
3
|
-
Version: 3.
|
|
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
|
|
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(
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
1010
|
-
|
|
1011
|
-
|
|
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:
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/contracts/mcp-tools.manifest.json
RENAMED
|
@@ -30336,7 +30336,7 @@
|
|
|
30336
30336
|
"defaultBaseUrl": "patient",
|
|
30337
30337
|
"name": "patient",
|
|
30338
30338
|
"path": "/patient",
|
|
30339
|
-
"toolCount":
|
|
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": "
|
|
41459
|
+
"catalogSha256": "035606bcc89f84b5a6efbc1ef52a2bcfd0359c1e14eac8e2b60e1f36e2d0c035"
|
|
41256
41460
|
},
|
|
41257
|
-
"toolCount":
|
|
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.
|
|
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"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/__init__.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/commands.py
RENAMED
|
File without changes
|
|
File without changes
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/operations.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/data_sources/projections.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/departments/operations.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/departments/projections.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/guardrails/operations.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/guardrails/projections.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/__init__.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/commands.py
RENAMED
|
File without changes
|
|
File without changes
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/operations.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.6.0 → healthcloud_cli-3.7.0}/healthcloud_cli/ams/organization/projections.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|