node-autotask 0.31.0 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/MCP_TOOL_CATALOG.json +6 -6
  3. package/MCP_TOOL_MANIFEST.md +8 -10
  4. package/MCP_TOOL_OVERRIDES.json +0 -12
  5. package/N8N_MCP_MANIFEST.json +8 -8
  6. package/N8N_MCP_MANIFEST.md +8 -8
  7. package/README.md +7 -5
  8. package/capabilities.json +1417 -1393
  9. package/dist/capabilities.cjs +2 -2
  10. package/dist/capabilities.js +1 -1
  11. package/dist/{chunk-QXUN2VCJ.cjs → chunk-DCTHO7CQ.cjs} +1416 -1392
  12. package/dist/{chunk-DQLEVKZS.cjs → chunk-FTF2FEQ6.cjs} +526 -490
  13. package/dist/{chunk-6UA544OR.js → chunk-FVHPN7ZR.js} +61 -25
  14. package/dist/{chunk-PCLL536B.cjs → chunk-IKPFR6K2.cjs} +6 -6
  15. package/dist/{chunk-EPOHRNSX.js → chunk-M2EGFPGF.js} +1416 -1392
  16. package/dist/{chunk-3H3RJ4LA.cjs → chunk-QCB4LYKD.cjs} +143 -143
  17. package/dist/{chunk-INSFDXHP.js → chunk-R6HLJCI2.js} +3 -3
  18. package/dist/{chunk-RQZUV74N.js → chunk-T6EDJRLT.js} +2 -2
  19. package/dist/{index-Uqj987EE.d.ts → index-Cw4aNNMo.d.ts} +63 -12
  20. package/dist/{index--5jrRl4u.d.cts → index-OkbCWcef.d.cts} +63 -12
  21. package/dist/index.cjs +23 -23
  22. package/dist/index.d.cts +2 -2
  23. package/dist/index.d.ts +2 -2
  24. package/dist/index.js +5 -5
  25. package/dist/operations/index.cjs +112 -112
  26. package/dist/operations/index.d.cts +2 -2
  27. package/dist/operations/index.d.ts +2 -2
  28. package/dist/operations/index.js +2 -2
  29. package/dist/pool.cjs +9 -9
  30. package/dist/pool.d.cts +1 -1
  31. package/dist/pool.d.ts +1 -1
  32. package/dist/pool.js +4 -4
  33. package/dist/resources/index.cjs +4 -4
  34. package/dist/resources/index.d.cts +1 -1
  35. package/dist/resources/index.d.ts +1 -1
  36. package/dist/resources/index.js +3 -3
  37. package/docs/API.md +61 -36
  38. package/package.json +1 -1
@@ -40,10 +40,10 @@ import {
40
40
  unwrapItems,
41
41
  unwrapOperation,
42
42
  vendorId
43
- } from "./chunk-INSFDXHP.js";
43
+ } from "./chunk-R6HLJCI2.js";
44
44
  import {
45
45
  CAPABILITIES
46
- } from "./chunk-EPOHRNSX.js";
46
+ } from "./chunk-M2EGFPGF.js";
47
47
 
48
48
  // src/resources/appointments.ts
49
49
  var AppointmentsResource = class extends BaseResource {
@@ -446,7 +446,7 @@ function dateOnlyWindowBounds(kind, now, tz) {
446
446
  }
447
447
  async function dateWindowQuery(http, path, field, kind, limit, operation, now, tz) {
448
448
  const zone = tz === void 0 ? void 0 : validateZone(tz, operation);
449
- const { from, to } = dateWindowBounds(kind, now, zone);
449
+ const { from, to } = dateOnlyWindowBounds(kind, now, zone);
450
450
  const filter = [
451
451
  { field, op: "gte", value: from },
452
452
  { field, op: "lte", value: to }
@@ -484,15 +484,16 @@ function customRangeDay(value, label, zone, opName) {
484
484
  return { y, mo, d, ms };
485
485
  }
486
486
  async function customRangeQuery(http, path, field, from, to, limit, operation, zone) {
487
- const fromB = customRangeDay(from, "from", zone, operation);
488
- const toB = customRangeDay(to, "to", zone, operation);
487
+ if (zone !== void 0) validateZone(zone, operation);
488
+ const fromB = customRangeDay(from, "from", void 0, operation);
489
+ const toB = customRangeDay(to, "to", void 0, operation);
489
490
  if (fromB.ms > toB.ms) {
490
491
  throw new AutotaskConfigError(
491
492
  `${operation}: from (${isoUtc(fromB.ms)}) must not be after to (${isoUtc(toB.ms)})`,
492
493
  { operation }
493
494
  );
494
495
  }
495
- const toEndMs = zone === void 0 ? toB.ms + DAY_MS - 1 : localDayEndMs(toB.y, toB.mo, toB.d, zone);
496
+ const toEndMs = toB.ms + DAY_MS - 1;
496
497
  const filter = [
497
498
  { field, op: "gte", value: isoUtc(fromB.ms) },
498
499
  { field, op: "lte", value: isoUtc(toEndMs) }
@@ -12575,6 +12576,33 @@ var TicketWebhooksResource = class extends BaseResource {
12575
12576
  }
12576
12577
  };
12577
12578
 
12579
+ // src/resources/special/time_entries/posted.ts
12580
+ var isApproved = (entry) => typeof entry.billingApprovalDateTime === "string" && entry.billingApprovalDateTime.trim() !== "";
12581
+ async function postedStateOf(http, entries, op) {
12582
+ const states = /* @__PURE__ */ new Map();
12583
+ const ids2 = [...new Set(entries.map((e) => e.id))];
12584
+ let incomplete = false;
12585
+ for (let c = 0; c < ids2.length; c += FETCH_BY_IDS_CHUNK_SIZE) {
12586
+ const chunk = ids2.slice(c, c + FETCH_BY_IDS_CHUNK_SIZE);
12587
+ const res = await http.request({
12588
+ method: "POST",
12589
+ idempotent: true,
12590
+ path: "/V1.0/BillingItems/query",
12591
+ body: { MaxRecords: 500, IncludeFields: ["id", "timeEntryID"], filter: [{ field: "timeEntryID", op: "in", value: chunk }] },
12592
+ operation: op
12593
+ });
12594
+ const { items, pageDetails } = unwrapItems(res);
12595
+ const billed = new Set(items.map((i) => i.timeEntryID));
12596
+ const more = pageDetails.nextPageUrl != null;
12597
+ if (more) incomplete = true;
12598
+ for (const id of chunk) {
12599
+ if (billed.has(id)) states.set(id, "posted");
12600
+ else if (!more) states.set(id, "unposted");
12601
+ }
12602
+ }
12603
+ return { states, incomplete };
12604
+ }
12605
+
12578
12606
  // src/resources/special/time_entries/windows.ts
12579
12607
  var DAY_MS2 = 864e5;
12580
12608
  var iso2 = (ms) => new Date(ms).toISOString().slice(0, 19) + "Z";
@@ -12615,14 +12643,23 @@ async function customRange3(_resource, from, to, http, opts) {
12615
12643
  const { items } = await queryTimeEntries(_resource, filter, clampWindowLimit(opts?.limit), "time_entries.customRange", http);
12616
12644
  return items;
12617
12645
  }
12618
- async function postedDateStatusQuery(_resource, mode, http, opts) {
12619
- const filter = [
12620
- { field: "postedDate", op: mode === "posted" ? "exist" : "notExist" }
12621
- ];
12622
- if (opts?.ticketID !== void 0) filter.push({ field: "ticketID", op: "eq", value: opts.ticketID });
12646
+ async function postedStateQuery(_resource, mode, http, opts) {
12623
12647
  const operation = mode === "posted" ? "time_entries.getPosted" : "time_entries.getUnposted";
12624
- const { items } = await queryTimeEntries(_resource, filter, clampWindowLimit(opts?.limit), operation, http);
12625
- return items;
12648
+ const window = { from: null, to: null };
12649
+ const filter = [];
12650
+ const fromB = opts?.dateFrom === void 0 ? void 0 : customRangeDay(opts.dateFrom, "from", void 0, operation);
12651
+ const toB = opts?.dateTo === void 0 ? void 0 : customRangeDay(opts.dateTo, "to", void 0, operation);
12652
+ if (fromB && toB && fromB.ms > toB.ms) {
12653
+ throw new AutotaskConfigError(`${operation}: dateFrom (${iso2(fromB.ms)}) must not be after dateTo (${iso2(toB.ms)})`, { operation });
12654
+ }
12655
+ if (fromB) filter.push({ field: "dateWorked", op: "gte", value: window.from = iso2(fromB.ms) });
12656
+ if (toB) filter.push({ field: "dateWorked", op: "lte", value: window.to = iso2(toB.ms + DAY_MS2 - 1) });
12657
+ if (opts?.ticketID !== void 0) filter.push({ field: "ticketID", op: "eq", value: opts.ticketID });
12658
+ const read = await queryTimeEntries(_resource, filter, clampWindowLimit(opts?.limit), operation, http);
12659
+ const { states, incomplete } = await postedStateOf(http, read.items, operation);
12660
+ const items = read.items.filter((e) => states.get(e.id) === mode);
12661
+ const approvedCount = items.filter(isApproved).length;
12662
+ return { items, hasMore: read.hasMore || incomplete, window, approvedCount, unapprovedCount: items.length - approvedCount };
12626
12663
  }
12627
12664
  async function postedWithin(_resource, from, to, http, opts, opLabel = "time_entries.postedWithin") {
12628
12665
  const OP11 = opLabel;
@@ -12637,26 +12674,25 @@ async function postedWithin(_resource, from, to, http, opts, opLabel = "time_ent
12637
12674
  { field: "dateWorked", op: "gte", value: iso2(fromB.ms) },
12638
12675
  { field: "dateWorked", op: "lte", value: iso2(windowToMs) }
12639
12676
  ];
12640
- if (opts?.posted === true) filter.push({ field: "postedDate", op: "exist" });
12641
- else if (opts?.posted === false) filter.push({ field: "postedDate", op: "notExist" });
12642
12677
  if (opts?.ticketID !== void 0) filter.push({ field: "ticketID", op: "eq", value: opts.ticketID });
12643
12678
  if (opts?.resourceID !== void 0) filter.push({ field: "resourceID", op: "eq", value: opts.resourceID });
12644
12679
  const { items, hasMore } = await queryTimeEntries(_resource, filter, clampWindowLimit(opts?.limit), OP11, http);
12680
+ const { states, incomplete } = await postedStateOf(http, items, OP11);
12645
12681
  let postedCount = 0;
12646
- for (const item of items) {
12647
- const postedDate = item.postedDate;
12648
- if (typeof postedDate === "string" ? postedDate.trim() !== "" : postedDate !== null && postedDate !== void 0) {
12649
- postedCount += 1;
12650
- }
12682
+ let unpostedCount = 0;
12683
+ for (const state of states.values()) {
12684
+ if (state === "posted") postedCount += 1;
12685
+ else unpostedCount += 1;
12651
12686
  }
12687
+ const keep = opts?.posted === void 0 ? void 0 : opts.posted ? "posted" : "unposted";
12652
12688
  return {
12653
- items,
12689
+ items: keep === void 0 ? items : items.filter((e) => states.get(e.id) === keep),
12654
12690
  postedCount,
12655
- unpostedCount: items.length - postedCount,
12691
+ unpostedCount,
12656
12692
  window: { from: iso2(fromB.ms), to: iso2(windowToMs) },
12657
12693
  // A live next-page cursor past the bounded read: the counts above cover
12658
12694
  // the returned rows ONLY when this is true (issue #66 AC1 — the 500-row cap case is the general case).
12659
- truncated: hasMore
12695
+ truncated: hasMore || incomplete
12660
12696
  };
12661
12697
  }
12662
12698
  async function dateWorkedWindow(resource, from, to, http, opts) {
@@ -13411,11 +13447,11 @@ var TimeEntriesResource = class extends BaseResource {
13411
13447
  }
13412
13448
  /** Node-parity special op (hand-written in src/resources/special/time_entries.ts). */
13413
13449
  getPosted(opts) {
13414
- return postedDateStatusQuery(this, "posted", this.http, opts);
13450
+ return postedStateQuery(this, "posted", this.http, opts);
13415
13451
  }
13416
13452
  /** Node-parity special op (hand-written in src/resources/special/time_entries.ts). */
13417
13453
  getUnposted(opts) {
13418
- return postedDateStatusQuery(this, "unposted", this.http, opts);
13454
+ return postedStateQuery(this, "unposted", this.http, opts);
13419
13455
  }
13420
13456
  /** Node-parity special op (hand-written in src/resources/special/time_entries.ts). */
13421
13457
  last14(opts) {
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _class7; var _class8;
2
2
 
3
- var _chunkQXUN2VCJcjs = require('./chunk-QXUN2VCJ.cjs');
3
+ var _chunkDCTHO7CQcjs = require('./chunk-DCTHO7CQ.cjs');
4
4
 
5
5
  // src/auth.ts
6
6
  var AUTH_HEADERS = {
@@ -10863,8 +10863,8 @@ var EMPTY_REASON_STAMPS = {
10863
10863
  var POINTER_NOTES = {
10864
10864
  "ticket_history.query": `W1 (ww29 (australia-nz) live 2026-10-04; vendor contract change live-verified 2026-10-06): the vendor requires ticket_history.query to be supplied with exactly ONE ticketID-equals filter \u2014 an unfiltered call 500s by contract (10-06 text: "When querying the TicketHistory entity you are required to supply one and only one ticketID equals filter"). WITH the filter the query WORKS (live 2026-10-06: rows returned, hasMore true \u2014 a single {op:'eq', field:'ticketID', value:<id>} condition; the filter vocabulary is eq/noteq/gt/gte/lt/lte/beginsWith/endsWith/contains/exist/notExist/in/notIn/and/or). tickets.timeline / tickets.fullContext remain the convenient composites; raw history rows come from the filtered query.`,
10865
10865
  "time_entries.create": `W-A3 (ww29 (australia-nz) live 2026-10-04; re-walled 2026-10-06; scope re-stamped 2026-10-21): for a time_entries.create whose (resourceID, roleID) pair selects the API-only user's own resource, the vendor refused with "API-only user cannot be selected for this field" (10-04 text: "The specified AssignedResourceID and AssignedRoleID combination is not currently defined"). This is a (resource, role) pair wall, NOT a blanket refusal of time-entry writes: live time_entries.create calls succeeded on 2026-10-21 (pair not recorded; two entries created, read back, 1.0 h total). The authorised pair set has NOT been re-adjudicated by a live probe; if a create fails with the pair-validation text, check the resource's role authorisation (whoAmI / the resource record) and try another pair. createForResource's 10-06 wall is likewise unconfirmed for other pairs. Working reads remain postedWithin / the window helpers (W-D3 pointer note).`,
10866
- "time_entries.getPosted": "W-D3 (ww29 (australia-nz) live 2026-10-04, re-verified live 2026-10-15): this zone's TimeEntry entity exposes NO posted-state filter field \u2014 the vendor rejects postingStatus, postedDate and status with \"Unable to find X in the TimeEntry Entity\" (live field-existence oracle 2026-10-04: only dateWorked / resourceID / ticketID / taskID resolve as filter fields). Re-probed 2026-10-15 (ww29 re-probe campaign): time_entries.getPosted live 500, vendor body verbatim 'Unable to find postedDate in the TimeEntry Entity.' \u2014 a vendor endpoint defect on AU03, not a transient fault: posted cannot be filtered server-side on this zone \u2014 use time_entries.postedWithin (the dateWorked window, live-verified working \u2014 omit the `posted` narrowing on this zone: it sends postedDate, which the zone 500s) or read time_entries.query and derive posted-state from the fields your zone's entity exposes",
10867
- "time_entries.getUnposted": "W-D3 (ww29 (australia-nz) live 2026-10-04, re-verified live 2026-10-15): this zone's TimeEntry entity exposes NO posted-state filter field \u2014 the vendor rejects postingStatus, postedDate and status with \"Unable to find X in the TimeEntry Entity\" (live field-existence oracle 2026-10-04: only dateWorked / resourceID / ticketID / taskID resolve as filter fields). Re-probed 2026-10-15 (ww29 re-probe campaign): time_entries.getUnposted live 500, vendor body verbatim 'Unable to find postedDate in the TimeEntry Entity.' \u2014 a vendor endpoint defect on AU03, not a transient fault: unposted cannot be filtered server-side on this zone \u2014 use time_entries.postedWithin (the dateWorked window, live-verified working \u2014 omit the `posted` narrowing on this zone: it sends postedDate, which the zone 500s) or read time_entries.query and derive posted-state from the fields your zone's entity exposes",
10866
+ "time_entries.getPosted": `W-D3 (ww29 (australia-nz) live 2026-10-04, re-verified live 2026-10-27): this zone's TimeEntry entity exposes NO posted-state field \u2014 reads carry no postedDate/postedOnTime and a postedDate exist/notExist filter fails with SERVER_ERROR "Unable to find postedDate in the TimeEntry Entity". time_entries.getPosted therefore no longer sends any postedDate condition: posted state is derived from billing_items.timeEntryID (an entry is posted when a billing item references it, whether or not billingApprovalDateTime is set \u2014 ww29 never populates it). If a call still fails, the fault is NOT the old postedDate wall: it is a billing_items join/read fault (typed error \u2014 429 RATE_LIMITED included \u2014 propagates, no partial result). The dateWorked window reads time_entries.postedWithin / time_entries.dateWorkedWindow use the same derivation.`,
10867
+ "time_entries.getUnposted": `W-D3 (ww29 (australia-nz) live 2026-10-04, re-verified live 2026-10-27): this zone's TimeEntry entity exposes NO posted-state field \u2014 reads carry no postedDate/postedOnTime and a postedDate exist/notExist filter fails with SERVER_ERROR "Unable to find postedDate in the TimeEntry Entity". time_entries.getUnposted therefore no longer sends any postedDate condition: unposted state is derived from billing_items.timeEntryID (an entry is posted when a billing item references it, whether or not billingApprovalDateTime is set \u2014 ww29 never populates it). If a call still fails, the fault is NOT the old postedDate wall: it is a billing_items join/read fault (typed error \u2014 429 RATE_LIMITED included \u2014 propagates, no partial result). The dateWorked window reads time_entries.postedWithin / time_entries.dateWorkedWindow use the same derivation.`,
10868
10868
  "authenticate.get": "W2: authenticate.get is DESIGNATOR-ONLY \u2014 it returns the designator identity, not a tenant data surface; a miss/empty here is not a data-empty signal (no emptyReason is attached by design \u2014 a false reason would be worse than none)"
10869
10869
  };
10870
10870
  var BLIND_SURFACE_INVENTORY = {
@@ -17031,7 +17031,7 @@ function buildEntityTypeMap() {
17031
17031
  }
17032
17032
  var ENTITY_TYPE_TO_RESOURCE = buildEntityTypeMap();
17033
17033
  var ENUMERABLE_RESOURCES = new Set(
17034
- _chunkQXUN2VCJcjs.CAPABILITIES.filter((rec) => rec.resource !== "operations" && (rec.operation === "query" || rec.operation === "get")).map(
17034
+ _chunkDCTHO7CQcjs.CAPABILITIES.filter((rec) => rec.resource !== "operations" && (rec.operation === "query" || rec.operation === "get")).map(
17035
17035
  (rec) => rec.resource
17036
17036
  )
17037
17037
  );
@@ -17108,7 +17108,7 @@ function capabilitySets() {
17108
17108
  const fieldOps = /* @__PURE__ */ new Map();
17109
17109
  const udfOps = /* @__PURE__ */ new Map();
17110
17110
  const resources = /* @__PURE__ */ new Set();
17111
- for (const rec of _chunkQXUN2VCJcjs.CAPABILITIES) {
17111
+ for (const rec of _chunkDCTHO7CQcjs.CAPABILITIES) {
17112
17112
  if (rec.resource === "operations") continue;
17113
17113
  resources.add(rec.resource);
17114
17114
  if (rec.operation === "getFieldInfo") fieldOps.set(rec.resource, rec);
@@ -17120,7 +17120,7 @@ var derivedIndex = null;
17120
17120
  function derivedFieldsFor(resource) {
17121
17121
  if (derivedIndex === null) {
17122
17122
  derivedIndex = /* @__PURE__ */ new Map();
17123
- for (const rec of _chunkQXUN2VCJcjs.CAPABILITIES) {
17123
+ for (const rec of _chunkDCTHO7CQcjs.CAPABILITIES) {
17124
17124
  const declared = _optionalChain([rec, 'access', _127 => _127.metadata, 'optionalAccess', _128 => _128.derivedFields]);
17125
17125
  if (!Array.isArray(declared) || derivedIndex.has(rec.resource)) continue;
17126
17126
  const rows = [];