ndi-cli 0.6.0__tar.gz → 0.8.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.6.0 → ndi_cli-0.8.0}/PKG-INFO +14 -3
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/README.md +12 -1
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/pyproject.toml +2 -2
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/pyproject.toml.orig +2 -2
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/__init__.py +1 -1
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/_output.py +8 -1
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/_render.py +170 -0
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/_workspace.py +64 -0
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/LICENSE +0 -0
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/_cli.py +0 -0
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/_config.py +0 -0
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/_docops.py +0 -0
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/_entrypoint.py +0 -0
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/_jobs.py +0 -0
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/_local.py +0 -0
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/_login.py +0 -0
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/_sources.py +0 -0
- {ndi_cli-0.6.0 → ndi_cli-0.8.0}/src/ndi_cli/_tools.py +0 -0
- {ndi_cli-0.6.0 → ndi_cli-0.8.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.8.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.22,<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
|
|
@@ -56,6 +56,8 @@ config location. Workspace-scoped verbs also take `--workspace ID`.
|
|
|
56
56
|
| `ndi files upload\|list\|get\|delete` | Workspace files (`--ingest` uploads then queues ingestion) |
|
|
57
57
|
| `ndi ingest` | Queue ingestion |
|
|
58
58
|
| `ndi deep-search QUERY` / `ndi fact-search QUERY` | Agentic / single-shot search |
|
|
59
|
+
| `ndi filtered-search QUERY` | Corpus filter/rank/count over the metadata catalog (`--cursor` pages an earlier run) |
|
|
60
|
+
| `ndi je-testing QUERY` | Journal-entry testing over a ledger package (`--path` to scope it, `--effort` for the level) |
|
|
59
61
|
| `ndi folder-metadata` / `file-metadata` / `read-file` / `ask-file` / `run-sql` / `hybrid-search` | Read-only workspace tools (the sandbox surface) |
|
|
60
62
|
|
|
61
63
|
## Sources
|
|
@@ -81,8 +83,17 @@ to **stderr**. `-o auto|md|json|payload|id` picks the shape. `--json` is an
|
|
|
81
83
|
alias for `-o json`. `--save PATH` / `--out-dir DIR` write files. `--async`
|
|
82
84
|
submits and prints the job id.
|
|
83
85
|
|
|
86
|
+
When a Parse result has external complete content, `-o auto` and `-o md` print
|
|
87
|
+
the inline preview. Use `-o json` or `-o payload` to inspect its disclosure and
|
|
88
|
+
authenticated full-content URL. A final-size preview carries
|
|
89
|
+
`content_truncated`; reduced-layout Office parsing carries `parse_fidelity`.
|
|
90
|
+
Documents use `document.content_url`; workbooks use the affected
|
|
91
|
+
`document.spreadsheet.sheets[].content_url`. The CLI does not download these
|
|
92
|
+
URLs automatically.
|
|
93
|
+
|
|
84
94
|
API failures exit 1. Usage / config errors exit 2. Schema-validation
|
|
85
95
|
failures also show up to five field constraints, each limited to 500
|
|
86
96
|
characters; request input and unrelated error-detail fields are omitted.
|
|
87
97
|
|
|
88
|
-
See
|
|
98
|
+
See the [CLI guide](https://docs.ndi.nace.ai/guides/cli) for the full command
|
|
99
|
+
reference. `SKILL.md` is the agent-facing guide to the six workspace tools.
|
|
@@ -32,6 +32,8 @@ config location. Workspace-scoped verbs also take `--workspace ID`.
|
|
|
32
32
|
| `ndi files upload\|list\|get\|delete` | Workspace files (`--ingest` uploads then queues ingestion) |
|
|
33
33
|
| `ndi ingest` | Queue ingestion |
|
|
34
34
|
| `ndi deep-search QUERY` / `ndi fact-search QUERY` | Agentic / single-shot search |
|
|
35
|
+
| `ndi filtered-search QUERY` | Corpus filter/rank/count over the metadata catalog (`--cursor` pages an earlier run) |
|
|
36
|
+
| `ndi je-testing QUERY` | Journal-entry testing over a ledger package (`--path` to scope it, `--effort` for the level) |
|
|
35
37
|
| `ndi folder-metadata` / `file-metadata` / `read-file` / `ask-file` / `run-sql` / `hybrid-search` | Read-only workspace tools (the sandbox surface) |
|
|
36
38
|
|
|
37
39
|
## Sources
|
|
@@ -57,8 +59,17 @@ to **stderr**. `-o auto|md|json|payload|id` picks the shape. `--json` is an
|
|
|
57
59
|
alias for `-o json`. `--save PATH` / `--out-dir DIR` write files. `--async`
|
|
58
60
|
submits and prints the job id.
|
|
59
61
|
|
|
62
|
+
When a Parse result has external complete content, `-o auto` and `-o md` print
|
|
63
|
+
the inline preview. Use `-o json` or `-o payload` to inspect its disclosure and
|
|
64
|
+
authenticated full-content URL. A final-size preview carries
|
|
65
|
+
`content_truncated`; reduced-layout Office parsing carries `parse_fidelity`.
|
|
66
|
+
Documents use `document.content_url`; workbooks use the affected
|
|
67
|
+
`document.spreadsheet.sheets[].content_url`. The CLI does not download these
|
|
68
|
+
URLs automatically.
|
|
69
|
+
|
|
60
70
|
API failures exit 1. Usage / config errors exit 2. Schema-validation
|
|
61
71
|
failures also show up to five field constraints, each limited to 500
|
|
62
72
|
characters; request input and unrelated error-detail fields are omitted.
|
|
63
73
|
|
|
64
|
-
See
|
|
74
|
+
See the [CLI guide](https://docs.ndi.nace.ai/guides/cli) for the full command
|
|
75
|
+
reference. `SKILL.md` is the agent-facing guide to the six workspace tools.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ndi-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.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.22,<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.8.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.22,<1",
|
|
31
31
|
"truststore>=0.10.4,<1",
|
|
32
32
|
]
|
|
33
33
|
|
|
@@ -6,7 +6,7 @@ import sys
|
|
|
6
6
|
from collections.abc import Callable
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
|
|
9
|
-
from ndi_sdk.models.jobs import IngestionResult, Job, ParseResult
|
|
9
|
+
from ndi_sdk.models.jobs import IngestionResult, JetResult, Job, ParseResult
|
|
10
10
|
|
|
11
11
|
OUTPUT_CHOICES = ("auto", "md", "json", "payload", "id")
|
|
12
12
|
|
|
@@ -108,6 +108,13 @@ def _content_failure(job: Job) -> int:
|
|
|
108
108
|
file=sys.stderr,
|
|
109
109
|
)
|
|
110
110
|
return 1
|
|
111
|
+
# A degraded JET run is the same shape: succeeded, but the procedure broke
|
|
112
|
+
# before it could finalize an answer, so `figures` is empty and only the
|
|
113
|
+
# working survives. `exhausted` and `partially_performed` stay zero — both
|
|
114
|
+
# are documented as successful partial answers, not failed work.
|
|
115
|
+
if isinstance(result, JetResult) and result.degraded:
|
|
116
|
+
print(f"error: journal-entry testing degraded: {result.degraded}", file=sys.stderr)
|
|
117
|
+
return 1
|
|
111
118
|
return 0
|
|
112
119
|
|
|
113
120
|
|
|
@@ -21,9 +21,11 @@ from ndi_sdk.models.jobs import (
|
|
|
21
21
|
ClassifyResult,
|
|
22
22
|
DeepSearchV2Result,
|
|
23
23
|
ExtractResult,
|
|
24
|
+
FilteredSearchResult,
|
|
24
25
|
GroundResult,
|
|
25
26
|
IngestionResult,
|
|
26
27
|
IntelligentSearchResult,
|
|
28
|
+
JetResult,
|
|
27
29
|
Job,
|
|
28
30
|
ParseResult,
|
|
29
31
|
SplitResult,
|
|
@@ -662,6 +664,174 @@ def search_job(job: Job) -> str:
|
|
|
662
664
|
return job_line(job)
|
|
663
665
|
|
|
664
666
|
|
|
667
|
+
def je_testing_job(job: Job) -> str:
|
|
668
|
+
"""The answer, then the numbers behind it — a JET result read at a glance.
|
|
669
|
+
|
|
670
|
+
Unlike :func:`search_job` this leads with ``figures``: the answer is prose a
|
|
671
|
+
person reads, and the figures are the same content a reviewer can check.
|
|
672
|
+
Each one names the receipt its numbers are readable out of, so
|
|
673
|
+
``ndi job <id> -o json`` is the next step rather than the only one.
|
|
674
|
+
"""
|
|
675
|
+
result = job.result
|
|
676
|
+
if not isinstance(result, JetResult):
|
|
677
|
+
return job_line(job)
|
|
678
|
+
lines: list[str] = []
|
|
679
|
+
if result.degraded:
|
|
680
|
+
lines.append(f"degraded: {result.degraded}")
|
|
681
|
+
if result.answer:
|
|
682
|
+
lines.append(result.answer.strip())
|
|
683
|
+
if result.population_statement:
|
|
684
|
+
lines.append(f"population: {result.population_statement}")
|
|
685
|
+
if result.figures:
|
|
686
|
+
lines.append(f"figures ({len(result.figures)}):")
|
|
687
|
+
for figure in result.figures:
|
|
688
|
+
if figure.cannot_be_performed:
|
|
689
|
+
lines.append(f" {figure.criterion}: not performed")
|
|
690
|
+
else:
|
|
691
|
+
value = f" | {figure.value}" if figure.value else ""
|
|
692
|
+
lines.append(f" {figure.criterion}: {figure.count}/{figure.population}{value}")
|
|
693
|
+
# The exception listing is the receipt worth opening: an unfiltered
|
|
694
|
+
# count points at every row, a listing points at the rows that failed.
|
|
695
|
+
receipt = figure.exception_listing_result_id or next(iter(figure.result_ids), None)
|
|
696
|
+
if receipt:
|
|
697
|
+
lines.append(f" receipt {receipt}")
|
|
698
|
+
for discrepancy in result.discrepancies:
|
|
699
|
+
lines.append(f"discrepancy: {discrepancy}")
|
|
700
|
+
lines.append(f"evidences ({len(result.evidences)}) | receipts ({len(result.sql_receipts)})")
|
|
701
|
+
if result.execution_status and result.execution_status != "performed":
|
|
702
|
+
lines.append(f"execution: {result.execution_status}")
|
|
703
|
+
if result.quality and result.quality.status != "clean":
|
|
704
|
+
lines.append(f"quality: {result.quality.status}")
|
|
705
|
+
if result.exhausted:
|
|
706
|
+
lines.append("exhausted: step budget ran out before the procedure finished")
|
|
707
|
+
return "\n".join(lines)
|
|
708
|
+
|
|
709
|
+
|
|
710
|
+
#: What a cell's non-``found`` status means, glossed for a reader who has only
|
|
711
|
+
#: this text. A field nothing could be read from is not an absent one, and
|
|
712
|
+
#: neither is one that resisted normalizing — keeping them distinct is what
|
|
713
|
+
#: makes a thin row interpretable without opening the JSON.
|
|
714
|
+
_CELL_STATUS = {
|
|
715
|
+
"not_found": "examined; field absent",
|
|
716
|
+
"ambiguous": "present, not normalizable",
|
|
717
|
+
"unexamined": "extraction could not say",
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def _cell_value(cell) -> str:
|
|
722
|
+
value = cell.value
|
|
723
|
+
if value is None:
|
|
724
|
+
return _CELL_STATUS.get(cell.status, cell.status)
|
|
725
|
+
match value.kind:
|
|
726
|
+
case "money":
|
|
727
|
+
# Amounts keep their own currency here because the server never
|
|
728
|
+
# converts one either; comparing across currencies is unsupported,
|
|
729
|
+
# so dropping the code would invent a comparison.
|
|
730
|
+
assumed = " (currency assumed)" if value.currency_source == "assumed" else ""
|
|
731
|
+
return f"{value.currency} {value.amount}{assumed}"
|
|
732
|
+
case "text":
|
|
733
|
+
return _clip(value.text)
|
|
734
|
+
case "date":
|
|
735
|
+
return value.date
|
|
736
|
+
case "number":
|
|
737
|
+
return value.number
|
|
738
|
+
case "boolean":
|
|
739
|
+
return "true" if value.value else "false"
|
|
740
|
+
return value.kind
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
def _page_range(row) -> str:
|
|
744
|
+
if row.page_start is None:
|
|
745
|
+
return ""
|
|
746
|
+
if row.page_end is not None and row.page_end != row.page_start:
|
|
747
|
+
return f" (pages {row.page_start}-{row.page_end})"
|
|
748
|
+
return f" (page {row.page_start})"
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def _filtered_rows(result: FilteredSearchResult) -> list[str]:
|
|
752
|
+
"""The rows, carrying the columns their membership hinges on.
|
|
753
|
+
|
|
754
|
+
A count-style question confirms ``total_matches`` and returns no rows at
|
|
755
|
+
all, so an empty listing is an answer rather than a gap — it prints nothing
|
|
756
|
+
instead of an empty header.
|
|
757
|
+
"""
|
|
758
|
+
if not result.rows:
|
|
759
|
+
return []
|
|
760
|
+
# The server marks which columns its published query filtered or ranked on,
|
|
761
|
+
# and those are the values every row's membership depends on; `requested` is
|
|
762
|
+
# what the reader asked to see. Everything else is context, left for --json.
|
|
763
|
+
hinges = [column.field for column in result.columns if column.filtered]
|
|
764
|
+
shown = hinges + [column.field for column in result.columns if column.requested and column.field not in hinges]
|
|
765
|
+
lines = [f"rows ({len(result.rows)}):"]
|
|
766
|
+
for row in result.rows:
|
|
767
|
+
where = f" [{row.component}]" if row.component else ""
|
|
768
|
+
lines.append(f" {row.source_path}{where}{_page_range(row)}")
|
|
769
|
+
for field in shown or list(dict.fromkeys(cell.field for cell in row.cells)):
|
|
770
|
+
# A repeated column carries one cell per item, so join them rather
|
|
771
|
+
# than showing the first and implying the field held one value.
|
|
772
|
+
values = [_cell_value(cell) for cell in row.cells if cell.field == field]
|
|
773
|
+
if values:
|
|
774
|
+
lines.append(f" {field}: {'; '.join(values)}")
|
|
775
|
+
return lines
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def _filtered_caveats(result: FilteredSearchResult) -> list[str]:
|
|
779
|
+
lines: list[str] = []
|
|
780
|
+
if not result.exhaustive:
|
|
781
|
+
lines.append("not exhaustive: the matches are valid, but total_matches is a floor and not a census")
|
|
782
|
+
if result.truncated:
|
|
783
|
+
lines.append("truncated: the published query matched more than this page carries")
|
|
784
|
+
if result.catalog_drifted:
|
|
785
|
+
lines.append("catalog drifted: the underlying data changed since the query first ran")
|
|
786
|
+
# `exhaustive` is scope-wide and almost always false, so it cannot say *what*
|
|
787
|
+
# was incomplete. Only a resolved value satisfies a predicate, so a field the
|
|
788
|
+
# extraction could not read drops its components silently — a correct query
|
|
789
|
+
# coming back thin for a reason unrelated to the question.
|
|
790
|
+
for entry in result.field_readability:
|
|
791
|
+
read = f"{entry.readable} of {entry.in_scope} values read"
|
|
792
|
+
if entry.readable == 0:
|
|
793
|
+
lines.append(f"unreadable: {entry.field} — {read}; a thin result here reflects that, not an absence")
|
|
794
|
+
else:
|
|
795
|
+
lines.append(f"partly readable: {entry.field} — {read}")
|
|
796
|
+
check = result.content_verification
|
|
797
|
+
if check and check.unverified_candidates:
|
|
798
|
+
lines.append(
|
|
799
|
+
f"content check: {check.unverified_candidates} of {check.candidates} candidates unverified "
|
|
800
|
+
f"for {_clip(check.condition, 120)!r} — unknown is not a non-match"
|
|
801
|
+
)
|
|
802
|
+
lines.extend(_coverage_lines(result.coverage))
|
|
803
|
+
if result.exhausted:
|
|
804
|
+
lines.append("exhausted: step budget ran out before the query finished")
|
|
805
|
+
return lines
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
def filtered_search_job(job: Job) -> str:
|
|
809
|
+
"""The totals, then the rows behind them — a catalog answer read at a glance.
|
|
810
|
+
|
|
811
|
+
A filter/rank/count question is answered by a number, so this leads with the
|
|
812
|
+
counts and prints the published query under them: the figure and the
|
|
813
|
+
statement it came out of travel together, the way a JET figure names its
|
|
814
|
+
receipt. Rows carry the columns the matches hinge on; ``-o json`` has every
|
|
815
|
+
cell, citation and column the catalog returned.
|
|
816
|
+
"""
|
|
817
|
+
result = job.result
|
|
818
|
+
if not isinstance(result, FilteredSearchResult):
|
|
819
|
+
return job_line(job)
|
|
820
|
+
lines: list[str] = []
|
|
821
|
+
if result.interpretation:
|
|
822
|
+
lines.append(f"interpretation: {_clip(result.interpretation)}")
|
|
823
|
+
files = f" ({result.total_files} files)" if result.total_files is not None else ""
|
|
824
|
+
confidence = f" | confidence: {result.confidence}" if result.confidence else ""
|
|
825
|
+
lines.append(f"matches: {result.total_matches}{files}{confidence}")
|
|
826
|
+
if result.applied_query:
|
|
827
|
+
lines.append(f"query: {_clip(result.applied_query, 300)}")
|
|
828
|
+
lines.extend(_filtered_rows(result))
|
|
829
|
+
lines.extend(_filtered_caveats(result))
|
|
830
|
+
if result.next_cursor:
|
|
831
|
+
lines.append(f"next: {result.next_cursor}")
|
|
832
|
+
return "\n".join(lines)
|
|
833
|
+
|
|
834
|
+
|
|
665
835
|
def _search_result(answer: str | None, clarification: str | None, evidences: list, *, receipts: int) -> str:
|
|
666
836
|
lines: list[str] = []
|
|
667
837
|
if clarification:
|
|
@@ -95,6 +95,33 @@ def add_parsers(sub: argparse._SubParsersAction, common: argparse.ArgumentParser
|
|
|
95
95
|
add_job_output_args(fact)
|
|
96
96
|
fact.set_defaults(run=_run_fact_search, family="workspace", needs_workspace=True, needs_client=True)
|
|
97
97
|
|
|
98
|
+
filtered = sub.add_parser(
|
|
99
|
+
"filtered-search", parents=[common], help="Run a filter/rank/count query over the metadata catalog."
|
|
100
|
+
)
|
|
101
|
+
filtered.add_argument("query", nargs="?", default=None)
|
|
102
|
+
filtered.add_argument(
|
|
103
|
+
"--cursor", default=None, help="Page an earlier run: paste its result's next_cursor. A page is a new job."
|
|
104
|
+
)
|
|
105
|
+
filtered.add_argument("--prefix", default=None, dest="path_prefix")
|
|
106
|
+
filtered.add_argument("--context", default=None)
|
|
107
|
+
# No --result-unit: the server deprecates the grouping override and returns
|
|
108
|
+
# catalog entries. No --allow-clarification: a clarification is answered by a
|
|
109
|
+
# new initial query, and a shell has nowhere to keep the thread — the reason
|
|
110
|
+
# deep-search omits --session-id.
|
|
111
|
+
add_job_output_args(filtered)
|
|
112
|
+
filtered.set_defaults(run=_run_filtered_search, family="workspace", needs_workspace=True, needs_client=True)
|
|
113
|
+
|
|
114
|
+
jet = sub.add_parser("je-testing", parents=[common], help="Run journal-entry testing over a ledger package.")
|
|
115
|
+
jet.add_argument("query")
|
|
116
|
+
jet.add_argument("--effort", choices=("low", "medium", "high"), default=None)
|
|
117
|
+
jet.add_argument("--prefix", default=None, dest="path_prefix")
|
|
118
|
+
jet.add_argument("--path", action="append", dest="paths", default=None, help="Scope to this file; repeatable.")
|
|
119
|
+
jet.add_argument("--context", default=None)
|
|
120
|
+
# No --session-id, for the reason deep-search omits it: a shell has nowhere
|
|
121
|
+
# to keep a thread. Omitting --effort leaves the deployment's own floor.
|
|
122
|
+
add_job_output_args(jet)
|
|
123
|
+
jet.set_defaults(run=_run_je_testing, family="workspace", needs_workspace=True, needs_client=True)
|
|
124
|
+
|
|
98
125
|
|
|
99
126
|
def _workspace_id(args: argparse.Namespace, fallback: str) -> str:
|
|
100
127
|
value = getattr(args, "workspace_id", None) or args.workspace or fallback
|
|
@@ -224,3 +251,40 @@ def _run_fact_search(client: NdiClient, workspace_id: str, args: argparse.Namesp
|
|
|
224
251
|
top_k=args.top_k,
|
|
225
252
|
)
|
|
226
253
|
return _finish_job(client, job, args, _render.search_job, "fact-search")
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def _run_filtered_search(client: NdiClient, workspace_id: str, args: argparse.Namespace) -> int:
|
|
257
|
+
if args.query and args.cursor:
|
|
258
|
+
print("error: pass a query or --cursor, not both", file=sys.stderr)
|
|
259
|
+
return 2
|
|
260
|
+
if not args.query and not args.cursor:
|
|
261
|
+
print("error: pass a query, or --cursor to page an earlier run", file=sys.stderr)
|
|
262
|
+
return 2
|
|
263
|
+
if args.cursor:
|
|
264
|
+
# A page re-executes the parent's own stored query, so the scoping flags
|
|
265
|
+
# cannot apply to it. Accepting and dropping them would quietly answer a
|
|
266
|
+
# different question than the one typed.
|
|
267
|
+
if args.path_prefix or args.context:
|
|
268
|
+
print("error: --prefix and --context belong to the initial query, not a page", file=sys.stderr)
|
|
269
|
+
return 2
|
|
270
|
+
job = client.search.filtered_page(workspace_id, cursor=args.cursor)
|
|
271
|
+
else:
|
|
272
|
+
job = client.search.filtered(
|
|
273
|
+
workspace_id,
|
|
274
|
+
query=args.query,
|
|
275
|
+
context=args.context,
|
|
276
|
+
path_prefix=args.path_prefix,
|
|
277
|
+
)
|
|
278
|
+
return _finish_job(client, job, args, _render.filtered_search_job, "filtered-search")
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _run_je_testing(client: NdiClient, workspace_id: str, args: argparse.Namespace) -> int:
|
|
282
|
+
job = client.search.je_testing(
|
|
283
|
+
workspace_id,
|
|
284
|
+
query=args.query,
|
|
285
|
+
context=args.context,
|
|
286
|
+
effort=args.effort,
|
|
287
|
+
path_prefix=args.path_prefix,
|
|
288
|
+
paths=args.paths,
|
|
289
|
+
)
|
|
290
|
+
return _finish_job(client, job, args, _render.je_testing_job, "je-testing")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|