iwork-mcp 0.3.0 → 0.4.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/dist/tools/keynote.js +21 -0
- package/dist/tools/numbers.js +116 -9
- package/dist/tools/pages.js +44 -18
- package/package.json +1 -1
package/dist/tools/keynote.js
CHANGED
|
@@ -171,6 +171,27 @@ export function registerKeynoteTools(server) {
|
|
|
171
171
|
const masters = doc.masterSlides();
|
|
172
172
|
return JSON.stringify(masters.map((m, i) => ({ index: i, name: m.name() })));
|
|
173
173
|
`, { documentName })));
|
|
174
|
+
server.tool("keynote_reorder_slide", "Move a slide from one position to another", {
|
|
175
|
+
documentName: z.string().describe("Name of the open presentation"),
|
|
176
|
+
fromSlideNumber: z.number().describe("Current slide number (1-based)"),
|
|
177
|
+
toSlideNumber: z.number().describe("Target slide number (1-based)"),
|
|
178
|
+
}, async ({ documentName, fromSlideNumber, toSlideNumber }) => handleJXA(() => runJXA(`
|
|
179
|
+
const app = Application("Keynote");
|
|
180
|
+
const doc = app.documents.byName(params.documentName);
|
|
181
|
+
const slide = doc.slides[params.fromSlideNumber - 1];
|
|
182
|
+
app.move(slide, { to: doc.slides[params.toSlideNumber - 1] });
|
|
183
|
+
return JSON.stringify({ moved: true, from: params.fromSlideNumber, to: params.toSlideNumber, totalSlides: doc.slides.length });
|
|
184
|
+
`, { documentName, fromSlideNumber, toSlideNumber })));
|
|
185
|
+
server.tool("keynote_skip_slide", "Mark a slide as skipped (hidden) or unskipped", {
|
|
186
|
+
documentName: z.string().describe("Name of the open presentation"),
|
|
187
|
+
slideNumber: z.number().describe("Slide number (1-based)"),
|
|
188
|
+
skipped: z.boolean().describe("True to skip/hide, false to unskip/show"),
|
|
189
|
+
}, async ({ documentName, slideNumber, skipped }) => handleJXA(() => runJXA(`
|
|
190
|
+
const app = Application("Keynote");
|
|
191
|
+
const doc = app.documents.byName(params.documentName);
|
|
192
|
+
doc.slides[params.slideNumber - 1].skipped = params.skipped;
|
|
193
|
+
return JSON.stringify({ slideNumber: params.slideNumber, skipped: params.skipped });
|
|
194
|
+
`, { documentName, slideNumber, skipped })));
|
|
174
195
|
server.tool("keynote_stop_slideshow", "Stop a running slideshow", {
|
|
175
196
|
documentName: z.string().describe("Name of the open presentation"),
|
|
176
197
|
}, async ({ documentName }) => handleJXA(() => runJXA(`
|
package/dist/tools/numbers.js
CHANGED
|
@@ -441,18 +441,113 @@ export function registerNumbersTools(server) {
|
|
|
441
441
|
table.columns.push(app.Column());
|
|
442
442
|
return JSON.stringify({ newColumnCount: table.columnCount() });
|
|
443
443
|
`, { documentName, sheetName: sheetName ?? null, tableName: tableName ?? null })));
|
|
444
|
+
server.tool("numbers_read_range", "Read a specific cell range (e.g. 'B2:D10') instead of the entire table. Faster for large tables.", {
|
|
445
|
+
documentName: z.string().describe("Name of the open document"),
|
|
446
|
+
cellRange: z.string().describe("Cell range, e.g. 'A1:C10'"),
|
|
447
|
+
sheetName: z.string().optional().describe("Sheet name (defaults to first sheet)"),
|
|
448
|
+
tableName: z.string().optional().describe("Table name (defaults to first table)"),
|
|
449
|
+
}, async ({ documentName, cellRange, sheetName, tableName }) => handleJXA(() => runJXA(`
|
|
450
|
+
const app = Application("Numbers");
|
|
451
|
+
const doc = app.documents.byName(params.documentName);
|
|
452
|
+
const sheet = params.sheetName ? doc.sheets.byName(params.sheetName) : doc.sheets[0];
|
|
453
|
+
const table = params.tableName ? sheet.tables.byName(params.tableName) : sheet.tables[0];
|
|
454
|
+
const range = table.ranges[params.cellRange];
|
|
455
|
+
const cells = range.cells();
|
|
456
|
+
const values = cells.map(c => c.value());
|
|
457
|
+
|
|
458
|
+
// Figure out the range dimensions from the cell references
|
|
459
|
+
const rangeRef = range.name();
|
|
460
|
+
const colCount = range.columnCount();
|
|
461
|
+
const rowCount = range.rowCount();
|
|
462
|
+
const data = [];
|
|
463
|
+
for (let r = 0; r < rowCount; r++) {
|
|
464
|
+
data.push(values.slice(r * colCount, (r + 1) * colCount));
|
|
465
|
+
}
|
|
466
|
+
return JSON.stringify(data);
|
|
467
|
+
`, { documentName, cellRange, sheetName: sheetName ?? null, tableName: tableName ?? null })));
|
|
468
|
+
server.tool("numbers_merge_cells", "Merge a range of cells", {
|
|
469
|
+
documentName: z.string().describe("Name of the open document"),
|
|
470
|
+
cellRange: z.string().describe("Cell range to merge, e.g. 'A1:C1'"),
|
|
471
|
+
sheetName: z.string().optional().describe("Sheet name (defaults to first sheet)"),
|
|
472
|
+
tableName: z.string().optional().describe("Table name (defaults to first table)"),
|
|
473
|
+
}, async ({ documentName, cellRange, sheetName, tableName }) => handleJXA(() => runJXA(`
|
|
474
|
+
const app = Application("Numbers");
|
|
475
|
+
const doc = app.documents.byName(params.documentName);
|
|
476
|
+
const sheet = params.sheetName ? doc.sheets.byName(params.sheetName) : doc.sheets[0];
|
|
477
|
+
const table = params.tableName ? sheet.tables.byName(params.tableName) : sheet.tables[0];
|
|
478
|
+
const range = table.ranges[params.cellRange];
|
|
479
|
+
range.merge();
|
|
480
|
+
return JSON.stringify({ merged: true, cellRange: params.cellRange });
|
|
481
|
+
`, { documentName, cellRange, sheetName: sheetName ?? null, tableName: tableName ?? null })));
|
|
482
|
+
server.tool("numbers_unmerge_cells", "Unmerge a previously merged cell range", {
|
|
483
|
+
documentName: z.string().describe("Name of the open document"),
|
|
484
|
+
cellRange: z.string().describe("Cell range to unmerge, e.g. 'A1:C1'"),
|
|
485
|
+
sheetName: z.string().optional().describe("Sheet name (defaults to first sheet)"),
|
|
486
|
+
tableName: z.string().optional().describe("Table name (defaults to first table)"),
|
|
487
|
+
}, async ({ documentName, cellRange, sheetName, tableName }) => handleJXA(() => runJXA(`
|
|
488
|
+
const app = Application("Numbers");
|
|
489
|
+
const doc = app.documents.byName(params.documentName);
|
|
490
|
+
const sheet = params.sheetName ? doc.sheets.byName(params.sheetName) : doc.sheets[0];
|
|
491
|
+
const table = params.tableName ? sheet.tables.byName(params.tableName) : sheet.tables[0];
|
|
492
|
+
const range = table.ranges[params.cellRange];
|
|
493
|
+
range.unmerge();
|
|
494
|
+
return JSON.stringify({ unmerged: true, cellRange: params.cellRange });
|
|
495
|
+
`, { documentName, cellRange, sheetName: sheetName ?? null, tableName: tableName ?? null })));
|
|
496
|
+
server.tool("numbers_clear_cells", "Clear the contents of a cell or range", {
|
|
497
|
+
documentName: z.string().describe("Name of the open document"),
|
|
498
|
+
cellRange: z.string().describe("Cell or range to clear, e.g. 'A1' or 'A1:C10'"),
|
|
499
|
+
sheetName: z.string().optional().describe("Sheet name (defaults to first sheet)"),
|
|
500
|
+
tableName: z.string().optional().describe("Table name (defaults to first table)"),
|
|
501
|
+
}, async ({ documentName, cellRange, sheetName, tableName }) => handleJXA(() => runJXA(`
|
|
502
|
+
const app = Application("Numbers");
|
|
503
|
+
const doc = app.documents.byName(params.documentName);
|
|
504
|
+
const sheet = params.sheetName ? doc.sheets.byName(params.sheetName) : doc.sheets[0];
|
|
505
|
+
const table = params.tableName ? sheet.tables.byName(params.tableName) : sheet.tables[0];
|
|
506
|
+
if (params.cellRange.includes(":")) {
|
|
507
|
+
const range = table.ranges[params.cellRange];
|
|
508
|
+
const cells = range.cells();
|
|
509
|
+
for (const cell of cells) { cell.value = null; }
|
|
510
|
+
} else {
|
|
511
|
+
table.cells[params.cellRange].value = null;
|
|
512
|
+
}
|
|
513
|
+
return JSON.stringify({ cleared: true, cellRange: params.cellRange });
|
|
514
|
+
`, { documentName, cellRange, sheetName: sheetName ?? null, tableName: tableName ?? null })));
|
|
515
|
+
server.tool("numbers_sort_rows", "Sort table rows by a column", {
|
|
516
|
+
documentName: z.string().describe("Name of the open document"),
|
|
517
|
+
column: z.string().describe("Column letter to sort by, e.g. 'A'"),
|
|
518
|
+
order: z.enum(["ascending", "descending"]).optional().describe("Sort order (default: ascending)"),
|
|
519
|
+
sheetName: z.string().optional().describe("Sheet name (defaults to first sheet)"),
|
|
520
|
+
tableName: z.string().optional().describe("Table name (defaults to first table)"),
|
|
521
|
+
}, async ({ documentName, column, order, sheetName, tableName }) => handleJXA(() => runJXA(`
|
|
522
|
+
const app = Application("Numbers");
|
|
523
|
+
const doc = app.documents.byName(params.documentName);
|
|
524
|
+
const sheet = params.sheetName ? doc.sheets.byName(params.sheetName) : doc.sheets[0];
|
|
525
|
+
const table = params.tableName ? sheet.tables.byName(params.tableName) : sheet.tables[0];
|
|
526
|
+
const colStr = params.column.toUpperCase();
|
|
527
|
+
let colIndex = 0;
|
|
528
|
+
for (let i = 0; i < colStr.length; i++) {
|
|
529
|
+
colIndex = colIndex * 26 + (colStr.charCodeAt(i) - 64);
|
|
530
|
+
}
|
|
531
|
+
colIndex -= 1;
|
|
532
|
+
const col = table.columns[colIndex];
|
|
533
|
+
const direction = params.order === "descending" ? "descending" : "ascending";
|
|
534
|
+
table.sort({ by: col, direction: direction });
|
|
535
|
+
return JSON.stringify({ sorted: true, column: params.column, order: direction });
|
|
536
|
+
`, { documentName, column, order: order ?? null, sheetName: sheetName ?? null, tableName: tableName ?? null })));
|
|
444
537
|
// ── Formatting Tools ──
|
|
445
|
-
server.tool("numbers_format_cells", "Set formatting on a cell or range: font, size, color, alignment, background color", {
|
|
538
|
+
server.tool("numbers_format_cells", "Set formatting on a cell or range: font, size, color, alignment, background color, bold, italic", {
|
|
446
539
|
documentName: z.string().describe("Name of the open document"),
|
|
447
540
|
cellRange: z.string().describe("Cell or range reference, e.g. 'A1' or 'A1:C3'"),
|
|
448
541
|
format: z.object({
|
|
449
|
-
bold: z.boolean().optional().describe("Set bold"),
|
|
450
|
-
italic: z.boolean().optional().describe("Set italic"),
|
|
542
|
+
bold: z.boolean().optional().describe("Set bold (switches to bold variant of current font)"),
|
|
543
|
+
italic: z.boolean().optional().describe("Set italic (switches to italic variant of current font)"),
|
|
451
544
|
fontSize: z.number().optional().describe("Font size in points"),
|
|
452
545
|
fontName: z.string().optional().describe("Font name"),
|
|
453
546
|
textColor: z.string().optional().describe("Text color as hex, e.g. '#FF0000'"),
|
|
454
547
|
backgroundColor: z.string().optional().describe("Background color as hex, e.g. '#0000FF'"),
|
|
455
548
|
alignment: z.enum(["left", "center", "right", "auto"]).optional().describe("Text alignment"),
|
|
549
|
+
verticalAlignment: z.enum(["top", "center", "bottom"]).optional().describe("Vertical alignment"),
|
|
550
|
+
textWrap: z.boolean().optional().describe("Enable text wrapping"),
|
|
456
551
|
}).describe("Formatting options"),
|
|
457
552
|
sheetName: z.string().optional().describe("Sheet name (defaults to first sheet)"),
|
|
458
553
|
tableName: z.string().optional().describe("Table name (defaults to first table)"),
|
|
@@ -463,7 +558,6 @@ export function registerNumbersTools(server) {
|
|
|
463
558
|
const table = params.tableName ? sheet.tables.byName(params.tableName) : sheet.tables[0];
|
|
464
559
|
const fmt = params.format;
|
|
465
560
|
|
|
466
|
-
// Parse range like "A1:C3" into individual cells
|
|
467
561
|
const rangeStr = params.cellRange;
|
|
468
562
|
let cells = [];
|
|
469
563
|
if (rangeStr.includes(":")) {
|
|
@@ -481,7 +575,6 @@ export function registerNumbersTools(server) {
|
|
|
481
575
|
}
|
|
482
576
|
|
|
483
577
|
for (const cell of cells) {
|
|
484
|
-
if (fmt.bold !== undefined) cell.textColor = cell.textColor; // touch to ensure access
|
|
485
578
|
if (fmt.fontSize !== undefined) cell.fontSize = fmt.fontSize;
|
|
486
579
|
if (fmt.fontName !== undefined) cell.fontName = fmt.fontName;
|
|
487
580
|
if (fmt.textColor !== undefined) {
|
|
@@ -493,11 +586,25 @@ export function registerNumbersTools(server) {
|
|
|
493
586
|
cell.backgroundColor = [r, g, b];
|
|
494
587
|
}
|
|
495
588
|
if (fmt.alignment !== undefined) {
|
|
496
|
-
|
|
497
|
-
|
|
589
|
+
cell.alignment = fmt.alignment;
|
|
590
|
+
}
|
|
591
|
+
if (fmt.verticalAlignment !== undefined) {
|
|
592
|
+
cell.verticalAlignment = fmt.verticalAlignment;
|
|
593
|
+
}
|
|
594
|
+
if (fmt.textWrap !== undefined) {
|
|
595
|
+
cell.textWrap = fmt.textWrap;
|
|
498
596
|
}
|
|
499
|
-
|
|
500
|
-
|
|
597
|
+
// Bold/italic: switch font to bold/italic variant
|
|
598
|
+
if (fmt.bold !== undefined || fmt.italic !== undefined) {
|
|
599
|
+
let fontName = fmt.fontName || cell.fontName();
|
|
600
|
+
const baseName = fontName.replace(/ ?(Bold|Italic|Bold Italic|BoldItalic)$/i, "").trim();
|
|
601
|
+
let suffix = "";
|
|
602
|
+
const wantBold = fmt.bold !== undefined ? fmt.bold : /Bold/i.test(fontName);
|
|
603
|
+
const wantItalic = fmt.italic !== undefined ? fmt.italic : /Italic/i.test(fontName);
|
|
604
|
+
if (wantBold && wantItalic) suffix = " Bold Italic";
|
|
605
|
+
else if (wantBold) suffix = " Bold";
|
|
606
|
+
else if (wantItalic) suffix = " Italic";
|
|
607
|
+
cell.fontName = baseName + suffix;
|
|
501
608
|
}
|
|
502
609
|
}
|
|
503
610
|
|
package/dist/tools/pages.js
CHANGED
|
@@ -109,17 +109,42 @@ export function registerPagesTools(server) {
|
|
|
109
109
|
})));
|
|
110
110
|
`, { documentName })));
|
|
111
111
|
// ── Text Writing Tools ──
|
|
112
|
-
server.tool("pages_add_text", "Append text to the end of the document body", {
|
|
112
|
+
server.tool("pages_add_text", "Append text to the end of the document body (preserves existing formatting)", {
|
|
113
113
|
documentName: z.string().describe("Name of the open document"),
|
|
114
114
|
text: z.string().describe("Text to append"),
|
|
115
115
|
}, async ({ documentName, text }) => handleJXA(() => runJXA(`
|
|
116
116
|
const app = Application("Pages");
|
|
117
117
|
const doc = app.documents.byName(params.documentName);
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
// Append by adding a new paragraph to preserve existing formatting
|
|
119
|
+
const para = app.Paragraph({ text: params.text });
|
|
120
|
+
doc.paragraphs.push(para);
|
|
121
|
+
return JSON.stringify({ appended: true, paragraphCount: doc.paragraphs.length });
|
|
121
122
|
`, { documentName, text })));
|
|
122
|
-
server.tool("
|
|
123
|
+
server.tool("pages_insert_text_at", "Insert text at a specific paragraph index", {
|
|
124
|
+
documentName: z.string().describe("Name of the open document"),
|
|
125
|
+
text: z.string().describe("Text to insert"),
|
|
126
|
+
afterParagraph: z.number().describe("Insert after this paragraph index (0-based). Use -1 to insert at the beginning."),
|
|
127
|
+
}, async ({ documentName, text, afterParagraph }) => handleJXA(() => runJXA(`
|
|
128
|
+
const app = Application("Pages");
|
|
129
|
+
const doc = app.documents.byName(params.documentName);
|
|
130
|
+
const para = app.Paragraph({ text: params.text });
|
|
131
|
+
if (params.afterParagraph < 0) {
|
|
132
|
+
doc.paragraphs.unshift(para);
|
|
133
|
+
} else {
|
|
134
|
+
doc.paragraphs.splice(params.afterParagraph + 1, 0, para);
|
|
135
|
+
}
|
|
136
|
+
return JSON.stringify({ inserted: true, paragraphCount: doc.paragraphs.length });
|
|
137
|
+
`, { documentName, text, afterParagraph })));
|
|
138
|
+
server.tool("pages_delete_text", "Delete a paragraph by index", {
|
|
139
|
+
documentName: z.string().describe("Name of the open document"),
|
|
140
|
+
paragraphIndex: z.number().describe("Paragraph index to delete (0-based)"),
|
|
141
|
+
}, async ({ documentName, paragraphIndex }) => handleJXA(() => runJXA(`
|
|
142
|
+
const app = Application("Pages");
|
|
143
|
+
const doc = app.documents.byName(params.documentName);
|
|
144
|
+
app.delete(doc.paragraphs[params.paragraphIndex]);
|
|
145
|
+
return JSON.stringify({ deleted: true, paragraphCount: doc.paragraphs.length });
|
|
146
|
+
`, { documentName, paragraphIndex })));
|
|
147
|
+
server.tool("pages_replace_text", "Find and replace text in a Pages document (operates per-paragraph to preserve formatting)", {
|
|
123
148
|
documentName: z.string().describe("Name of the open document"),
|
|
124
149
|
find: z.string().describe("Text to find"),
|
|
125
150
|
replace: z.string().describe("Replacement text"),
|
|
@@ -127,23 +152,24 @@ export function registerPagesTools(server) {
|
|
|
127
152
|
}, async ({ documentName, find, replace, all }) => handleJXA(() => runJXA(`
|
|
128
153
|
const app = Application("Pages");
|
|
129
154
|
const doc = app.documents.byName(params.documentName);
|
|
130
|
-
const
|
|
155
|
+
const paragraphs = doc.paragraphs();
|
|
131
156
|
let count = 0;
|
|
132
|
-
let
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
157
|
+
for (let i = 0; i < paragraphs.length; i++) {
|
|
158
|
+
const p = paragraphs[i];
|
|
159
|
+
let text;
|
|
160
|
+
try { text = p.text ? p.text() : ""; } catch(e) { continue; }
|
|
161
|
+
if (text.indexOf(params.find) === -1) continue;
|
|
162
|
+
if (params.all !== false) {
|
|
163
|
+
const parts = text.split(params.find);
|
|
164
|
+
count += parts.length - 1;
|
|
165
|
+
doc.paragraphs[i].text = parts.join(params.replace);
|
|
166
|
+
} else if (count === 0) {
|
|
167
|
+
const idx = text.indexOf(params.find);
|
|
168
|
+
doc.paragraphs[i].text = text.substring(0, idx) + params.replace + text.substring(idx + params.find.length);
|
|
141
169
|
count = 1;
|
|
142
|
-
|
|
143
|
-
newText = current;
|
|
170
|
+
break;
|
|
144
171
|
}
|
|
145
172
|
}
|
|
146
|
-
doc.bodyText = newText;
|
|
147
173
|
return JSON.stringify({ replacements: count });
|
|
148
174
|
`, { documentName, find, replace, all: all ?? true })));
|
|
149
175
|
server.tool("pages_format_text", "Set formatting on a paragraph: font, size, color, bold, italic", {
|