deyta-cli 0.1.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.
- deyta_cli-0.1.0/.gitignore +28 -0
- deyta_cli-0.1.0/LICENSE +21 -0
- deyta_cli-0.1.0/PKG-INFO +156 -0
- deyta_cli-0.1.0/README.md +125 -0
- deyta_cli-0.1.0/pyproject.toml +56 -0
- deyta_cli-0.1.0/src/deyta_cli/__init__.py +1 -0
- deyta_cli-0.1.0/src/deyta_cli/cli.py +65 -0
- deyta_cli-0.1.0/src/deyta_cli/client.py +108 -0
- deyta_cli-0.1.0/src/deyta_cli/commands/__init__.py +1 -0
- deyta_cli-0.1.0/src/deyta_cli/commands/_common.py +84 -0
- deyta_cli-0.1.0/src/deyta_cli/commands/aliases.py +47 -0
- deyta_cli-0.1.0/src/deyta_cli/commands/auth.py +25 -0
- deyta_cli-0.1.0/src/deyta_cli/commands/context.py +53 -0
- deyta_cli-0.1.0/src/deyta_cli/commands/db.py +58 -0
- deyta_cli-0.1.0/src/deyta_cli/commands/init.py +148 -0
- deyta_cli-0.1.0/src/deyta_cli/commands/memory.py +233 -0
- deyta_cli-0.1.0/src/deyta_cli/commands/namespace.py +117 -0
- deyta_cli-0.1.0/src/deyta_cli/commands/serve.py +102 -0
- deyta_cli-0.1.0/src/deyta_cli/commands/update.py +66 -0
- deyta_cli-0.1.0/src/deyta_cli/commands/version.py +36 -0
- deyta_cli-0.1.0/src/deyta_cli/config.py +349 -0
- deyta_cli-0.1.0/src/deyta_cli/docker.py +118 -0
- deyta_cli-0.1.0/src/deyta_cli/ingest_walk.py +49 -0
- deyta_cli-0.1.0/src/deyta_cli/render.py +121 -0
- deyta_cli-0.1.0/src/deyta_cli/server/__init__.py +1 -0
- deyta_cli-0.1.0/src/deyta_cli/server/app.py +80 -0
- deyta_cli-0.1.0/src/deyta_cli/server/routes.py +242 -0
- deyta_cli-0.1.0/src/deyta_cli/server/runner.py +151 -0
- deyta_cli-0.1.0/src/deyta_cli/templates/compose.yaml +52 -0
- deyta_cli-0.1.0/src/deyta_cli/versions.py +150 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
build/
|
|
6
|
+
dist/
|
|
7
|
+
|
|
8
|
+
# Virtual environment
|
|
9
|
+
.venv/
|
|
10
|
+
|
|
11
|
+
# Environment / secrets
|
|
12
|
+
.env
|
|
13
|
+
|
|
14
|
+
# Deyta CLI runtime state (detached daemon pid/port, logs)
|
|
15
|
+
.deyta/
|
|
16
|
+
|
|
17
|
+
# Local embedded databases (sqlite_lance backend)
|
|
18
|
+
*.db
|
|
19
|
+
*.db-*
|
|
20
|
+
*.lance/
|
|
21
|
+
khora.lance/
|
|
22
|
+
|
|
23
|
+
# Dev/test artifacts from running `deyta init` inside this repo.
|
|
24
|
+
# (Downstream projects DO commit their own deyta.toml — this ignore is repo-local.)
|
|
25
|
+
/deyta.toml
|
|
26
|
+
|
|
27
|
+
# OS
|
|
28
|
+
.DS_Store
|
deyta_cli-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 AllTheData Inc. (Deyta)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
deyta_cli-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: deyta-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A unified command-line tool for Deyta's services, wrapping Khora persistent memory for AI agents.
|
|
5
|
+
Project-URL: Homepage, https://github.com/DeytaHQ/deyta-cli
|
|
6
|
+
Project-URL: Repository, https://github.com/DeytaHQ/deyta-cli
|
|
7
|
+
Project-URL: Issues, https://github.com/DeytaHQ/deyta-cli/issues
|
|
8
|
+
Author-email: "AllTheData Inc. (Deyta)" <dev@deyta.ai>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,ai,cli,embeddings,khora,memory,rag,typer
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
19
|
+
Classifier: Topic :: Utilities
|
|
20
|
+
Requires-Python: >=3.13
|
|
21
|
+
Requires-Dist: fastapi>=0.115.0
|
|
22
|
+
Requires-Dist: httpx>=0.28.0
|
|
23
|
+
Requires-Dist: khora[embedded]>=0.19.0
|
|
24
|
+
Requires-Dist: packaging>=24.0
|
|
25
|
+
Requires-Dist: questionary>=2.1.1
|
|
26
|
+
Requires-Dist: rich>=15.0.0
|
|
27
|
+
Requires-Dist: tomli-w>=1.0.0
|
|
28
|
+
Requires-Dist: typer>=0.26.7
|
|
29
|
+
Requires-Dist: uvicorn[standard]>=0.34.0
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# Deyta CLI
|
|
33
|
+
|
|
34
|
+
A unified command-line tool for Deyta's services. Today it wraps
|
|
35
|
+
[Khora](https://github.com/DeytaHQ/khora) (persistent memory for AI agents); the
|
|
36
|
+
command surface is built so future services and a cloud platform slot in without
|
|
37
|
+
breaking existing commands.
|
|
38
|
+
|
|
39
|
+
## How it works
|
|
40
|
+
|
|
41
|
+
Khora is an in-process Python library, not a server. The CLI runs a local **daemon**
|
|
42
|
+
(`deyta serve` — a FastAPI app holding one `Khora` instance open) and talks to it over
|
|
43
|
+
HTTP. The CLI itself never imports Khora. The target server is resolved per command:
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
--host flag > DEYTA_HOST env > active context > http://localhost:8787
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
That resolution is the local↔cloud seam: switching to a cloud platform later is a new
|
|
50
|
+
context, not new commands.
|
|
51
|
+
|
|
52
|
+
## Requirements
|
|
53
|
+
|
|
54
|
+
- Python 3.13+
|
|
55
|
+
- `OPENAI_API_KEY` in the environment (Khora uses it for embeddings and entity extraction)
|
|
56
|
+
- Docker — only for the `postgres` backend (`deyta db up`); the embedded backend needs none
|
|
57
|
+
|
|
58
|
+
## Install
|
|
59
|
+
|
|
60
|
+
`deyta` is a CLI, so install it as an isolated **tool** rather than into a project
|
|
61
|
+
environment. This puts the `deyta` command on your PATH and keeps its dependencies
|
|
62
|
+
from colliding with anything else:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
uv tool install deyta-cli # recommended
|
|
66
|
+
# or:
|
|
67
|
+
pipx install deyta-cli
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Both create a dedicated environment just for Deyta and expose `deyta` everywhere — no
|
|
71
|
+
virtualenv to activate. To upgrade later: `uv tool upgrade deyta-cli` (or
|
|
72
|
+
`pipx upgrade deyta-cli`).
|
|
73
|
+
|
|
74
|
+
**macOS (Homebrew):**
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
brew tap deytahq/deyta
|
|
78
|
+
brew trust deytahq/deyta # current Homebrew requires trusting any third-party tap
|
|
79
|
+
brew install deyta
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This installs into its own virtualenv (using prebuilt wheels for the native
|
|
83
|
+
dependencies) and puts `deyta` on your PATH. Upgrade with `brew upgrade deyta`.
|
|
84
|
+
|
|
85
|
+
> The `brew trust` step is a Homebrew default for **all** non-official taps, not
|
|
86
|
+
> something specific to Deyta — without it Homebrew refuses to load the formula.
|
|
87
|
+
|
|
88
|
+
> Avoid `pip install deyta-cli`. Bare `pip` installs into whatever Python environment
|
|
89
|
+
> happens to be active, so the `deyta` command only works while that environment is
|
|
90
|
+
> activated — and it can clash with other packages. Use `uv tool` / `pipx` for CLIs.
|
|
91
|
+
|
|
92
|
+
### From source (development)
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
git clone https://github.com/DeytaHQ/deyta-cli && cd deyta-cli
|
|
96
|
+
uv sync # creates .venv with all deps
|
|
97
|
+
uv run deyta --help # run without activating the venv
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Quickstart (embedded, no Docker)
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
deyta init # choose "embedded (sqlite_lance, no Docker)"
|
|
104
|
+
deyta up # start the whole stack in the background (datastores if postgres, then the daemon)
|
|
105
|
+
|
|
106
|
+
deyta ns create demo # create a namespace; becomes active
|
|
107
|
+
deyta ingest ./docs # walk files, chunk + remember (Rich progress)
|
|
108
|
+
deyta query "your question"
|
|
109
|
+
|
|
110
|
+
deyta down # stop the stack when you're done
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`deyta up` is the one-command path; `deyta serve` still exists if you'd rather run the
|
|
114
|
+
daemon in the foreground (and `deyta db up` to manage just the datastores).
|
|
115
|
+
|
|
116
|
+
## Commands
|
|
117
|
+
|
|
118
|
+
| Command | Purpose |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `deyta init` | Scaffold `deyta.toml` (pick backend, ontology defaults) |
|
|
121
|
+
| `deyta serve [--port] [--detach]` | Start the daemon (foreground by default) |
|
|
122
|
+
| `deyta status` / `deyta stop` | Inspect / stop a detached daemon |
|
|
123
|
+
| `deyta up` / `deyta down` | Bring the whole local stack up/down (datastores + server) |
|
|
124
|
+
| `deyta db up\|down\|status\|logs` | Manage Postgres + Neo4j (Docker; postgres backend) |
|
|
125
|
+
| `deyta ns create\|list\|get\|delete\|use` | Manage namespaces (`namespace` is the long form) |
|
|
126
|
+
| `deyta memory remember\|recall\|forget\|ingest` | Khora primitives |
|
|
127
|
+
| `deyta ingest <path>` | Shorthand for `memory ingest` |
|
|
128
|
+
| `deyta query "<text>"` | Shorthand for `memory recall` (`--mode`, `-k`, `--json`, `--context`) |
|
|
129
|
+
| `deyta context use\|list\|current` | Switch between local and (future) cloud |
|
|
130
|
+
| `deyta login` / `logout` | Cloud auth (not yet available) |
|
|
131
|
+
| `deyta version [--no-check]` | Show installed CLI + Khora versions; flag PyPI updates |
|
|
132
|
+
| `deyta update [--yes] [--dry-run]` | Upgrade whichever of the CLI / Khora is outdated |
|
|
133
|
+
|
|
134
|
+
## Backends
|
|
135
|
+
|
|
136
|
+
- **embedded** (`sqlite_lance`) — SQLite + LanceDB, fully in-process, zero infra. Default for quickstart.
|
|
137
|
+
- **postgres** — Postgres + pgvector + Neo4j via `deyta db up` (vendored Docker Compose,
|
|
138
|
+
pinned to `pgvector/pgvector:pg17` and `neo4j:2025.12.1`).
|
|
139
|
+
|
|
140
|
+
## Configuration & state
|
|
141
|
+
|
|
142
|
+
- `./deyta.toml` — project config: backend, server port, default ontology, namespace
|
|
143
|
+
name→UUID aliases, active namespace. Commit this.
|
|
144
|
+
- `./.deyta/` — runtime state for a detached daemon (pid/port, logs). Gitignored.
|
|
145
|
+
- `~/.config/deyta/config.toml` — contexts (local/cloud). `auth.json` holds cloud tokens.
|
|
146
|
+
|
|
147
|
+
Ontology: `deyta init` writes a generic default `entity_types` / `relationship_types`
|
|
148
|
+
so `deyta ingest` works with no flags; override per run with `--entity-types` /
|
|
149
|
+
`--relationship-types`.
|
|
150
|
+
|
|
151
|
+
## Releasing
|
|
152
|
+
|
|
153
|
+
The package builds with hatchling; the `deyta` command comes from the
|
|
154
|
+
`[project.scripts]` entry point in `pyproject.toml`. Pushing a `vX.Y.Z` tag
|
|
155
|
+
publishes to PyPI (via GitHub Actions Trusted Publishing) and bumps the Homebrew
|
|
156
|
+
tap. See [RELEASING.md](RELEASING.md) for the one-time setup and the release steps.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Deyta CLI
|
|
2
|
+
|
|
3
|
+
A unified command-line tool for Deyta's services. Today it wraps
|
|
4
|
+
[Khora](https://github.com/DeytaHQ/khora) (persistent memory for AI agents); the
|
|
5
|
+
command surface is built so future services and a cloud platform slot in without
|
|
6
|
+
breaking existing commands.
|
|
7
|
+
|
|
8
|
+
## How it works
|
|
9
|
+
|
|
10
|
+
Khora is an in-process Python library, not a server. The CLI runs a local **daemon**
|
|
11
|
+
(`deyta serve` — a FastAPI app holding one `Khora` instance open) and talks to it over
|
|
12
|
+
HTTP. The CLI itself never imports Khora. The target server is resolved per command:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
--host flag > DEYTA_HOST env > active context > http://localhost:8787
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
That resolution is the local↔cloud seam: switching to a cloud platform later is a new
|
|
19
|
+
context, not new commands.
|
|
20
|
+
|
|
21
|
+
## Requirements
|
|
22
|
+
|
|
23
|
+
- Python 3.13+
|
|
24
|
+
- `OPENAI_API_KEY` in the environment (Khora uses it for embeddings and entity extraction)
|
|
25
|
+
- Docker — only for the `postgres` backend (`deyta db up`); the embedded backend needs none
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
`deyta` is a CLI, so install it as an isolated **tool** rather than into a project
|
|
30
|
+
environment. This puts the `deyta` command on your PATH and keeps its dependencies
|
|
31
|
+
from colliding with anything else:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv tool install deyta-cli # recommended
|
|
35
|
+
# or:
|
|
36
|
+
pipx install deyta-cli
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Both create a dedicated environment just for Deyta and expose `deyta` everywhere — no
|
|
40
|
+
virtualenv to activate. To upgrade later: `uv tool upgrade deyta-cli` (or
|
|
41
|
+
`pipx upgrade deyta-cli`).
|
|
42
|
+
|
|
43
|
+
**macOS (Homebrew):**
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
brew tap deytahq/deyta
|
|
47
|
+
brew trust deytahq/deyta # current Homebrew requires trusting any third-party tap
|
|
48
|
+
brew install deyta
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
This installs into its own virtualenv (using prebuilt wheels for the native
|
|
52
|
+
dependencies) and puts `deyta` on your PATH. Upgrade with `brew upgrade deyta`.
|
|
53
|
+
|
|
54
|
+
> The `brew trust` step is a Homebrew default for **all** non-official taps, not
|
|
55
|
+
> something specific to Deyta — without it Homebrew refuses to load the formula.
|
|
56
|
+
|
|
57
|
+
> Avoid `pip install deyta-cli`. Bare `pip` installs into whatever Python environment
|
|
58
|
+
> happens to be active, so the `deyta` command only works while that environment is
|
|
59
|
+
> activated — and it can clash with other packages. Use `uv tool` / `pipx` for CLIs.
|
|
60
|
+
|
|
61
|
+
### From source (development)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git clone https://github.com/DeytaHQ/deyta-cli && cd deyta-cli
|
|
65
|
+
uv sync # creates .venv with all deps
|
|
66
|
+
uv run deyta --help # run without activating the venv
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Quickstart (embedded, no Docker)
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
deyta init # choose "embedded (sqlite_lance, no Docker)"
|
|
73
|
+
deyta up # start the whole stack in the background (datastores if postgres, then the daemon)
|
|
74
|
+
|
|
75
|
+
deyta ns create demo # create a namespace; becomes active
|
|
76
|
+
deyta ingest ./docs # walk files, chunk + remember (Rich progress)
|
|
77
|
+
deyta query "your question"
|
|
78
|
+
|
|
79
|
+
deyta down # stop the stack when you're done
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`deyta up` is the one-command path; `deyta serve` still exists if you'd rather run the
|
|
83
|
+
daemon in the foreground (and `deyta db up` to manage just the datastores).
|
|
84
|
+
|
|
85
|
+
## Commands
|
|
86
|
+
|
|
87
|
+
| Command | Purpose |
|
|
88
|
+
|---|---|
|
|
89
|
+
| `deyta init` | Scaffold `deyta.toml` (pick backend, ontology defaults) |
|
|
90
|
+
| `deyta serve [--port] [--detach]` | Start the daemon (foreground by default) |
|
|
91
|
+
| `deyta status` / `deyta stop` | Inspect / stop a detached daemon |
|
|
92
|
+
| `deyta up` / `deyta down` | Bring the whole local stack up/down (datastores + server) |
|
|
93
|
+
| `deyta db up\|down\|status\|logs` | Manage Postgres + Neo4j (Docker; postgres backend) |
|
|
94
|
+
| `deyta ns create\|list\|get\|delete\|use` | Manage namespaces (`namespace` is the long form) |
|
|
95
|
+
| `deyta memory remember\|recall\|forget\|ingest` | Khora primitives |
|
|
96
|
+
| `deyta ingest <path>` | Shorthand for `memory ingest` |
|
|
97
|
+
| `deyta query "<text>"` | Shorthand for `memory recall` (`--mode`, `-k`, `--json`, `--context`) |
|
|
98
|
+
| `deyta context use\|list\|current` | Switch between local and (future) cloud |
|
|
99
|
+
| `deyta login` / `logout` | Cloud auth (not yet available) |
|
|
100
|
+
| `deyta version [--no-check]` | Show installed CLI + Khora versions; flag PyPI updates |
|
|
101
|
+
| `deyta update [--yes] [--dry-run]` | Upgrade whichever of the CLI / Khora is outdated |
|
|
102
|
+
|
|
103
|
+
## Backends
|
|
104
|
+
|
|
105
|
+
- **embedded** (`sqlite_lance`) — SQLite + LanceDB, fully in-process, zero infra. Default for quickstart.
|
|
106
|
+
- **postgres** — Postgres + pgvector + Neo4j via `deyta db up` (vendored Docker Compose,
|
|
107
|
+
pinned to `pgvector/pgvector:pg17` and `neo4j:2025.12.1`).
|
|
108
|
+
|
|
109
|
+
## Configuration & state
|
|
110
|
+
|
|
111
|
+
- `./deyta.toml` — project config: backend, server port, default ontology, namespace
|
|
112
|
+
name→UUID aliases, active namespace. Commit this.
|
|
113
|
+
- `./.deyta/` — runtime state for a detached daemon (pid/port, logs). Gitignored.
|
|
114
|
+
- `~/.config/deyta/config.toml` — contexts (local/cloud). `auth.json` holds cloud tokens.
|
|
115
|
+
|
|
116
|
+
Ontology: `deyta init` writes a generic default `entity_types` / `relationship_types`
|
|
117
|
+
so `deyta ingest` works with no flags; override per run with `--entity-types` /
|
|
118
|
+
`--relationship-types`.
|
|
119
|
+
|
|
120
|
+
## Releasing
|
|
121
|
+
|
|
122
|
+
The package builds with hatchling; the `deyta` command comes from the
|
|
123
|
+
`[project.scripts]` entry point in `pyproject.toml`. Pushing a `vX.Y.Z` tag
|
|
124
|
+
publishes to PyPI (via GitHub Actions Trusted Publishing) and bumps the Homebrew
|
|
125
|
+
tap. See [RELEASING.md](RELEASING.md) for the one-time setup and the release steps.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "deyta-cli"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A unified command-line tool for Deyta's services, wrapping Khora persistent memory for AI agents."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.13"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
license-files = ["LICENSE"]
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "AllTheData Inc. (Deyta)", email = "dev@deyta.ai" },
|
|
11
|
+
]
|
|
12
|
+
keywords = ["cli", "khora", "memory", "ai", "agents", "rag", "embeddings", "typer"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.13",
|
|
20
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
21
|
+
"Topic :: Utilities",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"khora[embedded]>=0.19.0",
|
|
25
|
+
"questionary>=2.1.1",
|
|
26
|
+
"rich>=15.0.0",
|
|
27
|
+
"typer>=0.26.7",
|
|
28
|
+
"fastapi>=0.115.0",
|
|
29
|
+
"uvicorn[standard]>=0.34.0",
|
|
30
|
+
"httpx>=0.28.0",
|
|
31
|
+
"tomli-w>=1.0.0",
|
|
32
|
+
"packaging>=24.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/DeytaHQ/deyta-cli"
|
|
37
|
+
Repository = "https://github.com/DeytaHQ/deyta-cli"
|
|
38
|
+
Issues = "https://github.com/DeytaHQ/deyta-cli/issues"
|
|
39
|
+
|
|
40
|
+
[project.scripts]
|
|
41
|
+
deyta = "deyta_cli.cli:app"
|
|
42
|
+
|
|
43
|
+
[build-system]
|
|
44
|
+
requires = ["hatchling"]
|
|
45
|
+
build-backend = "hatchling.build"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel]
|
|
48
|
+
packages = ["src/deyta_cli"]
|
|
49
|
+
|
|
50
|
+
[tool.hatch.build.targets.sdist]
|
|
51
|
+
include = [
|
|
52
|
+
"src/deyta_cli",
|
|
53
|
+
"README.md",
|
|
54
|
+
"LICENSE",
|
|
55
|
+
"pyproject.toml",
|
|
56
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Deyta CLI — unified entry point for Deyta's services.
|
|
2
|
+
|
|
3
|
+
Command altitudes:
|
|
4
|
+
- platform/runtime (top-level, never service-prefixed): init, serve, status, stop,
|
|
5
|
+
up, down, login, logout, context.
|
|
6
|
+
- data plane (flat in MVP): namespace/ns, memory, plus ingest/query shorthands.
|
|
7
|
+
|
|
8
|
+
The CLI is a thin HTTP client; all Khora work happens in the `deyta serve` daemon.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import typer
|
|
14
|
+
|
|
15
|
+
from .commands import auth, context, db, init, memory, namespace, serve, update, version
|
|
16
|
+
from .commands import aliases
|
|
17
|
+
|
|
18
|
+
app = typer.Typer(
|
|
19
|
+
help="Deyta — unified CLI for Deyta's services (memory powered by Khora).",
|
|
20
|
+
no_args_is_help=True,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.callback()
|
|
25
|
+
def main(
|
|
26
|
+
ctx: typer.Context,
|
|
27
|
+
host: str = typer.Option(
|
|
28
|
+
None,
|
|
29
|
+
"--host",
|
|
30
|
+
help="Target server URL. Overrides DEYTA_HOST and the active context.",
|
|
31
|
+
envvar="DEYTA_HOST",
|
|
32
|
+
),
|
|
33
|
+
) -> None:
|
|
34
|
+
"""Resolve the target host once and stash it for every command."""
|
|
35
|
+
ctx.obj = {"host": host}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ---- platform / runtime ---------------------------------------------------- #
|
|
39
|
+
app.command("init")(init.init)
|
|
40
|
+
app.command("serve")(serve.serve)
|
|
41
|
+
app.command("status")(serve.status)
|
|
42
|
+
app.command("stop")(serve.stop)
|
|
43
|
+
app.command("up")(serve.up)
|
|
44
|
+
app.command("down")(serve.down)
|
|
45
|
+
app.command("login")(auth.login)
|
|
46
|
+
app.command("logout")(auth.logout)
|
|
47
|
+
app.command("version")(version.version)
|
|
48
|
+
app.command("update")(update.update)
|
|
49
|
+
app.add_typer(context.app, name="context")
|
|
50
|
+
|
|
51
|
+
# ---- datastores (Docker) --------------------------------------------------- #
|
|
52
|
+
app.add_typer(db.app, name="db")
|
|
53
|
+
|
|
54
|
+
# ---- data plane ------------------------------------------------------------ #
|
|
55
|
+
app.add_typer(namespace.app, name="namespace")
|
|
56
|
+
app.add_typer(namespace.app, name="ns") # alias
|
|
57
|
+
app.add_typer(memory.app, name="memory")
|
|
58
|
+
|
|
59
|
+
# ---- top-level shorthands -------------------------------------------------- #
|
|
60
|
+
app.command("ingest")(aliases.ingest)
|
|
61
|
+
app.command("query")(aliases.query)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
if __name__ == "__main__":
|
|
65
|
+
app()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Thin HTTP client to a Deyta daemon (local ``deyta serve`` or, later, cloud).
|
|
2
|
+
|
|
3
|
+
The CLI never imports Khora. Every command builds a request, hands it to this
|
|
4
|
+
client, and renders the response. The base URL is resolved once (``--host`` >
|
|
5
|
+
``DEYTA_HOST`` > current context > localhost), so pointing the same commands at a
|
|
6
|
+
cloud platform is purely a matter of which context is active.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
from collections.abc import Iterator
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
import httpx
|
|
16
|
+
|
|
17
|
+
from . import config
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class DeytaClientError(Exception):
|
|
21
|
+
"""User-facing client error (connection refused, HTTP error, etc.)."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DeytaClient:
|
|
25
|
+
def __init__(self, host: str | None = None, *, timeout: float = 300.0) -> None:
|
|
26
|
+
self.host = config.resolve_host(host).rstrip("/")
|
|
27
|
+
self._timeout = timeout
|
|
28
|
+
token = config.auth_token(config.current_context()["name"])
|
|
29
|
+
self._headers = {"Authorization": f"Bearer {token}"} if token else {}
|
|
30
|
+
|
|
31
|
+
# ---- low-level -------------------------------------------------------- #
|
|
32
|
+
def _request(self, method: str, path: str, **kwargs: Any) -> Any:
|
|
33
|
+
url = f"{self.host}{path}"
|
|
34
|
+
try:
|
|
35
|
+
with httpx.Client(timeout=self._timeout, headers=self._headers) as c:
|
|
36
|
+
resp = c.request(method, url, **kwargs)
|
|
37
|
+
except httpx.ConnectError as exc:
|
|
38
|
+
raise DeytaClientError(
|
|
39
|
+
f"No Deyta server at {self.host}. Run `deyta serve` first."
|
|
40
|
+
) from exc
|
|
41
|
+
except httpx.HTTPError as exc:
|
|
42
|
+
raise DeytaClientError(f"Request to {url} failed: {exc}") from exc
|
|
43
|
+
if resp.status_code >= 400:
|
|
44
|
+
raise DeytaClientError(_error_detail(resp))
|
|
45
|
+
return resp.json() if resp.content else None
|
|
46
|
+
|
|
47
|
+
# ---- health ----------------------------------------------------------- #
|
|
48
|
+
def health(self) -> dict[str, Any]:
|
|
49
|
+
return self._request("GET", "/health")
|
|
50
|
+
|
|
51
|
+
def is_up(self) -> bool:
|
|
52
|
+
try:
|
|
53
|
+
self.health()
|
|
54
|
+
return True
|
|
55
|
+
except DeytaClientError:
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
# ---- namespaces ------------------------------------------------------- #
|
|
59
|
+
def create_namespace(self, name: str) -> dict[str, Any]:
|
|
60
|
+
return self._request("POST", "/namespaces", json={"name": name})
|
|
61
|
+
|
|
62
|
+
def list_namespaces(self) -> list[dict[str, Any]]:
|
|
63
|
+
return self._request("GET", "/namespaces")["namespaces"]
|
|
64
|
+
|
|
65
|
+
def get_namespace(self, namespace_id: str) -> dict[str, Any]:
|
|
66
|
+
return self._request("GET", f"/namespaces/{namespace_id}")
|
|
67
|
+
|
|
68
|
+
def delete_namespace(self, namespace_id: str) -> dict[str, Any]:
|
|
69
|
+
return self._request("DELETE", f"/namespaces/{namespace_id}")
|
|
70
|
+
|
|
71
|
+
# ---- memory ----------------------------------------------------------- #
|
|
72
|
+
def remember(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
73
|
+
return self._request("POST", "/remember", json=payload)
|
|
74
|
+
|
|
75
|
+
def recall(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
76
|
+
return self._request("POST", "/recall", json=payload)
|
|
77
|
+
|
|
78
|
+
def forget(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
79
|
+
return self._request("POST", "/forget", json=payload)
|
|
80
|
+
|
|
81
|
+
def ingest(self, payload: dict[str, Any]) -> Iterator[dict[str, Any]]:
|
|
82
|
+
"""Stream ingest progress as Server-Sent Events.
|
|
83
|
+
|
|
84
|
+
Yields ``{"type": "progress", "processed", "total"}`` events followed by a
|
|
85
|
+
single ``{"type": "result", ...}`` (or ``{"type": "error", ...}``) event.
|
|
86
|
+
"""
|
|
87
|
+
url = f"{self.host}/ingest"
|
|
88
|
+
try:
|
|
89
|
+
with httpx.Client(timeout=None, headers=self._headers) as c:
|
|
90
|
+
with c.stream("POST", url, json=payload) as resp:
|
|
91
|
+
if resp.status_code >= 400:
|
|
92
|
+
resp.read()
|
|
93
|
+
raise DeytaClientError(_error_detail(resp))
|
|
94
|
+
for line in resp.iter_lines():
|
|
95
|
+
if line.startswith("data: "):
|
|
96
|
+
yield json.loads(line[len("data: ") :])
|
|
97
|
+
except httpx.ConnectError as exc:
|
|
98
|
+
raise DeytaClientError(
|
|
99
|
+
f"No Deyta server at {self.host}. Run `deyta serve` first."
|
|
100
|
+
) from exc
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _error_detail(resp: httpx.Response) -> str:
|
|
104
|
+
try:
|
|
105
|
+
body = resp.json()
|
|
106
|
+
return str(body.get("detail", body))
|
|
107
|
+
except (json.JSONDecodeError, ValueError):
|
|
108
|
+
return f"HTTP {resp.status_code}: {resp.text[:200]}"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Typer command groups for the Deyta CLI."""
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Shared helpers for command implementations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from .. import config, docker, render
|
|
8
|
+
from ..client import DeytaClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def get_host(ctx: typer.Context) -> str | None:
|
|
12
|
+
obj = ctx.obj or {}
|
|
13
|
+
return obj.get("host")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def get_client(ctx: typer.Context) -> DeytaClient:
|
|
17
|
+
return DeytaClient(get_host(ctx))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def require_project(ctx: typer.Context) -> config.ProjectConfig:
|
|
21
|
+
"""Load ``deyta.toml`` or exit with guidance."""
|
|
22
|
+
cfg = config.load_project_config()
|
|
23
|
+
if cfg is None:
|
|
24
|
+
render.error("No deyta.toml found. Run `deyta init` first.")
|
|
25
|
+
raise typer.Exit(1)
|
|
26
|
+
return cfg
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def resolve_namespace(cfg: config.ProjectConfig, ns_opt: str | None) -> str:
|
|
30
|
+
"""Resolve a ``--namespace`` option (or the active namespace) to a UUID."""
|
|
31
|
+
target = ns_opt or cfg.active
|
|
32
|
+
if not target:
|
|
33
|
+
render.error(
|
|
34
|
+
"No namespace selected. Create one with `deyta ns create <name>` "
|
|
35
|
+
"and `deyta ns use <name>`, or pass --namespace."
|
|
36
|
+
)
|
|
37
|
+
raise typer.Exit(1)
|
|
38
|
+
return cfg.resolve_namespace(target)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def resolve_ontology(
|
|
42
|
+
cfg: config.ProjectConfig,
|
|
43
|
+
entity_types: str | None,
|
|
44
|
+
relationship_types: str | None,
|
|
45
|
+
) -> tuple[list[str], list[str]]:
|
|
46
|
+
"""Flags override the project default ontology; otherwise fall back to config."""
|
|
47
|
+
ents = _split(entity_types) if entity_types else cfg.entity_types
|
|
48
|
+
rels = _split(relationship_types) if relationship_types else cfg.relationship_types
|
|
49
|
+
return ents, rels
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _split(csv: str) -> list[str]:
|
|
53
|
+
return [item.strip() for item in csv.split(",") if item.strip()]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def bring_up_datastores() -> None:
|
|
57
|
+
"""Start Postgres + Neo4j and wait until healthy, with live progress.
|
|
58
|
+
|
|
59
|
+
Exits with a clear message on Docker errors or if they don't come up in time.
|
|
60
|
+
"""
|
|
61
|
+
try:
|
|
62
|
+
render.info("Starting datastores (Postgres + Neo4j)…")
|
|
63
|
+
docker.up(detach=True)
|
|
64
|
+
except docker.DockerError as exc:
|
|
65
|
+
render.error(str(exc))
|
|
66
|
+
raise typer.Exit(1) from exc
|
|
67
|
+
|
|
68
|
+
with render.console.status("[bold]Waiting for datastores…[/]", spinner="dots") as status:
|
|
69
|
+
|
|
70
|
+
def tick(states: dict[str, str]) -> None:
|
|
71
|
+
parts = " ".join(f"{svc}=[bold]{st}[/]" for svc, st in states.items())
|
|
72
|
+
status.update(
|
|
73
|
+
f"[bold]Waiting for datastores to be ready[/] "
|
|
74
|
+
f"— Neo4j can take ~30s {parts}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
healthy = docker.wait_healthy(on_tick=tick)
|
|
78
|
+
|
|
79
|
+
if not healthy:
|
|
80
|
+
render.error(
|
|
81
|
+
"Datastores didn't become healthy in time. Check `deyta db status` / `deyta db logs`."
|
|
82
|
+
)
|
|
83
|
+
raise typer.Exit(1)
|
|
84
|
+
render.success("Datastores healthy (postgres :5434, neo4j bolt :7688).")
|