@ascdong/nexus
v0.1.6
Published
Unified Azure data access CLI for Log Analytics, Kusto, SQL Server / Azure SQL, PostgreSQL, and Storage Account
Downloads
1,089
Readme
nexus
Unified Azure data access CLI for Log Analytics, Kusto (Azure Data Explorer), Azure SQL Database, PostgreSQL, and Storage Accounts (Table/Blob/Queue). One command surface, named connectors, and Agent-friendly output.
Why
nexus connects multiple Azure data resources through named connectors and exposes a single way to query them and inspect their schema. It is built primarily for Agent consumption (stable parsing, token-efficient output) while staying usable by humans.
Architecture
nexus is a thin, uniform data-access layer in front of several data services that otherwise each have their own SDK, auth flow, query dialect, and result shape. A caller (an Agent or a human) issues one kind of command; nexus resolves the named connector, attaches the right credential, dispatches to the right backend adapter, and returns one normalized result shape in the caller's chosen format.
+------------------------------------------------------------+
| Caller: an Agent or a human at a terminal |
+------------------------------+-----------------------------+
| one command surface
| query / schema / sa / connector
v
+------------------------------------------------------------+
| nexus CLI |
| |
| commands --> registry(by type) --> Connector interface: |
| query |
| listTables |
| describeTable |
| testConnection |
| |
| config: ~/.nexus/config.json |
| (named connectors = coordinates only, |
| never secrets) |
| credential: AAD / DefaultAzureCredential |
| (+ on-disk token cache, per-service scope) |
| |
| output: ResultSet --> table | json | envelope |
| (types normalized to one vocabulary: |
| string long real datetime bool dynamic) |
+-----+---------------+---------------+---------------+------+
| | | |
v v v v
+-----------+ +-----------+ +-----------+ +-----------+
|log- | |kusto | |azure- | |storage- |
|analytics | |(ADX) | |sql | |account |
|(KQL) | |(KQL) | |(SQL) | |(no query |
| | | | | | |language) |
|Connector | |Connector | |Connector | |via sa: |
|adapter | |adapter | |adapter | |tbl/blob/q |
+-----+-----+ +-----+-----+ +-----+-----+ +-----+-----+
v v v v
Azure Monitor ADX cluster Azure SQL DB Storage acctHow it acts as a unified data-access layer:
- One surface, many backends.
queryandschemawork identically whether the target speaks KQL (Log Analytics, Kusto) or SQL (themssqlconnector for SQL Server / Azure SQL, andpostgres). You name a connector alias; nexus looks up its type and routes to the matching adapter — you never touch a per-service SDK or remember per-service flags. - A single
Connectorcontract. Every query-language backend implements the same four methods —query,listTables,describeTable,testConnection. Aregistrymaps connector type → adapter, so adding a new resource type is one new adapter plus one registration; commands and core are untouched. - Shared identity, per-service scope. All backends authenticate through one AAD credential (
DefaultAzureCredential) with an on-disk token cache; each adapter just supplies its own token scope. Sign in once (az login) and every connector works. - One normalized result. Backends return a common
ResultSet(columns + rows), with column types normalized to a single vocabulary (string/long/real/datetime/bool/dynamic) regardless of source. The output layer renders that astable(humans) orjson/envelope(Agents). - Honest about shape differences. Storage Accounts aren't a query language, so they're deliberately not forced through
query/schema; they get their own read-onlynexus sacommand group (Table/Blob/Queue) while still sharing the same connector config, credential, and output layer.
Install
End users install from npm — no Bun required, it runs on Node:
npm install -g @ascdong/nexus # or: npm install @ascdong/nexusRequires Node 20+.
Authentication
nexus authenticates to Azure with Microsoft Entra ID (AAD) via DefaultAzureCredential. Before running queries, sign in with one of the standard mechanisms it understands:
az login # most common for local useDefaultAzureCredential also picks up environment variables, Managed Identity, and VS Code sign-in automatically.
Token cache
Acquiring an AAD token cold is slow (~2–3s, because DefaultAzureCredential spawns the az CLI). To avoid paying that on every command, nexus caches the acquired token to ~/.nexus/token-cache.json (file mode 0600), keyed by scope, and refreshes it 1 minute before expiry. The first query in ~an hour is cold; subsequent queries reuse the cached token (token lookup drops from ~2s to ~0ms).
Security note: unlike
config.json, the token cache does contain a live access token — anyone who can read the file can act as you against those resources until the token expires (typically ~1 hour). It is written0600(owner-only) and never committed to git. Clear it any time with:
nexus auth clear-cacheThe query round-trip and process startup are not affected by the cache; only token acquisition is.
Connectors
Connectors are named aliases stored in ~/.nexus/config.json (override the directory with NEXUS_CONFIG_DIR). Most connectors hold connection coordinates only — never tokens or secrets (they authenticate via AAD).
Exception — secret-bearing auth modes. Two opt-in modes persist a secret in
config.json: astorage-accountadded with--connection-string(which embeds an account key) and apostgresconnector (which stores its--password). When you use these, treat~/.nexus/config.jsonas sensitive — it is written owner-readable; do not commit it or share it. Prefer the AAD modes where a secret-free option exists (e.g.storage-account --account).
# Log Analytics
nexus connector add prod-logs --type log-analytics --workspace-id <workspace-guid>
# Kusto / Azure Data Explorer
nexus connector add telemetry --type kusto \
--cluster-uri https://help.kusto.windows.net --database Samples
# SQL Server / Azure SQL Database
nexus connector add warehouse --type mssql \
--server myserver.database.windows.net --database Sales
# PostgreSQL (username/password; works with Azure Database for PostgreSQL)
nexus connector add pgwarehouse --type postgres \
--host myserver.postgres.database.windows.net --database appdb \
--user appuser --password <password> --ssl require # --ssl disable for local/dev
# Storage Account (Table + Blob + Queue) — AAD auth
nexus connector add mydata --type storage-account --account <storageacct>
# Storage Account — connection-string auth (embeds an account key)
nexus connector add mydata-cs --type storage-account \
--connection-string 'DefaultEndpointsProtocol=https;AccountName=<acct>;AccountKey=<key>;EndpointSuffix=core.windows.net'
nexus connector list # list all connectors (table; secrets redacted)
nexus connector list --output json # machine-readable (secrets still redacted)
nexus connector test prod-logs # verify connectivity
nexus connector remove prod-logsconnector list renders a human-readable table (alias, type, status, auth, detail) and never prints secrets — a storage connection string or postgres password always shows as <redacted>, in every output format.
Disable / enable
A connector can be disabled to block all data access through it (query, schema, sa, and connector test) without deleting its config:
nexus connector disable silicon-db # anyone can disable (the safe direction)
nexus connector enable silicon-db # interactive terminal only — re-type the alias to confirmWhile disabled, every data-access command for that alias fails with a CONFIG_ERROR (exit 2); management commands (list, remove, enable) still work. enable is human-gated: it refuses to run unless invoked from an interactive TTY and the operator re-types the alias. A non-interactive caller (e.g. an automated agent piping stdin) is rejected before any change is made.
This is a guardrail against accidental/automated re-enabling, not a cryptographic boundary: a human and an agent share the same OS user and binary, so anything the human can type, the same shell could. The TTY gate raises the bar (an agent driving nexus non-interactively cannot enable), but it does not defend against an adversary that can allocate a real terminal.
Query
nexus query <alias> "<statement>" [--output <fmt>] [--max-rows N] [--timeout MS]The statement is KQL for Log Analytics / Kusto, and SQL for Azure SQL / PostgreSQL:
nexus query telemetry "StormEvents | take 5"
nexus query warehouse "SELECT TOP 5 * FROM Orders"--max-rows caps the returned rows (default 1000) to protect downstream context; when the cap is hit, the result is flagged truncated.
Schema introspection
nexus schema tables <alias> # list tables
nexus schema describe <alias> <table> # columns + normalized typesStorage Account
A storage-account connector exposes three services under nexus sa (alias of
storageaccount). It is not used with query/schema — those are for the query
languages. All operations are read-only.
# Table Storage — query is table + OData filter + select (not a single-string language)
nexus sa table list mydata
nexus sa table query mydata MyTable --filter "PartitionKey eq 'orders'" --select RowKey,Amount
# Blob
nexus sa blob containers mydata
nexus sa blob list mydata mycontainer --prefix logs/
nexus sa blob read mydata mycontainer report.csv # bytes -> stdout
nexus sa blob read mydata mycontainer image.png -o image.png # binary -> file
# Queue
nexus sa queue list mydata
nexus sa queue peek mydata myqueue --count 10 # does not dequeue
nexus sa queue count mydata myqueueThe tabular commands (table list/query, blob containers/list, queue list/peek) honor
--output table|json|envelope. blob read returns raw bytes and ignores output formatting
by design — use -o <file> for binary blobs. All three services authenticate with the same
AAD identity (scope https://storage.azure.com/.default); data access requires a data-plane
role such as Storage Blob/Table/Queue Data Reader (control-plane roles like Owner do not
grant data access).
Output formats
--output selects the format. Default is table (human-readable). Agents should use json or envelope.
| Format | Shape | Best for |
|---|---|---|
| table (default) | aligned columns | humans |
| json | array of objects keyed by column name | quick scripting |
| envelope | columns declared once + rows as arrays + metadata | Agents (token-efficient) |
Example envelope output:
{
"status": "ok",
"columns": [{ "name": "Name", "type": "string" }, { "name": "Count", "type": "long" }],
"rows": [["a", 1], ["b", 2]],
"row_count": 2,
"truncated": false
}The envelope form declares column names once (instead of repeating them on every row), which keeps large result sets compact in an Agent's context window. Column types are normalized to a unified vocabulary (string, long, real, datetime, bool, dynamic) across all three resource types.
Output channels & exit codes
- Results go to stdout; diagnostic logs go to stderr — so an Agent can parse stdout cleanly.
- Exit codes:
0success,1query/connection/auth error,2usage/config error.
On error, json/envelope mode writes a structured { "status": "error", "code": "...", "message": "..." } to stdout (so the single stream stays self-describing — status is "ok" or "error"), while the default table mode prints error [CODE]: message to stderr. Either way the exit code is non-zero.
Extending
- New resource type: implement the
Connectorinterface insrc/connectors/, then register it insrc/cli.ts— no changes to commands or core. - New auth method: implement
CredentialProviderinsrc/core/credential.tsand select it increateCredential— connectors are unaffected, since each connector supplies its own token scope.
Development
Development uses Bun for fast installs, running, and tests. The published package is plain Node — Bun is a dev-time tool only.
bun install # install deps (generates bun.lock)
bun test # run the test suite
bun run dev <args> # run from source, no build step
bun run typecheck # tsc --noEmit
bun run build # tsc → dist/ (the Node-runnable artifact that ships)Publishing: bun run build (via prepublishOnly) compiles src/ to standard Node ESM in dist/ with a #!/usr/bin/env node shebang, then bun publish (or npm publish) uploads it. End users npm install and run it on Node — they never need Bun.
Known limitation: the mssql connector under the Bun runtime
Querying via the mssql connector (SQL Server / Azure SQL) fails under bun run (ESOCKET — Connection lost / socket hang up). This is an open Bun regression in its node:net compatibility layer that breaks the tedious (mssql) TDS-over-TLS handshake on some kernels (including WSL2); it is not a bug in this project — the same mssql call works under Node. Kusto and Log Analytics use plain HTTPS and are unaffected.
The fix is to run mssql queries on Node during development:
bun run build # produce dist/ once
node dist/cli.js query <mssql-alias> "SELECT ..." # mssql: use node
bun run dev query <kusto-or-la-alias> "..." # Kusto / LA: bun is fineThe published package runs on Node, so end users are never affected — all three resource types work normally via npm install.
See docs/superpowers/specs/ and docs/superpowers/plans/ for the full design and implementation plan.
