Skip to content

Commit bd0997a

Browse files
BtXinclaude
andauthored
feat(cli): reframe skardi CLI as thin HTTP client for skardi-server (#170)
* docs: add CLI reframe design spec (thin HTTP client) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: spec update — skardi run accepts JSON request body via -d/--data Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: add CLI reframe implementation plan Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: plan without inline Rust code — behavior and interface specs only Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * refactor(cli): strip local engine, aliases, and features to thin-client skeleton * feat(cli): add ClientConfig resolution (flag > env > file > default) * feat(cli): add JSON-first param parsing and -d/--data body building * feat(cli): add JSON/table output rendering with stderr truncation notice * feat(cli): add ApiClient with bearer auth and uniform error mapping * feat(cli): skardi query posts SQL to the server /query endpoint * feat(cli): skardi run executes named server pipelines (replaces aliases) * feat(cli): add pipeline/schema/health discovery commands * feat(cli): port job commands onto shared ApiClient with global connection flags * feat(cli): add e2e smoke test and rewrite CLI docs for thin-client surface * fix(cli): usage errors exit 1 per exit-code contract; update jobs param docs Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs: migrate per-source docs and demo READMEs to server-based CLI workflow All docs that taught the old local-engine CLI (query --ctx, --schema, aliases, CLI feature flags, NAME:TYPE=VALUE params) now describe the thin-client flow: start skardi-server with --ctx/--semantics/--pipeline, then use skardi query / run / schema / pipeline against it. Demo READMEs (llm_wiki, rag) rewritten around real pipeline names; embedding docs point features at the server build; broken 'cargo install -p skardi-cli --features' commands removed. Every new example verified against the actual CLI/server flags and pipeline YAML params. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(cli): sort table columns explicitly — map order is feature-dependent CI runs the workspace with --all-features, which pulls in bson (mongo provider) and its serde_json/preserve_order feature; Cargo feature unification then makes serde_json::Map insertion-ordered for every crate in the build, flipping render_table's column order and failing its exact-string test. Sorting the first row's keys explicitly keeps column order alphabetical and deterministic regardless of feature unification. Reproduced locally by enabling preserve_order on the CLI's serde_json: test fails without the sort, passes with it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: start skardi-server before integration tests for CLI e2e smoke tests The integration step runs every #[ignore] test in the workspace; the CLI's e2e_smoke tests are ignored because their backing service is skardi-server itself, which CI never started — so they failed with connection errors. Provision it like the other integration services: launch the already-built server binary and wait for /health before the --ignored pass. Verified locally: both smoke tests pass against a bare server (SELECT 1 needs no sources; the 404 path needs no pipelines). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(cli): address PR review — encode URL components, warn on cleartext token, cap response bodies - Percent-encode user-supplied path segments and query values (pipeline/ job names, run ids) at all seven interpolation sites via a shared encode_component helper (percent-encoding crate, RFC 3986 unreserved set); a name like 'a/b' can no longer change the request route. The hand-rolled 3-character urlencode in jobs.rs is deleted in its favor. - Warn on stderr when a bearer token is configured together with a plain http:// URL to a non-loopback host (cleartext credential travel). - Cap buffered response bodies at 256 MiB, enforced while streaming chunks so chunked responses without Content-Length are covered too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(cli): treat empty config values as unset; align tables by char count - An exported-but-empty SKARDI_SERVER_URL/SKARDI_API_TOKEN (or empty --server/--token/file value) now falls through the precedence chain instead of producing an empty base URL or an empty Bearer header. - Table column widths are measured in characters, matching the unit format! pads by, so non-ASCII cells no longer shift the separators. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(cli): trim kept config values; recognize more loopback forms in cleartext check - non_empty now trims the value it keeps, so a padded SKARDI_SERVER_URL=" http://x " resolves to a clean URL instead of a malformed one. - The cleartext-token warning no longer misfires on IPv4-mapped IPv6 loopback ([::ffff:127.0.0.1]) or the unspecified addresses (0.0.0.0 / [::]), which route to localhost as connect targets. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent f41664f commit bd0997a

38 files changed

Lines changed: 3825 additions & 5411 deletions

.github/workflows/ci.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,21 @@ jobs:
554554
# NULL-bearing row: no `category` attribute, exercising NULL handling.
555555
put '{"product_id":{"S":"PROD005"},"name":{"S":"Desk Chair"},"price":{"N":"199.99"},"in_stock":{"BOOL":true}}'
556556
557+
- name: Start skardi-server for CLI e2e smoke tests
558+
run: |
559+
# The CLI's #[ignore] e2e_smoke tests are integration tests whose
560+
# backing service is skardi-server itself; they hit the default
561+
# http://127.0.0.1:8080 (no ctx/pipelines needed). The nextest step
562+
# above already compiled the server binary into the llvm-cov
563+
# target dir, so this starts without rebuilding.
564+
./target/llvm-cov-target/debug/skardi-server --port 8080 &
565+
for i in $(seq 1 30); do
566+
curl -sf http://127.0.0.1:8080/health >/dev/null && exit 0
567+
sleep 1
568+
done
569+
echo "skardi-server failed to become healthy" >&2
570+
exit 1
571+
557572
- name: Execute Integration tests
558573
run: cargo llvm-cov --no-report nextest --all-features -- --ignored
559574

Cargo.lock

Lines changed: 55 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
**The most agent-friendly backend for builders shipping their first AI agent.** The painful part of agent-building isn't the prompt — it's the data plumbing: a vector DB to stand up, an embedding pipeline to maintain, a chunker to debug, a tool-call wrapper to write for every query. Skardi auto-bootstraps the primitives every agent needs so you ship in hours, not weeks:
4444

4545
- **[`auto_rag`](https://github.com/SkardiLabs/skardi-skills/tree/main/auto_rag) — Auto-RAG (Retrieval Augmented Generation).** Server-backed hybrid search (vector + full-text + RRF) via `skardi-server` over a datastore you already control (Postgres + pgvector, MongoDB, or Lance). The skill renders the config, starts the server, and drives ingestion and queries through REST. One command from a datastore to a working retrieval API your agent calls as a tool — no Python orchestration layer, no glue code.
46-
- **[`auto_knowledge_base`](https://github.com/SkardiLabs/skardi-skills/tree/main/auto_knowledge_base) — Auto agent knowledge base.** Point it at a directory of documents and you have a queryable, citable local KB one command later. Chunking, embedding, indexing, and hybrid search are exposed to your agent as a `skardi grep` verb. Zero infra by default (SQLite + local embeddings), so any Claude Code / Cursor session gets a grounded knowledge base over your files.
46+
- **[`auto_knowledge_base`](https://github.com/SkardiLabs/skardi-skills/tree/main/auto_knowledge_base) — Auto agent knowledge base.** Point it at a directory of documents and you have a queryable, citable local KB one command later. Chunking, embedding, indexing, and hybrid search are exposed to your agent as a `skardi run` verb. Zero infra by default (SQLite + local embeddings), so any Claude Code / Cursor session gets a grounded knowledge base over your files.
4747
- **Zero bootstrap**`ctx.yaml`, pipelines, schema, server, all rendered for you by **[skardi-skills](https://github.com/SkardiLabs/skardi-skills)**. Install once and your agent has a working data tool the same hour.
4848

4949
You build the agent. Skardi handles the data plane.
@@ -96,8 +96,8 @@ spec:
9696
```
9797
9898
```bash
99-
$ skardi grep "turing machines" --limit=10 # shell tool, any Bash-tool agent
100-
$ curl -X POST :8080/wiki-search-hybrid/execute -d '{...}' # same pipeline, served as REST
99+
$ skardi run wiki-search-hybrid -p query="turing machines" -p limit=10 # shell tool, any Bash-tool agent
100+
$ curl -X POST :8080/wiki-search-hybrid/execute -d '{...}' # same pipeline, served as REST
101101
```
102102

103103
That uniformity is also what makes the *durable* reason to put a plane in front possible: **governance**. Once every read and write goes through one engine, three primitives compose on top of it instead of fragmenting across N SDKs:
@@ -120,7 +120,7 @@ For the longer technical read — each primitive's shipped vs. in-progress statu
120120
(YAML pipelines)
121121
```
122122

123-
- **`skardi` CLI**run federated SQL or any pipeline directly from a shell. Drop it into Claude Code, Cursor, or any agent with a Bash tool and it's wired with no MCP config.
123+
- **`skardi` CLI**a thin HTTP client: send ad-hoc SQL or call any pipeline against a running `skardi-server`, right from a shell. Drop it into Claude Code, Cursor, or any agent with a Bash tool and it's wired with no MCP config.
124124
- **`skardi-server`** — same engine over HTTP, with two surfaces: **online serving** (a YAML pipeline becomes a parameterized REST endpoint with an inferred request/response schema) and **offline jobs** (async batch writes into Lance or any read-write DB; if a job fails halfway you don't get a corrupted dataset, and every run is logged in a SQLite ledger you can list and inspect).
125125
- **Skardi-server is stateful but lightweight** — a single Rust process, plus a small SQLite file for the run ledger and (optional) auth. One server can serve many agents; deploy it next to your data, behind your usual auth.
126126

@@ -181,14 +181,11 @@ sudo mv skardi /usr/local/bin/
181181
182182
### First-time agent loop (two minutes)
183183

184-
**Step 1 — ad-hoc SQL, no server, no pre-registration.** The CLI prints results as a pretty-printed table to stdout — see [docs/cli.md](docs/cli.md).
184+
The CLI is a thin HTTP client — every command below talks to a running
185+
`skardi-server`, so step 1 is always starting one. See
186+
[docs/cli.md](docs/cli.md) for the full command reference.
185187

186-
```bash
187-
skardi query --sql "SELECT * FROM './data/products.csv' LIMIT 10"
188-
skardi query --sql "SELECT * FROM 's3://mybucket/events.parquet' LIMIT 10"
189-
```
190-
191-
**Step 2 — register named sources in a `ctx.yaml`.** Five example lines:
188+
**Step 1 — register named sources in a `ctx.yaml`, and start the server.** Five example lines:
192189

193190
```yaml
194191
# ctx.yaml — describes where your data lives. Each entry gets a name you use in SQL.
@@ -209,12 +206,23 @@ spec:
209206
```
210207

211208
```bash
212-
skardi query --ctx ./ctx.yaml --sql "SELECT * FROM products LIMIT 10"
209+
cargo run --bin skardi-server -- --ctx ./ctx.yaml --port 8080
213210
```
214211

215-
**Step 3 — turn a parameterized SQL into an agent-callable verb.** Two YAMLs from [`demo/llm_wiki/cli/`](demo/llm_wiki/cli/) — the actual files, not pseudo-code:
212+
**Step 2 — ad-hoc SQL against the running server.** The CLI prints the
213+
response's `data` array as pretty-printed JSON to stdout by default (pass
214+
`--table` for an ASCII table) — see [docs/cli.md](docs/cli.md).
215+
216+
```bash
217+
skardi query -e "SELECT * FROM products LIMIT 10"
218+
skardi query -e "SELECT * FROM products LIMIT 10" --table
219+
```
216220

217-
> ⚠️ Unlike Steps 1–2 (zero-dependency), this hybrid-search verb also needs a local embedding model at `models/…` + the `sqlite-vec` extension (`SQLITE_VEC_PATH`) and a seeded DB — so it is **not runnable by copy-paste alone**. The [`auto_knowledge_base` skill](https://github.com/SkardiLabs/skardi-skills/tree/main/auto_knowledge_base) sets all of this up for you; use it if you just want the verb working.
221+
**Step 3 — turn a parameterized SQL into an agent-callable pipeline.** One
222+
YAML from [`demo/llm_wiki/cli/`](demo/llm_wiki/cli/) — the actual file, not
223+
pseudo-code:
224+
225+
> ⚠️ Unlike Steps 1–2 (zero-dependency), this hybrid-search pipeline also needs a local embedding model at `models/…` + the `sqlite-vec` extension (`SQLITE_VEC_PATH`) and a seeded DB — so it is **not runnable by copy-paste alone**. The [`auto_knowledge_base` skill](https://github.com/SkardiLabs/skardi-skills/tree/main/auto_knowledge_base) sets all of this up for you; use it if you just want the pipeline working.
218226
219227
```yaml
220228
# pipelines/search_hybrid.yaml — declares the SQL once; Skardi infers the params
@@ -239,24 +247,22 @@ spec:
239247
ORDER BY rrf_score DESC LIMIT {limit}
240248
```
241249
242-
```yaml
243-
# aliases.yaml — gives the pipeline a short shell verb, with positional + default args
244-
kind: aliases
245-
spec:
246-
grep:
247-
pipeline: wiki-search-hybrid
248-
positional: [query]
249-
defaults: { text_query: "{query}", text_weight: "0.5", vector_weight: "0.5", limit: "10" }
250-
description: Hybrid search over the wiki (RRF of sqlite_knn + sqlite_fts)
251-
```
252-
253-
Now any agent with a shell can call it:
250+
Restart the server with `--pipeline pipelines/` so it loads this file (see
251+
[Skardi Server](#skardi-server--online-serving--offline-jobs) below), and
252+
any agent with a shell can call it by name — no separate alias file, no
253+
alias-management step:
254254

255255
```bash
256-
skardi grep "turing machine computation" --limit=10
256+
skardi run wiki-search-hybrid \
257+
-p query="turing machine computation" \
258+
-p text_query="turing machine computation" \
259+
-p vector_weight=0.5 -p text_weight=0.5 -p limit=10
257260
```
258261

259-
The output your agent sees is the standard Arrow-pretty table on stdout (`+----+--------+ ...`). Over the server (next section), the same pipeline is mounted at `POST /wiki-search-hybrid/execute` — the request body is a JSON object whose keys match the `{...}` placeholders in the SQL (Skardi infers this schema and serves it on `GET /data_source` so the agent can read it). One full cycle:
262+
The same pipeline is mounted at `POST /wiki-search-hybrid/execute` — the
263+
request body is a JSON object whose keys match the `{...}` placeholders in
264+
the SQL (Skardi infers this schema and serves it on `GET /data_source` so
265+
the agent can read it). One full cycle:
260266

261267
```bash
262268
curl -X POST http://localhost:8080/wiki-search-hybrid/execute \
@@ -272,7 +278,7 @@ curl -X POST http://localhost:8080/wiki-search-hybrid/execute \
272278
"rows": 10, "execution_time_ms": 23 }
273279
```
274280

275-
Drop `skardi` into a Claude Code or Cursor session and the agent can already use any pipeline you've declared as a tool via its Bash integration. No MCP config, no separate server — that's the MVP design intent.
281+
Drop `skardi` into a Claude Code or Cursor session and the agent can already use any pipeline you've declared as a tool via its Bash integration, as long as a `skardi-server` is reachable — no MCP config needed.
276282

277283
### Skardi Server — online serving + offline jobs
278284

@@ -439,8 +445,8 @@ We're **building in public**. `[x]` means shipped today, `[ ]` means open for co
439445
`3` Online serving (pipelines)
440446
- [x] Declarative YAML → parameterized REST endpoint with inferred request / response schema
441447
- [x] Built-in pipeline dashboard
442-
- [x] CLI pipeline binding + aliases `skardi run <pipeline> --param=…` and user-defined verb aliases ([#90](https://github.com/SkardiLabs/skardi/pull/90))
443-
- [x] CLI federated SQL `skardi query` against files, object stores, datalake formats, and databases with no server required
448+
- [x] CLI pipeline binding — `skardi run <pipeline> -p name=value` calls any named, server-loaded pipeline directly ([#90](https://github.com/SkardiLabs/skardi/pull/90))
449+
- [x] CLI as a thin HTTP client — `skardi query` / `skardi run` send ad-hoc SQL and pipeline calls to a running `skardi-server` over the network; federation across sources happens server-side (see [docs/cli.md](docs/cli.md))
444450

445451
`4` Offline jobs
446452
- [x] Async batch execution with submit / poll / cancel ([#98](https://github.com/SkardiLabs/skardi/pull/98))
@@ -451,7 +457,7 @@ We're **building in public**. `[x]` means shipped today, `[ ]` means open for co
451457
`5` Agent-facing bindings
452458
- [x] REST — every pipeline served as a parameterized HTTP endpoint
453459
- [x] Shell — every pipeline runnable as a `skardi` command; works in Claude Code, Cursor, and any agent with a Bash tool
454-
- [ ] Skills generator — `skardi skills generate --ctx <ctx.yaml> --out .claude/skills/` emits a skill Markdown per pipeline for Claude Code / Desktop auto-discovery
460+
- [ ] Skills generator — `skardi skills generate --server <URL> --out .claude/skills/` emits a skill Markdown per pipeline for Claude Code / Desktop auto-discovery
455461
- [ ] MCP binding — same pipeline YAML projected to MCP tools for non-Claude hosts
456462

457463
`6` Governance & lineage

crates/cli/Cargo.toml

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,41 +12,20 @@ license.workspace = true
1212
name = "skardi"
1313
path = "src/main.rs"
1414

15-
[features]
16-
default = ["candle", "gguf", "onnx", "remote-embed", "chunking"]
17-
candle = ["skardi/candle"]
18-
gguf = ["skardi/gguf"]
19-
onnx = ["skardi/onnx"]
20-
remote-embed = ["skardi/remote-embed"]
21-
# Umbrellas must enable the CLI's OWN feature flags (which the UDF registration
22-
# in main.rs is gated on), not just the underlying `skardi` crate's — otherwise
23-
# `--features embedding` compiles the deps but the CLI never registers the UDFs.
24-
embedding = ["onnx", "candle", "gguf", "remote-embed"]
25-
chunking = ["skardi/chunking"]
26-
# RAG umbrella: chunk → embed → write loop in one feature.
27-
rag = ["embedding", "chunking"]
28-
2915
[dependencies]
3016
anyhow = { workspace = true }
31-
arrow = { version = "57.0.1", features = ["prettyprint"] }
32-
async-trait = { workspace = true }
3317
clap = { version = "4.5", features = ["derive"] }
34-
datafusion = { workspace = true }
35-
datafusion-catalog = { version = "52.1.0" }
36-
datafusion-session = { version = "52.1.0" }
3718
dirs = "5.0"
38-
lance = { workspace = true }
39-
object_store = { workspace = true, features = ["aws", "gcp", "azure", "http"] }
19+
percent-encoding = "2.3"
4020
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
4121
serde = { workspace = true }
4222
serde_json = { workspace = true }
4323
serde_yaml = { workspace = true }
44-
skardi = { path = "../skardi" }
4524
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
46-
url = { workspace = true }
4725

4826
[dev-dependencies]
4927
tempfile = { workspace = true }
28+
wiremock = "0.6"
5029

5130
[lints]
5231
workspace = true

0 commit comments

Comments
 (0)