@jieunmarslim/server-editable-slides 0.2.11 β†’ 0.2.13

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.
Files changed (2) hide show
  1. package/index.js +140 -96
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -214,47 +214,64 @@ async function runDoctor() {
214
214
  const token = getAuthToken();
215
215
  const project = getProject();
216
216
 
217
+ const useColor = !process.env.NO_COLOR;
218
+ const red = (s) => (useColor ? `\x1b[1;31m${s}\x1b[0m` : s);
219
+ const green = (s) => (useColor ? `\x1b[1;32m${s}\x1b[0m` : s);
220
+ const yellow = (s) => (useColor ? `\x1b[1;33m${s}\x1b[0m` : s);
221
+ const cyan = (s) => (useColor ? `\x1b[1;36m${s}\x1b[0m` : s);
222
+ const bold = (s) => (useColor ? `\x1b[1m${s}\x1b[0m` : s);
223
+ const badgePass = useColor ? '\x1b[42;97;1m PASS \x1b[0m' : '[PASS]';
224
+ const badgeFail = useColor ? '\x1b[41;97;1m FAILED \x1b[0m' : '[FAILED]';
225
+ const badgeSkip = useColor ? '\x1b[43;30;1m SKIPPED \x1b[0m' : '[SKIPPED]';
226
+ const badgeWarn = useColor ? '\x1b[43;30;1m WARNING \x1b[0m' : '[WARNING]';
227
+
217
228
  console.error('\n========================================================================');
218
- console.error(' πŸ” EditNBLM MCP Pre-flight Diagnostic & Doctor');
229
+ console.error(bold(' πŸ” EditNBLM MCP Pre-flight Diagnostic & Doctor'));
219
230
  console.error('========================================================================\n');
220
231
 
221
- let hasError = false;
232
+ const failures = [];
222
233
 
223
234
  // 1. Check Auth
224
235
  if (account && token) {
225
- console.error(` [1/5] Google Cloud Authentication:`);
226
- console.error(` βœ“ Authenticated: ${account}`);
236
+ console.error(` [1/5] Google Cloud Authentication: ${badgePass}`);
237
+ console.error(` βœ“ Authenticated: ${green(account)}`);
227
238
  } else {
228
- hasError = true;
229
- console.error(` [1/5] Google Cloud Authentication:`);
230
- console.error(` βœ— FAILED: Not authenticated or token expired.`);
231
- console.error(` πŸ‘‰ Please run: gcloud auth login --enable-gdrive-access`);
239
+ failures.push({
240
+ step: 'Google Cloud Authentication',
241
+ fix: 'gcloud auth login --enable-gdrive-access',
242
+ });
243
+ console.error(` [1/5] Google Cloud Authentication: ${badgeFail}`);
244
+ console.error(` ${red('βœ— FAILED')}: Not authenticated or token expired.`);
245
+ console.error(` πŸ‘‰ ${bold('Required Fix')}: ${cyan('gcloud auth login --enable-gdrive-access')}`);
232
246
  }
233
247
 
234
248
  // 2. Check Google Drive Integration & Scope
235
249
  const hasDrive = await checkDriveScope(token);
236
- console.error(`\n [2/5] Google Drive Integration & Scope:`);
237
250
  if (hasDrive) {
238
- console.error(` βœ“ Active: Google Drive access granted.`);
239
- console.error(` βœ“ Presentations will automatically convert into live Google Slides.`);
251
+ console.error(`\n [2/5] Google Drive Integration & Scope: ${badgePass}`);
252
+ console.error(` βœ“ Active: Google Drive access granted (auto Google Slides conversion ready).`);
240
253
  } else {
241
- hasError = true;
242
- console.error(` βœ— FAILED: Token lacks Google Drive access.`);
243
- console.error(` πŸ‘‰ To enable automatic Google Slides creation in your Drive, run:`);
244
- console.error(` gcloud auth login --enable-gdrive-access`);
254
+ failures.push({
255
+ step: 'Google Drive Integration & Scope',
256
+ fix: 'gcloud auth login --enable-gdrive-access',
257
+ });
258
+ console.error(`\n [2/5] Google Drive Integration & Scope: ${badgeFail}`);
259
+ console.error(` ${red('βœ— FAILED')}: Token lacks Google Drive access.`);
260
+ console.error(` πŸ‘‰ ${bold('Required Fix')}: ${cyan('gcloud auth login --enable-gdrive-access')}`);
245
261
  }
246
262
 
247
- // 2. Check Project Configuration & Account Access Permissions
263
+ // 3. Check Project Configuration & Account Access Permissions
248
264
  let projectAccessOk = false;
249
265
  if (!project) {
250
- hasError = true;
251
- console.error(`\n [3/5] GEAP Billing & Quota Project:`);
252
- console.error(` βœ— FAILED: No GCP project configured.`);
253
- console.error(` πŸ‘‰ Please run: gcloud config set project YOUR_GCP_PROJECT_ID`);
254
- console.error(` πŸ‘‰ Or export: export EDITABLE_SLIDES_PROJECT=YOUR_GCP_PROJECT_ID`);
266
+ failures.push({
267
+ step: 'GEAP Billing & Quota Project',
268
+ fix: 'gcloud config set project <YOUR_GCP_PROJECT_ID>',
269
+ });
270
+ console.error(`\n [3/5] Target GCP Project Accessibility: ${badgeFail}`);
271
+ console.error(` ${red('βœ— FAILED')}: No GCP project configured.`);
272
+ console.error(` πŸ‘‰ ${bold('Required Fix')}: ${cyan('gcloud config set project YOUR_GCP_PROJECT_ID')}`);
255
273
  } else {
256
274
  try {
257
- // Actually verify that the logged-in account has access to this project
258
275
  const check = execSync(
259
276
  `gcloud projects describe ${project} --format="value(projectId)"`,
260
277
  { stdio: ['ignore', 'pipe', 'pipe'] },
@@ -263,48 +280,44 @@ async function runDoctor() {
263
280
  .trim();
264
281
  if (check === project) {
265
282
  projectAccessOk = true;
266
- console.error(`\n [3/5] Target GCP Project Accessibility:`);
267
- console.error(
268
- ` βœ“ Verified: Account [${account}] has active access to project [${project}].`,
269
- );
283
+ console.error(`\n [3/5] Target GCP Project Accessibility: ${badgePass}`);
284
+ console.error(` βœ“ Verified: Account [${green(account)}] has active access to [${green(project)}].`);
270
285
  }
271
286
  } catch (err) {
272
- hasError = true;
273
287
  const stderr = err.stderr ? err.stderr.toString() : err.message;
274
- console.error(`\n [3/5] Target GCP Project Accessibility:`);
275
288
  if (
276
289
  stderr.includes('does not have permission') ||
277
290
  stderr.includes('caller does not have permission')
278
291
  ) {
279
- console.error(
280
- ` βœ— ACCESS DENIED: Account [${account}] does NOT have permission to access project [${project}].`,
281
- );
282
- console.error(
283
- ` πŸ‘‰ Solution 1: Log in with the account that has access to [${project}]:`,
284
- );
285
- console.error(` gcloud auth login --enable-gdrive-access`);
286
- console.error(
287
- ` πŸ‘‰ Solution 2: Switch to a project that [${account}] can access:`,
288
- );
289
- console.error(
290
- ` gcloud config set project <ACCESSIBLE_PROJECT_ID>`,
291
- );
292
+ failures.push({
293
+ step: `Access to GCP Project [${project}]`,
294
+ fix: `Switch project: gcloud config set project <ACCESSIBLE_PROJECT_ID>\n Or login with owner account: gcloud auth login --enable-gdrive-access`,
295
+ });
296
+ console.error(`\n [3/5] Target GCP Project Accessibility: ${badgeFail}`);
297
+ console.error(` ${red('βœ— ACCESS DENIED')}: Account [${bold(account)}] does NOT have permission to access project [${bold(project)}].`);
298
+ console.error(` πŸ‘‰ ${bold('Solution 1 (Switch project)')}: ${cyan('gcloud config set project <ACCESSIBLE_PROJECT_ID>')}`);
299
+ console.error(` πŸ‘‰ ${bold('Solution 2 (Switch account)')}: ${cyan('gcloud auth login --enable-gdrive-access')}`);
292
300
  } else if (stderr.includes('not found') || stderr.includes('404')) {
293
- console.error(
294
- ` βœ— NOT FOUND: Project [${project}] does not exist or was deleted.`,
295
- );
296
- console.error(
297
- ` πŸ‘‰ Please run: gcloud config set project <VALID_PROJECT_ID>`,
298
- );
301
+ failures.push({
302
+ step: `GCP Project [${project}] Not Found`,
303
+ fix: 'gcloud config set project <VALID_PROJECT_ID>',
304
+ });
305
+ console.error(`\n [3/5] Target GCP Project Accessibility: ${badgeFail}`);
306
+ console.error(` ${red('βœ— NOT FOUND')}: Project [${bold(project)}] does not exist or was deleted.`);
307
+ console.error(` πŸ‘‰ ${bold('Required Fix')}: ${cyan('gcloud config set project <VALID_PROJECT_ID>')}`);
299
308
  } else {
300
- console.error(
301
- ` βœ— FAILED: Unable to verify project access (${stderr.trim()}).`,
302
- );
309
+ failures.push({
310
+ step: `Verify GCP Project [${project}]`,
311
+ fix: `Check gcloud permissions: ${stderr.trim().split('\n')[0]}`,
312
+ });
313
+ console.error(`\n [3/5] Target GCP Project Accessibility: ${badgeFail}`);
314
+ console.error(` ${red('βœ— FAILED')}: Unable to verify project access (${stderr.trim().split('\n')[0]}).`);
303
315
  }
304
316
  }
305
317
  }
306
318
 
307
319
  // 4. Verify One-Time IAM Delegation to EditNBLM Service Account
320
+ let iamDelegationOk = false;
308
321
  if (projectAccessOk) {
309
322
  try {
310
323
  const bindings = execSync(
@@ -315,62 +328,80 @@ async function runDoctor() {
315
328
  'serviceAccount:editnblm-mcp-runtime@editnblm-in-ge-2036.iam.gserviceaccount.com',
316
329
  );
317
330
 
318
- console.error(`\n [4/5] GEAP Quota & IAM Delegation:`);
319
331
  if (hasAiPlatform) {
320
- console.error(
321
- ` βœ“ GRANTED: Service account has roles/aiplatform.user on [${project}].`,
322
- );
332
+ iamDelegationOk = true;
333
+ console.error(`\n [4/5] GEAP Quota & IAM Delegation: ${badgePass}`);
334
+ console.error(` βœ“ GRANTED: Service account has roles/aiplatform.user on [${green(project)}].`);
323
335
  } else {
324
- hasError = true;
325
- console.error(
326
- ` βœ— MISSING: editnblm-mcp-runtime has NOT been granted roles/aiplatform.user on [${project}].`,
327
- );
328
- console.error(` πŸ‘‰ Please run the delegation commands below.`);
336
+ failures.push({
337
+ step: 'GEAP IAM Delegation',
338
+ fix: `Run the 2 IAM delegation commands shown below`,
339
+ });
340
+ console.error(`\n [4/5] GEAP Quota & IAM Delegation: ${badgeFail}`);
341
+ console.error(` ${red('βœ— MISSING')}: editnblm-mcp-runtime has NOT been granted roles/aiplatform.user on [${bold(project)}].`);
342
+ console.error(` πŸ‘‰ ${bold('Required Action')}: Execute the IAM delegation commands below.`);
329
343
  }
330
344
  } catch {
331
- console.error(`\n [4/5] GEAP Quota & IAM Delegation:`);
332
- console.error(
333
- ` ⚠️ Could not read IAM policy directly. Ensure the delegation commands below were executed.`,
334
- );
345
+ console.error(`\n [4/5] GEAP Quota & IAM Delegation: ${badgeWarn}`);
346
+ console.error(` ⚠️ Could not verify IAM policy directly. Ensure delegation commands were executed.`);
335
347
  }
336
348
  } else {
337
- console.error(`\n [4/5] GEAP Quota & IAM Delegation:`);
338
- console.error(` ⏸️ Skipped until project access is resolved.`);
349
+ console.error(`\n [4/5] GEAP Quota & IAM Delegation: ${badgeSkip}`);
350
+ console.error(` ⏸️ Skipped until project access error [3/5] is resolved.`);
339
351
  }
340
352
 
341
353
  // 5. Check Backend Connectivity
342
354
  try {
343
355
  const res = await fetch(`${BASE_URL}/health`);
344
356
  if (res.ok) {
345
- console.error(`\n [5/5] EditNBLM Cloud Backend:`);
346
- console.error(` βœ“ Status: Connected (${BASE_URL})`);
357
+ console.error(`\n [5/5] EditNBLM Cloud Backend: ${badgePass}`);
358
+ console.error(` βœ“ Status: Connected (${green(BASE_URL)})`);
347
359
  } else {
348
- console.error(`\n [5/5] EditNBLM Cloud Backend:`);
349
- console.error(` ⚠️ Status: HTTP ${res.status}`);
360
+ failures.push({
361
+ step: 'EditNBLM Cloud Backend',
362
+ fix: `Backend returned HTTP ${res.status}. Check service health.`,
363
+ });
364
+ console.error(`\n [5/5] EditNBLM Cloud Backend: ${badgeFail}`);
365
+ console.error(` ${red('βœ— HTTP ERROR')}: Status ${res.status}`);
350
366
  }
351
367
  } catch (err) {
352
- console.error(`\n [5/5] EditNBLM Cloud Backend:`);
353
- console.error(` βœ— Status: Unreachable (${err.message})`);
368
+ failures.push({
369
+ step: 'EditNBLM Cloud Backend',
370
+ fix: `Backend unreachable: ${err.message}`,
371
+ });
372
+ console.error(`\n [5/5] EditNBLM Cloud Backend: ${badgeFail}`);
373
+ console.error(` ${red('βœ— UNREACHABLE')}: ${err.message}`);
354
374
  }
355
375
 
356
- // 5. One-Time IAM Delegation command helper
357
- console.error('\n------------------------------------------------------------------------');
358
- console.error(' πŸ“‹ One-Time IAM Delegation Commands for your Project:');
359
- console.error('------------------------------------------------------------------------');
360
- const targetProject = project || 'YOUR_GCP_PROJECT_ID';
361
- console.error(`gcloud projects add-iam-policy-binding ${targetProject} \\`);
362
- console.error(' --member="serviceAccount:editnblm-mcp-runtime@editnblm-in-ge-2036.iam.gserviceaccount.com" \\');
363
- console.error(' --role="roles/aiplatform.user"\n');
364
- console.error(`gcloud projects add-iam-policy-binding ${targetProject} \\`);
365
- console.error(' --member="serviceAccount:editnblm-mcp-runtime@editnblm-in-ge-2036.iam.gserviceaccount.com" \\');
366
- console.error(' --role="roles/serviceusage.serviceUsageConsumer"');
367
- console.error('========================================================================\n');
376
+ // IAM Delegation Commands (only shown when project is accessible)
377
+ if (projectAccessOk && !iamDelegationOk) {
378
+ console.error('\n------------------------------------------------------------------------');
379
+ console.error(bold(' πŸ“‹ One-Time IAM Delegation Commands for your Project:'));
380
+ console.error('------------------------------------------------------------------------');
381
+ console.error(cyan(`gcloud projects add-iam-policy-binding ${project} \\`));
382
+ console.error(cyan(' --member="serviceAccount:editnblm-mcp-runtime@editnblm-in-ge-2036.iam.gserviceaccount.com" \\'));
383
+ console.error(cyan(' --role="roles/aiplatform.user"\n'));
384
+ console.error(cyan(`gcloud projects add-iam-policy-binding ${project} \\`));
385
+ console.error(cyan(' --member="serviceAccount:editnblm-mcp-runtime@editnblm-in-ge-2036.iam.gserviceaccount.com" \\'));
386
+ console.error(cyan(' --role="roles/serviceusage.serviceUsageConsumer"'));
387
+ }
368
388
 
369
- if (!hasError) {
370
- console.error('πŸŽ‰ All checks PASSED! Your environment is ready to use EditNBLM MCP.\n');
389
+ // Final Summary Banner
390
+ if (failures.length === 0) {
391
+ console.error('\n' + green('========================================================================'));
392
+ console.error(useColor ? '\x1b[42;97;1m πŸŽ‰ ALL CHECKS PASSED! Your environment is ready to use EditNBLM MCP. \x1b[0m' : 'πŸŽ‰ ALL CHECKS PASSED! Your environment is ready to use EditNBLM MCP.');
393
+ console.error(green('========================================================================\n'));
371
394
  process.exit(0);
372
395
  } else {
373
- console.error('⚠️ Setup incomplete. Please fix the items marked with βœ— above.\n');
396
+ console.error('\n' + red('========================================================================'));
397
+ console.error(useColor ? `\x1b[41;97;1m ❌ PRE-FLIGHT CHECK FAILED β€” ${failures.length} ACTION(S) REQUIRED \x1b[0m` : `❌ PRE-FLIGHT CHECK FAILED β€” ${failures.length} ACTION(S) REQUIRED`);
398
+ console.error(red('========================================================================'));
399
+ console.error(bold(`\n EditNBLM MCP cannot run until the following issue(s) are resolved:\n`));
400
+ failures.forEach((f, idx) => {
401
+ console.error(` ${red(bold(`[!] Issue ${idx + 1}`))}: ${bold(f.step)}`);
402
+ console.error(` πŸ‘‰ ${cyan(f.fix)}\n`);
403
+ });
404
+ console.error(red('========================================================================\n'));
374
405
  process.exit(1);
375
406
  }
376
407
  }
@@ -708,7 +739,7 @@ async function startMcpServer() {
708
739
  parsed.agentInstructions = [
709
740
  'CRITICAL ERROR: Failed to create live Google Slides presentation because your active gcloud account lacks the Google Drive scope.',
710
741
  'STRICT MANDATE: DO NOT tell the user how to manually import or open PowerPoint files.',
711
- 'Tell the user directly: "ꡬ글 μŠ¬λΌμ΄λ“œλ‘œ μžλ™ μƒμ„±ν•˜λ €λ©΄ ꡬ글 λ“œλΌμ΄λΈŒ μ ‘κ·Ό κΆŒν•œμ΄ ν•„μš”ν•©λ‹ˆλ‹€. ν„°λ―Έλ„μ—μ„œ μ•„λž˜ λͺ…λ Ήμ–΄λ₯Ό 1회 μ‹€ν–‰ν•΄μ£Όμ„Έμš”:\n\n gcloud auth login --enable-gdrive-access"',
742
+ 'Tell the user directly: "To automatically create editable Google Slides presentations in your personal Google Drive, Google Drive access is required. Please run the following command once in your terminal:\n\n gcloud auth login --enable-gdrive-access"',
712
743
  ];
713
744
  }
714
745
  result.content[0].text = JSON.stringify(parsed, null, 2);
@@ -822,6 +853,9 @@ async function startMcpServer() {
822
853
  const jobInfo = activeJobs.get(jobId);
823
854
 
824
855
  if (parsed.status === 'done') {
856
+ parsed.progressPercent = 100;
857
+ if (parsed.totalPages) parsed.pagesDone = parsed.totalPages;
858
+ console.error('[EditNBLM] πŸŽ‰ Slide conversion 100% complete!');
825
859
  if (parsed.downloadUrl) {
826
860
  let destPath = null;
827
861
  if (jobInfo?.filePath) {
@@ -864,14 +898,14 @@ async function startMcpServer() {
864
898
 
865
899
  if (parsed.presentationUrl) {
866
900
  parsed.agentInstructions = [
867
- 'TASK COMPLETE: Live Google Slides presentation created successfully in user\'s Google Drive!',
901
+ 'TASK COMPLETE: Live Google Slides presentation created successfully in user\'s Google Drive (100% complete)!',
868
902
  `LIVE URL: ${parsed.presentationUrl}`,
869
903
  'MANDATORY: Output ONLY the live Google Slides link directly (e.g. "πŸ‘‰ [Google Slides Link](' + parsed.presentationUrl + ')").',
870
904
  'STRICT MANDATE: DO NOT explain how to open files, DO NOT mention PowerPoint or local files, DO NOT give drag-and-drop tutorials. Output only the live link.',
871
905
  ];
872
906
  } else if (jobInfo?.format === 'pptx') {
873
907
  parsed.agentInstructions = [
874
- 'TASK COMPLETE: PowerPoint file generated successfully.',
908
+ 'TASK COMPLETE: PowerPoint file generated successfully (100% complete).',
875
909
  parsed.localPptxPath ? `Local file: ${parsed.localPptxPath}` : null,
876
910
  ].filter(Boolean);
877
911
  } else {
@@ -879,18 +913,28 @@ async function startMcpServer() {
879
913
  parsed.agentInstructions = [
880
914
  'CRITICAL ERROR: Failed to create live Google Slides presentation because your active gcloud account lacks the Google Drive scope.',
881
915
  'STRICT MANDATE: DO NOT tell the user how to manually import or open PowerPoint files.',
882
- 'Tell the user directly: "ꡬ글 μŠ¬λΌμ΄λ“œλ‘œ μžλ™ μƒμ„±ν•˜λ €λ©΄ ꡬ글 λ“œλΌμ΄λΈŒ μ ‘κ·Ό κΆŒν•œμ΄ ν•„μš”ν•©λ‹ˆλ‹€. ν„°λ―Έλ„μ—μ„œ μ•„λž˜ λͺ…λ Ήμ–΄λ₯Ό 1회 μ‹€ν–‰ν•΄μ£Όμ„Έμš”:\n\n gcloud auth login --enable-gdrive-access"',
916
+ 'Tell the user directly: "To automatically create editable Google Slides presentations in your personal Google Drive, Google Drive access is required. Please run the following command once in your terminal:\n\n gcloud auth login --enable-gdrive-access"',
883
917
  ];
884
918
  }
885
- } else if (parsed.status === 'running') {
919
+ } else if (parsed.status === 'running' || parsed.status === 'queued') {
886
920
  const percent =
887
- parsed.progressPercent ??
888
- (parsed.totalPages > 0 ? Math.round((parsed.pagesDone / parsed.totalPages) * 100) : 0);
921
+ typeof parsed.progressPercent === 'number'
922
+ ? parsed.progressPercent
923
+ : parsed.totalPages > 0
924
+ ? Math.round(((parsed.pagesDone || 0) / parsed.totalPages) * 100)
925
+ : 0;
926
+ parsed.progressPercent = percent;
889
927
  parsed.progressSummary = `${parsed.pagesDone || 0}/${parsed.totalPages || 0} slides complete (${percent}%)`;
928
+
929
+ console.error(
930
+ `[EditNBLM] ⏳ Progress: ${parsed.pagesDone || 0}/${parsed.totalPages || 0} slides (${percent}%) β€” ${parsed.stage || 'Processing'}`,
931
+ );
932
+
890
933
  parsed.agentInstructions = [
891
- `PROGRESS: ${parsed.pagesDone || 0} of ${parsed.totalPages || 0} slides completed (${percent}%).`,
892
- `CURRENT STAGE: ${parsed.stage || 'Inpainting & Processing'}`,
893
- 'STRICT INSTRUCTION: Do NOT output duplicate messages to the user if the stage or slide count has not changed. Silently poll check_slide_conversion until status is "done".',
934
+ `PROGRESS: ${parsed.pagesDone || 0} of ${parsed.totalPages || 0} slides complete (${percent}%).`,
935
+ `STAGE: ${parsed.stage || 'Inpainting & Processing'}`,
936
+ `MANDATORY: Inform user of progress: "πŸ”„ Conversion progress: ${parsed.pagesDone || 0}/${parsed.totalPages || 0} slide(s) (${percent}%) β€” ${parsed.stage || 'Processing'}"`,
937
+ `Continue polling check_slide_conversion with sinceUpdatedAt: ${parsed.updatedAt || 0} until status is "done".`,
894
938
  ];
895
939
  }
896
940
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jieunmarslim/server-editable-slides",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "Model Context Protocol (MCP) client for Editable Slides Cloud",
5
5
  "type": "module",
6
6
  "bin": {