opencontextengine-client 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.
- oce_client/__init__.py +33 -0
- oce_client/cli.py +250 -0
- oce_client/context.py +478 -0
- oce_client/defaults.py +2 -0
- oce_client/filesystem.py +62 -0
- oce_client/http.py +134 -0
- oce_client/identity.py +17 -0
- oce_client/ignore.py +84 -0
- oce_client/indexer.py +265 -0
- oce_client/mcp_server.py +309 -0
- oce_client/models.py +102 -0
- oce_client/ports.py +67 -0
- oce_client/runtime.py +245 -0
- oce_client/skill/SKILL.md +136 -0
- oce_client/skill/agents/openai.yaml +4 -0
- oce_client/state.py +288 -0
- oce_client/watcher.py +57 -0
- opencontextengine_client-0.1.0.dist-info/METADATA +162 -0
- opencontextengine_client-0.1.0.dist-info/RECORD +22 -0
- opencontextengine_client-0.1.0.dist-info/WHEEL +4 -0
- opencontextengine_client-0.1.0.dist-info/entry_points.txt +3 -0
- opencontextengine_client-0.1.0.dist-info/licenses/LICENSE +202 -0
oce_client/__init__.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""OpenContextEngine client public API."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
from .context import BlobCompatibilityError, CheckpointResetRequired, WorkspaceContext
|
|
6
|
+
from .defaults import DEFAULT_API_KEY, DEFAULT_API_URL
|
|
7
|
+
from .filesystem import FileAdmissionError, LocalFileSource
|
|
8
|
+
from .http import OceApiError, OceHttpClient
|
|
9
|
+
from .identity import Sha256BlobIdentity
|
|
10
|
+
from .ignore import LayeredIgnoreMatcher
|
|
11
|
+
from .models import (
|
|
12
|
+
BlobDelta,
|
|
13
|
+
BlobStatus,
|
|
14
|
+
BlobStatusResult,
|
|
15
|
+
BlobUpload,
|
|
16
|
+
CheckpointResult,
|
|
17
|
+
FileRecord,
|
|
18
|
+
FileStatus,
|
|
19
|
+
MissingResult,
|
|
20
|
+
RetrievalResult,
|
|
21
|
+
SyncResult,
|
|
22
|
+
UploadPlan,
|
|
23
|
+
UploadResult,
|
|
24
|
+
WorkspaceSnapshot,
|
|
25
|
+
)
|
|
26
|
+
from .ports import BlobApi, BlobIdentity, FileSource, IgnoreMatcher, StateStore, Watcher
|
|
27
|
+
from .runtime import (
|
|
28
|
+
ClientConfigurationError,
|
|
29
|
+
ClientRuntime,
|
|
30
|
+
ClientSettings,
|
|
31
|
+
McpConfiguration,
|
|
32
|
+
)
|
|
33
|
+
from .state import SQLiteStateStore
|
oce_client/cli.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import importlib.metadata
|
|
5
|
+
import importlib.resources
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import sys
|
|
10
|
+
from dataclasses import asdict, is_dataclass
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Sequence
|
|
14
|
+
|
|
15
|
+
from . import __version__ as PACKAGE_VERSION
|
|
16
|
+
from .context import BlobCompatibilityError, CheckpointResetRequired
|
|
17
|
+
from .filesystem import FileAdmissionError
|
|
18
|
+
from .http import OceApiError
|
|
19
|
+
from .runtime import (
|
|
20
|
+
DEFAULT_API_URL,
|
|
21
|
+
ClientConfigurationError,
|
|
22
|
+
ClientRuntime,
|
|
23
|
+
ClientSettings,
|
|
24
|
+
iter_runtime_patterns,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
VERSION = importlib.metadata.version("opencontextengine-client")
|
|
30
|
+
except importlib.metadata.PackageNotFoundError:
|
|
31
|
+
VERSION = PACKAGE_VERSION
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _json_default(value: object) -> object:
|
|
35
|
+
if isinstance(value, Enum):
|
|
36
|
+
return value.value
|
|
37
|
+
if isinstance(value, Path):
|
|
38
|
+
return value.as_posix()
|
|
39
|
+
if is_dataclass(value):
|
|
40
|
+
return asdict(value)
|
|
41
|
+
raise TypeError(f"cannot encode {type(value).__name__}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _dump(value: object) -> None:
|
|
45
|
+
print(json.dumps(value, default=_json_default, ensure_ascii=False, sort_keys=True))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _snapshot_payload(snapshot: object) -> dict[str, object]:
|
|
49
|
+
files = getattr(snapshot, "files")
|
|
50
|
+
return {
|
|
51
|
+
"checkpoint_id": getattr(snapshot, "checkpoint_id"),
|
|
52
|
+
"generation": getattr(snapshot, "generation"),
|
|
53
|
+
"files": {
|
|
54
|
+
path: {
|
|
55
|
+
"blob_name": record.blob_name,
|
|
56
|
+
"committed_blob_name": record.committed_blob_name,
|
|
57
|
+
"status": record.status.value,
|
|
58
|
+
"size": record.size,
|
|
59
|
+
"source": record.source,
|
|
60
|
+
"generation": record.generation,
|
|
61
|
+
}
|
|
62
|
+
for path, record in files.items()
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _skill_source() -> Path:
|
|
68
|
+
packaged = importlib.resources.files("oce_client").joinpath("skill")
|
|
69
|
+
if packaged.is_dir():
|
|
70
|
+
return Path(str(packaged))
|
|
71
|
+
source_tree = Path(__file__).resolve().parents[2] / "skills" / "oce-client"
|
|
72
|
+
if source_tree.is_dir():
|
|
73
|
+
return source_tree
|
|
74
|
+
raise ClientConfigurationError("the oce-client skill is not present in this installation")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _codex_skill_target() -> Path:
|
|
78
|
+
return Path(os.environ.get("CODEX_HOME", Path.home() / ".codex")) / "skills" / "oce-client"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
82
|
+
parser = argparse.ArgumentParser(
|
|
83
|
+
prog="oce-client",
|
|
84
|
+
description="Synchronize a local workspace with OpenContextEngine.",
|
|
85
|
+
)
|
|
86
|
+
parser.add_argument("--version", action="version", version=f"oce-client {VERSION}")
|
|
87
|
+
parser.add_argument("--root", default=None, help="workspace directory (default: OCE_WORKSPACE or .)")
|
|
88
|
+
parser.add_argument(
|
|
89
|
+
"--api-url",
|
|
90
|
+
default=None,
|
|
91
|
+
help=f"OCE API URL (default: OCE_API_URL or {DEFAULT_API_URL})",
|
|
92
|
+
)
|
|
93
|
+
parser.add_argument(
|
|
94
|
+
"--state-path",
|
|
95
|
+
default=None,
|
|
96
|
+
help="SQLite state path (default: OCE_STATE_PATH or workspace/.oce-client)",
|
|
97
|
+
)
|
|
98
|
+
parser.add_argument(
|
|
99
|
+
"--ignore",
|
|
100
|
+
action="append",
|
|
101
|
+
default=None,
|
|
102
|
+
metavar="PATTERN",
|
|
103
|
+
help="runtime ignore pattern; can be repeated or comma-separated (OCE_IGNORE)",
|
|
104
|
+
)
|
|
105
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
106
|
+
|
|
107
|
+
sync = subparsers.add_parser("sync", help="reconcile and upload the workspace")
|
|
108
|
+
sync.add_argument("--json", action="store_true", dest="as_json")
|
|
109
|
+
|
|
110
|
+
status = subparsers.add_parser("status", help="show local inventory and checkpoint state")
|
|
111
|
+
status.add_argument("--json", action="store_true", dest="as_json")
|
|
112
|
+
|
|
113
|
+
retrieve = subparsers.add_parser("retrieve", help="retrieve formatted code context")
|
|
114
|
+
retrieve.add_argument("query")
|
|
115
|
+
retrieve.add_argument("--scope", choices=("workspace", "working_set"), default="workspace")
|
|
116
|
+
retrieve.add_argument("--json", action="store_true", dest="as_json")
|
|
117
|
+
|
|
118
|
+
observe = subparsers.add_parser("observe", help="stage explicit file content")
|
|
119
|
+
observe.add_argument("path")
|
|
120
|
+
observe.add_argument("--content", default=None)
|
|
121
|
+
observe.add_argument("--file", dest="content_file", default=None, help="read content from a file")
|
|
122
|
+
observe.add_argument("--json", action="store_true", dest="as_json")
|
|
123
|
+
|
|
124
|
+
remove = subparsers.add_parser("remove", help="stage a file deletion")
|
|
125
|
+
remove.add_argument("path")
|
|
126
|
+
remove.add_argument("--json", action="store_true", dest="as_json")
|
|
127
|
+
|
|
128
|
+
watch = subparsers.add_parser("watch", help="watch the workspace and sync on changes")
|
|
129
|
+
watch.add_argument("--debounce-ms", type=int, default=300)
|
|
130
|
+
|
|
131
|
+
skill = subparsers.add_parser("skill", help="locate or install the Codex skill")
|
|
132
|
+
skill_subparsers = skill.add_subparsers(dest="skill_command", required=True)
|
|
133
|
+
skill_path = skill_subparsers.add_parser("path", help="print the bundled skill path")
|
|
134
|
+
skill_path.add_argument("--json", action="store_true", dest="as_json")
|
|
135
|
+
skill_install = skill_subparsers.add_parser("install", help="install the skill into Codex skills")
|
|
136
|
+
skill_install.add_argument("--target", default=None, help="installation directory")
|
|
137
|
+
skill_install.add_argument("--force", action="store_true", help="replace an existing installation")
|
|
138
|
+
skill_install.add_argument("--json", action="store_true", dest="as_json")
|
|
139
|
+
return parser
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _settings(args: argparse.Namespace) -> ClientSettings:
|
|
143
|
+
return ClientSettings.from_environment(
|
|
144
|
+
root=args.root,
|
|
145
|
+
api_url=args.api_url,
|
|
146
|
+
state_path=args.state_path,
|
|
147
|
+
runtime_patterns=(
|
|
148
|
+
iter_runtime_patterns(iter(args.ignore)) if args.ignore else None
|
|
149
|
+
),
|
|
150
|
+
require_api_key=args.command in {"sync", "retrieve", "watch"},
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _run(args: argparse.Namespace) -> int:
|
|
155
|
+
if args.command == "skill":
|
|
156
|
+
source = _skill_source()
|
|
157
|
+
if args.skill_command == "path":
|
|
158
|
+
payload = {"path": source.resolve().as_posix()}
|
|
159
|
+
if args.as_json:
|
|
160
|
+
_dump(payload)
|
|
161
|
+
else:
|
|
162
|
+
print(payload["path"])
|
|
163
|
+
return 0
|
|
164
|
+
target = Path(args.target).expanduser() if args.target else _codex_skill_target()
|
|
165
|
+
target = target.resolve()
|
|
166
|
+
if target == source.resolve():
|
|
167
|
+
raise ValueError("skill target is already the bundled skill directory")
|
|
168
|
+
if target.exists() and not args.force:
|
|
169
|
+
raise FileExistsError(f"skill target already exists: {target}; pass --force to replace it")
|
|
170
|
+
target.parent.mkdir(parents=True, exist_ok=True)
|
|
171
|
+
shutil.copytree(source, target, dirs_exist_ok=args.force)
|
|
172
|
+
payload = {"path": target.as_posix(), "status": "installed"}
|
|
173
|
+
if args.as_json:
|
|
174
|
+
_dump(payload)
|
|
175
|
+
else:
|
|
176
|
+
print(f"installed skill at {target}")
|
|
177
|
+
return 0
|
|
178
|
+
settings = _settings(args)
|
|
179
|
+
with ClientRuntime(settings) as runtime:
|
|
180
|
+
context = runtime.context()
|
|
181
|
+
if args.command == "sync":
|
|
182
|
+
result = context.sync()
|
|
183
|
+
if args.as_json:
|
|
184
|
+
_dump(result)
|
|
185
|
+
else:
|
|
186
|
+
print(f"synced checkpoint={result.checkpoint_id or '-'} uploaded={len(result.uploaded_blob_names)}")
|
|
187
|
+
return 0
|
|
188
|
+
if args.command == "status":
|
|
189
|
+
snapshot = context.snapshot()
|
|
190
|
+
if args.as_json:
|
|
191
|
+
_dump(_snapshot_payload(snapshot))
|
|
192
|
+
else:
|
|
193
|
+
present = sum(record.status.value == "present" for record in snapshot.files.values())
|
|
194
|
+
print(f"root={settings.root}")
|
|
195
|
+
print(f"checkpoint={snapshot.checkpoint_id or '-'} generation={snapshot.generation} present={present}")
|
|
196
|
+
return 0
|
|
197
|
+
if args.command == "retrieve":
|
|
198
|
+
result = context.retrieve(args.query, scope=args.scope)
|
|
199
|
+
if args.as_json:
|
|
200
|
+
_dump(result)
|
|
201
|
+
else:
|
|
202
|
+
print(result.formatted_retrieval)
|
|
203
|
+
return 0
|
|
204
|
+
if args.command == "observe":
|
|
205
|
+
if args.content is not None and args.content_file is not None:
|
|
206
|
+
raise ValueError("use either --content or --file, not both")
|
|
207
|
+
if args.content_file is not None:
|
|
208
|
+
content = Path(args.content_file).read_text(encoding="utf-8")
|
|
209
|
+
elif args.content is not None:
|
|
210
|
+
content = args.content
|
|
211
|
+
else:
|
|
212
|
+
content = sys.stdin.read()
|
|
213
|
+
context.observe_file(args.path, content)
|
|
214
|
+
payload = {"path": args.path, "status": "present"}
|
|
215
|
+
if args.as_json:
|
|
216
|
+
_dump(payload)
|
|
217
|
+
else:
|
|
218
|
+
print(f"observed {args.path}")
|
|
219
|
+
return 0
|
|
220
|
+
if args.command == "remove":
|
|
221
|
+
context.remove_file(args.path)
|
|
222
|
+
payload = {"path": args.path, "status": "deleted"}
|
|
223
|
+
if args.as_json:
|
|
224
|
+
_dump(payload)
|
|
225
|
+
else:
|
|
226
|
+
print(f"removed {args.path}")
|
|
227
|
+
return 0
|
|
228
|
+
if args.command == "watch":
|
|
229
|
+
handle = context.start_watching(debounce_ms=args.debounce_ms)
|
|
230
|
+
print(f"watching {settings.root}; press Ctrl-C to stop", file=sys.stderr)
|
|
231
|
+
try:
|
|
232
|
+
handle.join()
|
|
233
|
+
except KeyboardInterrupt:
|
|
234
|
+
handle.stop()
|
|
235
|
+
handle.join(2.0)
|
|
236
|
+
return 0
|
|
237
|
+
raise ValueError(f"unknown command: {args.command}")
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
241
|
+
args = build_parser().parse_args(argv)
|
|
242
|
+
try:
|
|
243
|
+
return _run(args)
|
|
244
|
+
except (ClientConfigurationError, FileAdmissionError, OceApiError, BlobCompatibilityError, CheckpointResetRequired, TimeoutError, ValueError, OSError) as exc:
|
|
245
|
+
print(f"oce-client: {exc}", file=sys.stderr)
|
|
246
|
+
return 1
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
if __name__ == "__main__":
|
|
250
|
+
raise SystemExit(main())
|