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