@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.
- package/index.js +140 -96
- 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
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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(
|
|
239
|
-
console.error(` β
|
|
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
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
//
|
|
263
|
+
// 3. Check Project Configuration & Account Access Permissions
|
|
248
264
|
let projectAccessOk = false;
|
|
249
265
|
if (!project) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
console.error(
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
);
|
|
285
|
-
console.error(`
|
|
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
|
-
|
|
294
|
-
`
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
301
|
-
|
|
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
|
-
|
|
321
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
);
|
|
328
|
-
console.error(
|
|
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
|
-
|
|
349
|
-
|
|
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
|
-
|
|
353
|
-
|
|
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
|
-
//
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
370
|
-
|
|
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('
|
|
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: "
|
|
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: "
|
|
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
|
-
|
|
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
|
|
892
|
-
`
|
|
893
|
-
|
|
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
|
|