dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Named database connections for DeepSeek Harness - configure MySQL in a settings page, then refer to them by name in chat. Read-only by default, writes opt-in per connection.
English | 中文
Named database connections for DeepSeek Harness. Configure your databases in a settings page, then refer to them by name in chat — say "use 128" and the model knows which database to reach and how.
Asking an AI to query a database normally means restating host, port, user, and password every time. This plugin stores those in configuration and leaves one short name (128) for you and the model to use:
connection as their first argument, so the model only carries names.Plugin directory: <path-to>/dsh-db-connections
Install dependencies first (mysql2 and yaml):
cd <path-to>/dsh-db-connections
npm install
Then install into a profile with the DSH plugin command. The Electron desktop app owns its desktop profile exclusively, so use the CLI the app ships:
# Default Windows desktop path; substitute your own if installed elsewhere.
"C:\Program Files\deepseek\resources\runtime\cli\bin\dsh.cmd" ^
plugin --profile desktop add "<path-to>/dsh-db-connections"
dsh plugin add records the package in the profile's dsh.profile.bundles, and the package's own cordis.patch.yml inserts the id: db-connections row with connections: [] (connect to nothing by default).
Any other profile (web, tui, your own) uses the ordinary CLI:
dsh plugin --profile <name> add /absolute/path/to/dsh-db-connections
Restart the desktop app once after installing, so the settings page appears: the client-plugin roster is scanned at startup, not re-scanned while running.
Conversely, the plugin directory must not be deleted, renamed, or moved: the profile references it through a
link:, and a missing directory producescannot resolve profile bundle "dsh-db-connections".
Two routes, one configuration — use whichever you prefer.
Open Settings → Databases to add, edit, and delete connections, pick read-only or read-write per connection from a dropdown, and press Test to verify a connection on the spot. Saving applies immediately — no restart, and no need to touch the configuration file.
The page follows the shell's own language setting (Chinese/English): dictionaries are contributed with ctx.locale.register() and lookups go through ctx.locale.bind(), rather than guessing from the browser, so the page follows a language switch.
Passwords are shown and edited as plain text: the value lives in a file you own, and hiding it only makes a wrong password impossible to spot.
Redefine the db-connections row in the profile's cordis.patch.yml. The loader replaces a row's whole config rather than deep-merging it, so restate every key:
- id: db-connections
name: "dsh-db-connections"
config:
connections:
- name: "128" # the name you and the model use in chat
host: 192.0.2.10
port: 3306 # optional, default 3306
user: root
password: "change-me" # or use passwordEnv, below
database: appdb # optional default schema
description: dev database # optional; shown in the prompt
allowWrite: false # optional, default false = read-only
Where the profile's cordis.patch.yml lives:
| Platform | Path |
|---|---|
| Windows | C:\Users\<you>\.dsh\profiles\desktop\cordis.patch.yml |
| macOS / Linux | ~/.dsh/profiles/desktop/cordis.patch.yml |
Press Reload on the settings page, or restart the app, to apply a hand edit. A change made through the settings page needs neither.
connections is a list, in order; names must be unique:
- id: db-connections
name: "dsh-db-connections"
config:
connections:
- name: "128"
host: 192.0.2.10
user: root
password: "change-me"
database: appdb
description: dev database
- name: "prod"
host: 192.0.2.9
user: readonly_user
passwordEnv: PROD_DB_PASSWORD # keeps the secret out of the file
database: app_prod
description: production (read-only account)
- name: "local"
host: 127.0.0.1
user: root
password: ""
description: local MySQL
Name an environment variable with passwordEnv; it is resolved at load, and a missing variable fails loudly rather than becoming an empty password:
passwordEnv: PROD_DB_PASSWORD
When both are present, passwordEnv wins. A settings-page save does not destroy this: the page edits raw configuration, so a resolved password is never written back to the file. Note the other direction: the settings page has no passwordEnv field of its own — creating the indirection is a hand edit of the configuration file.
Set per connection, read-only by default. The settings page's dropdown ("Read-only" / "Read-write") is exactly the allowWrite flag in the configuration file.
Read-only has two independent layers, neither of which is keyword matching in the plugin:
allowWrite: false (the default), db_execute refuses outright.db_query statement runs inside a server-enforced START TRANSACTION READ ONLY — MySQL rejects the write — so bypass-shaped statements such as SELECT ... INTO OUTFILE, SELECT ... FOR UPDATE, and WITH ... UPDATE are refused by the server too.Before enabling writes, confirm the database should accept changes from the agent. A read-only account is the better choice for most deployments than turning on allowWrite.
| Tool | Purpose |
|---|---|
db_query | Read-only queries, in a server-enforced read-only transaction. Returns columns plus rows. |
db_execute | Writes (INSERT/UPDATE/DELETE/DDL). Available only with allowWrite: true. |
db_schema | Inspect structure: omit database to list schemas; pass database to list tables; add table for columns, indexes, and DDL. |
You do not call these yourself. Typical requests:
db_schemadb_schemadb_querydb_execute (needs a writable connection)| Key | Default | Meaning |
|---|---|---|
name | required | Connection name, unique, ≤64 characters |
host | required | Hostname or IP |
user | required | Account name |
password | '' | Password, stored as plain text in the configuration file |
passwordEnv | — | Environment variable holding the password; wins over password |
port | 3306 | TCP port |
driver | mysql | Only mysql is supported |
database | — | Default schema; without it, qualify SQL as schema.table |
description | — | Shown in the prompt, for context |
allowWrite | false | Whether this connection accepts writes; the settings page's read-only / read-write dropdown |
ssl | false | Connect over TLS |
connectTimeoutMs | 10000 | Connection timeout |
queryTimeoutMs | 30000 | Per-statement timeout (client and server MAX_EXECUTION_TIME) |
maxRows | 200 | Rows returned per call; an excess is reported, never silently dropped |
maxCellChars | 500 | Characters kept per cell |
maxDdlChars | 20000 | Bound for the table DDL db_schema returns, kept separate so a CREATE TABLE is not cut off |
connectionLimit | 4 | Pool size |
A bad configuration fails at plugin load naming the offending key, rather than when the model first calls a tool.
multipleStatements: false): one call runs one statement.dateStrings, bigNumberStrings) so the plugin never invents a time-zone interpretation or loses precision.Content-Type: application/json on them (a cross-origin request with that type must preflight, and the API answers no preflight). Another web page therefore cannot read or change your passwords.A save edits the id: db-connections row in the running profile's cordis.patch.yml; the page shows the full path at the bottom. The write parses the document, replaces only that row, and re-serializes, so:
If the row is absent (for example you keep it in a different patch layer), a save creates it and leaves everything else alone. A file that is not valid YAML fails the save rather than being overwritten.
cd <path-to>/dsh-db-connections
npm install
node test-config.mjs # config validation + load/dispose lifecycle (no database)
node test-store.mjs # configuration file read/write + settings API (no database)
node test-client.mjs # browser half: slot registration, inject declarations, save payload
node test-render.mjs # renders the settings page: dropdown, save, test, add/remove (no browser)
node test-meta.mjs # plugin-list display name/description localization (locale files + export map)
node test-plugin-meta.mjs # calls the harness's own readPluginMeta to verify display metadata
node test-profile.mjs # package manifest + the harness's own scan deciding the page loads
node test-integration.mjs # real database: tools and every refusal path
node test-write.mjs # real database: read-write mode (creates and drops its own table)
node test-loader.mjs # loads through the real SystemPrompt/ToolRuntime services, then saves live
The scripts that contact a real database (test-integration, test-write, test-loader) hard-code connection settings at the top; read them before running.
Two tests run the harness's real logic rather than a restatement of it:
test-plugin-meta.mjs calls the exported readPluginMeta(), the code that decides what the plugin list displays. It imposes hard rules (the English file must be named exactly en.json, it must resolve through the export map, every language file must share its directory, filenames must be bare language ids); violating any one fails the whole metadata read and the list silently falls back to the English technical name. A hand-written check would miss that.test-profile.mjs calls ClientModuleRegistry.resolveMeta(). A client plugin must declare both dsh.client and exports["./client"]; without the latter the harness throws at startup — breaking the whole app, not merely hiding this plugin.dsh-db-connections/
├── index.js # entry: config, prompt section, three tools, settings API
├── client.js # browser half: the settings page (hand-written ModuleLoader bundle, no build step)
├── cordis.patch.yml # bundle layer: inserts the db-connections row
├── icon.svg # the icon shown in the plugin list
├── locale/
│ ├── en.json # plugin-list name and description, English
│ └── zh.json # plugin-list name and description, Chinese
├── src/
│ ├── api.js # settings API: same-origin, JSON, and error mapping
│ ├── config.js # config validation and normalization (fails loud at load)
│ ├── live.js # the mutable runtime connection set (a save swaps it in place)
│ ├── prompt.js # model-facing connection list (never a password)
│ ├── registry.js # pools plus the read-only execution path
│ ├── sql.js # statement classification and identifier quoting
│ ├── store.js # reads/writes the profile's cordis.patch.yml (keeps comments and other rows)
│ ├── tool.js # raw tool definition and argument validation
│ ├── tools.js # db_query / db_execute / db_schema
│ └── values.js # cell values to lossless JSON
└── test-*.mjs
The plugin has two independent localizations, covering two different surfaces:
| Surface | Mechanism | Files |
|---|---|---|
| Inside the settings page (field labels, buttons, hints) | the client locale service | the DICT in client.js, contributed with ctx.locale.register() and read through ctx.locale.bind() |
| The plugin list's name and description | package metadata | locale/en.json + locale/zh.json, each a meta.title / meta.description |
The second has hard rules, and violating any one silently falls back to the English technical name (no error is shown):
en.json; the harness finds it first, then scans that directory for other .json files as further languages.package.json must export "./locale/*.json": "./locale/*.json", or the harness cannot resolve it.zh.json is fine, so is zh-CN.json; zh_CN.json is not).en.json.package.json's description participates in fallback alongside the English dictionary; when the two disagree the list shows one text and other surfaces the other, so test-meta.mjs asserts they match.To add a third language, drop a file into locale/ (say ja.json); no code changes are needed.
src/registry.js, plus a SUPPORTED_DRIVERS entry in src/config.js.START TRANSACTION READ ONLY (MySQL 5.6+, MariaDB 10.0+). On older servers, use a read-only account instead.information_schema on every db_schema call — no cache, so what you see is current.dsh plugin remove and add again.