@mulmoclaude/core 0.2.10 → 0.2.12
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-invoice.md +77 -51
- package/assets/helps/collection-skills.md +16 -8
- package/dist/collection/core/schema.d.ts +34 -2
- package/dist/collection/paths.cjs +12 -0
- package/dist/collection/paths.cjs.map +1 -1
- package/dist/collection/paths.js +12 -1
- package/dist/collection/paths.js.map +1 -1
- package/dist/collection/server/discovery.d.ts +2 -0
- package/dist/collection/server/index.cjs +3 -1
- package/dist/collection/server/index.js +3 -3
- package/dist/collection/server/io.d.ts +28 -1
- package/dist/collection/server/templatePath.d.ts +9 -0
- package/dist/collection-watchers/index.cjs +1 -1
- package/dist/collection-watchers/index.js +1 -1
- package/dist/deriveAll-VRWrs3SF.cjs.map +1 -1
- package/dist/deriveAll-vzIhhKBK.js.map +1 -1
- package/dist/feeds/server/index.cjs +1 -1
- package/dist/feeds/server/index.js +1 -1
- package/dist/{server-q4WPXtbU.js → server-C7inPZNG.js} +73 -9
- package/dist/server-C7inPZNG.js.map +1 -0
- package/dist/{server-Db-x7OXz.cjs → server-Ceb6OQ8v.cjs} +77 -7
- package/dist/server-Ceb6OQ8v.cjs.map +1 -0
- package/package.json +1 -1
- package/dist/server-Db-x7OXz.cjs.map +0 -1
- package/dist/server-q4WPXtbU.js.map +0 -1
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
Read this when the user asks to **set up invoicing / billing** (sample query:
|
|
4
4
|
*"Set up invoicing for my business"*). It scaffolds **two** collection skills:
|
|
5
5
|
|
|
6
|
-
- **`profile`** — the user's **own** business
|
|
7
|
-
company name, tax ID, payment details).
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
- **`profile`** — the user's **own** business identit(ies) (the "bill-from" block:
|
|
7
|
+
company name, tax ID, payment details). One or more records, one per issuer; the
|
|
8
|
+
primary one is id `me`.
|
|
9
|
+
- **`invoice`** — an invoice ledger; each invoice references a client, picks an
|
|
10
|
+
issuer profile (`issuerId` → `profile`) which it embeds as the bill-from block,
|
|
11
|
+
and carries a table of line items with host-computed subtotal / tax / total.
|
|
11
12
|
|
|
12
13
|
This is **Bundle B** of the billing suite. It **builds on Bundle A**
|
|
13
14
|
(`clients` + `worklog`, see `config/helps/billing-clients-worklog.md`):
|
|
@@ -37,7 +38,10 @@ use the `mc-` prefix.**
|
|
|
37
38
|
> other collections in the workspace. The whole point is a reproducible billing
|
|
38
39
|
> suite. (For a *custom* collection, use `config/helps/collection-skills.md`
|
|
39
40
|
> instead.) Existing records under `data/invoice/items` / `data/profile/items`
|
|
40
|
-
>
|
|
41
|
+
> render as-is, with one exception: because `invoice.issuerId` is new and
|
|
42
|
+
> required, **backfill `issuerId: "me"` on any pre-existing invoice records** (a
|
|
43
|
+
> `manageCollection` `putItems` with `mode: "merge"`) so their issuer block
|
|
44
|
+
> resolves — otherwise they show a "missing issuer" prompt until set.
|
|
41
45
|
|
|
42
46
|
## Slug contract (do not change these)
|
|
43
47
|
|
|
@@ -45,7 +49,7 @@ use the `mc-` prefix.**
|
|
|
45
49
|
|
|
46
50
|
| Collection | slug | `dataPath` | referenced by |
|
|
47
51
|
|---|---|---|---|
|
|
48
|
-
| Profile | `profile` | `data/profile/items` | `invoice.issuer` (embed `
|
|
52
|
+
| Profile | `profile` | `data/profile/items` | `invoice.issuerId` (ref) + `invoice.issuer` (embed via `idField`) |
|
|
49
53
|
| Invoice | `invoice` | `data/invoice/items` | — |
|
|
50
54
|
| Clients | `clients` | `data/clients/items` | `invoice.clientId` (ref, from Bundle A) |
|
|
51
55
|
|
|
@@ -56,13 +60,15 @@ use the `mc-` prefix.**
|
|
|
56
60
|
|
|
57
61
|
## Order: `profile` before `invoice`
|
|
58
62
|
|
|
59
|
-
`invoice.
|
|
63
|
+
`invoice.issuerId` references a `profile` record (and `issuer` embeds it), so
|
|
64
|
+
create `profile` first.
|
|
60
65
|
|
|
61
66
|
---
|
|
62
67
|
|
|
63
68
|
## 1. `profile`
|
|
64
69
|
|
|
65
|
-
`data/skills/profile/schema.json` (
|
|
70
|
+
`data/skills/profile/schema.json` (one record per issuer; the primary one is id
|
|
71
|
+
`me` — **not** a singleton, so an invoice can pick among several issuers):
|
|
66
72
|
|
|
67
73
|
```json
|
|
68
74
|
{
|
|
@@ -70,7 +76,6 @@ use the `mc-` prefix.**
|
|
|
70
76
|
"icon": "badge",
|
|
71
77
|
"dataPath": "data/profile/items",
|
|
72
78
|
"primaryKey": "id",
|
|
73
|
-
"singleton": "me",
|
|
74
79
|
"fields": {
|
|
75
80
|
"id": { "type": "string", "label": "ID", "primary": true, "required": true },
|
|
76
81
|
"companyName": { "type": "string", "label": "Company / legal name", "required": true },
|
|
@@ -90,25 +95,27 @@ use the `mc-` prefix.**
|
|
|
90
95
|
```markdown
|
|
91
96
|
---
|
|
92
97
|
name: profile
|
|
93
|
-
description: The user's own business profile — the issuer ("bill-from") identity
|
|
94
|
-
used on invoices.
|
|
95
|
-
|
|
96
|
-
`/collections/profile`.
|
|
97
|
-
Read / Write / Edit on the JSON
|
|
98
|
+
description: The user's own business profile(s) — the issuer ("bill-from") identity
|
|
99
|
+
used on invoices. One or more records, each an issuer; the primary one is `me`.
|
|
100
|
+
Records live at `data/profile/items/<id>.json`; the user views and edits them at
|
|
101
|
+
`/collections/profile`. Each invoice picks one via its `issuerId` ref. Record I/O
|
|
102
|
+
via the `manageCollection` tool (raw Read / Write / Edit on the JSON is the escape hatch).
|
|
98
103
|
---
|
|
99
104
|
|
|
100
105
|
# Business Profile (schema-driven collection)
|
|
101
106
|
|
|
102
|
-
Holds the user's **own** business
|
|
103
|
-
Counterpart to `clients`, which holds the "bill-to" parties.
|
|
107
|
+
Holds the user's **own** business identit(ies) — the "bill-from" side of an
|
|
108
|
+
invoice. Counterpart to `clients`, which holds the "bill-to" parties.
|
|
104
109
|
|
|
105
|
-
##
|
|
106
|
-
This collection
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
## Multiple issuers — `me` is the primary one
|
|
111
|
+
This collection can hold **several** records, one per issuer identity (a personal
|
|
112
|
+
name, a company, a second LLC). The primary profile keeps the id `me`, stored at
|
|
113
|
+
`data/profile/items/me.json`; add more with their own kebab-case ids (e.g.
|
|
114
|
+
`pervasive`, `personal`). An invoice points at one via its `issuerId`, so a
|
|
115
|
+
profile must exist before an invoice can reference it.
|
|
109
116
|
|
|
110
117
|
## Record shape
|
|
111
|
-
- `id` — string, **primary key
|
|
118
|
+
- `id` — string, **primary key** (kebab-case slug; the primary profile is `me`)
|
|
112
119
|
- `companyName` — string, **required** (the legal/company name shown on invoices)
|
|
113
120
|
- `taxRegistrationId` — string (VAT / EIN / JP T-number — region-dependent)
|
|
114
121
|
- `email` — email
|
|
@@ -123,18 +130,20 @@ never invent another id — read, create, and update `me.json` only.
|
|
|
123
130
|
Leave optional fields the user hasn't given you out of the JSON entirely.
|
|
124
131
|
|
|
125
132
|
## What to do
|
|
126
|
-
**Set up / update**: Read `data/profile/items
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
**Set up / update**: Read the relevant `data/profile/items/<id>.json` (the primary
|
|
134
|
+
one is `me.json`, may not exist yet), merge changes, Write back. Preserve fields
|
|
135
|
+
you weren't asked to change. When creating the primary profile use `id: "me"`; for
|
|
136
|
+
an additional issuer, list `data/profile/items/` first and pick a fresh kebab-case
|
|
137
|
+
id.
|
|
130
138
|
|
|
131
|
-
**Look up**: Read
|
|
132
|
-
profile isn't set up yet and offer to
|
|
133
|
-
fields are needed at once).
|
|
139
|
+
**Look up**: Read the relevant profile JSON and answer from it. If the primary
|
|
140
|
+
`me.json` is missing, tell the user their profile isn't set up yet and offer to
|
|
141
|
+
collect it (`presentForm` only if several fields are needed at once).
|
|
134
142
|
|
|
135
|
-
**Never delete**
|
|
143
|
+
**Never delete** a profile unless the user explicitly asks to — an invoice's
|
|
144
|
+
`issuerId` ref breaks if you remove a profile it points at.
|
|
136
145
|
|
|
137
|
-
## Linking to
|
|
146
|
+
## Linking to a profile in chat
|
|
138
147
|
- Do: `[your business profile](/collections/profile?selected=me)`
|
|
139
148
|
- Don't: link the raw JSON path.
|
|
140
149
|
|
|
@@ -147,8 +156,9 @@ only when you genuinely need several fields they haven't provided.
|
|
|
147
156
|
|
|
148
157
|
## 2. `invoice`
|
|
149
158
|
|
|
150
|
-
`data/skills/invoice/schema.json` (`
|
|
151
|
-
`
|
|
159
|
+
`data/skills/invoice/schema.json` (`issuerId` is a `ref` **to `profile`** and
|
|
160
|
+
`issuer` embeds whichever profile it names via `idField`; `clientId` is a `ref`
|
|
161
|
+
**to `clients`**; subtotal / tax / total are `derived`):
|
|
152
162
|
|
|
153
163
|
```json
|
|
154
164
|
{
|
|
@@ -158,7 +168,8 @@ only when you genuinely need several fields they haven't provided.
|
|
|
158
168
|
"primaryKey": "id",
|
|
159
169
|
"fields": {
|
|
160
170
|
"id": { "type": "string", "label": "ID", "primary": true, "required": true },
|
|
161
|
-
"
|
|
171
|
+
"issuerId": { "type": "ref", "to": "profile", "label": "From (issuer)", "required": true },
|
|
172
|
+
"issuer": { "type": "embed", "to": "profile", "idField": "issuerId", "label": "From (issuer)" },
|
|
162
173
|
"clientId": { "type": "ref", "to": "clients", "label": "Client", "required": true },
|
|
163
174
|
"issueDate": { "type": "date", "label": "Issued", "required": true },
|
|
164
175
|
"dueDate": { "type": "date", "label": "Due" },
|
|
@@ -196,7 +207,8 @@ name: invoice
|
|
|
196
207
|
description: A simple invoice ledger — create, list, edit, and remove invoices.
|
|
197
208
|
Records live at `data/invoice/items/<id>.json`; the user views them at
|
|
198
209
|
`/collections/invoice`. Each invoice references a client (`clientId` → the
|
|
199
|
-
`clients` collection) and
|
|
210
|
+
`clients` collection) and an issuer profile (`issuerId` → the `profile`
|
|
211
|
+
collection), which it embeds as the bill-from block.
|
|
200
212
|
Subtotal / tax / total are host-computed — you don't write them.
|
|
201
213
|
---
|
|
202
214
|
|
|
@@ -207,9 +219,13 @@ description: A simple invoice ledger — create, list, edit, and remove invoices
|
|
|
207
219
|
counter): `INV-2026-0001`. List `data/invoice/items/` first to find the highest
|
|
208
220
|
number for the year, then increment; pick the next free number rather than
|
|
209
221
|
overwriting.
|
|
210
|
-
- `
|
|
211
|
-
|
|
212
|
-
|
|
222
|
+
- `issuerId` — ref → `profile`, **required**. The bill-from identity. Write the
|
|
223
|
+
raw profile slug (the primary profile is `me`); the host renders it as a dropdown
|
|
224
|
+
picker. List `data/profile/items/` to find the right slug; no profile set up yet
|
|
225
|
+
→ point the user at `/collections/profile`.
|
|
226
|
+
- `issuer` — **display-only** embed of the profile named by `issuerId` (the
|
|
227
|
+
bill-from block). You do **not** write this — it carries no stored value; it
|
|
228
|
+
follows whatever `issuerId` points at.
|
|
213
229
|
- `clientId` — ref → `clients`, **required**
|
|
214
230
|
- `issueDate` — ISO date `YYYY-MM-DD`, **required** (default today)
|
|
215
231
|
- `dueDate` — ISO date (optional)
|
|
@@ -232,8 +248,10 @@ or supply a literal slug. Never invent a clientId — it renders as a broken lin
|
|
|
232
248
|
|
|
233
249
|
## What to do
|
|
234
250
|
**Create**: derive an `id`, build the record, Write `data/invoice/items/<id>.json`.
|
|
235
|
-
Defaults: `status: "draft"`, `issueDate: <today
|
|
236
|
-
|
|
251
|
+
Defaults: `status: "draft"`, `issueDate: <today>`, `issuerId: "me"` (the primary
|
|
252
|
+
profile) unless the user names a different issuer — then resolve it to a real
|
|
253
|
+
`profile` slug. Set `currency` from context — don't silently default to USD. Don't
|
|
254
|
+
write `subtotal` / `tax` / `total`.
|
|
237
255
|
|
|
238
256
|
**Create from worklog hours** (common): when the user says "invoice Acme for the
|
|
239
257
|
work I did this month":
|
|
@@ -304,11 +322,12 @@ and present it in the canvas with the `presentDocument` tool. Steps:
|
|
|
304
322
|
|
|
305
323
|
1. **Resolve the recipient (Bill To).** Read `data/clients/items/<clientId>.json`
|
|
306
324
|
for the client's `name`, `address`, `email`.
|
|
307
|
-
2. **Resolve the issuer (From).** Read `data/profile/items
|
|
308
|
-
|
|
309
|
-
`
|
|
310
|
-
|
|
311
|
-
|
|
325
|
+
2. **Resolve the issuer (From).** Read `data/profile/items/<issuerId>.json` — the
|
|
326
|
+
invoice's own `issuerId` (fall back to `me` for a legacy invoice without one) —
|
|
327
|
+
for `companyName`, `taxRegistrationId`, `address`, `email`, `phone`,
|
|
328
|
+
`paymentDetails`. **If that file does not exist**, stop and tell the user that
|
|
329
|
+
issuer's business profile isn't set up — point them at `/collections/profile` —
|
|
330
|
+
and do not write a half-blank invoice.
|
|
312
331
|
3. **Compute totals** from line items: `subtotal` = Σ(`quantity` × `rate`), `tax`
|
|
313
332
|
= `subtotal` × `taxRate` (missing `taxRate` = 0), `total` = `subtotal` + `tax`.
|
|
314
333
|
Format money with the invoice's `currency`.
|
|
@@ -332,8 +351,11 @@ The invoice record is in the `<record_data_json>` block above. You are the
|
|
|
332
351
|
`accounting` role and own `manageAccounting`. Post the double-entry sale journal.
|
|
333
352
|
|
|
334
353
|
### 1. Resolve the book
|
|
335
|
-
1. Read `data/profile/items
|
|
336
|
-
|
|
354
|
+
1. Read `data/profile/items/<issuerId>.json` — the invoice's own `issuerId` (fall
|
|
355
|
+
back to `me` for a legacy invoice without one). If it has a non-empty
|
|
356
|
+
`defaultBookId`, use it as `bookId` and skip the rest of this step. If that
|
|
357
|
+
profile file doesn't exist, tell the user that issuer's profile isn't set up
|
|
358
|
+
(point them at `/collections/profile`) and stop.
|
|
337
359
|
2. Otherwise call `manageAccounting` `getBooks`. One book → use it. Several →
|
|
338
360
|
keep books whose currency/country fit the invoice; if one remains use it; else
|
|
339
361
|
prefer the book whose name matches the issuer's `companyName`; only if still
|
|
@@ -378,11 +400,14 @@ The invoice record is in the `<record_data_json>` block above. You are the
|
|
|
378
400
|
`accounting` role and own `manageAccounting`. The invoice is paid; post the
|
|
379
401
|
cash-receipt journal that clears the receivable. Payment is always direct deposit
|
|
380
402
|
to the issuer's bank account (the issuer's `paymentDetails` on
|
|
381
|
-
`data/profile/items
|
|
403
|
+
`data/profile/items/<issuerId>.json`), so the debit is the book's Cash / Checking
|
|
404
|
+
account.
|
|
382
405
|
|
|
383
406
|
### 1. Resolve the book
|
|
384
|
-
Read `data/profile/items
|
|
385
|
-
|
|
407
|
+
Read `data/profile/items/<issuerId>.json` — the invoice's own `issuerId` (fall
|
|
408
|
+
back to `me` for a legacy invoice) — for a non-empty `defaultBookId`; else
|
|
409
|
+
`getBooks` and pick as in the sale flow. No book → tell the user to set one up and
|
|
410
|
+
stop.
|
|
386
411
|
|
|
387
412
|
### 2. Resolve real account codes
|
|
388
413
|
`getAccounts` — never invent: **Cash / Checking / Bank** (asset, the debit; if
|
|
@@ -418,7 +443,8 @@ The invoice record is in the `<record_data_json>` block above. You are the
|
|
|
418
443
|
journals posted for it.
|
|
419
444
|
|
|
420
445
|
### 1. Resolve the book
|
|
421
|
-
Read `data/profile/items
|
|
446
|
+
Read `data/profile/items/<issuerId>.json` — the invoice's own `issuerId` (fall
|
|
447
|
+
back to `me` for a legacy invoice) — for a non-empty `defaultBookId`; else resolve
|
|
422
448
|
via `getBooks` (as in the sale flow). No book → nothing to void; say so and stop.
|
|
423
449
|
|
|
424
450
|
### 2. Resolve the Accounts Receivable code
|
|
@@ -168,10 +168,17 @@ Every field spec needs a `type` and a `label`. Extra keys by type:
|
|
|
168
168
|
A `derived` field on the same record can also **dereference** a `ref` to read
|
|
169
169
|
a numeric column off the record it points at — see the cross-collection
|
|
170
170
|
formula syntax below.
|
|
171
|
-
- **`embed`** — `to: "<target-slug>"`, `id
|
|
171
|
+
- **`embed`** — `to: "<target-slug>"`, plus **exactly one** of `id` (a _fixed_
|
|
172
|
+
record id, same for every row) or `idField` (a _per-record_ target). Pulls a
|
|
172
173
|
record from another collection into the read-only detail view (display-only,
|
|
173
|
-
**nothing is stored** on this record).
|
|
174
|
-
|
|
174
|
+
**nothing is stored** on this record). Fixed — every row embeds the same
|
|
175
|
+
singleton record (e.g. a one-record `settings` collection):
|
|
176
|
+
`{ "type": "embed", "to": "settings", "id": "app" }`.
|
|
177
|
+
Per-record — `idField` names a sibling field (typically a `ref`) holding the
|
|
178
|
+
target id, so each row embeds a different record; the host renders that
|
|
179
|
+
sibling as a dropdown picker in the editor and hides its own cell (the embed
|
|
180
|
+
owns it). E.g. a multi-issuer invoice: a `ref` field `issuerId → profile`
|
|
181
|
+
plus `{ "type": "embed", "to": "profile", "idField": "issuerId" }`.
|
|
175
182
|
- **`table`** — `of: { <col>: <sub-field-spec>, ... }`. An array of rows. Each
|
|
176
183
|
sub-field is a flat spec; sub-fields **cannot** be `table` or `derived`
|
|
177
184
|
(no nested tables, no computed columns).
|
|
@@ -851,8 +858,9 @@ recipe when the user wants any of them, and copy the schema verbatim:
|
|
|
851
858
|
- **`worklog`** — adds a `ref` (`clientId → clients`), a `date`, a `number`, a
|
|
852
859
|
`boolean`. A companion data source.
|
|
853
860
|
- **`config/helps/billing-invoice.md`** (Bundle B):
|
|
854
|
-
- **`profile`** —
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
`
|
|
858
|
-
|
|
861
|
+
- **`profile`** — one record per issuer identity (primary id `me`); each invoice
|
|
862
|
+
picks one.
|
|
863
|
+
- **`invoice`** — the full toolkit in one schema: a `ref` issuer (`issuerId →
|
|
864
|
+
profile`) embedded via an `idField` `embed`, a `ref` client (`clients`), a
|
|
865
|
+
`table` of line items, three `derived` money fields, an `enum` status, and four
|
|
866
|
+
`actions` (PDF always-on; sale / payment / void gated by `status` via `when`).
|
|
@@ -151,6 +151,27 @@ export interface CollectionCustomView {
|
|
|
151
151
|
/** Skill-relative path to the HTML file under `views/` (e.g.
|
|
152
152
|
* `views/year.html`). Path-safe, must end in `.html`. */
|
|
153
153
|
file: string;
|
|
154
|
+
/** Optional skill-relative path to a JSON translation dictionary co-located
|
|
155
|
+
* with the view (e.g. `views/year.i18n.json`). Shape mirrors **vue-i18n
|
|
156
|
+
* locale messages** so an author can lift their app's locale JSON
|
|
157
|
+
* verbatim:
|
|
158
|
+
*
|
|
159
|
+
* ```json
|
|
160
|
+
* { "en": { "next": "Next", "hello": "Hello, {name}" },
|
|
161
|
+
* "ja": { "next": "次へ", "hello": "{name} さん、こんにちは" } }
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* The host picks the block matching the active app locale (fallback
|
|
165
|
+
* `"en"`, else `{}`) and injects ONLY that flat string map into
|
|
166
|
+
* `window.__MC_VIEW.dict`. The iframe-side helper
|
|
167
|
+
* `__MC_VIEW.t(key, named?)` mirrors vue-i18n's `t('msg', { name: 'x' })`
|
|
168
|
+
* signature — named-interpolation only (no pluralization / linked
|
|
169
|
+
* messages in v1; shipping vue-i18n's full runtime into every sandboxed
|
|
170
|
+
* iframe would dominate page weight). The view never sees other locales'
|
|
171
|
+
* strings. Constrained to `views/*.i18n.json` so authors keep the
|
|
172
|
+
* translation file next to the HTML it translates. Absent ⇒ host-side
|
|
173
|
+
* no-op (an i18n-less view keeps working; `t(key)` echoes the key). */
|
|
174
|
+
i18n?: string;
|
|
154
175
|
/** What the view may do with the data endpoint. Defaults to `["read"]`
|
|
155
176
|
* (least privilege); declare `["read","write"]` only for views that
|
|
156
177
|
* edit records. The mint endpoint clamps any requested caps to this. */
|
|
@@ -199,9 +220,20 @@ export interface CollectionFieldSpec {
|
|
|
199
220
|
* record to pull from the `to` collection (e.g. `me` for the
|
|
200
221
|
* singleton mc-profile). Nothing is stored on this record — the
|
|
201
222
|
* embed is a display-only directive resolved at render time, so
|
|
202
|
-
* it never appears in the list table or the edit form.
|
|
203
|
-
*
|
|
223
|
+
* it never appears in the list table or the edit form. Supply
|
|
224
|
+
* either this (a fixed target, same for every record) or
|
|
225
|
+
* `idField` (a per-record target) — exactly one. Ignored on every
|
|
226
|
+
* other type. */
|
|
204
227
|
id?: string;
|
|
228
|
+
/** When `type === "embed"`: the name of a sibling top-level field
|
|
229
|
+
* whose value names the target record's primary key — letting the
|
|
230
|
+
* embed point at a *different* record per row (e.g. an invoice's
|
|
231
|
+
* `issuerId` ref selects which `profile` to embed as the
|
|
232
|
+
* bill-from block). The renderer reads `record[idField]` at render
|
|
233
|
+
* time; an absent/empty value resolves to "no record" (the same
|
|
234
|
+
* fail-soft as a missing fixed `id`). Mutually exclusive with `id`
|
|
235
|
+
* — an embed must declare exactly one. Ignored on every other type. */
|
|
236
|
+
idField?: string;
|
|
205
237
|
/** When `type === "money"` (or `type === "derived"` with
|
|
206
238
|
* `display: "money"`): a literal ISO 4217 currency code passed to
|
|
207
239
|
* `Intl.NumberFormat` for display — fixed for every record. The
|
|
@@ -36,8 +36,20 @@ var VIEWS_PREFIX = "views/";
|
|
|
36
36
|
function isSafeCustomViewPath(value) {
|
|
37
37
|
return value.startsWith(VIEWS_PREFIX) && /\.html$/.test(value) && isSafeTemplatePath(value);
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* A custom-view `i18n` value: the JSON dictionary file authors ship next to
|
|
41
|
+
* their `views/<id>.html`. Constrained to `views/<name>.i18n.json` so the
|
|
42
|
+
* field can only point at a translation file co-located with the view it
|
|
43
|
+
* translates — the same `views/` containment the HTML reader uses. The
|
|
44
|
+
* `.i18n.json` suffix (vs plain `.json`) makes the file's purpose grep-able
|
|
45
|
+
* for both authors and tooling.
|
|
46
|
+
*/
|
|
47
|
+
function isSafeCustomViewI18nPath(value) {
|
|
48
|
+
return value.startsWith(VIEWS_PREFIX) && /\.i18n\.json$/.test(value) && isSafeTemplatePath(value);
|
|
49
|
+
}
|
|
39
50
|
//#endregion
|
|
40
51
|
exports.isSafeActionTemplatePath = isSafeActionTemplatePath;
|
|
52
|
+
exports.isSafeCustomViewI18nPath = isSafeCustomViewI18nPath;
|
|
41
53
|
exports.isSafeCustomViewPath = isSafeCustomViewPath;
|
|
42
54
|
exports.isSafeTemplatePath = isSafeTemplatePath;
|
|
43
55
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.cjs","names":[],"sources":["../../src/collection/server/templatePath.ts"],"sourcesContent":["// Shared template-path safety, used by BOTH the collection schema\n// validator (`discovery.ts` `ActionSpecSchema`) and the skill-bridge\n// hook (`hooks/handlers/skillBridge.ts`). Centralised so the set of\n// action `template` paths a schema may declare is *exactly* the set\n// the bridge mirrors into `.claude/skills/<slug>/` — if the two\n// diverge, a schema can validate yet reference a template the bridge\n// silently drops (runtime \"template could not be read\").\n//\n// Keep this module dependency-free: the bridge is bundled into a lean\n// esbuild dispatcher, so it must not drag in workspace/path machinery.\n\n/** `templates/` — the subdir an action template must live under. */\nconst TEMPLATES_PREFIX = \"templates/\";\n\n/**\n * A safe skill-relative path: non-empty, no backslash, not absolute,\n * and every `/`-separated segment is a plain `[A-Za-z0-9._-]+` token\n * that isn't `.` / `..` (no traversal). Multi-segment (nested) paths\n * are allowed. The reader's realpath containment is the hard\n * guarantee; this fails a bad path fast.\n */\nexport function isSafeTemplatePath(value: string): boolean {\n if (value.length === 0 || value.includes(\"\\\\\") || value.startsWith(\"/\")) return false;\n return value.split(\"/\").every((seg) => seg.length > 0 && seg !== \".\" && seg !== \"..\" && /^[A-Za-z0-9._-]+$/.test(seg));\n}\n\n/**\n * An action `template` value: a safe path that lives under the skill's\n * `templates/` subdir. This is the canonical contract — the schema\n * validator rejects anything else up front, and the bridge mirrors\n * exactly these. Nested paths (`templates/mail/welcome.md`) and any\n * extension are allowed as long as the path is otherwise safe.\n */\nexport function isSafeActionTemplatePath(value: string): boolean {\n return value.startsWith(TEMPLATES_PREFIX) && isSafeTemplatePath(value);\n}\n\n/** `views/` — the subdir a custom-view HTML file must live under. */\nconst VIEWS_PREFIX = \"views/\";\n\n/**\n * A custom-view `file` value: a safe path under the skill's `views/`\n * subdir that ends in `.html`. Custom views are LLM-authored HTML the\n * host renders in a sandboxed iframe; constraining them to `views/*.html`\n * keeps the data folder and the schema/template files off-limits to the\n * view-file reader. Same per-segment safety as templates; the reader's\n * realpath containment is the hard guarantee.\n */\nexport function isSafeCustomViewPath(value: string): boolean {\n return value.startsWith(VIEWS_PREFIX) && /\\.html$/.test(value) && isSafeTemplatePath(value);\n}\n"],"mappings":";;;AAYA,IAAM,mBAAmB;;;;;;;;AASzB,SAAgB,mBAAmB,OAAwB;CACzD,IAAI,MAAM,WAAW,KAAK,MAAM,SAAS,IAAI,KAAK,MAAM,WAAW,GAAG,GAAG,OAAO;CAChF,OAAO,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,QAAQ,IAAI,SAAS,KAAK,QAAQ,OAAO,QAAQ,QAAQ,oBAAoB,KAAK,GAAG,CAAC;AACvH;;;;;;;;AASA,SAAgB,yBAAyB,OAAwB;CAC/D,OAAO,MAAM,WAAW,gBAAgB,KAAK,mBAAmB,KAAK;AACvE;;AAGA,IAAM,eAAe;;;;;;;;;AAUrB,SAAgB,qBAAqB,OAAwB;CAC3D,OAAO,MAAM,WAAW,YAAY,KAAK,UAAU,KAAK,KAAK,KAAK,mBAAmB,KAAK;AAC5F"}
|
|
1
|
+
{"version":3,"file":"paths.cjs","names":[],"sources":["../../src/collection/server/templatePath.ts"],"sourcesContent":["// Shared template-path safety, used by BOTH the collection schema\n// validator (`discovery.ts` `ActionSpecSchema`) and the skill-bridge\n// hook (`hooks/handlers/skillBridge.ts`). Centralised so the set of\n// action `template` paths a schema may declare is *exactly* the set\n// the bridge mirrors into `.claude/skills/<slug>/` — if the two\n// diverge, a schema can validate yet reference a template the bridge\n// silently drops (runtime \"template could not be read\").\n//\n// Keep this module dependency-free: the bridge is bundled into a lean\n// esbuild dispatcher, so it must not drag in workspace/path machinery.\n\n/** `templates/` — the subdir an action template must live under. */\nconst TEMPLATES_PREFIX = \"templates/\";\n\n/**\n * A safe skill-relative path: non-empty, no backslash, not absolute,\n * and every `/`-separated segment is a plain `[A-Za-z0-9._-]+` token\n * that isn't `.` / `..` (no traversal). Multi-segment (nested) paths\n * are allowed. The reader's realpath containment is the hard\n * guarantee; this fails a bad path fast.\n */\nexport function isSafeTemplatePath(value: string): boolean {\n if (value.length === 0 || value.includes(\"\\\\\") || value.startsWith(\"/\")) return false;\n return value.split(\"/\").every((seg) => seg.length > 0 && seg !== \".\" && seg !== \"..\" && /^[A-Za-z0-9._-]+$/.test(seg));\n}\n\n/**\n * An action `template` value: a safe path that lives under the skill's\n * `templates/` subdir. This is the canonical contract — the schema\n * validator rejects anything else up front, and the bridge mirrors\n * exactly these. Nested paths (`templates/mail/welcome.md`) and any\n * extension are allowed as long as the path is otherwise safe.\n */\nexport function isSafeActionTemplatePath(value: string): boolean {\n return value.startsWith(TEMPLATES_PREFIX) && isSafeTemplatePath(value);\n}\n\n/** `views/` — the subdir a custom-view HTML file must live under. */\nconst VIEWS_PREFIX = \"views/\";\n\n/**\n * A custom-view `file` value: a safe path under the skill's `views/`\n * subdir that ends in `.html`. Custom views are LLM-authored HTML the\n * host renders in a sandboxed iframe; constraining them to `views/*.html`\n * keeps the data folder and the schema/template files off-limits to the\n * view-file reader. Same per-segment safety as templates; the reader's\n * realpath containment is the hard guarantee.\n */\nexport function isSafeCustomViewPath(value: string): boolean {\n return value.startsWith(VIEWS_PREFIX) && /\\.html$/.test(value) && isSafeTemplatePath(value);\n}\n\n/**\n * A custom-view `i18n` value: the JSON dictionary file authors ship next to\n * their `views/<id>.html`. Constrained to `views/<name>.i18n.json` so the\n * field can only point at a translation file co-located with the view it\n * translates — the same `views/` containment the HTML reader uses. The\n * `.i18n.json` suffix (vs plain `.json`) makes the file's purpose grep-able\n * for both authors and tooling.\n */\nexport function isSafeCustomViewI18nPath(value: string): boolean {\n return value.startsWith(VIEWS_PREFIX) && /\\.i18n\\.json$/.test(value) && isSafeTemplatePath(value);\n}\n"],"mappings":";;;AAYA,IAAM,mBAAmB;;;;;;;;AASzB,SAAgB,mBAAmB,OAAwB;CACzD,IAAI,MAAM,WAAW,KAAK,MAAM,SAAS,IAAI,KAAK,MAAM,WAAW,GAAG,GAAG,OAAO;CAChF,OAAO,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,QAAQ,IAAI,SAAS,KAAK,QAAQ,OAAO,QAAQ,QAAQ,oBAAoB,KAAK,GAAG,CAAC;AACvH;;;;;;;;AASA,SAAgB,yBAAyB,OAAwB;CAC/D,OAAO,MAAM,WAAW,gBAAgB,KAAK,mBAAmB,KAAK;AACvE;;AAGA,IAAM,eAAe;;;;;;;;;AAUrB,SAAgB,qBAAqB,OAAwB;CAC3D,OAAO,MAAM,WAAW,YAAY,KAAK,UAAU,KAAK,KAAK,KAAK,mBAAmB,KAAK;AAC5F;;;;;;;;;AAUA,SAAgB,yBAAyB,OAAwB;CAC/D,OAAO,MAAM,WAAW,YAAY,KAAK,gBAAgB,KAAK,KAAK,KAAK,mBAAmB,KAAK;AAClG"}
|
package/dist/collection/paths.js
CHANGED
|
@@ -35,7 +35,18 @@ var VIEWS_PREFIX = "views/";
|
|
|
35
35
|
function isSafeCustomViewPath(value) {
|
|
36
36
|
return value.startsWith(VIEWS_PREFIX) && /\.html$/.test(value) && isSafeTemplatePath(value);
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* A custom-view `i18n` value: the JSON dictionary file authors ship next to
|
|
40
|
+
* their `views/<id>.html`. Constrained to `views/<name>.i18n.json` so the
|
|
41
|
+
* field can only point at a translation file co-located with the view it
|
|
42
|
+
* translates — the same `views/` containment the HTML reader uses. The
|
|
43
|
+
* `.i18n.json` suffix (vs plain `.json`) makes the file's purpose grep-able
|
|
44
|
+
* for both authors and tooling.
|
|
45
|
+
*/
|
|
46
|
+
function isSafeCustomViewI18nPath(value) {
|
|
47
|
+
return value.startsWith(VIEWS_PREFIX) && /\.i18n\.json$/.test(value) && isSafeTemplatePath(value);
|
|
48
|
+
}
|
|
38
49
|
//#endregion
|
|
39
|
-
export { isSafeActionTemplatePath, isSafeCustomViewPath, isSafeTemplatePath };
|
|
50
|
+
export { isSafeActionTemplatePath, isSafeCustomViewI18nPath, isSafeCustomViewPath, isSafeTemplatePath };
|
|
40
51
|
|
|
41
52
|
//# sourceMappingURL=paths.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","names":[],"sources":["../../src/collection/server/templatePath.ts"],"sourcesContent":["// Shared template-path safety, used by BOTH the collection schema\n// validator (`discovery.ts` `ActionSpecSchema`) and the skill-bridge\n// hook (`hooks/handlers/skillBridge.ts`). Centralised so the set of\n// action `template` paths a schema may declare is *exactly* the set\n// the bridge mirrors into `.claude/skills/<slug>/` — if the two\n// diverge, a schema can validate yet reference a template the bridge\n// silently drops (runtime \"template could not be read\").\n//\n// Keep this module dependency-free: the bridge is bundled into a lean\n// esbuild dispatcher, so it must not drag in workspace/path machinery.\n\n/** `templates/` — the subdir an action template must live under. */\nconst TEMPLATES_PREFIX = \"templates/\";\n\n/**\n * A safe skill-relative path: non-empty, no backslash, not absolute,\n * and every `/`-separated segment is a plain `[A-Za-z0-9._-]+` token\n * that isn't `.` / `..` (no traversal). Multi-segment (nested) paths\n * are allowed. The reader's realpath containment is the hard\n * guarantee; this fails a bad path fast.\n */\nexport function isSafeTemplatePath(value: string): boolean {\n if (value.length === 0 || value.includes(\"\\\\\") || value.startsWith(\"/\")) return false;\n return value.split(\"/\").every((seg) => seg.length > 0 && seg !== \".\" && seg !== \"..\" && /^[A-Za-z0-9._-]+$/.test(seg));\n}\n\n/**\n * An action `template` value: a safe path that lives under the skill's\n * `templates/` subdir. This is the canonical contract — the schema\n * validator rejects anything else up front, and the bridge mirrors\n * exactly these. Nested paths (`templates/mail/welcome.md`) and any\n * extension are allowed as long as the path is otherwise safe.\n */\nexport function isSafeActionTemplatePath(value: string): boolean {\n return value.startsWith(TEMPLATES_PREFIX) && isSafeTemplatePath(value);\n}\n\n/** `views/` — the subdir a custom-view HTML file must live under. */\nconst VIEWS_PREFIX = \"views/\";\n\n/**\n * A custom-view `file` value: a safe path under the skill's `views/`\n * subdir that ends in `.html`. Custom views are LLM-authored HTML the\n * host renders in a sandboxed iframe; constraining them to `views/*.html`\n * keeps the data folder and the schema/template files off-limits to the\n * view-file reader. Same per-segment safety as templates; the reader's\n * realpath containment is the hard guarantee.\n */\nexport function isSafeCustomViewPath(value: string): boolean {\n return value.startsWith(VIEWS_PREFIX) && /\\.html$/.test(value) && isSafeTemplatePath(value);\n}\n"],"mappings":";;AAYA,IAAM,mBAAmB;;;;;;;;AASzB,SAAgB,mBAAmB,OAAwB;CACzD,IAAI,MAAM,WAAW,KAAK,MAAM,SAAS,IAAI,KAAK,MAAM,WAAW,GAAG,GAAG,OAAO;CAChF,OAAO,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,QAAQ,IAAI,SAAS,KAAK,QAAQ,OAAO,QAAQ,QAAQ,oBAAoB,KAAK,GAAG,CAAC;AACvH;;;;;;;;AASA,SAAgB,yBAAyB,OAAwB;CAC/D,OAAO,MAAM,WAAW,gBAAgB,KAAK,mBAAmB,KAAK;AACvE;;AAGA,IAAM,eAAe;;;;;;;;;AAUrB,SAAgB,qBAAqB,OAAwB;CAC3D,OAAO,MAAM,WAAW,YAAY,KAAK,UAAU,KAAK,KAAK,KAAK,mBAAmB,KAAK;AAC5F"}
|
|
1
|
+
{"version":3,"file":"paths.js","names":[],"sources":["../../src/collection/server/templatePath.ts"],"sourcesContent":["// Shared template-path safety, used by BOTH the collection schema\n// validator (`discovery.ts` `ActionSpecSchema`) and the skill-bridge\n// hook (`hooks/handlers/skillBridge.ts`). Centralised so the set of\n// action `template` paths a schema may declare is *exactly* the set\n// the bridge mirrors into `.claude/skills/<slug>/` — if the two\n// diverge, a schema can validate yet reference a template the bridge\n// silently drops (runtime \"template could not be read\").\n//\n// Keep this module dependency-free: the bridge is bundled into a lean\n// esbuild dispatcher, so it must not drag in workspace/path machinery.\n\n/** `templates/` — the subdir an action template must live under. */\nconst TEMPLATES_PREFIX = \"templates/\";\n\n/**\n * A safe skill-relative path: non-empty, no backslash, not absolute,\n * and every `/`-separated segment is a plain `[A-Za-z0-9._-]+` token\n * that isn't `.` / `..` (no traversal). Multi-segment (nested) paths\n * are allowed. The reader's realpath containment is the hard\n * guarantee; this fails a bad path fast.\n */\nexport function isSafeTemplatePath(value: string): boolean {\n if (value.length === 0 || value.includes(\"\\\\\") || value.startsWith(\"/\")) return false;\n return value.split(\"/\").every((seg) => seg.length > 0 && seg !== \".\" && seg !== \"..\" && /^[A-Za-z0-9._-]+$/.test(seg));\n}\n\n/**\n * An action `template` value: a safe path that lives under the skill's\n * `templates/` subdir. This is the canonical contract — the schema\n * validator rejects anything else up front, and the bridge mirrors\n * exactly these. Nested paths (`templates/mail/welcome.md`) and any\n * extension are allowed as long as the path is otherwise safe.\n */\nexport function isSafeActionTemplatePath(value: string): boolean {\n return value.startsWith(TEMPLATES_PREFIX) && isSafeTemplatePath(value);\n}\n\n/** `views/` — the subdir a custom-view HTML file must live under. */\nconst VIEWS_PREFIX = \"views/\";\n\n/**\n * A custom-view `file` value: a safe path under the skill's `views/`\n * subdir that ends in `.html`. Custom views are LLM-authored HTML the\n * host renders in a sandboxed iframe; constraining them to `views/*.html`\n * keeps the data folder and the schema/template files off-limits to the\n * view-file reader. Same per-segment safety as templates; the reader's\n * realpath containment is the hard guarantee.\n */\nexport function isSafeCustomViewPath(value: string): boolean {\n return value.startsWith(VIEWS_PREFIX) && /\\.html$/.test(value) && isSafeTemplatePath(value);\n}\n\n/**\n * A custom-view `i18n` value: the JSON dictionary file authors ship next to\n * their `views/<id>.html`. Constrained to `views/<name>.i18n.json` so the\n * field can only point at a translation file co-located with the view it\n * translates — the same `views/` containment the HTML reader uses. The\n * `.i18n.json` suffix (vs plain `.json`) makes the file's purpose grep-able\n * for both authors and tooling.\n */\nexport function isSafeCustomViewI18nPath(value: string): boolean {\n return value.startsWith(VIEWS_PREFIX) && /\\.i18n\\.json$/.test(value) && isSafeTemplatePath(value);\n}\n"],"mappings":";;AAYA,IAAM,mBAAmB;;;;;;;;AASzB,SAAgB,mBAAmB,OAAwB;CACzD,IAAI,MAAM,WAAW,KAAK,MAAM,SAAS,IAAI,KAAK,MAAM,WAAW,GAAG,GAAG,OAAO;CAChF,OAAO,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,QAAQ,IAAI,SAAS,KAAK,QAAQ,OAAO,QAAQ,QAAQ,oBAAoB,KAAK,GAAG,CAAC;AACvH;;;;;;;;AASA,SAAgB,yBAAyB,OAAwB;CAC/D,OAAO,MAAM,WAAW,gBAAgB,KAAK,mBAAmB,KAAK;AACvE;;AAGA,IAAM,eAAe;;;;;;;;;AAUrB,SAAgB,qBAAqB,OAAwB;CAC3D,OAAO,MAAM,WAAW,YAAY,KAAK,UAAU,KAAK,KAAK,KAAK,mBAAmB,KAAK;AAC5F;;;;;;;;;AAUA,SAAgB,yBAAyB,OAAwB;CAC/D,OAAO,MAAM,WAAW,YAAY,KAAK,gBAAgB,KAAK,KAAK,KAAK,mBAAmB,KAAK;AAClG"}
|
|
@@ -32,6 +32,7 @@ export declare const CollectionSchemaZ: z.ZodObject<{
|
|
|
32
32
|
required: z.ZodOptional<z.ZodBoolean>;
|
|
33
33
|
to: z.ZodOptional<z.ZodString>;
|
|
34
34
|
id: z.ZodOptional<z.ZodString>;
|
|
35
|
+
idField: z.ZodOptional<z.ZodString>;
|
|
35
36
|
currency: z.ZodOptional<z.ZodString>;
|
|
36
37
|
currencyField: z.ZodOptional<z.ZodString>;
|
|
37
38
|
values: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -143,6 +144,7 @@ export declare const CollectionSchemaZ: z.ZodObject<{
|
|
|
143
144
|
label: z.ZodString;
|
|
144
145
|
icon: z.ZodOptional<z.ZodString>;
|
|
145
146
|
file: z.ZodString;
|
|
147
|
+
i18n: z.ZodOptional<z.ZodString>;
|
|
146
148
|
capabilities: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
147
149
|
read: "read";
|
|
148
150
|
write: "write";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_server = require("../../server-
|
|
2
|
+
const require_server = require("../../server-Ceb6OQ8v.cjs");
|
|
3
3
|
const require_collection_paths = require("../paths.cjs");
|
|
4
4
|
exports.COMPUTED_TYPES = require_server.COMPUTED_TYPES;
|
|
5
5
|
exports.CollectionSchemaZ = require_server.CollectionSchemaZ;
|
|
@@ -23,6 +23,7 @@ exports.getWorkspaceRoot = require_server.getWorkspaceRoot;
|
|
|
23
23
|
exports.isContainedInRoot = require_server.isContainedInRoot;
|
|
24
24
|
exports.isContainedInWorkspace = require_server.isContainedInWorkspace;
|
|
25
25
|
exports.isSafeActionTemplatePath = require_collection_paths.isSafeActionTemplatePath;
|
|
26
|
+
exports.isSafeCustomViewI18nPath = require_collection_paths.isSafeCustomViewI18nPath;
|
|
26
27
|
exports.isSafeCustomViewPath = require_collection_paths.isSafeCustomViewPath;
|
|
27
28
|
exports.isSafeTemplatePath = require_collection_paths.isSafeTemplatePath;
|
|
28
29
|
exports.isTriggerDue = require_server.isTriggerDue;
|
|
@@ -34,6 +35,7 @@ exports.maybeSpawnSuccessor = require_server.maybeSpawnSuccessor;
|
|
|
34
35
|
exports.parseCivil = require_server.parseCivil;
|
|
35
36
|
exports.publishCollectionChange = require_server.publishCollectionChange;
|
|
36
37
|
exports.readCustomViewHtml = require_server.readCustomViewHtml;
|
|
38
|
+
exports.readCustomViewI18n = require_server.readCustomViewI18n;
|
|
37
39
|
exports.readItem = require_server.readItem;
|
|
38
40
|
exports.readSkillTemplate = require_server.readSkillTemplate;
|
|
39
41
|
exports.resolveCreateItemId = require_server.resolveCreateItemId;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as
|
|
2
|
-
import { isSafeActionTemplatePath, isSafeCustomViewPath, isSafeTemplatePath } from "../paths.js";
|
|
3
|
-
export { COMPUTED_TYPES, CollectionSchemaZ, SCHEMA_FILE, acceptParsedSchema, advanceTriggerDate, buildActionSeedPrompt, buildCollectionActionSeedPrompt, computeSuccessor, configureCollectionHost, daysInMonth, deleteCollection, deleteCollectionRefusalMessage, deleteCustomView, deleteItem, discoverCollections, enrichItems, formatCivil, generateItemId, getWorkspaceRoot, isContainedInRoot, isContainedInWorkspace, isSafeActionTemplatePath, isSafeCustomViewPath, isSafeTemplatePath, isTriggerDue, itemFilePath, listItems, loadCollection, log, maybeSpawnSuccessor, parseCivil, publishCollectionChange, readCustomViewHtml, readItem, readSkillTemplate, resolveCreateItemId, resolveDataDir, resolveEvery, resolveTemplatePath, safeRecordId, safeSlugName, setCollectionChangePublisher, successorId, toDetail, toSummary, validateCollectionRecords, validateRecordObject, writeItem };
|
|
1
|
+
import { A as readItem, B as safeRecordId, C as buildActionSeedPrompt, D as listItems, E as generateItemId, F as isContainedInRoot, G as publishCollectionChange, H as configureCollectionHost, I as isContainedInWorkspace, K as setCollectionChangePublisher, L as itemFilePath, M as resolveCreateItemId, N as writeItem, O as readCustomViewHtml, P as SCHEMA_FILE, R as resolveDataDir, S as validateRecordObject, T as deleteItem, U as getWorkspaceRoot, V as safeSlugName, W as log, _ as loadCollection, a as computeSuccessor, b as COMPUTED_TYPES, c as isTriggerDue, d as resolveEvery, f as successorId, g as discoverCollections, h as acceptParsedSchema, i as advanceTriggerDate, j as readSkillTemplate, k as readCustomViewI18n, l as maybeSpawnSuccessor, m as CollectionSchemaZ, n as deleteCollection, o as daysInMonth, p as enrichItems, r as deleteCollectionRefusalMessage, s as formatCivil, t as deleteCustomView, u as parseCivil, v as toDetail, w as buildCollectionActionSeedPrompt, x as validateCollectionRecords, y as toSummary, z as resolveTemplatePath } from "../../server-C7inPZNG.js";
|
|
2
|
+
import { isSafeActionTemplatePath, isSafeCustomViewI18nPath, isSafeCustomViewPath, isSafeTemplatePath } from "../paths.js";
|
|
3
|
+
export { COMPUTED_TYPES, CollectionSchemaZ, SCHEMA_FILE, acceptParsedSchema, advanceTriggerDate, buildActionSeedPrompt, buildCollectionActionSeedPrompt, computeSuccessor, configureCollectionHost, daysInMonth, deleteCollection, deleteCollectionRefusalMessage, deleteCustomView, deleteItem, discoverCollections, enrichItems, formatCivil, generateItemId, getWorkspaceRoot, isContainedInRoot, isContainedInWorkspace, isSafeActionTemplatePath, isSafeCustomViewI18nPath, isSafeCustomViewPath, isSafeTemplatePath, isTriggerDue, itemFilePath, listItems, loadCollection, log, maybeSpawnSuccessor, parseCivil, publishCollectionChange, readCustomViewHtml, readCustomViewI18n, readItem, readSkillTemplate, resolveCreateItemId, resolveDataDir, resolveEvery, resolveTemplatePath, safeRecordId, safeSlugName, setCollectionChangePublisher, successorId, toDetail, toSummary, validateCollectionRecords, validateRecordObject, writeItem };
|
|
@@ -78,6 +78,13 @@ export declare function deleteItem(dataDir: string, itemId: string, opts?: IoOpt
|
|
|
78
78
|
* carry a primary-key value (UI shortcut — Claude normally derives a
|
|
79
79
|
* semantic id from the record's name). */
|
|
80
80
|
export declare function generateItemId(): string;
|
|
81
|
+
/** The shape `readSourceAwareFile` (and its public callers
|
|
82
|
+
* `readCustomViewHtml` / `readCustomViewI18n`) need from a loaded collection:
|
|
83
|
+
* the slug for the safe-name check, the source to pick the base set, and the
|
|
84
|
+
* discovered skill dir for the imported-layout fallback. Kept as a named
|
|
85
|
+
* alias so the three signatures stay in lockstep (and so sonarjs stops
|
|
86
|
+
* flagging the inline `Pick` union as duplication). */
|
|
87
|
+
type SourceAwareReadTarget = Pick<LoadedCollection, "slug" | "source" | "skillDir">;
|
|
81
88
|
/** Read a collection's custom-view HTML, path-safely. `viewFile` is a
|
|
82
89
|
* schema-validated `views/*.html` path, resolved with realpath containment.
|
|
83
90
|
* Returns the HTML, or null when the path is unsafe or the file is missing.
|
|
@@ -94,7 +101,26 @@ export declare function generateItemId(): string;
|
|
|
94
101
|
* so it only needs the single lookup. `resolveTemplatePath` does the
|
|
95
102
|
* containment / `..` defense per base, so the fallback never broadens the
|
|
96
103
|
* attack surface. */
|
|
97
|
-
export declare function readCustomViewHtml(collection:
|
|
104
|
+
export declare function readCustomViewHtml(collection: SourceAwareReadTarget, viewFile: string, opts?: IoOptions): Promise<string | null>;
|
|
105
|
+
export interface CustomViewI18nResult {
|
|
106
|
+
/** The locale the returned `dict` is keyed to — equals the requested locale
|
|
107
|
+
* when available, else the `"en"` fallback, else `""` when neither block is
|
|
108
|
+
* present (empty `dict`). The host echoes this back to the iframe so
|
|
109
|
+
* `__MC_VIEW.locale` reflects what the view actually got. */
|
|
110
|
+
locale: string;
|
|
111
|
+
/** Flat key → string map for the picked locale. Empty when the file is
|
|
112
|
+
* absent, malformed, or has no usable locale block. Non-string values in a
|
|
113
|
+
* locale block are dropped — `__MC_VIEW.dict` is contract-flat. */
|
|
114
|
+
dict: Record<string, string>;
|
|
115
|
+
}
|
|
116
|
+
/** Read a custom view's translation dictionary and return only the strings
|
|
117
|
+
* for the requested locale (or the `"en"` fallback, or empty). Same
|
|
118
|
+
* source-aware fallback as `readCustomViewHtml` so imported and authored
|
|
119
|
+
* project collections both work. The on-disk file is `{ <locale>: { <key>:
|
|
120
|
+
* <string> } }`; the host never streams other locales' strings to the view.
|
|
121
|
+
* Malformed JSON / unknown shape yields an empty dict — an i18n-less view
|
|
122
|
+
* keeps working unchanged (`__MC_VIEW.t(key)` falls back to the key). */
|
|
123
|
+
export declare function readCustomViewI18n(collection: SourceAwareReadTarget, i18nFile: string, locale: string, opts?: IoOptions): Promise<CustomViewI18nResult>;
|
|
98
124
|
/** The item id a CREATE should use for `schema`, or null when the
|
|
99
125
|
* caller should generate one. A singleton collection pins every
|
|
100
126
|
* create to its fixed `schema.singleton` id, so the "at most one
|
|
@@ -118,3 +144,4 @@ export declare function buildActionSeedPrompt(record: CollectionItem, templateTe
|
|
|
118
144
|
* record (see `progressSummary`) + the template verbatim. Pure +
|
|
119
145
|
* exported for tests. Domain-free — the template carries the specifics. */
|
|
120
146
|
export declare function buildCollectionActionSeedPrompt(items: CollectionItem[], schema: CollectionSchema, templateText: string): string;
|
|
147
|
+
export {};
|
|
@@ -23,3 +23,12 @@ export declare function isSafeActionTemplatePath(value: string): boolean;
|
|
|
23
23
|
* realpath containment is the hard guarantee.
|
|
24
24
|
*/
|
|
25
25
|
export declare function isSafeCustomViewPath(value: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* A custom-view `i18n` value: the JSON dictionary file authors ship next to
|
|
28
|
+
* their `views/<id>.html`. Constrained to `views/<name>.i18n.json` so the
|
|
29
|
+
* field can only point at a translation file co-located with the view it
|
|
30
|
+
* translates — the same `views/` containment the HTML reader uses. The
|
|
31
|
+
* `.i18n.json` suffix (vs plain `.json`) makes the file's purpose grep-able
|
|
32
|
+
* for both authors and tooling.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isSafeCustomViewI18nPath(value: string): boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_collection_index = require("../collection/index.cjs");
|
|
3
|
-
const require_server = require("../server-
|
|
3
|
+
const require_server = require("../server-Ceb6OQ8v.cjs");
|
|
4
4
|
const require_notifier = require("../notifier-bS8IEeLA.cjs");
|
|
5
5
|
let node_fs = require("node:fs");
|
|
6
6
|
let node_fs_promises = require("node:fs/promises");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { whenMatches } from "../collection/index.js";
|
|
2
|
-
import { D as listItems, _ as loadCollection, c as isTriggerDue, g as discoverCollections,
|
|
2
|
+
import { A as readItem, D as listItems, _ as loadCollection, c as isTriggerDue, g as discoverCollections, l as maybeSpawnSuccessor } from "../server-C7inPZNG.js";
|
|
3
3
|
import { d as publish, m as updateForPlugin, n as clear, s as listAll } from "../notifier-ChpY0XrY.js";
|
|
4
4
|
import { watch } from "node:fs";
|
|
5
5
|
import { mkdir } from "node:fs/promises";
|