apple-notes-mcp 2.9.11 → 2.9.13
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/README.md +13 -4
- package/build/index.js +297 -156
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -257,7 +257,7 @@ Creates a new note in Apple Notes.
|
|
|
257
257
|
| `content` | string | One of `content`/`contentPath` | The body content of the note (do not repeat the title here) |
|
|
258
258
|
| `contentPath` | string | One of `content`/`contentPath` | Absolute path of a local UTF-8 file to use as the body instead of `content`. Allowed in the same places [`save-attachment`](#save-attachment) may write (home, temp, `/Volumes`); symbolic links, non-regular files, invalid UTF-8 and files over 1 MiB are refused before anything is written. A leading byte-order mark is dropped |
|
|
259
259
|
| `tags` | string[] | No | Returned-only metadata — **NOT written to Notes.app**. Apple Notes tags can't be set via AppleScript, so values passed here are echoed back in the response but do not appear on the created note. Use inline `#hashtags` in `content` instead (Notes.app turns those into real tags). Refused with `format: "markdown"` |
|
|
260
|
-
| `folder` | string | No | Folder to create the note in. Supports nested paths like `"Work/Clients"`. **The folder must already exist** — create it first with [`create-folder`](#create-folder). Defaults to account root |
|
|
260
|
+
| `folder` | string | No | Folder to create the note in. Supports nested paths like `"Work/Clients"`. **The folder must already exist** — create it first with [`create-folder`](#create-folder). A smart folder is refused (see [`move-note`](#move-note)). Defaults to account root |
|
|
261
261
|
| `account` | string | No | Account name (defaults to Notes.app's default account; matched exactly or by a *unique* prefix — an ambiguous prefix is refused). Must be an account Notes.app already has configured — see [`list-accounts`](#list-accounts) |
|
|
262
262
|
| `format` | string | No | Content format: `"plaintext"` (default), `"html"`, or `"markdown"`. In all formats, the title is automatically prepended as the note's title line. In plaintext mode, newlines become `<br>`, tabs become `<br>`, and backslashes are preserved as HTML entities. `"markdown"` produces real Title/Heading/Subheading styles through a Shortcut; see [Markdown notes](#markdown-notes) |
|
|
263
263
|
| `markdownRoute` | string | No | With `format: "markdown"` only: `"shortcut"` (default) or `"html"`. See [Markdown through HTML](#markdown-through-html) |
|
|
@@ -982,6 +982,13 @@ Deletes a note (moves to Recently Deleted in Notes.app).
|
|
|
982
982
|
Title-only deletion is rejected. If the note changed after the supplied hash
|
|
983
983
|
was read, deletion is also rejected.
|
|
984
984
|
|
|
985
|
+
**Notes with large images.** Notes.app returns a note's body with each inline
|
|
986
|
+
image embedded as base64, so a note holding one 40 MB image has a body of about
|
|
987
|
+
110 MB. Body reads accept up to 512 MB of output, and `delete-note` compares a
|
|
988
|
+
body longer than 5 MB against a private temporary file rather than embedding it
|
|
989
|
+
in the AppleScript. The comparison still covers the whole body, and the file
|
|
990
|
+
is removed when the delete finishes.
|
|
991
|
+
|
|
985
992
|
**Copy-then-retire.** To delete an original only while its copy is still good,
|
|
986
993
|
read both notes, verify the copy, and pass the copy as `guardNoteId` with its
|
|
987
994
|
`contentHash` as `expectedGuardContentHash`. The copy's revision is re-read just
|
|
@@ -1025,6 +1032,8 @@ such a note for good, do it in Notes.app.
|
|
|
1025
1032
|
|
|
1026
1033
|
Moves a note to a different folder. The note is relocated in place via Notes.app's native `move`, so its id, creation date, and all embedded attachments (files, images, scans, PDFs, audio) are preserved. The destination folder must already exist — create it first with [`create-folder`](#create-folder).
|
|
1027
1034
|
|
|
1035
|
+
A smart folder is never a destination: it only gathers notes by its rules, and Notes.app would move the note to Recently Deleted or store a created note where no folder shows it. `create-note`, `create-note-with-attachment`, `move-note`, `batch-move-notes`, and `create-folder` refuse one before writing anything, with `code: "unsupported"`, `committed: false`, and `reason: "smart_folder_destination"`. An ordinary folder that shares a smart folder's name is still found. Detection reads the NoteStore database, so it needs Full Disk Access; without it, destinations resolve as before.
|
|
1036
|
+
|
|
1028
1037
|
| Parameter | Type | Required | Description |
|
|
1029
1038
|
|-----------|------|----------|-------------|
|
|
1030
1039
|
| `id` | string | Yes | Exact CoreData note ID returned by a read or search |
|
|
@@ -1395,7 +1404,7 @@ Creates a new folder, including a whole nested hierarchy in one call.
|
|
|
1395
1404
|
|
|
1396
1405
|
| Parameter | Type | Required | Description |
|
|
1397
1406
|
|-----------|------|----------|-------------|
|
|
1398
|
-
| `name` | string | Yes | Folder name, or a nested path separated by `/` (e.g. `"Retro Tech/PC/CPUs"`). Every intermediate folder is created; segments that already exist are skipped |
|
|
1407
|
+
| `name` | string | Yes | Folder name, or a nested path separated by `/` (e.g. `"Retro Tech/PC/CPUs"`). Every intermediate folder is created; segments that already exist are skipped. A segment that names a smart folder is refused before anything is created |
|
|
1399
1408
|
| `account` | string | No | Account to create folder in (defaults to Notes.app's default account; exact or unique-prefix match) |
|
|
1400
1409
|
|
|
1401
1410
|
**Example:**
|
|
@@ -1583,7 +1592,7 @@ Moves multiple notes to a folder.
|
|
|
1583
1592
|
| Parameter | Type | Required | Description |
|
|
1584
1593
|
|-----------|------|----------|-------------|
|
|
1585
1594
|
| `ids` | string[] | Yes | Array of note IDs to move (max 500 per request) |
|
|
1586
|
-
| `folder` | string | Yes | Destination folder name or nested path (e.g., `"Work/Clients"`). Must already exist — create it with [`create-folder`](#create-folder) |
|
|
1595
|
+
| `folder` | string | Yes | Destination folder name or nested path (e.g., `"Work/Clients"`). Must already exist — create it with [`create-folder`](#create-folder). A smart folder is refused for the whole call before any note moves |
|
|
1587
1596
|
| `account` | string | No | Account containing the folder |
|
|
1588
1597
|
|
|
1589
1598
|
**Returns:** Summary of successes and failures. Each success is reported only
|
|
@@ -2462,7 +2471,7 @@ All configuration is optional — the server works out of the box. Override beha
|
|
|
2462
2471
|
|
|
2463
2472
|
| Variable | Default | Description |
|
|
2464
2473
|
|----------|---------|-------------|
|
|
2465
|
-
| `APPLE_NOTES_MCP_MAX_BUFFER` | `67108864` (64 MB) | Max bytes captured from a single AppleScript invocation. Raise it if a very large export/list is truncated; lower it to cap memory. |
|
|
2474
|
+
| `APPLE_NOTES_MCP_MAX_BUFFER` | `67108864` (64 MB) | Max bytes captured from a single AppleScript invocation. Raise it if a very large export/list is truncated; lower it to cap memory. Reading one note body allows at least 512 MB regardless, since a body embeds its inline images. Output past the cap fails with an error naming this variable, not as a timeout. |
|
|
2466
2475
|
| `APPLE_NOTES_MCP_MAX_ATTACHMENT_BYTES` | `26214400` (25 MB) | Max size of an attachment that [`fetch-attachment`](#fetch-attachment) will base64-encode inline. Larger attachments are rejected with an error pointing at [`save-attachment`](#save-attachment) (which streams to disk and has no such limit). Raise it to fetch bigger attachments inline; lower it to cap memory. |
|
|
2467
2476
|
| `APPLE_NOTES_MCP_MAX_INLINE_IMAGE_BYTES` | `262144` (256 KB) | Per-image cap on the base64 payload kept inline in a [`get-note-content`](#get-note-content) response. Inline images over the cap are replaced with placeholders (with a warning appended) so an image-heavy note cannot exceed the MCP client's message limit and drop the connection; export the real files with [`save-attachment`](#save-attachment) or [`fetch-attachment`](#fetch-attachment). Raise it to keep bigger images inline. |
|
|
2468
2477
|
| `APPLE_NOTES_MCP_CONFIG_FILE` | `~/Library/Application Support/apple-notes-mcp/config.json` | Path to the JSON config file (see below). |
|
package/build/index.js
CHANGED
|
@@ -38983,6 +38983,7 @@ var StdioServerTransport = class {
|
|
|
38983
38983
|
|
|
38984
38984
|
// src/utils/applescript.ts
|
|
38985
38985
|
import { execFileSync } from "child_process";
|
|
38986
|
+
import { constants as bufferConstants } from "node:buffer";
|
|
38986
38987
|
|
|
38987
38988
|
// src/utils/docsUrls.ts
|
|
38988
38989
|
var FULL_DISK_ACCESS_GUIDE_URL = "https://github.com/sweetrb/apple-notes-mcp/blob/main/docs/FULL-DISK-ACCESS.md";
|
|
@@ -39020,6 +39021,21 @@ function envPositiveNumber(name) {
|
|
|
39020
39021
|
function getMaxBuffer() {
|
|
39021
39022
|
return envPositiveNumber("APPLE_NOTES_MCP_MAX_BUFFER") ?? DEFAULT_MAX_BUFFER_BYTES;
|
|
39022
39023
|
}
|
|
39024
|
+
var MAX_OUTPUT_BYTES = bufferConstants.MAX_STRING_LENGTH;
|
|
39025
|
+
var NOTE_BODY_MAX_BUFFER_BYTES = Math.min(512 * 1024 * 1024, MAX_OUTPUT_BYTES);
|
|
39026
|
+
function noteBodyMaxBuffer() {
|
|
39027
|
+
return Math.min(Math.max(getMaxBuffer(), NOTE_BODY_MAX_BUFFER_BYTES), MAX_OUTPUT_BYTES);
|
|
39028
|
+
}
|
|
39029
|
+
function isOutputOverflowError(error2) {
|
|
39030
|
+
const code = error2 instanceof Error ? error2.code : void 0;
|
|
39031
|
+
return code === "ENOBUFS" || code === "ERR_STRING_TOO_LONG";
|
|
39032
|
+
}
|
|
39033
|
+
function outputOverflowMessage(maxBufferBytes) {
|
|
39034
|
+
const mib = 1024 * 1024;
|
|
39035
|
+
const limit = maxBufferBytes >= mib ? `${Math.round(maxBufferBytes / mib)} MB` : `${maxBufferBytes} bytes`;
|
|
39036
|
+
const remedy = maxBufferBytes >= MAX_OUTPUT_BYTES ? "That is the longest string Node.js can hold, so raising APPLE_NOTES_MCP_MAX_BUFFER will not help; remove or shrink the large image or attachment in Notes.app." : "Raise APPLE_NOTES_MCP_MAX_BUFFER (in bytes) to allow more.";
|
|
39037
|
+
return `Notes.app returned more than ${limit} of output, the most this server accepts from one AppleScript call. A note whose body carries large inline images or attachments can reach this. ${remedy}`;
|
|
39038
|
+
}
|
|
39023
39039
|
var SCRIPT_TIMEOUT_HEADROOM_MS = 5e3;
|
|
39024
39040
|
var MIN_ATTEMPT_BUDGET_MS = 1e3;
|
|
39025
39041
|
function wrapWithTimeout(script, processTimeoutMs) {
|
|
@@ -39165,6 +39181,7 @@ function executeAppleScript(script, options = {}) {
|
|
|
39165
39181
|
const timeoutMs = options.timeoutMs ?? callTimeoutMs() ?? envPositiveNumber("APPLE_NOTES_MCP_TIMEOUT_MS") ?? DEFAULT_TIMEOUT_MS;
|
|
39166
39182
|
const maxRetries = options.maxRetries ?? envPositiveNumber("APPLE_NOTES_MCP_MAX_RETRIES") ?? DEFAULT_MAX_RETRIES;
|
|
39167
39183
|
const retryDelayMs = options.retryDelayMs ?? envPositiveNumber("APPLE_NOTES_MCP_RETRY_DELAY_MS") ?? DEFAULT_RETRY_DELAY_MS;
|
|
39184
|
+
const maxBufferBytes = Math.min(options.maxBufferBytes ?? getMaxBuffer(), MAX_OUTPUT_BYTES);
|
|
39168
39185
|
if (!script || !script.trim()) {
|
|
39169
39186
|
return {
|
|
39170
39187
|
success: false,
|
|
@@ -39195,7 +39212,7 @@ function executeAppleScript(script, options = {}) {
|
|
|
39195
39212
|
killSignal: "SIGKILL",
|
|
39196
39213
|
// Raise the output cap above Node's 1 MB default so large exports /
|
|
39197
39214
|
// long notes aren't truncated into an ENOBUFS failure. (#16)
|
|
39198
|
-
maxBuffer:
|
|
39215
|
+
maxBuffer: maxBufferBytes
|
|
39199
39216
|
});
|
|
39200
39217
|
const duration3 = Date.now() - attemptStart;
|
|
39201
39218
|
debugLog("AppleScript succeeded", {
|
|
@@ -39213,7 +39230,9 @@ function executeAppleScript(script, options = {}) {
|
|
|
39213
39230
|
let errorMessage;
|
|
39214
39231
|
let isTimeout = false;
|
|
39215
39232
|
let rawError;
|
|
39216
|
-
if (
|
|
39233
|
+
if (isOutputOverflowError(error2)) {
|
|
39234
|
+
errorMessage = outputOverflowMessage(maxBufferBytes);
|
|
39235
|
+
} else if (isTimeoutError(error2)) {
|
|
39217
39236
|
isTimeout = true;
|
|
39218
39237
|
const timeoutSecs = Math.round(timeoutMs / 1e3);
|
|
39219
39238
|
errorMessage = `Operation timed out after ${timeoutSecs} seconds. Notes.app may be unresponsive or the operation involves too many notes.`;
|
|
@@ -39569,6 +39588,77 @@ function getChecklistItems(noteId3) {
|
|
|
39569
39588
|
return { items };
|
|
39570
39589
|
}
|
|
39571
39590
|
|
|
39591
|
+
// src/utils/errorCodes.ts
|
|
39592
|
+
var CodedError = class extends Error {
|
|
39593
|
+
envelope;
|
|
39594
|
+
constructor(message, envelope) {
|
|
39595
|
+
super(message);
|
|
39596
|
+
this.name = "CodedError";
|
|
39597
|
+
this.envelope = envelope;
|
|
39598
|
+
}
|
|
39599
|
+
};
|
|
39600
|
+
var RULES = [
|
|
39601
|
+
{ code: "timeout_indeterminate", pattern: /timed out|\bETIMEDOUT\b/i },
|
|
39602
|
+
{
|
|
39603
|
+
code: "verification_failed",
|
|
39604
|
+
pattern: /\buncertain\b|may have (?:been|succeeded)|\baccepted (?:the|an) \w+, but|\bnot verified\b|Do not retry automatically|before any retry|read (?:the )?(?:exact )?(?:note|ID)\b[^.]*before retr/i
|
|
39605
|
+
},
|
|
39606
|
+
{
|
|
39607
|
+
code: "revision_conflict",
|
|
39608
|
+
pattern: /changed after it was read|revision changed|(?:pinned|note) state changed|changed during (?:read|preflight)|changed; read (?:it|the note) again|rich text do not match/i
|
|
39609
|
+
},
|
|
39610
|
+
{ code: "full_disk_access_missing", pattern: /Full Disk Access/i },
|
|
39611
|
+
{
|
|
39612
|
+
code: "shortcut_not_installed",
|
|
39613
|
+
pattern: /(?:Install|Import) the supplied|no such shortcut|Run apple-notes-mcp setup/i
|
|
39614
|
+
},
|
|
39615
|
+
{
|
|
39616
|
+
code: "not_found",
|
|
39617
|
+
pattern: /not found|does not exist|\bNo \w+ found\b|does not contain any checklist items|Can[’']t get (?:note|folder|account|attachment)|Cannot find note|Scope is absent/i
|
|
39618
|
+
},
|
|
39619
|
+
{ code: "ambiguous", pattern: /ambiguous|more than one|exactly once|Duplicate note IDs/i },
|
|
39620
|
+
{
|
|
39621
|
+
code: "notes_unavailable",
|
|
39622
|
+
pattern: /Notes\.app is (?:not responding|busy)|Lost connection to Notes|isn't running/i
|
|
39623
|
+
},
|
|
39624
|
+
{
|
|
39625
|
+
code: "unsupported",
|
|
39626
|
+
pattern: /password-protected|Locked notes|unsupported|not supported|is blocked|blocked because|has not passed live|refuses to sign|No (?:supported )?background|only one where|Real Notes link unavailable/i
|
|
39627
|
+
},
|
|
39628
|
+
{
|
|
39629
|
+
code: "validation_error",
|
|
39630
|
+
pattern: /\bis required\b|required\b|\bInvalid\b|\bmust\b|\bProvide\b|No (?:note IDs|reviewed notes) provided|response limit|Refusing to write|cannot be resolved|at most \d|between \d+ and \d+|supports the (?:end|default)|Use a distinctive|Use create-table|too large|equal cell counts|Folder path is empty/i
|
|
39631
|
+
}
|
|
39632
|
+
];
|
|
39633
|
+
var NOTHING_WRITTEN = /nothing (?:was )?(?:changed|created|written)|no replacement started|No content was (?:replaced|appended)|Nothing was created/i;
|
|
39634
|
+
var WRITE_ACCEPTED = /\baccepted (?:the|an) \w+, but/i;
|
|
39635
|
+
function classifyError(message, cause) {
|
|
39636
|
+
if (cause instanceof CodedError) return { ...cause.envelope };
|
|
39637
|
+
const text2 = cause instanceof Error && cause.message && !message.includes(cause.message) ? `${message}
|
|
39638
|
+
${cause.message}` : message;
|
|
39639
|
+
const timeoutCause = typeof cause === "object" && cause !== null && cause.code === "ETIMEDOUT";
|
|
39640
|
+
let code = "operation_failed";
|
|
39641
|
+
if (timeoutCause) code = "timeout_indeterminate";
|
|
39642
|
+
else if (isPermissionDenied(text2)) code = "permission_denied";
|
|
39643
|
+
else code = RULES.find((rule) => rule.pattern.test(text2))?.code ?? "operation_failed";
|
|
39644
|
+
const envelope = { code };
|
|
39645
|
+
if (code === "timeout_indeterminate" || code === "verification_failed") {
|
|
39646
|
+
envelope.indeterminate = true;
|
|
39647
|
+
if (WRITE_ACCEPTED.test(text2)) envelope.committed = true;
|
|
39648
|
+
} else if (code === "revision_conflict" || NOTHING_WRITTEN.test(text2)) {
|
|
39649
|
+
envelope.committed = false;
|
|
39650
|
+
envelope.indeterminate = false;
|
|
39651
|
+
}
|
|
39652
|
+
return envelope;
|
|
39653
|
+
}
|
|
39654
|
+
function errorResult(message, cause) {
|
|
39655
|
+
return {
|
|
39656
|
+
content: [{ type: "text", text: message }],
|
|
39657
|
+
structuredContent: classifyError(message, cause),
|
|
39658
|
+
isError: true
|
|
39659
|
+
};
|
|
39660
|
+
}
|
|
39661
|
+
|
|
39572
39662
|
// src/utils/inlineImages.ts
|
|
39573
39663
|
var DEFAULT_MAX_INLINE_IMAGE_BYTES = 256 * 1024;
|
|
39574
39664
|
function maxInlineImageBytes(env = process.env) {
|
|
@@ -42130,8 +42220,8 @@ function scopeConflictMessage(reason) {
|
|
|
42130
42220
|
|
|
42131
42221
|
// src/services/appleNotesManager.ts
|
|
42132
42222
|
var import_turndown = __toESM(require_turndown_cjs(), 1);
|
|
42133
|
-
import { existsSync as existsSync5 } from "fs";
|
|
42134
|
-
import { homedir as homedir8 } from "os";
|
|
42223
|
+
import { existsSync as existsSync5, mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync } from "fs";
|
|
42224
|
+
import { homedir as homedir8, tmpdir as tmpdir2 } from "os";
|
|
42135
42225
|
import { join as join9 } from "path";
|
|
42136
42226
|
var FIELD_SEP = "";
|
|
42137
42227
|
var RECORD_SEP = "";
|
|
@@ -42339,6 +42429,24 @@ function buildFolderReference(folderPath) {
|
|
|
42339
42429
|
}
|
|
42340
42430
|
var AS_ACCOUNT_REF = "__acctRef";
|
|
42341
42431
|
var LIVE_FOLDER_NOT_FOUND = "Folder not found";
|
|
42432
|
+
var SMART_FOLDER_DESTINATION = "SMART_FOLDER_DESTINATION";
|
|
42433
|
+
function readSmartFolderIds(read = readSmartFolders) {
|
|
42434
|
+
return (read().folders ?? []).map((folder) => folder.id).filter((id2) => FOLDER_ID_PATTERN.test(id2));
|
|
42435
|
+
}
|
|
42436
|
+
function smartFolderDestinationError(folderPath) {
|
|
42437
|
+
return new CodedError(
|
|
42438
|
+
`Refused: "${folderPath}" is a smart folder. Smart folders only gather notes by their rules and cannot hold notes or folders; choose an ordinary folder (list-folders). Nothing was changed`,
|
|
42439
|
+
{
|
|
42440
|
+
code: "unsupported",
|
|
42441
|
+
committed: false,
|
|
42442
|
+
indeterminate: false,
|
|
42443
|
+
reason: "smart_folder_destination"
|
|
42444
|
+
}
|
|
42445
|
+
);
|
|
42446
|
+
}
|
|
42447
|
+
function throwIfSmartFolderDestination(error2, folderPath) {
|
|
42448
|
+
if (error2?.includes(SMART_FOLDER_DESTINATION)) throw smartFolderDestinationError(folderPath);
|
|
42449
|
+
}
|
|
42342
42450
|
function buildLiveFolderResolution(folderPath, varName, opts = {}) {
|
|
42343
42451
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(varName)) throw new Error("Invalid AppleScript variable");
|
|
42344
42452
|
buildFolderReference(folderPath);
|
|
@@ -42347,10 +42455,26 @@ function buildLiveFolderResolution(folderPath, varName, opts = {}) {
|
|
|
42347
42455
|
const cid = `${varName}_cid`;
|
|
42348
42456
|
const any = `${varName}_any`;
|
|
42349
42457
|
const parent = `${varName}_p`;
|
|
42350
|
-
const
|
|
42351
|
-
const
|
|
42458
|
+
const quotedPath = escapePlainStringForAppleScript(folderPath);
|
|
42459
|
+
const notFound = `error "${LIVE_FOLDER_NOT_FOUND}: ${quotedPath}" number -1728`;
|
|
42460
|
+
const excluded = (opts.excludeFolderIds ?? []).filter((id2) => FOLDER_ID_PATTERN.test(id2));
|
|
42461
|
+
const skipIds = `${varName}_sx`;
|
|
42462
|
+
const skipped = `${varName}_sh`;
|
|
42463
|
+
const candidateTest = excluded.length ? [
|
|
42464
|
+
` if ${skipIds} contains ${cid} then`,
|
|
42465
|
+
` set ${skipped} to true`,
|
|
42466
|
+
` else if exists folder id ${cid} then`
|
|
42467
|
+
] : [` if exists folder id ${cid} then`];
|
|
42468
|
+
const unresolved = excluded.length ? [
|
|
42469
|
+
`if ${varName} is missing value then`,
|
|
42470
|
+
` if ${skipped} then error "${SMART_FOLDER_DESTINATION}: ${quotedPath}" number -1728`,
|
|
42471
|
+
` ${notFound}`,
|
|
42472
|
+
`end if`
|
|
42473
|
+
] : [`if ${varName} is missing value then ${notFound}`];
|
|
42474
|
+
const lines = excluded.length ? [`set ${skipIds} to {${excluded.map((id2) => `"${id2}"`).join(", ")}}`] : [];
|
|
42352
42475
|
parts.forEach((part, i) => {
|
|
42353
42476
|
const name = escapePlainStringForAppleScript(part);
|
|
42477
|
+
if (excluded.length) lines.push(`set ${skipped} to false`);
|
|
42354
42478
|
if (i === 0) {
|
|
42355
42479
|
lines.push(
|
|
42356
42480
|
`set ${varName} to missing value`,
|
|
@@ -42358,7 +42482,7 @@ function buildLiveFolderResolution(folderPath, varName, opts = {}) {
|
|
|
42358
42482
|
`repeat with ${c} in (folders of ${AS_ACCOUNT_REF} whose name is "${name}")`,
|
|
42359
42483
|
` try`,
|
|
42360
42484
|
` set ${cid} to id of ${c}`,
|
|
42361
|
-
|
|
42485
|
+
...candidateTest,
|
|
42362
42486
|
` if class of (container of ${c}) is not folder then`,
|
|
42363
42487
|
` set ${varName} to folder id ${cid}`,
|
|
42364
42488
|
` exit repeat`,
|
|
@@ -42378,7 +42502,7 @@ function buildLiveFolderResolution(folderPath, varName, opts = {}) {
|
|
|
42378
42502
|
`repeat with ${c} in (folders of ${parent} whose name is "${name}")`,
|
|
42379
42503
|
` try`,
|
|
42380
42504
|
` set ${cid} to id of ${c}`,
|
|
42381
|
-
|
|
42505
|
+
...candidateTest,
|
|
42382
42506
|
` set ${varName} to folder id ${cid}`,
|
|
42383
42507
|
` exit repeat`,
|
|
42384
42508
|
` end if`,
|
|
@@ -42386,7 +42510,7 @@ function buildLiveFolderResolution(folderPath, varName, opts = {}) {
|
|
|
42386
42510
|
`end repeat`
|
|
42387
42511
|
);
|
|
42388
42512
|
}
|
|
42389
|
-
lines.push(
|
|
42513
|
+
lines.push(...unresolved);
|
|
42390
42514
|
});
|
|
42391
42515
|
return lines.join("\n");
|
|
42392
42516
|
}
|
|
@@ -42442,6 +42566,34 @@ function buildAppLevelScript(command) {
|
|
|
42442
42566
|
end tell
|
|
42443
42567
|
`;
|
|
42444
42568
|
}
|
|
42569
|
+
var INLINE_EXPECTED_BODY_LIMIT = MAX_CONTENT_LENGTH;
|
|
42570
|
+
var ExpectedBodies = class {
|
|
42571
|
+
directory;
|
|
42572
|
+
files = 0;
|
|
42573
|
+
/**
|
|
42574
|
+
* Returns AppleScript `setup` to run before the comparison (empty for a
|
|
42575
|
+
* literal) and the `operand` to compare the note's body against.
|
|
42576
|
+
* `variable` names the AppleScript variable a file-backed body is read into.
|
|
42577
|
+
*/
|
|
42578
|
+
bind(body, variable) {
|
|
42579
|
+
if (body.length <= INLINE_EXPECTED_BODY_LIMIT) {
|
|
42580
|
+
return { setup: "", operand: `"${escapeHtmlForAppleScript(body)}"` };
|
|
42581
|
+
}
|
|
42582
|
+
this.directory ??= mkdtempSync2(join9(tmpdir2(), "apple-notes-expected-body-"));
|
|
42583
|
+
const file = join9(this.directory, `body-${this.files++}.html`);
|
|
42584
|
+
writeFileSync(file, body, { encoding: "utf8", mode: 384 });
|
|
42585
|
+
return {
|
|
42586
|
+
setup: `
|
|
42587
|
+
tell current application to set ${variable} to read (POSIX file "${escapePlainStringForAppleScript(file)}") as \xABclass utf8\xBB`,
|
|
42588
|
+
operand: variable
|
|
42589
|
+
};
|
|
42590
|
+
}
|
|
42591
|
+
/** Removes any temporary files. Safe to call more than once. */
|
|
42592
|
+
cleanup() {
|
|
42593
|
+
if (this.directory) rmSync2(this.directory, { recursive: true, force: true });
|
|
42594
|
+
this.directory = void 0;
|
|
42595
|
+
}
|
|
42596
|
+
};
|
|
42445
42597
|
function folderDeleteFactsScript(id2) {
|
|
42446
42598
|
if (!FOLDER_ID_PATTERN.test(id2)) throw new Error("An exact folder ID is required");
|
|
42447
42599
|
return `
|
|
@@ -42619,7 +42771,9 @@ var AppleNotesManager = class {
|
|
|
42619
42771
|
const safeBody = escapeHtmlForAppleScript(`<h1>${htmlTitle}</h1>${bodyContent}`);
|
|
42620
42772
|
let createCommand;
|
|
42621
42773
|
if (folder) {
|
|
42622
|
-
createCommand = `${buildLiveFolderResolution(folder, "__folder"
|
|
42774
|
+
createCommand = `${buildLiveFolderResolution(folder, "__folder", {
|
|
42775
|
+
excludeFolderIds: this.smartFolderIds()
|
|
42776
|
+
})}
|
|
42623
42777
|
make new note at __folder with properties {body:"${safeBody}"}`;
|
|
42624
42778
|
} else {
|
|
42625
42779
|
createCommand = `
|
|
@@ -42631,6 +42785,7 @@ var AppleNotesManager = class {
|
|
|
42631
42785
|
const result = executeMutationAppleScript(script);
|
|
42632
42786
|
if (!result.success) {
|
|
42633
42787
|
throwIfAccountResolutionFailed(result.error);
|
|
42788
|
+
if (folder) throwIfSmartFolderDestination(result.error, folder);
|
|
42634
42789
|
console.error(`Failed to create note "${title}":`, result.error);
|
|
42635
42790
|
return null;
|
|
42636
42791
|
}
|
|
@@ -42803,7 +42958,7 @@ var AppleNotesManager = class {
|
|
|
42803
42958
|
const safeTitle = escapePlainStringForAppleScript(title);
|
|
42804
42959
|
const getCommand = `get body of note "${safeTitle}"`;
|
|
42805
42960
|
const script = buildAccountScopedScript({ account: targetAccount }, getCommand);
|
|
42806
|
-
const result = executeAppleScript(script);
|
|
42961
|
+
const result = executeAppleScript(script, { maxBufferBytes: noteBodyMaxBuffer() });
|
|
42807
42962
|
if (!result.success) {
|
|
42808
42963
|
throwIfAccountResolutionFailed(result.error);
|
|
42809
42964
|
console.error(`Failed to get content of note "${title}":`, result.error);
|
|
@@ -42840,7 +42995,7 @@ var AppleNotesManager = class {
|
|
|
42840
42995
|
const safeId = sanitizeId(id2);
|
|
42841
42996
|
const getCommand = `get body of note id "${safeId}"`;
|
|
42842
42997
|
const script = buildAppLevelScript(getCommand);
|
|
42843
|
-
const result = executeAppleScript(script);
|
|
42998
|
+
const result = executeAppleScript(script, { maxBufferBytes: noteBodyMaxBuffer() });
|
|
42844
42999
|
if (!result.success) {
|
|
42845
43000
|
console.error(`Failed to get content of note with ID "${id2}":`, result.error);
|
|
42846
43001
|
return { body: "", error: result.error };
|
|
@@ -43063,25 +43218,25 @@ var AppleNotesManager = class {
|
|
|
43063
43218
|
*/
|
|
43064
43219
|
deleteNoteByIdIfUnchanged(id2, expectedBody, scope2, guards = []) {
|
|
43065
43220
|
const safeId = sanitizeNoteId(id2);
|
|
43066
|
-
validateLength(expectedBody, MAX_CONTENT_LENGTH, "Expected note content");
|
|
43067
|
-
const safeExpectedBody = escapeHtmlForAppleScript(expectedBody);
|
|
43068
43221
|
const trashIds = trashFolderIdList();
|
|
43069
|
-
const
|
|
43070
|
-
|
|
43071
|
-
|
|
43072
|
-
const
|
|
43073
|
-
|
|
43074
|
-
|
|
43075
|
-
|
|
43076
|
-
|
|
43077
|
-
|
|
43078
|
-
|
|
43222
|
+
const bodies = new ExpectedBodies();
|
|
43223
|
+
let result;
|
|
43224
|
+
try {
|
|
43225
|
+
const guardChecks = guards.map((guard, index) => {
|
|
43226
|
+
const safeGuardId = sanitizeNoteId(guard.id);
|
|
43227
|
+
const ref = `__guardRef${index}`;
|
|
43228
|
+
const folderVar = `__guardFolder${index}`;
|
|
43229
|
+
const inactive = (reason) => `return "SAFETY_GUARD_INACTIVE:${index}:${reason}"`;
|
|
43230
|
+
let bodyCheck = "";
|
|
43231
|
+
if (guard.expectedBody !== void 0) {
|
|
43232
|
+
const expected2 = bodies.bind(guard.expectedBody, `__expectedGuardBody${index}`);
|
|
43233
|
+
bodyCheck = `${expected2.setup}
|
|
43079
43234
|
set __guardBody to body of ${ref}
|
|
43080
43235
|
considering case
|
|
43081
|
-
if __guardBody is not
|
|
43236
|
+
if __guardBody is not ${expected2.operand} and __guardBody is not ${expected2.operand} & linefeed then return "SAFETY_GUARD_CONFLICT:${index}"
|
|
43082
43237
|
end considering`;
|
|
43083
|
-
|
|
43084
|
-
|
|
43238
|
+
}
|
|
43239
|
+
return `
|
|
43085
43240
|
if not (exists note id "${safeGuardId}") then ${inactive("missing")}
|
|
43086
43241
|
set ${ref} to note id "${safeGuardId}"
|
|
43087
43242
|
if password protected of ${ref} then ${inactive("locked")}
|
|
@@ -43091,18 +43246,19 @@ var AppleNotesManager = class {
|
|
|
43091
43246
|
end try
|
|
43092
43247
|
if ${folderVar} is missing value then ${inactive("folder unknown")}${inRecentlyDeletedScript(folderVar, "__guardInTrash", trashIds)}
|
|
43093
43248
|
if __guardInTrash then ${inactive("in Recently Deleted")}${bodyCheck}`;
|
|
43094
|
-
|
|
43095
|
-
|
|
43249
|
+
}).join("");
|
|
43250
|
+
const expected = bodies.bind(expectedBody, "__expectedBody");
|
|
43251
|
+
const script = buildAppLevelScript(`
|
|
43096
43252
|
set noteRef to note id "${safeId}"${buildScopeGuardScript("noteRef", scope2)}
|
|
43097
43253
|
set originalFolder to missing value
|
|
43098
43254
|
try
|
|
43099
43255
|
set originalFolder to container of noteRef
|
|
43100
43256
|
end try
|
|
43101
43257
|
if originalFolder is missing value then return "SAFETY_CONTAINER_UNKNOWN"${inRecentlyDeletedScript("originalFolder", "__inTrash", trashIds)}
|
|
43102
|
-
if __inTrash then return "SAFETY_IN_RECENTLY_DELETED"${guardChecks}
|
|
43258
|
+
if __inTrash then return "SAFETY_IN_RECENTLY_DELETED"${guardChecks}${expected.setup}
|
|
43103
43259
|
set currentBody to body of noteRef
|
|
43104
43260
|
considering case
|
|
43105
|
-
if currentBody is not
|
|
43261
|
+
if currentBody is not ${expected.operand} and currentBody is not ${expected.operand} & linefeed then return "SAFETY_CONFLICT"
|
|
43106
43262
|
delete noteRef
|
|
43107
43263
|
end considering
|
|
43108
43264
|
try
|
|
@@ -43110,7 +43266,10 @@ var AppleNotesManager = class {
|
|
|
43110
43266
|
end try
|
|
43111
43267
|
return "SAFETY_DELETED"
|
|
43112
43268
|
`);
|
|
43113
|
-
|
|
43269
|
+
result = executeMutationAppleScript(script);
|
|
43270
|
+
} finally {
|
|
43271
|
+
bodies.cleanup();
|
|
43272
|
+
}
|
|
43114
43273
|
if (!result.success) {
|
|
43115
43274
|
console.error(`Failed guarded delete for note ID "${id2}":`, result.error);
|
|
43116
43275
|
return { status: "failed" };
|
|
@@ -43231,6 +43390,35 @@ var AppleNotesManager = class {
|
|
|
43231
43390
|
return (resultList as text)${trashSuffix}
|
|
43232
43391
|
`;
|
|
43233
43392
|
}
|
|
43393
|
+
/**
|
|
43394
|
+
* Smart folder ids that a destination folder must never resolve to. Read
|
|
43395
|
+
* fresh on every write (a smart folder can be added at any time); empty
|
|
43396
|
+
* without Full Disk Access. See {@link readSmartFolderIds}.
|
|
43397
|
+
*/
|
|
43398
|
+
smartFolderIds() {
|
|
43399
|
+
return readSmartFolderIds();
|
|
43400
|
+
}
|
|
43401
|
+
/**
|
|
43402
|
+
* Read-only preflight: throws the smart-folder refusal when `folderPath`
|
|
43403
|
+
* resolves in `account` only to a smart folder. For write paths that create
|
|
43404
|
+
* something before they reach their own destination check (the Markdown
|
|
43405
|
+
* bridge creates first and moves second). Other failures are left to the
|
|
43406
|
+
* write itself, which reports them as before.
|
|
43407
|
+
*
|
|
43408
|
+
* @param folderPath - Destination folder path (list-folders syntax)
|
|
43409
|
+
* @param account - Account to resolve in (defaults to Notes' default)
|
|
43410
|
+
*/
|
|
43411
|
+
assertNotSmartFolderDestination(folderPath, account) {
|
|
43412
|
+
const excludeFolderIds = this.smartFolderIds();
|
|
43413
|
+
if (excludeFolderIds.length === 0) return;
|
|
43414
|
+
const script = buildAccountScopedScript(
|
|
43415
|
+
{ account: this.resolveAccount(account) },
|
|
43416
|
+
`${buildLiveFolderResolution(folderPath, "__folder", { excludeFolderIds })}
|
|
43417
|
+
return id of __folder`
|
|
43418
|
+
);
|
|
43419
|
+
const result = executeAppleScript(script);
|
|
43420
|
+
if (!result.success) throwIfSmartFolderDestination(result.error, folderPath);
|
|
43421
|
+
}
|
|
43234
43422
|
/**
|
|
43235
43423
|
* Lists every smart folder with its decoded query, read-only.
|
|
43236
43424
|
*
|
|
@@ -43772,37 +43960,40 @@ var AppleNotesManager = class {
|
|
|
43772
43960
|
console.error(`Invalid folder name: "${name}"`);
|
|
43773
43961
|
return null;
|
|
43774
43962
|
}
|
|
43963
|
+
const smart = { rootOnly: true, excludeFolderIds: this.smartFolderIds() };
|
|
43775
43964
|
for (let i = 0; i < parts.length; i++) {
|
|
43776
43965
|
const currentPath = parts.slice(0, i + 1).map((p) => escapeFolderName(p)).join("/");
|
|
43777
43966
|
const checkScript = buildAccountScopedScript(
|
|
43778
43967
|
{ account: targetAccount },
|
|
43779
|
-
`${buildLiveFolderResolution(currentPath, "__folder",
|
|
43968
|
+
`${buildLiveFolderResolution(currentPath, "__folder", smart)}
|
|
43780
43969
|
return id of __folder`
|
|
43781
43970
|
);
|
|
43782
43971
|
const checkResult = executeAppleScript(checkScript);
|
|
43783
43972
|
if (checkResult.success) {
|
|
43784
43973
|
continue;
|
|
43785
43974
|
}
|
|
43975
|
+
throwIfSmartFolderDestination(checkResult.error, currentPath);
|
|
43786
43976
|
const segmentName = escapePlainStringForAppleScript(parts[i]);
|
|
43787
43977
|
let createCommand;
|
|
43788
43978
|
if (i === 0) {
|
|
43789
43979
|
createCommand = `make new folder with properties {name:"${segmentName}"}`;
|
|
43790
43980
|
} else {
|
|
43791
43981
|
const parentPath = parts.slice(0, i).map((p) => escapeFolderName(p)).join("/");
|
|
43792
|
-
createCommand = `${buildLiveFolderResolution(parentPath, "__parent",
|
|
43982
|
+
createCommand = `${buildLiveFolderResolution(parentPath, "__parent", smart)}
|
|
43793
43983
|
make new folder at __parent with properties {name:"${segmentName}"}`;
|
|
43794
43984
|
}
|
|
43795
43985
|
const script = buildAccountScopedScript({ account: targetAccount }, createCommand);
|
|
43796
43986
|
const result = executeMutationAppleScript(script);
|
|
43797
43987
|
if (!result.success) {
|
|
43798
43988
|
throwIfAccountResolutionFailed(result.error);
|
|
43989
|
+
throwIfSmartFolderDestination(result.error, name);
|
|
43799
43990
|
console.error(`Failed to create folder "${name}":`, result.error);
|
|
43800
43991
|
return null;
|
|
43801
43992
|
}
|
|
43802
43993
|
}
|
|
43803
43994
|
const idScript = buildAccountScopedScript(
|
|
43804
43995
|
{ account: targetAccount },
|
|
43805
|
-
`${buildLiveFolderResolution(name, "__folder",
|
|
43996
|
+
`${buildLiveFolderResolution(name, "__folder", smart)}
|
|
43806
43997
|
return (id of __folder) & ${AS_FIELD_SEP} & (name of it)`
|
|
43807
43998
|
);
|
|
43808
43999
|
const idResult = executeAppleScript(idScript);
|
|
@@ -43861,7 +44052,9 @@ var AppleNotesManager = class {
|
|
|
43861
44052
|
moveNoteById(id2, destinationFolder, account, scope2) {
|
|
43862
44053
|
const targetAccount = this.resolveAccount(account);
|
|
43863
44054
|
const safeId = sanitizeNoteId(id2);
|
|
43864
|
-
const destFolderSetup = buildLiveFolderResolution(destinationFolder, "destFolder"
|
|
44055
|
+
const destFolderSetup = buildLiveFolderResolution(destinationFolder, "destFolder", {
|
|
44056
|
+
excludeFolderIds: this.smartFolderIds()
|
|
44057
|
+
});
|
|
43865
44058
|
const moveCommand = `
|
|
43866
44059
|
${buildAccountResolution(targetAccount)}
|
|
43867
44060
|
${destFolderSetup}
|
|
@@ -43876,6 +44069,7 @@ var AppleNotesManager = class {
|
|
|
43876
44069
|
const result = executeMutationAppleScript(script);
|
|
43877
44070
|
if (!result.success) {
|
|
43878
44071
|
throwIfAccountResolutionFailed(result.error);
|
|
44072
|
+
throwIfSmartFolderDestination(result.error, destinationFolder);
|
|
43879
44073
|
console.error(
|
|
43880
44074
|
`Cannot move note to "${destinationFolder}" (folder may not exist):`,
|
|
43881
44075
|
result.error
|
|
@@ -44770,7 +44964,9 @@ var AppleNotesManager = class {
|
|
|
44770
44964
|
batchMoveNotes(ids, folder, account) {
|
|
44771
44965
|
if (ids.length === 0) return [];
|
|
44772
44966
|
const targetAccount = this.resolveAccount(account);
|
|
44773
|
-
const destFolderSetup = buildLiveFolderResolution(folder, "destFolder"
|
|
44967
|
+
const destFolderSetup = buildLiveFolderResolution(folder, "destFolder", {
|
|
44968
|
+
excludeFolderIds: this.smartFolderIds()
|
|
44969
|
+
});
|
|
44774
44970
|
const results = new Array(ids.length);
|
|
44775
44971
|
const runnable = [];
|
|
44776
44972
|
ids.forEach((id2, i) => {
|
|
@@ -44826,6 +45022,7 @@ var AppleNotesManager = class {
|
|
|
44826
45022
|
const res = executeMutationAppleScript(script);
|
|
44827
45023
|
if (!res.success) {
|
|
44828
45024
|
throwIfAccountResolutionFailed(res.error);
|
|
45025
|
+
throwIfSmartFolderDestination(res.error, folder);
|
|
44829
45026
|
for (const r of runnable) {
|
|
44830
45027
|
results[r.index] = this.createBatchResult(
|
|
44831
45028
|
ids[r.index],
|
|
@@ -47592,8 +47789,8 @@ import { spawnSync } from "child_process";
|
|
|
47592
47789
|
|
|
47593
47790
|
// src/services/nativeTags.ts
|
|
47594
47791
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
47595
|
-
import { mkdtempSync as
|
|
47596
|
-
import { tmpdir as
|
|
47792
|
+
import { mkdtempSync as mkdtempSync3, writeFileSync as writeFileSync2, rmSync as rmSync3 } from "node:fs";
|
|
47793
|
+
import { tmpdir as tmpdir3 } from "node:os";
|
|
47597
47794
|
import { join as join16 } from "node:path";
|
|
47598
47795
|
|
|
47599
47796
|
// src/services/shortcutConsent.ts
|
|
@@ -47696,10 +47893,10 @@ function runNativeTagsShortcut(input) {
|
|
|
47696
47893
|
const status = nativeTagsStatus();
|
|
47697
47894
|
if (!status.installed)
|
|
47698
47895
|
throw new Error(`Import the supplied ${status.shortcut}.shortcut in Shortcuts first`);
|
|
47699
|
-
const directory =
|
|
47896
|
+
const directory = mkdtempSync3(join16(tmpdir3(), "apple-notes-native-tags-"));
|
|
47700
47897
|
try {
|
|
47701
47898
|
const path10 = join16(directory, "request.json");
|
|
47702
|
-
|
|
47899
|
+
writeFileSync2(path10, JSON.stringify(input), { mode: 384 });
|
|
47703
47900
|
execFileSync15("/usr/bin/shortcuts", ["run", status.identifier, "--input-path", path10], {
|
|
47704
47901
|
encoding: "utf8",
|
|
47705
47902
|
timeout: 6e4,
|
|
@@ -47714,14 +47911,14 @@ function runNativeTagsShortcut(input) {
|
|
|
47714
47911
|
{ shortcut: status.shortcut, code: error2?.code }
|
|
47715
47912
|
);
|
|
47716
47913
|
} finally {
|
|
47717
|
-
|
|
47914
|
+
rmSync3(directory, { recursive: true, force: true });
|
|
47718
47915
|
}
|
|
47719
47916
|
}
|
|
47720
47917
|
|
|
47721
47918
|
// src/services/backgroundNotes.ts
|
|
47722
47919
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
47723
|
-
import { mkdtempSync as
|
|
47724
|
-
import { tmpdir as
|
|
47920
|
+
import { mkdtempSync as mkdtempSync4, writeFileSync as writeFileSync3, rmSync as rmSync4 } from "node:fs";
|
|
47921
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
47725
47922
|
import { join as join17 } from "node:path";
|
|
47726
47923
|
|
|
47727
47924
|
// src/utils/appendMarkdown.ts
|
|
@@ -48047,10 +48244,10 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
48047
48244
|
throw new Error(
|
|
48048
48245
|
`Install the supplied "${status.shortcut}" Shortcut once; Shortcuts must list it exactly once`
|
|
48049
48246
|
);
|
|
48050
|
-
const directory =
|
|
48247
|
+
const directory = mkdtempSync4(join17(tmpdir4(), "apple-notes-background-"));
|
|
48051
48248
|
try {
|
|
48052
48249
|
const file = join17(directory, "request.json");
|
|
48053
|
-
|
|
48250
|
+
writeFileSync3(
|
|
48054
48251
|
file,
|
|
48055
48252
|
JSON.stringify({
|
|
48056
48253
|
operation: "",
|
|
@@ -48079,7 +48276,7 @@ function runBackgroundShortcut(input, status = backgroundStatus()) {
|
|
|
48079
48276
|
throw Object.assign(error2, { shortcut: status.shortcut });
|
|
48080
48277
|
}
|
|
48081
48278
|
} finally {
|
|
48082
|
-
|
|
48279
|
+
rmSync4(directory, { recursive: true, force: true });
|
|
48083
48280
|
}
|
|
48084
48281
|
}
|
|
48085
48282
|
function backgroundDependencies(manager) {
|
|
@@ -48307,12 +48504,15 @@ function createMarkdownNote(manager, request, run = runBackgroundShortcut) {
|
|
|
48307
48504
|
const segments = (path10) => JSON.stringify(splitFolderPath(path10).map((part) => part.toLocaleLowerCase()));
|
|
48308
48505
|
if (request.folder) {
|
|
48309
48506
|
const wanted = segments(request.folder);
|
|
48310
|
-
|
|
48311
|
-
|
|
48507
|
+
const accounts = manager.listAccounts().filter((account) => account.defaultFolder);
|
|
48508
|
+
if (!accounts.some(
|
|
48509
|
+
(account) => manager.listFolders(account.name).some((folder) => segments(folder.name) === wanted)
|
|
48312
48510
|
))
|
|
48313
48511
|
throw new Error(
|
|
48314
48512
|
`Folder "${request.folder}" does not exist; create it with create-folder first. Nothing was created`
|
|
48315
48513
|
);
|
|
48514
|
+
for (const account of accounts)
|
|
48515
|
+
manager.assertNotSmartFolderDestination(request.folder, account.name);
|
|
48316
48516
|
}
|
|
48317
48517
|
const defaultFolderNotes = () => new Map(
|
|
48318
48518
|
manager.listAccounts().flatMap(
|
|
@@ -48980,77 +49180,6 @@ function withJsonSchema2020_12(transport2) {
|
|
|
48980
49180
|
return transport2;
|
|
48981
49181
|
}
|
|
48982
49182
|
|
|
48983
|
-
// src/utils/errorCodes.ts
|
|
48984
|
-
var CodedError = class extends Error {
|
|
48985
|
-
envelope;
|
|
48986
|
-
constructor(message, envelope) {
|
|
48987
|
-
super(message);
|
|
48988
|
-
this.name = "CodedError";
|
|
48989
|
-
this.envelope = envelope;
|
|
48990
|
-
}
|
|
48991
|
-
};
|
|
48992
|
-
var RULES = [
|
|
48993
|
-
{ code: "timeout_indeterminate", pattern: /timed out|\bETIMEDOUT\b/i },
|
|
48994
|
-
{
|
|
48995
|
-
code: "verification_failed",
|
|
48996
|
-
pattern: /\buncertain\b|may have (?:been|succeeded)|\baccepted (?:the|an) \w+, but|\bnot verified\b|Do not retry automatically|before any retry|read (?:the )?(?:exact )?(?:note|ID)\b[^.]*before retr/i
|
|
48997
|
-
},
|
|
48998
|
-
{
|
|
48999
|
-
code: "revision_conflict",
|
|
49000
|
-
pattern: /changed after it was read|revision changed|(?:pinned|note) state changed|changed during (?:read|preflight)|changed; read (?:it|the note) again|rich text do not match/i
|
|
49001
|
-
},
|
|
49002
|
-
{ code: "full_disk_access_missing", pattern: /Full Disk Access/i },
|
|
49003
|
-
{
|
|
49004
|
-
code: "shortcut_not_installed",
|
|
49005
|
-
pattern: /(?:Install|Import) the supplied|no such shortcut|Run apple-notes-mcp setup/i
|
|
49006
|
-
},
|
|
49007
|
-
{
|
|
49008
|
-
code: "not_found",
|
|
49009
|
-
pattern: /not found|does not exist|\bNo \w+ found\b|does not contain any checklist items|Can[’']t get (?:note|folder|account|attachment)|Cannot find note|Scope is absent/i
|
|
49010
|
-
},
|
|
49011
|
-
{ code: "ambiguous", pattern: /ambiguous|more than one|exactly once|Duplicate note IDs/i },
|
|
49012
|
-
{
|
|
49013
|
-
code: "notes_unavailable",
|
|
49014
|
-
pattern: /Notes\.app is (?:not responding|busy)|Lost connection to Notes|isn't running/i
|
|
49015
|
-
},
|
|
49016
|
-
{
|
|
49017
|
-
code: "unsupported",
|
|
49018
|
-
pattern: /password-protected|Locked notes|unsupported|not supported|is blocked|blocked because|has not passed live|refuses to sign|No (?:supported )?background|only one where|Real Notes link unavailable/i
|
|
49019
|
-
},
|
|
49020
|
-
{
|
|
49021
|
-
code: "validation_error",
|
|
49022
|
-
pattern: /\bis required\b|required\b|\bInvalid\b|\bmust\b|\bProvide\b|No (?:note IDs|reviewed notes) provided|response limit|Refusing to write|cannot be resolved|at most \d|between \d+ and \d+|supports the (?:end|default)|Use a distinctive|Use create-table|too large|equal cell counts|Folder path is empty/i
|
|
49023
|
-
}
|
|
49024
|
-
];
|
|
49025
|
-
var NOTHING_WRITTEN = /nothing (?:was )?(?:changed|created|written)|no replacement started|No content was (?:replaced|appended)|Nothing was created/i;
|
|
49026
|
-
var WRITE_ACCEPTED = /\baccepted (?:the|an) \w+, but/i;
|
|
49027
|
-
function classifyError(message, cause) {
|
|
49028
|
-
if (cause instanceof CodedError) return { ...cause.envelope };
|
|
49029
|
-
const text2 = cause instanceof Error && cause.message && !message.includes(cause.message) ? `${message}
|
|
49030
|
-
${cause.message}` : message;
|
|
49031
|
-
const timeoutCause = typeof cause === "object" && cause !== null && cause.code === "ETIMEDOUT";
|
|
49032
|
-
let code = "operation_failed";
|
|
49033
|
-
if (timeoutCause) code = "timeout_indeterminate";
|
|
49034
|
-
else if (isPermissionDenied(text2)) code = "permission_denied";
|
|
49035
|
-
else code = RULES.find((rule) => rule.pattern.test(text2))?.code ?? "operation_failed";
|
|
49036
|
-
const envelope = { code };
|
|
49037
|
-
if (code === "timeout_indeterminate" || code === "verification_failed") {
|
|
49038
|
-
envelope.indeterminate = true;
|
|
49039
|
-
if (WRITE_ACCEPTED.test(text2)) envelope.committed = true;
|
|
49040
|
-
} else if (code === "revision_conflict" || NOTHING_WRITTEN.test(text2)) {
|
|
49041
|
-
envelope.committed = false;
|
|
49042
|
-
envelope.indeterminate = false;
|
|
49043
|
-
}
|
|
49044
|
-
return envelope;
|
|
49045
|
-
}
|
|
49046
|
-
function errorResult(message, cause) {
|
|
49047
|
-
return {
|
|
49048
|
-
content: [{ type: "text", text: message }],
|
|
49049
|
-
structuredContent: classifyError(message, cause),
|
|
49050
|
-
isError: true
|
|
49051
|
-
};
|
|
49052
|
-
}
|
|
49053
|
-
|
|
49054
49183
|
// src/utils/shutdown.ts
|
|
49055
49184
|
var SHUTDOWN_DRAIN_TIMEOUT_MS = 2e3;
|
|
49056
49185
|
function createShutdown(stream, exit, timeoutMs = SHUTDOWN_DRAIN_TIMEOUT_MS) {
|
|
@@ -49607,9 +49736,12 @@ function readNoteStructure(id2, { dbPath: dbPath2 = NOTES_DB_PATH7, includeText
|
|
|
49607
49736
|
|
|
49608
49737
|
// src/utils/bodyReadFailure.ts
|
|
49609
49738
|
var LARGE_ATTACHMENT_BYTES = 5 * 1024 * 1024;
|
|
49739
|
+
var OUTPUT_OVERFLOW = /Notes\.app returned more than [\d.]+ (?:MB|bytes) of output/;
|
|
49610
49740
|
function classifyBodyReadError(error2) {
|
|
49611
49741
|
if (!error2) return "other";
|
|
49612
|
-
if (/ENOBUFS|maxBuffer/i.test(error2))
|
|
49742
|
+
if (OUTPUT_OVERFLOW.test(error2) || /ENOBUFS|maxBuffer|ERR_STRING_TOO_LONG/i.test(error2)) {
|
|
49743
|
+
return "buffer";
|
|
49744
|
+
}
|
|
49613
49745
|
if (/timed out|timeout|-1712/i.test(error2)) return "timeout";
|
|
49614
49746
|
return "other";
|
|
49615
49747
|
}
|
|
@@ -49627,10 +49759,17 @@ function describeBodyReadFailure(title, error2, attachments) {
|
|
|
49627
49759
|
const kind = classifyBodyReadError(error2);
|
|
49628
49760
|
if (kind === "other") return base;
|
|
49629
49761
|
const large = attachments ? largeAttachments(attachments) : [];
|
|
49630
|
-
const
|
|
49631
|
-
|
|
49632
|
-
|
|
49633
|
-
|
|
49762
|
+
const listed = large.map((a) => `${a.name}, ${formatBytes2(a.bytes)}`).join("; ");
|
|
49763
|
+
const holds = large.length > 0 ? `The note holds ${large.length === 1 ? "a large attachment" : "large attachments"} (${listed}). ` : "";
|
|
49764
|
+
if (kind === "buffer") {
|
|
49765
|
+
const cause2 = `${holds}Notes.app returns images inside the note body as base64, so a large image makes the body bigger than this server can read in one call.`;
|
|
49766
|
+
const remedy2 = "Retrying, or a longer timeoutSeconds, will not help. Remove or shrink the attachment in Notes.app, or delete the note there; delete-note needs a successful read to verify the note first.";
|
|
49767
|
+
return `${base}
|
|
49768
|
+
|
|
49769
|
+
${cause2} ${remedy2}`;
|
|
49770
|
+
}
|
|
49771
|
+
const cause = large.length > 0 ? `${holds}Notes.app returns images inside the note body as base64, so a large image makes the body too big to read in time.` : "Notes.app returns images inside the note body as base64, so a note with a very large image can take longer to read than the timeout allows.";
|
|
49772
|
+
const remedy = "Retry with a longer timeoutSeconds (up to 120) or raise APPLE_NOTES_MCP_TIMEOUT_MS. If it still fails, delete or shrink the attachment in Notes.app; delete-note needs a successful read to verify the note first.";
|
|
49634
49773
|
return `${base}
|
|
49635
49774
|
|
|
49636
49775
|
${cause} ${remedy}`;
|
|
@@ -51386,13 +51525,13 @@ import {
|
|
|
51386
51525
|
closeSync as closeSync5,
|
|
51387
51526
|
constants as constants5,
|
|
51388
51527
|
fstatSync as fstatSync5,
|
|
51389
|
-
mkdtempSync as
|
|
51528
|
+
mkdtempSync as mkdtempSync5,
|
|
51390
51529
|
openSync as openSync5,
|
|
51391
51530
|
readFileSync as readFileSync3,
|
|
51392
|
-
rmSync as
|
|
51393
|
-
writeFileSync as
|
|
51531
|
+
rmSync as rmSync5,
|
|
51532
|
+
writeFileSync as writeFileSync4
|
|
51394
51533
|
} from "node:fs";
|
|
51395
|
-
import { tmpdir as
|
|
51534
|
+
import { tmpdir as tmpdir5 } from "node:os";
|
|
51396
51535
|
import { basename as basename5, extname as extname5, isAbsolute as isAbsolute3, join as join22 } from "node:path";
|
|
51397
51536
|
var noteId = exactIdInput(
|
|
51398
51537
|
"ICNote",
|
|
@@ -51516,7 +51655,9 @@ function registerDirectOperations(server2, manager) {
|
|
|
51516
51655
|
{
|
|
51517
51656
|
title: external_exports.string().min(1).max(2e3).refine((s) => !/[\r\n\0]/u.test(s), "One-line title"),
|
|
51518
51657
|
content: external_exports.string().min(1).max(1024 * 1024).optional().describe("Optional plain-text body placed above the attachment"),
|
|
51519
|
-
folder: external_exports.string().max(1e3).optional().describe(
|
|
51658
|
+
folder: external_exports.string().max(1e3).optional().describe(
|
|
51659
|
+
"Existing folder or nested path; create it first with create-folder. A smart folder is refused before the note is created"
|
|
51660
|
+
),
|
|
51520
51661
|
account: external_exports.string().max(200).optional().describe("Account name; defaults to Notes' default"),
|
|
51521
51662
|
...attachmentInput
|
|
51522
51663
|
},
|
|
@@ -51633,10 +51774,10 @@ function attachFile(manager, args) {
|
|
|
51633
51774
|
const bytes = localAttachment(path10);
|
|
51634
51775
|
const beforeAttachments = manager.listAttachmentsById(id2);
|
|
51635
51776
|
const beforeStored = storedAttachmentIds(manager, id2);
|
|
51636
|
-
const directory =
|
|
51777
|
+
const directory = mkdtempSync5(join22(tmpdir5(), "notes-attachment-add-"));
|
|
51637
51778
|
const temporaryFile = join22(directory, name);
|
|
51638
51779
|
try {
|
|
51639
|
-
|
|
51780
|
+
writeFileSync4(temporaryFile, bytes, { mode: 384 });
|
|
51640
51781
|
if (readSnapshot(manager, id2).hash !== before.hash) throw new Error("Note revision changed");
|
|
51641
51782
|
let returnedId;
|
|
51642
51783
|
let transportUncertain = false;
|
|
@@ -51696,7 +51837,7 @@ function attachFile(manager, args) {
|
|
|
51696
51837
|
} : {}
|
|
51697
51838
|
};
|
|
51698
51839
|
} finally {
|
|
51699
|
-
|
|
51840
|
+
rmSync5(directory, { recursive: true, force: true });
|
|
51700
51841
|
}
|
|
51701
51842
|
}
|
|
51702
51843
|
|
|
@@ -54686,11 +54827,11 @@ import {
|
|
|
54686
54827
|
chmodSync,
|
|
54687
54828
|
existsSync as existsSync14,
|
|
54688
54829
|
mkdirSync as mkdirSync6,
|
|
54689
|
-
mkdtempSync as
|
|
54830
|
+
mkdtempSync as mkdtempSync6,
|
|
54690
54831
|
readFileSync as readFileSync4,
|
|
54691
54832
|
renameSync,
|
|
54692
|
-
rmSync as
|
|
54693
|
-
writeFileSync as
|
|
54833
|
+
rmSync as rmSync6,
|
|
54834
|
+
writeFileSync as writeFileSync5
|
|
54694
54835
|
} from "node:fs";
|
|
54695
54836
|
import { homedir as homedir19, release as release3 } from "node:os";
|
|
54696
54837
|
import { dirname as dirname8, join as join24, resolve as resolve5 } from "node:path";
|
|
@@ -54708,7 +54849,7 @@ var PUBLIC_HELPER_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
54708
54849
|
"transcribe"
|
|
54709
54850
|
]);
|
|
54710
54851
|
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
54711
|
-
var
|
|
54852
|
+
var MAX_OUTPUT_BYTES2 = 256 * 1024 * 1024;
|
|
54712
54853
|
var publicManifestSchema = external_exports.object({
|
|
54713
54854
|
schemaVersion: external_exports.literal(1),
|
|
54714
54855
|
protocolVersion: external_exports.number().int(),
|
|
@@ -54836,7 +54977,7 @@ function callPublicHelper(action, fields = {}, deps = defaultPublicHelperDeps(),
|
|
|
54836
54977
|
encoding: "utf8",
|
|
54837
54978
|
timeout,
|
|
54838
54979
|
killSignal: "SIGKILL",
|
|
54839
|
-
maxBuffer:
|
|
54980
|
+
maxBuffer: MAX_OUTPUT_BYTES2
|
|
54840
54981
|
});
|
|
54841
54982
|
const errno = result.error?.code;
|
|
54842
54983
|
if (errno === "ETIMEDOUT" || result.signal && result.status === null)
|
|
@@ -54876,7 +55017,7 @@ async function callPublicHelperAsync(action, fields = {}, deps = defaultPublicHe
|
|
|
54876
55017
|
};
|
|
54877
55018
|
child.stdout?.on("data", (chunk) => {
|
|
54878
55019
|
size += chunk.length;
|
|
54879
|
-
if (size >
|
|
55020
|
+
if (size > MAX_OUTPUT_BYTES2)
|
|
54880
55021
|
stop(new PublicHelperError("invalid_response", "The helper response is too large."));
|
|
54881
55022
|
else chunks.push(chunk);
|
|
54882
55023
|
});
|
|
@@ -55044,13 +55185,13 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
|
|
|
55044
55185
|
steps.push({ step: "find compiler", ok: true, detail: compiler });
|
|
55045
55186
|
const installDir = publicHelperInstallDir(deps.env);
|
|
55046
55187
|
mkdirSync6(installDir, { recursive: true, mode: 448 });
|
|
55047
|
-
const staging =
|
|
55188
|
+
const staging = mkdtempSync6(join24(installDir, ".staging-"));
|
|
55048
55189
|
try {
|
|
55049
55190
|
const stagedBinary = join24(staging, PUBLIC_HELPER_BINARY);
|
|
55050
55191
|
const digestPath = join24(staging, "source-digest.swift");
|
|
55051
55192
|
const plistPath = join24(staging, "Info.plist");
|
|
55052
|
-
|
|
55053
|
-
|
|
55193
|
+
writeFileSync5(digestPath, sourceDigestSwift(sourceSha), { mode: 384 });
|
|
55194
|
+
writeFileSync5(plistPath, publicHelperInfoPlist(), { mode: 384 });
|
|
55054
55195
|
const compile = deps.spawn(
|
|
55055
55196
|
"/usr/bin/xcrun",
|
|
55056
55197
|
publicHelperCompileArguments(deps.sourcePath, digestPath, plistPath, stagedBinary),
|
|
@@ -55112,14 +55253,14 @@ function buildPublicHelper(checkOnly, deps = defaultPublicHelperBuildDeps()) {
|
|
|
55112
55253
|
};
|
|
55113
55254
|
chmodSync(stagedBinary, 448);
|
|
55114
55255
|
renameSync(stagedBinary, join24(installDir, PUBLIC_HELPER_BINARY));
|
|
55115
|
-
|
|
55256
|
+
writeFileSync5(
|
|
55116
55257
|
join24(installDir, PUBLIC_HELPER_MANIFEST),
|
|
55117
55258
|
JSON.stringify(manifest, null, 2) + "\n",
|
|
55118
55259
|
{ mode: 384 }
|
|
55119
55260
|
);
|
|
55120
55261
|
steps.push({ step: "install", ok: true, detail: installDir });
|
|
55121
55262
|
} finally {
|
|
55122
|
-
|
|
55263
|
+
rmSync6(staging, { recursive: true, force: true });
|
|
55123
55264
|
}
|
|
55124
55265
|
const installation = inspectPublicHelper(deps);
|
|
55125
55266
|
steps.push({
|
|
@@ -55669,10 +55810,10 @@ import {
|
|
|
55669
55810
|
chmodSync as chmodSync2,
|
|
55670
55811
|
existsSync as existsSync16,
|
|
55671
55812
|
mkdirSync as mkdirSync7,
|
|
55672
|
-
mkdtempSync as
|
|
55813
|
+
mkdtempSync as mkdtempSync7,
|
|
55673
55814
|
renameSync as renameSync2,
|
|
55674
|
-
rmSync as
|
|
55675
|
-
writeFileSync as
|
|
55815
|
+
rmSync as rmSync7,
|
|
55816
|
+
writeFileSync as writeFileSync6
|
|
55676
55817
|
} from "node:fs";
|
|
55677
55818
|
import { release as release4 } from "node:os";
|
|
55678
55819
|
import { join as join27 } from "node:path";
|
|
@@ -55697,7 +55838,7 @@ var HELPER_BINARY_NAME = "apple-notes-private-helper";
|
|
|
55697
55838
|
var HELPER_SOURCE_RELATIVE = "native/private-helper/apple-notes-private-helper.m";
|
|
55698
55839
|
var MANIFEST_NAME = "manifest.json";
|
|
55699
55840
|
var DEFAULT_TIMEOUT_MS3 = 2e4;
|
|
55700
|
-
var
|
|
55841
|
+
var MAX_OUTPUT_BYTES3 = 4 * 1024 * 1024;
|
|
55701
55842
|
var manifestSchema = external_exports.object({
|
|
55702
55843
|
schemaVersion: external_exports.literal(1),
|
|
55703
55844
|
protocolVersion: external_exports.number().int(),
|
|
@@ -55881,7 +56022,7 @@ function callPrivateHelper(action, fields = {}, deps = defaultDeps2(), options =
|
|
|
55881
56022
|
encoding: "utf8",
|
|
55882
56023
|
timeout,
|
|
55883
56024
|
killSignal: "SIGKILL",
|
|
55884
|
-
maxBuffer:
|
|
56025
|
+
maxBuffer: MAX_OUTPUT_BYTES3,
|
|
55885
56026
|
env: deps.env
|
|
55886
56027
|
});
|
|
55887
56028
|
const errno = result.error?.code;
|
|
@@ -56080,7 +56221,7 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
|
|
|
56080
56221
|
steps.push({ step: "find compiler", ok: true, detail: compiler });
|
|
56081
56222
|
const installDir = helperInstallDir(deps.env);
|
|
56082
56223
|
mkdirSync7(installDir, { recursive: true, mode: 448 });
|
|
56083
|
-
const staging =
|
|
56224
|
+
const staging = mkdtempSync7(join27(installDir, ".staging-"));
|
|
56084
56225
|
try {
|
|
56085
56226
|
const stagedBinary = join27(staging, HELPER_BINARY_NAME);
|
|
56086
56227
|
const compile = deps.spawn(
|
|
@@ -56160,12 +56301,12 @@ function buildPrivateHelper(checkOnly, deps = defaultBuildDeps()) {
|
|
|
56160
56301
|
};
|
|
56161
56302
|
chmodSync2(stagedBinary, 448);
|
|
56162
56303
|
renameSync2(stagedBinary, join27(installDir, HELPER_BINARY_NAME));
|
|
56163
|
-
|
|
56304
|
+
writeFileSync6(join27(installDir, MANIFEST_NAME), JSON.stringify(manifest, null, 2) + "\n", {
|
|
56164
56305
|
mode: 384
|
|
56165
56306
|
});
|
|
56166
56307
|
steps.push({ step: "install", ok: true, detail: installDir });
|
|
56167
56308
|
} finally {
|
|
56168
|
-
if (existsSync16(staging))
|
|
56309
|
+
if (existsSync16(staging)) rmSync7(staging, { recursive: true, force: true });
|
|
56169
56310
|
}
|
|
56170
56311
|
const installation = inspectInstallation(deps);
|
|
56171
56312
|
steps.push({
|
|
@@ -56546,7 +56687,7 @@ function registerTool(name, config2, cb) {
|
|
|
56546
56687
|
registerTool(
|
|
56547
56688
|
"create-note",
|
|
56548
56689
|
{
|
|
56549
|
-
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.",
|
|
56690
|
+
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. A smart folder is refused (code unsupported); it cannot hold notes.",
|
|
56550
56691
|
inputSchema: {
|
|
56551
56692
|
title: external_exports.string().min(1, "Title is required").max(MAX.TITLE),
|
|
56552
56693
|
content: external_exports.string().min(1, "Content is required").max(MAX.CONTENT).optional().describe(
|
|
@@ -58038,7 +58179,7 @@ registerTool(
|
|
|
58038
58179
|
registerTool(
|
|
58039
58180
|
"move-note",
|
|
58040
58181
|
{
|
|
58041
|
-
description: "Use when: moving one exact note to a different folder by id.\nReturns: confirmation and exact-ID readback.\nDo not use when: you only have a title or want to move many notes (batch-move-notes).\nNote: Notes.app's native move preserves the note id, creation date, body, and attachments. The destination folder must already exist. Optional ifFolderId, ifAncestorFolderId, and forbiddenAncestorFolderIds (which also covers the destination) are re-checked inside the move AppleScript.",
|
|
58182
|
+
description: "Use when: moving one exact note to a different folder by id.\nReturns: confirmation and exact-ID readback.\nDo not use when: you only have a title or want to move many notes (batch-move-notes).\nNote: Notes.app's native move preserves the note id, creation date, body, and attachments. The destination folder must already exist and cannot be a smart folder (refused with code unsupported before the move). Optional ifFolderId, ifAncestorFolderId, and forbiddenAncestorFolderIds (which also covers the destination) are re-checked inside the move AppleScript.",
|
|
58042
58183
|
inputSchema: {
|
|
58043
58184
|
id: noteIdInput,
|
|
58044
58185
|
folder: external_exports.string().min(1, "Destination folder is required").max(MAX.FOLDER),
|
|
@@ -58280,7 +58421,7 @@ ${lines.join("\n")}${fullyDecoded ? "" : '\n\nSome rules were not recognized; se
|
|
|
58280
58421
|
registerTool(
|
|
58281
58422
|
"create-folder",
|
|
58282
58423
|
{
|
|
58283
|
-
description: "Use when: creating a folder, including nested paths like 'Work/Clients' (intermediate folders are created, existing ones skipped).\nReturns: confirmation.\nDo not use when: creating a note (create-note).",
|
|
58424
|
+
description: "Use when: creating a folder, including nested paths like 'Work/Clients' (intermediate folders are created, existing ones skipped).\nReturns: confirmation.\nNote: a path segment that names a smart folder is refused (code unsupported) before anything is created; smart folders cannot hold folders.\nDo not use when: creating a note (create-note).",
|
|
58284
58425
|
inputSchema: {
|
|
58285
58426
|
name: external_exports.string().min(1, "Folder name is required").max(MAX.FOLDER).describe(
|
|
58286
58427
|
'Folder name or nested path separated by "/". E.g., "Retro Tech/PC/CPUs" creates all intermediate folders. Existing segments are skipped.'
|
|
@@ -58721,7 +58862,7 @@ registerTool(
|
|
|
58721
58862
|
registerTool(
|
|
58722
58863
|
"batch-move-notes",
|
|
58723
58864
|
{
|
|
58724
|
-
description: "Use when: moving multiple notes by id into one destination folder.\nReturns: per-id success/failure counts after destination-folder verification.\nDo not use when: moving a single note (move-note).\nSafety: each moved note's actual container ID is compared with the destination folder ID before success is reported. The destination folder must already exist (create-folder).",
|
|
58865
|
+
description: "Use when: moving multiple notes by id into one destination folder.\nReturns: per-id success/failure counts after destination-folder verification.\nDo not use when: moving a single note (move-note).\nSafety: each moved note's actual container ID is compared with the destination folder ID before success is reported. The destination folder must already exist (create-folder); a smart folder is refused for the whole call (code unsupported) before any note moves.",
|
|
58725
58866
|
inputSchema: {
|
|
58726
58867
|
ids: noteIdArrayInput.describe(
|
|
58727
58868
|
`Array of note IDs to move (max ${MAX.BATCH_IDS} per request)`
|
package/package.json
CHANGED