@mulmoclaude/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/helps/billing-clients-worklog.md +215 -0
- package/assets/helps/billing-invoice.md +458 -0
- package/assets/helps/business.md +104 -0
- package/assets/helps/collection-skills.md +810 -0
- package/assets/helps/custom-view.md +433 -0
- package/assets/helps/feeds.md +114 -0
- package/assets/helps/gemini.md +57 -0
- package/assets/helps/github.md +23 -0
- package/assets/helps/guide.md +61 -0
- package/assets/helps/index.md +89 -0
- package/assets/helps/lessons-collection.md +400 -0
- package/assets/helps/mulmoscript.md +249 -0
- package/assets/helps/portfolio-tracker.md +211 -0
- package/assets/helps/presentation-deck.md +828 -0
- package/assets/helps/presenthtml.md +89 -0
- package/assets/helps/sandbox.md +97 -0
- package/assets/helps/spreadsheet.md +43 -0
- package/assets/helps/storyteller.md +101 -0
- package/assets/helps/telegram.md +136 -0
- package/assets/helps/todo-collection.md +140 -0
- package/assets/helps/vocabulary.md +109 -0
- package/assets/helps/wiki.md +168 -0
- package/assets/skills-preset/mc-cooking-coach/SKILL.md +217 -0
- package/assets/skills-preset/mc-library/SKILL.md +188 -0
- package/assets/skills-preset/mc-manage-automations/SKILL.md +119 -0
- package/assets/skills-preset/mc-manage-skills/SKILL.md +141 -0
- package/assets/skills-preset/mc-wiki-deep-lint/SKILL.md +108 -0
- package/assets/skills-preset/mc-wiki-health-check/SKILL.md +61 -0
- package/assets/skills-preset/mc-wiki-ingest/SKILL.md +182 -0
- package/assets/skills-preset/mc-wiki-promote/SKILL.md +175 -0
- package/assets/skills-preset/mc-zenn/SKILL.md +136 -0
- package/dist/chunk-CKQMccvm.cjs +28 -0
- package/dist/collection/core/actionVisible.d.ts +34 -0
- package/dist/collection/core/calendarGrid.d.ts +120 -0
- package/dist/collection/core/deriveAll.d.ts +38 -0
- package/dist/collection/core/derivedFormula.d.ts +18 -0
- package/dist/collection/core/draft.d.ts +18 -0
- package/dist/collection/core/enumColors.d.ts +33 -0
- package/dist/collection/core/errorMessage.d.ts +4 -0
- package/dist/collection/core/itemLabel.d.ts +12 -0
- package/dist/collection/core/presentCollection.d.ts +13 -0
- package/dist/collection/core/promptSafety.d.ts +1 -0
- package/dist/collection/core/schema.d.ts +355 -0
- package/dist/collection/core/shortHexId.d.ts +8 -0
- package/dist/collection/core/sortItems.d.ts +29 -0
- package/dist/collection/core/uiTypes.d.ts +106 -0
- package/dist/collection/index.cjs +793 -0
- package/dist/collection/index.cjs.map +1 -0
- package/dist/collection/index.d.ts +14 -0
- package/dist/collection/index.js +740 -0
- package/dist/collection/index.js.map +1 -0
- package/dist/collection/paths.cjs +44 -0
- package/dist/collection/paths.cjs.map +1 -0
- package/dist/collection/paths.js +41 -0
- package/dist/collection/paths.js.map +1 -0
- package/dist/collection/server/atomic.d.ts +1 -0
- package/dist/collection/server/delete.d.ts +38 -0
- package/dist/collection/server/derive.d.ts +8 -0
- package/dist/collection/server/discoveredCollection.d.ts +18 -0
- package/dist/collection/server/discovery.d.ts +227 -0
- package/dist/collection/server/host.d.ts +77 -0
- package/dist/collection/server/index.cjs +1721 -0
- package/dist/collection/server/index.cjs.map +1 -0
- package/dist/collection/server/index.d.ts +11 -0
- package/dist/collection/server/index.js +1671 -0
- package/dist/collection/server/index.js.map +1 -0
- package/dist/collection/server/io.d.ts +114 -0
- package/dist/collection/server/paths.d.ts +52 -0
- package/dist/collection/server/spawn.d.ts +55 -0
- package/dist/collection/server/templatePath.d.ts +25 -0
- package/dist/collection/server/util.d.ts +3 -0
- package/dist/collection/server/validate.d.ts +19 -0
- package/dist/collection/server/views.d.ts +20 -0
- package/dist/deriveAll-C15OpM3K.cjs +399 -0
- package/dist/deriveAll-C15OpM3K.cjs.map +1 -0
- package/dist/deriveAll-C6BYnpBL.js +364 -0
- package/dist/deriveAll-C6BYnpBL.js.map +1 -0
- package/dist/file-change/index.cjs +72 -0
- package/dist/file-change/index.cjs.map +1 -0
- package/dist/file-change/index.d.ts +43 -0
- package/dist/file-change/index.js +66 -0
- package/dist/file-change/index.js.map +1 -0
- package/dist/notifier/engine.d.ts +72 -0
- package/dist/notifier/index.cjs +484 -0
- package/dist/notifier/index.cjs.map +1 -0
- package/dist/notifier/index.d.ts +3 -0
- package/dist/notifier/index.js +464 -0
- package/dist/notifier/index.js.map +1 -0
- package/dist/notifier/store.d.ts +18 -0
- package/dist/notifier/types.d.ts +118 -0
- package/dist/notifier/validate.d.ts +17 -0
- package/dist/scheduler/adapter.d.ts +48 -0
- package/dist/scheduler/index.cjs +352 -0
- package/dist/scheduler/index.cjs.map +1 -0
- package/dist/scheduler/index.d.ts +2 -0
- package/dist/scheduler/index.js +343 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/task-manager.d.ts +51 -0
- package/dist/whisper/client.cjs +241 -0
- package/dist/whisper/client.cjs.map +1 -0
- package/dist/whisper/client.d.ts +35 -0
- package/dist/whisper/client.js +239 -0
- package/dist/whisper/client.js.map +1 -0
- package/dist/whisper/ffmpeg.d.ts +6 -0
- package/dist/whisper/index.cjs +433 -0
- package/dist/whisper/index.cjs.map +1 -0
- package/dist/whisper/index.d.ts +5 -0
- package/dist/whisper/index.js +425 -0
- package/dist/whisper/index.js.map +1 -0
- package/dist/whisper/internal.d.ts +11 -0
- package/dist/whisper/models.d.ts +49 -0
- package/dist/whisper/sidecar.d.ts +8 -0
- package/dist/whisper/whisper.d.ts +28 -0
- package/dist/workspace-setup/assets.d.ts +10 -0
- package/dist/workspace-setup/index.d.ts +3 -0
- package/dist/workspace-setup/index.js +556 -0
- package/dist/workspace-setup/index.js.map +1 -0
- package/dist/workspace-setup/slug.d.ts +6 -0
- package/dist/workspace-setup/slug.js +13 -0
- package/dist/workspace-setup/slug.js.map +1 -0
- package/dist/workspace-setup/sync.d.ts +94 -0
- package/package.json +95 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# presentHtml — Authoring Guide
|
|
2
|
+
|
|
3
|
+
Reference for the `presentHtml` plugin. The HTML you provide is saved to `artifacts/html/<YYYY>/<MM>/<slug>-<timestamp>.html` and rendered in the canvas. The user may also open the file directly from disk via `file://`, so it must remain portable: every URL inside the document has to resolve both when served by the app and when loaded straight off the filesystem.
|
|
4
|
+
|
|
5
|
+
## Two ways to call it: `html` vs `path`
|
|
6
|
+
|
|
7
|
+
`presentHtml` accepts **either** `html` **or** `path` (not both):
|
|
8
|
+
|
|
9
|
+
- **`html`** — a complete self-contained document. The host saves it to a fresh `artifacts/html/<YYYY>/<MM>/<slug>-<timestamp>.html` and presents it. The response's `data.filePath` is the saved path — capture it if you need to store a reference.
|
|
10
|
+
- **`path`** — the workspace-relative path of an HTML file you **already wrote** under `artifacts/html/…`. The host presents that existing page **without re-saving a copy**. Use this for pages you authored directly on disk with the `Write` tool (e.g. a pre-built lesson), so presenting them later doesn't duplicate the file. The path must end in `.html` and live under `artifacts/html/`.
|
|
11
|
+
|
|
12
|
+
Saving (with `html`) also renders in the user's canvas, so do **not** loop it to batch-create many pages quietly — write those directly to disk and present them on demand with `path`.
|
|
13
|
+
|
|
14
|
+
## Self-Contained Document
|
|
15
|
+
|
|
16
|
+
- Full document including `<!DOCTYPE html>` and `<html>` / `<body>` tags.
|
|
17
|
+
- All CSS and JavaScript inline, or loaded via a public CDN. No local script / stylesheet files (the app does not host arbitrary `.css` / `.js`).
|
|
18
|
+
|
|
19
|
+
### Allowed CDNs
|
|
20
|
+
|
|
21
|
+
The preview iframe enforces a CSP that only permits a curated set of CDNs. **Use these origins or your script / stylesheet will be silently blocked** and the page will render broken (e.g. `Plotly is not defined` if the chart library was blocked):
|
|
22
|
+
|
|
23
|
+
- `https://cdn.jsdelivr.net` — broadest coverage; preferred for any npm-shaped library
|
|
24
|
+
- `https://unpkg.com` — same scope as jsdelivr; fallback
|
|
25
|
+
- `https://cdnjs.cloudflare.com` — curated mirror; common for older libraries
|
|
26
|
+
- `https://fonts.googleapis.com` + `https://fonts.gstatic.com` — Google Fonts
|
|
27
|
+
- `https://cdn.plot.ly` — Plotly's first-party CDN (also reachable via jsdelivr)
|
|
28
|
+
|
|
29
|
+
When in doubt, pull from `cdn.jsdelivr.net` — it mirrors most npm packages and is always safe. Examples:
|
|
30
|
+
|
|
31
|
+
```html
|
|
32
|
+
<!-- Plotly via jsdelivr (preferred) -->
|
|
33
|
+
<script src="https://cdn.jsdelivr.net/npm/plotly.js-dist@2/plotly.min.js"></script>
|
|
34
|
+
|
|
35
|
+
<!-- D3 -->
|
|
36
|
+
<script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
|
|
37
|
+
|
|
38
|
+
<!-- Tailwind (browser play CDN) -->
|
|
39
|
+
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
If a library you need is hosted only on a CDN not in this list (e.g. `cdn.example.org`), inline the library code directly — do not link to an unlisted host.
|
|
43
|
+
|
|
44
|
+
## Referencing Workspace Files
|
|
45
|
+
|
|
46
|
+
The output HTML lives three directory levels deep under `artifacts/`. To reference an image / chart / other artifact, use a **relative path with exactly three `../`** to climb out of `html/<YYYY>/<MM>/`:
|
|
47
|
+
|
|
48
|
+
```html
|
|
49
|
+
<!-- GOOD -->
|
|
50
|
+
<img src="../../../images/2026/04/foo.png">
|
|
51
|
+
|
|
52
|
+
<!-- BAD: absolute path breaks under file:// -->
|
|
53
|
+
<img src="/artifacts/images/2026/04/foo.png">
|
|
54
|
+
|
|
55
|
+
<!-- BAD: workspace-rooted path resolves against the page URL -->
|
|
56
|
+
<img src="artifacts/images/2026/04/foo.png">
|
|
57
|
+
|
|
58
|
+
<!-- BAD: runtime artifact, not a stored convention -->
|
|
59
|
+
<img src="/api/files/raw?path=artifacts/images/2026/04/foo.png">
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Workspace paths returned by other tools (e.g. an image-generating tool returns `artifacts/images/2026/04/foo.png`): replace the leading `artifacts/` with `../../../`, giving `../../../images/2026/04/foo.png`.
|
|
63
|
+
|
|
64
|
+
The same rule applies to anywhere a path appears: `<img>`, `<source>`, `<video poster>`, `<audio src>`, CSS `url(...)`, etc.
|
|
65
|
+
|
|
66
|
+
## Local Images Not Already Under `artifacts/`
|
|
67
|
+
|
|
68
|
+
If you want to embed a local image that lives **outside** `artifacts/` — e.g. a file the user pasted into `data/attachments/2026/04/foo.png`, a wiki source under `data/wiki/sources/foo.png`, or any other workspace path — **do not link to it directly**. The three-`../` math only resolves cleanly for files under `artifacts/`; references that climb further or sideways break under `file://` and are fragile across workspace reorganisation.
|
|
69
|
+
|
|
70
|
+
Copy the file into `artifacts/images/<YYYY>/<MM>/` first, then reference the copy. Use `mkdir -p` for the partition directory and a UTC year/month matching the convention (`saveImage()` shards by UTC, so doing the same keeps copies grouped with same-month generated images):
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
mkdir -p artifacts/images/2026/04
|
|
74
|
+
cp data/attachments/2026/04/foo.png artifacts/images/2026/04/foo.png
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Then in the HTML:
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
<img src="../../../images/2026/04/foo.png">
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Files **already** under `artifacts/` (e.g. `artifacts/images/2026/03/bar.png`, `artifacts/charts/2026/04/baz.svg`) — reference them in place; do **not** copy.
|
|
84
|
+
|
|
85
|
+
## Why Three `../`
|
|
86
|
+
|
|
87
|
+
The file is saved at `artifacts/html/<YYYY>/<MM>/<slug>-<timestamp>.html`. From that location, `../../../` climbs out of `<MM>/`, `<YYYY>/`, and `html/` to land in `artifacts/`. From `artifacts/`, the next path segment is the sibling artifact directory you want — `images/`, `charts/`, `documents/`, etc.
|
|
88
|
+
|
|
89
|
+
Absolute paths like `/artifacts/...` work in-app (the server mounts `/artifacts/images` as a static route) but break under `file://`, where root-relative URLs resolve against the filesystem root. Workspace-rooted paths without a leading slash (`artifacts/...`) get joined to the page URL by the browser and 404 every time.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Sandbox
|
|
2
|
+
|
|
3
|
+
MulmoClaude runs the Claude Code agent inside a **Docker sandbox** when Docker is available. This isolates the agent's file-system access and limits what it can do on the host.
|
|
4
|
+
|
|
5
|
+
## How It Works
|
|
6
|
+
|
|
7
|
+
- On each agent invocation, the server checks whether Docker is running.
|
|
8
|
+
- If Docker is available (and `DISABLE_SANDBOX` is not set), Claude Code runs inside a disposable container (`mulmoclaude-sandbox`) built from `Dockerfile.sandbox`.
|
|
9
|
+
- If Docker is not available, Claude Code runs directly on the host with the workspace as its working directory.
|
|
10
|
+
|
|
11
|
+
## What the Container Can Access
|
|
12
|
+
|
|
13
|
+
| Mount | Container path | Mode |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Workspace | `/home/node/mulmoclaude` | read-write |
|
|
16
|
+
| `node_modules/` | `/app/node_modules` | read-only |
|
|
17
|
+
| `server/` | `/app/server` | read-only |
|
|
18
|
+
| `src/` | `/app/src` | read-only |
|
|
19
|
+
| `~/.claude/` | `/home/node/.claude` | read-write |
|
|
20
|
+
| `~/.claude.json` | `/home/node/.claude.json` | read-write |
|
|
21
|
+
|
|
22
|
+
The container runs with `--cap-drop ALL` and as the host user's UID/GID, so it has no elevated privileges.
|
|
23
|
+
|
|
24
|
+
## Disabling the Sandbox
|
|
25
|
+
|
|
26
|
+
Set the environment variable `DISABLE_SANDBOX=1` to always run the agent directly on the host, even when Docker is available. Equivalently, pass the `--disable-sandbox` CLI flag — `yarn dev --disable-sandbox` or `npx mulmoclaude --disable-sandbox`. The flag form is handy on Windows PowerShell (no inline `VAR=value` syntax), in IDE / launcher run configs, and for quick ad-hoc debugging. Both set the same internal switch; the env var stays supported in parallel.
|
|
27
|
+
|
|
28
|
+
## Debug aids (opt-in env vars)
|
|
29
|
+
|
|
30
|
+
These flags exist for development / debugging only. Off by default so production runs aren't surprised. Each has an equivalent `--flag` CLI form (drop the `=1`, kebab-case the name) accepted by both `yarn dev` and `npx mulmoclaude` — e.g. `DISABLE_SANDBOX=1` ≡ `--disable-sandbox`, `PERSIST_TOOL_CALLS=1` ≡ `--persist-tool-calls`, `DISABLE_MACOS_REMINDER_NOTIFICATIONS=1` ≡ `--disable-macos-reminders`, `JOURNAL_FORCE_RUN_ON_STARTUP=1` ≡ `--journal-force-run`, `CHAT_INDEX_FORCE_RUN_ON_STARTUP=1` ≡ `--chat-index-force-run`. Secret-bearing vars (auth token, API keys) have no flag form on purpose — argv is visible via `ps` / shell history.
|
|
31
|
+
|
|
32
|
+
- `DISABLE_SANDBOX=1` — see above. Bypasses the Docker sandbox.
|
|
33
|
+
- `PERSIST_TOOL_CALLS=1` — also persist `tool_call` events to the session jsonl alongside `tool_result`. Useful for reading the args sent to a tool after the run is over (page refresh / server restart). Off by default because args can be large and may carry payload bytes (inline images, full MulmoScript JSON) you didn't expect to land in the jsonl. See [issue #1096](https://github.com/receptron/mulmoclaude/issues/1096) for the rationale.
|
|
34
|
+
|
|
35
|
+
## Host Credentials (opt-in)
|
|
36
|
+
|
|
37
|
+
The sandbox is credential-free by default. Two opt-in flags let you expose the minimum needed for `git` / `gh` to authenticate without leaking private keys into the container:
|
|
38
|
+
|
|
39
|
+
- `SANDBOX_SSH_AGENT_FORWARD=1` — forwards the host's SSH agent socket (private keys stay on the host).
|
|
40
|
+
- `SANDBOX_MOUNT_CONFIGS=gh,gitconfig` — allowlisted read-only config mounts.
|
|
41
|
+
|
|
42
|
+
Full contract, what's deliberately excluded, and troubleshooting: [`docs/sandbox-credentials.md`](../../docs/sandbox-credentials.md).
|
|
43
|
+
|
|
44
|
+
## Checking the Current Sandbox State
|
|
45
|
+
|
|
46
|
+
Two places surface what's actually attached to **your** running container — useful when you've just toggled an opt-in flag and want to confirm it took effect.
|
|
47
|
+
|
|
48
|
+
### From the UI — lock icon popup
|
|
49
|
+
|
|
50
|
+
Click the 🔒 icon in the top bar. The popup shows:
|
|
51
|
+
|
|
52
|
+
- **Sandbox enabled / disabled** — whether Docker was detected and `DISABLE_SANDBOX` is off.
|
|
53
|
+
- **Host credentials attached** (only when the sandbox is on):
|
|
54
|
+
- **SSH agent**: `forwarded` when `SANDBOX_SSH_AGENT_FORWARD=1` **and** `$SSH_AUTH_SOCK` points at a live socket. If the flag is on but the socket is missing, the popup shows `not forwarded` and the server log carries the reason.
|
|
55
|
+
- **Mounted configs**: allowlisted names from `SANDBOX_MOUNT_CONFIGS` whose host path exists. Names you typed but whose host path is missing are silently dropped from the UI (they appear in the server log as `config mount skipped`).
|
|
56
|
+
|
|
57
|
+
The popup also has a **sample query button** that asks Claude to summarise this information in natural language.
|
|
58
|
+
|
|
59
|
+
### From inside a chat session
|
|
60
|
+
|
|
61
|
+
Inside the container, you can verify each piece directly:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
# SSH agent — lists identities the host agent will sign with
|
|
65
|
+
ssh-add -l
|
|
66
|
+
|
|
67
|
+
# gh config — mounted read-only, so `gh auth status` should succeed
|
|
68
|
+
ls /home/node/.config/gh && gh auth status
|
|
69
|
+
|
|
70
|
+
# gitconfig — mounted read-only
|
|
71
|
+
cat /home/node/.gitconfig
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### From the server log
|
|
75
|
+
|
|
76
|
+
The popup intentionally exposes **names only**, never host paths or skip reasons. Full debug detail lives in the server log:
|
|
77
|
+
|
|
78
|
+
- `[sandbox] host credentials attached to container` — one-line summary of what was mounted on agent spawn.
|
|
79
|
+
- `[sandbox] unknown SANDBOX_MOUNT_CONFIGS entries ignored` — typo in the CSV.
|
|
80
|
+
- `[sandbox] config mount skipped (host path missing)` — name is in the allowlist but the host file/dir doesn't exist.
|
|
81
|
+
- `[sandbox] SSH agent forward requested but skipped` — flag on but `$SSH_AUTH_SOCK` unset or non-existent.
|
|
82
|
+
|
|
83
|
+
If the popup isn't showing what you expect, grep the startup log for `[sandbox]` first.
|
|
84
|
+
|
|
85
|
+
## First-Time Setup (macOS)
|
|
86
|
+
|
|
87
|
+
On macOS, the Docker container uses a separate credential store from the host. Before using the sandbox for the first time (and whenever the credential expires), run:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
yarn sandbox:login
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This opens an interactive `claude login` session inside the container so that the sandbox has valid credentials.
|
|
94
|
+
|
|
95
|
+
## Building the Image
|
|
96
|
+
|
|
97
|
+
The sandbox image is built automatically on first use. If `Dockerfile.sandbox` changes, the image is rebuilt on the next agent invocation. No manual build step is needed.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Spreadsheet Authoring Guide
|
|
2
|
+
|
|
3
|
+
Reference for the `presentSpreadsheet` plugin. Read this before building a spreadsheet.
|
|
4
|
+
|
|
5
|
+
## Cell Format
|
|
6
|
+
|
|
7
|
+
Every cell is an object `{"v": value, "f": format}`.
|
|
8
|
+
|
|
9
|
+
- `v` — value: text, number, date string, or formula (string starting with `=`)
|
|
10
|
+
- `f` — optional format code for display
|
|
11
|
+
|
|
12
|
+
## Formulas
|
|
13
|
+
|
|
14
|
+
Set `v` to a string starting with `=`. Use Excel-style A1 cell references.
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
{"v": "=B2*1.05", "f": "$#,##0.00"}
|
|
18
|
+
{"v": "=SUM(A1:A10)", "f": "#,##0"}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Never pre-calculate — let the spreadsheet compute using cell refs, functions, and arithmetic.
|
|
22
|
+
|
|
23
|
+
## Available Functions
|
|
24
|
+
|
|
25
|
+
SUM, AVERAGE, COUNT, MIN, MAX, IF, AND, OR, NOT, ROUND, ABS, TODAY, DATE, DATEDIF, YEAR, MONTH, DAY, CONCATENATE, LEFT, RIGHT, MID, LEN, TRIM, UPPER, LOWER, VLOOKUP, INDEX, MATCH, PMT, FV, PV, NPV, IRR.
|
|
26
|
+
|
|
27
|
+
## Dates
|
|
28
|
+
|
|
29
|
+
Use date strings like `01/15/2025` or date formulas like `=TODAY()` or `=DATE(2025,1,15)`. The spreadsheet auto-parses common formats (MM/DD/YYYY, YYYY-MM-DD, DD-MMM-YYYY) into date serial numbers. Date arithmetic works: `=B2-TODAY()` calculates days between dates.
|
|
30
|
+
|
|
31
|
+
## Format Codes
|
|
32
|
+
|
|
33
|
+
| Code | Use |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `$#,##0.00` | Currency |
|
|
36
|
+
| `#,##0` | Integer with commas |
|
|
37
|
+
| `0.00%` | Percent |
|
|
38
|
+
| `0.00` | Decimal |
|
|
39
|
+
| `MM/DD/YYYY` | Date |
|
|
40
|
+
| `DD-MMM-YYYY` | Date |
|
|
41
|
+
| `YYYY-MM-DD` | ISO date |
|
|
42
|
+
|
|
43
|
+
Format is optional for plain text/numbers.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Storyteller Template (MulmoScript)
|
|
2
|
+
|
|
3
|
+
Use `presentMulmoScript` to create character-driven, narrated stories. Follow this template exactly.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
Reach for `presentMulmoScript` when the user asks for a story, fairy tale, narrative, or any character-driven imaginative content meant to be read aloud over visuals.
|
|
8
|
+
|
|
9
|
+
## Beat Format (CRITICAL)
|
|
10
|
+
|
|
11
|
+
- Every beat MUST have a top-level `imagePrompt` (string) and `imageNames` (array of character keys from `imageParams.images`).
|
|
12
|
+
- NEVER use an `image` object with a `type` field — no `textSlide`, `chart`, `mermaid`, `html_tailwind`, `markdown` on any beat.
|
|
13
|
+
- `imagePrompt` and `imageNames` are top-level fields on the beat, NOT nested under `image`.
|
|
14
|
+
- Every beat needs both fields, even when a single character appears alone.
|
|
15
|
+
|
|
16
|
+
## Image Prompts
|
|
17
|
+
|
|
18
|
+
- Do NOT re-describe character appearance in `imagePrompt` — their look is already encoded in `imageParams.images`.
|
|
19
|
+
- Focus the prompt on setting, action, mood, and composition.
|
|
20
|
+
- Set the art style ONCE in `imageParams.style` — do NOT repeat it per beat. The style is applied globally.
|
|
21
|
+
|
|
22
|
+
## Narrator Voice
|
|
23
|
+
|
|
24
|
+
Set `speechOptions.instruction` on the Narrator speaker to match the story's tone. Pick a `voiceId` from this list:
|
|
25
|
+
|
|
26
|
+
| Tone | Voice IDs |
|
|
27
|
+
|---|---|
|
|
28
|
+
| Bright / upbeat | Zephyr, Leda, Autonoe, Callirrhoe |
|
|
29
|
+
| Neutral / clear | Kore, Charon, Fenrir, Orus |
|
|
30
|
+
| Warm / smooth | Schedar, Sulafat, Despina, Erinome |
|
|
31
|
+
| Deep / authoritative | Alnilam, Iapetus, Algieba |
|
|
32
|
+
| Soft / gentle | Aoede, Umbriel, Laomedeia, Achernar, Rasalgethi, Pulcherrima, Vindemiatrix, Sadachbia, Sadaltager, Zubenelgenubi |
|
|
33
|
+
|
|
34
|
+
## Other Rules
|
|
35
|
+
|
|
36
|
+
- Always use Google providers (`gemini` for TTS, `google` for image generation).
|
|
37
|
+
- Default transition: `fade` in `movieParams.transition`, unless the user requests a different style.
|
|
38
|
+
- Keep narration text conversational and evocative, as if read aloud to a listener.
|
|
39
|
+
|
|
40
|
+
## Template
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"$mulmocast": { "version": "1.1" },
|
|
45
|
+
"title": "The Silver Wolf and the Red-Haired Girl",
|
|
46
|
+
"description": "A girl lost in an enchanted forest befriends a wise silver wolf who shows her the way home.",
|
|
47
|
+
"lang": "en",
|
|
48
|
+
"speechParams": {
|
|
49
|
+
"speakers": {
|
|
50
|
+
"Narrator": {
|
|
51
|
+
"provider": "gemini",
|
|
52
|
+
"voiceId": "Schedar",
|
|
53
|
+
"displayName": { "en": "Narrator" },
|
|
54
|
+
"speechOptions": {
|
|
55
|
+
"instruction": "Speak as a warm, captivating storyteller — slow and deliberate, with gentle wonder for magical moments and tender warmth for emotional ones."
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"imageParams": {
|
|
61
|
+
"provider": "google",
|
|
62
|
+
"model": "gemini-3.1-flash-image-preview",
|
|
63
|
+
"style": "painterly watercolor illustration",
|
|
64
|
+
"images": {
|
|
65
|
+
"mara": {
|
|
66
|
+
"type": "imagePrompt",
|
|
67
|
+
"prompt": "A girl, age 10, with wild curly red hair and bright green eyes, wearing a worn blue dress and muddy boots, curious and brave expression"
|
|
68
|
+
},
|
|
69
|
+
"wolf": {
|
|
70
|
+
"type": "imagePrompt",
|
|
71
|
+
"prompt": "A large silver wolf with a thick luminous coat, wise amber eyes, and a calm, gentle demeanor — majestic but not threatening"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"movieParams": {
|
|
76
|
+
"provider": "google",
|
|
77
|
+
"model": "veo-3.1-generate",
|
|
78
|
+
"transition": { "type": "fade", "duration": 0.5 }
|
|
79
|
+
},
|
|
80
|
+
"beats": [
|
|
81
|
+
{
|
|
82
|
+
"speaker": "Narrator",
|
|
83
|
+
"text": "Deep in the emerald forest, young Mara wandered further than she ever had before.",
|
|
84
|
+
"imageNames": ["mara"],
|
|
85
|
+
"imagePrompt": "A small figure standing at the edge of a vast ancient forest, towering trees with glowing moss, golden afternoon light filtering through the canopy, a sense of wonder and apprehension"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"speaker": "Narrator",
|
|
89
|
+
"text": "Then, from the shadows between the roots, came the Silver Wolf — ancient, patient, and utterly still.",
|
|
90
|
+
"imageNames": ["mara", "wolf"],
|
|
91
|
+
"imagePrompt": "A girl and a large wolf facing each other in a misty forest clearing, shafts of light between them, tension softening into curiosity"
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"speaker": "Narrator",
|
|
95
|
+
"text": "Side by side, they walked through the night until the lanterns of home flickered into view.",
|
|
96
|
+
"imageNames": ["mara", "wolf"],
|
|
97
|
+
"imagePrompt": "A girl and a wolf walking together along a moonlit forest path, distant warm cottage lights glowing through the trees, fireflies drifting around them"
|
|
98
|
+
}
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
```
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Telegram Bridge
|
|
2
|
+
|
|
3
|
+
The Telegram bridge lets you talk to your MulmoClaude from the Telegram app on your phone or desktop. Your own custom Telegram bot forwards messages to the MulmoClaude server running on your computer, and replies come back through the same bot.
|
|
4
|
+
|
|
5
|
+
This is useful when you want to reach your MulmoClaude away from your computer — on a walk, from a phone, or from a friend's device — without exposing the server to the public internet.
|
|
6
|
+
|
|
7
|
+
## How It Works
|
|
8
|
+
|
|
9
|
+
- You create a **bot** with Telegram's BotFather; it gives you a token.
|
|
10
|
+
- You run a **bridge process** (`yarn telegram`) on the same machine as the MulmoClaude server. The bridge uses your bot token to receive messages from Telegram, forwards them to MulmoClaude over `localhost:3001`, and sends the replies back to the Telegram user.
|
|
11
|
+
- A short **allowlist** of Telegram chat IDs controls who can talk to the bot. Everyone else gets `"Access denied"`.
|
|
12
|
+
|
|
13
|
+
Your computer has to be on and connected to the internet for the bot to respond. Close the laptop → the bot goes silent.
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- MulmoClaude checked out and runnable (`yarn dev` works).
|
|
18
|
+
- A Telegram account.
|
|
19
|
+
- Two terminals free: one for `yarn dev`, one for `yarn telegram`.
|
|
20
|
+
|
|
21
|
+
## Step 1 — Create the Bot with BotFather
|
|
22
|
+
|
|
23
|
+
1. In Telegram, search for `@BotFather` (the official account has a blue check) and start a chat.
|
|
24
|
+
2. Send `/newbot`.
|
|
25
|
+
3. Answer the two prompts:
|
|
26
|
+
- **Display name** — what appears in the chat header. Anything, e.g. `"Alice's MulmoClaude"`.
|
|
27
|
+
- **Username** — must end in `bot` and be unique on Telegram, e.g. `alice_mulmoclaude_bot`.
|
|
28
|
+
4. BotFather replies with a **token** like `1234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw`. This token is the bot's password — anyone who has it can impersonate the bot. Keep it secret.
|
|
29
|
+
|
|
30
|
+
Optional polish (can be done anytime later via BotFather):
|
|
31
|
+
|
|
32
|
+
- `/setdescription` — text shown when users open the chat for the first time.
|
|
33
|
+
- `/setuserpic` — the bot's avatar.
|
|
34
|
+
- `/setprivacy` → `Disable` — lets the bot see all messages in group chats (by default it only sees messages starting with `/`).
|
|
35
|
+
|
|
36
|
+
## Step 2 — Start MulmoClaude and the Bridge
|
|
37
|
+
|
|
38
|
+
In terminal A, start MulmoClaude:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
yarn dev
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Wait until you see `[server] listening port=3001`.
|
|
45
|
+
|
|
46
|
+
In terminal B, start the bridge. Leave the allowlist **empty on purpose** for the first run — you will need to discover your own chat ID before you can add it.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
export TELEGRAM_BOT_TOKEN='1234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw'
|
|
50
|
+
export TELEGRAM_ALLOWED_CHAT_IDS=''
|
|
51
|
+
yarn telegram
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Expected output:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
MulmoClaude Telegram bridge
|
|
58
|
+
Allowlist: (empty — all chats will be denied)
|
|
59
|
+
Connected (<socket id>).
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Step 3 — Find Your Chat ID and Allowlist It
|
|
63
|
+
|
|
64
|
+
1. In Telegram, open your new bot (search the username you picked) and send it any message — `hi` works.
|
|
65
|
+
2. In terminal B, you will see a log line like:
|
|
66
|
+
```
|
|
67
|
+
[telegram] denied chat=987654321 user=@alice — not on allowlist
|
|
68
|
+
```
|
|
69
|
+
That number (`987654321`) is **your Telegram chat ID**.
|
|
70
|
+
3. Stop the bridge (`Ctrl+C`), put your ID in the allowlist, restart:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
export TELEGRAM_ALLOWED_CHAT_IDS='987654321'
|
|
74
|
+
yarn telegram
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
4. Send the bot another message. MulmoClaude should now reply.
|
|
78
|
+
|
|
79
|
+
## Step 4 — Invite a Friend
|
|
80
|
+
|
|
81
|
+
To let another person use your MulmoClaude:
|
|
82
|
+
|
|
83
|
+
1. Share the bot's username with them; they search for it on Telegram and send it a message.
|
|
84
|
+
2. Their chat ID appears in terminal B's `denied` log line, just like yours did in Step 3.
|
|
85
|
+
3. Append their ID (comma-separated) and restart the bridge:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
export TELEGRAM_ALLOWED_CHAT_IDS='987654321,123456789'
|
|
89
|
+
yarn telegram
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
4. When they message the bot again, it works.
|
|
93
|
+
|
|
94
|
+
To avoid re-exporting every time, put `TELEGRAM_BOT_TOKEN` and `TELEGRAM_ALLOWED_CHAT_IDS` in a `.env` file at the repo root. The bridge auto-loads `.env` on startup via `dotenv/config`, so the vars take effect just by restarting `yarn telegram` — no shell `export` needed.
|
|
95
|
+
|
|
96
|
+
```dotenv
|
|
97
|
+
# .env (repo root)
|
|
98
|
+
TELEGRAM_BOT_TOKEN=1234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw
|
|
99
|
+
TELEGRAM_ALLOWED_CHAT_IDS=987654321,123456789
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Bot Commands
|
|
103
|
+
|
|
104
|
+
These are typed directly into the Telegram chat. They mirror the CLI:
|
|
105
|
+
|
|
106
|
+
- `/help` — show available commands.
|
|
107
|
+
- `/reset` — start a fresh conversation session (drops prior context).
|
|
108
|
+
- `/roles` — list available roles.
|
|
109
|
+
- `/role <id>` — switch to a specific role (e.g. `/role office`).
|
|
110
|
+
- `/status` — show the current session info (session ID, current role).
|
|
111
|
+
- `//<skill> [args...]` — shortcut for `/reset` followed by `/<skill> [args...]`: start a fresh session and run the skill (with any args) in one tap (e.g. `//mag2 https://example.com/post`).
|
|
112
|
+
|
|
113
|
+
Any other text is treated as a message to the assistant.
|
|
114
|
+
|
|
115
|
+
## Troubleshooting
|
|
116
|
+
|
|
117
|
+
**`Connect error: bearer token rejected`** — MulmoClaude was restarted, so its bearer token changed. Restart `yarn telegram` to pick up the new one. To avoid this, pin `MULMOCLAUDE_AUTH_TOKEN` to the same value on both sides (see `docs/developer.md` §Auth).
|
|
118
|
+
|
|
119
|
+
**`TELEGRAM_ALLOWED_CHAT_IDS: "foo" is not an integer chat id`** — typo in the allowlist. Chat IDs are plain integers only — no spaces, quotes, or `#` prefix. Negative integers (for group chats) are allowed.
|
|
120
|
+
|
|
121
|
+
**Friend gets `"Access denied"` after you added their ID** — the allowlist is read once at startup. Restart `yarn telegram` after changing `TELEGRAM_ALLOWED_CHAT_IDS`.
|
|
122
|
+
|
|
123
|
+
**Messages stop flowing with no error** — check that `yarn dev` is still running. If the MulmoClaude server is down, the bridge stays up but has nothing to forward to. The next inbound message will log `Connect error` or `Disconnected`.
|
|
124
|
+
|
|
125
|
+
**Bot responds in a group chat you did not expect** — group chat IDs are **negative**. If you want the bot to work in a specific group, add that negative ID. By default BotFather enables "group privacy mode", so the bot only sees messages starting with `/` in groups — toggle it via BotFather's `/setprivacy` if you need full visibility.
|
|
126
|
+
|
|
127
|
+
## Security Notes
|
|
128
|
+
|
|
129
|
+
- The bot token is a password. If it leaks, regenerate it via BotFather's `/revoke`.
|
|
130
|
+
- The allowlist is the only thing standing between "my friends" and "every Telegram user on Earth". Keep it current — remove chat IDs when you no longer want that person to have access, and restart the bridge.
|
|
131
|
+
- The bridge logs chat IDs, usernames, and message lengths, but **not** message contents or the bot token. If you need a full audit trail, record it separately.
|
|
132
|
+
- The MulmoClaude bearer token never leaves your machine. The bridge only talks to `localhost:3001`; your friends talk to Telegram's servers, which then talk to your bridge.
|
|
133
|
+
|
|
134
|
+
## Full Operator Guide
|
|
135
|
+
|
|
136
|
+
For the complete operator walkthrough (including screenshots-worthy step numbering and a Japanese translation), see `docs/message_apps/telegram/README.md` (English) and `docs/message_apps/telegram/README.ja.md` (Japanese) in the MulmoClaude repository.
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Todo list — the canonical collection recipe
|
|
2
|
+
|
|
3
|
+
Read this whenever you build (or migrate) a **todo / task list** as a collection.
|
|
4
|
+
It is the authoritative template; copy it rather than reinventing one from the
|
|
5
|
+
generic DSL fragments in `config/helps/collection-skills.md`. Read that file
|
|
6
|
+
first for the general schema rules — this one is the todo-specific specialization.
|
|
7
|
+
|
|
8
|
+
The design in one line: **the `status` enum is the single source of truth, and
|
|
9
|
+
the "done" checkbox is a `toggle` field that projects it.** There is NO separate
|
|
10
|
+
stored `done` boolean to keep in sync.
|
|
11
|
+
|
|
12
|
+
> **The #1 mistake:** omitting the `done` toggle. Without it the list still works
|
|
13
|
+
> — but there is **no checkbox**, only a status dropdown and a kanban column.
|
|
14
|
+
> A todo list almost always wants the checkbox. Always include the toggle.
|
|
15
|
+
|
|
16
|
+
## schema.json
|
|
17
|
+
|
|
18
|
+
Author it at `data/skills/todos/schema.json` (the bridge mirrors it to
|
|
19
|
+
`.claude/skills/todos/`; the user opens it at `/collections/todos`):
|
|
20
|
+
|
|
21
|
+
```json
|
|
22
|
+
{
|
|
23
|
+
"title": "Todos",
|
|
24
|
+
"icon": "checklist",
|
|
25
|
+
"dataPath": "data/todos/items",
|
|
26
|
+
"primaryKey": "id",
|
|
27
|
+
"fields": {
|
|
28
|
+
"id": { "type": "string", "label": "ID", "primary": true, "required": true },
|
|
29
|
+
"done": { "type": "toggle", "label": "Done", "field": "status", "onValue": "done", "offValue": "todo" },
|
|
30
|
+
"text": { "type": "string", "label": "Task", "required": true },
|
|
31
|
+
"status": { "type": "enum", "label": "Status", "values": ["todo", "doing", "done"], "required": true },
|
|
32
|
+
"priority": { "type": "enum", "label": "Priority", "values": ["urgent", "high", "medium", "low"] },
|
|
33
|
+
"dueDate": { "type": "date", "label": "Due" },
|
|
34
|
+
"note": { "type": "markdown", "label": "Note" }
|
|
35
|
+
},
|
|
36
|
+
"displayField": "text",
|
|
37
|
+
"kanbanField": "status",
|
|
38
|
+
"calendarField": "dueDate",
|
|
39
|
+
"completionField": "status",
|
|
40
|
+
"completionDoneValues": ["done"],
|
|
41
|
+
"notifyWhen": { "field": "priority", "in": ["urgent", "high"] }
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Every key earns its place:
|
|
46
|
+
|
|
47
|
+
| Key | What it gives the user |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `done` (`toggle`) | The **checkbox** — in every table row and on every kanban card. Checking it sets `status` to `onValue`; the card jumps to the Done column. `offValue` is the status to return to on uncheck (your default open column). Both must be members of the `status` enum's `values`. |
|
|
50
|
+
| `status` (`enum`) | The kanban columns and the single source of truth for "done". |
|
|
51
|
+
| `kanbanField` | Pins the board to `status` (drag a card → writes `status`). |
|
|
52
|
+
| `displayField` | Card / bell label (the task text, not the opaque id). |
|
|
53
|
+
| `calendarField` | A due-date calendar view (only if you keep `dueDate`). |
|
|
54
|
+
| `completionField` + `completionDoneValues` | The bell: fires while a todo is open, clears when `status` → `done`. |
|
|
55
|
+
| `notifyWhen` | Fire the bell **only** for `urgent`/`high` priority (not every todo). Omit it to bell every open todo. |
|
|
56
|
+
|
|
57
|
+
## SKILL.md
|
|
58
|
+
|
|
59
|
+
`data/skills/todos/SKILL.md`:
|
|
60
|
+
|
|
61
|
+
```markdown
|
|
62
|
+
---
|
|
63
|
+
name: todos
|
|
64
|
+
description: The user's personal todo list. Use whenever they add, list, edit,
|
|
65
|
+
mark done, or remove a todo ("todo 追加", "やることリスト", "add a todo",
|
|
66
|
+
"what's on my list?", "mark X as done"). Records live at
|
|
67
|
+
`data/todos/items/<id>.json`; the user views them at `/collections/todos`.
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
# Todos (schema-driven collection)
|
|
71
|
+
|
|
72
|
+
## Record shape
|
|
73
|
+
- `id` — string, primary key (the filename). New items: `todo-<slug>` or
|
|
74
|
+
`todo-<YYYYMMDDHHmm>`.
|
|
75
|
+
- `text` — the task line (required).
|
|
76
|
+
- `status` — `todo` | `doing` | `done` (required). This IS the done state.
|
|
77
|
+
- `priority` — `urgent` | `high` | `medium` | `low` (optional).
|
|
78
|
+
- `dueDate` — `YYYY-MM-DD` (optional).
|
|
79
|
+
- `note` — markdown (optional; URLs, context).
|
|
80
|
+
- Do NOT write a `done` field — it's a projection of `status`.
|
|
81
|
+
|
|
82
|
+
## What to do
|
|
83
|
+
- **Add**: derive an id, default `status: "todo"`, Write the JSON.
|
|
84
|
+
- **List**: read `data/todos/items/`, answer from the files; point the user at
|
|
85
|
+
`/collections/todos` rather than reciting the table.
|
|
86
|
+
- **Mark done**: Read → set `status: "done"` → Write.
|
|
87
|
+
- **Edit / Delete**: Read → mutate / remove the file (preserve untouched fields).
|
|
88
|
+
- After a change, call `presentCollection` with slug `todos` (and the id) to show
|
|
89
|
+
it inline.
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## A record on disk
|
|
93
|
+
|
|
94
|
+
One JSON per item — note there is **no `done` key**:
|
|
95
|
+
|
|
96
|
+
```json
|
|
97
|
+
{ "id": "todo-buy-milk", "text": "Buy milk", "status": "todo", "priority": "high", "dueDate": "2026-06-10" }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## What the user gets, with zero host code
|
|
101
|
+
|
|
102
|
+
- **Table** — a `done` checkbox per row (ticking it sets `status: "done"`), plus
|
|
103
|
+
inline `status` / `priority` dropdowns.
|
|
104
|
+
- **Kanban** — columns from `status`; drag a card to move it, or tick its
|
|
105
|
+
per-card `done` checkbox (same write).
|
|
106
|
+
- **Calendar** — todos on their `dueDate`.
|
|
107
|
+
- **Bell** — fires for open `urgent`/`high` todos, clears on done / delete.
|
|
108
|
+
|
|
109
|
+
## Options
|
|
110
|
+
|
|
111
|
+
- **Reminder timing.** Add `"triggerField": "dueDate"` to hold the bell until the
|
|
112
|
+
due date instead of firing on create; add `"triggerLeadDays": 2` to fire it N
|
|
113
|
+
days early. `triggerField` must name a real `date` field.
|
|
114
|
+
- **Notify on everything.** Drop `notifyWhen` to bell every open todo (not just
|
|
115
|
+
high priority).
|
|
116
|
+
- **Severity color.** `notifyWhen` controls *whether* the bell fires, not its
|
|
117
|
+
color — the bell uses the standard severity; per-value red-vs-amber isn't
|
|
118
|
+
modelled.
|
|
119
|
+
|
|
120
|
+
## Migrating the legacy `todo-plugin`
|
|
121
|
+
|
|
122
|
+
Old records live at `data/plugins/%40mulmoclaude%2Ftodo-plugin/todos.json` (a
|
|
123
|
+
single array) with the columns in the sibling `columns.json`. Convert them:
|
|
124
|
+
|
|
125
|
+
1. **Columns → `status` values.** Use the `columns.json` ids as the `status`
|
|
126
|
+
enum `values` (keep the user's own columns — e.g.
|
|
127
|
+
`["todo", "mulmoclaude", "mag2", "done"]`). Set `completionDoneValues` to the
|
|
128
|
+
column whose `isDone` is `true`, and `kanbanField: "status"`.
|
|
129
|
+
2. **Add the `done` toggle.** `onValue` = the done column, `offValue` = the
|
|
130
|
+
default open column (e.g. `"todo"`). **Do not skip this** — it's the checkbox
|
|
131
|
+
the legacy list had.
|
|
132
|
+
3. **Fold `completed` into `status`.** A legacy `completed: true` item belongs in
|
|
133
|
+
the done column; don't carry a separate boolean.
|
|
134
|
+
4. **One file per item.** Split the array into `data/todos/items/<id>.json`,
|
|
135
|
+
preserving each legacy `id` verbatim.
|
|
136
|
+
5. **Convert `createdAt`.** The legacy value is Unix milliseconds — convert it to
|
|
137
|
+
a `YYYY-MM-DD` `date` field. Carry `priority`, `dueDate`, `note` straight
|
|
138
|
+
across.
|
|
139
|
+
6. Leave the legacy plugin files in place (don't delete) unless the user asks.
|
|
140
|
+
```
|