apple-numbers-mcp 1.2.11 → 1.3.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/README.md +6 -3
- package/build/index.js +59 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -303,7 +303,7 @@ Case-insensitive partial match across every cell in a `.numbers` file.
|
|
|
303
303
|
|
|
304
304
|
Export a table to CSV, TSV, or JSON.
|
|
305
305
|
|
|
306
|
-
**⚠️ Safety:** Writes a file to disk at `outputPath`.
|
|
306
|
+
**⚠️ Safety:** Writes a file to disk at `outputPath`. An existing file there is **refused** unless you pass `overwrite: true` — confirm with the user before passing it. Paths are bounded: `outputPath` must resolve — after `~` expansion and symlink resolution — to a location under your **home directory**, `/tmp`, `/private/tmp`, or `/Volumes`; anything else is rejected with an error naming those roots.
|
|
307
307
|
|
|
308
308
|
| Parameter | Type | Required | Description |
|
|
309
309
|
|-----------|------|----------|-------------|
|
|
@@ -312,6 +312,7 @@ Export a table to CSV, TSV, or JSON.
|
|
|
312
312
|
| `outputPath` | string | Yes | Path for the output file |
|
|
313
313
|
| `sheet` | string | No | Sheet name (default: first sheet) |
|
|
314
314
|
| `table` | string | No | Table name (default: first table) |
|
|
315
|
+
| `overwrite` | boolean | No | Replace an existing file at the destination (default: `false` — refused) |
|
|
315
316
|
|
|
316
317
|
---
|
|
317
318
|
|
|
@@ -321,7 +322,7 @@ Export a table to CSV, TSV, or JSON.
|
|
|
321
322
|
|
|
322
323
|
Create a new `.numbers` file with one sheet and table.
|
|
323
324
|
|
|
324
|
-
**⚠️ Safety:**
|
|
325
|
+
**⚠️ Safety:** An existing file at `path` is **refused** unless you pass `overwrite: true` — confirm with the user before passing it. Paths are bounded: `path` must resolve — after `~` expansion and symlink resolution — to a location under your **home directory**, `/tmp`, `/private/tmp`, or `/Volumes`; anything else is rejected with an error naming those roots.
|
|
325
326
|
|
|
326
327
|
| Parameter | Type | Required | Description |
|
|
327
328
|
|-----------|------|----------|-------------|
|
|
@@ -330,6 +331,7 @@ Create a new `.numbers` file with one sheet and table.
|
|
|
330
331
|
| `rows` | (string \| number \| boolean \| null)[][] | No | Initial data rows |
|
|
331
332
|
| `sheetName` | string | No | Default: `"Sheet 1"` |
|
|
332
333
|
| `tableName` | string | No | Default: `"Table 1"` |
|
|
334
|
+
| `overwrite` | boolean | No | Replace an existing file at the destination (default: `false` — refused) |
|
|
333
335
|
|
|
334
336
|
---
|
|
335
337
|
|
|
@@ -573,7 +575,7 @@ Merge a rectangular range of cells, or undo a merge.
|
|
|
573
575
|
|
|
574
576
|
Import a CSV, TSV, or JSON file into a new `.numbers` spreadsheet. Auto-detects format from extension, or pass `format` explicitly.
|
|
575
577
|
|
|
576
|
-
**⚠️ Safety:**
|
|
578
|
+
**⚠️ Safety:** An existing file at `outputPath` is **refused** unless you pass `overwrite: true` — confirm with the user before passing it. Paths are bounded: both `inputPath` and `outputPath` must resolve — after `~` expansion and symlink resolution — to locations under your **home directory**, `/tmp`, `/private/tmp`, or `/Volumes`; anything else is rejected with an error naming those roots.
|
|
577
579
|
|
|
578
580
|
| Parameter | Type | Required | Description |
|
|
579
581
|
|-----------|------|----------|-------------|
|
|
@@ -582,6 +584,7 @@ Import a CSV, TSV, or JSON file into a new `.numbers` spreadsheet. Auto-detects
|
|
|
582
584
|
| `format` | `"auto" \| "csv" \| "tsv" \| "json"` | No | Default: auto-detect from extension |
|
|
583
585
|
| `sheetName` | string | No | Default: `"Sheet 1"` |
|
|
584
586
|
| `tableName` | string | No | Default: `"Table 1"` |
|
|
587
|
+
| `overwrite` | boolean | No | Replace an existing file at the destination (default: `false` — refused) |
|
|
585
588
|
|
|
586
589
|
**CSV/TSV fields are auto-typed.** Every field from a `csv`/`tsv` source is converted before it is written: `""` → empty cell, `true`/`false` → boolean, and anything Python's `int()`/`float()` parses → number. So a zip code `01234` becomes the number `1234`, `007` becomes `7`, and `1e5` becomes the number `100000`. This is deliberately less conservative than `set-cell`/`add-rows`, which keep leading-zero strings intact, and there is no per-column type control on import. For zero-padded identifiers, import from JSON instead (JSON values are passed through untouched) or repair the column afterwards with `set-cells-batch` using `type: "string"`.
|
|
587
590
|
|
package/build/index.js
CHANGED
|
@@ -37252,7 +37252,7 @@ function unmergeCells(filePath, sheet, table, startRow, startCol, endRow, endCol
|
|
|
37252
37252
|
}
|
|
37253
37253
|
|
|
37254
37254
|
// src/services/numbersManager.ts
|
|
37255
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
37255
|
+
import { existsSync as existsSync2, lstatSync as lstatSync2 } from "node:fs";
|
|
37256
37256
|
import { extname } from "node:path";
|
|
37257
37257
|
|
|
37258
37258
|
// src/utils/exportPath.ts
|
|
@@ -37421,6 +37421,29 @@ var NumbersManager = class {
|
|
|
37421
37421
|
resolveReadPath(filePath) {
|
|
37422
37422
|
return resolveWithinAllowedRoots(filePath, "Input path");
|
|
37423
37423
|
}
|
|
37424
|
+
/**
|
|
37425
|
+
* Refuse to replace an existing file unless the caller opted in.
|
|
37426
|
+
*
|
|
37427
|
+
* `export-table`, `create-spreadsheet` and `import-csv` used to overwrite
|
|
37428
|
+
* whatever was at the destination, so a reused or mistyped path silently
|
|
37429
|
+
* destroyed a real file — for the last two, a real spreadsheet. The roots
|
|
37430
|
+
* boundary limits WHERE they write; this limits WHAT they replace. Checked on
|
|
37431
|
+
* the resolved path with `lstat`, so a dangling symlink also counts as taken.
|
|
37432
|
+
*/
|
|
37433
|
+
assertWritable(resolved, overwrite) {
|
|
37434
|
+
if (overwrite) return;
|
|
37435
|
+
let taken = false;
|
|
37436
|
+
try {
|
|
37437
|
+
lstatSync2(resolved);
|
|
37438
|
+
taken = true;
|
|
37439
|
+
} catch {
|
|
37440
|
+
}
|
|
37441
|
+
if (taken) {
|
|
37442
|
+
throw new Error(
|
|
37443
|
+
`Output file already exists: ${resolved}. Pass overwrite: true to replace it, or choose a new path.`
|
|
37444
|
+
);
|
|
37445
|
+
}
|
|
37446
|
+
}
|
|
37424
37447
|
/**
|
|
37425
37448
|
* Get file structure: sheets, tables, dimensions, headers.
|
|
37426
37449
|
*/
|
|
@@ -37459,9 +37482,10 @@ var NumbersManager = class {
|
|
|
37459
37482
|
/**
|
|
37460
37483
|
* Export a table to CSV, TSV, or JSON file.
|
|
37461
37484
|
*/
|
|
37462
|
-
exportTable(filePath, format, outputPath, sheet, table) {
|
|
37485
|
+
exportTable(filePath, format, outputPath, sheet, table, overwrite) {
|
|
37463
37486
|
const resolved = this.validatePath(filePath);
|
|
37464
37487
|
const outputResolved = this.resolveWritePath(outputPath);
|
|
37488
|
+
this.assertWritable(outputResolved, overwrite);
|
|
37465
37489
|
const args = [resolved, format, outputResolved];
|
|
37466
37490
|
if (sheet) args.push("--sheet", sheet);
|
|
37467
37491
|
if (table) args.push("--table", table);
|
|
@@ -37485,6 +37509,7 @@ var NumbersManager = class {
|
|
|
37485
37509
|
*/
|
|
37486
37510
|
createSpreadsheet(filePath, headers, options) {
|
|
37487
37511
|
const resolved = this.validateOutputPath(filePath);
|
|
37512
|
+
this.assertWritable(resolved, options?.overwrite);
|
|
37488
37513
|
const args = [resolved, JSON.stringify(headers)];
|
|
37489
37514
|
if (options?.sheetName) args.push("--sheet-name", options.sheetName);
|
|
37490
37515
|
if (options?.tableName) args.push("--table-name", options.tableName);
|
|
@@ -37580,6 +37605,7 @@ var NumbersManager = class {
|
|
|
37580
37605
|
throw new Error(`Input file not found: ${inputResolved}`);
|
|
37581
37606
|
}
|
|
37582
37607
|
const outputResolved = this.validateOutputPath(outputPath);
|
|
37608
|
+
this.assertWritable(outputResolved, options?.overwrite);
|
|
37583
37609
|
const args = [inputResolved, outputResolved];
|
|
37584
37610
|
if (options?.format && options.format !== "auto") args.push("--format", options.format);
|
|
37585
37611
|
if (options?.sheetName) args.push("--sheet-name", options.sheetName);
|
|
@@ -38273,13 +38299,16 @@ ${lines.join("\n")}`, {
|
|
|
38273
38299
|
registerTool(
|
|
38274
38300
|
"export-table",
|
|
38275
38301
|
{
|
|
38276
|
-
description: "Use when: you want to write a table's data out to a CSV, TSV, or JSON file on disk. Defaults to the first sheet and first table if sheet/table are omitted.\nReturns: the exported row count, format, and output path.\nDo not use when: you want the data inline in the response rather than a file \u2014 use read-table; or you want to build a new .numbers file from a CSV/TSV/JSON source \u2014 use import-csv.\nSafety: writes a file to disk at outputPath.
|
|
38302
|
+
description: "Use when: you want to write a table's data out to a CSV, TSV, or JSON file on disk. Defaults to the first sheet and first table if sheet/table are omitted.\nReturns: the exported row count, format, and output path.\nDo not use when: you want the data inline in the response rather than a file \u2014 use read-table; or you want to build a new .numbers file from a CSV/TSV/JSON source \u2014 use import-csv.\nSafety: writes a file to disk at outputPath. An existing file at outputPath is refused unless overwrite is true; confirm with the user before passing it. outputPath must resolve \u2014 after ~ expansion and symlink resolution \u2014 to a path under your home directory, /tmp, /private/tmp, or /Volumes; anything else is rejected with an error naming those roots.",
|
|
38277
38303
|
inputSchema: {
|
|
38278
38304
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
38279
38305
|
format: external_exports.enum(["csv", "tsv", "json"]).describe("Output format"),
|
|
38280
38306
|
outputPath: external_exports.string().describe("Path for the output file"),
|
|
38281
38307
|
sheet: external_exports.string().optional().describe("Sheet name (default: first sheet)"),
|
|
38282
|
-
table: external_exports.string().optional().describe("Table name (default: first table)")
|
|
38308
|
+
table: external_exports.string().optional().describe("Table name (default: first table)"),
|
|
38309
|
+
overwrite: external_exports.boolean().optional().describe(
|
|
38310
|
+
"Replace an existing file at the destination (default: false \u2014 an existing file is refused)"
|
|
38311
|
+
)
|
|
38283
38312
|
},
|
|
38284
38313
|
outputSchema: {
|
|
38285
38314
|
outputPath: external_exports.string().optional(),
|
|
@@ -38289,8 +38318,8 @@ registerTool(
|
|
|
38289
38318
|
tableName: external_exports.string().optional()
|
|
38290
38319
|
}
|
|
38291
38320
|
},
|
|
38292
|
-
withErrorHandling(({ path, format, outputPath, sheet, table }) => {
|
|
38293
|
-
const result = manager.exportTable(path, format, outputPath, sheet, table);
|
|
38321
|
+
withErrorHandling(({ path, format, outputPath, sheet, table, overwrite }) => {
|
|
38322
|
+
const result = manager.exportTable(path, format, outputPath, sheet, table, overwrite);
|
|
38294
38323
|
return successResponse(
|
|
38295
38324
|
`Exported ${result.rowCount} rows from "${result.sheetName}/${result.tableName}" to ${result.format.toUpperCase()}: ${result.outputPath}`,
|
|
38296
38325
|
{ ...result }
|
|
@@ -38337,13 +38366,16 @@ Merged: yes`;
|
|
|
38337
38366
|
registerTool(
|
|
38338
38367
|
"create-spreadsheet",
|
|
38339
38368
|
{
|
|
38340
|
-
description: "Use when: you want to create a brand-new .numbers file with a single sheet and table from a list of headers, optionally with initial data rows.\nReturns: the file path, sheet name, table name, header count, and data-row count.\nDo not use when: you want to build the file from an existing CSV/TSV/JSON source \u2014 use import-csv; or you want to add to a file that already exists \u2014 use add-sheet / add-table / add-rows.\nSafety: writes a .numbers file via the numbers-parser sidecar (does not require Numbers.app).
|
|
38369
|
+
description: "Use when: you want to create a brand-new .numbers file with a single sheet and table from a list of headers, optionally with initial data rows.\nReturns: the file path, sheet name, table name, header count, and data-row count.\nDo not use when: you want to build the file from an existing CSV/TSV/JSON source \u2014 use import-csv; or you want to add to a file that already exists \u2014 use add-sheet / add-table / add-rows.\nSafety: writes a .numbers file via the numbers-parser sidecar (does not require Numbers.app). An existing file at path is refused unless overwrite is true; confirm with the user before passing it. path must resolve \u2014 after ~ expansion and symlink resolution \u2014 to a location under your home directory, /tmp, /private/tmp, or /Volumes; anything else is rejected with an error naming those roots.",
|
|
38341
38370
|
inputSchema: {
|
|
38342
38371
|
path: external_exports.string().describe("Path for the new .numbers file"),
|
|
38343
38372
|
headers: external_exports.array(headerCellSchema).min(1).max(MAX_BATCH).describe("Column header names"),
|
|
38344
38373
|
rows: external_exports.array(external_exports.array(external_exports.union([external_exports.string(), external_exports.number(), external_exports.boolean(), external_exports.null()]))).max(MAX_BATCH).optional().describe("Optional data rows (array of arrays)"),
|
|
38345
38374
|
sheetName: external_exports.string().max(MAX_NAME_LEN).optional().describe("Sheet name (default: 'Sheet 1')"),
|
|
38346
|
-
tableName: external_exports.string().max(MAX_NAME_LEN).optional().describe("Table name (default: 'Table 1')")
|
|
38375
|
+
tableName: external_exports.string().max(MAX_NAME_LEN).optional().describe("Table name (default: 'Table 1')"),
|
|
38376
|
+
overwrite: external_exports.boolean().optional().describe(
|
|
38377
|
+
"Replace an existing file at the destination (default: false \u2014 an existing file is refused)"
|
|
38378
|
+
)
|
|
38347
38379
|
},
|
|
38348
38380
|
outputSchema: {
|
|
38349
38381
|
path: external_exports.string().optional(),
|
|
@@ -38353,8 +38385,13 @@ registerTool(
|
|
|
38353
38385
|
numRows: external_exports.number().optional()
|
|
38354
38386
|
}
|
|
38355
38387
|
},
|
|
38356
|
-
withErrorHandling(({ path, headers, rows, sheetName, tableName }) => {
|
|
38357
|
-
const result = manager.createSpreadsheet(path, headers, {
|
|
38388
|
+
withErrorHandling(({ path, headers, rows, sheetName, tableName, overwrite }) => {
|
|
38389
|
+
const result = manager.createSpreadsheet(path, headers, {
|
|
38390
|
+
sheetName,
|
|
38391
|
+
tableName,
|
|
38392
|
+
rows,
|
|
38393
|
+
overwrite
|
|
38394
|
+
});
|
|
38358
38395
|
return successResponse(
|
|
38359
38396
|
`Created ${result.path}
|
|
38360
38397
|
Sheet: "${result.sheetName}" | Table: "${result.tableName}"
|
|
@@ -38543,13 +38580,16 @@ registerTool(
|
|
|
38543
38580
|
registerTool(
|
|
38544
38581
|
"import-csv",
|
|
38545
38582
|
{
|
|
38546
|
-
description: 'Use when: you want to convert an existing CSV, TSV, or JSON file into a new .numbers spreadsheet. Format is auto-detected from the input file extension unless you specify it explicitly; any unrecognized extension falls back to CSV. CSV/TSV fields are auto-typed (numbers, booleans, empty cells), so zero-padded identifiers LOSE their leading zeros \u2014 "01234" becomes 1234. JSON values are passed through untouched, but an array of objects takes its column set from the FIRST object only, silently dropping keys that appear later.\nReturns: the imported row and column counts, detected format, and the input/output paths plus sheet/table names.\nDo not use when: building a file from headers/rows you already have in hand \u2014 use create-spreadsheet; or exporting a .numbers table out to CSV/TSV/JSON \u2014 use export-table.\nSafety: writes the output .numbers file via the numbers-parser sidecar (does not require Numbers.app).
|
|
38583
|
+
description: 'Use when: you want to convert an existing CSV, TSV, or JSON file into a new .numbers spreadsheet. Format is auto-detected from the input file extension unless you specify it explicitly; any unrecognized extension falls back to CSV. CSV/TSV fields are auto-typed (numbers, booleans, empty cells), so zero-padded identifiers LOSE their leading zeros \u2014 "01234" becomes 1234. JSON values are passed through untouched, but an array of objects takes its column set from the FIRST object only, silently dropping keys that appear later.\nReturns: the imported row and column counts, detected format, and the input/output paths plus sheet/table names.\nDo not use when: building a file from headers/rows you already have in hand \u2014 use create-spreadsheet; or exporting a .numbers table out to CSV/TSV/JSON \u2014 use export-table.\nSafety: writes the output .numbers file via the numbers-parser sidecar (does not require Numbers.app). An existing file at outputPath is refused unless overwrite is true; confirm with the user before passing it. Both inputPath and outputPath must resolve \u2014 after ~ expansion and symlink resolution \u2014 to locations under your home directory, /tmp, /private/tmp, or /Volumes; anything else is rejected with an error naming those roots.',
|
|
38547
38584
|
inputSchema: {
|
|
38548
38585
|
inputPath: external_exports.string().describe("Path to the CSV/TSV/JSON input file"),
|
|
38549
38586
|
outputPath: external_exports.string().describe("Path for the output .numbers file"),
|
|
38550
38587
|
format: external_exports.enum(["auto", "csv", "tsv", "json"]).optional().describe("Input format (default: auto-detect from extension)"),
|
|
38551
38588
|
sheetName: external_exports.string().max(MAX_NAME_LEN).optional().describe("Sheet name (default: 'Sheet 1')"),
|
|
38552
|
-
tableName: external_exports.string().max(MAX_NAME_LEN).optional().describe("Table name (default: 'Table 1')")
|
|
38589
|
+
tableName: external_exports.string().max(MAX_NAME_LEN).optional().describe("Table name (default: 'Table 1')"),
|
|
38590
|
+
overwrite: external_exports.boolean().optional().describe(
|
|
38591
|
+
"Replace an existing file at the destination (default: false \u2014 an existing file is refused)"
|
|
38592
|
+
)
|
|
38553
38593
|
},
|
|
38554
38594
|
outputSchema: {
|
|
38555
38595
|
path: external_exports.string().optional(),
|
|
@@ -38561,8 +38601,13 @@ registerTool(
|
|
|
38561
38601
|
numRows: external_exports.number().optional()
|
|
38562
38602
|
}
|
|
38563
38603
|
},
|
|
38564
|
-
withErrorHandling(({ inputPath, outputPath, format, sheetName, tableName }) => {
|
|
38565
|
-
const result = manager.importFile(inputPath, outputPath, {
|
|
38604
|
+
withErrorHandling(({ inputPath, outputPath, format, sheetName, tableName, overwrite }) => {
|
|
38605
|
+
const result = manager.importFile(inputPath, outputPath, {
|
|
38606
|
+
format,
|
|
38607
|
+
sheetName,
|
|
38608
|
+
tableName,
|
|
38609
|
+
overwrite
|
|
38610
|
+
});
|
|
38566
38611
|
return successResponse(
|
|
38567
38612
|
`Imported ${result.numRows} rows (${result.numHeaders} columns) from ${result.format.toUpperCase()}
|
|
38568
38613
|
Input: ${result.inputPath}
|
package/package.json
CHANGED