healthcloud-cli 3.7.1__tar.gz → 3.7.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/PKG-INFO +65 -3
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/README.md +64 -2
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/errors.py +2 -15
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/pretty.py +128 -1
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/cli.py +17 -9
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/contracts/mcp-tools.manifest.json +533 -3
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/output.py +13 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/pyproject.toml +1 -1
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/.gitignore +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/hatch_build.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/admins/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/admins/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/admins/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/admins/operations.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/agents/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/agents/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/agents/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/agents/operations.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/agents/projections.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/auth.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/compatibility.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/context.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/data_sources/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/data_sources/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/data_sources/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/data_sources/operations.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/data_sources/projections.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/departments/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/departments/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/departments/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/departments/operations.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/departments/projections.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/domain_errors.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/guardrails/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/guardrails/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/guardrails/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/guardrails/operations.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/guardrails/projections.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/organization/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/organization/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/organization/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/organization/operations.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/organization/projections.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/output.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/policies/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/policies/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/policies/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/policies/operations.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/policies/projections.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/registry.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/results.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/session.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/sessions/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/sessions/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/sessions/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/sessions/operations.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/sessions/projections.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/skills/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/skills/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/skills/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/skills/operations.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/skills/projections.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/target.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/teams/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/teams/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/teams/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/teams/operations.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/teams/projections.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/tools/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/tools/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/tools/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/tools/operations.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/tools/projections.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/tuning.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/ui.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/ams/windows_files.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/context.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/contracts/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/main.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/manifest.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/params.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/tenants/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/tenants/commands.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/tenants/group.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/tenants/operations.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/healthcloud_cli/util.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/scripts/__init__.py +0 -0
- {healthcloud_cli-3.7.1 → healthcloud_cli-3.7.2}/scripts/bundle_manifest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: healthcloud-cli
|
|
3
|
-
Version: 3.7.
|
|
3
|
+
Version: 3.7.2
|
|
4
4
|
Summary: Command-line interface for the HealthCloud MCP gateway, generated from the shared MCP tool manifest.
|
|
5
5
|
Project-URL: Issues, https://github.com/healthcloudservices/healthcloud-services/issues
|
|
6
6
|
Project-URL: Repository, https://github.com/healthcloudservices/healthcloud-services
|
|
@@ -316,6 +316,27 @@ otherwise it prompts (with hidden password input). It stores only the email,
|
|
|
316
316
|
tenant/environment binding, access/refresh tokens and expiry in the strict
|
|
317
317
|
owner-only `~/.healthcloud/cli/ams-session.json`; it never stores the password.
|
|
318
318
|
|
|
319
|
+
By default (`pretty`) these three commands print readable lines — never a raw
|
|
320
|
+
JSON blob and never a token:
|
|
321
|
+
|
|
322
|
+
```text
|
|
323
|
+
$ hc login --environment dev --tenant-id TENANT_ID
|
|
324
|
+
saved session: dev / TENANT_ID
|
|
325
|
+
email: admin@example.test
|
|
326
|
+
expires_at: 2026-11-02T01:42:29Z
|
|
327
|
+
$ hc logout
|
|
328
|
+
saved session removed: true
|
|
329
|
+
server session revoked: false
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
`hc login --status` prints the same three lines for the saved session, and a
|
|
333
|
+
second `hc logout` reports `saved session removed: false`. `--output json` (and
|
|
334
|
+
`--output raw`, which these commands treat as JSON) prints exactly the objects
|
|
335
|
+
they have always printed: `{"schema_version": 1, "ok": true, "session":
|
|
336
|
+
{"schema_version": 1, "environment", "tenant_id", "email", "expires_at"}}`
|
|
337
|
+
for `login`/`--status`, and `{"schema_version": 1, "ok": true, "removed":
|
|
338
|
+
true|false, "server_session_revoked": false}` for `logout`.
|
|
339
|
+
|
|
319
340
|
For CI and agents, log in non-interactively: pass `--email` and pipe the password
|
|
320
341
|
to `--password-stdin` (read as exactly one stdin line, never echoed, never a
|
|
321
342
|
flag value — there is no `--password` option):
|
|
@@ -545,6 +566,16 @@ explicitly that
|
|
|
545
566
|
fields — a disabled agent can still be selected by those paths — and that
|
|
546
567
|
in-flight runs, WSS sessions and schedules are untouched by this route.
|
|
547
568
|
|
|
569
|
+
Default `pretty` output prints those facts as labelled lines — `transition`,
|
|
570
|
+
`requested`, `evidence` (`saved`, `side_effects`, `runtime`), `eligibility`,
|
|
571
|
+
`mutation_replayed: false` — and closes with a `verification:` block, so a
|
|
572
|
+
deactivate reads `transition: deactivate`, then
|
|
573
|
+
`evidence: / saved: / status: confirmed`, then `verification: / saved_state:
|
|
574
|
+
confirmed`, `side_effects: unverified`, `runtime: unverified`; it is never one
|
|
575
|
+
raw JSON line. `--output json` prints the full envelope above, byte-identical to
|
|
576
|
+
before. An unknown outcome still prints its one-line `error:` plus the
|
|
577
|
+
reconciliation on stderr.
|
|
578
|
+
|
|
548
579
|
Pre-checks refuse with exit 1 and **no write**: a missing `--confirm` (exit 2
|
|
549
580
|
usage error, before login), an agent outside the explicit tenant, an already
|
|
550
581
|
in-effect state, an unsupported `archived`/`draft` target (named as having no
|
|
@@ -615,6 +646,12 @@ server audit log records an `UPDATE` with the payload. Runtime enforcement of
|
|
|
615
646
|
the new references is unobservable: no serving readback route exists, so this
|
|
616
647
|
command never claims it.
|
|
617
648
|
|
|
649
|
+
Default `pretty` output prints the same facts as labelled lines: `agent_id`,
|
|
650
|
+
`changed` (each field with `before`/`after`/`added`/`removed`),
|
|
651
|
+
`config_version: {before, after}`, `evidence` (`saved`, `side_effects`,
|
|
652
|
+
`runtime`), `mutation_replayed: false`, and a closing `verification:` block.
|
|
653
|
+
`--output json` prints the full envelope described here, unchanged.
|
|
654
|
+
|
|
618
655
|
`tool_ids` and `data_source_ids` address **unscoped global catalogs** by design;
|
|
619
656
|
the server checks tenant scope only for the department, team, skill, policy and
|
|
620
657
|
guardrail references. This command does not imply otherwise, and it does not
|
|
@@ -689,6 +726,13 @@ of restore and has no `--force`, no `--dry-run`, no interactive prompt and no
|
|
|
689
726
|
`--expected-config-version` — the route has no `If-Match` or version check, so a
|
|
690
727
|
version flag would advertise protection the server does not offer.
|
|
691
728
|
|
|
729
|
+
Default `pretty` output prints the result as labelled lines next to the stderr
|
|
730
|
+
disclosure: `agent_id`, `deleted: true`, `target`, `disclosure`, `evidence`
|
|
731
|
+
(`deleted`/`side_effects`/`runtime`), `mutation_replayed: false`, and a closing
|
|
732
|
+
`verification:` block. `--output json` prints the full envelope, unchanged. The
|
|
733
|
+
reviewed target and disclosure above are still printed to stderr in `pretty`
|
|
734
|
+
mode only; `--output json`/`raw` keep stderr machine-parsable.
|
|
735
|
+
|
|
692
736
|
Refusals claim nothing and send nothing extra: a missing `--confirm` is an exit
|
|
693
737
|
2 usage error before login or any request; an agent outside the explicit tenant,
|
|
694
738
|
the tenant identity mismatch and a delete target that no longer exists are
|
|
@@ -997,6 +1041,14 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
|
|
|
997
1041
|
"continuation": null}`). `get` emits `operation: "sessions.get"` and
|
|
998
1042
|
`data.session`. The projections never include the session's protected
|
|
999
1043
|
`initial_prompt`, tenant or user identity, or arbitrary response fields.
|
|
1044
|
+
- Default `pretty` output prints `list` as a table with honest headers —
|
|
1045
|
+
`ID`, `AGENT ID`, `OPERATION`, `STATUS`, `EXPIRES AT` for sessions and `ID`,
|
|
1046
|
+
`ROLE`, `MODE`, `PLAN ID`, `CREATED AT` for messages — followed by an entry
|
|
1047
|
+
count (`1 entry` / `N entries`), and `(no entries)` for an empty list. These
|
|
1048
|
+
projections carry no `name`, so no `NAME` column is invented and the `id` is
|
|
1049
|
+
always shown. `get`, `create` and `messages add` print `key: value` lines.
|
|
1050
|
+
`--output json` prints the full envelope exactly as before; pretty output is
|
|
1051
|
+
additive and never changes the envelope.
|
|
1000
1052
|
- `create` and `messages add` require a saved `hc login` session (env-var
|
|
1001
1053
|
credentials are not accepted for writes). Each write is sent exactly once and
|
|
1002
1054
|
the envelope carries the server's authoritative readback:
|
|
@@ -1169,6 +1221,12 @@ uv run --frozen hc ams sessions plans reconcile --environment dev --tenant-id TE
|
|
|
1169
1221
|
(create: reference counts by kind) and finding counts. No prompt,
|
|
1170
1222
|
instructions, greeting, summary, message content or plan value is printed to
|
|
1171
1223
|
stdout, stderr, a diagnostic or an error string.
|
|
1224
|
+
- Default `pretty` output renders `generate`, `get`, `apply`, `rollback` and
|
|
1225
|
+
`reconcile` as labelled lines — `plan:` with the bounded metadata, then
|
|
1226
|
+
`evidence:` (`saved`/`side_effects`/`runtime`) and `mutation_replayed: false`
|
|
1227
|
+
where the command reports them, and a closing `verification:` block for the
|
|
1228
|
+
writes. A write is never printed as a raw JSON blob in the default mode.
|
|
1229
|
+
`--output json` prints the full envelope, unchanged.
|
|
1172
1230
|
- The prompt is protected free text and never comes from argv: `--prompt-file`
|
|
1173
1231
|
reads one owner-only regular text file (no symlinks, at most 20000
|
|
1174
1232
|
characters) and sends it once. `--prompt-file` is optional — the server falls
|
|
@@ -1423,8 +1481,12 @@ and `hc ams organization hierarchy`) render in two modes (`--output` on the root
|
|
|
1423
1481
|
`hc` command; `pretty` is the default):
|
|
1424
1482
|
|
|
1425
1483
|
- **`pretty`** (default): a compact human view. Lists print an `ID`/`NAME`
|
|
1426
|
-
table with an entry count; a
|
|
1427
|
-
|
|
1484
|
+
table with an entry count; a collection whose rows have no `name` (sessions,
|
|
1485
|
+
messages, attachments) prints a table of that command's own columns plus the
|
|
1486
|
+
count, and `(no entries)` when empty. A single record prints `key: value`
|
|
1487
|
+
lines, the organization hierarchy prints an indented tree, and write or
|
|
1488
|
+
reconciliation evidence prints labelled `key: value` lines (the write's
|
|
1489
|
+
`verification:` block included) instead of JSON. Each failed AMS command
|
|
1428
1490
|
prints one stderr line, `error: <message> (<category>)` (flag/argument
|
|
1429
1491
|
usage errors from Click print Click's standard usage message instead).
|
|
1430
1492
|
- **`json`**: the stable result envelope (below) on stdout. When stdout is not
|
|
@@ -298,6 +298,27 @@ otherwise it prompts (with hidden password input). It stores only the email,
|
|
|
298
298
|
tenant/environment binding, access/refresh tokens and expiry in the strict
|
|
299
299
|
owner-only `~/.healthcloud/cli/ams-session.json`; it never stores the password.
|
|
300
300
|
|
|
301
|
+
By default (`pretty`) these three commands print readable lines — never a raw
|
|
302
|
+
JSON blob and never a token:
|
|
303
|
+
|
|
304
|
+
```text
|
|
305
|
+
$ hc login --environment dev --tenant-id TENANT_ID
|
|
306
|
+
saved session: dev / TENANT_ID
|
|
307
|
+
email: admin@example.test
|
|
308
|
+
expires_at: 2026-11-02T01:42:29Z
|
|
309
|
+
$ hc logout
|
|
310
|
+
saved session removed: true
|
|
311
|
+
server session revoked: false
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`hc login --status` prints the same three lines for the saved session, and a
|
|
315
|
+
second `hc logout` reports `saved session removed: false`. `--output json` (and
|
|
316
|
+
`--output raw`, which these commands treat as JSON) prints exactly the objects
|
|
317
|
+
they have always printed: `{"schema_version": 1, "ok": true, "session":
|
|
318
|
+
{"schema_version": 1, "environment", "tenant_id", "email", "expires_at"}}`
|
|
319
|
+
for `login`/`--status`, and `{"schema_version": 1, "ok": true, "removed":
|
|
320
|
+
true|false, "server_session_revoked": false}` for `logout`.
|
|
321
|
+
|
|
301
322
|
For CI and agents, log in non-interactively: pass `--email` and pipe the password
|
|
302
323
|
to `--password-stdin` (read as exactly one stdin line, never echoed, never a
|
|
303
324
|
flag value — there is no `--password` option):
|
|
@@ -527,6 +548,16 @@ explicitly that
|
|
|
527
548
|
fields — a disabled agent can still be selected by those paths — and that
|
|
528
549
|
in-flight runs, WSS sessions and schedules are untouched by this route.
|
|
529
550
|
|
|
551
|
+
Default `pretty` output prints those facts as labelled lines — `transition`,
|
|
552
|
+
`requested`, `evidence` (`saved`, `side_effects`, `runtime`), `eligibility`,
|
|
553
|
+
`mutation_replayed: false` — and closes with a `verification:` block, so a
|
|
554
|
+
deactivate reads `transition: deactivate`, then
|
|
555
|
+
`evidence: / saved: / status: confirmed`, then `verification: / saved_state:
|
|
556
|
+
confirmed`, `side_effects: unverified`, `runtime: unverified`; it is never one
|
|
557
|
+
raw JSON line. `--output json` prints the full envelope above, byte-identical to
|
|
558
|
+
before. An unknown outcome still prints its one-line `error:` plus the
|
|
559
|
+
reconciliation on stderr.
|
|
560
|
+
|
|
530
561
|
Pre-checks refuse with exit 1 and **no write**: a missing `--confirm` (exit 2
|
|
531
562
|
usage error, before login), an agent outside the explicit tenant, an already
|
|
532
563
|
in-effect state, an unsupported `archived`/`draft` target (named as having no
|
|
@@ -597,6 +628,12 @@ server audit log records an `UPDATE` with the payload. Runtime enforcement of
|
|
|
597
628
|
the new references is unobservable: no serving readback route exists, so this
|
|
598
629
|
command never claims it.
|
|
599
630
|
|
|
631
|
+
Default `pretty` output prints the same facts as labelled lines: `agent_id`,
|
|
632
|
+
`changed` (each field with `before`/`after`/`added`/`removed`),
|
|
633
|
+
`config_version: {before, after}`, `evidence` (`saved`, `side_effects`,
|
|
634
|
+
`runtime`), `mutation_replayed: false`, and a closing `verification:` block.
|
|
635
|
+
`--output json` prints the full envelope described here, unchanged.
|
|
636
|
+
|
|
600
637
|
`tool_ids` and `data_source_ids` address **unscoped global catalogs** by design;
|
|
601
638
|
the server checks tenant scope only for the department, team, skill, policy and
|
|
602
639
|
guardrail references. This command does not imply otherwise, and it does not
|
|
@@ -671,6 +708,13 @@ of restore and has no `--force`, no `--dry-run`, no interactive prompt and no
|
|
|
671
708
|
`--expected-config-version` — the route has no `If-Match` or version check, so a
|
|
672
709
|
version flag would advertise protection the server does not offer.
|
|
673
710
|
|
|
711
|
+
Default `pretty` output prints the result as labelled lines next to the stderr
|
|
712
|
+
disclosure: `agent_id`, `deleted: true`, `target`, `disclosure`, `evidence`
|
|
713
|
+
(`deleted`/`side_effects`/`runtime`), `mutation_replayed: false`, and a closing
|
|
714
|
+
`verification:` block. `--output json` prints the full envelope, unchanged. The
|
|
715
|
+
reviewed target and disclosure above are still printed to stderr in `pretty`
|
|
716
|
+
mode only; `--output json`/`raw` keep stderr machine-parsable.
|
|
717
|
+
|
|
674
718
|
Refusals claim nothing and send nothing extra: a missing `--confirm` is an exit
|
|
675
719
|
2 usage error before login or any request; an agent outside the explicit tenant,
|
|
676
720
|
the tenant identity mismatch and a delete target that no longer exists are
|
|
@@ -979,6 +1023,14 @@ uv run --frozen hc ams sessions messages add --environment dev --tenant-id TENAN
|
|
|
979
1023
|
"continuation": null}`). `get` emits `operation: "sessions.get"` and
|
|
980
1024
|
`data.session`. The projections never include the session's protected
|
|
981
1025
|
`initial_prompt`, tenant or user identity, or arbitrary response fields.
|
|
1026
|
+
- Default `pretty` output prints `list` as a table with honest headers —
|
|
1027
|
+
`ID`, `AGENT ID`, `OPERATION`, `STATUS`, `EXPIRES AT` for sessions and `ID`,
|
|
1028
|
+
`ROLE`, `MODE`, `PLAN ID`, `CREATED AT` for messages — followed by an entry
|
|
1029
|
+
count (`1 entry` / `N entries`), and `(no entries)` for an empty list. These
|
|
1030
|
+
projections carry no `name`, so no `NAME` column is invented and the `id` is
|
|
1031
|
+
always shown. `get`, `create` and `messages add` print `key: value` lines.
|
|
1032
|
+
`--output json` prints the full envelope exactly as before; pretty output is
|
|
1033
|
+
additive and never changes the envelope.
|
|
982
1034
|
- `create` and `messages add` require a saved `hc login` session (env-var
|
|
983
1035
|
credentials are not accepted for writes). Each write is sent exactly once and
|
|
984
1036
|
the envelope carries the server's authoritative readback:
|
|
@@ -1151,6 +1203,12 @@ uv run --frozen hc ams sessions plans reconcile --environment dev --tenant-id TE
|
|
|
1151
1203
|
(create: reference counts by kind) and finding counts. No prompt,
|
|
1152
1204
|
instructions, greeting, summary, message content or plan value is printed to
|
|
1153
1205
|
stdout, stderr, a diagnostic or an error string.
|
|
1206
|
+
- Default `pretty` output renders `generate`, `get`, `apply`, `rollback` and
|
|
1207
|
+
`reconcile` as labelled lines — `plan:` with the bounded metadata, then
|
|
1208
|
+
`evidence:` (`saved`/`side_effects`/`runtime`) and `mutation_replayed: false`
|
|
1209
|
+
where the command reports them, and a closing `verification:` block for the
|
|
1210
|
+
writes. A write is never printed as a raw JSON blob in the default mode.
|
|
1211
|
+
`--output json` prints the full envelope, unchanged.
|
|
1154
1212
|
- The prompt is protected free text and never comes from argv: `--prompt-file`
|
|
1155
1213
|
reads one owner-only regular text file (no symlinks, at most 20000
|
|
1156
1214
|
characters) and sends it once. `--prompt-file` is optional — the server falls
|
|
@@ -1405,8 +1463,12 @@ and `hc ams organization hierarchy`) render in two modes (`--output` on the root
|
|
|
1405
1463
|
`hc` command; `pretty` is the default):
|
|
1406
1464
|
|
|
1407
1465
|
- **`pretty`** (default): a compact human view. Lists print an `ID`/`NAME`
|
|
1408
|
-
table with an entry count; a
|
|
1409
|
-
|
|
1466
|
+
table with an entry count; a collection whose rows have no `name` (sessions,
|
|
1467
|
+
messages, attachments) prints a table of that command's own columns plus the
|
|
1468
|
+
count, and `(no entries)` when empty. A single record prints `key: value`
|
|
1469
|
+
lines, the organization hierarchy prints an indented tree, and write or
|
|
1470
|
+
reconciliation evidence prints labelled `key: value` lines (the write's
|
|
1471
|
+
`verification:` block included) instead of JSON. Each failed AMS command
|
|
1410
1472
|
prints one stderr line, `error: <message> (<category>)` (flag/argument
|
|
1411
1473
|
usage errors from Click print Click's standard usage message instead).
|
|
1412
1474
|
- **`json`**: the stable result envelope (below) on stdout. When stdout is not
|
|
@@ -10,7 +10,7 @@ from typing import Any
|
|
|
10
10
|
import click
|
|
11
11
|
from pydantic import ValidationError
|
|
12
12
|
|
|
13
|
-
from ..
|
|
13
|
+
from ..output import output_mode
|
|
14
14
|
from .domain_errors import DomainReadRefused
|
|
15
15
|
from .pretty import error_line, success_text, unknown_details
|
|
16
16
|
from .results import failed
|
|
@@ -135,19 +135,6 @@ def emit_compact_json(value: dict, *, err: bool = False) -> None:
|
|
|
135
135
|
click.echo(json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True), err=err)
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
def _output_mode() -> str:
|
|
139
|
-
"""The root ``--output`` mode for the running command; pretty is the default.
|
|
140
|
-
|
|
141
|
-
Raw is refused for AMS before any read runs, but its refusals keep the
|
|
142
|
-
structured JSON error envelope, so raw renders like json.
|
|
143
|
-
"""
|
|
144
|
-
|
|
145
|
-
ctx = click.get_current_context(silent=True)
|
|
146
|
-
cfg = ctx.find_object(CliConfig) if ctx is not None else None
|
|
147
|
-
mode = getattr(cfg, "output", None)
|
|
148
|
-
return "json" if mode in ("json", "raw") else "pretty"
|
|
149
|
-
|
|
150
|
-
|
|
151
138
|
def _render_success(envelope: dict, mode: str) -> None:
|
|
152
139
|
if mode != "json":
|
|
153
140
|
click.echo(success_text(envelope))
|
|
@@ -190,7 +177,7 @@ def run_domain(operation: str, target: dict, callback: Callable[[], Any], *, rea
|
|
|
190
177
|
``verification`` block in the error envelope.
|
|
191
178
|
"""
|
|
192
179
|
|
|
193
|
-
mode =
|
|
180
|
+
mode = output_mode()
|
|
194
181
|
|
|
195
182
|
def _fail(error: BaseException) -> None:
|
|
196
183
|
"""Classify ``error``, render the error envelope, and exit bounded."""
|
|
@@ -107,13 +107,109 @@ def _is_list(data: dict) -> bool:
|
|
|
107
107
|
)
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
# A collection whose rows carry an ``id`` but no ``name`` (sessions, messages,
|
|
111
|
+
# attachments) still renders as a table; these are the honest columns per
|
|
112
|
+
# collection. A collection without a spec here falls back to ``id`` plus the
|
|
113
|
+
# scalar keys every row shares, so no column is invented.
|
|
114
|
+
_LIST_FIELDS = {
|
|
115
|
+
"sessions": ("id", "agent_id", "operation", "status", "expires_at"),
|
|
116
|
+
"messages": ("id", "role", "mode", "plan_id", "created_at"),
|
|
117
|
+
"attachments": ("id", "filename", "content_type", "size_bytes", "index_status"),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
|
|
110
121
|
def _is_item(data: dict) -> bool:
|
|
111
122
|
values = list(data.values())
|
|
112
123
|
return len(data) == 1 and isinstance(values[0], dict)
|
|
113
124
|
|
|
114
125
|
|
|
126
|
+
def _id_list(data: dict) -> tuple[str, list[dict]] | None:
|
|
127
|
+
"""The ``(collection, rows)`` of a list whose rows have an ``id`` only.
|
|
128
|
+
|
|
129
|
+
Catalog lists already match ``_is_list`` (they add a ``name``); this covers
|
|
130
|
+
the projections that do not name their row (sessions, messages, ...) without
|
|
131
|
+
loosening ``_is_list`` and changing existing catalog output.
|
|
132
|
+
"""
|
|
133
|
+
|
|
134
|
+
collections = [(key, value) for key, value in data.items() if key != "page"]
|
|
135
|
+
if len(collections) != 1:
|
|
136
|
+
return None
|
|
137
|
+
key, rows = collections[0]
|
|
138
|
+
if not isinstance(rows, list):
|
|
139
|
+
return None
|
|
140
|
+
if not all(isinstance(row, dict) and isinstance(row.get("id"), str) for row in rows):
|
|
141
|
+
return None
|
|
142
|
+
return key, rows
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _columns(collection: str, rows: list[dict]) -> tuple[str, ...]:
|
|
146
|
+
fields = _LIST_FIELDS.get(collection)
|
|
147
|
+
if fields is not None:
|
|
148
|
+
return fields
|
|
149
|
+
shared = set(rows[0])
|
|
150
|
+
for row in rows[1:]:
|
|
151
|
+
shared &= set(row)
|
|
152
|
+
return ("id", *sorted(shared - {"id"}))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _columns_table_lines(rows: list[dict], fields: tuple[str, ...]) -> list[str]:
|
|
156
|
+
headers = [field.replace("_", " ").upper() for field in fields]
|
|
157
|
+
cells = [[_scalar(row.get(field)) for field in fields] for row in rows]
|
|
158
|
+
widths = [max(len(headers[index]), *(len(cell[index]) for cell in cells)) for index in range(len(fields))]
|
|
159
|
+
|
|
160
|
+
def _line(values: list[str]) -> str:
|
|
161
|
+
# The trailing column is never padded: no trailing whitespace.
|
|
162
|
+
if len(values) == 1:
|
|
163
|
+
return values[0]
|
|
164
|
+
padded = [value.ljust(width) for value, width in zip(values[:-1], widths[:-1], strict=True)]
|
|
165
|
+
return " ".join(padded) + " " + values[-1]
|
|
166
|
+
|
|
167
|
+
lines = [_line(headers)]
|
|
168
|
+
lines.extend(_line(cell) for cell in cells)
|
|
169
|
+
count = len(rows)
|
|
170
|
+
lines.append("")
|
|
171
|
+
lines.append(f"{count} entry" if count == 1 else f"{count} entries")
|
|
172
|
+
return lines
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
# Domains whose multi-key ``data`` is write or reconciliation evidence. A
|
|
176
|
+
# non-AMS envelope (``admins.*``, ``tenants.*``) keeps the compact-JSON fallback:
|
|
177
|
+
# their pretty output is a separate story, not this one's to change.
|
|
178
|
+
_EVIDENCE_DOMAINS = frozenset(
|
|
179
|
+
{
|
|
180
|
+
"agents",
|
|
181
|
+
"data-sources",
|
|
182
|
+
"departments",
|
|
183
|
+
"guardrails",
|
|
184
|
+
"organization",
|
|
185
|
+
"policies",
|
|
186
|
+
"sessions",
|
|
187
|
+
"skills",
|
|
188
|
+
"teams",
|
|
189
|
+
"tools",
|
|
190
|
+
}
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _is_evidence(envelope: dict, data: dict) -> bool:
|
|
195
|
+
operation = envelope.get("operation")
|
|
196
|
+
if not isinstance(operation, str) or operation.split(".", 1)[0] not in _EVIDENCE_DOMAINS:
|
|
197
|
+
return False
|
|
198
|
+
return len([key for key in data if key != "page"]) > 1
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _evidence_lines(envelope: dict, data: dict) -> list[str]:
|
|
202
|
+
lines = _detail_lines(data)
|
|
203
|
+
verification = envelope.get("verification")
|
|
204
|
+
if isinstance(verification, dict) and verification:
|
|
205
|
+
lines.append("")
|
|
206
|
+
lines.append("verification:")
|
|
207
|
+
lines.extend(_detail_lines(verification, 1))
|
|
208
|
+
return lines
|
|
209
|
+
|
|
210
|
+
|
|
115
211
|
def success_text(envelope: dict) -> str:
|
|
116
|
-
"""Render a succeeded
|
|
212
|
+
"""Render a succeeded envelope as compact human text."""
|
|
117
213
|
data = envelope.get("data")
|
|
118
214
|
if not isinstance(data, dict) or not data:
|
|
119
215
|
return json.dumps(data, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
|
|
@@ -123,11 +219,42 @@ def success_text(envelope: dict) -> str:
|
|
|
123
219
|
if _is_list(data):
|
|
124
220
|
rows = [value for key, value in data.items() if key != "page"][0]
|
|
125
221
|
return "\n".join(_table_lines(rows)) if rows else "(no entries)"
|
|
222
|
+
identified = _id_list(data)
|
|
223
|
+
if identified is not None:
|
|
224
|
+
collection, rows = identified
|
|
225
|
+
if not rows:
|
|
226
|
+
return "(no entries)"
|
|
227
|
+
return "\n".join(_columns_table_lines(rows, _columns(collection, rows)))
|
|
126
228
|
if _is_item(data):
|
|
127
229
|
return "\n".join(_item_lines(list(data.values())[0]))
|
|
230
|
+
if _is_evidence(envelope, data):
|
|
231
|
+
return "\n".join(_evidence_lines(envelope, data))
|
|
128
232
|
return json.dumps(data, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
|
|
129
233
|
|
|
130
234
|
|
|
235
|
+
_SESSION_FIELDS = ("email", "expires_at")
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def saved_session_text(payload: dict) -> str:
|
|
239
|
+
"""Pretty text for ``hc login`` and ``hc login --status``; never a token."""
|
|
240
|
+
session = payload.get("session")
|
|
241
|
+
if not isinstance(session, dict):
|
|
242
|
+
return "(no saved session)"
|
|
243
|
+
lines = [f"saved session: {_value(session.get('environment'))} / {_value(session.get('tenant_id'))}"]
|
|
244
|
+
lines.extend(f"{field}: {_value(session[field])}" for field in _SESSION_FIELDS if field in session)
|
|
245
|
+
return "\n".join(lines)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def logout_text(payload: dict) -> str:
|
|
249
|
+
"""Pretty text for ``hc logout``: what was removed, and what was not undone."""
|
|
250
|
+
removed = payload.get("removed") is True
|
|
251
|
+
lines = [f"saved session removed: {'true' if removed else 'false'}"]
|
|
252
|
+
if isinstance(payload.get("server_session_revoked"), bool):
|
|
253
|
+
revoked = payload["server_session_revoked"]
|
|
254
|
+
lines.append(f"server session revoked: {'true' if revoked else 'false'}")
|
|
255
|
+
return "\n".join(lines)
|
|
256
|
+
|
|
257
|
+
|
|
131
258
|
def error_line(category: str, message: str) -> str:
|
|
132
259
|
"""One-line stderr rendering of a failed read envelope."""
|
|
133
260
|
return f"error: {_text(message)} ({_text(category)})"
|
|
@@ -23,9 +23,10 @@ import click
|
|
|
23
23
|
from . import manifest as manifest_module
|
|
24
24
|
from .admins import create_admins_group
|
|
25
25
|
from .ams.group import create_ams_group
|
|
26
|
+
from .ams.pretty import logout_text, saved_session_text
|
|
26
27
|
from .ams.session import delete_session, load_session, new_session, save_session
|
|
27
28
|
from .context import CliConfig, resolve_config
|
|
28
|
-
from .output import dispatch, dump_rpc_result, read_stdin
|
|
29
|
+
from .output import dispatch, dump_rpc_result, output_mode, read_stdin
|
|
29
30
|
from .params import ParamSpec, build_param_specs, make_click_option, merge_arguments, validate_final
|
|
30
31
|
from .tenants import create_tenants_group
|
|
31
32
|
from .util import (
|
|
@@ -332,7 +333,7 @@ def login_cmd(
|
|
|
332
333
|
session = load_session()
|
|
333
334
|
if session is None:
|
|
334
335
|
raise click.ClickException("No saved AMS session; run hc login.")
|
|
335
|
-
|
|
336
|
+
_emit_session({"schema_version": 1, "ok": True, "session": session.public_value()})
|
|
336
337
|
return
|
|
337
338
|
if environment is None or tenant_id is None or not tenant_id.strip():
|
|
338
339
|
raise click.UsageError("Login requires --environment and a nonblank --tenant-id.")
|
|
@@ -377,19 +378,26 @@ def login_cmd(
|
|
|
377
378
|
raise
|
|
378
379
|
except Exception:
|
|
379
380
|
raise click.ClickException("Tenant-admin login failed; no session was saved.") from None
|
|
380
|
-
|
|
381
|
+
_emit_session({"schema_version": 1, "ok": True, "session": session.public_value()})
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _emit_session(payload: dict) -> None:
|
|
385
|
+
"""Render the saved-session object: readable text by default, today's JSON for json."""
|
|
386
|
+
if output_mode() == "pretty":
|
|
387
|
+
click.echo(saved_session_text(payload))
|
|
388
|
+
return
|
|
389
|
+
click.echo(json.dumps(payload, sort_keys=True))
|
|
381
390
|
|
|
382
391
|
|
|
383
392
|
@click.command("logout")
|
|
384
393
|
def logout_cmd() -> None:
|
|
385
394
|
"""Remove the validated local tenant-admin session (local only; tokens stay valid until expiry)."""
|
|
386
395
|
removed = delete_session()
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
)
|
|
396
|
+
payload = {"schema_version": 1, "ok": True, "removed": removed, "server_session_revoked": False}
|
|
397
|
+
if output_mode() == "pretty":
|
|
398
|
+
click.echo(logout_text(payload))
|
|
399
|
+
return
|
|
400
|
+
click.echo(json.dumps(payload, sort_keys=True))
|
|
393
401
|
|
|
394
402
|
|
|
395
403
|
hc.add_command(login_cmd)
|