Skip to content
dsh.fish
Bundle

dsh-nebula

DSH plugin: connect to a NebulaGraph 5.0 server and execute GQL queries, returning results as structured rows plus an ngql-style table render. Bundles the gql-query-generator skill.

Source
xiajingchun
stars
4 stars
License
MIT
Updated
Updated 1 hour ago

Readme

# dsh-nebula

A [DeepSeek Harness](https://github.com/deepseek-ai) plugin that connects to a
**NebulaGraph v5** server and executes **ISO-GQL** statements, in the spirit of
the `ngql` console.

The plugin speaks the native NebulaGraph v5 wire protocol (gRPC +
`nebula.proto.graph.GraphService`) with a **pure-JS client** — no native
modules, no external gateway. Results come back as structured JSON rows plus
an ngql-style ASCII table render.

## Features

- Connect / authenticate against a graphd (`nebula_connect`), execute GQL on
  the same server-side session (`nebula_execute`), and close it
  (`nebula_disconnect`).
- **Named instance profiles (Settings → NebulaGraph)**: manage any number of
  connection presets in the DSH settings page — each with its own
  host/port/user/passwordRef/TLS policy — addressed by a short alias. The
  model then just says *"connect to the `prod` Nebula"* and calls
  `nebula_connect(instance: "prod")`; the profile supplies the parameters.
  An instance can be marked as the default, used when no alias is given.
  Per-call tool arguments still override the profile.
- Decodes the columnar `VectorResultTable` payload exactly like the official
  nebula-go v5 client: scalars, strings, temporal values, lists, sets, maps,
  records, vertices, edges, paths, embedding vectors, geography, `Any`-typed
  columns, const vectors, and null bitmaps.
- **Schema exploration (`nebula_schema`)**: one call runs `SHOW GRAPHS`,
  resolves the target graph (explicit `graph=` argument, the session working
  graph from `SESSION SET graph`, or the sole graph), then `DESC GRAPH TYPE`
  and returns the graph type's node types and edge types — labels,
  primary/multiedge keys, and properties. Read-only: the session working graph
  is never changed.
- **Interactive graph rendering (Web Client)**: when a `nebula_execute` result
  contains nodes, edges, or paths, the result is projected into a replayable
  graph payload and the bundled Web Client plugin renders it as an interactive
  [AntV G6](https://g6.antv.antgroup.com/) graph (drag / zoom / hover),
  alongside the normal table output. 
- Bundles the **`gql-query-generator`** skill: the plugin registers a
  `ctx.skills` provider so the agent's `skill` tool can load NebulaGraph
  GQL-writing guidance (reference docs ship in `gql-query-generator/references/`
  and resolve against the packaged directory).
- Plugin config supplies default connection parameters; every tool argument
  can override them per call.
- **Password never travels through the model**: `nebula_connect` takes a
  `passwordRef` (a credential reference / environment variable name), not the
  password value. The value is resolved per connection through the DSH
  credentials seam (`ctx.credentials`, falling back to the process
  environment) and cleared from memory right after authentication. Tool
  arguments, output, and the registry never carry the plaintext.
- **TLS by default, plaintext fallback available**: connections try TLS first
  (`tls: "auto"`, the default) and fall back to plaintext only when the server
  has no TLS listener — the fallback is reported to the caller as a warning.
  Set `tls: "on"` to require TLS (fails if the server does not speak it) or
  `tls: "off"` to force plaintext. CA / client certificate / key / server name
  overrides are configurable for private CAs and mutual TLS.
- Unloading the plugin closes every open session (effect-based cleanup).

## Tools

| Tool | Purpose |
| --- | --- |
| `nebula_connect` | Connect to a graphd and open a session. Arguments: `instance` (alias of a profile configured in Settings → NebulaGraph), `host`, `port`, `user`, `passwordRef`, `tls`, `ca`, `timeoutMs` (all optional). Resolution order: explicit `instance` alias → the default instance (if set) → plugin config; per-call arguments override the profile. The password itself is never an argument — it resolves from `passwordRef` via the DSH credentials seam / environment. Returns a `connectionId` (plus `instance`, `viaDefault`, `warning`, and `tlsFallback` when relevant). |
| `nebula_execute` | Run one GQL statement on a connection. Arguments: `connectionId` (required), `gql` (required), `timeoutMs`. Returns `{ ok, columns, rows, numRows, latencyUs, summary?, error? }`. |
| `nebula_schema` | Introspect a graph's schema. Arguments: `connectionId` (required), `graph` (optional — defaults to the session working graph, then the sole graph). Runs `SHOW GRAPHS` + `DESC GRAPH TYPE`, returns `{ graphs, graph, nodes, edges, … }`. Read-only. |
| `nebula_disconnect` | Close a connection and release its server-side session. |

## Skill

The plugin registers a `bundled` skill provider on `ctx.skills` named
`dsh-nebula` that serves the **`gql-query-generator`** skill from the packaged
`gql-query-generator/` directory. The model can load it through the `skill`
tool (or a direct user invocation); its `references/*.md` resolve against the
packaged directory via the skill's `resourceBase`. The skill body is read
from disk on each load, so editing `SKILL.md` takes effect without a rebuild
for link-installed plugins.

Typical agent flow:

```text
nebula_connect (host: 192.168.8.6, port: 9669, user: root, passwordRef: NEBULA_PASSWORD)
  → { connectionId: "…", tlsFallback: false }
nebula_execute (connectionId, gql: "SHOW GRAPHS")
nebula_schema  (connectionId, graph: "movie")
  → { graphs: […], graph: { name: "movie", graphType: "movie_type", … },
      nodes: [ { name: "Actor", labels: [Person], primaryKey: [id], … } ],
      edges: [ { name: "Act", source: "Actor", target: "Movie", … } ] }
nebula_execute (connectionId, gql: "SESSION SET graph movie")
nebula_execute (connectionId, gql: "MATCH (n) RETURN n LIMIT 5")
nebula_disconnect (connectionId)
```

## Install

The plugin is an out-of-tree DSH bundle: a plain npm package whose manifest
declares a `dsh.bundle` patch. Any DSH installation (rc.5+; the web profile
ships `ctx.skills`, so the bundled skill provider works out of the box) can
install it in one command, from any of these sources (the release tarball is
the smoothest):

```sh
# recommended — the latest release's prebuilt tarball (stable URL, always current)
dsh plugin --profile web add https://github.com/xiajingchun/dsh-nebulagraph-v5/releases/latest/download/dsh-nebula.tgz

# a specific release (versioned, reproducible)
dsh plugin --profile web add https://github.com/xiajingchun/dsh-nebulagraph-v5/releases/download/v0.1.0/dsh-nebula-0.1.0.tgz

# from the npm registry (once published)
dsh plugin --profile web add dsh-nebula

# straight from the git repository (builds from source — see note below)
dsh plugin --profile web add github:xiajingchun/dsh-nebulagraph-v5#v0.1.0

# from a local tarball / checkout while developing
dsh plugin --profile web add /path/to/dsh-nebula-0.1.0.tgz
dsh plugin --profile web add link:/path/to/dsh-nebula
```

The release tarballs are the `pnpm pack` output: prebuilt `lib/`, so the
release and registry installs need no install-time build and no build-script
approval. This runs `pnpm add` in the profile directory, then appends
`dsh-nebula` to `dsh.profile.bundles` because the package declares a
`dsh.bundle` patch (`cordis.patch.yml` inserts the plugin row). Restart the
profile (`dsh web`) for the new bundle to mount.

> **Git installs build from source.** pnpm fetches sources, not built
> artifacts: it runs the package's `prepare` script (here `pnpm build`), which
> needs the dev toolchain, and pnpm ≥10 refuses to run that script until it is
> explicitly allowed — the first `add` fails and prints the package key. Copy
> that key under `allowBuilds` in the profile's `pnpm-workspace.yaml` and
> re-run. Prefer the release tarball or registry installs to avoid this, and
> pin a commit (`github:…/#<sha>`) if you do install from git.

`nodeLinker: hoisted` profiles must approve the `protobufjs` build script (the
shipped web profile already does).

### Releasing (for maintainers)

Every **`v*` tag push** triggers the GitHub Actions workflow
([`.github/workflows/release.yml`](.github/workflows/release.yml)): it verifies
the tag matches `package.json`'s version, installs, runs the tests, packs, and
uploads both the versioned `dsh-nebula-<version>.tgz` and a stable
`dsh-nebula.tgz` alias to the release — so
`releases/latest/download/dsh-nebula.tgz` always points at the newest build:

```sh
git tag v0.1.0 && git push origin v0.1.0
```

Manually (same result, no CI):

```sh
pnpm build && pnpm pack     # → dsh-nebula-0.1.0.tgz
# attach the tarball to a GitHub release (optionally also as dsh-nebula.tgz)
```

For an npm release instead:

```sh
npm login        # once
pnpm publish     # runs build + tests via prepublishOnly
```

The tarball (`pnpm pack` → `dsh-nebula-0.1.0.tgz`) is fully self-contained:
compiled `lib/`, vendored protos, the packaged `gql-query-generator/` skill
(including its `.feature` evidence files), `cordis.patch.yml`, README, and
LICENSE. It was verified by installing the tarball into a fresh throwaway
profile: the bundle joins `dsh.profile.bundles`, the module resolves, and the
skill provider lists/loads `gql-query-generator` with its resource base inside
the installed package.

### Config

Plugin config lives in the profile's patch layer (e.g.
`~/.dsh/profiles/web/cordis.patch.yml`):

```yaml
- insert:
    - id: nebula
      name: dsh-nebula
      config:
        host: 127.0.0.1
        port: 9669
        user: root
        passwordRef: NEBULA_PASSWORD   # preferred: credential reference (env var name)
        tls: auto                      # auto | on | off
        timeoutMs: 30000
        maxConnections: 5
```

| Field | Default | Meaning |
| --- | --- | --- |
| `host` | `127.0.0.1` | Default graphd host. |
| `port` | `9669` | Default graphd gRPC port. |
| `user` | `root` | Default login user name. |
| `passwordRef` | *(none)* | Credential reference (environment variable name) resolving to the default password via the DSH credentials seam. **Preferred over `password`** — the value never appears in config surfaces, tool arguments, or logs. |
| `password` | `''` | Default login password (plaintext). Fallback used only when `passwordRef` is unset; keep it out of shared config files. |
| `tls` | `'auto'` | TLS mode: `auto` (try TLS, fall back to plaintext when the server has no TLS listener), `on` (require TLS), `off` (plaintext only). |
| `ca` | *(none)* | CA bundle (PEM) for verifying the server certificate (private / self-signed CAs). |
| `cert` / `key` | *(none)* | Client certificate and private key (PEM) for mutual TLS. The key is config-only and never exposed as a tool argument. |
| `servername` | *(none)* | Override the server name used for SNI and certificate verification. |
| `timeoutMs` | `30000` | Default per-request deadline (ms). |
| `maxConnections` | `5` | Upper bound on concurrently open connections. |

To use a `passwordRef`, store the value in the DSH credentials document
(`~/.dsh/.credentials.yaml`, mode 0600) or the process environment, e.g.:

```yaml
# ~/.dsh/.credentials.yaml
NEBULA_PASSWORD: s3cr3t
```

The password is resolved per connection at `nebula_connect` time and cleared
from memory immediately after authentication succeeds.

## Instance profiles (Settings → NebulaGraph)

![@ Settings](assets/settings.png)
When the profile composes a settings provider (the Web GUI does), the plugin
registers a **`dsh-nebula`** settings namespace and a **Settings →
NebulaGraph** page. There you can:

- **Add** connection presets — each with an **alias** (letters/digits/`_`/`-`,
  ≤ 64 chars), host, port, user, `passwordRef`, TLS mode, per-instance
  CA / client cert / key / servername, `timeoutMs`, and a note.
- **Edit / delete** presets and **mark one as the default**.
- **Manage the credential values** behind the `passwordRef`s from the same
  page: every reference the instances name (plus extra references you add,
  e.g. the plugin-config default's) gets a write-only password field and a
  clear button. Values are written through the harness **credentials RPC**
  (`credentials.set` / `credentials.unset`) and persisted to the DSH
  credentials document (`~/.dsh/.credentials.yaml`). The page only ever shows
  whether a value is configured — never the value itself — and inputs always
  start blank.
- Passwords never enter the settings document: only the `passwordRef`
  (credential reference / environment variable name) is stored, and it is
  resolved through the DSH credentials seam at connect time like the
  plugin-config default.

`nebula_connect` resolution order:

1. explicit `instance: <alias>` — an unknown alias is a hard error that lists
   the configured aliases;
2. the **default** instance, when one is set (the connect output reports
   `viaDefault: true`; a stale default referencing a deleted instance falls
   back to plugin config with a `warning`);
3. plugin config (`cordis.patch.yml`).

Per-call `host`/`port`/`user`/`passwordRef`/`tls`/`ca`/`timeoutMs` arguments
still override the resolved profile. A non-`auto` TLS mode inside a profile is
an enforced transport policy exactly like plugin config: a conflicting `tls`
tool argument is rejected.

The page is implemented by the plugin's Web Client bundle
(`client/NebulaInstancesSection.tsx`), which reads and writes the namespace
through a **plugin-owned Web route** (`/dsh-nebula/api`, see
`src/instances-api.ts`) with the same browser-trust fence as the harness
gateway. A plugin-owned route is required because the harness's own settings
RPC (`api.settings.*`) serves only namespaces on its explicit exposure
allowlist — the established third-party pattern in this deployment
(`dsh-better-sidebar`'s `/sidebar/api`). The host reads the same namespace at
connect time (`src/instances.ts`). Without a settings provider or a web
surface the route simply never mounts and the plugin keeps working on plugin
config alone.

## Development

```sh
pnpm install
pnpm typecheck
pnpm build     # tsc → lib/ + copies vendored protos to lib/proto
pnpm test      # decoder unit tests + gRPC integration tests (in-process fake GraphService)
```

The package is plain ESM (`"type": "module"`) with no runtime dependencies
beyond `@grpc/grpc-js`, `@grpc/proto-loader`, and `schemastery`.

## How it works

- **Protocol** — the vendored protos under `src/proto/nebula/` are the
  official NebulaGraph 5.0 definitions (graph/common/vector) from
  nebula-go v5. `AuthRequest.auth_info` is `JSON.stringify({ password })` and
  `ClientInfo.lang` advertises `JAVASCRIPT`; `Status.code == "00000"` means
  success. The password is sent only inside the one-time `Authenticate` call
  and cleared from the client/registry options immediately after success.
- **Transport security** — the channel uses gRPC credentials per the `tls`
  mode: `on` → `createSsl` (with optional CA / client cert / server-name
  override), `off` → `createInsecure`, `auto` → try TLS first, then recreate
  the channel with plaintext credentials only when the handshake fails at the
  transport level (never on an authentication failure). A plaintext fallback
  is surfaced as `tlsFallback` in the connect output and as a warning line.
- **Logout** — the v5 gRPC service has no signout RPC; closing a session
  executes the `SESSION CLOSE` statement before releasing the channel
  (mirroring nebula-go v5 `connection.Close()`), so the server-side session
  is released immediately instead of lingering until its idle timeout.
- **Decoder** — `src/decode/` is a faithful TypeScript port of nebula-go v5's
  `internal/decode` (column type schemas, flat/const vector layouts, chunked
  strings, node/edge property vectors, path adjacency lists, composite value
  encoding).
- **Registry** — open connections live in a per-plugin registry; disposing the
  plugin closes them all.
- **Instance profiles** — the `dsh-nebula` settings namespace is registered
  through `installSettingsSection` with the plugin config as the composition
  base: without a settings provider the tools read an empty section and fall
  back to config exactly as before, while the Web GUI edits the same
  namespace over the plugin-owned `/dsh-nebula/api` route. Alias resolution
  and validation are pure functions in `src/instances.ts`, shared by the
  tools and the settings surface contract.

## License

MIT

Install

dsh plugin --profile web add github:xiajingchun/dsh-nebulagraph-v5

Profile: web

  • This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source