papermark 0.9.0 → 0.10.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/dist/index.js CHANGED
@@ -94,6 +94,86 @@ function getRuntime() {
94
94
  return current;
95
95
  }
96
96
 
97
+ // package.json
98
+ var package_default = {
99
+ name: "papermark",
100
+ version: "0.10.1",
101
+ description: "Command-line interface for Papermark \u2014 share documents, create data rooms, generate access links, and read analytics.",
102
+ license: "MIT",
103
+ homepage: "https://www.papermark.com",
104
+ repository: {
105
+ type: "git",
106
+ url: "git+https://github.com/papermark/papermark-cli.git"
107
+ },
108
+ bugs: {
109
+ url: "https://github.com/papermark/papermark-cli/issues"
110
+ },
111
+ keywords: [
112
+ "papermark",
113
+ "dataroom",
114
+ "datarooms",
115
+ "data room",
116
+ "virtual data room",
117
+ "document sharing",
118
+ "secure file sharing",
119
+ "access links",
120
+ "analytics",
121
+ "cli"
122
+ ],
123
+ type: "module",
124
+ bin: {
125
+ papermark: "dist/index.js"
126
+ },
127
+ files: [
128
+ "dist",
129
+ "docs",
130
+ "skills",
131
+ "openai.yaml",
132
+ "README.md",
133
+ "LICENSE"
134
+ ],
135
+ engines: {
136
+ node: ">=24"
137
+ },
138
+ scripts: {
139
+ build: "tsup",
140
+ dev: "tsx src/index.ts",
141
+ typecheck: "tsc --noEmit",
142
+ test: "vitest run",
143
+ "test:watch": "vitest",
144
+ changeset: "changeset",
145
+ version: "changeset version",
146
+ release: "npm run build && changeset publish",
147
+ prepublishOnly: "npm run build"
148
+ },
149
+ dependencies: {
150
+ chalk: "^5.3.0",
151
+ commander: "^14.0.3",
152
+ conf: "^13.0.1",
153
+ "mime-types": "^3.0.2",
154
+ oauth4webapi: "^3.8.5",
155
+ open: "^11.0.0",
156
+ ora: "^9.4.0",
157
+ prompts: "^2.4.2"
158
+ },
159
+ devDependencies: {
160
+ "@changesets/changelog-github": "^0.6.0",
161
+ "@changesets/cli": "^2.31.0",
162
+ "@types/mime-types": "^3.0.1",
163
+ "@types/node": "^22.13.5",
164
+ "@types/prompts": "^2.4.9",
165
+ tsup: "^8.3.5",
166
+ tsx: "^4.21.0",
167
+ typescript: "^5.7.2",
168
+ vitest: "^4.1.5"
169
+ }
170
+ };
171
+
172
+ // src/version.ts
173
+ var VERSION = package_default.version;
174
+ var CLIENT_ID = "cli";
175
+ var USER_AGENT = `papermark-cli/${VERSION} (node ${process.version}; ${process.platform} ${process.arch})`;
176
+
97
177
  // src/client.ts
98
178
  var ApiError = class extends Error {
99
179
  code;
@@ -161,7 +241,15 @@ function buildUrl(path, query) {
161
241
  async function request(path, opts = {}) {
162
242
  const headers = {
163
243
  Accept: "application/json",
164
- ...opts.headers ?? {}
244
+ ...opts.headers ?? {},
245
+ // Identify the caller so the API can attribute usage to the CLI (vs the
246
+ // MCP server or raw HTTP) and the client version. Dedicated headers keep
247
+ // server-side parsing unambiguous; the User-Agent mirrors them for
248
+ // proxy/access logs. Set last so attribution can't be spoofed or dropped
249
+ // by caller-supplied headers.
250
+ "User-Agent": USER_AGENT,
251
+ "x-papermark-client": CLIENT_ID,
252
+ "x-papermark-client-version": VERSION
165
253
  };
166
254
  if (!opts.anonymous) {
167
255
  const token = getToken();
@@ -205,7 +293,8 @@ async function request(path, opts = {}) {
205
293
  debug("http", "network error after", `${Date.now() - started}ms`, String(err));
206
294
  throw new NetworkError(url, err);
207
295
  }
208
- debug("http", method, url, `\u2192 ${res.status}`, `(${Date.now() - started}ms)`);
296
+ const requestId = res.headers.get("x-request-id") ?? "";
297
+ debug("http", method, url, `\u2192 ${res.status}`, requestId, `(${Date.now() - started}ms)`);
209
298
  if (res.status === 204) return void 0;
210
299
  const text = await res.text();
211
300
  const parsed = text ? safeParseJson(text) : null;
@@ -656,7 +745,7 @@ import ora from "ora";
656
745
  // src/oauth.ts
657
746
  import * as oauth from "oauth4webapi";
658
747
  import open from "open";
659
- var CLIENT_ID = "papermark-cli";
748
+ var CLIENT_ID2 = "papermark-cli";
660
749
  var DEFAULT_SCOPES = [
661
750
  "openid",
662
751
  "offline_access",
@@ -697,7 +786,7 @@ async function requestDeviceAuthorization(as, scopes) {
697
786
  );
698
787
  }
699
788
  const body = new URLSearchParams({
700
- client_id: CLIENT_ID,
789
+ client_id: CLIENT_ID2,
701
790
  scope: scopes.join(" ")
702
791
  });
703
792
  const json2 = await postFormJson(
@@ -765,7 +854,7 @@ async function pollForToken(as, deviceCode, {
765
854
  const body = new URLSearchParams({
766
855
  grant_type: "urn:ietf:params:oauth:grant-type:device_code",
767
856
  device_code: deviceCode,
768
- client_id: CLIENT_ID
857
+ client_id: CLIENT_ID2
769
858
  });
770
859
  let parsed;
771
860
  try {
@@ -790,6 +879,28 @@ async function tryOpenBrowser(url) {
790
879
  } catch {
791
880
  }
792
881
  }
882
+ async function revokeToken(token) {
883
+ try {
884
+ const issuer = deriveIssuer();
885
+ const as = await discover(issuer);
886
+ if (!as.revocation_endpoint) return "unsupported";
887
+ const res = await fetch(as.revocation_endpoint, {
888
+ method: "POST",
889
+ headers: {
890
+ "Content-Type": "application/x-www-form-urlencoded",
891
+ Accept: "application/json"
892
+ },
893
+ body: new URLSearchParams({
894
+ token,
895
+ token_type_hint: "access_token",
896
+ client_id: CLIENT_ID2
897
+ })
898
+ });
899
+ return res.ok ? "revoked" : "failed";
900
+ } catch {
901
+ return "failed";
902
+ }
903
+ }
793
904
 
794
905
  // src/commands/auth.ts
795
906
  function registerAuthCommands(program2) {
@@ -814,9 +925,24 @@ function registerAuthCommands(program2) {
814
925
  }
815
926
  }
816
927
  );
817
- program2.command("logout").description("Remove the stored API token").action(() => {
928
+ program2.command("logout").description("Remove the stored API token").action(async () => {
929
+ const token = getToken();
930
+ const source = getTokenSource();
931
+ const revocation = token && source === "config" ? await revokeToken(token) : "skipped";
818
932
  clearToken();
819
- emit({ removed: true }, () => chalk3.green("\u2713") + " Token removed.");
933
+ if (revocation === "skipped" && (source === "env" || source === "credentials-file")) {
934
+ process.stderr.write(
935
+ chalk3.dim(`Note: token comes from ${source}; not revoked server-side.`) + "\n"
936
+ );
937
+ } else if (revocation === "failed") {
938
+ process.stderr.write(
939
+ chalk3.yellow("! ") + "Could not revoke the token server-side; it remains valid until expiry.\n"
940
+ );
941
+ }
942
+ emit(
943
+ { removed: true, revocation },
944
+ () => revocation === "revoked" ? chalk3.green("\u2713") + " Token revoked and removed." : chalk3.green("\u2713") + " Token removed."
945
+ );
820
946
  });
821
947
  program2.command("whoami").description("Show the active token and API URL").action(() => {
822
948
  const token = getToken();
@@ -1203,6 +1329,18 @@ function parsePermissionEntries(opts) {
1203
1329
  );
1204
1330
  }
1205
1331
 
1332
+ // src/validate.ts
1333
+ function parseLimit(value) {
1334
+ if (value === void 0) return void 0;
1335
+ const n = Number(value);
1336
+ if (!Number.isInteger(n) || n < 1 || n > 100) {
1337
+ throw new ValidationError(
1338
+ `Invalid --limit ${JSON.stringify(value)}. Expected an integer 1-100.`
1339
+ );
1340
+ }
1341
+ return n;
1342
+ }
1343
+
1206
1344
  // src/commands/datarooms.ts
1207
1345
  function registerDataroomsCommands(program2) {
1208
1346
  const dr = program2.command("datarooms").alias("dr").description("Manage datarooms");
@@ -1211,7 +1349,7 @@ function registerDataroomsCommands(program2) {
1211
1349
  if (opts.json) setRuntime({ json: true });
1212
1350
  try {
1213
1351
  const res = await api.listDatarooms({
1214
- limit: opts.limit ? Number(opts.limit) : void 0,
1352
+ limit: parseLimit(opts.limit),
1215
1353
  cursor: opts.cursor,
1216
1354
  query: opts.query
1217
1355
  });
@@ -1280,14 +1418,14 @@ More results: --cursor ${res.next_cursor}`) : out;
1280
1418
  const norm = v.toLowerCase();
1281
1419
  if (norm === "on") return true;
1282
1420
  if (norm === "off") return false;
1283
- throw new Error(
1421
+ throw new ValidationError(
1284
1422
  `Invalid value for ${flag}: ${JSON.stringify(v)} (expected "on" or "off").`
1285
1423
  );
1286
1424
  };
1287
1425
  const bulk = tri(opts.bulkDownload, "--bulk-download");
1288
1426
  if (bulk !== void 0) body.allow_bulk_download = bulk;
1289
1427
  if (Object.keys(body).length === 0) {
1290
- throw new Error(
1428
+ throw new ValidationError(
1291
1429
  "Pass at least one field to update (e.g. --name, --description, --bulk-download off)."
1292
1430
  );
1293
1431
  }
@@ -1310,7 +1448,7 @@ More results: --cursor ${res.next_cursor}`) : out;
1310
1448
  try {
1311
1449
  const res = await api.listLinks({
1312
1450
  dataroom_id: id,
1313
- limit: opts.limit ? Number(opts.limit) : void 0,
1451
+ limit: parseLimit(opts.limit),
1314
1452
  cursor: opts.cursor
1315
1453
  });
1316
1454
  emit(res, () => {
@@ -1339,7 +1477,7 @@ More results: --cursor ${res.next_cursor}`) : out;
1339
1477
  try {
1340
1478
  const res = await api.listVisitors({
1341
1479
  dataroom_id: id,
1342
- limit: opts.limit ? Number(opts.limit) : void 0,
1480
+ limit: parseLimit(opts.limit),
1343
1481
  cursor: opts.cursor,
1344
1482
  email: opts.email
1345
1483
  });
@@ -1454,12 +1592,12 @@ More results: --cursor ${res.next_cursor}`) : out;
1454
1592
  try {
1455
1593
  const folderIsRoot = opts.folderId === "" || opts.folderId === "root";
1456
1594
  if (opts.root && opts.folderId !== void 0 && !folderIsRoot) {
1457
- throw new Error(
1595
+ throw new ValidationError(
1458
1596
  "Pass either --root or --folder-id <fid>, not both."
1459
1597
  );
1460
1598
  }
1461
1599
  if (!opts.root && opts.folderId === void 0) {
1462
- throw new Error(
1600
+ throw new ValidationError(
1463
1601
  'Pass --folder-id <fid>, or --root (or --folder-id "") to move to the dataroom root.'
1464
1602
  );
1465
1603
  }
@@ -1727,7 +1865,7 @@ More results: --cursor ${res.next_cursor}`) : out;
1727
1865
  if (opts.json) setRuntime({ json: true });
1728
1866
  try {
1729
1867
  const res = await api.listDataroomFolders(id, {
1730
- limit: opts.limit ? Number(opts.limit) : void 0,
1868
+ limit: parseLimit(opts.limit),
1731
1869
  cursor: opts.cursor,
1732
1870
  parent_id: opts.parent
1733
1871
  });
@@ -1786,7 +1924,7 @@ More results: --cursor ${res.next_cursor}`) : out;
1786
1924
  if (opts.color !== void 0)
1787
1925
  body.color = opts.color === "" ? null : opts.color;
1788
1926
  if (Object.keys(body).length === 0) {
1789
- throw new Error(
1927
+ throw new ValidationError(
1790
1928
  "Pass at least one of --name, --icon, --color to update."
1791
1929
  );
1792
1930
  }
@@ -1827,7 +1965,7 @@ More results: --cursor ${res.next_cursor}`) : out;
1827
1965
  async (id, fid, opts) => {
1828
1966
  try {
1829
1967
  if (opts.parent === void 0) {
1830
- throw new Error(
1968
+ throw new ValidationError(
1831
1969
  "Pass --parent <fid> (use empty string to move to the root)."
1832
1970
  );
1833
1971
  }
@@ -1850,7 +1988,7 @@ More results: --cursor ${res.next_cursor}`) : out;
1850
1988
  if (opts.json) setRuntime({ json: true });
1851
1989
  try {
1852
1990
  const res = await api.listDataroomDocuments(id, {
1853
- limit: opts.limit ? Number(opts.limit) : void 0,
1991
+ limit: parseLimit(opts.limit),
1854
1992
  cursor: opts.cursor,
1855
1993
  folder_id: opts.folder
1856
1994
  });
@@ -1986,7 +2124,7 @@ function registerDocumentsCommands(program2) {
1986
2124
  if (opts.json) setRuntime({ json: true });
1987
2125
  try {
1988
2126
  const res = await api.listDocuments({
1989
- limit: opts.limit ? Number(opts.limit) : void 0,
2127
+ limit: parseLimit(opts.limit),
1990
2128
  cursor: opts.cursor
1991
2129
  });
1992
2130
  emit(res, () => {
@@ -2019,7 +2157,7 @@ More results: --cursor ${res.next_cursor}`) : out;
2019
2157
  try {
2020
2158
  const res = await api.searchDocuments(
2021
2159
  query,
2022
- opts.limit ? Number(opts.limit) : void 0
2160
+ parseLimit(opts.limit)
2023
2161
  );
2024
2162
  emit(res, () => {
2025
2163
  const rows = res.data.map((d) => ({
@@ -2043,7 +2181,7 @@ More results: --cursor ${res.next_cursor}`) : out;
2043
2181
  body.folder_id = opts.folderId === "" ? null : opts.folderId;
2044
2182
  }
2045
2183
  if (Object.keys(body).length === 0) {
2046
- throw new Error(
2184
+ throw new ValidationError(
2047
2185
  "Pass at least one of --name or --folder-id to update."
2048
2186
  );
2049
2187
  }
@@ -2073,8 +2211,13 @@ More results: --cursor ${res.next_cursor}`) : out;
2073
2211
  async (filePath, opts) => {
2074
2212
  const spinner = ora2();
2075
2213
  try {
2076
- const stats = await stat(filePath);
2077
- if (!stats.isFile()) throw new Error(`${filePath} is not a file.`);
2214
+ const stats = await stat(filePath).catch((statErr) => {
2215
+ throw new ValidationError(
2216
+ `Cannot read ${filePath}: ${statErr.message}`,
2217
+ { code: statErr.code }
2218
+ );
2219
+ });
2220
+ if (!stats.isFile()) throw new ValidationError(`${filePath} is not a file.`);
2078
2221
  const fileName = opts.name ?? basename(filePath);
2079
2222
  const contentType = lookupMimeType(filePath) || "application/octet-stream";
2080
2223
  spinner.start("Requesting upload URL");
@@ -2083,6 +2226,15 @@ More results: --cursor ${res.next_cursor}`) : out;
2083
2226
  content_type: contentType,
2084
2227
  content_length: stats.size
2085
2228
  });
2229
+ if (getRuntime().dryRun) {
2230
+ spinner.stop();
2231
+ process.stderr.write(
2232
+ `[dry-run] PUT <presigned upload_url from the response above> (file: ${filePath}, ${humanSize(stats.size)})
2233
+ [dry-run] POST ${"/v1/documents"} (name, upload_id, folder_id, create_link)
2234
+ `
2235
+ );
2236
+ return;
2237
+ }
2086
2238
  spinner.succeed("Got presigned URL");
2087
2239
  spinner.start(`Uploading ${humanSize(stats.size)}`);
2088
2240
  const fileStream = Readable.toWeb(createReadStream(filePath));
@@ -2193,8 +2345,13 @@ More results: --cursor ${res.next_cursor}`) : out;
2193
2345
  versions.command("add <id> <file>").description("Upload a new version of an existing document").action(async (id, filePath) => {
2194
2346
  const spinner = ora2();
2195
2347
  try {
2196
- const stats = await stat(filePath);
2197
- if (!stats.isFile()) throw new Error(`${filePath} is not a file.`);
2348
+ const stats = await stat(filePath).catch((statErr) => {
2349
+ throw new ValidationError(
2350
+ `Cannot read ${filePath}: ${statErr.message}`,
2351
+ { code: statErr.code }
2352
+ );
2353
+ });
2354
+ if (!stats.isFile()) throw new ValidationError(`${filePath} is not a file.`);
2198
2355
  const fileName = basename(filePath);
2199
2356
  const contentType = lookupMimeType(filePath) || "application/octet-stream";
2200
2357
  spinner.start("Requesting upload URL");
@@ -2203,6 +2360,15 @@ More results: --cursor ${res.next_cursor}`) : out;
2203
2360
  content_type: contentType,
2204
2361
  content_length: stats.size
2205
2362
  });
2363
+ if (getRuntime().dryRun) {
2364
+ spinner.stop();
2365
+ process.stderr.write(
2366
+ `[dry-run] PUT <presigned upload_url from the response above> (file: ${filePath}, ${humanSize(stats.size)})
2367
+ [dry-run] POST /v1/documents/${id}/versions (upload_id)
2368
+ `
2369
+ );
2370
+ return;
2371
+ }
2206
2372
  spinner.succeed("Got presigned URL");
2207
2373
  spinner.start(`Uploading ${humanSize(stats.size)}`);
2208
2374
  const fileStream = Readable.toWeb(createReadStream(filePath));
@@ -2271,6 +2437,7 @@ More results: --cursor ${res.next_cursor}`) : out;
2271
2437
  });
2272
2438
  }
2273
2439
  async function finalizeDocumentCreation(doc, wantLink) {
2440
+ if (getRuntime().dryRun) return;
2274
2441
  let link = null;
2275
2442
  let linkWarning = null;
2276
2443
  if (wantLink) {
@@ -2322,7 +2489,7 @@ function registerFoldersCommands(program2) {
2322
2489
  if (opts.json) setRuntime({ json: true });
2323
2490
  try {
2324
2491
  const res = await api.listFolders({
2325
- limit: opts.limit ? Number(opts.limit) : void 0,
2492
+ limit: parseLimit(opts.limit),
2326
2493
  cursor: opts.cursor,
2327
2494
  parent_id: opts.parent
2328
2495
  });
@@ -2381,7 +2548,7 @@ More results: --cursor ${res.next_cursor}`) : out;
2381
2548
  if (opts.color !== void 0)
2382
2549
  body.color = opts.color === "" ? null : opts.color;
2383
2550
  if (Object.keys(body).length === 0) {
2384
- throw new Error(
2551
+ throw new ValidationError(
2385
2552
  "Pass at least one of --name, --icon, --color to update."
2386
2553
  );
2387
2554
  }
@@ -2417,7 +2584,7 @@ More results: --cursor ${res.next_cursor}`) : out;
2417
2584
  ).action(async (id, opts) => {
2418
2585
  try {
2419
2586
  if (opts.parent === void 0) {
2420
- throw new Error(
2587
+ throw new ValidationError(
2421
2588
  "Pass --parent <id> (use empty string to move to the root)."
2422
2589
  );
2423
2590
  }
@@ -2475,42 +2642,42 @@ function parseWatermarkConfig(opts) {
2475
2642
  if (provided.length === 0) return void 0;
2476
2643
  const missing = Object.entries(fields).filter(([, v]) => v === void 0).map(([k]) => flagNames[k]);
2477
2644
  if (missing.length > 0) {
2478
- throw new Error(
2645
+ throw new ValidationError(
2479
2646
  `Watermark config is partial. Missing: ${missing.join(", ")}.`
2480
2647
  );
2481
2648
  }
2482
2649
  const tiled = fields.is_tiled.toLowerCase() === "true" || fields.is_tiled.toLowerCase() === "on";
2483
2650
  const tiledOff = fields.is_tiled.toLowerCase() === "false" || fields.is_tiled.toLowerCase() === "off";
2484
2651
  if (!tiled && !tiledOff) {
2485
- throw new Error(
2652
+ throw new ValidationError(
2486
2653
  `Invalid --watermark-tiled value ${JSON.stringify(fields.is_tiled)} (expected on/off).`
2487
2654
  );
2488
2655
  }
2489
2656
  if (!WATERMARK_POSITIONS.includes(fields.position)) {
2490
- throw new Error(
2657
+ throw new ValidationError(
2491
2658
  `Invalid --watermark-position ${JSON.stringify(fields.position)}. Expected one of: ${WATERMARK_POSITIONS.join(", ")}.`
2492
2659
  );
2493
2660
  }
2494
2661
  const rotation = Number(fields.rotation);
2495
2662
  if (!Number.isFinite(rotation) || !WATERMARK_ROTATIONS.includes(rotation)) {
2496
- throw new Error(
2663
+ throw new ValidationError(
2497
2664
  `Invalid --watermark-rotation ${JSON.stringify(fields.rotation)}. Expected one of: ${WATERMARK_ROTATIONS.join(", ")}.`
2498
2665
  );
2499
2666
  }
2500
2667
  if (!/^#([0-9A-Fa-f]{3}){1,2}$/.test(fields.color)) {
2501
- throw new Error(
2668
+ throw new ValidationError(
2502
2669
  `Invalid --watermark-color ${JSON.stringify(fields.color)}. Expected #RGB or #RRGGBB.`
2503
2670
  );
2504
2671
  }
2505
2672
  const fontSize = Number(fields.font_size);
2506
2673
  if (!Number.isInteger(fontSize) || fontSize < 1 || fontSize > 96) {
2507
- throw new Error(
2674
+ throw new ValidationError(
2508
2675
  `Invalid --watermark-font-size ${JSON.stringify(fields.font_size)}. Expected integer 1-96.`
2509
2676
  );
2510
2677
  }
2511
2678
  const opacity = Number(fields.opacity);
2512
2679
  if (!Number.isFinite(opacity) || opacity < 0 || opacity > 1) {
2513
- throw new Error(
2680
+ throw new ValidationError(
2514
2681
  `Invalid --watermark-opacity ${JSON.stringify(fields.opacity)}. Expected 0-1.`
2515
2682
  );
2516
2683
  }
@@ -2543,7 +2710,7 @@ function registerLinksCommands(program2) {
2543
2710
  if (opts.json) setRuntime({ json: true });
2544
2711
  try {
2545
2712
  const res = await api.listLinks({
2546
- limit: opts.limit ? Number(opts.limit) : void 0,
2713
+ limit: parseLimit(opts.limit),
2547
2714
  cursor: opts.cursor,
2548
2715
  document_id: opts.document,
2549
2716
  dataroom_id: opts.dataroom,
@@ -2608,7 +2775,7 @@ More results: --cursor ${res.next_cursor}`) : out;
2608
2775
  async (opts) => {
2609
2776
  try {
2610
2777
  if (!opts.document && !opts.dataroom) {
2611
- throw new Error("Either --document <id> or --dataroom <id> is required.");
2778
+ throw new ValidationError("Either --document <id> or --dataroom <id> is required.");
2612
2779
  }
2613
2780
  if (opts.group && !opts.dataroom) {
2614
2781
  throw new Error("--group requires --dataroom <id>.");
@@ -2677,7 +2844,7 @@ More results: --cursor ${res.next_cursor}`) : out;
2677
2844
  const norm = v.toLowerCase();
2678
2845
  if (norm === "on") return true;
2679
2846
  if (norm === "off") return false;
2680
- throw new Error(
2847
+ throw new ValidationError(
2681
2848
  `Invalid value for ${flag}: ${JSON.stringify(v)} (expected "on" or "off").`
2682
2849
  );
2683
2850
  };
@@ -2712,7 +2879,7 @@ More results: --cursor ${res.next_cursor}`) : out;
2712
2879
  if (wm !== void 0) body.enable_watermark = wm;
2713
2880
  const watermarkConfig = parseWatermarkConfig(opts);
2714
2881
  if (opts.watermarkClear && watermarkConfig !== void 0) {
2715
- throw new Error(
2882
+ throw new ValidationError(
2716
2883
  "Pass either --watermark-clear or --watermark-* flags, not both."
2717
2884
  );
2718
2885
  }
@@ -2733,7 +2900,7 @@ More results: --cursor ${res.next_cursor}`) : out;
2733
2900
  if (opts.domain !== void 0) body.domain = opts.domain;
2734
2901
  if (opts.slug !== void 0) body.slug = opts.slug;
2735
2902
  if (Object.keys(body).length === 0) {
2736
- throw new Error(
2903
+ throw new ValidationError(
2737
2904
  "Pass at least one field to update (e.g. --name, --expires, --password)."
2738
2905
  );
2739
2906
  }
@@ -2832,7 +2999,7 @@ function registerViewsCommands(program2) {
2832
2999
  if (opts.json) setRuntime({ json: true });
2833
3000
  try {
2834
3001
  const res = await api.listViews(opts.link, {
2835
- limit: opts.limit ? Number(opts.limit) : void 0,
3002
+ limit: parseLimit(opts.limit),
2836
3003
  cursor: opts.cursor
2837
3004
  });
2838
3005
  emit(res, () => {
@@ -2864,7 +3031,7 @@ function registerVisitorsCommands(program2) {
2864
3031
  if (opts.json) setRuntime({ json: true });
2865
3032
  try {
2866
3033
  const res = await api.listVisitors({
2867
- limit: opts.limit ? Number(opts.limit) : void 0,
3034
+ limit: parseLimit(opts.limit),
2868
3035
  cursor: opts.cursor,
2869
3036
  email: opts.email,
2870
3037
  dataroom_id: opts.dataroom
@@ -2900,7 +3067,7 @@ More results: --cursor ${res.next_cursor}`) : out;
2900
3067
  if (opts.json) setRuntime({ json: true });
2901
3068
  try {
2902
3069
  const res = await api.listVisitorViews(id, {
2903
- limit: opts.limit ? Number(opts.limit) : void 0,
3070
+ limit: parseLimit(opts.limit),
2904
3071
  cursor: opts.cursor
2905
3072
  });
2906
3073
  emit(res, () => {
@@ -2925,7 +3092,7 @@ More results: --cursor ${res.next_cursor}`) : out;
2925
3092
 
2926
3093
  // src/index.ts
2927
3094
  var program = new Command2();
2928
- program.name("papermark").description("Papermark CLI \u2014 manage documents, links, and views").version("0.1.0").option(
3095
+ program.name("papermark").description("Papermark CLI \u2014 manage documents, links, and views").version(VERSION).option(
2929
3096
  "--json",
2930
3097
  "emit machine-readable JSON on stdout (auto-enabled when piped)"
2931
3098
  ).option(