@mulmoclaude/core 0.22.1 → 0.23.1
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/collection-skills.md +110 -1
- package/assets/helps/custom-view.md +38 -0
- package/assets/helps/error-recovery.md +36 -0
- package/assets/skills-preset/mc-zenn/SKILL.md +31 -0
- package/dist/collection/core/queryZ.d.ts +90 -0
- package/dist/collection/core/schema.d.ts +14 -1
- package/dist/collection/core/schemaZ.d.ts +18 -1
- package/dist/collection/index.cjs +3 -2
- package/dist/collection/index.cjs.map +1 -1
- package/dist/collection/index.d.ts +1 -0
- package/dist/collection/index.js +3 -3
- package/dist/collection/index.js.map +1 -1
- package/dist/collection/registry/server/index.cjs +12 -4
- package/dist/collection/registry/server/index.cjs.map +1 -1
- package/dist/collection/registry/server/index.js +12 -4
- package/dist/collection/registry/server/index.js.map +1 -1
- package/dist/collection/server/csvQuery.d.ts +27 -0
- package/dist/collection/server/csvStore.d.ts +51 -0
- package/dist/collection/server/discoveredCollection.d.ts +9 -1
- package/dist/collection/server/discovery.d.ts +7 -3
- package/dist/collection/server/index.cjs +17 -2
- package/dist/collection/server/index.d.ts +6 -0
- package/dist/collection/server/index.js +3 -3
- package/dist/collection/server/jsonlQuery.d.ts +4 -0
- package/dist/collection/server/manageTool.d.ts +4 -0
- package/dist/collection/server/store.d.ts +31 -0
- package/dist/collection-watchers/index.cjs +104 -3
- package/dist/collection-watchers/index.cjs.map +1 -1
- package/dist/collection-watchers/index.js +102 -3
- package/dist/collection-watchers/index.js.map +1 -1
- package/dist/feeds/index.cjs +2 -2
- package/dist/feeds/index.js +2 -2
- package/dist/feeds/server/index.cjs +3 -3
- package/dist/feeds/server/index.js +3 -3
- package/dist/google/auth.d.ts +18 -3
- package/dist/google/authFlow.d.ts +1 -0
- package/dist/google/calendar.d.ts +44 -0
- package/dist/google/index.cjs +165 -47
- package/dist/google/index.cjs.map +1 -1
- package/dist/google/index.d.ts +2 -2
- package/dist/google/index.js +159 -48
- package/dist/google/index.js.map +1 -1
- package/dist/{ingestTypes-DEjpiZGM.js → ingestTypes-B-dXxUF9.js} +2 -2
- package/dist/{ingestTypes-DEjpiZGM.js.map → ingestTypes-B-dXxUF9.js.map} +1 -1
- package/dist/{ingestTypes-Dh5lniBL.cjs → ingestTypes-Cev9q77C.cjs} +2 -2
- package/dist/{ingestTypes-Dh5lniBL.cjs.map → ingestTypes-Cev9q77C.cjs.map} +1 -1
- package/dist/{promptSafety-DN5V__Ku.cjs → promptSafety-DRd15gQ6.cjs} +15 -1
- package/dist/promptSafety-DRd15gQ6.cjs.map +1 -0
- package/dist/{promptSafety-DyG3EuC7.js → promptSafety-rDWA9_JS.js} +10 -2
- package/dist/promptSafety-rDWA9_JS.js.map +1 -0
- package/dist/{server-Bd8l1bhv.js → server-D2ibbixF.js} +772 -79
- package/dist/server-D2ibbixF.js.map +1 -0
- package/dist/{server-Bh-DPK-H.cjs → server-Jaogu3Ky.cjs} +860 -76
- package/dist/server-Jaogu3Ky.cjs.map +1 -0
- package/package.json +3 -1
- package/dist/promptSafety-DN5V__Ku.cjs.map +0 -1
- package/dist/promptSafety-DyG3EuC7.js.map +0 -1
- package/dist/server-Bd8l1bhv.js.map +0 -1
- package/dist/server-Bh-DPK-H.cjs.map +0 -1
|
@@ -126,7 +126,8 @@ skipped, never crashes the host):
|
|
|
126
126
|
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
127
127
|
| `title` | Human name shown in the sidebar / header. Required. |
|
|
128
128
|
| `icon` | A **Material Symbols** name (`receipt_long`, `people`, `schedule`, `menu_book`). Required. |
|
|
129
|
-
| `dataPath` | Workspace-relative records folder, e.g. `data/recipes/items`. Must stay under the workspace. Required.
|
|
129
|
+
| `dataPath` | Workspace-relative records folder, e.g. `data/recipes/items`. Must stay under the workspace. Required — unless `dataSource` is set (declare exactly ONE of the two). |
|
|
130
|
+
| `dataSource` | Optional. `{ "type": "csv", "path": "data/students.csv" }` — the records ARE the rows of an external data file (workspace-relative, containment-checked like `dataPath`). Makes the collection **read-only** in every UI/tool write path; see "External data (CSV) collections" below. Mutually exclusive with `dataPath`, `singleton`, `ingest`, `spawn`, and `mutate` actions. |
|
|
130
131
|
| `primaryKey` | The field name whose value is the filename. That field MUST set `primary: true`. The value must be a valid record id (see the **Records** section's id-charset rule). Required. |
|
|
131
132
|
| `singleton` | Optional. When set, at most one record exists, pinned to this exact id (e.g. `me`). Host pre-fills + locks the create form and hides Add once it exists. |
|
|
132
133
|
| `fields` | Ordered map of field-name → field spec. **Insertion order = column order** in the table. Required. |
|
|
@@ -853,6 +854,13 @@ records through **`manageCollection`**, not raw file I/O:
|
|
|
853
854
|
/ `fields` on large collections to keep the result small — e.g.
|
|
854
855
|
`fields: ["id"]` to check for an id collision before an add.
|
|
855
856
|
- **Delete** — remove the record file (`manageCollection` has no delete).
|
|
857
|
+
- **Aggregate — `queryItems`.** Counts, sums, averages, group-bys on ANY
|
|
858
|
+
collection via a structured query (`groupBy` / `aggregates` / `where` /
|
|
859
|
+
`orderBy` / `limit` — full shape in the "External data (CSV) collections"
|
|
860
|
+
section below). On file-backed collections it aggregates the ENRICHED
|
|
861
|
+
records, so computed fields (`derived` / `rollup` / `toggle`) are
|
|
862
|
+
queryable columns — "sum of invoice totals" works even when `total` is a
|
|
863
|
+
formula. Prefer it over doing arithmetic on `getItems` output.
|
|
856
864
|
- **Cross-collection questions — `getOntology`.** Returns every collection in
|
|
857
865
|
the workspace with its `primaryKey`, effective `displayField`, record count,
|
|
858
866
|
and its `ref` / `embed` / `backlinks` / `rollup` relations (field → related slug, including refs
|
|
@@ -905,6 +913,107 @@ directly:
|
|
|
905
913
|
(This safety net applies after `putItems` batches too, but direct writes are
|
|
906
914
|
where it earns its keep.)
|
|
907
915
|
|
|
916
|
+
## External data (CSV) collections — `dataSource`
|
|
917
|
+
|
|
918
|
+
When the user has a data file they want to "manage" / "visualize" / "見たい"
|
|
919
|
+
(a student roster, an HR export, a product list — the BI use case), do NOT
|
|
920
|
+
import the rows into record files. Define a collection **on top of** the file
|
|
921
|
+
with `dataSource` — the file stays the single source of truth and the whole
|
|
922
|
+
collection UI (table, kanban, calendar, custom views, remote views) works over
|
|
923
|
+
its rows via the host's DuckDB-backed CSV store.
|
|
924
|
+
|
|
925
|
+
**The schema-inference recipe** (user: "この CSV を管理したい" / "make this CSV a
|
|
926
|
+
collection"):
|
|
927
|
+
|
|
928
|
+
1. **Inspect the file** — Read the first ~30 lines. Note the header row
|
|
929
|
+
(column names), each column's apparent type, and which column uniquely
|
|
930
|
+
identifies a row.
|
|
931
|
+
2. **Pick the key column** — set `primaryKey` to that column's name and flag
|
|
932
|
+
its field `primary: true`. Prefer an ID-ish column (student number, SKU,
|
|
933
|
+
email) over a name. Check for duplicates if unsure — duplicated key values
|
|
934
|
+
don't error, but the LAST row silently wins.
|
|
935
|
+
**Declare the key field as `type: "string"` even when the column is
|
|
936
|
+
numeric** (a row number, an integer ID): record ids are strings, and the
|
|
937
|
+
store overwrites the key field's value with the id — a `number`-typed key
|
|
938
|
+
would just hold a string anyway. Consequence to keep in mind: sorting by
|
|
939
|
+
the key column is lexicographic ("10" before "2"); if numeric ordering
|
|
940
|
+
matters to the user, sort by another column.
|
|
941
|
+
3. **Declare `fields` matching the column names** — field name = CSV column
|
|
942
|
+
name, verbatim (Japanese column names are fine). Only declared fields
|
|
943
|
+
render as table columns; extra CSV columns still ride along in the record
|
|
944
|
+
detail. Use `number` / `date` / `enum` (when a column has a small closed
|
|
945
|
+
value set) / `string` for the rest — DuckDB sniffs the raw types, the
|
|
946
|
+
field spec controls rendering.
|
|
947
|
+
4. **Set `displayField`** to the most human-readable column (a name). This
|
|
948
|
+
matters extra here: a key value that isn't a safe record id (Japanese
|
|
949
|
+
text, spaces) is hex-encoded into the record's address, and
|
|
950
|
+
`displayField` is what keeps lists and notifications readable.
|
|
951
|
+
5. **Write the schema** with `dataSource` instead of `dataPath`, plus a
|
|
952
|
+
normal `SKILL.md`, under `data/skills/<slug>/` — same create flow as any
|
|
953
|
+
collection. In the SKILL body, point aggregation questions at
|
|
954
|
+
`manageCollection` `queryItems` (see below) — NOT at python/pandas and
|
|
955
|
+
NOT at `getItems`; SKILL.md text outlives help updates, so a wrong
|
|
956
|
+
steer here misroutes every future session on this collection.
|
|
957
|
+
|
|
958
|
+
Semantics to remember (and to tell the user):
|
|
959
|
+
|
|
960
|
+
- **Read-only** — no Add/Edit/Delete in the UI, `putItems` refuses, HTTP
|
|
961
|
+
writes answer 405. To change the data, **edit or replace the file itself**
|
|
962
|
+
(you can do that with the normal file tools when asked); open views refresh
|
|
963
|
+
automatically via a file watcher.
|
|
964
|
+
- **Encoding** — Shift_JIS / CP932 and UTF-16 files work as-is; the host
|
|
965
|
+
decodes to a cache copy and never rewrites the user's file. Don't convert
|
|
966
|
+
the file to UTF-8 "to be safe" — an Excel re-export would just undo it.
|
|
967
|
+
- **Row cap** — `getItems` / the UI list stops at 5,000 rows (a warn is
|
|
968
|
+
logged). Fine for browsing — but NEVER compute an aggregate from
|
|
969
|
+
`getItems` output on a large file (a capped scan gives a silently wrong
|
|
970
|
+
number). Use `queryItems` instead.
|
|
971
|
+
- **Aggregation — `manageCollection` `queryItems`**: a structured query over
|
|
972
|
+
the WHOLE file (uncapped scan, DuckDB underneath). Answer counts / sums /
|
|
973
|
+
averages / group-bys with it — don't shell out to python/pandas for
|
|
974
|
+
questions it covers. (It works on EVERY collection — see the Records
|
|
975
|
+
section; this bullet is about the dataSource specifics.) Shape:
|
|
976
|
+
|
|
977
|
+
```json
|
|
978
|
+
{
|
|
979
|
+
"groupBy": ["Category"],
|
|
980
|
+
"aggregates": { "total": { "op": "sum", "column": "Price" }, "n": { "op": "count" } },
|
|
981
|
+
"where": [{ "field": "Availability", "op": "eq", "value": "in_stock" }],
|
|
982
|
+
"orderBy": [{ "field": "total", "dir": "desc" }],
|
|
983
|
+
"limit": 100
|
|
984
|
+
}
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
Ops: `count` (column optional) / `sum` / `avg` / `min` / `max`; `where`
|
|
988
|
+
ops are the familiar `eq/ne/in/gt/gte/lt/lte/contains`; `orderBy` sorts
|
|
989
|
+
by a groupBy column or an aggregate alias; result rows are clamped
|
|
990
|
+
(default 1,000). At least one of `groupBy`/`aggregates` is required.
|
|
991
|
+
`sum`/`avg` skip non-numeric cells. A custom view can run the same
|
|
992
|
+
query shape via `POST <dataUrl>/query` with its read token — see
|
|
993
|
+
`config/helps/custom-view.md` — which is how dataSource dashboards
|
|
994
|
+
chart live data.
|
|
995
|
+
- **Not registry material** — dataSource collections can't be imported from
|
|
996
|
+
or contributed to a registry (the data file is machine-local).
|
|
997
|
+
|
|
998
|
+
Minimal example (Japanese roster, Shift_JIS file dropped at
|
|
999
|
+
`data/students.csv`):
|
|
1000
|
+
|
|
1001
|
+
```json
|
|
1002
|
+
{
|
|
1003
|
+
"title": "生徒名簿",
|
|
1004
|
+
"icon": "school",
|
|
1005
|
+
"dataSource": { "type": "csv", "path": "data/students.csv" },
|
|
1006
|
+
"primaryKey": "学籍番号",
|
|
1007
|
+
"displayField": "氏名",
|
|
1008
|
+
"fields": {
|
|
1009
|
+
"学籍番号": { "type": "string", "label": "学籍番号", "primary": true },
|
|
1010
|
+
"氏名": { "type": "string", "label": "氏名" },
|
|
1011
|
+
"学年": { "type": "enum", "label": "学年", "values": ["1", "2", "3"] },
|
|
1012
|
+
"入学日": { "type": "date", "label": "入学日" }
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
```
|
|
1016
|
+
|
|
908
1017
|
## End-to-end: creating a new collection skill
|
|
909
1018
|
|
|
910
1019
|
1. Pick a `<slug>` (lowercase-hyphen, no `mc-` prefix) and a `dataPath`
|
|
@@ -101,6 +101,44 @@ toggles, embeds) — the same numbers the user sees elsewhere.
|
|
|
101
101
|
/ shows one record at a time. Combinable with `fields`.
|
|
102
102
|
- The primary key is always returned regardless of `fields`.
|
|
103
103
|
|
|
104
|
+
### Aggregation queries (`read` capability)
|
|
105
|
+
|
|
106
|
+
A view can run structured aggregations over the collection — on a
|
|
107
|
+
`dataSource` (CSV) collection this scans the WHOLE file (the record read
|
|
108
|
+
above is row-capped at 5,000 there, so an aggregate computed from it would
|
|
109
|
+
be silently wrong); on a file-backed collection it aggregates the ENRICHED
|
|
110
|
+
records, so computed fields (`derived` / `rollup` / `toggle`) are queryable
|
|
111
|
+
columns:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
const res = await fetch(dataUrl + "/query", {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: { Authorization: "Bearer " + token, "Content-Type": "application/json" },
|
|
117
|
+
body: JSON.stringify({
|
|
118
|
+
query: {
|
|
119
|
+
groupBy: ["Category"],
|
|
120
|
+
aggregates: { total: { op: "sum", column: "Price" }, n: { op: "count" } },
|
|
121
|
+
where: [{ field: "Availability", op: "eq", value: "in_stock" }],
|
|
122
|
+
orderBy: [{ field: "total", dir: "desc" }],
|
|
123
|
+
limit: 100,
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
126
|
+
});
|
|
127
|
+
const { rows } = await res.json(); // [{ Category, total, n }, ...] — chart these
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
- Ops: `count` (column optional) / `sum` / `avg` / `min` / `max`; `where`
|
|
131
|
+
ops: `eq/ne/in/gt/gte/lt/lte/contains`; at least one of
|
|
132
|
+
`groupBy`/`aggregates`; `orderBy` sorts by a groupBy column or an
|
|
133
|
+
aggregate alias; result rows clamp at 1,000 by default.
|
|
134
|
+
- Structured JSON only — there is **no SQL surface**, by design.
|
|
135
|
+
- Combine with `onChange` (below) to stay live: in the callback, re-run
|
|
136
|
+
**this POST query** (wrap it in your own `refresh()` and register that)
|
|
137
|
+
— NOT the `?fields=` record read from the section above, which would
|
|
138
|
+
silently repaint an aggregation view with non-aggregated data. A
|
|
139
|
+
replaced CSV or edited records then redraw the chart — that's a live
|
|
140
|
+
dashboard.
|
|
141
|
+
|
|
104
142
|
### Writing records (only with the `write` capability)
|
|
105
143
|
|
|
106
144
|
```js
|
|
@@ -183,6 +183,36 @@ plugin via the `/skills` UI to refresh both the tgz and the ledger.
|
|
|
183
183
|
A version skew on a peer dep means the plugin was built against an
|
|
184
184
|
older host — bump the plugin via the Discover tab's update flow.
|
|
185
185
|
|
|
186
|
+
## dataSource (CSV) collection reads fail — "DuckDB is unavailable on this host"
|
|
187
|
+
|
|
188
|
+
A collection whose schema declares `dataSource` (external CSV) reads its rows
|
|
189
|
+
through `@duckdb/node-api`, a NATIVE module with per-platform prebuilt
|
|
190
|
+
bindings. When the binding is missing or fails to load, ONLY dataSource
|
|
191
|
+
collections break (every file-backed collection keeps working) and reads
|
|
192
|
+
fail with `DuckDB is unavailable on this host (@duckdb/node-api failed to
|
|
193
|
+
load: …)`.
|
|
194
|
+
|
|
195
|
+
Diagnosis + fixes, in order:
|
|
196
|
+
|
|
197
|
+
1. **Reinstall dependencies** — `yarn install` at the app root. The usual
|
|
198
|
+
cause is an install that skipped the platform package
|
|
199
|
+
(`@duckdb/node-bindings-<platform>-<arch>`), e.g. after copying
|
|
200
|
+
`node_modules` between machines or architectures (an arm64 → x64 Docker
|
|
201
|
+
volume mount does exactly this).
|
|
202
|
+
2. **Check the platform is supported** — `ls node_modules/@duckdb/ | cat`.
|
|
203
|
+
You should see a `node-bindings-<your platform>` package. If DuckDB ships
|
|
204
|
+
no prebuilt binding for the host (rare: musl/alpine, exotic arch), there
|
|
205
|
+
is no local fix — tell the user dataSource collections need a supported
|
|
206
|
+
platform (glibc Linux x64/arm64, macOS, Windows) and that their other
|
|
207
|
+
collections are unaffected.
|
|
208
|
+
3. **Docker**: make sure the image installs dependencies INSIDE the
|
|
209
|
+
container (matching libc/arch) rather than bind-mounting a host
|
|
210
|
+
`node_modules`.
|
|
211
|
+
|
|
212
|
+
Related, not an error: a dataSource CSV in Shift_JIS / UTF-16 is decoded
|
|
213
|
+
automatically to a cache copy under the OS temp dir — never "fix" the
|
|
214
|
+
user's file by re-encoding it.
|
|
215
|
+
|
|
186
216
|
## Fallback
|
|
187
217
|
|
|
188
218
|
If none of the above matches the failing tool output:
|
|
@@ -308,6 +338,7 @@ shipped builders produce, and asserts `handlePermission` comes back over the MCP
|
|
|
308
338
|
- **"multiple client_secret_*.json files found"**.
|
|
309
339
|
- **"Google Calendar API: HTTP 403"** with a hint about enabling the API.
|
|
310
340
|
- **"could not obtain a Google access token"** (grant revoked).
|
|
341
|
+
- `calendarListCalendars` / non-primary calendar lookups fail with **HTTP 401/403 / insufficient scope**, or the list comes back empty even though the user has other calendars.
|
|
311
342
|
|
|
312
343
|
### Cause
|
|
313
344
|
|
|
@@ -328,6 +359,11 @@ Two ways the link can be minted, and the tool picks automatically:
|
|
|
328
359
|
- **Not linked / grant revoked** — ask the user to link (or re-link) the account from this app's
|
|
329
360
|
settings, then retry. Do NOT tell them to create a Google Cloud project, and do NOT try to
|
|
330
361
|
create or edit the token file yourself.
|
|
362
|
+
- **Calendar-list / non-primary lookup fails with insufficient scope (or lists nothing)** — the
|
|
363
|
+
account was linked before the calendar-list read scope (`calendar.calendarlist.readonly`) was
|
|
364
|
+
added, so the stored grant predates it. Ask the user to re-link from settings to add the scope,
|
|
365
|
+
then retry. Reading events on a non-primary calendar by id needs no re-link once the calendar
|
|
366
|
+
list is visible.
|
|
331
367
|
- **Sign-in service unreachable / HTTP error** — the broker is down or the network is blocked.
|
|
332
368
|
It is only needed to link and to renew an expired access token; ask the user to retry shortly.
|
|
333
369
|
(A user with their own `~/.secrets/client_secret_*.json` never depends on it.)
|
|
@@ -43,9 +43,11 @@ unless the user already told you:
|
|
|
43
43
|
|
|
44
44
|
- **Clone an existing Zenn repo** (they already write Zenn on GitHub). Get the
|
|
45
45
|
repo URL, then:
|
|
46
|
+
|
|
46
47
|
```bash
|
|
47
48
|
git clone <url> github/zenn
|
|
48
49
|
```
|
|
50
|
+
|
|
49
51
|
`npx zenn` fetches zenn-cli on demand, so a missing local dependency is fine.
|
|
50
52
|
|
|
51
53
|
- **Create a fresh project** (standard zenn-cli flow):
|
|
@@ -90,6 +92,7 @@ fill it in. **A published slug becomes the article URL and can't change** — pi
|
|
|
90
92
|
it deliberately.
|
|
91
93
|
|
|
92
94
|
**Step 3 — write the frontmatter** (Zenn house style):
|
|
95
|
+
|
|
93
96
|
```yaml
|
|
94
97
|
---
|
|
95
98
|
title: "<a clear title, in the user's language>"
|
|
@@ -99,6 +102,7 @@ topics: ["MulmoClaude", "<related>"]
|
|
|
99
102
|
published: true
|
|
100
103
|
---
|
|
101
104
|
```
|
|
105
|
+
|
|
102
106
|
- **Always include `MulmoClaude` in `topics`.** At most 5 topics, no spaces
|
|
103
107
|
inside a single topic.
|
|
104
108
|
- `type` defaults to `tech`; `published` defaults to `true` (use `false` when
|
|
@@ -109,6 +113,33 @@ implementation (fenced ` ```lang ` blocks, runnable commands) → result → a s
|
|
|
109
113
|
wrap-up. Informative but casual; describe the work, not yourself. Put images in
|
|
110
114
|
`github/zenn/images/` and reference them as ``.
|
|
111
115
|
|
|
116
|
+
Write it the way a person would, not the way a model defaults to. Each rule
|
|
117
|
+
below comes from real reader feedback — ignore them and the draft reads as
|
|
118
|
+
machine output:
|
|
119
|
+
|
|
120
|
+
- **Sound human, not like a generated listicle.** Prefer flowing prose over
|
|
121
|
+
walls of bullets, use bold sparingly, and let paragraphs connect into an
|
|
122
|
+
argument. If every section is just a heading plus a list, vary the rhythm.
|
|
123
|
+
- **No uncommon loanwords or jargon.** Use only terms a typical reader already
|
|
124
|
+
knows. Niche English acronyms, trendy katakana, and insider slang either get
|
|
125
|
+
dropped or replaced with plain words. A term the field itself hasn't settled
|
|
126
|
+
on ("code smell" and the like) is a red flag — say the plain thing instead
|
|
127
|
+
("code that works now but bites you later").
|
|
128
|
+
- **Define every term and acronym on first use**, in plain language, and put the
|
|
129
|
+
foundational ones up front — define "DRY" before the article leans on it. A
|
|
130
|
+
reader should never hit a word they can't parse.
|
|
131
|
+
- **Carry a source's essence, don't just cite it.** When an idea comes from a
|
|
132
|
+
book or article, explain the idea itself in your own words; "see <book>"
|
|
133
|
+
teaches nothing. Search for the actual content when you're unsure what it says,
|
|
134
|
+
then write the substance.
|
|
135
|
+
- **Make the article self-contained.** Anything project-specific — a repo's
|
|
136
|
+
internals, a config choice, a domain constraint — needs enough general
|
|
137
|
+
background that a reader with zero context can follow. Running longer is fine
|
|
138
|
+
when the extra length buys understanding.
|
|
139
|
+
- **Match the stated audience.** "Explain to intermediate developers" means
|
|
140
|
+
patient explanations, concrete examples, real code from the work, and links —
|
|
141
|
+
not a terse summary.
|
|
142
|
+
|
|
112
143
|
**Step 5 — save + preview.** Write `github/zenn/articles/<slug>.md`. Tell the
|
|
113
144
|
user the path and how to preview: `cd github/zenn && npx zenn preview`
|
|
114
145
|
(http://localhost:8000). Surface the title, slug, and topics.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Hard ceiling on returned rows; `limit` clamps below it. A group-by on
|
|
3
|
+
* a near-unique column would otherwise return one row per source row —
|
|
4
|
+
* the exact materialization the aggregate path exists to avoid. */
|
|
5
|
+
export declare const MAX_QUERY_ROWS = 10000;
|
|
6
|
+
/** Default row cap when the query declares no `limit`. */
|
|
7
|
+
export declare const DEFAULT_QUERY_ROWS = 1000;
|
|
8
|
+
/** Cap on aggregate expressions per query — a record shape has no
|
|
9
|
+
* intrinsic size limit, and thousands of expressions would make one
|
|
10
|
+
* full-file scan arbitrarily wide. */
|
|
11
|
+
export declare const MAX_QUERY_AGGREGATES = 32;
|
|
12
|
+
/** One aggregate column: `count` (rows; `column` optional to count
|
|
13
|
+
* non-null cells) or `sum`/`avg`/`min`/`max` over a named CSV column. */
|
|
14
|
+
export declare const QueryAggregateZ: z.ZodObject<{
|
|
15
|
+
op: z.ZodEnum<{
|
|
16
|
+
sum: "sum";
|
|
17
|
+
count: "count";
|
|
18
|
+
avg: "avg";
|
|
19
|
+
min: "min";
|
|
20
|
+
max: "max";
|
|
21
|
+
}>;
|
|
22
|
+
column: z.ZodOptional<z.ZodString>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
/** One filter condition. Same op vocabulary as the schema-level `where`
|
|
25
|
+
* (`core/where.ts`) so authors learn one set; values may be typed
|
|
26
|
+
* (number / boolean) since CSV columns are. `in` requires an array
|
|
27
|
+
* value, every other op a scalar. */
|
|
28
|
+
export declare const QueryWhereZ: z.ZodObject<{
|
|
29
|
+
field: z.ZodString;
|
|
30
|
+
op: z.ZodEnum<{
|
|
31
|
+
in: "in";
|
|
32
|
+
eq: "eq";
|
|
33
|
+
ne: "ne";
|
|
34
|
+
gt: "gt";
|
|
35
|
+
gte: "gte";
|
|
36
|
+
lt: "lt";
|
|
37
|
+
lte: "lte";
|
|
38
|
+
contains: "contains";
|
|
39
|
+
}>;
|
|
40
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>]>;
|
|
41
|
+
}, z.core.$strip>;
|
|
42
|
+
export declare const QueryOrderZ: z.ZodObject<{
|
|
43
|
+
field: z.ZodString;
|
|
44
|
+
dir: z.ZodOptional<z.ZodEnum<{
|
|
45
|
+
asc: "asc";
|
|
46
|
+
desc: "desc";
|
|
47
|
+
}>>;
|
|
48
|
+
}, z.core.$strip>;
|
|
49
|
+
/** The whole query. At least one of `groupBy` / `aggregates` must be
|
|
50
|
+
* present: bare `groupBy` is a DISTINCT listing, bare `aggregates` a
|
|
51
|
+
* whole-file scalar row, together a grouped aggregation. */
|
|
52
|
+
export declare const CollectionQueryZ: z.ZodObject<{
|
|
53
|
+
groupBy: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
54
|
+
aggregates: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
55
|
+
op: z.ZodEnum<{
|
|
56
|
+
sum: "sum";
|
|
57
|
+
count: "count";
|
|
58
|
+
avg: "avg";
|
|
59
|
+
min: "min";
|
|
60
|
+
max: "max";
|
|
61
|
+
}>;
|
|
62
|
+
column: z.ZodOptional<z.ZodString>;
|
|
63
|
+
}, z.core.$strip>>>;
|
|
64
|
+
where: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
65
|
+
field: z.ZodString;
|
|
66
|
+
op: z.ZodEnum<{
|
|
67
|
+
in: "in";
|
|
68
|
+
eq: "eq";
|
|
69
|
+
ne: "ne";
|
|
70
|
+
gt: "gt";
|
|
71
|
+
gte: "gte";
|
|
72
|
+
lt: "lt";
|
|
73
|
+
lte: "lte";
|
|
74
|
+
contains: "contains";
|
|
75
|
+
}>;
|
|
76
|
+
value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>>]>;
|
|
77
|
+
}, z.core.$strip>>>;
|
|
78
|
+
orderBy: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
79
|
+
field: z.ZodString;
|
|
80
|
+
dir: z.ZodOptional<z.ZodEnum<{
|
|
81
|
+
asc: "asc";
|
|
82
|
+
desc: "desc";
|
|
83
|
+
}>>;
|
|
84
|
+
}, z.core.$strip>>>;
|
|
85
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
86
|
+
}, z.core.$strip>;
|
|
87
|
+
export type CollectionQueryAggregate = z.infer<typeof QueryAggregateZ>;
|
|
88
|
+
export type CollectionQueryWhere = z.infer<typeof QueryWhereZ>;
|
|
89
|
+
export type CollectionQueryOrder = z.infer<typeof QueryOrderZ>;
|
|
90
|
+
export type CollectionQuery = z.infer<typeof CollectionQueryZ>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { ActionSpecZ, CollectionSchemaZ, CustomViewZ, DynamicIconRuleZ, DynamicIconSourceZ, DynamicIconSpecZ, EveryFieldDrivenZ, EveryLiteralZ, EveryZ, FieldSpecZ, IngestZ, SpawnZ, SubFieldSpecZ, WhenZ } from './schemaZ';
|
|
2
|
+
import { ActionSpecZ, CollectionSchemaZ, CustomViewZ, DataSourceZ, DynamicIconRuleZ, DynamicIconSourceZ, DynamicIconSpecZ, EveryFieldDrivenZ, EveryLiteralZ, EveryZ, FieldSpecZ, IngestZ, SpawnZ, SubFieldSpecZ, WhenZ } from './schemaZ';
|
|
3
3
|
/** Minimal "this collection is a feed" descriptor carried on the schema.
|
|
4
4
|
* Deliberately narrow — the canonical collection contract stays
|
|
5
5
|
* independent of the host's feeds subsystem. The host's richer retrieval
|
|
@@ -138,14 +138,27 @@ export type DynamicIconSpec = z.infer<typeof DynamicIconSpecZ>;
|
|
|
138
138
|
* union on `kind` (declarative retrievers | agent worker). The feeds
|
|
139
139
|
* subsystem's `IngestSpec` is the same union under its historical name. */
|
|
140
140
|
export type CollectionIngestSpec = z.infer<typeof IngestZ>;
|
|
141
|
+
/** The `dataSource` block: this collection's records are the rows of an
|
|
142
|
+
* external read-only data file (v1: CSV). See `DataSourceZ`. */
|
|
143
|
+
export type CollectionDataSource = z.infer<typeof DataSourceZ>;
|
|
141
144
|
/** The whole `schema.json` contract. Key-level docs live on
|
|
142
145
|
* `CollectionSchemaZ` in `./schemaZ`. */
|
|
143
146
|
export type CollectionSchema = z.infer<typeof CollectionSchemaZ>;
|
|
147
|
+
/** True when `schema` declares an external `dataSource` — i.e. the
|
|
148
|
+
* collection is READ-ONLY through every UI/tool write path (updates
|
|
149
|
+
* happen by editing/replacing the data file itself). Isomorphic: both
|
|
150
|
+
* the server write guards and the client's control hiding key off this
|
|
151
|
+
* one predicate. */
|
|
152
|
+
export declare function isReadOnlySchema(schema: Pick<CollectionSchema, "dataSource">): boolean;
|
|
144
153
|
export interface CollectionSummary {
|
|
145
154
|
slug: string;
|
|
146
155
|
title: string;
|
|
147
156
|
icon: string;
|
|
148
157
|
source: CollectionSource;
|
|
158
|
+
/** Present (true) when the collection is backed by an external
|
|
159
|
+
* `dataSource` and therefore read-only in every UI/tool write path.
|
|
160
|
+
* Absent-when-writable, matching the other optional summary flags. */
|
|
161
|
+
readonly?: true;
|
|
149
162
|
/** Slugs of the source collection(s) a `dynamicIcon` icon was computed
|
|
150
163
|
* from — present only when `schema.dynamicIcon` is set. Lets a client
|
|
151
164
|
* know which collection change-channel(s) to watch for a live icon
|
|
@@ -608,10 +608,27 @@ export declare const DynamicIconSpecZ: z.ZodObject<{
|
|
|
608
608
|
}, z.core.$strip>>;
|
|
609
609
|
fallback: z.ZodOptional<z.ZodString>;
|
|
610
610
|
}, z.core.$strip>;
|
|
611
|
+
/** External-data collection: the records ARE the rows of a user-supplied
|
|
612
|
+
* data file (v1: CSV), queried through DuckDB — never copied into
|
|
613
|
+
* `<dataDir>/<id>.json` files. Declaring `dataSource` makes the collection
|
|
614
|
+
* **read-only** in every UI/tool write path; updates happen by replacing /
|
|
615
|
+
* editing the file itself (file-watch republishes the views). `path` is
|
|
616
|
+
* workspace-relative and containment-checked exactly like `dataPath`. The
|
|
617
|
+
* row-id column is the schema's existing `primaryKey` — there is
|
|
618
|
+
* deliberately no second key concept here.
|
|
619
|
+
* See plans/feat-collection-csv-duckdb-source.md. */
|
|
620
|
+
export declare const DataSourceZ: z.ZodObject<{
|
|
621
|
+
type: z.ZodLiteral<"csv">;
|
|
622
|
+
path: z.ZodString;
|
|
623
|
+
}, z.core.$strip>;
|
|
611
624
|
export declare const CollectionSchemaZ: z.ZodObject<{
|
|
612
625
|
title: z.ZodString;
|
|
613
626
|
icon: z.ZodString;
|
|
614
|
-
dataPath: z.ZodString
|
|
627
|
+
dataPath: z.ZodOptional<z.ZodString>;
|
|
628
|
+
dataSource: z.ZodOptional<z.ZodObject<{
|
|
629
|
+
type: z.ZodLiteral<"csv">;
|
|
630
|
+
path: z.ZodString;
|
|
631
|
+
}, z.core.$strip>>;
|
|
615
632
|
primaryKey: z.ZodString;
|
|
616
633
|
singleton: z.ZodOptional<z.ZodString>;
|
|
617
634
|
fields: z.ZodRecord<z.ZodString, z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_promptSafety = require("../promptSafety-
|
|
2
|
+
const require_promptSafety = require("../promptSafety-DRd15gQ6.cjs");
|
|
3
3
|
const require_errorMessage = require("../errorMessage--LvXe_Jx.cjs");
|
|
4
4
|
//#region src/collection/core/presentCollection.ts
|
|
5
5
|
var TOOL_NAME = "presentCollection";
|
|
@@ -38,7 +38,7 @@ var executePresentCollection = async (_context, args) => {
|
|
|
38
38
|
message: `Presented collection ${itemId ? `${collectionSlug} / ${itemId}` : collectionSlug}`,
|
|
39
39
|
data,
|
|
40
40
|
jsonData: data,
|
|
41
|
-
instructions: "The collection has been presented to the user as an interactive card. They can browse
|
|
41
|
+
instructions: "The collection has been presented to the user as an interactive card. They can browse and open records directly; on a writable collection they can also edit, create, and delete (a read-only dataSource collection shows no edit controls — its records change by editing the backing data file). No further action is needed unless they ask."
|
|
42
42
|
};
|
|
43
43
|
};
|
|
44
44
|
//#endregion
|
|
@@ -478,6 +478,7 @@ exports.fieldVisible = require_promptSafety.fieldVisible;
|
|
|
478
478
|
exports.firstDateField = require_promptSafety.firstDateField;
|
|
479
479
|
exports.firstMissingRequiredField = firstMissingRequiredField;
|
|
480
480
|
exports.isFieldDrivenEvery = require_promptSafety.isFieldDrivenEvery;
|
|
481
|
+
exports.isReadOnlySchema = require_promptSafety.isReadOnlySchema;
|
|
481
482
|
exports.isSafeRecordId = require_promptSafety.isSafeRecordId;
|
|
482
483
|
exports.isSafeSlug = require_promptSafety.isSafeSlug;
|
|
483
484
|
exports.isSortableField = isSortableField;
|