Skip to content

The CLI

This page assumes you know what the MCP tools do, which MCP tools covers, and what the owner role is, which Row level security covers. The code is src/aizk/cli.py, src/aizk/commands/ and src/aizk/admin.py.

aizk is one cyclopts app with two halves that never meet.

aizk
├── auth login | logout | status ── talks MCP over the network
├── recall | remember | share | status
└── admin ... ── talks to PostgreSQL and Logto directly
├── health
├── server mcp | api | worker
├── queue status | doctor | retry {conversion,graph,profile}
├── database setup | migrate | make-migration | install-queue
│ check-rls | backup | restore | reset
├── graph rebuild | diagnose-extraction | decay | reembed
│ communities | raptor | forget
├── data ingest | promote | export | audit | rechunk
│ reconvert-web-pages | reconvert-scanned-documents
├── ontology define-entity | define-relation | list
├── auth audit | apply | roles | check-public | check-web
├── settings show | validate
└── api openapi

The client half is what a person installs. It holds no database credential and reaches a server only through the four MCP tools, so anything it can do, an agent could also do. The admin half is what an operator runs next to the deployment, and it needs credentials the client half never sees.

main() catches FileNotFoundError, PermissionError, ProtocolError, ValidationError, ValueError and httpx.HTTPError, prints one error: ... line and exits 2. Everything else keeps its traceback, because an unexpected failure is a bug worth seeing.

Every client command takes --server to override the selected profile and --json to print the model rather than a rendered summary.

Command What it calls
aizk auth login [server] interactive OAuth, then status to prove the session
aizk auth logout clears this server’s OAuth material only
aizk auth status validates stored credentials without opening a browser
aizk find [query] the find tool, reading stdin when the argument is absent
aizk keep [paths...] the keep tool, in text, URI or upload mode
aizk share <ids...> the share tool, also taking --query, --move and --dry-run
aizk status the status tool, rendered as account, usage and processing lines

Two stores back this. ProfileStore writes the nonsecret server selection under the XDG config root. Tokens go to the system keyring through KeyringStore, never to that file.

aizk keep ./contract.pdf is worth tracing, because it is three round trips and no credential ever touches the upload.

CLI ──▶ MCP server remember(upload={filename, media_type, size, sha256})
MCP server ──▶ CLI UploadTicketAccepted{status, upload_url, expires_seconds}
CLI ──▶ HTTP API PUT upload_url, raw bytes, no Authorization header
HTTP API ──▶ CLI ArtifactReceipt

The declaration is made before any bytes move, so an oversized or duplicate file is refused cheaply. The ticket is one-time and short-lived, and MemoryClient.upload streams the file in one megabyte chunks with a plain httpx client rather than the authenticated one. Passing several paths loops the whole exchange once per file and returns a RememberBatchResult.

File paths cannot be combined with --source-uri, --observed-at, --expires-at or --preserve-source, and the CLI rejects that before contacting the server.

User.owner opens the RLS-bypassing engine built from settings.admin_database_url, and it raises PermissionError unless the caller is the system identity. Anything that provisions, inspects across tenants, or rewrites rows in place needs it.

Needs the owner DSN Why
admin health reads schema, roles and row counts through the admin engine
admin database * migrations, queue install, backup, restore and reset
admin queue status, admin queue doctor queue tables sit outside row security
admin server worker scope_roster() reads distinct scope arrays past RLS
admin graph communities --everywhere walks that same roster, so it needs the owner DSN too
admin data reconvert-* and rechunk read and requeue originals across every scope set
admin graph reembed and admin graph raptor rewrite stored vectors and summary tiers in place
admin graph diagnose-extraction loads one chunk by ID with no caller

The rest run as an ordinary caller through the app role. admin data ingest, promote, export and audit, admin graph rebuild, decay, communities and forget, and the admin ontology commands all open User.system(scopes) and are filtered by the same policies a request would be. They take --user to act as a specific identity and --scopes where a destination is needed.

Three admin data sweeps exist for the other half of that problem, text already stored under an older policy. reconvert-web-pages requeues fetched HTML so the boilerplate cleaner reaches pages converted before it existed, reconvert-scanned-documents requeues what OCR read so a corrected engine or language rewrites it, and both take --limit, run oldest conversion first, and are safe to repeat until the backlog is gone.

rechunk is the cheap third one. Reconversion pays Docling and OCR to rebuild text that did not change, so when the chunk size, the lexical prefix or the embedding model moved instead, this re-splits and re-embeds from the Markdown already in PostgreSQL and never touches the original bytes. It walks least recently indexed first on the indexed_at column each finished job stamps, so repeating it with --limit marches through the corpus rather than circling the same head.

admin graph communities is the one to reach for after a deploy that changes how themes are cut. Alone it rebuilds a single scope set, the operator’s own by default, while --everywhere walks the same scope roster the weekly fan-out uses and refreshes every private and shared corpus the deployment stores. It replaces one generation per scope set, so it is safe to repeat and costs one model call per theme.

Three groups touch no database at all. admin auth audit, apply and roles talk only to Logto and are covered on The Logto boundary. roles prints every global role under the managed prefix with the accounts assigned to it, which is how an operator sees who holds aizk-admin before granting or revoking it. admin settings show prints the effective configuration with every field named in _SENSITIVE_FIELDS or ending in _api_key, _password, _secret or _token replaced by <redacted>. admin api openapi builds a throwaway API around an InertIntake and writes src/web/openapi.json.

admin queue doctor exits 1 when the report is not healthy, admin database check-rls exits 1 and prints each violation, and admin auth audit exits 1 when the tenant has drifted from src/deploy/logto.conf. All three are meant for a release gate rather than a person reading output.

admin database reset takes a --confirm argument that must match settings.db_name exactly, and raises otherwise. Run every one of these with chefe run from the monorepo root.