unique-sdk 2026.26.0.dev9__tar.gz → 2026.26.0.dev10__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.
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/PKG-INFO +1 -1
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/pyproject.toml +1 -1
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/cli.py +58 -3
- unique_sdk-2026.26.0.dev10/unique_sdk/cli/commands/read.py +176 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/shell.py +78 -6
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +60 -0
- unique_sdk-2026.26.0.dev9/unique_sdk/cli/commands/read.py +0 -93
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/README.md +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/__init__.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_api_requestor.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_api_resource.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_api_version.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_error.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_http_client.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_list_object.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_object_classes.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_request_options.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_unique_object.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_unique_ql.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_unique_response.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_util.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_version.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/_webhook.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/__init__.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_acronyms.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_agentic_table.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_analytics_order.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_benchmarking.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_briefing.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_chat_completion.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_content.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_dynamic_frontend.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_elicitation.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_embedding.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_event.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_folder.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_group.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_integrated.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_llm_models.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_mcp.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_message.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_message_assessment.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_message_execution.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_message_log.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_message_tool.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_module.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_scheduled_task.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_search.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_search_string.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_short_term_memory.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_space.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_user.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_web_search.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/__init__.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/__main__.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/__init__.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/cite_file.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/dynamic_frontend.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/elicitation.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/files.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/folders.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/mcp.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/navigation.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/search.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/subagent.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/web_search.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/web_search_config.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/config.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/formatting.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/state.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/utils/analytics_order_run.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/utils/benchmarking_run.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/utils/chat_history.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/utils/chat_in_space.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/utils/file_io.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/utils/sources.py +0 -0
- {unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/utils/token.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: unique-sdk
|
|
3
|
-
Version: 2026.26.0.
|
|
3
|
+
Version: 2026.26.0.dev10
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Martin Fadler, Konstantin Krauss, Andreas Hauri
|
|
6
6
|
Author-email: Martin Fadler <martin.fadler@unique.ch>, Konstantin Krauss <konstantin@unique.ch>, Andreas Hauri <andreas@unique.ch>
|
|
@@ -398,9 +398,41 @@ def cite(
|
|
|
398
398
|
|
|
399
399
|
@main.command(name="read")
|
|
400
400
|
@click.argument("cont_id")
|
|
401
|
+
@click.option(
|
|
402
|
+
"--page",
|
|
403
|
+
"-p",
|
|
404
|
+
type=int,
|
|
405
|
+
default=None,
|
|
406
|
+
help="Read a single page (shorthand for --from-page N --to-page N).",
|
|
407
|
+
)
|
|
408
|
+
@click.option(
|
|
409
|
+
"--from-page",
|
|
410
|
+
type=int,
|
|
411
|
+
default=None,
|
|
412
|
+
help="First page to include (inclusive).",
|
|
413
|
+
)
|
|
414
|
+
@click.option(
|
|
415
|
+
"--to-page",
|
|
416
|
+
type=int,
|
|
417
|
+
default=None,
|
|
418
|
+
help="Last page to include (inclusive).",
|
|
419
|
+
)
|
|
420
|
+
@click.option(
|
|
421
|
+
"--max-chars",
|
|
422
|
+
type=int,
|
|
423
|
+
default=None,
|
|
424
|
+
help="Truncate the printed text to at most N characters.",
|
|
425
|
+
)
|
|
401
426
|
@click.pass_context
|
|
402
|
-
def read_cmd(
|
|
403
|
-
|
|
427
|
+
def read_cmd(
|
|
428
|
+
ctx: click.Context,
|
|
429
|
+
cont_id: str,
|
|
430
|
+
page: int | None,
|
|
431
|
+
from_page: int | None,
|
|
432
|
+
to_page: int | None,
|
|
433
|
+
max_chars: int | None,
|
|
434
|
+
) -> None:
|
|
435
|
+
"""Read indexed text chunks for a known content ID.
|
|
404
436
|
|
|
405
437
|
\b
|
|
406
438
|
CONT_ID must be a content ID (cont_...) obtained from a prior `ls` or
|
|
@@ -411,11 +443,34 @@ def read_cmd(ctx: click.Context, cont_id: str) -> None:
|
|
|
411
443
|
Use `search` when you need to find documents by topic or keyword.
|
|
412
444
|
Use `read` when you already know the content ID and want the full text.
|
|
413
445
|
|
|
446
|
+
\b
|
|
447
|
+
Restrict to a page range with --page (single page) or --from-page/--to-page.
|
|
448
|
+
A chunk spanning pages 2-4 is returned for any overlapping request; files
|
|
449
|
+
without page numbers (e.g. plain text/markdown) are returned only without a
|
|
450
|
+
page range.
|
|
451
|
+
|
|
414
452
|
\b
|
|
415
453
|
Examples:
|
|
416
454
|
unique-cli read cont_abc123
|
|
455
|
+
unique-cli read cont_abc123 --page 12
|
|
456
|
+
unique-cli read cont_abc123 --from-page 5 --to-page 9
|
|
457
|
+
unique-cli read cont_abc123 --to-page 3 --max-chars 8000
|
|
417
458
|
"""
|
|
418
|
-
|
|
459
|
+
if page is not None and (from_page is not None or to_page is not None):
|
|
460
|
+
click.echo(
|
|
461
|
+
"read: use either --page or --from-page/--to-page, not both", err=True
|
|
462
|
+
)
|
|
463
|
+
raise SystemExit(1)
|
|
464
|
+
if page is not None:
|
|
465
|
+
from_page = page
|
|
466
|
+
to_page = page
|
|
467
|
+
output = cmd_read(
|
|
468
|
+
LazyState.get(ctx),
|
|
469
|
+
cont_id,
|
|
470
|
+
from_page=from_page,
|
|
471
|
+
to_page=to_page,
|
|
472
|
+
max_chars=max_chars,
|
|
473
|
+
)
|
|
419
474
|
if _is_read_error_output(output):
|
|
420
475
|
click.echo(output, err=True)
|
|
421
476
|
raise SystemExit(1)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Read command: retrieve all indexed text chunks for a known content ID.
|
|
2
|
+
|
|
3
|
+
Calls ``Content.search(where={"id": {"equals": cont_id}})`` — a direct
|
|
4
|
+
Postgres lookup that returns every indexed chunk for the document in one
|
|
5
|
+
request, no vector search involved.
|
|
6
|
+
|
|
7
|
+
Use this when you already know the ``cont_*`` ID (e.g. from a prior ``ls``
|
|
8
|
+
or ``unique-cli search`` result) and want to read the full document text.
|
|
9
|
+
For discovery or query-based retrieval use ``unique-cli search`` instead.
|
|
10
|
+
|
|
11
|
+
Pass ``from_page``/``to_page`` to read only part of a long document by page
|
|
12
|
+
range; chunks are filtered client-side on the ``startPage``/``endPage`` the
|
|
13
|
+
platform already returns, so no ingestion changes are required.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
import unique_sdk
|
|
21
|
+
from unique_sdk.cli.state import ShellState
|
|
22
|
+
|
|
23
|
+
READ_ERROR_PREFIX = "read:"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _chunk_in_page_range(
|
|
27
|
+
chunk: dict[str, Any],
|
|
28
|
+
from_page: int | None,
|
|
29
|
+
to_page: int | None,
|
|
30
|
+
) -> bool:
|
|
31
|
+
"""Return True if *chunk* overlaps the requested ``[from_page, to_page]`` span.
|
|
32
|
+
|
|
33
|
+
A chunk covers ``startPage``..``endPage`` inclusive. With page-based chunking
|
|
34
|
+
these are equal (one chunk per page); otherwise a single chunk can span
|
|
35
|
+
several pages, so we keep any chunk that *overlaps* the requested range
|
|
36
|
+
rather than one fully contained in it. Chunks without page numbers are
|
|
37
|
+
excluded, since they cannot be placed on a page. ``from_page``/``to_page``
|
|
38
|
+
that are ``None`` act as open bounds.
|
|
39
|
+
"""
|
|
40
|
+
start: int | None = chunk.get("startPage")
|
|
41
|
+
end: int | None = chunk.get("endPage")
|
|
42
|
+
if start is None:
|
|
43
|
+
start = end
|
|
44
|
+
if end is None:
|
|
45
|
+
end = start
|
|
46
|
+
if start is None or end is None:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
low = from_page if from_page is not None else start
|
|
50
|
+
high = to_page if to_page is not None else end
|
|
51
|
+
return start <= high and end >= low
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _format_requested_range(from_page: int | None, to_page: int | None) -> str:
|
|
55
|
+
"""Human-readable label for a requested page range (for messages)."""
|
|
56
|
+
if from_page is not None and to_page is not None:
|
|
57
|
+
return str(from_page) if from_page == to_page else f"{from_page}-{to_page}"
|
|
58
|
+
if from_page is not None:
|
|
59
|
+
return f"{from_page}+"
|
|
60
|
+
return f"up to {to_page}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def cmd_read(
|
|
64
|
+
state: ShellState,
|
|
65
|
+
cont_id: str,
|
|
66
|
+
from_page: int | None = None,
|
|
67
|
+
to_page: int | None = None,
|
|
68
|
+
max_chars: int | None = None,
|
|
69
|
+
) -> str:
|
|
70
|
+
"""Return indexed text chunks for *cont_id* as plain text.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
state: Shell state carrying user/company credentials.
|
|
74
|
+
cont_id: A content ID (``cont_...``) to retrieve.
|
|
75
|
+
from_page: First page to include (inclusive). ``None`` = open start.
|
|
76
|
+
to_page: Last page to include (inclusive). ``None`` = open end.
|
|
77
|
+
max_chars: Truncate the returned text to at most this many characters.
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
A formatted string of chunks, or an error message prefixed with
|
|
81
|
+
``read:``.
|
|
82
|
+
|
|
83
|
+
When ``from_page``/``to_page`` are given, chunks are filtered to those that
|
|
84
|
+
overlap the requested pages. The page numbers come from ingestion; nothing
|
|
85
|
+
needs to change there. A chunk spanning pages 2-4 is returned for any range
|
|
86
|
+
touching 2-4, so the text may include a little from neighbouring pages.
|
|
87
|
+
"""
|
|
88
|
+
if not cont_id.startswith("cont_"):
|
|
89
|
+
return f"{READ_ERROR_PREFIX} expected a content ID starting with 'cont_', got: {cont_id!r}"
|
|
90
|
+
|
|
91
|
+
if from_page is not None and to_page is not None and from_page > to_page:
|
|
92
|
+
return f"{READ_ERROR_PREFIX} invalid page range ({from_page} > {to_page})"
|
|
93
|
+
|
|
94
|
+
if max_chars is not None and max_chars < 1:
|
|
95
|
+
return f"{READ_ERROR_PREFIX} invalid --max-chars ({max_chars}); must be >= 1"
|
|
96
|
+
|
|
97
|
+
# Enforce the same .unique-search.json workspace boundary as search/ls/rm.
|
|
98
|
+
# Content.search has no scopeIds param, so we guard by owner scope before
|
|
99
|
+
# the point-lookup — matching rm/mv, not search's API-level scopeIds filter.
|
|
100
|
+
if not state.is_content_within_workspace(cont_id):
|
|
101
|
+
return f"{READ_ERROR_PREFIX} permission denied (outside workspace scope)"
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
results = unique_sdk.Content.search(
|
|
105
|
+
user_id=state.config.user_id,
|
|
106
|
+
company_id=state.config.company_id,
|
|
107
|
+
where={"id": {"equals": cont_id}},
|
|
108
|
+
)
|
|
109
|
+
except unique_sdk.APIError as e:
|
|
110
|
+
return f"{READ_ERROR_PREFIX} {e}"
|
|
111
|
+
|
|
112
|
+
if not results:
|
|
113
|
+
return f"{READ_ERROR_PREFIX} no content found for ID: {cont_id}"
|
|
114
|
+
|
|
115
|
+
content = results[0]
|
|
116
|
+
title = getattr(content, "title", None) or getattr(content, "key", None) or cont_id
|
|
117
|
+
chunks = getattr(content, "chunks", None) or []
|
|
118
|
+
|
|
119
|
+
if not chunks:
|
|
120
|
+
return (
|
|
121
|
+
f"Content: {title} ({cont_id})\n"
|
|
122
|
+
"No indexed chunks found — the document may still be ingesting or ingestion failed."
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
sorted_chunks = sorted(chunks, key=lambda c: c.get("order") or 0)
|
|
126
|
+
|
|
127
|
+
if from_page is not None or to_page is not None:
|
|
128
|
+
sorted_chunks = [
|
|
129
|
+
c for c in sorted_chunks if _chunk_in_page_range(c, from_page, to_page)
|
|
130
|
+
]
|
|
131
|
+
if not sorted_chunks:
|
|
132
|
+
page_range = _format_requested_range(from_page, to_page)
|
|
133
|
+
return (
|
|
134
|
+
f"Content: {title} ({cont_id})\n"
|
|
135
|
+
f"No indexed chunks found in page range {page_range}. The document "
|
|
136
|
+
"may not have page numbers (e.g. plain text/markdown) or spans a "
|
|
137
|
+
"different range — read without a page range to see all text."
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
lines: list[str] = [
|
|
141
|
+
f"Content: {title} ({cont_id}) — {len(sorted_chunks)} chunk(s)\n"
|
|
142
|
+
]
|
|
143
|
+
for chunk in sorted_chunks:
|
|
144
|
+
text = (chunk.get("text") or "").strip()
|
|
145
|
+
if not text:
|
|
146
|
+
continue
|
|
147
|
+
start = chunk.get("startPage")
|
|
148
|
+
end = chunk.get("endPage")
|
|
149
|
+
if start is not None or end is not None:
|
|
150
|
+
page_start = start if start is not None else end
|
|
151
|
+
page_end = end if end is not None else start
|
|
152
|
+
if page_start is not None and page_end is not None:
|
|
153
|
+
page_ref = (
|
|
154
|
+
f"[p.{page_start}]"
|
|
155
|
+
if page_start == page_end
|
|
156
|
+
else f"[p.{page_start}-{page_end}]"
|
|
157
|
+
)
|
|
158
|
+
lines.append(f"{page_ref} {text}")
|
|
159
|
+
else:
|
|
160
|
+
lines.append(text)
|
|
161
|
+
else:
|
|
162
|
+
lines.append(text)
|
|
163
|
+
|
|
164
|
+
output = "\n\n".join(lines)
|
|
165
|
+
if max_chars is not None and len(output) > max_chars:
|
|
166
|
+
if from_page is not None or to_page is not None:
|
|
167
|
+
hint = "narrow the page range or raise --max-chars to see more"
|
|
168
|
+
else:
|
|
169
|
+
hint = "use a page range (--page/--from-page/--to-page) or raise --max-chars to see more"
|
|
170
|
+
output = f"{output[:max_chars]}\n... [truncated at {max_chars} chars; {hint}]"
|
|
171
|
+
return output
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def is_error_output(output: str) -> bool:
|
|
175
|
+
"""Return ``True`` when *output* is an error message from ``cmd_read``."""
|
|
176
|
+
return output.startswith(READ_ERROR_PREFIX)
|
|
@@ -68,7 +68,11 @@ OVERVIEW_HELP = textwrap.dedent("""\
|
|
|
68
68
|
--folder <path|id> Restrict to a folder
|
|
69
69
|
--metadata <key=value> Filter by metadata (repeatable)
|
|
70
70
|
--limit <N> Max results (default: 200)
|
|
71
|
-
read <cont_id>
|
|
71
|
+
read <cont_id> [options] Read indexed text chunks for a content ID
|
|
72
|
+
--page / -p <N> Read a single page
|
|
73
|
+
--from-page <N> First page (inclusive)
|
|
74
|
+
--to-page <N> Last page (inclusive)
|
|
75
|
+
--max-chars <N> Truncate output to N characters
|
|
72
76
|
|
|
73
77
|
MCP:
|
|
74
78
|
mcp [options] <json> Call an MCP server tool
|
|
@@ -470,27 +474,95 @@ class UniqueShell(cmd.Cmd):
|
|
|
470
474
|
return
|
|
471
475
|
self._print(cmd_cite_file(self.state, positional[0], pages))
|
|
472
476
|
|
|
477
|
+
def _parse_int(self, raw: str, flag: str) -> tuple[int | None, bool]:
|
|
478
|
+
"""Parse an int option value, returning (value, ok). Prints on failure."""
|
|
479
|
+
try:
|
|
480
|
+
return int(raw), True
|
|
481
|
+
except ValueError:
|
|
482
|
+
self._print(f"Invalid {flag}: {raw} (expected an integer)")
|
|
483
|
+
return None, False
|
|
484
|
+
|
|
473
485
|
def do_read(self, arg: str) -> None:
|
|
474
|
-
"""Read
|
|
486
|
+
"""Read indexed text chunks for a known content ID (optionally by page).
|
|
475
487
|
|
|
476
|
-
Usage: read <cont_id>
|
|
488
|
+
Usage: read <cont_id> [--page N | --from-page N --to-page M] [--max-chars N]
|
|
477
489
|
|
|
478
490
|
Retrieves every indexed chunk for the document directly from the
|
|
479
|
-
database — no vector search, no query string needed.
|
|
491
|
+
database — no vector search, no query string needed. Use --page for a
|
|
492
|
+
single page or --from-page/--to-page for a range; a chunk spanning
|
|
493
|
+
pages 2-4 is returned for any overlapping request.
|
|
480
494
|
|
|
481
495
|
Use `search` to find documents by topic; use `read` once you have
|
|
482
496
|
the content ID and want the full text.
|
|
483
497
|
|
|
484
498
|
Examples:
|
|
485
499
|
/Reports> read cont_abc123
|
|
500
|
+
/Reports> read cont_abc123 --page 12
|
|
501
|
+
/Reports> read cont_abc123 --from-page 5 --to-page 9
|
|
486
502
|
"""
|
|
487
503
|
from unique_sdk.cli.commands.read import cmd_read
|
|
488
504
|
|
|
489
505
|
parts = shlex.split(arg)
|
|
506
|
+
usage = (
|
|
507
|
+
"Usage: read <cont_id> "
|
|
508
|
+
"[--page N | --from-page N --to-page M] [--max-chars N]"
|
|
509
|
+
)
|
|
490
510
|
if not parts:
|
|
491
|
-
self._print(
|
|
511
|
+
self._print(usage)
|
|
492
512
|
return
|
|
493
|
-
|
|
513
|
+
|
|
514
|
+
cont_id: str | None = None
|
|
515
|
+
page: int | None = None
|
|
516
|
+
from_page: int | None = None
|
|
517
|
+
to_page: int | None = None
|
|
518
|
+
max_chars: int | None = None
|
|
519
|
+
|
|
520
|
+
int_flags = ("--page", "-p", "--from-page", "--to-page", "--max-chars")
|
|
521
|
+
i = 0
|
|
522
|
+
while i < len(parts):
|
|
523
|
+
tok = parts[i]
|
|
524
|
+
if tok in int_flags:
|
|
525
|
+
if i + 1 >= len(parts):
|
|
526
|
+
self._print(f"Missing value for {tok}")
|
|
527
|
+
return
|
|
528
|
+
value, ok = self._parse_int(parts[i + 1], tok)
|
|
529
|
+
if not ok:
|
|
530
|
+
return
|
|
531
|
+
if tok in ("--page", "-p"):
|
|
532
|
+
page = value
|
|
533
|
+
elif tok == "--from-page":
|
|
534
|
+
from_page = value
|
|
535
|
+
elif tok == "--to-page":
|
|
536
|
+
to_page = value
|
|
537
|
+
else: # --max-chars
|
|
538
|
+
max_chars = value
|
|
539
|
+
i += 2
|
|
540
|
+
elif cont_id is None:
|
|
541
|
+
cont_id = tok
|
|
542
|
+
i += 1
|
|
543
|
+
else:
|
|
544
|
+
self._print(f"Unknown argument: {tok}")
|
|
545
|
+
return
|
|
546
|
+
|
|
547
|
+
if cont_id is None:
|
|
548
|
+
self._print(usage)
|
|
549
|
+
return
|
|
550
|
+
if page is not None and (from_page is not None or to_page is not None):
|
|
551
|
+
self._print("read: use either --page or --from-page/--to-page, not both")
|
|
552
|
+
return
|
|
553
|
+
if page is not None:
|
|
554
|
+
from_page = page
|
|
555
|
+
to_page = page
|
|
556
|
+
|
|
557
|
+
self._print(
|
|
558
|
+
cmd_read(
|
|
559
|
+
self.state,
|
|
560
|
+
cont_id,
|
|
561
|
+
from_page=from_page,
|
|
562
|
+
to_page=to_page,
|
|
563
|
+
max_chars=max_chars,
|
|
564
|
+
)
|
|
565
|
+
)
|
|
494
566
|
|
|
495
567
|
def do_rm(self, arg: str) -> None:
|
|
496
568
|
"""Delete a file.
|
|
@@ -4,6 +4,8 @@ description: >-
|
|
|
4
4
|
Manage files and folders on the Unique AI Platform using the unique-cli
|
|
5
5
|
command-line tool. Use when the user asks to upload, download, delete,
|
|
6
6
|
rename, list, find, restore versions, list versions, look for, or organize files and folders on Unique,
|
|
7
|
+
or to read / view / quote the text contents of a known file (optionally by
|
|
8
|
+
page or page range, e.g. "what's on page 5?", "read pages 10-12"),
|
|
7
9
|
or when working with scope IDs (scope_*) or content IDs (cont_*).
|
|
8
10
|
IMPORTANT: When a user says they are "looking for a file" or wants to
|
|
9
11
|
"find a file", they typically mean locating it within the Unique AI
|
|
@@ -64,6 +66,13 @@ unique-cli restore-version cver_abc123
|
|
|
64
66
|
unique-cli download report.pdf ./local/
|
|
65
67
|
unique-cli download cont_abc123 ~/Desktop/
|
|
66
68
|
|
|
69
|
+
# Read a file's extracted text by content ID (whole file)
|
|
70
|
+
unique-cli read cont_abc123
|
|
71
|
+
|
|
72
|
+
# Read a single page or a page range
|
|
73
|
+
unique-cli read cont_abc123 --page 12
|
|
74
|
+
unique-cli read cont_abc123 --from-page 5 --to-page 9
|
|
75
|
+
|
|
67
76
|
# Declare page citations after reading a file
|
|
68
77
|
unique-cli cite report.pdf --pages 3,5,7
|
|
69
78
|
unique-cli cite cont_abc123 --pages 1-4
|
|
@@ -141,6 +150,57 @@ unique-cli mkdir "2025/Q1/Financials"
|
|
|
141
150
|
unique-cli upload ./budget.xlsx /2025/Q1/Financials/
|
|
142
151
|
```
|
|
143
152
|
|
|
153
|
+
## Reading File Contents (by page range)
|
|
154
|
+
|
|
155
|
+
Use `read` to retrieve the **extracted text** of a single, known file — for
|
|
156
|
+
example to answer "what does page 5 say?", to quote an exact passage, or to
|
|
157
|
+
read a long document a few pages at a time. This differs from `search`:
|
|
158
|
+
`search` ranks chunks across many files by relevance; `read` returns the text
|
|
159
|
+
of one file in document order.
|
|
160
|
+
|
|
161
|
+
`read` takes a **content ID** (`cont_...`), not a file name. Get the ID first
|
|
162
|
+
from `ls` or `search`, then pass it to `read`.
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
# Whole file
|
|
166
|
+
unique-cli read cont_abc123
|
|
167
|
+
|
|
168
|
+
# A single page
|
|
169
|
+
unique-cli read cont_abc123 --page 12
|
|
170
|
+
|
|
171
|
+
# A page range (inclusive)
|
|
172
|
+
unique-cli read cont_abc123 --from-page 5 --to-page 9
|
|
173
|
+
|
|
174
|
+
# Cap the output size (protects your context window on huge files)
|
|
175
|
+
unique-cli read cont_abc123 --to-page 3 --max-chars 8000
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
| Option | Description |
|
|
179
|
+
|--------|-------------|
|
|
180
|
+
| `--page` / `-p N` | Read only page N (shorthand for `--from-page N --to-page N`) |
|
|
181
|
+
| `--from-page N` | First page to include (inclusive) |
|
|
182
|
+
| `--to-page N` | Last page to include (inclusive) |
|
|
183
|
+
| `--max-chars N` | Truncate the printed text to N characters |
|
|
184
|
+
|
|
185
|
+
Each chunk is prefixed with its source page(s) as `[p.N]` or `[p.N-M]`, so you
|
|
186
|
+
can attribute text to pages.
|
|
187
|
+
|
|
188
|
+
### How page filtering behaves (important)
|
|
189
|
+
|
|
190
|
+
- **Page numbers come from ingestion.** Each chunk carries a `startPage` and
|
|
191
|
+
`endPage`; the page filter is applied to those values. Nothing in the
|
|
192
|
+
ingestion pipeline needs to change.
|
|
193
|
+
- **Ranges overlap, they don't slice.** A chunk that spans pages 2-4 is
|
|
194
|
+
returned for `--page 3` (or any range touching 2-4). The returned text is the
|
|
195
|
+
whole chunk, so it may include a little from neighbouring pages. Treat the
|
|
196
|
+
result as "the chunks covering these pages", not a pixel-perfect page cut.
|
|
197
|
+
- **Some files have no page numbers.** Plain text, markdown, and similar
|
|
198
|
+
content has no page numbers; those chunks are returned only when you read
|
|
199
|
+
**without** a page range. A page-filtered read of such a file returns nothing.
|
|
200
|
+
- **Empty / not indexed?** If `read` reports the file is still ingesting or has
|
|
201
|
+
no indexed chunks, there is no extracted text to return — use `download` to
|
|
202
|
+
fetch the original bytes instead.
|
|
203
|
+
|
|
144
204
|
## Citing File Pages
|
|
145
205
|
|
|
146
206
|
After reading **any** file and using its content in your answer, declare citations:
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
"""Read command: retrieve all indexed text chunks for a known content ID.
|
|
2
|
-
|
|
3
|
-
Calls ``Content.search(where={"id": {"equals": cont_id}})`` — a direct
|
|
4
|
-
Postgres lookup that returns every indexed chunk for the document in one
|
|
5
|
-
request, no vector search involved.
|
|
6
|
-
|
|
7
|
-
Use this when you already know the ``cont_*`` ID (e.g. from a prior ``ls``
|
|
8
|
-
or ``unique-cli search`` result) and want to read the full document text.
|
|
9
|
-
For discovery or query-based retrieval use ``unique-cli search`` instead.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
from __future__ import annotations
|
|
13
|
-
|
|
14
|
-
import unique_sdk
|
|
15
|
-
from unique_sdk.cli.state import ShellState
|
|
16
|
-
|
|
17
|
-
READ_ERROR_PREFIX = "read:"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def cmd_read(state: ShellState, cont_id: str) -> str:
|
|
21
|
-
"""Return all indexed text chunks for *cont_id* as plain text.
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
state: Shell state carrying user/company credentials.
|
|
25
|
-
cont_id: A content ID (``cont_...``) to retrieve.
|
|
26
|
-
|
|
27
|
-
Returns:
|
|
28
|
-
A formatted string of chunks, or an error message prefixed with
|
|
29
|
-
``read:``.
|
|
30
|
-
"""
|
|
31
|
-
if not cont_id.startswith("cont_"):
|
|
32
|
-
return f"{READ_ERROR_PREFIX} expected a content ID starting with 'cont_', got: {cont_id!r}"
|
|
33
|
-
|
|
34
|
-
# Enforce the same .unique-search.json workspace boundary as search/ls/rm.
|
|
35
|
-
# Content.search has no scopeIds param, so we guard by owner scope before
|
|
36
|
-
# the point-lookup — matching rm/mv, not search's API-level scopeIds filter.
|
|
37
|
-
if not state.is_content_within_workspace(cont_id):
|
|
38
|
-
return f"{READ_ERROR_PREFIX} permission denied (outside workspace scope)"
|
|
39
|
-
|
|
40
|
-
try:
|
|
41
|
-
results = unique_sdk.Content.search(
|
|
42
|
-
user_id=state.config.user_id,
|
|
43
|
-
company_id=state.config.company_id,
|
|
44
|
-
where={"id": {"equals": cont_id}},
|
|
45
|
-
)
|
|
46
|
-
except unique_sdk.APIError as e:
|
|
47
|
-
return f"{READ_ERROR_PREFIX} {e}"
|
|
48
|
-
|
|
49
|
-
if not results:
|
|
50
|
-
return f"{READ_ERROR_PREFIX} no content found for ID: {cont_id}"
|
|
51
|
-
|
|
52
|
-
content = results[0]
|
|
53
|
-
title = getattr(content, "title", None) or getattr(content, "key", None) or cont_id
|
|
54
|
-
chunks = getattr(content, "chunks", None) or []
|
|
55
|
-
|
|
56
|
-
if not chunks:
|
|
57
|
-
return (
|
|
58
|
-
f"Content: {title} ({cont_id})\n"
|
|
59
|
-
"No indexed chunks found — the document may still be ingesting or ingestion failed."
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
sorted_chunks = sorted(chunks, key=lambda c: c.get("order") or 0)
|
|
63
|
-
|
|
64
|
-
lines: list[str] = [
|
|
65
|
-
f"Content: {title} ({cont_id}) — {len(sorted_chunks)} chunk(s)\n"
|
|
66
|
-
]
|
|
67
|
-
for chunk in sorted_chunks:
|
|
68
|
-
text = (chunk.get("text") or "").strip()
|
|
69
|
-
if not text:
|
|
70
|
-
continue
|
|
71
|
-
start = chunk.get("startPage")
|
|
72
|
-
end = chunk.get("endPage")
|
|
73
|
-
if start is not None or end is not None:
|
|
74
|
-
page_start = start if start is not None else end
|
|
75
|
-
page_end = end if end is not None else start
|
|
76
|
-
if page_start is not None and page_end is not None:
|
|
77
|
-
page_ref = (
|
|
78
|
-
f"[p.{page_start}]"
|
|
79
|
-
if page_start == page_end
|
|
80
|
-
else f"[p.{page_start}-{page_end}]"
|
|
81
|
-
)
|
|
82
|
-
lines.append(f"{page_ref} {text}")
|
|
83
|
-
else:
|
|
84
|
-
lines.append(text)
|
|
85
|
-
else:
|
|
86
|
-
lines.append(text)
|
|
87
|
-
|
|
88
|
-
return "\n\n".join(lines)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def is_error_output(output: str) -> bool:
|
|
92
|
-
"""Return ``True`` when *output* is an error message from ``cmd_read``."""
|
|
93
|
-
return output.startswith(READ_ERROR_PREFIX)
|
|
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
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/__init__.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_acronyms.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_agentic_table.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_benchmarking.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_briefing.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_content.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_elicitation.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_embedding.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_folder.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_integrated.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_llm_models.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_message_log.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_message_tool.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_module.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_scheduled_task.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_search.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_search_string.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/api_resources/_web_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/cite_file.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/dynamic_frontend.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/elicitation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/navigation.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/scheduled_tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/subagent.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/cli/commands/web_search.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
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/utils/analytics_order_run.py
RENAMED
|
File without changes
|
{unique_sdk-2026.26.0.dev9 → unique_sdk-2026.26.0.dev10}/unique_sdk/utils/benchmarking_run.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|