querri 2.0.0__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. {querri-2.0.0 → querri-2.1.0}/CHANGELOG.md +20 -0
  2. {querri-2.0.0 → querri-2.1.0}/PKG-INFO +2 -1
  3. {querri-2.0.0 → querri-2.1.0}/README.md +1 -0
  4. querri-2.1.0/docs/data-library.md +180 -0
  5. {querri-2.0.0 → querri-2.1.0}/querri/_client.py +19 -0
  6. {querri-2.0.0 → querri-2.1.0}/querri/_exceptions.py +21 -2
  7. querri-2.1.0/querri/_version.py +1 -0
  8. {querri-2.0.0 → querri-2.1.0}/querri/cli/_app.py +6 -0
  9. querri-2.1.0/querri/cli/library.py +1763 -0
  10. querri-2.1.0/querri/resources/library.py +1180 -0
  11. querri-2.1.0/querri/types/library.py +355 -0
  12. {querri-2.0.0 → querri-2.1.0}/tests/test_cli.py +5 -3
  13. {querri-2.0.0 → querri-2.1.0}/tests/test_exceptions.py +34 -0
  14. querri-2.1.0/tests/test_library.py +842 -0
  15. querri-2.1.0/tests/test_library_streaming.py +325 -0
  16. querri-2.0.0/querri/_version.py +0 -1
  17. {querri-2.0.0 → querri-2.1.0}/.github/CONTRIBUTING.md +0 -0
  18. {querri-2.0.0 → querri-2.1.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  19. {querri-2.0.0 → querri-2.1.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  20. {querri-2.0.0 → querri-2.1.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  21. {querri-2.0.0 → querri-2.1.0}/.github/SECURITY.md +0 -0
  22. {querri-2.0.0 → querri-2.1.0}/.github/workflows/ci.yml +0 -0
  23. {querri-2.0.0 → querri-2.1.0}/.github/workflows/release.yml +0 -0
  24. {querri-2.0.0 → querri-2.1.0}/.gitignore +0 -0
  25. {querri-2.0.0 → querri-2.1.0}/CLAUDE.md +0 -0
  26. {querri-2.0.0 → querri-2.1.0}/LICENSE +0 -0
  27. {querri-2.0.0 → querri-2.1.0}/TODO.md +0 -0
  28. {querri-2.0.0 → querri-2.1.0}/docs/MIGRATION.md +0 -0
  29. {querri-2.0.0 → querri-2.1.0}/docs/server-sdk.md +0 -0
  30. {querri-2.0.0 → querri-2.1.0}/examples/01_quick_start.py +0 -0
  31. {querri-2.0.0 → querri-2.1.0}/examples/02_user_management.py +0 -0
  32. {querri-2.0.0 → querri-2.1.0}/examples/03_access_policies.py +0 -0
  33. {querri-2.0.0 → querri-2.1.0}/examples/04_embed_session.py +0 -0
  34. {querri-2.0.0 → querri-2.1.0}/examples/05_get_session.py +0 -0
  35. {querri-2.0.0 → querri-2.1.0}/examples/06_projects.py +0 -0
  36. {querri-2.0.0 → querri-2.1.0}/examples/07_dashboards.py +0 -0
  37. {querri-2.0.0 → querri-2.1.0}/examples/08_chat_streaming.py +0 -0
  38. {querri-2.0.0 → querri-2.1.0}/examples/09_async_client.py +0 -0
  39. {querri-2.0.0 → querri-2.1.0}/examples/10_error_handling.py +0 -0
  40. {querri-2.0.0 → querri-2.1.0}/examples/11_user_scoped_client.py +0 -0
  41. {querri-2.0.0 → querri-2.1.0}/examples/12_data_operations.py +0 -0
  42. {querri-2.0.0 → querri-2.1.0}/examples/web-embed/index.html +0 -0
  43. {querri-2.0.0 → querri-2.1.0}/examples/web-embed/server.py +0 -0
  44. {querri-2.0.0 → querri-2.1.0}/pyproject.toml +0 -0
  45. {querri-2.0.0 → querri-2.1.0}/querri/__init__.py +0 -0
  46. {querri-2.0.0 → querri-2.1.0}/querri/_auth.py +0 -0
  47. {querri-2.0.0 → querri-2.1.0}/querri/_base_client.py +0 -0
  48. {querri-2.0.0 → querri-2.1.0}/querri/_config.py +0 -0
  49. {querri-2.0.0 → querri-2.1.0}/querri/_convenience.py +0 -0
  50. {querri-2.0.0 → querri-2.1.0}/querri/_pagination.py +0 -0
  51. {querri-2.0.0 → querri-2.1.0}/querri/_streaming.py +0 -0
  52. {querri-2.0.0 → querri-2.1.0}/querri/_user_client.py +0 -0
  53. {querri-2.0.0 → querri-2.1.0}/querri/cli/__init__.py +0 -0
  54. {querri-2.0.0 → querri-2.1.0}/querri/cli/_context.py +0 -0
  55. {querri-2.0.0 → querri-2.1.0}/querri/cli/_image.py +0 -0
  56. {querri-2.0.0 → querri-2.1.0}/querri/cli/_output.py +0 -0
  57. {querri-2.0.0 → querri-2.1.0}/querri/cli/audit.py +0 -0
  58. {querri-2.0.0 → querri-2.1.0}/querri/cli/auth.py +0 -0
  59. {querri-2.0.0 → querri-2.1.0}/querri/cli/chat.py +0 -0
  60. {querri-2.0.0 → querri-2.1.0}/querri/cli/chats.py +0 -0
  61. {querri-2.0.0 → querri-2.1.0}/querri/cli/dashboards.py +0 -0
  62. {querri-2.0.0 → querri-2.1.0}/querri/cli/embed.py +0 -0
  63. {querri-2.0.0 → querri-2.1.0}/querri/cli/files.py +0 -0
  64. {querri-2.0.0 → querri-2.1.0}/querri/cli/keys.py +0 -0
  65. {querri-2.0.0 → querri-2.1.0}/querri/cli/policies.py +0 -0
  66. {querri-2.0.0 → querri-2.1.0}/querri/cli/projects.py +0 -0
  67. {querri-2.0.0 → querri-2.1.0}/querri/cli/sharing.py +0 -0
  68. {querri-2.0.0 → querri-2.1.0}/querri/cli/sources.py +0 -0
  69. {querri-2.0.0 → querri-2.1.0}/querri/cli/steps.py +0 -0
  70. {querri-2.0.0 → querri-2.1.0}/querri/cli/usage.py +0 -0
  71. {querri-2.0.0 → querri-2.1.0}/querri/cli/users.py +0 -0
  72. {querri-2.0.0 → querri-2.1.0}/querri/cli/views.py +0 -0
  73. {querri-2.0.0 → querri-2.1.0}/querri/cli/whoami.py +0 -0
  74. {querri-2.0.0 → querri-2.1.0}/querri/py.typed +0 -0
  75. {querri-2.0.0 → querri-2.1.0}/querri/resources/__init__.py +0 -0
  76. {querri-2.0.0 → querri-2.1.0}/querri/resources/audit.py +0 -0
  77. {querri-2.0.0 → querri-2.1.0}/querri/resources/dashboards.py +0 -0
  78. {querri-2.0.0 → querri-2.1.0}/querri/resources/embed.py +0 -0
  79. {querri-2.0.0 → querri-2.1.0}/querri/resources/files.py +0 -0
  80. {querri-2.0.0 → querri-2.1.0}/querri/resources/keys.py +0 -0
  81. {querri-2.0.0 → querri-2.1.0}/querri/resources/policies.py +0 -0
  82. {querri-2.0.0 → querri-2.1.0}/querri/resources/projects.py +0 -0
  83. {querri-2.0.0 → querri-2.1.0}/querri/resources/sharing.py +0 -0
  84. {querri-2.0.0 → querri-2.1.0}/querri/resources/sources.py +0 -0
  85. {querri-2.0.0 → querri-2.1.0}/querri/resources/usage.py +0 -0
  86. {querri-2.0.0 → querri-2.1.0}/querri/resources/users.py +0 -0
  87. {querri-2.0.0 → querri-2.1.0}/querri/resources/views.py +0 -0
  88. {querri-2.0.0 → querri-2.1.0}/querri/types/__init__.py +0 -0
  89. {querri-2.0.0 → querri-2.1.0}/querri/types/audit.py +0 -0
  90. {querri-2.0.0 → querri-2.1.0}/querri/types/chat.py +0 -0
  91. {querri-2.0.0 → querri-2.1.0}/querri/types/dashboard.py +0 -0
  92. {querri-2.0.0 → querri-2.1.0}/querri/types/data.py +0 -0
  93. {querri-2.0.0 → querri-2.1.0}/querri/types/embed.py +0 -0
  94. {querri-2.0.0 → querri-2.1.0}/querri/types/file.py +0 -0
  95. {querri-2.0.0 → querri-2.1.0}/querri/types/key.py +0 -0
  96. {querri-2.0.0 → querri-2.1.0}/querri/types/policy.py +0 -0
  97. {querri-2.0.0 → querri-2.1.0}/querri/types/project.py +0 -0
  98. {querri-2.0.0 → querri-2.1.0}/querri/types/sharing.py +0 -0
  99. {querri-2.0.0 → querri-2.1.0}/querri/types/usage.py +0 -0
  100. {querri-2.0.0 → querri-2.1.0}/querri/types/user.py +0 -0
  101. {querri-2.0.0 → querri-2.1.0}/scripts/release.sh +0 -0
  102. {querri-2.0.0 → querri-2.1.0}/skills/README.md +0 -0
  103. {querri-2.0.0 → querri-2.1.0}/skills/querri-cli/SKILL.md +0 -0
  104. {querri-2.0.0 → querri-2.1.0}/skills/querri-cli/references/json-shapes.md +0 -0
  105. {querri-2.0.0 → querri-2.1.0}/skills/querri-cli/references/troubleshooting.md +0 -0
  106. {querri-2.0.0 → querri-2.1.0}/tests/__init__.py +0 -0
  107. {querri-2.0.0 → querri-2.1.0}/tests/conftest.py +0 -0
  108. {querri-2.0.0 → querri-2.1.0}/tests/rls-integration-results.md +0 -0
  109. {querri-2.0.0 → querri-2.1.0}/tests/test_cli_commands.py +0 -0
  110. {querri-2.0.0 → querri-2.1.0}/tests/test_client.py +0 -0
  111. {querri-2.0.0 → querri-2.1.0}/tests/test_config.py +0 -0
  112. {querri-2.0.0 → querri-2.1.0}/tests/test_convenience.py +0 -0
  113. {querri-2.0.0 → querri-2.1.0}/tests/test_integration.py +0 -0
  114. {querri-2.0.0 → querri-2.1.0}/tests/test_new_features.py +0 -0
  115. {querri-2.0.0 → querri-2.1.0}/tests/test_pagination.py +0 -0
  116. {querri-2.0.0 → querri-2.1.0}/tests/test_resources.py +0 -0
  117. {querri-2.0.0 → querri-2.1.0}/tests/test_rls_integration.py +0 -0
  118. {querri-2.0.0 → querri-2.1.0}/tests/test_security.py +0 -0
  119. {querri-2.0.0 → querri-2.1.0}/tests/test_streaming.py +0 -0
  120. {querri-2.0.0 → querri-2.1.0}/tests/test_streaming_v2.py +0 -0
  121. {querri-2.0.0 → querri-2.1.0}/uv.lock +0 -0
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.1.0 (2026-09-24)
4
+
5
+ ### Added
6
+
7
+ - **`client.library`** (sync and async) and the `querri library` CLI: the Data
8
+ Library surface — libraries, collections, anchor and refining questions,
9
+ graph search and zoom, `record_fact`, the Librarian `chat` / `chat_stream`,
10
+ `ask`, `intake`, `consolidate`, `eval`, backfill and health. Reference:
11
+ [docs/data-library.md](docs/data-library.md). This is the `dev` line that
12
+ had been building since 1.1.0; it ships here unchanged.
13
+
14
+ ### Fixed
15
+
16
+ - **Error bodies without a Stripe-style envelope keep their message and
17
+ code.** FastAPI raises `{"detail": {"error": {...}}}` (or a plain string
18
+ `detail`); `APIError.message`, `.code` and `.type` were left as
19
+ `"HTTP <status>"` / `None` for those, so a `NotFoundError` for a step with
20
+ no data read the same as one for a missing project. Both shapes are parsed
21
+ now.
22
+
3
23
  ## 2.0.0 (2026-08-31)
4
24
 
5
25
  Migration guide with before/after snippets: [docs/MIGRATION.md](docs/MIGRATION.md).
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: querri
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: Python SDK and CLI for the Querri data analysis platform
5
5
  Project-URL: Homepage, https://querri.com
6
6
  Project-URL: Documentation, https://docs.querri.com/sdk/python
@@ -290,6 +290,7 @@ See **[docs/server-sdk.md](docs/server-sdk.md#user-scoped-client-as_user)** for
290
290
  | `client.projects.chats` | Chats within projects | `create`, `list`, `get`, `stream`, `cancel`, `delete` |
291
291
  | `client.sources` | Sources, connectors & data | `list`, `create`, `create_data_source`, `query`, `source_data`, `append_rows`, `replace_data`, `ask`, `sync`, `list_connectors` |
292
292
  | `client.views` | SQL-defined views | `list`, `create`, `get`, `update`, `delete`, `run`, `get_run`, `wait_for_run`, `preview`, `chat` |
293
+ | `client.library` | Data Library — graph + librarian agent | `create_library`, `create_collection`, `create_anchor_question`, `create_refining_question`, `link`, `status`, `list_nodes`, `get_node`, `search`, `zoom`, `record_fact`, `chat`, `chat_stream`, `backfill`, `seed_fixture`, `health` |
293
294
  | `client.files` | File management | `upload`, `list`, `get`, `delete` |
294
295
  | `client.keys` | API key management | `create`, `list`, `get`, `delete` |
295
296
  | `client.sharing` | Sharing & permissions | `share_project`, `share_dashboard`, `share_source`, `list_project_shares`, `list_dashboard_shares`, `revoke_project_share`, `revoke_dashboard_share`, `org_share_source` |
@@ -244,6 +244,7 @@ See **[docs/server-sdk.md](docs/server-sdk.md#user-scoped-client-as_user)** for
244
244
  | `client.projects.chats` | Chats within projects | `create`, `list`, `get`, `stream`, `cancel`, `delete` |
245
245
  | `client.sources` | Sources, connectors & data | `list`, `create`, `create_data_source`, `query`, `source_data`, `append_rows`, `replace_data`, `ask`, `sync`, `list_connectors` |
246
246
  | `client.views` | SQL-defined views | `list`, `create`, `get`, `update`, `delete`, `run`, `get_run`, `wait_for_run`, `preview`, `chat` |
247
+ | `client.library` | Data Library — graph + librarian agent | `create_library`, `create_collection`, `create_anchor_question`, `create_refining_question`, `link`, `status`, `list_nodes`, `get_node`, `search`, `zoom`, `record_fact`, `chat`, `chat_stream`, `backfill`, `seed_fixture`, `health` |
247
248
  | `client.files` | File management | `upload`, `list`, `get`, `delete` |
248
249
  | `client.keys` | API key management | `create`, `list`, `get`, `delete` |
249
250
  | `client.sharing` | Sharing & permissions | `share_project`, `share_dashboard`, `share_source`, `list_project_shares`, `list_dashboard_shares`, `revoke_project_share`, `revoke_dashboard_share`, `org_share_source` |
@@ -0,0 +1,180 @@
1
+ # Data Library — CLI + SDK reference
2
+
3
+ The Data Library is Querri's knowledge-graph + semantic-search layer over Collections, Questions, Sources, Views, KPIs, and Facts. The `querri library` CLI subcommand + `client.library` SDK resource expose the full surface.
4
+
5
+ This doc is the user-facing reference. For module-level architecture see [`~/Q/Querri/server-api/querri_core/library/MODULE.md`](https://github.com/Querri-inc/server-api/blob/feat/data-overview/server-api/querri_core/library/MODULE.md) and the program-level [`documentation/data-library/`](https://github.com/Querri-inc/server-api/tree/feat/data-overview/documentation/data-library).
6
+
7
+ ## Auth + setup
8
+
9
+ ```bash
10
+ export QUERRI_API_KEY=qk_...
11
+ export QUERRI_ORG_ID=<tenant-id> # required for API-key auth
12
+ export QUERRI_HOST=http://localhost # dev; production defaults to https://app.querri.com
13
+ ```
14
+
15
+ Most commands take `--library-id` explicitly. To save typing, set an active library once and subsequent commands pick it up:
16
+
17
+ ```bash
18
+ querri library use lib_<tenant-id>_<uuid>
19
+ ```
20
+
21
+ The active library is stored in `~/.querri/library_context.json`. Override per-call with `--library-id` or `QUERRI_LIBRARY_ID`.
22
+
23
+ ## Quick start — the demo path
24
+
25
+ ```bash
26
+ # 1. Create a library and select it.
27
+ querri library create-library "Primary Library"
28
+ querri library use <library-id-printed-above>
29
+
30
+ # 2. Seed it with realistic demo data (5 collections, 16 sources, ~150 series).
31
+ querri library seed-demo --fixture demo-acme
32
+
33
+ # 3. Inspect.
34
+ querri library status
35
+ querri library list Collection
36
+ querri library list ViewStub
37
+
38
+ # 4. Search semantically.
39
+ querri library search "customer churn signals"
40
+ querri library zoom "marketing attribution by channel" --zoom 50 --explain
41
+
42
+ # 5. Chat with the Librarian agent.
43
+ querri library chat "What collections relate to revenue?" --new --show-tools
44
+
45
+ # 6. Record a Fact (CLI-direct OR via the agent).
46
+ querri library record-fact \
47
+ "orders.status enum gained 'awaiting_carrier' in Mar 2026" \
48
+ --attach <source-node-id> --kind data_quality
49
+
50
+ # 7. Have the agent commission a new view.
51
+ querri library chat \
52
+ "Commission a view called orders_by_country that sums total_amount grouped by shipping_country, using the orders source. Build it." \
53
+ --show-tools
54
+ ```
55
+
56
+ ## CLI reference
57
+
58
+ All commands accept `--json` (a top-level flag, e.g. `querri --json library status`) for scripting.
59
+
60
+ ### Workspace state
61
+
62
+ | Command | What it does |
63
+ |---|---|
64
+ | `querri library use <library-id>` | Set active library (persists across invocations). |
65
+ | `querri library status [--library-id <id>]` | Counts per node_kind + tenant + total. |
66
+ | `querri library health` | Tenant-scoped health probe (Mongo doc count, embedding model). |
67
+
68
+ ### Create
69
+
70
+ | Command | What it does |
71
+ |---|---|
72
+ | `querri library create-library <name> [--summary <s>]` | New `Library` node. |
73
+ | `querri library create-collection <name> [--anchor "<text>"] [--summary <s>]` | New `Collection`. With `--anchor` also mints an `AnchorQuestion` + the `anchor_of` edge in one call. |
74
+ | `querri library create-question <text> [--refining] [--name <n>]` | Anchor question by default; pass `--refining` for a `RefiningQuestion`. |
75
+ | `querri library add-refining <anchor-id> "<text>"` | New `RefiningQuestion` linked to the anchor via `refines`. |
76
+ | `querri library link <a> <b> <relation> [--weight X] [--confidence X]` | Bidirectional edge between two existing nodes. `<relation>` is one of `contains`, `anchor_of`, `refines`, `parent_of`, `in_category`, `uses_source`, `uses_view`, `connector_provides`, `view_built_from`, `series_in`, `about`, `semantic_neighbor`, `mentions`. |
77
+
78
+ ### Read
79
+
80
+ | Command | What it does |
81
+ |---|---|
82
+ | `querri library get <node-id>` | Fetch a single node by id. |
83
+ | `querri library list <node-kind> [--limit N]` | All nodes of a given kind in the active library. |
84
+ | `querri library search <query> [--kind K] [--limit N] [--surface S]` | Vector ANN search — flat top-K, no graph traversal. Fast surface. |
85
+ | `querri library zoom <query> [--zoom N] [--budget N] [--top-k N] [--threshold X] [--kind K] [--explain]` | Vector-seeded multi-focal graph zoom: ANN-resolves focals, BFS up to 2 hops, scores by `edge.confidence × edge.weight × (zoom/100)^hops`. `--explain` prints per-stage latency + algorithm decisions. |
86
+ | `querri library zoom --focal <id> [--focal <id>] [--zoom N]` | Skip ANN; zoom directly from given focal IDs (for iterative drill-in). |
87
+
88
+ ### Write — facts + view commissioning
89
+
90
+ | Command | What it does |
91
+ |---|---|
92
+ | `querri library record-fact "<statement>" --attach <node-id> [--attach <id>] [--kind <K>] [--evidence <url>] [--confidence X]` | Create a `Fact` node + `about` edges to each `--attach` target. `--kind` is one of `contextual_note` (default), `data_quality`, `timing`, `scope_constraint`. |
93
+ | `querri library chat "<message>" [--new] [--chat-id <id>] [--show-tools]` | Streaming chat with the LibrarianAgent. Three tools available to the agent: `search_graph`, `record_fact`, `commission_view`. `--show-tools` prints tool calls + Views-agent progress dots in real time. Active chat id persists per-library so subsequent calls continue the same conversation; `--new` starts fresh. |
94
+
95
+ ### Bulk / admin
96
+
97
+ | Command | What it does |
98
+ |---|---|
99
+ | `querri library seed-demo [--fixture <name>]` | Apply a named demo fixture (currently: `demo-acme`). Writes both library_graph stubs AND real legacy `sources` + `connectors` docs so the Views agent can resolve fixture sources. Idempotent. |
100
+ | `querri library backfill [--library-id <id>] [--no-series]` | Walk the tenant's existing `connectors` + `sources` collections and create matching stubs in `library_graph`. Idempotent. |
101
+
102
+ ## SDK reference
103
+
104
+ Sync (`client.library`) and async (`client.library` on `AsyncQuerri`) have identical surfaces.
105
+
106
+ ```python
107
+ from querri import Querri
108
+
109
+ client = Querri(api_key="qk_...", org_id="<tenant>", host="http://localhost")
110
+
111
+ # Workspace
112
+ status = client.library.status(library_id="lib_...")
113
+ print(status.counts_by_kind) # {"Collection": 8, "Fact": 4, ...}
114
+
115
+ # Search
116
+ hits = client.library.search(query="churn risk", library_id="lib_...", limit=5)
117
+ for h in hits.results:
118
+ print(h.score, h.node_kind, h.name)
119
+
120
+ # Vector-seeded graph zoom
121
+ zoom = client.library.zoom(
122
+ library_id="lib_...",
123
+ query="channel attribution and ROI",
124
+ zoom=50, budget_tokens=2000, top_k_focal=4,
125
+ )
126
+ for f in zoom.focal_nodes:
127
+ print(f.score, f.confidence_tier, f.node_kind, f.name)
128
+ for n in zoom.subgraph_nodes:
129
+ print(n.edge_strength, n.hops, "/".join(n.edge_path), n.name)
130
+
131
+ # Record a fact via the API directly
132
+ fact = client.library.record_fact(
133
+ library_id="lib_...",
134
+ statement="orders.status enum gained 'awaiting_carrier' in Mar 2026",
135
+ fact_kind="data_quality",
136
+ source_node_ids=["src_..."],
137
+ evidence_refs=["https://runbooks/orders-status-evolution"],
138
+ confidence=0.95,
139
+ )
140
+
141
+ # Chat — drained ChatResponse
142
+ result = client.library.chat(
143
+ library_id="lib_...",
144
+ message="What collections relate to revenue?",
145
+ chat_id=None, # omit to start fresh
146
+ )
147
+ print(result.assistant_message)
148
+ print(result.tool_calls) # list of {name, input, result, duration_ms}
149
+
150
+ # Chat — streaming events (sync iterator)
151
+ for event in client.library.chat_stream(
152
+ library_id="lib_...",
153
+ message="Commission a payment_failures view from the payments source",
154
+ ):
155
+ print(event["type"], event.get("name") or event.get("text", "")[:80])
156
+ ```
157
+
158
+ ### Streaming event types
159
+
160
+ The chat stream yields these `type`s (in order, typically):
161
+
162
+ | Type | Payload |
163
+ |---|---|
164
+ | `tool_use` | `{tool_use_id, name, input}` — emitted right before each tool call |
165
+ | `tool_progress` | `{tool_use_id, raw}` — pass-through inner-agent SSE chunks during `commission_view` |
166
+ | `tool_result` | `{tool_use_id, name, input, result, duration_ms}` |
167
+ | `assistant_text` | `{text}` — terminal LLM text in the final turn |
168
+ | `done` | `{chat_id, library_id, assistant_message, turns_used, stop_reason, input_tokens, output_tokens, total_ms}` |
169
+ | `error` | `{message}` — stream-level failure |
170
+
171
+ ## Scopes
172
+
173
+ Library routes require API-key scopes:
174
+
175
+ | Scope | Routes |
176
+ |---|---|
177
+ | `admin:library:read` | GET `/library/nodes`, `/library/nodes/{id}`, `/library/status`, `/library/health`, POST `/library/search`, `/library/zoom` |
178
+ | `admin:library:write` | POST `/library/libraries`, `/library/collections`, `/library/questions/*`, `/library/edges`, `/library/facts`, `/library/backfill`, `/library/seed-fixture`, `/library/chat` |
179
+
180
+ The wildcard `*` superadmin scope satisfies both. JWT auth gives both scopes to `member` role.
@@ -14,6 +14,7 @@ if TYPE_CHECKING:
14
14
  from .resources.embed import AsyncEmbed, Embed
15
15
  from .resources.files import AsyncFiles, Files
16
16
  from .resources.keys import AsyncKeys, Keys
17
+ from .resources.library import AsyncLibrary, Library
17
18
  from .resources.policies import AsyncPolicies, Policies
18
19
  from .resources.projects import AsyncProjects, Projects
19
20
  from .resources.sharing import AsyncSharing, Sharing
@@ -87,6 +88,7 @@ class Querri:
87
88
  self._sharing: object | None = None
88
89
  self._usage: object | None = None
89
90
  self._audit: object | None = None
91
+ self._library: object | None = None
90
92
 
91
93
  @property
92
94
  def users(self) -> "Users":
@@ -184,6 +186,14 @@ class Querri:
184
186
  self._audit = Audit(self._http)
185
187
  return self._audit # type: ignore[return-value]
186
188
 
189
+ @property
190
+ def library(self) -> "Library":
191
+ if self._library is None:
192
+ from .resources.library import Library
193
+
194
+ self._library = Library(self._http)
195
+ return self._library # type: ignore[return-value]
196
+
187
197
  def as_user(self, session: dict[str, Any]) -> "UserQuerri":
188
198
  """Create a user-scoped client from a ``get_session()`` result.
189
199
 
@@ -270,6 +280,7 @@ class AsyncQuerri:
270
280
  self._sharing: object | None = None
271
281
  self._usage: object | None = None
272
282
  self._audit: object | None = None
283
+ self._library: object | None = None
273
284
 
274
285
  @property
275
286
  def users(self) -> "AsyncUsers":
@@ -367,6 +378,14 @@ class AsyncQuerri:
367
378
  self._audit = AsyncAudit(self._http)
368
379
  return self._audit # type: ignore[return-value]
369
380
 
381
+ @property
382
+ def library(self) -> "AsyncLibrary":
383
+ if self._library is None:
384
+ from .resources.library import AsyncLibrary
385
+
386
+ self._library = AsyncLibrary(self._http)
387
+ return self._library # type: ignore[return-value]
388
+
370
389
  def as_user(self, session: dict[str, Any]) -> "AsyncUserQuerri":
371
390
  """Create a user-scoped async client from a ``get_session()`` result.
372
391
 
@@ -163,16 +163,35 @@ def raise_for_status(
163
163
  retry_after: Retry-After value from response headers.
164
164
  """
165
165
  error = body.get("error", {})
166
- if isinstance(error, dict):
166
+ if isinstance(error, dict) and error:
167
167
  err_type = str(error.get("type", ""))
168
168
  err_code = str(error.get("code", ""))
169
169
  err_message = str(error.get("message", f"HTTP {status}"))
170
170
  err_doc_url = error.get("doc_url")
171
171
  else:
172
+ # FastAPI's `HTTPException(detail=...)` produces `{"detail": ...}`
173
+ # with no `error` envelope. Prefer the `detail` payload over a
174
+ # generic "HTTP {status}" so admin-gate / validation errors carry
175
+ # the helpful server-side message through to the CLI user.
176
+ # `detail` may be a string OR a nested dict (FastAPI lets you raise
177
+ # an HTTPException with a structured detail).
178
+ detail = body.get("detail")
172
179
  err_type = ""
173
180
  err_code = ""
174
- err_message = str(body) if body else f"HTTP {status}"
175
181
  err_doc_url = None
182
+ if isinstance(detail, str) and detail:
183
+ err_message = detail
184
+ elif isinstance(detail, dict) and detail:
185
+ nested_error = detail.get("error")
186
+ if isinstance(nested_error, dict) and nested_error:
187
+ err_type = str(nested_error.get("type", ""))
188
+ err_code = str(nested_error.get("code", ""))
189
+ err_message = str(nested_error.get("message", f"HTTP {status}"))
190
+ err_doc_url = nested_error.get("doc_url")
191
+ else:
192
+ err_message = str(detail)
193
+ else:
194
+ err_message = str(body) if body else f"HTTP {status}"
176
195
 
177
196
  exc_class = _STATUS_MAP.get(status, APIError)
178
197
 
@@ -0,0 +1 @@
1
+ __version__ = "2.1.0"
@@ -14,6 +14,7 @@ from querri.cli.dashboards import dashboards_app
14
14
  from querri.cli.embed import embed_app
15
15
  from querri.cli.files import files_app
16
16
  from querri.cli.keys import keys_app
17
+ from querri.cli.library import library_app
17
18
  from querri.cli.policies import policies_app
18
19
  from querri.cli.projects import projects_app
19
20
  from querri.cli.sharing import sharing_app
@@ -227,6 +228,11 @@ main_app.add_typer(
227
228
  name="dashboard",
228
229
  rich_help_panel="[#f15a24]Projects & Data[/#f15a24]",
229
230
  )
231
+ main_app.add_typer(
232
+ library_app,
233
+ name="library",
234
+ rich_help_panel="[#f15a24]Projects & Data[/#f15a24]",
235
+ )
230
236
 
231
237
  # ── Administration ──────────────────────────────────────────────────────
232
238
  main_app.add_typer(