Bundle
dsh-notes
DSH bundle: 笔记——按目录分组的本地 Markdown 笔记。选择一个本地目录作为根目录(可在页面内即时切换并记住),读取根/分组下的 AGENTS.md 等规则 md 作为上下文,以根目录的直接子目录(=二级目录)为分组,把粘贴的文本或整理好的内容存成 <分组>/<笔记>.md。
- Source
- suomir1995
- License
- MIT
- Updated
- Updated 20 hours ago
Readme
# dsh-notes — Notes
English | [中文](README.zh.md)
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 to `lib/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:
```markdown
---
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_rules` and 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 explicit `rootDir`
in 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: true` overwrites);
- `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:
```sh
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
```sh
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):
```sh
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`:
```yaml
- 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
```sh
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 (keeps `created`)/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.js` registers `dsh-notes` as a
`__ModuleLoader__` closure and exports `name: 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.
Install
dsh plugin --profile web add github:suomir1995/dsh-notes
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-notes from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.