@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.
Files changed (122) hide show
  1. package/assets/helps/billing-clients-worklog.md +215 -0
  2. package/assets/helps/billing-invoice.md +458 -0
  3. package/assets/helps/business.md +104 -0
  4. package/assets/helps/collection-skills.md +810 -0
  5. package/assets/helps/custom-view.md +433 -0
  6. package/assets/helps/feeds.md +114 -0
  7. package/assets/helps/gemini.md +57 -0
  8. package/assets/helps/github.md +23 -0
  9. package/assets/helps/guide.md +61 -0
  10. package/assets/helps/index.md +89 -0
  11. package/assets/helps/lessons-collection.md +400 -0
  12. package/assets/helps/mulmoscript.md +249 -0
  13. package/assets/helps/portfolio-tracker.md +211 -0
  14. package/assets/helps/presentation-deck.md +828 -0
  15. package/assets/helps/presenthtml.md +89 -0
  16. package/assets/helps/sandbox.md +97 -0
  17. package/assets/helps/spreadsheet.md +43 -0
  18. package/assets/helps/storyteller.md +101 -0
  19. package/assets/helps/telegram.md +136 -0
  20. package/assets/helps/todo-collection.md +140 -0
  21. package/assets/helps/vocabulary.md +109 -0
  22. package/assets/helps/wiki.md +168 -0
  23. package/assets/skills-preset/mc-cooking-coach/SKILL.md +217 -0
  24. package/assets/skills-preset/mc-library/SKILL.md +188 -0
  25. package/assets/skills-preset/mc-manage-automations/SKILL.md +119 -0
  26. package/assets/skills-preset/mc-manage-skills/SKILL.md +141 -0
  27. package/assets/skills-preset/mc-wiki-deep-lint/SKILL.md +108 -0
  28. package/assets/skills-preset/mc-wiki-health-check/SKILL.md +61 -0
  29. package/assets/skills-preset/mc-wiki-ingest/SKILL.md +182 -0
  30. package/assets/skills-preset/mc-wiki-promote/SKILL.md +175 -0
  31. package/assets/skills-preset/mc-zenn/SKILL.md +136 -0
  32. package/dist/chunk-CKQMccvm.cjs +28 -0
  33. package/dist/collection/core/actionVisible.d.ts +34 -0
  34. package/dist/collection/core/calendarGrid.d.ts +120 -0
  35. package/dist/collection/core/deriveAll.d.ts +38 -0
  36. package/dist/collection/core/derivedFormula.d.ts +18 -0
  37. package/dist/collection/core/draft.d.ts +18 -0
  38. package/dist/collection/core/enumColors.d.ts +33 -0
  39. package/dist/collection/core/errorMessage.d.ts +4 -0
  40. package/dist/collection/core/itemLabel.d.ts +12 -0
  41. package/dist/collection/core/presentCollection.d.ts +13 -0
  42. package/dist/collection/core/promptSafety.d.ts +1 -0
  43. package/dist/collection/core/schema.d.ts +355 -0
  44. package/dist/collection/core/shortHexId.d.ts +8 -0
  45. package/dist/collection/core/sortItems.d.ts +29 -0
  46. package/dist/collection/core/uiTypes.d.ts +106 -0
  47. package/dist/collection/index.cjs +793 -0
  48. package/dist/collection/index.cjs.map +1 -0
  49. package/dist/collection/index.d.ts +14 -0
  50. package/dist/collection/index.js +740 -0
  51. package/dist/collection/index.js.map +1 -0
  52. package/dist/collection/paths.cjs +44 -0
  53. package/dist/collection/paths.cjs.map +1 -0
  54. package/dist/collection/paths.js +41 -0
  55. package/dist/collection/paths.js.map +1 -0
  56. package/dist/collection/server/atomic.d.ts +1 -0
  57. package/dist/collection/server/delete.d.ts +38 -0
  58. package/dist/collection/server/derive.d.ts +8 -0
  59. package/dist/collection/server/discoveredCollection.d.ts +18 -0
  60. package/dist/collection/server/discovery.d.ts +227 -0
  61. package/dist/collection/server/host.d.ts +77 -0
  62. package/dist/collection/server/index.cjs +1721 -0
  63. package/dist/collection/server/index.cjs.map +1 -0
  64. package/dist/collection/server/index.d.ts +11 -0
  65. package/dist/collection/server/index.js +1671 -0
  66. package/dist/collection/server/index.js.map +1 -0
  67. package/dist/collection/server/io.d.ts +114 -0
  68. package/dist/collection/server/paths.d.ts +52 -0
  69. package/dist/collection/server/spawn.d.ts +55 -0
  70. package/dist/collection/server/templatePath.d.ts +25 -0
  71. package/dist/collection/server/util.d.ts +3 -0
  72. package/dist/collection/server/validate.d.ts +19 -0
  73. package/dist/collection/server/views.d.ts +20 -0
  74. package/dist/deriveAll-C15OpM3K.cjs +399 -0
  75. package/dist/deriveAll-C15OpM3K.cjs.map +1 -0
  76. package/dist/deriveAll-C6BYnpBL.js +364 -0
  77. package/dist/deriveAll-C6BYnpBL.js.map +1 -0
  78. package/dist/file-change/index.cjs +72 -0
  79. package/dist/file-change/index.cjs.map +1 -0
  80. package/dist/file-change/index.d.ts +43 -0
  81. package/dist/file-change/index.js +66 -0
  82. package/dist/file-change/index.js.map +1 -0
  83. package/dist/notifier/engine.d.ts +72 -0
  84. package/dist/notifier/index.cjs +484 -0
  85. package/dist/notifier/index.cjs.map +1 -0
  86. package/dist/notifier/index.d.ts +3 -0
  87. package/dist/notifier/index.js +464 -0
  88. package/dist/notifier/index.js.map +1 -0
  89. package/dist/notifier/store.d.ts +18 -0
  90. package/dist/notifier/types.d.ts +118 -0
  91. package/dist/notifier/validate.d.ts +17 -0
  92. package/dist/scheduler/adapter.d.ts +48 -0
  93. package/dist/scheduler/index.cjs +352 -0
  94. package/dist/scheduler/index.cjs.map +1 -0
  95. package/dist/scheduler/index.d.ts +2 -0
  96. package/dist/scheduler/index.js +343 -0
  97. package/dist/scheduler/index.js.map +1 -0
  98. package/dist/scheduler/task-manager.d.ts +51 -0
  99. package/dist/whisper/client.cjs +241 -0
  100. package/dist/whisper/client.cjs.map +1 -0
  101. package/dist/whisper/client.d.ts +35 -0
  102. package/dist/whisper/client.js +239 -0
  103. package/dist/whisper/client.js.map +1 -0
  104. package/dist/whisper/ffmpeg.d.ts +6 -0
  105. package/dist/whisper/index.cjs +433 -0
  106. package/dist/whisper/index.cjs.map +1 -0
  107. package/dist/whisper/index.d.ts +5 -0
  108. package/dist/whisper/index.js +425 -0
  109. package/dist/whisper/index.js.map +1 -0
  110. package/dist/whisper/internal.d.ts +11 -0
  111. package/dist/whisper/models.d.ts +49 -0
  112. package/dist/whisper/sidecar.d.ts +8 -0
  113. package/dist/whisper/whisper.d.ts +28 -0
  114. package/dist/workspace-setup/assets.d.ts +10 -0
  115. package/dist/workspace-setup/index.d.ts +3 -0
  116. package/dist/workspace-setup/index.js +556 -0
  117. package/dist/workspace-setup/index.js.map +1 -0
  118. package/dist/workspace-setup/slug.d.ts +6 -0
  119. package/dist/workspace-setup/slug.js +13 -0
  120. package/dist/workspace-setup/slug.js.map +1 -0
  121. package/dist/workspace-setup/sync.d.ts +94 -0
  122. package/package.json +95 -0
@@ -0,0 +1,215 @@
1
+ # Clients + Worklog — the client & time-tracking recipe
2
+
3
+ Read this when the user asks to **set up client tracking, a timesheet, or
4
+ "track my consulting work"** (sample query: *"Set up client and time tracking
5
+ for my consulting work"*). It scaffolds **two** collection skills that work
6
+ together:
7
+
8
+ - **`clients`** — a contact database (who you work for / bill).
9
+ - **`worklog`** — a timesheet; each entry references a client.
10
+
11
+ This is **Bundle A** of the billing suite. It is fully self-contained (no
12
+ dependency on anything else). The companion **Bundle B** (`invoice` + `profile`,
13
+ see `config/helps/billing-invoice.md`) builds on it: invoices reference these
14
+ `clients` and can pull billable hours from this `worklog`. **Recommended order:
15
+ set up this bundle first**, then invoicing.
16
+
17
+ Read `config/helps/collection-skills.md` first for the general schema DSL — this
18
+ file is the billing-specific specialization. Author everything under
19
+ `data/skills/<slug>/` (the bridge mirrors it to `.claude/skills/<slug>/`; the
20
+ user opens it at `/collections/<slug>`). **Do not use the `mc-` prefix.**
21
+
22
+ > **Follow this recipe verbatim — do NOT redesign.** The schemas below are
23
+ > fixed and known-good. Write them exactly as given (you may only adjust the
24
+ > `id`/`icon`/`title` if the user explicitly asks). Do **not** add fields, do
25
+ > **not** call `presentForm` to ask the user design questions, and do **not**
26
+ > mimic other collections in the workspace. The whole point is a reproducible
27
+ > billing suite. (If the user wants a *custom* collection instead, that's a
28
+ > different task — use `config/helps/collection-skills.md`.) Existing records
29
+ > under `data/clients/items` / `data/worklog/items` already match these schemas
30
+ > and will render as-is once you author the skill — no data edits needed.
31
+
32
+ ## Slug contract (do not change these)
33
+
34
+ Bundle B's `invoice` references `clients` by this exact slug, and its line-item
35
+ flow reads `worklog`. Author the two collections with **exactly** these slugs so
36
+ the cross-bundle links resolve:
37
+
38
+ | Collection | slug | `dataPath` |
39
+ |---|---|---|
40
+ | Clients | `clients` | `data/clients/items` |
41
+ | Worklog | `worklog` | `data/worklog/items` |
42
+
43
+ > The `dataPath` values are deliberately prefix-free. If the user previously used
44
+ > the legacy `mc-clients` / `mc-worklog` preset skills, these same paths hold
45
+ > their existing records — re-creating with these slugs **re-attaches to that
46
+ > data** (no migration, no data loss). They can then Unstar the old `mc-*` skills
47
+ > in the skill manager.
48
+
49
+ ## Order: `clients` before `worklog`
50
+
51
+ `worklog.clientId` is a `ref` to `clients`, so create `clients` first (its
52
+ records are what the worklog's client picker lists). Then create `worklog`.
53
+
54
+ ---
55
+
56
+ ## 1. `clients`
57
+
58
+ `data/skills/clients/schema.json`:
59
+
60
+ ```json
61
+ {
62
+ "title": "Clients",
63
+ "icon": "people",
64
+ "dataPath": "data/clients/items",
65
+ "primaryKey": "id",
66
+ "fields": {
67
+ "id": { "type": "string", "label": "ID", "primary": true, "required": true },
68
+ "name": { "type": "string", "label": "Name", "required": true },
69
+ "email": { "type": "email", "label": "Email" },
70
+ "address": { "type": "text", "label": "Address" },
71
+ "notes": { "type": "markdown", "label": "Notes" }
72
+ }
73
+ }
74
+ ```
75
+
76
+ `data/skills/clients/SKILL.md`:
77
+
78
+ ```markdown
79
+ ---
80
+ name: clients
81
+ description: A simple client database. Use whenever the user asks to add, list,
82
+ update, or delete a client. Records live at `data/clients/items/<id>.json`
83
+ (one JSON file per client); the user views them at `/collections/clients`,
84
+ rendered from `schema.json` by the host. You do all I/O via Read / Write /
85
+ Edit on the JSON files.
86
+ ---
87
+
88
+ # Clients (schema-driven collection)
89
+
90
+ ## Record shape
91
+ - `id` — string, **primary key** (the filename, no extension). A short
92
+ kebab-case slug derived from the name (e.g. `acme-corp`, `globex`); lowercase
93
+ letters, digits, hyphens; 1–48 chars. Pick a fresh suffix (`acme-corp-2`) if
94
+ the obvious slug is taken.
95
+ - `name` — string, **required**
96
+ - `email` — email
97
+ - `address` — multi-line text
98
+ - `notes` — markdown
99
+
100
+ Don't push for fields the user hasn't given you — leave optional fields out of
101
+ the JSON entirely.
102
+
103
+ ## What to do
104
+ **Add**: derive an `id`, build the record, Write `data/clients/items/<id>.json`.
105
+ List the directory first and pick a fresh slug if the file already exists — don't
106
+ silently overwrite.
107
+
108
+ **List / look up**: read `data/clients/items/`, answer from those files. Don't
109
+ recite the whole table in chat — the user sees it at `/collections/clients`. A
110
+ one-line confirmation ("Added Acme Corp.") is enough.
111
+
112
+ **Update**: Read → merge changes → Write back. Preserve fields you weren't asked
113
+ to change.
114
+
115
+ **Delete**: confirm once if the request is ambiguous, then remove the file.
116
+
117
+ ## Linking to a client in chat
118
+ Link to the collection view, not the raw JSON path:
119
+ - Do: `[Acme Corp](/collections/clients?selected=acme-corp)`
120
+ - Don't: `[Acme Corp](data/clients/items/acme-corp.json)`
121
+
122
+ Always include `?selected=<id>` to open that client's detail view; omit it only
123
+ for a general reference to the whole list.
124
+
125
+ ## When to ask vs. when to act
126
+ If the user gives a name and email in one sentence, just add the client. Use
127
+ `presentForm` only when you genuinely need information they haven't provided.
128
+ ```
129
+
130
+ ---
131
+
132
+ ## 2. `worklog`
133
+
134
+ `data/skills/worklog/schema.json` (note `clientId` is a `ref` **to `clients`**):
135
+
136
+ ```json
137
+ {
138
+ "title": "Worklog",
139
+ "icon": "schedule",
140
+ "dataPath": "data/worklog/items",
141
+ "primaryKey": "id",
142
+ "fields": {
143
+ "id": { "type": "string", "label": "ID", "primary": true, "required": true },
144
+ "date": { "type": "date", "label": "Date", "required": true },
145
+ "clientId": { "type": "ref", "to": "clients", "label": "Client", "required": true },
146
+ "hours": { "type": "number", "label": "Hours", "required": true },
147
+ "billable": { "type": "boolean", "label": "Billable" },
148
+ "notes": { "type": "markdown", "label": "Notes" }
149
+ }
150
+ }
151
+ ```
152
+
153
+ `data/skills/worklog/SKILL.md`:
154
+
155
+ ```markdown
156
+ ---
157
+ name: worklog
158
+ description: A simple timesheet — log billable / non-billable hours per client
159
+ per day. Use whenever the user logs, lists, edits, or removes worked hours.
160
+ Records live at `data/worklog/items/<id>.json`; the user views them at
161
+ `/collections/worklog`. `clientId` references the `clients` collection.
162
+ ---
163
+
164
+ # Worklog (schema-driven collection)
165
+
166
+ ## Record shape
167
+ - `id` — string, **primary key** (the filename). Format
168
+ `{date}-{clientId}-{4-char-hex}` (e.g. `2026-05-23-acme-corp-a1b2`); the hex
169
+ suffix avoids collisions for multiple sessions in the same day for the same
170
+ client. Generate it randomly and check the file doesn't already exist.
171
+ - `date` — ISO date `YYYY-MM-DD`, **required**
172
+ - `clientId` — ref → `clients`, **required** (the client record's slug)
173
+ - `hours` — decimal number, **required** (1.5 = 90 minutes)
174
+ - `billable` — boolean (default `true` unless the user says otherwise)
175
+ - `notes` — markdown (what was worked on)
176
+
177
+ ## clientId resolution
178
+ `clientId` is a `ref` to the `clients` collection — write the raw client slug.
179
+ When the user says "log 2 hours for Acme":
180
+ - List `data/clients/items/` and find the slug whose `name` matches "Acme"
181
+ (case-insensitive substring is fine).
182
+ - If no match: ask whether to create the client first (via the `clients` skill)
183
+ or use a literal slug they supply. Never invent a clientId that doesn't exist —
184
+ it renders as a broken link.
185
+
186
+ ## What to do
187
+ **Log hours**: derive `id`, default `billable: true`, default `date` to today if
188
+ unspecified, Write the JSON. (This skill tracks total hours per day per client —
189
+ not start/end times.)
190
+
191
+ **List / summarize**: read `data/worklog/items/`, answer from the files. Don't
192
+ recite the table — point at `/collections/worklog`. For aggregates ("how many
193
+ hours did I bill Acme last month?") group by clientId + date range and answer in
194
+ one line.
195
+
196
+ **Edit / delete**: Read → merge / remove. Preserve untouched fields.
197
+
198
+ ## Linking to an entry in chat
199
+ - Do: `[2026-05-24 Acme](/collections/worklog?selected=2026-05-24-acme-corp-a1b2)`
200
+ - Don't: link the raw JSON path.
201
+
202
+ ## When to ask vs. when to act
203
+ If the user gives a clear "log N hours for X today", just write it. Use
204
+ `presentForm` only when genuinely ambiguous (e.g. several clients match the name).
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Done
210
+
211
+ Tell the user the two collections are ready at `/collections/clients` and
212
+ `/collections/worklog`. The bridge mirrors the files and re-scans, so they appear
213
+ without a restart. If invoicing is the goal, point them at the next step: run
214
+ *"Set up invoicing for my business"* (the `config/helps/billing-invoice.md`
215
+ recipe, which references these `clients` and pulls hours from this `worklog`).
@@ -0,0 +1,458 @@
1
+ # Invoice + Profile — the invoicing recipe
2
+
3
+ Read this when the user asks to **set up invoicing / billing** (sample query:
4
+ *"Set up invoicing for my business"*). It scaffolds **two** collection skills:
5
+
6
+ - **`profile`** — the user's **own** business identity (the "bill-from" block:
7
+ company name, tax ID, payment details). A singleton (one record, id `me`).
8
+ - **`invoice`** — an invoice ledger; each invoice references a client, embeds the
9
+ profile as the issuer, and carries a table of line items with host-computed
10
+ subtotal / tax / total.
11
+
12
+ This is **Bundle B** of the billing suite. It **builds on Bundle A**
13
+ (`clients` + `worklog`, see `config/helps/billing-clients-worklog.md`):
14
+
15
+ - `invoice.clientId` is a **required `ref` to `clients`** — without the `clients`
16
+ collection the client picker is empty and you can't link an invoice to a client.
17
+ - The "invoice my hours this month" flow reads the `worklog` collection.
18
+
19
+ **Check the dependency before you finish.** List `data/skills/` and
20
+ `data/clients/items/`. If there is **no `clients` collection** (no
21
+ `data/skills/clients/` and no client records), tell the user invoicing works best
22
+ with the Clients & Worklog bundle and **offer to set it up too** (run the
23
+ `config/helps/billing-clients-worklog.md` recipe). Don't silently produce an
24
+ invoice collection that can't link a client. If they decline, proceed anyway —
25
+ the schema is valid on its own and the client picker simply stays empty until
26
+ `clients` exists (dangling refs render fail-soft, not as errors).
27
+
28
+ Read `config/helps/collection-skills.md` first for the general schema DSL. Author
29
+ everything under `data/skills/<slug>/` (the bridge mirrors it to
30
+ `.claude/skills/<slug>/`; the user opens it at `/collections/<slug>`). **Do not
31
+ use the `mc-` prefix.**
32
+
33
+ > **Follow this recipe verbatim — do NOT redesign.** The schemas and templates
34
+ > below are fixed and known-good. Write them exactly as given (you may only
35
+ > adjust `id`/`icon`/`title` if the user explicitly asks). Do **not** add fields,
36
+ > do **not** call `presentForm` to ask design questions, and do **not** mimic
37
+ > other collections in the workspace. The whole point is a reproducible billing
38
+ > suite. (For a *custom* collection, use `config/helps/collection-skills.md`
39
+ > instead.) Existing records under `data/invoice/items` / `data/profile/items`
40
+ > already match these schemas and will render as-is — no data edits needed.
41
+
42
+ ## Slug contract (do not change these)
43
+
44
+ `invoice` references the other collections by these exact slugs:
45
+
46
+ | Collection | slug | `dataPath` | referenced by |
47
+ |---|---|---|---|
48
+ | Profile | `profile` | `data/profile/items` | `invoice.issuer` (embed `profile/me`) |
49
+ | Invoice | `invoice` | `data/invoice/items` | — |
50
+ | Clients | `clients` | `data/clients/items` | `invoice.clientId` (ref, from Bundle A) |
51
+
52
+ > The `dataPath` values are prefix-free. If the user previously used the legacy
53
+ > `mc-invoice` / `mc-profile` preset skills, these same paths hold their existing
54
+ > records — re-creating with these slugs **re-attaches to that data** (no
55
+ > migration). They can then Unstar the old `mc-*` skills in the skill manager.
56
+
57
+ ## Order: `profile` before `invoice`
58
+
59
+ `invoice.issuer` embeds the `profile/me` record, so create `profile` first.
60
+
61
+ ---
62
+
63
+ ## 1. `profile`
64
+
65
+ `data/skills/profile/schema.json` (a **singleton** — exactly one record, id `me`):
66
+
67
+ ```json
68
+ {
69
+ "title": "Business Profile",
70
+ "icon": "badge",
71
+ "dataPath": "data/profile/items",
72
+ "primaryKey": "id",
73
+ "singleton": "me",
74
+ "fields": {
75
+ "id": { "type": "string", "label": "ID", "primary": true, "required": true },
76
+ "companyName": { "type": "string", "label": "Company / legal name", "required": true },
77
+ "taxRegistrationId": { "type": "string", "label": "Tax registration ID (VAT / EIN / T-number)" },
78
+ "email": { "type": "email", "label": "Email" },
79
+ "phone": { "type": "string", "label": "Phone" },
80
+ "address": { "type": "text", "label": "Address" },
81
+ "paymentDetails": { "type": "markdown", "label": "Payment details (bank / wire / PayPal)" },
82
+ "defaultBookId": { "type": "string", "label": "Default accounting book ID" },
83
+ "notes": { "type": "markdown", "label": "Notes" }
84
+ }
85
+ }
86
+ ```
87
+
88
+ `data/skills/profile/SKILL.md`:
89
+
90
+ ```markdown
91
+ ---
92
+ name: profile
93
+ description: The user's own business profile — the issuer ("bill-from") identity
94
+ used on invoices. A singleton collection with exactly one record, id `me`. The
95
+ record lives at `data/profile/items/me.json`; the user views and edits it at
96
+ `/collections/profile`. Record I/O via the `manageCollection` tool (raw
97
+ Read / Write / Edit on the JSON file is the escape hatch).
98
+ ---
99
+
100
+ # Business Profile (schema-driven collection)
101
+
102
+ Holds the user's **own** business identity — the "bill-from" side of an invoice.
103
+ Counterpart to `clients`, which holds the "bill-to" parties.
104
+
105
+ ## Singleton — exactly one record, id `me`
106
+ This collection has **one** record. Its primary key is always the literal string
107
+ `me`, stored at `data/profile/items/me.json`. Never create a second record and
108
+ never invent another id — read, create, and update `me.json` only.
109
+
110
+ ## Record shape
111
+ - `id` — string, **primary key**, always `me`
112
+ - `companyName` — string, **required** (the legal/company name shown on invoices)
113
+ - `taxRegistrationId` — string (VAT / EIN / JP T-number — region-dependent)
114
+ - `email` — email
115
+ - `phone` — string
116
+ - `address` — multi-line text
117
+ - `paymentDetails` — markdown (free-form bank / wire / PayPal instructions)
118
+ - `defaultBookId` — string (the accounting book the invoice bookkeeping actions
119
+ post journals into; the `accounting` role reads it to skip book selection.
120
+ Leave unset and the role resolves the book at posting time)
121
+ - `notes` — markdown
122
+
123
+ Leave optional fields the user hasn't given you out of the JSON entirely.
124
+
125
+ ## What to do
126
+ **Set up / update**: Read `data/profile/items/me.json` (may not exist yet), merge
127
+ changes, Write back. Preserve fields you weren't asked to change. If missing and
128
+ the user wants to set their profile, create it with `id: "me"` plus the fields
129
+ they provided.
130
+
131
+ **Look up**: Read `me.json` and answer from it. If missing, tell the user their
132
+ profile isn't set up yet and offer to collect it (`presentForm` only if several
133
+ fields are needed at once).
134
+
135
+ **Never delete** the profile unless the user explicitly asks to reset it.
136
+
137
+ ## Linking to the profile in chat
138
+ - Do: `[your business profile](/collections/profile?selected=me)`
139
+ - Don't: link the raw JSON path.
140
+
141
+ ## When to ask vs. when to act
142
+ If the user gives the details in a sentence, just write them. Use `presentForm`
143
+ only when you genuinely need several fields they haven't provided.
144
+ ```
145
+
146
+ ---
147
+
148
+ ## 2. `invoice`
149
+
150
+ `data/skills/invoice/schema.json` (`issuer` embeds `profile/me`; `clientId` is a
151
+ `ref` **to `clients`**; subtotal / tax / total are `derived`):
152
+
153
+ ```json
154
+ {
155
+ "title": "Invoices",
156
+ "icon": "receipt_long",
157
+ "dataPath": "data/invoice/items",
158
+ "primaryKey": "id",
159
+ "fields": {
160
+ "id": { "type": "string", "label": "ID", "primary": true, "required": true },
161
+ "issuer": { "type": "embed", "to": "profile", "id": "me", "label": "From (issuer)" },
162
+ "clientId": { "type": "ref", "to": "clients", "label": "Client", "required": true },
163
+ "issueDate": { "type": "date", "label": "Issued", "required": true },
164
+ "dueDate": { "type": "date", "label": "Due" },
165
+ "status": { "type": "enum", "values": ["draft", "sent", "paid", "void"], "label": "Status", "required": true },
166
+ "currency": { "type": "enum", "values": ["USD", "JPY", "EUR", "GBP", "CNY", "KRW", "AUD", "CAD", "CHF", "HKD", "SGD"], "label": "Currency", "required": true },
167
+ "lineItems": {
168
+ "type": "table",
169
+ "label": "Line items",
170
+ "of": {
171
+ "description": { "type": "string", "label": "Description", "required": true },
172
+ "quantity": { "type": "number", "label": "Qty", "required": true },
173
+ "rate": { "type": "money", "label": "Rate", "currencyField": "currency", "currency": "USD", "required": true }
174
+ }
175
+ },
176
+ "subtotal": { "type": "derived", "label": "Subtotal", "formula": "sum(lineItems[].quantity * lineItems[].rate)", "display": "money", "currencyField": "currency", "currency": "USD" },
177
+ "taxRate": { "type": "number", "label": "Tax rate (e.g. 0.10 for 10%)" },
178
+ "tax": { "type": "derived", "label": "Tax", "formula": "subtotal * taxRate", "display": "money", "currencyField": "currency", "currency": "USD" },
179
+ "total": { "type": "derived", "label": "Total", "formula": "subtotal + tax", "display": "money", "currencyField": "currency", "currency": "USD" },
180
+ "notes": { "type": "markdown", "label": "Notes" }
181
+ },
182
+ "actions": [
183
+ { "id": "pdf", "label": "Generate PDF", "icon": "picture_as_pdf", "kind": "chat", "role": "accounting", "template": "templates/invoice.md" },
184
+ { "id": "journal-sale", "label": "Record sale", "icon": "request_quote", "kind": "chat", "role": "accounting", "template": "templates/journal-sale.md", "when": { "field": "status", "in": ["sent", "paid"] } },
185
+ { "id": "journal-payment", "label": "Record payment", "icon": "payments", "kind": "chat", "role": "accounting", "template": "templates/journal-payment.md", "when": { "field": "status", "in": ["paid"] } },
186
+ { "id": "journal-void", "label": "Record void", "icon": "block", "kind": "chat", "role": "accounting", "template": "templates/journal-void.md", "when": { "field": "status", "in": ["void"] } }
187
+ ]
188
+ }
189
+ ```
190
+
191
+ `data/skills/invoice/SKILL.md`:
192
+
193
+ ```markdown
194
+ ---
195
+ name: invoice
196
+ description: A simple invoice ledger — create, list, edit, and remove invoices.
197
+ Records live at `data/invoice/items/<id>.json`; the user views them at
198
+ `/collections/invoice`. Each invoice references a client (`clientId` → the
199
+ `clients` collection) and embeds the user's `profile/me` as the issuer.
200
+ Subtotal / tax / total are host-computed — you don't write them.
201
+ ---
202
+
203
+ # Invoice (schema-driven collection)
204
+
205
+ ## Record shape (read `schema.json` for authoritative types)
206
+ - `id` — string, **primary key**. Format `INV-YYYY-NNNN` (year + zero-padded
207
+ counter): `INV-2026-0001`. List `data/invoice/items/` first to find the highest
208
+ number for the year, then increment; pick the next free number rather than
209
+ overwriting.
210
+ - `issuer` — **display-only** embed of `profile/me` (the bill-from block). You do
211
+ **not** write this — it carries no stored value. If the profile isn't set up,
212
+ the view shows a "set it up" prompt; point the user at `/collections/profile`.
213
+ - `clientId` — ref → `clients`, **required**
214
+ - `issueDate` — ISO date `YYYY-MM-DD`, **required** (default today)
215
+ - `dueDate` — ISO date (optional)
216
+ - `status` — enum `draft | sent | paid | void`, **required** (default `draft`)
217
+ - `currency` — enum ISO 4217 code, **required**. Governs how `rate` and the
218
+ computed totals render. Infer it from context (client locale, the user's
219
+ `profile`, or what they state) — e.g. a Japan-based issuer billing in yen →
220
+ `JPY`. When you genuinely can't tell, ask rather than defaulting to USD.
221
+ - `lineItems` — array of `{ description, quantity, rate }`. `rate` is a plain
222
+ number in the invoice's `currency` (no symbol) — `20000` on a `JPY` invoice = ¥20,000.
223
+ - `taxRate` — decimal (e.g. `0.10` for 10%)
224
+ - `notes` — markdown
225
+ - `subtotal`, `tax`, `total` — **host-computed**; never write these.
226
+
227
+ ## clientId resolution
228
+ `clientId` is a `ref` to `clients` — write the raw client slug. For "invoice Acme
229
+ for May consulting": list `data/clients/items/`, find the slug whose `name`
230
+ matches "Acme". No match → ask whether to create the client first (via `clients`)
231
+ or supply a literal slug. Never invent a clientId — it renders as a broken link.
232
+
233
+ ## What to do
234
+ **Create**: derive an `id`, build the record, Write `data/invoice/items/<id>.json`.
235
+ Defaults: `status: "draft"`, `issueDate: <today>`. Set `currency` from context —
236
+ don't silently default to USD. Don't write `subtotal` / `tax` / `total`.
237
+
238
+ **Create from worklog hours** (common): when the user says "invoice Acme for the
239
+ work I did this month":
240
+ 1. Resolve "Acme" → a real client slug from `data/clients/items/`.
241
+ 2. List `data/worklog/items/` and filter to entries where `clientId` matches AND
242
+ `date` falls in the requested period. (If there is no `worklog` collection,
243
+ skip to manual line items.)
244
+ 3. Group matching worklog entries into line items (simplest: one line item per
245
+ entry — `description = entry.notes`, `quantity = entry.hours`, `rate = the
246
+ user's standing rate or asked`).
247
+ 4. If you have no rate on file, ASK via `presentForm` — don't invent one.
248
+ 5. Write the invoice; the host displays Subtotal / Tax / Total automatically.
249
+
250
+ If the worklog has no matching entries (or doesn't exist), tell the user and ask
251
+ whether to create one-off line items instead.
252
+
253
+ **List / summarize**: read `data/invoice/items/`, answer from the files. Point at
254
+ `/collections/invoice` rather than reciting the table. For aggregates group by
255
+ clientId + date range and answer in one line.
256
+
257
+ **Mark sent / paid / void**: Read → change `status` → Write.
258
+ **Edit line items**: Read → mutate `lineItems` → Write. Preserve untouched fields.
259
+ **Delete**: confirm once if ambiguous, then remove the file.
260
+
261
+ ## Linking to an invoice in chat
262
+ - Do: `[INV-2026-0002](/collections/invoice?selected=INV-2026-0002)`
263
+ - Don't: link the raw JSON path.
264
+ Always include `?selected=<id>`; omit it only for a general reference to the list.
265
+
266
+ ## Host actions (detail-view buttons)
267
+ The detail view shows schema-declared buttons. Each opens a *new* chat in the
268
+ `accounting` role seeded with a template + the invoice data — you don't trigger
269
+ them yourself; point the user at the button if they ask.
270
+ - **Generate PDF** (always) — renders the printable document via `presentDocument`.
271
+ - **Record sale** (status `sent`/`paid`) — posts the receivable journal.
272
+ - **Record payment** (status `paid`) — posts the cash receipt.
273
+ - **Record void** (status `void`) — voids the entries posted for this invoice.
274
+
275
+ The bookkeeping actions have no shared id store, so every entry they post carries
276
+ the invoice `id` in its memo (e.g. `INV-2026-0001 sale`); payment/void locate
277
+ prior entries by searching memos for that id. They post into the issuer's
278
+ `defaultBookId` (from `profile`), or resolve the book at posting time when unset.
279
+
280
+ ## When to ask vs. when to act
281
+ Clear info ("invoice Acme $5000 for May consulting") → just write it: one line
282
+ item (`description` "May consulting", `quantity` 1, `rate` 5000); `currency` USD
283
+ (the `$` is the tell; `¥` → `JPY`); `status` draft; today's `issueDate`. Use
284
+ `presentForm` only when something is ambiguous.
285
+ ```
286
+
287
+ ### Action templates
288
+
289
+ Also author these four files under `data/skills/invoice/templates/` (the bridge
290
+ mirrors `templates/*.md` alongside the schema). They are the natural-language
291
+ bodies the `accounting` role runs when the user clicks a detail-view button.
292
+
293
+ `data/skills/invoice/templates/invoice.md`:
294
+
295
+ ````markdown
296
+ ## Task: generate a printable invoice document
297
+
298
+ The invoice record is in the `<record_data_json>` block above (fields: `id`,
299
+ `clientId`, `issueDate`, `dueDate`, `status`, `lineItems[]` (`description`,
300
+ `quantity`, `rate`), `taxRate`, `notes`).
301
+
302
+ Produce a clean, print-ready invoice as a Markdown document (inline HTML is fine)
303
+ and present it in the canvas with the `presentDocument` tool. Steps:
304
+
305
+ 1. **Resolve the recipient (Bill To).** Read `data/clients/items/<clientId>.json`
306
+ for the client's `name`, `address`, `email`.
307
+ 2. **Resolve the issuer (From).** Read `data/profile/items/me.json` for
308
+ `companyName`, `taxRegistrationId`, `address`, `email`, `phone`,
309
+ `paymentDetails`. **If that file does not exist**, stop and tell the user their
310
+ business profile isn't set up — point them at `/collections/profile` — and do
311
+ not write a half-blank invoice.
312
+ 3. **Compute totals** from line items: `subtotal` = Σ(`quantity` × `rate`), `tax`
313
+ = `subtotal` × `taxRate` (missing `taxRate` = 0), `total` = `subtotal` + `tax`.
314
+ Format money with the invoice's `currency`.
315
+ 4. **Render** a clean invoice layout (header with `id` / issue & due dates; BILL
316
+ TO from the client; FROM from the profile; a line-item table with per-row
317
+ amount = quantity × rate; subtotal / tax / total; a payment block from the
318
+ issuer's `paymentDetails`). Omit any block whose source field is empty.
319
+ 5. **Present it** with `presentDocument`: `title` = `Invoice {id}`, `markdown` =
320
+ the rendered document, `filenamePrefix` = the invoice `id`. This is the only
321
+ correct way to surface the document — don't paste markdown into chat or write a
322
+ raw file.
323
+ 6. **Confirm** in one short sentence that the invoice is ready in the canvas.
324
+ ````
325
+
326
+ `data/skills/invoice/templates/journal-sale.md`:
327
+
328
+ ````markdown
329
+ ## Task: record the sale (account-receivable) journal for this invoice
330
+
331
+ The invoice record is in the `<record_data_json>` block above. You are the
332
+ `accounting` role and own `manageAccounting`. Post the double-entry sale journal.
333
+
334
+ ### 1. Resolve the book
335
+ 1. Read `data/profile/items/me.json`. If it has a non-empty `defaultBookId`, use
336
+ it as `bookId` and skip the rest of this step.
337
+ 2. Otherwise call `manageAccounting` `getBooks`. One book → use it. Several →
338
+ keep books whose currency/country fit the invoice; if one remains use it; else
339
+ prefer the book whose name matches the issuer's `companyName`; only if still
340
+ ambiguous, `presentForm` to ask.
341
+ 3. No book at all → tell the user to set one up first (the `accounting` role can
342
+ `createBook`) and stop.
343
+
344
+ ### 2. Resolve real account codes
345
+ Call `getAccounts` and pick actual codes — never invent one: **Accounts
346
+ Receivable** (asset, the debit), **Revenue / Sales** (income, credit for the
347
+ subtotal), **Output tax / VAT payable** (liability, credit for the tax;
348
+ `upsertAccount` one if missing and tax is non-zero). Note the A/R code.
349
+
350
+ ### 3. Guard against double-posting (idempotency)
351
+ The **memo is the join key**. Before posting, look it up with a compact, bounded
352
+ ledger query — NOT `getJournalEntries`. Call `getReport` with `kind: "ledger"`,
353
+ `accountCode: "<A/R code>"`, `period: { "kind": "range", "from": "<issueDate>",
354
+ "to": "<today>" }`. If any row's memo contains this invoice `id` and the word
355
+ `sale`, the sale is already recorded — tell the user (link via `openBook`) and
356
+ stop; do not double-post.
357
+
358
+ ### 4. Compute amounts
359
+ `subtotal` = Σ(`quantity` × `rate`); `tax` = `subtotal` × `taxRate` (0 if
360
+ missing); `total` = `subtotal` + `tax`. Don't trust any stored subtotal/tax/total.
361
+
362
+ ### 5. Post one balanced entry
363
+ `addEntries` with a single entry dated the invoice `issueDate`: **Dr** A/R =
364
+ `total`; **Cr** Revenue = `subtotal`; **Cr** Output tax = `tax` (omit when 0). Set
365
+ the `memo` to include the invoice id and the word `sale` (e.g. `INV-2026-0001
366
+ sale`). Σ debit must equal Σ credit.
367
+
368
+ ### 6. Confirm
369
+ One sentence on what was posted, and link the book with `openBook`.
370
+ ````
371
+
372
+ `data/skills/invoice/templates/journal-payment.md`:
373
+
374
+ ````markdown
375
+ ## Task: record the payment (cash-receipt) journal for this invoice
376
+
377
+ The invoice record is in the `<record_data_json>` block above. You are the
378
+ `accounting` role and own `manageAccounting`. The invoice is paid; post the
379
+ cash-receipt journal that clears the receivable. Payment is always direct deposit
380
+ to the issuer's bank account (the issuer's `paymentDetails` on
381
+ `data/profile/items/me.json`), so the debit is the book's Cash / Checking account.
382
+
383
+ ### 1. Resolve the book
384
+ Read `data/profile/items/me.json` for a non-empty `defaultBookId`; else `getBooks`
385
+ and pick as in the sale flow. No book → tell the user to set one up and stop.
386
+
387
+ ### 2. Resolve real account codes
388
+ `getAccounts` — never invent: **Cash / Checking / Bank** (asset, the debit; if
389
+ several, match the one in the issuer's `paymentDetails`), **Accounts Receivable**
390
+ (asset, the credit — same account the sale debited). Note the A/R code.
391
+
392
+ ### 3. Find the open receivable + guard against double-posting
393
+ The **memo is the join key**. Compact, bounded ledger query — NOT
394
+ `getJournalEntries`. `getReport` with `kind: "ledger"`, `accountCode: "<A/R
395
+ code>"`, `period: { "kind": "range", "from": "<issueDate>", "to": "<today>" }`.
396
+ Find the **sale** row whose memo contains this invoice `id`; if none, tell the
397
+ user to click **Record sale** first and stop. If a row's memo contains this `id`
398
+ and `payment`, payment is already recorded — tell the user (`openBook`) and stop.
399
+
400
+ ### 4. Post one balanced entry
401
+ `addEntries` with a single entry dated the payment date (today if none) for the
402
+ invoice `total` (subtotal + tax, recomputed): **Dr** Cash/Checking = `total`;
403
+ **Cr** A/R = `total`. Set `memo` to include the invoice id and `payment` (e.g.
404
+ `INV-2026-0001 payment`). If `notes` carries a payment reference, append it;
405
+ otherwise don't prompt for one.
406
+
407
+ ### 5. Confirm
408
+ One sentence confirming the cash receipt, link the book with `openBook`.
409
+ ````
410
+
411
+ `data/skills/invoice/templates/journal-void.md`:
412
+
413
+ ````markdown
414
+ ## Task: void the bookkeeping journals for this invoice
415
+
416
+ The invoice record is in the `<record_data_json>` block above. You are the
417
+ `accounting` role and own `manageAccounting`. The invoice is voided; reverse the
418
+ journals posted for it.
419
+
420
+ ### 1. Resolve the book
421
+ Read `data/profile/items/me.json` for a non-empty `defaultBookId`; else resolve
422
+ via `getBooks` (as in the sale flow). No book → nothing to void; say so and stop.
423
+
424
+ ### 2. Resolve the Accounts Receivable code
425
+ `getAccounts` and note the **Accounts Receivable** code — step 3 uses it.
426
+
427
+ ### 3. Find the entries to void (memo is the join key)
428
+ Compact, bounded ledger query — NOT `getJournalEntries`. `getReport` with `kind:
429
+ "ledger"`, `accountCode: "<A/R code>"`, `period: { "kind": "range", "from":
430
+ "<issueDate>", "to": "<today>" }`. Select entries with these deterministic rules:
431
+ 1. **Keep only original sale/payment rows** — memo contains this invoice `id`
432
+ **and** the word `sale` or `payment` (this excludes prior void reversals, whose
433
+ memo is `Void INV-…`).
434
+ 2. **Deduplicate by `entryId`**.
435
+ 3. **Skip anything already voided** (a matching opposite-sign `Void INV-…`
436
+ reversal already exists). If nothing survives, tell the user there's nothing to
437
+ void and stop.
438
+
439
+ ### 4. Confirm, then void
440
+ Voiding is irreversible. **Confirm via `presentForm` first** — list the entries
441
+ (date, accounts, amount, `entryId`) and ask. On confirmation, call `voidEntry`
442
+ once per entry with its `entryId` and a `reason` naming the invoice (e.g. `Void
443
+ INV-2026-0001`). `voidEntry` appends a reversing pair — the journal stays
444
+ append-only.
445
+
446
+ ### 5. Confirm
447
+ One sentence on which entries were voided (or that there were none); link the book
448
+ with `openBook`.
449
+ ````
450
+
451
+ ---
452
+
453
+ ## Done
454
+
455
+ Tell the user invoicing is ready at `/collections/invoice` (and their profile at
456
+ `/collections/profile`). The bridge mirrors the files and re-scans, so they appear
457
+ without a restart. Remind them: an invoice needs a `clients` collection to link a
458
+ recipient (Bundle A) and a filled-in `profile` to render the issuer block.