apple-notes-mcp 2.8.5 → 2.8.7

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 (3) hide show
  1. package/README.md +44 -0
  2. package/build/index.js +543 -109
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -303,6 +303,26 @@ attachment-bearing notes; edit them in Notes.app.
303
303
 
304
304
  ---
305
305
 
306
+ #### `get-native-objects`
307
+
308
+ Reads native object identities and ranges, checklist item IDs and state, actual
309
+ native tags, and native table data from one exact note ID. Table output includes
310
+ stable row and column identifiers. `tableCellsComplete` is false when Notes
311
+ metadata cannot be decoded completely. This tool is read-only and requires Full
312
+ Disk Access.
313
+
314
+ ---
315
+
316
+ #### `list-native-tags`
317
+
318
+ Lists actual native Notes tags used within one explicit `account` and `folder`,
319
+ mapping each tag to its matching note IDs. This differs from textual hashtag
320
+ search. The response reports `complete: false` and per-note errors when some
321
+ native metadata is unavailable. This tool is read-only and requires Full Disk
322
+ Access.
323
+
324
+ ---
325
+
306
326
  #### `get-note-plaintext`
307
327
 
308
328
  Retrieves a note's body as plain text, with no HTML markup.
@@ -639,6 +659,21 @@ Creates a new folder, including a whole nested hierarchy in one call.
639
659
 
640
660
  ---
641
661
 
662
+ #### `get-folder-by-id`
663
+
664
+ Reads one exact folder's current name and parent ID. Use these values with
665
+ `rename-folder`; this avoids relying on ambiguous folder names or paths.
666
+
667
+ ---
668
+
669
+ #### `rename-folder`
670
+
671
+ Renames an existing folder in place using its exact `id`, `expectedName`,
672
+ `expectedParentId`, and `newName`. The operation preserves the folder ID, notes,
673
+ and descendants. It refuses stale metadata and a conflicting sibling name.
674
+
675
+ ---
676
+
642
677
  #### `delete-folder`
643
678
 
644
679
  Deletes a folder.
@@ -815,6 +850,15 @@ Reads note metadata that AppleScript cannot expose, by querying the NoteStore SQ
815
850
 
816
851
  ---
817
852
 
853
+ #### `add-attachment`
854
+
855
+ Adds one nonempty local file of at most 64 MiB to an exact note using `id`, the
856
+ latest `expectedContentHash`, and an absolute `path`. The server never retries
857
+ the insertion. It verifies that existing rich content survived and compares the
858
+ fetched attachment bytes with the source before reporting success.
859
+
860
+ ---
861
+
818
862
  #### `list-attachments`
819
863
 
820
864
  Lists attachments in a note.
package/build/index.js CHANGED
@@ -417,11 +417,11 @@ var require_codegen = __commonJS({
417
417
  const rhs = this.rhs === void 0 ? "" : ` = ${this.rhs}`;
418
418
  return `${varKind} ${this.name}${rhs};` + _n;
419
419
  }
420
- optimizeNames(names, constants) {
420
+ optimizeNames(names, constants2) {
421
421
  if (!names[this.name.str])
422
422
  return;
423
423
  if (this.rhs)
424
- this.rhs = optimizeExpr(this.rhs, names, constants);
424
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
425
425
  return this;
426
426
  }
427
427
  get names() {
@@ -438,10 +438,10 @@ var require_codegen = __commonJS({
438
438
  render({ _n }) {
439
439
  return `${this.lhs} = ${this.rhs};` + _n;
440
440
  }
441
- optimizeNames(names, constants) {
441
+ optimizeNames(names, constants2) {
442
442
  if (this.lhs instanceof code_1.Name && !names[this.lhs.str] && !this.sideEffects)
443
443
  return;
444
- this.rhs = optimizeExpr(this.rhs, names, constants);
444
+ this.rhs = optimizeExpr(this.rhs, names, constants2);
445
445
  return this;
446
446
  }
447
447
  get names() {
@@ -502,8 +502,8 @@ var require_codegen = __commonJS({
502
502
  optimizeNodes() {
503
503
  return `${this.code}` ? this : void 0;
504
504
  }
505
- optimizeNames(names, constants) {
506
- this.code = optimizeExpr(this.code, names, constants);
505
+ optimizeNames(names, constants2) {
506
+ this.code = optimizeExpr(this.code, names, constants2);
507
507
  return this;
508
508
  }
509
509
  get names() {
@@ -532,12 +532,12 @@ var require_codegen = __commonJS({
532
532
  }
533
533
  return nodes.length > 0 ? this : void 0;
534
534
  }
535
- optimizeNames(names, constants) {
535
+ optimizeNames(names, constants2) {
536
536
  const { nodes } = this;
537
537
  let i = nodes.length;
538
538
  while (i--) {
539
539
  const n = nodes[i];
540
- if (n.optimizeNames(names, constants))
540
+ if (n.optimizeNames(names, constants2))
541
541
  continue;
542
542
  subtractNames(names, n.names);
543
543
  nodes.splice(i, 1);
@@ -590,12 +590,12 @@ var require_codegen = __commonJS({
590
590
  return void 0;
591
591
  return this;
592
592
  }
593
- optimizeNames(names, constants) {
593
+ optimizeNames(names, constants2) {
594
594
  var _a;
595
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants);
596
- if (!(super.optimizeNames(names, constants) || this.else))
595
+ this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
596
+ if (!(super.optimizeNames(names, constants2) || this.else))
597
597
  return;
598
- this.condition = optimizeExpr(this.condition, names, constants);
598
+ this.condition = optimizeExpr(this.condition, names, constants2);
599
599
  return this;
600
600
  }
601
601
  get names() {
@@ -618,10 +618,10 @@ var require_codegen = __commonJS({
618
618
  render(opts) {
619
619
  return `for(${this.iteration})` + super.render(opts);
620
620
  }
621
- optimizeNames(names, constants) {
622
- if (!super.optimizeNames(names, constants))
621
+ optimizeNames(names, constants2) {
622
+ if (!super.optimizeNames(names, constants2))
623
623
  return;
624
- this.iteration = optimizeExpr(this.iteration, names, constants);
624
+ this.iteration = optimizeExpr(this.iteration, names, constants2);
625
625
  return this;
626
626
  }
627
627
  get names() {
@@ -657,10 +657,10 @@ var require_codegen = __commonJS({
657
657
  render(opts) {
658
658
  return `for(${this.varKind} ${this.name} ${this.loop} ${this.iterable})` + super.render(opts);
659
659
  }
660
- optimizeNames(names, constants) {
661
- if (!super.optimizeNames(names, constants))
660
+ optimizeNames(names, constants2) {
661
+ if (!super.optimizeNames(names, constants2))
662
662
  return;
663
- this.iterable = optimizeExpr(this.iterable, names, constants);
663
+ this.iterable = optimizeExpr(this.iterable, names, constants2);
664
664
  return this;
665
665
  }
666
666
  get names() {
@@ -702,11 +702,11 @@ var require_codegen = __commonJS({
702
702
  (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNodes();
703
703
  return this;
704
704
  }
705
- optimizeNames(names, constants) {
705
+ optimizeNames(names, constants2) {
706
706
  var _a, _b;
707
- super.optimizeNames(names, constants);
708
- (_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants);
709
- (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants);
707
+ super.optimizeNames(names, constants2);
708
+ (_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
709
+ (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants2);
710
710
  return this;
711
711
  }
712
712
  get names() {
@@ -1007,7 +1007,7 @@ var require_codegen = __commonJS({
1007
1007
  function addExprNames(names, from) {
1008
1008
  return from instanceof code_1._CodeOrName ? addNames(names, from.names) : names;
1009
1009
  }
1010
- function optimizeExpr(expr, names, constants) {
1010
+ function optimizeExpr(expr, names, constants2) {
1011
1011
  if (expr instanceof code_1.Name)
1012
1012
  return replaceName(expr);
1013
1013
  if (!canOptimize(expr))
@@ -1022,14 +1022,14 @@ var require_codegen = __commonJS({
1022
1022
  return items;
1023
1023
  }, []));
1024
1024
  function replaceName(n) {
1025
- const c = constants[n.str];
1025
+ const c = constants2[n.str];
1026
1026
  if (c === void 0 || names[n.str] !== 1)
1027
1027
  return n;
1028
1028
  delete names[n.str];
1029
1029
  return c;
1030
1030
  }
1031
1031
  function canOptimize(e) {
1032
- return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants[c.str] !== void 0);
1032
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants2[c.str] !== void 0);
1033
1033
  }
1034
1034
  }
1035
1035
  function subtractNames(names, from) {
@@ -3358,9 +3358,9 @@ var require_utils = __commonJS({
3358
3358
  let output = "";
3359
3359
  for (let i = 0; i < input.length; i++) {
3360
3360
  if (input[i] === "%" && i + 2 < input.length) {
3361
- const hex = input.slice(i + 1, i + 3);
3362
- if (isHexPair(hex)) {
3363
- const normalizedHex = hex.toUpperCase();
3361
+ const hex2 = input.slice(i + 1, i + 3);
3362
+ if (isHexPair(hex2)) {
3363
+ const normalizedHex = hex2.toUpperCase();
3364
3364
  const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3365
3365
  if (decodeUnreserved && isUnreserved(decoded)) {
3366
3366
  output += decoded;
@@ -3380,9 +3380,9 @@ var require_utils = __commonJS({
3380
3380
  for (let i = 0; i < input.length; i++) {
3381
3381
  const ch = input[i];
3382
3382
  if (ch === "%" && i + 2 < input.length) {
3383
- const hex = input.slice(i + 1, i + 3);
3384
- if (isHexPair(hex)) {
3385
- const normalizedHex = hex.toUpperCase();
3383
+ const hex2 = input.slice(i + 1, i + 3);
3384
+ if (isHexPair(hex2)) {
3385
+ const normalizedHex = hex2.toUpperCase();
3386
3386
  const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3387
3387
  if (decoded !== "." && isUnreserved(decoded)) {
3388
3388
  output += decoded;
@@ -3422,9 +3422,9 @@ var require_utils = __commonJS({
3422
3422
  for (let i = 0; i < input.length; i++) {
3423
3423
  const ch = input[i];
3424
3424
  if (ch === "%" && i + 2 < input.length) {
3425
- const hex = input.slice(i + 1, i + 3);
3426
- if (isHexPair(hex)) {
3427
- output += "%" + hex.toUpperCase();
3425
+ const hex2 = input.slice(i + 1, i + 3);
3426
+ if (isHexPair(hex2)) {
3427
+ output += "%" + hex2.toUpperCase();
3428
3428
  i += 2;
3429
3429
  continue;
3430
3430
  }
@@ -3460,9 +3460,9 @@ var require_utils = __commonJS({
3460
3460
  for (let i = 0; i < input.length; i++) {
3461
3461
  const ch = input[i];
3462
3462
  if (ch === "%" && i + 2 < input.length) {
3463
- const hex = input.slice(i + 1, i + 3);
3464
- if (isHexPair(hex)) {
3465
- output += "%" + hex.toUpperCase();
3463
+ const hex2 = input.slice(i + 1, i + 3);
3464
+ if (isHexPair(hex2)) {
3465
+ output += "%" + hex2.toUpperCase();
3466
3466
  i += 2;
3467
3467
  continue;
3468
3468
  }
@@ -3507,9 +3507,9 @@ var require_utils = __commonJS({
3507
3507
  for (let i = 0; i < input.length; i++) {
3508
3508
  const ch = input[i];
3509
3509
  if (ch === "%" && i + 2 < input.length) {
3510
- const hex = input.slice(i + 1, i + 3);
3511
- if (isHexPair(hex)) {
3512
- const normalizedHex = hex.toUpperCase();
3510
+ const hex2 = input.slice(i + 1, i + 3);
3511
+ if (isHexPair(hex2)) {
3512
+ const normalizedHex = hex2.toUpperCase();
3513
3513
  const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
3514
3514
  if (isUnreserved(decoded)) {
3515
3515
  output += decoded;
@@ -3547,9 +3547,9 @@ var require_utils = __commonJS({
3547
3547
  let output = "";
3548
3548
  for (let i = 0; i < input.length; i++) {
3549
3549
  if (input[i] === "%" && i + 2 < input.length) {
3550
- const hex = input.slice(i + 1, i + 3);
3551
- if (isHexPair(hex)) {
3552
- output += "%" + hex.toUpperCase();
3550
+ const hex2 = input.slice(i + 1, i + 3);
3551
+ if (isHexPair(hex2)) {
3552
+ output += "%" + hex2.toUpperCase();
3553
3553
  i += 2;
3554
3554
  continue;
3555
3555
  }
@@ -7422,7 +7422,7 @@ var require_DOMException = __commonJS({
7422
7422
  "INVALID_NODE_TYPE_ERR (24): the supplied node is invalid or has an invalid ancestor for this operation",
7423
7423
  "DATA_CLONE_ERR (25): the object can not be cloned."
7424
7424
  ];
7425
- var constants = {
7425
+ var constants2 = {
7426
7426
  INDEX_SIZE_ERR,
7427
7427
  DOMSTRING_SIZE_ERR: 2,
7428
7428
  // historical
@@ -7461,8 +7461,8 @@ var require_DOMException = __commonJS({
7461
7461
  this.name = names[code];
7462
7462
  }
7463
7463
  DOMException.prototype.__proto__ = Error.prototype;
7464
- for (c in constants) {
7465
- v = { value: constants[c] };
7464
+ for (c in constants2) {
7465
+ v = { value: constants2[c] };
7466
7466
  Object.defineProperty(DOMException, c, v);
7467
7467
  Object.defineProperty(DOMException.prototype, c, v);
7468
7468
  }
@@ -9329,8 +9329,8 @@ var require_select = __commonJS({
9329
9329
  regex = regex.replace(name, val.source || val);
9330
9330
  return new RegExp(regex);
9331
9331
  };
9332
- var truncateUrl = function(url, num) {
9333
- return url.replace(/^(?:\w+:\/\/|\/+)/, "").replace(/(?:\/+|\/*#.*?)$/, "").split("/", num).join("/");
9332
+ var truncateUrl = function(url, num2) {
9333
+ return url.replace(/^(?:\w+:\/\/|\/+)/, "").replace(/(?:\/+|\/*#.*?)$/, "").split("/", num2).join("/");
9334
9334
  };
9335
9335
  var parseNth = function(param_, test) {
9336
9336
  var param = param_.replace(/\s+/g, ""), cap;
@@ -24465,14 +24465,14 @@ var require_turndown_cjs = __commonJS({
24465
24465
  } else if (node.nodeType === 1) {
24466
24466
  replacement = replacementForNode.call(self, node);
24467
24467
  }
24468
- return join8(output, replacement);
24468
+ return join9(output, replacement);
24469
24469
  }, "");
24470
24470
  }
24471
24471
  function postProcess(output) {
24472
24472
  var self = this;
24473
24473
  this.rules.forEach(function(rule) {
24474
24474
  if (typeof rule.append === "function") {
24475
- output = join8(output, rule.append(self.options));
24475
+ output = join9(output, rule.append(self.options));
24476
24476
  }
24477
24477
  });
24478
24478
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -24484,7 +24484,7 @@ var require_turndown_cjs = __commonJS({
24484
24484
  if (whitespace.leading || whitespace.trailing) content = content.trim();
24485
24485
  return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
24486
24486
  }
24487
- function join8(output, replacement) {
24487
+ function join9(output, replacement) {
24488
24488
  var s1 = trimTrailingNewlines(output);
24489
24489
  var s2 = trimLeadingNewlines(replacement);
24490
24490
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -24777,8 +24777,8 @@ var ZodError = class _ZodError extends Error {
24777
24777
  constructor(issues) {
24778
24778
  super();
24779
24779
  this.issues = [];
24780
- this.addIssue = (sub) => {
24781
- this.issues = [...this.issues, sub];
24780
+ this.addIssue = (sub2) => {
24781
+ this.issues = [...this.issues, sub2];
24782
24782
  };
24783
24783
  this.addIssues = (subs = []) => {
24784
24784
  this.issues = [...this.issues, ...subs];
@@ -24845,13 +24845,13 @@ var ZodError = class _ZodError extends Error {
24845
24845
  flatten(mapper = (issue2) => issue2.message) {
24846
24846
  const fieldErrors = {};
24847
24847
  const formErrors = [];
24848
- for (const sub of this.issues) {
24849
- if (sub.path.length > 0) {
24850
- const firstEl = sub.path[0];
24848
+ for (const sub2 of this.issues) {
24849
+ if (sub2.path.length > 0) {
24850
+ const firstEl = sub2.path[0];
24851
24851
  fieldErrors[firstEl] = fieldErrors[firstEl] || [];
24852
- fieldErrors[firstEl].push(mapper(sub));
24852
+ fieldErrors[firstEl].push(mapper(sub2));
24853
24853
  } else {
24854
- formErrors.push(mapper(sub));
24854
+ formErrors.push(mapper(sub2));
24855
24855
  }
24856
24856
  }
24857
24857
  return { formErrors, fieldErrors };
@@ -29150,12 +29150,12 @@ var $ZodRealError = $constructor("$ZodError", initializer, { Parent: Error });
29150
29150
  function flattenError(error2, mapper = (issue2) => issue2.message) {
29151
29151
  const fieldErrors = {};
29152
29152
  const formErrors = [];
29153
- for (const sub of error2.issues) {
29154
- if (sub.path.length > 0) {
29155
- fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || [];
29156
- fieldErrors[sub.path[0]].push(mapper(sub));
29153
+ for (const sub2 of error2.issues) {
29154
+ if (sub2.path.length > 0) {
29155
+ fieldErrors[sub2.path[0]] = fieldErrors[sub2.path[0]] || [];
29156
+ fieldErrors[sub2.path[0]].push(mapper(sub2));
29157
29157
  } else {
29158
- formErrors.push(mapper(sub));
29158
+ formErrors.push(mapper(sub2));
29159
29159
  }
29160
29160
  }
29161
29161
  return { formErrors, fieldErrors };
@@ -39350,10 +39350,10 @@ function hasFullDiskAccess() {
39350
39350
  return false;
39351
39351
  }
39352
39352
  }
39353
- function queryNoteData(noteId) {
39354
- const pkMatch = noteId.match(/\/p(\d+)$/);
39353
+ function queryNoteData(noteId2) {
39354
+ const pkMatch = noteId2.match(/\/p(\d+)$/);
39355
39355
  if (!pkMatch) {
39356
- console.error(`Invalid note ID format: ${noteId}`);
39356
+ console.error(`Invalid note ID format: ${noteId2}`);
39357
39357
  return { hex: null, error: "invalid_id" };
39358
39358
  }
39359
39359
  const pk = pkMatch[1];
@@ -39364,9 +39364,9 @@ function queryNoteData(noteId) {
39364
39364
  timeout: 5e3,
39365
39365
  stdio: ["pipe", "pipe", "pipe"]
39366
39366
  });
39367
- const hex = result.trim();
39368
- if (!hex) return { hex: null };
39369
- return { hex };
39367
+ const hex2 = result.trim();
39368
+ if (!hex2) return { hex: null };
39369
+ return { hex: hex2 };
39370
39370
  } catch (error2) {
39371
39371
  const message = error2 instanceof Error ? error2.message : String(error2);
39372
39372
  console.error(`Failed to query NoteStore database: ${message}`);
@@ -39376,10 +39376,10 @@ function queryNoteData(noteId) {
39376
39376
  return { hex: null };
39377
39377
  }
39378
39378
  }
39379
- function hexToBytes(hex) {
39380
- const bytes = new Uint8Array(hex.length / 2);
39381
- for (let i = 0; i < hex.length; i += 2) {
39382
- bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);
39379
+ function hexToBytes(hex2) {
39380
+ const bytes = new Uint8Array(hex2.length / 2);
39381
+ for (let i = 0; i < hex2.length; i += 2) {
39382
+ bytes[i / 2] = parseInt(hex2.substring(i, i + 2), 16);
39383
39383
  }
39384
39384
  return bytes;
39385
39385
  }
@@ -39438,13 +39438,13 @@ function parseChecklistFromProtobuf(data) {
39438
39438
  return null;
39439
39439
  }
39440
39440
  }
39441
- function getChecklistItems(noteId) {
39442
- const { hex: hexData, error: queryError } = queryNoteData(noteId);
39441
+ function getChecklistItems(noteId2) {
39442
+ const { hex: hexData, error: queryError } = queryNoteData(noteId2);
39443
39443
  if (queryError === "invalid_id") {
39444
39444
  return {
39445
39445
  items: null,
39446
39446
  error: "invalid_id",
39447
- message: `Invalid note ID format: "${noteId}". Expected format: x-coredata://UUID/ICNote/pNNN`
39447
+ message: `Invalid note ID format: "${noteId2}". Expected format: x-coredata://UUID/ICNote/pNNN`
39448
39448
  };
39449
39449
  }
39450
39450
  if (queryError === "no_fda") {
@@ -40281,20 +40281,20 @@ var AppleNotesManager = class {
40281
40281
  return null;
40282
40282
  }
40283
40283
  const rawOutput = result.output.trim();
40284
- const noteId = extractCoreDataId(rawOutput, "note") || rawOutput;
40285
- if (!noteId) {
40284
+ const noteId2 = extractCoreDataId(rawOutput, "note") || rawOutput;
40285
+ if (!noteId2) {
40286
40286
  console.error(`Created note "${title}" but Notes.app returned no canonical note ID`);
40287
40287
  return null;
40288
40288
  }
40289
40289
  try {
40290
- sanitizeNoteId(noteId);
40290
+ sanitizeNoteId(noteId2);
40291
40291
  } catch {
40292
40292
  console.error(`Created note "${title}" but Notes.app returned an invalid note ID`);
40293
40293
  return null;
40294
40294
  }
40295
40295
  const now = /* @__PURE__ */ new Date();
40296
40296
  return {
40297
- id: noteId,
40297
+ id: noteId2,
40298
40298
  title,
40299
40299
  content,
40300
40300
  tags,
@@ -40408,11 +40408,11 @@ var AppleNotesManager = class {
40408
40408
  for (const item of items) {
40409
40409
  const [title, id, folder2, created, modified] = item.split(FIELD_SEP);
40410
40410
  if (!title?.trim()) continue;
40411
- const noteId = id?.trim() || generateFallbackId();
40412
- if (seenIds.has(noteId)) continue;
40413
- seenIds.add(noteId);
40411
+ const noteId2 = id?.trim() || generateFallbackId();
40412
+ if (seenIds.has(noteId2)) continue;
40413
+ seenIds.add(noteId2);
40414
40414
  notes.push({
40415
- id: noteId,
40415
+ id: noteId2,
40416
40416
  title: title.trim(),
40417
40417
  content: "",
40418
40418
  // Not fetched in search
@@ -40780,10 +40780,10 @@ var AppleNotesManager = class {
40780
40780
  for (const item of output.split(RECORD_SEP)) {
40781
40781
  const [title, id] = item.split(FIELD_SEP);
40782
40782
  if (!title?.trim()) continue;
40783
- const noteId = id?.trim() || generateFallbackId();
40784
- if (seenIds.has(noteId)) continue;
40785
- seenIds.add(noteId);
40786
- refs.push({ title: title.trim(), id: noteId });
40783
+ const noteId2 = id?.trim() || generateFallbackId();
40784
+ if (seenIds.has(noteId2)) continue;
40785
+ seenIds.add(noteId2);
40786
+ refs.push({ title: title.trim(), id: noteId2 });
40787
40787
  if (safeLimit !== void 0 && refs.length >= safeLimit) break;
40788
40788
  }
40789
40789
  return refs;
@@ -40930,6 +40930,69 @@ var AppleNotesManager = class {
40930
40930
  // ===========================================================================
40931
40931
  // Folder Operations
40932
40932
  // ===========================================================================
40933
+ /** Rename a folder in place while preserving its identity and contents. */
40934
+ renameFolderById(id, expectedName, expectedParentId, newName) {
40935
+ if (!/^x-coredata:\/\/[0-9a-f-]+\/ICFolder\/p\d+$/i.test(id))
40936
+ throw new Error("An exact folder ID is required");
40937
+ if (!newName.trim() || newName.length > 1e3 || Array.from(newName).some((char) => char.charCodeAt(0) < 32))
40938
+ throw new Error("Invalid folder name");
40939
+ const literal2 = (value) => `"${escapePlainStringForAppleScript(value)}"`;
40940
+ const script = `tell application "Notes"
40941
+ set targetFolder to folder id ${literal2(id)}
40942
+ set parentRef to container of targetFolder
40943
+ set parentId to id of parentRef
40944
+ considering case
40945
+ if name of targetFolder is not ${literal2(expectedName)} or parentId is not ${literal2(expectedParentId)} then error "Folder changed; read it again"
40946
+ end considering
40947
+ repeat with sibling in folders of parentRef
40948
+ if name of sibling is ${literal2(newName)} and id of sibling is not ${literal2(id)} then error "A sibling folder already has this name"
40949
+ end repeat
40950
+ set name of targetFolder to ${literal2(newName)}
40951
+ considering case
40952
+ if id of targetFolder is not ${literal2(id)} or name of targetFolder is not ${literal2(newName)} or id of container of targetFolder is not parentId then error "Rename readback failed"
40953
+ end considering
40954
+ return id of targetFolder
40955
+ end tell`;
40956
+ const result = executeMutationAppleScript(script);
40957
+ if (!result.success)
40958
+ throw new Error(
40959
+ result.error || "Rename outcome uncertain; read folder by ID before retrying"
40960
+ );
40961
+ return { id, name: newName, parentId: expectedParentId };
40962
+ }
40963
+ /** Read the exact name and parent identity used by guarded folder rename. */
40964
+ getFolderById(id) {
40965
+ if (!/^x-coredata:\/\/[0-9a-f-]+\/ICFolder\/p\d+$/i.test(id))
40966
+ throw new Error("An exact folder ID is required");
40967
+ const result = executeAppleScript(`tell application "Notes"
40968
+ set f to folder id "${id}"
40969
+ return (name of f) & ${AS_FIELD_SEP} & (id of container of f)
40970
+ end tell`);
40971
+ if (!result.success) throw new Error(result.error || "Folder not found");
40972
+ const [name, parentId] = result.output.replace(/\n$/, "").split(FIELD_SEP);
40973
+ if (!name || !parentId) throw new Error("Incomplete folder metadata");
40974
+ return { id, name, parentId };
40975
+ }
40976
+ /** Insert one file into an unchanged exact note and return Notes' attachment ID. */
40977
+ addAttachmentById(id, expectedBody, filePath) {
40978
+ if (!/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i.test(id))
40979
+ throw new Error("Exact note ID required");
40980
+ const quote = (value) => `"${escapePlainStringForAppleScript(value)}"`;
40981
+ const result = executeMutationAppleScript(`tell application "Notes"
40982
+ set n to note id ${quote(id)}
40983
+ if password protected of n then error "Locked note"
40984
+ considering case
40985
+ if body of n is not ${quote(expectedBody)} and body of n is not (${quote(expectedBody)} & linefeed) then error "Note changed before attachment insertion"
40986
+ end considering
40987
+ set a to make new attachment at n with data (POSIX file ${quote(filePath)})
40988
+ return id of a
40989
+ end tell`);
40990
+ if (!result.success)
40991
+ throw new Error(
40992
+ result.error || "Attachment insertion outcome uncertain; read note before retrying"
40993
+ );
40994
+ return result.output.trim();
40995
+ }
40933
40996
  /**
40934
40997
  * Lists all folders in an account with full hierarchical paths.
40935
40998
  *
@@ -41372,8 +41435,8 @@ var AppleNotesManager = class {
41372
41435
  * @param separately - Open in a separate window when supported by Notes.app
41373
41436
  * @returns true if Notes.app revealed the attachment, false otherwise
41374
41437
  */
41375
- showAttachmentById(noteId, attachmentId, separately = false) {
41376
- const safeNoteId = sanitizeId(noteId);
41438
+ showAttachmentById(noteId2, attachmentId, separately = false) {
41439
+ const safeNoteId = sanitizeId(noteId2);
41377
41440
  const safeAttId = escapePlainStringForAppleScript(attachmentId);
41378
41441
  const separatelyClause = separately ? " separately true" : "";
41379
41442
  const script = `
@@ -41393,13 +41456,13 @@ var AppleNotesManager = class {
41393
41456
  const result = executeMutationAppleScript(script);
41394
41457
  if (!result.success) {
41395
41458
  console.error(
41396
- `Failed to show attachment "${attachmentId}" on note "${noteId}":`,
41459
+ `Failed to show attachment "${attachmentId}" on note "${noteId2}":`,
41397
41460
  result.error
41398
41461
  );
41399
41462
  return false;
41400
41463
  }
41401
41464
  if ((result.output ?? "").trim().startsWith("ERR")) {
41402
- console.error(`Attachment "${attachmentId}" not found on note "${noteId}"`);
41465
+ console.error(`Attachment "${attachmentId}" not found on note "${noteId2}"`);
41403
41466
  return false;
41404
41467
  }
41405
41468
  return true;
@@ -41799,7 +41862,7 @@ var AppleNotesManager = class {
41799
41862
  * @param savePath - absolute destination file path (within home / temp / /Volumes)
41800
41863
  * @returns { success, savedPath?, name?, contentType?, error? }
41801
41864
  */
41802
- saveAttachmentById(noteId, attachmentId, savePath) {
41865
+ saveAttachmentById(noteId2, attachmentId, savePath) {
41803
41866
  let abs;
41804
41867
  try {
41805
41868
  abs = assertSafeSavePath(savePath);
@@ -41807,7 +41870,7 @@ var AppleNotesManager = class {
41807
41870
  } catch (e) {
41808
41871
  return { success: false, error: e instanceof Error ? e.message : String(e) };
41809
41872
  }
41810
- const safeNoteId = sanitizeId(noteId);
41873
+ const safeNoteId = sanitizeId(noteId2);
41811
41874
  const safeAttId = escapePlainStringForAppleScript(attachmentId);
41812
41875
  const safePath = escapePlainStringForAppleScript(abs);
41813
41876
  const script = `
@@ -41871,11 +41934,11 @@ var AppleNotesManager = class {
41871
41934
  * @param attachmentId - id of the attachment
41872
41935
  * @returns { success, name?, contentType?, base64?, bytes?, error? }
41873
41936
  */
41874
- getAttachmentBase64ById(noteId, attachmentId) {
41937
+ getAttachmentBase64ById(noteId2, attachmentId) {
41875
41938
  const dir = makeTempDir();
41876
41939
  try {
41877
41940
  const dest = `${dir}/attachment.bin`;
41878
- const saved = this.saveAttachmentById(noteId, attachmentId, dest);
41941
+ const saved = this.saveAttachmentById(noteId2, attachmentId, dest);
41879
41942
  if (!saved.success || !saved.savedPath) {
41880
41943
  return { success: false, error: saved.error };
41881
41944
  }
@@ -42375,13 +42438,13 @@ function presentColumns() {
42375
42438
  }
42376
42439
  return cols;
42377
42440
  }
42378
- function getNoteMetadata(noteId) {
42379
- const pkMatch = noteId.match(/\/p(\d+)$/);
42441
+ function getNoteMetadata(noteId2) {
42442
+ const pkMatch = noteId2.match(/\/p(\d+)$/);
42380
42443
  if (!pkMatch) {
42381
42444
  return {
42382
42445
  metadata: null,
42383
42446
  error: "invalid_id",
42384
- message: `Invalid note ID format: "${noteId}". Expected format: x-coredata://UUID/ICNote/pNNN`
42447
+ message: `Invalid note ID format: "${noteId2}". Expected format: x-coredata://UUID/ICNote/pNNN`
42385
42448
  };
42386
42449
  }
42387
42450
  const pk = pkMatch[1];
@@ -42402,7 +42465,7 @@ function getNoteMetadata(noteId) {
42402
42465
  return {
42403
42466
  metadata: null,
42404
42467
  error: "not_found",
42405
- message: `No note found in the database for ID "${noteId}".`
42468
+ message: `No note found in the database for ID "${noteId2}".`
42406
42469
  };
42407
42470
  }
42408
42471
  const raw = JSON.parse(row);
@@ -42511,7 +42574,7 @@ function decodeHtmlEntities(text) {
42511
42574
  }
42512
42575
  return String.fromCodePoint(codePoint);
42513
42576
  };
42514
- return text.replace(/&#x([0-9a-f]+);?/gi, (match, hex) => decodeCodePoint(match, hex, 16)).replace(/&#([0-9]+);?/g, (match, decimal) => decodeCodePoint(match, decimal, 10)).replace(/&nbsp(?:;|(?![0-9a-z]))/gi, " ").replace(/&quot(?:;|(?![0-9a-z]))/gi, '"').replace(/&apos(?:;|(?![0-9a-z]))/gi, "'").replace(/&lt(?:;|(?![0-9a-z]))/gi, "<").replace(/&gt(?:;|(?![0-9a-z]))/gi, ">").replace(/&amp(?:;|(?![0-9a-z]))/gi, "&");
42577
+ return text.replace(/&#x([0-9a-f]+);?/gi, (match, hex2) => decodeCodePoint(match, hex2, 16)).replace(/&#([0-9]+);?/g, (match, decimal) => decodeCodePoint(match, decimal, 10)).replace(/&nbsp(?:;|(?![0-9a-z]))/gi, " ").replace(/&quot(?:;|(?![0-9a-z]))/gi, '"').replace(/&apos(?:;|(?![0-9a-z]))/gi, "'").replace(/&lt(?:;|(?![0-9a-z]))/gi, "<").replace(/&gt(?:;|(?![0-9a-z]))/gi, ">").replace(/&amp(?:;|(?![0-9a-z]))/gi, "&");
42515
42578
  }
42516
42579
  function firstVisibleHtmlLine(html) {
42517
42580
  let text = html;
@@ -42852,6 +42915,282 @@ function comparableVisibleText(html) {
42852
42915
  ).replace(/\s+/g, " ").trim();
42853
42916
  }
42854
42917
 
42918
+ // src/utils/noteTables.ts
42919
+ import { gunzipSync as gunzipSync3 } from "node:zlib";
42920
+ var sub = (f, n) => {
42921
+ const value = embeddedMessage(getField(f, n));
42922
+ if (!value) throw new Error(`Missing table field ${n}`);
42923
+ return value;
42924
+ };
42925
+ var num = (f, n) => {
42926
+ const v = varintValue(getField(f, n));
42927
+ if (v === void 0) throw new Error(`Missing table index ${n}`);
42928
+ return v;
42929
+ };
42930
+ var many = (f, n) => getFields(f, n).map((v) => {
42931
+ const m = embeddedMessage(v);
42932
+ if (!m) throw new Error("Invalid table entry");
42933
+ return m;
42934
+ });
42935
+ var hex = (f) => {
42936
+ if (!(f?.value instanceof Uint8Array)) throw new Error("Missing table UUID");
42937
+ return Buffer.from(f.value).toString("hex");
42938
+ };
42939
+ function parseNoteTable(compressed) {
42940
+ const root = decodeMessage(gunzipSync3(compressed, { maxOutputLength: 16 * 1024 * 1024 }));
42941
+ const data = sub(sub(root, 2), 3), entries = many(data, 3);
42942
+ if (entries.length > 1e5) throw new Error("Table too large");
42943
+ const keys = getFields(data, 4).map(stringValue), types = getFields(data, 5).map(stringValue), uuids = getFields(data, 6).map(hex);
42944
+ const entry = (index) => {
42945
+ if (!entries[index]) throw new Error("Invalid table reference");
42946
+ return entries[index];
42947
+ };
42948
+ const uuidIndex = (index) => num(sub(many(sub(entry(index), 13), 3)[0], 2), 2);
42949
+ const roots = entries.filter((e) => {
42950
+ const map = embeddedMessage(getField(e, 13));
42951
+ return map && types[num(map, 1)] === "com.apple.notes.ICTable";
42952
+ });
42953
+ if (roots.length !== 1) throw new Error("Ambiguous native table root");
42954
+ const refs = new Map(
42955
+ many(sub(roots[0], 13), 3).filter((m) => ["crRows", "crColumns", "cellColumns"].includes(keys[num(m, 1)] || "")).map((m) => [keys[num(m, 1)], num(sub(m, 2), 6)])
42956
+ );
42957
+ const ordered = (key) => {
42958
+ const ref = refs.get(key);
42959
+ if (ref === void 0) throw new Error("Missing table dimension");
42960
+ const ordering = sub(sub(entry(ref), 16), 1), array2 = sub(ordering, 1);
42961
+ const ids = many(array2, 2).map((a) => hex(getField(a, 2)));
42962
+ const map = /* @__PURE__ */ new Map();
42963
+ ids.forEach((id, i) => {
42964
+ const index = uuids.indexOf(id);
42965
+ if (index < 0) throw new Error("Missing dimension UUID");
42966
+ map.set(index, i);
42967
+ });
42968
+ const aliases = many(sub(ordering, 2), 1).map((pair) => [
42969
+ uuidIndex(num(sub(pair, 1), 6)),
42970
+ uuidIndex(num(sub(pair, 2), 6))
42971
+ ]);
42972
+ for (let pass = 0; pass < aliases.length + 1; pass++) {
42973
+ let changed = false;
42974
+ for (const [key2, value] of aliases)
42975
+ if (map.has(key2) && !map.has(value)) {
42976
+ map.set(value, map.get(key2));
42977
+ changed = true;
42978
+ }
42979
+ if (!changed) break;
42980
+ }
42981
+ return { ids, map };
42982
+ };
42983
+ const rows = ordered("crRows"), columns = ordered("crColumns");
42984
+ if (!rows.ids.length || !columns.ids.length || rows.ids.length * columns.ids.length > 1e5)
42985
+ throw new Error("Unsupported table size");
42986
+ const values = rows.ids.map(() => columns.ids.map(() => ""));
42987
+ const cellRef = refs.get("cellColumns");
42988
+ if (cellRef === void 0) throw new Error("Missing table cells");
42989
+ for (const column of many(sub(entry(cellRef), 6), 1)) {
42990
+ const ci = columns.map.get(uuidIndex(num(sub(column, 1), 6)));
42991
+ const cells = entry(num(sub(column, 2), 6));
42992
+ for (const row of many(sub(cells, 6), 1)) {
42993
+ const ri = rows.map.get(uuidIndex(num(sub(row, 1), 6)));
42994
+ if (ri === void 0 || ci === void 0) continue;
42995
+ const note = sub(entry(num(sub(row, 2), 6)), 10);
42996
+ const text = stringValue(getField(note, 2));
42997
+ if (text === void 0 || text.includes("\uFFFC"))
42998
+ throw new Error("Embedded or unsupported table cell");
42999
+ values[ri][ci] = text.replace(/\n$/u, "");
43000
+ }
43001
+ }
43002
+ const rtl = entries.some((e) => {
43003
+ const map = embeddedMessage(getField(e, 13));
43004
+ return map && many(map, 3).some(
43005
+ (m) => stringValue(getField(sub(m, 2), 4)) === "CRTableColumnDirectionRightToLeft"
43006
+ );
43007
+ });
43008
+ if (rtl) {
43009
+ for (const row of values) row.reverse();
43010
+ columns.ids.reverse();
43011
+ }
43012
+ return { rows: values, rowIds: rows.ids, columnIds: columns.ids };
43013
+ }
43014
+
43015
+ // src/tools/directOperations.ts
43016
+ import { createHash as createHash2 } from "node:crypto";
43017
+ import {
43018
+ closeSync,
43019
+ constants,
43020
+ fstatSync,
43021
+ mkdtempSync as mkdtempSync2,
43022
+ openSync,
43023
+ readFileSync as readFileSync3,
43024
+ rmSync as rmSync2,
43025
+ writeFileSync
43026
+ } from "node:fs";
43027
+ import { tmpdir as tmpdir2 } from "node:os";
43028
+ import { basename, isAbsolute as isAbsolute2, join as join8 } from "node:path";
43029
+ var noteId = external_exports.string().regex(/^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i);
43030
+ var revision = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/);
43031
+ function readSnapshot(manager, id) {
43032
+ const note = manager.getNoteById(id);
43033
+ if (!note) throw new Error("Note not found");
43034
+ if (note.passwordProtected) throw new Error("Locked notes cannot receive attachments");
43035
+ const body = manager.getNoteContentById(id);
43036
+ if (!body) throw new Error("Note content is unavailable");
43037
+ const enriched = enrichNoteRead(id, body);
43038
+ const rich = readRichNote(id);
43039
+ if (!enriched.complete || enriched.revision !== rich.revision)
43040
+ throw new Error("Native metadata changed during read; read the note again");
43041
+ return { id, title: note.title, body, rich, hash: richContentHash(body, enriched) };
43042
+ }
43043
+ function assertExistingContentPreserved(before, after) {
43044
+ if (before.id !== after.id || before.title !== after.title)
43045
+ throw new Error("Note identity changed");
43046
+ const tidy = (text) => text.replace(/\r\n/g, "\n").replace(/[\s\ufffc]+$/gu, "");
43047
+ if (!tidy(after.rich.text).startsWith(tidy(before.rich.text)))
43048
+ throw new Error("Existing note text was not preserved");
43049
+ if (linkSignature(before.rich.links) !== linkSignature(after.rich.links.slice(0, before.rich.links.length)))
43050
+ throw new Error("Existing links were not preserved");
43051
+ if (before.rich.nativeObjectIds.some((id) => !after.rich.nativeObjectIds.includes(id)))
43052
+ throw new Error("Existing native object was lost");
43053
+ if (before.rich.nativeTags.some((tag) => !after.rich.nativeTags.includes(tag)))
43054
+ throw new Error("Existing native tag was lost");
43055
+ for (const item of before.rich.checklistItems || []) {
43056
+ const actual = after.rich.checklistItems?.find((current) => current.id === item.id);
43057
+ if (!actual || actual.text !== item.text || actual.done !== item.done)
43058
+ throw new Error("Existing checklist item identity or state changed");
43059
+ }
43060
+ for (const object3 of before.rich.objectData || []) {
43061
+ const actual = after.rich.objectData?.find((current) => current.id === object3.id);
43062
+ if (!actual || actual.mergeable !== object3.mergeable || actual.view !== object3.view)
43063
+ throw new Error("Existing native object content or presentation changed");
43064
+ }
43065
+ }
43066
+ function localAttachment(path4) {
43067
+ if (!isAbsolute2(path4)) throw new Error("An absolute local file path is required");
43068
+ const descriptor = openSync(path4, constants.O_RDONLY | constants.O_NOFOLLOW);
43069
+ try {
43070
+ const stat = fstatSync(descriptor);
43071
+ if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
43072
+ throw new Error("Attachment must be a nonempty regular file of at most 64 MiB");
43073
+ const bytes = readFileSync3(descriptor);
43074
+ if (bytes.length !== stat.size)
43075
+ throw new Error("Attachment changed while it was being read; try again");
43076
+ return bytes;
43077
+ } finally {
43078
+ closeSync(descriptor);
43079
+ }
43080
+ }
43081
+ function registerDirectOperations(server2, manager) {
43082
+ function tool(name, description, inputSchema, handler, readOnly = false) {
43083
+ server2.registerTool(
43084
+ name,
43085
+ {
43086
+ description,
43087
+ inputSchema,
43088
+ outputSchema: external_exports.object({ ok: external_exports.boolean().optional() }).passthrough(),
43089
+ annotations: { readOnlyHint: readOnly }
43090
+ },
43091
+ (async (args) => {
43092
+ try {
43093
+ const result = handler(args);
43094
+ return {
43095
+ content: [{ type: "text", text: JSON.stringify(result) }],
43096
+ structuredContent: result
43097
+ };
43098
+ } catch (error2) {
43099
+ return {
43100
+ content: [
43101
+ {
43102
+ type: "text",
43103
+ text: error2 instanceof Error ? error2.message : String(error2)
43104
+ }
43105
+ ],
43106
+ isError: true
43107
+ };
43108
+ }
43109
+ })
43110
+ );
43111
+ }
43112
+ tool(
43113
+ "get-folder-by-id",
43114
+ "Use when: reading the exact folder name and parent before a guarded rename.\nReturns: folder id, current name, and parent id.\nDo not use when: listing folders by path (list-folders).\nSafety: read-only.",
43115
+ { id: external_exports.string().max(2e3) },
43116
+ ({ id }) => manager.getFolderById(id),
43117
+ true
43118
+ );
43119
+ tool(
43120
+ "rename-folder",
43121
+ "Use when: renaming one previously read folder in place.\nReturns: the unchanged folder id, new name, and parent id after readback.\nDo not use when: creating, moving, or deleting a folder.\nSafety: requires the expected current name and parent; refuses stale metadata and sibling conflicts.",
43122
+ {
43123
+ id: external_exports.string().max(2e3),
43124
+ expectedName: external_exports.string().max(1e3),
43125
+ expectedParentId: external_exports.string().max(2e3),
43126
+ newName: external_exports.string().min(1).max(1e3)
43127
+ },
43128
+ (args) => ({
43129
+ ok: true,
43130
+ ...manager.renameFolderById(args.id, args.expectedName, args.expectedParentId, args.newName)
43131
+ })
43132
+ );
43133
+ tool(
43134
+ "add-attachment",
43135
+ "Use when: adding one local file to an exact note without replacing its body.\nReturns: the new attachment id, byte count, and post-write content hash after exact byte verification.\nDo not use when: reading or exporting an existing attachment.\nSafety: requires a fresh rich revision, copies at most 64 MiB through a private temporary file, never retries insertion, and verifies existing content plus fetched bytes.",
43136
+ { id: noteId, expectedContentHash: revision, path: external_exports.string().min(1).max(4096) },
43137
+ ({ id, expectedContentHash, path: path4 }) => {
43138
+ const before = readSnapshot(manager, id);
43139
+ if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
43140
+ const bytes = localAttachment(path4);
43141
+ const beforeAttachments = manager.listAttachmentsById(id);
43142
+ const directory = mkdtempSync2(join8(tmpdir2(), "notes-attachment-add-"));
43143
+ const temporaryFile = join8(directory, basename(path4));
43144
+ try {
43145
+ writeFileSync(temporaryFile, bytes, { mode: 384 });
43146
+ if (readSnapshot(manager, id).hash !== before.hash)
43147
+ throw new Error("Note revision changed");
43148
+ let returnedId;
43149
+ let transportUncertain = false;
43150
+ try {
43151
+ returnedId = manager.addAttachmentById(id, before.body, temporaryFile);
43152
+ } catch {
43153
+ transportUncertain = true;
43154
+ }
43155
+ const after = readSnapshot(manager, id);
43156
+ assertExistingContentPreserved(before, after);
43157
+ const readInserted = () => [
43158
+ ...new Map(manager.listAttachmentsById(id).map((item) => [item.id, item])).values()
43159
+ ].filter((item) => !beforeAttachments.some((existing) => existing.id === item.id));
43160
+ let inserted = readInserted();
43161
+ for (let attempt = 0; attempt < 4 && (inserted.length !== 1 || returnedId && returnedId !== inserted[0].id); attempt++) {
43162
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250);
43163
+ inserted = readInserted();
43164
+ }
43165
+ const persistentReturnedId = returnedId && /\/ICAttachment\/p\d+$/.test(returnedId);
43166
+ if (inserted.length !== 1 || persistentReturnedId && returnedId !== inserted[0].id)
43167
+ throw new Error(
43168
+ "Attachment insertion outcome uncertain; read the exact note before retrying"
43169
+ );
43170
+ const attachmentId = inserted[0].id;
43171
+ const fetched = manager.getAttachmentBase64ById(id, attachmentId);
43172
+ const actual = typeof fetched.base64 === "string" ? Buffer.from(fetched.base64, "base64") : null;
43173
+ if (!actual || createHash2("sha256").update(actual).digest("hex") !== createHash2("sha256").update(bytes).digest("hex"))
43174
+ throw new Error(
43175
+ "Attachment bytes were not verified; read the exact note before retrying"
43176
+ );
43177
+ return {
43178
+ ok: true,
43179
+ id,
43180
+ attachmentId,
43181
+ contentHash: after.hash,
43182
+ bytes: bytes.length,
43183
+ ...transportUncertain ? {
43184
+ transportWarning: "Transport was uncertain; exact bytes and prior content were verified"
43185
+ } : {}
43186
+ };
43187
+ } finally {
43188
+ rmSync2(directory, { recursive: true, force: true });
43189
+ }
43190
+ }
43191
+ );
43192
+ }
43193
+
42855
43194
  // src/index.ts
42856
43195
  loadFileConfig();
42857
43196
  var require2 = createRequire(import.meta.url);
@@ -42862,6 +43201,7 @@ var server = new McpServer({
42862
43201
  description: "MCP server for managing Apple Notes - create, search, update, and organize notes"
42863
43202
  });
42864
43203
  var notesManager = new AppleNotesManager();
43204
+ registerDirectOperations(server, notesManager);
42865
43205
  function successResponse(message, structured) {
42866
43206
  const res = { content: [{ type: "text", text: message }] };
42867
43207
  if (structured) res.structuredContent = structured;
@@ -43406,6 +43746,100 @@ registerTool(
43406
43746
  return successResponse(`Shown account with ID "${id}" in Notes.app`, { id, separately });
43407
43747
  }, "Error showing account")
43408
43748
  );
43749
+ registerTool(
43750
+ "get-native-objects",
43751
+ {
43752
+ description: "Use when: inspecting native objects, checklist identities, or tables in one exact note.\nReturns: native object IDs and ranges, checklist IDs and state, actual native tags, decoded tables, and the current rich content hash.\nDo not use when: you only need the note body (get-note-content) or AppleScript attachment metadata (list-attachments).\nSafety: read-only; requires Full Disk Access and reports incomplete table metadata instead of guessing.",
43753
+ inputSchema: { id: noteIdInput },
43754
+ outputSchema: {
43755
+ id: external_exports.string().optional(),
43756
+ contentHash: external_exports.string().optional(),
43757
+ objects: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
43758
+ checklistItems: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
43759
+ nativeTags: external_exports.array(external_exports.string()).optional(),
43760
+ tables: external_exports.array(external_exports.record(external_exports.unknown())).optional(),
43761
+ tableCellsComplete: external_exports.boolean().optional()
43762
+ },
43763
+ annotations: { readOnlyHint: true }
43764
+ },
43765
+ withErrorHandling(({ id }) => {
43766
+ const note = notesManager.getNoteById(id);
43767
+ if (!note) return errorResponse(`Note with ID "${id}" not found`);
43768
+ const body = notesManager.getNoteContentById(id);
43769
+ if (!body) return errorResponse(`Failed to read content of note "${note.title}"`);
43770
+ const rich = readRichNote(id);
43771
+ const tables = (rich.objectData || []).filter((object3) => object3.type?.includes("table")).map((object3) => {
43772
+ try {
43773
+ return {
43774
+ id: object3.id,
43775
+ attachmentId: id.replace(/ICNote\/p\d+$/, `ICAttachment/p${object3.pk}`),
43776
+ complete: true,
43777
+ ...parseNoteTable(Buffer.from(object3.mergeable, "hex"))
43778
+ };
43779
+ } catch (error2) {
43780
+ return { id: object3.id, complete: false, reason: String(error2) };
43781
+ }
43782
+ });
43783
+ for (const object3 of rich.objects || []) {
43784
+ if (object3.type.includes("table") && !tables.some((table) => table.id === object3.id)) {
43785
+ tables.push({
43786
+ id: object3.id,
43787
+ complete: false,
43788
+ reason: "Native table metadata is unavailable"
43789
+ });
43790
+ }
43791
+ }
43792
+ const richRead = {
43793
+ content: body,
43794
+ links: rich.links,
43795
+ nativeTags: rich.nativeTags,
43796
+ complete: true,
43797
+ writable: !rich.hasNativeObjects && !rich.hasChecklist,
43798
+ revision: rich.revision
43799
+ };
43800
+ return successResponse("Native objects read from the exact note", {
43801
+ id,
43802
+ contentHash: richContentHash(body, richRead),
43803
+ objects: rich.objects,
43804
+ checklistItems: rich.checklistItems,
43805
+ nativeTags: rich.nativeTags,
43806
+ tables,
43807
+ tableCellsComplete: tables.every((table) => table.complete)
43808
+ });
43809
+ }, "Error reading native objects")
43810
+ );
43811
+ registerTool(
43812
+ "list-native-tags",
43813
+ {
43814
+ description: "Use when: listing actual native Notes tags used in one explicit account and folder.\nReturns: each native tag mapped to exact matching note IDs, plus completeness and per-note errors.\nDo not use when: searching textual #hashtags in note bodies (search-notes).\nSafety: read-only; requires Full Disk Access and discloses partial reads.",
43815
+ inputSchema: {
43816
+ account: external_exports.string().min(1).max(MAX.ACCOUNT),
43817
+ folder: external_exports.string().min(1).max(MAX.FOLDER)
43818
+ },
43819
+ outputSchema: {
43820
+ tags: external_exports.record(external_exports.array(external_exports.string())).optional(),
43821
+ complete: external_exports.boolean().optional(),
43822
+ errors: external_exports.record(external_exports.string()).optional()
43823
+ },
43824
+ annotations: { readOnlyHint: true }
43825
+ },
43826
+ withErrorHandling(({ account, folder }) => {
43827
+ const tags = {};
43828
+ const errors = {};
43829
+ for (const note of notesManager.listNoteRefs(account, folder)) {
43830
+ try {
43831
+ for (const tag of readRichNote(note.id).nativeTags) (tags[tag] ||= []).push(note.id);
43832
+ } catch {
43833
+ errors[note.id] = "Native metadata unavailable";
43834
+ }
43835
+ }
43836
+ return successResponse("Native tags read from the requested folder", {
43837
+ tags,
43838
+ complete: Object.keys(errors).length === 0,
43839
+ errors
43840
+ });
43841
+ }, "Error listing native tags")
43842
+ );
43409
43843
  registerTool(
43410
43844
  "update-note",
43411
43845
  {
@@ -44263,8 +44697,8 @@ registerTool(
44263
44697
  contentType: external_exports.string().optional()
44264
44698
  }
44265
44699
  },
44266
- withErrorHandling(({ noteId, attachmentId, savePath }) => {
44267
- const r = notesManager.saveAttachmentById(noteId, attachmentId, savePath);
44700
+ withErrorHandling(({ noteId: noteId2, attachmentId, savePath }) => {
44701
+ const r = notesManager.saveAttachmentById(noteId2, attachmentId, savePath);
44268
44702
  if (!r.success) {
44269
44703
  return errorResponse(`Failed to save attachment: ${r.error ?? "unknown error"}`);
44270
44704
  }
@@ -44290,8 +44724,8 @@ registerTool(
44290
44724
  base64: external_exports.string().optional()
44291
44725
  }
44292
44726
  },
44293
- withErrorHandling(({ noteId, attachmentId }) => {
44294
- const r = notesManager.getAttachmentBase64ById(noteId, attachmentId);
44727
+ withErrorHandling(({ noteId: noteId2, attachmentId }) => {
44728
+ const r = notesManager.getAttachmentBase64ById(noteId2, attachmentId);
44295
44729
  if (!r.success || !r.base64) {
44296
44730
  return errorResponse(`Failed to fetch attachment: ${r.error ?? "unknown error"}`);
44297
44731
  }
@@ -44316,13 +44750,13 @@ registerTool(
44316
44750
  separately: external_exports.boolean().optional()
44317
44751
  }
44318
44752
  },
44319
- withErrorHandling(({ noteId, attachmentId, separately = false }) => {
44320
- const success = notesManager.showAttachmentById(noteId, attachmentId, separately);
44753
+ withErrorHandling(({ noteId: noteId2, attachmentId, separately = false }) => {
44754
+ const success = notesManager.showAttachmentById(noteId2, attachmentId, separately);
44321
44755
  if (!success) {
44322
- return errorResponse(`Failed to show attachment "${attachmentId}" on note "${noteId}"`);
44756
+ return errorResponse(`Failed to show attachment "${attachmentId}" on note "${noteId2}"`);
44323
44757
  }
44324
44758
  return successResponse(`Shown attachment "${attachmentId}" in Notes.app`, {
44325
- noteId,
44759
+ noteId: noteId2,
44326
44760
  attachmentId,
44327
44761
  separately
44328
44762
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apple-notes-mcp",
3
- "version": "2.8.5",
3
+ "version": "2.8.7",
4
4
  "description": "MCP server for Apple Notes - create, search, update, and manage notes via Claude and other AI assistants",
5
5
  "type": "module",
6
6
  "main": "build/index.js",