moontide-mcp 2.1.0 → 2.4.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/README.md +54 -331
- package/dist/ask.js +59 -12
- package/dist/catalog.js +118 -9
- package/dist/instruments.js +83 -9
- package/dist/mcp.js +146 -20
- package/dist/midi-state.js +43 -1
- package/dist/protocol.js +123 -5
- package/dist/reference.js +12 -0
- package/dist/request-status.js +221 -0
- package/dist/session.js +203 -7
- package/dist/vocabulary.js +420 -0
- package/package.json +3 -2
- package/page/ask-view.js +118 -50
- package/page/ask.js +139 -169
- package/page/bar-model.js +44 -2
- package/page/change-timing.js +35 -0
- package/page/index.html +6 -2
- package/page/instruments-model.js +50 -3
- package/page/instruments-view.js +14 -2
- package/page/jam-analysis.js +68 -23
- package/page/jam-model.js +185 -29
- package/page/jam-resolve.js +271 -0
- package/page/jam-view.js +165 -10
- package/page/lights.js +29 -7
- package/page/main.js +807 -66
- package/page/midi-clock.js +22 -3
- package/page/midi-in.js +407 -0
- package/page/midi-out.js +557 -48
- package/page/midi-plan.js +42 -6
- package/page/midi-rig.js +286 -13
- package/page/midi-settings.js +507 -0
- package/page/mixer-view.js +7 -1
- package/page/note-panel.js +29 -8
- package/page/note-tag.js +19 -4
- package/page/page-rewrite.js +1 -1
- package/page/played-buffer.js +208 -0
- package/page/query-check.js +21 -2
- package/page/request-status-view.js +419 -0
- package/page/request-status.js +686 -0
- package/page/scratch-gesture.js +239 -0
- package/page/scratch-listen.js +260 -0
- package/page/scratch-model.js +1696 -0
- package/page/scratch-view.js +2176 -0
- package/page/song-grid-model.js +15 -0
- package/page/song-grid-view.js +1 -1
- package/page/structure.js +2 -1
- package/page/style.css +1243 -43
- package/page/tempo-glide.js +330 -0
- package/page/tempo-guard.js +62 -10
- package/page/timeline-edit-view.js +1 -1
- package/page/timeline.js +2 -1
- package/page/top-bar-view.js +18 -13
- package/page/views.js +20 -7
- package/reference/midi.md +2 -0
- package/reference/motif.md +83 -0
- package/reference/settings.md +161 -0
- package/reference/transitions.md +13 -1
package/README.md
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
> The moon guides the tide.
|
|
4
4
|
|
|
5
|
+
**Early:** Moontide is young and has known bugs, and its Scratchpad is early experimental (see the
|
|
6
|
+
[known limits](https://konni-arcadia.github.io/moontide/troubleshooting.html#known-limits)).
|
|
7
|
+
|
|
5
8
|
**A hobby project: Claude plays live music for you. Describe a mood, a theme or an idea, and
|
|
6
9
|
Claude writes [Strudel](https://strudel.cc) code that plays in your browser and changes without
|
|
7
10
|
ever stopping.**
|
|
@@ -20,339 +23,74 @@ written by an AI coding assistant working under my direction. I set the goals, l
|
|
|
20
23
|
every change, tried it on my own instruments, judged the results by ear, and made the
|
|
21
24
|
decisions. I am responsible for what is here, errors included.
|
|
22
25
|
|
|
23
|
-
## Why
|
|
24
|
-
|
|
25
|
-
Curiosity. I wanted to see how far an AI assistant can go at making real music, not a
|
|
26
|
-
technical demo, when it is given a good instrument. Strudel is that instrument: a live-coding
|
|
27
|
-
language where a change lands on the next cycle, with no cut, and where the music is plain text
|
|
28
|
-
that both of us can read.
|
|
29
|
-
|
|
30
|
-
So Claude writes the code, and the code stays on screen while it plays. You can watch it, edit
|
|
31
|
-
it by hand, go back to any earlier version, or point at something in the page and ask for a
|
|
32
|
-
change.
|
|
33
|
-
|
|
34
26
|
## What it does
|
|
35
27
|
|
|
36
28
|
- You talk ("start something dreamy in D minor", "darker", "add a bass"). Claude writes
|
|
37
|
-
Strudel code, and a browser page plays it at once
|
|
38
|
-
of a second, without stopping the music.
|
|
29
|
+
Strudel code, and a browser page plays it at once, without stopping the music.
|
|
39
30
|
- The code is always visible and editable. Every version that played is kept, and Claude can
|
|
40
31
|
bring any of them back.
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
or a tree, a mixer with groups and lights whose moves rewrite the code, and a jam view with
|
|
44
|
-
the scale on a piano or a guitar.
|
|
32
|
+
- The page shows the music in several views: a timeline of named tracks where you can edit
|
|
33
|
+
notes, a Song grid, the history, a mixer and a jam view with the scale on a piano or a guitar.
|
|
45
34
|
- A right-click on anything in the page sends Claude a request with that context.
|
|
46
|
-
-
|
|
35
|
+
- Claude can move the tempo up or down over a number of bars, instead of jumping.
|
|
36
|
+
- The same code can play real MIDI instruments from the page, and a
|
|
37
|
+
[Scratchpad](https://konni-arcadia.github.io/moontide/scratchpad.html) (early experimental) lets you draw or play in a motif and send
|
|
38
|
+
it to Claude.
|
|
39
|
+
- On an instrument whose model describes them, Claude can set tone, part and scene
|
|
40
|
+
[settings](https://konni-arcadia.github.io/moontide/synth-settings.html) that no program change carries, written in the code like a sound
|
|
41
|
+
or a control. Moontide stores none of them. The models that describe settings are files of your own;
|
|
42
|
+
none ships with Moontide.
|
|
47
43
|
|
|
48
44
|
Moontide does not start from scratch. The music engine is
|
|
49
45
|
**[Strudel](https://strudel.cc)**, the JavaScript port of TidalCycles by Felix Roos, Alex McLean and the
|
|
50
46
|
Strudel contributors. Moontide is the bridge between Claude and a Strudel page.
|
|
51
47
|
|
|
52
|
-
## How it works
|
|
53
|
-
|
|
54
|
-
```
|
|
55
|
-
you ──talk──► Claude ──MCP (stdio)──► Moontide ──WebSocket (localhost)──► browser page
|
|
56
|
-
│ Strudel plays
|
|
57
|
-
└─ history file on your disk the code, sound
|
|
58
|
-
and Web MIDI out
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
1. Moontide runs as an MCP server that Claude connects to. It starts nothing until you ask
|
|
62
|
-
for music or say "start Moontide".
|
|
63
|
-
2. It then opens a page in your browser with a Strudel editor.
|
|
64
|
-
3. Claude writes the code, the page plays it, and you watch the code come alive.
|
|
65
|
-
4. Claude can stop the music at any point. The page has its own Play and Stop button too.
|
|
66
|
-
5. Right-click anything in the page to ask Claude about it. A right-click on the Timeline also
|
|
67
|
-
sends the moment under the pointer: the cycle and the beat, the version that was playing, and
|
|
68
|
-
the notes sounding there or starting in the same sixteenth, with their velocities and, for a
|
|
69
|
-
MIDI track, its instrument and channel. The Session drawer (the Session button in the top
|
|
70
|
-
bar) says whether page requests are live (they reach Claude right away) or wait for Claude
|
|
71
|
-
(they reach it with its next action). Its (i) explains how to switch.
|
|
72
|
-
|
|
73
48
|
## Install
|
|
74
49
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
claude mcp add --scope user moontide -- npx -y moontide-mcp
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Then start a new Claude session and ask for music. The steps below explain each part.
|
|
82
|
-
|
|
83
|
-
### 1. What you need
|
|
84
|
-
|
|
85
|
-
- **Node 22 or later**, with npm and npx (they come with Node).
|
|
86
|
-
- **Claude Code or Claude Desktop.** Moontide is an MCP server: Claude talks to it.
|
|
87
|
-
- **Chrome**, the browser Moontide was tried with. For MIDI (Web MIDI), use Chrome or Edge.
|
|
88
|
-
- **Internet access**: Strudel and its sounds load from the web.
|
|
89
|
-
- **Speakers or headphones.**
|
|
90
|
-
|
|
91
|
-
### 2. Check Node
|
|
92
|
-
|
|
93
|
-
In a terminal, run:
|
|
94
|
-
|
|
95
|
-
```bash
|
|
96
|
-
node --version
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
It must print `v22` or higher. If Node is missing or older, install the LTS version from
|
|
100
|
-
[nodejs.org](https://nodejs.org). On macOS with Homebrew, `brew install node` works too. Open a
|
|
101
|
-
new terminal afterwards and check again.
|
|
102
|
-
|
|
103
|
-
### 3. Get Claude
|
|
104
|
-
|
|
105
|
-
Install Claude Code or Claude Desktop from [claude.com](https://claude.com/claude-code) and sign
|
|
106
|
-
in once. Moontide works with either; Claude Code is the one tried most.
|
|
107
|
-
|
|
108
|
-
### 4. Add Moontide to Claude
|
|
109
|
-
|
|
110
|
-
<details open>
|
|
111
|
-
<summary>Claude Code (tested on macOS; Linux untested)</summary>
|
|
112
|
-
|
|
113
|
-
In a terminal, run:
|
|
50
|
+
What you need: **Node 22 or later**, **Claude Code or Claude Desktop**, **Chrome** (Chrome or Edge
|
|
51
|
+
for MIDI), internet access (Strudel and its sounds load from the web), and speakers or headphones.
|
|
114
52
|
|
|
115
53
|
```bash
|
|
116
54
|
claude mcp add --scope user moontide -- npx -y moontide-mcp
|
|
117
55
|
```
|
|
118
56
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
Keep the name `moontide`. The page-request commands below use it.
|
|
124
|
-
|
|
125
|
-
Check the install with:
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
claude mcp get moontide
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
The same line is meant for Linux. It is untested there.
|
|
132
|
-
|
|
133
|
-
</details>
|
|
134
|
-
|
|
135
|
-
<details>
|
|
136
|
-
<summary>Claude Desktop (tested on macOS; Windows untested)</summary>
|
|
137
|
-
|
|
138
|
-
Open Settings, then Developer, then Edit Config. The file is:
|
|
139
|
-
|
|
140
|
-
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
141
|
-
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
142
|
-
|
|
143
|
-
Add Moontide to it:
|
|
144
|
-
|
|
145
|
-
```json
|
|
146
|
-
{
|
|
147
|
-
"mcpServers": {
|
|
148
|
-
"moontide": {
|
|
149
|
-
"command": "npx",
|
|
150
|
-
"args": ["-y", "moontide-mcp"]
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
If the file already has an `mcpServers` block, add the `moontide` entry inside it. Quit Claude
|
|
157
|
-
Desktop fully and start it again. Claude Desktop has no Linux version.
|
|
158
|
-
|
|
159
|
-
</details>
|
|
160
|
-
|
|
161
|
-
<details>
|
|
162
|
-
<summary>Windows with Claude Code (untested)</summary>
|
|
57
|
+
Then start a new Claude session and ask for music. Claude Desktop, Windows, running from the
|
|
58
|
+
source code, updating and uninstalling are on the manual's
|
|
59
|
+
[Install page](https://konni-arcadia.github.io/moontide/install.html).
|
|
163
60
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
```bash
|
|
167
|
-
claude mcp add --scope user moontide -- cmd /c npx -y moontide-mcp
|
|
168
|
-
```
|
|
169
|
-
|
|
170
|
-
Check it with `claude mcp get moontide`. The command must read `cmd` with `/c npx -y moontide-mcp`. If it shows `C:/` instead of `/c`, edit the `moontide` entry in the `.claude.json` file of your user folder so it reads `"command": "cmd", "args": ["/c", "npx", "-y", "moontide-mcp"]`.
|
|
171
|
-
|
|
172
|
-
This form comes from community reports. It has not been tried here.
|
|
173
|
-
|
|
174
|
-
</details>
|
|
175
|
-
|
|
176
|
-
<details>
|
|
177
|
-
<summary>From the source code</summary>
|
|
178
|
-
|
|
179
|
-
To run Moontide from a copy of this repository instead of npm:
|
|
180
|
-
|
|
181
|
-
```bash
|
|
182
|
-
git clone https://github.com/konni-arcadia/moontide.git
|
|
183
|
-
cd moontide
|
|
184
|
-
npm ci
|
|
185
|
-
npm run build
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
Then add it with the full path of `dist/index.js`, written here as `<folder>`:
|
|
189
|
-
|
|
190
|
-
```bash
|
|
191
|
-
claude mcp add --scope user moontide -- node <folder>/dist/index.js
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
`npm test` runs the test suite. The real-browser checks (`npm run probe:page`) need Chrome.
|
|
195
|
-
|
|
196
|
-
</details>
|
|
197
|
-
|
|
198
|
-
### 5. First music
|
|
61
|
+
## First music
|
|
199
62
|
|
|
200
63
|
1. Start a new Claude session. Nothing opens yet: Moontide waits until you want it.
|
|
201
|
-
2. Ask for music, for example "Play something calm to start the day"
|
|
202
|
-
groove". Or say "start Moontide". In Claude Code, `/mcp__moontide__start` does the same.
|
|
64
|
+
2. Ask for music, for example "Play something calm to start the day". Or say "start Moontide".
|
|
203
65
|
3. The page opens in a new tab at http://localhost:5757, or on another port when 5757 is busy.
|
|
204
|
-
Claude's replies name the address. The tab title names the folder Claude runs in and the
|
|
205
|
-
port, for example "Moontide · my-project · 5757".
|
|
206
66
|
4. Click once anywhere in the page. Browsers start sound only after a click.
|
|
207
67
|
5. Ask for changes: "darker", "add a bass", "slower".
|
|
208
68
|
|
|
209
|
-
|
|
210
|
-
address from Claude's reply yourself.
|
|
211
|
-
|
|
212
|
-
### Update
|
|
213
|
-
|
|
214
|
-
To see the version that runs, run `npx -y moontide-mcp --version`. Releases are listed in
|
|
215
|
-
[CHANGELOG.md](https://github.com/konni-arcadia/moontide/blob/main/CHANGELOG.md). npx can keep
|
|
216
|
-
using a copy it already downloaded; to move to the newest release, add Moontide again with
|
|
217
|
-
`@latest`:
|
|
218
|
-
|
|
219
|
-
```bash
|
|
220
|
-
claude mcp remove moontide --scope user
|
|
221
|
-
claude mcp add --scope user moontide -- npx -y moontide-mcp@latest
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
### Uninstall
|
|
225
|
-
|
|
226
|
-
```bash
|
|
227
|
-
claude mcp remove moontide --scope user
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
In Claude Desktop, remove the `moontide` entry from the config file and restart it. Moontide
|
|
231
|
-
keeps its history and settings in your data folder (see History); delete that folder if you
|
|
232
|
-
want them gone too.
|
|
233
|
-
|
|
234
|
-
### MIDI instruments (optional)
|
|
235
|
-
|
|
236
|
-
Moontide can play MIDI instruments from the code through Web MIDI. It ships ready models for the
|
|
237
|
-
Roland MT-32 and SC-55. You can describe your own instruments and models, add sound catalogs and
|
|
238
|
-
send MIDI clock. The setup is in
|
|
239
|
-
[docs/midi.md](https://github.com/konni-arcadia/moontide/blob/main/docs/midi.md).
|
|
240
|
-
|
|
241
|
-
## Page requests without a permission prompt
|
|
242
|
-
|
|
243
|
-
In the Code tab of the Claude app, ask Claude to turn page requests on or off. Claude calls
|
|
244
|
-
Moontide's `set_page_requests` tool. In Claude Code in a terminal, type
|
|
245
|
-
`/mcp__moontide__page-requests-on` to make them live or `/mcp__moontide__page-requests-off` to
|
|
246
|
-
make them wait. Live, Claude starts a monitor, which lasts at most 30 minutes and is started
|
|
247
|
-
again when a tool reply asks. Waiting, tool replies stop offering to start it until page
|
|
248
|
-
requests are turned on again. Page requests need an open page, so start Moontide first. These
|
|
249
|
-
names assume the server was added as `moontide`.
|
|
250
|
-
|
|
251
|
-
Claude Code asks permission before it starts the monitor for page requests, because it runs a
|
|
252
|
-
command. You can approve it each time. If you want a narrower permission rule instead, see the
|
|
253
|
-
permissions section of the Claude Code documentation. Do not allow broad `node` commands.
|
|
254
|
-
|
|
255
|
-
## Musical reference
|
|
256
|
-
|
|
257
|
-
Claude gets a factual reference of the exact Strudel version the page runs, through the
|
|
258
|
-
`get_strudel_reference` tool (also offered as MCP resources), covering nine topics: Strudel
|
|
259
|
-
vs TidalCycles, mini-notation, pattern functions, effects and sound controls, the sound
|
|
260
|
-
palette, music theory in Strudel, transitions, song structure, and MIDI output. It holds facts only: what
|
|
261
|
-
exists and how to write it. Moontide ships no mood vocabulary, no style material and no model
|
|
262
|
-
pieces; Claude translates your words with its own knowledge of music theory, then tells you in
|
|
263
|
-
one line what it chose. Ask for a surprise in your own words: there is no menu command for it.
|
|
264
|
-
Set a frame for the session, for example gradual changes, and Claude follows it for every
|
|
265
|
-
change until you lift it. Say what matters to you (a melody, a rhythm) and Claude keeps it,
|
|
266
|
-
and asks before a full rewrite.
|
|
69
|
+
More in [First music](https://konni-arcadia.github.io/moontide/first-music.html).
|
|
267
70
|
|
|
268
|
-
##
|
|
71
|
+
## The manual
|
|
269
72
|
|
|
270
|
-
|
|
271
|
-
|
|
73
|
+
The manual is at [https://konni-arcadia.github.io/moontide/](https://konni-arcadia.github.io/moontide/). It is written in plain words,
|
|
74
|
+
and every page ends with a box that says what was checked and what was not.
|
|
272
75
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
76
|
+
- Getting started: [Install](https://konni-arcadia.github.io/moontide/install.html),
|
|
77
|
+
[First music](https://konni-arcadia.github.io/moontide/first-music.html) and
|
|
78
|
+
[the page and its views](https://konni-arcadia.github.io/moontide/the-page.html).
|
|
79
|
+
- Working with Claude: [asking Claude](https://konni-arcadia.github.io/moontide/asking-claude.html),
|
|
80
|
+
requests from the page, small edits, song structure, tempo ramps and history.
|
|
81
|
+
- MIDI gear: [instruments and the setup file](https://konni-arcadia.github.io/moontide/midi-instruments.html),
|
|
82
|
+
[sounds and catalogs](https://konni-arcadia.github.io/moontide/midi-sounds.html),
|
|
83
|
+
[tone, part and scene settings](https://konni-arcadia.github.io/moontide/synth-settings.html),
|
|
84
|
+
[the Scratchpad](https://konni-arcadia.github.io/moontide/scratchpad.html),
|
|
85
|
+
[keyboard input](https://konni-arcadia.github.io/moontide/keyboard-input.html) and
|
|
86
|
+
[sending a motif](https://konni-arcadia.github.io/moontide/sending-a-motif.html).
|
|
87
|
+
- Troubleshooting: [problems and known limits](https://konni-arcadia.github.io/moontide/troubleshooting.html).
|
|
88
|
+
- How it works: [the pieces and the tools](https://konni-arcadia.github.io/moontide/how-it-works.html),
|
|
89
|
+
what was measured, and
|
|
90
|
+
[network, privacy, credits and license](https://konni-arcadia.github.io/moontide/privacy-and-credits.html).
|
|
276
91
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
Every new piece Claude writes starts with one `const song` at the top. It names the sections
|
|
280
|
-
with their lengths in cycles, says which version (variation) of each track plays in each section
|
|
281
|
-
or that the track rests, and may put tracks into groups. It is still ordinary Strudel, and the
|
|
282
|
-
same code plays in plain Strudel. A small request on an older piece is made in place and does not
|
|
283
|
-
restructure it. A piece without a song plays and looks as before.
|
|
284
|
-
|
|
285
|
-
For a piece with a song, the Parts view is called **Song**. The Song grid shows the tracks under
|
|
286
|
-
foldable groups, with one column per section and the playing section marked.
|
|
287
|
-
|
|
288
|
-
- Click a section header to launch that section on the next line the toolbar names: the end of
|
|
289
|
-
the playing section, the next phrase or the next bar. Click again to cancel.
|
|
290
|
-
- Click a cell to pick another variation for that track and section, or a rest. It takes over
|
|
291
|
-
with your usual hand-edit timing.
|
|
292
|
-
- A fixed variation says whether its notes fit the section's harmony: fits, does not fit, or
|
|
293
|
-
cannot check. It only says; it never blocks.
|
|
294
|
-
- Play after Stop starts from the first section. The Jam view's Declared line follows the
|
|
295
|
-
playing section.
|
|
296
|
-
|
|
297
|
-
The **Mixer** draws the groups as frames that fold together with the grid. One click on a
|
|
298
|
-
group's M or S mutes or solos every track below it, as one change. There is no group fader,
|
|
299
|
-
because plain Strudel has no gain for a group. A small light on each strip and each group blinks
|
|
300
|
-
when its note is heard. With motion off in the Session drawer, a light stays lit while notes come.
|
|
301
|
-
|
|
302
|
-
On the **Timeline**, click a note to select it. Drag it up or down, or press the Up and Down
|
|
303
|
-
keys, to change its pitch by semitones. Delete, Backspace or "Make a rest" turns it into a rest.
|
|
304
|
-
Two buttons change its velocity by a tenth. The page checks an edit before it writes it, says
|
|
305
|
-
first when one edit changes the note in several places, and uses your hand-edit timing. A note
|
|
306
|
-
the page cannot edit says why. Your gesture then opens the Ask box with a ready-made request,
|
|
307
|
-
and it goes to Claude only when you press Enter. Drag across one lane, or hold Shift and press
|
|
308
|
-
Left or Right, to select a range of whole cycles, and use "Ask Claude" to send it.
|
|
309
|
-
|
|
310
|
-
A right-click on a section, a track, a cell or a group asks Claude about that thing.
|
|
311
|
-
|
|
312
|
-
## History
|
|
313
|
-
|
|
314
|
-
Every version that plays is kept: Claude's own changes and your Ctrl+Enter edits, each with
|
|
315
|
-
its time, who made it and a short label. Failed code and stops are not versions.
|
|
316
|
-
|
|
317
|
-
Ask Claude things like "go back to the previous version", "the one from 3 changes ago", "the
|
|
318
|
-
one with the bass", or "keep this one, call it Take two", and "list my favorites". The
|
|
319
|
-
Session drawer shows the number of the version that is playing, in its Last change card, for
|
|
320
|
-
example `v42`.
|
|
321
|
-
|
|
322
|
-
The history file lives in your OS's per-user data folder:
|
|
323
|
-
|
|
324
|
-
- macOS: `~/Library/Application Support/moontide/history.jsonl`
|
|
325
|
-
- Linux: `$XDG_DATA_HOME/moontide/history.jsonl`, or `~/.local/share/moontide/history.jsonl`
|
|
326
|
-
- Windows: `%APPDATA%\moontide\history.jsonl`
|
|
327
|
-
|
|
328
|
-
Set `MOONTIDE_DATA_DIR` to use another folder. The history is one text file with one entry
|
|
329
|
-
per line, and survives the end of the Claude session.
|
|
330
|
-
|
|
331
|
-
The history keeps the last 1000 versions; older ones are dropped in batches and Claude is
|
|
332
|
-
told. Favorites are never dropped.
|
|
333
|
-
|
|
334
|
-
If a line of the file cannot be read, Claude is told which lines; they stay in the file
|
|
335
|
-
untouched. If the file cannot be read or written at all, the music keeps working and Claude
|
|
336
|
-
is told.
|
|
337
|
-
|
|
338
|
-
Run one Moontide at a time with the same history file. Two at once can give two versions the
|
|
339
|
-
same number.
|
|
340
|
-
|
|
341
|
-
## Troubleshooting
|
|
342
|
-
|
|
343
|
-
**"Failed to connect" right after installing.** The first start downloads the package, and Claude Code gives up after 30 seconds. Wait a little and check again with `claude mcp list`. If it keeps failing, start Claude Code with a longer limit: `MCP_TIMEOUT=60000 claude`. In PowerShell, use `$env:MCP_TIMEOUT = "60000"; claude`.
|
|
344
|
-
|
|
345
|
-
**Moontide is missing in another folder.** It was added without `--scope user`. Add it again with the Install line.
|
|
346
|
-
|
|
347
|
-
**The page did not open.** Claude's replies name its address. Open it yourself, or ask Claude to start Moontide again: it gives the address and opens a new tab when no page is connected.
|
|
348
|
-
|
|
349
|
-
**Several Moontide tabs.** Each Claude session that started Moontide has its own page and port. The tab title says "Moontide · <folder> · <port>", with the folder that session runs in, and the Session drawer shows the same. Ask Claude which tab is its own: it answers with the address, the port and the tab title.
|
|
350
|
-
|
|
351
|
-
**No sound.** Click once in the page. Check that the browser tab is not muted, and keep the page open.
|
|
352
|
-
|
|
353
|
-
**Claude Desktop does not find `npx`.** Put the full path in `"command"`: the path printed by `which npx` on macOS or `where npx` on Windows, written here as `<full path printed by which npx>`. This is common advice and has not been tried here. The Claude Desktop logs are in `~/Library/Logs/Claude` on macOS and `%APPDATA%\Claude\logs` on Windows.
|
|
354
|
-
|
|
355
|
-
**MIDI says to use Chrome or Edge.** Web MIDI is missing in that browser.
|
|
92
|
+
The same pages can be read on GitHub, as Markdown, in the
|
|
93
|
+
[site folder](https://github.com/konni-arcadia/moontide/tree/main/site).
|
|
356
94
|
|
|
357
95
|
## Status
|
|
358
96
|
|
|
@@ -360,25 +98,15 @@ Early and personal. It runs on one setup: a Mac, Chrome, Claude Code and Claude
|
|
|
360
98
|
for MIDI, a Roland SC-55, the Munt MT-32 emulator and a Roland JUPITER-Xm. It has not been tried
|
|
361
99
|
anywhere else. The test suite runs on Windows, Linux and macOS in CI, but nobody has played
|
|
362
100
|
music on Windows or Linux yet. There are known bugs. Feedback is welcome, but support is not
|
|
363
|
-
something I can promise.
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
- Tried by hand only on macOS, with Chrome, Claude Code and Claude Desktop.
|
|
368
|
-
- Windows and Linux are untested by hand. Edge, Safari and Firefox are untested.
|
|
369
|
-
- Internet access is needed.
|
|
370
|
-
- MIDI needs Chrome or Edge.
|
|
371
|
-
- Run one Moontide at a time with the same history file.
|
|
372
|
-
- The first start through npx downloads the package and can be slow.
|
|
101
|
+
something I can promise. The list is in
|
|
102
|
+
[Known limits](https://konni-arcadia.github.io/moontide/troubleshooting.html#known-limits).
|
|
103
|
+
What is not yet checked by hand is listed at the end of each manual page and in the
|
|
104
|
+
[changelog](https://github.com/konni-arcadia/moontide/blob/main/CHANGELOG.md).
|
|
373
105
|
|
|
374
106
|
## Network and privacy
|
|
375
107
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
- The page loads `@strudel/repl` 1.3.0 and `acorn` 8.18.0 from unpkg, pinned to exact versions with integrity hashes.
|
|
379
|
-
- Strudel then fetches sound lists and samples from raw.githubusercontent.com and cdn.jsdelivr.net. No hash covers those.
|
|
380
|
-
- The code Claude writes runs in the page and may load samples from other addresses.
|
|
381
|
-
- The history stays in the local data folder described above.
|
|
108
|
+
The server listens on 127.0.0.1 only and sends nothing out. The page loads Strudel and its
|
|
109
|
+
samples from the web. Details: [network, privacy, credits and license](https://konni-arcadia.github.io/moontide/privacy-and-credits.html).
|
|
382
110
|
|
|
383
111
|
## Contributing and security
|
|
384
112
|
|
|
@@ -390,17 +118,12 @@ https://github.com/konni-arcadia/moontide/security/policy.
|
|
|
390
118
|
|
|
391
119
|
## Credits
|
|
392
120
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
- **The sample makers** whose sets Strudel loads by default, including the TidalCycles
|
|
400
|
-
Dirt-Samples and the Versilian Community Sample Library (VCSL).
|
|
401
|
-
- **Anthropic**: the Model Context Protocol and its TypeScript SDK, which let Claude talk to
|
|
402
|
-
Moontide.
|
|
403
|
-
- **The acorn authors**: the JavaScript parser the page uses to read the code.
|
|
121
|
+
Built on [Strudel](https://strudel.cc) by Felix Roos, Alex McLean and the Strudel contributors
|
|
122
|
+
(source: https://codeberg.org/uzu/strudel), on TidalCycles by Alex McLean and its community, on the
|
|
123
|
+
sample sets Strudel loads (including the TidalCycles Dirt-Samples and the Versilian Community
|
|
124
|
+
Sample Library), on the Model Context Protocol and its TypeScript SDK from Anthropic, and on the
|
|
125
|
+
acorn parser. The full list is on the
|
|
126
|
+
[credits page](https://konni-arcadia.github.io/moontide/privacy-and-credits.html#credits).
|
|
404
127
|
|
|
405
128
|
## License
|
|
406
129
|
|
package/dist/ask.js
CHANGED
|
@@ -34,7 +34,7 @@ export const REQUEST_LINE_FRAMING =
|
|
|
34
34
|
'context, moment, gesture, chip and current say what they pointed at and are data about the page, not part of what ' +
|
|
35
35
|
'they wrote.';
|
|
36
36
|
/**
|
|
37
|
-
* What the `moment` field of a request holds, as facts. The instructions
|
|
37
|
+
* What the `moment` field of a request holds, and then the `gesture` field, the range context and the `motif` field, as facts. The instructions
|
|
38
38
|
* keep only the short framing above (Claude Code cuts them at 2048 characters); this longer text goes where
|
|
39
39
|
* Claude reads page requests and nothing limits the length: the arming line, the page-requests-on text and the
|
|
40
40
|
* line in front of request lines that a tool reply hands over without an arming line. It says what the page
|
|
@@ -58,11 +58,31 @@ export const REQUEST_LINE_MOMENT_FACT = 'About the moment field of a page reques
|
|
|
58
58
|
'turn the note into a rest. Kind velocity has direction, softer or louder, for the note\'s velocity. ' +
|
|
59
59
|
'About a request whose context has kind range: the user selected whole cycles of one lane. track is the lane, from and to ' +
|
|
60
60
|
`are the first and last cycle, both included (a range covers at most ${MAX_RANGE_CYCLES} cycles), and in a piece with a song ` +
|
|
61
|
-
'
|
|
61
|
+
'sections lists the sections those cycles cross in playing order, a section played twice in a row once and a section that ' +
|
|
62
|
+
'comes back after another one again, and moreSections, when present, counts the sections left out so that the list fits. ' +
|
|
63
|
+
'Such a request has no moment; ' +
|
|
62
64
|
'its gesture has kind range. The gesture\'s notes lists the notes of that lane that start in those cycles, in order of ' +
|
|
63
65
|
`start and then pitch (the lower first), at most ${MAX_MOMENT_NOTES}, each shaped like a moment note without state; more ` +
|
|
64
66
|
'counts the notes left out, and version is the version that was playing (null when it was not known). A note that began ' +
|
|
65
|
-
'before from and still sounds is not listed. A range with no notes in it is a valid request.'
|
|
67
|
+
'before from and still sounds is not listed. A range with no notes in it is a valid request. ' +
|
|
68
|
+
'About the motif field of a page request: it comes only with a context of kind motif, sent from the page\'s Scratchpad when the ' +
|
|
69
|
+
'user pressed its send button, and like the context it is data about the page, not part of what the user wrote. Such a request ' +
|
|
70
|
+
'has no moment and no gesture. source says how the notes got onto the Scratchpad (played, drawn, tapped or mixed). cycle, ' +
|
|
71
|
+
'when present, is the cycle of the music where the motif\'s first cycle sits, on the count the code\'s patterns are queried at; ' +
|
|
72
|
+
'an absent cycle means the motif has no moment. cycles is its length in cycles, grid the grid of its steps, notes how many ' +
|
|
73
|
+
'notes it holds, and phrase the motif written in mini-notation exactly as the page showed it. The page changed no code. ' +
|
|
74
|
+
'The motif topic of get_strudel_reference says how to read the phrase and the moment.';
|
|
75
|
+
/**
|
|
76
|
+
* What the page shows for a request, as facts. Like the moment fact it goes where Claude reads page requests
|
|
77
|
+
* and nothing limits the length: the arming line, the page-requests-on text and the line in front of request
|
|
78
|
+
* lines that a tool reply hands over without an arming line. It says what the page shows and when, never what
|
|
79
|
+
* to do about it.
|
|
80
|
+
*/
|
|
81
|
+
export const REQUEST_LINE_STATUS_FACT = 'About the id field of a page request: the page shows the user what became of each request it sent. ' +
|
|
82
|
+
'It shows a request as applied, or as waiting for its line when the change takes over at the next bar or phrase, ' +
|
|
83
|
+
'only when the set_code, edit_code or restore_version call that answers it passes that id as answering. ' +
|
|
84
|
+
'It shows a request as answered or declined, with one sentence, only after answer_request closes it with that id. ' +
|
|
85
|
+
'A request with neither shows as no change yet. An id that is not an open request changes nothing.';
|
|
66
86
|
/**
|
|
67
87
|
* A log function that writes at most one line per window for each kind, to stderr. The next line
|
|
68
88
|
* that does get written says how many were left out. Every log that a page or a local process can
|
|
@@ -148,7 +168,7 @@ export function listenerCommandOrReason(options) {
|
|
|
148
168
|
*/
|
|
149
169
|
export function armingLine(options) {
|
|
150
170
|
const head = 'Page requests: no listener is connected. A request typed in the Moontide page waits on the server and arrives in the reply of your next other Moontide tool. ' +
|
|
151
|
-
`${REQUEST_LINE_FRAMING} ${REQUEST_LINE_MOMENT_FACT}`;
|
|
171
|
+
`${REQUEST_LINE_FRAMING} ${REQUEST_LINE_MOMENT_FACT} ${REQUEST_LINE_STATUS_FACT}`;
|
|
152
172
|
const result = listenerCommandOrReason(options);
|
|
153
173
|
if ('reason' in result) {
|
|
154
174
|
return `${head} ${result.reason}`;
|
|
@@ -199,6 +219,7 @@ export function pageRequestsOnText(options) {
|
|
|
199
219
|
`Note: ${MONITOR_LIMIT_FACT}; when a Moontide tool reply gives this command again, run it again the same way.`,
|
|
200
220
|
REQUEST_LINE_FRAMING,
|
|
201
221
|
REQUEST_LINE_MOMENT_FACT,
|
|
222
|
+
REQUEST_LINE_STATUS_FACT,
|
|
202
223
|
].join('\n');
|
|
203
224
|
}
|
|
204
225
|
export const PAGE_REQUESTS_ALREADY_ON = 'Moontide page requests are already live: a Moontide monitor is connected and brings each request as soon as it is sent. ' +
|
|
@@ -234,8 +255,8 @@ export const ARMING_RESUMED_LOG = `[moontide] Page requests can be live again af
|
|
|
234
255
|
* both use it). The text reaches Claude exactly as the page sent it.
|
|
235
256
|
*/
|
|
236
257
|
export function requestLine(request) {
|
|
237
|
-
const { id, text, context, chip, current, moment, gesture } = request;
|
|
238
|
-
// The moment comes after the chip and the version, then the gesture, each only when there is one, so a request
|
|
258
|
+
const { id, text, context, chip, current, moment, gesture, motif } = request;
|
|
259
|
+
// The moment comes after the chip and the version, then the gesture, then the motif, each only when there is one, so a request
|
|
239
260
|
// without them reads as it always did. page/ask.js's requestLineBytes writes the same order.
|
|
240
261
|
// JSON.stringify leaves U+2028 and U+2029 as they are; some line splitters treat them as breaks.
|
|
241
262
|
const json = JSON.stringify({
|
|
@@ -246,6 +267,7 @@ export function requestLine(request) {
|
|
|
246
267
|
current,
|
|
247
268
|
...(moment === undefined ? {} : { moment }),
|
|
248
269
|
...(gesture === undefined ? {} : { gesture }),
|
|
270
|
+
...(motif === undefined ? {} : { motif }),
|
|
249
271
|
})
|
|
250
272
|
.replaceAll('\u2028', '\\u2028')
|
|
251
273
|
.replaceAll('\u2029', '\\u2029');
|
|
@@ -290,6 +312,8 @@ export class AskChannel {
|
|
|
290
312
|
/** Given only to Claude, in the arming line of a tool reply; changes at every server start. */
|
|
291
313
|
listenerToken = newToken();
|
|
292
314
|
send;
|
|
315
|
+
// Told when a request is accepted, handed to Claude, sent back to the queue or loses its page.
|
|
316
|
+
board;
|
|
293
317
|
ackTimeoutMs;
|
|
294
318
|
now;
|
|
295
319
|
trusted = new Set();
|
|
@@ -307,6 +331,7 @@ export class AskChannel {
|
|
|
307
331
|
logRefusal;
|
|
308
332
|
constructor(options) {
|
|
309
333
|
this.send = options.send;
|
|
334
|
+
this.board = options.board;
|
|
310
335
|
this.ackTimeoutMs = options.ackTimeoutMs ?? LISTENER_ACK_TIMEOUT_MS;
|
|
311
336
|
this.now = options.now ?? Date.now;
|
|
312
337
|
this.logRefusal = makeThrottledLog(ASK_RATE_WINDOW_MS, this.now);
|
|
@@ -409,8 +434,11 @@ export class AskChannel {
|
|
|
409
434
|
this.refuse(conn, msg.id, 'line-too-long');
|
|
410
435
|
return;
|
|
411
436
|
}
|
|
412
|
-
// The id is chosen by the page. A repeat would overwrite the in-flight slot and orphan its timer
|
|
413
|
-
|
|
437
|
+
// The id is chosen by the page. A repeat would overwrite the in-flight slot and orphan its timer, or
|
|
438
|
+
// take over the board row of a request Claude already holds, which may be another page's.
|
|
439
|
+
if (this.inFlight.has(msg.id) ||
|
|
440
|
+
this.queue.some((waiting) => waiting.id === msg.id) ||
|
|
441
|
+
this.board?.has(msg.id) === true) {
|
|
414
442
|
this.logRefusal('duplicate', '[moontide] Refused a page request: its id is already in use');
|
|
415
443
|
this.refuse(conn, msg.id, 'invalid', 'duplicate request id');
|
|
416
444
|
return;
|
|
@@ -418,6 +446,7 @@ export class AskChannel {
|
|
|
418
446
|
this.recent.push(now);
|
|
419
447
|
const entry = { id: msg.id, line, origin: conn, answered: 'none' };
|
|
420
448
|
if (this.listener !== null) {
|
|
449
|
+
this.board?.opened(msg.id, conn, { queued: false, text: msg.text, chip: msg.chip });
|
|
421
450
|
this.dispatch(entry);
|
|
422
451
|
return;
|
|
423
452
|
}
|
|
@@ -427,6 +456,7 @@ export class AskChannel {
|
|
|
427
456
|
return;
|
|
428
457
|
}
|
|
429
458
|
entry.answered = 'waiting';
|
|
459
|
+
this.board?.opened(msg.id, conn, { queued: true, text: msg.text, chip: msg.chip });
|
|
430
460
|
this.queue.push(entry);
|
|
431
461
|
this.send(conn, { type: 'ask-result', id: msg.id, status: 'waiting', waiting: this.queue.length }, 'the ask answer');
|
|
432
462
|
}
|
|
@@ -479,6 +509,7 @@ export class AskChannel {
|
|
|
479
509
|
this.pushPageRequestsState();
|
|
480
510
|
const requeued = this.requeueInFlight();
|
|
481
511
|
for (const entry of requeued) {
|
|
512
|
+
this.board?.waiting(entry.id);
|
|
482
513
|
if (entry.answered === 'none') {
|
|
483
514
|
entry.answered = 'waiting';
|
|
484
515
|
if (entry.origin !== null) {
|
|
@@ -512,6 +543,7 @@ export class AskChannel {
|
|
|
512
543
|
}
|
|
513
544
|
this.inFlight.delete(ack);
|
|
514
545
|
const { entry } = slot;
|
|
546
|
+
this.board?.delivered([entry.id]);
|
|
515
547
|
if (entry.origin === null) {
|
|
516
548
|
return;
|
|
517
549
|
}
|
|
@@ -525,6 +557,7 @@ export class AskChannel {
|
|
|
525
557
|
/** A page connection closed: its trust goes, and nobody is answered for its requests any more. */
|
|
526
558
|
detachPage(conn) {
|
|
527
559
|
this.trusted.delete(conn);
|
|
560
|
+
this.board?.pageGone(conn);
|
|
528
561
|
for (const entry of this.queue) {
|
|
529
562
|
if (entry.origin === conn) {
|
|
530
563
|
entry.origin = null;
|
|
@@ -541,17 +574,31 @@ export class AskChannel {
|
|
|
541
574
|
}
|
|
542
575
|
this.untrustedRefusals.delete(conn);
|
|
543
576
|
}
|
|
577
|
+
/**
|
|
578
|
+
* A reloaded page took over these requests from the board: the ones still waiting or in flight whose
|
|
579
|
+
* page has gone now answer to it, so it is told when they are handed over or go back to waiting.
|
|
580
|
+
*/
|
|
581
|
+
adoptOrigin(conn, ids) {
|
|
582
|
+
const adopted = new Set(ids);
|
|
583
|
+
const entries = [...this.queue, ...[...this.inFlight.values()].map((slot) => slot.entry)];
|
|
584
|
+
for (const entry of entries) {
|
|
585
|
+
if (entry.origin === null && adopted.has(entry.id)) {
|
|
586
|
+
entry.origin = conn;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
}
|
|
544
590
|
/**
|
|
545
591
|
* Called by the tool replies: takes the WHOLE waiting queue at once and returns its request lines
|
|
546
592
|
* (oldest first), telling each page its requests were handed over. Then, while no listener is
|
|
547
593
|
* connected, a page has authenticated and page-requests-off has not paused it, the arming line. When
|
|
548
|
-
* there is request text but no arming line, the moment fact
|
|
594
|
+
* there is request text but no arming line, the moment fact and the status fact go in front of the request lines.
|
|
549
595
|
*/
|
|
550
596
|
drainLines(options) {
|
|
551
597
|
const lines = [];
|
|
552
598
|
const entries = this.queue;
|
|
553
599
|
this.queue = [];
|
|
554
600
|
const handedOver = new Map();
|
|
601
|
+
this.board?.delivered(entries.map((entry) => entry.id));
|
|
555
602
|
for (const entry of entries) {
|
|
556
603
|
lines.push(entry.line);
|
|
557
604
|
if (entry.origin !== null) {
|
|
@@ -564,13 +611,13 @@ export class AskChannel {
|
|
|
564
611
|
this.send(origin, { type: 'ask-received', ids }, 'the ask hand-over');
|
|
565
612
|
}
|
|
566
613
|
if (!this.hasListener() && this.hasTrustedPage() && !this.armingPaused) {
|
|
567
|
-
// The arming line carries
|
|
614
|
+
// The arming line carries both facts itself.
|
|
568
615
|
lines.push(armingLine({ scriptPath: options.scriptPath, port: options.port, token: this.listenerToken }));
|
|
569
616
|
}
|
|
570
617
|
else if (lines.length > 0) {
|
|
571
618
|
// No arming line (page requests are paused, or a listener is connected): the reply still says what the
|
|
572
|
-
// moment of the request lines it carries holds.
|
|
573
|
-
lines.unshift(REQUEST_LINE_MOMENT_FACT);
|
|
619
|
+
// moment of the request lines it carries holds and what the page shows for a request.
|
|
620
|
+
lines.unshift(REQUEST_LINE_MOMENT_FACT, REQUEST_LINE_STATUS_FACT);
|
|
574
621
|
}
|
|
575
622
|
return lines;
|
|
576
623
|
}
|