@rubytech/create-maxy-code 0.1.138 → 0.1.142

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 (108) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/config/brand.json +1 -1
  3. package/payload/platform/lib/obsidian-parser/dist/index.d.ts +98 -0
  4. package/payload/platform/lib/obsidian-parser/dist/index.d.ts.map +1 -0
  5. package/payload/platform/lib/obsidian-parser/dist/index.js +480 -0
  6. package/payload/platform/lib/obsidian-parser/dist/index.js.map +1 -0
  7. package/payload/platform/lib/obsidian-parser/src/index.ts +572 -0
  8. package/payload/platform/lib/obsidian-parser/tsconfig.json +8 -0
  9. package/payload/platform/neo4j/schema.cypher +48 -67
  10. package/payload/platform/package.json +2 -2
  11. package/payload/platform/plugins/.claude-plugin/marketplace.json +15 -0
  12. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.d.ts.map +1 -1
  13. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js +14 -8
  14. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-erase.js.map +1 -1
  15. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.d.ts +1 -1
  16. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.d.ts.map +1 -1
  17. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.js +10 -7
  18. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-export.js.map +1 -1
  19. package/payload/platform/plugins/docs/references/internals.md +1 -1
  20. package/payload/platform/plugins/docs/references/neo4j.md +3 -3
  21. package/payload/platform/plugins/docs/references/plugins-guide.md +3 -0
  22. package/payload/platform/plugins/email/.claude-plugin/plugin.json +1 -1
  23. package/payload/platform/plugins/email/PLUGIN.md +3 -7
  24. package/payload/platform/plugins/email/mcp/dist/index.js +7 -46
  25. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
  26. package/payload/platform/plugins/email/mcp/dist/lib/credentials.d.ts.map +1 -1
  27. package/payload/platform/plugins/email/mcp/dist/lib/credentials.js +4 -15
  28. package/payload/platform/plugins/email/mcp/dist/lib/credentials.js.map +1 -1
  29. package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts +51 -43
  30. package/payload/platform/plugins/email/mcp/dist/lib/graph.d.ts.map +1 -1
  31. package/payload/platform/plugins/email/mcp/dist/lib/graph.js +223 -229
  32. package/payload/platform/plugins/email/mcp/dist/lib/graph.js.map +1 -1
  33. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js +7 -4
  34. package/payload/platform/plugins/email/mcp/dist/scripts/email-fetch.js.map +1 -1
  35. package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.d.ts +14 -4
  36. package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.d.ts.map +1 -1
  37. package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.js +69 -84
  38. package/payload/platform/plugins/email/mcp/dist/tools/email-graph-query.js.map +1 -1
  39. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.d.ts.map +1 -1
  40. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js +49 -64
  41. package/payload/platform/plugins/email/mcp/dist/tools/email-reply.js.map +1 -1
  42. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.d.ts.map +1 -1
  43. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js +1 -2
  44. package/payload/platform/plugins/email/mcp/dist/tools/email-setup.js.map +1 -1
  45. package/payload/platform/plugins/memory/PLUGIN.md +3 -0
  46. package/payload/platform/plugins/memory/mcp/dist/index.js +89 -2
  47. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  48. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts +1 -0
  49. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.d.ts.map +1 -1
  50. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js +3 -0
  51. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/index.js.map +1 -1
  52. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts +1 -1
  53. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.d.ts.map +1 -1
  54. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js +1 -0
  55. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/types.js.map +1 -1
  56. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/x-dm.d.ts +3 -0
  57. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/x-dm.d.ts.map +1 -0
  58. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/x-dm.js +101 -0
  59. package/payload/platform/plugins/memory/mcp/dist/lib/conversation-normalisers/x-dm.js.map +1 -0
  60. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.d.ts.map +1 -1
  61. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js +7 -4
  62. package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js.map +1 -1
  63. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +122 -1
  64. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
  65. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +580 -0
  66. package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
  67. package/payload/platform/plugins/memory/mcp/dist/tools/obsidian-vault-import.d.ts +127 -0
  68. package/payload/platform/plugins/memory/mcp/dist/tools/obsidian-vault-import.d.ts.map +1 -0
  69. package/payload/platform/plugins/memory/mcp/dist/tools/obsidian-vault-import.js +477 -0
  70. package/payload/platform/plugins/memory/mcp/dist/tools/obsidian-vault-import.js.map +1 -0
  71. package/payload/platform/plugins/memory/references/schema-base.md +4 -4
  72. package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +2 -1
  73. package/payload/platform/plugins/memory/skills/document-ingest/SKILL.md +1 -0
  74. package/payload/platform/plugins/notion-import/.claude-plugin/plugin.json +8 -0
  75. package/payload/platform/plugins/notion-import/PLUGIN.md +27 -0
  76. package/payload/platform/plugins/notion-import/skills/notion-import/SKILL.md +114 -0
  77. package/payload/platform/plugins/notion-import/skills/notion-import/references/attachments.md +55 -0
  78. package/payload/platform/plugins/notion-import/skills/notion-import/references/databases.md +83 -0
  79. package/payload/platform/plugins/notion-import/skills/notion-import/references/page-tree.md +61 -0
  80. package/payload/platform/plugins/notion-import/skills/notion-import/references/workspace-export.md +41 -0
  81. package/payload/platform/plugins/obsidian-import/.claude-plugin/plugin.json +8 -0
  82. package/payload/platform/plugins/obsidian-import/PLUGIN.md +39 -0
  83. package/payload/platform/plugins/obsidian-import/skills/obsidian-import/SKILL.md +92 -0
  84. package/payload/platform/plugins/obsidian-import/skills/obsidian-import/references/attachments.md +80 -0
  85. package/payload/platform/plugins/obsidian-import/skills/obsidian-import/references/daily-notes.md +31 -0
  86. package/payload/platform/plugins/obsidian-import/skills/obsidian-import/references/vault-structure.md +46 -0
  87. package/payload/platform/plugins/obsidian-import/skills/obsidian-import/references/wikilinks.md +70 -0
  88. package/payload/platform/plugins/scheduling/PLUGIN.md +18 -0
  89. package/payload/platform/plugins/scheduling/mcp/dist/index.js +93 -0
  90. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  91. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.d.ts +80 -0
  92. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.d.ts.map +1 -0
  93. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.js +395 -0
  94. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics-graph-ingest.js.map +1 -0
  95. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.d.ts +16 -0
  96. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.d.ts.map +1 -1
  97. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.js +111 -2
  98. package/payload/platform/plugins/scheduling/mcp/dist/lib/ics.js.map +1 -1
  99. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-archive-ics.d.ts +20 -0
  100. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-archive-ics.d.ts.map +1 -0
  101. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-archive-ics.js +63 -0
  102. package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-archive-ics.js.map +1 -0
  103. package/payload/platform/plugins/x-import/.claude-plugin/plugin.json +8 -0
  104. package/payload/platform/plugins/x-import/PLUGIN.md +33 -0
  105. package/payload/platform/plugins/x-import/references/archive-shape.md +124 -0
  106. package/payload/platform/plugins/x-import/references/transcript-shape.md +121 -0
  107. package/payload/platform/plugins/x-import/skills/x-import/SKILL.md +123 -0
  108. package/payload/platform/templates/specialists/agents/database-operator.md +18 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ics-graph-ingest.d.ts","sourceRoot":"","sources":["../../src/lib/ics-graph-ingest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAM3E,MAAM,WAAW,eAAe;IAC9B,gEAAgE;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,+EAA+E;IAC/E,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EACF,uBAAuB,GACvB,wBAAwB,GACxB,aAAa,GACb,YAAY,CAAC;IACjB,qCAAqC;IACrC,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,SAAS,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kCAAkC;IAClC,OAAO,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;QAC5B,oBAAoB,EAAE,MAAM,CAAC;QAC7B,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,uFAAuF;IACvF,eAAe,EAAE,oBAAoB,EAAE,CAAC;IACxC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,yBAAyB,EAAE,MAAM,CAAC;IAClC,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,MAAM,eAAe,GAAG,qBAAqB,GAAG,qBAAqB,CAAC;AAM5E,wBAAsB,SAAS,CAC7B,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,eAAe,CAAC,CA8D1B;AAkYD,YAAY,EAAE,cAAc,EAAE,CAAC"}
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Graph-ingest path for ICS calendar archives.
3
+ *
4
+ * .ics file ──▶ parseICS ──▶ (dry-run preview │ commit writes)
5
+ *
6
+ * Writes one `:Meeting` per VEVENT, anchors it to the importing `:AdminUser`
7
+ * via `[:IMPORTED]`, resolves attendees to `:Person` by lowercased email, and
8
+ * MERGEs `[:ATTENDED]` edges with role + partstat.
9
+ *
10
+ * Cross-account collision rule: `Person.email` is globally unique in the
11
+ * schema. When an attendee email already exists under a different accountId,
12
+ * the import skips the attendance edge (no edge written, the foreign Person
13
+ * is untouched) and increments `skippedCrossAccountPerson`. The Meeting still
14
+ * lands, anchored to the importer's AdminUser. This preserves absolute brand
15
+ * / account isolation while keeping the import as a whole non-fatal.
16
+ */
17
+ import { randomUUID } from "node:crypto";
18
+ import { parseICS } from "./ics.js";
19
+ // ---------------------------------------------------------------------------
20
+ // Entry point
21
+ // ---------------------------------------------------------------------------
22
+ export async function ingestIcs(params) {
23
+ const start = Date.now();
24
+ const importId = randomUUID();
25
+ const { fileContent, filename, mode, accountId, userId, sessionId, session } = params;
26
+ const parsed = parseICS(fileContent);
27
+ // Partition events into valid / skipped buckets. parseICS already rejects
28
+ // VEVENTs without DTSTART (they land in parseErrors); the parser stops short
29
+ // of inferring skip-reason buckets, so we classify here too. UID is required
30
+ // for the (accountId, uid) composite key.
31
+ const valid = [];
32
+ let skippedNoUid = 0;
33
+ let skippedNoStartTime = 0;
34
+ for (const ev of parsed.events) {
35
+ if (!ev.dtstart) {
36
+ skippedNoStartTime++;
37
+ continue;
38
+ }
39
+ if (!ev.uid) {
40
+ skippedNoUid++;
41
+ continue;
42
+ }
43
+ valid.push(ev);
44
+ }
45
+ // Events the parser already rejected for missing DTSTART surface via
46
+ // parseErrors. Count those too so the operator-visible signal matches the
47
+ // file's actual content.
48
+ for (const err of parsed.parseErrors) {
49
+ if (err.includes("missing required DTSTART"))
50
+ skippedNoStartTime++;
51
+ }
52
+ if (mode === "dry-run") {
53
+ return dryRunPreview({
54
+ session,
55
+ accountId,
56
+ valid,
57
+ filename,
58
+ importId,
59
+ skippedNoUid,
60
+ skippedNoStartTime,
61
+ parseErrors: parsed.parseErrors,
62
+ eventsParsed: parsed.events.length,
63
+ start,
64
+ });
65
+ }
66
+ return commit({
67
+ session,
68
+ accountId,
69
+ userId,
70
+ sessionId,
71
+ valid,
72
+ filename,
73
+ importId,
74
+ skippedNoUid,
75
+ skippedNoStartTime,
76
+ parseErrors: parsed.parseErrors,
77
+ eventsParsed: parsed.events.length,
78
+ start,
79
+ });
80
+ }
81
+ async function dryRunPreview(ctx) {
82
+ // Flatten attendees across valid events. Track raw counts so the preview
83
+ // numbers match what the commit phase would emit.
84
+ let attendees = 0;
85
+ let organizers = 0;
86
+ let nonMailto = 0;
87
+ const addressOrder = []; // dedup-preserving insertion order
88
+ const addressSeen = new Set();
89
+ const addressDisplayName = new Map();
90
+ for (const ev of ctx.valid) {
91
+ for (const a of ev.attendees) {
92
+ if (a.role === "organizer")
93
+ organizers++;
94
+ else
95
+ attendees++;
96
+ if (!a.address) {
97
+ nonMailto++;
98
+ continue;
99
+ }
100
+ if (!addressSeen.has(a.address)) {
101
+ addressSeen.add(a.address);
102
+ addressOrder.push(a.address);
103
+ }
104
+ if (a.displayName && !addressDisplayName.has(a.address)) {
105
+ addressDisplayName.set(a.address, a.displayName);
106
+ }
107
+ }
108
+ }
109
+ // One round-trip to classify every unique address against the live graph.
110
+ const existing = new Map();
111
+ if (addressOrder.length > 0) {
112
+ const result = await ctx.session.run(`
113
+ UNWIND $emails AS email
114
+ MATCH (p:Person {email: email})
115
+ RETURN p.email AS email, p.accountId AS accountId, p.displayName AS displayName
116
+ `, { emails: addressOrder });
117
+ for (const rec of result.records) {
118
+ existing.set(rec.get("email"), {
119
+ accountId: rec.get("accountId") ?? null,
120
+ displayName: rec.get("displayName") ?? null,
121
+ });
122
+ }
123
+ }
124
+ let existingSameAccount = 0;
125
+ let existingCrossAccount = 0;
126
+ let willCreate = 0;
127
+ const sample = [];
128
+ const SAMPLE_CAP = 20;
129
+ for (const address of addressOrder) {
130
+ const hit = existing.get(address);
131
+ let status;
132
+ let existingDisplayName;
133
+ if (!hit) {
134
+ willCreate++;
135
+ status = "will-create";
136
+ }
137
+ else if (hit.accountId === ctx.accountId) {
138
+ existingSameAccount++;
139
+ status = "existing-same-account";
140
+ if (hit.displayName)
141
+ existingDisplayName = hit.displayName;
142
+ }
143
+ else {
144
+ existingCrossAccount++;
145
+ status = "existing-cross-account";
146
+ if (hit.displayName)
147
+ existingDisplayName = hit.displayName;
148
+ }
149
+ if (sample.length < SAMPLE_CAP) {
150
+ const entry = { address, status };
151
+ const dn = addressDisplayName.get(address);
152
+ if (dn)
153
+ entry.displayName = dn;
154
+ if (existingDisplayName)
155
+ entry.existingDisplayName = existingDisplayName;
156
+ sample.push(entry);
157
+ }
158
+ }
159
+ // Non-mailto attendees are interesting enough to sample at least one.
160
+ if (nonMailto > 0 && sample.length < SAMPLE_CAP) {
161
+ for (const ev of ctx.valid) {
162
+ for (const a of ev.attendees) {
163
+ if (a.address)
164
+ continue;
165
+ sample.push({
166
+ address: a.rawValue,
167
+ status: "non-mailto",
168
+ ...(a.displayName ? { displayName: a.displayName } : {}),
169
+ });
170
+ if (sample.length >= SAMPLE_CAP)
171
+ break;
172
+ }
173
+ if (sample.length >= SAMPLE_CAP)
174
+ break;
175
+ }
176
+ }
177
+ const ms = Date.now() - ctx.start;
178
+ process.stderr.write(`[ics-ingest] importId=${ctx.importId} filename=${ctx.filename} mode=dry-run ` +
179
+ `events=${ctx.eventsParsed} meetings=${ctx.valid.length} ` +
180
+ `attendees=${attendees} organizers=${organizers} nonMailto=${nonMailto} ` +
181
+ `existingSame=${existingSameAccount} existingCross=${existingCrossAccount} ` +
182
+ `willCreate=${willCreate} skippedNoUid=${ctx.skippedNoUid} ` +
183
+ `skippedNoStartTime=${ctx.skippedNoStartTime} ms=${ms}\n`);
184
+ return {
185
+ mode: "dry-run",
186
+ filename: ctx.filename,
187
+ importId: ctx.importId,
188
+ events: ctx.eventsParsed,
189
+ validMeetings: ctx.valid.length,
190
+ skippedNoUid: ctx.skippedNoUid,
191
+ skippedNoStartTime: ctx.skippedNoStartTime,
192
+ preview: {
193
+ attendees,
194
+ organizers,
195
+ nonMailtoAttendees: nonMailto,
196
+ existingSameAccount,
197
+ existingCrossAccount,
198
+ willCreate,
199
+ },
200
+ sampleAttendees: sample,
201
+ parseErrors: ctx.parseErrors,
202
+ ms,
203
+ };
204
+ }
205
+ async function commit(ctx) {
206
+ // Resolve the importing AdminUser once. We refuse to start the commit if
207
+ // the userId doesn't resolve — anchoring every Meeting to a real AdminUser
208
+ // is what keeps :Meeting from being an orphan.
209
+ const adminCheck = await ctx.session.run(`MATCH (a:AdminUser {userId: $userId}) RETURN elementId(a) AS id LIMIT 1`, { userId: ctx.userId });
210
+ if (adminCheck.records.length === 0) {
211
+ throw new Error(`ics-ingest: no :AdminUser found for userId=${ctx.userId}. ` +
212
+ `The importer must exist in the graph before ingesting an .ics archive.`);
213
+ }
214
+ let meetingsCreated = 0;
215
+ let meetingsUpdated = 0;
216
+ let peopleCreated = 0;
217
+ let existingPersonsLinked = 0;
218
+ let skippedCrossAccountPerson = 0;
219
+ let attendees = 0;
220
+ let organizers = 0;
221
+ // One transaction per meeting keeps the blast radius small — if attendee
222
+ // resolution fails on event #4000, events 1..3999 are already durably
223
+ // committed. Calendar archives are long flat lists, not interlinked.
224
+ for (const ev of ctx.valid) {
225
+ const meetingProps = {
226
+ uid: ev.uid,
227
+ title: ev.summary,
228
+ startsAt: ev.dtstart,
229
+ endsAt: ev.dtend ?? null,
230
+ location: ev.location ?? null,
231
+ description: ev.description ?? null,
232
+ isAllDay: ev.isAllDay,
233
+ sourceFilename: ctx.filename,
234
+ accountId: ctx.accountId,
235
+ sessionId: ctx.sessionId ?? null,
236
+ };
237
+ const meetingResult = await ctx.session.executeWrite(async (tx) => {
238
+ const r = await tx.run(`
239
+ MATCH (a:AdminUser {userId: $userId})
240
+ MERGE (m:Meeting {accountId: $accountId, uid: $props.uid})
241
+ ON CREATE SET
242
+ m.title = $props.title,
243
+ m.startsAt = datetime($props.startsAt),
244
+ m.endsAt = CASE WHEN $props.endsAt IS NULL THEN null ELSE datetime($props.endsAt) END,
245
+ m.location = $props.location,
246
+ m.description = $props.description,
247
+ m.isAllDay = $props.isAllDay,
248
+ m.source = 'ics',
249
+ m.sourceFilename = $props.sourceFilename,
250
+ m.scope = 'admin',
251
+ m.createdAt = datetime(),
252
+ m.updatedAt = datetime(),
253
+ m.createdByAgent = 'ics-ingest',
254
+ m.createdBySource = 'ics-ingest',
255
+ m.createdBySession = $sessionId,
256
+ m._created = true
257
+ ON MATCH SET
258
+ m.title = $props.title,
259
+ m.startsAt = datetime($props.startsAt),
260
+ m.endsAt = CASE WHEN $props.endsAt IS NULL THEN null ELSE datetime($props.endsAt) END,
261
+ m.location = $props.location,
262
+ m.description = $props.description,
263
+ m.isAllDay = $props.isAllDay,
264
+ m.sourceFilename = $props.sourceFilename,
265
+ m.updatedAt = datetime(),
266
+ m._created = false
267
+ WITH a, m, m._created AS created
268
+ REMOVE m._created
269
+ MERGE (a)-[imp:IMPORTED]->(m)
270
+ ON CREATE SET imp.createdAt = datetime(), imp.source = 'ics-ingest'
271
+ RETURN created, elementId(m) AS meetingId
272
+ `, {
273
+ userId: ctx.userId,
274
+ accountId: ctx.accountId,
275
+ props: meetingProps,
276
+ sessionId: ctx.sessionId ?? null,
277
+ });
278
+ const created = r.records[0].get("created");
279
+ const meetingId = r.records[0].get("meetingId");
280
+ return { created, meetingId };
281
+ });
282
+ if (meetingResult.created)
283
+ meetingsCreated++;
284
+ else
285
+ meetingsUpdated++;
286
+ // Attendee + organizer resolution. We separate organizers from attendees
287
+ // only for counting; the edge written is :ATTENDED in both cases, with the
288
+ // role property carrying the distinction.
289
+ for (const att of ev.attendees) {
290
+ if (att.role === "organizer")
291
+ organizers++;
292
+ else
293
+ attendees++;
294
+ if (!att.address)
295
+ continue; // non-mailto: counted but no graph effect
296
+ const personResult = await ctx.session.executeWrite(async (tx) => {
297
+ // Pre-flight existence check so we never silently create an :ATTENDED
298
+ // edge from a foreign-accountId Person.
299
+ const lookup = await tx.run(`MATCH (p:Person {email: $email}) RETURN p.accountId AS accountId LIMIT 1`, { email: att.address });
300
+ const existingAccountId = lookup.records[0]?.get("accountId");
301
+ if (existingAccountId && existingAccountId !== ctx.accountId) {
302
+ return { outcome: "cross-account-skip" };
303
+ }
304
+ if (existingAccountId === ctx.accountId) {
305
+ // Same-account Person: link without overwriting their identity.
306
+ await tx.run(`
307
+ MATCH (p:Person {email: $email})
308
+ MATCH (m:Meeting) WHERE elementId(m) = $meetingId
309
+ MERGE (p)-[r:ATTENDED]->(m)
310
+ ON CREATE SET r.role = $role, r.partstat = $partstat,
311
+ r.createdAt = datetime(),
312
+ r.createdBySource = 'ics-ingest'
313
+ ON MATCH SET r.role = $role, r.partstat = $partstat
314
+ FOREACH (_ IN CASE WHEN p.displayName IS NULL AND $displayName IS NOT NULL THEN [1] ELSE [] END |
315
+ SET p.displayName = $displayName
316
+ )
317
+ `, {
318
+ email: att.address,
319
+ meetingId: meetingResult.meetingId,
320
+ role: att.role,
321
+ partstat: att.partstat ?? null,
322
+ displayName: att.displayName ?? null,
323
+ });
324
+ return { outcome: "linked-existing" };
325
+ }
326
+ // Brand new Person stub under the importer's account.
327
+ await tx.run(`
328
+ MATCH (m:Meeting) WHERE elementId(m) = $meetingId
329
+ CREATE (p:Person {
330
+ email: $email,
331
+ accountId: $accountId,
332
+ scope: 'admin',
333
+ source: 'ics-ingest',
334
+ displayName: $displayName,
335
+ createdAt: datetime(),
336
+ createdByAgent: 'ics-ingest',
337
+ createdBySource: 'ics-ingest',
338
+ createdBySession: $sessionId
339
+ })
340
+ CREATE (p)-[:ATTENDED {
341
+ role: $role,
342
+ partstat: $partstat,
343
+ createdAt: datetime(),
344
+ createdBySource: 'ics-ingest'
345
+ }]->(m)
346
+ `, {
347
+ email: att.address,
348
+ accountId: ctx.accountId,
349
+ displayName: att.displayName ?? null,
350
+ sessionId: ctx.sessionId ?? null,
351
+ meetingId: meetingResult.meetingId,
352
+ role: att.role,
353
+ partstat: att.partstat ?? null,
354
+ });
355
+ return { outcome: "person-created" };
356
+ });
357
+ if (personResult.outcome === "cross-account-skip") {
358
+ skippedCrossAccountPerson++;
359
+ }
360
+ else if (personResult.outcome === "linked-existing") {
361
+ existingPersonsLinked++;
362
+ }
363
+ else {
364
+ peopleCreated++;
365
+ }
366
+ }
367
+ }
368
+ const ms = Date.now() - ctx.start;
369
+ process.stderr.write(`[ics-ingest] importId=${ctx.importId} filename=${ctx.filename} mode=commit ` +
370
+ `events=${ctx.eventsParsed} meetings=${ctx.valid.length} ` +
371
+ `meetingsCreated=${meetingsCreated} meetingsUpdated=${meetingsUpdated} ` +
372
+ `peopleCreated=${peopleCreated} existingPersonsLinked=${existingPersonsLinked} ` +
373
+ `attendees=${attendees} organizers=${organizers} ` +
374
+ `skippedCrossAccountPerson=${skippedCrossAccountPerson} ` +
375
+ `skippedNoUid=${ctx.skippedNoUid} skippedNoStartTime=${ctx.skippedNoStartTime} ` +
376
+ `ms=${ms}\n`);
377
+ return {
378
+ mode: "commit",
379
+ filename: ctx.filename,
380
+ importId: ctx.importId,
381
+ events: ctx.eventsParsed,
382
+ meetingsCreated,
383
+ meetingsUpdated,
384
+ peopleCreated,
385
+ existingPersonsLinked,
386
+ attendees,
387
+ organizers,
388
+ skippedNoUid: ctx.skippedNoUid,
389
+ skippedNoStartTime: ctx.skippedNoStartTime,
390
+ skippedCrossAccountPerson,
391
+ parseErrors: ctx.parseErrors,
392
+ ms,
393
+ };
394
+ }
395
+ //# sourceMappingURL=ics-graph-ingest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ics-graph-ingest.js","sourceRoot":"","sources":["../../src/lib/ics-graph-ingest.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,18 @@ export interface ICSEvent {
20
20
  recurrence?: string;
21
21
  timezone?: string;
22
22
  }
23
+ export type AttendeeRole = "organizer" | "chair" | "req-participant" | "opt-participant" | "non-participant";
24
+ export interface ParsedAttendee {
25
+ /** Lowercased email local+domain from `mailto:` URI; null for non-mailto values (rooms, resources, groups). */
26
+ address: string | null;
27
+ /** CN parameter, unescaped. */
28
+ displayName?: string;
29
+ role: AttendeeRole;
30
+ /** PARTSTAT parameter, lowercased (e.g. "accepted", "declined", "tentative", "needs-action"). */
31
+ partstat?: string;
32
+ /** Raw URI value, kept verbatim for visibility on non-mailto entries. */
33
+ rawValue: string;
34
+ }
23
35
  export interface ParsedEvent {
24
36
  summary: string;
25
37
  dtstart: string;
@@ -28,6 +40,10 @@ export interface ParsedEvent {
28
40
  description?: string;
29
41
  uid?: string;
30
42
  recurrence?: string;
43
+ /** True when DTSTART carries `VALUE=DATE` (RFC 5545 §3.3.4 date-only). */
44
+ isAllDay: boolean;
45
+ /** Every ATTENDEE plus the ORGANIZER if present. Empty array if none. */
46
+ attendees: ParsedAttendee[];
31
47
  }
32
48
  export interface ParseResult {
33
49
  events: ParsedEvent[];
@@ -1 +1 @@
1
- {"version":3,"file":"ics.d.ts","sourceRoot":"","sources":["../../src/lib/ics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AA6GD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAkCrD;AAmCD,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,MAAM,CA6BjF;AAiFD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CA6GrD"}
1
+ {"version":3,"file":"ics.d.ts","sourceRoot":"","sources":["../../src/lib/ics.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AASH,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,YAAY,GACpB,WAAW,GACX,OAAO,GACP,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,cAAc;IAC7B,+GAA+G;IAC/G,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,+BAA+B;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,YAAY,CAAC;IACnB,iGAAiG;IACjG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAC;IAClB,yEAAyE;IACzE,SAAS,EAAE,cAAc,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AA6GD,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAkCrD;AAmCD,MAAM,WAAW,eAAe;IAC9B,8EAA8E;IAC9E,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,QAAQ,EAAE,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,MAAM,CA6BjF;AAiFD,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,CAkJrD"}
@@ -275,6 +275,13 @@ export function parseICS(content) {
275
275
  let inVEvent = false;
276
276
  let nestedDepth = 0; // tracks BEGIN:VALARM, BEGIN:VTIMEZONE etc. inside VEVENT
277
277
  let currentProps = new Map();
278
+ // ATTENDEE may repeat per VEVENT (Map.set would overwrite). Keep raw lines
279
+ // and parse at END:VEVENT.
280
+ let attendeeLines = [];
281
+ let organizerLine = null;
282
+ // VALUE=DATE on DTSTART signals an all-day event (RFC 5545 §3.3.4).
283
+ // The Map-based prop store strips parameters, so we detect this inline.
284
+ let dtStartIsDate = false;
278
285
  let eventIndex = 0;
279
286
  for (const line of rawLines) {
280
287
  const trimmed = line.trim();
@@ -282,6 +289,9 @@ export function parseICS(content) {
282
289
  inVEvent = true;
283
290
  nestedDepth = 0;
284
291
  currentProps = new Map();
292
+ attendeeLines = [];
293
+ organizerLine = null;
294
+ dtStartIsDate = false;
285
295
  eventIndex++;
286
296
  continue;
287
297
  }
@@ -306,9 +316,22 @@ export function parseICS(content) {
306
316
  parseErrors.push(`VEVENT #${eventIndex}: missing required DTSTART property`);
307
317
  continue;
308
318
  }
319
+ const attendees = [];
320
+ if (organizerLine) {
321
+ const a = parseAttendeeLine(organizerLine, "organizer");
322
+ if (a)
323
+ attendees.push(a);
324
+ }
325
+ for (const al of attendeeLines) {
326
+ const a = parseAttendeeLine(al, null);
327
+ if (a)
328
+ attendees.push(a);
329
+ }
309
330
  const parsed = {
310
331
  summary: summary ? unescapeText(summary) : "(untitled)",
311
332
  dtstart: fromICSDateTime(extractDateValue(dtstart)),
333
+ isAllDay: dtStartIsDate,
334
+ attendees,
312
335
  };
313
336
  const dtend = currentProps.get("DTEND");
314
337
  if (dtend)
@@ -334,9 +357,23 @@ export function parseICS(content) {
334
357
  if (colonIdx === -1)
335
358
  continue;
336
359
  const propFull = line.substring(0, colonIdx);
337
- const value = line.substring(colonIdx + 1);
338
- // Strip parameters (e.g., DTSTART;TZID=Europe/London:20260413...)
339
360
  const propName = propFull.split(";")[0].trim().toUpperCase();
361
+ // ATTENDEE may repeat; ORGANIZER appears at most once. Route both away
362
+ // from the single-value currentProps Map so re-occurrences aren't lost.
363
+ if (propName === "ATTENDEE") {
364
+ attendeeLines.push(line);
365
+ continue;
366
+ }
367
+ if (propName === "ORGANIZER") {
368
+ organizerLine = line;
369
+ continue;
370
+ }
371
+ // DTSTART;VALUE=DATE:20260413 → all-day event. Detect before stripping
372
+ // parameters.
373
+ if (propName === "DTSTART" && /;VALUE=DATE(?:[;:]|$)/i.test(propFull)) {
374
+ dtStartIsDate = true;
375
+ }
376
+ const value = line.substring(colonIdx + 1);
340
377
  currentProps.set(propName, value);
341
378
  }
342
379
  }
@@ -359,4 +396,76 @@ export function parseICS(content) {
359
396
  function extractDateValue(value) {
360
397
  return value.trim();
361
398
  }
399
+ // ---------------------------------------------------------------------------
400
+ // ATTENDEE / ORGANIZER parsing (RFC 5545 §3.8.4)
401
+ // ---------------------------------------------------------------------------
402
+ const ROLE_NORMALISE = {
403
+ CHAIR: "chair",
404
+ "REQ-PARTICIPANT": "req-participant",
405
+ "OPT-PARTICIPANT": "opt-participant",
406
+ "NON-PARTICIPANT": "non-participant",
407
+ };
408
+ /**
409
+ * Parse one raw `ATTENDEE` or `ORGANIZER` line.
410
+ *
411
+ * `forcedRole` is `"organizer"` when parsing the ORGANIZER property (the role
412
+ * isn't carried in the line itself — it's the property name). For ATTENDEE
413
+ * lines pass `null` and the ROLE parameter (or its absence) decides.
414
+ *
415
+ * Returns null only if the line has no colon at all (malformed).
416
+ */
417
+ function parseAttendeeLine(line, forcedRole) {
418
+ const colonIdx = line.indexOf(":");
419
+ if (colonIdx === -1)
420
+ return null;
421
+ const propFull = line.substring(0, colonIdx);
422
+ const value = line.substring(colonIdx + 1).trim();
423
+ // Params live between the property name and the first colon, semicolon-
424
+ // separated. e.g. `ATTENDEE;CN="Alice";ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED`
425
+ const paramSegments = propFull.split(";").slice(1);
426
+ const params = new Map();
427
+ for (const seg of paramSegments) {
428
+ const eq = seg.indexOf("=");
429
+ if (eq === -1)
430
+ continue;
431
+ const k = seg.substring(0, eq).trim().toUpperCase();
432
+ let v = seg.substring(eq + 1).trim();
433
+ // Strip surrounding double quotes from quoted-string param values.
434
+ if (v.startsWith('"') && v.endsWith('"') && v.length >= 2)
435
+ v = v.slice(1, -1);
436
+ params.set(k, v);
437
+ }
438
+ // Address resolution. Only `mailto:` URIs become graph identities; other
439
+ // schemes (urn:uuid:..., resource://room/...) are recorded with null and
440
+ // surfaced in the dry-run preview rather than dropped.
441
+ let address = null;
442
+ const mailtoMatch = value.match(/^mailto:(.+)$/i);
443
+ if (mailtoMatch) {
444
+ address = mailtoMatch[1].trim().toLowerCase();
445
+ if (!address)
446
+ address = null;
447
+ }
448
+ // Role: ORGANIZER is fixed; ATTENDEE uses ROLE param (default REQ-PARTICIPANT
449
+ // per RFC 5545 §3.2.18).
450
+ let role;
451
+ if (forcedRole) {
452
+ role = forcedRole;
453
+ }
454
+ else {
455
+ const roleParam = params.get("ROLE")?.toUpperCase() ?? "REQ-PARTICIPANT";
456
+ role = ROLE_NORMALISE[roleParam] ?? "req-participant";
457
+ }
458
+ const attendee = {
459
+ address,
460
+ role,
461
+ rawValue: value,
462
+ };
463
+ const cn = params.get("CN");
464
+ if (cn)
465
+ attendee.displayName = unescapeText(cn);
466
+ const partstat = params.get("PARTSTAT");
467
+ if (partstat)
468
+ attendee.partstat = partstat.toLowerCase();
469
+ return attendee;
470
+ }
362
471
  //# sourceMappingURL=ics.js.map