apple-notes-mcp 2.8.29 → 2.8.31

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 +74 -6
  2. package/build/index.js +786 -191
  3. package/package.json +1 -1
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, constants2) {
420
+ optimizeNames(names, constants3) {
421
421
  if (!names[this.name.str])
422
422
  return;
423
423
  if (this.rhs)
424
- this.rhs = optimizeExpr(this.rhs, names, constants2);
424
+ this.rhs = optimizeExpr(this.rhs, names, constants3);
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, constants2) {
441
+ optimizeNames(names, constants3) {
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, constants2);
444
+ this.rhs = optimizeExpr(this.rhs, names, constants3);
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, constants2) {
506
- this.code = optimizeExpr(this.code, names, constants2);
505
+ optimizeNames(names, constants3) {
506
+ this.code = optimizeExpr(this.code, names, constants3);
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, constants2) {
535
+ optimizeNames(names, constants3) {
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, constants2))
540
+ if (n.optimizeNames(names, constants3))
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, constants2) {
593
+ optimizeNames(names, constants3) {
594
594
  var _a;
595
- this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants2);
596
- if (!(super.optimizeNames(names, constants2) || this.else))
595
+ this.else = (_a = this.else) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants3);
596
+ if (!(super.optimizeNames(names, constants3) || this.else))
597
597
  return;
598
- this.condition = optimizeExpr(this.condition, names, constants2);
598
+ this.condition = optimizeExpr(this.condition, names, constants3);
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, constants2) {
622
- if (!super.optimizeNames(names, constants2))
621
+ optimizeNames(names, constants3) {
622
+ if (!super.optimizeNames(names, constants3))
623
623
  return;
624
- this.iteration = optimizeExpr(this.iteration, names, constants2);
624
+ this.iteration = optimizeExpr(this.iteration, names, constants3);
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, constants2) {
661
- if (!super.optimizeNames(names, constants2))
660
+ optimizeNames(names, constants3) {
661
+ if (!super.optimizeNames(names, constants3))
662
662
  return;
663
- this.iterable = optimizeExpr(this.iterable, names, constants2);
663
+ this.iterable = optimizeExpr(this.iterable, names, constants3);
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, constants2) {
705
+ optimizeNames(names, constants3) {
706
706
  var _a, _b;
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);
707
+ super.optimizeNames(names, constants3);
708
+ (_a = this.catch) === null || _a === void 0 ? void 0 : _a.optimizeNames(names, constants3);
709
+ (_b = this.finally) === null || _b === void 0 ? void 0 : _b.optimizeNames(names, constants3);
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, constants2) {
1010
+ function optimizeExpr(expr, names, constants3) {
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 = constants2[n.str];
1025
+ const c = constants3[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 && constants2[c.str] !== void 0);
1032
+ return e instanceof code_1._Code && e._items.some((c) => c instanceof code_1.Name && names[c.str] === 1 && constants3[c.str] !== void 0);
1033
1033
  }
1034
1034
  }
1035
1035
  function subtractNames(names, from) {
@@ -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 constants2 = {
7425
+ var constants3 = {
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 constants2) {
7465
- v = { value: constants2[c] };
7464
+ for (c in constants3) {
7465
+ v = { value: constants3[c] };
7466
7466
  Object.defineProperty(DOMException, c, v);
7467
7467
  Object.defineProperty(DOMException.prototype, c, v);
7468
7468
  }
@@ -9872,9 +9872,9 @@ var require_select = __commonJS({
9872
9872
  var makeSubject = function() {
9873
9873
  var target;
9874
9874
  function subject(el) {
9875
- var node = el.ownerDocument, scope = node.getElementsByTagName(subject.lname), i = scope.length;
9875
+ var node = el.ownerDocument, scope2 = node.getElementsByTagName(subject.lname), i = scope2.length;
9876
9876
  while (i--) {
9877
- if (subject.test(scope[i]) && target === el) {
9877
+ if (subject.test(scope2[i]) && target === el) {
9878
9878
  target = null;
9879
9879
  return true;
9880
9880
  }
@@ -9902,16 +9902,16 @@ var require_select = __commonJS({
9902
9902
  };
9903
9903
  };
9904
9904
  var find = function(sel, node) {
9905
- var results = [], test = compile(sel), scope = node.getElementsByTagName(test.qname), i = 0, el;
9906
- while (el = scope[i++]) {
9905
+ var results = [], test = compile(sel), scope2 = node.getElementsByTagName(test.qname), i = 0, el;
9906
+ while (el = scope2[i++]) {
9907
9907
  if (test(el)) results.push(el);
9908
9908
  }
9909
9909
  if (test.sel) {
9910
9910
  while (test.sel) {
9911
9911
  test = compile(test.sel);
9912
- scope = node.getElementsByTagName(test.qname);
9912
+ scope2 = node.getElementsByTagName(test.qname);
9913
9913
  i = 0;
9914
- while (el = scope[i++]) {
9914
+ while (el = scope2[i++]) {
9915
9915
  if (test(el) && indexOf.call(results, el) === -1) {
9916
9916
  results.push(el);
9917
9917
  }
@@ -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 join15(output, replacement);
24468
+ return join16(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 = join15(output, rule.append(self.options));
24475
+ output = join16(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 join15(output, replacement) {
24487
+ function join16(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 = (sub3) => {
24781
- this.issues = [...this.issues, sub3];
24780
+ this.addIssue = (sub4) => {
24781
+ this.issues = [...this.issues, sub4];
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 sub3 of this.issues) {
24849
- if (sub3.path.length > 0) {
24850
- const firstEl = sub3.path[0];
24848
+ for (const sub4 of this.issues) {
24849
+ if (sub4.path.length > 0) {
24850
+ const firstEl = sub4.path[0];
24851
24851
  fieldErrors[firstEl] = fieldErrors[firstEl] || [];
24852
- fieldErrors[firstEl].push(mapper(sub3));
24852
+ fieldErrors[firstEl].push(mapper(sub4));
24853
24853
  } else {
24854
- formErrors.push(mapper(sub3));
24854
+ formErrors.push(mapper(sub4));
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 sub3 of error2.issues) {
29154
- if (sub3.path.length > 0) {
29155
- fieldErrors[sub3.path[0]] = fieldErrors[sub3.path[0]] || [];
29156
- fieldErrors[sub3.path[0]].push(mapper(sub3));
29153
+ for (const sub4 of error2.issues) {
29154
+ if (sub4.path.length > 0) {
29155
+ fieldErrors[sub4.path[0]] = fieldErrors[sub4.path[0]] || [];
29156
+ fieldErrors[sub4.path[0]].push(mapper(sub4));
29157
29157
  } else {
29158
- formErrors.push(mapper(sub3));
29158
+ formErrors.push(mapper(sub4));
29159
29159
  }
29160
29160
  }
29161
29161
  return { formErrors, fieldErrors };
@@ -38990,6 +38990,22 @@ var NODE_RUNTIME_TCC_GUIDE_URL = "https://github.com/sweetrb/apple-notes-mcp/blo
38990
38990
  var AUTOMATION_PERMISSION_GUIDE_URL = "https://github.com/sweetrb/apple-notes-mcp#permission-denied";
38991
38991
  var AUTOMATION_REMEDIATION = `Grant automation access in System Settings > Privacy & Security > Automation to the app that launches this server (Claude Desktop / Terminal / iTerm2), then fully quit and relaunch it \u2014 run the doctor tool to verify. See: ${AUTOMATION_PERMISSION_GUIDE_URL}`;
38992
38992
 
38993
+ // src/utils/callTimeout.ts
38994
+ import { AsyncLocalStorage } from "node:async_hooks";
38995
+ var CALL_TIMEOUT_SECONDS = { min: 1, max: 120 };
38996
+ var scope = new AsyncLocalStorage();
38997
+ function runWithCallTimeout(seconds, fn) {
38998
+ if (seconds === void 0) return fn();
38999
+ if (!Number.isInteger(seconds) || seconds < CALL_TIMEOUT_SECONDS.min || seconds > CALL_TIMEOUT_SECONDS.max)
39000
+ throw new Error(
39001
+ `timeoutSeconds must be a whole number from ${CALL_TIMEOUT_SECONDS.min} to ${CALL_TIMEOUT_SECONDS.max}`
39002
+ );
39003
+ return scope.run(seconds * 1e3, fn);
39004
+ }
39005
+ function callTimeoutMs() {
39006
+ return scope.getStore();
39007
+ }
39008
+
38993
39009
  // src/utils/applescript.ts
38994
39010
  var DEFAULT_TIMEOUT_MS = 3e4;
38995
39011
  var DEFAULT_MAX_BUFFER_BYTES = 64 * 1024 * 1024;
@@ -39146,7 +39162,7 @@ function parseErrorMessage(errorOutput) {
39146
39162
  return coreError.trim() || "Unknown AppleScript error";
39147
39163
  }
39148
39164
  function executeAppleScript(script, options = {}) {
39149
- const timeoutMs = options.timeoutMs ?? envPositiveNumber("APPLE_NOTES_MCP_TIMEOUT_MS") ?? DEFAULT_TIMEOUT_MS;
39165
+ const timeoutMs = options.timeoutMs ?? callTimeoutMs() ?? envPositiveNumber("APPLE_NOTES_MCP_TIMEOUT_MS") ?? DEFAULT_TIMEOUT_MS;
39150
39166
  const maxRetries = options.maxRetries ?? envPositiveNumber("APPLE_NOTES_MCP_MAX_RETRIES") ?? DEFAULT_MAX_RETRIES;
39151
39167
  const retryDelayMs = options.retryDelayMs ?? envPositiveNumber("APPLE_NOTES_MCP_RETRY_DELAY_MS") ?? DEFAULT_RETRY_DELAY_MS;
39152
39168
  if (!script || !script.trim()) {
@@ -39275,7 +39291,7 @@ function decodeVarint(buf, offset) {
39275
39291
  }
39276
39292
  throw new Error(`Unexpected end of buffer reading varint at offset ${offset}`);
39277
39293
  }
39278
- function decodeMessage(buf) {
39294
+ function decodeMessage(buf, options = {}) {
39279
39295
  const fields = [];
39280
39296
  let offset = 0;
39281
39297
  while (offset < buf.length) {
@@ -39296,10 +39312,13 @@ function decodeMessage(buf) {
39296
39312
  const value = buf.slice(offset, offset + length);
39297
39313
  fields.push({ fieldNumber, wireType, value });
39298
39314
  offset += length;
39299
- } else if (wireType === 5) {
39300
- offset += 4;
39301
- } else if (wireType === 1) {
39302
- offset += 8;
39315
+ } else if (wireType === 5 || wireType === 1) {
39316
+ const width = wireType === 5 ? 4 : 8;
39317
+ if (offset + width > buf.length) break;
39318
+ if (options.keepFixed) {
39319
+ fields.push({ fieldNumber, wireType, value: buf.slice(offset, offset + width) });
39320
+ }
39321
+ offset += width;
39303
39322
  } else {
39304
39323
  break;
39305
39324
  }
@@ -39330,6 +39349,11 @@ function embeddedMessage(field) {
39330
39349
  if (!bytes) return void 0;
39331
39350
  return decodeMessage(bytes);
39332
39351
  }
39352
+ function fixed64Double(field) {
39353
+ if (!field || field.wireType !== 1 || !(field.value instanceof Uint8Array)) return void 0;
39354
+ if (field.value.length !== 8) return void 0;
39355
+ return new DataView(field.value.buffer, field.value.byteOffset, 8).getFloat64(0, true);
39356
+ }
39333
39357
  var ProtobufDecodeError = class extends Error {
39334
39358
  constructor(message) {
39335
39359
  super(message);
@@ -39901,9 +39925,373 @@ function assertLinkedWrite(rich, content, format, allowLinkChanges = false) {
39901
39925
  }
39902
39926
  }
39903
39927
 
39904
- // src/utils/noteTables.ts
39928
+ // src/utils/audioTranscripts.ts
39929
+ import { execFileSync as execFileSync4 } from "node:child_process";
39930
+ import { homedir as homedir3 } from "node:os";
39931
+ import { join as join3 } from "node:path";
39905
39932
  import { gunzipSync as gunzipSync3 } from "node:zlib";
39933
+ var NOTES_DB_PATH2 = join3(
39934
+ homedir3(),
39935
+ "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
39936
+ );
39937
+ var DEFAULT_MAX_SEGMENTS = 2e3;
39938
+ var MAX_SEGMENTS_LIMIT = 2e4;
39939
+ var AudioTranscriptError = class extends Error {
39940
+ constructor(kind, message) {
39941
+ super(message);
39942
+ this.kind = kind;
39943
+ this.name = "AudioTranscriptError";
39944
+ }
39945
+ kind;
39946
+ };
39947
+ var decode = (bytes) => decodeMessage(bytes, { keepFixed: true });
39906
39948
  var sub = (f, n) => {
39949
+ const bytes = getField(f, n)?.value;
39950
+ return bytes instanceof Uint8Array ? decode(bytes) : void 0;
39951
+ };
39952
+ var need = (value, what) => {
39953
+ if (value === void 0) throw new Error(`Missing ${what}`);
39954
+ return value;
39955
+ };
39956
+ var many = (f, n) => getFields(f, n).map((field) => {
39957
+ if (!(field.value instanceof Uint8Array)) throw new Error("Invalid nested entry");
39958
+ return decode(field.value);
39959
+ });
39960
+ function parseAudioRecording(data) {
39961
+ const root = decode(data);
39962
+ const entries = many(root, 3);
39963
+ if (entries.length === 0) throw new Error("No mergeable objects");
39964
+ if (entries.length > 2e6) throw new Error("Recording too large");
39965
+ const keys = getFields(root, 4).map((f) => stringValue(f) ?? "");
39966
+ const types = getFields(root, 5).map((f) => stringValue(f) ?? "");
39967
+ const uuids = getFields(root, 6).map(
39968
+ (f) => f.value instanceof Uint8Array ? Buffer.from(f.value).toString("hex") : ""
39969
+ );
39970
+ const uuidSlot = /* @__PURE__ */ new Map();
39971
+ uuids.forEach((uuid2, i) => {
39972
+ if (uuid2 && !uuidSlot.has(uuid2)) uuidSlot.set(uuid2, i);
39973
+ });
39974
+ const entry = (index) => {
39975
+ const value = entries[index];
39976
+ if (!value) throw new Error("Invalid object reference");
39977
+ return value;
39978
+ };
39979
+ const customMap = (e) => {
39980
+ const map = sub(e, 13);
39981
+ if (!map) return void 0;
39982
+ const values = /* @__PURE__ */ new Map();
39983
+ for (const item of many(map, 3)) {
39984
+ const key = keys[need(varintValue(getField(item, 1)), "map key")];
39985
+ const value = sub(item, 2);
39986
+ if (key !== void 0 && value) values.set(key, value);
39987
+ }
39988
+ return { type: types[need(varintValue(getField(map, 1)), "map type")] ?? "", values };
39989
+ };
39990
+ const objectIndex = (id2) => id2 ? varintValue(getField(id2, 6)) : void 0;
39991
+ const registerTarget = (id2) => {
39992
+ const index = objectIndex(id2);
39993
+ if (index === void 0) return void 0;
39994
+ const register = sub(entry(index), 1);
39995
+ if (!register) return void 0;
39996
+ const target = objectIndex(sub(register, 2));
39997
+ return target === void 0 ? void 0 : entry(target);
39998
+ };
39999
+ const registerPrimitive = (id2) => {
40000
+ const target = registerTarget(id2);
40001
+ const map = target && customMap(target);
40002
+ if (!map) return void 0;
40003
+ const self = map.values.get("self");
40004
+ if (self) return stringValue(getField(self, 4));
40005
+ const double = map.values.get("doubleValue");
40006
+ if (double) return fixed64Double(getField(double, 3));
40007
+ const integer2 = map.values.get("integerValue");
40008
+ if (integer2) return varintValue(getField(integer2, 2));
40009
+ return void 0;
40010
+ };
40011
+ const registerNoteText = (id2) => {
40012
+ const target = registerTarget(id2);
40013
+ const note = target && sub(target, 10);
40014
+ const text = note && stringValue(getField(note, 2));
40015
+ return text?.replace(/\n+$/u, "") || void 0;
40016
+ };
40017
+ const recordings = entries.map((e) => customMap(e)).filter((m) => m?.type === "com.apple.notes.ICTTAudioRecording");
40018
+ if (recordings.length !== 1) throw new Error("Expected exactly one audio recording object");
40019
+ const recording = recordings[0];
40020
+ const fragments = [];
40021
+ const fragmentList = objectIndex(recording.values.get("fragments"));
40022
+ if (fragmentList !== void 0) {
40023
+ const list = need(sub(entry(fragmentList), 5), "fragment list");
40024
+ for (const item of many(list, 1)) {
40025
+ const fragment = customMap(entry(need(objectIndex(sub(item, 2)), "fragment reference")));
40026
+ if (fragment?.type !== "com.apple.notes.ICTTAudioRecording.Fragment")
40027
+ throw new Error("Unexpected fragment type");
40028
+ const identity = stringValue(getField(fragment.values.get("identity") ?? [], 4));
40029
+ const segments = [];
40030
+ const transcriptRef = objectIndex(fragment.values.get("transcript"));
40031
+ if (transcriptRef !== void 0) {
40032
+ const set = sub(entry(transcriptRef), 15);
40033
+ if (!set) throw new Error("Unsupported transcript container");
40034
+ const bySlot = /* @__PURE__ */ new Map();
40035
+ for (const element of many(sub(set, 2) ?? [], 1)) {
40036
+ const keyMap = customMap(entry(need(objectIndex(sub(element, 1)), "segment key")));
40037
+ const slot = varintValue(getField(keyMap?.values.get("UUIDIndex") ?? [], 2));
40038
+ const value = objectIndex(sub(element, 2));
40039
+ if (slot === void 0 || value === void 0) throw new Error("Invalid segment key");
40040
+ bySlot.set(slot, value);
40041
+ }
40042
+ const ordering = many(need(sub(set, 1), "transcript ordering"), 2).map((pair) => ({
40043
+ index: need(varintValue(getField(pair, 1)), "segment index"),
40044
+ uuid: getField(pair, 2)?.value
40045
+ })).sort((a, b) => a.index - b.index);
40046
+ for (const { uuid: uuid2 } of ordering) {
40047
+ if (!(uuid2 instanceof Uint8Array)) throw new Error("Invalid segment UUID");
40048
+ const slot = uuidSlot.get(Buffer.from(uuid2).toString("hex"));
40049
+ const segmentIndex = slot === void 0 ? void 0 : bySlot.get(slot);
40050
+ if (segmentIndex === void 0) throw new Error("Unresolved transcript segment");
40051
+ const segment = customMap(entry(segmentIndex));
40052
+ if (segment?.type !== "com.apple.notes.ICTTTranscriptSegment")
40053
+ throw new Error("Unexpected segment type");
40054
+ const text = registerPrimitive(segment.values.get("text"));
40055
+ if (typeof text !== "string") throw new Error("Segment without text");
40056
+ const start = registerPrimitive(segment.values.get("timestamp"));
40057
+ const duration3 = registerPrimitive(segment.values.get("duration"));
40058
+ const speaker = registerPrimitive(segment.values.get("speaker"));
40059
+ segments.push({
40060
+ text,
40061
+ ...typeof start === "number" ? { start } : {},
40062
+ ...typeof duration3 === "number" ? { duration: duration3 } : {},
40063
+ ...typeof speaker === "string" && speaker ? { speaker } : {}
40064
+ });
40065
+ }
40066
+ }
40067
+ fragments.push({ ...identity ? { identity } : {}, segments });
40068
+ }
40069
+ }
40070
+ const summary = registerNoteText(recording.values.get("summary"));
40071
+ const topLineSummary = registerNoteText(recording.values.get("topLineSummary"));
40072
+ return {
40073
+ fragments,
40074
+ ...summary ? { summary } : {},
40075
+ ...topLineSummary ? { topLineSummary } : {}
40076
+ };
40077
+ }
40078
+ function joinSegments(segments) {
40079
+ let out = "";
40080
+ for (const { text } of segments) {
40081
+ if (!text) continue;
40082
+ const glue = !out || /\s$/u.test(out) || /^\s/u.test(text) || /^[.,!?;:%)\]}…'’]/u.test(text) ? "" : " ";
40083
+ out += glue + text;
40084
+ }
40085
+ return out.trim();
40086
+ }
40087
+ function parseNoteId(noteId3) {
40088
+ const match = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/ICNote\/p(\d+)$/.exec(noteId3);
40089
+ if (!match) {
40090
+ throw new AudioTranscriptError(
40091
+ "invalid_id",
40092
+ `Invalid note ID format: "${noteId3}". Expected format: x-coredata://UUID/ICNote/pNNN`
40093
+ );
40094
+ }
40095
+ return { store: match[1], pk: match[2] };
40096
+ }
40097
+ var AUDIO_UTI_SQL = "(a.ZTYPEUTI LIKE '%audio%' OR a.ZTYPEUTI IN ('public.mp3'))";
40098
+ function buildTranscriptSql(pk, available) {
40099
+ if (!/^\d+$/.test(pk)) throw new Error("Invalid primary key");
40100
+ const optional2 = (column, key, alias = "a") => available.has(column) ? `, '${key}', ${alias}.${column}` : "";
40101
+ const locked = available.has("ZISPASSWORDPROTECTED") ? "COALESCE(n.ZISPASSWORDPROTECTED, 0)" : "0";
40102
+ return [
40103
+ "BEGIN;",
40104
+ // Every statement yields exactly one row, so output lines stay positional.
40105
+ `SELECT json_object('found', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = ${pk}), 'locked', (SELECT ${locked} FROM ZICCLOUDSYNCINGOBJECT n WHERE n.Z_PK = ${pk}));`,
40106
+ `SELECT COALESCE((SELECT hex(ZDATA) FROM ZICNOTEDATA WHERE ZNOTE = ${pk} LIMIT 1), '');`,
40107
+ "SELECT json_group_array(json_object('pk', a.Z_PK, 'identifier', a.ZIDENTIFIER, 'uti', a.ZTYPEUTI, 'data', hex(a.ZMERGEABLEDATA1)" + optional2("ZDURATION", "duration") + optional2("ZNEEDSTRANSCRIPTION", "needsTranscription") + ", 'fragments', json((SELECT json_group_array(json_object('identifier', c.ZIDENTIFIER" + optional2("ZDURATION", "duration", "c") + `)) FROM ZICCLOUDSYNCINGOBJECT c WHERE c.ZPARENTATTACHMENT = a.Z_PK)))) FROM ZICCLOUDSYNCINGOBJECT a WHERE a.ZNOTE = ${pk} AND a.ZPARENTATTACHMENT IS NULL AND ${AUDIO_UTI_SQL};`,
40108
+ "COMMIT;"
40109
+ ].join("\n");
40110
+ }
40111
+ var REQUIRED_COLUMNS = [
40112
+ "ZMERGEABLEDATA1",
40113
+ "ZPARENTATTACHMENT",
40114
+ "ZTYPEUTI",
40115
+ "ZNOTE",
40116
+ "ZIDENTIFIER"
40117
+ ];
40118
+ function runSqlite(dbPath2, sql) {
40119
+ return execFileSync4("sqlite3", ["-readonly", dbPath2, sql], {
40120
+ encoding: "utf8",
40121
+ timeout: 1e4,
40122
+ maxBuffer: 256 * 1024 * 1024,
40123
+ stdio: ["pipe", "pipe", "pipe"]
40124
+ });
40125
+ }
40126
+ function bodyAttachmentOrder(gzipped) {
40127
+ const doc = decodeMessage(gunzipSync3(gzipped, { maxOutputLength: 64 * 1024 * 1024 }));
40128
+ const body = embeddedMessage(getField(embeddedMessage(getField(doc, 2)) ?? [], 3));
40129
+ if (!body) throw new Error("Unsupported Notes document structure");
40130
+ const ids = [];
40131
+ for (const run of getFields(body, 5)) {
40132
+ const attachment = embeddedMessage(getField(embeddedMessage(run) ?? [], 12));
40133
+ const id2 = attachment && stringValue(getField(attachment, 1));
40134
+ if (id2 && !ids.includes(id2)) ids.push(id2);
40135
+ }
40136
+ return ids;
40137
+ }
40138
+ function readAudioTranscripts(noteId3, options = {}) {
40139
+ const { store, pk } = parseNoteId(noteId3);
40140
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH2;
40141
+ const maxSegments = Math.min(
40142
+ Math.max(1, Math.floor(options.maxSegments ?? DEFAULT_MAX_SEGMENTS)),
40143
+ MAX_SEGMENTS_LIMIT
40144
+ );
40145
+ let lines;
40146
+ try {
40147
+ const available = new Set(
40148
+ runSqlite(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").split("\n").map((line) => line.trim()).filter(Boolean)
40149
+ );
40150
+ const missing = REQUIRED_COLUMNS.filter((c) => !available.has(c));
40151
+ if (missing.length)
40152
+ throw new AudioTranscriptError(
40153
+ "query_error",
40154
+ `This macOS version's Notes database lacks ${missing.join(", ")}; stored transcripts cannot be read.`
40155
+ );
40156
+ lines = runSqlite(dbPath2, buildTranscriptSql(pk, available)).split("\n");
40157
+ } catch (error2) {
40158
+ if (error2 instanceof AudioTranscriptError) throw error2;
40159
+ const message = error2 instanceof Error ? error2.message : String(error2);
40160
+ if (/authorization denied|unable to open database/i.test(message))
40161
+ throw new AudioTranscriptError(
40162
+ "no_fda",
40163
+ `Full Disk Access is required to read stored transcripts. Grant it to the app that launches this server, then fully quit and relaunch it. Setup guide: ${FULL_DISK_ACCESS_GUIDE_URL}`
40164
+ );
40165
+ throw new AudioTranscriptError("query_error", "Failed to read the Notes database.");
40166
+ }
40167
+ const [noteLine = "{}", bodyLine = "", rowsLine = "[]"] = lines;
40168
+ const note = JSON.parse(noteLine || "{}");
40169
+ if (!note.found)
40170
+ throw new AudioTranscriptError(
40171
+ "not_found",
40172
+ `No note found in the database for ID "${noteId3}".`
40173
+ );
40174
+ if (note.locked)
40175
+ throw new AudioTranscriptError(
40176
+ "locked",
40177
+ "This note is password-protected; its transcripts are encrypted and cannot be read."
40178
+ );
40179
+ const rows = JSON.parse(rowsLine || "[]");
40180
+ let order;
40181
+ try {
40182
+ if (/^[0-9A-F]+$/i.test(bodyLine.trim()))
40183
+ order = bodyAttachmentOrder(Buffer.from(bodyLine.trim(), "hex"));
40184
+ } catch {
40185
+ order = void 0;
40186
+ }
40187
+ const ordered = order ? order.flatMap((id2) => rows.filter((row) => row.identifier === id2)) : [...rows].sort((a, b) => a.pk - b.pk);
40188
+ const attachments = ordered.map((row) => describeRow(row, store, options, maxSegments));
40189
+ return { id: noteId3, attachments, bodyOrder: Boolean(order), truncated: false };
40190
+ }
40191
+ function describeRow(row, store, options, maxSegments) {
40192
+ const childDurations = (row.fragments ?? []).map((f) => f.duration).filter((d) => typeof d === "number" && d > 0);
40193
+ const durationSeconds = typeof row.duration === "number" && row.duration > 0 ? row.duration : childDurations.length ? childDurations.reduce((a, b) => a + b, 0) : void 0;
40194
+ const base = {
40195
+ attachmentId: `x-coredata://${store}/ICAttachment/p${row.pk}`,
40196
+ identifier: row.identifier,
40197
+ typeUti: row.uti,
40198
+ status: "none",
40199
+ ...durationSeconds !== void 0 ? { durationSeconds } : {},
40200
+ ...typeof row.needsTranscription === "number" ? { needsTranscription: row.needsTranscription === 1 } : {}
40201
+ };
40202
+ if (!row.data) return base;
40203
+ let recording;
40204
+ try {
40205
+ recording = parseAudioRecording(Buffer.from(row.data, "hex"));
40206
+ } catch (error2) {
40207
+ return {
40208
+ ...base,
40209
+ status: "undecodable",
40210
+ reason: error2 instanceof Error ? error2.message : String(error2)
40211
+ };
40212
+ }
40213
+ const multi = recording.fragments.length > 1;
40214
+ const all = recording.fragments.flatMap(
40215
+ (fragment, i) => fragment.segments.map((s) => multi ? { ...s, fragment: i } : s)
40216
+ );
40217
+ const text = recording.fragments.map((f) => joinSegments(f.segments)).filter(Boolean).join("\n\n");
40218
+ const speakers = [...new Set(all.flatMap((s) => s.speaker ? [s.speaker] : []))];
40219
+ return {
40220
+ ...base,
40221
+ status: all.length ? "ok" : "none",
40222
+ fragmentCount: recording.fragments.length,
40223
+ wordCount: all.length,
40224
+ ...text ? { text } : {},
40225
+ ...speakers.length ? { speakers } : {},
40226
+ ...recording.summary ? { summary: recording.summary } : {},
40227
+ ...recording.topLineSummary ? { topLineSummary: recording.topLineSummary } : {},
40228
+ ...options.includeSegments && all.length ? {
40229
+ segments: all.slice(0, maxSegments),
40230
+ ...all.length > maxSegments ? { segmentsTruncated: true } : {}
40231
+ } : {}
40232
+ };
40233
+ }
40234
+ function fitTranscriptsToBudget(result, maxBytes, measure) {
40235
+ if (measure(result) <= maxBytes) return result;
40236
+ let next = {
40237
+ ...result,
40238
+ truncated: true,
40239
+ attachments: result.attachments.map((a) => {
40240
+ if (!a.segments) return a;
40241
+ const rest = { ...a, segmentsTruncated: true };
40242
+ delete rest.segments;
40243
+ return rest;
40244
+ })
40245
+ };
40246
+ for (let share = 0.5; measure(next) > maxBytes && share > 1e-4; share /= 2) {
40247
+ next = {
40248
+ ...next,
40249
+ attachments: next.attachments.map((a) => {
40250
+ const original = result.attachments.find((o) => o.attachmentId === a.attachmentId);
40251
+ if (!original?.text) return a;
40252
+ const keep = Math.floor(original.text.length * share);
40253
+ return { ...a, text: original.text.slice(0, keep), textTruncated: true };
40254
+ })
40255
+ };
40256
+ }
40257
+ return next;
40258
+ }
40259
+ var clock = (seconds) => {
40260
+ const total = Math.round(seconds);
40261
+ const h = Math.floor(total / 3600);
40262
+ const m = Math.floor(total % 3600 / 60);
40263
+ const s = total % 60;
40264
+ const mm = h ? String(m).padStart(2, "0") : String(m);
40265
+ return `${h ? `${h}:` : ""}${mm}:${String(s).padStart(2, "0")}`;
40266
+ };
40267
+ function formatTranscriptsText(result) {
40268
+ const count = result.attachments.length;
40269
+ if (count === 0) return `No audio attachments found in note ${result.id}.`;
40270
+ const lines = [`${count} audio attachment${count === 1 ? "" : "s"} in note ${result.id}:`];
40271
+ result.attachments.forEach((a, i) => {
40272
+ const facts = [
40273
+ `status ${a.status}`,
40274
+ ...a.durationSeconds !== void 0 ? [clock(a.durationSeconds)] : [],
40275
+ ...a.wordCount !== void 0 ? [`${a.wordCount} words`] : [],
40276
+ ...a.fragmentCount && a.fragmentCount > 1 ? [`${a.fragmentCount} fragments`] : [],
40277
+ ...a.speakers?.length ? [`${a.speakers.length} speakers`] : [],
40278
+ ...a.reason ? [`reason: ${a.reason}`] : []
40279
+ ];
40280
+ lines.push("", `[${i + 1}] ${a.attachmentId} (${facts.join(", ")})`);
40281
+ if (a.summary) lines.push(`Summary: ${a.summary}`);
40282
+ if (a.text) lines.push(a.textTruncated ? `${a.text} [truncated]` : a.text);
40283
+ });
40284
+ if (result.truncated)
40285
+ lines.push(
40286
+ "",
40287
+ "Some segments or text were left out to stay under the response size limit (APPLE_NOTES_MCP_EXPORT_MAX_BYTES)."
40288
+ );
40289
+ return lines.join("\n");
40290
+ }
40291
+
40292
+ // src/utils/noteTables.ts
40293
+ import { gunzipSync as gunzipSync4 } from "node:zlib";
40294
+ var sub2 = (f, n) => {
39907
40295
  const value = embeddedMessage(getField(f, n));
39908
40296
  if (!value) throw new Error(`Missing table field ${n}`);
39909
40297
  return value;
@@ -39913,7 +40301,7 @@ var num = (f, n) => {
39913
40301
  if (v === void 0) throw new Error(`Missing table index ${n}`);
39914
40302
  return v;
39915
40303
  };
39916
- var many = (f, n) => getFields(f, n).map((v) => {
40304
+ var many2 = (f, n) => getFields(f, n).map((v) => {
39917
40305
  const m = embeddedMessage(v);
39918
40306
  if (!m) throw new Error("Invalid table entry");
39919
40307
  return m;
@@ -39930,37 +40318,37 @@ function parseNoteTableCells(compressed) {
39930
40318
  return decodeTable(compressed, false);
39931
40319
  }
39932
40320
  function decodeTable(compressed, strict) {
39933
- const root = decodeMessage(gunzipSync3(compressed, { maxOutputLength: 16 * 1024 * 1024 }));
39934
- const data = sub(sub(root, 2), 3), entries = many(data, 3);
40321
+ const root = decodeMessage(gunzipSync4(compressed, { maxOutputLength: 16 * 1024 * 1024 }));
40322
+ const data = sub2(sub2(root, 2), 3), entries = many2(data, 3);
39935
40323
  if (entries.length > 1e5) throw new Error("Table too large");
39936
40324
  const keys = getFields(data, 4).map(stringValue), types = getFields(data, 5).map(stringValue), uuids = getFields(data, 6).map(hex);
39937
40325
  const entry = (index) => {
39938
40326
  if (!entries[index]) throw new Error("Invalid table reference");
39939
40327
  return entries[index];
39940
40328
  };
39941
- const uuidIndex = (index) => num(sub(many(sub(entry(index), 13), 3)[0], 2), 2);
40329
+ const uuidIndex = (index) => num(sub2(many2(sub2(entry(index), 13), 3)[0], 2), 2);
39942
40330
  const roots = entries.filter((e) => {
39943
40331
  const map = embeddedMessage(getField(e, 13));
39944
40332
  return map && types[num(map, 1)] === "com.apple.notes.ICTable";
39945
40333
  });
39946
40334
  if (roots.length !== 1) throw new Error("Ambiguous native table root");
39947
40335
  const refs = new Map(
39948
- 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)])
40336
+ many2(sub2(roots[0], 13), 3).filter((m) => ["crRows", "crColumns", "cellColumns"].includes(keys[num(m, 1)] || "")).map((m) => [keys[num(m, 1)], num(sub2(m, 2), 6)])
39949
40337
  );
39950
40338
  const ordered = (key) => {
39951
40339
  const ref = refs.get(key);
39952
40340
  if (ref === void 0) throw new Error("Missing table dimension");
39953
- const ordering = sub(sub(entry(ref), 16), 1), array2 = sub(ordering, 1);
39954
- const ids = many(array2, 2).map((a) => hex(getField(a, 2)));
40341
+ const ordering = sub2(sub2(entry(ref), 16), 1), array2 = sub2(ordering, 1);
40342
+ const ids = many2(array2, 2).map((a) => hex(getField(a, 2)));
39955
40343
  const map = /* @__PURE__ */ new Map();
39956
40344
  ids.forEach((id2, i) => {
39957
40345
  const index = uuids.indexOf(id2);
39958
40346
  if (index < 0) throw new Error("Missing dimension UUID");
39959
40347
  map.set(index, i);
39960
40348
  });
39961
- const aliases = many(sub(ordering, 2), 1).map((pair) => [
39962
- uuidIndex(num(sub(pair, 1), 6)),
39963
- uuidIndex(num(sub(pair, 2), 6))
40349
+ const aliases = many2(sub2(ordering, 2), 1).map((pair) => [
40350
+ uuidIndex(num(sub2(pair, 1), 6)),
40351
+ uuidIndex(num(sub2(pair, 2), 6))
39964
40352
  ]);
39965
40353
  for (let pass = 0; pass < aliases.length + 1; pass++) {
39966
40354
  let changed = false;
@@ -39980,13 +40368,13 @@ function decodeTable(compressed, strict) {
39980
40368
  const incomplete = [];
39981
40369
  const cellRef = refs.get("cellColumns");
39982
40370
  if (cellRef === void 0) throw new Error("Missing table cells");
39983
- for (const column of many(sub(entry(cellRef), 6), 1)) {
39984
- const ci = columns.map.get(uuidIndex(num(sub(column, 1), 6)));
39985
- const cells = entry(num(sub(column, 2), 6));
39986
- for (const row of many(sub(cells, 6), 1)) {
39987
- const ri = rows.map.get(uuidIndex(num(sub(row, 1), 6)));
40371
+ for (const column of many2(sub2(entry(cellRef), 6), 1)) {
40372
+ const ci = columns.map.get(uuidIndex(num(sub2(column, 1), 6)));
40373
+ const cells = entry(num(sub2(column, 2), 6));
40374
+ for (const row of many2(sub2(cells, 6), 1)) {
40375
+ const ri = rows.map.get(uuidIndex(num(sub2(row, 1), 6)));
39988
40376
  if (ri === void 0 || ci === void 0) continue;
39989
- const note = sub(entry(num(sub(row, 2), 6)), 10);
40377
+ const note = sub2(entry(num(sub2(row, 2), 6)), 10);
39990
40378
  const text = stringValue(getField(note, 2));
39991
40379
  if (text === void 0 || text.includes("\uFFFC")) {
39992
40380
  if (strict) throw new Error("Embedded or unsupported table cell");
@@ -40003,8 +40391,8 @@ function decodeTable(compressed, strict) {
40003
40391
  }
40004
40392
  const rtl = entries.some((e) => {
40005
40393
  const map = embeddedMessage(getField(e, 13));
40006
- return map && many(map, 3).some(
40007
- (m) => stringValue(getField(sub(m, 2), 4)) === "CRTableColumnDirectionRightToLeft"
40394
+ return map && many2(map, 3).some(
40395
+ (m) => stringValue(getField(sub2(m, 2), 4)) === "CRTableColumnDirectionRightToLeft"
40008
40396
  );
40009
40397
  });
40010
40398
  const width = columns.ids.length;
@@ -40081,11 +40469,11 @@ function collectNoteTables(rich, noteId3) {
40081
40469
  }
40082
40470
 
40083
40471
  // src/utils/smartFolders.ts
40084
- import { execFileSync as execFileSync4 } from "child_process";
40472
+ import { execFileSync as execFileSync5 } from "child_process";
40085
40473
  import * as fs2 from "fs";
40086
40474
  import * as os2 from "os";
40087
40475
  import * as path2 from "path";
40088
- var NOTES_DB_PATH2 = path2.join(
40476
+ var NOTES_DB_PATH3 = path2.join(
40089
40477
  os2.homedir(),
40090
40478
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
40091
40479
  );
@@ -40376,7 +40764,7 @@ WHERE Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICHashtag')
40376
40764
  AND ZSTANDARDIZEDCONTENT IS NOT NULL
40377
40765
  AND COALESCE(ZMARKEDFORDELETION, 0) = 0;
40378
40766
  COMMIT;`;
40379
- var REQUIRED_COLUMNS = [
40767
+ var REQUIRED_COLUMNS2 = [
40380
40768
  "ZIDENTIFIER",
40381
40769
  "ZTITLE2",
40382
40770
  "ZFOLDERTYPE",
@@ -40430,21 +40818,21 @@ function buildSmartFolders(output) {
40430
40818
  (a, b) => (a.account ?? "").localeCompare(b.account ?? "") || (a.name ?? "").localeCompare(b.name ?? "") || a.id.localeCompare(b.id)
40431
40819
  );
40432
40820
  }
40433
- function runSqlite(dbPath2, sql) {
40434
- return execFileSync4("sqlite3", ["-readonly", dbPath2, sql], {
40821
+ function runSqlite2(dbPath2, sql) {
40822
+ return execFileSync5("sqlite3", ["-readonly", dbPath2, sql], {
40435
40823
  encoding: "utf8",
40436
40824
  timeout: 5e3,
40437
40825
  maxBuffer: 16 * 1024 * 1024,
40438
40826
  stdio: ["pipe", "pipe", "pipe"]
40439
40827
  });
40440
40828
  }
40441
- function readSmartFolders(dbPath2 = NOTES_DB_PATH2) {
40829
+ function readSmartFolders(dbPath2 = NOTES_DB_PATH3) {
40442
40830
  if (!fs2.existsSync(dbPath2)) return { folders: null, error: "no_fda", message: FDA_MESSAGE };
40443
40831
  try {
40444
40832
  const columns = new Set(
40445
- runSqlite(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").trim().split("\n")
40833
+ runSqlite2(dbPath2, "SELECT name FROM pragma_table_info('ZICCLOUDSYNCINGOBJECT');").trim().split("\n")
40446
40834
  );
40447
- const missing = REQUIRED_COLUMNS.filter((column) => !columns.has(column));
40835
+ const missing = REQUIRED_COLUMNS2.filter((column) => !columns.has(column));
40448
40836
  if (missing.length) {
40449
40837
  return {
40450
40838
  folders: null,
@@ -40452,7 +40840,7 @@ function readSmartFolders(dbPath2 = NOTES_DB_PATH2) {
40452
40840
  message: `This Notes database does not have the columns smart folders need (${missing.join(", ")}).`
40453
40841
  };
40454
40842
  }
40455
- return { folders: buildSmartFolders(runSqlite(dbPath2, SMART_FOLDERS_SQL)) };
40843
+ return { folders: buildSmartFolders(runSqlite2(dbPath2, SMART_FOLDERS_SQL)) };
40456
40844
  } catch (error2) {
40457
40845
  const message = error2 instanceof Error ? error2.message : String(error2);
40458
40846
  if (message.includes("authorization denied") || message.includes("unable to open database")) {
@@ -40465,20 +40853,24 @@ function readSmartFolders(dbPath2 = NOTES_DB_PATH2) {
40465
40853
 
40466
40854
  // src/utils/attachmentFs.ts
40467
40855
  import {
40856
+ closeSync,
40857
+ constants,
40468
40858
  existsSync as existsSync3,
40859
+ fstatSync,
40469
40860
  lstatSync,
40470
40861
  mkdirSync,
40471
40862
  mkdtempSync,
40863
+ openSync,
40472
40864
  readFileSync,
40473
40865
  realpathSync,
40474
40866
  rmSync,
40475
40867
  statSync
40476
40868
  } from "fs";
40477
- import { dirname, isAbsolute, join as join4, relative, resolve, sep } from "path";
40478
- import { homedir as homedir4, tmpdir } from "os";
40869
+ import { dirname, isAbsolute, join as join5, relative, resolve, sep } from "path";
40870
+ import { homedir as homedir5, tmpdir } from "os";
40479
40871
  function allowedSaveRoots() {
40480
40872
  return [
40481
- resolve(homedir4()),
40873
+ resolve(homedir5()),
40482
40874
  resolve(tmpdir()),
40483
40875
  "/Volumes",
40484
40876
  "/private/var/folders",
@@ -40552,7 +40944,7 @@ function assertSafeSavePath(p, roots = allowedSaveRoots()) {
40552
40944
  if (suffix.split(sep).includes("..")) {
40553
40945
  throw new Error(`Refusing to write outside allowed locations (home, temp, /Volumes): "${abs}"`);
40554
40946
  }
40555
- const canonicalDest = suffix ? join4(canonicalAncestor, suffix) : canonicalAncestor;
40947
+ const canonicalDest = suffix ? join5(canonicalAncestor, suffix) : canonicalAncestor;
40556
40948
  const allowed = canonicalRoots(roots);
40557
40949
  if (!isWithinRoots(canonicalAncestor, allowed) || !isWithinRoots(canonicalDest, allowed)) {
40558
40950
  throw new Error(
@@ -40592,6 +40984,48 @@ function fileSize(p) {
40592
40984
  function makeTempDir() {
40593
40985
  return mkdtempSync(resolve(tmpdir(), "apple-notes-att-"));
40594
40986
  }
40987
+ function readAllowedTextFile(p, maxBytes, roots = allowedSaveRoots()) {
40988
+ if (!p || !p.trim()) throw new Error("A content file path is required.");
40989
+ if (!isAbsolute(p)) throw new Error(`Content file path must be absolute: "${p}"`);
40990
+ const abs = resolve(p);
40991
+ if (!isWithinRoots(abs, roots))
40992
+ throw new Error(`Refusing to read outside allowed locations (home, temp, /Volumes): "${abs}"`);
40993
+ let canonical;
40994
+ try {
40995
+ canonical = canonicalize(abs);
40996
+ } catch {
40997
+ throw new Error(`Content file does not exist or cannot be resolved: "${abs}"`);
40998
+ }
40999
+ if (!isWithinRoots(canonical, canonicalRoots(roots)))
41000
+ throw new Error(
41001
+ `Refusing to read outside allowed locations (home, temp, /Volumes): "${abs}" resolves to "${canonical}".`
41002
+ );
41003
+ let descriptor;
41004
+ try {
41005
+ descriptor = openSync(abs, constants.O_RDONLY | constants.O_NOFOLLOW);
41006
+ } catch (error2) {
41007
+ if (error2.code === "ELOOP")
41008
+ throw new Error(`Refusing to read the symbolic link "${abs}".`);
41009
+ throw error2;
41010
+ }
41011
+ try {
41012
+ const stat = fstatSync(descriptor);
41013
+ if (!stat.isFile()) throw new Error(`Content file is not a regular file: "${abs}"`);
41014
+ if (stat.size === 0) throw new Error(`Content file is empty: "${abs}"`);
41015
+ if (stat.size > maxBytes)
41016
+ throw new Error(`Content file is ${stat.size} bytes, over the ${maxBytes}-byte limit.`);
41017
+ const bytes = readFileSync(descriptor);
41018
+ if (bytes.length !== stat.size)
41019
+ throw new Error("Content file changed while it was being read; try again");
41020
+ try {
41021
+ return new TextDecoder("utf-8", { fatal: true }).decode(bytes).replace(/^\uFEFF/, "");
41022
+ } catch {
41023
+ throw new Error(`Content file is not valid UTF-8 text: "${abs}"`);
41024
+ }
41025
+ } finally {
41026
+ closeSync(descriptor);
41027
+ }
41028
+ }
40595
41029
  function cleanupTempDir(dir) {
40596
41030
  try {
40597
41031
  if (existsSync3(dir)) rmSync(dir, { recursive: true, force: true });
@@ -40602,8 +41036,8 @@ function cleanupTempDir(dir) {
40602
41036
  // src/services/appleNotesManager.ts
40603
41037
  var import_turndown = __toESM(require_turndown_cjs(), 1);
40604
41038
  import { existsSync as existsSync4 } from "fs";
40605
- import { homedir as homedir5 } from "os";
40606
- import { join as join5 } from "path";
41039
+ import { homedir as homedir6 } from "os";
41040
+ import { join as join6 } from "path";
40607
41041
  var FIELD_SEP = "";
40608
41042
  var RECORD_SEP = "";
40609
41043
  var AS_FIELD_SEP = "(character id 31)";
@@ -40810,10 +41244,10 @@ function buildAccountResolution(account) {
40810
41244
  if ${AS_ACCOUNT_REF} is missing value then error "${ACCOUNT_RESOLUTION_ERROR} Account \\"${safeAccount}\\" not found"
40811
41245
  `;
40812
41246
  }
40813
- function buildAccountScopedScript(scope, command) {
41247
+ function buildAccountScopedScript(scope2, command) {
40814
41248
  return `
40815
41249
  tell application "Notes"
40816
- ${buildAccountResolution(scope.account)}
41250
+ ${buildAccountResolution(scope2.account)}
40817
41251
  tell ${AS_ACCOUNT_REF}
40818
41252
  ${command}
40819
41253
  end tell
@@ -40831,7 +41265,7 @@ function getNoteLinkFromDB(coreDataId2) {
40831
41265
  const match = coreDataId2.match(/\/p(\d+)$/);
40832
41266
  if (!match) return null;
40833
41267
  const pk = parseInt(match[1], 10);
40834
- const dbPath2 = join5(homedir5(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
41268
+ const dbPath2 = join6(homedir6(), "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite");
40835
41269
  if (!existsSync4(dbPath2)) return null;
40836
41270
  try {
40837
41271
  const { DatabaseSync } = __require("node:sqlite");
@@ -41393,11 +41827,20 @@ var AppleNotesManager = class {
41393
41827
  const safeExpectedBody = escapeHtmlForAppleScript(expectedBody);
41394
41828
  const script = buildAppLevelScript(`
41395
41829
  set noteRef to note id "${safeId}"
41830
+ set originalFolder to missing value
41831
+ try
41832
+ set originalFolder to container of noteRef
41833
+ end try
41396
41834
  set currentBody to body of noteRef
41397
41835
  considering case
41398
41836
  if currentBody is not "${safeExpectedBody}" and currentBody is not "${safeExpectedBody}" & linefeed then return "SAFETY_CONFLICT"
41399
41837
  delete noteRef
41400
41838
  end considering
41839
+ if originalFolder is not missing value then
41840
+ try
41841
+ if (id of notes of originalFolder) contains "${safeId}" then return "SAFETY_NOT_DELETED"
41842
+ end try
41843
+ end if
41401
41844
  return "SAFETY_DELETED"
41402
41845
  `);
41403
41846
  const result = executeMutationAppleScript(script);
@@ -41407,6 +41850,7 @@ var AppleNotesManager = class {
41407
41850
  }
41408
41851
  const status = result.output.trim();
41409
41852
  if (status === "SAFETY_CONFLICT") return { status: "conflict" };
41853
+ if (status === "SAFETY_NOT_DELETED") return { status: "not-deleted" };
41410
41854
  return status === "SAFETY_DELETED" ? { status: "deleted" } : { status: "failed" };
41411
41855
  }
41412
41856
  /**
@@ -43140,18 +43584,31 @@ var AppleNotesManager = class {
43140
43584
  }
43141
43585
  return markdown;
43142
43586
  }
43587
+ /**
43588
+ * Reads the transcripts Notes has stored for a note's top-level audio
43589
+ * recordings, one entry per attachment in body order. Read-only: queries the
43590
+ * NoteStore database with `sqlite3 -readonly` and needs Full Disk Access.
43591
+ *
43592
+ * @param id - CoreData URL identifier for the note
43593
+ * @param options - word-level segment inclusion and cap
43594
+ * @throws AudioTranscriptError for an invalid id, a missing or locked note,
43595
+ * missing Full Disk Access, or a database read failure
43596
+ */
43597
+ getAudioTranscripts(id2, options = {}) {
43598
+ return readAudioTranscripts(id2, options);
43599
+ }
43143
43600
  };
43144
43601
 
43145
43602
  // src/utils/syncDetection.ts
43146
- import { execFileSync as execFileSync5 } from "child_process";
43603
+ import { execFileSync as execFileSync6 } from "child_process";
43147
43604
  import * as fs3 from "fs";
43148
43605
  import * as path3 from "path";
43149
43606
  import * as os3 from "os";
43150
- var NOTES_DB_PATH3 = path3.join(
43607
+ var NOTES_DB_PATH4 = path3.join(
43151
43608
  os3.homedir(),
43152
43609
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
43153
43610
  );
43154
- var WAL_PATH = `${NOTES_DB_PATH3}-wal`;
43611
+ var WAL_PATH = `${NOTES_DB_PATH4}-wal`;
43155
43612
  var RECENT_ACTIVITY_THRESHOLD_SECONDS = 5;
43156
43613
  var SYNC_STATUS_CACHE_TTL_MS = 2e3;
43157
43614
  var cachedSyncStatus = null;
@@ -43167,7 +43624,7 @@ function getSyncStatus(useCache = true) {
43167
43624
  recentActivity: false
43168
43625
  };
43169
43626
  try {
43170
- if (!fs3.existsSync(NOTES_DB_PATH3)) {
43627
+ if (!fs3.existsSync(NOTES_DB_PATH4)) {
43171
43628
  status.error = "Notes database not found";
43172
43629
  cachedSyncStatus = status;
43173
43630
  cacheTimestamp = Date.now();
@@ -43188,9 +43645,9 @@ function getSyncStatus(useCache = true) {
43188
43645
  WHERE object.ZCLOUDSTATE = state.Z_PK
43189
43646
  );
43190
43647
  `;
43191
- const result = execFileSync5(
43648
+ const result = execFileSync6(
43192
43649
  "sqlite3",
43193
- ["-readonly", NOTES_DB_PATH3, query.replace(/\n/g, " ")],
43650
+ ["-readonly", NOTES_DB_PATH4, query.replace(/\n/g, " ")],
43194
43651
  {
43195
43652
  encoding: "utf8",
43196
43653
  timeout: 5e3,
@@ -43247,11 +43704,11 @@ function withSyncAwarenessSync(operation, fn) {
43247
43704
  }
43248
43705
 
43249
43706
  // src/utils/noteMetadata.ts
43250
- import { execFileSync as execFileSync6 } from "child_process";
43707
+ import { execFileSync as execFileSync7 } from "child_process";
43251
43708
  import * as fs4 from "fs";
43252
43709
  import * as path4 from "path";
43253
43710
  import * as os4 from "os";
43254
- var NOTES_DB_PATH4 = path4.join(
43711
+ var NOTES_DB_PATH5 = path4.join(
43255
43712
  os4.homedir(),
43256
43713
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
43257
43714
  );
@@ -43267,15 +43724,15 @@ var COLUMN_MAP = [
43267
43724
  { key: "widgetSnippet", column: "ZWIDGETSNIPPET", type: "text" },
43268
43725
  { key: "smartFolderQuery", column: "ZSMARTFOLDERQUERYJSON", type: "text" }
43269
43726
  ];
43270
- function runSqlite2(query) {
43271
- return execFileSync6("sqlite3", ["-readonly", NOTES_DB_PATH4, query], {
43727
+ function runSqlite3(query) {
43728
+ return execFileSync7("sqlite3", ["-readonly", NOTES_DB_PATH5, query], {
43272
43729
  encoding: "utf8",
43273
43730
  timeout: 5e3,
43274
43731
  stdio: ["pipe", "pipe", "pipe"]
43275
43732
  }).trim();
43276
43733
  }
43277
43734
  function presentColumns() {
43278
- const out = runSqlite2("PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);");
43735
+ const out = runSqlite3("PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);");
43279
43736
  const cols = /* @__PURE__ */ new Set();
43280
43737
  for (const line of out.split("\n")) {
43281
43738
  const name = line.split("|")[1];
@@ -43293,7 +43750,7 @@ function getNoteMetadata(noteId3) {
43293
43750
  };
43294
43751
  }
43295
43752
  const pk = pkMatch[1];
43296
- if (!fs4.existsSync(NOTES_DB_PATH4)) {
43753
+ if (!fs4.existsSync(NOTES_DB_PATH5)) {
43297
43754
  return { metadata: null, error: "no_fda", message: FDA_MESSAGE2 };
43298
43755
  }
43299
43756
  try {
@@ -43303,7 +43760,7 @@ function getNoteMetadata(noteId3) {
43303
43760
  return { metadata: {} };
43304
43761
  }
43305
43762
  const pairs = selected.map((c) => `'${c.key}', ${c.column}`).join(", ");
43306
- const row = runSqlite2(
43763
+ const row = runSqlite3(
43307
43764
  `SELECT json_object(${pairs}) FROM ZICCLOUDSYNCINGOBJECT WHERE Z_PK = ${pk};`
43308
43765
  );
43309
43766
  if (!row) {
@@ -43336,11 +43793,11 @@ function getNoteMetadata(noteId3) {
43336
43793
  }
43337
43794
 
43338
43795
  // src/utils/noteIdentifiers.ts
43339
- import { execFileSync as execFileSync7 } from "child_process";
43796
+ import { execFileSync as execFileSync8 } from "child_process";
43340
43797
  import * as fs5 from "fs";
43341
43798
  import * as os5 from "os";
43342
43799
  import * as path5 from "path";
43343
- var NOTES_DB_PATH5 = path5.join(
43800
+ var NOTES_DB_PATH6 = path5.join(
43344
43801
  os5.homedir(),
43345
43802
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
43346
43803
  );
@@ -43418,7 +43875,7 @@ function runJsonQuery(sql, dbPath2) {
43418
43875
  if (!fs5.existsSync(dbPath2)) throw new IdentifierResolutionError("no_fda", NO_FDA_MESSAGE);
43419
43876
  let out;
43420
43877
  try {
43421
- out = execFileSync7("sqlite3", ["-readonly", dbPath2, sql], {
43878
+ out = execFileSync8("sqlite3", ["-readonly", dbPath2, sql], {
43422
43879
  encoding: "utf8",
43423
43880
  timeout: 5e3,
43424
43881
  stdio: ["pipe", "pipe", "pipe"]
@@ -43438,7 +43895,7 @@ function runJsonQuery(sql, dbPath2) {
43438
43895
  function coreDataId(store, entity2, pk) {
43439
43896
  return `x-coredata://${store}/${entity2}/p${pk}`;
43440
43897
  }
43441
- function resolveIdentifiers(values, entity2, dbPath2 = NOTES_DB_PATH5) {
43898
+ function resolveIdentifiers(values, entity2, dbPath2 = NOTES_DB_PATH6) {
43442
43899
  const resolved = /* @__PURE__ */ new Map();
43443
43900
  const keys = [];
43444
43901
  const uuids = [];
@@ -43484,7 +43941,7 @@ function resolveIdentifiers(values, entity2, dbPath2 = NOTES_DB_PATH5) {
43484
43941
  return resolved;
43485
43942
  }
43486
43943
  var COREDATA_PARTS = /^x-coredata:\/\/([0-9A-Fa-f-]+)\/(ICNote|ICFolder|ICAccount)\/p(\d{1,18})$/;
43487
- function lookupStableIdentifiers(ids, entity2, dbPath2 = NOTES_DB_PATH5) {
43944
+ function lookupStableIdentifiers(ids, entity2, dbPath2 = NOTES_DB_PATH6) {
43488
43945
  const result = /* @__PURE__ */ new Map();
43489
43946
  const wanted = /* @__PURE__ */ new Map();
43490
43947
  for (const id2 of ids) {
@@ -43520,7 +43977,7 @@ function lookupStableIdentifiers(ids, entity2, dbPath2 = NOTES_DB_PATH5) {
43520
43977
  }
43521
43978
  return result;
43522
43979
  }
43523
- function withStableIdentifiers(items, entity2, dbPath2 = NOTES_DB_PATH5) {
43980
+ function withStableIdentifiers(items, entity2, dbPath2 = NOTES_DB_PATH6) {
43524
43981
  const ids = items.map((item) => item.id).filter((id2) => typeof id2 === "string");
43525
43982
  if (ids.length === 0) return items;
43526
43983
  const found = lookupStableIdentifiers(ids, entity2, dbPath2);
@@ -44108,12 +44565,12 @@ function positiveTextTerms(node, negated = false) {
44108
44565
  }
44109
44566
 
44110
44567
  // src/utils/noteQueryStore.ts
44111
- import { execFileSync as execFileSync8 } from "child_process";
44568
+ import { execFileSync as execFileSync9 } from "child_process";
44112
44569
  import * as fs6 from "fs";
44113
44570
  import * as os6 from "os";
44114
44571
  import * as path6 from "path";
44115
- import { gunzipSync as gunzipSync4 } from "zlib";
44116
- var NOTES_DB_PATH6 = path6.join(
44572
+ import { gunzipSync as gunzipSync5 } from "zlib";
44573
+ var NOTES_DB_PATH7 = path6.join(
44117
44574
  os6.homedir(),
44118
44575
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
44119
44576
  );
@@ -44201,13 +44658,13 @@ function countWords(text) {
44201
44658
  }
44202
44659
  return count;
44203
44660
  }
44204
- var REQUIRED_COLUMNS2 = ["Z_PK", "Z_ENT", "ZTITLE1", "ZFOLDER", "ZMODIFICATIONDATE1"];
44661
+ var REQUIRED_COLUMNS3 = ["Z_PK", "Z_ENT", "ZTITLE1", "ZFOLDER", "ZMODIFICATIONDATE1"];
44205
44662
  function col(available, alias, name) {
44206
44663
  return available.has(name) ? `${alias}.${name}` : "NULL";
44207
44664
  }
44208
44665
  var entity = (name) => `(SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME='${name}')`;
44209
44666
  function buildScanSql(available, options) {
44210
- const missing = REQUIRED_COLUMNS2.filter((c) => !available.has(c));
44667
+ const missing = REQUIRED_COLUMNS3.filter((c) => !available.has(c));
44211
44668
  if (missing.length) {
44212
44669
  throw new NoteQueryStoreError(
44213
44670
  `This macOS version's Notes database lacks columns query-notes needs (${missing.join(", ")}).`,
@@ -44248,8 +44705,8 @@ function buildScanSql(available, options) {
44248
44705
  "COMMIT;"
44249
44706
  ].join(" ");
44250
44707
  }
44251
- function runSqlite3(dbPath2, query) {
44252
- return execFileSync8("sqlite3", ["-readonly", dbPath2, query], {
44708
+ function runSqlite4(dbPath2, query) {
44709
+ return execFileSync9("sqlite3", ["-readonly", dbPath2, query], {
44253
44710
  encoding: "utf8",
44254
44711
  timeout: 3e4,
44255
44712
  maxBuffer: 512 * 1024 * 1024,
@@ -44258,7 +44715,7 @@ function runSqlite3(dbPath2, query) {
44258
44715
  }
44259
44716
  function presentColumns2(dbPath2) {
44260
44717
  const cols = /* @__PURE__ */ new Set();
44261
- for (const line of runSqlite3(dbPath2, "PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);").split("\n")) {
44718
+ for (const line of runSqlite4(dbPath2, "PRAGMA table_info(ZICCLOUDSYNCINGOBJECT);").split("\n")) {
44262
44719
  const name = line.split("|")[1];
44263
44720
  if (name) cols.add(name);
44264
44721
  }
@@ -44313,12 +44770,12 @@ function queryNotes(expression, options = {}) {
44313
44770
  const includeDeleted = options.includeDeleted ?? false;
44314
44771
  const withBodies = needsContent(ast);
44315
44772
  const withTags = needsTags(ast);
44316
- const dbPath2 = options.dbPath ?? NOTES_DB_PATH6;
44773
+ const dbPath2 = options.dbPath ?? NOTES_DB_PATH7;
44317
44774
  if (!fs6.existsSync(dbPath2)) throw new NoteQueryStoreError(QUERY_FDA_MESSAGE, "no_fda");
44318
44775
  let output;
44319
44776
  try {
44320
44777
  const available = presentColumns2(dbPath2);
44321
- output = runSqlite3(
44778
+ output = runSqlite4(
44322
44779
  dbPath2,
44323
44780
  buildScanSql(available, { scanLimit, includeDeleted, withBodies, withTags })
44324
44781
  );
@@ -44363,7 +44820,7 @@ function queryNotes(expression, options = {}) {
44363
44820
  const locked = Boolean(row.locked);
44364
44821
  let decoded;
44365
44822
  let content;
44366
- const decode = () => {
44823
+ const decode2 = () => {
44367
44824
  if (decoded !== void 0) return decoded;
44368
44825
  decoded = null;
44369
44826
  if (locked) return decoded;
@@ -44374,7 +44831,7 @@ function queryNotes(expression, options = {}) {
44374
44831
  try {
44375
44832
  decoded = decodeNoteBody(
44376
44833
  new Uint8Array(
44377
- gunzipSync4(Buffer.from(row.data, "hex"), { maxOutputLength: 32 * 1024 * 1024 })
44834
+ gunzipSync5(Buffer.from(row.data, "hex"), { maxOutputLength: 32 * 1024 * 1024 })
44378
44835
  )
44379
44836
  );
44380
44837
  } catch {
@@ -44394,7 +44851,7 @@ function queryNotes(expression, options = {}) {
44394
44851
  modified: coreDataMs(row.modified),
44395
44852
  content: () => {
44396
44853
  if (content !== void 0) return content;
44397
- const body2 = decode();
44854
+ const body2 = decode2();
44398
44855
  if (!body2) return content = null;
44399
44856
  const text = body2.text;
44400
44857
  const firstBreak = text.indexOf("\n");
@@ -44419,7 +44876,7 @@ function queryNotes(expression, options = {}) {
44419
44876
  if (!evaluateNoteQuery(ast, note)) continue;
44420
44877
  matched++;
44421
44878
  if (hits.length >= limit) continue;
44422
- const body = locked ? null : decode();
44879
+ const body = locked ? null : decode2();
44423
44880
  hits.push({
44424
44881
  id: `x-coredata://${uuid2}/ICNote/p${row.pk}`,
44425
44882
  title: row.title ?? "",
@@ -44449,10 +44906,10 @@ function queryNotes(expression, options = {}) {
44449
44906
  import { spawnSync } from "child_process";
44450
44907
 
44451
44908
  // src/services/nativeTags.ts
44452
- import { execFileSync as execFileSync9 } from "node:child_process";
44909
+ import { execFileSync as execFileSync10 } from "node:child_process";
44453
44910
  import { mkdtempSync as mkdtempSync2, writeFileSync, rmSync as rmSync2 } from "node:fs";
44454
44911
  import { tmpdir as tmpdir2 } from "node:os";
44455
- import { join as join10 } from "node:path";
44912
+ import { join as join11 } from "node:path";
44456
44913
 
44457
44914
  // src/services/shortcutConsent.ts
44458
44915
  function shortcutConsentHint(shortcut) {
@@ -44528,7 +44985,7 @@ function addNativeTags(request, deps) {
44528
44985
  };
44529
44986
  }
44530
44987
  function listInstalledShortcuts() {
44531
- return execFileSync9("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
44988
+ return execFileSync10("/usr/bin/shortcuts", ["list", "--show-identifiers"], {
44532
44989
  encoding: "utf8",
44533
44990
  timeout: 15e3,
44534
44991
  maxBuffer: 1024 * 1024,
@@ -44554,11 +45011,11 @@ function runNativeTagsShortcut(input) {
44554
45011
  const status = nativeTagsStatus();
44555
45012
  if (!status.installed)
44556
45013
  throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
44557
- const directory = mkdtempSync2(join10(tmpdir2(), "apple-notes-native-tags-"));
45014
+ const directory = mkdtempSync2(join11(tmpdir2(), "apple-notes-native-tags-"));
44558
45015
  try {
44559
- const path7 = join10(directory, "request.json");
45016
+ const path7 = join11(directory, "request.json");
44560
45017
  writeFileSync(path7, JSON.stringify(input), { mode: 384 });
44561
- execFileSync9("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path7], {
45018
+ execFileSync10("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path7], {
44562
45019
  encoding: "utf8",
44563
45020
  timeout: 6e4,
44564
45021
  maxBuffer: 1024 * 1024,
@@ -44577,14 +45034,29 @@ function runNativeTagsShortcut(input) {
44577
45034
  }
44578
45035
 
44579
45036
  // src/services/backgroundNotes.ts
44580
- import { execFileSync as execFileSync10 } from "node:child_process";
45037
+ import { execFileSync as execFileSync11 } from "node:child_process";
44581
45038
  import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
44582
45039
  import { tmpdir as tmpdir3 } from "node:os";
44583
- import { join as join11 } from "node:path";
45040
+ import { join as join12 } from "node:path";
44584
45041
 
44585
45042
  // src/utils/appendMarkdown.ts
44586
45043
  var escape2 = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
44587
- function appendMarkdownHtml(markdown) {
45044
+ var TASK_GLYPHS = { open: "\u2610", done: "\u2611" };
45045
+ var TASK_ITEM = /^\[([ xX])\][ \t]+(\S.*)$/;
45046
+ function stripDuplicateTitleHeading(markdown, title) {
45047
+ const normalized2 = markdown.replace(/^\uFEFF/, "");
45048
+ const lines = normalized2.split(/\r?\n/);
45049
+ if (lines[0] !== `# ${title}`) return { content: markdown, stripped: false };
45050
+ const rest = lines.slice(lines.length > 1 && lines[1] === "" ? 2 : 1);
45051
+ return { content: rest.join("\n"), stripped: true };
45052
+ }
45053
+ function countTaskItems(markdown) {
45054
+ return markdown.replace(/\r\n/g, "\n").split("\n").filter((line) => {
45055
+ const item = /^[-+*]\s+(.+)$/.exec(line);
45056
+ return item !== null && TASK_ITEM.test(item[1]);
45057
+ }).length;
45058
+ }
45059
+ function appendMarkdownHtml(markdown, options = {}) {
44588
45060
  if (markdown.includes("\uE000") || markdown.includes("\uE001"))
44589
45061
  throw new Error("Unsupported reserved characters");
44590
45062
  if (Array.from(markdown).some((c) => c.charCodeAt(0) < 32 && !["\n", "\r", " "].includes(c)))
@@ -44623,7 +45095,8 @@ function appendMarkdownHtml(markdown) {
44623
45095
  list = kind;
44624
45096
  html += `<${kind}>`;
44625
45097
  }
44626
- html += `<li>${inline(item[2])}</li>`;
45098
+ const task = options.taskGlyphs && item[1] ? TASK_ITEM.exec(item[2]) : null;
45099
+ html += task ? `<li>${task[1] === " " ? TASK_GLYPHS.open : TASK_GLYPHS.done} ${inline(task[2])}</li>` : `<li>${inline(item[2])}</li>`;
44627
45100
  continue;
44628
45101
  }
44629
45102
  close();
@@ -44769,9 +45242,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
44769
45242
  throw new Error(
44770
45243
  `Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
44771
45244
  );
44772
- const directory = mkdtempSync3(join11(tmpdir3(), "apple-notes-background-"));
45245
+ const directory = mkdtempSync3(join12(tmpdir3(), "apple-notes-background-"));
44773
45246
  try {
44774
- const file = join11(directory, "request.json");
45247
+ const file = join12(directory, "request.json");
44775
45248
  writeFileSync2(
44776
45249
  file,
44777
45250
  JSON.stringify({
@@ -44791,9 +45264,9 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
44791
45264
  { mode: 384 }
44792
45265
  );
44793
45266
  try {
44794
- execFileSync10("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
45267
+ execFileSync11("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", file], {
44795
45268
  encoding: "utf8",
44796
- timeout: 6e4,
45269
+ timeout: callTimeoutMs() ?? 6e4,
44797
45270
  maxBuffer: 1024 * 1024,
44798
45271
  stdio: ["ignore", "pipe", "pipe"]
44799
45272
  });
@@ -44808,9 +45281,9 @@ function backgroundDependencies(manager) {
44808
45281
  return {
44809
45282
  read: (id2) => readBackgroundSnapshot(manager, id2),
44810
45283
  run: runBackgroundShortcut,
44811
- candidates: (title, scope) => manager.listAccounts().flatMap(
45284
+ candidates: (title, scope2) => manager.listAccounts().flatMap(
44812
45285
  (account) => manager.searchNotes(title, false, account.name).filter(
44813
- (note) => note.title === title && !note.passwordProtected && manager.getNotePlaintextById(note.id).includes(scope)
45286
+ (note) => note.title === title && !note.passwordProtected && manager.getNotePlaintextById(note.id).includes(scope2)
44814
45287
  ).map((note) => note.id)
44815
45288
  )
44816
45289
  };
@@ -45083,7 +45556,7 @@ ${request.content}`
45083
45556
  }
45084
45557
 
45085
45558
  // src/services/capabilityMatrix.ts
45086
- import { execFileSync as execFileSync11 } from "node:child_process";
45559
+ import { execFileSync as execFileSync12 } from "node:child_process";
45087
45560
  import { release } from "node:os";
45088
45561
  var MACOS_SHORTCUTS_CLI = "12.0";
45089
45562
  var MACOS_MARKDOWN_IMPORT = "26.0";
@@ -45290,7 +45763,7 @@ function evaluateFeatures(env, features = FEATURES) {
45290
45763
  function readMacOSVersion() {
45291
45764
  if (process.platform !== "darwin") return null;
45292
45765
  try {
45293
- const out = execFileSync11("/usr/bin/sw_vers", ["-productVersion"], {
45766
+ const out = execFileSync12("/usr/bin/sw_vers", ["-productVersion"], {
45294
45767
  encoding: "utf8",
45295
45768
  timeout: 3e3,
45296
45769
  stdio: ["ignore", "pipe", "ignore"]
@@ -45444,12 +45917,12 @@ function formatDoctorReport(r) {
45444
45917
 
45445
45918
  // src/services/fileConfig.ts
45446
45919
  import { existsSync as existsSync9, readFileSync as readFileSync2 } from "fs";
45447
- import { join as join12 } from "path";
45448
- import { homedir as homedir10 } from "os";
45920
+ import { join as join13 } from "path";
45921
+ import { homedir as homedir11 } from "os";
45449
45922
  function fileConfigPath(env = process.env) {
45450
45923
  const override = env.APPLE_NOTES_MCP_CONFIG_FILE;
45451
45924
  if (override && override.trim()) return override.trim();
45452
- return join12(homedir10(), "Library", "Application Support", "apple-notes-mcp", "config.json");
45925
+ return join13(homedir11(), "Library", "Application Support", "apple-notes-mcp", "config.json");
45453
45926
  }
45454
45927
  function loadFileConfig(env = process.env, path7 = fileConfigPath(env)) {
45455
45928
  const applied = [];
@@ -45752,11 +46225,11 @@ function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS) {
45752
46225
  }
45753
46226
 
45754
46227
  // src/utils/noteBlocks.ts
45755
- import { execFileSync as execFileSync12 } from "node:child_process";
46228
+ import { execFileSync as execFileSync13 } from "node:child_process";
45756
46229
  import { existsSync as existsSync10 } from "node:fs";
45757
- import { homedir as homedir11 } from "node:os";
45758
- import { join as join13 } from "node:path";
45759
- import { gunzipSync as gunzipSync5 } from "node:zlib";
46230
+ import { homedir as homedir12 } from "node:os";
46231
+ import { join as join14 } from "node:path";
46232
+ import { gunzipSync as gunzipSync6 } from "node:zlib";
45760
46233
  var NoteBlocksError = class extends Error {
45761
46234
  code;
45762
46235
  constructor(code, message) {
@@ -45805,7 +46278,7 @@ var stringOf = (fields, n) => {
45805
46278
  const b = bytesOf(fields, n);
45806
46279
  return b ? utf8.decode(b) : void 0;
45807
46280
  };
45808
- var sub2 = (fields, n) => {
46281
+ var sub3 = (fields, n) => {
45809
46282
  const b = bytesOf(fields, n);
45810
46283
  return b ? decodeWireFields(b) : void 0;
45811
46284
  };
@@ -45838,7 +46311,7 @@ function decodeParagraph(fields, tally) {
45838
46311
  blockQuote: (varintOf(fields, 8) ?? 0) !== 0
45839
46312
  };
45840
46313
  if (attrs.styleType === -1) attrs.styleType = null;
45841
- const checklist = sub2(fields, 5);
46314
+ const checklist = sub3(fields, 5);
45842
46315
  const checklistId = checklist && bytesOf(checklist, 1);
45843
46316
  if (attrs.styleType === 103 && checklistId)
45844
46317
  attrs.checklist = { id: hex2(checklistId), done: varintOf(checklist, 2) === 1 };
@@ -45848,7 +46321,7 @@ function decodeParagraph(fields, tally) {
45848
46321
  }
45849
46322
  function decodeInline(fields) {
45850
46323
  const inline = {};
45851
- const font = sub2(fields, 3);
46324
+ const font = sub3(fields, 3);
45852
46325
  const hints = font ? varintOf(font, 3) ?? 0 : 0;
45853
46326
  const weight = varintOf(fields, 5) ?? 0;
45854
46327
  if (weight === 1 || weight === 3 || hints & 1) inline.bold = true;
@@ -45858,7 +46331,7 @@ function decodeInline(fields) {
45858
46331
  const baseline = varintOf(fields, 8) ?? 0;
45859
46332
  if (baseline > 0) inline.superscript = true;
45860
46333
  if (baseline < 0) inline.subscript = true;
45861
- const color = sub2(fields, 10);
46334
+ const color = sub3(fields, 10);
45862
46335
  const colorValue = color && colorOf(color);
45863
46336
  if (colorValue) inline.color = colorValue;
45864
46337
  const emphasis = varintOf(fields, 14);
@@ -45880,7 +46353,7 @@ function decodeInline(fields) {
45880
46353
  ...size !== void 0 && Number.isFinite(size) ? { size } : {}
45881
46354
  };
45882
46355
  }
45883
- const attachment = sub2(fields, 12);
46356
+ const attachment = sub3(fields, 12);
45884
46357
  const attachmentId = attachment && stringOf(attachment, 1);
45885
46358
  if (attachmentId)
45886
46359
  inline.attachment = { id: attachmentId, uti: stringOf(attachment, 2) || "unknown" };
@@ -45895,8 +46368,8 @@ function wrap(error2) {
45895
46368
  function decodeNoteBlocks(data) {
45896
46369
  let note;
45897
46370
  try {
45898
- const document = sub2(decodeWireFields(data), 2);
45899
- note = document && sub2(document, 3);
46371
+ const document = sub3(decodeWireFields(data), 2);
46372
+ note = document && sub3(document, 3);
45900
46373
  } catch (error2) {
45901
46374
  wrap(error2);
45902
46375
  }
@@ -45921,7 +46394,7 @@ function decodeNoteBlocks(data) {
45921
46394
  runs.push({
45922
46395
  start: position,
45923
46396
  length,
45924
- paragraph: decodeParagraph(sub2(fields, 2), paragraphTally),
46397
+ paragraph: decodeParagraph(sub3(fields, 2), paragraphTally),
45925
46398
  inline: decodeInline(fields)
45926
46399
  });
45927
46400
  position += length;
@@ -46044,7 +46517,7 @@ function summarize(blocks, attachments) {
46044
46517
  function decodeCompressedNoteBlocks(compressed) {
46045
46518
  let data;
46046
46519
  try {
46047
- data = gunzipSync5(compressed, { maxOutputLength: 32 * 1024 * 1024 });
46520
+ data = gunzipSync6(compressed, { maxOutputLength: 32 * 1024 * 1024 });
46048
46521
  } catch (error2) {
46049
46522
  throw new NoteBlocksError(
46050
46523
  "decompress-failed",
@@ -46098,11 +46571,11 @@ function pageNoteBlocks(doc, { offset = 0, limit = 500, maxBytes = blocksMaxResp
46098
46571
  undecodedFields: doc.undecodedFields
46099
46572
  };
46100
46573
  }
46101
- var NOTES_DB_PATH7 = join13(
46102
- homedir11(),
46574
+ var NOTES_DB_PATH8 = join14(
46575
+ homedir12(),
46103
46576
  "Library/Group Containers/group.com.apple.notes/NoteStore.sqlite"
46104
46577
  );
46105
- function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH7 } = {}) {
46578
+ function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH8 } = {}) {
46106
46579
  const pk = /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p([0-9]{1,18})$/i.exec(id2)?.[1];
46107
46580
  if (!pk)
46108
46581
  throw new NoteBlocksError(
@@ -46114,7 +46587,7 @@ function readNoteBlocks(id2, { dbPath: dbPath2 = NOTES_DB_PATH7 } = {}) {
46114
46587
  const sql = "SELECT json_object('exists', (SELECT count(*) FROM ZICCLOUDSYNCINGOBJECT WHERE Z_PK = @pk AND Z_ENT = (SELECT Z_ENT FROM Z_PRIMARYKEY WHERE Z_NAME = 'ICNote')), 'data', (SELECT hex(ZDATA) FROM ZICNOTEDATA WHERE ZNOTE = @pk), 'encrypted', (SELECT ZCRYPTOINITIALIZATIONVECTOR IS NOT NULL FROM ZICNOTEDATA WHERE ZNOTE = @pk));";
46115
46588
  let output;
46116
46589
  try {
46117
- output = execFileSync12(
46590
+ output = execFileSync13(
46118
46591
  "/usr/bin/sqlite3",
46119
46592
  ["-readonly", "-cmd", ".parameter init", "-cmd", `.parameter set @pk ${pk}`, dbPath2, sql],
46120
46593
  {
@@ -46265,17 +46738,17 @@ function insertLink(request, deps) {
46265
46738
  // src/tools/directOperations.ts
46266
46739
  import { createHash as createHash2 } from "node:crypto";
46267
46740
  import {
46268
- closeSync,
46269
- constants,
46270
- fstatSync,
46741
+ closeSync as closeSync2,
46742
+ constants as constants2,
46743
+ fstatSync as fstatSync2,
46271
46744
  mkdtempSync as mkdtempSync4,
46272
- openSync,
46745
+ openSync as openSync2,
46273
46746
  readFileSync as readFileSync3,
46274
46747
  rmSync as rmSync4,
46275
46748
  writeFileSync as writeFileSync3
46276
46749
  } from "node:fs";
46277
46750
  import { tmpdir as tmpdir4 } from "node:os";
46278
- import { basename, isAbsolute as isAbsolute2, join as join14 } from "node:path";
46751
+ import { basename, isAbsolute as isAbsolute2, join as join15 } from "node:path";
46279
46752
  var noteId = exactIdInput(
46280
46753
  "ICNote",
46281
46754
  /^x-coredata:\/\/[0-9a-f-]+\/ICNote\/p\d+$/i,
@@ -46320,9 +46793,9 @@ function assertExistingContentPreserved(before, after) {
46320
46793
  }
46321
46794
  function localAttachment(path7) {
46322
46795
  if (!isAbsolute2(path7)) throw new Error("An absolute local file path is required");
46323
- const descriptor = openSync(path7, constants.O_RDONLY | constants.O_NOFOLLOW);
46796
+ const descriptor = openSync2(path7, constants2.O_RDONLY | constants2.O_NOFOLLOW);
46324
46797
  try {
46325
- const stat = fstatSync(descriptor);
46798
+ const stat = fstatSync2(descriptor);
46326
46799
  if (!stat.isFile() || stat.size === 0 || stat.size > 64 * 1024 * 1024)
46327
46800
  throw new Error("Attachment must be a nonempty regular file of at most 64 MiB");
46328
46801
  const bytes = readFileSync3(descriptor);
@@ -46330,7 +46803,7 @@ function localAttachment(path7) {
46330
46803
  throw new Error("Attachment changed while it was being read; try again");
46331
46804
  return bytes;
46332
46805
  } finally {
46333
- closeSync(descriptor);
46806
+ closeSync2(descriptor);
46334
46807
  }
46335
46808
  }
46336
46809
  function registerDirectOperations(server2, manager) {
@@ -46389,8 +46862,8 @@ function registerDirectOperations(server2, manager) {
46389
46862
  if (before.hash !== expectedContentHash) throw new Error("Note revision changed");
46390
46863
  const bytes = localAttachment(path7);
46391
46864
  const beforeAttachments = manager.listAttachmentsById(id2);
46392
- const directory = mkdtempSync4(join14(tmpdir4(), "notes-attachment-add-"));
46393
- const temporaryFile = join14(directory, basename(path7));
46865
+ const directory = mkdtempSync4(join15(tmpdir4(), "notes-attachment-add-"));
46866
+ const temporaryFile = join15(directory, basename(path7));
46394
46867
  try {
46395
46868
  writeFileSync3(temporaryFile, bytes, { mode: 384 });
46396
46869
  if (readSnapshot(manager, id2).hash !== before.hash)
@@ -46511,9 +46984,9 @@ function registerNativeTagsBridge(server2, manager) {
46511
46984
  { id: id2, expectedContentHash, scopeText, tags, title: initial.title },
46512
46985
  {
46513
46986
  read,
46514
- candidates: (title, scope) => manager.listAccounts().flatMap(
46987
+ candidates: (title, scope2) => manager.listAccounts().flatMap(
46515
46988
  (account) => manager.searchNotes(title, false, account.name).filter(
46516
- (note) => !note.passwordProtected && manager.getNotePlaintextById(note.id).toLocaleLowerCase().includes(scope.toLocaleLowerCase())
46989
+ (note) => !note.passwordProtected && manager.getNotePlaintextById(note.id).toLocaleLowerCase().includes(scope2.toLocaleLowerCase())
46517
46990
  ).map((note) => note.id)
46518
46991
  ),
46519
46992
  run: runNativeTagsShortcut
@@ -47052,7 +47525,11 @@ function errorResponse(message, cause) {
47052
47525
  function withErrorHandling(handler, errorPrefix) {
47053
47526
  return async (params) => {
47054
47527
  try {
47055
- return handler(params);
47528
+ const seconds = params.timeoutSeconds;
47529
+ return runWithCallTimeout(
47530
+ typeof seconds === "number" ? seconds : void 0,
47531
+ () => handler(params)
47532
+ );
47056
47533
  } catch (error2) {
47057
47534
  const message = error2 instanceof Error ? error2.message : "Unknown error";
47058
47535
  return errorResponse(`${errorPrefix}: ${message}`, error2);
@@ -47072,6 +47549,7 @@ var MAX = {
47072
47549
  TAGS: 100,
47073
47550
  BATCH_IDS: 500
47074
47551
  };
47552
+ var MAX_CONTENT_FILE_BYTES = 1024 * 1024;
47075
47553
  var noteTitleSchema = {
47076
47554
  title: external_exports.string().min(1, "Note title is required").max(MAX.TITLE),
47077
47555
  account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
@@ -47100,6 +47578,9 @@ function noteIdentifiers(id2) {
47100
47578
  var expectedContentHashInput = external_exports.string().regex(/^sha256:[a-f0-9]{64}$/, "expectedContentHash must come from get-note-content").describe(
47101
47579
  "Revision token returned by get-note-content for this exact ID. The mutation stops if the note changed since that read."
47102
47580
  );
47581
+ var timeoutSecondsInput = external_exports.number().int().min(CALL_TIMEOUT_SECONDS.min).max(CALL_TIMEOUT_SECONDS.max).optional().describe(
47582
+ `Per-call timeout in seconds (${CALL_TIMEOUT_SECONDS.min}-${CALL_TIMEOUT_SECONDS.max}) for each Notes.app automation step this call runs, overriding APPLE_NOTES_MCP_TIMEOUT_MS. A timed-out write is uncertain, not failed: read the note by id before any retry.`
47583
+ );
47103
47584
  function readExactNoteSnapshot(id2) {
47104
47585
  const note = notesManager.getNoteById(id2);
47105
47586
  if (!note) return { error: `Note with ID "${id2}" not found` };
@@ -47113,6 +47594,7 @@ function readExactNoteSnapshot(id2) {
47113
47594
  const rich = enrichNoteRead(id2, body);
47114
47595
  return { note, body, rich, contentHash: richContentHash(body, rich) };
47115
47596
  }
47597
+ var NOT_DELETED_MESSAGE = "Notes.app accepted the delete, but the note is still in its original folder, so it was not moved to Recently Deleted. Nothing was deleted; read the note again before retrying.";
47116
47598
  function revisionConflictMessage(title) {
47117
47599
  return `Note "${title}" changed after it was read. Read it again and review the newer version before retrying.`;
47118
47600
  }
@@ -47136,11 +47618,17 @@ registerTool(
47136
47618
  description: "Use when: the user wants to create a brand-new Apple Note.\nReturns: the new note's title and id \u2014 reuse the id for follow-up reads/edits.\nDo not use when: editing an existing note (use update-note).\nNote: the title is prepended as an <h1>; true Apple Notes checklists cannot be created via AppleScript (see the content field). A 'folder' must already exist \u2014 create-folder first (it is idempotent), since this tool does not create it.",
47137
47619
  inputSchema: {
47138
47620
  title: external_exports.string().min(1, "Title is required").max(MAX.TITLE),
47139
- content: external_exports.string().min(1, "Content is required").max(MAX.CONTENT).describe(
47140
- 'Note body. AppleScript cannot create true Apple Notes checklists \u2014 `<input type="checkbox">`, checklist CSS classes, and markdown `- [ ]` lines do not render as checkable items. To produce a checklist, create the note with a plain `<ul>` or `- ` list and convert it in Notes.app with \u21E7\u2318L.'
47621
+ content: external_exports.string().min(1, "Content is required").max(MAX.CONTENT).optional().describe(
47622
+ 'Note body; required unless contentPath is given (pass exactly one). AppleScript cannot create true Apple Notes checklists \u2014 `<input type="checkbox">`, checklist CSS classes, and markdown `- [ ]` lines do not render as checkable items. To produce a checklist, create the note with a plain `<ul>` or `- ` list and convert it in Notes.app with \u21E7\u2318L.'
47623
+ ),
47624
+ contentPath: external_exports.string().min(1).max(MAX.SAVE_PATH).optional().describe(
47625
+ `Absolute path of a local UTF-8 file to use as the body instead of content (pass exactly one). The same locations save-attachment may write to are allowed (home, temp, /Volumes); symbolic links and non-regular files are refused. Limit ${MAX_CONTENT_FILE_BYTES} bytes.`
47141
47626
  ),
47142
47627
  format: external_exports.enum(["plaintext", "html", "markdown"]).optional().default("plaintext").describe(
47143
- "Content format: 'plaintext' (default), 'html' for rich formatting, or 'markdown' for real Title/Heading/Subheading styles through the Create Markdown Note Shortcut (iCloud only; see get-capabilities)"
47628
+ "Content format: 'plaintext' (default), 'html' for rich formatting, or 'markdown'. Markdown whose first line is exactly `# <title>` (same case and spacing) has that line and one blank line after it removed, since the title is supplied separately."
47629
+ ),
47630
+ markdownRoute: external_exports.enum(["shortcut", "html"]).optional().default("shortcut").describe(
47631
+ "How format 'markdown' is imported. 'shortcut' (default) uses the Create Markdown Note Shortcut for real Title/Heading/Subheading styles (iCloud only, no tags; see get-capabilities). 'html' is the fallback when that Shortcut is not installed or the note is outside iCloud: it converts the same bounded Markdown subset to HTML and creates the note through AppleScript in any account, rendering `- [ ]` / `- [x]` task items as ordinary list rows that start with a visible \u2610 / \u2611 character \u2014 not native, checkable checklist items."
47144
47632
  ),
47145
47633
  tags: external_exports.array(external_exports.string().max(MAX.TAG)).max(MAX.TAGS).optional().describe(
47146
47634
  "Returned-only metadata \u2014 NOT written to Notes.app. Apple Notes tags can't be set via AppleScript, so any values passed here are echoed back in the response but do not appear on the created note. Use #hashtags in the body for searchable text; this does not create native tag objects. Native tags need the Notes Shortcuts action. Refused with format 'markdown': add tags afterwards with add-native-tags."
@@ -47150,7 +47638,8 @@ registerTool(
47150
47638
  ),
47151
47639
  account: external_exports.string().max(MAX.ACCOUNT).optional().describe(
47152
47640
  "Account name (defaults to the account Notes.app itself reports as default). Matched exactly, or by a unique prefix; an ambiguous prefix is refused. Must be an account Notes.app already has configured \u2014 see list-accounts."
47153
- )
47641
+ ),
47642
+ timeoutSeconds: timeoutSecondsInput
47154
47643
  },
47155
47644
  outputSchema: {
47156
47645
  ok: external_exports.boolean().optional(),
@@ -47159,10 +47648,66 @@ registerTool(
47159
47648
  folder: external_exports.string().optional(),
47160
47649
  account: external_exports.string().optional(),
47161
47650
  contentHash: external_exports.string().optional(),
47162
- verified: external_exports.boolean().optional()
47651
+ verified: external_exports.boolean().optional(),
47652
+ strippedDuplicateTitle: external_exports.boolean().optional(),
47653
+ taskItemsRendered: external_exports.number().optional()
47163
47654
  }
47164
47655
  },
47165
- withErrorHandling(({ title, content, format = "plaintext", tags = [], folder, account }) => {
47656
+ withErrorHandling((params) => {
47657
+ const {
47658
+ title,
47659
+ contentPath,
47660
+ format = "plaintext",
47661
+ markdownRoute = "shortcut",
47662
+ tags = [],
47663
+ folder,
47664
+ account
47665
+ } = params;
47666
+ if (params.content === void 0 === (contentPath === void 0))
47667
+ return errorResponse("Provide exactly one of content or contentPath");
47668
+ let content = params.content ?? readAllowedTextFile(contentPath, MAX_CONTENT_FILE_BYTES);
47669
+ if (format !== "markdown" && markdownRoute !== "shortcut")
47670
+ return errorResponse('markdownRoute applies to format "markdown" only');
47671
+ let strippedDuplicateTitle = false;
47672
+ if (format === "markdown") {
47673
+ const stripped = stripDuplicateTitleHeading(content, title);
47674
+ strippedDuplicateTitle = stripped.stripped;
47675
+ content = stripped.content;
47676
+ if (!content.trim())
47677
+ return errorResponse(
47678
+ "The Markdown holds only the title heading; add body content, or use format 'plaintext' for a title-only note"
47679
+ );
47680
+ }
47681
+ const titleNote = strippedDuplicateTitle ? { strippedDuplicateTitle: true } : {};
47682
+ if (format === "markdown" && markdownRoute === "html") {
47683
+ const taskItemsRendered = countTaskItems(content);
47684
+ const html = appendMarkdownHtml(content, { taskGlyphs: true });
47685
+ const note2 = notesManager.createNote(title, html, tags, folder, account, "html");
47686
+ if (!note2)
47687
+ return errorResponse(
47688
+ `Failed to create note "${title}". Check that the folder and account exist (list-folders, list-accounts) and that this server has Automation access (run the doctor tool).`
47689
+ );
47690
+ const createdBody2 = notesManager.getNoteContentById(note2.id);
47691
+ if (!notesManager.getNoteById(note2.id) || !createdBody2)
47692
+ return errorResponse(
47693
+ `A note may have been created, but its exact ID could not be verified. Do not retry automatically. Returned ID: ${note2.id}`
47694
+ );
47695
+ const glyphNote = taskItemsRendered ? ` ${taskItemsRendered} task item(s) were rendered as visible \u2610 / \u2611 text, not native checklist items.` : "";
47696
+ return successResponse(
47697
+ `Note created from Markdown: "${note2.title}" [id: ${note2.id}]${glyphNote}`,
47698
+ {
47699
+ ok: true,
47700
+ id: note2.id,
47701
+ title: note2.title,
47702
+ folder,
47703
+ account,
47704
+ contentHash: richContentHash(createdBody2, enrichNoteRead(note2.id, createdBody2)),
47705
+ verified: true,
47706
+ taskItemsRendered,
47707
+ ...titleNote
47708
+ }
47709
+ );
47710
+ }
47166
47711
  if (format === "markdown") {
47167
47712
  if (account)
47168
47713
  return errorResponse(
@@ -47174,7 +47719,10 @@ registerTool(
47174
47719
  );
47175
47720
  requireValidated("create-note-markdown");
47176
47721
  const result = createMarkdownNote(notesManager, { title, content, folder });
47177
- return successResponse(`Note created from Markdown: "${title}" [id: ${result.id}]`, result);
47722
+ return successResponse(`Note created from Markdown: "${title}" [id: ${result.id}]`, {
47723
+ ...result,
47724
+ ...titleNote
47725
+ });
47178
47726
  }
47179
47727
  const note = notesManager.createNote(title, content, tags, folder, account, format);
47180
47728
  if (!note) {
@@ -47320,7 +47868,7 @@ registerTool(
47320
47868
  }
47321
47869
  throw error2;
47322
47870
  }
47323
- const scope = `scanned ${result.scanned} of ${result.eligible} notes` + (result.scanTruncated ? `, the most recent ${result.scanLimit}; pass a higher scanLimit to include older notes` : "");
47871
+ const scope2 = `scanned ${result.scanned} of ${result.eligible} notes` + (result.scanTruncated ? `, the most recent ${result.scanLimit}; pass a higher scanLimit to include older notes` : "");
47324
47872
  const notes = [];
47325
47873
  if (result.truncated) {
47326
47874
  notes.push(
@@ -47336,7 +47884,7 @@ registerTool(
47336
47884
 
47337
47885
  ${notes.join("\n")}` : "";
47338
47886
  if (result.count === 0) {
47339
- return successResponse(`No notes matched (${scope}).${footer}`, { ...result });
47887
+ return successResponse(`No notes matched (${scope2}).${footer}`, { ...result });
47340
47888
  }
47341
47889
  const lines = result.notes.map((n) => {
47342
47890
  const where = [n.account, n.folder].filter(Boolean).join(" / ");
@@ -47345,7 +47893,7 @@ ${notes.join("\n")}` : "";
47345
47893
  return ` - ${n.title}${where ? ` (${where})` : ""}${n.locked ? " [locked]" : ""} [id: ${n.id}]${snippet}`;
47346
47894
  }).join("\n");
47347
47895
  return successResponse(
47348
- `Found ${result.matched} matching notes (${scope}):
47896
+ `Found ${result.matched} matching notes (${scope2}):
47349
47897
  ${lines}${footer}`,
47350
47898
  { ...result }
47351
47899
  );
@@ -47892,7 +48440,8 @@ registerTool(
47892
48440
  newContent: external_exports.string().min(1, "New content is required").max(MAX.CONTENT).describe(
47893
48441
  "New note body. AppleScript cannot produce true Apple Notes checklists; checkbox inputs and `- [ ]` markdown do not render as checkable items. Use a plain list and convert in Notes.app with \u21E7\u2318L."
47894
48442
  ),
47895
- format: external_exports.enum(["plaintext", "html"]).optional().default("plaintext").describe("Content format: 'plaintext' (default) or 'html' for rich formatting")
48443
+ format: external_exports.enum(["plaintext", "html"]).optional().default("plaintext").describe("Content format: 'plaintext' (default) or 'html' for rich formatting"),
48444
+ timeoutSeconds: timeoutSecondsInput
47896
48445
  },
47897
48446
  outputSchema: {
47898
48447
  ok: external_exports.boolean().optional(),
@@ -48103,10 +48652,11 @@ registerTool(
48103
48652
  content: external_exports.string().min(1, "Content to append is required").max(MAX.CONTENT).describe("Text to append to the note body"),
48104
48653
  scopeText: external_exports.string().min(12).max(500).optional().describe("Existing unique phrase required for native append to protected notes"),
48105
48654
  position: external_exports.enum(["after", "before"]).optional().default("after").describe(
48106
- "Where to insert: 'after' appends to the end (default), 'before' prepends to the start"
48655
+ "Where to insert: 'after' appends to the end (default); 'before' inserts directly below the note's title line, so the title stays first"
48107
48656
  ),
48108
48657
  separator: external_exports.string().max(20).optional().default("\n\n").describe("String placed between existing content and new content (default: two newlines)"),
48109
- format: external_exports.enum(["plaintext", "html"]).optional().default("plaintext").describe("Format of the content being appended: 'plaintext' (default) or 'html'")
48658
+ format: external_exports.enum(["plaintext", "html"]).optional().default("plaintext").describe("Format of the content being appended: 'plaintext' (default) or 'html'"),
48659
+ timeoutSeconds: timeoutSecondsInput
48110
48660
  },
48111
48661
  outputSchema: {
48112
48662
  ok: external_exports.boolean().optional(),
@@ -48195,7 +48745,8 @@ registerTool(
48195
48745
  description: "Use when: moving one exact note to Recently Deleted after reading and reviewing it.\nReturns: confirmation with the exact id.\nDo not use when: you only have a title or the note changed since review.\nSafety: requires id and expectedContentHash from get-note-content. The body comparison and delete happen in one AppleScript, so a newer edit is preserved.",
48196
48746
  inputSchema: {
48197
48747
  id: noteIdInput,
48198
- expectedContentHash: expectedContentHashInput
48748
+ expectedContentHash: expectedContentHashInput,
48749
+ timeoutSeconds: timeoutSecondsInput
48199
48750
  },
48200
48751
  outputSchema: {
48201
48752
  ok: external_exports.boolean().optional(),
@@ -48215,6 +48766,9 @@ registerTool(
48215
48766
  if (result.status === "conflict") {
48216
48767
  return errorResponse(revisionConflictMessage(snapshot.note.title));
48217
48768
  }
48769
+ if (result.status === "not-deleted") {
48770
+ return errorResponse(NOT_DELETED_MESSAGE);
48771
+ }
48218
48772
  if (result.status !== "deleted") {
48219
48773
  return errorResponse(
48220
48774
  `The delete result for note "${snapshot.note.title}" is uncertain. Inspect exact ID ${id2} before retrying.`
@@ -48240,7 +48794,8 @@ registerTool(
48240
48794
  inputSchema: {
48241
48795
  id: noteIdInput,
48242
48796
  folder: external_exports.string().min(1, "Destination folder is required").max(MAX.FOLDER),
48243
- account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account containing the note/folder")
48797
+ account: external_exports.string().max(MAX.ACCOUNT).optional().describe("Account containing the note/folder"),
48798
+ timeoutSeconds: timeoutSecondsInput
48244
48799
  },
48245
48800
  outputSchema: {
48246
48801
  ok: external_exports.boolean().optional(),
@@ -48803,6 +49358,8 @@ registerTool(
48803
49358
  if (result.status === "conflict") {
48804
49359
  return { id: id2, success: false, error: revisionConflictMessage(snapshot.note.title) };
48805
49360
  }
49361
+ if (result.status === "not-deleted")
49362
+ return { id: id2, success: false, error: NOT_DELETED_MESSAGE };
48806
49363
  return { id: id2, success: false, error: "Delete result uncertain; inspect this exact ID" };
48807
49364
  });
48808
49365
  const succeeded = results.filter((r) => r.success).length;
@@ -49098,6 +49655,44 @@ ${summary}`,
49098
49655
  );
49099
49656
  }, "Error reading checklist state")
49100
49657
  );
49658
+ registerTool(
49659
+ "get-audio-transcripts",
49660
+ {
49661
+ description: "Use when: reading the transcript (and summary, if any) that Notes already computed for the audio recordings in one note, by id.\nReturns: one entry per top-level audio attachment in body order, with attachmentId, durationSeconds, status (ok, none when no transcript is stored, or undecodable with a reason), joined transcript text, wordCount, speakers and summary when stored, and optional word-level segments.\nDo not use when: you need the audio file itself (save-attachment) or the note body (get-note-content). This tool does not transcribe; it only reads what Notes stored.\nSafety: read-only; requires Full Disk Access and reads the NoteStore database. Password-protected notes are refused. Large responses drop segments first, then shorten text, to stay under APPLE_NOTES_MCP_EXPORT_MAX_BYTES.",
49662
+ inputSchema: {
49663
+ id: noteIdInput,
49664
+ includeSegments: external_exports.boolean().optional().describe(
49665
+ "Also return word-level segments (text, start and duration in seconds, speaker). Default false."
49666
+ ),
49667
+ maxSegments: external_exports.number().int().min(1).max(MAX_SEGMENTS_LIMIT).optional().describe(
49668
+ `Cap on segments returned per attachment when includeSegments is true (default ${DEFAULT_MAX_SEGMENTS}).`
49669
+ )
49670
+ },
49671
+ outputSchema: {
49672
+ id: external_exports.string().optional(),
49673
+ attachments: external_exports.array(external_exports.object({}).passthrough()).optional(),
49674
+ bodyOrder: external_exports.boolean().optional(),
49675
+ truncated: external_exports.boolean().optional()
49676
+ },
49677
+ annotations: { readOnlyHint: true }
49678
+ },
49679
+ withErrorHandling(({ id: id2, includeSegments, maxSegments }) => {
49680
+ let result;
49681
+ try {
49682
+ result = notesManager.getAudioTranscripts(id2, { includeSegments, maxSegments });
49683
+ } catch (error2) {
49684
+ if (error2 instanceof AudioTranscriptError) return errorResponse(error2.message);
49685
+ throw error2;
49686
+ }
49687
+ const toResponse = (r) => successResponse(formatTranscriptsText(r), { ...r });
49688
+ const fitted = fitTranscriptsToBudget(
49689
+ result,
49690
+ exportMaxResponseBytes(),
49691
+ (r) => Buffer.byteLength(JSON.stringify(toResponse(r)))
49692
+ );
49693
+ return toResponse(fitted);
49694
+ }, "Error reading audio transcripts")
49695
+ );
49101
49696
  registerTool(
49102
49697
  "get-note-metadata",
49103
49698
  {