ndi-cli 0.12.0__tar.gz → 0.13.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ndi-cli
3
- Version: 0.12.0
3
+ Version: 0.13.0
4
4
  Summary: NDI platform CLI: document operations, jobs, workspaces, and the agent workspace tools
5
5
  Keywords: ndi,cli,document-intelligence,coding-agents
6
6
  Author: Nace AI
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Programming Language :: Python :: 3.14
17
17
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
18
  Classifier: Typing :: Typed
19
- Requires-Dist: ndi-sdk>=0.27,<1
19
+ Requires-Dist: ndi-sdk>=0.28,<1
20
20
  Requires-Dist: truststore>=0.10.4,<1
21
21
  Requires-Python: >=3.11
22
22
  Project-URL: Homepage, https://ndi-api.nace.ai
@@ -53,6 +53,7 @@ config location. Workspace-scoped verbs also take `--workspace ID`.
53
53
  | `ndi ground SOURCE --target id=TEXT` | Locate quoted text (standalone quotes only; after search, poll `grounding_job_ids`) |
54
54
  | `ndi job ID` / `ndi jobs` / `ndi cancel ID` | Inspect or cancel jobs |
55
55
  | `ndi workspace create\|list\|get\|stats\|delete\|use` | Workspace lifecycle |
56
+ | `ndi workspace global-context [--set TEXT\|--file PATH\|--clear]` | Show, replace, or remove (`--clear`) the background every query without `--context` runs with; `--no-context` on a query runs with none |
56
57
  | `ndi files upload\|list\|get\|delete` | Workspace files (a directory walks nested files; `--ingest` then queues ingestion) |
57
58
  | `ndi ingest` | Queue ingestion |
58
59
  | `ndi automatic-search QUERY` | Service answers with fact search or an agent over files, tables and the catalog; prints the route, the output job id, and the answer (plus the catalog table when there is one) |
@@ -29,6 +29,7 @@ config location. Workspace-scoped verbs also take `--workspace ID`.
29
29
  | `ndi ground SOURCE --target id=TEXT` | Locate quoted text (standalone quotes only; after search, poll `grounding_job_ids`) |
30
30
  | `ndi job ID` / `ndi jobs` / `ndi cancel ID` | Inspect or cancel jobs |
31
31
  | `ndi workspace create\|list\|get\|stats\|delete\|use` | Workspace lifecycle |
32
+ | `ndi workspace global-context [--set TEXT\|--file PATH\|--clear]` | Show, replace, or remove (`--clear`) the background every query without `--context` runs with; `--no-context` on a query runs with none |
32
33
  | `ndi files upload\|list\|get\|delete` | Workspace files (a directory walks nested files; `--ingest` then queues ingestion) |
33
34
  | `ndi ingest` | Queue ingestion |
34
35
  | `ndi automatic-search QUERY` | Service answers with fact search or an agent over files, tables and the catalog; prints the route, the output job id, and the answer (plus the catalog table when there is one) |
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ndi-cli"
3
- version = "0.12.0"
3
+ version = "0.13.0"
4
4
  description = "NDI platform CLI: document operations, jobs, workspaces, and the agent workspace tools"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -24,7 +24,7 @@ classifiers = [
24
24
  "Typing :: Typed",
25
25
  ]
26
26
  dependencies = [
27
- "ndi-sdk>=0.27,<1",
27
+ "ndi-sdk>=0.28,<1",
28
28
  "truststore>=0.10.4,<1",
29
29
  ]
30
30
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ndi-cli"
3
- version = "0.12.0"
3
+ version = "0.13.0"
4
4
  description = "NDI platform CLI: document operations, jobs, workspaces, and the agent workspace tools"
5
5
  authors = [
6
6
  { name = "Nace AI", email = "engineering@nace.ai" }
@@ -27,7 +27,7 @@ classifiers = [
27
27
  "Typing :: Typed",
28
28
  ]
29
29
  dependencies = [
30
- "ndi-sdk>=0.27,<1",
30
+ "ndi-sdk>=0.28,<1",
31
31
  "truststore>=0.10.4,<1",
32
32
  ]
33
33
 
@@ -6,4 +6,4 @@ methods. The six workspace tools (``folder-metadata``, ``file-metadata``,
6
6
  in-sandbox surface.
7
7
  """
8
8
 
9
- __version__ = "0.12.0"
9
+ __version__ = "0.13.0"
@@ -14,6 +14,7 @@ import uuid
14
14
 
15
15
  import html
16
16
  import re
17
+ import textwrap
17
18
  from typing import Literal
18
19
 
19
20
  from ndi_sdk.models.common import Coverage, Evidence, File
@@ -809,6 +810,12 @@ def workspace(item: Workspace) -> str:
809
810
  return f"{item.workspace_id} {item.name} {item.domain_slug}@{item.domain_version} {item.status}{purpose}"
810
811
 
811
812
 
813
+ def workspace_detail(item: Workspace) -> str:
814
+ if not item.global_context:
815
+ return workspace(item)
816
+ return f"{workspace(item)}\nglobal context:\n{textwrap.indent(item.global_context, ' ')}"
817
+
818
+
812
819
  def workspace_page(page) -> str:
813
820
  lines = [f"workspaces ({page.total_count}):"]
814
821
  lines.extend(f" {workspace(item)}" for item in page.items)
@@ -24,6 +24,8 @@ from ndi_cli._sources import collect_workspace_uploads, folder_upload_prefix, wo
24
24
  DEFAULT_UPLOAD_PARALLEL = 8
25
25
  INGEST_FILE_ID_BATCH = 1000
26
26
  _SKIP_WARNING_LIST_CAP = 20
27
+ _CONTEXT_HELP = "Background for this run, replacing the workspace's global context. Omit to use the global context."
28
+ _NO_CONTEXT_HELP = "Run with no context at all, not even the workspace's global context."
27
29
 
28
30
 
29
31
  def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser) -> None:
@@ -43,6 +45,19 @@ def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser
43
45
  get.add_argument("workspace_id", nargs="?", default=None)
44
46
  get.set_defaults(run=_run_ws_get, family="workspace", needs_workspace=False, needs_client=True)
45
47
 
48
+ global_context = ws.add_parser(
49
+ "global-context",
50
+ parents=[common],
51
+ help="Show, replace (--set/--file) or remove (--clear) the workspace's global context: the background every "
52
+ "query without --context runs with.",
53
+ )
54
+ global_context.add_argument("workspace_id", nargs="?", default=None)
55
+ replace = global_context.add_mutually_exclusive_group()
56
+ replace.add_argument("--set", default=None, dest="text", help="Replace the global context with this text.")
57
+ replace.add_argument("--file", default=None, type=Path, help="Replace the global context with this file's text.")
58
+ replace.add_argument("--clear", action="store_true", help="Remove the global context from the workspace.")
59
+ global_context.set_defaults(run=_run_ws_global_context, family="workspace", needs_workspace=False, needs_client=True)
60
+
46
61
  stats = ws.add_parser("stats", parents=[common], help="Content summary for a workspace.")
47
62
  stats.add_argument("workspace_id", nargs="?", default=None)
48
63
  stats.set_defaults(run=_run_ws_stats, family="workspace", needs_workspace=False, needs_client=True)
@@ -104,7 +119,7 @@ def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser
104
119
  deep.add_argument("query")
105
120
  deep.add_argument("--effort", choices=("low", "medium", "high"), default=None)
106
121
  deep.add_argument("--prefix", default=None, dest="path_prefix")
107
- deep.add_argument("--context", default=None)
122
+ _add_context_args(deep)
108
123
  add_job_output_args(deep)
109
124
  deep.set_defaults(run=_run_deep_search, family="workspace", needs_workspace=True, needs_client=True)
110
125
 
@@ -112,7 +127,7 @@ def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser
112
127
  fact.add_argument("query")
113
128
  fact.add_argument("--prefix", default=None, dest="path_prefix")
114
129
  fact.add_argument("--top-k", type=int, default=None)
115
- fact.add_argument("--context", default=None)
130
+ _add_context_args(fact)
116
131
  add_job_output_args(fact)
117
132
  fact.set_defaults(run=_run_fact_search, family="workspace", needs_workspace=True, needs_client=True)
118
133
 
@@ -126,7 +141,7 @@ def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser
126
141
  automatic.add_argument("query")
127
142
  automatic.add_argument("--prefix", default=None, dest="path_prefix")
128
143
  automatic.add_argument("--top-k", type=int, default=None, help="Cap the citation list when the fact pipeline answers.")
129
- automatic.add_argument("--context", default=None)
144
+ _add_context_args(automatic)
130
145
  add_job_output_args(automatic)
131
146
  automatic.set_defaults(run=_run_automatic_search, family="workspace", needs_workspace=True, needs_client=True)
132
147
 
@@ -138,7 +153,7 @@ def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser
138
153
  "--cursor", default=None, help="Page an earlier run: paste its result's next_cursor. A page is a new job."
139
154
  )
140
155
  filtered.add_argument("--prefix", default=None, dest="path_prefix")
141
- filtered.add_argument("--context", default=None)
156
+ _add_context_args(filtered)
142
157
  # No --result-unit: the server deprecates the grouping override and returns
143
158
  # catalog entries. No --allow-clarification: a clarification is answered by a
144
159
  # new initial query, and a shell has nowhere to keep the thread — the reason
@@ -151,7 +166,7 @@ def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser
151
166
  jet.add_argument("--reasoning-effort", choices=("none", "minimal", "low", "medium", "high"), default=None)
152
167
  jet.add_argument("--prefix", default=None, dest="path_prefix")
153
168
  jet.add_argument("--path", action="append", dest="paths", default=None, help="Scope to this file; repeatable.")
154
- jet.add_argument("--context", default=None)
169
+ _add_context_args(jet)
155
170
  # No --session-id, for the reason deep-search omits it: a shell has nowhere
156
171
  # to keep a thread. Omitting --reasoning-effort uses the service default.
157
172
  add_job_output_args(jet)
@@ -205,6 +220,13 @@ def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser
205
220
  lu_selection.set_defaults(run=_run_ledger_selection, family="workspace", needs_workspace=True, needs_client=True)
206
221
 
207
222
 
223
+ def _add_context_args(parser: argparse.ArgumentParser) -> None:
224
+ context = parser.add_mutually_exclusive_group()
225
+ context.add_argument("--context", default=None, help=_CONTEXT_HELP)
226
+ # The server reads an empty context as "none" and an omitted one as "use the global context".
227
+ context.add_argument("--no-context", action="store_const", const="", dest="context", help=_NO_CONTEXT_HELP)
228
+
229
+
208
230
  def _workspace_id(args: argparse.Namespace, fallback: str) -> str:
209
231
  value = getattr(args, "workspace_id", None) or args.workspace or fallback
210
232
  if not value:
@@ -235,7 +257,20 @@ def _run_ws_list(client: NdiClient, workspace_id: str, args: argparse.Namespace)
235
257
 
236
258
  def _run_ws_get(client: NdiClient, workspace_id: str, args: argparse.Namespace) -> int:
237
259
  workspace = client.workspaces.get(_workspace_id(args, workspace_id))
238
- return _dump_or_text(workspace, args, text=_render.workspace(workspace), stem="workspace")
260
+ return _dump_or_text(workspace, args, text=_render.workspace_detail(workspace), stem="workspace")
261
+
262
+
263
+ def _run_ws_global_context(client: NdiClient, workspace_id: str, args: argparse.Namespace) -> int:
264
+ target = _workspace_id(args, workspace_id)
265
+ if args.clear:
266
+ workspace = client.workspaces.update(target, global_context="")
267
+ elif args.file is not None:
268
+ workspace = client.workspaces.update(target, global_context=args.file.read_text(encoding="utf-8"))
269
+ elif args.text is not None:
270
+ workspace = client.workspaces.update(target, global_context=args.text)
271
+ else:
272
+ workspace = client.workspaces.get(target)
273
+ return _dump_or_text(workspace, args, text=workspace.global_context or "(no global context)", stem="workspace")
239
274
 
240
275
 
241
276
  def _run_ws_stats(client: NdiClient, workspace_id: str, args: argparse.Namespace) -> int:
@@ -521,8 +556,8 @@ def _run_filtered_search(client: NdiClient, workspace_id: str, args: argparse.Na
521
556
  # A page re-executes the parent's own stored query, so the scoping flags
522
557
  # cannot apply to it. Accepting and dropping them would quietly answer a
523
558
  # different question than the one typed.
524
- if args.path_prefix or args.context:
525
- print("error: --prefix and --context belong to the initial query, not a page", file=sys.stderr)
559
+ if args.path_prefix or args.context is not None:
560
+ print("error: --prefix, --context and --no-context belong to the initial query, not a page", file=sys.stderr)
526
561
  return 2
527
562
  job = client.search.filtered_page(workspace_id, cursor=args.cursor)
528
563
  else:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes