ndi-cli 0.13.0__tar.gz → 0.16.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.
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/PKG-INFO +10 -3
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/README.md +8 -1
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/pyproject.toml +2 -2
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/pyproject.toml.orig +2 -2
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/__init__.py +1 -1
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/_cli.py +20 -3
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/_docops.py +1 -1
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/_jobs.py +5 -4
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/_output.py +65 -4
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/_render.py +18 -9
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/_tools.py +6 -2
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/_workspace.py +8 -5
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/LICENSE +0 -0
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/_config.py +0 -0
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/_entrypoint.py +0 -0
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/_local.py +0 -0
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/_login.py +0 -0
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/_sources.py +0 -0
- {ndi_cli-0.13.0 → ndi_cli-0.16.0}/src/ndi_cli/commands.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ndi-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.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.
|
|
19
|
+
Requires-Dist: ndi-sdk>=0.31,<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
|
|
@@ -94,7 +94,14 @@ file ids. A single file still requires `--path`.
|
|
|
94
94
|
|
|
95
95
|
Result content goes to **stdout**; status (`job <id> queued`, `saved …`) goes
|
|
96
96
|
to **stderr**. `-o auto|md|json|payload|id` picks the shape. `--json` is an
|
|
97
|
-
alias for `-o json`.
|
|
97
|
+
alias for `-o json`.
|
|
98
|
+
|
|
99
|
+
The default (`auto`) prints the full JSON response. When that is over
|
|
100
|
+
`$NDI_OUTPUT_LIMIT` characters (default 40,000), the compact text rendering
|
|
101
|
+
prints instead and stderr carries `note: output shortened …` with the full
|
|
102
|
+
size. `--json` always prints it whole, and `--save` / `--out-dir` always write
|
|
103
|
+
it whole. Commands whose output is the content itself (`read-file`,
|
|
104
|
+
`ws context`, parse markdown) print it as before. `--save PATH` / `--out-dir DIR` write files. `--async`
|
|
98
105
|
submits and prints the job id.
|
|
99
106
|
|
|
100
107
|
When a Parse result has external complete content, `-o auto` and `-o md` print
|
|
@@ -70,7 +70,14 @@ file ids. A single file still requires `--path`.
|
|
|
70
70
|
|
|
71
71
|
Result content goes to **stdout**; status (`job <id> queued`, `saved …`) goes
|
|
72
72
|
to **stderr**. `-o auto|md|json|payload|id` picks the shape. `--json` is an
|
|
73
|
-
alias for `-o json`.
|
|
73
|
+
alias for `-o json`.
|
|
74
|
+
|
|
75
|
+
The default (`auto`) prints the full JSON response. When that is over
|
|
76
|
+
`$NDI_OUTPUT_LIMIT` characters (default 40,000), the compact text rendering
|
|
77
|
+
prints instead and stderr carries `note: output shortened …` with the full
|
|
78
|
+
size. `--json` always prints it whole, and `--save` / `--out-dir` always write
|
|
79
|
+
it whole. Commands whose output is the content itself (`read-file`,
|
|
80
|
+
`ws context`, parse markdown) print it as before. `--save PATH` / `--out-dir DIR` write files. `--async`
|
|
74
81
|
submits and prints the job id.
|
|
75
82
|
|
|
76
83
|
When a Parse result has external complete content, `-o auto` and `-o md` print
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ndi-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.16.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
|
+
"ndi-sdk>=0.31,<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.
|
|
3
|
+
version = "0.16.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.
|
|
30
|
+
"ndi-sdk>=0.31,<1",
|
|
31
31
|
"truststore>=0.10.4,<1",
|
|
32
32
|
]
|
|
33
33
|
|
|
@@ -17,6 +17,7 @@ from ndi_sdk.resources.tools import SyncTools
|
|
|
17
17
|
from ndi_cli import _docops, _jobs, _login, _tools, _workspace
|
|
18
18
|
from ndi_cli._config import ConfigError, resolve_settings
|
|
19
19
|
from ndi_cli._local import KG_ENV, SOCKET_ENV, TOKEN_ENV, LocalTransport
|
|
20
|
+
from ndi_cli._output import full_json, output_limit, print_full_or_summary
|
|
20
21
|
from ndi_cli._tools import GraphCommands, parse_pages
|
|
21
22
|
|
|
22
23
|
# qa-file runs a durable workflow that legitimately takes minutes; the metadata,
|
|
@@ -39,6 +40,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
39
40
|
if args.command is None:
|
|
40
41
|
parser.print_help()
|
|
41
42
|
return 2
|
|
43
|
+
try:
|
|
44
|
+
# Checked before any request: a job is billed before its output prints.
|
|
45
|
+
output_limit()
|
|
46
|
+
except ValueError as exc:
|
|
47
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
48
|
+
return 2
|
|
42
49
|
family = getattr(args, "family", None)
|
|
43
50
|
workspace_flag = getattr(args, "workspace", None)
|
|
44
51
|
if workspace_flag is not None and not workspace_flag.strip():
|
|
@@ -80,7 +87,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
80
87
|
timeout = getattr(args, "timeout", DEFAULT_TIMEOUT_SECONDS)
|
|
81
88
|
with NdiClient(api_key=settings.api_key, base_url=settings.base_url, timeout=timeout) as client:
|
|
82
89
|
if family == "tool":
|
|
83
|
-
return
|
|
90
|
+
return _print_tool_full(args, args.run(client.tools, settings.workspace_id, args))
|
|
84
91
|
return args.run(client, settings.workspace_id, args)
|
|
85
92
|
except ConfigError as exc:
|
|
86
93
|
print(f"error: {exc}", file=sys.stderr)
|
|
@@ -100,13 +107,21 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
100
107
|
|
|
101
108
|
|
|
102
109
|
def _print_tool(args: argparse.Namespace, response) -> int:
|
|
110
|
+
"""The text rendering. An agent run always gets it: the renderings are what its agents are evaluated against."""
|
|
103
111
|
if args.json:
|
|
104
|
-
print(response
|
|
112
|
+
print(full_json(response))
|
|
105
113
|
return 0
|
|
106
114
|
print(args.render(response))
|
|
107
115
|
return 0
|
|
108
116
|
|
|
109
117
|
|
|
118
|
+
def _print_tool_full(args: argparse.Namespace, response) -> int:
|
|
119
|
+
"""The full JSON response by default, unless the command's rendering is the content itself."""
|
|
120
|
+
if args.json or getattr(args, "verbatim", False):
|
|
121
|
+
return _print_tool(args, response)
|
|
122
|
+
return print_full_or_summary(response, args, lambda: args.render(response))
|
|
123
|
+
|
|
124
|
+
|
|
110
125
|
def _validation_error_lines(detail: object) -> list[str]:
|
|
111
126
|
"""Keep field constraints actionable without echoing request inputs or arbitrary detail."""
|
|
112
127
|
errors = detail.get("errors") if isinstance(detail, dict) else None
|
|
@@ -143,7 +158,9 @@ def _build_parser(*, graph: GraphCommands = "all") -> argparse.ArgumentParser:
|
|
|
143
158
|
),
|
|
144
159
|
)
|
|
145
160
|
common = argparse.ArgumentParser(add_help=False)
|
|
146
|
-
common.add_argument(
|
|
161
|
+
common.add_argument(
|
|
162
|
+
"--json", action="store_true", help="Always print the full response JSON, never the shortened text rendering."
|
|
163
|
+
)
|
|
147
164
|
common.add_argument("--workspace", metavar="ID", default=None, help="Override $NDI_WORKSPACE_ID for this call.")
|
|
148
165
|
common.add_argument(
|
|
149
166
|
"--timeout",
|
|
@@ -48,7 +48,7 @@ def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser
|
|
|
48
48
|
parse.add_argument("--format", dest="formats", default=None, help="Comma list: markdown,text,blocks.")
|
|
49
49
|
parse.add_argument("--table-format", choices=("html", "markdown"), default="html")
|
|
50
50
|
parse.add_argument("--chunk", choices=("none", "page", "section"), default="none")
|
|
51
|
-
parse.add_argument("--figures", choices=("omit", "include", "describe"), default="
|
|
51
|
+
parse.add_argument("--figures", choices=("omit", "include", "describe"), default="include")
|
|
52
52
|
parse.add_argument("--sheets", default=None, help="Comma-separated workbook sheet names.")
|
|
53
53
|
parse.add_argument("--password", default=None, help="PDF password. Never stored.")
|
|
54
54
|
parse.set_defaults(run=_run_parse, family="docops", needs_workspace=False, needs_client=True)
|
|
@@ -13,7 +13,7 @@ from ndi_sdk.models.jobs import Job
|
|
|
13
13
|
from ndi_sdk.resources.jobs import check_terminal
|
|
14
14
|
|
|
15
15
|
from ndi_cli import _render
|
|
16
|
-
from ndi_cli._output import add_job_output_args, emit_text, output_mode, render_job
|
|
16
|
+
from ndi_cli._output import add_job_output_args, emit_text, full_json, output_mode, render_job, shortened
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser) -> None:
|
|
@@ -107,15 +107,16 @@ def _run_list(client: NdiClient, workspace_id: str, args: argparse.Namespace) ->
|
|
|
107
107
|
limit=args.limit,
|
|
108
108
|
)
|
|
109
109
|
mode = output_mode(args)
|
|
110
|
-
if mode == "json":
|
|
111
|
-
return emit_text(page.model_dump_json(indent=2, exclude_none=True), args, default_stem="jobs", suffix=".json")
|
|
112
110
|
if mode in ("md", "payload", "id"):
|
|
113
111
|
# A listing is not a job, so these shapes have nothing to render. Say so
|
|
114
112
|
# instead of silently printing the default listing.
|
|
115
113
|
print(f"error: -o {mode} is not available for `jobs`; use auto or json", file=sys.stderr)
|
|
116
114
|
return 2
|
|
117
115
|
# Route through emit_text so --save / --out-dir work here as they do elsewhere.
|
|
118
|
-
|
|
116
|
+
full = full_json(page)
|
|
117
|
+
if mode == "auto" and shortened(full, args):
|
|
118
|
+
return emit_text(_render.job_page(page), args, default_stem="jobs", suffix=".txt")
|
|
119
|
+
return emit_text(full, args, default_stem="jobs", suffix=".json")
|
|
119
120
|
|
|
120
121
|
|
|
121
122
|
def _run_cancel(client: NdiClient, workspace_id: str, args: argparse.Namespace) -> int:
|
|
@@ -2,14 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import os
|
|
5
6
|
import sys
|
|
6
7
|
from collections.abc import Callable
|
|
7
8
|
from pathlib import Path
|
|
8
9
|
|
|
9
10
|
from ndi_sdk.models.jobs import IngestionResult, JetResult, Job, ParseResult
|
|
11
|
+
from pydantic import BaseModel
|
|
10
12
|
|
|
11
13
|
OUTPUT_CHOICES = ("auto", "md", "json", "payload", "id")
|
|
12
14
|
|
|
15
|
+
#: Default output is the full JSON response while it stays within this many
|
|
16
|
+
#: characters on stdout. Past it the compact text rendering stands in, so one
|
|
17
|
+
#: command cannot flood a reading agent's context; a stderr note says so.
|
|
18
|
+
FULL_OUTPUT_LIMIT = 40_000
|
|
19
|
+
OUTPUT_LIMIT_ENV = "NDI_OUTPUT_LIMIT"
|
|
20
|
+
|
|
13
21
|
|
|
14
22
|
def add_job_output_args(parser) -> None:
|
|
15
23
|
parser.add_argument(
|
|
@@ -17,7 +25,10 @@ def add_job_output_args(parser) -> None:
|
|
|
17
25
|
"--output",
|
|
18
26
|
choices=OUTPUT_CHOICES,
|
|
19
27
|
default="auto",
|
|
20
|
-
help=
|
|
28
|
+
help=(
|
|
29
|
+
f"Result shape. auto (default) prints the full JSON response, or the text rendering when that is over "
|
|
30
|
+
f"${OUTPUT_LIMIT_ENV} chars (default {FULL_OUTPUT_LIMIT:,}). --json is an alias for json."
|
|
31
|
+
),
|
|
21
32
|
)
|
|
22
33
|
parser.add_argument("--save", default=None, metavar="PATH", help="Write one result to this file.")
|
|
23
34
|
parser.add_argument("--out-dir", default=None, metavar="DIR", help="Write one file per input.")
|
|
@@ -30,6 +41,49 @@ def output_mode(args) -> str:
|
|
|
30
41
|
return getattr(args, "output", None) or "auto"
|
|
31
42
|
|
|
32
43
|
|
|
44
|
+
def full_json(model: BaseModel) -> str:
|
|
45
|
+
return model.model_dump_json(indent=2, exclude_none=True)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def shortened(full: str, args, *, get_full: str = "rerun with --json") -> bool:
|
|
49
|
+
"""Whether ``full`` is too long for stdout; if so, tell stderr how to get it.
|
|
50
|
+
|
|
51
|
+
A file target is never shortened — the limit protects a reader's context,
|
|
52
|
+
and ``--save`` / ``--out-dir`` asked for the whole thing. ``get_full`` names
|
|
53
|
+
the command that prints it whole.
|
|
54
|
+
"""
|
|
55
|
+
if getattr(args, "save", None) or getattr(args, "out_dir", None):
|
|
56
|
+
return False
|
|
57
|
+
limit = output_limit()
|
|
58
|
+
if len(full) <= limit:
|
|
59
|
+
return False
|
|
60
|
+
print(
|
|
61
|
+
f"note: output shortened — the full response is {len(full):,} chars, over the {limit:,} limit "
|
|
62
|
+
f"(${OUTPUT_LIMIT_ENV}); {get_full} for all of it",
|
|
63
|
+
file=sys.stderr,
|
|
64
|
+
)
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def print_full_or_summary(model: BaseModel, args, summary: Callable[[], str]) -> int:
|
|
69
|
+
full = full_json(model)
|
|
70
|
+
print(summary() if shortened(full, args) else full)
|
|
71
|
+
return 0
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def output_limit() -> int:
|
|
75
|
+
raw = os.environ.get(OUTPUT_LIMIT_ENV)
|
|
76
|
+
if raw is None or not raw.strip():
|
|
77
|
+
return FULL_OUTPUT_LIMIT
|
|
78
|
+
try:
|
|
79
|
+
limit = int(raw)
|
|
80
|
+
except ValueError:
|
|
81
|
+
limit = -1
|
|
82
|
+
if limit < 0:
|
|
83
|
+
raise ValueError(f"bad ${OUTPUT_LIMIT_ENV} {raw!r}: use a character count (0 always prints the text rendering)")
|
|
84
|
+
return limit
|
|
85
|
+
|
|
86
|
+
|
|
33
87
|
def emit_text(text: str, args, *, default_stem: str, suffix: str) -> int:
|
|
34
88
|
save = getattr(args, "save", None)
|
|
35
89
|
out_dir = getattr(args, "out_dir", None)
|
|
@@ -77,20 +131,27 @@ def render_job(job: Job, args, *, auto: Callable[[Job], str], stem: str) -> int:
|
|
|
77
131
|
print(job.job_id)
|
|
78
132
|
return _content_failure(job)
|
|
79
133
|
if mode == "json":
|
|
80
|
-
code = emit_text(job
|
|
134
|
+
code = emit_text(full_json(job), args, default_stem=stem, suffix=".json")
|
|
81
135
|
elif mode == "payload":
|
|
82
136
|
if job.result is None:
|
|
83
137
|
print("error: job has no result payload", file=sys.stderr)
|
|
84
138
|
return 1
|
|
85
|
-
code = emit_text(job.result
|
|
139
|
+
code = emit_text(full_json(job.result), args, default_stem=stem, suffix=".json")
|
|
86
140
|
elif mode == "md":
|
|
87
141
|
markdown = _parse_markdown(job)
|
|
88
142
|
if markdown is None:
|
|
89
143
|
print("error: this job has no markdown; use -o auto|json|payload|id", file=sys.stderr)
|
|
90
144
|
return 1
|
|
91
145
|
code = emit_text(markdown, args, default_stem=stem, suffix=".md")
|
|
92
|
-
|
|
146
|
+
elif isinstance(job.result, ParseResult):
|
|
147
|
+
# A parse renders the document itself, not a summary of the response,
|
|
148
|
+
# so it is never swapped for JSON.
|
|
93
149
|
code = emit_text(auto(job), args, default_stem=stem, suffix=_auto_suffix(job, auto))
|
|
150
|
+
# Rerunning a job command submits (and bills) a new job; reading this one does not.
|
|
151
|
+
elif shortened(full := full_json(job), args, get_full=f"`ndi job {job.job_id} --json`"):
|
|
152
|
+
code = emit_text(auto(job), args, default_stem=stem, suffix=".txt")
|
|
153
|
+
else:
|
|
154
|
+
code = emit_text(full, args, default_stem=stem, suffix=".json")
|
|
94
155
|
return code or _content_failure(job)
|
|
95
156
|
|
|
96
157
|
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
The audience is a coding agent reading terminal output: every fact the
|
|
4
4
|
response carries is either shown or explicitly counted, long free text is
|
|
5
5
|
kept whole (summaries and answers are the payload), and nothing is styled.
|
|
6
|
-
|
|
6
|
+
Outside an agent run they print only when the full JSON response is over the
|
|
7
|
+
output limit (``_output.FULL_OUTPUT_LIMIT``); ``--json`` always prints it whole.
|
|
7
8
|
"""
|
|
8
9
|
|
|
9
10
|
from __future__ import annotations
|
|
@@ -931,12 +932,15 @@ def search_job(job: Job) -> str:
|
|
|
931
932
|
|
|
932
933
|
|
|
933
934
|
def je_testing_job(job: Job) -> str:
|
|
934
|
-
"""
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
``
|
|
935
|
+
"""Whether the requested tests ran, then the answer and the numbers behind it.
|
|
936
|
+
|
|
937
|
+
A run that did not perform every requested test says so first, with each
|
|
938
|
+
test's outcome and blocker: its prose answer can read like a finding when
|
|
939
|
+
nothing was measured. Unlike :func:`search_job` it then leads with
|
|
940
|
+
``figures``: the answer is prose a person reads, and the figures are the
|
|
941
|
+
same content a reviewer can check. Each one names the receipt its numbers
|
|
942
|
+
are readable out of, so ``ndi job <id> -o json`` is the next step rather
|
|
943
|
+
than the only one.
|
|
940
944
|
"""
|
|
941
945
|
result = job.result
|
|
942
946
|
if not isinstance(result, JetResult):
|
|
@@ -944,6 +948,13 @@ def je_testing_job(job: Job) -> str:
|
|
|
944
948
|
lines: list[str] = []
|
|
945
949
|
if result.degraded:
|
|
946
950
|
lines.append(f"degraded: {result.degraded}")
|
|
951
|
+
if result.execution_status and result.execution_status != "performed":
|
|
952
|
+
lines.append(f"execution: {result.execution_status}")
|
|
953
|
+
for criterion in result.criteria:
|
|
954
|
+
outcome = criterion.status
|
|
955
|
+
if criterion.status == "incomplete":
|
|
956
|
+
outcome += f" ({criterion.blocker or criterion.incomplete_reason or 'no reason given'})"
|
|
957
|
+
lines.append(f" {criterion.description or criterion.criterion_id}: {outcome}")
|
|
947
958
|
if result.answer:
|
|
948
959
|
lines.append(result.answer.strip())
|
|
949
960
|
if result.population_statement:
|
|
@@ -964,8 +975,6 @@ def je_testing_job(job: Job) -> str:
|
|
|
964
975
|
for discrepancy in result.discrepancies:
|
|
965
976
|
lines.append(f"discrepancy: {discrepancy}")
|
|
966
977
|
lines.append(f"evidences ({len(result.evidences)}) | receipts ({len(result.sql_receipts)})")
|
|
967
|
-
if result.execution_status and result.execution_status != "performed":
|
|
968
|
-
lines.append(f"execution: {result.execution_status}")
|
|
969
978
|
if result.quality and result.quality.status != "clean":
|
|
970
979
|
lines.append(f"quality: {result.quality.status}")
|
|
971
980
|
if result.exhausted:
|
|
@@ -103,7 +103,9 @@ def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser
|
|
|
103
103
|
read.add_argument("--component", default=None, help="One tab or section, named as file-metadata lists it.")
|
|
104
104
|
read.add_argument("--pages", default=None, metavar="SPEC", help="1-based pages, e.g. 3 or 1-5,8. Not for spreadsheets.")
|
|
105
105
|
read.add_argument("--max-chars", type=int, default=None, help="Character budget for the returned text.")
|
|
106
|
-
read.set_defaults(
|
|
106
|
+
read.set_defaults(
|
|
107
|
+
run=_run_read_file, render=_render_read_file, verbatim=True, family="tool", needs_workspace=True, needs_client=True
|
|
108
|
+
)
|
|
107
109
|
|
|
108
110
|
ask_file = sub.add_parser(
|
|
109
111
|
WorkspaceCommand.ASK_FILE,
|
|
@@ -264,7 +266,9 @@ def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser
|
|
|
264
266
|
),
|
|
265
267
|
)
|
|
266
268
|
kg_answer.add_argument("query", help="The question, verbatim.")
|
|
267
|
-
kg_answer.set_defaults(
|
|
269
|
+
kg_answer.set_defaults(
|
|
270
|
+
run=_run_kg_answer, render=_render_kg_answer, verbatim=True, family="tool", needs_workspace=True, needs_client=True
|
|
271
|
+
)
|
|
268
272
|
|
|
269
273
|
|
|
270
274
|
def _run_folder_metadata(tools: SyncTools, workspace_id: str, args: argparse.Namespace):
|
|
@@ -18,7 +18,7 @@ from ndi_sdk.resources.jobs import check_terminal
|
|
|
18
18
|
from ndi_cli import _render
|
|
19
19
|
from ndi_cli._config import ConfigError, config_path, read_config_values, resolve_settings
|
|
20
20
|
from ndi_cli._jobs import wait_and_report, wait_job
|
|
21
|
-
from ndi_cli._output import add_job_output_args, emit_text, output_mode,
|
|
21
|
+
from ndi_cli._output import _content_failure, add_job_output_args, emit_text, full_json, output_mode, print_full_or_summary
|
|
22
22
|
from ndi_cli._sources import collect_workspace_uploads, folder_upload_prefix, workspace_dest_path
|
|
23
23
|
|
|
24
24
|
DEFAULT_UPLOAD_PARALLEL = 8
|
|
@@ -236,9 +236,8 @@ def _workspace_id(args: argparse.Namespace, fallback: str) -> str:
|
|
|
236
236
|
|
|
237
237
|
def _dump_or_text(model, args, *, text: str, stem: str) -> int:
|
|
238
238
|
if output_mode(args) == "json":
|
|
239
|
-
return emit_text(model
|
|
240
|
-
|
|
241
|
-
return 0
|
|
239
|
+
return emit_text(full_json(model), args, default_stem=stem, suffix=".json")
|
|
240
|
+
return print_full_or_summary(model, args, lambda: text)
|
|
242
241
|
|
|
243
242
|
|
|
244
243
|
def _finish_job(client: NdiClient, job: Job, args: argparse.Namespace, auto, stem: str) -> int:
|
|
@@ -270,7 +269,11 @@ def _run_ws_global_context(client: NdiClient, workspace_id: str, args: argparse.
|
|
|
270
269
|
workspace = client.workspaces.update(target, global_context=args.text)
|
|
271
270
|
else:
|
|
272
271
|
workspace = client.workspaces.get(target)
|
|
273
|
-
|
|
272
|
+
# The context text is the content asked for, not a summary of the workspace.
|
|
273
|
+
if output_mode(args) == "json":
|
|
274
|
+
return emit_text(full_json(workspace), args, default_stem="workspace", suffix=".json")
|
|
275
|
+
print(workspace.global_context or "(no global context)")
|
|
276
|
+
return 0
|
|
274
277
|
|
|
275
278
|
|
|
276
279
|
def _run_ws_stats(client: NdiClient, workspace_id: str, args: argparse.Namespace) -> int:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|