@mulmoclaude/core 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/assets/helps/billing-clients-worklog.md +215 -0
  2. package/assets/helps/billing-invoice.md +458 -0
  3. package/assets/helps/business.md +104 -0
  4. package/assets/helps/collection-skills.md +810 -0
  5. package/assets/helps/custom-view.md +433 -0
  6. package/assets/helps/feeds.md +114 -0
  7. package/assets/helps/gemini.md +57 -0
  8. package/assets/helps/github.md +23 -0
  9. package/assets/helps/guide.md +61 -0
  10. package/assets/helps/index.md +89 -0
  11. package/assets/helps/lessons-collection.md +400 -0
  12. package/assets/helps/mulmoscript.md +249 -0
  13. package/assets/helps/portfolio-tracker.md +211 -0
  14. package/assets/helps/presentation-deck.md +828 -0
  15. package/assets/helps/presenthtml.md +89 -0
  16. package/assets/helps/sandbox.md +97 -0
  17. package/assets/helps/spreadsheet.md +43 -0
  18. package/assets/helps/storyteller.md +101 -0
  19. package/assets/helps/telegram.md +136 -0
  20. package/assets/helps/todo-collection.md +140 -0
  21. package/assets/helps/vocabulary.md +109 -0
  22. package/assets/helps/wiki.md +168 -0
  23. package/assets/skills-preset/mc-cooking-coach/SKILL.md +217 -0
  24. package/assets/skills-preset/mc-library/SKILL.md +188 -0
  25. package/assets/skills-preset/mc-manage-automations/SKILL.md +119 -0
  26. package/assets/skills-preset/mc-manage-skills/SKILL.md +141 -0
  27. package/assets/skills-preset/mc-wiki-deep-lint/SKILL.md +108 -0
  28. package/assets/skills-preset/mc-wiki-health-check/SKILL.md +61 -0
  29. package/assets/skills-preset/mc-wiki-ingest/SKILL.md +182 -0
  30. package/assets/skills-preset/mc-wiki-promote/SKILL.md +175 -0
  31. package/assets/skills-preset/mc-zenn/SKILL.md +136 -0
  32. package/dist/chunk-CKQMccvm.cjs +28 -0
  33. package/dist/collection/core/actionVisible.d.ts +34 -0
  34. package/dist/collection/core/calendarGrid.d.ts +120 -0
  35. package/dist/collection/core/deriveAll.d.ts +38 -0
  36. package/dist/collection/core/derivedFormula.d.ts +18 -0
  37. package/dist/collection/core/draft.d.ts +18 -0
  38. package/dist/collection/core/enumColors.d.ts +33 -0
  39. package/dist/collection/core/errorMessage.d.ts +4 -0
  40. package/dist/collection/core/itemLabel.d.ts +12 -0
  41. package/dist/collection/core/presentCollection.d.ts +13 -0
  42. package/dist/collection/core/promptSafety.d.ts +1 -0
  43. package/dist/collection/core/schema.d.ts +355 -0
  44. package/dist/collection/core/shortHexId.d.ts +8 -0
  45. package/dist/collection/core/sortItems.d.ts +29 -0
  46. package/dist/collection/core/uiTypes.d.ts +106 -0
  47. package/dist/collection/index.cjs +793 -0
  48. package/dist/collection/index.cjs.map +1 -0
  49. package/dist/collection/index.d.ts +14 -0
  50. package/dist/collection/index.js +740 -0
  51. package/dist/collection/index.js.map +1 -0
  52. package/dist/collection/paths.cjs +44 -0
  53. package/dist/collection/paths.cjs.map +1 -0
  54. package/dist/collection/paths.js +41 -0
  55. package/dist/collection/paths.js.map +1 -0
  56. package/dist/collection/server/atomic.d.ts +1 -0
  57. package/dist/collection/server/delete.d.ts +38 -0
  58. package/dist/collection/server/derive.d.ts +8 -0
  59. package/dist/collection/server/discoveredCollection.d.ts +18 -0
  60. package/dist/collection/server/discovery.d.ts +227 -0
  61. package/dist/collection/server/host.d.ts +77 -0
  62. package/dist/collection/server/index.cjs +1721 -0
  63. package/dist/collection/server/index.cjs.map +1 -0
  64. package/dist/collection/server/index.d.ts +11 -0
  65. package/dist/collection/server/index.js +1671 -0
  66. package/dist/collection/server/index.js.map +1 -0
  67. package/dist/collection/server/io.d.ts +114 -0
  68. package/dist/collection/server/paths.d.ts +52 -0
  69. package/dist/collection/server/spawn.d.ts +55 -0
  70. package/dist/collection/server/templatePath.d.ts +25 -0
  71. package/dist/collection/server/util.d.ts +3 -0
  72. package/dist/collection/server/validate.d.ts +19 -0
  73. package/dist/collection/server/views.d.ts +20 -0
  74. package/dist/deriveAll-C15OpM3K.cjs +399 -0
  75. package/dist/deriveAll-C15OpM3K.cjs.map +1 -0
  76. package/dist/deriveAll-C6BYnpBL.js +364 -0
  77. package/dist/deriveAll-C6BYnpBL.js.map +1 -0
  78. package/dist/file-change/index.cjs +72 -0
  79. package/dist/file-change/index.cjs.map +1 -0
  80. package/dist/file-change/index.d.ts +43 -0
  81. package/dist/file-change/index.js +66 -0
  82. package/dist/file-change/index.js.map +1 -0
  83. package/dist/notifier/engine.d.ts +72 -0
  84. package/dist/notifier/index.cjs +484 -0
  85. package/dist/notifier/index.cjs.map +1 -0
  86. package/dist/notifier/index.d.ts +3 -0
  87. package/dist/notifier/index.js +464 -0
  88. package/dist/notifier/index.js.map +1 -0
  89. package/dist/notifier/store.d.ts +18 -0
  90. package/dist/notifier/types.d.ts +118 -0
  91. package/dist/notifier/validate.d.ts +17 -0
  92. package/dist/scheduler/adapter.d.ts +48 -0
  93. package/dist/scheduler/index.cjs +352 -0
  94. package/dist/scheduler/index.cjs.map +1 -0
  95. package/dist/scheduler/index.d.ts +2 -0
  96. package/dist/scheduler/index.js +343 -0
  97. package/dist/scheduler/index.js.map +1 -0
  98. package/dist/scheduler/task-manager.d.ts +51 -0
  99. package/dist/whisper/client.cjs +241 -0
  100. package/dist/whisper/client.cjs.map +1 -0
  101. package/dist/whisper/client.d.ts +35 -0
  102. package/dist/whisper/client.js +239 -0
  103. package/dist/whisper/client.js.map +1 -0
  104. package/dist/whisper/ffmpeg.d.ts +6 -0
  105. package/dist/whisper/index.cjs +433 -0
  106. package/dist/whisper/index.cjs.map +1 -0
  107. package/dist/whisper/index.d.ts +5 -0
  108. package/dist/whisper/index.js +425 -0
  109. package/dist/whisper/index.js.map +1 -0
  110. package/dist/whisper/internal.d.ts +11 -0
  111. package/dist/whisper/models.d.ts +49 -0
  112. package/dist/whisper/sidecar.d.ts +8 -0
  113. package/dist/whisper/whisper.d.ts +28 -0
  114. package/dist/workspace-setup/assets.d.ts +10 -0
  115. package/dist/workspace-setup/index.d.ts +3 -0
  116. package/dist/workspace-setup/index.js +556 -0
  117. package/dist/workspace-setup/index.js.map +1 -0
  118. package/dist/workspace-setup/slug.d.ts +6 -0
  119. package/dist/workspace-setup/slug.js +13 -0
  120. package/dist/workspace-setup/slug.js.map +1 -0
  121. package/dist/workspace-setup/sync.d.ts +94 -0
  122. package/package.json +95 -0
@@ -0,0 +1,740 @@
1
+ import { a as INGEST_KINDS, i as FEED_SCHEDULES, n as resolveRowRefs, o as isFieldDrivenEvery, r as evaluateDerived, t as deriveAll } from "../deriveAll-C6BYnpBL.js";
2
+ //#region src/collection/core/presentCollection.ts
3
+ var TOOL_NAME = "presentCollection";
4
+ var TOOL_DEFINITION = {
5
+ type: "function",
6
+ name: TOOL_NAME,
7
+ description: "Display a schema-driven collection inline in the chat as an interactive, editable card. Shows the collection's list of records. Pass `itemId` to open one specific record on mount.",
8
+ parameters: {
9
+ type: "object",
10
+ properties: {
11
+ collectionSlug: {
12
+ type: "string",
13
+ description: "The slug of the collection to display (e.g. 'clients', 'invoices', 'contacts')."
14
+ },
15
+ itemId: {
16
+ type: "string",
17
+ description: "Optional primary-key value of a single record to open in detail view on mount. Omit to show the full list."
18
+ }
19
+ },
20
+ required: ["collectionSlug"]
21
+ },
22
+ prompt: `After making changes to schema-driven collections, use ${TOOL_NAME} to present either the collection or the item`
23
+ };
24
+ var executePresentCollection = async (_context, args) => {
25
+ const collectionSlug = typeof args?.collectionSlug === "string" ? args.collectionSlug.trim() : "";
26
+ if (!collectionSlug) return {
27
+ message: "presentCollection error: collectionSlug is required",
28
+ instructions: "Tell the user you couldn't display the collection because no collection was specified, and ask which collection they mean."
29
+ };
30
+ const itemId = typeof args.itemId === "string" && args.itemId.trim().length > 0 ? args.itemId.trim() : void 0;
31
+ const data = itemId ? {
32
+ collectionSlug,
33
+ itemId
34
+ } : { collectionSlug };
35
+ return {
36
+ message: `Presented collection ${itemId ? `${collectionSlug} / ${itemId}` : collectionSlug}`,
37
+ data,
38
+ jsonData: data,
39
+ instructions: "The collection has been presented to the user as an interactive card. They can browse, open, edit, create, and delete records directly. No further action is needed unless they ask."
40
+ };
41
+ };
42
+ //#endregion
43
+ //#region src/collection/core/enumColors.ts
44
+ var PALETTE = [
45
+ {
46
+ card: "border-indigo-200 bg-indigo-50 text-indigo-600 hover:bg-indigo-100",
47
+ dot: "bg-indigo-500",
48
+ badge: "bg-indigo-100 text-indigo-700",
49
+ border: "border-indigo-200"
50
+ },
51
+ {
52
+ card: "border-sky-200 bg-sky-50 text-sky-600 hover:bg-sky-100",
53
+ dot: "bg-sky-500",
54
+ badge: "bg-sky-100 text-sky-700",
55
+ border: "border-sky-200"
56
+ },
57
+ {
58
+ card: "border-cyan-200 bg-cyan-50 text-cyan-600 hover:bg-cyan-100",
59
+ dot: "bg-cyan-500",
60
+ badge: "bg-cyan-100 text-cyan-700",
61
+ border: "border-cyan-200"
62
+ },
63
+ {
64
+ card: "border-teal-200 bg-teal-50 text-teal-600 hover:bg-teal-100",
65
+ dot: "bg-teal-500",
66
+ badge: "bg-teal-100 text-teal-700",
67
+ border: "border-teal-200"
68
+ },
69
+ {
70
+ card: "border-emerald-200 bg-emerald-50 text-emerald-600 hover:bg-emerald-100",
71
+ dot: "bg-emerald-500",
72
+ badge: "bg-emerald-100 text-emerald-700",
73
+ border: "border-emerald-200"
74
+ },
75
+ {
76
+ card: "border-lime-200 bg-lime-50 text-lime-600 hover:bg-lime-100",
77
+ dot: "bg-lime-500",
78
+ badge: "bg-lime-100 text-lime-700",
79
+ border: "border-lime-200"
80
+ },
81
+ {
82
+ card: "border-violet-200 bg-violet-50 text-violet-600 hover:bg-violet-100",
83
+ dot: "bg-violet-500",
84
+ badge: "bg-violet-100 text-violet-700",
85
+ border: "border-violet-200"
86
+ },
87
+ {
88
+ card: "border-fuchsia-200 bg-fuchsia-50 text-fuchsia-600 hover:bg-fuchsia-100",
89
+ dot: "bg-fuchsia-500",
90
+ badge: "bg-fuchsia-100 text-fuchsia-700",
91
+ border: "border-fuchsia-200"
92
+ }
93
+ ];
94
+ /** Neutral styling for the empty / Uncategorized bucket — never a palette
95
+ * colour, so an unset or unknown value reads grey across every surface. */
96
+ var ENUM_NEUTRAL = {
97
+ card: "border-slate-200 bg-slate-50 text-slate-500 hover:bg-slate-100",
98
+ dot: "bg-slate-300",
99
+ badge: "bg-slate-100 text-slate-500",
100
+ border: "border-slate-200"
101
+ };
102
+ /** The urgent notification colour (red), matching the bell's `urgent`
103
+ * severity. The first value a schema's `notifyWhen` flags reads this. */
104
+ var ENUM_ALERT = {
105
+ card: "border-red-200 bg-red-50 text-red-600 hover:bg-red-100",
106
+ dot: "bg-red-500",
107
+ badge: "bg-red-100 text-red-700",
108
+ border: "border-red-200"
109
+ };
110
+ /** The nudge notification colour (amber), matching the bell's `nudge`
111
+ * severity. Flagged `notifyWhen` values after the first read this. */
112
+ var ENUM_NUDGE = {
113
+ card: "border-amber-200 bg-amber-50 text-amber-600 hover:bg-amber-100",
114
+ dot: "bg-amber-500",
115
+ badge: "bg-amber-100 text-amber-700",
116
+ border: "border-amber-200"
117
+ };
118
+ /** Classes for the enum value at `index` in its field's `values` array. A
119
+ * negative index (value unset or not among the declared values) reads
120
+ * neutral. */
121
+ function enumColorClasses(index) {
122
+ if (index < 0) return ENUM_NEUTRAL;
123
+ return PALETTE[index % PALETTE.length] ?? ENUM_NEUTRAL;
124
+ }
125
+ /** Index of `value` within an enum field's declared `values`, or -1 when the
126
+ * value is empty / unknown (→ neutral). */
127
+ function enumValueIndex(values, value) {
128
+ if (value === void 0 || value === null || value === "") return -1;
129
+ return values?.indexOf(String(value)) ?? -1;
130
+ }
131
+ /** The flagged values when `fieldKey` is the schema's `notifyWhen` target (the
132
+ * "notification enum"); undefined for every other field. */
133
+ function notifyValuesFor(schema, fieldKey) {
134
+ const spec = schema.notifyWhen;
135
+ return spec && spec.field === fieldKey ? spec.in : void 0;
136
+ }
137
+ /** Resolve a value's colour for enum field `fieldKey`:
138
+ * - Notification enum (`notifyWhen` targets it): the first flagged value (in
139
+ * `notifyWhen.in` order, the most urgent) reads notification red, the rest
140
+ * amber, and every non-flagged value neutral grey.
141
+ * - Any other enum: the standard palette by the value's declared index. */
142
+ function resolveEnumColor(schema, fieldKey, value) {
143
+ const notifyValues = notifyValuesFor(schema, fieldKey);
144
+ if (notifyValues) {
145
+ const str = value === void 0 || value === null ? "" : String(value);
146
+ const rank = notifyValues.indexOf(str);
147
+ if (rank < 0) return ENUM_NEUTRAL;
148
+ return rank === 0 ? ENUM_ALERT : ENUM_NUDGE;
149
+ }
150
+ return enumColorClasses(enumValueIndex(schema.fields[fieldKey]?.values, value));
151
+ }
152
+ //#endregion
153
+ //#region src/collection/core/actionVisible.ts
154
+ /** Core matcher:
155
+ * - no `when` ⇒ always true (visible);
156
+ * - otherwise true only when `record[when.field]` is present and its
157
+ * stringified value is one of `when.in`.
158
+ * A missing/undefined/null field is treated as "not a match"
159
+ * (hidden), so a status-gated target never shows on a record that
160
+ * lacks the status. */
161
+ function whenMatches(when, record) {
162
+ if (!when) return true;
163
+ const value = record[when.field];
164
+ if (value === void 0 || value === null) return false;
165
+ return when.in.includes(String(value));
166
+ }
167
+ /** True when the action's button should render against `record`
168
+ * (see whenMatches). */
169
+ function actionVisible(action, record) {
170
+ return whenMatches(action.when, record);
171
+ }
172
+ /** True when the field should render against `record`. A field with
173
+ * no `when` is always shown; otherwise it's shown only when the
174
+ * record matches (e.g. hide a rating field until `visited` is true).
175
+ * Purely presentational — a hidden field's stored value is never
176
+ * altered, so toggling the gate back on restores it. */
177
+ function fieldVisible(field, record) {
178
+ return whenMatches(field.when, record);
179
+ }
180
+ //#endregion
181
+ //#region src/collection/core/draft.ts
182
+ /** A fresh, empty row draft for a `table` field's sub-schema. */
183
+ function emptyRow(subFields) {
184
+ const text = {};
185
+ const bool = {};
186
+ const boolOriginallyPresent = {};
187
+ const boolTouched = {};
188
+ for (const [subKey, subField] of Object.entries(subFields)) if (subField.type === "boolean") {
189
+ bool[subKey] = false;
190
+ boolOriginallyPresent[subKey] = false;
191
+ boolTouched[subKey] = false;
192
+ } else text[subKey] = "";
193
+ return {
194
+ text,
195
+ bool,
196
+ boolOriginallyPresent,
197
+ boolTouched
198
+ };
199
+ }
200
+ /** Build a row draft from an existing persisted row. */
201
+ function rowFromItem(item, subFields) {
202
+ const text = {};
203
+ const bool = {};
204
+ const boolOriginallyPresent = {};
205
+ const boolTouched = {};
206
+ for (const [subKey, subField] of Object.entries(subFields)) {
207
+ const raw = item[subKey];
208
+ if (subField.type === "boolean") {
209
+ bool[subKey] = raw === true;
210
+ boolOriginallyPresent[subKey] = typeof raw === "boolean";
211
+ boolTouched[subKey] = false;
212
+ } else text[subKey] = raw === void 0 || raw === null ? "" : String(raw);
213
+ }
214
+ return {
215
+ text,
216
+ bool,
217
+ boolOriginallyPresent,
218
+ boolTouched
219
+ };
220
+ }
221
+ /** Decide whether a boolean field's draft value should be emitted (vs.
222
+ * omitted so a downstream default applies). */
223
+ function shouldEmitBoolean(state, key, field) {
224
+ return Boolean(state.boolOriginallyPresent[key] || state.boolTouched[key] || field.required);
225
+ }
226
+ /** Convert a scalar draft slot to its persisted form. `undefined` = omit. */
227
+ function scalarDraftToValue(raw, fieldType) {
228
+ if (raw === void 0 || raw === "") return void 0;
229
+ if (fieldType === "number" || fieldType === "money") {
230
+ const num = Number(raw);
231
+ return Number.isFinite(num) ? num : raw;
232
+ }
233
+ return raw;
234
+ }
235
+ /** Convert one table row draft to its persisted record. */
236
+ function rowDraftToRecord(rowDraft, subFields) {
237
+ const row = {};
238
+ for (const [subKey, subField] of Object.entries(subFields)) {
239
+ if (subField.type === "boolean") {
240
+ const value = rowDraft.bool[subKey] === true;
241
+ if (rowDraft.boolOriginallyPresent[subKey] || rowDraft.boolTouched[subKey] || value || subField.required) row[subKey] = value;
242
+ continue;
243
+ }
244
+ const value = scalarDraftToValue(rowDraft.text[subKey], subField.type);
245
+ if (value !== void 0) row[subKey] = value;
246
+ }
247
+ return row;
248
+ }
249
+ /** Convert a full edit draft to the record to persist. */
250
+ function draftToRecord(state, schema) {
251
+ const record = {};
252
+ for (const [key, field] of Object.entries(schema.fields)) {
253
+ if (field.type === "derived" || field.type === "embed" || field.type === "toggle") continue;
254
+ if (field.type === "boolean") {
255
+ if (shouldEmitBoolean(state, key, field)) record[key] = state.bool[key] === true;
256
+ continue;
257
+ }
258
+ if (field.type === "table" && field.of) {
259
+ const subFields = field.of;
260
+ record[key] = (state.table[key] ?? []).map((rowDraft) => rowDraftToRecord(rowDraft, subFields));
261
+ continue;
262
+ }
263
+ const value = scalarDraftToValue(state.text[key], field.type);
264
+ if (value !== void 0) record[key] = value;
265
+ }
266
+ return record;
267
+ }
268
+ /** Normalise a raw inline-edit input (table-cell checkbox/select) to its
269
+ * persisted form. Mirrors `draftToRecord`'s boolean strictness; an empty
270
+ * enum selection (the placeholder option) clears the field via `undefined`. */
271
+ function coerceInlineValue(field, raw) {
272
+ if (field.type === "boolean") return raw === true;
273
+ return raw === "" ? void 0 : raw;
274
+ }
275
+ /** Build the full record to PUT for a single-cell inline edit, without
276
+ * mutating `item`. A `undefined` value omits the key (enum cleared),
277
+ * matching `draftToRecord`'s omission semantics. */
278
+ function buildUpdatedRecord(item, key, value) {
279
+ if (value === void 0) {
280
+ const { [key]: __omit, ...rest } = item;
281
+ return rest;
282
+ }
283
+ return {
284
+ ...item,
285
+ [key]: value
286
+ };
287
+ }
288
+ /** Empty for required-field validation — NOT a truthiness check (a
289
+ * numeric `0` is a filled value). */
290
+ function isMissingDraftValue(value) {
291
+ return value === void 0 || value === null || value === "";
292
+ }
293
+ /** Label of the first required table sub-field empty in any row, else null. */
294
+ function firstMissingTableSubField(field, rows) {
295
+ if (!field.of || !rows) return null;
296
+ for (let rowIdx = 0; rowIdx < rows.length; rowIdx++) {
297
+ const row = rows[rowIdx];
298
+ for (const [subKey, subField] of Object.entries(field.of)) {
299
+ if (!subField.required || subField.type === "boolean") continue;
300
+ if (isMissingDraftValue(row.text[subKey])) return `${field.label} #${rowIdx + 1}: ${subField.label}`;
301
+ }
302
+ }
303
+ return null;
304
+ }
305
+ function validateOneField(key, field, draft, record) {
306
+ if (!fieldVisible(field, record)) return null;
307
+ if (field.type === "table" && field.of) {
308
+ const rows = draft.table[key];
309
+ if (field.required && (!rows || rows.length === 0)) return field.label;
310
+ return firstMissingTableSubField(field, rows);
311
+ }
312
+ if (!field.required) return null;
313
+ if (draft.mode === "create" && field.primary === true) return null;
314
+ if (field.type === "boolean" || field.type === "derived" || field.type === "embed" || field.type === "toggle") return null;
315
+ return isMissingDraftValue(draft.text[key]) ? field.label : null;
316
+ }
317
+ /** Human-readable label of the first missing required field, or null. */
318
+ function firstMissingRequiredField(draft, schema) {
319
+ const record = draftToRecord(draft, schema);
320
+ for (const [key, field] of Object.entries(schema.fields)) {
321
+ const missing = validateOneField(key, field, draft, record);
322
+ if (missing) return missing;
323
+ }
324
+ return null;
325
+ }
326
+ //#endregion
327
+ //#region src/collection/core/sortItems.ts
328
+ var EMPTY = { empty: true };
329
+ /** Field types that render no value text in the table, so offer no sort. */
330
+ var NON_SORTABLE = new Set([
331
+ "markdown",
332
+ "table",
333
+ "image",
334
+ "file",
335
+ "embed"
336
+ ]);
337
+ function isSortableField(field) {
338
+ return !NON_SORTABLE.has(field.type);
339
+ }
340
+ /** Cycle one column's state: none → asc → desc → none. */
341
+ function nextSortDirection(current) {
342
+ if (current === null) return "asc";
343
+ if (current === "asc") return "desc";
344
+ return null;
345
+ }
346
+ function numericSortValue(raw) {
347
+ if (raw == null || raw === "") return EMPTY;
348
+ const num = typeof raw === "number" ? raw : Number(raw);
349
+ return Number.isFinite(num) ? {
350
+ empty: false,
351
+ num
352
+ } : EMPTY;
353
+ }
354
+ function stringSortValue(raw) {
355
+ if (raw == null) return EMPTY;
356
+ const str = String(raw);
357
+ return str.trim() === "" ? EMPTY : {
358
+ empty: false,
359
+ str
360
+ };
361
+ }
362
+ function dateSortValue(raw) {
363
+ if (raw == null || raw === "") return EMPTY;
364
+ const epoch = Date.parse(String(raw));
365
+ return Number.isNaN(epoch) ? stringSortValue(raw) : {
366
+ empty: false,
367
+ num: epoch
368
+ };
369
+ }
370
+ /** Enum sorts by the value's index in the declared `values` list. A value
371
+ * outside the list (or unset) is treated as empty → last. */
372
+ function enumSortValue(values, raw) {
373
+ if (raw == null || raw === "") return EMPTY;
374
+ const idx = values ? values.indexOf(String(raw)) : -1;
375
+ return idx < 0 ? EMPTY : {
376
+ empty: false,
377
+ num: idx
378
+ };
379
+ }
380
+ /** Boolean / toggle: false < true, never empty (unset reads as false). */
381
+ function boolSortValue(checked) {
382
+ return {
383
+ empty: false,
384
+ num: checked ? 1 : 0
385
+ };
386
+ }
387
+ function compareSortValues(left, right) {
388
+ if (left.num !== void 0 && right.num !== void 0) return left.num - right.num;
389
+ const leftStr = left.str ?? String(left.num ?? "");
390
+ const rightStr = right.str ?? String(right.num ?? "");
391
+ return leftStr.localeCompare(rightStr);
392
+ }
393
+ /** Stable sort of `items` by `valueOf`. Empties always last; ties hold
394
+ * source order. Returns a new array (does not mutate `items`). */
395
+ function sortItems(items, direction, valueOf) {
396
+ const dir = direction === "asc" ? 1 : -1;
397
+ return items.map((item, index) => ({
398
+ item,
399
+ index,
400
+ sv: valueOf(item)
401
+ })).sort((left, right) => {
402
+ if (left.sv.empty || right.sv.empty) {
403
+ if (left.sv.empty && right.sv.empty) return left.index - right.index;
404
+ return left.sv.empty ? 1 : -1;
405
+ }
406
+ const base = compareSortValues(left.sv, right.sv);
407
+ return base !== 0 ? base * dir : left.index - right.index;
408
+ }).map((decorated) => decorated.item);
409
+ }
410
+ //#endregion
411
+ //#region src/collection/core/itemLabel.ts
412
+ var LABEL_FIELD_TYPES = new Set([
413
+ "string",
414
+ "text",
415
+ "markdown",
416
+ "email"
417
+ ]);
418
+ /** Which field labels a record: the schema's explicit `displayField`, else the
419
+ * first non-primary text-like field (so a collection without a displayField
420
+ * shows e.g. the title rather than the opaque primaryKey), else null → the
421
+ * caller falls back to the primary-key value. */
422
+ function labelFieldFor(schema) {
423
+ if (schema.displayField) return schema.displayField;
424
+ for (const [key, spec] of Object.entries(schema.fields)) if (key !== schema.primaryKey && LABEL_FIELD_TYPES.has(spec.type)) return key;
425
+ return null;
426
+ }
427
+ /** A record's primary-key value as a string. */
428
+ function itemIdOf(item, schema) {
429
+ return String(item[schema.primaryKey] ?? "");
430
+ }
431
+ /** A record's display label: the resolved `labelField` value, else the
432
+ * primary key. Pass the result of `labelFieldFor(schema)` as `labelField`
433
+ * (compute it once per render rather than per item). */
434
+ function itemLabelOf(item, schema, labelField) {
435
+ if (labelField) {
436
+ const value = item[labelField];
437
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
438
+ const text = String(value);
439
+ if (text.length > 0) return text;
440
+ }
441
+ }
442
+ return itemIdOf(item, schema);
443
+ }
444
+ //#endregion
445
+ //#region src/collection/core/calendarGrid.ts
446
+ var MS_PER_DAY = 864e5;
447
+ var ISO_DATE_RE = /^(\d{4})-(\d{2})-(\d{2})$/;
448
+ var TWO_DIGIT_RE = /^\d{2}$/;
449
+ var CLOCK_RE = /(\d{1,2}):(\d{2})/g;
450
+ var RANGE_SEP_RE = /[-–—~〜~]/;
451
+ /** Minutes in a full day — the timeline's vertical extent. */
452
+ var MINUTES_PER_DAY = 1440;
453
+ function pad2(value) {
454
+ return String(value).padStart(2, "0");
455
+ }
456
+ /** Canonical `YYYY-MM-DD` string for a civil date. */
457
+ function ymdKey(ymd) {
458
+ return `${String(ymd.year).padStart(4, "0")}-${pad2(ymd.month)}-${pad2(ymd.day)}`;
459
+ }
460
+ /** Strictly parse a `YYYY-MM-DD` string into a civil date, rejecting
461
+ * anything that isn't a real calendar day (e.g. `2026-02-30`, `2026-13-01`).
462
+ * Returns null for non-strings and malformed values so callers can route
463
+ * records with no usable date into the "no date" tray rather than crash. */
464
+ function parseIsoDate(value) {
465
+ if (typeof value !== "string") return null;
466
+ const match = ISO_DATE_RE.exec(value.trim());
467
+ if (!match) return null;
468
+ const year = Number(match[1]);
469
+ const month = Number(match[2]);
470
+ const day = Number(match[3]);
471
+ const probe = new Date(Date.UTC(year, month - 1, day));
472
+ if (probe.getUTCFullYear() !== year || probe.getUTCMonth() !== month - 1 || probe.getUTCDate() !== day) return null;
473
+ return {
474
+ year,
475
+ month,
476
+ day
477
+ };
478
+ }
479
+ /** Minutes-of-day for an `HH:MM` pair, or null when out of range. */
480
+ function clockToMinutes(hours, minutes) {
481
+ if (hours < 0 || hours > 23 || minutes < 0 || minutes > 59) return null;
482
+ return hours * 60 + minutes;
483
+ }
484
+ /** Strictly parse a `YYYY-MM-DDTHH:MM` (optional `:SS`) datetime into its
485
+ * civil date and minutes-of-day. Returns null for anything that isn't a real
486
+ * calendar day or a valid 24h clock. */
487
+ function parseIsoDateTime(value) {
488
+ if (typeof value !== "string") return null;
489
+ const trimmed = value.trim();
490
+ const tIndex = trimmed.indexOf("T");
491
+ if (tIndex === -1) return null;
492
+ const ymd = parseIsoDate(trimmed.slice(0, tIndex));
493
+ if (!ymd) return null;
494
+ const parts = trimmed.slice(tIndex + 1).split(":");
495
+ if (parts.length < 2 || parts.length > 3 || !parts.every((part) => TWO_DIGIT_RE.test(part))) return null;
496
+ const minutes = clockToMinutes(Number(parts[0]), Number(parts[1]));
497
+ if (minutes === null) return null;
498
+ return {
499
+ ymd,
500
+ minutes
501
+ };
502
+ }
503
+ /** Civil date from either a `YYYY-MM-DD` or a `YYYY-MM-DDTHH:MM` value, so the
504
+ * month grid buckets date-only and datetime anchors alike. */
505
+ function dateOf(value) {
506
+ return parseIsoDate(value) ?? parseIsoDateTime(value)?.ymd ?? null;
507
+ }
508
+ /** Minutes-of-day from a datetime value, or null for date-only / invalid. */
509
+ function timeOf(value) {
510
+ return parseIsoDateTime(value)?.minutes ?? null;
511
+ }
512
+ /** Parse a free-form time-string field into start/end minutes-of-day.
513
+ * Handles the common shapes in user data:
514
+ * "14:00-17:00" → { start: 840, end: 1020 } (range → block)
515
+ * "17:00-" → { start: 1020, end: null } (open end → single line)
516
+ * "16:30" → { start: 990, end: null } (point in time → single line)
517
+ * "終日" / "" → null (no clock → all-day)
518
+ * Returns null when no clock token is parseable. */
519
+ function parseTimeRange(value) {
520
+ if (typeof value !== "string") return null;
521
+ const text = value.trim();
522
+ if (!text) return null;
523
+ const tokens = [...text.matchAll(CLOCK_RE)];
524
+ if (tokens.length === 0) return null;
525
+ const minutesOf = (match) => clockToMinutes(Number(match[1]), Number(match[2]));
526
+ if (!RANGE_SEP_RE.test(text)) {
527
+ const startMin = minutesOf(tokens[0]);
528
+ return startMin === null ? null : {
529
+ startMin,
530
+ endMin: null
531
+ };
532
+ }
533
+ const sepIndex = text.search(RANGE_SEP_RE);
534
+ let startMin = null;
535
+ let endMin = null;
536
+ for (const token of tokens) if ((token.index ?? 0) < sepIndex) startMin = minutesOf(token);
537
+ else endMin = minutesOf(token);
538
+ if (startMin === null) return null;
539
+ return {
540
+ startMin,
541
+ endMin
542
+ };
543
+ }
544
+ function ymdToUtcMs(ymd) {
545
+ return Date.UTC(ymd.year, ymd.month - 1, ymd.day);
546
+ }
547
+ function utcMsToYmd(epochMs) {
548
+ const date = new Date(epochMs);
549
+ return {
550
+ year: date.getUTCFullYear(),
551
+ month: date.getUTCMonth() + 1,
552
+ day: date.getUTCDate()
553
+ };
554
+ }
555
+ /** Chronological comparison: negative if `left` precedes `right`, 0 if the
556
+ * same day, positive if after. */
557
+ function compareYmd(left, right) {
558
+ return ymdToUtcMs(left) - ymdToUtcMs(right);
559
+ }
560
+ /** True iff `day` falls within the inclusive span `[span.start, span.end]`. */
561
+ function spanCoversDay(span, day) {
562
+ return compareYmd(span.start, day) <= 0 && compareYmd(day, span.end) <= 0;
563
+ }
564
+ /** Build the 6×7 (42-cell) grid for the given month, including the
565
+ * leading/trailing days of the adjacent months so every week is full.
566
+ * `month` is 1-12. `weekStartsOn` is 0 (Sunday) … 6 (Saturday). */
567
+ function buildMonthGrid(year, month, weekStartsOn = 0) {
568
+ const lead = (new Date(Date.UTC(year, month - 1, 1)).getUTCDay() - weekStartsOn + 7) % 7;
569
+ const startMs = Date.UTC(year, month - 1, 1) - lead * MS_PER_DAY;
570
+ const cells = [];
571
+ for (let i = 0; i < 42; i++) {
572
+ const ymd = utcMsToYmd(startMs + i * MS_PER_DAY);
573
+ cells.push({
574
+ ymd,
575
+ inMonth: ymd.year === year && ymd.month === month,
576
+ key: ymdKey(ymd)
577
+ });
578
+ }
579
+ return cells;
580
+ }
581
+ /** Resolve a record's calendar span from its date/datetime fields. Returns
582
+ * null when the anchor date is missing/invalid (→ the caller's "no date"
583
+ * tray). An end date that is missing, invalid, or earlier than the start
584
+ * collapses to a single-day span — never an inverted range.
585
+ *
586
+ * Times for the day (time-allocation) view come from, in priority order:
587
+ * 1. the clock on a `datetime` anchor/end value, else
588
+ * 2. `timeField` — a separate free-form time-string column (e.g. "14:00-17:00").
589
+ * A record with no resolvable clock has `startMin === endMin === null`. */
590
+ function recordSpan(item, anchorField, endField, timeField) {
591
+ const startRaw = item[anchorField];
592
+ const start = dateOf(startRaw);
593
+ if (!start) return null;
594
+ let end = start;
595
+ let startMin = timeOf(startRaw);
596
+ let endMin = null;
597
+ if (endField) {
598
+ const endRaw = item[endField];
599
+ const parsedEnd = dateOf(endRaw);
600
+ if (parsedEnd && compareYmd(parsedEnd, start) >= 0) {
601
+ end = parsedEnd;
602
+ endMin = timeOf(endRaw);
603
+ }
604
+ }
605
+ if (timeField && startMin === null && endMin === null) {
606
+ const range = parseTimeRange(item[timeField]);
607
+ if (range) ({startMin, endMin} = range);
608
+ }
609
+ return {
610
+ item,
611
+ start,
612
+ end,
613
+ startMin,
614
+ endMin
615
+ };
616
+ }
617
+ /** Split records into those that land on the calendar (with their spans)
618
+ * and those with no usable anchor date (the "no date" tray). Spans are
619
+ * sorted by start day so same-day stacking is stable across renders. */
620
+ function bucketRecords(items, anchorField, endField, timeField) {
621
+ const spans = [];
622
+ const noDate = [];
623
+ for (const item of items) {
624
+ const span = recordSpan(item, anchorField, endField, timeField);
625
+ if (span) spans.push(span);
626
+ else noDate.push(item);
627
+ }
628
+ spans.sort((left, right) => compareYmd(left.start, right.start));
629
+ return {
630
+ spans,
631
+ noDate
632
+ };
633
+ }
634
+ /** Project a record's span onto a single day for the time-allocation view, or
635
+ * null when the span doesn't cover that day. */
636
+ function daySlice(span, day) {
637
+ if (!spanCoversDay(span, day)) return null;
638
+ const hasStart = span.startMin !== null;
639
+ const hasEnd = span.endMin !== null;
640
+ if (!hasStart && !hasEnd) return {
641
+ kind: "allDay",
642
+ startMin: 0,
643
+ endMin: MINUTES_PER_DAY,
644
+ bleedsBefore: false,
645
+ bleedsAfter: false
646
+ };
647
+ const singleDay = compareYmd(span.start, span.end) === 0;
648
+ const isStartDay = compareYmd(day, span.start) === 0;
649
+ const isEndDay = compareYmd(day, span.end) === 0;
650
+ if (singleDay && hasStart && !hasEnd) return {
651
+ kind: "line",
652
+ startMin: span.startMin,
653
+ endMin: span.startMin,
654
+ bleedsBefore: false,
655
+ bleedsAfter: false
656
+ };
657
+ const startMin = isStartDay && hasStart ? span.startMin : 0;
658
+ const endMin = isEndDay && hasEnd ? span.endMin : MINUTES_PER_DAY;
659
+ if (singleDay && endMin <= startMin) return {
660
+ kind: "line",
661
+ startMin,
662
+ endMin: startMin,
663
+ bleedsBefore: false,
664
+ bleedsAfter: false
665
+ };
666
+ return {
667
+ kind: "block",
668
+ startMin,
669
+ endMin,
670
+ bleedsBefore: !isStartDay,
671
+ bleedsAfter: !isEndDay
672
+ };
673
+ }
674
+ function assignLanes(blocks) {
675
+ const order = [...blocks.keys()].sort((left, right) => blocks[left].startMin - blocks[right].startMin || blocks[left].endMin - blocks[right].endMin);
676
+ const result = blocks.map(() => ({
677
+ lane: 0,
678
+ lanes: 1
679
+ }));
680
+ let cluster = [];
681
+ let clusterEnd = Number.NEGATIVE_INFINITY;
682
+ const laneEnds = [];
683
+ const flush = () => {
684
+ for (const index of cluster) result[index].lanes = laneEnds.length;
685
+ cluster = [];
686
+ laneEnds.length = 0;
687
+ clusterEnd = Number.NEGATIVE_INFINITY;
688
+ };
689
+ for (const index of order) {
690
+ const block = blocks[index];
691
+ if (cluster.length > 0 && block.startMin >= clusterEnd) flush();
692
+ let lane = laneEnds.findIndex((end) => end <= block.startMin);
693
+ if (lane === -1) {
694
+ lane = laneEnds.length;
695
+ laneEnds.push(block.endMin);
696
+ } else laneEnds[lane] = block.endMin;
697
+ result[index].lane = lane;
698
+ cluster.push(index);
699
+ clusterEnd = Math.max(clusterEnd, block.endMin);
700
+ }
701
+ flush();
702
+ return result;
703
+ }
704
+ /** Month label key inputs — returns the 1st of the month as a `Date` so the
705
+ * component can feed it to `Intl.DateTimeFormat(locale, …)` for a localized
706
+ * "April 2026" header without this module taking a locale dependency. */
707
+ function monthAnchorDate(year, month) {
708
+ return new Date(Date.UTC(year, month - 1, 1));
709
+ }
710
+ //#endregion
711
+ //#region src/collection/core/errorMessage.ts
712
+ /** Normalise an unknown thrown value to a display string. The view layer's host
713
+ * capabilities already return normalised `{ ok: false, error }` results, so this
714
+ * only backs the defensive `catch` around an unexpected throw. */
715
+ function errorMessage(err) {
716
+ return err instanceof Error ? err.message : String(err);
717
+ }
718
+ //#endregion
719
+ //#region src/collection/core/shortHexId.ts
720
+ /**
721
+ * 8-char hex id — short, slug-safe, and editable. Produces the same id *shape*
722
+ * as the server's `generateItemId()` (8 hex chars) so a UI-created collection
723
+ * record looks like one the server would have generated for a form submitted
724
+ * with a blank primary key. The source of randomness differs (UUID-derived here
725
+ * vs `randomBytes` on the server); only the shape is intentionally shared.
726
+ */
727
+ function shortHexId() {
728
+ return crypto.randomUUID().replace(/-/g, "").slice(0, 8);
729
+ }
730
+ //#endregion
731
+ //#region src/collection/core/promptSafety.ts
732
+ /** Max chars kept — the first batch of a validation issue is enough to act on. */
733
+ var DEFANG_MAX_LEN = 200;
734
+ function defangForPrompt(value) {
735
+ return value.replace(/[<>]/g, "").replace(/`/g, "'").replace(/\$\{/g, "$ {").replace(/\s+/g, " ").slice(0, DEFANG_MAX_LEN);
736
+ }
737
+ //#endregion
738
+ export { ENUM_ALERT, ENUM_NEUTRAL, ENUM_NUDGE, FEED_SCHEDULES, INGEST_KINDS, MINUTES_PER_DAY, TOOL_DEFINITION, TOOL_NAME, actionVisible, assignLanes, boolSortValue, bucketRecords, buildMonthGrid, buildUpdatedRecord, coerceInlineValue, compareSortValues, compareYmd, dateOf, dateSortValue, daySlice, defangForPrompt, deriveAll, draftToRecord, emptyRow, enumColorClasses, enumSortValue, enumValueIndex, errorMessage, evaluateDerived, executePresentCollection, fieldVisible, firstMissingRequiredField, isFieldDrivenEvery, isSortableField, itemIdOf, itemLabelOf, labelFieldFor, monthAnchorDate, nextSortDirection, numericSortValue, parseIsoDate, parseIsoDateTime, parseTimeRange, recordSpan, resolveEnumColor, resolveRowRefs, rowFromItem, shortHexId, sortItems, spanCoversDay, stringSortValue, whenMatches, ymdKey };
739
+
740
+ //# sourceMappingURL=index.js.map