unique-sdk 2026.44.0.dev2__tar.gz → 2026.44.0.dev4__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.44.0.dev2 → unique_sdk-2026.44.0.dev4}/PKG-INFO +1 -1
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/pyproject.toml +1 -1
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/cli.py +19 -7
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/files.py +29 -2
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/folders.py +64 -5
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/mcp.py +76 -7
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/navigation.py +90 -6
- unique_sdk-2026.44.0.dev4/unique_sdk/cli/paths.py +42 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/shell.py +7 -3
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/state.py +16 -5
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/README.md +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/__init__.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_api_requestor.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_api_resource.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_api_version.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_error.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_http_client.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_list_object.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_object_classes.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_request_options.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_unique_object.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_unique_ql.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_unique_response.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_util.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_version.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/_webhook.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/__init__.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_acronyms.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_agentic_table.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_analytics_order.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_benchmarking.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_briefing.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_chat_completion.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_content.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_context_memory.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_elicitation.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_embedding.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_event.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_folder.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_group.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_integrated.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_llm_models.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_mcp.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_message.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_message_assessment.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_message_execution.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_message_log.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_message_tool.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_module.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_scheduled_task.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_search.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_search_string.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_short_term_memory.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_space.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_user.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_web_search.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/__init__.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/__main__.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/__init__.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/agentic_table.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/agentic_table_write.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/browser.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/cite_file.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/elicitation.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/read.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/search.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/subagent.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/web_search.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/web_search_config.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/config.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/formatting.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/identity.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/metadata_filter.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/skills/unique-cli-agentic-table/SKILL.md +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/skills/unique-cli-uploaded-search/SKILL.md +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/workspace.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/utils/analytics_order_run.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/utils/benchmarking_run.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/utils/chat_history.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/utils/chat_in_space.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/utils/file_io.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/utils/sources.py +0 -0
- {unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/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.44.0.
|
|
3
|
+
Version: 2026.44.0.dev4
|
|
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>
|
|
@@ -58,12 +58,21 @@ from unique_sdk.cli.commands.files import (
|
|
|
58
58
|
from unique_sdk.cli.commands.files import (
|
|
59
59
|
is_permission_denied_output as _is_permission_denied_output,
|
|
60
60
|
)
|
|
61
|
+
from unique_sdk.cli.commands.files import (
|
|
62
|
+
is_upload_error_output as _is_upload_error_output,
|
|
63
|
+
)
|
|
61
64
|
from unique_sdk.cli.commands.folders import cmd_mkdir, cmd_mvdir, cmd_rmdir
|
|
62
65
|
from unique_sdk.cli.commands.folders import (
|
|
63
66
|
is_mkdir_error_output as _is_mkdir_error_output,
|
|
64
67
|
)
|
|
65
68
|
from unique_sdk.cli.commands.mcp import cmd_mcp
|
|
69
|
+
from unique_sdk.cli.commands.mcp import (
|
|
70
|
+
is_error_output as _is_mcp_error_output,
|
|
71
|
+
)
|
|
66
72
|
from unique_sdk.cli.commands.navigation import cmd_cd, cmd_ls, cmd_pwd
|
|
73
|
+
from unique_sdk.cli.commands.navigation import (
|
|
74
|
+
is_ls_error_output as _is_ls_error_output,
|
|
75
|
+
)
|
|
67
76
|
from unique_sdk.cli.commands.read import cmd_read
|
|
68
77
|
from unique_sdk.cli.commands.read import (
|
|
69
78
|
is_error_output as _is_read_error_output,
|
|
@@ -319,23 +328,25 @@ def ls(ctx: click.Context, target: str | None) -> None:
|
|
|
319
328
|
unique-cli ls scope_abc List by scope ID
|
|
320
329
|
"""
|
|
321
330
|
output = cmd_ls(LazyState.get(ctx), target)
|
|
322
|
-
emit(output)
|
|
331
|
+
emit(output, is_error=_is_ls_error_output)
|
|
323
332
|
|
|
324
333
|
|
|
325
334
|
@main.command()
|
|
326
335
|
@click.argument("name")
|
|
327
336
|
@click.pass_context
|
|
328
337
|
def mkdir(ctx: click.Context, name: str) -> None:
|
|
329
|
-
"""Create a
|
|
338
|
+
"""Create a folder by relative name or absolute path.
|
|
330
339
|
|
|
331
340
|
\b
|
|
332
|
-
NAME is
|
|
333
|
-
(
|
|
341
|
+
A relative NAME is created under the current directory. An absolute
|
|
342
|
+
path (starting with /) is created from the root, including under
|
|
343
|
+
/home-<userId>. Nested paths create the full hierarchy.
|
|
334
344
|
|
|
335
345
|
\b
|
|
336
346
|
Examples:
|
|
337
347
|
unique-cli mkdir Q2
|
|
338
348
|
unique-cli mkdir "2025/Q1"
|
|
349
|
+
unique-cli mkdir /home-user_1/second-brain/clients/X
|
|
339
350
|
"""
|
|
340
351
|
output = cmd_mkdir(LazyState.get(ctx), name)
|
|
341
352
|
emit(output, is_error=_is_mkdir_error_output)
|
|
@@ -418,7 +429,7 @@ def upload(ctx: click.Context, local_path: str, destination: str | None) -> None
|
|
|
418
429
|
unique-cli upload ./report.pdf /Archive/2025/
|
|
419
430
|
"""
|
|
420
431
|
output = cmd_upload(LazyState.get(ctx), local_path, destination)
|
|
421
|
-
emit(output)
|
|
432
|
+
emit(output, is_error=_is_upload_error_output)
|
|
422
433
|
|
|
423
434
|
|
|
424
435
|
@main.command()
|
|
@@ -844,7 +855,7 @@ def mcp(
|
|
|
844
855
|
"""
|
|
845
856
|
resolved_message_id = _resolve_cli_message_id(ctx, message_id, required=True)
|
|
846
857
|
resolved_chat_id = _resolve_cli_chat_id(ctx, chat_id, required=True)
|
|
847
|
-
|
|
858
|
+
emit(
|
|
848
859
|
cmd_mcp(
|
|
849
860
|
LazyState.get(ctx),
|
|
850
861
|
chat_id=resolved_chat_id or "",
|
|
@@ -852,7 +863,8 @@ def mcp(
|
|
|
852
863
|
payload=payload,
|
|
853
864
|
file=file_path,
|
|
854
865
|
stdin=use_stdin,
|
|
855
|
-
)
|
|
866
|
+
),
|
|
867
|
+
is_error=_is_mcp_error_output,
|
|
856
868
|
)
|
|
857
869
|
|
|
858
870
|
|
|
@@ -11,6 +11,7 @@ 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.paths import canonical_folder_path, same_folder_path
|
|
14
15
|
from unique_sdk.cli.state import ShellState
|
|
15
16
|
from unique_sdk.utils.file_io import download_content, upload_file
|
|
16
17
|
|
|
@@ -280,6 +281,18 @@ def _resolve_upload_destination(
|
|
|
280
281
|
resolved_id = info.get("id")
|
|
281
282
|
if not resolved_id:
|
|
282
283
|
raise ValueError(f"folder not found: {folder_path}")
|
|
284
|
+
try:
|
|
285
|
+
actual = canonical_folder_path(
|
|
286
|
+
state.config.user_id,
|
|
287
|
+
state.config.company_id,
|
|
288
|
+
resolved_id,
|
|
289
|
+
)
|
|
290
|
+
except (ValueError, unique_sdk.UniqueError) as e:
|
|
291
|
+
raise ValueError(f"refused to upload to {folder_path}: {e}") from e
|
|
292
|
+
if not same_folder_path(actual, folder_path):
|
|
293
|
+
raise ValueError(
|
|
294
|
+
f"refused to upload to {folder_path}: resolved to {actual}"
|
|
295
|
+
)
|
|
283
296
|
return resolved_id, display_name
|
|
284
297
|
|
|
285
298
|
scope_id = state.scope_id
|
|
@@ -350,7 +363,12 @@ def cmd_upload(
|
|
|
350
363
|
versioning_enabled=True,
|
|
351
364
|
)
|
|
352
365
|
|
|
353
|
-
content_id =
|
|
366
|
+
content_id = getattr(result, "id", None)
|
|
367
|
+
if not content_id:
|
|
368
|
+
return (
|
|
369
|
+
f"upload: refused to upload {display_name} to {scope_id}: "
|
|
370
|
+
"no content id returned"
|
|
371
|
+
)
|
|
354
372
|
folder_path = unique_sdk.Folder.get_folder_path(
|
|
355
373
|
user_id=state.config.user_id,
|
|
356
374
|
company_id=state.config.company_id,
|
|
@@ -358,7 +376,7 @@ def cmd_upload(
|
|
|
358
376
|
).get("folderPath", scope_id)
|
|
359
377
|
return f"Uploaded: {display_name} ({content_id}) to {folder_path}"
|
|
360
378
|
|
|
361
|
-
except (ValueError, unique_sdk.UniqueError, OSError) as e:
|
|
379
|
+
except (ValueError, unique_sdk.UniqueError, OSError, RuntimeError) as e:
|
|
362
380
|
return f"upload: {e}"
|
|
363
381
|
|
|
364
382
|
|
|
@@ -492,6 +510,15 @@ def cmd_mv_file(state: ShellState, old_name: str, new_name: str) -> str:
|
|
|
492
510
|
return f"mv: {e}"
|
|
493
511
|
|
|
494
512
|
|
|
513
|
+
def is_upload_error_output(output: str) -> bool:
|
|
514
|
+
"""Return True when *output* is a ``cmd_upload`` error.
|
|
515
|
+
|
|
516
|
+
Success text is ``Uploaded: ...``. Local misses, missing destinations,
|
|
517
|
+
and API failures all start with ``upload:`` and must exit non-zero.
|
|
518
|
+
"""
|
|
519
|
+
return output.startswith("upload:")
|
|
520
|
+
|
|
521
|
+
|
|
495
522
|
def is_permission_denied_output(output: str) -> bool:
|
|
496
523
|
"""Return ``True`` when a file-op result is a permission/scope denial.
|
|
497
524
|
|
|
@@ -6,6 +6,7 @@ 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.paths import canonical_folder_path, same_folder_path
|
|
9
10
|
from unique_sdk.cli.state import ShellState
|
|
10
11
|
|
|
11
12
|
|
|
@@ -40,8 +41,60 @@ def _metadata_filter_denies_folder(state: ShellState, scope_id: str | None) -> b
|
|
|
40
41
|
return not state.folder_allowed_by_metadata_filter(scope_id)
|
|
41
42
|
|
|
42
43
|
|
|
44
|
+
def _existing_folder_id(state: ShellState, folder_path: str) -> str | None:
|
|
45
|
+
"""Return the scope id at *folder_path* when that exact folder exists."""
|
|
46
|
+
try:
|
|
47
|
+
info = unique_sdk.Folder.get_info(
|
|
48
|
+
user_id=state.config.user_id,
|
|
49
|
+
company_id=state.config.company_id,
|
|
50
|
+
folderPath=folder_path,
|
|
51
|
+
)
|
|
52
|
+
except (ValueError, unique_sdk.UniqueError):
|
|
53
|
+
return None
|
|
54
|
+
folder_id = info.get("id")
|
|
55
|
+
if not folder_id:
|
|
56
|
+
return None
|
|
57
|
+
try:
|
|
58
|
+
actual = canonical_folder_path(
|
|
59
|
+
state.config.user_id,
|
|
60
|
+
state.config.company_id,
|
|
61
|
+
folder_id,
|
|
62
|
+
)
|
|
63
|
+
except (ValueError, unique_sdk.UniqueError):
|
|
64
|
+
return None
|
|
65
|
+
if not same_folder_path(actual, folder_path):
|
|
66
|
+
return None
|
|
67
|
+
return folder_id
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _created_folder_error(
|
|
71
|
+
state: ShellState,
|
|
72
|
+
scope_id: str | None,
|
|
73
|
+
folder_path: str,
|
|
74
|
+
) -> str | None:
|
|
75
|
+
"""Return an ``mkdir:`` error when *scope_id* is not exactly *folder_path*.
|
|
76
|
+
|
|
77
|
+
``create_paths`` can return the ``/home-<userId>`` id in place of the leaf.
|
|
78
|
+
A failed lookup is a refusal: printing ``Created:`` would claim the folder
|
|
79
|
+
exists.
|
|
80
|
+
"""
|
|
81
|
+
if not scope_id:
|
|
82
|
+
return f"mkdir: refused to create {folder_path}"
|
|
83
|
+
try:
|
|
84
|
+
actual = canonical_folder_path(
|
|
85
|
+
state.config.user_id,
|
|
86
|
+
state.config.company_id,
|
|
87
|
+
scope_id,
|
|
88
|
+
)
|
|
89
|
+
except (ValueError, unique_sdk.UniqueError) as e:
|
|
90
|
+
return f"mkdir: refused to create {folder_path}: {e}"
|
|
91
|
+
if not same_folder_path(actual, folder_path):
|
|
92
|
+
return f"mkdir: refused to create {folder_path}: resolved to {actual}"
|
|
93
|
+
return None
|
|
94
|
+
|
|
95
|
+
|
|
43
96
|
def cmd_mkdir(state: ShellState, name: str) -> str:
|
|
44
|
-
"""Create a
|
|
97
|
+
"""Create a folder by relative name or absolute path."""
|
|
45
98
|
# A per-message metaDataFilter *replaces* the static scopeIds, so the
|
|
46
99
|
# static-scope check must not over-deny an in-filter destination; the
|
|
47
100
|
# normalized path is gated against the filter below. See UN-21780.
|
|
@@ -49,7 +102,7 @@ def cmd_mkdir(state: ShellState, name: str) -> str:
|
|
|
49
102
|
state.workspace_metadata_filter is None
|
|
50
103
|
and not state.is_folder_target_within_workspace(name)
|
|
51
104
|
):
|
|
52
|
-
return "mkdir: permission denied (outside workspace scope)"
|
|
105
|
+
return f"mkdir: permission denied: refused {name} (outside workspace scope)"
|
|
53
106
|
# Resolve the destination (collapsing any `..`) and gate the *path*, not
|
|
54
107
|
# just the current scope id: `mkdir ../../Other/X` would otherwise pass a
|
|
55
108
|
# cwd-only check and create structure outside the per-message task scope.
|
|
@@ -68,9 +121,15 @@ def cmd_mkdir(state: ShellState, name: str) -> str:
|
|
|
68
121
|
)
|
|
69
122
|
created = result.get("createdFolders", [])
|
|
70
123
|
if created:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
124
|
+
last_id = created[-1].get("id")
|
|
125
|
+
mismatch = _created_folder_error(state, last_id, full_path)
|
|
126
|
+
if mismatch:
|
|
127
|
+
return mismatch
|
|
128
|
+
return f"Created: {full_path} ({last_id})"
|
|
129
|
+
existing_id = _existing_folder_id(state, full_path)
|
|
130
|
+
if existing_id:
|
|
131
|
+
return f"Created: {full_path} ({existing_id})"
|
|
132
|
+
return f"mkdir: refused to create {full_path}"
|
|
74
133
|
except (ValueError, unique_sdk.UniqueError) as e:
|
|
75
134
|
return f"mkdir: {e}"
|
|
76
135
|
|
|
@@ -26,6 +26,9 @@ from unique_sdk.cli.workspace import unique_dir as workspace_unique_dir
|
|
|
26
26
|
|
|
27
27
|
_LOGGER = logging.getLogger(__name__)
|
|
28
28
|
|
|
29
|
+
MCP_ERROR_PREFIX = "mcp:"
|
|
30
|
+
_FORMATTER_ERROR_PREFIX = "mcp: formatter error"
|
|
31
|
+
|
|
29
32
|
# Per-turn manifest of MCP tool output text, consumed by the Swappable
|
|
30
33
|
# Intelligence runner to ground the hallucination check against MCP-retrieved
|
|
31
34
|
# information (UN-21951). Carries *text only* — no source numbers/markers
|
|
@@ -1058,6 +1061,58 @@ def _parse_and_validate(raw: str) -> tuple[str, dict[str, Any]]:
|
|
|
1058
1061
|
return name, arguments
|
|
1059
1062
|
|
|
1060
1063
|
|
|
1064
|
+
class McpCommandResult(str):
|
|
1065
|
+
"""Text returned by ``cmd_mcp``, with whether that call failed.
|
|
1066
|
+
|
|
1067
|
+
Subclasses ``str`` so callers can print and search the text. ``failed``
|
|
1068
|
+
is set when the process should exit 1: a payload error, any
|
|
1069
|
+
``UniqueError``, or a tool result with ``isError`` set.
|
|
1070
|
+
"""
|
|
1071
|
+
|
|
1072
|
+
failed: bool
|
|
1073
|
+
|
|
1074
|
+
def __new__(cls, text: str, *, failed: bool) -> McpCommandResult:
|
|
1075
|
+
result = str.__new__(cls, text)
|
|
1076
|
+
result.failed = failed
|
|
1077
|
+
return result
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
def _response_is_error(response: Any) -> bool:
|
|
1081
|
+
"""Return whether the tool result itself is marked ``isError``.
|
|
1082
|
+
|
|
1083
|
+
Args:
|
|
1084
|
+
response (Any): Raw MCP tool response.
|
|
1085
|
+
|
|
1086
|
+
Returns:
|
|
1087
|
+
bool: True when ``isError`` is set. False when it is absent, false,
|
|
1088
|
+
or unreadable, so a formatter failure on a successful result still
|
|
1089
|
+
exits 0.
|
|
1090
|
+
"""
|
|
1091
|
+
try:
|
|
1092
|
+
return bool(getattr(response, "isError", False))
|
|
1093
|
+
except Exception:
|
|
1094
|
+
return False
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
def is_error_output(output: str) -> bool:
|
|
1098
|
+
"""Return whether an MCP command result should exit non-zero.
|
|
1099
|
+
|
|
1100
|
+
Args:
|
|
1101
|
+
output (str): Text returned by ``cmd_mcp``.
|
|
1102
|
+
|
|
1103
|
+
Returns:
|
|
1104
|
+
bool: True for payload errors, SDK errors, and ``isError`` tool
|
|
1105
|
+
results. False when the call succeeded, including when formatting
|
|
1106
|
+
that successful result failed and the fallback text was returned.
|
|
1107
|
+
"""
|
|
1108
|
+
failed = getattr(output, "failed", None)
|
|
1109
|
+
if isinstance(failed, bool):
|
|
1110
|
+
return failed
|
|
1111
|
+
if output.startswith(_FORMATTER_ERROR_PREFIX):
|
|
1112
|
+
return False
|
|
1113
|
+
return output.startswith(MCP_ERROR_PREFIX)
|
|
1114
|
+
|
|
1115
|
+
|
|
1061
1116
|
def cmd_mcp(
|
|
1062
1117
|
state: ShellState,
|
|
1063
1118
|
chat_id: str,
|
|
@@ -1065,8 +1120,21 @@ def cmd_mcp(
|
|
|
1065
1120
|
payload: str | None = None,
|
|
1066
1121
|
file: str | None = None,
|
|
1067
1122
|
stdin: bool = False,
|
|
1068
|
-
) ->
|
|
1069
|
-
"""Call an MCP tool with a JSON payload containing name and arguments.
|
|
1123
|
+
) -> McpCommandResult:
|
|
1124
|
+
"""Call an MCP tool with a JSON payload containing name and arguments.
|
|
1125
|
+
|
|
1126
|
+
Args:
|
|
1127
|
+
state (ShellState): CLI session holding the user and company ids.
|
|
1128
|
+
chat_id (str): Chat the tool call is attributed to.
|
|
1129
|
+
message_id (str): Message the tool call is attributed to.
|
|
1130
|
+
payload (str | None): Inline JSON payload.
|
|
1131
|
+
file (str | None): Path to a JSON payload file.
|
|
1132
|
+
stdin (bool): Read the JSON payload from stdin.
|
|
1133
|
+
|
|
1134
|
+
Returns:
|
|
1135
|
+
McpCommandResult: Formatted tool output, or an ``mcp:`` error line.
|
|
1136
|
+
``failed`` is true when the call itself failed.
|
|
1137
|
+
"""
|
|
1070
1138
|
try:
|
|
1071
1139
|
raw = _read_payload(payload, file, stdin)
|
|
1072
1140
|
name, arguments = _parse_and_validate(raw)
|
|
@@ -1080,8 +1148,8 @@ def cmd_mcp(
|
|
|
1080
1148
|
arguments=arguments,
|
|
1081
1149
|
)
|
|
1082
1150
|
|
|
1083
|
-
except (ValueError, OSError, unique_sdk.
|
|
1084
|
-
return f"
|
|
1151
|
+
except (ValueError, OSError, unique_sdk.UniqueError) as e:
|
|
1152
|
+
return McpCommandResult(f"{MCP_ERROR_PREFIX} {e}", failed=True)
|
|
1085
1153
|
|
|
1086
1154
|
try:
|
|
1087
1155
|
formatted = format_mcp_response(response, tool_name=name)
|
|
@@ -1090,7 +1158,8 @@ def cmd_mcp(
|
|
|
1090
1158
|
fallback = json.dumps(dict(response), indent=2, default=str)
|
|
1091
1159
|
except Exception:
|
|
1092
1160
|
fallback = repr(response)
|
|
1093
|
-
formatted = f"
|
|
1161
|
+
formatted = f"{_FORMATTER_ERROR_PREFIX} ({fmt_exc}); raw response:\n{fallback}"
|
|
1162
|
+
call_failed = _response_is_error(response)
|
|
1094
1163
|
|
|
1095
1164
|
server_name = _server_name_from_tool(name) or getattr(response, "mcpServerId", None)
|
|
1096
1165
|
sources_block = record_mcp_citations(
|
|
@@ -1104,5 +1173,5 @@ def cmd_mcp(
|
|
|
1104
1173
|
# Sources block FIRST: large outputs get spilled/truncated tail-first by
|
|
1105
1174
|
# the agent harness, and a trailing block is what partial reads miss.
|
|
1106
1175
|
if sources_block:
|
|
1107
|
-
|
|
1108
|
-
return formatted
|
|
1176
|
+
formatted = sources_block + "\n\n" + formatted
|
|
1177
|
+
return McpCommandResult(formatted, failed=call_failed)
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/navigation.py
RENAMED
|
@@ -7,6 +7,7 @@ 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.paths import canonical_folder_path, same_folder_path
|
|
10
11
|
from unique_sdk.cli.state import ShellState
|
|
11
12
|
|
|
12
13
|
|
|
@@ -23,13 +24,73 @@ def cmd_cd(state: ShellState, target: str) -> str:
|
|
|
23
24
|
return f"cd: {e}"
|
|
24
25
|
|
|
25
26
|
|
|
27
|
+
def _canonical_listing_error(
|
|
28
|
+
state: ShellState,
|
|
29
|
+
label: str,
|
|
30
|
+
claimed_path: str,
|
|
31
|
+
scope_id: str,
|
|
32
|
+
) -> str | None:
|
|
33
|
+
"""Return an ``ls:`` error when *scope_id* is not exactly *claimed_path*.
|
|
34
|
+
|
|
35
|
+
``get_info`` can hand back an ancestor for ``/home-<userId>/...``. The
|
|
36
|
+
comparison runs before any rows are trusted, including when that ancestor
|
|
37
|
+
has children. A failed lookup is an error: printing ``(empty)`` would hide
|
|
38
|
+
it.
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
actual = canonical_folder_path(
|
|
42
|
+
state.config.user_id,
|
|
43
|
+
state.config.company_id,
|
|
44
|
+
scope_id,
|
|
45
|
+
)
|
|
46
|
+
except (ValueError, unique_sdk.UniqueError) as e:
|
|
47
|
+
return f"ls: cannot list {label}: {e}"
|
|
48
|
+
if not same_folder_path(actual, claimed_path):
|
|
49
|
+
return (
|
|
50
|
+
f"ls: cannot list {label}: resolved to {actual} instead (not listable here)"
|
|
51
|
+
)
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _counts_without_rows_error(
|
|
56
|
+
label: str,
|
|
57
|
+
total_folders: int,
|
|
58
|
+
total_files: int,
|
|
59
|
+
) -> str | None:
|
|
60
|
+
"""Return an ``ls:`` error when the API reports rows it did not return."""
|
|
61
|
+
if total_folders or total_files:
|
|
62
|
+
return (
|
|
63
|
+
f"ls: cannot list {label}: listing returned no entries "
|
|
64
|
+
f"({total_folders} folder(s), {total_files} file(s) reported)"
|
|
65
|
+
)
|
|
66
|
+
return None
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _with_unlistable_marker(output: str, unlistable: list[str]) -> str:
|
|
70
|
+
"""Append folders that could not be listed, or fail when none could."""
|
|
71
|
+
if not unlistable:
|
|
72
|
+
return output
|
|
73
|
+
if output.startswith("(empty)"):
|
|
74
|
+
return "ls: cannot list " + ", ".join(unlistable) + " (not listable)"
|
|
75
|
+
return output + "\nnot listable: " + ", ".join(unlistable)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def is_ls_error_output(output: str) -> bool:
|
|
79
|
+
"""Return True when *output* is a ``cmd_ls`` error.
|
|
80
|
+
|
|
81
|
+
Success listings start with a table, ``(empty)``, or a count. Failures
|
|
82
|
+
start with ``ls:`` and must exit non-zero from the one-shot CLI.
|
|
83
|
+
"""
|
|
84
|
+
return output.startswith("ls:")
|
|
85
|
+
|
|
86
|
+
|
|
26
87
|
def cmd_ls(state: ShellState, target: str | None = None) -> str:
|
|
27
88
|
"""List folders and files at the given (or current) path."""
|
|
28
89
|
try:
|
|
29
90
|
if target is not None:
|
|
30
|
-
|
|
91
|
+
resolved_path, scope_id = state.resolve_path(target)
|
|
31
92
|
else:
|
|
32
|
-
scope_id = state.scope_id
|
|
93
|
+
resolved_path, scope_id = state.cwd, state.scope_id
|
|
33
94
|
|
|
34
95
|
# A non-root target must lie inside the per-message scope: without
|
|
35
96
|
# this, `ls <path>` would enumerate out-of-scope folders/files that
|
|
@@ -56,6 +117,7 @@ def cmd_ls(state: ShellState, target: str | None = None) -> str:
|
|
|
56
117
|
# UN-21780.
|
|
57
118
|
folder_ids = state.navigable_folder_ids()
|
|
58
119
|
scoped_folders: list[Any] = []
|
|
120
|
+
unlistable_folders: list[str] = []
|
|
59
121
|
for sid in folder_ids:
|
|
60
122
|
try:
|
|
61
123
|
scoped_folders.append(
|
|
@@ -66,7 +128,7 @@ def cmd_ls(state: ShellState, target: str | None = None) -> str:
|
|
|
66
128
|
)
|
|
67
129
|
)
|
|
68
130
|
except unique_sdk.UniqueError:
|
|
69
|
-
|
|
131
|
+
unlistable_folders.append(sid)
|
|
70
132
|
scoped_files: list[Any] = []
|
|
71
133
|
for cid in content_ids:
|
|
72
134
|
# A contentId mentioned in the filter is not necessarily in
|
|
@@ -93,12 +155,13 @@ def cmd_ls(state: ShellState, target: str | None = None) -> str:
|
|
|
93
155
|
f"\n{len(scoped_folders)} folder(s), {len(scoped_files)} "
|
|
94
156
|
"file(s) in task scope"
|
|
95
157
|
)
|
|
96
|
-
return output + summary
|
|
158
|
+
return _with_unlistable_marker(output + summary, unlistable_folders)
|
|
97
159
|
|
|
98
160
|
# When at root with a workspace restriction, show only the allowed scope
|
|
99
161
|
# folders — the agent must not see the full company folder tree.
|
|
100
162
|
if scope_id is None and state.workspace_scope_ids:
|
|
101
163
|
folders: list[Any] = []
|
|
164
|
+
unlistable_folders = []
|
|
102
165
|
for ws_id in state.workspace_scope_ids:
|
|
103
166
|
try:
|
|
104
167
|
info = unique_sdk.Folder.get_info(
|
|
@@ -108,10 +171,21 @@ def cmd_ls(state: ShellState, target: str | None = None) -> str:
|
|
|
108
171
|
)
|
|
109
172
|
folders.append(info)
|
|
110
173
|
except unique_sdk.UniqueError:
|
|
111
|
-
|
|
174
|
+
unlistable_folders.append(ws_id)
|
|
112
175
|
output = format_ls(folders, [])
|
|
113
176
|
summary = f"\n{len(folders)} folder(s), 0 file(s)"
|
|
114
|
-
return output + summary
|
|
177
|
+
return _with_unlistable_marker(output + summary, unlistable_folders)
|
|
178
|
+
|
|
179
|
+
if scope_id:
|
|
180
|
+
label = target if target is not None else resolved_path
|
|
181
|
+
path_error = _canonical_listing_error(
|
|
182
|
+
state,
|
|
183
|
+
label,
|
|
184
|
+
resolved_path,
|
|
185
|
+
scope_id,
|
|
186
|
+
)
|
|
187
|
+
if path_error:
|
|
188
|
+
return path_error
|
|
115
189
|
|
|
116
190
|
folder_params: dict[str, Any] = {}
|
|
117
191
|
content_params: dict[str, Any] = {}
|
|
@@ -136,6 +210,16 @@ def cmd_ls(state: ShellState, target: str | None = None) -> str:
|
|
|
136
210
|
total_folders = folder_result.get("totalCount", len(folders))
|
|
137
211
|
total_files = content_result.get("totalCount", len(files))
|
|
138
212
|
|
|
213
|
+
if scope_id and not folders and not files:
|
|
214
|
+
label = target if target is not None else resolved_path
|
|
215
|
+
counts_error = _counts_without_rows_error(
|
|
216
|
+
label,
|
|
217
|
+
total_folders,
|
|
218
|
+
total_files,
|
|
219
|
+
)
|
|
220
|
+
if counts_error:
|
|
221
|
+
return counts_error
|
|
222
|
+
|
|
139
223
|
# With a per-message filter, listing inside an allowed folder must not
|
|
140
224
|
# reveal files the filter excludes (e.g. a combined folder + contentId
|
|
141
225
|
# allowlist): read/cite would deny them, so ls must hide them too.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Folder-path comparisons shared by the CLI commands."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
import unique_sdk
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def same_folder_path(left: str, right: str) -> bool:
|
|
11
|
+
"""True when two folder paths name the same location.
|
|
12
|
+
|
|
13
|
+
A missing leading slash and a trailing slash do not make two paths differ.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def norm(value: str) -> str:
|
|
17
|
+
text = value.strip()
|
|
18
|
+
if text and not text.startswith("/"):
|
|
19
|
+
text = f"/{text}"
|
|
20
|
+
return os.path.normpath(text.rstrip("/")) or "/"
|
|
21
|
+
|
|
22
|
+
return norm(left) == norm(right)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def canonical_folder_path(user_id: str, company_id: str, scope_id: str) -> str:
|
|
26
|
+
"""Return the stored path for *scope_id*.
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValueError: The folder has no path.
|
|
30
|
+
unique_sdk.UniqueError: The path lookup failed.
|
|
31
|
+
"""
|
|
32
|
+
actual = (
|
|
33
|
+
unique_sdk.Folder.get_folder_path(
|
|
34
|
+
user_id=user_id,
|
|
35
|
+
company_id=company_id,
|
|
36
|
+
scope_id=scope_id,
|
|
37
|
+
).get("folderPath")
|
|
38
|
+
or ""
|
|
39
|
+
)
|
|
40
|
+
if not actual:
|
|
41
|
+
raise ValueError(f"folder {scope_id} has no path")
|
|
42
|
+
return actual
|
|
@@ -239,12 +239,13 @@ class UniqueShell(cmd.Cmd):
|
|
|
239
239
|
# -- Folder operations --
|
|
240
240
|
|
|
241
241
|
def do_mkdir(self, arg: str) -> None:
|
|
242
|
-
"""Create a
|
|
242
|
+
"""Create a folder by relative name or absolute path.
|
|
243
243
|
|
|
244
244
|
Usage: mkdir <name>
|
|
245
245
|
|
|
246
|
-
|
|
247
|
-
|
|
246
|
+
A relative name is created under the current directory. An absolute
|
|
247
|
+
path (starting with /) is created from the root, including under
|
|
248
|
+
/home-<userId>. Nested paths create the full hierarchy.
|
|
248
249
|
|
|
249
250
|
Examples:
|
|
250
251
|
/Reports> mkdir Q2
|
|
@@ -252,6 +253,9 @@ class UniqueShell(cmd.Cmd):
|
|
|
252
253
|
|
|
253
254
|
/Reports> mkdir "2025/Q1"
|
|
254
255
|
Created: /Reports/2025/Q1 (scope_xyz999)
|
|
256
|
+
|
|
257
|
+
/> mkdir /home-user_1/second-brain/clients/X
|
|
258
|
+
Created: /home-user_1/second-brain/clients/X (scope_home1)
|
|
255
259
|
"""
|
|
256
260
|
name = arg.strip()
|
|
257
261
|
if not name:
|
|
@@ -10,6 +10,7 @@ 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.paths import canonical_folder_path, same_folder_path
|
|
13
14
|
from unique_sdk.cli.workspace import manifest_path as workspace_manifest_path
|
|
14
15
|
|
|
15
16
|
_SEARCH_CONFIG_FILENAME = ".unique-search.json"
|
|
@@ -561,6 +562,19 @@ class ShellState:
|
|
|
561
562
|
def prompt(self) -> str:
|
|
562
563
|
return f"{self._path}> "
|
|
563
564
|
|
|
565
|
+
def _enter_folder(self, scope_id: str, folder_path: str) -> str:
|
|
566
|
+
"""Store *folder_path* only when *scope_id* is that exact folder."""
|
|
567
|
+
actual = canonical_folder_path(
|
|
568
|
+
self.config.user_id,
|
|
569
|
+
self.config.company_id,
|
|
570
|
+
scope_id,
|
|
571
|
+
)
|
|
572
|
+
if not same_folder_path(actual, folder_path):
|
|
573
|
+
raise ValueError(f"cannot change to {folder_path}: resolved to {actual}")
|
|
574
|
+
self._scope_id = scope_id
|
|
575
|
+
self._path = folder_path
|
|
576
|
+
return self._path
|
|
577
|
+
|
|
564
578
|
def cd(self, target: str) -> str:
|
|
565
579
|
"""Change directory. Returns the new path.
|
|
566
580
|
|
|
@@ -590,8 +604,7 @@ class ShellState:
|
|
|
590
604
|
company_id=self.config.company_id,
|
|
591
605
|
folderPath=parent,
|
|
592
606
|
)
|
|
593
|
-
self.
|
|
594
|
-
self._scope_id = info["id"]
|
|
607
|
+
return self._enter_folder(info["id"], parent)
|
|
595
608
|
return self._path
|
|
596
609
|
|
|
597
610
|
if target.startswith("scope_"):
|
|
@@ -619,9 +632,7 @@ class ShellState:
|
|
|
619
632
|
company_id=self.config.company_id,
|
|
620
633
|
folderPath=resolved_path,
|
|
621
634
|
)
|
|
622
|
-
self.
|
|
623
|
-
self._path = resolved_path
|
|
624
|
-
return self._path
|
|
635
|
+
return self._enter_folder(info["id"], resolved_path)
|
|
625
636
|
|
|
626
637
|
def resolve_path(self, target: str | None) -> tuple[str, str | None]:
|
|
627
638
|
"""Resolve a target to (path, scope_id) without changing state.
|
|
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.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/__init__.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_acronyms.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_agentic_table.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_analytics_order.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_benchmarking.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_briefing.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_chat_completion.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_content.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_context_memory.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_elicitation.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_embedding.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_integrated.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_llm_models.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_message.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_message_log.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_message_tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_scheduled_task.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_search_string.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/api_resources/_web_search.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/agentic_table.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/cite_file.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/elicitation.py
RENAMED
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/scheduled_tasks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/cli/commands/web_search.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/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.44.0.dev2 → unique_sdk-2026.44.0.dev4}/unique_sdk/utils/analytics_order_run.py
RENAMED
|
File without changes
|
{unique_sdk-2026.44.0.dev2 → unique_sdk-2026.44.0.dev4}/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
|