apple-numbers-mcp 1.2.11 → 1.3.1
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 +139 -38
- 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
|
@@ -3104,9 +3104,9 @@ var require_data = __commonJS({
|
|
|
3104
3104
|
}
|
|
3105
3105
|
});
|
|
3106
3106
|
|
|
3107
|
-
// node_modules/.pnpm/fast-uri@3.1.
|
|
3107
|
+
// node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/utils.js
|
|
3108
3108
|
var require_utils = __commonJS({
|
|
3109
|
-
"node_modules/.pnpm/fast-uri@3.1.
|
|
3109
|
+
"node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/utils.js"(exports, module) {
|
|
3110
3110
|
"use strict";
|
|
3111
3111
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
3112
3112
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
@@ -3338,6 +3338,37 @@ var require_utils = __commonJS({
|
|
|
3338
3338
|
}
|
|
3339
3339
|
return output2.join("");
|
|
3340
3340
|
}
|
|
3341
|
+
function removeDotSegmentsRelative(path) {
|
|
3342
|
+
if (path === "" || path[0] === "/") {
|
|
3343
|
+
return removeDotSegments(path);
|
|
3344
|
+
}
|
|
3345
|
+
const segments = path.split("/");
|
|
3346
|
+
const last = segments.length - 1;
|
|
3347
|
+
const output2 = [];
|
|
3348
|
+
let trailingDirectory = false;
|
|
3349
|
+
for (let i = 0; i <= last; i++) {
|
|
3350
|
+
const segment = segments[i];
|
|
3351
|
+
if (segment === "." || segment === "..") {
|
|
3352
|
+
if (segment === "..") {
|
|
3353
|
+
if (output2.length !== 0 && output2[output2.length - 1] !== "..") {
|
|
3354
|
+
output2.pop();
|
|
3355
|
+
} else {
|
|
3356
|
+
output2.push("..");
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
trailingDirectory = i === last;
|
|
3360
|
+
} else {
|
|
3361
|
+
output2.push(segment);
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
if (trailingDirectory && output2[output2.length - 1] !== "..") {
|
|
3365
|
+
output2.push("");
|
|
3366
|
+
}
|
|
3367
|
+
if (output2.length === 0 || output2[0] === "" || output2[0].includes(":")) {
|
|
3368
|
+
output2.unshift(".");
|
|
3369
|
+
}
|
|
3370
|
+
return output2.join("/");
|
|
3371
|
+
}
|
|
3341
3372
|
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
3342
3373
|
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
3343
3374
|
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
@@ -3598,6 +3629,7 @@ var require_utils = __commonJS({
|
|
|
3598
3629
|
encodeFragment,
|
|
3599
3630
|
escapePreservingEscapes,
|
|
3600
3631
|
removeDotSegments,
|
|
3632
|
+
removeDotSegmentsRelative,
|
|
3601
3633
|
isIPv4,
|
|
3602
3634
|
isUUID,
|
|
3603
3635
|
normalizeIPv6,
|
|
@@ -3606,9 +3638,9 @@ var require_utils = __commonJS({
|
|
|
3606
3638
|
}
|
|
3607
3639
|
});
|
|
3608
3640
|
|
|
3609
|
-
// node_modules/.pnpm/fast-uri@3.1.
|
|
3641
|
+
// node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/schemes.js
|
|
3610
3642
|
var require_schemes = __commonJS({
|
|
3611
|
-
"node_modules/.pnpm/fast-uri@3.1.
|
|
3643
|
+
"node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/lib/schemes.js"(exports, module) {
|
|
3612
3644
|
"use strict";
|
|
3613
3645
|
var { isUUID } = require_utils();
|
|
3614
3646
|
var URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu;
|
|
@@ -3817,20 +3849,22 @@ var require_schemes = __commonJS({
|
|
|
3817
3849
|
}
|
|
3818
3850
|
});
|
|
3819
3851
|
|
|
3820
|
-
// node_modules/.pnpm/fast-uri@3.1.
|
|
3852
|
+
// node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/index.js
|
|
3821
3853
|
var require_fast_uri = __commonJS({
|
|
3822
|
-
"node_modules/.pnpm/fast-uri@3.1.
|
|
3854
|
+
"node_modules/.pnpm/fast-uri@3.1.9/node_modules/fast-uri/index.js"(exports, module) {
|
|
3823
3855
|
"use strict";
|
|
3824
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3856
|
+
var { normalizeIPv6, removeDotSegments, removeDotSegmentsRelative, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
3825
3857
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
3826
3858
|
var VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u;
|
|
3827
3859
|
var MALFORMED_SCHEME_ERROR = "URI scheme is malformed.";
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3860
|
+
var MALFORMED_HOST_ERROR = "URI host is malformed.";
|
|
3861
|
+
var FORBIDDEN_DOMAIN_HOST_CHARACTER = /[\0-\x20"#/:<>?@[\\\]^`{|}\x7F]/u;
|
|
3862
|
+
function validateScheme(scheme) {
|
|
3863
|
+
const stringScheme = String(scheme);
|
|
3864
|
+
if (!VALID_SCHEME.test(stringScheme)) {
|
|
3831
3865
|
throw new TypeError(MALFORMED_SCHEME_ERROR);
|
|
3832
3866
|
}
|
|
3833
|
-
return
|
|
3867
|
+
return stringScheme;
|
|
3834
3868
|
}
|
|
3835
3869
|
function normalize(uri, options) {
|
|
3836
3870
|
if (typeof uri === "string") {
|
|
@@ -3867,6 +3901,9 @@ var require_fast_uri = __commonJS({
|
|
|
3867
3901
|
const resolvedSchemeHandler = getSchemeHandler(options && options.scheme || resolved.scheme);
|
|
3868
3902
|
const resolvedHost = resolved.host;
|
|
3869
3903
|
const resolvedHostIsIP = resolvedHost !== void 0 && resolvedHost !== "" && (isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6);
|
|
3904
|
+
if (hasForbiddenDomainHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)) {
|
|
3905
|
+
throw new Error(MALFORMED_HOST_ERROR);
|
|
3906
|
+
}
|
|
3870
3907
|
canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP);
|
|
3871
3908
|
const encodedASCIIHost = resolvedHost && resolvedHost.indexOf("%") !== -1 && !new RegExp("\\P{ASCII}", "u").test(resolvedHost);
|
|
3872
3909
|
if (resolved.error && !encodedASCIIHost) {
|
|
@@ -3908,14 +3945,15 @@ var require_fast_uri = __commonJS({
|
|
|
3908
3945
|
if (relative.path[0] === "/") {
|
|
3909
3946
|
target.path = removeDotSegments(relative.path);
|
|
3910
3947
|
} else {
|
|
3911
|
-
|
|
3948
|
+
const baseHasAuthority = base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0;
|
|
3949
|
+
if (baseHasAuthority && !base.path) {
|
|
3912
3950
|
target.path = "/" + relative.path;
|
|
3913
3951
|
} else if (!base.path) {
|
|
3914
3952
|
target.path = relative.path;
|
|
3915
3953
|
} else {
|
|
3916
3954
|
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative.path;
|
|
3917
3955
|
}
|
|
3918
|
-
target.path = removeDotSegments(target.path);
|
|
3956
|
+
target.path = base.scheme === void 0 && !baseHasAuthority && target.path[0] !== "/" ? removeDotSegmentsRelative(target.path) : removeDotSegments(target.path);
|
|
3919
3957
|
}
|
|
3920
3958
|
target.query = relative.query;
|
|
3921
3959
|
}
|
|
@@ -3953,12 +3991,13 @@ var require_fast_uri = __commonJS({
|
|
|
3953
3991
|
const options = Object.assign({}, opts);
|
|
3954
3992
|
const uriTokens = [];
|
|
3955
3993
|
if (component.scheme) {
|
|
3956
|
-
component.scheme =
|
|
3994
|
+
component.scheme = validateScheme(component.scheme);
|
|
3957
3995
|
}
|
|
3958
3996
|
const schemeHandler = getSchemeHandler(options.scheme || component.scheme);
|
|
3959
3997
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
|
|
3960
3998
|
const hasAuthority = component.userinfo !== void 0 || component.host !== void 0 || component.port !== void 0;
|
|
3961
|
-
const
|
|
3999
|
+
const schemelessReference = (component.scheme === void 0 || options.reference === "suffix") && !hasAuthority;
|
|
4000
|
+
const pathNoScheme = !options.skipEscape && schemelessReference;
|
|
3962
4001
|
if (component.path !== void 0) {
|
|
3963
4002
|
if (!options.skipEscape) {
|
|
3964
4003
|
component.path = serializePathEncoding(component.path, pathNoScheme);
|
|
@@ -3967,7 +4006,7 @@ var require_fast_uri = __commonJS({
|
|
|
3967
4006
|
}
|
|
3968
4007
|
}
|
|
3969
4008
|
if (options.reference !== "suffix" && component.scheme) {
|
|
3970
|
-
component.scheme =
|
|
4009
|
+
component.scheme = validateScheme(component.scheme);
|
|
3971
4010
|
uriTokens.push(component.scheme, ":");
|
|
3972
4011
|
}
|
|
3973
4012
|
const authority = recomposeAuthority(component);
|
|
@@ -3983,9 +4022,9 @@ var require_fast_uri = __commonJS({
|
|
|
3983
4022
|
if (component.path !== void 0) {
|
|
3984
4023
|
let s = component.path;
|
|
3985
4024
|
if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
|
|
3986
|
-
s = removeDotSegments(s);
|
|
4025
|
+
s = schemelessReference && s[0] !== "/" ? removeDotSegmentsRelative(s) : removeDotSegments(s);
|
|
3987
4026
|
}
|
|
3988
|
-
if (
|
|
4027
|
+
if (schemelessReference) {
|
|
3989
4028
|
s = serializePathEncoding(s, true);
|
|
3990
4029
|
}
|
|
3991
4030
|
if (authority === void 0 && s[0] === "/" && s[1] === "/") {
|
|
@@ -4032,16 +4071,34 @@ var require_fast_uri = __commonJS({
|
|
|
4032
4071
|
return hasMalformedPercentEncoding(matches[3]) || host !== void 0 && !isIPLiteral(host) && hasMalformedPercentEncoding(host) || hasMalformedPercentEncoding(matches[6]) || hasMalformedPercentEncoding(matches[7]) || hasMalformedPercentEncoding(matches[8]);
|
|
4033
4072
|
}
|
|
4034
4073
|
function canonicalizeHost(parsed, options, schemeHandler, isIP) {
|
|
4074
|
+
if (hasForbiddenDomainHost(parsed, options, schemeHandler, isIP)) {
|
|
4075
|
+
parsed.error = parsed.error || MALFORMED_HOST_ERROR;
|
|
4076
|
+
return true;
|
|
4077
|
+
}
|
|
4035
4078
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport) && parsed.host && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
4079
|
+
let url2;
|
|
4036
4080
|
try {
|
|
4037
|
-
|
|
4081
|
+
url2 = new URL("http://" + parsed.host);
|
|
4038
4082
|
} catch (e) {
|
|
4039
4083
|
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e;
|
|
4040
4084
|
return true;
|
|
4041
4085
|
}
|
|
4086
|
+
if (!isHostOnlyURL(url2)) {
|
|
4087
|
+
parsed.error = parsed.error || MALFORMED_HOST_ERROR;
|
|
4088
|
+
return true;
|
|
4089
|
+
}
|
|
4090
|
+
parsed.host = url2.hostname;
|
|
4042
4091
|
}
|
|
4043
4092
|
return false;
|
|
4044
4093
|
}
|
|
4094
|
+
function hasForbiddenDomainHost(parsed, options, schemeHandler, isIP) {
|
|
4095
|
+
return Boolean(
|
|
4096
|
+
parsed.host && isIP === false && !isIPLiteral(parsed.host) && (options.domainHost || schemeHandler && schemeHandler.domainHost) && FORBIDDEN_DOMAIN_HOST_CHARACTER.test(parsed.host)
|
|
4097
|
+
);
|
|
4098
|
+
}
|
|
4099
|
+
function isHostOnlyURL(url2) {
|
|
4100
|
+
return url2.username === "" && url2.password === "" && url2.port === "" && url2.pathname === "/" && url2.search === "" && url2.hash === "" && url2.hostname !== "";
|
|
4101
|
+
}
|
|
4045
4102
|
function parseWithStatus(uri, opts) {
|
|
4046
4103
|
const options = Object.assign({}, opts);
|
|
4047
4104
|
const parsed = {
|
|
@@ -4096,9 +4153,8 @@ var require_fast_uri = __commonJS({
|
|
|
4096
4153
|
parsed.query = matches[7];
|
|
4097
4154
|
parsed.fragment = matches[8];
|
|
4098
4155
|
if (parsed.scheme !== void 0) {
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
parsed.scheme = decodedScheme.toLowerCase();
|
|
4156
|
+
if (VALID_SCHEME.test(parsed.scheme)) {
|
|
4157
|
+
parsed.scheme = parsed.scheme.toLowerCase();
|
|
4102
4158
|
} else {
|
|
4103
4159
|
parsed.error = parsed.error || MALFORMED_SCHEME_ERROR;
|
|
4104
4160
|
malformedScheme = true;
|
|
@@ -4126,7 +4182,7 @@ var require_fast_uri = __commonJS({
|
|
|
4126
4182
|
malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true);
|
|
4127
4183
|
parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase();
|
|
4128
4184
|
if (malformedIPLiteral) {
|
|
4129
|
-
parsed.error = parsed.error ||
|
|
4185
|
+
parsed.error = parsed.error || MALFORMED_HOST_ERROR;
|
|
4130
4186
|
malformedAuthorityOrPort = true;
|
|
4131
4187
|
}
|
|
4132
4188
|
} else {
|
|
@@ -4154,7 +4210,7 @@ var require_fast_uri = __commonJS({
|
|
|
4154
4210
|
if (!isIP) {
|
|
4155
4211
|
host = normalizePercentEncoding(host.toLowerCase());
|
|
4156
4212
|
}
|
|
4157
|
-
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4213
|
+
parsed.host = isIP || options.scheme !== "null" ? reescapeHostDelimiters(host, isIP) : host;
|
|
4158
4214
|
}
|
|
4159
4215
|
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
4160
4216
|
if (parsed.path) {
|
|
@@ -37252,7 +37308,7 @@ function unmergeCells(filePath, sheet, table, startRow, startCol, endRow, endCol
|
|
|
37252
37308
|
}
|
|
37253
37309
|
|
|
37254
37310
|
// src/services/numbersManager.ts
|
|
37255
|
-
import { existsSync as existsSync2 } from "node:fs";
|
|
37311
|
+
import { existsSync as existsSync2, lstatSync as lstatSync2 } from "node:fs";
|
|
37256
37312
|
import { extname } from "node:path";
|
|
37257
37313
|
|
|
37258
37314
|
// src/utils/exportPath.ts
|
|
@@ -37421,6 +37477,29 @@ var NumbersManager = class {
|
|
|
37421
37477
|
resolveReadPath(filePath) {
|
|
37422
37478
|
return resolveWithinAllowedRoots(filePath, "Input path");
|
|
37423
37479
|
}
|
|
37480
|
+
/**
|
|
37481
|
+
* Refuse to replace an existing file unless the caller opted in.
|
|
37482
|
+
*
|
|
37483
|
+
* `export-table`, `create-spreadsheet` and `import-csv` used to overwrite
|
|
37484
|
+
* whatever was at the destination, so a reused or mistyped path silently
|
|
37485
|
+
* destroyed a real file — for the last two, a real spreadsheet. The roots
|
|
37486
|
+
* boundary limits WHERE they write; this limits WHAT they replace. Checked on
|
|
37487
|
+
* the resolved path with `lstat`, so a dangling symlink also counts as taken.
|
|
37488
|
+
*/
|
|
37489
|
+
assertWritable(resolved, overwrite) {
|
|
37490
|
+
if (overwrite) return;
|
|
37491
|
+
let taken = false;
|
|
37492
|
+
try {
|
|
37493
|
+
lstatSync2(resolved);
|
|
37494
|
+
taken = true;
|
|
37495
|
+
} catch {
|
|
37496
|
+
}
|
|
37497
|
+
if (taken) {
|
|
37498
|
+
throw new Error(
|
|
37499
|
+
`Output file already exists: ${resolved}. Pass overwrite: true to replace it, or choose a new path.`
|
|
37500
|
+
);
|
|
37501
|
+
}
|
|
37502
|
+
}
|
|
37424
37503
|
/**
|
|
37425
37504
|
* Get file structure: sheets, tables, dimensions, headers.
|
|
37426
37505
|
*/
|
|
@@ -37459,9 +37538,10 @@ var NumbersManager = class {
|
|
|
37459
37538
|
/**
|
|
37460
37539
|
* Export a table to CSV, TSV, or JSON file.
|
|
37461
37540
|
*/
|
|
37462
|
-
exportTable(filePath, format, outputPath, sheet, table) {
|
|
37541
|
+
exportTable(filePath, format, outputPath, sheet, table, overwrite) {
|
|
37463
37542
|
const resolved = this.validatePath(filePath);
|
|
37464
37543
|
const outputResolved = this.resolveWritePath(outputPath);
|
|
37544
|
+
this.assertWritable(outputResolved, overwrite);
|
|
37465
37545
|
const args = [resolved, format, outputResolved];
|
|
37466
37546
|
if (sheet) args.push("--sheet", sheet);
|
|
37467
37547
|
if (table) args.push("--table", table);
|
|
@@ -37485,6 +37565,7 @@ var NumbersManager = class {
|
|
|
37485
37565
|
*/
|
|
37486
37566
|
createSpreadsheet(filePath, headers, options) {
|
|
37487
37567
|
const resolved = this.validateOutputPath(filePath);
|
|
37568
|
+
this.assertWritable(resolved, options?.overwrite);
|
|
37488
37569
|
const args = [resolved, JSON.stringify(headers)];
|
|
37489
37570
|
if (options?.sheetName) args.push("--sheet-name", options.sheetName);
|
|
37490
37571
|
if (options?.tableName) args.push("--table-name", options.tableName);
|
|
@@ -37580,6 +37661,7 @@ var NumbersManager = class {
|
|
|
37580
37661
|
throw new Error(`Input file not found: ${inputResolved}`);
|
|
37581
37662
|
}
|
|
37582
37663
|
const outputResolved = this.validateOutputPath(outputPath);
|
|
37664
|
+
this.assertWritable(outputResolved, options?.overwrite);
|
|
37583
37665
|
const args = [inputResolved, outputResolved];
|
|
37584
37666
|
if (options?.format && options.format !== "auto") args.push("--format", options.format);
|
|
37585
37667
|
if (options?.sheetName) args.push("--sheet-name", options.sheetName);
|
|
@@ -38273,13 +38355,16 @@ ${lines.join("\n")}`, {
|
|
|
38273
38355
|
registerTool(
|
|
38274
38356
|
"export-table",
|
|
38275
38357
|
{
|
|
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.
|
|
38358
|
+
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
38359
|
inputSchema: {
|
|
38278
38360
|
path: external_exports.string().describe("Path to the .numbers file"),
|
|
38279
38361
|
format: external_exports.enum(["csv", "tsv", "json"]).describe("Output format"),
|
|
38280
38362
|
outputPath: external_exports.string().describe("Path for the output file"),
|
|
38281
38363
|
sheet: external_exports.string().optional().describe("Sheet name (default: first sheet)"),
|
|
38282
|
-
table: external_exports.string().optional().describe("Table name (default: first table)")
|
|
38364
|
+
table: external_exports.string().optional().describe("Table name (default: first table)"),
|
|
38365
|
+
overwrite: external_exports.boolean().optional().describe(
|
|
38366
|
+
"Replace an existing file at the destination (default: false \u2014 an existing file is refused)"
|
|
38367
|
+
)
|
|
38283
38368
|
},
|
|
38284
38369
|
outputSchema: {
|
|
38285
38370
|
outputPath: external_exports.string().optional(),
|
|
@@ -38289,8 +38374,8 @@ registerTool(
|
|
|
38289
38374
|
tableName: external_exports.string().optional()
|
|
38290
38375
|
}
|
|
38291
38376
|
},
|
|
38292
|
-
withErrorHandling(({ path, format, outputPath, sheet, table }) => {
|
|
38293
|
-
const result = manager.exportTable(path, format, outputPath, sheet, table);
|
|
38377
|
+
withErrorHandling(({ path, format, outputPath, sheet, table, overwrite }) => {
|
|
38378
|
+
const result = manager.exportTable(path, format, outputPath, sheet, table, overwrite);
|
|
38294
38379
|
return successResponse(
|
|
38295
38380
|
`Exported ${result.rowCount} rows from "${result.sheetName}/${result.tableName}" to ${result.format.toUpperCase()}: ${result.outputPath}`,
|
|
38296
38381
|
{ ...result }
|
|
@@ -38337,13 +38422,16 @@ Merged: yes`;
|
|
|
38337
38422
|
registerTool(
|
|
38338
38423
|
"create-spreadsheet",
|
|
38339
38424
|
{
|
|
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).
|
|
38425
|
+
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
38426
|
inputSchema: {
|
|
38342
38427
|
path: external_exports.string().describe("Path for the new .numbers file"),
|
|
38343
38428
|
headers: external_exports.array(headerCellSchema).min(1).max(MAX_BATCH).describe("Column header names"),
|
|
38344
38429
|
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
38430
|
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')")
|
|
38431
|
+
tableName: external_exports.string().max(MAX_NAME_LEN).optional().describe("Table name (default: 'Table 1')"),
|
|
38432
|
+
overwrite: external_exports.boolean().optional().describe(
|
|
38433
|
+
"Replace an existing file at the destination (default: false \u2014 an existing file is refused)"
|
|
38434
|
+
)
|
|
38347
38435
|
},
|
|
38348
38436
|
outputSchema: {
|
|
38349
38437
|
path: external_exports.string().optional(),
|
|
@@ -38353,8 +38441,13 @@ registerTool(
|
|
|
38353
38441
|
numRows: external_exports.number().optional()
|
|
38354
38442
|
}
|
|
38355
38443
|
},
|
|
38356
|
-
withErrorHandling(({ path, headers, rows, sheetName, tableName }) => {
|
|
38357
|
-
const result = manager.createSpreadsheet(path, headers, {
|
|
38444
|
+
withErrorHandling(({ path, headers, rows, sheetName, tableName, overwrite }) => {
|
|
38445
|
+
const result = manager.createSpreadsheet(path, headers, {
|
|
38446
|
+
sheetName,
|
|
38447
|
+
tableName,
|
|
38448
|
+
rows,
|
|
38449
|
+
overwrite
|
|
38450
|
+
});
|
|
38358
38451
|
return successResponse(
|
|
38359
38452
|
`Created ${result.path}
|
|
38360
38453
|
Sheet: "${result.sheetName}" | Table: "${result.tableName}"
|
|
@@ -38543,13 +38636,16 @@ registerTool(
|
|
|
38543
38636
|
registerTool(
|
|
38544
38637
|
"import-csv",
|
|
38545
38638
|
{
|
|
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).
|
|
38639
|
+
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
38640
|
inputSchema: {
|
|
38548
38641
|
inputPath: external_exports.string().describe("Path to the CSV/TSV/JSON input file"),
|
|
38549
38642
|
outputPath: external_exports.string().describe("Path for the output .numbers file"),
|
|
38550
38643
|
format: external_exports.enum(["auto", "csv", "tsv", "json"]).optional().describe("Input format (default: auto-detect from extension)"),
|
|
38551
38644
|
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')")
|
|
38645
|
+
tableName: external_exports.string().max(MAX_NAME_LEN).optional().describe("Table name (default: 'Table 1')"),
|
|
38646
|
+
overwrite: external_exports.boolean().optional().describe(
|
|
38647
|
+
"Replace an existing file at the destination (default: false \u2014 an existing file is refused)"
|
|
38648
|
+
)
|
|
38553
38649
|
},
|
|
38554
38650
|
outputSchema: {
|
|
38555
38651
|
path: external_exports.string().optional(),
|
|
@@ -38561,8 +38657,13 @@ registerTool(
|
|
|
38561
38657
|
numRows: external_exports.number().optional()
|
|
38562
38658
|
}
|
|
38563
38659
|
},
|
|
38564
|
-
withErrorHandling(({ inputPath, outputPath, format, sheetName, tableName }) => {
|
|
38565
|
-
const result = manager.importFile(inputPath, outputPath, {
|
|
38660
|
+
withErrorHandling(({ inputPath, outputPath, format, sheetName, tableName, overwrite }) => {
|
|
38661
|
+
const result = manager.importFile(inputPath, outputPath, {
|
|
38662
|
+
format,
|
|
38663
|
+
sheetName,
|
|
38664
|
+
tableName,
|
|
38665
|
+
overwrite
|
|
38666
|
+
});
|
|
38566
38667
|
return successResponse(
|
|
38567
38668
|
`Imported ${result.numRows} rows (${result.numHeaders} columns) from ${result.format.toUpperCase()}
|
|
38568
38669
|
Input: ${result.inputPath}
|
package/package.json
CHANGED