This release is a pre-release and may not be stable for production use.
cisternal
Status: alpha. APIs may change without notice before 1.0.
Cisternal is a shared telemetry substrate and agent-asset export toolkit for the Praxia tool family. It has two parts:
- Telemetry — a lightweight, non-blocking event pipeline (JSONL export, OTLP export, MCP-tool registration wrapper) for instrumenting Python tools and MCP servers.
- Agent-asset export — a CLI that takes a registry of MCP tools/commands and emits native plugin/config bundles for downstream coding-agent surfaces: Claude Code, Cursor, GitHub Copilot, Antigravity, OpenCode, Pi, and JCode.
Install
pip install cisternal
For OTLP export support:
pip install "cisternal[otlp]"
Telemetry quickstart
import cisternal
cisternal.init() # log_dir defaults to ~/.cisternal/logs, or env-resolved
with cisternal.span("my.operation", request_id="abc123"):
do_work()
cisternal.emit_event("my.custom_event", tool="foo")
print(cisternal.status())
Check your effective telemetry configuration from the shell:
cisternal telemetry doctor
cisternal telemetry doctor --json --strict
Registering MCP tools
import cisternal
@cisternal.tool
def my_tool(x: int) -> int:
return x * 2
registry = cisternal.wire(server, app, adapter=my_adapter)
cisternal.tool is a pure-metadata decorator — it returns the original function unchanged. cisternal.wire() snapshots the registry at call time and registers each tool on a FastMCP server (and optionally a Cyclopts CLI app), returning a WiredRegistry for introspection.
Agent-asset export
A tool's agent plugin (skills, agents, hooks, MCP servers, declared in its
.praxia/manifest.toml) reaches a coding agent by one of three paths:
| Path 1: install through the tool | Path 2: direct surface export | Path 3: install from GitHub | |
|---|---|---|---|
| Who runs it | anyone who has the tool installed | the tool's developer, in its repo | anyone with read access to the repo |
| Command | <tool> plugin install claude (e.g. bth plugin install claude) |
cisternal assets export --manifest … --surface <s> --out DIR |
/plugin marketplace add maraxen/<tool> (or the maraxen/plugins index) |
Needs the cisternal CLI |
no — the tool mounts cisternal's sub-app | yes | no |
| Needs the tool installed | yes | yes (source checkout) | no, with [plugin.mcp] launch = "uvx" |
| Installs into the agent | yes (Claude Code) | no — writes files only (assets install / publish-shared also register them with Claude) |
yes (Claude Code) |
| Surfaces | install: claude · export: all seven |
all seven | claude |
Path 1 — <tool> plugin install|update
Users of a tool that mounts the sub-app:
bth plugin install claude # publish + register marketplace + install (user scope)
bth plugin install claude --scope project
bth plugin update claude # republish + update the installed copy
bth plugin export cursor --out DIR # files only, any surface
bth plugin info # where the bundle and the marketplace resolve from
bth plugin install claude --dry-run # show what would happen, change nothing
install publishes into the shared marketplace, registers that marketplace
with Claude Code if it isn't already, then installs the plugin at --scope.
If the plugin is already installed at that scope, install updates it (or
does nothing when it is current). Installs at other scopes are left alone.
update refuses to run when the plugin isn't installed, and updates every
stale install at the scope it was installed at. Both commands report skill and
agent copies in ~/.claude/ that shadow the plugin; --prune-shadowed moves
them into a backup. Restart Claude Code to
load the change. cisternal dogfoods this itself: cisternal plugin install claude.
Tool authors add it in three steps:
-
Mount the sub-app in the tool's cyclopts CLI:
from cisternal.plugin import PluginSpec, plugin_app app.command(plugin_app(PluginSpec(name="bathos", package="bathos", cli="bth")))
PluginSpecalso takesregistry/imports(merge@cisternal.toolcommands, for surfaces that emit them),version(defaults to the installed distribution's version),snapshotandmanifestpaths. -
Freeze the manifest into package data, so wheel installs work without the repo:
cisternal assets snapshot --manifest .praxia/manifest.toml --out src/bathos/agent_plugin.json
[tool.setuptools.package-data] # or your build backend's equivalent bathos = ["agent_plugin.json"]
-
Keep it in sync in CI:
cisternal assets snapshot --manifest .praxia/manifest.toml --out src/bathos/agent_plugin.json --checkexits 1 if the snapshot is missing or stale.
The bundle is taken from the first of: --manifest PATH; the tool's own
.praxia/manifest.toml above the imported package, for an editable install
(it is never searched for from inside site-packages, and must name this
plugin); the packaged snapshot. plugin info shows which one won.
Path 2 — direct surface export (cisternal assets …)
From the tool's source checkout, with the cisternal CLI:
cisternal assets export --manifest .praxia/manifest.toml --surface cursor --out ./dist/cursor
cisternal assets export --manifest .praxia/manifest.toml --dry-run # paths + sha256, writes nothing
cisternal assets inspect --manifest .praxia/manifest.toml # JSON load report
cisternal assets validate --manifest .praxia/manifest.toml # structural + golden checks
cisternal assets publish-shared --manifest .praxia/manifest.toml # into the shared marketplace (+ refresh)
cisternal assets update-all # republish every enrolled plugin
cisternal assets install --manifest .praxia/manifest.toml # standalone single-plugin marketplace
assets export only writes files. Nothing loads them until something
registers them. assets install makes the bundle its own single-plugin
marketplace and registers it with Claude Code. It requires a
[plugin.marketplace] table in the manifest:
[plugin.marketplace]
name = "my-plugin-marketplace"
[plugin.marketplace.owner]
name = "Your Name"
publish-shared instead adds the plugin to the shared multi-tool
marketplace, the same one Path 1 uses. update-all republishes every plugin
there from the repo that last published it. A plugin installed through Path 1
is not rebuilt by update-all; instead it prints that tool's own
<tool> plugin update claude, because the tool owns the recipe (version,
snapshot vs. checkout).
Path 3 — install straight from GitHub
A tool's repo becomes a one-plugin marketplace once it commits the bundle
that plugin repo-bundle generates:
<repo>/
├── .claude-plugin/marketplace.json # one entry, "source": "./plugin"
└── plugin/ # the Claude bundle; version = <release>+<content digest>
bth plugin repo-bundle # (re)write plugin/ and .claude-plugin/marketplace.json; commit both
bth plugin repo-bundle --check # CI: exit 1 if either is missing or stale
Users then run /plugin marketplace add maraxen/bathos and
/plugin install bathos@bathos. The bundle sits in plugin/, not the repo
root, so a root .mcp.json never registers a duplicate project MCP server
for people working in the repo. Private repos work for anyone whose git can
already clone them (Claude Code uses the machine's own git credentials).
The family index, maraxen/plugins, lists every tool from one catalog.
Each entry is a git-subdir source pinned to that tool's release tag;
bth plugin index-entry --repo maraxen/bathos prints it:
{"name": "bathos", "source": {"source": "git-subdir", "url": "maraxen/bathos", "path": "plugin", "ref": "v0.13.0a6"}}
A plugin installed from GitHub gets the tool's files, not its Python package, so give the MCP server a launcher that fetches the matching release:
[plugin.mcp]
command = ["bth-mcp"]
launch = "uvx" # default "path": run `command` as-is
uvx_from = "bathos[mcp]=={version}" # default "<plugin>=={version}"; or
# "git+https://github.com/maraxen/<repo>@v{version}"
{version} is filled in from the release version the bundle is built from,
so the server is always the same release as the plugin (and as the index
entry's ref). Install the plugin from one marketplace per machine, either
cisternal-local or GitHub: the same plugin from both appears twice.
Where the shared marketplace lives
It is derived data, so its location is configuration, never a baked-in path.
The first match wins (<tool> plugin info shows which):
--marketplace PATH$CISTERNAL_PLUGIN_MARKETPLACE(none/empty disables)[tool.cisternal] plugin_marketplace = "PATH"in the nearestpyproject.tomlplugin_marketplace = "PATH"in${XDG_CONFIG_HOME:-~/.config}/cisternal/config.toml
If none of these is set, the first command that needs the marketplace
(plugin install|update, assets publish-shared, assets update-all)
generates the per-machine config and reports it on stderr:
# ~/.config/cisternal/config.toml
plugin_marketplace = "~/.cisternal/claude-plugin-marketplace"
That file then decides, and you can change it. An existing config file keeps
its contents: the key is added as its first line. A malformed config file is
an error and is never overwritten. plugin info and --dry-run only report
that the file would be written. Set CISTERNAL_PLUGIN_MARKETPLACE=none to opt
out; commands then fail instead of generating anything.
Surfaces
Export targets: Claude Code, Cursor, GitHub Copilot, Antigravity, OpenCode, Pi, JCode.
Install (plugin install|update) is implemented for Claude Code. For the
other surfaces, plugin install <surface> points at plugin export.
Codex is not supported yet: there is no emitter, and Codex's documented
CLI can add a marketplace (codex plugin marketplace add) but installs only
through its /plugins UI. The installer table (INSTALLABLE_SURFACES in
cisternal.plugin.app) is where it would plug in.
The CLI is fastmcp-free by design — cisternal.cli imports and runs even in environments without fastmcp installed; asset-export logic never depends on the telemetry/registration surface.
Design notes
- Telemetry emission never raises: if the pipeline isn't initialized,
emit_event/spanare no-ops. - Registry state is process-scoped — call
cisternal.clear_registry()between tests to avoid cross-test contamination. - The M2 wire-time MCP callable is a pure passthrough: telemetry and shape adaptation are exclusively owned by the telemetry middleware, never by the registration wrapper itself.
License
MIT — see LICENSE.
Metadata
Release files for cisternal 0.1.1a14
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cisternal-0.1.1a14.tar.gz | 357.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cisternal-0.1.1a14-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 609.2 kB
Release files / cisternal-0.1.1a14.tar.gz
| Download URL | cisternal-0.1.1a14.tar.gz |
|---|---|
| Size | 357.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5b00eff4698d943b12f7cbaf71f3b67156339d0bb0249b4d28d56854d5d9fd73
|
|
BLAKE2b-256 checksum How to use checksums |
943c350cc06fed94bdd8a1052cfebf3f61fa6692259ccafd77456bb702a3175c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.22 {"installer":{"name":"uv","version":"0.12.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / cisternal-0.1.1a14-py3-none-any.whl
| Download URL | cisternal-0.1.1a14-py3-none-any.whl |
|---|---|
| Size | 251.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
731d5a04c2c98cf03ff5af7edb2d2297002534c06e4d77fefb3eca97380abd2c
|
|
BLAKE2b-256 checksum How to use checksums |
eb815b8a55617837bad2799e604da980011ffa08f37cb6d5eedc5b7c70951cf5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.12.22 {"installer":{"name":"uv","version":"0.12.22","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|