@mulmoclaude/core 0.13.0 → 0.14.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.
Files changed (56) hide show
  1. package/assets/helps/billing-clients-worklog.md +15 -12
  2. package/assets/helps/collection-skills.md +42 -19
  3. package/assets/helps/gemini.md +5 -3
  4. package/assets/helps/todo-collection.md +13 -7
  5. package/dist/{deriveAll-CMFXDQ_G.js → calendarGrid-C1rfCXJ3.cjs} +468 -3
  6. package/dist/calendarGrid-C1rfCXJ3.cjs.map +1 -0
  7. package/dist/{deriveAll-BQ_p5HSB.cjs → calendarGrid-kO6rGfm0.js} +289 -74
  8. package/dist/calendarGrid-kO6rGfm0.js.map +1 -0
  9. package/dist/collection/core/ids.d.ts +12 -0
  10. package/dist/collection/core/recordZ.d.ts +25 -0
  11. package/dist/collection/core/schema.d.ts +65 -418
  12. package/dist/collection/core/schemaZ.d.ts +846 -0
  13. package/dist/collection/core/templatePath.d.ts +34 -0
  14. package/dist/collection/core/where.d.ts +9 -15
  15. package/dist/collection/index.cjs +34 -295
  16. package/dist/collection/index.cjs.map +1 -1
  17. package/dist/collection/index.d.ts +1 -0
  18. package/dist/collection/index.js +5 -270
  19. package/dist/collection/index.js.map +1 -1
  20. package/dist/collection/paths.cjs +1 -1
  21. package/dist/collection/paths.cjs.map +1 -1
  22. package/dist/collection/paths.js +1 -1
  23. package/dist/collection/paths.js.map +1 -1
  24. package/dist/collection/registry/server/index.cjs +1 -1
  25. package/dist/collection/registry/server/index.js +1 -1
  26. package/dist/collection/server/discovery.d.ts +2 -244
  27. package/dist/collection/server/index.cjs +3 -1
  28. package/dist/collection/server/index.js +2 -2
  29. package/dist/collection/server/templatePath.d.ts +1 -34
  30. package/dist/collection/server/validate.d.ts +11 -7
  31. package/dist/collection-watchers/clock.d.ts +1 -0
  32. package/dist/collection-watchers/index.cjs +13 -4
  33. package/dist/collection-watchers/index.cjs.map +1 -1
  34. package/dist/collection-watchers/index.d.ts +1 -0
  35. package/dist/collection-watchers/index.js +13 -5
  36. package/dist/collection-watchers/index.js.map +1 -1
  37. package/dist/feeds/index.cjs +4 -4
  38. package/dist/feeds/index.js +2 -2
  39. package/dist/feeds/ingestTypes.d.ts +15 -40
  40. package/dist/feeds/server/index.cjs +5 -5
  41. package/dist/feeds/server/index.js +3 -3
  42. package/dist/{ingestTypes-C7EheYZX.cjs → ingestTypes-BIFXlw7M.cjs} +3 -3
  43. package/dist/ingestTypes-BIFXlw7M.cjs.map +1 -0
  44. package/dist/{ingestTypes-CmJeOUJc.js → ingestTypes-KuYPX9Ea.js} +2 -2
  45. package/dist/ingestTypes-KuYPX9Ea.js.map +1 -0
  46. package/dist/{server-DKXXFTbw.js → server-Cc6XcyOA.js} +417 -187
  47. package/dist/server-Cc6XcyOA.js.map +1 -0
  48. package/dist/{server-G6GtOdaW.cjs → server-Cd5WdM8a.cjs} +440 -198
  49. package/dist/server-Cd5WdM8a.cjs.map +1 -0
  50. package/package.json +2 -2
  51. package/dist/deriveAll-BQ_p5HSB.cjs.map +0 -1
  52. package/dist/deriveAll-CMFXDQ_G.js.map +0 -1
  53. package/dist/ingestTypes-C7EheYZX.cjs.map +0 -1
  54. package/dist/ingestTypes-CmJeOUJc.js.map +0 -1
  55. package/dist/server-DKXXFTbw.js.map +0 -1
  56. package/dist/server-G6GtOdaW.cjs.map +0 -1
@@ -103,18 +103,19 @@ Don't push for fields the user hasn't given you — leave optional fields out of
103
103
  the JSON entirely.
104
104
 
105
105
  ## What to do
106
- **Add**: derive an `id`, build the record, Write `data/clients/items/<id>.json`.
107
- List the directory first and pick a fresh slug if the file already exists — don't
108
- silently overwrite.
106
+ **Add**: derive an `id`, build the record, write via `manageCollection`
107
+ `putItems` with `mode: "create"` an id collision comes back as a `rejected`
108
+ row instead of silently overwriting; pick a fresh slug and retry.
109
109
 
110
- **List / look up**: read `data/clients/items/`, answer from those files. Don't
110
+ **List / look up**: `manageCollection` `getItems`, answer from the rows. Don't
111
111
  recite the whole table in chat — the user sees it at `/collections/clients`. A
112
112
  one-line confirmation ("Added Acme Corp.") is enough.
113
113
 
114
- **Update**: Read merge changes Write back. Preserve fields you weren't asked
115
- to change.
114
+ **Update**: `putItems` with `mode: "merge"` and a partial row
115
+ (`{ id, <changed fields> }`) — the default upsert replaces the whole record.
116
116
 
117
- **Delete**: confirm once if the request is ambiguous, then remove the file.
117
+ **Delete**: confirm once if the request is ambiguous, then remove the file
118
+ (`manageCollection` has no delete).
118
119
 
119
120
  ## Linking to a client in chat
120
121
  Link to the collection view, not the raw JSON path:
@@ -179,18 +180,20 @@ description: A simple timesheet — log billable / non-billable hours per client
179
180
  ## clientId resolution
180
181
  `clientId` is a `ref` to the `clients` collection — write the raw client slug.
181
182
  When the user says "log 2 hours for Acme":
182
- - List `data/clients/items/` and find the slug whose `name` matches "Acme"
183
- (case-insensitive substring is fine).
183
+ - `manageCollection` `getItems` on `clients` (`fields: ["id", "name"]`) and
184
+ find the slug whose `name` matches "Acme" (case-insensitive substring is
185
+ fine).
184
186
  - If no match: ask whether to create the client first (via the `clients` skill)
185
187
  or use a literal slug they supply. Never invent a clientId that doesn't exist —
186
188
  it renders as a broken link.
187
189
 
188
190
  ## What to do
189
191
  **Log hours**: derive `id`, default `billable: true`, default `date` to today if
190
- unspecified, Write the JSON. (This skill tracks total hours per day per client —
191
- not start/end times.)
192
+ unspecified, write via `manageCollection` `putItems` (`mode: "create"`; fix any
193
+ `rejected` row from its `problem` text and retry). (This skill tracks total
194
+ hours per day per client — not start/end times.)
192
195
 
193
- **List / summarize**: read `data/worklog/items/`, answer from the files. Don't
196
+ **List / summarize**: `manageCollection` `getItems`, answer from the rows. Don't
194
197
  recite the table — point at `/collections/worklog`. For aggregates ("how many
195
198
  hours did I bill Acme last month?") group by clientId + date range and answer in
196
199
  one line.
@@ -31,7 +31,7 @@ data/skills/<slug>/ ← YOU write here (Write / Edit)
31
31
  SKILL.md · schema.json · templates/*.md
32
32
 
33
33
  data/<name>/items/ ← the records (separate from the skill dir)
34
- <id>.json ← one record per file (you write; host reads + renders)
34
+ <id>.json ← one record per file (write via manageCollection putItems)
35
35
  ```
36
36
 
37
37
  - **Author under `data/skills/<slug>/`, NEVER `.claude/skills/<slug>/`
@@ -761,10 +761,25 @@ single source of truth and the "done" checkbox is a `toggle` field projecting it
761
761
 
762
762
  ## Records — one JSON object per file
763
763
 
764
- - Write each record to `<dataPath>/<id>.json` via the **Write** tool; the `id`
765
- field's value is the filename (no extension).
764
+ Each record is a plain file at `<dataPath>/<id>.json` (the `id` field's value is
765
+ the filename, no extension) — that is the storage model. But you read and write
766
+ records through **`manageCollection`**, not raw file I/O:
767
+
768
+ - **Create / update — `putItems`.** Every row is validated against the schema
769
+ BEFORE the write (required fields, enum membership, primaryKey = record id)
770
+ and the result reports `{ written, rejected }` — fix each rejected row from
771
+ its `problem` text and retry just those rows. Use `mode: "create"` when
772
+ adding, so an id collision is rejected instead of silently overwritten, and
773
+ `mode: "merge"` with a partial row (`{ id, <changed fields> }`) when
774
+ updating — the default upsert replaces the WHOLE record and would erase
775
+ every optional field the row omits.
776
+ - **Read / list — `getItems`.** The only way to see host-computed `derived` /
777
+ `toggle` / `embed` values (the stored JSON never contains them). Pass `ids`
778
+ / `fields` on large collections to keep the result small — e.g.
779
+ `fields: ["id"]` to check for an id collision before an add.
780
+ - **Delete** — remove the record file (`manageCollection` has no delete).
766
781
  - **Id charset** (enforced by `safeRecordId` in
767
- `packages/plugins/collection-plugin/src/server/paths.ts` — the single source of
782
+ `packages/core/src/collection/server/paths.ts` — the single source of
768
783
  truth; `manageCollection` rejects ids that fail it): start and end with a
769
784
  letter or digit; inside, also `-`, `_`, and `.` are allowed (so natural keys
770
785
  like a Slack ts `1718900000.123456` or a SemVer `1.2.3` work). **No** path
@@ -774,6 +789,24 @@ single source of truth and the "done" checkbox is a `toggle` field projecting it
774
789
  enforces this on every targeted read/write, so an id that only _looks_ fine in
775
790
  a full `getItems` listing but violates the rule can't be updated or deleted by
776
791
  id — fix the id, don't work around it with raw file I/O.
792
+ - **Never write `derived` fields**, and never write an `embed` field — both are
793
+ display-only / host-computed (`putItems` rejects rows that carry them).
794
+ - Leave optional fields out of the row entirely rather than writing empty
795
+ strings.
796
+ - For a `ref` field, write the raw target slug, and make sure that record
797
+ actually exists in the target collection — an invalid slug renders as a broken
798
+ link. The host enforces structure and safety; **you own semantic correctness**
799
+ (valid refs, sane values).
800
+
801
+ ### Raw file I/O on records — the escape hatch
802
+
803
+ Read / Write / Edit on the record files stays available (files are the source
804
+ of truth), but it skips `putItems`' pre-write validation — a mistake lands on
805
+ disk instead of coming back as a `rejected` row. Reach for it only when the
806
+ tool can't do the job: bulk file surgery, or repairing a file so malformed
807
+ that `manageCollection` can't address it. If you do write record files
808
+ directly:
809
+
777
810
  - **The file MUST be valid JSON.** A malformed record is **silently skipped** at
778
811
  read time (logged server-side, but invisible in the UI) — so one bad file out
779
812
  of fifteen looks like "fourteen records vanished." The #1 cause is an
@@ -782,22 +815,12 @@ single source of truth and the "done" checkbox is a `toggle` field projecting it
782
815
  (`text`, `markdown`, a long `objective`), either escape every inner ASCII quote
783
816
  as `\"`, or — better — use the language's own quotation marks (`「」`/`『』` for
784
817
  Japanese, `‘ ’`/`“ ”` or `'…'` for English) so no escaping is needed.
785
- `presentCollection` re-validates the records and reports any unreadable /
818
+ - `presentCollection` re-validates the records and reports any unreadable /
786
819
  malformed / schema-violating files back to you (a `⚠️` in its result) — so
787
- always follow a batch of writes with a `presentCollection` call and **act on
788
- any ⚠️ it returns** (Read → fix → Write), rather than assuming every record
789
- landed.
790
- - **List the directory first** and pick a fresh id rather than silently
791
- overwriting. Update = Read, merge, Write back (preserve fields you weren't
792
- asked to change). Delete = remove the file.
793
- - **Never write `derived` fields**, and never write an `embed` field — both are
794
- display-only / host-computed.
795
- - Leave optional fields out of the JSON entirely rather than writing empty
796
- strings.
797
- - For a `ref` field, write the raw target slug, and make sure that record
798
- actually exists in the target collection — an invalid slug renders as a broken
799
- link. The host enforces structure and safety; **you own semantic correctness**
800
- (valid refs, sane values).
820
+ always follow a batch of direct writes with a `presentCollection` call and
821
+ **act on any ⚠️ it returns**, rather than assuming every record landed.
822
+ (This safety net applies after `putItems` batches too, but direct writes are
823
+ where it earns its keep.)
801
824
 
802
825
  ## End-to-end: creating a new collection skill
803
826
 
@@ -36,19 +36,21 @@ The Gemini API has a **free tier that is sufficient for personal use**. Higher-v
36
36
  1. Open [Google AI Studio → API keys](https://aistudio.google.com/apikey) and sign in with a Google account.
37
37
  2. Click **Create API key**. If prompted, select or create a Google Cloud project (any project will do).
38
38
  3. Copy the key — it starts with `AIza…`.
39
- 4. Open the project's `.env` file in the repository root (copy `.env.example` first if it doesn't exist yet) and add the line:
39
+ 4. Create (or open) a `.env` file **in the directory you launch MulmoClaude from** — i.e. the directory where you run `npx mulmoclaude` (in a cloned repo, that's the repo root). Add the line:
40
40
 
41
- ```
41
+ ```dotenv
42
42
  GEMINI_API_KEY=AIza…your-key…
43
43
  ```
44
44
 
45
+ The key stays in your launch directory, not in the `~/mulmoclaude` workspace (which is managed by the assistant). You can also just `export GEMINI_API_KEY=…` in your shell before launching — an exported value takes precedence over the `.env` file.
46
+
45
47
  5. Restart MulmoClaude so the new environment variable is picked up.
46
48
 
47
49
  ## Verifying It's Active
48
50
 
49
51
  The quickest check: switch to the **Artist** role and ask for _"an image of a red panda"_. If a real image appears in the canvas (instead of an italic text marker or a disabled-role hint), the key is wired up correctly.
50
52
 
51
- You can also inspect the server log on startup: messages like `GEMINI_API_KEY not set — image placeholders will render as text markers` indicate the key is missing or misread.
53
+ You can also inspect the server log on startup: a `GEMINI_API_KEY not set — image / audio / video generation is unavailable` warning means the key was not found (missing, misread, or placed outside your launch directory).
52
54
 
53
55
  ## Security
54
56
 
@@ -80,11 +80,16 @@ description: The user's personal todo list. Use whenever they add, list, edit,
80
80
  - Do NOT write a `done` field — it's a projection of `status`.
81
81
 
82
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).
83
+ - **Add**: derive an id, default `status: "todo"`, write via `manageCollection`
84
+ `putItems` (`mode: "create"`; a rejected row comes back with a `problem` to
85
+ fix and retry).
86
+ - **List**: `manageCollection` `getItems` (the only way to see the computed
87
+ `done` toggle); point the user at `/collections/todos` rather than reciting
88
+ the table.
89
+ - **Mark done**: `putItems` with `mode: "merge"` and `{ id, status: "done" }`.
90
+ - **Edit**: `putItems` `mode: "merge"` with a partial row — the default upsert
91
+ replaces the whole record. **Delete**: remove the file (`manageCollection`
92
+ has no delete).
88
93
  - After a change, call `presentCollection` with slug `todos` (and the id) to show
89
94
  it inline.
90
95
  ```
@@ -131,8 +136,9 @@ single array) with the columns in the sibling `columns.json`. Convert them:
131
136
  the legacy list had.
132
137
  3. **Fold `completed` into `status`.** A legacy `completed: true` item belongs in
133
138
  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.
139
+ 4. **One record per item.** Split the array into one `putItems` row per item
140
+ (stored as `data/todos/items/<id>.json`), preserving each legacy `id`
141
+ verbatim.
136
142
  5. **Convert `createdAt`.** The legacy value is Unix milliseconds — convert it to
137
143
  a `YYYY-MM-DD` `date` field. Carry `priority`, `dueDate`, `note` straight
138
144
  across.