Skip to content
dsh.fish
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

  • This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.
Source