@piotr-agier/google-drive-mcp 1.5.0 → 1.6.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 CHANGED
@@ -402,6 +402,24 @@ Add the server to your Claude Desktop configuration:
402
402
  - `exportMimeType`: For Google Workspace files, MIME type to export as (optional, e.g., 'application/pdf', 'text/csv')
403
403
  - `overwrite`: Whether to overwrite existing files (optional, default: false)
404
404
 
405
+ #### PDF Ingestion and Conversion (v1.6.0)
406
+ - **convertPdfToGoogleDoc** - Convert a PDF already stored in Drive into an editable Google Doc
407
+ - `fileId`: Source PDF file ID
408
+ - `newName`: Optional destination doc name
409
+ - `parentFolderId`: Optional destination folder
410
+
411
+ - **bulkConvertFolderPdfs** - Convert all PDFs in a folder and return per-file success/failure summary
412
+ - `folderId`: Source folder ID
413
+ - `maxResults`: Maximum PDFs to process (optional, default: 100)
414
+ - `continueOnError`: Continue processing after individual failures (optional, default: true)
415
+
416
+ - **uploadPdfWithSplit** - Upload a local PDF, optionally split into chunked PDF parts before upload
417
+ - `localPath`: Absolute local path to PDF
418
+ - `split`: Enable split mode metadata output (optional, default: false)
419
+ - `maxPagesPerChunk`: Advisory chunk size for split planning (optional)
420
+ - `parentFolderId`: Optional destination folder
421
+ - `namePrefix`: Optional uploaded file name prefix
422
+
405
423
  ### Folder Operations
406
424
  - **createFolder** - Create a new folder
407
425
  - `name`: Folder name
@@ -433,6 +451,24 @@ Add the server to your Claude Desktop configuration:
433
451
  - `documentId`: Document ID
434
452
  - `includeContent`: Include content summary (character count) for each tab (optional)
435
453
 
454
+ - **addDocumentTab** - Add a new tab in a Google Doc
455
+ - `documentId`: Document ID
456
+ - `title`: Tab title
457
+
458
+ - **renameDocumentTab** - Rename an existing tab in a Google Doc
459
+ - `documentId`: Document ID
460
+ - `tabId`: Tab ID
461
+ - `title`: New tab title
462
+
463
+ - **insertSmartChip** - Insert a person smart chip (mention) at a document index. Only person chips are supported by the Docs API; date and file chips are read-only.
464
+ - `documentId`: Document ID
465
+ - `index`: Insertion index (1-based)
466
+ - `chipType`: `person` (only supported type)
467
+ - `personEmail`: Email address for the person mention
468
+
469
+ - **readSmartChips** - Read smart chip-like elements (person mentions, rich links, date chips) from the default tab of a document. Only the default tab is scanned; other tabs are not included.
470
+ - `documentId`: Document ID
471
+
436
472
  - **listGoogleDocs** - List Google Documents with optional filtering
437
473
  - `query`: Search query to filter by name or content (optional)
438
474
  - `maxResults`: Maximum documents to return, 1-100 (optional, default: 20)
package/dist/index.js CHANGED
@@ -609,7 +609,7 @@ async function authenticate() {
609
609
  // src/index.ts
610
610
  import { fileURLToPath as fileURLToPath2 } from "url";
611
611
  import { readFileSync } from "fs";
612
- import { join as join3, dirname as dirname4 } from "path";
612
+ import { join as join4, dirname as dirname4 } from "path";
613
613
 
614
614
  // src/utils.ts
615
615
  function buildCalendarEventUpdate(existing, overrides) {
@@ -690,6 +690,10 @@ __export(drive_exports, {
690
690
  });
691
691
  import { z } from "zod";
692
692
  import { existsSync as existsSync2, statSync as statSync2, createReadStream } from "fs";
693
+ import { mkdtemp, readFile as readFile3, writeFile as writeFile2, rm } from "fs/promises";
694
+ import { tmpdir } from "os";
695
+ import { basename as basename2, extname as extname2, join as join3 } from "path";
696
+ import { PDFDocument } from "pdf-lib";
693
697
 
694
698
  // src/download-file.ts
695
699
  import { createWriteStream, existsSync, renameSync, statSync, unlinkSync } from "fs";
@@ -986,6 +990,44 @@ var ShareFileSchema = z.object({
986
990
  role: z.enum(["writer", "commenter", "reader"]).default("reader"),
987
991
  sendNotificationEmail: z.boolean().optional().default(true)
988
992
  });
993
+ var ConvertPdfToGoogleDocSchema = z.object({
994
+ fileId: z.string().min(1, "File ID is required"),
995
+ newName: z.string().optional(),
996
+ parentFolderId: z.string().optional()
997
+ });
998
+ var BulkConvertFolderPdfsSchema = z.object({
999
+ folderId: z.string().min(1, "Folder ID is required"),
1000
+ maxResults: z.number().int().min(1).max(200).optional().default(100),
1001
+ continueOnError: z.boolean().optional().default(true)
1002
+ });
1003
+ var UploadPdfWithSplitSchema = z.object({
1004
+ localPath: z.string().min(1, "Local file path is required"),
1005
+ split: z.boolean().optional().default(false),
1006
+ maxPagesPerChunk: z.number().int().min(1).max(500).optional(),
1007
+ parentFolderId: z.string().optional(),
1008
+ namePrefix: z.string().optional()
1009
+ });
1010
+ async function splitPdfIntoChunkFiles(localPath, maxPagesPerChunk) {
1011
+ const sourceBytes = await readFile3(localPath);
1012
+ const source = await PDFDocument.load(sourceBytes);
1013
+ const pageCount = source.getPageCount();
1014
+ if (pageCount === 0) {
1015
+ throw new Error("PDF contains no pages.");
1016
+ }
1017
+ const tempDir = await mkdtemp(join3(tmpdir(), "gdrive-mcp-split-"));
1018
+ const files = [];
1019
+ for (let start = 0, part = 1; start < pageCount; start += maxPagesPerChunk, part++) {
1020
+ const end = Math.min(start + maxPagesPerChunk, pageCount);
1021
+ const chunkDoc = await PDFDocument.create();
1022
+ const pages = await chunkDoc.copyPages(source, Array.from({ length: end - start }, (_, i) => start + i));
1023
+ for (const page of pages) chunkDoc.addPage(page);
1024
+ const chunkBytes = await chunkDoc.save();
1025
+ const chunkPath = join3(tempDir, `part-${part}.pdf`);
1026
+ await writeFile2(chunkPath, chunkBytes);
1027
+ files.push(chunkPath);
1028
+ }
1029
+ return { tempDir, files };
1030
+ }
989
1031
  var toolDefinitions = [
990
1032
  {
991
1033
  name: "search",
@@ -1208,6 +1250,47 @@ var toolDefinitions = [
1208
1250
  },
1209
1251
  required: ["fileId", "emailAddress"]
1210
1252
  }
1253
+ },
1254
+ {
1255
+ name: "convertPdfToGoogleDoc",
1256
+ description: "Convert an existing PDF in Drive into an editable Google Doc",
1257
+ inputSchema: {
1258
+ type: "object",
1259
+ properties: {
1260
+ fileId: { type: "string", description: "PDF file ID in Google Drive" },
1261
+ newName: { type: "string", description: "Optional name for converted Doc" },
1262
+ parentFolderId: { type: "string", description: "Optional destination folder ID" }
1263
+ },
1264
+ required: ["fileId"]
1265
+ }
1266
+ },
1267
+ {
1268
+ name: "bulkConvertFolderPdfs",
1269
+ description: "Convert all PDFs in a folder into Google Docs and return per-file results",
1270
+ inputSchema: {
1271
+ type: "object",
1272
+ properties: {
1273
+ folderId: { type: "string", description: "Folder ID containing PDFs" },
1274
+ maxResults: { type: "number", description: "Maximum PDFs to process (1-200, default: 100)" },
1275
+ continueOnError: { type: "boolean", description: "Continue conversion when one file fails (default: true)" }
1276
+ },
1277
+ required: ["folderId"]
1278
+ }
1279
+ },
1280
+ {
1281
+ name: "uploadPdfWithSplit",
1282
+ description: "Upload PDF and optionally split into chunked parts (metadata split plan for now)",
1283
+ inputSchema: {
1284
+ type: "object",
1285
+ properties: {
1286
+ localPath: { type: "string", description: "Absolute path to local PDF" },
1287
+ split: { type: "boolean", description: "Enable split mode" },
1288
+ maxPagesPerChunk: { type: "number", description: "Target max pages per chunk (advisory metadata)" },
1289
+ parentFolderId: { type: "string", description: "Optional destination folder ID" },
1290
+ namePrefix: { type: "string", description: "Optional output name prefix" }
1291
+ },
1292
+ required: ["localPath"]
1293
+ }
1211
1294
  }
1212
1295
  ];
1213
1296
  async function handleTool(toolName, args, ctx) {
@@ -1712,6 +1795,127 @@ ${lines.join("\n")}` }], isError: false };
1712
1795
  isError: false
1713
1796
  };
1714
1797
  }
1798
+ case "convertPdfToGoogleDoc": {
1799
+ const validation = ConvertPdfToGoogleDocSchema.safeParse(args);
1800
+ if (!validation.success) return errorResponse(validation.error.errors[0].message);
1801
+ const data = validation.data;
1802
+ const source = await ctx.getDrive().files.get({
1803
+ fileId: data.fileId,
1804
+ fields: "id,name,mimeType,parents",
1805
+ supportsAllDrives: true
1806
+ });
1807
+ if (source.data.mimeType !== "application/pdf") {
1808
+ return errorResponse(`File ${data.fileId} is not a PDF (mimeType=${source.data.mimeType || "unknown"})`);
1809
+ }
1810
+ const parentId = data.parentFolderId || source.data.parents?.[0];
1811
+ const converted = await ctx.getDrive().files.copy({
1812
+ fileId: data.fileId,
1813
+ requestBody: {
1814
+ name: data.newName || `${source.data.name || "Converted PDF"} (Doc)`,
1815
+ mimeType: "application/vnd.google-apps.document",
1816
+ ...parentId ? { parents: [parentId] } : {}
1817
+ },
1818
+ fields: "id,name,webViewLink,mimeType",
1819
+ supportsAllDrives: true
1820
+ });
1821
+ return { content: [{ type: "text", text: `Converted PDF to Google Doc: ${converted.data.name}
1822
+ ID: ${converted.data.id}
1823
+ Link: ${converted.data.webViewLink}` }], isError: false };
1824
+ }
1825
+ case "bulkConvertFolderPdfs": {
1826
+ const validation = BulkConvertFolderPdfsSchema.safeParse(args);
1827
+ if (!validation.success) return errorResponse(validation.error.errors[0].message);
1828
+ const data = validation.data;
1829
+ const list = await ctx.getDrive().files.list({
1830
+ q: `'${escapeDriveQuery(data.folderId)}' in parents and mimeType='application/pdf' and trashed=false`,
1831
+ pageSize: data.maxResults,
1832
+ fields: "files(id,name,mimeType)",
1833
+ includeItemsFromAllDrives: true,
1834
+ supportsAllDrives: true
1835
+ });
1836
+ const files = list.data.files || [];
1837
+ const results = [];
1838
+ for (const f of files) {
1839
+ try {
1840
+ const converted = await ctx.getDrive().files.copy({
1841
+ fileId: f.id,
1842
+ requestBody: {
1843
+ name: `${f.name || "Converted PDF"} (Doc)`,
1844
+ mimeType: "application/vnd.google-apps.document",
1845
+ parents: [data.folderId]
1846
+ },
1847
+ fields: "id,name",
1848
+ supportsAllDrives: true
1849
+ });
1850
+ results.push({ id: f.id || void 0, name: f.name || void 0, docId: converted.data.id || void 0, ok: true });
1851
+ } catch (err) {
1852
+ const message = err?.message || "Unknown conversion error";
1853
+ results.push({ id: f.id || void 0, name: f.name || void 0, ok: false, error: message });
1854
+ if (!data.continueOnError) break;
1855
+ }
1856
+ }
1857
+ const ok = results.filter((r) => r.ok).length;
1858
+ const fail = results.length - ok;
1859
+ return {
1860
+ content: [{ type: "text", text: `Bulk PDF conversion finished. Processed=${results.length}, Success=${ok}, Failed=${fail}
1861
+
1862
+ ${results.map((r) => r.ok ? `\u2705 ${r.name} -> ${r.docId}` : `\u274C ${r.name}: ${r.error}`).join("\n")}` }],
1863
+ isError: false
1864
+ };
1865
+ }
1866
+ case "uploadPdfWithSplit": {
1867
+ const validation = UploadPdfWithSplitSchema.safeParse(args);
1868
+ if (!validation.success) return errorResponse(validation.error.errors[0].message);
1869
+ const data = validation.data;
1870
+ if (!existsSync2(data.localPath)) return errorResponse(`File not found: ${data.localPath}`);
1871
+ const parentId = await ctx.resolveFolderId(data.parentFolderId);
1872
+ if (!data.split) {
1873
+ const fileName = data.namePrefix || data.localPath.split(/[\\/]/).pop() || "upload.pdf";
1874
+ const uploaded = await ctx.getDrive().files.create({
1875
+ requestBody: { name: fileName, parents: [parentId] },
1876
+ media: { mimeType: "application/pdf", body: createReadStream(data.localPath) },
1877
+ fields: "id,name,webViewLink",
1878
+ supportsAllDrives: true
1879
+ });
1880
+ return {
1881
+ content: [{ type: "text", text: `Uploaded PDF without split: ${uploaded.data.name}
1882
+ ID: ${uploaded.data.id}` }],
1883
+ isError: false
1884
+ };
1885
+ }
1886
+ const maxPagesPerChunk = data.maxPagesPerChunk ?? 25;
1887
+ const baseName = data.namePrefix || basename2(data.localPath, extname2(data.localPath));
1888
+ let tempDir;
1889
+ try {
1890
+ const splitResult = await splitPdfIntoChunkFiles(data.localPath, maxPagesPerChunk);
1891
+ tempDir = splitResult.tempDir;
1892
+ const uploadedParts = [];
1893
+ for (let i = 0; i < splitResult.files.length; i++) {
1894
+ const partPath = splitResult.files[i];
1895
+ const partName = `${baseName}-part-${i + 1}.pdf`;
1896
+ const uploaded = await ctx.getDrive().files.create({
1897
+ requestBody: { name: partName, parents: [parentId] },
1898
+ media: { mimeType: "application/pdf", body: createReadStream(partPath) },
1899
+ fields: "id,name,webViewLink",
1900
+ supportsAllDrives: true
1901
+ });
1902
+ uploadedParts.push({ id: uploaded.data.id, name: uploaded.data.name });
1903
+ }
1904
+ const lines = uploadedParts.map((p, idx) => `- part ${idx + 1}: ${p.name} (ID: ${p.id})`);
1905
+ return {
1906
+ content: [{
1907
+ type: "text",
1908
+ text: `Uploaded split PDF into ${uploadedParts.length} part(s) using maxPagesPerChunk=${maxPagesPerChunk}
1909
+ ${lines.join("\n")}`
1910
+ }],
1911
+ isError: false
1912
+ };
1913
+ } finally {
1914
+ if (tempDir) {
1915
+ await rm(tempDir, { recursive: true, force: true });
1916
+ }
1917
+ }
1918
+ }
1715
1919
  default:
1716
1920
  return null;
1717
1921
  }
@@ -1725,7 +1929,7 @@ __export(docs_exports, {
1725
1929
  });
1726
1930
  import { z as z2 } from "zod";
1727
1931
  import { createReadStream as createReadStream2, existsSync as existsSync3 } from "fs";
1728
- import { basename as basename2, extname as extname2 } from "path";
1932
+ import { basename as basename3, extname as extname3 } from "path";
1729
1933
  function hexToRgbColor(hex) {
1730
1934
  if (!hex) return null;
1731
1935
  let hexClean = hex.startsWith("#") ? hex.slice(1) : hex;
@@ -2003,7 +2207,7 @@ async function uploadImageToDriveHelper(ctx, localFilePath, parentFolderId, make
2003
2207
  if (!existsSync3(localFilePath)) {
2004
2208
  throw new Error(`Image file not found: ${localFilePath}`);
2005
2209
  }
2006
- const fileName = basename2(localFilePath);
2210
+ const fileName = basename3(localFilePath);
2007
2211
  const mimeTypeMap = {
2008
2212
  ".jpg": "image/jpeg",
2009
2213
  ".jpeg": "image/jpeg",
@@ -2013,7 +2217,7 @@ async function uploadImageToDriveHelper(ctx, localFilePath, parentFolderId, make
2013
2217
  ".webp": "image/webp",
2014
2218
  ".svg": "image/svg+xml"
2015
2219
  };
2016
- const ext = extname2(localFilePath).toLowerCase();
2220
+ const ext = extname3(localFilePath).toLowerCase();
2017
2221
  const mimeType = mimeTypeMap[ext] || "application/octet-stream";
2018
2222
  const fileMetadata = {
2019
2223
  name: fileName,
@@ -2197,6 +2401,24 @@ var FindAndReplaceInDocSchema = z2.object({
2197
2401
  matchCase: z2.boolean().optional().default(false),
2198
2402
  dryRun: z2.boolean().optional().default(false)
2199
2403
  });
2404
+ var AddDocumentTabSchema = z2.object({
2405
+ documentId: z2.string().min(1, "Document ID is required"),
2406
+ title: z2.string().min(1, "Tab title is required")
2407
+ });
2408
+ var RenameDocumentTabSchema = z2.object({
2409
+ documentId: z2.string().min(1, "Document ID is required"),
2410
+ tabId: z2.string().min(1, "Tab ID is required"),
2411
+ title: z2.string().min(1, "Tab title is required")
2412
+ });
2413
+ var InsertSmartChipSchema = z2.object({
2414
+ documentId: z2.string().min(1, "Document ID is required"),
2415
+ index: z2.number().int().min(1, "Index must be at least 1"),
2416
+ chipType: z2.enum(["person"]),
2417
+ personEmail: z2.string().email("Valid email is required for person chip")
2418
+ });
2419
+ var ReadSmartChipsSchema = z2.object({
2420
+ documentId: z2.string().min(1, "Document ID is required")
2421
+ });
2200
2422
  var toolDefinitions2 = [
2201
2423
  {
2202
2424
  name: "createGoogleDoc",
@@ -2549,6 +2771,56 @@ var toolDefinitions2 = [
2549
2771
  },
2550
2772
  required: ["documentId"]
2551
2773
  }
2774
+ },
2775
+ {
2776
+ name: "addDocumentTab",
2777
+ description: "Add a new tab in a Google Doc",
2778
+ inputSchema: {
2779
+ type: "object",
2780
+ properties: {
2781
+ documentId: { type: "string", description: "Document ID" },
2782
+ title: { type: "string", description: "Tab title" }
2783
+ },
2784
+ required: ["documentId", "title"]
2785
+ }
2786
+ },
2787
+ {
2788
+ name: "renameDocumentTab",
2789
+ description: "Rename an existing Google Doc tab",
2790
+ inputSchema: {
2791
+ type: "object",
2792
+ properties: {
2793
+ documentId: { type: "string", description: "Document ID" },
2794
+ tabId: { type: "string", description: "Tab ID" },
2795
+ title: { type: "string", description: "New tab title" }
2796
+ },
2797
+ required: ["documentId", "tabId", "title"]
2798
+ }
2799
+ },
2800
+ {
2801
+ name: "insertSmartChip",
2802
+ description: "Insert a person smart chip (mention) at a document index. Only person chips are supported by the Docs API; date and file chips are read-only.",
2803
+ inputSchema: {
2804
+ type: "object",
2805
+ properties: {
2806
+ documentId: { type: "string", description: "Document ID" },
2807
+ index: { type: "number", description: "Insertion index (1-based)" },
2808
+ chipType: { type: "string", enum: ["person"], description: "Smart chip type (only 'person' is supported)" },
2809
+ personEmail: { type: "string", description: "Email address for the person mention" }
2810
+ },
2811
+ required: ["documentId", "index", "chipType", "personEmail"]
2812
+ }
2813
+ },
2814
+ {
2815
+ name: "readSmartChips",
2816
+ description: "Read smart chip-like elements (person mentions, rich links, date chips) from the default tab of a document",
2817
+ inputSchema: {
2818
+ type: "object",
2819
+ properties: {
2820
+ documentId: { type: "string", description: "Document ID" }
2821
+ },
2822
+ required: ["documentId"]
2823
+ }
2552
2824
  }
2553
2825
  ];
2554
2826
  async function handleTool2(toolName, args, ctx) {
@@ -3660,6 +3932,65 @@ Image URL: ${imageUrl}` }],
3660
3932
  `;
3661
3933
  return { content: [{ type: "text", text: result }], isError: false };
3662
3934
  }
3935
+ case "addDocumentTab": {
3936
+ const validation = AddDocumentTabSchema.safeParse(args);
3937
+ if (!validation.success) return errorResponse(validation.error.errors[0].message);
3938
+ const a = validation.data;
3939
+ const docs = ctx.google.docs({ version: "v1", auth: ctx.authClient });
3940
+ await docs.documents.batchUpdate({
3941
+ documentId: a.documentId,
3942
+ // createTab is not yet in the googleapis TypeScript types — cast required
3943
+ requestBody: { requests: [{ createTab: { tabProperties: { title: a.title } } }] }
3944
+ });
3945
+ return { content: [{ type: "text", text: `Requested creation of tab "${a.title}" in document ${a.documentId}.` }], isError: false };
3946
+ }
3947
+ case "renameDocumentTab": {
3948
+ const validation = RenameDocumentTabSchema.safeParse(args);
3949
+ if (!validation.success) return errorResponse(validation.error.errors[0].message);
3950
+ const a = validation.data;
3951
+ const docs = ctx.google.docs({ version: "v1", auth: ctx.authClient });
3952
+ await docs.documents.batchUpdate({
3953
+ documentId: a.documentId,
3954
+ // updateTabProperties is not yet in the googleapis TypeScript types — cast required
3955
+ requestBody: { requests: [{ updateTabProperties: { tabId: a.tabId, tabProperties: { title: a.title }, fields: "title" } }] }
3956
+ });
3957
+ return { content: [{ type: "text", text: `Renamed tab ${a.tabId} to "${a.title}".` }], isError: false };
3958
+ }
3959
+ case "insertSmartChip": {
3960
+ const validation = InsertSmartChipSchema.safeParse(args);
3961
+ if (!validation.success) return errorResponse(validation.error.errors[0].message);
3962
+ const a = validation.data;
3963
+ const docs = ctx.google.docs({ version: "v1", auth: ctx.authClient });
3964
+ await docs.documents.batchUpdate({
3965
+ documentId: a.documentId,
3966
+ requestBody: {
3967
+ requests: [{
3968
+ insertPerson: {
3969
+ personProperties: { email: a.personEmail },
3970
+ location: { index: a.index }
3971
+ }
3972
+ // insertPerson is not yet in the googleapis TypeScript types — cast required
3973
+ }]
3974
+ }
3975
+ });
3976
+ return { content: [{ type: "text", text: `Inserted person smart chip for ${a.personEmail} at index ${a.index}.` }], isError: false };
3977
+ }
3978
+ case "readSmartChips": {
3979
+ const validation = ReadSmartChipsSchema.safeParse(args);
3980
+ if (!validation.success) return errorResponse(validation.error.errors[0].message);
3981
+ const a = validation.data;
3982
+ const docs = ctx.google.docs({ version: "v1", auth: ctx.authClient });
3983
+ const doc = await docs.documents.get({ documentId: a.documentId });
3984
+ const body = doc.data.body?.content || [];
3985
+ const hits = [];
3986
+ for (const block of body) {
3987
+ for (const el of block?.paragraph?.elements || []) {
3988
+ if (el?.richLink) hits.push(`richLink: ${el.richLink.richLinkProperties?.uri || "unknown"}`);
3989
+ if (el?.person) hits.push(`person: ${el.person.personProperties?.email || "unknown"}`);
3990
+ }
3991
+ }
3992
+ return { content: [{ type: "text", text: hits.length ? hits.join("\n") : "No smart chips detected (note: only the default tab is scanned)." }], isError: false };
3993
+ }
3663
3994
  default:
3664
3995
  return null;
3665
3996
  }
@@ -6677,7 +7008,7 @@ var authClient = null;
6677
7008
  var authenticationPromise = null;
6678
7009
  var __filename = fileURLToPath2(import.meta.url);
6679
7010
  var __dirname = dirname4(__filename);
6680
- var packageJsonPath = join3(__dirname, "..", "package.json");
7011
+ var packageJsonPath = join4(__dirname, "..", "package.json");
6681
7012
  var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
6682
7013
  var VERSION = packageJson.version;
6683
7014
  function log(message, data) {