unique-sdk 2026.32.0.dev1__tar.gz → 2026.32.0.dev3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/PKG-INFO +1 -1
  2. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/pyproject.toml +1 -1
  3. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_web_search.py +8 -0
  4. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/cli.py +235 -0
  5. unique_sdk-2026.32.0.dev3/unique_sdk/cli/commands/agentic_table.py +150 -0
  6. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/web_search.py +10 -0
  7. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/formatting.py +136 -0
  8. unique_sdk-2026.32.0.dev3/unique_sdk/cli/skills/unique-cli-agentic-table/SKILL.md +110 -0
  9. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/README.md +0 -0
  10. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/__init__.py +0 -0
  11. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_api_requestor.py +0 -0
  12. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_api_resource.py +0 -0
  13. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_api_version.py +0 -0
  14. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_error.py +0 -0
  15. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_http_client.py +0 -0
  16. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_list_object.py +0 -0
  17. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_object_classes.py +0 -0
  18. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_request_options.py +0 -0
  19. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_unique_object.py +0 -0
  20. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_unique_ql.py +0 -0
  21. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_unique_response.py +0 -0
  22. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_util.py +0 -0
  23. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_version.py +0 -0
  24. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/_webhook.py +0 -0
  25. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/__init__.py +0 -0
  26. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_acronyms.py +0 -0
  27. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_agentic_table.py +0 -0
  28. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_analytics_order.py +0 -0
  29. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_benchmarking.py +0 -0
  30. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_briefing.py +0 -0
  31. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_chat_completion.py +0 -0
  32. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_content.py +0 -0
  33. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_dynamic_frontend.py +0 -0
  34. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_elicitation.py +0 -0
  35. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_embedding.py +0 -0
  36. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_event.py +0 -0
  37. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_folder.py +0 -0
  38. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_group.py +0 -0
  39. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_integrated.py +0 -0
  40. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_llm_models.py +0 -0
  41. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_mcp.py +0 -0
  42. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_message.py +0 -0
  43. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_message_assessment.py +0 -0
  44. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_message_execution.py +0 -0
  45. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_message_log.py +0 -0
  46. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_message_tool.py +0 -0
  47. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_module.py +0 -0
  48. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_scheduled_task.py +0 -0
  49. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_search.py +0 -0
  50. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_search_string.py +0 -0
  51. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_short_term_memory.py +0 -0
  52. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_space.py +0 -0
  53. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/api_resources/_user.py +0 -0
  54. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/__init__.py +0 -0
  55. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/__main__.py +0 -0
  56. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/__init__.py +0 -0
  57. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
  58. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/browser.py +0 -0
  59. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/cite_file.py +0 -0
  60. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/dynamic_frontend.py +0 -0
  61. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/elicitation.py +0 -0
  62. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/files.py +0 -0
  63. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/folders.py +0 -0
  64. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/mcp.py +0 -0
  65. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/navigation.py +0 -0
  66. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/read.py +0 -0
  67. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
  68. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/search.py +0 -0
  69. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/subagent.py +0 -0
  70. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/commands/web_search_config.py +0 -0
  71. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/config.py +0 -0
  72. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/identity.py +0 -0
  73. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/metadata_filter.py +0 -0
  74. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/shell.py +0 -0
  75. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/skills/unique-cli-dynamic-frontend/SKILL.md +0 -0
  76. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +0 -0
  77. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
  78. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
  79. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
  80. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
  81. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
  82. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/skills/unique-cli-uploaded-search/SKILL.md +0 -0
  83. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
  84. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/cli/state.py +0 -0
  85. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/utils/analytics_order_run.py +0 -0
  86. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/utils/benchmarking_run.py +0 -0
  87. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/utils/chat_history.py +0 -0
  88. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/utils/chat_in_space.py +0 -0
  89. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/utils/file_io.py +0 -0
  90. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/unique_sdk/utils/sources.py +0 -0
  91. {unique_sdk-2026.32.0.dev1 → unique_sdk-2026.32.0.dev3}/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.32.0.dev1
3
+ Version: 2026.32.0.dev3
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.32.0.dev1"
3
+ version = "2026.32.0.dev3"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -58,6 +58,10 @@ class WebSearch(APIResource["WebSearch"]):
58
58
  searchEngineConfig: NotRequired[dict[str, Any] | None]
59
59
  # Used only when includeContent=True and the engine needs scraping.
60
60
  crawlerConfig: NotRequired[dict[str, Any] | None]
61
+ # Optional: when set, the space's Web Search toggle is enforced
62
+ # server-side for this call. Omit for callers not acting on
63
+ # behalf of a specific chat (unchanged legacy behavior).
64
+ chatId: NotRequired[str | None]
61
65
 
62
66
  # Response envelope.
63
67
  engine: str
@@ -130,6 +134,10 @@ class WebCrawl(APIResource["WebCrawl"]):
130
134
  urls: list[str]
131
135
  parallel: NotRequired[int | None]
132
136
  crawlerConfig: NotRequired[dict[str, Any] | None]
137
+ # Optional: when set, the space's Web Search toggle is enforced
138
+ # server-side for this call. Omit for callers not acting on
139
+ # behalf of a specific chat (unchanged legacy behavior).
140
+ chatId: NotRequired[str | None]
133
141
 
134
142
  # Response envelope.
135
143
  crawler: str
@@ -9,6 +9,15 @@ from typing import Any
9
9
  import click
10
10
 
11
11
  from unique_sdk.cli import __version__
12
+ from unique_sdk.cli.commands.agentic_table import (
13
+ cmd_cell_history,
14
+ cmd_get_cell,
15
+ cmd_get_sheet,
16
+ cmd_list_exports,
17
+ )
18
+ from unique_sdk.cli.commands.agentic_table import (
19
+ is_error_output as _is_agentic_table_error_output,
20
+ )
12
21
  from unique_sdk.cli.commands.browser import (
13
22
  cmd_browser_action,
14
23
  cmd_browser_control,
@@ -167,6 +176,7 @@ Examples:
167
176
  unique-cli web-search crawl URL Crawl a URL via the public API
168
177
  unique-cli dynamic-frontend list List manageable Dynamic Frontend spaces
169
178
  unique-cli browser get-dom Read the user's live Chrome tab (a11y tree)
179
+ unique-cli agentic-table get-sheet mt_abc123 Show a magic-table sheet summary
170
180
  """
171
181
 
172
182
 
@@ -2135,6 +2145,18 @@ Examples:
2135
2145
  is_flag=True,
2136
2146
  help="Output results as JSON (suitable for piping into web-search crawl --stdin).",
2137
2147
  )
2148
+ @click.option(
2149
+ "--chat-id",
2150
+ "chat_id",
2151
+ default=None,
2152
+ envvar="UNIQUE_CHAT_ID",
2153
+ help=(
2154
+ "Chat id this call is made on behalf of. Defaults to $UNIQUE_CHAT_ID "
2155
+ "(auto-set in Conduct sandboxes). When set, the space's Web Search "
2156
+ "toggle is enforced server-side and the call is rejected if Web "
2157
+ "Search is disabled for that space."
2158
+ ),
2159
+ )
2138
2160
  @click.pass_context
2139
2161
  def web_search_search_cmd(
2140
2162
  ctx: click.Context,
@@ -2145,6 +2167,7 @@ def web_search_search_cmd(
2145
2167
  crawler_config_raw: str | None,
2146
2168
  config_path: str | None,
2147
2169
  output_json: bool,
2170
+ chat_id: str | None,
2148
2171
  ) -> None:
2149
2172
  """Search the web via the Unique public API."""
2150
2173
  resolved_config = _resolve_web_search_config_path(ctx, config_path)
@@ -2157,6 +2180,7 @@ def web_search_search_cmd(
2157
2180
  crawler_config_raw=crawler_config_raw,
2158
2181
  output_json=output_json,
2159
2182
  config_path=resolved_config,
2183
+ chat_id=chat_id,
2160
2184
  )
2161
2185
  _emit_web_search(ctx, output)
2162
2186
 
@@ -2212,6 +2236,18 @@ Examples:
2212
2236
  is_flag=True,
2213
2237
  help="Output results as JSON.",
2214
2238
  )
2239
+ @click.option(
2240
+ "--chat-id",
2241
+ "chat_id",
2242
+ default=None,
2243
+ envvar="UNIQUE_CHAT_ID",
2244
+ help=(
2245
+ "Chat id this call is made on behalf of. Defaults to $UNIQUE_CHAT_ID "
2246
+ "(auto-set in Conduct sandboxes). When set, the space's Web Search "
2247
+ "toggle is enforced server-side and the call is rejected if Web "
2248
+ "Search is disabled for that space."
2249
+ ),
2250
+ )
2215
2251
  @click.pass_context
2216
2252
  def web_search_crawl_cmd(
2217
2253
  ctx: click.Context,
@@ -2221,6 +2257,7 @@ def web_search_crawl_cmd(
2221
2257
  crawler_config_raw: str | None,
2222
2258
  config_path: str | None,
2223
2259
  output_json: bool,
2260
+ chat_id: str | None,
2224
2261
  ) -> None:
2225
2262
  """Crawl URLs via the Unique public API."""
2226
2263
  url_list: list[str] = list(urls)
@@ -2238,5 +2275,203 @@ def web_search_crawl_cmd(
2238
2275
  crawler_config_raw=crawler_config_raw,
2239
2276
  output_json=output_json,
2240
2277
  config_path=resolved_config,
2278
+ chat_id=chat_id,
2241
2279
  )
2242
2280
  _emit_web_search(ctx, output)
2281
+
2282
+
2283
+ # -- Agentic Table ---------------------------------------------------------
2284
+
2285
+
2286
+ _AGENTIC_TABLE_HELP = """\
2287
+ Read Agentic Table (magic table) sheets, cells, and cell history.
2288
+
2289
+ \b
2290
+ These are Tier 0 reads over the public magic-table API: no confirmation,
2291
+ no writes. Every call is scoped to the current user/company; sheet-role
2292
+ access (Owner / Can manage / Can edit) is enforced server-side and a denial
2293
+ is reported as `agentic-table: permission denied`.
2294
+
2295
+ \b
2296
+ Subcommands:
2297
+ get-sheet Show a sheet summary (state, row count, metadata, cells)
2298
+ get-cell Show a single cell by row/column order
2299
+ cell-history Show a single cell's log/edit history
2300
+ list-exports List a sheet's export artifacts (reports, question exports)
2301
+
2302
+ \b
2303
+ Examples:
2304
+ unique-cli agentic-table get-sheet mt_abc123
2305
+ unique-cli agentic-table get-sheet mt_abc123 --cells --metadata
2306
+ unique-cli agentic-table get-cell mt_abc123 --row 1 --col 2
2307
+ unique-cli agentic-table cell-history mt_abc123 --row 1 --col 2 --json
2308
+ unique-cli agentic-table list-exports mt_abc123
2309
+ """
2310
+
2311
+
2312
+ @main.group("agentic-table", help=_AGENTIC_TABLE_HELP)
2313
+ def agentic_table() -> None:
2314
+ pass
2315
+
2316
+
2317
+ @agentic_table.command(name="get-sheet")
2318
+ @click.argument("table_id")
2319
+ @click.option(
2320
+ "--cells",
2321
+ "include_cells",
2322
+ is_flag=True,
2323
+ default=False,
2324
+ help="Include cell values in the output.",
2325
+ )
2326
+ @click.option(
2327
+ "--metadata",
2328
+ "include_metadata",
2329
+ is_flag=True,
2330
+ default=False,
2331
+ help="Include sheet-level metadata entries.",
2332
+ )
2333
+ @click.option(
2334
+ "--json", "output_json", is_flag=True, default=False, help="Print raw JSON."
2335
+ )
2336
+ @click.pass_context
2337
+ def agentic_table_get_sheet(
2338
+ ctx: click.Context,
2339
+ table_id: str,
2340
+ include_cells: bool,
2341
+ include_metadata: bool,
2342
+ output_json: bool,
2343
+ ) -> None:
2344
+ """Show a magic-table sheet summary.
2345
+
2346
+ \b
2347
+ TABLE_ID is the magic-table sheet id. By default only the summary
2348
+ (name, state, row count) is shown; add --metadata for sheet metadata
2349
+ and --cells to include cell values.
2350
+
2351
+ \b
2352
+ Examples:
2353
+ unique-cli agentic-table get-sheet mt_abc123
2354
+ unique-cli agentic-table get-sheet mt_abc123 --cells --metadata
2355
+ """
2356
+ emit(
2357
+ cmd_get_sheet(
2358
+ LazyState.get(ctx),
2359
+ table_id,
2360
+ include_cells=include_cells,
2361
+ include_metadata=include_metadata,
2362
+ output_json=output_json,
2363
+ ),
2364
+ is_error=_is_agentic_table_error_output,
2365
+ )
2366
+
2367
+
2368
+ @agentic_table.command(name="get-cell")
2369
+ @click.argument("table_id")
2370
+ @click.option(
2371
+ "--row", "row_order", type=int, required=True, help="Row order (0-based)."
2372
+ )
2373
+ @click.option(
2374
+ "--col", "column_order", type=int, required=True, help="Column order (0-based)."
2375
+ )
2376
+ @click.option(
2377
+ "--json", "output_json", is_flag=True, default=False, help="Print raw JSON."
2378
+ )
2379
+ @click.pass_context
2380
+ def agentic_table_get_cell(
2381
+ ctx: click.Context,
2382
+ table_id: str,
2383
+ row_order: int,
2384
+ column_order: int,
2385
+ output_json: bool,
2386
+ ) -> None:
2387
+ """Show a single cell by row/column order.
2388
+
2389
+ \b
2390
+ Examples:
2391
+ unique-cli agentic-table get-cell mt_abc123 --row 1 --col 2
2392
+ unique-cli agentic-table get-cell mt_abc123 --row 1 --col 2 --json
2393
+ """
2394
+ emit(
2395
+ cmd_get_cell(
2396
+ LazyState.get(ctx),
2397
+ table_id,
2398
+ row_order=row_order,
2399
+ column_order=column_order,
2400
+ output_json=output_json,
2401
+ ),
2402
+ is_error=_is_agentic_table_error_output,
2403
+ )
2404
+
2405
+
2406
+ @agentic_table.command(name="cell-history")
2407
+ @click.argument("table_id")
2408
+ @click.option(
2409
+ "--row", "row_order", type=int, required=True, help="Row order (0-based)."
2410
+ )
2411
+ @click.option(
2412
+ "--col", "column_order", type=int, required=True, help="Column order (0-based)."
2413
+ )
2414
+ @click.option(
2415
+ "--json", "output_json", is_flag=True, default=False, help="Print raw JSON."
2416
+ )
2417
+ @click.pass_context
2418
+ def agentic_table_cell_history(
2419
+ ctx: click.Context,
2420
+ table_id: str,
2421
+ row_order: int,
2422
+ column_order: int,
2423
+ output_json: bool,
2424
+ ) -> None:
2425
+ """Show a single cell's log/edit history.
2426
+
2427
+ \b
2428
+ Lists the cell's log entries (actor, timestamp, source message id, and
2429
+ the logged text) as recorded by prior edits.
2430
+
2431
+ \b
2432
+ Examples:
2433
+ unique-cli agentic-table cell-history mt_abc123 --row 1 --col 2
2434
+ unique-cli agentic-table cell-history mt_abc123 --row 1 --col 2 --json
2435
+ """
2436
+ emit(
2437
+ cmd_cell_history(
2438
+ LazyState.get(ctx),
2439
+ table_id,
2440
+ row_order=row_order,
2441
+ column_order=column_order,
2442
+ output_json=output_json,
2443
+ ),
2444
+ is_error=_is_agentic_table_error_output,
2445
+ )
2446
+
2447
+
2448
+ @agentic_table.command(name="list-exports")
2449
+ @click.argument("table_id")
2450
+ @click.option(
2451
+ "--json", "output_json", is_flag=True, default=False, help="Print raw JSON."
2452
+ )
2453
+ @click.pass_context
2454
+ def agentic_table_list_exports(
2455
+ ctx: click.Context,
2456
+ table_id: str,
2457
+ output_json: bool,
2458
+ ) -> None:
2459
+ """List a sheet's export artifacts.
2460
+
2461
+ \b
2462
+ Shows generated exports (full report, question export, agentic report)
2463
+ with their state and, once ready, the content id to download.
2464
+
2465
+ \b
2466
+ Examples:
2467
+ unique-cli agentic-table list-exports mt_abc123
2468
+ unique-cli agentic-table list-exports mt_abc123 --json
2469
+ """
2470
+ emit(
2471
+ cmd_list_exports(
2472
+ LazyState.get(ctx),
2473
+ table_id,
2474
+ output_json=output_json,
2475
+ ),
2476
+ is_error=_is_agentic_table_error_output,
2477
+ )
@@ -0,0 +1,150 @@
1
+ """Agentic Table (magic table) read commands for the Unique CLI.
2
+
3
+ Tier 0 reads over the public magic-table API (``2023-12-06``): fetch a sheet
4
+ summary, a single cell, or a cell's edit history. Every call is scoped to the
5
+ runner-injected user/company; sheet-role enforcement (Owner / Can manage /
6
+ Can edit) happens server-side via the platform's access guard, and a 403 is
7
+ surfaced as ``agentic-table: permission denied``.
8
+
9
+ These reads require no confirmation. Later write commands live in their own
10
+ module so the audit/activity-status plumbing lands with its first caller.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import asyncio
16
+ import json
17
+
18
+ from unique_sdk._error import UniqueError
19
+ from unique_sdk.api_resources._agentic_table import AgenticTable
20
+ from unique_sdk.cli.formatting import (
21
+ format_agentic_table_artifacts,
22
+ format_agentic_table_cell,
23
+ format_agentic_table_cell_history,
24
+ format_agentic_table_sheet,
25
+ )
26
+ from unique_sdk.cli.state import ShellState
27
+
28
+ AGENTIC_TABLE_ERROR_PREFIX = "agentic-table:"
29
+
30
+
31
+ def is_error_output(output: str) -> bool:
32
+ """Return ``True`` when *output* is an error message from an ``agentic-table`` command."""
33
+ return output.startswith(AGENTIC_TABLE_ERROR_PREFIX)
34
+
35
+
36
+ def _error(exc: UniqueError) -> str:
37
+ """Render an SDK error as a CLI error line.
38
+
39
+ A 403 (the platform's sheet-access guard) is collapsed to a stable
40
+ ``permission denied`` message so agent ``&&`` chains stop cleanly and the
41
+ wording does not leak backend detail; other errors pass through verbatim.
42
+ """
43
+ if exc.http_status == 403:
44
+ return f"{AGENTIC_TABLE_ERROR_PREFIX} permission denied"
45
+ return f"{AGENTIC_TABLE_ERROR_PREFIX} {exc}"
46
+
47
+
48
+ def cmd_get_sheet(
49
+ state: ShellState,
50
+ table_id: str,
51
+ *,
52
+ include_cells: bool = False,
53
+ include_metadata: bool = False,
54
+ output_json: bool = False,
55
+ ) -> str:
56
+ """Fetch a sheet summary: name, state, row count, and (optionally) metadata/cells."""
57
+ try:
58
+ sheet = asyncio.run(
59
+ AgenticTable.get_sheet_data(
60
+ user_id=state.config.user_id,
61
+ company_id=state.config.company_id,
62
+ tableId=table_id,
63
+ includeCells=include_cells,
64
+ includeRowCount=True,
65
+ includeSheetMetadata=include_metadata,
66
+ )
67
+ )
68
+ except UniqueError as exc:
69
+ return _error(exc)
70
+
71
+ if output_json:
72
+ return json.dumps(sheet, indent=2, default=str)
73
+ return format_agentic_table_sheet(sheet, include_cells=include_cells)
74
+
75
+
76
+ def cmd_get_cell(
77
+ state: ShellState,
78
+ table_id: str,
79
+ *,
80
+ row_order: int,
81
+ column_order: int,
82
+ output_json: bool = False,
83
+ ) -> str:
84
+ """Fetch a single cell by row/column order."""
85
+ try:
86
+ cell = asyncio.run(
87
+ AgenticTable.get_cell(
88
+ user_id=state.config.user_id,
89
+ company_id=state.config.company_id,
90
+ tableId=table_id,
91
+ rowOrder=row_order,
92
+ columnOrder=column_order,
93
+ )
94
+ )
95
+ except UniqueError as exc:
96
+ return _error(exc)
97
+
98
+ if output_json:
99
+ return json.dumps(cell, indent=2, default=str)
100
+ return format_agentic_table_cell(cell)
101
+
102
+
103
+ def cmd_cell_history(
104
+ state: ShellState,
105
+ table_id: str,
106
+ *,
107
+ row_order: int,
108
+ column_order: int,
109
+ output_json: bool = False,
110
+ ) -> str:
111
+ """Fetch a single cell's log/edit history (``logEntries`` on the cell record)."""
112
+ try:
113
+ cell = asyncio.run(
114
+ AgenticTable.get_cell(
115
+ user_id=state.config.user_id,
116
+ company_id=state.config.company_id,
117
+ tableId=table_id,
118
+ rowOrder=row_order,
119
+ columnOrder=column_order,
120
+ )
121
+ )
122
+ except UniqueError as exc:
123
+ return _error(exc)
124
+
125
+ if output_json:
126
+ return json.dumps(cell.get("logEntries") or [], indent=2, default=str)
127
+ return format_agentic_table_cell_history(cell)
128
+
129
+
130
+ def cmd_list_exports(
131
+ state: ShellState,
132
+ table_id: str,
133
+ *,
134
+ output_json: bool = False,
135
+ ) -> str:
136
+ """List a sheet's export artifacts (reports, question exports)."""
137
+ try:
138
+ artifacts = asyncio.run(
139
+ AgenticTable.list_artifacts(
140
+ user_id=state.config.user_id,
141
+ company_id=state.config.company_id,
142
+ tableId=table_id,
143
+ )
144
+ )
145
+ except UniqueError as exc:
146
+ return _error(exc)
147
+
148
+ if output_json:
149
+ return json.dumps(artifacts, indent=2, default=str)
150
+ return format_agentic_table_artifacts(artifacts)
@@ -317,6 +317,7 @@ def cmd_web_search(
317
317
  crawler_config_raw: str | None = None,
318
318
  output_json: bool = False,
319
319
  config_path: str | None = None,
320
+ chat_id: str | None = None,
320
321
  ) -> str:
321
322
  """Run a web search via the public API.
322
323
 
@@ -341,6 +342,8 @@ def cmd_web_search(
341
342
  then ``~/.unique-websearch.json``.
342
343
  output_json: When ``True``, return a JSON envelope instead of a
343
344
  human-friendly table.
345
+ chat_id: Optional chat id this call is made on behalf of. When
346
+ set, the space's Web Search toggle is enforced server-side.
344
347
  """
345
348
  file_overrides = _load_file_overrides(config_path)
346
349
  if isinstance(file_overrides, str):
@@ -368,6 +371,8 @@ def cmd_web_search(
368
371
  params["searchEngineConfig"] = engine_override
369
372
  if crawler_override is not None:
370
373
  params["crawlerConfig"] = crawler_override
374
+ if chat_id:
375
+ params["chatId"] = chat_id
371
376
 
372
377
  try:
373
378
  resource = unique_sdk.WebSearch.search(
@@ -394,6 +399,7 @@ def cmd_web_crawl(
394
399
  crawler_config_raw: str | None = None,
395
400
  output_json: bool = False,
396
401
  config_path: str | None = None,
402
+ chat_id: str | None = None,
397
403
  ) -> str:
398
404
  """Crawl a list of URLs via the public API.
399
405
 
@@ -410,6 +416,8 @@ def cmd_web_crawl(
410
416
  :func:`cmd_web_search` for resolution rules.
411
417
  output_json: When ``True``, return a JSON envelope instead of a
412
418
  human-friendly table.
419
+ chat_id: Optional chat id this call is made on behalf of. When
420
+ set, the space's Web Search toggle is enforced server-side.
413
421
  """
414
422
  if not urls:
415
423
  return f"{WEB_CRAWL_ERROR_PREFIX} no URLs provided. Pass URLs as arguments or use --stdin."
@@ -432,6 +440,8 @@ def cmd_web_crawl(
432
440
  params: dict[str, Any] = {"urls": list(urls), "parallel": parallel}
433
441
  if crawler_override is not None:
434
442
  params["crawlerConfig"] = crawler_override
443
+ if chat_id:
444
+ params["chatId"] = chat_id
435
445
 
436
446
  try:
437
447
  resource = unique_sdk.WebCrawl.crawl(
@@ -11,6 +11,11 @@ from unique_sdk.api_resources._content import Content
11
11
  from unique_sdk.api_resources._folder import Folder
12
12
 
13
13
  if TYPE_CHECKING:
14
+ from unique_sdk.api_resources._agentic_table import (
15
+ AgenticTableCell,
16
+ AgenticTableSheet,
17
+ MagicTableArtifact,
18
+ )
14
19
  from unique_sdk.api_resources._mcp import MCP
15
20
  from unique_sdk.api_resources._scheduled_task import ScheduledTask
16
21
 
@@ -37,6 +42,14 @@ def _format_date(iso_str: str | None) -> str:
37
42
  return iso_str[:16] if len(iso_str) >= 16 else iso_str
38
43
 
39
44
 
45
+ def _snippet(text: str | None, limit: int = 80) -> str:
46
+ """Collapse *text* to a single-line snippet no longer than *limit* chars."""
47
+ collapsed = (text or "").replace("\n", " ").strip()
48
+ if len(collapsed) <= limit:
49
+ return collapsed
50
+ return collapsed[:limit] + "..."
51
+
52
+
40
53
  def _pad_columns(rows: list[list[str]]) -> list[str]:
41
54
  """Pad columns to align output."""
42
55
  if not rows:
@@ -332,3 +345,126 @@ def format_mcp_response(response: MCP, *, tool_name: str | None = None) -> str:
332
345
  lines.append(f"[{content_type}] (unsupported content type)")
333
346
 
334
347
  return "\n".join(lines)
348
+
349
+
350
+ def format_agentic_table_sheet(
351
+ sheet: AgenticTableSheet,
352
+ *,
353
+ include_cells: bool = False,
354
+ ) -> str:
355
+ """Format an Agentic Table sheet summary as a key-value display.
356
+
357
+ Sheet-level metadata and cell values are only rendered when the caller
358
+ requested them (``includeSheetMetadata`` / ``includeCells``); absent
359
+ sections are skipped rather than shown empty.
360
+ """
361
+ rows = [
362
+ ["Sheet:", sheet.get("name", "?")],
363
+ ["ID:", sheet.get("sheetId", "?")],
364
+ ["State:", str(sheet.get("state", "?"))],
365
+ ]
366
+ if "magicTableRowCount" in sheet:
367
+ rows.append(["Rows:", str(sheet.get("magicTableRowCount"))])
368
+ chat_id = sheet.get("chatId")
369
+ if chat_id:
370
+ rows.append(["Chat:", chat_id])
371
+ rows.append(["Created by:", sheet.get("createdBy", "?")])
372
+ rows.append(["Created:", _format_date(sheet.get("createdAt"))])
373
+ lines = _pad_columns(rows)
374
+
375
+ metadata = sheet.get("magicTableSheetMetadata") or []
376
+ if metadata:
377
+ lines.append("")
378
+ lines.append("Metadata:")
379
+ meta_rows = [
380
+ [f" {entry.get('key', '?')}:", entry.get("value", "")]
381
+ for entry in metadata
382
+ ]
383
+ lines.extend(_pad_columns(meta_rows))
384
+
385
+ cells = sheet.get("magicTableCells") or []
386
+ if include_cells and cells:
387
+ lines.append("")
388
+ lines.append(f"Cells ({len(cells)}):")
389
+ cell_rows: list[list[str]] = [["ROW", "COL", "TEXT"]]
390
+ for cell in cells:
391
+ cell_rows.append(
392
+ [
393
+ str(cell.get("rowOrder", "?")),
394
+ str(cell.get("columnOrder", "?")),
395
+ _snippet(cell.get("text", "")),
396
+ ]
397
+ )
398
+ lines.extend(_pad_columns(cell_rows))
399
+
400
+ return "\n".join(lines)
401
+
402
+
403
+ def format_agentic_table_cell(cell: AgenticTableCell) -> str:
404
+ """Format a single Agentic Table cell (text + lock state)."""
405
+ rows = [
406
+ ["Sheet:", cell.get("sheetId", "?")],
407
+ ["Row:", str(cell.get("rowOrder", "?"))],
408
+ ["Column:", str(cell.get("columnOrder", "?"))],
409
+ ["Locked:", "yes" if cell.get("rowLocked") else "no"],
410
+ ]
411
+ lines = _pad_columns(rows)
412
+ lines.append("")
413
+ lines.append("Text:")
414
+ lines.append(cell.get("text", ""))
415
+ return "\n".join(lines)
416
+
417
+
418
+ def format_agentic_table_cell_history(cell: AgenticTableCell) -> str:
419
+ """Format an Agentic Table cell's log/edit history newest-to-oldest.
420
+
421
+ Each entry shows its timestamp, actor, optional source message id, and the
422
+ logged text (the actor + reason recorded by writes). The order is whatever
423
+ the API returns; no client-side sorting is imposed.
424
+ """
425
+ entries = cell.get("logEntries") or []
426
+ row = cell.get("rowOrder", "?")
427
+ col = cell.get("columnOrder", "?")
428
+ plural = "entry" if len(entries) == 1 else "entries"
429
+ header = f"Cell history (row {row}, col {col}) — {len(entries)} {plural}"
430
+ if not entries:
431
+ return f"{header}\n(no log entries)"
432
+
433
+ lines = [header, ""]
434
+ for entry in entries:
435
+ actor = entry.get("actorType", "?")
436
+ when = _format_date(entry.get("createdAt"))
437
+ message_id = entry.get("messageId")
438
+ suffix = f" [{message_id}]" if message_id else ""
439
+ lines.append(f"- {when} {actor}{suffix}")
440
+ text = (entry.get("text") or "").strip()
441
+ for text_line in text.splitlines():
442
+ lines.append(f" {text_line}")
443
+ return "\n".join(lines)
444
+
445
+
446
+ def format_agentic_table_artifacts(artifacts: list[MagicTableArtifact]) -> str:
447
+ """Format a sheet's export artifacts as a table.
448
+
449
+ ``contentId`` is only populated once an artifact reaches the ``DONE`` state;
450
+ pending/in-progress artifacts show ``-`` in the CONTENT column.
451
+ """
452
+ if not artifacts:
453
+ return "No export artifacts found."
454
+
455
+ header = ["TYPE", "STATE", "ID", "CONTENT", "UPDATED"]
456
+ rows: list[list[str]] = [header]
457
+ for artifact in artifacts:
458
+ rows.append(
459
+ [
460
+ str(artifact.get("artifactType", "?")),
461
+ str(artifact.get("artifactState", "?")),
462
+ artifact.get("id", "?"),
463
+ artifact.get("contentId") or "-",
464
+ _format_date(artifact.get("updatedAt")),
465
+ ]
466
+ )
467
+
468
+ lines = [f"{len(artifacts)} export artifact(s):\n"]
469
+ lines.extend(_pad_columns(rows))
470
+ return "\n".join(lines)
@@ -0,0 +1,110 @@
1
+ ---
2
+ name: unique-cli-agentic-table
3
+ description: >-
4
+ Read Agentic Table (magic table / due-diligence) sheets through the
5
+ unique-cli agentic-table command. Use when the user or task involves
6
+ inspecting an Agentic Table: a sheet's state and metadata, a specific
7
+ cell's value or lock state, a cell's edit/answer history, or the sheet's
8
+ generated export artifacts (reports, question exports). These are
9
+ read-only (Tier 0) commands — they never modify the sheet and never
10
+ require confirmation. Access to each sheet is enforced by the platform;
11
+ a denial is reported as `agentic-table: permission denied`.
12
+ ---
13
+
14
+ # Unique CLI -- Agentic Table (read)
15
+
16
+ Inspect Agentic Table (a.k.a. magic table) sheets over the public magic-table
17
+ API. Every command is scoped to the current user/company automatically; you
18
+ never pass credentials. Sheet-role access (Owner / Can manage / Can edit) is
19
+ enforced **server-side** — if you are not entitled to a sheet the command exits
20
+ with `agentic-table: permission denied` rather than returning data.
21
+
22
+ All commands here are **reads only**. There are no write commands in this skill.
23
+
24
+ ## Commands
25
+
26
+ ### Sheet summary
27
+
28
+ ```bash
29
+ unique-cli agentic-table get-sheet <table_id>
30
+ ```
31
+
32
+ Shows the sheet name, state, and row count. Add flags for more detail:
33
+
34
+ | Flag | Effect |
35
+ |------|--------|
36
+ | `--metadata` | Include sheet-level metadata entries |
37
+ | `--cells` | Include cell values (row, column, text snippet) |
38
+ | `--json` | Print the raw sheet JSON instead of the formatted view |
39
+
40
+ ```bash
41
+ unique-cli agentic-table get-sheet mt_abc123 --metadata --cells
42
+ ```
43
+
44
+ ### Single cell
45
+
46
+ ```bash
47
+ unique-cli agentic-table get-cell <table_id> --row N --col N
48
+ ```
49
+
50
+ Shows one cell's text and lock state. `--row`/`--col` are 0-based orders.
51
+ Add `--json` for the raw cell record.
52
+
53
+ ```bash
54
+ unique-cli agentic-table get-cell mt_abc123 --row 1 --col 2
55
+ ```
56
+
57
+ ### Cell history
58
+
59
+ ```bash
60
+ unique-cli agentic-table cell-history <table_id> --row N --col N
61
+ ```
62
+
63
+ Shows a single cell's log/edit history (actor, timestamp, source message id,
64
+ and the logged text) newest-to-oldest as returned by the API. Add `--json`
65
+ to get the raw log entries.
66
+
67
+ ```bash
68
+ unique-cli agentic-table cell-history mt_abc123 --row 1 --col 2
69
+ ```
70
+
71
+ ### Export artifacts
72
+
73
+ ```bash
74
+ unique-cli agentic-table list-exports <table_id>
75
+ ```
76
+
77
+ Lists the sheet's generated exports (full report, question export, agentic
78
+ report) with their state. The content id needed to download a file is only
79
+ present once an artifact reaches the `DONE` state. Add `--json` for the raw
80
+ list.
81
+
82
+ ```bash
83
+ unique-cli agentic-table list-exports mt_abc123
84
+ ```
85
+
86
+ ## Rules
87
+
88
+ 1. Treat these as read-only diagnostics. To answer a question about a sheet,
89
+ fetch exactly what you need (`get-cell` for one value, `get-sheet --cells`
90
+ for an overview) rather than dumping the whole sheet unless asked.
91
+ 2. `--row` and `--col` are **0-based** orders. Row 0 is the header row.
92
+ 3. A `agentic-table: permission denied` line means the current user cannot
93
+ access that sheet. Do not retry with different ids to probe access — report
94
+ the denial and stop.
95
+ 4. Use `--json` when you need to parse fields programmatically (e.g. to read a
96
+ `contentId` before downloading an export); use the default formatted output
97
+ when summarising for the user.
98
+
99
+ ## Prerequisites
100
+
101
+ The platform sets these environment variables automatically:
102
+
103
+ ```bash
104
+ UNIQUE_USER_ID
105
+ UNIQUE_COMPANY_ID
106
+ UNIQUE_API_KEY
107
+ UNIQUE_APP_ID
108
+ ```
109
+
110
+ Install: `pip install unique-sdk`