@mulmoclaude/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/helps/billing-clients-worklog.md +215 -0
- package/assets/helps/billing-invoice.md +458 -0
- package/assets/helps/business.md +104 -0
- package/assets/helps/collection-skills.md +810 -0
- package/assets/helps/custom-view.md +433 -0
- package/assets/helps/feeds.md +114 -0
- package/assets/helps/gemini.md +57 -0
- package/assets/helps/github.md +23 -0
- package/assets/helps/guide.md +61 -0
- package/assets/helps/index.md +89 -0
- package/assets/helps/lessons-collection.md +400 -0
- package/assets/helps/mulmoscript.md +249 -0
- package/assets/helps/portfolio-tracker.md +211 -0
- package/assets/helps/presentation-deck.md +828 -0
- package/assets/helps/presenthtml.md +89 -0
- package/assets/helps/sandbox.md +97 -0
- package/assets/helps/spreadsheet.md +43 -0
- package/assets/helps/storyteller.md +101 -0
- package/assets/helps/telegram.md +136 -0
- package/assets/helps/todo-collection.md +140 -0
- package/assets/helps/vocabulary.md +109 -0
- package/assets/helps/wiki.md +168 -0
- package/assets/skills-preset/mc-cooking-coach/SKILL.md +217 -0
- package/assets/skills-preset/mc-library/SKILL.md +188 -0
- package/assets/skills-preset/mc-manage-automations/SKILL.md +119 -0
- package/assets/skills-preset/mc-manage-skills/SKILL.md +141 -0
- package/assets/skills-preset/mc-wiki-deep-lint/SKILL.md +108 -0
- package/assets/skills-preset/mc-wiki-health-check/SKILL.md +61 -0
- package/assets/skills-preset/mc-wiki-ingest/SKILL.md +182 -0
- package/assets/skills-preset/mc-wiki-promote/SKILL.md +175 -0
- package/assets/skills-preset/mc-zenn/SKILL.md +136 -0
- package/dist/chunk-CKQMccvm.cjs +28 -0
- package/dist/collection/core/actionVisible.d.ts +34 -0
- package/dist/collection/core/calendarGrid.d.ts +120 -0
- package/dist/collection/core/deriveAll.d.ts +38 -0
- package/dist/collection/core/derivedFormula.d.ts +18 -0
- package/dist/collection/core/draft.d.ts +18 -0
- package/dist/collection/core/enumColors.d.ts +33 -0
- package/dist/collection/core/errorMessage.d.ts +4 -0
- package/dist/collection/core/itemLabel.d.ts +12 -0
- package/dist/collection/core/presentCollection.d.ts +13 -0
- package/dist/collection/core/promptSafety.d.ts +1 -0
- package/dist/collection/core/schema.d.ts +355 -0
- package/dist/collection/core/shortHexId.d.ts +8 -0
- package/dist/collection/core/sortItems.d.ts +29 -0
- package/dist/collection/core/uiTypes.d.ts +106 -0
- package/dist/collection/index.cjs +793 -0
- package/dist/collection/index.cjs.map +1 -0
- package/dist/collection/index.d.ts +14 -0
- package/dist/collection/index.js +740 -0
- package/dist/collection/index.js.map +1 -0
- package/dist/collection/paths.cjs +44 -0
- package/dist/collection/paths.cjs.map +1 -0
- package/dist/collection/paths.js +41 -0
- package/dist/collection/paths.js.map +1 -0
- package/dist/collection/server/atomic.d.ts +1 -0
- package/dist/collection/server/delete.d.ts +38 -0
- package/dist/collection/server/derive.d.ts +8 -0
- package/dist/collection/server/discoveredCollection.d.ts +18 -0
- package/dist/collection/server/discovery.d.ts +227 -0
- package/dist/collection/server/host.d.ts +77 -0
- package/dist/collection/server/index.cjs +1721 -0
- package/dist/collection/server/index.cjs.map +1 -0
- package/dist/collection/server/index.d.ts +11 -0
- package/dist/collection/server/index.js +1671 -0
- package/dist/collection/server/index.js.map +1 -0
- package/dist/collection/server/io.d.ts +114 -0
- package/dist/collection/server/paths.d.ts +52 -0
- package/dist/collection/server/spawn.d.ts +55 -0
- package/dist/collection/server/templatePath.d.ts +25 -0
- package/dist/collection/server/util.d.ts +3 -0
- package/dist/collection/server/validate.d.ts +19 -0
- package/dist/collection/server/views.d.ts +20 -0
- package/dist/deriveAll-C15OpM3K.cjs +399 -0
- package/dist/deriveAll-C15OpM3K.cjs.map +1 -0
- package/dist/deriveAll-C6BYnpBL.js +364 -0
- package/dist/deriveAll-C6BYnpBL.js.map +1 -0
- package/dist/file-change/index.cjs +72 -0
- package/dist/file-change/index.cjs.map +1 -0
- package/dist/file-change/index.d.ts +43 -0
- package/dist/file-change/index.js +66 -0
- package/dist/file-change/index.js.map +1 -0
- package/dist/notifier/engine.d.ts +72 -0
- package/dist/notifier/index.cjs +484 -0
- package/dist/notifier/index.cjs.map +1 -0
- package/dist/notifier/index.d.ts +3 -0
- package/dist/notifier/index.js +464 -0
- package/dist/notifier/index.js.map +1 -0
- package/dist/notifier/store.d.ts +18 -0
- package/dist/notifier/types.d.ts +118 -0
- package/dist/notifier/validate.d.ts +17 -0
- package/dist/scheduler/adapter.d.ts +48 -0
- package/dist/scheduler/index.cjs +352 -0
- package/dist/scheduler/index.cjs.map +1 -0
- package/dist/scheduler/index.d.ts +2 -0
- package/dist/scheduler/index.js +343 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/task-manager.d.ts +51 -0
- package/dist/whisper/client.cjs +241 -0
- package/dist/whisper/client.cjs.map +1 -0
- package/dist/whisper/client.d.ts +35 -0
- package/dist/whisper/client.js +239 -0
- package/dist/whisper/client.js.map +1 -0
- package/dist/whisper/ffmpeg.d.ts +6 -0
- package/dist/whisper/index.cjs +433 -0
- package/dist/whisper/index.cjs.map +1 -0
- package/dist/whisper/index.d.ts +5 -0
- package/dist/whisper/index.js +425 -0
- package/dist/whisper/index.js.map +1 -0
- package/dist/whisper/internal.d.ts +11 -0
- package/dist/whisper/models.d.ts +49 -0
- package/dist/whisper/sidecar.d.ts +8 -0
- package/dist/whisper/whisper.d.ts +28 -0
- package/dist/workspace-setup/assets.d.ts +10 -0
- package/dist/workspace-setup/index.d.ts +3 -0
- package/dist/workspace-setup/index.js +556 -0
- package/dist/workspace-setup/index.js.map +1 -0
- package/dist/workspace-setup/slug.d.ts +6 -0
- package/dist/workspace-setup/slug.js +13 -0
- package/dist/workspace-setup/slug.js.map +1 -0
- package/dist/workspace-setup/sync.d.ts +94 -0
- package/package.json +95 -0
|
@@ -0,0 +1,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.
|