apple-notes-mcp 2.11.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/index.js CHANGED
@@ -24477,14 +24477,14 @@ var require_turndown_cjs = __commonJS({
24477
24477
  } else if (node.nodeType === 1) {
24478
24478
  replacement = replacementForNode.call(self, node);
24479
24479
  }
24480
- return join31(output, replacement);
24480
+ return join32(output, replacement);
24481
24481
  }, "");
24482
24482
  }
24483
24483
  function postProcess(output) {
24484
24484
  var self = this;
24485
24485
  this.rules.forEach(function(rule) {
24486
24486
  if (typeof rule.append === "function") {
24487
- output = join31(output, rule.append(self.options));
24487
+ output = join32(output, rule.append(self.options));
24488
24488
  }
24489
24489
  });
24490
24490
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -24496,7 +24496,7 @@ var require_turndown_cjs = __commonJS({
24496
24496
  if (whitespace.leading || whitespace.trailing) content = content.trim();
24497
24497
  return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
24498
24498
  }
24499
- function join31(output, replacement) {
24499
+ function join32(output, replacement) {
24500
24500
  var s1 = trimTrailingNewlines(output);
24501
24501
  var s2 = trimLeadingNewlines(replacement);
24502
24502
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -51023,13 +51023,13 @@ var SidecarWriter = class {
51023
51023
  }
51024
51024
  this.created = true;
51025
51025
  }
51026
- const safe = safeAssetName(name, mime);
51027
- const ext = extname3(safe);
51028
- const stem = safe.slice(0, safe.length - ext.length);
51026
+ const safe2 = safeAssetName(name, mime);
51027
+ const ext = extname3(safe2);
51028
+ const stem = safe2.slice(0, safe2.length - ext.length);
51029
51029
  let target;
51030
51030
  let out;
51031
51031
  for (let n = 1; n <= 1e3 && out === void 0; n++) {
51032
- target = join19(this.dir, n === 1 ? safe : `${stem}-${n}${ext}`);
51032
+ target = join19(this.dir, n === 1 ? safe2 : `${stem}-${n}${ext}`);
51033
51033
  try {
51034
51034
  out = openSync4(target, CREATE_FLAGS, 420);
51035
51035
  } catch (error2) {
@@ -51714,7 +51714,8 @@ var PUBLIC_HELPER_SETUP_COMMAND = "apple-notes-mcp setup --public-helper";
51714
51714
  var PUBLIC_HELPER_ACTIONS = /* @__PURE__ */ new Set([
51715
51715
  "hello",
51716
51716
  "decode_drawing",
51717
- "transcribe"
51717
+ "transcribe",
51718
+ "speech_status"
51718
51719
  ]);
51719
51720
  var DEFAULT_TIMEOUT_MS2 = 3e4;
51720
51721
  var MAX_OUTPUT_BYTES2 = 256 * 1024 * 1024;
@@ -52888,16 +52889,16 @@ var NOTE_PLACEHOLDERS = [
52888
52889
  "exportStem"
52889
52890
  ];
52890
52891
  var PLACEHOLDER_MODIFIERS = ["raw", "yaml"];
52891
- var wrap2 = (before, after = "", join31) => ({
52892
+ var wrap2 = (before, after = "", join32) => ({
52892
52893
  mode: "wrap",
52893
52894
  before,
52894
52895
  after,
52895
- ...join31 ? { join: join31 } : {}
52896
+ ...join32 ? { join: join32 } : {}
52896
52897
  });
52897
- var pattern = (value, join31) => ({
52898
+ var pattern = (value, join32) => ({
52898
52899
  mode: "pattern",
52899
52900
  value,
52900
- ...join31 ? { join: join31 } : {}
52901
+ ...join32 ? { join: join32 } : {}
52901
52902
  });
52902
52903
  var STANDARD = {
52903
52904
  schemaVersion: 1,
@@ -53969,12 +53970,12 @@ function pathComponent(value) {
53969
53970
  return clean || "untitled";
53970
53971
  }
53971
53972
  function templateAssetsDir(directory, outputDir, values) {
53972
- const safe = {};
53973
+ const safe2 = {};
53973
53974
  for (const [key, value] of Object.entries(values)) {
53974
53975
  const raw = typeof value === "string" ? value : value?.raw;
53975
- if (raw !== void 0) safe[key] = pathComponent(raw);
53976
+ if (raw !== void 0) safe2[key] = pathComponent(raw);
53976
53977
  }
53977
- const filled = fillPlaceholders(directory, safe);
53978
+ const filled = fillPlaceholders(directory, safe2);
53978
53979
  const base = resolve5(outputDir);
53979
53980
  const dir = resolve5(base, filled);
53980
53981
  if (isAbsolute3(filled) || filled.split("/").includes("..") || !dir.startsWith(base + sep4))
@@ -59405,6 +59406,710 @@ function formatHelperBuild(report) {
59405
59406
  return lines.join("\n");
59406
59407
  }
59407
59408
 
59409
+ // src/services/permissions.ts
59410
+ import { spawnSync as spawnSync6 } from "node:child_process";
59411
+ import { createInterface } from "node:readline";
59412
+ var PRIVACY = "System Settings > Privacy & Security";
59413
+ var PANE_ANCHORS = {
59414
+ fullDiskAccess: { anchor: "Privacy_AllFiles", label: "Full Disk Access" },
59415
+ automation: { anchor: "Privacy_Automation", label: "Automation" },
59416
+ speechRecognition: { anchor: "Privacy_SpeechRecognition", label: "Speech Recognition" }
59417
+ };
59418
+ function settingsUrl(pane, macOSVersion) {
59419
+ const legacy = macOSVersion !== null && compareVersions(macOSVersion, "13.0") < 0;
59420
+ const base = legacy ? "x-apple.systempreferences:com.apple.preference.security" : "x-apple.systempreferences:com.apple.settings.PrivacySecurity.extension";
59421
+ return `${base}?${PANE_ANCHORS[pane].anchor}`;
59422
+ }
59423
+ function paneFields(pane, macOSVersion) {
59424
+ return {
59425
+ settingsPane: `${PRIVACY} > ${PANE_ANCHORS[pane].label}`,
59426
+ settingsUrl: settingsUrl(pane, macOSVersion)
59427
+ };
59428
+ }
59429
+ function defaultCheckOptions() {
59430
+ return { probeAutomation: false, check: false, env: process.env };
59431
+ }
59432
+ function automationSkipReason(options) {
59433
+ if (!options.probeAutomation)
59434
+ return "this check sends no Apple event to Notes.app unless you pass --probe-automation";
59435
+ if (options.env.SSH_CONNECTION)
59436
+ return "this is an SSH session, where macOS cannot show its permission prompt, so no Apple event was sent";
59437
+ if (options.check)
59438
+ return "--check reports state only and never sends an Apple event to Notes.app";
59439
+ return null;
59440
+ }
59441
+ function checkPermissions(probes = defaultPermissionProbes(), options = defaultCheckOptions()) {
59442
+ const macOSVersion = safe(probes.macOSVersion, null);
59443
+ const launchingApp = safe(probes.launchingApp, null);
59444
+ const who = launchingApp ?? "the app that launches the server";
59445
+ const items = [
59446
+ fullDiskAccessItem(probes, macOSVersion),
59447
+ automationItem(probes, macOSVersion, who, automationSkipReason(options)),
59448
+ shortcutsItem(probes),
59449
+ speechItem(probes, macOSVersion, who)
59450
+ ];
59451
+ return {
59452
+ ready: items.every((item) => !item.required || item.status === "granted" || item.notProbed),
59453
+ launchingApp,
59454
+ execPath: probes.execPath,
59455
+ macOSVersion,
59456
+ items
59457
+ };
59458
+ }
59459
+ function safe(fn, fallback) {
59460
+ try {
59461
+ return fn();
59462
+ } catch {
59463
+ return fallback;
59464
+ }
59465
+ }
59466
+ function errorText(error2) {
59467
+ return error2 instanceof Error ? error2.message : String(error2);
59468
+ }
59469
+ function fullDiskAccessItem(probes, macOSVersion) {
59470
+ const base = {
59471
+ id: "fullDiskAccess",
59472
+ title: "Full Disk Access",
59473
+ required: true,
59474
+ ...paneFields("fullDiskAccess", macOSVersion)
59475
+ };
59476
+ let granted;
59477
+ try {
59478
+ granted = probes.fullDiskAccess();
59479
+ } catch (error2) {
59480
+ return {
59481
+ ...base,
59482
+ status: "unknown",
59483
+ detail: `could not probe the Notes database: ${errorText(error2)}`,
59484
+ fix: fdaRemediation(probes.execPath)
59485
+ };
59486
+ }
59487
+ return granted ? {
59488
+ ...base,
59489
+ status: "granted",
59490
+ detail: "the Notes database (NoteStore.sqlite) is readable",
59491
+ fix: null
59492
+ } : {
59493
+ ...base,
59494
+ status: "missing",
59495
+ detail: "the Notes database is not readable, so query-notes, checklist state, note metadata, note links, native objects, exports and the bridges' readback do not work",
59496
+ fix: fdaRemediation(probes.execPath)
59497
+ };
59498
+ }
59499
+ var PROBE_AUTOMATION_COMMAND = "apple-notes-mcp setup --permissions --probe-automation";
59500
+ function automationItem(probes, macOSVersion, who, skipReason) {
59501
+ const base = {
59502
+ id: "notesAutomation",
59503
+ title: "Automation of Notes.app",
59504
+ required: true,
59505
+ ...paneFields("automation", macOSVersion)
59506
+ };
59507
+ if (skipReason !== null)
59508
+ return {
59509
+ ...base,
59510
+ status: "unknown",
59511
+ detail: `not probed: ${skipReason}`,
59512
+ fix: `On this Mac, in a terminal, run \`${PROBE_AUTOMATION_COMMAND}\`. It sends one read-only Apple event to Notes.app, and macOS may ask whether this app may control Notes; choose Allow.`,
59513
+ notProbed: true
59514
+ };
59515
+ let result;
59516
+ try {
59517
+ result = probes.notesAutomation();
59518
+ } catch (error2) {
59519
+ result = { success: false, error: errorText(error2) };
59520
+ }
59521
+ if (result.success)
59522
+ return {
59523
+ ...base,
59524
+ status: "granted",
59525
+ detail: "Notes.app answered a read-only Apple event",
59526
+ fix: null
59527
+ };
59528
+ if (isPermissionDenied(result.error))
59529
+ return {
59530
+ ...base,
59531
+ status: "missing",
59532
+ detail: "macOS refused the Apple event to Notes.app (Automation denied)",
59533
+ fix: `In ${PRIVACY} > Automation, expand ${who} and turn on Notes. Then fully quit (Cmd+Q) and relaunch that app. If Notes is not listed, run this check again and choose Allow when macOS asks to control Notes.`
59534
+ };
59535
+ return {
59536
+ ...base,
59537
+ status: "unknown",
59538
+ detail: `Notes.app did not answer: ${result.error || "no response"}`,
59539
+ fix: "Open Notes.app once, then run this check again. If macOS asks whether this app may control Notes, choose Allow."
59540
+ };
59541
+ }
59542
+ function shortcutsItem(probes) {
59543
+ const base = {
59544
+ id: "shortcutBridges",
59545
+ title: "Shortcut bridges",
59546
+ required: false,
59547
+ settingsPane: null,
59548
+ settingsUrl: null
59549
+ };
59550
+ let report;
59551
+ try {
59552
+ report = probes.shortcuts();
59553
+ } catch (error2) {
59554
+ return {
59555
+ ...base,
59556
+ status: "unknown",
59557
+ detail: `could not run the shortcuts command: ${errorText(error2)}`,
59558
+ fix: "Run `apple-notes-mcp setup --check`."
59559
+ };
59560
+ }
59561
+ const required2 = report.items.filter((item) => !item.optional);
59562
+ const failed = required2.filter((item) => item.error && !item.installed);
59563
+ const missing = required2.filter((item) => !item.installed);
59564
+ const consent = "After install or upgrade, run each bridge once in Shortcuts.app and choose Always Allow.";
59565
+ if (missing.length === 0)
59566
+ return {
59567
+ ...base,
59568
+ status: "granted",
59569
+ detail: `both required bridges are installed (${required2.map((item) => item.name).join(", ")}). ${consent}`,
59570
+ fix: null
59571
+ };
59572
+ if (failed.length === missing.length && failed.length > 0)
59573
+ return {
59574
+ ...base,
59575
+ status: "unknown",
59576
+ detail: `could not inspect: ${failed.map((item) => `${item.name}: ${item.error}`).join("; ")}`,
59577
+ fix: "Run `apple-notes-mcp setup --check`."
59578
+ };
59579
+ return {
59580
+ ...base,
59581
+ status: "missing",
59582
+ detail: `not installed: ${missing.map((item) => item.name).join(", ")}. The native-write tools (append-native, checklists, tables, native tags, pinning, note links) need them; everything else works without them`,
59583
+ fix: `Run \`apple-notes-mcp setup\` and approve Add Shortcut in macOS. ${consent}`
59584
+ };
59585
+ }
59586
+ function speechItem(probes, macOSVersion, who) {
59587
+ const base = {
59588
+ id: "speechRecognition",
59589
+ title: "Speech Recognition",
59590
+ required: false,
59591
+ ...paneFields("speechRecognition", macOSVersion)
59592
+ };
59593
+ let answer;
59594
+ try {
59595
+ answer = probes.speech();
59596
+ } catch (error2) {
59597
+ answer = { ok: false, reason: errorText(error2) };
59598
+ }
59599
+ if (!answer.ok)
59600
+ return {
59601
+ ...base,
59602
+ status: "unknown",
59603
+ detail: `only transcribe-note-audio needs this; could not read the status: ${answer.reason}`,
59604
+ fix: `Build the public native helper with \`${PUBLIC_HELPER_SETUP_COMMAND}\`, then run this check again.`
59605
+ };
59606
+ const state = answer.speechAuthorization;
59607
+ if (state === "authorized")
59608
+ return {
59609
+ ...base,
59610
+ status: "granted",
59611
+ detail: "transcribe-note-audio can use on-device speech recognition",
59612
+ fix: null
59613
+ };
59614
+ if (state === "restricted")
59615
+ return {
59616
+ ...base,
59617
+ status: "missing",
59618
+ detail: "Speech Recognition is restricted on this Mac (for example by a management profile)",
59619
+ fix: "Ask the Mac's administrator; the restriction cannot be lifted in System Settings."
59620
+ };
59621
+ if (state === "denied")
59622
+ return {
59623
+ ...base,
59624
+ status: "missing",
59625
+ detail: "Speech Recognition was denied, so transcribe-note-audio stops with permission_required",
59626
+ fix: `In ${PRIVACY} > Speech Recognition, turn on ${who}, then relaunch it.`
59627
+ };
59628
+ if (!answer.requiresGrant)
59629
+ return {
59630
+ ...base,
59631
+ status: "not_needed",
59632
+ detail: "not granted, and not needed: on macOS 26 and later transcription runs on-device without a Speech Recognition grant",
59633
+ fix: null
59634
+ };
59635
+ return {
59636
+ ...base,
59637
+ status: "missing",
59638
+ detail: `status ${state}. Before macOS 26, transcription needs this grant, and the server never shows the permission prompt`,
59639
+ fix: `In ${PRIVACY} > Speech Recognition, turn on ${who} if it is listed, then relaunch it.`
59640
+ };
59641
+ }
59642
+ function findLaunchingApp(startPid = process.ppid, readProcess = readProcessEntry) {
59643
+ let pid = startPid;
59644
+ for (let depth = 0; depth < 32 && pid > 1; depth++) {
59645
+ const entry = readProcess(pid);
59646
+ if (!entry) return null;
59647
+ const match = /^(.*?\.app)\//.exec(entry.command);
59648
+ if (match) return match[1];
59649
+ pid = entry.ppid;
59650
+ }
59651
+ return null;
59652
+ }
59653
+ function readProcessEntry(pid) {
59654
+ const result = spawnSync6("/bin/ps", ["-o", "ppid=", "-o", "comm=", "-p", String(pid)], {
59655
+ encoding: "utf8",
59656
+ timeout: 3e3
59657
+ });
59658
+ const match = /^\s*(\d+)\s+(.+)$/.exec(String(result.stdout ?? "").trim());
59659
+ return match ? { ppid: Number(match[1]), command: match[2] } : null;
59660
+ }
59661
+ var AUTOMATION_TIMEOUT_MS = 6e4;
59662
+ function defaultPermissionProbes() {
59663
+ return {
59664
+ fullDiskAccess: hasFullDiskAccess,
59665
+ notesAutomation: () => {
59666
+ const result = executeAppleScript('tell application "Notes" to get name of account 1', {
59667
+ timeoutMs: AUTOMATION_TIMEOUT_MS,
59668
+ maxRetries: 1
59669
+ });
59670
+ return { success: result.success, error: result.error };
59671
+ },
59672
+ shortcuts: () => setupShortcuts(true),
59673
+ speech: () => {
59674
+ const install = inspectPublicHelper();
59675
+ if (!install.ready)
59676
+ return { ok: false, reason: install.detail ?? "the public native helper is not built" };
59677
+ const answer = callPublicHelper("speech_status");
59678
+ return {
59679
+ ok: true,
59680
+ speechAuthorization: String(answer.speechAuthorization ?? "unknown"),
59681
+ requiresGrant: answer.requiresGrant !== false
59682
+ };
59683
+ },
59684
+ macOSVersion: readMacOSVersion,
59685
+ launchingApp: () => findLaunchingApp(),
59686
+ execPath: process.execPath
59687
+ };
59688
+ }
59689
+ function openSettingsPane(item, open = openUrl) {
59690
+ if (!item.settingsUrl || !item.settingsUrl.startsWith("x-apple.systempreferences:"))
59691
+ return { ok: false, error: `${item.title} has no System Settings pane` };
59692
+ return open(item.settingsUrl);
59693
+ }
59694
+ function openUrl(url) {
59695
+ const result = spawnSync6("/usr/bin/open", [url], { encoding: "utf8" });
59696
+ return result.status === 0 ? { ok: true } : { ok: false, error: result.stderr || result.error?.message || "open failed" };
59697
+ }
59698
+ function pendingItems(report) {
59699
+ return report.items.filter(
59700
+ (item) => !item.notProbed && (item.status === "missing" || item.status === "unknown")
59701
+ );
59702
+ }
59703
+ var ICONS = {
59704
+ granted: "\u2713",
59705
+ not_needed: "\u2713",
59706
+ missing: "\u2717",
59707
+ unknown: "?"
59708
+ };
59709
+ function formatPermissionsReport(report) {
59710
+ const lines = ["Apple Notes MCP permissions", ""];
59711
+ lines.push(
59712
+ report.launchingApp ? `macOS attributes these grants to ${report.launchingApp}, the app that launched this check.` : `No launching app was found above this process, so macOS attributes these grants to ${report.execPath}.`
59713
+ );
59714
+ lines.push(
59715
+ "An MCP host (Claude Desktop, an editor) can hold different grants: run this check from the same app, or use the doctor tool there.",
59716
+ ""
59717
+ );
59718
+ for (const item of report.items) {
59719
+ const tag = item.required ? "" : " (optional)";
59720
+ lines.push(`${ICONS[item.status]} ${item.title}${tag}: ${item.detail}`);
59721
+ if (item.notProbed) {
59722
+ if (item.fix) lines.push(` To check: ${item.fix}`);
59723
+ } else if (item.status === "missing" || item.status === "unknown") {
59724
+ if (item.settingsPane) lines.push(` Pane: ${item.settingsPane}`);
59725
+ if (item.settingsUrl) lines.push(` URL: ${item.settingsUrl}`);
59726
+ if (item.fix) lines.push(` Fix: ${item.fix}`);
59727
+ }
59728
+ }
59729
+ lines.push("");
59730
+ const unchecked = report.items.some((item) => item.notProbed);
59731
+ lines.push(
59732
+ !report.ready ? "Required permissions are missing; the server works only partly until they are granted." : unchecked ? "Every required permission that was checked is granted. Automation of Notes.app was not checked." : "Every required permission is granted."
59733
+ );
59734
+ return lines.join("\n");
59735
+ }
59736
+ function parsePermissionsArgs(args) {
59737
+ return {
59738
+ open: args.includes("--open"),
59739
+ once: args.includes("--once") || args.includes("--check"),
59740
+ json: args.includes("--json"),
59741
+ probeAutomation: args.includes("--probe-automation"),
59742
+ check: args.includes("--check")
59743
+ };
59744
+ }
59745
+ async function runPermissionsCli(options, deps) {
59746
+ const opened = /* @__PURE__ */ new Set();
59747
+ for (; ; ) {
59748
+ const report = deps.check();
59749
+ deps.write(
59750
+ options.json ? JSON.stringify(report, null, 2) + "\n" : formatPermissionsReport(report) + "\n"
59751
+ );
59752
+ const pending = pendingItems(report);
59753
+ if (options.open) {
59754
+ for (const item of pending) {
59755
+ if (!item.settingsUrl || opened.has(item.id)) continue;
59756
+ opened.add(item.id);
59757
+ const result = deps.open(item);
59758
+ if (!options.json)
59759
+ deps.write(
59760
+ result.ok ? `Opened ${item.settingsPane}.
59761
+ ` : `Could not open ${item.settingsPane}: ${result.error}
59762
+ `
59763
+ );
59764
+ }
59765
+ }
59766
+ if (pending.length === 0 || options.once || !deps.interactive) return report.ready ? 0 : 1;
59767
+ deps.write("\nPress Enter to check again, or type q and press Enter to quit. ");
59768
+ const line = await deps.waitForEnter();
59769
+ if (line === null || line.trim().toLowerCase() === "q") return report.ready ? 0 : 1;
59770
+ deps.write("\n");
59771
+ }
59772
+ }
59773
+ function defaultPermissionsCliDeps(options) {
59774
+ const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
59775
+ const rl = interactive ? createInterface({ input: process.stdin }) : null;
59776
+ const lines = [];
59777
+ const waiters = [];
59778
+ let closed = false;
59779
+ rl?.on("line", (line) => {
59780
+ const waiter = waiters.shift();
59781
+ if (waiter) waiter(line);
59782
+ else lines.push(line);
59783
+ });
59784
+ rl?.on("close", () => {
59785
+ closed = true;
59786
+ for (const waiter of waiters.splice(0)) waiter(null);
59787
+ });
59788
+ return {
59789
+ check: () => checkPermissions(defaultPermissionProbes(), {
59790
+ probeAutomation: options.probeAutomation,
59791
+ check: options.check,
59792
+ env: process.env
59793
+ }),
59794
+ open: (item) => openSettingsPane(item),
59795
+ write: (text2) => process.stdout.write(text2),
59796
+ waitForEnter: () => new Promise((resolveLine) => {
59797
+ if (lines.length) resolveLine(lines.shift());
59798
+ else if (closed || !rl) resolveLine(null);
59799
+ else waiters.push(resolveLine);
59800
+ }),
59801
+ interactive,
59802
+ close: () => rl?.close()
59803
+ };
59804
+ }
59805
+
59806
+ // src/services/permissionsWindow.ts
59807
+ import { spawn as spawn2, spawnSync as spawnSync7 } from "node:child_process";
59808
+ import {
59809
+ chmodSync as chmodSync3,
59810
+ existsSync as existsSync17,
59811
+ mkdirSync as mkdirSync10,
59812
+ mkdtempSync as mkdtempSync9,
59813
+ readFileSync as readFileSync6,
59814
+ renameSync as renameSync4,
59815
+ rmSync as rmSync9,
59816
+ writeFileSync as writeFileSync8
59817
+ } from "node:fs";
59818
+ import { homedir as homedir22 } from "node:os";
59819
+ import { join as join31 } from "node:path";
59820
+ import { createInterface as createInterface2 } from "node:readline";
59821
+ var PERMISSIONS_WINDOW_PROTOCOL = 1;
59822
+ var PERMISSIONS_WINDOW_DIR_ENV = "APPLE_NOTES_MCP_PERMISSIONS_WINDOW_DIR";
59823
+ var PERMISSIONS_WINDOW_BINARY = "apple-notes-permissions-window";
59824
+ var PERMISSIONS_WINDOW_SOURCE = "native/permissions-window/apple-notes-permissions-window.swift";
59825
+ var PERMISSIONS_WINDOW_MANIFEST = "manifest.json";
59826
+ var PERMISSIONS_WINDOW_SETUP_COMMAND = "apple-notes-mcp setup --permissions-window";
59827
+ var PERMISSIONS_WINDOW_BUNDLE_ID = "apple-notes-mcp.permissions-window";
59828
+ var windowManifestSchema = external_exports.object({
59829
+ schemaVersion: external_exports.literal(1),
59830
+ protocolVersion: external_exports.number().int(),
59831
+ sourceSha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
59832
+ binarySha256: external_exports.string().regex(/^[a-f0-9]{64}$/),
59833
+ builtAt: external_exports.string(),
59834
+ compiler: external_exports.string()
59835
+ });
59836
+ function defaultPermissionsWindowDeps(overrides = {}) {
59837
+ return {
59838
+ env: process.env,
59839
+ platform: process.platform,
59840
+ sourcePath: join31(packageRoot(), PERMISSIONS_WINDOW_SOURCE),
59841
+ exists: existsSync17,
59842
+ readFile: (path10) => readFileSync6(path10),
59843
+ spawn: spawnSync7,
59844
+ now: () => /* @__PURE__ */ new Date(),
59845
+ ...overrides
59846
+ };
59847
+ }
59848
+ function permissionsWindowInstallDir(env = process.env) {
59849
+ const override = env[PERMISSIONS_WINDOW_DIR_ENV]?.trim();
59850
+ if (override) return override;
59851
+ return join31(homedir22(), "Library", "Application Support", "apple-notes-mcp", "permissions-window");
59852
+ }
59853
+ function inspectPermissionsWindow(deps = defaultPermissionsWindowDeps()) {
59854
+ const installDir = permissionsWindowInstallDir(deps.env);
59855
+ const binaryPath = join31(installDir, PERMISSIONS_WINDOW_BINARY);
59856
+ const fail = (reason, detail) => ({ ready: false, reason, detail, installDir, binaryPath });
59857
+ if (deps.platform !== "darwin") return fail("unsupported_platform", "macOS only");
59858
+ const rebuild = `Run \`${PERMISSIONS_WINDOW_SETUP_COMMAND}\`.`;
59859
+ if (!deps.exists(deps.sourcePath))
59860
+ return fail("window_not_installed", `Packaged window source is missing: ${deps.sourcePath}`);
59861
+ const manifestPath = join31(installDir, PERMISSIONS_WINDOW_MANIFEST);
59862
+ if (!deps.exists(binaryPath) || !deps.exists(manifestPath))
59863
+ return fail("window_not_installed", `The permissions window is not built. ${rebuild}`);
59864
+ let manifest;
59865
+ try {
59866
+ manifest = windowManifestSchema.parse(JSON.parse(deps.readFile(manifestPath).toString("utf8")));
59867
+ } catch {
59868
+ return fail("window_manifest_invalid", `The window manifest is unreadable. ${rebuild}`);
59869
+ }
59870
+ if (manifest.sourceSha256 !== sha256Hex(deps.readFile(deps.sourcePath)) || manifest.protocolVersion !== PERMISSIONS_WINDOW_PROTOCOL)
59871
+ return fail(
59872
+ "window_stale",
59873
+ `The installed window was built from a different source than this version ships. ${rebuild}`
59874
+ );
59875
+ if (sha256Hex(deps.readFile(binaryPath)) !== manifest.binarySha256)
59876
+ return fail(
59877
+ "window_modified",
59878
+ `The window binary no longer matches the checksum recorded when it was built. ${rebuild}`
59879
+ );
59880
+ return { ready: true, reason: null, detail: null, installDir, binaryPath };
59881
+ }
59882
+ function permissionsWindowInfoPlist() {
59883
+ return [
59884
+ '<?xml version="1.0" encoding="UTF-8"?>',
59885
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
59886
+ '<plist version="1.0">',
59887
+ "<dict>",
59888
+ " <key>CFBundleIdentifier</key>",
59889
+ ` <string>${PERMISSIONS_WINDOW_BUNDLE_ID}</string>`,
59890
+ " <key>CFBundleName</key>",
59891
+ " <string>Apple Notes MCP Permissions</string>",
59892
+ " <key>CFBundleInfoDictionaryVersion</key>",
59893
+ " <string>6.0</string>",
59894
+ "</dict>",
59895
+ "</plist>",
59896
+ ""
59897
+ ].join("\n");
59898
+ }
59899
+ function permissionsWindowCompileArguments(sourcePath, digestPath, plistPath, outputPath) {
59900
+ return [
59901
+ "swiftc",
59902
+ "-O",
59903
+ "-parse-as-library",
59904
+ "-framework",
59905
+ "AppKit",
59906
+ "-framework",
59907
+ "SwiftUI",
59908
+ "-Xlinker",
59909
+ "-sectcreate",
59910
+ "-Xlinker",
59911
+ "__TEXT",
59912
+ "-Xlinker",
59913
+ "__info_plist",
59914
+ "-Xlinker",
59915
+ plistPath,
59916
+ sourcePath,
59917
+ digestPath,
59918
+ "-o",
59919
+ outputPath
59920
+ ];
59921
+ }
59922
+ var helloSchema2 = external_exports.object({
59923
+ type: external_exports.literal("hello"),
59924
+ protocolVersion: external_exports.number().int(),
59925
+ sourceSha256: external_exports.string()
59926
+ });
59927
+ function buildPermissionsWindow(checkOnly, deps = defaultPermissionsWindowDeps()) {
59928
+ const steps = [];
59929
+ const finish = () => {
59930
+ const installation2 = inspectPermissionsWindow(deps);
59931
+ return {
59932
+ ok: installation2.ready && steps.every((s) => s.ok),
59933
+ checkOnly,
59934
+ steps,
59935
+ installation: installation2
59936
+ };
59937
+ };
59938
+ if (checkOnly) {
59939
+ const installation2 = inspectPermissionsWindow(deps);
59940
+ steps.push({
59941
+ step: "inspect installed window",
59942
+ ok: installation2.ready,
59943
+ detail: installation2.ready ? installation2.binaryPath : installation2.detail ?? void 0
59944
+ });
59945
+ return finish();
59946
+ }
59947
+ if (deps.platform !== "darwin") {
59948
+ steps.push({ step: "platform", ok: false, detail: "macOS only" });
59949
+ return finish();
59950
+ }
59951
+ if (!deps.exists(deps.sourcePath)) {
59952
+ steps.push({ step: "locate source", ok: false, detail: deps.sourcePath });
59953
+ return finish();
59954
+ }
59955
+ const sourceSha = sha256Hex(deps.readFile(deps.sourcePath));
59956
+ steps.push({ step: "locate source", ok: true, detail: `sha256 ${sourceSha}` });
59957
+ const version3 = deps.spawn("/usr/bin/xcrun", ["swiftc", "--version"], { encoding: "utf8" });
59958
+ if (version3.status !== 0) {
59959
+ steps.push({
59960
+ step: "find compiler",
59961
+ ok: false,
59962
+ detail: "No Swift compiler found. Install the Command Line Tools with `xcode-select --install`."
59963
+ });
59964
+ return finish();
59965
+ }
59966
+ const compiler = String(version3.stdout || version3.stderr || "").split("\n").find((line) => line.includes("Swift version"))?.trim() || "swiftc";
59967
+ steps.push({ step: "find compiler", ok: true, detail: compiler });
59968
+ const installDir = permissionsWindowInstallDir(deps.env);
59969
+ mkdirSync10(installDir, { recursive: true, mode: 448 });
59970
+ const staging = mkdtempSync9(join31(installDir, ".staging-"));
59971
+ try {
59972
+ const stagedBinary = join31(staging, PERMISSIONS_WINDOW_BINARY);
59973
+ const digestPath = join31(staging, "source-digest.swift");
59974
+ const plistPath = join31(staging, "Info.plist");
59975
+ writeFileSync8(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
59976
+ writeFileSync8(plistPath, permissionsWindowInfoPlist(), { mode: 384 });
59977
+ const compile = deps.spawn(
59978
+ "/usr/bin/xcrun",
59979
+ permissionsWindowCompileArguments(deps.sourcePath, digestPath, plistPath, stagedBinary),
59980
+ { encoding: "utf8", timeout: 3e5 }
59981
+ );
59982
+ if (compile.status !== 0) {
59983
+ steps.push({
59984
+ step: "compile",
59985
+ ok: false,
59986
+ detail: String(compile.stderr || compile.error?.message || "swiftc failed").slice(0, 4e3)
59987
+ });
59988
+ return finish();
59989
+ }
59990
+ steps.push({ step: "compile", ok: true });
59991
+ const sign = deps.spawn(
59992
+ "/usr/bin/codesign",
59993
+ ["--force", "--sign", "-", "--identifier", PERMISSIONS_WINDOW_BUNDLE_ID, stagedBinary],
59994
+ { encoding: "utf8" }
59995
+ );
59996
+ if (sign.status !== 0) {
59997
+ steps.push({ step: "ad-hoc sign", ok: false, detail: String(sign.stderr || "failed") });
59998
+ return finish();
59999
+ }
60000
+ steps.push({ step: "ad-hoc sign", ok: true });
60001
+ const hello = deps.spawn(stagedBinary, [], {
60002
+ input: JSON.stringify({ type: "hello" }) + "\n",
60003
+ encoding: "utf8",
60004
+ timeout: 1e4,
60005
+ killSignal: "SIGKILL"
60006
+ });
60007
+ let parsed = null;
60008
+ try {
60009
+ parsed = helloSchema2.parse(JSON.parse(String(hello.stdout ?? "").trim()));
60010
+ } catch {
60011
+ parsed = null;
60012
+ }
60013
+ if (!parsed || parsed.protocolVersion !== PERMISSIONS_WINDOW_PROTOCOL || parsed.sourceSha256 !== sourceSha) {
60014
+ steps.push({
60015
+ step: "handshake",
60016
+ ok: false,
60017
+ detail: parsed ? `window reported protocol ${parsed.protocolVersion}, source ${parsed.sourceSha256}` : `no valid hello (exit ${hello.status})`
60018
+ });
60019
+ return finish();
60020
+ }
60021
+ steps.push({ step: "handshake", ok: true });
60022
+ const manifest = {
60023
+ schemaVersion: 1,
60024
+ protocolVersion: parsed.protocolVersion,
60025
+ sourceSha256: sourceSha,
60026
+ binarySha256: sha256Hex(deps.readFile(stagedBinary)),
60027
+ builtAt: deps.now().toISOString(),
60028
+ compiler
60029
+ };
60030
+ chmodSync3(stagedBinary, 448);
60031
+ renameSync4(stagedBinary, join31(installDir, PERMISSIONS_WINDOW_BINARY));
60032
+ writeFileSync8(
60033
+ join31(installDir, PERMISSIONS_WINDOW_MANIFEST),
60034
+ JSON.stringify(manifest, null, 2) + "\n",
60035
+ { mode: 384 }
60036
+ );
60037
+ steps.push({ step: "install", ok: true, detail: installDir });
60038
+ } finally {
60039
+ rmSync9(staging, { recursive: true, force: true });
60040
+ }
60041
+ const installation = inspectPermissionsWindow(deps);
60042
+ steps.push({
60043
+ step: "verify installation",
60044
+ ok: installation.ready,
60045
+ detail: installation.ready ? void 0 : installation.detail ?? void 0
60046
+ });
60047
+ return finish();
60048
+ }
60049
+ function formatPermissionsWindowBuild(report) {
60050
+ const lines = ["Apple Notes MCP permissions window", ""];
60051
+ for (const step of report.steps)
60052
+ lines.push(`${step.ok ? "\u2713" : "\u2717"} ${step.step}${step.detail ? `: ${step.detail}` : ""}`);
60053
+ lines.push("");
60054
+ if (report.ok)
60055
+ lines.push(
60056
+ `Installed at ${report.installation.binaryPath}. Open it with \`apple-notes-mcp setup --permissions --window\`.`
60057
+ );
60058
+ else if (report.checkOnly) lines.push(`Run \`${PERMISSIONS_WINDOW_SETUP_COMMAND}\` to build it.`);
60059
+ else lines.push("The window was not installed. Fix the failed step above and run setup again.");
60060
+ return lines.join("\n");
60061
+ }
60062
+ var windowMessageSchema = external_exports.discriminatedUnion("type", [
60063
+ external_exports.object({ type: external_exports.literal("open"), id: external_exports.string().max(64) }),
60064
+ external_exports.object({ type: external_exports.literal("recheck") })
60065
+ ]);
60066
+ function runPermissionsWindow(binaryPath, session) {
60067
+ const launch = session.launch ?? ((path10) => spawn2(path10, [], { stdio: ["pipe", "pipe", "inherit"] }));
60068
+ const log = session.log ?? (() => {
60069
+ });
60070
+ let report = session.check();
60071
+ const child2 = launch(binaryPath);
60072
+ const sendReport = () => {
60073
+ if (child2.stdin && !child2.stdin.destroyed)
60074
+ child2.stdin.write(JSON.stringify({ type: "report", report }) + "\n");
60075
+ };
60076
+ child2.stdin?.on("error", () => {
60077
+ });
60078
+ sendReport();
60079
+ if (child2.stdout) {
60080
+ const lines = createInterface2({ input: child2.stdout });
60081
+ lines.on("line", (line) => {
60082
+ let message;
60083
+ try {
60084
+ message = windowMessageSchema.parse(JSON.parse(line));
60085
+ } catch {
60086
+ return;
60087
+ }
60088
+ if (message.type === "recheck") {
60089
+ report = session.check();
60090
+ sendReport();
60091
+ return;
60092
+ }
60093
+ const item = report.items.find((candidate) => candidate.id === message.id);
60094
+ if (!item || item.notProbed) return;
60095
+ const result = session.open(item);
60096
+ log(
60097
+ result.ok ? `Opened ${item.settingsPane}.
60098
+ ` : `Could not open ${item.settingsPane ?? item.title}: ${result.error}
60099
+ `
60100
+ );
60101
+ });
60102
+ }
60103
+ return new Promise((resolveExit) => {
60104
+ child2.on("error", (error2) => {
60105
+ log(`Could not run the permissions window: ${error2.message}
60106
+ `);
60107
+ resolveExit(report.ready ? 0 : 1);
60108
+ });
60109
+ child2.on("close", () => resolveExit(report.ready ? 0 : 1));
60110
+ });
60111
+ }
60112
+
59408
60113
  // src/tools/privateHelperTools.ts
59409
60114
  var coreDataId2 = external_exports.string().regex(/^x-coredata:\/\/[0-9A-F-]+\/ICNote\/p\d+$/i);
59410
60115
  var notesUuid = external_exports.string().regex(UUID_PATTERN);
@@ -59514,6 +60219,733 @@ function registerPrivateHelperTools(server2, manager, depsFactory = () => defaul
59514
60219
  );
59515
60220
  }
59516
60221
 
60222
+ // src/utils/localServer.ts
60223
+ import { randomBytes as randomBytes3, timingSafeEqual } from "node:crypto";
60224
+ import { networkInterfaces } from "node:os";
60225
+ function isTailnetIPv4(address) {
60226
+ const match = /^100\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(address);
60227
+ if (!match) return false;
60228
+ const [second, third, fourth] = match.slice(1).map(Number);
60229
+ return second >= 64 && second <= 127 && third <= 255 && fourth <= 255;
60230
+ }
60231
+ function findTailnetAddress(interfaces = networkInterfaces()) {
60232
+ const candidates = [];
60233
+ for (const [name, infos] of Object.entries(interfaces)) {
60234
+ for (const info of infos ?? []) {
60235
+ if (info.family === "IPv4" && !info.internal && isTailnetIPv4(info.address))
60236
+ candidates.push({ address: info.address, interface: name });
60237
+ }
60238
+ }
60239
+ return candidates.find((c) => c.interface.startsWith("utun")) ?? candidates[0];
60240
+ }
60241
+ function newServerToken() {
60242
+ return randomBytes3(32).toString("hex");
60243
+ }
60244
+ function bearerToken(req) {
60245
+ const auth = req.headers.authorization;
60246
+ return auth?.startsWith("Bearer ") ? auth.slice(7) : void 0;
60247
+ }
60248
+ function tokenMatches(given, token) {
60249
+ if (!given) return false;
60250
+ const a = Buffer.from(given, "utf8");
60251
+ const b = Buffer.from(token, "utf8");
60252
+ return a.length === b.length && timingSafeEqual(a, b);
60253
+ }
60254
+ function hostAuthority(host, port) {
60255
+ return host.includes(":") ? `[${host}]:${port}` : `${host}:${port}`;
60256
+ }
60257
+ function crossOriginRefusal(req, origin, requireOrigin = false) {
60258
+ const site = req.headers["sec-fetch-site"];
60259
+ if (site && site !== "same-origin" && site !== "none") return "cross-site";
60260
+ const requestOrigin = req.headers.origin;
60261
+ if (requestOrigin !== void 0 && requestOrigin !== origin) return "foreign-origin";
60262
+ if (requireOrigin && requestOrigin !== origin) return "missing-origin";
60263
+ return void 0;
60264
+ }
60265
+
60266
+ // src/services/templateEditor.ts
60267
+ import { randomBytes as randomBytes4 } from "node:crypto";
60268
+ import { createServer } from "node:http";
60269
+
60270
+ // src/utils/templateSamples.ts
60271
+ function sampleBlock(text2, style = "body", extra = {}, runs = [{ text: text2 }]) {
60272
+ return {
60273
+ index: 0,
60274
+ start: 0,
60275
+ length: runs.reduce((sum, run) => sum + run.text.length, 0),
60276
+ text: runs.map((run) => run.text).join(""),
60277
+ style,
60278
+ styleType: null,
60279
+ indent: 0,
60280
+ alignment: "left",
60281
+ blockQuote: false,
60282
+ ...extra,
60283
+ runs: runs.map((run) => ({ start: 0, length: run.text.length, ...run })),
60284
+ attachments: []
60285
+ };
60286
+ }
60287
+ var sampleAttachmentRun = (id2, uti) => ({
60288
+ text: "\uFFFC",
60289
+ attachment: { id: id2, uti }
60290
+ });
60291
+ function sampleAttachment(id2, uti, extra = {}) {
60292
+ return { id: id2, pk: 0, uti, kind: classifyUti(uti), children: [], ...extra };
60293
+ }
60294
+ function sampleNote(pk, blocks, attachments = []) {
60295
+ let offset = 0;
60296
+ const indexed = blocks.map((b, index) => {
60297
+ const start = offset;
60298
+ offset += b.length + 1;
60299
+ let at = start;
60300
+ const runs = b.runs.map((run) => {
60301
+ const shifted = { ...run, start: at };
60302
+ at += run.length;
60303
+ return shifted;
60304
+ });
60305
+ const markers2 = runs.flatMap(
60306
+ (run) => run.attachment ? [{ ...run.attachment, start: run.start, blockIndex: index }] : []
60307
+ );
60308
+ return { ...b, index, start, runs, attachments: markers2 };
60309
+ });
60310
+ const markers = indexed.flatMap((b) => b.attachments);
60311
+ return {
60312
+ id: `x-coredata://SAMPLE/ICNote/p${pk}`,
60313
+ title: indexed[0]?.text ?? "",
60314
+ doc: {
60315
+ text: indexed.map((b) => b.text).join("\n"),
60316
+ textLength: offset,
60317
+ blocks: indexed,
60318
+ attachments: markers,
60319
+ undecodedFields: { attributeRun: {}, paragraphStyle: {} },
60320
+ summary: summarize(indexed, markers)
60321
+ },
60322
+ attachments: new Map(attachments.map((a) => [a.id, a])),
60323
+ ordered: attachments.map((a, i) => ({ ...a, pk: i + 1 }))
60324
+ };
60325
+ }
60326
+ function sampleMeta(uuid2) {
60327
+ return {
60328
+ uuid: uuid2,
60329
+ created: "2026-01-05T14:30:00.000Z",
60330
+ modified: "2026-02-10T09:15:00.000Z",
60331
+ folder: "Sample Folder",
60332
+ account: "iCloud"
60333
+ };
60334
+ }
60335
+ function templateSamples() {
60336
+ return [
60337
+ {
60338
+ id: "structure",
60339
+ label: "Headings, lists and checklists",
60340
+ meta: sampleMeta("00000000-0000-4000-8000-000000000001"),
60341
+ note: sampleNote(1, [
60342
+ sampleBlock("Weekly Plan", "title"),
60343
+ sampleBlock("Goals", "heading"),
60344
+ sampleBlock("Details", "subheading"),
60345
+ sampleBlock("A plain paragraph with *characters* that need escaping."),
60346
+ sampleBlock("First step", "numbered"),
60347
+ sampleBlock("Second step", "numbered"),
60348
+ sampleBlock("Nested step", "numbered", { indent: 1 }),
60349
+ sampleBlock("A bullet", "bulleted"),
60350
+ sampleBlock("A dash", "dashed"),
60351
+ sampleBlock("Nested bullet", "bulleted", { indent: 1 }),
60352
+ sampleBlock("Done task", "checklist", { checklist: { id: "sample-a", done: true } }),
60353
+ sampleBlock("Open task", "checklist", { checklist: { id: "sample-b", done: false } }),
60354
+ sampleBlock("A quoted line", "body", { blockQuote: true }),
60355
+ sampleBlock("const answer = 42;", "monospaced"),
60356
+ sampleBlock('console.log("sample");', "monospaced")
60357
+ ])
60358
+ },
60359
+ {
60360
+ id: "inline",
60361
+ label: "Inline formatting and links",
60362
+ meta: sampleMeta("00000000-0000-4000-8000-000000000002"),
60363
+ note: sampleNote(2, [
60364
+ sampleBlock("Formatting Sample", "title"),
60365
+ sampleBlock("", "body", {}, [
60366
+ { text: "Bold", bold: true },
60367
+ { text: ", " },
60368
+ { text: "italic", italic: true },
60369
+ { text: ", " },
60370
+ { text: "both", bold: true, italic: true },
60371
+ { text: ", " },
60372
+ { text: "struck", strikethrough: true },
60373
+ { text: ", " },
60374
+ { text: "underlined", underline: true },
60375
+ { text: "." }
60376
+ ]),
60377
+ sampleBlock("", "body", {}, [
60378
+ { text: "Highlighted", highlight: "purple" },
60379
+ { text: ", x" },
60380
+ { text: "2", superscript: true },
60381
+ { text: ", H" },
60382
+ { text: "2", subscript: true },
60383
+ { text: "O, and " },
60384
+ { text: "red text", color: "#FF3B30" },
60385
+ { text: "." }
60386
+ ]),
60387
+ sampleBlock("", "body", {}, [
60388
+ { text: "A " },
60389
+ { text: "web link", link: "https://example.com/page", linkSafe: true },
60390
+ { text: " and a " },
60391
+ { text: "bold link", link: "https://example.com/bold", linkSafe: true, bold: true },
60392
+ { text: "." }
60393
+ ])
60394
+ ])
60395
+ },
60396
+ {
60397
+ id: "attachments",
60398
+ label: "Attachments and tags",
60399
+ meta: sampleMeta("00000000-0000-4000-8000-000000000003"),
60400
+ note: sampleNote(
60401
+ 3,
60402
+ [
60403
+ sampleBlock("Trip Notes", "title"),
60404
+ sampleBlock("", "body", {}, [
60405
+ { text: "Tagged " },
60406
+ sampleAttachmentRun("SAMPLE-TAG", "com.apple.notes.inlinetextattachment.hashtag"),
60407
+ { text: " for later." }
60408
+ ]),
60409
+ sampleBlock("", "body", {}, [sampleAttachmentRun("SAMPLE-IMG", "public.jpeg")]),
60410
+ sampleBlock("", "body", {}, [sampleAttachmentRun("SAMPLE-PDF", "com.adobe.pdf")]),
60411
+ sampleBlock("", "body", {}, [
60412
+ sampleAttachmentRun("SAMPLE-DIV", "com.apple.notes.inlinetextattachment.dividerline")
60413
+ ]),
60414
+ sampleBlock("", "body", {}, [sampleAttachmentRun("SAMPLE-URL", "public.url")]),
60415
+ sampleBlock("", "body", {}, [sampleAttachmentRun("SAMPLE-IMGURL", "public.url")])
60416
+ ],
60417
+ [
60418
+ sampleAttachment("SAMPLE-TAG", "com.apple.notes.inlinetextattachment.hashtag", {
60419
+ altText: "#travel"
60420
+ }),
60421
+ sampleAttachment("SAMPLE-IMG", "public.jpeg", { title: "harbor.jpg" }),
60422
+ sampleAttachment("SAMPLE-PDF", "com.adobe.pdf", { title: "itinerary.pdf" }),
60423
+ sampleAttachment("SAMPLE-DIV", "com.apple.notes.inlinetextattachment.dividerline"),
60424
+ sampleAttachment("SAMPLE-URL", "public.url", {
60425
+ title: "Example Domain",
60426
+ url: "https://example.com/"
60427
+ }),
60428
+ sampleAttachment("SAMPLE-IMGURL", "public.url", {
60429
+ title: "Harbor at dusk",
60430
+ url: "https://example.com/photos/harbor.jpg"
60431
+ })
60432
+ ]
60433
+ )
60434
+ }
60435
+ ];
60436
+ }
60437
+
60438
+ // src/services/templateEditorPage.ts
60439
+ var EDITOR_STYLE = `
60440
+ :root { color-scheme: light dark; --bg: #fafafa; --fg: #1d1d1f; --muted: #6e6e73;
60441
+ --panel: #ffffff; --border: #d2d2d7; --accent: #0a66d8; --bad: #c1272d; --ok: #1f7a3a; }
60442
+ @media (prefers-color-scheme: dark) { :root { --bg: #1c1c1e; --fg: #f2f2f7; --muted: #a1a1a6;
60443
+ --panel: #2c2c2e; --border: #3a3a3c; --accent: #4c9bff; --bad: #ff6b6b; --ok: #4cd07d; } }
60444
+ * { box-sizing: border-box; }
60445
+ body { margin: 0; font: 14px/1.4 -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
60446
+ background: var(--bg); color: var(--fg); }
60447
+ header { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; padding: 10px 16px;
60448
+ border-bottom: 1px solid var(--border); background: var(--panel); }
60449
+ header h1 { font-size: 15px; margin: 0 12px 0 0; }
60450
+ label { display: inline-flex; gap: 6px; align-items: center; color: var(--muted); }
60451
+ select, input, button, textarea { font: inherit; color: var(--fg); background: var(--bg);
60452
+ border: 1px solid var(--border); border-radius: 6px; padding: 4px 8px; }
60453
+ button { background: var(--accent); color: #fff; border-color: var(--accent); cursor: pointer; }
60454
+ main { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; padding: 12px 16px;
60455
+ height: calc(100vh - 110px); min-height: 360px; }
60456
+ @media (max-width: 800px) { main { grid-template-columns: 1fr; height: auto; } }
60457
+ section { display: flex; flex-direction: column; min-height: 0; min-width: 0; }
60458
+ h2 { font-size: 13px; margin: 0 0 6px; color: var(--muted); font-weight: 600; }
60459
+ textarea { flex: 1; min-height: 300px; width: 100%; resize: none;
60460
+ font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; }
60461
+ pre { flex: 1; margin: 0; overflow: auto; padding: 8px; background: var(--panel);
60462
+ border: 1px solid var(--border); border-radius: 6px; min-height: 300px;
60463
+ font: 12px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; }
60464
+ ul { margin: 6px 0 0; padding-left: 18px; max-height: 120px; overflow: auto; }
60465
+ .bad { color: var(--bad); } .ok { color: var(--ok); } .muted { color: var(--muted); }
60466
+ footer { padding: 0 16px 12px; }
60467
+ `;
60468
+ var EDITOR_SCRIPT = `
60469
+ "use strict";
60470
+ const token = new URLSearchParams(location.search).get("token") || "";
60471
+ const $ = (id) => document.getElementById(id);
60472
+ const api = async (path, body) => {
60473
+ const res = await fetch(path, body === undefined
60474
+ ? { headers: { Authorization: "Bearer " + token } }
60475
+ : { method: "POST", headers: { Authorization: "Bearer " + token,
60476
+ "Content-Type": "application/json" }, body: JSON.stringify(body) });
60477
+ const data = await res.json().catch(() => ({}));
60478
+ if (!res.ok) { const e = new Error((data.error && data.error.message) || res.statusText);
60479
+ e.detail = data.error; throw e; }
60480
+ return data;
60481
+ };
60482
+ const setText = (id, text, cls) => { const el = $(id); el.textContent = text;
60483
+ el.className = cls || ""; };
60484
+ const list = (id, items) => { const ul = $(id); ul.replaceChildren();
60485
+ for (const item of items) { const li = document.createElement("li");
60486
+ li.textContent = item; ul.append(li); } };
60487
+ let timer;
60488
+ async function refresh() {
60489
+ try {
60490
+ const out = await api("/api/preview", { template: $("json").value, sample: $("sample").value });
60491
+ if (out.valid) setText("status", "Valid template.", "ok");
60492
+ else setText("status", out.errors.length + " problem(s):", "bad");
60493
+ list("errors", out.errors.map((e) => e.path + ": " + e.message));
60494
+ list("warnings", out.warnings.map((w) => w.code + (w.attachmentId ? " (" + w.attachmentId + ")" : "")));
60495
+ $("preview").textContent = out.valid ? out.markdown : "";
60496
+ } catch (e) { setText("status", "Preview failed: " + e.message, "bad"); }
60497
+ }
60498
+ const schedule = () => { clearTimeout(timer); timer = setTimeout(refresh, 250); };
60499
+ async function load(name) {
60500
+ try {
60501
+ const out = await api("/api/template?name=" + encodeURIComponent(name));
60502
+ $("json").value = JSON.stringify(out.template, null, 2) + "\\n";
60503
+ $("name").value = out.source === "saved" ? out.name : "";
60504
+ refresh();
60505
+ } catch (e) { setText("status", "Could not open " + name + ": " + e.message, "bad"); }
60506
+ }
60507
+ async function save() {
60508
+ try {
60509
+ const out = await api("/api/save", { name: $("name").value.trim(),
60510
+ template: $("json").value, force: $("force").checked });
60511
+ setText("saved", (out.replaced ? "Replaced " : "Saved ") + out.name + " at " + out.path, "ok");
60512
+ $("force").checked = false;
60513
+ await fillPicker(out.name);
60514
+ } catch (e) {
60515
+ const errors = (e.detail && e.detail.errors) || [];
60516
+ setText("saved", e.message + (errors.length ? " " + errors.map((x) => x.path + ": " + x.message).join("; ") : ""), "bad");
60517
+ }
60518
+ }
60519
+ async function fillPicker(selected) {
60520
+ const state = await api("/api/state");
60521
+ const picker = $("picker"); picker.replaceChildren();
60522
+ const group = (label, names) => { const g = document.createElement("optgroup");
60523
+ g.label = label; for (const n of names) { const o = document.createElement("option");
60524
+ o.value = n; o.textContent = n; g.append(o); } picker.append(g); };
60525
+ group("Built-in", state.builtins);
60526
+ if (state.saved.length) group("Saved", state.saved);
60527
+ picker.value = selected;
60528
+ setText("dir", "Library: " + state.dir, "muted");
60529
+ return state;
60530
+ }
60531
+ (async () => {
60532
+ try {
60533
+ const state = await fillPicker("");
60534
+ const sample = $("sample");
60535
+ for (const s of state.samples) { const o = document.createElement("option");
60536
+ o.value = s.id; o.textContent = s.label; sample.append(o); }
60537
+ $("picker").value = state.initial.name;
60538
+ $("json").value = JSON.stringify(state.initial.template, null, 2) + "\\n";
60539
+ $("name").value = state.initial.source === "saved" ? state.initial.name : "";
60540
+ $("json").addEventListener("input", schedule);
60541
+ sample.addEventListener("change", refresh);
60542
+ $("picker").addEventListener("change", () => load($("picker").value));
60543
+ $("save").addEventListener("click", save);
60544
+ refresh();
60545
+ } catch (e) { setText("status", "Could not start: " + e.message, "bad"); }
60546
+ })();
60547
+ `;
60548
+ var EDITOR_BODY = `
60549
+ <header>
60550
+ <h1>Markdown template editor</h1>
60551
+ <label>Open <select id="picker"></select></label>
60552
+ <label>Preview <select id="sample"></select></label>
60553
+ <span id="dir" class="muted"></span>
60554
+ </header>
60555
+ <main>
60556
+ <section>
60557
+ <h2>Template JSON</h2>
60558
+ <textarea id="json" spellcheck="false" aria-label="Template JSON"></textarea>
60559
+ <div id="status" role="status"></div>
60560
+ <ul id="errors" class="bad"></ul>
60561
+ </section>
60562
+ <section>
60563
+ <h2>Markdown preview</h2>
60564
+ <pre id="preview" aria-label="Markdown preview"></pre>
60565
+ <ul id="warnings" class="muted" aria-label="Warnings"></ul>
60566
+ </section>
60567
+ </main>
60568
+ <footer>
60569
+ <label>Save as <input id="name" placeholder="my-template" maxlength="64" autocomplete="off"></label>
60570
+ <label><input id="force" type="checkbox"> Replace an existing template</label>
60571
+ <button id="save" type="button">Save</button>
60572
+ <span id="saved" role="status"></span>
60573
+ </footer>
60574
+ `;
60575
+ function templateEditorPage(nonce) {
60576
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="referrer" content="no-referrer"><title>Template Editor</title><style nonce="${nonce}">${EDITOR_STYLE}</style></head><body>${EDITOR_BODY}<script nonce="${nonce}">${EDITOR_SCRIPT}</script></body></html>`;
60577
+ }
60578
+
60579
+ // src/services/templateEditor.ts
60580
+ var DEFAULT_EDITOR_IDLE_MS = 30 * 60 * 1e3;
60581
+ var MAX_EDITOR_BODY_BYTES = MAX_TEMPLATE_BYTES * 2 + 4096;
60582
+ var EditorHttpError = class extends Error {
60583
+ constructor(status, code, message, errors) {
60584
+ super(message);
60585
+ this.status = status;
60586
+ this.code = code;
60587
+ this.errors = errors;
60588
+ }
60589
+ status;
60590
+ code;
60591
+ errors;
60592
+ };
60593
+ function editorPresentedToken(req, url) {
60594
+ return bearerToken(req)?.trim() ?? url.searchParams.get("token") ?? void 0;
60595
+ }
60596
+ var CROSS_ORIGIN_MESSAGE = {
60597
+ "cross-site": "Cross-site requests are refused.",
60598
+ "foreign-origin": "Cross-origin requests are refused.",
60599
+ "missing-origin": "POST requires the editor's own Origin."
60600
+ };
60601
+ async function readEditorJson(req) {
60602
+ const type = req.headers["content-type"] ?? "";
60603
+ if (!/^application\/json(?:\s*;|$)/i.test(type))
60604
+ throw new EditorHttpError(415, "unsupported-media-type", "Send JSON (application/json).");
60605
+ const chunks = [];
60606
+ let size = 0;
60607
+ for await (const chunk of req) {
60608
+ size += chunk.length;
60609
+ if (size > MAX_EDITOR_BODY_BYTES)
60610
+ throw new EditorHttpError(413, "too-large", "Request body too large.");
60611
+ chunks.push(chunk);
60612
+ }
60613
+ let parsed;
60614
+ try {
60615
+ parsed = JSON.parse(Buffer.concat(chunks).toString("utf8"));
60616
+ } catch {
60617
+ throw new EditorHttpError(400, "bad-request", "Request body is not valid JSON.");
60618
+ }
60619
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
60620
+ throw new EditorHttpError(400, "bad-request", "Request body must be a JSON object.");
60621
+ return parsed;
60622
+ }
60623
+ var editorStringField = (body, key) => {
60624
+ const value = body[key];
60625
+ if (typeof value !== "string")
60626
+ throw new EditorHttpError(400, "bad-request", `"${key}" must be a string.`);
60627
+ return value;
60628
+ };
60629
+ async function startTemplateEditor(options = {}) {
60630
+ const host = options.host ?? "127.0.0.1";
60631
+ const idleMs = options.idleMs ?? DEFAULT_EDITOR_IDLE_MS;
60632
+ const store = options.store ?? new TemplateStore();
60633
+ const token = options.token ?? newServerToken();
60634
+ const samples = templateSamples();
60635
+ const initialName = options.name ?? "standard-markdown";
60636
+ const initial = isBuiltinTemplate(initialName) ? builtinTemplate(initialName) : store.get(initialName);
60637
+ const initialSource = isBuiltinTemplate(initialName) ? "builtin" : "saved";
60638
+ let origin = "";
60639
+ let hostHeader = "";
60640
+ let idleTimer;
60641
+ let closeReason = "closed";
60642
+ const sockets = /* @__PURE__ */ new Set();
60643
+ const loadTemplate = (name) => {
60644
+ if (isBuiltinTemplate(name))
60645
+ return { name, source: "builtin", template: builtinTemplate(name) };
60646
+ return { name, source: "saved", template: store.get(name) };
60647
+ };
60648
+ const state = () => {
60649
+ const listing = store.list();
60650
+ return {
60651
+ builtins: BUILTIN_TEMPLATE_NAMES,
60652
+ saved: listing.templates.map((t) => t.name),
60653
+ dir: listing.dir,
60654
+ samples: [
60655
+ ...samples.map((s) => ({ id: s.id, label: s.label })),
60656
+ ...options.note ? [{ id: "note", label: `Note: ${options.note.note.title}` }] : []
60657
+ ],
60658
+ initial: { name: initialName, source: initialSource, template: initial }
60659
+ };
60660
+ };
60661
+ const preview = (body) => {
60662
+ const text2 = editorStringField(body, "template");
60663
+ const sampleId = typeof body.sample === "string" ? body.sample : samples[0].id;
60664
+ const source = sampleId === "note" && options.note ? options.note : samples.find((s) => s.id === sampleId);
60665
+ if (!source) throw new EditorHttpError(400, "bad-request", `Unknown sample "${sampleId}".`);
60666
+ let portable;
60667
+ try {
60668
+ portable = parseTemplate(text2);
60669
+ } catch (error2) {
60670
+ if (error2 instanceof TemplateValidationError)
60671
+ return { valid: false, errors: error2.errors, markdown: "", warnings: [] };
60672
+ throw error2;
60673
+ }
60674
+ const result = renderNotesWithTemplate(
60675
+ [source.note],
60676
+ { stats: emptyStats() },
60677
+ {
60678
+ template: resolveTemplate(portable),
60679
+ exportStem: "preview",
60680
+ metaFor: () => source.meta
60681
+ }
60682
+ );
60683
+ return { valid: true, errors: [], markdown: result.markdown, warnings: result.warnings };
60684
+ };
60685
+ const save = (body) => {
60686
+ const name = editorStringField(body, "name");
60687
+ const text2 = editorStringField(body, "template");
60688
+ const force = body.force === true;
60689
+ try {
60690
+ const saved = store.save(name, text2, { force });
60691
+ return { name, path: saved.path, bytes: saved.bytes, replaced: saved.replaced };
60692
+ } catch (error2) {
60693
+ if (error2 instanceof TemplateValidationError)
60694
+ throw new EditorHttpError(
60695
+ 422,
60696
+ "invalid-template",
60697
+ "The template is invalid.",
60698
+ error2.errors
60699
+ );
60700
+ if (error2 instanceof TemplateStoreError && error2.code === "template-exists")
60701
+ throw new EditorHttpError(
60702
+ 409,
60703
+ error2.code,
60704
+ `A template named "${name}" already exists. Tick "Replace an existing template" to replace it.`
60705
+ );
60706
+ if (error2 instanceof TemplateStoreError)
60707
+ throw new EditorHttpError(400, error2.code, error2.message);
60708
+ throw error2;
60709
+ }
60710
+ };
60711
+ const send = (res, status, type, body, extra = {}) => {
60712
+ res.writeHead(status, {
60713
+ "Content-Type": type,
60714
+ "Cache-Control": "no-store",
60715
+ "Referrer-Policy": "no-referrer",
60716
+ "X-Content-Type-Options": "nosniff",
60717
+ "X-Frame-Options": "DENY",
60718
+ "Cross-Origin-Resource-Policy": "same-origin",
60719
+ "Cross-Origin-Opener-Policy": "same-origin",
60720
+ ...extra
60721
+ });
60722
+ res.end(body);
60723
+ };
60724
+ const sendJson = (res, status, value) => send(res, status, "application/json; charset=utf-8", JSON.stringify(value));
60725
+ const touch = () => {
60726
+ if (idleTimer) clearTimeout(idleTimer);
60727
+ if (idleMs > 0)
60728
+ idleTimer = setTimeout(() => {
60729
+ closeReason = "idle";
60730
+ void close();
60731
+ }, idleMs);
60732
+ };
60733
+ const handle = async (req, res) => {
60734
+ if (req.headers.host !== hostHeader)
60735
+ throw new EditorHttpError(421, "wrong-host", "Unexpected Host header.");
60736
+ const url = new URL(req.url ?? "/", origin);
60737
+ if (!tokenMatches(editorPresentedToken(req, url), token))
60738
+ throw new EditorHttpError(401, "unauthorized", "Missing or wrong token.");
60739
+ const refusal = crossOriginRefusal(req, origin, req.method === "POST");
60740
+ if (refusal) throw new EditorHttpError(403, "cross-origin", CROSS_ORIGIN_MESSAGE[refusal]);
60741
+ touch();
60742
+ const route = `${req.method} ${url.pathname}`;
60743
+ switch (route) {
60744
+ case "GET /": {
60745
+ const nonce = randomBytes4(16).toString("base64");
60746
+ return send(res, 200, "text/html; charset=utf-8", templateEditorPage(nonce), {
60747
+ "Content-Security-Policy": `default-src 'none'; script-src 'nonce-${nonce}'; style-src 'nonce-${nonce}'; connect-src 'self'; img-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'`
60748
+ });
60749
+ }
60750
+ case "GET /api/state":
60751
+ return sendJson(res, 200, state());
60752
+ case "GET /api/template": {
60753
+ const name = url.searchParams.get("name") ?? "";
60754
+ try {
60755
+ return sendJson(res, 200, loadTemplate(name));
60756
+ } catch (error2) {
60757
+ if (error2 instanceof TemplateStoreError)
60758
+ throw new EditorHttpError(404, error2.code, error2.message);
60759
+ if (error2 instanceof TemplateValidationError)
60760
+ throw new EditorHttpError(
60761
+ 422,
60762
+ "invalid-template",
60763
+ "The saved template is invalid.",
60764
+ error2.errors
60765
+ );
60766
+ throw error2;
60767
+ }
60768
+ }
60769
+ case "POST /api/preview":
60770
+ return sendJson(res, 200, preview(await readEditorJson(req)));
60771
+ case "POST /api/save":
60772
+ return sendJson(res, 200, save(await readEditorJson(req)));
60773
+ default:
60774
+ throw new EditorHttpError(404, "not-found", "Not found.");
60775
+ }
60776
+ };
60777
+ const server2 = createServer((req, res) => {
60778
+ handle(req, res).catch((error2) => {
60779
+ const http = error2 instanceof EditorHttpError ? error2 : new EditorHttpError(
60780
+ 500,
60781
+ "internal-error",
60782
+ "The editor could not complete the request."
60783
+ );
60784
+ if (!res.headersSent)
60785
+ sendJson(res, http.status, {
60786
+ error: {
60787
+ code: http.code,
60788
+ message: http.message,
60789
+ ...http.errors ? { errors: http.errors } : {}
60790
+ }
60791
+ });
60792
+ else res.end();
60793
+ });
60794
+ });
60795
+ server2.on("connection", (socket) => {
60796
+ sockets.add(socket);
60797
+ socket.on("close", () => sockets.delete(socket));
60798
+ });
60799
+ let resolveClosed;
60800
+ const closed = new Promise((resolve9) => resolveClosed = resolve9);
60801
+ let closing;
60802
+ const close = () => {
60803
+ closing ??= new Promise((resolve9) => {
60804
+ if (idleTimer) clearTimeout(idleTimer);
60805
+ server2.close(() => {
60806
+ resolveClosed(closeReason);
60807
+ resolve9();
60808
+ });
60809
+ for (const socket of sockets) socket.destroy();
60810
+ });
60811
+ return closing;
60812
+ };
60813
+ await new Promise((resolve9, reject) => {
60814
+ server2.once("error", reject);
60815
+ server2.listen({ host, port: options.port ?? 0, exclusive: true }, () => {
60816
+ server2.off("error", reject);
60817
+ resolve9();
60818
+ });
60819
+ });
60820
+ const port = server2.address().port;
60821
+ const authority = hostAuthority(host, port);
60822
+ origin = `http://${authority}`;
60823
+ hostHeader = authority;
60824
+ touch();
60825
+ return { url: `${origin}/?token=${token}`, host, port, token, closed, close };
60826
+ }
60827
+
60828
+ // src/services/templateEditorCli.ts
60829
+ var TEMPLATES_USAGE = `Usage: apple-notes-mcp templates edit [name] [options]
60830
+
60831
+ Open a local web editor for Markdown export templates. The editor validates
60832
+ and previews as you type, against built-in sample notes, and saves into the
60833
+ template library (create-only unless you tick "replace").
60834
+
60835
+ name Template to open: standard-markdown, obsidian, or a saved name
60836
+ --port N Port to listen on (default: a free port)
60837
+ --idle-minutes N Stop after N minutes without a request (default 30; 0: never)
60838
+ --note ID Also preview one real note (x-coredata://.../ICNote/pN), read-only
60839
+ --tailnet Listen on this Mac's Tailscale address instead of 127.0.0.1,
60840
+ so other devices on your tailnet can open the editor.
60841
+ Anyone on the tailnet who has the printed URL can read and save
60842
+ templates, and with --note can read the full body of that note.
60843
+
60844
+ Every request needs the per-run token in the printed URL. Press Ctrl-C to stop.
60845
+ `;
60846
+ function parseTemplatesArgs(argv) {
60847
+ if (argv.length === 0 || argv.includes("--help") || argv.includes("-h")) return "help";
60848
+ const [command, ...rest] = argv;
60849
+ if (command !== "edit") throw new Error(`Unknown templates command "${command}".`);
60850
+ const args = {
60851
+ idleMinutes: DEFAULT_EDITOR_IDLE_MS / 6e4,
60852
+ tailnet: false
60853
+ };
60854
+ const number3 = (flag3, value, max) => {
60855
+ if (value === void 0 || !/^\d+$/.test(value) || Number(value) > max)
60856
+ throw new Error(`${flag3} needs a whole number from 0 to ${max}.`);
60857
+ return Number(value);
60858
+ };
60859
+ for (let i = 0; i < rest.length; i++) {
60860
+ const arg = rest[i];
60861
+ if (arg === "--port") args.port = number3(arg, rest[++i], 65535);
60862
+ else if (arg === "--idle-minutes") args.idleMinutes = number3(arg, rest[++i], 24 * 60);
60863
+ else if (arg === "--note") {
60864
+ const id2 = rest[++i];
60865
+ if (!id2 || !/^x-coredata:\/\/[^/\s]+\/ICNote\/p[0-9]{1,18}$/.test(id2))
60866
+ throw new Error("--note needs a note id such as x-coredata://\u2026/ICNote/p123.");
60867
+ args.noteId = id2;
60868
+ } else if (arg === "--tailnet") args.tailnet = true;
60869
+ else if (arg.startsWith("-")) throw new Error(`Unknown option ${arg}.`);
60870
+ else if (args.name === void 0) args.name = arg;
60871
+ else throw new Error(`Unexpected argument "${arg}".`);
60872
+ }
60873
+ return args;
60874
+ }
60875
+ var editorSignals = (stop) => {
60876
+ process.once("SIGINT", stop);
60877
+ process.once("SIGTERM", stop);
60878
+ return () => {
60879
+ process.off("SIGINT", stop);
60880
+ process.off("SIGTERM", stop);
60881
+ };
60882
+ };
60883
+ function tailnetWarning(host, withNote) {
60884
+ const lines = [
60885
+ `Listening on the tailnet address ${host}. Any device on your tailnet that has this URL can read and save templates. Keep the URL private.`,
60886
+ withNote ? "--note is set: the full body of that note is shown in the preview, so anyone with this URL can read the entire note. Stop the editor when you are done." : "--note exposes the full note body to anyone with this URL, so do not combine it with --tailnet unless you want that."
60887
+ ];
60888
+ return `${lines.join("\n")}
60889
+ `;
60890
+ }
60891
+ async function runTemplatesCommand(argv, deps = {}) {
60892
+ const out = deps.out ?? ((text2) => process.stdout.write(text2));
60893
+ const err = deps.err ?? ((text2) => process.stderr.write(text2));
60894
+ let args;
60895
+ try {
60896
+ args = parseTemplatesArgs(argv);
60897
+ } catch (error2) {
60898
+ err(`${error2.message}
60899
+
60900
+ ${TEMPLATES_USAGE}`);
60901
+ return 2;
60902
+ }
60903
+ if (args === "help") {
60904
+ out(TEMPLATES_USAGE);
60905
+ return 0;
60906
+ }
60907
+ let host = "127.0.0.1";
60908
+ if (args.tailnet) {
60909
+ const found = (deps.tailnetAddress ?? findTailnetAddress)();
60910
+ if (!found) {
60911
+ err(
60912
+ "No Tailscale address found (no 100.64.0.0/10 IPv4 address on this Mac). Connect Tailscale first, or run without --tailnet.\n"
60913
+ );
60914
+ return 1;
60915
+ }
60916
+ host = found.address;
60917
+ }
60918
+ let handle;
60919
+ try {
60920
+ const note = args.noteId ? (deps.readNote ?? ((id2) => ({ note: readExportNote(id2), meta: readExportNoteMeta(id2) })))(args.noteId) : void 0;
60921
+ handle = await (deps.start ?? startTemplateEditor)({
60922
+ host,
60923
+ port: args.port,
60924
+ idleMs: args.idleMinutes * 6e4,
60925
+ name: args.name,
60926
+ note
60927
+ });
60928
+ } catch (error2) {
60929
+ err(`Could not start the template editor: ${error2.message}
60930
+ `);
60931
+ return 1;
60932
+ }
60933
+ out(`Template editor: ${handle.url}
60934
+ `);
60935
+ if (args.tailnet) err(tailnetWarning(handle.host, args.noteId !== void 0));
60936
+ err(
60937
+ args.idleMinutes > 0 ? `Press Ctrl-C to stop. Stops by itself after ${args.idleMinutes} idle minute(s).
60938
+ ` : "Press Ctrl-C to stop.\n"
60939
+ );
60940
+ const unregister = (deps.onSignal ?? editorSignals)(() => void handle.close());
60941
+ const reason = await handle.closed;
60942
+ unregister();
60943
+ err(
60944
+ reason === "idle" ? "Template editor stopped after the idle timeout.\n" : "Template editor stopped.\n"
60945
+ );
60946
+ return 0;
60947
+ }
60948
+
59517
60949
  // src/index.ts
59518
60950
  loadFileConfig();
59519
60951
  var require2 = createRequire(import.meta.url);
@@ -59528,11 +60960,40 @@ if (process.argv[2] === "setup" && process.argv.slice(3).includes("--native-help
59528
60960
  process.stdout.write(formatHelperBuild(report) + "\n");
59529
60961
  process.exit(report.ok ? 0 : 1);
59530
60962
  }
60963
+ if (process.argv[2] === "setup" && process.argv.slice(3).includes("--permissions-window")) {
60964
+ const report = buildPermissionsWindow(process.argv.slice(3).includes("--check"));
60965
+ process.stdout.write(formatPermissionsWindowBuild(report) + "\n");
60966
+ process.exit(report.ok ? 0 : 1);
60967
+ }
60968
+ if (process.argv[2] === "setup" && process.argv.slice(3).includes("--permissions")) {
60969
+ const args = process.argv.slice(3);
60970
+ const options = parsePermissionsArgs(args);
60971
+ const cli = defaultPermissionsCliDeps(options);
60972
+ let code;
60973
+ const window2 = args.includes("--window") ? inspectPermissionsWindow() : null;
60974
+ if (window2?.ready) {
60975
+ code = await runPermissionsWindow(window2.binaryPath, {
60976
+ check: cli.check,
60977
+ open: (item) => openSettingsPane(item),
60978
+ log: cli.write
60979
+ });
60980
+ } else {
60981
+ if (window2) cli.write(`${window2.detail} Showing the checklist here instead.
60982
+
60983
+ `);
60984
+ code = await runPermissionsCli(options, cli);
60985
+ }
60986
+ cli.close();
60987
+ process.exit(code);
60988
+ }
59531
60989
  if (process.argv[2] === "setup") {
59532
60990
  const report = setupShortcuts(process.argv.slice(3).includes("--check"));
59533
60991
  process.stdout.write(formatShortcutSetup(report) + "\n");
59534
60992
  process.exit(report.ready || !report.checkOnly ? 0 : 1);
59535
60993
  }
60994
+ if (process.argv[2] === "templates") {
60995
+ process.exit(await runTemplatesCommand(process.argv.slice(3)));
60996
+ }
59536
60997
  var server = new McpServer({
59537
60998
  name: "apple-notes",
59538
60999
  version: version2,