dsh-notes — Notes
English | 中文
A DeepSeek Harness plugin (bundle) that keeps your notes as local Markdown files, grouped by directory.
It is a DSH bundle in two halves:
- Host half (
index.js, plain ESM, no build step) — the read/write service for your notes. Choose a local directory as the root (default<DSH_HOME>/dsh-notes; switchable inside the page and remembered, no restart required); a direct subdirectory of the root is a group (second-level directory, created automatically when missing); an md file inside a group is a note; rule md files under the root or a group — AGENTS.md / CLAUDE.md / *.rules.md / *规则.md and the like — are read as convention context. Loopback + same-origin guarded routes serve the Web UI, and six agent tools are registered:note_root/note_list/note_rules/note_create/note_read/note_delete. - Client half (
src/client/*, built tolib/client.js) — the sidebar entry 「笔记」 opens a page: group list, rule viewer, note create (paste text) / view / edit / rename / delete, and switching the root directory.
Storage model (a directory is a group, an md file is a note)
<root>/ ← the local directory you chose (level 1)
AGENTS.md ← root-level rule file (optional, matched case-insensitively)
<group>/ ← level-2 directory = a group (created automatically if missing)
AGENTS.md ← that group's rule file (optional)
<note>.md ← one note (every other .md counts as a note)
Rule files = md files whose name matches (case-insensitive): AGENTS.md,
AGENTS.local.md, CLAUDE.md, *.rules.md, *规则.md / *規則.md. A note with the same
name cannot be created (that name is reserved for rules). Tolerant parsing: hand-written
plain Markdown can be dropped into a group as a note — a missing title falls back to the
file name, a missing group to the directory name; pasting a complete md file that already
carries front matter stores it verbatim and never wraps it twice.
Each note the plugin creates:
---
title: useState 笔记
group: 前端
created: 2025-01-01T00:00:00.000Z
updated: 2025-01-01T00:00:00.000Z
---
Body (any Markdown).
Features
- Groups = second-level directories under the root: list / create; writing a note creates the directory when the group does not exist.
- Notes: paste text or organized content → stored as
<group>/<note>.md; view / edit / rename / delete; the body is any Markdown. - Rule context: the AGENTS-style rule md files of the root and of each group are shown as
chips — click one to read it in full; before writing, the model can read the conventions
with
note_rulesand then file the note. - Root directory: switched inside the page, effective immediately and remembered (the
host stores the current path in
<DSH_HOME>/dsh-notes/.notes-root; an explicitrootDirin the config file takes precedence). - Agent tools:
note_root— view / switch the root directory;note_list— list the group overview, or list the notes inside one group;note_rules— read the root rules (plus a given group's rules) as writing context;note_create— create / update a note (the group is created automatically;update: trueoverwrites);note_read— read one note in full;note_delete— delete one note (removes only that md, never the group directory).
- Chinese UI; the sidebar entry DOM and visual language follow the skill center (36px
rounded row + 24px icon box, shell
--dsw-alias-*tokens, collapsible sidebar).
Layout
| Path | Purpose |
|---|---|
index.js |
Host half: group/note/rule read-write + agent tools + guarded routes |
cordis.patch.yml |
Bundle layer: host row notes |
src/client/* |
Browser half: sidebar entry + notes page overlay (React) |
lib/client.js |
Built client artifact (exports["./client"]) |
tsdown.config.ts |
Client build (closure-factory __ModuleLoader__ format) |
scripts/smoke-host.mjs |
Host-half smoke (md parsing/CRUD/rules/tools, isolated DSH_HOME) |
scripts/loader-boot.mjs |
Real Loader + webServer HTTP route smoke |
scripts/smoke-client.mjs |
Client artifact smoke (executes the closure in a stub browser) |
Build
The client half needs a build; the host half ships as plain ESM:
npm run build:client # tsdown → lib/client.js (+ map)
The tsdown binary comes from the DeepSeek Harness checkout. React is resolved through the
shell's module table (react, react-dom/client) and is never bundled. The smoke scripts
need @deepseek-ai/* to be resolvable locally (a temporary
ln -s ~/.dsh/profiles/node_modules node_modules, removed afterwards, is enough).
Install
dsh plugin --profile web add ./dsh-notes
This appends a dsh-notes layer; restart the profile afterwards (both host and client
changes require a restart):
pnpm dsh web
After the restart a「笔记」entry appears in the left sidebar. The default root directory is
<DSH_HOME>/dsh-notes (created automatically); use "switch directory" in the page to point
it at any absolute local path (~ is supported; leave it empty to restore the default).
Plugin config
All optional (defaults shown); override in the profile's own cordis.patch.yml:
- id: notes
config:
rootDir: '' # explicit root dir (absolute path / ~/…); empty = use the remembered one
maxRuleBytes: 524288 # single rule-file read limit
maxNoteBytes: 2097152 # single note read limit
maxBodyBytes: 1048576 # submitted note body limit
Host routes (all loopback + same-origin guarded)
| Route | Method | Purpose |
|---|---|---|
/api/notes/state |
GET | Root state (current path / config-pinned / exists) |
/api/notes/root |
PUT | Switch the root (empty = restore default) |
/api/notes/groups |
GET | Group overview (note count + rule file names in the group) |
/api/notes/group |
POST | Create a group directory |
/api/notes/rules?group= |
GET | Root (or group) rule file list |
/api/notes/rule?group=&file= |
GET | Read one rule file in full |
/api/notes/notes?group= |
GET | Note list of one group |
/api/notes/note?group=&file= |
GET / DELETE | Read / delete a note |
/api/notes/note/create |
POST | Create a note (group created automatically; duplicate name 409) |
/api/notes/note/update |
POST | Update a note (keeps created, refreshes updated) |
/api/notes/note/rename |
POST | Rename a note file |
Security boundaries
The browser reaches data only through the guarded routes above and never touches the disk
directly; group and note names are sanitized first (path-traversal characters and hidden
files rejected) and then, after resolve, checked to still be inside the root; switching
the root writes a dedicated pointer file; every Markdown write goes through a temp file +
atomic rename with a single-writer queue against concurrent corruption; a missing directory
is created automatically. Rule and note reads are byte-capped (413 when exceeded).
Remove
dsh plugin --profile web remove dsh-notes
Verification record
scripts/smoke-host.mjs(isolated DSH_HOME + temp root, no network) — all passed: front matter / note parsing and idempotent round-trips, tolerant parsing of hand-written md, a pasted whole md file stored verbatim (no double wrapping), name sanitizing and rejection of reserved rule names (AGENTS/CLAUDE/*rules/*规则), rule-file detection (root/group), group CRUD (duplicate name 409, auto-creation), note add/read/duplicate-name 409/in-place update (keepscreated)/rename (implicit title follows)/delete/repeat-delete 404, over-limit body 413,note_list/note_rules(root + group rule text) /note_create(auto-creates the group) /note_read/note_delete/note_root.scripts/loader-boot.mjs(real Cordis Loader +dsh-host-webserver, isolated DSH_HOME) — all passed: row loading, webServer binding, state/root switching and persistence, HTTP CRUD for groups/rules/notes, rule text reading, foreign-host 403, wrong method 405, unknown route 404, malformed JSON 400, invalid group name 400, deleting a missing note 404.scripts/smoke-client.mjs— all passed:lib/client.jsregistersdsh-notesas a__ModuleLoader__closure and exportsname: notes-ui / inject / apply; no machine paths leak; only module-table externals (the react family) are resolved.- Static preflight:
node …/dsh-plugin-development/scripts/check-artifact.mjs bundle .→ PASS, 0 warnings.
No comments yet. Be the first to write one.