healthcloud-cli 3.7.5__tar.gz → 3.7.7__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.7.5 → healthcloud_cli-3.7.7}/PKG-INFO +38 -13
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/README.md +37 -12
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/context.py +25 -9
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/sessions/commands.py +2 -2
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/sessions/operations.py +80 -22
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/pyproject.toml +1 -1
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/.gitignore +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/hatch_build.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/admins/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/admins/commands.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/admins/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/admins/operations.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/agents/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/agents/commands.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/agents/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/agents/operations.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/agents/projections.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/auth.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/commands.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/compatibility.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/data_sources/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/data_sources/commands.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/data_sources/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/data_sources/operations.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/data_sources/projections.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/departments/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/departments/commands.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/departments/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/departments/operations.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/departments/projections.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/domain_errors.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/errors.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/guardrails/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/guardrails/commands.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/guardrails/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/guardrails/operations.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/guardrails/projections.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/organization/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/organization/commands.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/organization/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/organization/operations.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/organization/projections.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/output.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/policies/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/policies/commands.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/policies/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/policies/operations.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/policies/projections.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/pretty.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/registry.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/results.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/session.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/sessions/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/sessions/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/sessions/projections.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/skills/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/skills/commands.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/skills/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/skills/operations.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/skills/projections.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/target.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/teams/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/teams/commands.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/teams/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/teams/operations.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/teams/projections.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/tools/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/tools/commands.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/tools/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/tools/operations.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/tools/projections.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/tuning.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/ui.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/windows_files.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/cli.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/context.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/contracts/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/contracts/mcp-tools.manifest.json +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/main.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/manifest.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/output.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/params.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/tenants/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/tenants/commands.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/tenants/group.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/tenants/operations.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/util.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/scripts/__init__.py +0 -0
- {healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/scripts/bundle_manifest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: healthcloud-cli
|
|
3
|
-
Version: 3.7.
|
|
3
|
+
Version: 3.7.7
|
|
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
|
|
@@ -1082,9 +1082,14 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
|
|
|
1082
1082
|
|
|
1083
1083
|
- `list` emits `operation: "sessions.list"`, `data.sessions` (bounded projections
|
|
1084
1084
|
of session ID, agent ID, operation, mode, status and audit timestamps) and a
|
|
1085
|
-
`page` block
|
|
1086
|
-
|
|
1087
|
-
`
|
|
1085
|
+
`page` block. These routes page with a limit only, so the block always says
|
|
1086
|
+
which limit was in force: with `--limit N` it is
|
|
1087
|
+
`{"kind": "limit", "limit": N, "returned_count": N, "continuation": null}`,
|
|
1088
|
+
and without it the server's own default page was applied, which the block
|
|
1089
|
+
reports as `{"kind": "server_default", "server_default_limit": 50,
|
|
1090
|
+
"returned_count": N, "continuation": null}` (50 sessions, 100 messages) — so
|
|
1091
|
+
a truncated page is never mistaken for a complete list. `get` emits
|
|
1092
|
+
`operation: "sessions.get"` and `data.session`. The projections never include the session's protected
|
|
1088
1093
|
`initial_prompt`, tenant or user identity, or arbitrary response fields.
|
|
1089
1094
|
- Default `pretty` output prints `list` as a table with honest headers —
|
|
1090
1095
|
`ID`, `AGENT ID`, `OPERATION`, `STATUS`, `EXPIRES AT` for sessions and `ID`,
|
|
@@ -1099,8 +1104,12 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
|
|
|
1099
1104
|
the envelope carries the server's authoritative readback:
|
|
1100
1105
|
`operation: "sessions.create"` with `data.session`, and
|
|
1101
1106
|
`operation: "sessions.messages.add"` with `data.message` (message ID, role,
|
|
1102
|
-
mode, plan ID, model, timestamps) plus `data.idempotency_key`
|
|
1103
|
-
|
|
1107
|
+
mode, plan ID, model, timestamps) plus `data.idempotency_key` — the key the
|
|
1108
|
+
server read back, when the returned message carries one. When the readback
|
|
1109
|
+
does not echo a key, the field is omitted: the envelope never asserts a
|
|
1110
|
+
key→message association the server did not confirm. An `--idempotency-key`
|
|
1111
|
+
longer than 128 characters (or an empty one) is refused locally with a
|
|
1112
|
+
`validation` error naming the flag, before anything is sent. Protected free text — `--initial-prompt` and `--content` — is sent to
|
|
1104
1113
|
the server but never appears in the envelope, a refusal, a diagnostic or a log.
|
|
1105
1114
|
- The protected free text can also arrive from an owner-only file, which keeps it
|
|
1106
1115
|
out of the command line entirely (argv, the process table under
|
|
@@ -1129,18 +1138,24 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
|
|
|
1129
1138
|
- `--initial-prompt` becomes the session's first `user` message in the same
|
|
1130
1139
|
server transaction; `hc ams sessions messages list` shows it afterwards.
|
|
1131
1140
|
- `--mode implement` is not available from the server yet: `create` reports one
|
|
1132
|
-
`unsupported` refusal (the server answers 501) and creates nothing
|
|
1141
|
+
`unsupported` refusal (the server answers 501) and creates nothing, and a
|
|
1142
|
+
`messages add` the server answers 501 for is the same `unsupported` refusal
|
|
1143
|
+
with no message added. Both refusals report the 501 the server actually sent
|
|
1144
|
+
and name implement mode as the documented reason, rather than asserting the
|
|
1145
|
+
requested mode was the cause. A message
|
|
1133
1146
|
whose `--mode` differs from its session's mode is refused by the server with
|
|
1134
|
-
409 and reported as one write-worded `conflict` refusal that
|
|
1135
|
-
mode
|
|
1136
|
-
message is added, and the read-era conflict wording
|
|
1147
|
+
409 and reported as one write-worded `conflict` refusal that names the
|
|
1148
|
+
session, mentions a mode mismatch as one possible cause, and points at
|
|
1149
|
+
`hc ams sessions get`; no message is added, and the read-era conflict wording
|
|
1150
|
+
is never used.
|
|
1137
1151
|
- A missing, expired or other user's session is a `not_found` refusal naming the
|
|
1138
1152
|
session on `get`, `messages list` and `messages add`; on `create`, a 404 names
|
|
1139
1153
|
the requested `--agent-id`.
|
|
1140
1154
|
- Messages with an `--idempotency-key` are replay-safe: the server answers 201
|
|
1141
1155
|
with the existing message instead of inserting a second one, and the command
|
|
1142
1156
|
reports that message (its original ID) — a replay is never reported as a new
|
|
1143
|
-
write, and the command itself never re-sends a write.
|
|
1157
|
+
write, and the command itself never re-sends a write. The envelope echoes the
|
|
1158
|
+
server's read-back key, never a key the server did not confirm.
|
|
1144
1159
|
- If the transport times out or the connection drops mid-write, the write's
|
|
1145
1160
|
outcome is unknown: the CLI reports one `transport` error whose message says so
|
|
1146
1161
|
and points at `hc ams sessions list`/`hc ams sessions messages list` before
|
|
@@ -1149,8 +1164,15 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
|
|
|
1149
1164
|
landed write is never reported as stopped.
|
|
1150
1165
|
- Reads refuse target files, root credentials or base URLs, raw output, blank or
|
|
1151
1166
|
dot-segment session IDs, out-of-range limits, and installed SDKs without the
|
|
1152
|
-
session list/get and message list
|
|
1153
|
-
read.
|
|
1167
|
+
session list/get and message list methods before tenant-admin login or any
|
|
1168
|
+
read. `messages list` needs only `messages.list` — the write method `add` is
|
|
1169
|
+
not required for the read. The writes check their own methods in the same
|
|
1170
|
+
preflight — each command requires exactly the write method it calls, so
|
|
1171
|
+
`create` refuses an installed SDK without `sessions.create` and `messages add`
|
|
1172
|
+
one without `messages.add` before tenant-admin login and before any read or
|
|
1173
|
+
write, and neither is refused for the write the other command performs. A single-object route whose 2xx body is not a JSON
|
|
1174
|
+
object is refused as a `validation` error, never reported as an internal
|
|
1175
|
+
failure. The existing `hc ams plan|show|apply|restore` tuning commands and their
|
|
1154
1176
|
signatures are unchanged.
|
|
1155
1177
|
- These session and message commands do not attach files, apply or roll back
|
|
1156
1178
|
configuration, or send session history to a model. Plan review is its own
|
|
@@ -1241,6 +1263,9 @@ uv run --frozen hc ams sessions plans reconcile --environment dev --tenant-id TE
|
|
|
1241
1263
|
message write, no `PUT` and no apply. The server runs the planner once. It
|
|
1242
1264
|
requires a saved `hc login` session and reports
|
|
1243
1265
|
`operation: "sessions.plans.generate"` with `data.plan` and `data.out`.
|
|
1266
|
+
An `--idempotency-key` longer than 128 characters (or an empty one) is
|
|
1267
|
+
refused locally with a `validation` error naming the flag, before anything
|
|
1268
|
+
is sent.
|
|
1244
1269
|
`get` sends exactly one `GET /vibecoder/plans/PLAN_ID` and reports
|
|
1245
1270
|
`operation: "sessions.plans.get"`; a reload is immutable, so it returns the
|
|
1246
1271
|
same `plan_hash` and diff however the session changed afterwards. `get` is a
|
|
@@ -1064,9 +1064,14 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
|
|
|
1064
1064
|
|
|
1065
1065
|
- `list` emits `operation: "sessions.list"`, `data.sessions` (bounded projections
|
|
1066
1066
|
of session ID, agent ID, operation, mode, status and audit timestamps) and a
|
|
1067
|
-
`page` block
|
|
1068
|
-
|
|
1069
|
-
`
|
|
1067
|
+
`page` block. These routes page with a limit only, so the block always says
|
|
1068
|
+
which limit was in force: with `--limit N` it is
|
|
1069
|
+
`{"kind": "limit", "limit": N, "returned_count": N, "continuation": null}`,
|
|
1070
|
+
and without it the server's own default page was applied, which the block
|
|
1071
|
+
reports as `{"kind": "server_default", "server_default_limit": 50,
|
|
1072
|
+
"returned_count": N, "continuation": null}` (50 sessions, 100 messages) — so
|
|
1073
|
+
a truncated page is never mistaken for a complete list. `get` emits
|
|
1074
|
+
`operation: "sessions.get"` and `data.session`. The projections never include the session's protected
|
|
1070
1075
|
`initial_prompt`, tenant or user identity, or arbitrary response fields.
|
|
1071
1076
|
- Default `pretty` output prints `list` as a table with honest headers —
|
|
1072
1077
|
`ID`, `AGENT ID`, `OPERATION`, `STATUS`, `EXPIRES AT` for sessions and `ID`,
|
|
@@ -1081,8 +1086,12 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
|
|
|
1081
1086
|
the envelope carries the server's authoritative readback:
|
|
1082
1087
|
`operation: "sessions.create"` with `data.session`, and
|
|
1083
1088
|
`operation: "sessions.messages.add"` with `data.message` (message ID, role,
|
|
1084
|
-
mode, plan ID, model, timestamps) plus `data.idempotency_key`
|
|
1085
|
-
|
|
1089
|
+
mode, plan ID, model, timestamps) plus `data.idempotency_key` — the key the
|
|
1090
|
+
server read back, when the returned message carries one. When the readback
|
|
1091
|
+
does not echo a key, the field is omitted: the envelope never asserts a
|
|
1092
|
+
key→message association the server did not confirm. An `--idempotency-key`
|
|
1093
|
+
longer than 128 characters (or an empty one) is refused locally with a
|
|
1094
|
+
`validation` error naming the flag, before anything is sent. Protected free text — `--initial-prompt` and `--content` — is sent to
|
|
1086
1095
|
the server but never appears in the envelope, a refusal, a diagnostic or a log.
|
|
1087
1096
|
- The protected free text can also arrive from an owner-only file, which keeps it
|
|
1088
1097
|
out of the command line entirely (argv, the process table under
|
|
@@ -1111,18 +1120,24 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
|
|
|
1111
1120
|
- `--initial-prompt` becomes the session's first `user` message in the same
|
|
1112
1121
|
server transaction; `hc ams sessions messages list` shows it afterwards.
|
|
1113
1122
|
- `--mode implement` is not available from the server yet: `create` reports one
|
|
1114
|
-
`unsupported` refusal (the server answers 501) and creates nothing
|
|
1123
|
+
`unsupported` refusal (the server answers 501) and creates nothing, and a
|
|
1124
|
+
`messages add` the server answers 501 for is the same `unsupported` refusal
|
|
1125
|
+
with no message added. Both refusals report the 501 the server actually sent
|
|
1126
|
+
and name implement mode as the documented reason, rather than asserting the
|
|
1127
|
+
requested mode was the cause. A message
|
|
1115
1128
|
whose `--mode` differs from its session's mode is refused by the server with
|
|
1116
|
-
409 and reported as one write-worded `conflict` refusal that
|
|
1117
|
-
mode
|
|
1118
|
-
message is added, and the read-era conflict wording
|
|
1129
|
+
409 and reported as one write-worded `conflict` refusal that names the
|
|
1130
|
+
session, mentions a mode mismatch as one possible cause, and points at
|
|
1131
|
+
`hc ams sessions get`; no message is added, and the read-era conflict wording
|
|
1132
|
+
is never used.
|
|
1119
1133
|
- A missing, expired or other user's session is a `not_found` refusal naming the
|
|
1120
1134
|
session on `get`, `messages list` and `messages add`; on `create`, a 404 names
|
|
1121
1135
|
the requested `--agent-id`.
|
|
1122
1136
|
- Messages with an `--idempotency-key` are replay-safe: the server answers 201
|
|
1123
1137
|
with the existing message instead of inserting a second one, and the command
|
|
1124
1138
|
reports that message (its original ID) — a replay is never reported as a new
|
|
1125
|
-
write, and the command itself never re-sends a write.
|
|
1139
|
+
write, and the command itself never re-sends a write. The envelope echoes the
|
|
1140
|
+
server's read-back key, never a key the server did not confirm.
|
|
1126
1141
|
- If the transport times out or the connection drops mid-write, the write's
|
|
1127
1142
|
outcome is unknown: the CLI reports one `transport` error whose message says so
|
|
1128
1143
|
and points at `hc ams sessions list`/`hc ams sessions messages list` before
|
|
@@ -1131,8 +1146,15 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
|
|
|
1131
1146
|
landed write is never reported as stopped.
|
|
1132
1147
|
- Reads refuse target files, root credentials or base URLs, raw output, blank or
|
|
1133
1148
|
dot-segment session IDs, out-of-range limits, and installed SDKs without the
|
|
1134
|
-
session list/get and message list
|
|
1135
|
-
read.
|
|
1149
|
+
session list/get and message list methods before tenant-admin login or any
|
|
1150
|
+
read. `messages list` needs only `messages.list` — the write method `add` is
|
|
1151
|
+
not required for the read. The writes check their own methods in the same
|
|
1152
|
+
preflight — each command requires exactly the write method it calls, so
|
|
1153
|
+
`create` refuses an installed SDK without `sessions.create` and `messages add`
|
|
1154
|
+
one without `messages.add` before tenant-admin login and before any read or
|
|
1155
|
+
write, and neither is refused for the write the other command performs. A single-object route whose 2xx body is not a JSON
|
|
1156
|
+
object is refused as a `validation` error, never reported as an internal
|
|
1157
|
+
failure. The existing `hc ams plan|show|apply|restore` tuning commands and their
|
|
1136
1158
|
signatures are unchanged.
|
|
1137
1159
|
- These session and message commands do not attach files, apply or roll back
|
|
1138
1160
|
configuration, or send session history to a model. Plan review is its own
|
|
@@ -1223,6 +1245,9 @@ uv run --frozen hc ams sessions plans reconcile --environment dev --tenant-id TE
|
|
|
1223
1245
|
message write, no `PUT` and no apply. The server runs the planner once. It
|
|
1224
1246
|
requires a saved `hc login` session and reports
|
|
1225
1247
|
`operation: "sessions.plans.generate"` with `data.plan` and `data.out`.
|
|
1248
|
+
An `--idempotency-key` longer than 128 characters (or an empty one) is
|
|
1249
|
+
refused locally with a `validation` error naming the flag, before anything
|
|
1250
|
+
is sent.
|
|
1226
1251
|
`get` sends exactly one `GET /vibecoder/plans/PLAN_ID` and reports
|
|
1227
1252
|
`operation: "sessions.plans.get"`; a reload is immutable, so it returns the
|
|
1228
1253
|
same `plan_hash` and diff however the session changed afterwards. `get` is a
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from collections.abc import Callable
|
|
5
|
+
from collections.abc import Callable, Sequence
|
|
6
6
|
from contextlib import AbstractContextManager
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from pathlib import Path
|
|
@@ -236,8 +236,20 @@ def preflight_policies(invocation: AmsContext, target: TenantTarget) -> None:
|
|
|
236
236
|
sdk.close()
|
|
237
237
|
|
|
238
238
|
|
|
239
|
-
def preflight_sessions(
|
|
240
|
-
|
|
239
|
+
def preflight_sessions(
|
|
240
|
+
invocation: AmsContext,
|
|
241
|
+
target: TenantTarget,
|
|
242
|
+
*,
|
|
243
|
+
write_methods: Sequence[str] = (),
|
|
244
|
+
) -> None:
|
|
245
|
+
"""Validate local session compatibility before login or any request.
|
|
246
|
+
|
|
247
|
+
``write_methods`` names exactly the write methods the calling command will
|
|
248
|
+
call, as ``"<resource>.<method>"`` (``sessions.create``, ``messages.add``),
|
|
249
|
+
so a write command is refused here — before tenant-admin login and before any
|
|
250
|
+
read or write — only for a method it actually needs; a write it never
|
|
251
|
+
performs is never required of the installed SDK.
|
|
252
|
+
"""
|
|
241
253
|
|
|
242
254
|
_check_root(invocation)
|
|
243
255
|
if invocation.target_file is not None:
|
|
@@ -245,12 +257,16 @@ def preflight_sessions(invocation: AmsContext, target: TenantTarget) -> None:
|
|
|
245
257
|
sdk = _public_sdk(target.environment, target.tenant_id)
|
|
246
258
|
try:
|
|
247
259
|
resource = getattr(getattr(sdk, "ams_admin", None), "sessions", None)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
260
|
+
messages = getattr(resource, "messages", None)
|
|
261
|
+
resources = {"sessions": resource, "messages": messages}
|
|
262
|
+
required = (resource, "list"), (resource, "get"), (messages, "list")
|
|
263
|
+
missing = any(not callable(getattr(holder, method, None)) for holder, method in required)
|
|
264
|
+
for qualified in write_methods:
|
|
265
|
+
owner, _, method = qualified.partition(".")
|
|
266
|
+
holder = resources.get(owner)
|
|
267
|
+
if not method or not callable(getattr(holder, method, None)):
|
|
268
|
+
missing = True
|
|
269
|
+
if missing:
|
|
254
270
|
raise click.ClickException(
|
|
255
271
|
"SDK missing ams_admin sessions; use the reviewed SDK version required by this CLI."
|
|
256
272
|
)
|
|
@@ -125,7 +125,7 @@ def create_create_command(dependencies: AmsDependencies) -> click.Command:
|
|
|
125
125
|
_require_login()
|
|
126
126
|
target = resolve_tenant_target(environment, tenant_id)
|
|
127
127
|
target_data.update(environment=target.environment, tenant_id=target.tenant_id)
|
|
128
|
-
preflight_sessions(invocation, target)
|
|
128
|
+
preflight_sessions(invocation, target, write_methods=("sessions.create",))
|
|
129
129
|
return create_session(
|
|
130
130
|
dependencies,
|
|
131
131
|
target,
|
|
@@ -561,7 +561,7 @@ def create_messages_add_command(dependencies: AmsDependencies) -> click.Command:
|
|
|
561
561
|
_require_login()
|
|
562
562
|
target = resolve_tenant_target(environment, tenant_id)
|
|
563
563
|
target_data.update(environment=target.environment, tenant_id=target.tenant_id)
|
|
564
|
-
preflight_sessions(invocation, target)
|
|
564
|
+
preflight_sessions(invocation, target, write_methods=("messages.add",))
|
|
565
565
|
return add_message(
|
|
566
566
|
dependencies,
|
|
567
567
|
target,
|
|
@@ -25,7 +25,11 @@ from .projections import (
|
|
|
25
25
|
)
|
|
26
26
|
|
|
27
27
|
SESSION_LIMIT_MAX = 100
|
|
28
|
+
# The server's own defaults when ``--limit`` is omitted: an omitted limit is a
|
|
29
|
+
# real, truncating page, so the envelope reports which default was applied.
|
|
30
|
+
SESSION_LIMIT_DEFAULT = 50
|
|
28
31
|
MESSAGE_LIMIT_MAX = 200
|
|
32
|
+
MESSAGE_LIMIT_DEFAULT = 100
|
|
29
33
|
# Mirror the server's own bounds so an obviously unacceptable file is never
|
|
30
34
|
# put on the wire; the server's refusal still wins when it disagrees.
|
|
31
35
|
ATTACHMENT_MAX_BYTES = 5 * 1024 * 1024
|
|
@@ -87,6 +91,28 @@ def _session_id(value: str) -> str:
|
|
|
87
91
|
return value
|
|
88
92
|
|
|
89
93
|
|
|
94
|
+
IDEMPOTENCY_KEY_MAX_CHARS = 128
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _idempotency_key(value, refuse):
|
|
98
|
+
"""Mirror the SDK's 128-character bound locally (the help text advertises it).
|
|
99
|
+
|
|
100
|
+
Refused before login or any request, so the bound holds regardless of the
|
|
101
|
+
installed SDK version. A non-empty string within the bound passes through
|
|
102
|
+
unchanged; the caller keeps its own refusal class (message write vs plan
|
|
103
|
+
review).
|
|
104
|
+
"""
|
|
105
|
+
if value is None:
|
|
106
|
+
return None
|
|
107
|
+
if not isinstance(value, str) or not value or len(value) > IDEMPOTENCY_KEY_MAX_CHARS:
|
|
108
|
+
raise refuse(
|
|
109
|
+
"validation",
|
|
110
|
+
f"--idempotency-key must be a non-empty string of at most {IDEMPOTENCY_KEY_MAX_CHARS} "
|
|
111
|
+
"characters; nothing was sent.",
|
|
112
|
+
)
|
|
113
|
+
return value
|
|
114
|
+
|
|
115
|
+
|
|
90
116
|
def _limit(value: int | None, maximum: int, name: str) -> int | None:
|
|
91
117
|
if value is None:
|
|
92
118
|
return None
|
|
@@ -540,6 +566,26 @@ def _identity(session, target: TenantTarget, session_id: str | None) -> None:
|
|
|
540
566
|
)
|
|
541
567
|
|
|
542
568
|
|
|
569
|
+
def _page(limit: int | None, server_default: int, returned: int) -> dict:
|
|
570
|
+
"""The page block, honest about an omitted ``--limit``.
|
|
571
|
+
|
|
572
|
+
These routes page with a limit only: an omitted limit is not "unpaged", it
|
|
573
|
+
is the server's own default page, which can truncate the list. The block
|
|
574
|
+
therefore reports either the limit that was sent, or the server default
|
|
575
|
+
that was applied instead, so a consumer can always tell whether more rows
|
|
576
|
+
may exist.
|
|
577
|
+
"""
|
|
578
|
+
|
|
579
|
+
if limit is None:
|
|
580
|
+
return {
|
|
581
|
+
"kind": "server_default",
|
|
582
|
+
"server_default_limit": server_default,
|
|
583
|
+
"returned_count": returned,
|
|
584
|
+
"continuation": None,
|
|
585
|
+
}
|
|
586
|
+
return {"kind": "limit", "limit": limit, "returned_count": returned, "continuation": None}
|
|
587
|
+
|
|
588
|
+
|
|
543
589
|
def list_sessions(dependencies: AmsDependencies, target: TenantTarget, *, limit: int | None = None) -> dict:
|
|
544
590
|
"""`hc ams sessions list`: the caller's non-expired sessions, newest first."""
|
|
545
591
|
|
|
@@ -554,12 +600,7 @@ def list_sessions(dependencies: AmsDependencies, target: TenantTarget, *, limit:
|
|
|
554
600
|
_target(target),
|
|
555
601
|
{
|
|
556
602
|
"sessions": projections,
|
|
557
|
-
"page":
|
|
558
|
-
"kind": "limit",
|
|
559
|
-
"limit": limit,
|
|
560
|
-
"returned_count": len(projections),
|
|
561
|
-
"continuation": None,
|
|
562
|
-
},
|
|
603
|
+
"page": _page(limit, SESSION_LIMIT_DEFAULT, len(projections)),
|
|
563
604
|
},
|
|
564
605
|
read_only=True,
|
|
565
606
|
)
|
|
@@ -572,7 +613,9 @@ def get_session(dependencies: AmsDependencies, target: TenantTarget, session_id:
|
|
|
572
613
|
with dependencies.authenticated_sdk_factory(target) as sdk:
|
|
573
614
|
session = _sessions(sdk).get(session_id)
|
|
574
615
|
if session is None:
|
|
575
|
-
|
|
616
|
+
# A 200 with a JSON null body is the server saying "no such session
|
|
617
|
+
# here" — not an identity mismatch.
|
|
618
|
+
raise SessionReadRefused("not_found", f"Session '{session_id}' was not found in this tenant.")
|
|
576
619
|
_identity(session, target, session_id)
|
|
577
620
|
return succeeded(
|
|
578
621
|
"sessions.get", _target(target, session_id), {"session": session_projection(session)}, read_only=True
|
|
@@ -622,20 +665,33 @@ def _message_conflict(error: BaseException, session_id: str) -> SessionWriteRefu
|
|
|
622
665
|
return None
|
|
623
666
|
return SessionWriteRefused(
|
|
624
667
|
"conflict",
|
|
625
|
-
f"The message was refused for session '{session_id}': the server reported a conflict
|
|
626
|
-
"message's mode
|
|
627
|
-
"before retrying. No message was added.",
|
|
668
|
+
f"The message was refused for session '{session_id}': the server reported a conflict — for "
|
|
669
|
+
"example, a message's mode not matching its session's mode. Read the session with "
|
|
670
|
+
"`hc ams sessions get` before retrying. No message was added.",
|
|
628
671
|
)
|
|
629
672
|
|
|
630
673
|
|
|
631
674
|
def _not_implemented(error: BaseException, *, session: bool) -> SessionWriteRefused | None:
|
|
675
|
+
"""A 501 is the server saying it does not serve this write yet.
|
|
676
|
+
|
|
677
|
+
Both routes report the 501 the server actually sent rather than asserting
|
|
678
|
+
the requested mode was the cause; implement mode is named as the documented
|
|
679
|
+
reason, not as a certainty.
|
|
680
|
+
"""
|
|
681
|
+
|
|
632
682
|
if getattr(error, "status_code", None) != 501:
|
|
633
683
|
return None
|
|
634
|
-
|
|
684
|
+
if session:
|
|
685
|
+
return SessionWriteRefused(
|
|
686
|
+
"unsupported",
|
|
687
|
+
"The server does not serve this session write yet (it answered 501, not implemented); the "
|
|
688
|
+
"write was refused and no changes were made. Implement mode is the documented reason: "
|
|
689
|
+
"implement-mode authoring is not available from this server yet.",
|
|
690
|
+
)
|
|
635
691
|
return SessionWriteRefused(
|
|
636
692
|
"unsupported",
|
|
637
|
-
|
|
638
|
-
"
|
|
693
|
+
"The server does not serve this message write yet (it answered 501, not implemented); no "
|
|
694
|
+
"message was added. Implement-mode messages are not available from this server yet.",
|
|
639
695
|
)
|
|
640
696
|
|
|
641
697
|
|
|
@@ -709,7 +765,7 @@ def list_messages(
|
|
|
709
765
|
limit = _limit(limit, MESSAGE_LIMIT_MAX, "--limit")
|
|
710
766
|
with dependencies.authenticated_sdk_factory(target) as sdk:
|
|
711
767
|
try:
|
|
712
|
-
messages = _messages(sdk).list(session_id, **({} if limit is None else {"limit": limit}))
|
|
768
|
+
messages = _messages(sdk, ("list",)).list(session_id, **({} if limit is None else {"limit": limit}))
|
|
713
769
|
except HealthCloudHTTPError as error:
|
|
714
770
|
if getattr(error, "status_code", None) == 404:
|
|
715
771
|
raise SessionReadRefused(
|
|
@@ -724,12 +780,7 @@ def list_messages(
|
|
|
724
780
|
_target(target, session_id),
|
|
725
781
|
{
|
|
726
782
|
"messages": projections,
|
|
727
|
-
"page":
|
|
728
|
-
"kind": "limit",
|
|
729
|
-
"limit": limit,
|
|
730
|
-
"returned_count": len(projections),
|
|
731
|
-
"continuation": None,
|
|
732
|
-
},
|
|
783
|
+
"page": _page(limit, MESSAGE_LIMIT_DEFAULT, len(projections)),
|
|
733
784
|
},
|
|
734
785
|
read_only=True,
|
|
735
786
|
)
|
|
@@ -944,6 +995,7 @@ def generate_plan(
|
|
|
944
995
|
session_id = _session_id(session_id)
|
|
945
996
|
operation = _plan_operation(operation)
|
|
946
997
|
prompt = None if prompt_file is None else _prompt_file(prompt_file)
|
|
998
|
+
idempotency_key = _idempotency_key(idempotency_key, PlanReviewRefused)
|
|
947
999
|
with private_output(out) as write, dependencies.authenticated_sdk_factory(target) as sdk:
|
|
948
1000
|
resource = _plans(sdk, ("generate",), write=True)
|
|
949
1001
|
try:
|
|
@@ -1038,6 +1090,7 @@ def add_message(
|
|
|
1038
1090
|
refuse=SessionWriteRefused,
|
|
1039
1091
|
required=True,
|
|
1040
1092
|
)
|
|
1093
|
+
idempotency_key = _idempotency_key(idempotency_key, SessionWriteRefused)
|
|
1041
1094
|
with dependencies.authenticated_sdk_factory(target) as sdk:
|
|
1042
1095
|
resource = _messages(sdk, ("add",), write=True)
|
|
1043
1096
|
try:
|
|
@@ -1067,8 +1120,13 @@ def add_message(
|
|
|
1067
1120
|
"anything else.",
|
|
1068
1121
|
)
|
|
1069
1122
|
data: dict = {"message": message_projection(message)}
|
|
1070
|
-
|
|
1071
|
-
|
|
1123
|
+
# Echo the server's readback key when the message carries one, so the
|
|
1124
|
+
# envelope never asserts a key→message association the server did not
|
|
1125
|
+
# confirm. When the readback omits the key entirely the field is omitted
|
|
1126
|
+
# too — the caller can see the key they passed in their own command line.
|
|
1127
|
+
readback_key = getattr(message, "idempotency_key", None)
|
|
1128
|
+
if readback_key is not None:
|
|
1129
|
+
data["idempotency_key"] = readback_key
|
|
1072
1130
|
return succeeded("sessions.messages.add", _target(target, session_id), data, read_only=False)
|
|
1073
1131
|
|
|
1074
1132
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "healthcloud-cli"
|
|
7
|
-
version = "3.7.
|
|
7
|
+
version = "3.7.7"
|
|
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.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/data_sources/__init__.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/data_sources/commands.py
RENAMED
|
File without changes
|
|
File without changes
|
{healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/data_sources/operations.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/data_sources/projections.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/departments/operations.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/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.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/guardrails/operations.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/guardrails/projections.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/organization/__init__.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/organization/commands.py
RENAMED
|
File without changes
|
|
File without changes
|
{healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/ams/organization/operations.py
RENAMED
|
File without changes
|
{healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/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
|
{healthcloud_cli-3.7.5 → healthcloud_cli-3.7.7}/healthcloud_cli/contracts/mcp-tools.manifest.json
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
|