reindex-cli 0.3.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.
Files changed (53) hide show
  1. reindex_cli-0.3.0/PKG-INFO +94 -0
  2. reindex_cli-0.3.0/README.md +70 -0
  3. reindex_cli-0.3.0/pyproject.toml +46 -0
  4. reindex_cli-0.3.0/setup.cfg +4 -0
  5. reindex_cli-0.3.0/src/reindex_cli/__init__.py +6 -0
  6. reindex_cli-0.3.0/src/reindex_cli/__main__.py +3 -0
  7. reindex_cli-0.3.0/src/reindex_cli/api.py +69 -0
  8. reindex_cli-0.3.0/src/reindex_cli/archives.py +97 -0
  9. reindex_cli-0.3.0/src/reindex_cli/bundled_skills/reindex-create/SKILL.md +12 -0
  10. reindex_cli-0.3.0/src/reindex_cli/bundled_skills/reindex-data/SKILL.md +22 -0
  11. reindex_cli-0.3.0/src/reindex_cli/bundled_skills/reindex-scan/SKILL.md +13 -0
  12. reindex_cli-0.3.0/src/reindex_cli/cli.py +176 -0
  13. reindex_cli-0.3.0/src/reindex_cli/collection/__init__.py +9 -0
  14. reindex_cli-0.3.0/src/reindex_cli/collection/resolver.py +51 -0
  15. reindex_cli-0.3.0/src/reindex_cli/collection/state.py +88 -0
  16. reindex_cli-0.3.0/src/reindex_cli/config.py +49 -0
  17. reindex_cli-0.3.0/src/reindex_cli/errors.py +10 -0
  18. reindex_cli-0.3.0/src/reindex_cli/get_ops.py +183 -0
  19. reindex_cli-0.3.0/src/reindex_cli/manifest/__init__.py +4 -0
  20. reindex_cli-0.3.0/src/reindex_cli/manifest/models.py +31 -0
  21. reindex_cli-0.3.0/src/reindex_cli/manifest/parser.py +211 -0
  22. reindex_cli-0.3.0/src/reindex_cli/manifest/yaml_support.py +52 -0
  23. reindex_cli-0.3.0/src/reindex_cli/package/__init__.py +15 -0
  24. reindex_cli-0.3.0/src/reindex_cli/package/cards.py +41 -0
  25. reindex_cli-0.3.0/src/reindex_cli/package/renderer.py +190 -0
  26. reindex_cli-0.3.0/src/reindex_cli/package/validation.py +211 -0
  27. reindex_cli-0.3.0/src/reindex_cli/parsers/__init__.py +1 -0
  28. reindex_cli-0.3.0/src/reindex_cli/parsers/common.py +44 -0
  29. reindex_cli-0.3.0/src/reindex_cli/parsers/csv_parser.py +76 -0
  30. reindex_cli-0.3.0/src/reindex_cli/parsers/docling_pdf.py +222 -0
  31. reindex_cli-0.3.0/src/reindex_cli/parsers/generic.py +25 -0
  32. reindex_cli-0.3.0/src/reindex_cli/parsers/markdown.py +38 -0
  33. reindex_cli-0.3.0/src/reindex_cli/parsers/profiles.py +77 -0
  34. reindex_cli-0.3.0/src/reindex_cli/parsers/registry.py +49 -0
  35. reindex_cli-0.3.0/src/reindex_cli/pipeline/__init__.py +3 -0
  36. reindex_cli-0.3.0/src/reindex_cli/pipeline/assembly.py +172 -0
  37. reindex_cli-0.3.0/src/reindex_cli/pipeline/discovery.py +73 -0
  38. reindex_cli-0.3.0/src/reindex_cli/pipeline/inspection.py +124 -0
  39. reindex_cli-0.3.0/src/reindex_cli/pipeline/models.py +102 -0
  40. reindex_cli-0.3.0/src/reindex_cli/pipeline/parsing.py +63 -0
  41. reindex_cli-0.3.0/src/reindex_cli/pipeline/planning.py +54 -0
  42. reindex_cli-0.3.0/src/reindex_cli/pipeline/publish.py +26 -0
  43. reindex_cli-0.3.0/src/reindex_cli/pipeline/runner.py +188 -0
  44. reindex_cli-0.3.0/src/reindex_cli/remote_ops.py +109 -0
  45. reindex_cli-0.3.0/src/reindex_cli/remote_state.py +39 -0
  46. reindex_cli-0.3.0/src/reindex_cli/skills.py +92 -0
  47. reindex_cli-0.3.0/src/reindex_cli/util.py +50 -0
  48. reindex_cli-0.3.0/src/reindex_cli.egg-info/PKG-INFO +94 -0
  49. reindex_cli-0.3.0/src/reindex_cli.egg-info/SOURCES.txt +51 -0
  50. reindex_cli-0.3.0/src/reindex_cli.egg-info/dependency_links.txt +1 -0
  51. reindex_cli-0.3.0/src/reindex_cli.egg-info/entry_points.txt +3 -0
  52. reindex_cli-0.3.0/src/reindex_cli.egg-info/requires.txt +4 -0
  53. reindex_cli-0.3.0/src/reindex_cli.egg-info/top_level.txt +1 -0
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: reindex-cli
3
+ Version: 0.3.0
4
+ Summary: Compile local files into ReIndex packages.
5
+ Project-URL: Homepage, https://github.com/yechenyan/reIndex
6
+ Project-URL: Documentation, https://github.com/yechenyan/reIndex/tree/main/wiki
7
+ Project-URL: Repository, https://github.com/yechenyan/reIndex
8
+ Project-URL: Issues, https://github.com/yechenyan/reIndex/issues
9
+ Keywords: agents,cli,knowledge,search
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Text Processing :: Indexing
17
+ Classifier: Topic :: Utilities
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+ Requires-Dist: docling<3,>=2
21
+ Requires-Dist: httpx<1,>=0.27
22
+ Requires-Dist: pypdfium2<6,>=5
23
+ Requires-Dist: pyyaml<7,>=6
24
+
25
+ # ReIndex CLI
26
+
27
+ `rei` builds local files into ReIndex 1.0 packages and connects them to a ReIndex API. `reindex` is an alias.
28
+
29
+ ## Install and initialize
30
+
31
+ Give an AI Agent this sentence:
32
+
33
+ > Run `uv tool install --upgrade reindex-cli`, then run `rei init <data-directory> --agent <current-agent>` to create or update the ReIndex skills.
34
+
35
+ The installation is complete when `rei --help` works directly, without
36
+ `uv run`. `pipx install reindex-cli` and `python -m pip install reindex-cli`
37
+ are also supported. Python 3.12 or newer is required; the Docling dependency
38
+ makes the first installation and first PDF scan larger than a typical CLI.
39
+
40
+ `<current-agent>` is `codex`, `claude`, `cursor`, or `copilot`. `init` is idempotent, creates or reuses local identity, and installs or safely updates the three bundled skills. It does not scan, push, or download tutorial data.
41
+
42
+ ```bash
43
+ rei init <collection-dir> [--name <name>] [--agent codex]
44
+ rei rename <collection-dir> <new-name>
45
+ ```
46
+
47
+ ## Local commands
48
+
49
+ ```bash
50
+ rei inspect <path>
51
+ rei scan <path> [--collection-root <collection-dir>]
52
+ rei check <path>
53
+ ```
54
+
55
+ `inspect` is read-only. `scan` stages, validates, and atomically publishes a package. `check` verifies package resources, protected Node metadata, and current inputs.
56
+
57
+ For the normal author path, `push` runs the same package check again, so an
58
+ explicit `check` is mainly useful after manually editing cards or in CI:
59
+
60
+ ```bash
61
+ rei init <collection-dir> --name <name> --agent codex
62
+ rei inspect <collection-dir>
63
+ rei scan <collection-dir>
64
+ rei push <collection-dir> --api-url <api-url>
65
+ ```
66
+
67
+ ## Remote commands
68
+
69
+ ```bash
70
+ rei set-api <base-url>
71
+ rei push [path]
72
+ rei pull <name> [--output <directory>]
73
+ rei search "<query>" [--remote <name>] [--mode lexical|semantic|hybrid]
74
+ rei get <node-path> [--target card|source|content|asset]
75
+ rei get raw://<path>
76
+ ```
77
+
78
+ `push` synchronously sends the complete validated package and exactly referenced raw sources. `pull` downloads only the complete `.node.md` tree. `get` checks a complete local package, local authoring source, and the SHA-256 cache before downloading one exact resource.
79
+
80
+ Collection names are the user-facing remote identifier. UUIDs remain internal stable identity. Resource logical paths stay Collection-relative and are scoped by the internal Collection UUID.
81
+
82
+ ## Skills
83
+
84
+ ```bash
85
+ rei skills install --agent codex
86
+ rei skills update --agent codex
87
+ rei skills update --agent codex --force
88
+ ```
89
+
90
+ Normal updates overwrite only unmodified ReIndex-managed copies. A modified skill reports a conflict unless `--force` is explicit.
91
+
92
+ The CLI does not include or provision a hosted API. Remote commands use the
93
+ configured ReIndex API; without one, the default is the local development
94
+ endpoint at `http://127.0.0.1:8000`.
@@ -0,0 +1,70 @@
1
+ # ReIndex CLI
2
+
3
+ `rei` builds local files into ReIndex 1.0 packages and connects them to a ReIndex API. `reindex` is an alias.
4
+
5
+ ## Install and initialize
6
+
7
+ Give an AI Agent this sentence:
8
+
9
+ > Run `uv tool install --upgrade reindex-cli`, then run `rei init <data-directory> --agent <current-agent>` to create or update the ReIndex skills.
10
+
11
+ The installation is complete when `rei --help` works directly, without
12
+ `uv run`. `pipx install reindex-cli` and `python -m pip install reindex-cli`
13
+ are also supported. Python 3.12 or newer is required; the Docling dependency
14
+ makes the first installation and first PDF scan larger than a typical CLI.
15
+
16
+ `<current-agent>` is `codex`, `claude`, `cursor`, or `copilot`. `init` is idempotent, creates or reuses local identity, and installs or safely updates the three bundled skills. It does not scan, push, or download tutorial data.
17
+
18
+ ```bash
19
+ rei init <collection-dir> [--name <name>] [--agent codex]
20
+ rei rename <collection-dir> <new-name>
21
+ ```
22
+
23
+ ## Local commands
24
+
25
+ ```bash
26
+ rei inspect <path>
27
+ rei scan <path> [--collection-root <collection-dir>]
28
+ rei check <path>
29
+ ```
30
+
31
+ `inspect` is read-only. `scan` stages, validates, and atomically publishes a package. `check` verifies package resources, protected Node metadata, and current inputs.
32
+
33
+ For the normal author path, `push` runs the same package check again, so an
34
+ explicit `check` is mainly useful after manually editing cards or in CI:
35
+
36
+ ```bash
37
+ rei init <collection-dir> --name <name> --agent codex
38
+ rei inspect <collection-dir>
39
+ rei scan <collection-dir>
40
+ rei push <collection-dir> --api-url <api-url>
41
+ ```
42
+
43
+ ## Remote commands
44
+
45
+ ```bash
46
+ rei set-api <base-url>
47
+ rei push [path]
48
+ rei pull <name> [--output <directory>]
49
+ rei search "<query>" [--remote <name>] [--mode lexical|semantic|hybrid]
50
+ rei get <node-path> [--target card|source|content|asset]
51
+ rei get raw://<path>
52
+ ```
53
+
54
+ `push` synchronously sends the complete validated package and exactly referenced raw sources. `pull` downloads only the complete `.node.md` tree. `get` checks a complete local package, local authoring source, and the SHA-256 cache before downloading one exact resource.
55
+
56
+ Collection names are the user-facing remote identifier. UUIDs remain internal stable identity. Resource logical paths stay Collection-relative and are scoped by the internal Collection UUID.
57
+
58
+ ## Skills
59
+
60
+ ```bash
61
+ rei skills install --agent codex
62
+ rei skills update --agent codex
63
+ rei skills update --agent codex --force
64
+ ```
65
+
66
+ Normal updates overwrite only unmodified ReIndex-managed copies. A modified skill reports a conflict unless `--force` is explicit.
67
+
68
+ The CLI does not include or provision a hosted API. Remote commands use the
69
+ configured ReIndex API; without one, the default is the local development
70
+ endpoint at `http://127.0.0.1:8000`.
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "reindex-cli"
7
+ version = "0.3.0"
8
+ description = "Compile local files into ReIndex packages."
9
+ requires-python = ">=3.12"
10
+ readme = "README.md"
11
+ keywords = ["agents", "cli", "knowledge", "search"]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Environment :: Console",
15
+ "Intended Audience :: Developers",
16
+ "Operating System :: OS Independent",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Topic :: Text Processing :: Indexing",
20
+ "Topic :: Utilities",
21
+ ]
22
+ dependencies = [
23
+ "docling>=2,<3",
24
+ "httpx>=0.27,<1",
25
+ "pypdfium2>=5,<6",
26
+ "pyyaml>=6,<7",
27
+ ]
28
+
29
+ [project.urls]
30
+ Homepage = "https://github.com/yechenyan/reIndex"
31
+ Documentation = "https://github.com/yechenyan/reIndex/tree/main/wiki"
32
+ Repository = "https://github.com/yechenyan/reIndex"
33
+ Issues = "https://github.com/yechenyan/reIndex/issues"
34
+
35
+ [project.scripts]
36
+ rei = "reindex_cli.cli:main"
37
+ reindex = "reindex_cli.cli:main"
38
+
39
+ [tool.setuptools.package-dir]
40
+ "" = "src"
41
+
42
+ [tool.setuptools.packages.find]
43
+ where = ["src"]
44
+
45
+ [tool.setuptools.package-data]
46
+ reindex_cli = ["bundled_skills/*/SKILL.md"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,6 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ __version__ = version("reindex-cli")
5
+ except PackageNotFoundError: # Source-only execution without an installed distribution.
6
+ __version__ = "0.0.0+local"
@@ -0,0 +1,3 @@
1
+ from reindex_cli.cli import main
2
+
3
+ raise SystemExit(main())
@@ -0,0 +1,69 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import httpx
7
+
8
+ from reindex_cli.errors import ReIndexError
9
+
10
+
11
+ class ApiClient:
12
+ def __init__(self, base_url: str, timeout: float = 1800.0) -> None:
13
+ self.base_url = base_url.rstrip("/")
14
+ self.timeout = timeout
15
+
16
+ def health(self) -> dict:
17
+ with httpx.Client(base_url=self.base_url, timeout=30.0) as client:
18
+ return self._json(client.get("/health"))
19
+
20
+ def push(self, name: str, package: Path, sources: Path) -> dict:
21
+ with (
22
+ package.open("rb") as package_stream,
23
+ sources.open("rb") as source_stream,
24
+ httpx.Client(base_url=self.base_url, timeout=self.timeout) as client,
25
+ ):
26
+ response = client.post(
27
+ "/v1/push",
28
+ data={"name": name},
29
+ files={
30
+ "package": ("package.zip", package_stream, "application/zip"),
31
+ "sources": ("sources.zip", source_stream, "application/zip"),
32
+ },
33
+ )
34
+ return self._json(response)
35
+
36
+ def json(self, path: str, payload: dict) -> dict:
37
+ with httpx.Client(base_url=self.base_url, timeout=self.timeout) as client:
38
+ return self._json(client.post(path, json=payload))
39
+
40
+ def bytes(self, path: str, payload: dict) -> tuple[bytes, dict[str, str]]:
41
+ with httpx.Client(base_url=self.base_url, timeout=self.timeout) as client:
42
+ response = client.post(path, json=payload)
43
+ self._raise(response)
44
+ return response.content, dict(response.headers)
45
+
46
+ def _json(self, response: httpx.Response) -> dict:
47
+ self._raise(response)
48
+ try:
49
+ value = response.json()
50
+ except json.JSONDecodeError as error:
51
+ raise ReIndexError("Server returned invalid JSON") from error
52
+ if not isinstance(value, dict):
53
+ raise ReIndexError("Server returned an invalid response")
54
+ return value
55
+
56
+ @staticmethod
57
+ def _raise(response: httpx.Response) -> None:
58
+ if response.is_success:
59
+ return
60
+ try:
61
+ body = response.json()
62
+ detail = body.get("error", body)
63
+ message = detail.get("message") if isinstance(detail, dict) else None
64
+ except (json.JSONDecodeError, AttributeError):
65
+ message = None
66
+ raise ReIndexError(
67
+ f"Server request failed ({response.status_code}): "
68
+ f"{message or response.text or response.reason_phrase}"
69
+ )
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import tempfile
4
+ import zipfile
5
+ from pathlib import Path, PurePosixPath
6
+
7
+ from reindex_cli.errors import ReIndexError
8
+ from reindex_cli.package.cards import parse_card
9
+ from reindex_cli.util import sha256_file
10
+
11
+
12
+ def build_push_archives(context) -> tuple[Path, Path, tempfile.TemporaryDirectory]:
13
+ temporary = tempfile.TemporaryDirectory(prefix="rei-push-")
14
+ root = Path(temporary.name)
15
+ package_zip = root / "package.zip"
16
+ source_zip = root / "sources.zip"
17
+ with zipfile.ZipFile(package_zip, "w", zipfile.ZIP_DEFLATED) as bundle:
18
+ for file in sorted(context.output_dir.rglob("*")):
19
+ if file.is_file():
20
+ relative = Path(context.output_dir.name) / file.relative_to(
21
+ context.output_dir
22
+ )
23
+ bundle.write(file, relative.as_posix())
24
+ references = raw_references(context.output_dir)
25
+ with zipfile.ZipFile(source_zip, "w", zipfile.ZIP_DEFLATED) as bundle:
26
+ for relative, expected in sorted(references.items()):
27
+ source = context.root / relative
28
+ if not source.is_file() or sha256_file(source) != expected:
29
+ temporary.cleanup()
30
+ raise ReIndexError(f"Missing or changed source: {relative}")
31
+ bundle.write(source, relative)
32
+ return package_zip, source_zip, temporary
33
+
34
+
35
+ def raw_references(package: Path) -> dict[str, str]:
36
+ result: dict[str, str] = {}
37
+ for card_path in package.rglob("*.node.md"):
38
+ metadata, _body = parse_card(card_path)
39
+ values = [
40
+ metadata.get("source"),
41
+ metadata.get("content"),
42
+ *metadata.get("assets", []),
43
+ ]
44
+ for value in values:
45
+ if not isinstance(value, dict):
46
+ continue
47
+ uri = str(value.get("uri", ""))
48
+ if not uri.startswith("raw://"):
49
+ continue
50
+ relative = _safe(uri.removeprefix("raw://"))
51
+ digest = str(value.get("sha256", ""))
52
+ if relative in result and result[relative] != digest:
53
+ raise ReIndexError(f"Conflicting source hashes: {relative}")
54
+ result[relative] = digest
55
+ return result
56
+
57
+
58
+ def extract_node_archive(content: bytes, target: Path) -> int:
59
+ target.mkdir(parents=True, exist_ok=True)
60
+ archive_path = target / ".download.zip"
61
+ archive_path.write_bytes(content)
62
+ count = 0
63
+ try:
64
+ with zipfile.ZipFile(archive_path) as bundle:
65
+ names: set[str] = set()
66
+ for item in bundle.infolist():
67
+ path = PurePosixPath(item.filename)
68
+ if (
69
+ item.filename in names
70
+ or path.is_absolute()
71
+ or ".." in path.parts
72
+ or not item.filename.endswith(".node.md")
73
+ or (item.external_attr >> 16) & 0o170000 == 0o120000
74
+ ):
75
+ raise ReIndexError(
76
+ f"Unsafe or non-Node pull entry: {item.filename}"
77
+ )
78
+ names.add(item.filename)
79
+ if item.is_dir():
80
+ continue
81
+ destination = target.joinpath(*path.parts)
82
+ destination.parent.mkdir(parents=True, exist_ok=True)
83
+ destination.write_bytes(bundle.read(item))
84
+ parse_card(destination)
85
+ count += 1
86
+ finally:
87
+ archive_path.unlink(missing_ok=True)
88
+ if not (target / "index.node.md").is_file():
89
+ raise ReIndexError("Pulled Node tree has no root index.node.md")
90
+ return count
91
+
92
+
93
+ def _safe(value: str) -> str:
94
+ path = PurePosixPath(value)
95
+ if path.is_absolute() or ".." in path.parts or not path.parts:
96
+ raise ReIndexError(f"Unsafe raw path: {value}")
97
+ return path.as_posix()
@@ -0,0 +1,12 @@
1
+ ---
2
+ name: reindex-create
3
+ description: Initialize a ReIndex project when the user asks to set up ReIndex or run rei init/create.
4
+ ---
5
+
6
+ # ReIndex create
7
+
8
+ 1. Resolve the exact directory named by the user.
9
+ 2. Prefer `rei init <directory> --agent <current-agent>` for normal setup. It is idempotent and manages skills.
10
+ 3. Use `rei create <directory>` only when identity-only initialization was explicitly requested.
11
+ 4. Report the Collection name and whether it was created or reused. UUID is internal and normally omitted.
12
+ 5. Do not scan, push, delete source files, or replace `.rei/collection.json` during initialization.
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: reindex-data
3
+ description: Push, pull, search, and get exact ReIndex data when the user asks to publish or use remote ReIndex knowledge.
4
+ ---
5
+
6
+ # ReIndex data
7
+
8
+ ## Publish
9
+
10
+ 1. Run `rei check <path>` before `rei push <path>`; scan first if stale.
11
+ 2. `rei push` synchronously sends the validated package and exactly referenced sources.
12
+ 3. Report the user-facing Collection name and ready status, not internal UUID details.
13
+
14
+ ## Find and fetch
15
+
16
+ 1. Use `rei search "<question>"` before downloading large files.
17
+ 2. Select the result whose Evidence supports the task.
18
+ 3. Use the result's Node path with `rei get <node-path> --target content`; use `source` only when original bytes are required.
19
+ 4. Use `rei get raw://<path>` for an explicitly named raw source.
20
+ 5. Answer from the fetched file and cite the Collection name and Node or raw path.
21
+
22
+ `rei pull <name>` downloads the complete Node tree only. It intentionally excludes source, content and assets; fetch exact resources later with `rei get`.
@@ -0,0 +1,13 @@
1
+ ---
2
+ name: reindex-scan
3
+ description: Scan raw files into ReIndex when the user asks to scan, compile, ingest, or convert local data.
4
+ ---
5
+
6
+ # ReIndex scan
7
+
8
+ 1. Run `rei inspect <path>` and review effective inputs, relationships and changes.
9
+ 2. Apply only evidence-backed manifest corrections; never delete raw files just to pass validation.
10
+ 3. Run `rei scan <path>` and review changes, warnings and generated Node cards.
11
+ 4. Edit Markdown card bodies only. The CLI owns YAML frontmatter.
12
+ 5. Run `rei check <path>` after manual card edits.
13
+ 6. Report the Collection name, Node count, warnings and package location. Passing checks is not human approval.
@@ -0,0 +1,176 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import httpx
9
+
10
+ from reindex_cli import __version__
11
+ from reindex_cli.collection import create_collection, resolve_collection
12
+ from reindex_cli.config import set_api_url
13
+ from reindex_cli.errors import ReIndexError
14
+ from reindex_cli.skills import AGENTS, manage_skills
15
+
16
+
17
+ def build_parser() -> argparse.ArgumentParser:
18
+ parser = argparse.ArgumentParser(
19
+ prog="rei", description="Build and use ReIndex knowledge Collections."
20
+ )
21
+ parser.add_argument("--version", action="version", version=__version__)
22
+ commands = parser.add_subparsers(dest="command", required=True)
23
+ init = commands.add_parser("init", help="Initialize ReIndex and Agent skills.")
24
+ init.add_argument("path", type=Path, nargs="?", default=Path.cwd())
25
+ init.add_argument("--name")
26
+ init.add_argument("--agent", choices=AGENTS, default="codex")
27
+ init.add_argument("--codex-home", type=Path)
28
+ create = commands.add_parser("create", help="Create only local identity state.")
29
+ create.add_argument("path", type=Path)
30
+ create.add_argument("--name")
31
+ rename = commands.add_parser("rename", help="Change the Collection name.")
32
+ rename.add_argument("path", type=Path)
33
+ rename.add_argument("name")
34
+ inspect = commands.add_parser("inspect", help="Inspect inputs without writing.")
35
+ inspect.add_argument("path", type=Path)
36
+ scan = commands.add_parser("scan", help="Compile a validated local package.")
37
+ scan.add_argument("path", type=Path)
38
+ scan.add_argument("--collection-root", type=Path)
39
+ check = commands.add_parser("check", help="Validate the current package.")
40
+ check.add_argument("path", type=Path)
41
+ skills = commands.add_parser("skills", help="Manage bundled Agent skills.")
42
+ skill_commands = skills.add_subparsers(dest="skill_command", required=True)
43
+ for name in ("install", "update"):
44
+ command = skill_commands.add_parser(name)
45
+ command.add_argument("--agent", choices=AGENTS, default="codex")
46
+ command.add_argument("--workspace-root", type=Path, default=Path.cwd())
47
+ command.add_argument("--codex-home", type=Path)
48
+ command.add_argument("--force", action="store_true")
49
+ api = commands.add_parser("set-api", help="Persist the default API URL.")
50
+ api.add_argument("url")
51
+ push = commands.add_parser(
52
+ "push", help="Synchronously publish package and sources."
53
+ )
54
+ push.add_argument("path", type=Path, nargs="?", default=Path.cwd())
55
+ push.add_argument("--api-url")
56
+ pull = commands.add_parser("pull", help="Download a Node-only ReIndex tree.")
57
+ pull.add_argument("name")
58
+ pull.add_argument("--output", type=Path)
59
+ pull.add_argument("--api-url")
60
+ pull.add_argument("--force", action="store_true")
61
+ search = commands.add_parser("search", help="Search a remote Collection.")
62
+ search.add_argument("query")
63
+ search.add_argument("--path", type=Path, default=Path.cwd())
64
+ search.add_argument("--remote")
65
+ search.add_argument("--api-url")
66
+ search.add_argument(
67
+ "--mode", choices=("lexical", "semantic", "hybrid"), default="lexical"
68
+ )
69
+ search.add_argument("--limit", type=int, default=10)
70
+ get = commands.add_parser("get", help="Reuse or fetch one exact resource.")
71
+ get.add_argument("reference")
72
+ get.add_argument("--path", type=Path, default=Path.cwd())
73
+ get.add_argument("--remote")
74
+ get.add_argument("--api-url")
75
+ get.add_argument("--target", choices=("card", "source", "content", "asset"))
76
+ get.add_argument("--asset-ordinal", type=int)
77
+ get.add_argument("--output", type=Path)
78
+ return parser
79
+
80
+
81
+ def main(argv: list[str] | None = None) -> int:
82
+ try:
83
+ args = build_parser().parse_args(argv)
84
+ output = _execute(args)
85
+ print(json.dumps(output, ensure_ascii=False, indent=2))
86
+ return 0
87
+ except (ReIndexError, OSError, ValueError, httpx.HTTPError) as error:
88
+ print(
89
+ json.dumps({"status": "error", "error": str(error)}, ensure_ascii=False),
90
+ file=sys.stderr,
91
+ )
92
+ return 1
93
+
94
+
95
+ def _execute(args) -> dict:
96
+ if args.command == "init":
97
+ root = args.path.expanduser().resolve()
98
+ collection = create_collection(root, args.name)
99
+ skills = manage_skills(
100
+ args.agent,
101
+ root,
102
+ update=True,
103
+ codex_home=args.codex_home,
104
+ )
105
+ return {
106
+ "status": "ready",
107
+ **collection,
108
+ "collection_id": collection["id"],
109
+ "skills": [vars(item) for item in skills],
110
+ }
111
+ if args.command in {"create", "rename"}:
112
+ name = args.name
113
+ result = create_collection(args.path.expanduser().resolve(), name)
114
+ return {
115
+ "status": "ready",
116
+ **result,
117
+ "collection_id": result["id"],
118
+ "renamed": args.command == "rename",
119
+ }
120
+ if args.command == "skills":
121
+ results = manage_skills(
122
+ args.agent,
123
+ args.workspace_root.expanduser().resolve(),
124
+ update=args.skill_command == "update",
125
+ force=args.force,
126
+ codex_home=args.codex_home,
127
+ )
128
+ return {"status": "ready", "skills": [vars(item) for item in results]}
129
+ if args.command == "set-api":
130
+ return {"status": "ready", "api_url": set_api_url(args.url)}
131
+ if args.command in {"push", "pull", "search"}:
132
+ from reindex_cli.remote_ops import (
133
+ pull_collection,
134
+ push_collection,
135
+ search_remote,
136
+ )
137
+
138
+ if args.command == "push":
139
+ return push_collection(args.path, args.api_url)
140
+ if args.command == "pull":
141
+ return pull_collection(
142
+ args.name,
143
+ args.output or Path.cwd() / args.name,
144
+ args.api_url,
145
+ force=args.force,
146
+ )
147
+ if args.command == "search":
148
+ return search_remote(
149
+ args.query, args.path, args.remote, args.api_url, args.mode, args.limit
150
+ )
151
+ if args.command == "get":
152
+ from reindex_cli.get_ops import get_resource
153
+
154
+ return get_resource(
155
+ args.reference,
156
+ args.path,
157
+ target=args.target,
158
+ asset_ordinal=args.asset_ordinal,
159
+ output=args.output,
160
+ remote=args.remote,
161
+ api_url=args.api_url,
162
+ )
163
+ from reindex_cli.pipeline.runner import (
164
+ check_collection,
165
+ inspect_collection,
166
+ run_scan,
167
+ )
168
+
169
+ context = resolve_collection(
170
+ args.path, args.collection_root if args.command == "scan" else None
171
+ )
172
+ if args.command == "inspect":
173
+ return inspect_collection(context)
174
+ if args.command == "scan":
175
+ return run_scan(context)
176
+ return check_collection(context)
@@ -0,0 +1,9 @@
1
+ from .resolver import CollectionContext, resolve_collection
2
+ from .state import create_collection, load_collection
3
+
4
+ __all__ = [
5
+ "CollectionContext",
6
+ "create_collection",
7
+ "load_collection",
8
+ "resolve_collection",
9
+ ]