dig-client 0.1.0.dev4__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.
dig_client/__init__.py ADDED
@@ -0,0 +1,153 @@
1
+ """dig-client — a dependency-free Python client for a local dig daemon (`dig serve`).
2
+
3
+ Drives the same surface as the CLI over HTTP: search, drift, history, export
4
+ (read) and organize / reconcile / undo (mutations, preview-by-default).
5
+ Local-first: it talks only to the loopback daemon you run.
6
+
7
+ Example:
8
+ from dig_client import DigClient
9
+
10
+ dig = DigClient() # http://127.0.0.1:3978
11
+ hits = dig.find("invoice acme", mode="hybrid", limit=5)
12
+ dig.org(apply=True) # commit a reorg (reversible)
13
+ dig.undo()
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ import urllib.error
20
+ import urllib.parse
21
+ import urllib.request
22
+ from typing import Any
23
+
24
+ __all__ = ["DigClient", "DigError"]
25
+
26
+ DEFAULT_BASE_URL = "http://127.0.0.1:3978"
27
+
28
+
29
+ class DigError(Exception):
30
+ """Raised when the daemon returns a non-2xx response."""
31
+
32
+ def __init__(self, message: str, status: int) -> None:
33
+ super().__init__(message)
34
+ self.status = status
35
+
36
+
37
+ class DigClient:
38
+ """Client for a local dig daemon."""
39
+
40
+ def __init__(self, base_url: str = DEFAULT_BASE_URL, timeout: float = 120.0) -> None:
41
+ self.base_url = base_url.rstrip("/")
42
+ self.timeout = timeout
43
+
44
+ def health(self) -> dict[str, Any]:
45
+ """Liveness + daemon version."""
46
+ return self._request("GET", "/health", {})
47
+
48
+ def find(
49
+ self,
50
+ query: str,
51
+ kb: str | None = None,
52
+ mode: str | None = None,
53
+ limit: int | None = None,
54
+ ) -> list[dict[str, Any]]:
55
+ """Search the KB, ranked. ``mode`` is fts (default), vector, or hybrid."""
56
+ return self._request(
57
+ "GET", "/find", {"kb": kb, "query": query, "mode": mode, "limit": limit}
58
+ )
59
+
60
+ def recall(
61
+ self,
62
+ query: str,
63
+ kb: str | None = None,
64
+ mode: str | None = None,
65
+ budget: int | None = None,
66
+ ) -> dict[str, Any]:
67
+ """Load a token-budgeted, provenance-tagged context pack for ``query``.
68
+
69
+ The agent-memory recall primitive: snippets land on the matching passage.
70
+ ``budget`` caps the pack in tokens; ``mode`` is fts (default), vector, or
71
+ hybrid.
72
+ """
73
+ return self._request(
74
+ "GET", "/recall", {"kb": kb, "query": query, "mode": mode, "budget": budget}
75
+ )
76
+
77
+ def retain(
78
+ self, content: str, kb: str | None = None, as_: str | None = None
79
+ ) -> Any:
80
+ """Capture ``content`` into the KB and index it — the capture primitive.
81
+
82
+ Writes to a dated ``memory/`` path by default; pass ``as_`` to choose the
83
+ path. Reversible with :meth:`undo`.
84
+ """
85
+ return self._request(
86
+ "POST", "/retain", {"kb": kb, "as": as_}, data=content.encode("utf-8")
87
+ )
88
+
89
+ def drift(self, kb: str | None = None) -> Any:
90
+ """Report how the KB diverges from its policy. Read-only."""
91
+ return self._request("GET", "/drift", {"kb": kb})
92
+
93
+ def log(self, kb: str | None = None) -> Any:
94
+ """Browse change history, newest first. Read-only."""
95
+ return self._request("GET", "/log", {"kb": kb})
96
+
97
+ def export(
98
+ self, kb: str | None = None, filter: str | None = None, at: str | None = None
99
+ ) -> str:
100
+ """Export a reproducible, provenance-tagged dataset (JSONL text). Read-only."""
101
+ body = self._request("GET", "/export", {"kb": kb, "filter": filter, "at": at})
102
+ if isinstance(body, dict):
103
+ return str(body.get("output", ""))
104
+ return ""
105
+
106
+ def org(self, kb: str | None = None, apply: bool = False) -> Any:
107
+ """Apply organization policy. Previews unless ``apply`` is True (reversible)."""
108
+ return self._request("POST", "/org", {"kb": kb, "apply": apply})
109
+
110
+ def reconcile(self, kb: str | None = None, apply: bool = False) -> Any:
111
+ """Converge the KB to policy. Previews unless ``apply`` is True (reversible)."""
112
+ return self._request("POST", "/reconcile", {"kb": kb, "apply": apply})
113
+
114
+ def undo(self, kb: str | None = None) -> Any:
115
+ """Revert the last changeset."""
116
+ return self._request("POST", "/undo", {"kb": kb})
117
+
118
+ def _request(
119
+ self, method: str, path: str, params: dict[str, Any], data: bytes | None = None
120
+ ) -> Any:
121
+ query = {k: _str(v) for k, v in params.items() if v is not None}
122
+ url = self.base_url + path
123
+ if query:
124
+ url += "?" + urllib.parse.urlencode(query)
125
+ req = urllib.request.Request(url, data=data, method=method) # noqa: S310 (loopback only)
126
+ try:
127
+ with urllib.request.urlopen(req, timeout=self.timeout) as resp: # noqa: S310
128
+ return _parse(resp.read())
129
+ except urllib.error.HTTPError as exc:
130
+ body = exc.read()
131
+ raise DigError(_error(body) or exc.reason, exc.code) from None
132
+
133
+
134
+ def _str(value: Any) -> str:
135
+ if isinstance(value, bool):
136
+ return "true" if value else "false"
137
+ return str(value)
138
+
139
+
140
+ def _parse(raw: bytes) -> Any:
141
+ if not raw:
142
+ return None
143
+ return json.loads(raw.decode("utf-8"))
144
+
145
+
146
+ def _error(raw: bytes) -> str | None:
147
+ try:
148
+ body = json.loads(raw.decode("utf-8"))
149
+ except (ValueError, UnicodeDecodeError):
150
+ return None
151
+ if isinstance(body, dict) and "error" in body:
152
+ return str(body["error"])
153
+ return None
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: dig-client
3
+ Version: 0.1.0.dev4
4
+ Summary: Python client for a local dig daemon — search, organize, reconcile, and export a knowledge base over HTTP.
5
+ Project-URL: Homepage, https://dig.vllnt.com
6
+ Project-URL: Repository, https://github.com/vllnt/dig
7
+ Author: vllnt
8
+ License-Expression: MIT
9
+ Keywords: ai-agents,dig,knowledge-base,memory,rag,retrieval
10
+ Requires-Python: >=3.10
11
+ Description-Content-Type: text/markdown
12
+
13
+ # dig-client
14
+
15
+ Python client for a local [dig](https://github.com/vllnt/dig) daemon — search,
16
+ organize, reconcile, and export a knowledge base over HTTP. Dependency-free
17
+ (standard library only), local-first.
18
+
19
+ ## Install
20
+
21
+ ```sh
22
+ pip install dig-client
23
+ ```
24
+
25
+ Start a daemon next to your KB (dig binary from https://dig.vllnt.com):
26
+
27
+ ```sh
28
+ dig serve # binds 127.0.0.1:3978 (loopback only)
29
+ ```
30
+
31
+ ## Use
32
+
33
+ ```python
34
+ from dig_client import DigClient
35
+
36
+ dig = DigClient() # http://127.0.0.1:3978
37
+
38
+ # search — fts (default), vector, or hybrid (semantic)
39
+ hits = dig.find("invoice acme 2024", mode="hybrid", limit=5)
40
+
41
+ # agent memory — capture, then recall a token-budgeted pack
42
+ dig.retain(session_markdown, as_="memory/sessions/today.md")
43
+ pack = dig.recall("billing ledger decision", budget=1000)
44
+
45
+ # reorganize by policy — preview, then apply (reversible)
46
+ dig.org(apply=False) # preview the plan
47
+ dig.org(apply=True) # commit it
48
+ dig.undo() # step back
49
+
50
+ # reproducible dataset export (JSONL text)
51
+ jsonl = dig.export(filter="label:finance")
52
+
53
+ # read-only inspection
54
+ dig.drift()
55
+ dig.log()
56
+ ```
57
+
58
+ Target a specific KB with `kb="/path/or/name"` on any call; omit it to use the
59
+ KB at the daemon's working directory. Errors raise `DigError` carrying the HTTP
60
+ status.
61
+
62
+ The client speaks the same contract as `dig serve`, a thin adapter over the dig
63
+ CLI — so it never drifts from the tool.
@@ -0,0 +1,4 @@
1
+ dig_client/__init__.py,sha256=bZ0DnzBVYF1AzJwwkxHWzdRk1vljV0TTGOnwJ61cNGM,5295
2
+ dig_client-0.1.0.dev4.dist-info/METADATA,sha256=LJlXd0lDSSrgmLWjl_cqcLW4p2YqwgAJ-b3VtrOVLxI,1836
3
+ dig_client-0.1.0.dev4.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
4
+ dig_client-0.1.0.dev4.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