@mgsoftwarebv/mcp-server-bridge 3.3.3 → 3.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -93101,6 +93101,9 @@ var tickets = pgTable(
93101
93101
  precision: 10,
93102
93102
  scale: 2
93103
93103
  }).default(0),
93104
+ // Deadline / due date - shown as a virtual "deadline" event in the agenda
93105
+ dueDate: timestamp("due_date", { withTimezone: true, mode: "string" }),
93106
+ dueDateAllDay: boolean3("due_date_all_day").notNull().default(false),
93104
93107
  // Origin tracking - how the ticket was created
93105
93108
  origin: ticketOriginEnum().default("manual").notNull(),
93106
93109
  // Structured context (e.g. visual review anchor metadata)
@@ -93177,6 +93180,7 @@ var tickets = pgTable(
93177
93180
  index("idx_tickets_status").on(table.status),
93178
93181
  index("idx_tickets_priority").on(table.priority),
93179
93182
  index("idx_tickets_created_at").using("btree", table.createdAt.desc()),
93183
+ index("idx_tickets_due_date").on(table.teamId, table.dueDate),
93180
93184
  index("idx_tickets_fts").using(
93181
93185
  "gin",
93182
93186
  table.fts.asc().nullsLast().op("tsvector_ops")
@@ -95053,6 +95057,15 @@ var quotations = pgTable(
95053
95057
  topBlock: jsonb("top_block"),
95054
95058
  bottomBlock: jsonb("bottom_block"),
95055
95059
  template: jsonb(),
95060
+ // Typst block-document content (mirrors `documents.blocks`). The pricing
95061
+ // block is the financial source of truth; line_items/amount/subtotal/vat
95062
+ // are derived from it on save for invoicing/KPIs/AI.
95063
+ blocks: jsonb().default([]).notNull(),
95064
+ branding: jsonb().default({}).notNull(),
95065
+ pageSize: text("page_size").default("a4").notNull(),
95066
+ // PDF staleness marker: hash of the compiled block content + file size.
95067
+ compiledHash: text("compiled_hash"),
95068
+ fileSize: integer2("file_size"),
95056
95069
  vat: numericCasted({ precision: 10, scale: 2 }),
95057
95070
  tax: numericCasted({ precision: 10, scale: 2 }),
95058
95071
  discount: numericCasted({ precision: 10, scale: 2 }),
@@ -105507,7 +105520,7 @@ var TOOLS = [
105507
105520
  },
105508
105521
  {
105509
105522
  name: "get-tickets",
105510
- description: "Get tickets with optional filtering by status, priority, project, customer, or search query",
105523
+ description: "Get tickets with optional filtering by status, priority, project, customer, tags, or search query. Each ticket includes its tags.",
105511
105524
  inputSchema: {
105512
105525
  type: "object",
105513
105526
  properties: {
@@ -105533,6 +105546,20 @@ var TOOLS = [
105533
105546
  type: "string",
105534
105547
  description: "Search query for ticket number, title, or description"
105535
105548
  },
105549
+ tag: {
105550
+ type: "string",
105551
+ description: "Filter by a single tag name (case-insensitive, team-specific)"
105552
+ },
105553
+ tags: {
105554
+ type: "array",
105555
+ items: { type: "string" },
105556
+ description: "Filter by tag names (case-insensitive). Tickets matching any listed tag are returned."
105557
+ },
105558
+ tagIds: {
105559
+ type: "array",
105560
+ items: { type: "string" },
105561
+ description: "Filter by tag IDs"
105562
+ },
105536
105563
  pageSize: { type: "number", default: 20, maximum: 100 }
105537
105564
  },
105538
105565
  required: []
@@ -105540,7 +105567,7 @@ var TOOLS = [
105540
105567
  },
105541
105568
  {
105542
105569
  name: "get-ticket-by-id",
105543
- description: "Get a specific ticket by its ID, including comment text and a full attachment listing (with ids). Images from ticket and comment attachments are downloaded and returned inline as base64. For non-image attachments, call get-ticket-attachment with the listed id to get a download URL.",
105570
+ description: "Get a specific ticket by its ID, including tags, comment text and a full attachment listing (with ids). Images from ticket and comment attachments are downloaded and returned inline as base64. For non-image attachments, call get-ticket-attachment with the listed id to get a download URL.",
105544
105571
  inputSchema: {
105545
105572
  type: "object",
105546
105573
  properties: {
@@ -105552,7 +105579,7 @@ var TOOLS = [
105552
105579
  },
105553
105580
  {
105554
105581
  name: "create-ticket",
105555
- description: "Create a new ticket",
105582
+ description: "Create a new ticket. Tags can be passed by name (auto-created if missing) or by existing tag IDs. Tag names are matched case-insensitively within the team.",
105556
105583
  inputSchema: {
105557
105584
  type: "object",
105558
105585
  properties: {
@@ -105589,14 +105616,28 @@ var TOOLS = [
105589
105616
  default: "task"
105590
105617
  },
105591
105618
  projectId: { type: "string" },
105592
- customerId: { type: "string" }
105619
+ customerId: { type: "string" },
105620
+ dueDate: {
105621
+ type: "string",
105622
+ description: "Optional deadline / due date (ISO 8601, e.g. 2026-06-30). Shows as a deadline event in the agenda."
105623
+ },
105624
+ tags: {
105625
+ type: "array",
105626
+ items: { type: "string" },
105627
+ description: "Tag names to attach. Missing tags are created as general team tags (case-insensitive deduplication)."
105628
+ },
105629
+ tagIds: {
105630
+ type: "array",
105631
+ items: { type: "string" },
105632
+ description: "Existing tag IDs to attach"
105633
+ }
105593
105634
  },
105594
105635
  required: ["title"]
105595
105636
  }
105596
105637
  },
105597
105638
  {
105598
105639
  name: "update-ticket",
105599
- description: "Update an existing ticket's fields (title, description, status, priority, type, project, customer, assignee, estimated hours). Only provided fields are changed. Changes are written to the ticket activity feed but do NOT send notifications. Set assigneeId to null to unassign; a provided assigneeId must be a member of the ticket's team. Common workflow: set status=in_progress when starting work; after merge/push set status=review and assigneeId to the requester (creator) id from get-ticket-by-id.",
105640
+ description: "Update an existing ticket's fields (title, description, status, priority, type, project, customer, assignee, estimated hours, deadline/due date, tags). Only provided fields are changed. Changes are written to the ticket activity feed but do NOT send notifications. Set assigneeId to null to unassign; a provided assigneeId must be a member of the ticket's team. Set dueDate (ISO 8601, e.g. 2026-06-30 or 2026-06-30T00:00:00.000Z) to give the ticket a deadline that shows up in the agenda; set dueDate to null to clear it. Tags: use tagMode replace (default) to set the full tag list, merge to add tags, or remove to drop listed tags. Common workflow: set status=in_progress when starting work; after merge/push set status=review and assigneeId to the requester (creator) id from get-ticket-by-id.",
105600
105641
  inputSchema: {
105601
105642
  type: "object",
105602
105643
  properties: {
@@ -105639,7 +105680,159 @@ var TOOLS = [
105639
105680
  type: ["string", "null"],
105640
105681
  description: "User ID to assign, or null to unassign."
105641
105682
  },
105642
- estimatedHours: { type: "number" }
105683
+ estimatedHours: { type: "number" },
105684
+ dueDate: {
105685
+ type: ["string", "null"],
105686
+ description: "Deadline / due date (ISO 8601, e.g. 2026-06-30 or 2026-06-30T00:00:00.000Z). Shows as a deadline event in the agenda. Pass null to clear the deadline."
105687
+ },
105688
+ tags: {
105689
+ type: "array",
105690
+ items: { type: "string" },
105691
+ description: "Tag names. With tagMode replace (default), sets the ticket's tags. With merge, adds tags. With remove, removes these tags. Missing names are auto-created when adding."
105692
+ },
105693
+ tagIds: {
105694
+ type: "array",
105695
+ items: { type: "string" },
105696
+ description: "Tag IDs (same tagMode semantics as tags)"
105697
+ },
105698
+ tagMode: {
105699
+ type: "string",
105700
+ enum: ["replace", "merge", "remove"],
105701
+ default: "replace",
105702
+ description: "How to apply tags/tagIds: replace = set full list, merge = add, remove = remove listed tags"
105703
+ }
105704
+ },
105705
+ required: ["id"]
105706
+ }
105707
+ },
105708
+ {
105709
+ name: "get-tags",
105710
+ description: "List tags for the provider team. Tags are team-specific; optional projectId includes general tags plus project-specific tags.",
105711
+ inputSchema: {
105712
+ type: "object",
105713
+ properties: {
105714
+ teamId: teamIdProp,
105715
+ projectId: {
105716
+ type: "string",
105717
+ description: "Optional project ID to include project-specific tags"
105718
+ },
105719
+ pageSize: { type: "number", default: 100, maximum: 200 }
105720
+ },
105721
+ required: []
105722
+ }
105723
+ },
105724
+ {
105725
+ name: "create-tag",
105726
+ description: "Create a team tag. Tag names are deduplicated case-insensitively within the same team/project scope.",
105727
+ inputSchema: {
105728
+ type: "object",
105729
+ properties: {
105730
+ teamId: teamIdProp,
105731
+ name: { type: "string", description: "Tag name" },
105732
+ projectId: {
105733
+ type: "string",
105734
+ description: "Optional project ID for a project-specific tag; omit for a general team tag"
105735
+ }
105736
+ },
105737
+ required: ["name"]
105738
+ }
105739
+ },
105740
+ {
105741
+ name: "get-calendar-items",
105742
+ description: "List agenda/calendar items (deadlines, meetings, reminders, deliveries) with optional filters by date range, project, ticket, customer, assignee, type, or status. Returns items with id, title, startsAt, endsAt, dueDate, linked ticket/project/customer ids, and status.",
105743
+ inputSchema: {
105744
+ type: "object",
105745
+ properties: {
105746
+ teamId: teamIdProp,
105747
+ projectId: { type: "string" },
105748
+ ticketId: { type: "string" },
105749
+ customerId: { type: "string" },
105750
+ assigneeId: { type: "string" },
105751
+ dateFrom: {
105752
+ type: "string",
105753
+ description: "ISO datetime or YYYY-MM-DD (inclusive start)"
105754
+ },
105755
+ dateTo: {
105756
+ type: "string",
105757
+ description: "ISO datetime or YYYY-MM-DD (inclusive end)"
105758
+ },
105759
+ status: {
105760
+ type: "string",
105761
+ enum: ["draft", "scheduled", "completed", "cancelled"]
105762
+ },
105763
+ type: {
105764
+ type: "string",
105765
+ enum: ["deadline", "meeting", "reminder", "delivery"]
105766
+ },
105767
+ pageSize: { type: "number", default: 50, maximum: 100 }
105768
+ },
105769
+ required: []
105770
+ }
105771
+ },
105772
+ {
105773
+ name: "create-calendar-item",
105774
+ description: "Create an agenda/calendar item linked to tickets, projects, or customers. Use type 'deadline' with dueDate (YYYY-MM-DD) for delivery deadlines. Prevents duplicate deadline items for the same ticket \u2014 updates the existing one instead. Types: deadline, meeting, reminder, delivery.",
105775
+ inputSchema: {
105776
+ type: "object",
105777
+ properties: {
105778
+ teamId: teamIdProp,
105779
+ title: { type: "string" },
105780
+ description: { type: "string" },
105781
+ dueDate: {
105782
+ type: "string",
105783
+ description: "Deadline date YYYY-MM-DD (all-day). Preferred for deadlines."
105784
+ },
105785
+ startsAt: {
105786
+ type: "string",
105787
+ description: "Start datetime (ISO). Use when a specific time is needed."
105788
+ },
105789
+ endsAt: { type: "string", description: "End datetime (ISO)" },
105790
+ projectId: { type: "string" },
105791
+ ticketId: { type: "string" },
105792
+ customerId: { type: "string" },
105793
+ assigneeId: {
105794
+ type: "string",
105795
+ description: "User ID of the assignee (defaults to the API key user)"
105796
+ },
105797
+ type: {
105798
+ type: "string",
105799
+ enum: ["deadline", "meeting", "reminder", "delivery"],
105800
+ default: "deadline"
105801
+ },
105802
+ status: {
105803
+ type: "string",
105804
+ enum: ["draft", "scheduled", "completed"],
105805
+ default: "scheduled"
105806
+ }
105807
+ },
105808
+ required: ["title"]
105809
+ }
105810
+ },
105811
+ {
105812
+ name: "update-calendar-item",
105813
+ description: "Update an existing agenda/calendar item by id. Supports changing title, dates, status, type, and linked ticket/project/customer. Set status to 'completed' or 'cancelled' to finish or remove the item.",
105814
+ inputSchema: {
105815
+ type: "object",
105816
+ properties: {
105817
+ teamId: teamIdProp,
105818
+ id: { type: "string", description: "Calendar item ID" },
105819
+ title: { type: "string" },
105820
+ description: { type: ["string", "null"] },
105821
+ dueDate: { type: "string", description: "YYYY-MM-DD" },
105822
+ startsAt: { type: "string" },
105823
+ endsAt: { type: "string" },
105824
+ projectId: { type: ["string", "null"] },
105825
+ ticketId: { type: ["string", "null"] },
105826
+ customerId: { type: ["string", "null"] },
105827
+ assigneeId: { type: "string" },
105828
+ type: {
105829
+ type: "string",
105830
+ enum: ["deadline", "meeting", "reminder", "delivery"]
105831
+ },
105832
+ status: {
105833
+ type: "string",
105834
+ enum: ["draft", "scheduled", "completed", "cancelled"]
105835
+ }
105643
105836
  },
105644
105837
  required: ["id"]
105645
105838
  }
@@ -106494,120 +106687,661 @@ async function resolveTeamScope(requestedTeamId) {
106494
106687
  return { ok: true, teamIds, projectIds, customerIds };
106495
106688
  }
106496
106689
 
106497
- // src/tools/customers.ts
106498
- async function handleGetCustomers(input) {
106499
- const { q: q3, pageSize = 20 } = input;
106500
- const resolved = await resolveTeamId(input.teamId);
106501
- if (!resolved.ok) return resolved.response;
106502
- const customerIds = await getAccessibleCustomerIds(resolved.teamId);
106503
- if (customerIds.length === 0) {
106504
- return {
106505
- content: [
106506
- {
106507
- type: "text",
106508
- text: "No customers found or no access to any customers."
106509
- }
106510
- ]
106511
- };
106512
- }
106513
- const filters = [inArray(schema_exports.customers.id, customerIds)];
106514
- if (q3) {
106515
- const pattern = `%${q3}%`;
106516
- filters.push(
106517
- or(
106518
- ilike(schema_exports.customers.name, pattern),
106519
- ilike(schema_exports.customers.email, pattern)
106520
- )
106521
- );
106522
- }
106523
- const rows = await db.select({
106524
- id: schema_exports.customers.id,
106525
- name: schema_exports.customers.name,
106526
- email: schema_exports.customers.email,
106527
- website: schema_exports.customers.website,
106528
- createdAt: schema_exports.customers.createdAt
106529
- }).from(schema_exports.customers).where(and(...filters)).orderBy(asc(schema_exports.customers.name)).limit(Math.min(pageSize, 100));
106690
+ // src/tools/ticket-access.ts
106691
+ function notFoundResponse(ticketId) {
106530
106692
  return {
106531
106693
  content: [
106532
106694
  {
106533
106695
  type: "text",
106534
- text: `Found ${rows.length} customers:
106535
-
106536
- ${rows.map(
106537
- (c6) => `**${c6.name}**
106538
- ${c6.email ? `Email: ${c6.email}
106539
- ` : ""}${c6.website ? `Website: ${c6.website}
106540
- ` : ""}Created: ${new Date(c6.createdAt).toLocaleDateString()}
106541
- `
106542
- ).join("\n") || "No customers found."}`
106696
+ text: `Ticket not found or no access: ${ticketId}. Call get-tickets to find the correct ticket.`
106543
106697
  }
106544
106698
  ]
106545
106699
  };
106546
106700
  }
106547
- async function handleCreateCustomer(input) {
106548
- const { name: name21, email: email5, website } = input;
106549
- const resolved = await resolveTeamId(input.teamId);
106550
- if (!resolved.ok) return resolved.response;
106551
- await db.insert(schema_exports.customers).values({
106552
- teamId: resolved.teamId,
106553
- name: name21,
106554
- email: email5 ?? "",
106555
- website: website ?? null
106556
- });
106557
- return {
106558
- content: [
106559
- {
106560
- type: "text",
106561
- text: `\u2705 **Customer Created Successfully!**
106701
+ async function loadAccessibleTicket(requestedTeamId, ticketId) {
106702
+ const scope = await resolveTeamScope(requestedTeamId);
106703
+ if (!scope.ok) return scope;
106704
+ const [ticket] = await db.select({
106705
+ id: schema_exports.tickets.id,
106706
+ teamId: schema_exports.tickets.teamId,
106707
+ projectId: schema_exports.tickets.projectId,
106708
+ customerId: schema_exports.tickets.customerId,
106709
+ ticketNumber: schema_exports.tickets.ticketNumber,
106710
+ title: schema_exports.tickets.title,
106711
+ status: schema_exports.tickets.status,
106712
+ priority: schema_exports.tickets.priority,
106713
+ type: schema_exports.tickets.type,
106714
+ assigneeId: schema_exports.tickets.assigneeId
106715
+ }).from(schema_exports.tickets).where(eq(schema_exports.tickets.id, ticketId)).limit(1);
106716
+ if (!ticket) return { ok: false, response: notFoundResponse(ticketId) };
106717
+ const hasAccess = scope.teamIds.includes(ticket.teamId) || !!ticket.projectId && scope.projectIds.includes(ticket.projectId) || !!ticket.customerId && scope.customerIds.includes(ticket.customerId);
106718
+ if (!hasAccess) return { ok: false, response: notFoundResponse(ticketId) };
106719
+ return { ok: true, ticket };
106720
+ }
106562
106721
 
106563
- Name: ${name21}
106564
- ${email5 ? `Email: ${email5}
106565
- ` : ""}${website ? `Website: ${website}
106566
- ` : ""}`
106567
- }
106568
- ]
106722
+ // src/tools/calendar-items.ts
106723
+ function mapInputType(type) {
106724
+ switch (type) {
106725
+ case "reminder":
106726
+ return "task";
106727
+ case "delivery":
106728
+ return "work";
106729
+ default:
106730
+ return type;
106731
+ }
106732
+ }
106733
+ function mapOutputType(type, metadata) {
106734
+ const meta5 = metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : null;
106735
+ const stored = meta5?.calendarType;
106736
+ if (typeof stored === "string") return stored;
106737
+ if (type === "task") return "reminder";
106738
+ if (type === "work") return "delivery";
106739
+ return type;
106740
+ }
106741
+ function mapOutputStatus(status, metadata, isDeleted) {
106742
+ if (isDeleted) return "cancelled";
106743
+ const meta5 = metadata && typeof metadata === "object" && !Array.isArray(metadata) ? metadata : null;
106744
+ if (meta5?.completed === true) return "completed";
106745
+ return status === "draft" ? "draft" : "scheduled";
106746
+ }
106747
+ function parseDueDate(dueDate) {
106748
+ const dateOnly = dueDate.split("T")[0];
106749
+ return {
106750
+ startTime: `${dateOnly}T00:00:00.000Z`,
106751
+ endTime: `${dateOnly}T23:59:59.999Z`
106569
106752
  };
106570
106753
  }
106571
-
106572
- // ../document/src/humanizer/rules.ts
106573
- var REPLACEMENTS = [
106574
- // --- Dutch clichés ---
106575
- { pattern: /\bbovendien\b/gi, replacement: "daarnaast", rule: "nl-cliche" },
106576
- { pattern: /\btevens\b/gi, replacement: "ook", rule: "nl-cliche" },
106577
- { pattern: /\bkortom\b/gi, replacement: "samengevat", rule: "nl-cliche" },
106578
- { pattern: /\bnaadloze\b/gi, replacement: "soepele", rule: "nl-cliche" },
106579
- { pattern: /\bnaadloos\b/gi, replacement: "soepel", rule: "nl-cliche" },
106580
- { pattern: /\brobuuste\b/gi, replacement: "solide", rule: "nl-cliche" },
106581
- { pattern: /\brobuust\b/gi, replacement: "solide", rule: "nl-cliche" },
106582
- { pattern: /\bcruciale\b/gi, replacement: "belangrijke", rule: "nl-cliche" },
106583
- { pattern: /\bcruciaal\b/gi, replacement: "belangrijk", rule: "nl-cliche" },
106584
- {
106585
- pattern: /\bessentiële\b/gi,
106586
- replacement: "belangrijke",
106587
- rule: "nl-cliche"
106588
- },
106589
- { pattern: /\bessentieel\b/gi, replacement: "belangrijk", rule: "nl-cliche" },
106590
- {
106591
- pattern: /\been breed scala aan\b/gi,
106592
- replacement: "veel",
106593
- rule: "nl-cliche"
106594
- },
106595
- {
106596
- pattern: /\boptimaal benutten\b/gi,
106597
- replacement: "goed benutten",
106598
- rule: "nl-cliche"
106599
- },
106600
- {
106601
- pattern: /\bin de wereld van vandaag\b/gi,
106602
- replacement: "vandaag de dag",
106603
- rule: "nl-cliche"
106604
- },
106605
- {
106606
- pattern: /\bin het huidige digitale tijdperk\b/gi,
106607
- replacement: "tegenwoordig",
106608
- rule: "nl-cliche"
106609
- },
106610
- {
106754
+ function resolveEventTimes(input) {
106755
+ if (input.dueDate) {
106756
+ const { startTime, endTime } = parseDueDate(input.dueDate);
106757
+ return { startTime, endTime, allDay: true };
106758
+ }
106759
+ if (input.startsAt) {
106760
+ const startTime = input.startsAt.includes("T") ? input.startsAt : `${input.startsAt}T09:00:00.000Z`;
106761
+ const endTime = input.endsAt ? input.endsAt.includes("T") ? input.endsAt : `${input.endsAt}T10:00:00.000Z` : null;
106762
+ const allDay = input.type === "deadline" || !input.endsAt && !input.startsAt.includes(":");
106763
+ return { startTime, endTime, allDay };
106764
+ }
106765
+ throw new Error(
106766
+ "Provide dueDate (YYYY-MM-DD) or startsAt (ISO datetime) for the calendar item."
106767
+ );
106768
+ }
106769
+ function formatCalendarItem(row) {
106770
+ const dueDate = row.allDay || row.type === "deadline" ? row.startTime.split("T")[0] : void 0;
106771
+ return {
106772
+ id: row.id,
106773
+ title: row.title,
106774
+ description: row.description,
106775
+ type: mapOutputType(row.type, row.metadata),
106776
+ startsAt: row.startTime,
106777
+ endsAt: row.endTime,
106778
+ dueDate,
106779
+ projectId: row.projectId,
106780
+ customerId: row.customerId,
106781
+ assigneeId: row.userId,
106782
+ ticketIds: row.ticketIds,
106783
+ status: mapOutputStatus(row.status, row.metadata, row.isDeleted)
106784
+ };
106785
+ }
106786
+ async function loadTicketLinks(eventIds) {
106787
+ const map3 = /* @__PURE__ */ new Map();
106788
+ if (eventIds.length === 0) return map3;
106789
+ const links = await db.select({
106790
+ eventId: schema_exports.timesheetEventTickets.timesheetEventId,
106791
+ ticketId: schema_exports.timesheetEventTickets.ticketId
106792
+ }).from(schema_exports.timesheetEventTickets).where(
106793
+ inArray(schema_exports.timesheetEventTickets.timesheetEventId, eventIds)
106794
+ );
106795
+ for (const link of links) {
106796
+ const existing = map3.get(link.eventId) ?? [];
106797
+ existing.push(link.ticketId);
106798
+ map3.set(link.eventId, existing);
106799
+ }
106800
+ return map3;
106801
+ }
106802
+ async function findExistingTicketDeadline(teamId, ticketId) {
106803
+ const links = await db.select({ eventId: schema_exports.timesheetEventTickets.timesheetEventId }).from(schema_exports.timesheetEventTickets).innerJoin(
106804
+ schema_exports.timesheetEvents,
106805
+ eq(
106806
+ schema_exports.timesheetEvents.id,
106807
+ schema_exports.timesheetEventTickets.timesheetEventId
106808
+ )
106809
+ ).where(
106810
+ and(
106811
+ eq(schema_exports.timesheetEventTickets.ticketId, ticketId),
106812
+ eq(schema_exports.timesheetEvents.teamId, teamId),
106813
+ eq(schema_exports.timesheetEvents.type, "deadline"),
106814
+ eq(schema_exports.timesheetEvents.isDeleted, false)
106815
+ )
106816
+ ).limit(1);
106817
+ return links[0]?.eventId ?? null;
106818
+ }
106819
+ async function validateProjectAccess(teamId, projectId, projectIds) {
106820
+ if (!projectIds.includes(projectId)) {
106821
+ throw new Error(
106822
+ `Project not found or no access: ${projectId}. Call get-projects first.`
106823
+ );
106824
+ }
106825
+ }
106826
+ async function validateAssignee(teamId, assigneeId) {
106827
+ const member2 = await isUserTeamMember(assigneeId, teamId);
106828
+ if (!member2) {
106829
+ throw new Error(
106830
+ `Assignee ${assigneeId} is not a member of team ${teamId}.`
106831
+ );
106832
+ }
106833
+ }
106834
+ async function loadCalendarItemById(teamId, id) {
106835
+ const [event] = await db.select({
106836
+ id: schema_exports.timesheetEvents.id,
106837
+ title: schema_exports.timesheetEvents.title,
106838
+ description: schema_exports.timesheetEvents.description,
106839
+ type: schema_exports.timesheetEvents.type,
106840
+ status: schema_exports.timesheetEvents.status,
106841
+ startTime: schema_exports.timesheetEvents.startTime,
106842
+ endTime: schema_exports.timesheetEvents.endTime,
106843
+ allDay: schema_exports.timesheetEvents.allDay,
106844
+ projectId: schema_exports.timesheetEvents.projectId,
106845
+ customerId: schema_exports.timesheetEvents.customerId,
106846
+ userId: schema_exports.timesheetEvents.userId,
106847
+ metadata: schema_exports.timesheetEvents.metadata,
106848
+ isDeleted: schema_exports.timesheetEvents.isDeleted
106849
+ }).from(schema_exports.timesheetEvents).where(
106850
+ and(
106851
+ eq(schema_exports.timesheetEvents.id, id),
106852
+ eq(schema_exports.timesheetEvents.teamId, teamId),
106853
+ eq(schema_exports.timesheetEvents.isDeleted, false)
106854
+ )
106855
+ ).limit(1);
106856
+ if (!event) return null;
106857
+ const ticketLinks = await loadTicketLinks([event.id]);
106858
+ return {
106859
+ ...event,
106860
+ ticketIds: ticketLinks.get(event.id) ?? []
106861
+ };
106862
+ }
106863
+ async function handleGetCalendarItems(input) {
106864
+ getAuthContext();
106865
+ const resolved = await resolveTeamId(input.teamId);
106866
+ if (!resolved.ok) return resolved.response;
106867
+ const teamId = resolved.teamId;
106868
+ const teamIds = await getAccessibleTeamIds(teamId);
106869
+ const pageSize = Math.min(input.pageSize ?? 50, 100);
106870
+ const dateFrom = input.dateFrom ?? (/* @__PURE__ */ new Date()).toISOString().split("T")[0] + "T00:00:00.000Z";
106871
+ const dateTo = input.dateTo ?? new Date(Date.now() + 90 * 24 * 60 * 60 * 1e3).toISOString();
106872
+ const filters = [
106873
+ inArray(schema_exports.timesheetEvents.teamId, teamIds),
106874
+ gte(schema_exports.timesheetEvents.startTime, dateFrom),
106875
+ lte(schema_exports.timesheetEvents.startTime, dateTo),
106876
+ eq(schema_exports.timesheetEvents.isDeleted, false)
106877
+ ];
106878
+ if (input.projectId) {
106879
+ filters.push(eq(schema_exports.timesheetEvents.projectId, input.projectId));
106880
+ }
106881
+ if (input.customerId) {
106882
+ filters.push(eq(schema_exports.timesheetEvents.customerId, input.customerId));
106883
+ }
106884
+ if (input.assigneeId) {
106885
+ filters.push(eq(schema_exports.timesheetEvents.userId, input.assigneeId));
106886
+ }
106887
+ if (input.status === "draft") {
106888
+ filters.push(eq(schema_exports.timesheetEvents.status, "draft"));
106889
+ } else if (input.status === "scheduled") {
106890
+ filters.push(eq(schema_exports.timesheetEvents.status, "confirmed"));
106891
+ }
106892
+ let eventIdsForTicket;
106893
+ if (input.ticketId) {
106894
+ const links = await db.select({ eventId: schema_exports.timesheetEventTickets.timesheetEventId }).from(schema_exports.timesheetEventTickets).where(eq(schema_exports.timesheetEventTickets.ticketId, input.ticketId));
106895
+ eventIdsForTicket = links.map((l4) => l4.eventId);
106896
+ if (eventIdsForTicket.length === 0) {
106897
+ return {
106898
+ content: [
106899
+ {
106900
+ type: "text",
106901
+ text: JSON.stringify({ count: 0, items: [] }, null, 2)
106902
+ }
106903
+ ]
106904
+ };
106905
+ }
106906
+ filters.push(inArray(schema_exports.timesheetEvents.id, eventIdsForTicket));
106907
+ }
106908
+ const rows = await db.select({
106909
+ id: schema_exports.timesheetEvents.id,
106910
+ title: schema_exports.timesheetEvents.title,
106911
+ description: schema_exports.timesheetEvents.description,
106912
+ type: schema_exports.timesheetEvents.type,
106913
+ status: schema_exports.timesheetEvents.status,
106914
+ startTime: schema_exports.timesheetEvents.startTime,
106915
+ endTime: schema_exports.timesheetEvents.endTime,
106916
+ allDay: schema_exports.timesheetEvents.allDay,
106917
+ projectId: schema_exports.timesheetEvents.projectId,
106918
+ customerId: schema_exports.timesheetEvents.customerId,
106919
+ userId: schema_exports.timesheetEvents.userId,
106920
+ metadata: schema_exports.timesheetEvents.metadata,
106921
+ isDeleted: schema_exports.timesheetEvents.isDeleted
106922
+ }).from(schema_exports.timesheetEvents).where(and(...filters)).orderBy(asc(schema_exports.timesheetEvents.startTime)).limit(pageSize);
106923
+ const ticketLinks = await loadTicketLinks(rows.map((r6) => r6.id));
106924
+ let items = rows.map(
106925
+ (row) => formatCalendarItem({
106926
+ ...row,
106927
+ ticketIds: ticketLinks.get(row.id) ?? []
106928
+ })
106929
+ );
106930
+ if (input.status === "completed") {
106931
+ items = items.filter((item) => item.status === "completed");
106932
+ } else if (input.status === "scheduled") {
106933
+ items = items.filter((item) => item.status === "scheduled");
106934
+ }
106935
+ if (input.type) {
106936
+ items = items.filter((item) => item.type === input.type);
106937
+ }
106938
+ return {
106939
+ content: [
106940
+ {
106941
+ type: "text",
106942
+ text: JSON.stringify({ count: items.length, items }, null, 2)
106943
+ }
106944
+ ]
106945
+ };
106946
+ }
106947
+ async function handleCreateCalendarItem(input) {
106948
+ const ctx = getAuthContext();
106949
+ const resolved = await resolveTeamId(input.teamId);
106950
+ if (!resolved.ok) return resolved.response;
106951
+ const teamId = resolved.teamId;
106952
+ const projectIds = await getAccessibleProjectIds(ctx.userId, teamId);
106953
+ const customerIds = await getAccessibleCustomerIds(teamId);
106954
+ if (input.projectId) {
106955
+ await validateProjectAccess(teamId, input.projectId, projectIds);
106956
+ }
106957
+ if (input.customerId && !customerIds.includes(input.customerId)) {
106958
+ throw new Error(
106959
+ `Customer not found or no access: ${input.customerId}. Call get-customers first.`
106960
+ );
106961
+ }
106962
+ let ticketId = input.ticketId;
106963
+ let projectId = input.projectId ?? null;
106964
+ let customerId = input.customerId ?? null;
106965
+ if (ticketId) {
106966
+ const access = await loadAccessibleTicket(input.teamId, ticketId);
106967
+ if (!access.ok) return access.response;
106968
+ if (!projectId) projectId = access.ticket.projectId;
106969
+ if (!customerId) customerId = access.ticket.customerId;
106970
+ }
106971
+ const itemType = input.type ?? "deadline";
106972
+ const timesheetType = mapInputType(itemType);
106973
+ if (ticketId && timesheetType === "deadline") {
106974
+ const existingId = await findExistingTicketDeadline(teamId, ticketId);
106975
+ if (existingId) {
106976
+ return handleUpdateCalendarItem({
106977
+ teamId: input.teamId,
106978
+ id: existingId,
106979
+ title: input.title,
106980
+ description: input.description,
106981
+ dueDate: input.dueDate,
106982
+ startsAt: input.startsAt,
106983
+ endsAt: input.endsAt,
106984
+ projectId: projectId ?? void 0,
106985
+ customerId: customerId ?? void 0,
106986
+ assigneeId: input.assigneeId,
106987
+ status: input.status,
106988
+ type: itemType
106989
+ });
106990
+ }
106991
+ }
106992
+ const assigneeId = input.assigneeId ?? ctx.userId;
106993
+ await validateAssignee(teamId, assigneeId);
106994
+ const { startTime, endTime, allDay } = resolveEventTimes({
106995
+ dueDate: input.dueDate,
106996
+ startsAt: input.startsAt,
106997
+ endsAt: input.endsAt,
106998
+ type: itemType
106999
+ });
107000
+ const metadata = {
107001
+ calendarType: itemType,
107002
+ source: "mcp_calendar_item"
107003
+ };
107004
+ if (input.status === "completed") metadata.completed = true;
107005
+ const [created] = await db.insert(schema_exports.timesheetEvents).values({
107006
+ teamId,
107007
+ userId: assigneeId,
107008
+ title: input.title,
107009
+ description: input.description ?? null,
107010
+ type: timesheetType,
107011
+ status: input.status === "draft" ? "draft" : "confirmed",
107012
+ startTime,
107013
+ endTime,
107014
+ allDay,
107015
+ projectId,
107016
+ customerId,
107017
+ metadata,
107018
+ billingStatus: "unbillable"
107019
+ }).returning({ id: schema_exports.timesheetEvents.id });
107020
+ if (!created) throw new Error("Failed to create calendar item.");
107021
+ if (ticketId) {
107022
+ await db.insert(schema_exports.timesheetEventTickets).values({
107023
+ timesheetEventId: created.id,
107024
+ ticketId
107025
+ });
107026
+ }
107027
+ const item = await loadCalendarItemById(teamId, created.id);
107028
+ return {
107029
+ content: [
107030
+ {
107031
+ type: "text",
107032
+ text: `\u2705 **Calendar item created**
107033
+
107034
+ ` + JSON.stringify(formatCalendarItem(item), null, 2)
107035
+ }
107036
+ ]
107037
+ };
107038
+ }
107039
+ async function handleUpdateCalendarItem(input) {
107040
+ const ctx = getAuthContext();
107041
+ const resolved = await resolveTeamId(input.teamId);
107042
+ if (!resolved.ok) return resolved.response;
107043
+ const teamId = resolved.teamId;
107044
+ const existing = await loadCalendarItemById(teamId, input.id);
107045
+ if (!existing) {
107046
+ return {
107047
+ content: [
107048
+ {
107049
+ type: "text",
107050
+ text: `Calendar item not found or no access: ${input.id}. Call get-calendar-items to find the correct id.`
107051
+ }
107052
+ ]
107053
+ };
107054
+ }
107055
+ const projectIds = await getAccessibleProjectIds(ctx.userId, teamId);
107056
+ const customerIds = await getAccessibleCustomerIds(teamId);
107057
+ if (input.projectId) {
107058
+ await validateProjectAccess(teamId, input.projectId, projectIds);
107059
+ }
107060
+ if (input.customerId && !customerIds.includes(input.customerId)) {
107061
+ throw new Error(`Customer not found or no access: ${input.customerId}.`);
107062
+ }
107063
+ if (input.assigneeId) {
107064
+ await validateAssignee(teamId, input.assigneeId);
107065
+ }
107066
+ const updateValues = {
107067
+ updatedAt: sql`NOW()`
107068
+ };
107069
+ if (input.title !== void 0) updateValues.title = input.title;
107070
+ if (input.description !== void 0) {
107071
+ updateValues.description = input.description;
107072
+ }
107073
+ if (input.projectId !== void 0) updateValues.projectId = input.projectId;
107074
+ if (input.customerId !== void 0) {
107075
+ updateValues.customerId = input.customerId;
107076
+ }
107077
+ if (input.assigneeId !== void 0) updateValues.userId = input.assigneeId;
107078
+ if (input.type !== void 0) {
107079
+ updateValues.type = mapInputType(input.type);
107080
+ }
107081
+ if (input.dueDate !== void 0 || input.startsAt !== void 0 || input.endsAt !== void 0) {
107082
+ const currentType = input.type ?? mapOutputType(existing.type, existing.metadata);
107083
+ const { startTime, endTime, allDay } = resolveEventTimes({
107084
+ dueDate: input.dueDate,
107085
+ startsAt: input.startsAt ?? existing.startTime,
107086
+ endsAt: input.endsAt ?? existing.endTime ?? void 0,
107087
+ type: currentType
107088
+ });
107089
+ updateValues.startTime = startTime;
107090
+ updateValues.endTime = endTime;
107091
+ updateValues.allDay = allDay;
107092
+ }
107093
+ const existingMeta = existing.metadata && typeof existing.metadata === "object" && !Array.isArray(existing.metadata) ? existing.metadata : {};
107094
+ const nextMeta = { ...existingMeta };
107095
+ if (input.type !== void 0) nextMeta.calendarType = input.type;
107096
+ if (input.status === "completed") {
107097
+ nextMeta.completed = true;
107098
+ updateValues.status = "confirmed";
107099
+ } else if (input.status === "cancelled") {
107100
+ await db.update(schema_exports.timesheetEvents).set({
107101
+ isDeleted: true,
107102
+ deletedAt: sql`NOW()`,
107103
+ updatedAt: sql`NOW()`
107104
+ }).where(
107105
+ and(
107106
+ eq(schema_exports.timesheetEvents.id, input.id),
107107
+ eq(schema_exports.timesheetEvents.teamId, teamId)
107108
+ )
107109
+ );
107110
+ const cancelled = await db.select({ id: schema_exports.timesheetEvents.id }).from(schema_exports.timesheetEvents).where(eq(schema_exports.timesheetEvents.id, input.id)).limit(1);
107111
+ if (cancelled[0]) {
107112
+ return {
107113
+ content: [
107114
+ {
107115
+ type: "text",
107116
+ text: `\u2705 Calendar item ${input.id} cancelled (soft-deleted).`
107117
+ }
107118
+ ]
107119
+ };
107120
+ }
107121
+ } else if (input.status === "draft") {
107122
+ updateValues.status = "draft";
107123
+ delete nextMeta.completed;
107124
+ } else if (input.status === "scheduled") {
107125
+ updateValues.status = "confirmed";
107126
+ delete nextMeta.completed;
107127
+ }
107128
+ updateValues.metadata = nextMeta;
107129
+ await db.update(schema_exports.timesheetEvents).set(updateValues).where(
107130
+ and(
107131
+ eq(schema_exports.timesheetEvents.id, input.id),
107132
+ eq(schema_exports.timesheetEvents.teamId, teamId)
107133
+ )
107134
+ );
107135
+ if (input.ticketId !== void 0) {
107136
+ await db.delete(schema_exports.timesheetEventTickets).where(eq(schema_exports.timesheetEventTickets.timesheetEventId, input.id));
107137
+ if (input.ticketId) {
107138
+ const access = await loadAccessibleTicket(input.teamId, input.ticketId);
107139
+ if (!access.ok) return access.response;
107140
+ await db.insert(schema_exports.timesheetEventTickets).values({
107141
+ timesheetEventId: input.id,
107142
+ ticketId: input.ticketId
107143
+ });
107144
+ }
107145
+ }
107146
+ const item = await loadCalendarItemById(teamId, input.id);
107147
+ return {
107148
+ content: [
107149
+ {
107150
+ type: "text",
107151
+ text: `\u2705 **Calendar item updated**
107152
+
107153
+ ` + JSON.stringify(formatCalendarItem(item), null, 2)
107154
+ }
107155
+ ]
107156
+ };
107157
+ }
107158
+ async function syncTicketDeadline(teamId, ticket, dueDate) {
107159
+ const ctx = getAuthContext();
107160
+ if (dueDate === null) {
107161
+ const existingId = await findExistingTicketDeadline(teamId, ticket.id);
107162
+ if (existingId) {
107163
+ await db.update(schema_exports.timesheetEvents).set({ isDeleted: true, deletedAt: sql`NOW()`, updatedAt: sql`NOW()` }).where(eq(schema_exports.timesheetEvents.id, existingId));
107164
+ }
107165
+ return "deadline removed";
107166
+ }
107167
+ if (dueDate) {
107168
+ const title = `Deadline: ${ticket.title}`;
107169
+ const { startTime, endTime, allDay } = parseDueDate(dueDate);
107170
+ const assigneeId = ticket.assigneeId ?? ctx.userId;
107171
+ const existingId = await findExistingTicketDeadline(teamId, ticket.id);
107172
+ if (existingId) {
107173
+ await db.update(schema_exports.timesheetEvents).set({
107174
+ title,
107175
+ startTime,
107176
+ endTime,
107177
+ allDay,
107178
+ projectId: ticket.projectId,
107179
+ customerId: ticket.customerId,
107180
+ userId: assigneeId,
107181
+ type: "deadline",
107182
+ status: "confirmed",
107183
+ metadata: {
107184
+ calendarType: "deadline",
107185
+ source: "mcp_ticket_due_date"
107186
+ },
107187
+ updatedAt: sql`NOW()`
107188
+ }).where(eq(schema_exports.timesheetEvents.id, existingId));
107189
+ return "deadline updated";
107190
+ }
107191
+ const [created] = await db.insert(schema_exports.timesheetEvents).values({
107192
+ teamId,
107193
+ userId: assigneeId,
107194
+ title,
107195
+ type: "deadline",
107196
+ status: "confirmed",
107197
+ startTime,
107198
+ endTime,
107199
+ allDay,
107200
+ projectId: ticket.projectId,
107201
+ customerId: ticket.customerId,
107202
+ metadata: {
107203
+ calendarType: "deadline",
107204
+ source: "mcp_ticket_due_date"
107205
+ },
107206
+ billingStatus: "unbillable"
107207
+ }).returning({ id: schema_exports.timesheetEvents.id });
107208
+ if (created) {
107209
+ await db.insert(schema_exports.timesheetEventTickets).values({
107210
+ timesheetEventId: created.id,
107211
+ ticketId: ticket.id
107212
+ });
107213
+ }
107214
+ return "deadline created";
107215
+ }
107216
+ if (ticket.status === "resolved" || ticket.status === "closed") {
107217
+ const existingId = await findExistingTicketDeadline(teamId, ticket.id);
107218
+ if (existingId) {
107219
+ const [existing] = await db.select({ metadata: schema_exports.timesheetEvents.metadata }).from(schema_exports.timesheetEvents).where(eq(schema_exports.timesheetEvents.id, existingId)).limit(1);
107220
+ const meta5 = existing?.metadata && typeof existing.metadata === "object" && !Array.isArray(existing.metadata) ? existing.metadata : {};
107221
+ await db.update(schema_exports.timesheetEvents).set({
107222
+ metadata: { ...meta5, completed: true },
107223
+ updatedAt: sql`NOW()`
107224
+ }).where(eq(schema_exports.timesheetEvents.id, existingId));
107225
+ return "deadline marked completed";
107226
+ }
107227
+ }
107228
+ return null;
107229
+ }
107230
+
107231
+ // src/tools/customers.ts
107232
+ async function handleGetCustomers(input) {
107233
+ const { q: q3, pageSize = 20 } = input;
107234
+ const resolved = await resolveTeamId(input.teamId);
107235
+ if (!resolved.ok) return resolved.response;
107236
+ const customerIds = await getAccessibleCustomerIds(resolved.teamId);
107237
+ if (customerIds.length === 0) {
107238
+ return {
107239
+ content: [
107240
+ {
107241
+ type: "text",
107242
+ text: "No customers found or no access to any customers."
107243
+ }
107244
+ ]
107245
+ };
107246
+ }
107247
+ const filters = [inArray(schema_exports.customers.id, customerIds)];
107248
+ if (q3) {
107249
+ const pattern = `%${q3}%`;
107250
+ filters.push(
107251
+ or(
107252
+ ilike(schema_exports.customers.name, pattern),
107253
+ ilike(schema_exports.customers.email, pattern)
107254
+ )
107255
+ );
107256
+ }
107257
+ const rows = await db.select({
107258
+ id: schema_exports.customers.id,
107259
+ name: schema_exports.customers.name,
107260
+ email: schema_exports.customers.email,
107261
+ website: schema_exports.customers.website,
107262
+ createdAt: schema_exports.customers.createdAt
107263
+ }).from(schema_exports.customers).where(and(...filters)).orderBy(asc(schema_exports.customers.name)).limit(Math.min(pageSize, 100));
107264
+ return {
107265
+ content: [
107266
+ {
107267
+ type: "text",
107268
+ text: `Found ${rows.length} customers:
107269
+
107270
+ ${rows.map(
107271
+ (c6) => `**${c6.name}**
107272
+ ${c6.email ? `Email: ${c6.email}
107273
+ ` : ""}${c6.website ? `Website: ${c6.website}
107274
+ ` : ""}Created: ${new Date(c6.createdAt).toLocaleDateString()}
107275
+ `
107276
+ ).join("\n") || "No customers found."}`
107277
+ }
107278
+ ]
107279
+ };
107280
+ }
107281
+ async function handleCreateCustomer(input) {
107282
+ const { name: name21, email: email5, website } = input;
107283
+ const resolved = await resolveTeamId(input.teamId);
107284
+ if (!resolved.ok) return resolved.response;
107285
+ await db.insert(schema_exports.customers).values({
107286
+ teamId: resolved.teamId,
107287
+ name: name21,
107288
+ email: email5 ?? "",
107289
+ website: website ?? null
107290
+ });
107291
+ return {
107292
+ content: [
107293
+ {
107294
+ type: "text",
107295
+ text: `\u2705 **Customer Created Successfully!**
107296
+
107297
+ Name: ${name21}
107298
+ ${email5 ? `Email: ${email5}
107299
+ ` : ""}${website ? `Website: ${website}
107300
+ ` : ""}`
107301
+ }
107302
+ ]
107303
+ };
107304
+ }
107305
+
107306
+ // ../document/src/humanizer/rules.ts
107307
+ var REPLACEMENTS = [
107308
+ // --- Dutch clichés ---
107309
+ { pattern: /\bbovendien\b/gi, replacement: "daarnaast", rule: "nl-cliche" },
107310
+ { pattern: /\btevens\b/gi, replacement: "ook", rule: "nl-cliche" },
107311
+ { pattern: /\bkortom\b/gi, replacement: "samengevat", rule: "nl-cliche" },
107312
+ { pattern: /\bnaadloze\b/gi, replacement: "soepele", rule: "nl-cliche" },
107313
+ { pattern: /\bnaadloos\b/gi, replacement: "soepel", rule: "nl-cliche" },
107314
+ { pattern: /\brobuuste\b/gi, replacement: "solide", rule: "nl-cliche" },
107315
+ { pattern: /\brobuust\b/gi, replacement: "solide", rule: "nl-cliche" },
107316
+ { pattern: /\bcruciale\b/gi, replacement: "belangrijke", rule: "nl-cliche" },
107317
+ { pattern: /\bcruciaal\b/gi, replacement: "belangrijk", rule: "nl-cliche" },
107318
+ {
107319
+ pattern: /\bessentiële\b/gi,
107320
+ replacement: "belangrijke",
107321
+ rule: "nl-cliche"
107322
+ },
107323
+ { pattern: /\bessentieel\b/gi, replacement: "belangrijk", rule: "nl-cliche" },
107324
+ {
107325
+ pattern: /\been breed scala aan\b/gi,
107326
+ replacement: "veel",
107327
+ rule: "nl-cliche"
107328
+ },
107329
+ {
107330
+ pattern: /\boptimaal benutten\b/gi,
107331
+ replacement: "goed benutten",
107332
+ rule: "nl-cliche"
107333
+ },
107334
+ {
107335
+ pattern: /\bin de wereld van vandaag\b/gi,
107336
+ replacement: "vandaag de dag",
107337
+ rule: "nl-cliche"
107338
+ },
107339
+ {
107340
+ pattern: /\bin het huidige digitale tijdperk\b/gi,
107341
+ replacement: "tegenwoordig",
107342
+ rule: "nl-cliche"
107343
+ },
107344
+ {
106611
107345
  pattern: /\bstate-of-the-art\b/gi,
106612
107346
  replacement: "moderne",
106613
107347
  rule: "nl-cliche"
@@ -118800,38 +119534,6 @@ var storage = new Proxy({}, {
118800
119534
  }
118801
119535
  });
118802
119536
 
118803
- // src/tools/ticket-access.ts
118804
- function notFoundResponse(ticketId) {
118805
- return {
118806
- content: [
118807
- {
118808
- type: "text",
118809
- text: `Ticket not found or no access: ${ticketId}. Call get-tickets to find the correct ticket.`
118810
- }
118811
- ]
118812
- };
118813
- }
118814
- async function loadAccessibleTicket(requestedTeamId, ticketId) {
118815
- const scope = await resolveTeamScope(requestedTeamId);
118816
- if (!scope.ok) return scope;
118817
- const [ticket] = await db.select({
118818
- id: schema_exports.tickets.id,
118819
- teamId: schema_exports.tickets.teamId,
118820
- projectId: schema_exports.tickets.projectId,
118821
- customerId: schema_exports.tickets.customerId,
118822
- ticketNumber: schema_exports.tickets.ticketNumber,
118823
- title: schema_exports.tickets.title,
118824
- status: schema_exports.tickets.status,
118825
- priority: schema_exports.tickets.priority,
118826
- type: schema_exports.tickets.type,
118827
- assigneeId: schema_exports.tickets.assigneeId
118828
- }).from(schema_exports.tickets).where(eq(schema_exports.tickets.id, ticketId)).limit(1);
118829
- if (!ticket) return { ok: false, response: notFoundResponse(ticketId) };
118830
- const hasAccess = scope.teamIds.includes(ticket.teamId) || !!ticket.projectId && scope.projectIds.includes(ticket.projectId) || !!ticket.customerId && scope.customerIds.includes(ticket.customerId);
118831
- if (!hasAccess) return { ok: false, response: notFoundResponse(ticketId) };
118832
- return { ok: true, ticket };
118833
- }
118834
-
118835
119537
  // src/tools/ticket-attachments.ts
118836
119538
  var ALLOWED_IMAGE_TYPES = [
118837
119539
  "image/jpeg",
@@ -119182,6 +119884,262 @@ ${rendered}`
119182
119884
  };
119183
119885
  }
119184
119886
 
119887
+ // src/tools/ticket-tags.ts
119888
+ function normalizeTagName(name21) {
119889
+ return name21.toLowerCase().trim();
119890
+ }
119891
+ function formatTagList(tags2) {
119892
+ if (tags2.length === 0) return "";
119893
+ return tags2.map((t8) => t8.name).join(", ");
119894
+ }
119895
+ async function getTagsForTickets(ticketIds) {
119896
+ const result = /* @__PURE__ */ new Map();
119897
+ if (ticketIds.length === 0) return result;
119898
+ const rows = await db.select({
119899
+ ticketId: schema_exports.ticketTags.ticketId,
119900
+ id: schema_exports.tags.id,
119901
+ name: schema_exports.tags.name
119902
+ }).from(schema_exports.ticketTags).innerJoin(schema_exports.tags, eq(schema_exports.tags.id, schema_exports.ticketTags.tagId)).where(inArray(schema_exports.ticketTags.ticketId, ticketIds)).orderBy(schema_exports.tags.name);
119903
+ for (const row of rows) {
119904
+ const existing = result.get(row.ticketId) ?? [];
119905
+ existing.push({ id: row.id, name: row.name });
119906
+ result.set(row.ticketId, existing);
119907
+ }
119908
+ return result;
119909
+ }
119910
+ async function resolveTagFilterIds(teamId, input) {
119911
+ const ids = new Set(input.tagIds ?? []);
119912
+ const names = [
119913
+ ...input.tag ? [input.tag] : [],
119914
+ ...input.tags ?? []
119915
+ ].map(normalizeTagName).filter(Boolean);
119916
+ if (names.length > 0) {
119917
+ const nameConditions = names.map(
119918
+ (name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
119919
+ );
119920
+ const rows = await db.select({ id: schema_exports.tags.id }).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
119921
+ for (const row of rows) ids.add(row.id);
119922
+ }
119923
+ return [...ids];
119924
+ }
119925
+ async function resolveTags(teamId, input) {
119926
+ const tags2 = [];
119927
+ const errors = [];
119928
+ const seenIds = /* @__PURE__ */ new Set();
119929
+ if (input.tagIds?.length) {
119930
+ const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
119931
+ and(
119932
+ eq(schema_exports.tags.teamId, teamId),
119933
+ inArray(schema_exports.tags.id, input.tagIds)
119934
+ )
119935
+ );
119936
+ for (const row of rows) {
119937
+ if (seenIds.has(row.id)) continue;
119938
+ seenIds.add(row.id);
119939
+ tags2.push(row);
119940
+ }
119941
+ for (const id of input.tagIds) {
119942
+ if (!seenIds.has(id)) errors.push(`Unknown tag ID: ${id}`);
119943
+ }
119944
+ }
119945
+ const rawNames = input.tagNames ?? [];
119946
+ if (rawNames.length === 0) return { tags: tags2, errors };
119947
+ const normalizedNames = [
119948
+ ...new Set(rawNames.map(normalizeTagName).filter(Boolean))
119949
+ ];
119950
+ if (normalizedNames.length === 0) return { tags: tags2, errors };
119951
+ const nameConditions = normalizedNames.map(
119952
+ (name21) => sql`lower(${schema_exports.tags.name}) = ${name21}`
119953
+ );
119954
+ const existing = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(and(eq(schema_exports.tags.teamId, teamId), or(...nameConditions)));
119955
+ const existingByNorm = /* @__PURE__ */ new Map();
119956
+ for (const tag of existing) {
119957
+ existingByNorm.set(normalizeTagName(tag.name), tag);
119958
+ }
119959
+ for (const rawName of rawNames) {
119960
+ const norm = normalizeTagName(rawName);
119961
+ if (!norm) continue;
119962
+ const found = existingByNorm.get(norm);
119963
+ if (found) {
119964
+ if (!seenIds.has(found.id)) {
119965
+ seenIds.add(found.id);
119966
+ tags2.push(found);
119967
+ }
119968
+ continue;
119969
+ }
119970
+ if (!input.createMissing) {
119971
+ errors.push(`Tag not found: ${rawName}`);
119972
+ continue;
119973
+ }
119974
+ try {
119975
+ const [created] = await db.insert(schema_exports.tags).values({
119976
+ teamId,
119977
+ name: norm,
119978
+ projectId: input.projectId ?? null
119979
+ }).returning({ id: schema_exports.tags.id, name: schema_exports.tags.name });
119980
+ if (created) {
119981
+ seenIds.add(created.id);
119982
+ tags2.push(created);
119983
+ existingByNorm.set(norm, created);
119984
+ }
119985
+ } catch {
119986
+ const [retry2] = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
119987
+ and(
119988
+ eq(schema_exports.tags.teamId, teamId),
119989
+ sql`lower(${schema_exports.tags.name}) = ${norm}`,
119990
+ input.projectId ? or(
119991
+ eq(schema_exports.tags.projectId, input.projectId),
119992
+ isNull(schema_exports.tags.projectId)
119993
+ ) : isNull(schema_exports.tags.projectId)
119994
+ )
119995
+ ).limit(1);
119996
+ if (retry2 && !seenIds.has(retry2.id)) {
119997
+ seenIds.add(retry2.id);
119998
+ tags2.push(retry2);
119999
+ existingByNorm.set(norm, retry2);
120000
+ } else {
120001
+ errors.push(`Failed to create tag: ${rawName}`);
120002
+ }
120003
+ }
120004
+ }
120005
+ return { tags: tags2, errors };
120006
+ }
120007
+ async function syncTicketTags(ticketId, teamId, tagIds, mode = "replace") {
120008
+ const current = await db.select({ tagId: schema_exports.ticketTags.tagId }).from(schema_exports.ticketTags).where(eq(schema_exports.ticketTags.ticketId, ticketId));
120009
+ const currentIds = new Set(current.map((row) => row.tagId));
120010
+ let targetIds;
120011
+ if (mode === "remove") {
120012
+ targetIds = new Set(
120013
+ [...currentIds].filter((id) => !tagIds.includes(id))
120014
+ );
120015
+ } else if (mode === "merge") {
120016
+ targetIds = /* @__PURE__ */ new Set([...currentIds, ...tagIds]);
120017
+ } else {
120018
+ targetIds = new Set(tagIds);
120019
+ }
120020
+ const toInsert = [...targetIds].filter((id) => !currentIds.has(id));
120021
+ const toDelete = [...currentIds].filter((id) => !targetIds.has(id));
120022
+ if (toDelete.length > 0) {
120023
+ await db.delete(schema_exports.ticketTags).where(
120024
+ and(
120025
+ eq(schema_exports.ticketTags.ticketId, ticketId),
120026
+ inArray(schema_exports.ticketTags.tagId, toDelete)
120027
+ )
120028
+ );
120029
+ }
120030
+ if (toInsert.length > 0) {
120031
+ await db.insert(schema_exports.ticketTags).values(
120032
+ toInsert.map((tagId) => ({
120033
+ ticketId,
120034
+ tagId,
120035
+ teamId
120036
+ }))
120037
+ );
120038
+ }
120039
+ const tagIdsToDescribe = [.../* @__PURE__ */ new Set([...toInsert, ...toDelete])];
120040
+ const tagNamesById = /* @__PURE__ */ new Map();
120041
+ if (tagIdsToDescribe.length > 0) {
120042
+ const rows = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(inArray(schema_exports.tags.id, tagIdsToDescribe));
120043
+ for (const row of rows) tagNamesById.set(row.id, row.name);
120044
+ }
120045
+ return {
120046
+ added: toInsert.map((id) => tagNamesById.get(id) ?? id),
120047
+ removed: toDelete.map((id) => tagNamesById.get(id) ?? id)
120048
+ };
120049
+ }
120050
+
120051
+ // src/tools/tags.ts
120052
+ async function handleGetTags(input) {
120053
+ const resolved = await resolveTeamId(input.teamId);
120054
+ if (!resolved.ok) return resolved.response;
120055
+ const filters = [eq(schema_exports.tags.teamId, resolved.teamId)];
120056
+ if (input.projectId !== void 0) {
120057
+ filters.push(
120058
+ or(
120059
+ eq(schema_exports.tags.projectId, input.projectId),
120060
+ isNull(schema_exports.tags.projectId)
120061
+ )
120062
+ );
120063
+ }
120064
+ const rows = await db.select({
120065
+ id: schema_exports.tags.id,
120066
+ name: schema_exports.tags.name,
120067
+ projectId: schema_exports.tags.projectId,
120068
+ createdAt: schema_exports.tags.createdAt
120069
+ }).from(schema_exports.tags).where(and(...filters)).orderBy(asc(schema_exports.tags.name)).limit(Math.min(input.pageSize ?? 100, 200));
120070
+ return {
120071
+ content: [
120072
+ {
120073
+ type: "text",
120074
+ text: `Found ${rows.length} tags (team-specific${input.projectId ? ", including general tags for this project" : ""}):
120075
+
120076
+ ` + (rows.map(
120077
+ (tag) => `**${tag.name}** (id: ${tag.id})${tag.projectId ? " [project-specific]" : " [general]"}`
120078
+ ).join("\n") || "No tags found.")
120079
+ }
120080
+ ]
120081
+ };
120082
+ }
120083
+ async function handleCreateTag(input) {
120084
+ const name21 = input.name.trim();
120085
+ if (!name21) {
120086
+ return {
120087
+ content: [{ type: "text", text: "Tag name is required." }]
120088
+ };
120089
+ }
120090
+ const resolved = await resolveTeamId(input.teamId);
120091
+ if (!resolved.ok) return resolved.response;
120092
+ const normalized = normalizeTagName(name21);
120093
+ const scopeFilter = input.projectId ? eq(schema_exports.tags.projectId, input.projectId) : isNull(schema_exports.tags.projectId);
120094
+ const [existing] = await db.select({ id: schema_exports.tags.id, name: schema_exports.tags.name }).from(schema_exports.tags).where(
120095
+ and(
120096
+ eq(schema_exports.tags.teamId, resolved.teamId),
120097
+ scopeFilter,
120098
+ sql`lower(${schema_exports.tags.name}) = ${normalized}`
120099
+ )
120100
+ ).limit(1);
120101
+ if (existing) {
120102
+ return {
120103
+ content: [
120104
+ {
120105
+ type: "text",
120106
+ text: `\u2139\uFE0F Tag already exists (case-insensitive match):
120107
+
120108
+ Name: **${existing.name}**
120109
+ ID: ${existing.id}`
120110
+ }
120111
+ ]
120112
+ };
120113
+ }
120114
+ const [created] = await db.insert(schema_exports.tags).values({
120115
+ teamId: resolved.teamId,
120116
+ name: name21,
120117
+ projectId: input.projectId ?? null
120118
+ }).returning({
120119
+ id: schema_exports.tags.id,
120120
+ name: schema_exports.tags.name,
120121
+ projectId: schema_exports.tags.projectId
120122
+ });
120123
+ if (!created) {
120124
+ return {
120125
+ content: [{ type: "text", text: "Failed to create tag." }]
120126
+ };
120127
+ }
120128
+ return {
120129
+ content: [
120130
+ {
120131
+ type: "text",
120132
+ text: `\u2705 **Tag Created**
120133
+
120134
+ Name: **${created.name}**
120135
+ ID: ${created.id}
120136
+ ${created.projectId ? `Project ID: ${created.projectId}
120137
+ ` : "Scope: general (team-wide)\n"}`
120138
+ }
120139
+ ]
120140
+ };
120141
+ }
120142
+
119185
120143
  // src/tools/ticket-update.ts
119186
120144
  async function handleUpdateTicket(input) {
119187
120145
  const ctx = getAuthContext();
@@ -119227,6 +120185,38 @@ async function handleUpdateTicket(input) {
119227
120185
  if (input.estimatedHours !== void 0) {
119228
120186
  updateValues.estimatedHours = input.estimatedHours;
119229
120187
  }
120188
+ if (input.dueDate !== void 0) {
120189
+ updateValues.dueDate = input.dueDate;
120190
+ updateValues.dueDateAllDay = input.dueDate ? true : false;
120191
+ }
120192
+ let deadlineChange = null;
120193
+ if (input.dueDate !== void 0) {
120194
+ deadlineChange = await syncTicketDeadline(
120195
+ ticket.teamId,
120196
+ {
120197
+ id: ticket.id,
120198
+ title: input.title ?? ticket.title,
120199
+ status: input.status ?? ticket.status,
120200
+ projectId: input.projectId ?? ticket.projectId,
120201
+ customerId: input.customerId ?? ticket.customerId,
120202
+ assigneeId: input.assigneeId !== void 0 ? input.assigneeId : ticket.assigneeId
120203
+ },
120204
+ input.dueDate
120205
+ );
120206
+ } else if (input.status !== void 0 && (input.status === "resolved" || input.status === "closed") && input.status !== ticket.status) {
120207
+ deadlineChange = await syncTicketDeadline(
120208
+ ticket.teamId,
120209
+ {
120210
+ id: ticket.id,
120211
+ title: input.title ?? ticket.title,
120212
+ status: input.status,
120213
+ projectId: input.projectId ?? ticket.projectId,
120214
+ customerId: input.customerId ?? ticket.customerId,
120215
+ assigneeId: input.assigneeId !== void 0 ? input.assigneeId : ticket.assigneeId
120216
+ },
120217
+ void 0
120218
+ );
120219
+ }
119230
120220
  await db.update(schema_exports.tickets).set(updateValues).where(eq(schema_exports.tickets.id, ticket.id));
119231
120221
  const changes = [];
119232
120222
  const activities2 = [];
@@ -119271,6 +120261,61 @@ async function handleUpdateTicket(input) {
119271
120261
  if (input.projectId !== void 0) changes.push("project updated");
119272
120262
  if (input.customerId !== void 0) changes.push("customer updated");
119273
120263
  if (input.estimatedHours !== void 0) changes.push("estimated hours updated");
120264
+ if (deadlineChange) changes.push(deadlineChange);
120265
+ if (input.tags !== void 0 || input.tagIds !== void 0) {
120266
+ const tagMode = input.tagMode ?? "replace";
120267
+ const createMissing = tagMode === "replace" || tagMode === "merge";
120268
+ const resolvedTags = await resolveTags(ticket.teamId, {
120269
+ tagNames: input.tags,
120270
+ tagIds: input.tagIds,
120271
+ projectId: input.projectId ?? ticket.projectId,
120272
+ createMissing
120273
+ });
120274
+ if (resolvedTags.errors.length > 0) {
120275
+ return {
120276
+ content: [
120277
+ {
120278
+ type: "text",
120279
+ text: `Cannot update tags on ${ticket.ticketNumber}:
120280
+ ` + resolvedTags.errors.map((e6) => ` \u2022 ${e6}`).join("\n")
120281
+ }
120282
+ ]
120283
+ };
120284
+ }
120285
+ const { added, removed } = await syncTicketTags(
120286
+ ticket.id,
120287
+ ticket.teamId,
120288
+ resolvedTags.tags.map((t8) => t8.id),
120289
+ tagMode
120290
+ );
120291
+ if (added.length > 0) {
120292
+ changes.push(`tags added: ${added.join(", ")}`);
120293
+ for (const tagName of added) {
120294
+ await db.insert(schema_exports.ticketActivity).values({
120295
+ ticketId: ticket.id,
120296
+ teamId: ticket.teamId,
120297
+ userId: ctx.userId,
120298
+ activityType: "tag_added",
120299
+ newValue: tagName
120300
+ });
120301
+ }
120302
+ }
120303
+ if (removed.length > 0) {
120304
+ changes.push(`tags removed: ${removed.join(", ")}`);
120305
+ for (const tagName of removed) {
120306
+ await db.insert(schema_exports.ticketActivity).values({
120307
+ ticketId: ticket.id,
120308
+ teamId: ticket.teamId,
120309
+ userId: ctx.userId,
120310
+ activityType: "tag_removed",
120311
+ newValue: tagName
120312
+ });
120313
+ }
120314
+ }
120315
+ if (tagMode === "replace" && added.length === 0 && removed.length === 0) {
120316
+ changes.push("tags cleared");
120317
+ }
120318
+ }
119274
120319
  for (const activity of activities2) {
119275
120320
  await db.insert(schema_exports.ticketActivity).values({
119276
120321
  ticketId: ticket.id,
@@ -119330,7 +120375,17 @@ async function downloadImageAsBase64(storageKey) {
119330
120375
  }
119331
120376
  async function handleGetTickets(input) {
119332
120377
  const ctx = getAuthContext();
119333
- const { status, priority, projectId, customerId, q: q3, pageSize = 20 } = input;
120378
+ const {
120379
+ status,
120380
+ priority,
120381
+ projectId,
120382
+ customerId,
120383
+ q: q3,
120384
+ tag,
120385
+ tags: tags2,
120386
+ tagIds,
120387
+ pageSize = 20
120388
+ } = input;
119334
120389
  const resolved = await resolveTeamId(input.teamId);
119335
120390
  if (!resolved.ok) return resolved.response;
119336
120391
  const teamId = resolved.teamId;
@@ -119357,6 +120412,29 @@ async function handleGetTickets(input) {
119357
120412
  )
119358
120413
  );
119359
120414
  }
120415
+ const filterTagIds = await resolveTagFilterIds(teamId, { tag, tags: tags2, tagIds });
120416
+ if (tag || tags2?.length || tagIds?.length) {
120417
+ if (filterTagIds.length === 0) {
120418
+ return {
120419
+ content: [
120420
+ {
120421
+ type: "text",
120422
+ text: "No tickets found (no matching tags for the given filter)."
120423
+ }
120424
+ ]
120425
+ };
120426
+ }
120427
+ filters.push(
120428
+ sql`EXISTS (
120429
+ SELECT 1 FROM ${schema_exports.ticketTags}
120430
+ WHERE ${schema_exports.ticketTags.ticketId} = ${schema_exports.tickets.id}
120431
+ AND ${schema_exports.ticketTags.tagId} IN (${sql.join(
120432
+ filterTagIds.map((id) => sql`${id}`),
120433
+ sql`, `
120434
+ )})
120435
+ )`
120436
+ );
120437
+ }
119360
120438
  const rows = await db.select({
119361
120439
  id: schema_exports.tickets.id,
119362
120440
  ticketNumber: schema_exports.tickets.ticketNumber,
@@ -119374,20 +120452,23 @@ async function handleGetTickets(input) {
119374
120452
  schema_exports.customers,
119375
120453
  eq(schema_exports.customers.id, schema_exports.tickets.customerId)
119376
120454
  ).where(and(...filters)).orderBy(desc(schema_exports.tickets.createdAt)).limit(Math.min(pageSize, 100));
120455
+ const tagsByTicket = await getTagsForTickets(rows.map((t8) => t8.id));
119377
120456
  return {
119378
120457
  content: [
119379
120458
  {
119380
120459
  type: "text",
119381
120460
  text: `Found ${rows.length} tickets:
119382
120461
 
119383
- ${rows.map(
119384
- (t8) => `**${t8.ticketNumber}**: ${t8.title}
120462
+ ${rows.map((t8) => {
120463
+ const ticketTags2 = tagsByTicket.get(t8.id) ?? [];
120464
+ return `**${t8.ticketNumber}**: ${t8.title}
119385
120465
  Status: ${t8.status} | Priority: ${t8.priority}
119386
- ${t8.projectName ? `Project: ${t8.projectName}
120466
+ ${ticketTags2.length > 0 ? `Tags: ${formatTagList(ticketTags2)}
120467
+ ` : ""}${t8.projectName ? `Project: ${t8.projectName}
119387
120468
  ` : ""}${t8.customerName ? `Customer: ${t8.customerName}
119388
120469
  ` : ""}Created: ${new Date(t8.createdAt).toLocaleDateString()}
119389
- `
119390
- ).join("\n") || "No tickets found."}`
120470
+ `;
120471
+ }).join("\n") || "No tickets found."}`
119391
120472
  }
119392
120473
  ]
119393
120474
  };
@@ -119500,6 +120581,11 @@ ${text3.split("\n").map((l4) => ` ${l4}`).join("\n")}`;
119500
120581
  const assigneeLine = ticketRow.assignee ? `Assignee: ${ticketRow.assignee.fullName || "Unknown"} [id: ${ticketRow.assignee.id}]
119501
120582
  ` : ticketRow.requester ? `Assignee: (unassigned) \u2014 use requester id ${ticketRow.requester.id} for review handoff
119502
120583
  ` : `Assignee: (unassigned)
120584
+ `;
120585
+ const ticketTagRows = await getTagsForTickets([id]);
120586
+ const ticketTags2 = ticketTagRows.get(id) ?? [];
120587
+ const tagsLine = ticketTags2.length > 0 ? `Tags: ${formatTagList(ticketTags2)}
120588
+ ` : `Tags: (none)
119503
120589
  `;
119504
120590
  const content = [
119505
120591
  {
@@ -119512,10 +120598,11 @@ Team id: ${ticketRow.teamId}
119512
120598
  Status: ${ticketRow.status}
119513
120599
  Priority: ${ticketRow.priority}
119514
120600
  Type: ${ticketRow.type}
119515
- ${ticketRow.description ? `Description: ${tiptapToPlainText(ticketRow.description)}
120601
+ ${ticketRow.dueDate ? `Deadline: ${new Date(ticketRow.dueDate).toLocaleDateString()}
120602
+ ` : ""}${ticketRow.description ? `Description: ${tiptapToPlainText(ticketRow.description)}
119516
120603
  ` : ""}${ticketRow.project?.name ? `Project: ${ticketRow.project.name}
119517
120604
  ` : ""}${ticketRow.customer?.name ? `Customer: ${ticketRow.customer.name}
119518
- ` : ""}` + assigneeLine + requesterLine + `Created: ${new Date(ticketRow.createdAt).toLocaleDateString()}
120605
+ ` : ""}` + tagsLine + assigneeLine + requesterLine + `Created: ${new Date(ticketRow.createdAt).toLocaleDateString()}
119519
120606
  ` + attachmentList + commentList
119520
120607
  }
119521
120608
  ];
@@ -119592,7 +120679,10 @@ async function handleCreateTicket(input) {
119592
120679
  priority = "medium",
119593
120680
  type = "task",
119594
120681
  projectId,
119595
- customerId
120682
+ customerId,
120683
+ dueDate,
120684
+ tags: tags2,
120685
+ tagIds
119596
120686
  } = input;
119597
120687
  const resolved = await resolveTeamId(input.teamId);
119598
120688
  if (!resolved.ok) return resolved.response;
@@ -119649,7 +120739,7 @@ async function handleCreateTicket(input) {
119649
120739
  const count = Number(countRow?.n ?? 0);
119650
120740
  ticketNumber = `${year2}-${String(count + 1).padStart(3, "0")}`;
119651
120741
  }
119652
- await db.insert(schema_exports.tickets).values({
120742
+ const [created] = await db.insert(schema_exports.tickets).values({
119653
120743
  teamId: resolvedTeamId,
119654
120744
  ticketNumber,
119655
120745
  title,
@@ -119659,20 +120749,54 @@ async function handleCreateTicket(input) {
119659
120749
  type,
119660
120750
  projectId: projectId ?? null,
119661
120751
  customerId: resolvedCustomerId ?? null,
119662
- requesterId: ctx.userId
120752
+ requesterId: ctx.userId,
120753
+ dueDate: dueDate ?? null,
120754
+ dueDateAllDay: dueDate ? true : false
120755
+ }).returning({
120756
+ id: schema_exports.tickets.id,
120757
+ ticketNumber: schema_exports.tickets.ticketNumber
119663
120758
  });
120759
+ if (!created) {
120760
+ throw new Error("Failed to create ticket");
120761
+ }
120762
+ let appliedTags = [];
120763
+ const tagErrors = [];
120764
+ if ((tags2?.length ?? 0) > 0 || (tagIds?.length ?? 0) > 0) {
120765
+ const resolvedTags = await resolveTags(resolvedTeamId, {
120766
+ tagNames: tags2,
120767
+ tagIds,
120768
+ projectId: projectId ?? null,
120769
+ createMissing: true
120770
+ });
120771
+ tagErrors.push(...resolvedTags.errors);
120772
+ if (resolvedTags.tags.length > 0) {
120773
+ await syncTicketTags(
120774
+ created.id,
120775
+ resolvedTeamId,
120776
+ resolvedTags.tags.map((t8) => t8.id),
120777
+ "replace"
120778
+ );
120779
+ appliedTags = resolvedTags.tags;
120780
+ }
120781
+ }
119664
120782
  return {
119665
120783
  content: [
119666
120784
  {
119667
120785
  type: "text",
119668
120786
  text: `\u2705 **Ticket Created Successfully!**
119669
120787
 
119670
- Ticket Number: **${ticketNumber}**
120788
+ Ticket Number: **${created.ticketNumber}**
120789
+ ID: ${created.id}
119671
120790
  Title: ${title}
119672
120791
  Status: ${status}
119673
120792
  Priority: ${priority}
119674
120793
  Type: ${type}
119675
- `
120794
+ ${dueDate ? `Deadline: ${new Date(dueDate).toLocaleDateString()}
120795
+ ` : ""}${appliedTags.length > 0 ? `Tags: ${formatTagList(appliedTags)}
120796
+ ` : ""}${tagErrors.length > 0 ? `
120797
+ \u26A0\uFE0F Tag warnings:
120798
+ ${tagErrors.map((e6) => ` \u2022 ${e6}`).join("\n")}
120799
+ ` : ""}`
119676
120800
  }
119677
120801
  ]
119678
120802
  };
@@ -119723,6 +120847,22 @@ function createMcpServer() {
119723
120847
  return await handleCreateTicket(asToolArgs(toolArgs));
119724
120848
  case "update-ticket":
119725
120849
  return await handleUpdateTicket(asToolArgs(toolArgs));
120850
+ case "get-tags":
120851
+ return await handleGetTags(asToolArgs(toolArgs));
120852
+ case "create-tag":
120853
+ return await handleCreateTag(asToolArgs(toolArgs));
120854
+ case "get-calendar-items":
120855
+ return await handleGetCalendarItems(
120856
+ asToolArgs(toolArgs)
120857
+ );
120858
+ case "create-calendar-item":
120859
+ return await handleCreateCalendarItem(
120860
+ asToolArgs(toolArgs)
120861
+ );
120862
+ case "update-calendar-item":
120863
+ return await handleUpdateCalendarItem(
120864
+ asToolArgs(toolArgs)
120865
+ );
119726
120866
  case "add-ticket-comment":
119727
120867
  return await handleAddTicketComment(
119728
120868
  asToolArgs(toolArgs)