unique-sdk 2026.32.0.dev0__tar.gz → 2026.32.0.dev2__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.dev0 → unique_sdk-2026.32.0.dev2}/PKG-INFO +1 -1
  2. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/pyproject.toml +1 -1
  3. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/cli.py +207 -0
  4. unique_sdk-2026.32.0.dev2/unique_sdk/cli/commands/agentic_table.py +150 -0
  5. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/elicitation.py +12 -2
  6. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/formatting.py +136 -0
  7. unique_sdk-2026.32.0.dev2/unique_sdk/cli/skills/unique-cli-agentic-table/SKILL.md +110 -0
  8. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/skills/unique-cli-elicitation/SKILL.md +7 -0
  9. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/README.md +0 -0
  10. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/__init__.py +0 -0
  11. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_api_requestor.py +0 -0
  12. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_api_resource.py +0 -0
  13. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_api_version.py +0 -0
  14. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_error.py +0 -0
  15. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_http_client.py +0 -0
  16. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_list_object.py +0 -0
  17. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_object_classes.py +0 -0
  18. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_request_options.py +0 -0
  19. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_unique_object.py +0 -0
  20. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_unique_ql.py +0 -0
  21. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_unique_response.py +0 -0
  22. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_util.py +0 -0
  23. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_version.py +0 -0
  24. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/_webhook.py +0 -0
  25. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/__init__.py +0 -0
  26. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_acronyms.py +0 -0
  27. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_agentic_table.py +0 -0
  28. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_analytics_order.py +0 -0
  29. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_benchmarking.py +0 -0
  30. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_briefing.py +0 -0
  31. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_chat_completion.py +0 -0
  32. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_content.py +0 -0
  33. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_dynamic_frontend.py +0 -0
  34. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_elicitation.py +0 -0
  35. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_embedding.py +0 -0
  36. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_event.py +0 -0
  37. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_folder.py +0 -0
  38. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_group.py +0 -0
  39. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_integrated.py +0 -0
  40. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_llm_models.py +0 -0
  41. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_mcp.py +0 -0
  42. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_message.py +0 -0
  43. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_message_assessment.py +0 -0
  44. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_message_execution.py +0 -0
  45. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_message_log.py +0 -0
  46. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_message_tool.py +0 -0
  47. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_module.py +0 -0
  48. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_scheduled_task.py +0 -0
  49. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_search.py +0 -0
  50. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_search_string.py +0 -0
  51. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_short_term_memory.py +0 -0
  52. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_space.py +0 -0
  53. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_user.py +0 -0
  54. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/api_resources/_web_search.py +0 -0
  55. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/__init__.py +0 -0
  56. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/__main__.py +0 -0
  57. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/__init__.py +0 -0
  58. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/_citation_manifest.py +0 -0
  59. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/browser.py +0 -0
  60. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/cite_file.py +0 -0
  61. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/dynamic_frontend.py +0 -0
  62. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/files.py +0 -0
  63. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/folders.py +0 -0
  64. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/mcp.py +0 -0
  65. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/navigation.py +0 -0
  66. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/read.py +0 -0
  67. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/scheduled_tasks.py +0 -0
  68. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/search.py +0 -0
  69. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/subagent.py +0 -0
  70. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/web_search.py +0 -0
  71. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/commands/web_search_config.py +0 -0
  72. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/config.py +0 -0
  73. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/identity.py +0 -0
  74. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/metadata_filter.py +0 -0
  75. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/shell.py +0 -0
  76. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/skills/unique-cli-dynamic-frontend/SKILL.md +0 -0
  77. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/skills/unique-cli-file-management/SKILL.md +0 -0
  78. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/skills/unique-cli-mcp/SKILL.md +0 -0
  79. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/skills/unique-cli-scheduled-tasks/SKILL.md +0 -0
  80. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/skills/unique-cli-search/SKILL.md +0 -0
  81. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/skills/unique-cli-subagent/SKILL.md +0 -0
  82. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/skills/unique-cli-uploaded-search/SKILL.md +0 -0
  83. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/skills/unique-cli-web-search/SKILL.md +0 -0
  84. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/cli/state.py +0 -0
  85. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/utils/analytics_order_run.py +0 -0
  86. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/utils/benchmarking_run.py +0 -0
  87. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/utils/chat_history.py +0 -0
  88. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/utils/chat_in_space.py +0 -0
  89. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/utils/file_io.py +0 -0
  90. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/unique_sdk/utils/sources.py +0 -0
  91. {unique_sdk-2026.32.0.dev0 → unique_sdk-2026.32.0.dev2}/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.dev0
3
+ Version: 2026.32.0.dev2
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.dev0"
3
+ version = "2026.32.0.dev2"
4
4
  description = ""
5
5
  readme = "README.md"
6
6
  license = { text = "MIT" }
@@ -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
 
@@ -2240,3 +2250,200 @@ def web_search_crawl_cmd(
2240
2250
  config_path=resolved_config,
2241
2251
  )
2242
2252
  _emit_web_search(ctx, output)
2253
+
2254
+
2255
+ # -- Agentic Table ---------------------------------------------------------
2256
+
2257
+
2258
+ _AGENTIC_TABLE_HELP = """\
2259
+ Read Agentic Table (magic table) sheets, cells, and cell history.
2260
+
2261
+ \b
2262
+ These are Tier 0 reads over the public magic-table API: no confirmation,
2263
+ no writes. Every call is scoped to the current user/company; sheet-role
2264
+ access (Owner / Can manage / Can edit) is enforced server-side and a denial
2265
+ is reported as `agentic-table: permission denied`.
2266
+
2267
+ \b
2268
+ Subcommands:
2269
+ get-sheet Show a sheet summary (state, row count, metadata, cells)
2270
+ get-cell Show a single cell by row/column order
2271
+ cell-history Show a single cell's log/edit history
2272
+ list-exports List a sheet's export artifacts (reports, question exports)
2273
+
2274
+ \b
2275
+ Examples:
2276
+ unique-cli agentic-table get-sheet mt_abc123
2277
+ unique-cli agentic-table get-sheet mt_abc123 --cells --metadata
2278
+ unique-cli agentic-table get-cell mt_abc123 --row 1 --col 2
2279
+ unique-cli agentic-table cell-history mt_abc123 --row 1 --col 2 --json
2280
+ unique-cli agentic-table list-exports mt_abc123
2281
+ """
2282
+
2283
+
2284
+ @main.group("agentic-table", help=_AGENTIC_TABLE_HELP)
2285
+ def agentic_table() -> None:
2286
+ pass
2287
+
2288
+
2289
+ @agentic_table.command(name="get-sheet")
2290
+ @click.argument("table_id")
2291
+ @click.option(
2292
+ "--cells",
2293
+ "include_cells",
2294
+ is_flag=True,
2295
+ default=False,
2296
+ help="Include cell values in the output.",
2297
+ )
2298
+ @click.option(
2299
+ "--metadata",
2300
+ "include_metadata",
2301
+ is_flag=True,
2302
+ default=False,
2303
+ help="Include sheet-level metadata entries.",
2304
+ )
2305
+ @click.option(
2306
+ "--json", "output_json", is_flag=True, default=False, help="Print raw JSON."
2307
+ )
2308
+ @click.pass_context
2309
+ def agentic_table_get_sheet(
2310
+ ctx: click.Context,
2311
+ table_id: str,
2312
+ include_cells: bool,
2313
+ include_metadata: bool,
2314
+ output_json: bool,
2315
+ ) -> None:
2316
+ """Show a magic-table sheet summary.
2317
+
2318
+ \b
2319
+ TABLE_ID is the magic-table sheet id. By default only the summary
2320
+ (name, state, row count) is shown; add --metadata for sheet metadata
2321
+ and --cells to include cell values.
2322
+
2323
+ \b
2324
+ Examples:
2325
+ unique-cli agentic-table get-sheet mt_abc123
2326
+ unique-cli agentic-table get-sheet mt_abc123 --cells --metadata
2327
+ """
2328
+ emit(
2329
+ cmd_get_sheet(
2330
+ LazyState.get(ctx),
2331
+ table_id,
2332
+ include_cells=include_cells,
2333
+ include_metadata=include_metadata,
2334
+ output_json=output_json,
2335
+ ),
2336
+ is_error=_is_agentic_table_error_output,
2337
+ )
2338
+
2339
+
2340
+ @agentic_table.command(name="get-cell")
2341
+ @click.argument("table_id")
2342
+ @click.option(
2343
+ "--row", "row_order", type=int, required=True, help="Row order (0-based)."
2344
+ )
2345
+ @click.option(
2346
+ "--col", "column_order", type=int, required=True, help="Column order (0-based)."
2347
+ )
2348
+ @click.option(
2349
+ "--json", "output_json", is_flag=True, default=False, help="Print raw JSON."
2350
+ )
2351
+ @click.pass_context
2352
+ def agentic_table_get_cell(
2353
+ ctx: click.Context,
2354
+ table_id: str,
2355
+ row_order: int,
2356
+ column_order: int,
2357
+ output_json: bool,
2358
+ ) -> None:
2359
+ """Show a single cell by row/column order.
2360
+
2361
+ \b
2362
+ Examples:
2363
+ unique-cli agentic-table get-cell mt_abc123 --row 1 --col 2
2364
+ unique-cli agentic-table get-cell mt_abc123 --row 1 --col 2 --json
2365
+ """
2366
+ emit(
2367
+ cmd_get_cell(
2368
+ LazyState.get(ctx),
2369
+ table_id,
2370
+ row_order=row_order,
2371
+ column_order=column_order,
2372
+ output_json=output_json,
2373
+ ),
2374
+ is_error=_is_agentic_table_error_output,
2375
+ )
2376
+
2377
+
2378
+ @agentic_table.command(name="cell-history")
2379
+ @click.argument("table_id")
2380
+ @click.option(
2381
+ "--row", "row_order", type=int, required=True, help="Row order (0-based)."
2382
+ )
2383
+ @click.option(
2384
+ "--col", "column_order", type=int, required=True, help="Column order (0-based)."
2385
+ )
2386
+ @click.option(
2387
+ "--json", "output_json", is_flag=True, default=False, help="Print raw JSON."
2388
+ )
2389
+ @click.pass_context
2390
+ def agentic_table_cell_history(
2391
+ ctx: click.Context,
2392
+ table_id: str,
2393
+ row_order: int,
2394
+ column_order: int,
2395
+ output_json: bool,
2396
+ ) -> None:
2397
+ """Show a single cell's log/edit history.
2398
+
2399
+ \b
2400
+ Lists the cell's log entries (actor, timestamp, source message id, and
2401
+ the logged text) as recorded by prior edits.
2402
+
2403
+ \b
2404
+ Examples:
2405
+ unique-cli agentic-table cell-history mt_abc123 --row 1 --col 2
2406
+ unique-cli agentic-table cell-history mt_abc123 --row 1 --col 2 --json
2407
+ """
2408
+ emit(
2409
+ cmd_cell_history(
2410
+ LazyState.get(ctx),
2411
+ table_id,
2412
+ row_order=row_order,
2413
+ column_order=column_order,
2414
+ output_json=output_json,
2415
+ ),
2416
+ is_error=_is_agentic_table_error_output,
2417
+ )
2418
+
2419
+
2420
+ @agentic_table.command(name="list-exports")
2421
+ @click.argument("table_id")
2422
+ @click.option(
2423
+ "--json", "output_json", is_flag=True, default=False, help="Print raw JSON."
2424
+ )
2425
+ @click.pass_context
2426
+ def agentic_table_list_exports(
2427
+ ctx: click.Context,
2428
+ table_id: str,
2429
+ output_json: bool,
2430
+ ) -> None:
2431
+ """List a sheet's export artifacts.
2432
+
2433
+ \b
2434
+ Shows generated exports (full report, question export, agentic report)
2435
+ with their state and, once ready, the content id to download.
2436
+
2437
+ \b
2438
+ Examples:
2439
+ unique-cli agentic-table list-exports mt_abc123
2440
+ unique-cli agentic-table list-exports mt_abc123 --json
2441
+ """
2442
+ emit(
2443
+ cmd_list_exports(
2444
+ LazyState.get(ctx),
2445
+ table_id,
2446
+ output_json=output_json,
2447
+ ),
2448
+ is_error=_is_agentic_table_error_output,
2449
+ )
@@ -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)
@@ -687,9 +687,19 @@ def cmd_elicit_wait(
687
687
  if final_status in TERMINAL_STATUSES:
688
688
  terminal_status = final_status
689
689
  return format_elicitation(final)
690
+ # Non-terminal timeout: this is the one output every harness
691
+ # (Claude Code, Codex, ...) reads after every poll. Make it an
692
+ # explicit instruction to poll again, not just a description of
693
+ # what happened — a model that stops here has nowhere else to
694
+ # look for "what do I do next" (see UN-23181).
690
695
  return (
691
- f"elicit: timed out after {timeout}s waiting for "
692
- f"{elicitation_id} (last status: {final_status or 'UNKNOWN'})\n\n"
696
+ f"elicit: still PENDING after {timeout}s waiting for "
697
+ f"{elicitation_id} (last status: {final_status or 'UNKNOWN'}) "
698
+ "— this is NOT a stopping condition.\n"
699
+ f"Call again now: unique-cli elicit wait {elicitation_id} "
700
+ f"--timeout {timeout} --poll-interval {poll_interval}\n"
701
+ "Do not tell the user you stopped waiting; keep polling "
702
+ "until a terminal status or expiry.\n\n"
693
703
  f"{format_elicitation(last)}"
694
704
  )
695
705
  time.sleep(poll_interval)
@@ -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`
@@ -52,6 +52,13 @@ default — comfortably under Claude Code's ~120s):
52
52
  2. **Poll in short bursts**, each well under your harness's Bash foreground
53
53
  timeout:
54
54
 
55
+ **You MUST NOT stop after a single `PENDING` result.** If `elicit wait`
56
+ returns `PENDING`, immediately call `elicit wait` again (same elicitation
57
+ id) — as its own new tool call if you are not running the loop below —
58
+ and keep doing so until you see a terminal status or the elicitation
59
+ reaches its `--expires-in` deadline. Never tell the user you "stopped
60
+ waiting" or "gave up" while `PENDING` is the last known status.
61
+
55
62
  ```bash
56
63
  status="PENDING"
57
64
  elapsed=0