unique-sdk 2026.42.0.dev4__tar.gz → 2026.42.0.dev5__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.42.0.dev4 → unique_sdk-2026.42.0.dev5}/PKG-INFO +1 -1
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/pyproject.toml +1 -2
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_content.py +0 -1
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/files.py +0 -11
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/folders.py +2 -11
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/navigation.py +2 -20
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/read.py +0 -3
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/search.py +0 -1
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -1
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/state.py +0 -42
- unique_sdk-2026.42.0.dev4/unique_sdk/cli/skill_guard.py +0 -314
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/README.md +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/__init__.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_api_requestor.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_api_resource.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_api_version.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_error.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_http_client.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_list_object.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_object_classes.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_request_options.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_unique_object.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_unique_ql.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_unique_response.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_util.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_version.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/_webhook.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/__init__.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_acronyms.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_agentic_table.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_analytics_order.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_benchmarking.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_briefing.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_chat_completion.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_context_memory.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_elicitation.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_embedding.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_event.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_folder.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_group.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_integrated.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_llm_models.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_mcp.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_message.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_message_assessment.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_message_execution.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_message_log.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_message_tool.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_module.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_scheduled_task.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_search.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_search_string.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_short_term_memory.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_space.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_user.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_web_search.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/__init__.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/__main__.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/cli.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/__init__.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/agentic_table.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/agentic_table_write.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/browser.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/cite_file.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/elicitation.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/mcp.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/subagent.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/web_search.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/web_search_config.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/config.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/formatting.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/identity.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/metadata_filter.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/shell.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/skills/unique-cli-agentic-table/SKILL.md +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/skills/unique-cli-uploaded-search/SKILL.md +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/workspace.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/utils/analytics_order_run.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/utils/benchmarking_run.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/utils/chat_history.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/utils/chat_in_space.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/utils/file_io.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/utils/sources.py +0 -0
- {unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/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.42.0.
|
|
3
|
+
Version: 2026.42.0.dev5
|
|
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>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "unique_sdk"
|
|
3
|
-
version = "2026.42.0.
|
|
3
|
+
version = "2026.42.0.dev5"
|
|
4
4
|
description = ""
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -91,7 +91,6 @@ markers = [
|
|
|
91
91
|
"asyncio: asyncio tests",
|
|
92
92
|
"integration: integration tests that require API access or credentials",
|
|
93
93
|
"serial: tests that must run serially",
|
|
94
|
-
"skill_guard: CLI tests that exercise the real skill guard",
|
|
95
94
|
"unit: unit tests",
|
|
96
95
|
"verified: AI-generated tests with human verification",
|
|
97
96
|
]
|
|
@@ -11,7 +11,6 @@ from typing import Any
|
|
|
11
11
|
|
|
12
12
|
import unique_sdk
|
|
13
13
|
from unique_sdk.cli.formatting import format_content_info
|
|
14
|
-
from unique_sdk.cli.skill_guard import skill_denial
|
|
15
14
|
from unique_sdk.cli.state import ShellState
|
|
16
15
|
from unique_sdk.utils.file_io import download_content, upload_file
|
|
17
16
|
|
|
@@ -338,10 +337,6 @@ def cmd_upload(
|
|
|
338
337
|
"upload: permission denied: destination is outside your "
|
|
339
338
|
f"task scope ({state.scope_denial_hint()})."
|
|
340
339
|
)
|
|
341
|
-
if state.skill_guard.is_folder_write_denied(
|
|
342
|
-
scope_id, new_file_name=display_name
|
|
343
|
-
):
|
|
344
|
-
return skill_denial("upload", display_name)
|
|
345
340
|
|
|
346
341
|
mime_type = _detect_upload_mime_type(path)
|
|
347
342
|
|
|
@@ -431,8 +426,6 @@ def cmd_download(
|
|
|
431
426
|
"""Download a file by name or content ID."""
|
|
432
427
|
try:
|
|
433
428
|
content_id, display_name = _resolve_content_id(state, name_or_id)
|
|
434
|
-
if state.is_skill_content_read_denied(content_id):
|
|
435
|
-
return skill_denial("download", display_name)
|
|
436
429
|
|
|
437
430
|
raw_name = (
|
|
438
431
|
display_name if not display_name.startswith("cont_") else f"{content_id}"
|
|
@@ -469,8 +462,6 @@ def cmd_rm(state: ShellState, name_or_id: str) -> str:
|
|
|
469
462
|
content_id, display_name = _resolve_content_id(
|
|
470
463
|
state, name_or_id, allow_chat_files=False
|
|
471
464
|
)
|
|
472
|
-
if state.is_skill_content_write_denied(content_id):
|
|
473
|
-
return skill_denial("rm", display_name)
|
|
474
465
|
unique_sdk.Content.delete(
|
|
475
466
|
user_id=state.config.user_id,
|
|
476
467
|
company_id=state.config.company_id,
|
|
@@ -490,8 +481,6 @@ def cmd_mv_file(state: ShellState, old_name: str, new_name: str) -> str:
|
|
|
490
481
|
content_id, display_name = _resolve_content_id(
|
|
491
482
|
state, old_name, allow_chat_files=False
|
|
492
483
|
)
|
|
493
|
-
if state.is_skill_content_write_denied(content_id, new_name=new_name):
|
|
494
|
-
return skill_denial("mv", display_name)
|
|
495
484
|
result = unique_sdk.Content.update(
|
|
496
485
|
user_id=state.config.user_id,
|
|
497
486
|
company_id=state.config.company_id,
|
|
@@ -6,7 +6,6 @@ import os
|
|
|
6
6
|
|
|
7
7
|
import unique_sdk
|
|
8
8
|
from unique_sdk.cli.formatting import format_folder_info
|
|
9
|
-
from unique_sdk.cli.skill_guard import skill_denial
|
|
10
9
|
from unique_sdk.cli.state import ShellState
|
|
11
10
|
|
|
12
11
|
|
|
@@ -60,8 +59,6 @@ def cmd_mkdir(state: ShellState, name: str) -> str:
|
|
|
60
59
|
"mkdir: permission denied: destination is outside your task scope "
|
|
61
60
|
f"({state.scope_denial_hint()})."
|
|
62
61
|
)
|
|
63
|
-
if state.skill_guard.is_path_write_denied(full_path):
|
|
64
|
-
return skill_denial("mkdir", full_path)
|
|
65
62
|
try:
|
|
66
63
|
result = unique_sdk.Folder.create_paths(
|
|
67
64
|
user_id=state.config.user_id,
|
|
@@ -87,14 +84,11 @@ def cmd_rmdir(state: ShellState, target: str, recursive: bool = False) -> str:
|
|
|
87
84
|
and not state.is_folder_target_within_workspace(target)
|
|
88
85
|
):
|
|
89
86
|
return "rmdir: permission denied (outside workspace scope)"
|
|
90
|
-
|
|
91
|
-
if _metadata_filter_denies_folder(state, scope_id):
|
|
87
|
+
if _metadata_filter_denies_folder(state, _resolve_folder_scope_id(state, target)):
|
|
92
88
|
return (
|
|
93
89
|
"rmdir: permission denied: target is outside your task scope "
|
|
94
90
|
f"({state.scope_denial_hint()})."
|
|
95
91
|
)
|
|
96
|
-
if state.skill_guard.is_folder_write_denied(scope_id, include_subtree=True):
|
|
97
|
-
return skill_denial("rmdir", target)
|
|
98
92
|
try:
|
|
99
93
|
if target.startswith("scope_"):
|
|
100
94
|
unique_sdk.Folder.delete(
|
|
@@ -128,14 +122,11 @@ def cmd_mvdir(state: ShellState, old_name: str, new_name: str) -> str:
|
|
|
128
122
|
and not state.is_folder_target_within_workspace(old_name)
|
|
129
123
|
):
|
|
130
124
|
return "mvdir: permission denied (outside workspace scope)"
|
|
131
|
-
|
|
132
|
-
if _metadata_filter_denies_folder(state, scope_id):
|
|
125
|
+
if _metadata_filter_denies_folder(state, _resolve_folder_scope_id(state, old_name)):
|
|
133
126
|
return (
|
|
134
127
|
"mvdir: permission denied: target is outside your task scope "
|
|
135
128
|
f"({state.scope_denial_hint()})."
|
|
136
129
|
)
|
|
137
|
-
if state.skill_guard.is_folder_write_denied(scope_id, include_subtree=True):
|
|
138
|
-
return skill_denial("mvdir", old_name)
|
|
139
130
|
try:
|
|
140
131
|
if old_name.startswith("scope_"):
|
|
141
132
|
result = unique_sdk.Folder.update(
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/navigation.py
RENAMED
|
@@ -7,7 +7,6 @@ from typing import Any
|
|
|
7
7
|
import unique_sdk
|
|
8
8
|
from unique_sdk.cli.formatting import format_ls
|
|
9
9
|
from unique_sdk.cli.metadata_filter import _collect_filter_targets
|
|
10
|
-
from unique_sdk.cli.skill_guard import skill_denial
|
|
11
10
|
from unique_sdk.cli.state import ShellState
|
|
12
11
|
|
|
13
12
|
|
|
@@ -44,8 +43,6 @@ def cmd_ls(state: ShellState, target: str | None = None) -> str:
|
|
|
44
43
|
f"ls: permission denied: target is outside your task scope "
|
|
45
44
|
f"({state.scope_denial_hint()})."
|
|
46
45
|
)
|
|
47
|
-
if scope_id is not None and state.skill_guard.is_folder_hidden(scope_id):
|
|
48
|
-
return skill_denial("ls", target or state.cwd)
|
|
49
46
|
|
|
50
47
|
# At root with a per-message KB scope (e.g. an Agentic Table column's
|
|
51
48
|
# scope_rules), show only the in-scope folders and explicitly-scoped
|
|
@@ -70,11 +67,6 @@ def cmd_ls(state: ShellState, target: str | None = None) -> str:
|
|
|
70
67
|
)
|
|
71
68
|
except unique_sdk.UniqueError:
|
|
72
69
|
pass
|
|
73
|
-
scoped_folders = [
|
|
74
|
-
folder
|
|
75
|
-
for folder in scoped_folders
|
|
76
|
-
if not state.skill_guard.is_folder_hidden(folder.get("id", ""))
|
|
77
|
-
]
|
|
78
70
|
scoped_files: list[Any] = []
|
|
79
71
|
for cid in content_ids:
|
|
80
72
|
# A contentId mentioned in the filter is not necessarily in
|
|
@@ -85,8 +77,6 @@ def cmd_ls(state: ShellState, target: str | None = None) -> str:
|
|
|
85
77
|
# UN-21780.
|
|
86
78
|
if not state.is_content_within_workspace(cid):
|
|
87
79
|
continue
|
|
88
|
-
if state.is_skill_content_read_denied(cid):
|
|
89
|
-
continue
|
|
90
80
|
try:
|
|
91
81
|
info = unique_sdk.Content.get_info(
|
|
92
82
|
user_id=state.config.user_id,
|
|
@@ -119,11 +109,6 @@ def cmd_ls(state: ShellState, target: str | None = None) -> str:
|
|
|
119
109
|
folders.append(info)
|
|
120
110
|
except unique_sdk.UniqueError:
|
|
121
111
|
pass
|
|
122
|
-
folders = [
|
|
123
|
-
folder
|
|
124
|
-
for folder in folders
|
|
125
|
-
if not state.skill_guard.is_folder_hidden(folder.get("id", ""))
|
|
126
|
-
]
|
|
127
112
|
output = format_ls(folders, [])
|
|
128
113
|
summary = f"\n{len(folders)} folder(s), 0 file(s)"
|
|
129
114
|
return output + summary
|
|
@@ -139,8 +124,7 @@ def cmd_ls(state: ShellState, target: str | None = None) -> str:
|
|
|
139
124
|
company_id=state.config.company_id,
|
|
140
125
|
**folder_params,
|
|
141
126
|
)
|
|
142
|
-
|
|
143
|
-
folders = state.skill_guard.visible_child_folders(listed_folders)
|
|
127
|
+
folders = folder_result.get("folderInfos", [])
|
|
144
128
|
|
|
145
129
|
content_result = unique_sdk.Content.get_infos(
|
|
146
130
|
user_id=state.config.user_id,
|
|
@@ -149,9 +133,7 @@ def cmd_ls(state: ShellState, target: str | None = None) -> str:
|
|
|
149
133
|
)
|
|
150
134
|
files = content_result.get("contentInfos", [])
|
|
151
135
|
|
|
152
|
-
total_folders = folder_result.get("totalCount", len(
|
|
153
|
-
len(listed_folders) - len(folders)
|
|
154
|
-
)
|
|
136
|
+
total_folders = folder_result.get("totalCount", len(folders))
|
|
155
137
|
total_files = content_result.get("totalCount", len(files))
|
|
156
138
|
|
|
157
139
|
# With a per-message filter, listing inside an allowed folder must not
|
|
@@ -18,7 +18,6 @@ from __future__ import annotations
|
|
|
18
18
|
from typing import Any
|
|
19
19
|
|
|
20
20
|
import unique_sdk
|
|
21
|
-
from unique_sdk.cli.skill_guard import skill_denial
|
|
22
21
|
from unique_sdk.cli.state import ShellState
|
|
23
22
|
|
|
24
23
|
READ_ERROR_PREFIX = "read:"
|
|
@@ -104,8 +103,6 @@ def cmd_read(
|
|
|
104
103
|
f"task scope ({state.scope_denial_hint()}). Use 'unique-cli search' "
|
|
105
104
|
"or 'ls' within that scope instead."
|
|
106
105
|
)
|
|
107
|
-
if state.is_skill_content_read_denied(cont_id):
|
|
108
|
-
return skill_denial("read", cont_id)
|
|
109
106
|
|
|
110
107
|
try:
|
|
111
108
|
results = unique_sdk.Content.search(
|
|
@@ -280,7 +280,6 @@ def cmd_search(
|
|
|
280
280
|
company_id=state.config.company_id,
|
|
281
281
|
**search_params,
|
|
282
282
|
)
|
|
283
|
-
results = [hit for hit in results if not state.is_skill_search_hit_hidden(hit)]
|
|
284
283
|
|
|
285
284
|
except (ValueError, unique_sdk.UniqueError) as e:
|
|
286
285
|
return f"{SEARCH_ERROR_PREFIX} {e}"
|
|
@@ -288,7 +288,6 @@ verified page numbers (if any) and `--read-method`.
|
|
|
288
288
|
- File-not-found and folder-not-found errors are returned as text, not exceptions.
|
|
289
289
|
- Successful results print to stdout -- parse output as needed.
|
|
290
290
|
- Scope denials (e.g. a file or folder outside the task scope) print to **stderr** and exit with a **non-zero** status, so a denial in an `&&` chain stops the chain instead of being treated as success. Read the stderr message: it names the in-scope folders/documents to redirect you, rather than retrying the same out-of-scope target.
|
|
291
|
-
- Skills in the knowledge base are off limits. A skill is a folder with a `SKILL.md` (or `skills.md`) file, including everything inside it. `search` and `ls` hide them, and `read`, `download`, `upload`, `rm`, `mv`, `mkdir`, `rmdir` and `mvdir` refuse them. Only your personal skills folders (`<home>/skills-conduct/space-<spaceId>` in your own home) are exempt. Never retry a denied command another way.
|
|
292
291
|
|
|
293
292
|
## Interactive Mode
|
|
294
293
|
|
|
@@ -10,7 +10,6 @@ from typing import Any
|
|
|
10
10
|
import unique_sdk
|
|
11
11
|
from unique_sdk.cli.config import Config
|
|
12
12
|
from unique_sdk.cli.metadata_filter import MetadataFilter
|
|
13
|
-
from unique_sdk.cli.skill_guard import SkillGuard, folder_ids_from_metadata
|
|
14
13
|
from unique_sdk.cli.workspace import manifest_path as workspace_manifest_path
|
|
15
14
|
|
|
16
15
|
_SEARCH_CONFIG_FILENAME = ".unique-search.json"
|
|
@@ -181,9 +180,6 @@ class ShellState:
|
|
|
181
180
|
self.mcp_tool_reference_mappings: dict[str, dict[str, Any]] = (
|
|
182
181
|
_load_mcp_tool_configs()
|
|
183
182
|
)
|
|
184
|
-
# Independent of scopeIds and metaDataFilter: a skill folder inside the
|
|
185
|
-
# KB scope is still off limits for writes. See UN-25784.
|
|
186
|
-
self.skill_guard = SkillGuard(config)
|
|
187
183
|
|
|
188
184
|
@property
|
|
189
185
|
def uploaded_search_available(self) -> bool:
|
|
@@ -424,44 +420,6 @@ class ShellState:
|
|
|
424
420
|
self._content_info_cache[content_id] = info
|
|
425
421
|
return info
|
|
426
422
|
|
|
427
|
-
def is_skill_content_write_denied(
|
|
428
|
-
self, content_id: str, *, new_name: str | None = None
|
|
429
|
-
) -> bool:
|
|
430
|
-
"""True when changing *content_id* would change a protected skill.
|
|
431
|
-
|
|
432
|
-
Content owned by a chat instead of a folder is never part of a skill.
|
|
433
|
-
Fails closed when the content can't be looked up.
|
|
434
|
-
"""
|
|
435
|
-
info = self._get_content_info(content_id)
|
|
436
|
-
if not info:
|
|
437
|
-
return True
|
|
438
|
-
owner_id = info.get("ownerId") or ""
|
|
439
|
-
if not owner_id.startswith("scope_"):
|
|
440
|
-
return False
|
|
441
|
-
return self.skill_guard.is_folder_write_denied(owner_id, new_file_name=new_name)
|
|
442
|
-
|
|
443
|
-
def is_skill_content_read_denied(self, content_id: str) -> bool:
|
|
444
|
-
"""True when *content_id* is part of a protected skill.
|
|
445
|
-
|
|
446
|
-
Content owned by a chat instead of a folder is never part of a skill.
|
|
447
|
-
Fails closed when the content can't be looked up.
|
|
448
|
-
"""
|
|
449
|
-
info = self._get_content_info(content_id)
|
|
450
|
-
if not info:
|
|
451
|
-
return True
|
|
452
|
-
owner_id = info.get("ownerId") or ""
|
|
453
|
-
if not owner_id.startswith("scope_"):
|
|
454
|
-
return False
|
|
455
|
-
return self.skill_guard.is_folder_hidden(owner_id)
|
|
456
|
-
|
|
457
|
-
def is_skill_search_hit_hidden(self, hit: Any) -> bool:
|
|
458
|
-
"""True when a search result sits in a protected skill."""
|
|
459
|
-
folder_ids = folder_ids_from_metadata(getattr(hit, "metadata", None))
|
|
460
|
-
if folder_ids:
|
|
461
|
-
return self.skill_guard.is_folder_path_hidden(folder_ids)
|
|
462
|
-
content_id = getattr(hit, "id", "") or ""
|
|
463
|
-
return bool(content_id) and self.is_skill_content_read_denied(content_id)
|
|
464
|
-
|
|
465
423
|
def resolve_content_title(self, content_id: str) -> str:
|
|
466
424
|
"""Human-readable title for a content id, falling back to the id.
|
|
467
425
|
|
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
"""Keep CLI reads and writes out of skill folders (a ``skill.md``/``skills.md`` marker
|
|
2
|
-
at or above the folder) outside the caller's personal skills
|
|
3
|
-
(``<home>/skills-conduct/space-<spaceId>``)."""
|
|
4
|
-
|
|
5
|
-
from __future__ import annotations
|
|
6
|
-
|
|
7
|
-
import re
|
|
8
|
-
from collections.abc import Sequence
|
|
9
|
-
from dataclasses import dataclass
|
|
10
|
-
from enum import StrEnum
|
|
11
|
-
from typing import Any
|
|
12
|
-
|
|
13
|
-
import unique_sdk
|
|
14
|
-
from unique_sdk.cli.config import Config
|
|
15
|
-
|
|
16
|
-
_MARKER_NAME_RE = re.compile(r"^skills?\.md$", re.IGNORECASE)
|
|
17
|
-
|
|
18
|
-
# The SDK JSON-encodes request params without an Enum encoder, so
|
|
19
|
-
# ``mode: insensitive`` can't be sent. Match the common spellings instead and
|
|
20
|
-
# re-check every hit with ``is_skill_marker_name``.
|
|
21
|
-
_MARKER_KEY_SUFFIXES = tuple(
|
|
22
|
-
f"{stem}{extension}"
|
|
23
|
-
for stem in ("skill", "Skill", "SKILL", "skills", "Skills", "SKILLS")
|
|
24
|
-
for extension in (".md", ".MD")
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
_FOLDER_ID_PATH_PREFIX = "uniquepathid://"
|
|
28
|
-
_MAX_FOLDER_DEPTH = 64
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class PersonalSkillsFolder(StrEnum):
|
|
32
|
-
"""Folder names in ``/home/<UserName>/skills-conduct/space-<spaceId>``."""
|
|
33
|
-
|
|
34
|
-
HOME_ROOT = "home"
|
|
35
|
-
LEGACY_HOME_PREFIX = "home-"
|
|
36
|
-
SKILLS = "skills-conduct"
|
|
37
|
-
SPACE_PREFIX = "space-"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def is_skill_marker_name(name: str) -> bool:
|
|
41
|
-
"""True when the last segment of *name* is a skill marker file name."""
|
|
42
|
-
base = name.replace("\\", "/").rsplit("/", 1)[-1].strip()
|
|
43
|
-
return bool(_MARKER_NAME_RE.match(base))
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def skill_denial(command: str, target: str) -> str:
|
|
47
|
-
"""Denial text in the ``<command>: permission denied`` shape the CLI exits on."""
|
|
48
|
-
return (
|
|
49
|
-
f"{command}: permission denied: {target} is part of a skill outside your "
|
|
50
|
-
"personal skills, or its skill status could not be checked."
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
@dataclass(frozen=True)
|
|
55
|
-
class _FolderNode:
|
|
56
|
-
scope_id: str
|
|
57
|
-
name: str
|
|
58
|
-
parent_id: str | None
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
@dataclass(frozen=True)
|
|
62
|
-
class _SkillMarker:
|
|
63
|
-
skill_folder_id: str
|
|
64
|
-
# Empty when the content has no ``folderIdPath``; looked up on demand.
|
|
65
|
-
folder_ids: frozenset[str]
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
class SkillGuard:
|
|
69
|
-
"""Answers whether a read or write would touch a protected skill."""
|
|
70
|
-
|
|
71
|
-
def __init__(self, config: Config) -> None:
|
|
72
|
-
self._config = config
|
|
73
|
-
self._folders: dict[str, _FolderNode] = {}
|
|
74
|
-
self._markers: tuple[_SkillMarker, ...] | None = None
|
|
75
|
-
self._hidden: dict[str, bool] = {}
|
|
76
|
-
|
|
77
|
-
def is_folder_hidden(self, scope_id: str) -> bool:
|
|
78
|
-
"""True when *scope_id* is inside a protected skill. Fails closed."""
|
|
79
|
-
cached = self._hidden.get(scope_id)
|
|
80
|
-
if cached is not None:
|
|
81
|
-
return cached
|
|
82
|
-
try:
|
|
83
|
-
chain = self._folder_chain(scope_id)
|
|
84
|
-
hidden = not self._is_in_own_personal_layer(chain) and self._has_marker_in(
|
|
85
|
-
[node.scope_id for node in chain]
|
|
86
|
-
)
|
|
87
|
-
except unique_sdk.UniqueError:
|
|
88
|
-
hidden = True
|
|
89
|
-
self._hidden[scope_id] = hidden
|
|
90
|
-
return hidden
|
|
91
|
-
|
|
92
|
-
def is_folder_path_hidden(self, folder_ids: Sequence[str]) -> bool:
|
|
93
|
-
"""True when a ``folderIdPath`` runs through a protected skill. Fails closed."""
|
|
94
|
-
try:
|
|
95
|
-
skill_folder_ids = {
|
|
96
|
-
marker.skill_folder_id for marker in self._all_skill_markers()
|
|
97
|
-
}
|
|
98
|
-
except unique_sdk.UniqueError:
|
|
99
|
-
return True
|
|
100
|
-
return any(
|
|
101
|
-
self.is_folder_hidden(folder_id)
|
|
102
|
-
for folder_id in folder_ids
|
|
103
|
-
if folder_id in skill_folder_ids
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
def visible_child_folders(self, folders: Sequence[Any]) -> list[Any]:
|
|
107
|
-
"""Drop protected skills from the child folders of a visible folder."""
|
|
108
|
-
folder_ids = [folder.get("id") for folder in folders if folder.get("id")]
|
|
109
|
-
if not folder_ids:
|
|
110
|
-
return list(folders)
|
|
111
|
-
skill_folder_ids = {
|
|
112
|
-
_marker_owner_id(content)
|
|
113
|
-
for content in self._search_markers({"ownerId": {"in_": folder_ids}})
|
|
114
|
-
if is_skill_marker_name(content.get("key") or "")
|
|
115
|
-
}
|
|
116
|
-
return [
|
|
117
|
-
folder
|
|
118
|
-
for folder in folders
|
|
119
|
-
if folder.get("id") not in skill_folder_ids
|
|
120
|
-
or not self.is_folder_hidden(folder["id"])
|
|
121
|
-
]
|
|
122
|
-
|
|
123
|
-
def is_folder_write_denied(
|
|
124
|
-
self,
|
|
125
|
-
scope_id: str | None,
|
|
126
|
-
*,
|
|
127
|
-
include_subtree: bool = False,
|
|
128
|
-
new_file_name: str | None = None,
|
|
129
|
-
) -> bool:
|
|
130
|
-
"""True when writing in *scope_id* would change a protected skill.
|
|
131
|
-
|
|
132
|
-
``include_subtree`` also protects skills below the folder (``rmdir``,
|
|
133
|
-
``mvdir``). ``new_file_name`` protects against creating a marker, which
|
|
134
|
-
would turn the folder into a skill. Fails closed when the folder is
|
|
135
|
-
unknown or a lookup fails.
|
|
136
|
-
"""
|
|
137
|
-
if not scope_id:
|
|
138
|
-
return True
|
|
139
|
-
try:
|
|
140
|
-
return self._is_folder_write_denied(
|
|
141
|
-
scope_id,
|
|
142
|
-
include_subtree=include_subtree,
|
|
143
|
-
new_file_name=new_file_name,
|
|
144
|
-
)
|
|
145
|
-
except unique_sdk.UniqueError:
|
|
146
|
-
return True
|
|
147
|
-
|
|
148
|
-
def is_path_write_denied(self, folder_path: str) -> bool:
|
|
149
|
-
"""True when creating *folder_path* would add structure inside a protected skill.
|
|
150
|
-
|
|
151
|
-
Checks the deepest existing folder on the path, because the target does
|
|
152
|
-
not exist yet.
|
|
153
|
-
"""
|
|
154
|
-
segments = [segment for segment in folder_path.split("/") if segment]
|
|
155
|
-
while segments:
|
|
156
|
-
candidate = "/" + "/".join(segments)
|
|
157
|
-
try:
|
|
158
|
-
info = unique_sdk.Folder.get_info(
|
|
159
|
-
user_id=self._config.user_id,
|
|
160
|
-
company_id=self._config.company_id,
|
|
161
|
-
folderPath=candidate,
|
|
162
|
-
)
|
|
163
|
-
except unique_sdk.UniqueError:
|
|
164
|
-
segments.pop()
|
|
165
|
-
continue
|
|
166
|
-
scope_id = info.get("id")
|
|
167
|
-
if not scope_id:
|
|
168
|
-
return True
|
|
169
|
-
return self.is_folder_write_denied(scope_id)
|
|
170
|
-
return False
|
|
171
|
-
|
|
172
|
-
def _is_folder_write_denied(
|
|
173
|
-
self,
|
|
174
|
-
scope_id: str,
|
|
175
|
-
*,
|
|
176
|
-
include_subtree: bool,
|
|
177
|
-
new_file_name: str | None,
|
|
178
|
-
) -> bool:
|
|
179
|
-
chain = self._folder_chain(scope_id)
|
|
180
|
-
if self._is_in_own_personal_layer(chain):
|
|
181
|
-
return False
|
|
182
|
-
if new_file_name is not None and is_skill_marker_name(new_file_name):
|
|
183
|
-
return True
|
|
184
|
-
if self._has_marker_in([node.scope_id for node in chain]):
|
|
185
|
-
return True
|
|
186
|
-
if include_subtree:
|
|
187
|
-
return any(
|
|
188
|
-
scope_id in self._marker_folder_ids(marker)
|
|
189
|
-
for marker in self._all_skill_markers()
|
|
190
|
-
)
|
|
191
|
-
return False
|
|
192
|
-
|
|
193
|
-
def _is_in_own_personal_layer(self, chain: list[_FolderNode]) -> bool:
|
|
194
|
-
"""True inside ``<home>/skills-conduct/space-<spaceId>``.
|
|
195
|
-
|
|
196
|
-
The API resolves no other user's home for the caller, so a home in the
|
|
197
|
-
chain is the caller's own. The API re-checks the owner on write.
|
|
198
|
-
"""
|
|
199
|
-
return any(
|
|
200
|
-
node.name.startswith(PersonalSkillsFolder.SPACE_PREFIX)
|
|
201
|
-
and len(chain) > index + 2
|
|
202
|
-
and chain[index + 1].name == PersonalSkillsFolder.SKILLS
|
|
203
|
-
and self._is_own_home(chain[index + 2 :])
|
|
204
|
-
for index, node in enumerate(chain)
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
def _is_own_home(self, chain_from_home: list[_FolderNode]) -> bool:
|
|
208
|
-
"""``/home/<UserName>``, or the legacy root ``/home-<userId>``."""
|
|
209
|
-
home, *above = chain_from_home
|
|
210
|
-
if not above:
|
|
211
|
-
return (
|
|
212
|
-
home.name
|
|
213
|
-
== f"{PersonalSkillsFolder.LEGACY_HOME_PREFIX}{self._config.user_id}"
|
|
214
|
-
)
|
|
215
|
-
return (
|
|
216
|
-
len(above) == 1
|
|
217
|
-
and above[0].name == PersonalSkillsFolder.HOME_ROOT
|
|
218
|
-
and above[0].parent_id is None
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
def _has_marker_in(self, folder_ids: list[str]) -> bool:
|
|
222
|
-
if not folder_ids:
|
|
223
|
-
return False
|
|
224
|
-
results = self._search_markers({"ownerId": {"in_": folder_ids}})
|
|
225
|
-
return any(
|
|
226
|
-
is_skill_marker_name(content.get("key") or "") for content in results
|
|
227
|
-
)
|
|
228
|
-
|
|
229
|
-
def _marker_folder_ids(self, marker: _SkillMarker) -> frozenset[str]:
|
|
230
|
-
if marker.folder_ids:
|
|
231
|
-
return marker.folder_ids
|
|
232
|
-
return frozenset(
|
|
233
|
-
node.scope_id for node in self._folder_chain(marker.skill_folder_id)
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
def _folder_chain(self, scope_id: str) -> list[_FolderNode]:
|
|
237
|
-
"""The folder and its ancestors, nearest first."""
|
|
238
|
-
chain: list[_FolderNode] = []
|
|
239
|
-
current: str | None = scope_id
|
|
240
|
-
while current and len(chain) < _MAX_FOLDER_DEPTH:
|
|
241
|
-
node = self._folder(current)
|
|
242
|
-
chain.append(node)
|
|
243
|
-
current = node.parent_id
|
|
244
|
-
return chain
|
|
245
|
-
|
|
246
|
-
def _folder(self, scope_id: str) -> _FolderNode:
|
|
247
|
-
cached = self._folders.get(scope_id)
|
|
248
|
-
if cached is not None:
|
|
249
|
-
return cached
|
|
250
|
-
info = unique_sdk.Folder.get_info(
|
|
251
|
-
user_id=self._config.user_id,
|
|
252
|
-
company_id=self._config.company_id,
|
|
253
|
-
scopeId=scope_id,
|
|
254
|
-
)
|
|
255
|
-
node = _FolderNode(
|
|
256
|
-
scope_id=info["id"],
|
|
257
|
-
name=info.get("name") or "",
|
|
258
|
-
parent_id=info.get("parentId"),
|
|
259
|
-
)
|
|
260
|
-
self._folders[scope_id] = node
|
|
261
|
-
return node
|
|
262
|
-
|
|
263
|
-
def _search_markers(
|
|
264
|
-
self, where: unique_sdk.Content.ContentWhereInput | None = None
|
|
265
|
-
) -> list[unique_sdk.Content]:
|
|
266
|
-
key_filter: unique_sdk.Content.ContentWhereInput = {
|
|
267
|
-
"OR": [{"key": {"endsWith": suffix}} for suffix in _MARKER_KEY_SUFFIXES]
|
|
268
|
-
}
|
|
269
|
-
clauses = [key_filter] if where is None else [key_filter, where]
|
|
270
|
-
return unique_sdk.Content.search(
|
|
271
|
-
user_id=self._config.user_id,
|
|
272
|
-
company_id=self._config.company_id,
|
|
273
|
-
where={"AND": clauses},
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
def _all_skill_markers(self) -> tuple[_SkillMarker, ...]:
|
|
277
|
-
"""Every marker the caller can read. Only subtree checks need this."""
|
|
278
|
-
if self._markers is not None:
|
|
279
|
-
return self._markers
|
|
280
|
-
markers: list[_SkillMarker] = []
|
|
281
|
-
for content in self._search_markers():
|
|
282
|
-
if not is_skill_marker_name(content.get("key") or ""):
|
|
283
|
-
continue
|
|
284
|
-
path_ids = folder_ids_from_metadata(content.get("metadata"))
|
|
285
|
-
owner_id = _marker_owner_id(content)
|
|
286
|
-
if not owner_id.startswith("scope_"):
|
|
287
|
-
continue
|
|
288
|
-
markers.append(
|
|
289
|
-
_SkillMarker(
|
|
290
|
-
skill_folder_id=owner_id,
|
|
291
|
-
folder_ids=frozenset({owner_id, *path_ids})
|
|
292
|
-
if path_ids
|
|
293
|
-
else frozenset(),
|
|
294
|
-
)
|
|
295
|
-
)
|
|
296
|
-
self._markers = tuple(markers)
|
|
297
|
-
return self._markers
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
def _marker_owner_id(content: unique_sdk.Content) -> str:
|
|
301
|
-
"""The marker's folder: ``ownerId``, else the last ``folderIdPath`` segment."""
|
|
302
|
-
path_ids = folder_ids_from_metadata(content.get("metadata"))
|
|
303
|
-
return content.get("ownerId") or (path_ids[-1] if path_ids else "")
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def folder_ids_from_metadata(metadata: object) -> list[str]:
|
|
307
|
-
"""Folder ids in a content's ``folderIdPath``, or ``[]`` when absent."""
|
|
308
|
-
if not isinstance(metadata, dict):
|
|
309
|
-
return []
|
|
310
|
-
folder_id_path = metadata.get("folderIdPath")
|
|
311
|
-
if not isinstance(folder_id_path, str):
|
|
312
|
-
return []
|
|
313
|
-
path = folder_id_path.removeprefix(_FOLDER_ID_PATH_PREFIX)
|
|
314
|
-
return [segment for segment in path.split("/") if segment.startswith("scope_")]
|
|
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.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/__init__.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_acronyms.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_agentic_table.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_analytics_order.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_benchmarking.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_briefing.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_chat_completion.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_context_memory.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_elicitation.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_embedding.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_integrated.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_llm_models.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_message_log.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_message_tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_scheduled_task.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_search_string.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/api_resources/_web_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/agentic_table.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/cite_file.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/elicitation.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/scheduled_tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/web_search.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/cli/commands/web_search_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/unique_sdk/utils/analytics_order_run.py
RENAMED
|
File without changes
|
{unique_sdk-2026.42.0.dev4 → unique_sdk-2026.42.0.dev5}/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
|