cocoindex-code-plus 0.1.0__py3-none-any.whl

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.
@@ -0,0 +1,384 @@
1
+ # © 2025 CocoIndex Inc. All rights reserved.
2
+ # SPDX-License-Identifier: LicenseRef-CocoIndex-Proprietary
3
+ """The ``ccx`` client CLI.
4
+
5
+ Commands stay close to the CocoIndex Code open source edition, but talk to a
6
+ remote query server over HTTP instead of a local daemon.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ import subprocess
13
+ from collections.abc import Callable
14
+ from typing import TypeVar
15
+
16
+ import httpx
17
+ import typer
18
+
19
+ from . import client, settings
20
+ from ._version import __version__
21
+
22
+ T = TypeVar("T")
23
+
24
+ # Matches the namespace/repo path in a GitHub or GitLab remote URL (SSH or
25
+ # HTTPS, optional trailing `.git`). The path may be nested for GitLab subgroups,
26
+ # e.g. `git@gitlab.com:group/subgroup/project.git` -> `group/subgroup/project`;
27
+ # `https://github.com/owner/repo` -> `owner/repo`.
28
+ _REMOTE_RE = re.compile(r"(?:github|gitlab)\.com[:/](?P<path>.+?)(?:\.git)?/?$")
29
+
30
+
31
+ def _parse_remote_url(url: str) -> str | None:
32
+ """Extract the ``<namespace>/<repo>`` path from a GitHub/GitLab remote URL,
33
+ or ``None`` if it isn't one. The server resolves this name to a ``repo_key``."""
34
+ match = _REMOTE_RE.search(url)
35
+ return match["path"] if match else None
36
+
37
+
38
+ def _detect_local_repo() -> str | None:
39
+ """Return the ``<namespace>/<repo>`` of the CWD's ``origin`` remote (GitHub
40
+ or GitLab), or ``None`` if not in a git checkout / the remote isn't one of
41
+ those hosts."""
42
+ try:
43
+ proc = subprocess.run(
44
+ ["git", "remote", "get-url", "origin"],
45
+ capture_output=True,
46
+ text=True,
47
+ timeout=5,
48
+ check=True,
49
+ )
50
+ except (OSError, subprocess.SubprocessError):
51
+ return None
52
+ return _parse_remote_url(proc.stdout.strip())
53
+
54
+
55
+ app = typer.Typer(
56
+ name="ccx",
57
+ help="CocoIndex Code Plus — query indexed codebases from the command line.",
58
+ no_args_is_help=True,
59
+ )
60
+
61
+
62
+ @app.callback()
63
+ def _load_env() -> None:
64
+ """Auto-load .env (non-overriding) before running any command."""
65
+ settings.load_env()
66
+
67
+
68
+ @app.command()
69
+ def status(
70
+ server: str | None = typer.Option(
71
+ None,
72
+ "--server",
73
+ help="Query server base URL (defaults to $CCX_SERVER_URL).",
74
+ ),
75
+ ) -> None:
76
+ """Check whether the query server is healthy."""
77
+ base_url = server or settings.server_url()
78
+ try:
79
+ result = client.health(base_url)
80
+ except httpx.HTTPError as exc:
81
+ typer.secho(f"Query server unreachable at {base_url}: {exc}", fg=typer.colors.RED)
82
+ raise typer.Exit(code=1) from exc
83
+
84
+ typer.secho(f"Query server: {result.status}", fg=typer.colors.GREEN)
85
+ typer.echo(f" url: {base_url}")
86
+ typer.echo(f" version: {result.version}")
87
+ typer.echo(f" uptime: {result.uptime_seconds:.1f}s")
88
+
89
+
90
+ def _error_detail(exc: httpx.HTTPStatusError) -> str:
91
+ """Pull the server's ``detail`` message out of an error response."""
92
+ try:
93
+ body = exc.response.json()
94
+ except ValueError:
95
+ return exc.response.text or str(exc)
96
+ detail = body.get("detail") if isinstance(body, dict) else None
97
+ return str(detail) if detail else str(exc)
98
+
99
+
100
+ @app.command()
101
+ def search(
102
+ query: list[str] = typer.Argument(..., help="Search query (words are joined)."),
103
+ top_k: int = typer.Option(5, "-k", "--top-k", help="Max results to return."),
104
+ repo: str | None = typer.Option(
105
+ None,
106
+ "--repo",
107
+ help="Restrict results to one indexed repo, as '<owner>/<repo>' or bare "
108
+ "'<repo>'. Default: auto-detect from the current git checkout.",
109
+ ),
110
+ all_repos: bool = typer.Option(
111
+ False,
112
+ "--all-repos",
113
+ help="Search across all indexed repos (disables repo auto-detection).",
114
+ ),
115
+ git_ref: str | None = typer.Option(
116
+ None,
117
+ "--git-ref",
118
+ help="Restrict results to one indexed git ref of the repo, qualified as "
119
+ "'heads/<branch>' or 'tags/<tag>' (requires a repo scope).",
120
+ ),
121
+ server: str | None = typer.Option(
122
+ None,
123
+ "--server",
124
+ help="Query server base URL (defaults to $CCX_SERVER_URL).",
125
+ ),
126
+ ) -> None:
127
+ """Semantic search across the indexed codebases.
128
+
129
+ By default this filters to the repo of the current git checkout (detected
130
+ from its ``origin`` GitHub remote). Pass ``--repo`` to target another, or
131
+ ``--all-repos`` to search everything.
132
+ """
133
+ if all_repos and repo is not None:
134
+ typer.secho("Pass either --repo or --all-repos, not both.", fg=typer.colors.RED)
135
+ raise typer.Exit(code=1)
136
+
137
+ if all_repos:
138
+ selected_repo: str | None = None
139
+ elif repo is not None:
140
+ selected_repo = repo
141
+ else:
142
+ selected_repo = _detect_local_repo()
143
+ if selected_repo is not None:
144
+ typer.secho(
145
+ f"Filtering to {selected_repo} (use --all-repos to search everything).",
146
+ fg=typer.colors.BRIGHT_BLACK,
147
+ err=True,
148
+ )
149
+ else:
150
+ typer.secho(
151
+ "No GitHub repo detected in the current directory; searching all "
152
+ "indexed repos (use --repo <owner>/<repo> to scope).",
153
+ fg=typer.colors.BRIGHT_BLACK,
154
+ err=True,
155
+ )
156
+
157
+ if git_ref is not None and selected_repo is None:
158
+ typer.secho(
159
+ "--git-ref requires a repo scope; pass --repo <owner>/<repo> "
160
+ "(not available with --all-repos).",
161
+ fg=typer.colors.RED,
162
+ )
163
+ raise typer.Exit(code=1)
164
+
165
+ base_url = server or settings.server_url()
166
+ query_text = " ".join(query)
167
+ try:
168
+ result = client.search(
169
+ query_text,
170
+ top_k=top_k,
171
+ repo=selected_repo,
172
+ git_ref=git_ref,
173
+ base_url=base_url,
174
+ )
175
+ except httpx.HTTPStatusError as exc:
176
+ typer.secho(
177
+ f"Search failed (HTTP {exc.response.status_code}): {_error_detail(exc)}",
178
+ fg=typer.colors.RED,
179
+ )
180
+ raise typer.Exit(code=1) from exc
181
+ except httpx.HTTPError as exc:
182
+ typer.secho(f"Query server unreachable at {base_url}: {exc}", fg=typer.colors.RED)
183
+ raise typer.Exit(code=1) from exc
184
+
185
+ if not result.results:
186
+ typer.echo("No results.")
187
+ return
188
+
189
+ for item in result.results:
190
+ typer.secho(
191
+ f"[{item.score:.3f}] {item.repo_key} {item.filename} "
192
+ f"(L{item.start_line}-L{item.end_line})",
193
+ fg=typer.colors.CYAN,
194
+ )
195
+ typer.echo(item.code.rstrip("\n"))
196
+ typer.echo("---")
197
+
198
+
199
+ @app.command()
200
+ def grep(
201
+ pattern: str = typer.Argument(
202
+ ...,
203
+ help=r"Structural pattern matched against the AST (not text). The '\' sigil "
204
+ r"marks metavariables, e.g. 'def \NAME(\(ARGS*\)):' or 'foo(\X)'.",
205
+ ),
206
+ language: str = typer.Option(
207
+ ..., "-l", "--language", help="Source language to parse + match (e.g. python, rust, c++)."
208
+ ),
209
+ git_ref: str = typer.Option(
210
+ ..., "--git-ref", help="Indexed git ref, qualified as 'heads/<branch>' or 'tags/<tag>'."
211
+ ),
212
+ repo: str | None = typer.Option(
213
+ None, "--repo", help="Indexed repo (default: auto-detect from the git checkout)."
214
+ ),
215
+ paths: list[str] = typer.Option(
216
+ None, "--path", help="Restrict to files matching this glob (repeatable, e.g. 'src/*.py')."
217
+ ),
218
+ limit: int = typer.Option(100, "-k", "--limit", help="Max matches to return."),
219
+ offset: int = typer.Option(0, "--offset", help="Skip this many matches (pagination)."),
220
+ server: str | None = typer.Option(None, "--server", help="Query server base URL."),
221
+ ) -> None:
222
+ """AST-based structural grep over an indexed repo at a given ref.
223
+
224
+ The pattern is parsed and matched against the code's syntax tree, so
225
+ ``foo(\\X)`` matches calls to ``foo`` regardless of formatting and never matches
226
+ the text inside comments or strings.
227
+ """
228
+ base_url = server or settings.server_url()
229
+ result = _run(
230
+ base_url,
231
+ lambda: client.grep(
232
+ pattern=pattern,
233
+ language=language,
234
+ repo=_require_repo(repo),
235
+ git_ref=git_ref,
236
+ paths=list(paths or []),
237
+ limit=limit,
238
+ offset=offset,
239
+ base_url=base_url,
240
+ ),
241
+ )
242
+ if not result.matches:
243
+ typer.echo("No matches.")
244
+ return
245
+ for m in result.matches:
246
+ header = f"{m.filename}:{m.start_line}-{m.end_line} [{m.kind}]"
247
+ typer.secho(header, fg=typer.colors.CYAN)
248
+ if m.captures:
249
+ caps = " ".join(f"{name}={text!r}" for name, text in sorted(m.captures.items()))
250
+ typer.secho(f" captures: {caps}", fg=typer.colors.BRIGHT_BLACK)
251
+ typer.echo(m.code.rstrip("\n"))
252
+ typer.echo("---")
253
+ if result.truncated:
254
+ typer.secho(
255
+ f"… more matches past {offset + len(result.matches)}; page with --offset/--limit.",
256
+ fg=typer.colors.BRIGHT_BLACK,
257
+ err=True,
258
+ )
259
+
260
+
261
+ def _require_repo(repo: str | None) -> str:
262
+ """Resolve the repo for a single-repo command: ``--repo`` if given, else the
263
+ current git checkout's ``origin`` remote. Exits 1 with guidance if neither."""
264
+ selected = repo if repo is not None else _detect_local_repo()
265
+ if selected is None:
266
+ typer.secho(
267
+ "No repo specified and none detected from the current git checkout; "
268
+ "pass --repo <owner>/<repo>.",
269
+ fg=typer.colors.RED,
270
+ )
271
+ raise typer.Exit(code=1)
272
+ return selected
273
+
274
+
275
+ def _run(base_url: str, call: Callable[[], T]) -> T:
276
+ """Run a ``client`` request, mapping HTTP/transport errors to a clean CLI exit
277
+ (mirrors the ``search`` command's error handling)."""
278
+ try:
279
+ return call()
280
+ except httpx.HTTPStatusError as exc:
281
+ typer.secho(
282
+ f"Request failed (HTTP {exc.response.status_code}): {_error_detail(exc)}",
283
+ fg=typer.colors.RED,
284
+ )
285
+ raise typer.Exit(code=1) from exc
286
+ except httpx.HTTPError as exc:
287
+ typer.secho(f"Query server unreachable at {base_url}: {exc}", fg=typer.colors.RED)
288
+ raise typer.Exit(code=1) from exc
289
+
290
+
291
+ @app.command(name="read-file")
292
+ def read_file(
293
+ path: str = typer.Argument(..., help="Repo-relative file path to read."),
294
+ git_ref: str = typer.Option(
295
+ ..., "--git-ref", help="Indexed git ref, qualified as 'heads/<branch>' or 'tags/<tag>'."
296
+ ),
297
+ repo: str | None = typer.Option(
298
+ None, "--repo", help="Indexed repo (default: auto-detect from the git checkout)."
299
+ ),
300
+ offset: int = typer.Option(1, "--offset", help="1-based first line to print."),
301
+ limit: int | None = typer.Option(None, "--limit", help="Number of lines (default: to EOF)."),
302
+ server: str | None = typer.Option(None, "--server", help="Query server base URL."),
303
+ ) -> None:
304
+ """Print a file's contents as they exist in a given ref."""
305
+ base_url = server or settings.server_url()
306
+ result = _run(
307
+ base_url,
308
+ lambda: client.read_file(
309
+ repo=_require_repo(repo),
310
+ git_ref=git_ref,
311
+ path=path,
312
+ offset=offset,
313
+ limit=limit,
314
+ base_url=base_url,
315
+ ),
316
+ )
317
+ typer.echo(result.content, nl=False)
318
+
319
+
320
+ @app.command(name="find-files")
321
+ def find_files(
322
+ patterns: list[str] = typer.Argument(
323
+ None, help="Path globs (e.g. 'src/*.py'); none lists all files."
324
+ ),
325
+ git_ref: str = typer.Option(..., "--git-ref", help="Indexed git ref (e.g. 'heads/main')."),
326
+ repo: str | None = typer.Option(
327
+ None, "--repo", help="Indexed repo (default: auto-detect from the git checkout)."
328
+ ),
329
+ case: str = typer.Option("smart", "--case", help="smart | sensitive | insensitive."),
330
+ limit: int = typer.Option(100, "--limit", help="Max paths to return."),
331
+ offset: int = typer.Option(0, "--offset", help="Skip this many matches (pagination)."),
332
+ server: str | None = typer.Option(None, "--server", help="Query server base URL."),
333
+ ) -> None:
334
+ """List indexed files in a ref, optionally filtered by glob patterns."""
335
+ base_url = server or settings.server_url()
336
+ result = _run(
337
+ base_url,
338
+ lambda: client.find_files(
339
+ repo=_require_repo(repo),
340
+ git_ref=git_ref,
341
+ patterns=list(patterns or []),
342
+ case=case,
343
+ limit=limit,
344
+ offset=offset,
345
+ base_url=base_url,
346
+ ),
347
+ )
348
+ for p in result.paths:
349
+ typer.echo(p)
350
+ shown = len(result.paths)
351
+ total = result.total
352
+ if total > shown + offset:
353
+ typer.secho(
354
+ f"… {total} total (showing {shown}); page with --offset/--limit.",
355
+ fg=typer.colors.BRIGHT_BLACK,
356
+ err=True,
357
+ )
358
+
359
+
360
+ @app.command()
361
+ def repositories(
362
+ repo: str | None = typer.Argument(
363
+ None, help="Indexed repo (default: auto-detect from the git checkout)."
364
+ ),
365
+ server: str | None = typer.Option(None, "--server", help="Query server base URL."),
366
+ ) -> None:
367
+ """List a repo's indexed git refs and the commit SHA indexed for each."""
368
+ base_url = server or settings.server_url()
369
+ result = _run(base_url, lambda: client.repositories(_require_repo(repo), base_url=base_url))
370
+ typer.secho(result.repo_key, fg=typer.colors.CYAN)
371
+ for ref in result.refs:
372
+ typer.echo(f" {ref.git_ref}\t{ref.commit_sha}")
373
+ if not result.refs:
374
+ typer.echo(" (no indexed refs)")
375
+
376
+
377
+ @app.command()
378
+ def version() -> None:
379
+ """Print the CLI version."""
380
+ typer.echo(__version__)
381
+
382
+
383
+ if __name__ == "__main__":
384
+ app()
@@ -0,0 +1,208 @@
1
+ # © 2025 CocoIndex Inc. All rights reserved.
2
+ # SPDX-License-Identifier: LicenseRef-CocoIndex-Proprietary
3
+ """Client-side helpers for talking to the query server over HTTP.
4
+
5
+ The CLI uses these functions; each opens its own short-lived HTTP request, so
6
+ there is no persistent client object to manage.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from urllib.parse import quote
12
+
13
+ import httpx
14
+
15
+ from . import settings
16
+ from .protocol import (
17
+ FIND_FILES_PATH,
18
+ GREP_PATH,
19
+ READ_FILE_PATH,
20
+ REPOSITORIES_PATH,
21
+ SEMANTIC_SEARCH_PATH,
22
+ FindFilesResponse,
23
+ GrepResponse,
24
+ HealthResponse,
25
+ ReadFileResponse,
26
+ RepositoriesResponse,
27
+ SearchResponse,
28
+ )
29
+
30
+ DEFAULT_TIMEOUT = 10.0
31
+ # Searching embeds the query and hits the database, so allow a bit more time.
32
+ SEARCH_TIMEOUT = 30.0
33
+
34
+
35
+ def _auth_headers() -> dict[str, str]:
36
+ """``Authorization: Bearer`` header when a token is configured, else empty.
37
+
38
+ The token comes from ``CCX_API_TOKEN`` (see ``settings.api_token``). Sending
39
+ it is harmless against a ``none``-mode server and required by an
40
+ ``apiKey``-mode one; ``/health`` is auth-exempt but carrying the header there
41
+ too keeps one code path.
42
+ """
43
+ token = settings.api_token()
44
+ return {"Authorization": f"Bearer {token}"} if token else {}
45
+
46
+
47
+ def health(base_url: str | None = None) -> HealthResponse:
48
+ """Query the server's health endpoint.
49
+
50
+ Raises ``httpx.HTTPError`` if the server is unreachable or returns a
51
+ non-2xx status.
52
+ """
53
+ url = (base_url or settings.server_url()).rstrip("/")
54
+ resp = httpx.get(f"{url}/health", timeout=DEFAULT_TIMEOUT, headers=_auth_headers())
55
+ resp.raise_for_status()
56
+ return HealthResponse.model_validate(resp.json())
57
+
58
+
59
+ def search(
60
+ query: str,
61
+ *,
62
+ top_k: int = 5,
63
+ offset: int = 0,
64
+ repo: str | None = None,
65
+ git_ref: str | None = None,
66
+ paths: list[str] | None = None,
67
+ base_url: str | None = None,
68
+ ) -> SearchResponse:
69
+ """Run a semantic search against the query server.
70
+
71
+ ``repo`` names one indexed repo as ``<owner>/<repo>`` or bare ``<repo>``;
72
+ ``None`` searches across all indexed repos. ``offset`` paginates the ranked
73
+ results; ``paths`` (globs) scopes to matching filenames.
74
+
75
+ Raises ``httpx.HTTPError`` if the server is unreachable or returns a
76
+ non-2xx status (e.g. 503 when the index hasn't been built yet).
77
+ """
78
+ url = (base_url or settings.server_url()).rstrip("/")
79
+ payload: dict[str, object] = {"query": query, "top_k": top_k, "offset": offset}
80
+ if repo is not None:
81
+ payload["repo"] = repo
82
+ if git_ref is not None:
83
+ payload["git_ref"] = git_ref
84
+ if paths:
85
+ payload["paths"] = paths
86
+ resp = httpx.post(
87
+ f"{url}{SEMANTIC_SEARCH_PATH}",
88
+ json=payload,
89
+ timeout=SEARCH_TIMEOUT,
90
+ headers=_auth_headers(),
91
+ )
92
+ resp.raise_for_status()
93
+ return SearchResponse.model_validate(resp.json())
94
+
95
+
96
+ def grep(
97
+ *,
98
+ pattern: str,
99
+ language: str,
100
+ repo: str,
101
+ git_ref: str,
102
+ paths: list[str] | None = None,
103
+ limit: int = 100,
104
+ offset: int = 0,
105
+ base_url: str | None = None,
106
+ ) -> GrepResponse:
107
+ """Run an AST structural grep against the query server.
108
+
109
+ ``pattern`` is a by-example structural pattern (``\\`` sigil for metavariables);
110
+ ``language`` is the source language to parse + match. Matching is scoped to
111
+ ``git_ref`` of ``repo``; ``paths`` (globs) narrows the file set. Raises
112
+ ``httpx.HTTPError`` on an unreachable server or a non-2xx status (400 bad
113
+ pattern / unknown repo, 503 grep unavailable or index not built yet).
114
+ """
115
+ url = (base_url or settings.server_url()).rstrip("/")
116
+ payload: dict[str, object] = {
117
+ "pattern": pattern,
118
+ "language": language,
119
+ "repo": repo,
120
+ "git_ref": git_ref,
121
+ "limit": limit,
122
+ "offset": offset,
123
+ }
124
+ if paths:
125
+ payload["paths"] = paths
126
+ resp = httpx.post(
127
+ f"{url}{GREP_PATH}", json=payload, timeout=SEARCH_TIMEOUT, headers=_auth_headers()
128
+ )
129
+ resp.raise_for_status()
130
+ return GrepResponse.model_validate(resp.json())
131
+
132
+
133
+ def read_file(
134
+ *,
135
+ repo: str,
136
+ git_ref: str,
137
+ path: str,
138
+ offset: int = 1,
139
+ limit: int | None = None,
140
+ base_url: str | None = None,
141
+ ) -> ReadFileResponse:
142
+ """Read a line window of ``path`` as it exists in ``git_ref`` of ``repo``.
143
+
144
+ ``offset`` is the 1-based first line; ``limit`` the number of lines (``None``
145
+ → to end of file). Raises ``httpx.HTTPError`` on an unreachable server or a
146
+ non-2xx status (400 unknown repo / bad range, 404 path not in ref, 503 index
147
+ not built yet).
148
+ """
149
+ url = (base_url or settings.server_url()).rstrip("/")
150
+ payload: dict[str, object] = {"repo": repo, "git_ref": git_ref, "path": path, "offset": offset}
151
+ if limit is not None:
152
+ payload["limit"] = limit
153
+ resp = httpx.post(
154
+ f"{url}{READ_FILE_PATH}", json=payload, timeout=DEFAULT_TIMEOUT, headers=_auth_headers()
155
+ )
156
+ resp.raise_for_status()
157
+ return ReadFileResponse.model_validate(resp.json())
158
+
159
+
160
+ def find_files(
161
+ *,
162
+ repo: str,
163
+ git_ref: str,
164
+ patterns: list[str] | None = None,
165
+ case: str = "smart",
166
+ limit: int = 100,
167
+ offset: int = 0,
168
+ base_url: str | None = None,
169
+ ) -> FindFilesResponse:
170
+ """List files in ``git_ref`` of ``repo`` matching any glob in ``patterns``.
171
+
172
+ Empty ``patterns`` lists all files. Raises ``httpx.HTTPError`` on an
173
+ unreachable server or a non-2xx status (400 unknown repo / bad args, 503
174
+ index not built yet).
175
+ """
176
+ url = (base_url or settings.server_url()).rstrip("/")
177
+ payload: dict[str, object] = {
178
+ "repo": repo,
179
+ "git_ref": git_ref,
180
+ "patterns": patterns or [],
181
+ "case": case,
182
+ "limit": limit,
183
+ "offset": offset,
184
+ }
185
+ resp = httpx.post(
186
+ f"{url}{FIND_FILES_PATH}", json=payload, timeout=DEFAULT_TIMEOUT, headers=_auth_headers()
187
+ )
188
+ resp.raise_for_status()
189
+ return FindFilesResponse.model_validate(resp.json())
190
+
191
+
192
+ def repositories(repo: str, *, base_url: str | None = None) -> RepositoriesResponse:
193
+ """List a repo's indexed refs and their HEAD commit SHAs.
194
+
195
+ ``repo`` is ``<owner>/<repo>``, a bare ``<repo>``, or the exact ``repo_key``
196
+ (slashes preserved for a GitLab nested namespace). Raises ``httpx.HTTPError``
197
+ on an unreachable server or a non-2xx status (400 unknown repo, 503 index not
198
+ built yet).
199
+ """
200
+ url = (base_url or settings.server_url()).rstrip("/")
201
+ # Keep slashes — the route captures the rest of the path as the repo id.
202
+ resp = httpx.get(
203
+ f"{url}{REPOSITORIES_PATH}/{quote(repo, safe='/')}",
204
+ timeout=DEFAULT_TIMEOUT,
205
+ headers=_auth_headers(),
206
+ )
207
+ resp.raise_for_status()
208
+ return RepositoriesResponse.model_validate(resp.json())
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: cocoindex-code-plus
3
+ Version: 0.1.0
4
+ Summary: Client CLI for CocoIndex Code Plus: query indexed codebases from your workstation.
5
+ License-Expression: LicenseRef-CocoIndex-Proprietary
6
+ Requires-Python: >=3.11
7
+ Requires-Dist: cocoindex-code-plus-common
8
+ Requires-Dist: httpx>=0.27.0
9
+ Requires-Dist: typer>=0.12.0
10
+ Description-Content-Type: text/markdown
11
+
12
+ # cocoindex-code-plus (CLI)
13
+
14
+ The `ccx` client CLI for CocoIndex Code Plus. See the repo root README.
@@ -0,0 +1,6 @@
1
+ cocoindex_code_plus/cli.py,sha256=qNj5ebJZ8Mkz7IZ9Paf1vvL_AaLkOEneoWNe1dAasdA,13517
2
+ cocoindex_code_plus/client.py,sha256=CBYFbPm3YJuR5MI132DC3Nkj4kcIUzZkJxKRhLZQ_IE,6818
3
+ cocoindex_code_plus-0.1.0.dist-info/METADATA,sha256=Tjf4eFLotcWRdNTVFZC4S0prgfcRdmcL5BqV87prlHI,474
4
+ cocoindex_code_plus-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
5
+ cocoindex_code_plus-0.1.0.dist-info/entry_points.txt,sha256=osXgNNJtf-AaZGSdNL8vbfCPbMqrA8i-UVuDdii4fqs,52
6
+ cocoindex_code_plus-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ccx = cocoindex_code_plus.cli:app