@crypto512/jicon-mcp 1.1.0 → 1.2.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/PROMPT.md +214 -0
- package/README.md +54 -8
- package/TOOL_LIST.md +241 -58
- package/dist/confluence/client.d.ts +7 -2
- package/dist/confluence/client.d.ts.map +1 -1
- package/dist/confluence/client.js +28 -10
- package/dist/confluence/client.js.map +1 -1
- package/dist/confluence/tools.d.ts +64 -8
- package/dist/confluence/tools.d.ts.map +1 -1
- package/dist/confluence/tools.js +587 -33
- package/dist/confluence/tools.js.map +1 -1
- package/dist/permissions/tool-registry.d.ts +9 -9
- package/dist/permissions/tool-registry.d.ts.map +1 -1
- package/dist/permissions/tool-registry.js +10 -3
- package/dist/permissions/tool-registry.js.map +1 -1
- package/dist/permissions/write-home-validator.d.ts.map +1 -1
- package/dist/permissions/write-home-validator.js +23 -3
- package/dist/permissions/write-home-validator.js.map +1 -1
- package/dist/tempo/client.js +1 -1
- package/dist/tempo/client.js.map +1 -1
- package/dist/tempo/tools.js +1 -1
- package/dist/tempo/tools.js.map +1 -1
- package/dist/utils/buffer-tools.d.ts +10 -0
- package/dist/utils/buffer-tools.d.ts.map +1 -1
- package/dist/utils/buffer-tools.js +139 -28
- package/dist/utils/buffer-tools.js.map +1 -1
- package/dist/utils/content-buffer.d.ts +5 -1
- package/dist/utils/content-buffer.d.ts.map +1 -1
- package/dist/utils/content-buffer.js +6 -3
- package/dist/utils/content-buffer.js.map +1 -1
- package/dist/utils/jicon-help.d.ts +1 -1
- package/dist/utils/jicon-help.d.ts.map +1 -1
- package/dist/utils/jicon-help.js +78 -19
- package/dist/utils/jicon-help.js.map +1 -1
- package/dist/utils/plantuml/client.d.ts +15 -1
- package/dist/utils/plantuml/client.d.ts.map +1 -1
- package/dist/utils/plantuml/client.js +56 -3
- package/dist/utils/plantuml/client.js.map +1 -1
- package/dist/utils/plantuml/include-expander.d.ts +15 -0
- package/dist/utils/plantuml/include-expander.d.ts.map +1 -1
- package/dist/utils/plantuml/include-expander.js +47 -8
- package/dist/utils/plantuml/include-expander.js.map +1 -1
- package/dist/utils/plantuml/index.d.ts +1 -1
- package/dist/utils/plantuml/index.d.ts.map +1 -1
- package/dist/utils/plantuml/index.js +1 -1
- package/dist/utils/plantuml/index.js.map +1 -1
- package/dist/utils/plantuml/service.d.ts +1 -1
- package/dist/utils/plantuml/service.d.ts.map +1 -1
- package/dist/utils/plantuml/service.js +1 -1
- package/dist/utils/plantuml/service.js.map +1 -1
- package/dist/utils/plantuml/tools.d.ts.map +1 -1
- package/dist/utils/plantuml/tools.js +5 -2
- package/dist/utils/plantuml/tools.js.map +1 -1
- package/dist/utils/url-tools.d.ts +27 -1
- package/dist/utils/url-tools.d.ts.map +1 -1
- package/dist/utils/url-tools.js +142 -1
- package/dist/utils/url-tools.js.map +1 -1
- package/dist/utils/xhtml/index.d.ts +1 -1
- package/dist/utils/xhtml/index.d.ts.map +1 -1
- package/dist/utils/xhtml/index.js +1 -1
- package/dist/utils/xhtml/index.js.map +1 -1
- package/dist/utils/xhtml/plantuml.d.ts +24 -6
- package/dist/utils/xhtml/plantuml.d.ts.map +1 -1
- package/dist/utils/xhtml/plantuml.js +70 -12
- package/dist/utils/xhtml/plantuml.js.map +1 -1
- package/dist/utils/xhtml/validator.js +2 -2
- package/dist/utils/xhtml/validator.js.map +1 -1
- package/package.json +2 -2
package/dist/confluence/tools.js
CHANGED
|
@@ -7,7 +7,9 @@ import { contentBuffer } from "../utils/content-buffer.js";
|
|
|
7
7
|
import { formatPageMetadata } from "./formatters.js";
|
|
8
8
|
import { validateXhtmlAsync, parseXhtml, parseStructure, serializeXhtml, enhanceXhtmlError } from "../utils/xhtml/index.js";
|
|
9
9
|
import { detectRawPlantUml, detectDiagramType } from "../utils/xhtml/plantuml.js";
|
|
10
|
-
import { expandPlantUmlInXhtml } from "../utils/plantuml/index.js";
|
|
10
|
+
import { expandPlantUmlInXhtml, collapseExpandedIncludesInXhtml } from "../utils/plantuml/index.js";
|
|
11
|
+
import { parseUrl } from "../utils/url-tools.js";
|
|
12
|
+
import { DEFAULT_PAGE_EXPAND } from "./defaults.js";
|
|
11
13
|
/**
|
|
12
14
|
* Generate a summary of content structure for draft responses.
|
|
13
15
|
* Helps AI assistants verify that diagrams and other elements are included.
|
|
@@ -225,7 +227,7 @@ WARNING: Use text~ (not content~ or body~). Use space KEY (not name).`,
|
|
|
225
227
|
hint += "- ✅ RIGHT: (text~\"a\" OR text~\"b\") - Each term needs its own text~\n";
|
|
226
228
|
}
|
|
227
229
|
hint += "\nExamples:\n";
|
|
228
|
-
hint += " text~\"
|
|
230
|
+
hint += " text~\"Mike Tasc\" - finds pages mentioning this person\n";
|
|
229
231
|
hint += " text~\"meeting\" AND space=MESH - finds meetings in MESH space\n";
|
|
230
232
|
hint += " title~\"sprint review\" - finds pages with sprint review in title";
|
|
231
233
|
return formatError({
|
|
@@ -256,7 +258,7 @@ WARNING: Use text~ (not content~ or body~). Use space KEY (not name).`,
|
|
|
256
258
|
PREFERRED method when you have a page ID from search results. Faster and more reliable than get_page_by_title.
|
|
257
259
|
|
|
258
260
|
Returns pageId, version, bufferId, and structure (element IDs) for structured editing.
|
|
259
|
-
Use buffer_edit
|
|
261
|
+
Use buffer_edit(bufferId, after=ID, content/plantuml/fromBufferId) to add content, then confluence_draft_create(pageId=..., bufferId=...) for user review.`,
|
|
260
262
|
inputSchema: z.object({
|
|
261
263
|
pageId: z.coerce.string().describe("Page ID (accepts string or number)"),
|
|
262
264
|
expand: z
|
|
@@ -267,7 +269,9 @@ Use buffer_edit with element IDs to modify, then confluence_draft_create for use
|
|
|
267
269
|
handler: async (args) => {
|
|
268
270
|
try {
|
|
269
271
|
const result = await client.getPage(args.pageId, args.expand);
|
|
270
|
-
const
|
|
272
|
+
const rawContent = result.body?.storage?.value || "";
|
|
273
|
+
// Collapse expanded includes back to !include directives
|
|
274
|
+
const content = collapseExpandedIncludesInXhtml(rawContent);
|
|
271
275
|
// Store content with element IDs for structured editing
|
|
272
276
|
const { bufferId, structure } = storeXhtmlWithStructure(content, {
|
|
273
277
|
resourceType: "confluence_page",
|
|
@@ -284,7 +288,7 @@ Use buffer_edit with element IDs to modify, then confluence_draft_create for use
|
|
|
284
288
|
bufferId,
|
|
285
289
|
...(structure && { structure }),
|
|
286
290
|
contentSize: content.length,
|
|
287
|
-
message: "Page loaded. Use buffer_edit to modify, then confluence_draft_create for user review.",
|
|
291
|
+
message: "Page loaded. Use buffer_edit to modify, then confluence_draft_create(pageId=..., bufferId=...) for user review.",
|
|
288
292
|
});
|
|
289
293
|
}
|
|
290
294
|
catch (error) {
|
|
@@ -298,7 +302,7 @@ Use buffer_edit with element IDs to modify, then confluence_draft_create for use
|
|
|
298
302
|
Use ONLY when you don't have a page ID. IMPORTANT: Use the space KEY (e.g. 'MESH', 'TC'), NOT the space name.
|
|
299
303
|
|
|
300
304
|
Returns pageId, version, bufferId, and structure (element IDs) for structured editing.
|
|
301
|
-
Use buffer_edit
|
|
305
|
+
Use buffer_edit(bufferId, after=ID, content/plantuml/fromBufferId) to add content, then confluence_draft_create(pageId=..., bufferId=...) for user review.`,
|
|
302
306
|
inputSchema: z.object({
|
|
303
307
|
spaceKey: z.string().describe("Space key (short code like 'MESH', 'TC'), NOT the full space name"),
|
|
304
308
|
title: z.string().describe("Page title"),
|
|
@@ -314,7 +318,9 @@ Use buffer_edit with element IDs to modify, then confluence_draft_create for use
|
|
|
314
318
|
statusCode: 404,
|
|
315
319
|
});
|
|
316
320
|
}
|
|
317
|
-
const
|
|
321
|
+
const rawContent = result.body?.storage?.value || "";
|
|
322
|
+
// Collapse expanded includes back to !include directives
|
|
323
|
+
const content = collapseExpandedIncludesInXhtml(rawContent);
|
|
318
324
|
// Store content with element IDs for structured editing
|
|
319
325
|
const { bufferId, structure } = storeXhtmlWithStructure(content, {
|
|
320
326
|
resourceType: "confluence_page",
|
|
@@ -331,7 +337,196 @@ Use buffer_edit with element IDs to modify, then confluence_draft_create for use
|
|
|
331
337
|
bufferId,
|
|
332
338
|
...(structure && { structure }),
|
|
333
339
|
contentSize: content.length,
|
|
334
|
-
message: "Page loaded. Use buffer_edit to modify, then confluence_draft_create for user review.",
|
|
340
|
+
message: "Page loaded. Use buffer_edit to modify, then confluence_draft_create(pageId=..., bufferId=...) for user review.",
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
return formatError(isApiError(error) ? error : new Error(String(error)));
|
|
345
|
+
}
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
confluence_edit: {
|
|
349
|
+
description: `Smart page/draft loader - auto-resolves URLs, pageIds, draftIds, or SPACE/Title.
|
|
350
|
+
|
|
351
|
+
ACCEPTS ANY OF:
|
|
352
|
+
- Full URL: https://confluence.example.com/pages/viewpage.action?pageId=123
|
|
353
|
+
- Full URL: https://confluence.example.com/pages/resumedraft.action?draftId=456
|
|
354
|
+
- Full URL: https://confluence.example.com/display/SPACE/Page+Title
|
|
355
|
+
- Page ID: "123456"
|
|
356
|
+
- Draft ID: "draft:123456" (prefix with "draft:")
|
|
357
|
+
- Space/Title: "DOCS/API Guide"
|
|
358
|
+
|
|
359
|
+
SMART BEHAVIOR:
|
|
360
|
+
- URLs are parsed automatically to extract pageId or draftId
|
|
361
|
+
- Draft IDs: tries to load draft; if 404 (published), finds page by title
|
|
362
|
+
- Returns bufferId + structure + pageId for editing
|
|
363
|
+
|
|
364
|
+
WORKFLOW:
|
|
365
|
+
1. confluence_edit(input) → bufferId, structure, pageId
|
|
366
|
+
2. buffer_edit(bufferId, ...) → modify content
|
|
367
|
+
3. confluence_draft_create(pageId=..., bufferId=...) → draft linked to original page
|
|
368
|
+
4. User publishes via Confluence UI (updates original page)
|
|
369
|
+
5. For more edits: confluence_edit(same URL or "SPACE/Title") → auto-resolves`,
|
|
370
|
+
inputSchema: z.object({
|
|
371
|
+
input: z.string().describe('URL, pageId, "draft:ID", or "SPACE/Title"'),
|
|
372
|
+
}),
|
|
373
|
+
handler: async (args) => {
|
|
374
|
+
const input = args.input.trim();
|
|
375
|
+
if (!input) {
|
|
376
|
+
return formatError({
|
|
377
|
+
error: true,
|
|
378
|
+
message: "Input is required. Provide a URL, pageId, draft:ID, or SPACE/Title.",
|
|
379
|
+
statusCode: 400,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
// Helper to load page content and return formatted result
|
|
383
|
+
const loadPageContent = async (pageResult) => {
|
|
384
|
+
const rawContent = pageResult.body?.storage?.value || "";
|
|
385
|
+
const content = collapseExpandedIncludesInXhtml(rawContent);
|
|
386
|
+
const { bufferId, structure } = storeXhtmlWithStructure(content, {
|
|
387
|
+
resourceType: "confluence_page",
|
|
388
|
+
resourceId: String(pageResult.id),
|
|
389
|
+
contentType: "xhtml",
|
|
390
|
+
version: pageResult.version?.number,
|
|
391
|
+
spaceKey: pageResult.space?.key,
|
|
392
|
+
title: pageResult.title,
|
|
393
|
+
});
|
|
394
|
+
return {
|
|
395
|
+
pageId: pageResult.id,
|
|
396
|
+
spaceKey: pageResult.space?.key,
|
|
397
|
+
title: pageResult.title,
|
|
398
|
+
version: pageResult.version?.number,
|
|
399
|
+
bufferId,
|
|
400
|
+
structure,
|
|
401
|
+
contentSize: content.length,
|
|
402
|
+
message: "Content loaded. Use buffer_edit(bufferId, after=ID, content/plantuml/fromBufferId) to add content, then confluence_draft_create(pageId=..., bufferId=...) for user review.",
|
|
403
|
+
};
|
|
404
|
+
};
|
|
405
|
+
// Helper to load draft content
|
|
406
|
+
const loadDraftContent = async (draftId) => {
|
|
407
|
+
const result = await client.getDraft(draftId);
|
|
408
|
+
const rawContent = result.body?.storage?.value || "";
|
|
409
|
+
const content = collapseExpandedIncludesInXhtml(rawContent);
|
|
410
|
+
const { bufferId, structure } = storeXhtmlWithStructure(content, {
|
|
411
|
+
resourceType: "confluence_page",
|
|
412
|
+
resourceId: String(result.id),
|
|
413
|
+
contentType: "xhtml",
|
|
414
|
+
spaceKey: result.space?.key,
|
|
415
|
+
title: result.title,
|
|
416
|
+
isDraft: true,
|
|
417
|
+
});
|
|
418
|
+
return {
|
|
419
|
+
draftId: result.id,
|
|
420
|
+
spaceKey: result.space?.key,
|
|
421
|
+
title: result.title,
|
|
422
|
+
bufferId,
|
|
423
|
+
structure,
|
|
424
|
+
contentSize: content.length,
|
|
425
|
+
message: "Draft loaded. Use buffer_edit to modify, then confluence_draft_save to update.",
|
|
426
|
+
};
|
|
427
|
+
};
|
|
428
|
+
try {
|
|
429
|
+
// Case 1: Full URL
|
|
430
|
+
if (input.startsWith("http://") || input.startsWith("https://")) {
|
|
431
|
+
const parsed = parseUrl(input);
|
|
432
|
+
if (parsed.type === "confluence_page" && parsed.pageId) {
|
|
433
|
+
const result = await client.getPage(parsed.pageId);
|
|
434
|
+
return formatSuccess(await loadPageContent(result));
|
|
435
|
+
}
|
|
436
|
+
if (parsed.type === "confluence_draft" && parsed.draftId) {
|
|
437
|
+
try {
|
|
438
|
+
return formatSuccess(await loadDraftContent(parsed.draftId));
|
|
439
|
+
}
|
|
440
|
+
catch (error) {
|
|
441
|
+
// Draft may have been published - try to find page by title
|
|
442
|
+
if (isApiError(error) && error.statusCode === 404 && parsed.spaceKey && parsed.title) {
|
|
443
|
+
const pageResult = await client.getPageByTitle(parsed.spaceKey, parsed.title);
|
|
444
|
+
if (pageResult) {
|
|
445
|
+
return formatSuccess({
|
|
446
|
+
...await loadPageContent(pageResult),
|
|
447
|
+
note: "Draft was published. Loaded the published page instead.",
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
throw error;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (parsed.type === "confluence_space_path" && parsed.spaceKey && parsed.title) {
|
|
455
|
+
const pageResult = await client.getPageByTitle(parsed.spaceKey, parsed.title);
|
|
456
|
+
if (!pageResult) {
|
|
457
|
+
return formatError({
|
|
458
|
+
error: true,
|
|
459
|
+
message: `Page not found: "${parsed.title}" in space ${parsed.spaceKey}`,
|
|
460
|
+
statusCode: 404,
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
return formatSuccess(await loadPageContent(pageResult));
|
|
464
|
+
}
|
|
465
|
+
return formatError({
|
|
466
|
+
error: true,
|
|
467
|
+
message: `Unable to parse URL: "${input}". Use Confluence page, draft, or display URL.`,
|
|
468
|
+
statusCode: 400,
|
|
469
|
+
});
|
|
470
|
+
}
|
|
471
|
+
// Case 2: Draft ID (prefix with "draft:")
|
|
472
|
+
if (input.startsWith("draft:")) {
|
|
473
|
+
const draftId = input.substring(6);
|
|
474
|
+
try {
|
|
475
|
+
return formatSuccess(await loadDraftContent(draftId));
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
if (isApiError(error) && error.statusCode === 404) {
|
|
479
|
+
return formatError({
|
|
480
|
+
error: true,
|
|
481
|
+
message: `Draft ${draftId} not found. It may have been published. Try using the page URL or "SPACE/Title" format.`,
|
|
482
|
+
statusCode: 404,
|
|
483
|
+
details: {
|
|
484
|
+
hint: "If the draft was published, ask the user for the published page URL.",
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Case 3: Space/Title format (e.g., "DOCS/API Guide")
|
|
492
|
+
if (input.includes("/") && !input.match(/^\d+$/)) {
|
|
493
|
+
const slashIndex = input.indexOf("/");
|
|
494
|
+
const spaceKey = input.substring(0, slashIndex).trim();
|
|
495
|
+
const title = input.substring(slashIndex + 1).trim();
|
|
496
|
+
if (!spaceKey || !title) {
|
|
497
|
+
return formatError({
|
|
498
|
+
error: true,
|
|
499
|
+
message: `Invalid SPACE/Title format: "${input}". Expected "SPACEKEY/Page Title".`,
|
|
500
|
+
statusCode: 400,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
const pageResult = await client.getPageByTitle(spaceKey, title);
|
|
504
|
+
if (!pageResult) {
|
|
505
|
+
return formatError({
|
|
506
|
+
error: true,
|
|
507
|
+
message: `Page not found: "${title}" in space ${spaceKey}`,
|
|
508
|
+
statusCode: 404,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
return formatSuccess(await loadPageContent(pageResult));
|
|
512
|
+
}
|
|
513
|
+
// Case 4: Pure numeric page ID
|
|
514
|
+
if (/^\d+$/.test(input)) {
|
|
515
|
+
const result = await client.getPage(input);
|
|
516
|
+
return formatSuccess(await loadPageContent(result));
|
|
517
|
+
}
|
|
518
|
+
return formatError({
|
|
519
|
+
error: true,
|
|
520
|
+
message: `Unable to parse input: "${input}". Expected URL, pageId, "draft:ID", or "SPACE/Title".`,
|
|
521
|
+
statusCode: 400,
|
|
522
|
+
details: {
|
|
523
|
+
examples: [
|
|
524
|
+
"https://confluence.example.com/pages/viewpage.action?pageId=123",
|
|
525
|
+
"123456 (page ID)",
|
|
526
|
+
"draft:456789 (draft ID)",
|
|
527
|
+
"DOCS/API Guide (space/title)",
|
|
528
|
+
],
|
|
529
|
+
},
|
|
335
530
|
});
|
|
336
531
|
}
|
|
337
532
|
catch (error) {
|
|
@@ -521,22 +716,32 @@ Returns the user's personal space key and details. Use this to verify your perso
|
|
|
521
716
|
confluence_draft_create: {
|
|
522
717
|
description: `Create a Confluence draft for user review. Returns draftId, bufferId, structure (element IDs), and clickable URL.
|
|
523
718
|
|
|
719
|
+
IMPORTANT: Call help(topic="plantuml") BEFORE creating content with PlantUML diagrams.
|
|
720
|
+
IMPORTANT: Call help(topic="storage") BEFORE creating XHTML content for proper syntax.
|
|
524
721
|
IMPORTANT: User must validate the draft in Confluence UI before publishing.
|
|
525
|
-
IMPORTANT: Raw @startuml outside macros is NOT supported. Use buffer_edit
|
|
722
|
+
IMPORTANT: Raw @startuml outside macros is NOT supported. Use buffer_edit with plantuml parameter.
|
|
526
723
|
|
|
527
|
-
|
|
724
|
+
Two modes:
|
|
725
|
+
1. NEW PAGE: Provide spaceKey + title + content/bufferId → creates standalone draft
|
|
726
|
+
2. EDIT EXISTING PAGE (Review Workflow): Provide pageId + bufferId
|
|
727
|
+
- Creates "[jicon-mcp REVIEW] Title" draft linked to original via label
|
|
728
|
+
- REQUIRED: bufferId must come from confluence_get_page(pageId) or confluence_edit(pageId)
|
|
729
|
+
- Use confluence_review_publish(draftId) to apply changes to original
|
|
730
|
+
- Use confluence_review_discard(draftId) to cancel without changes
|
|
731
|
+
- Use confluence_review_list() to find all review drafts
|
|
528
732
|
|
|
529
|
-
Workflow:
|
|
530
|
-
1.
|
|
531
|
-
2.
|
|
532
|
-
3.
|
|
533
|
-
4.
|
|
534
|
-
5.
|
|
733
|
+
Workflow for editing existing page:
|
|
734
|
+
1. confluence_get_page(pageId) → bufferId, structure
|
|
735
|
+
2. buffer_edit(bufferId, ...) → modify content
|
|
736
|
+
3. confluence_draft_create(pageId=..., bufferId=...) → creates review draft
|
|
737
|
+
4. User reviews in Confluence UI
|
|
738
|
+
5. confluence_review_publish(draftId) → applies changes to original page
|
|
535
739
|
|
|
536
740
|
Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit).`,
|
|
537
741
|
inputSchema: z.object({
|
|
538
|
-
|
|
539
|
-
|
|
742
|
+
pageId: z.coerce.string().optional().describe("Existing page ID to create edit draft for. When provided, bufferId must come from that page."),
|
|
743
|
+
spaceKey: z.string().optional().describe("Space key (required for new pages, auto-populated when pageId is provided)"),
|
|
744
|
+
title: z.string().optional().describe("Page title (required for new pages, auto-populated when pageId is provided)"),
|
|
540
745
|
content: z
|
|
541
746
|
.string()
|
|
542
747
|
.optional()
|
|
@@ -546,6 +751,83 @@ Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit)
|
|
|
546
751
|
labels: z.array(z.string()).optional().describe("Array of labels"),
|
|
547
752
|
}),
|
|
548
753
|
handler: async (args) => {
|
|
754
|
+
// Determine the mode: editing existing page vs creating new draft
|
|
755
|
+
let spaceKey = args.spaceKey;
|
|
756
|
+
let title = args.title;
|
|
757
|
+
let parentId = args.parentId;
|
|
758
|
+
let originalPageId;
|
|
759
|
+
let originalPageVersion;
|
|
760
|
+
if (args.pageId) {
|
|
761
|
+
// MODE: Edit existing page - validate bufferId came from this page
|
|
762
|
+
if (!args.bufferId) {
|
|
763
|
+
return formatError({
|
|
764
|
+
error: true,
|
|
765
|
+
message: "When pageId is provided, bufferId is required (must come from confluence_get_page or confluence_edit of that page)",
|
|
766
|
+
statusCode: 400,
|
|
767
|
+
details: {
|
|
768
|
+
hint: "First call confluence_get_page(pageId) or confluence_edit(pageId) to get a bufferId, then modify with buffer_edit",
|
|
769
|
+
},
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
// Validate buffer originated from the specified page
|
|
773
|
+
const bufferInfo = contentBuffer.getInfo(args.bufferId);
|
|
774
|
+
if (!bufferInfo) {
|
|
775
|
+
return formatError({
|
|
776
|
+
error: true,
|
|
777
|
+
message: `Buffer not found or expired: ${args.bufferId}`,
|
|
778
|
+
statusCode: 404,
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
const bufferSourceId = bufferInfo.metadata?.resourceId;
|
|
782
|
+
if (bufferSourceId !== args.pageId) {
|
|
783
|
+
return formatError({
|
|
784
|
+
error: true,
|
|
785
|
+
message: `Buffer '${args.bufferId}' does not belong to page '${args.pageId}'`,
|
|
786
|
+
statusCode: 400,
|
|
787
|
+
details: {
|
|
788
|
+
bufferSource: bufferSourceId || "unknown",
|
|
789
|
+
expectedPage: args.pageId,
|
|
790
|
+
hint: "Two options: (1) Load page first with confluence_edit(pageId) then use buffer_edit to modify, OR (2) If you already created content in a separate buffer, use buffer_edit(pageBufferId, after=N, fromBufferId=yourNewBuffer) to merge it into the page buffer",
|
|
791
|
+
},
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
// Fetch original page info to get space, title, parent, etc.
|
|
795
|
+
try {
|
|
796
|
+
const originalPage = await client.getPage(args.pageId, ["space", "ancestors", "version"]);
|
|
797
|
+
spaceKey = originalPage.space?.key;
|
|
798
|
+
title = args.title || originalPage.title; // Allow title override
|
|
799
|
+
originalPageId = originalPage.id;
|
|
800
|
+
originalPageVersion = originalPage.version?.number;
|
|
801
|
+
// Get parent from ancestors if not explicitly provided
|
|
802
|
+
if (!parentId && originalPage.ancestors && originalPage.ancestors.length > 0) {
|
|
803
|
+
parentId = originalPage.ancestors[originalPage.ancestors.length - 1].id;
|
|
804
|
+
}
|
|
805
|
+
if (!spaceKey) {
|
|
806
|
+
return formatError({
|
|
807
|
+
error: true,
|
|
808
|
+
message: `Could not determine space key for page ${args.pageId}`,
|
|
809
|
+
statusCode: 400,
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
catch (error) {
|
|
814
|
+
return formatError({
|
|
815
|
+
error: true,
|
|
816
|
+
message: `Failed to fetch original page ${args.pageId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
817
|
+
statusCode: 404,
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
// MODE: New page - require spaceKey and title
|
|
823
|
+
if (!spaceKey || !title) {
|
|
824
|
+
return formatError({
|
|
825
|
+
error: true,
|
|
826
|
+
message: "For new drafts, both 'spaceKey' and 'title' are required. For editing existing pages, provide 'pageId' instead.",
|
|
827
|
+
statusCode: 400,
|
|
828
|
+
});
|
|
829
|
+
}
|
|
830
|
+
}
|
|
549
831
|
// Resolve content from either content string or bufferId
|
|
550
832
|
const resolved = resolveContentFromBuffer(args.content, args.bufferId);
|
|
551
833
|
if (resolved.error) {
|
|
@@ -580,14 +862,66 @@ Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit)
|
|
|
580
862
|
// Expand PlantUML !include directives before sending to Confluence
|
|
581
863
|
const expandedContent = await expandPlantUmlInXhtml(content);
|
|
582
864
|
try {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
865
|
+
// Constants for review workflow
|
|
866
|
+
const REVIEW_PREFIX = '[jicon-mcp REVIEW] ';
|
|
867
|
+
const REVIEW_LABEL_PREFIX = 'jicon-review-';
|
|
868
|
+
let result;
|
|
869
|
+
let existingReviewDraft = null;
|
|
870
|
+
let reviewLabel;
|
|
871
|
+
if (originalPageId) {
|
|
872
|
+
// REVIEW WORKFLOW: Creating/updating review draft for existing page
|
|
873
|
+
reviewLabel = `${REVIEW_LABEL_PREFIX}${originalPageId}`;
|
|
874
|
+
// Check if review draft already exists for this page
|
|
875
|
+
existingReviewDraft = await client.findDraftByLabel(reviewLabel);
|
|
876
|
+
if (existingReviewDraft) {
|
|
877
|
+
// Delete existing review draft to update it (API doesn't support draft updates)
|
|
878
|
+
await client.deleteDraft(existingReviewDraft.id);
|
|
879
|
+
}
|
|
880
|
+
// Prevent nested review drafts
|
|
881
|
+
const reviewTitle = title.startsWith(REVIEW_PREFIX)
|
|
882
|
+
? title
|
|
883
|
+
: `${REVIEW_PREFIX}${title}`;
|
|
884
|
+
// Create review draft with label linking to original page
|
|
885
|
+
const labels = [...(args.labels || []), reviewLabel];
|
|
886
|
+
result = await client.createDraft({
|
|
887
|
+
spaceKey: spaceKey,
|
|
888
|
+
title: reviewTitle,
|
|
889
|
+
content: expandedContent,
|
|
890
|
+
parentId: parentId,
|
|
891
|
+
labels: labels,
|
|
892
|
+
});
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
// NEW PAGE MODE: Create standalone draft
|
|
896
|
+
result = await client.createDraft({
|
|
897
|
+
spaceKey: spaceKey,
|
|
898
|
+
title: title,
|
|
899
|
+
content: expandedContent,
|
|
900
|
+
parentId: parentId,
|
|
901
|
+
labels: args.labels,
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
// Verify draft is readable with labels (catches label attachment failures)
|
|
905
|
+
if (originalPageId && reviewLabel) {
|
|
906
|
+
const verifyDraft = await client.getDraft(result.id, [
|
|
907
|
+
...DEFAULT_PAGE_EXPAND,
|
|
908
|
+
"metadata.labels",
|
|
909
|
+
]);
|
|
910
|
+
const labels = verifyDraft.metadata?.labels?.results || [];
|
|
911
|
+
const hasReviewLabel = labels.some((l) => l.name === reviewLabel);
|
|
912
|
+
if (!hasReviewLabel) {
|
|
913
|
+
// Label attachment may have failed - try to add it again
|
|
914
|
+
// This can happen due to eventual consistency or API timing
|
|
915
|
+
try {
|
|
916
|
+
await client.addLabels(result.id, [reviewLabel], true);
|
|
917
|
+
}
|
|
918
|
+
catch {
|
|
919
|
+
// If label add fails, warn but continue (draft still exists)
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
590
923
|
// Store content with element IDs for structured editing
|
|
924
|
+
// Track originalPageId in metadata so we can link the draft to its source
|
|
591
925
|
const { bufferId: newBufferId } = storeXhtmlWithStructure(content, {
|
|
592
926
|
resourceType: "confluence_page",
|
|
593
927
|
resourceId: result.id,
|
|
@@ -595,13 +929,16 @@ Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit)
|
|
|
595
929
|
spaceKey: result.space?.key,
|
|
596
930
|
title: result.title,
|
|
597
931
|
isDraft: true,
|
|
932
|
+
originalPageId: originalPageId,
|
|
933
|
+
originalPageVersion: originalPageVersion,
|
|
598
934
|
});
|
|
599
935
|
// Build the draft edit URL (drafts use resumedraft.action, not the webui link)
|
|
600
936
|
const baseUrl = client.getBaseUrl();
|
|
601
937
|
const draftUrl = `${baseUrl}/pages/resumedraft.action?draftId=${result.id}`;
|
|
602
938
|
// Generate content summary for verification
|
|
603
939
|
const summary = getContentSummary(content);
|
|
604
|
-
|
|
940
|
+
// Build response with original page info if editing existing page
|
|
941
|
+
const response = {
|
|
605
942
|
draftId: result.id,
|
|
606
943
|
bufferId: newBufferId,
|
|
607
944
|
title: result.title,
|
|
@@ -616,11 +953,37 @@ Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit)
|
|
|
616
953
|
hasTable: summary.hasTable,
|
|
617
954
|
headings: summary.headingCount,
|
|
618
955
|
},
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
956
|
+
};
|
|
957
|
+
// Add review workflow info when editing existing page
|
|
958
|
+
if (originalPageId) {
|
|
959
|
+
response.reviewDraft = {
|
|
960
|
+
originalPageId: originalPageId,
|
|
961
|
+
originalPageVersion: originalPageVersion,
|
|
962
|
+
reviewLabel: reviewLabel,
|
|
963
|
+
wasUpdated: !!existingReviewDraft,
|
|
964
|
+
note: "This is a REVIEW draft linked to the original page.",
|
|
965
|
+
};
|
|
966
|
+
response.message = existingReviewDraft
|
|
967
|
+
? `Review draft updated for page ${originalPageId}. Use confluence_review_publish(${result.id}) to apply changes.`
|
|
968
|
+
: `Review draft created for page ${originalPageId}. Use confluence_review_publish(${result.id}) to apply changes.`;
|
|
969
|
+
response.nextSteps = {
|
|
970
|
+
toPublish: `confluence_review_publish(reviewDraftId="${result.id}")`,
|
|
971
|
+
toDiscard: `confluence_review_discard(reviewDraftId="${result.id}")`,
|
|
972
|
+
toList: "confluence_review_list()",
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
response.message = "Draft created. IMPORTANT: Display the URL above to the user so they can review and publish.";
|
|
977
|
+
response.afterPublish = {
|
|
978
|
+
note: "After user publishes, the draft ID becomes invalid.",
|
|
979
|
+
editAgain: `confluence_edit("${result.space?.key}/${result.title}")`,
|
|
980
|
+
tip: "Use confluence_edit with the same SPACE/Title or page URL to edit the published page.",
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
if (summary.plantumlCount === 0) {
|
|
984
|
+
response.warning = "No PlantUML diagrams found in content. If diagrams were expected, use buffer_edit with plantuml parameter.";
|
|
985
|
+
}
|
|
986
|
+
return formatSuccess(response);
|
|
624
987
|
}
|
|
625
988
|
catch (error) {
|
|
626
989
|
// Try to enhance XHTML errors with location info for targeted fixes
|
|
@@ -637,14 +1000,16 @@ Provide either 'content' (string) OR 'bufferId' (from buffer_create/buffer_edit)
|
|
|
637
1000
|
confluence_draft_open: {
|
|
638
1001
|
description: `Open an existing draft page for editing. Loads content into buffer with structure (element IDs).
|
|
639
1002
|
|
|
640
|
-
Use buffer_edit
|
|
1003
|
+
Use buffer_edit(bufferId, after=ID, content/plantuml/fromBufferId) to modify, then confluence_draft_save. User publishes via Confluence UI.`,
|
|
641
1004
|
inputSchema: z.object({
|
|
642
1005
|
draftId: z.coerce.string().describe("Draft page ID"),
|
|
643
1006
|
}),
|
|
644
1007
|
handler: async (args) => {
|
|
645
1008
|
try {
|
|
646
1009
|
const result = await client.getDraft(args.draftId);
|
|
647
|
-
const
|
|
1010
|
+
const rawContent = result.body?.storage?.value || "";
|
|
1011
|
+
// Collapse expanded includes back to !include directives
|
|
1012
|
+
const content = collapseExpandedIncludesInXhtml(rawContent);
|
|
648
1013
|
// Store content with element IDs for structured editing
|
|
649
1014
|
const { bufferId, structure } = storeXhtmlWithStructure(content, {
|
|
650
1015
|
resourceType: "confluence_page",
|
|
@@ -859,6 +1224,195 @@ Drafts are NOT sent to trash - they are permanently deleted.`,
|
|
|
859
1224
|
}
|
|
860
1225
|
},
|
|
861
1226
|
},
|
|
1227
|
+
// ========================================
|
|
1228
|
+
// Review Workflow Tools
|
|
1229
|
+
// ========================================
|
|
1230
|
+
// These tools manage the "[jicon-mcp REVIEW]" workflow for editing existing pages.
|
|
1231
|
+
// Review drafts are linked to original pages via labels and can be published
|
|
1232
|
+
// to apply changes to the original page.
|
|
1233
|
+
confluence_review_publish: {
|
|
1234
|
+
description: `Publish a review draft to apply changes to the original page.
|
|
1235
|
+
|
|
1236
|
+
This tool:
|
|
1237
|
+
1. Validates the draft is a "[jicon-mcp REVIEW]" draft with proper label
|
|
1238
|
+
2. Copies the draft content to the original page (creates new version)
|
|
1239
|
+
3. Deletes the review draft
|
|
1240
|
+
|
|
1241
|
+
Use this after user has reviewed the draft in Confluence UI.`,
|
|
1242
|
+
inputSchema: z.object({
|
|
1243
|
+
reviewDraftId: z.coerce.string().describe("ID of the [jicon-mcp REVIEW] draft to publish"),
|
|
1244
|
+
}),
|
|
1245
|
+
handler: async (args) => {
|
|
1246
|
+
const REVIEW_PREFIX = '[jicon-mcp REVIEW] ';
|
|
1247
|
+
const REVIEW_LABEL_PREFIX = 'jicon-review-';
|
|
1248
|
+
try {
|
|
1249
|
+
// 1. Get review draft with labels
|
|
1250
|
+
const reviewDraft = await client.getDraft(args.reviewDraftId, [
|
|
1251
|
+
...DEFAULT_PAGE_EXPAND,
|
|
1252
|
+
"metadata.labels",
|
|
1253
|
+
]);
|
|
1254
|
+
// 2. Validate it's a review draft
|
|
1255
|
+
if (!reviewDraft.title.startsWith(REVIEW_PREFIX)) {
|
|
1256
|
+
return formatError({
|
|
1257
|
+
error: true,
|
|
1258
|
+
message: `Not a jicon review draft. Title must start with "${REVIEW_PREFIX}"`,
|
|
1259
|
+
statusCode: 400,
|
|
1260
|
+
details: {
|
|
1261
|
+
actualTitle: reviewDraft.title,
|
|
1262
|
+
hint: "Use confluence_draft_create with pageId to create a review draft",
|
|
1263
|
+
},
|
|
1264
|
+
});
|
|
1265
|
+
}
|
|
1266
|
+
// 3. Find original page from label
|
|
1267
|
+
const labels = reviewDraft.metadata?.labels?.results || [];
|
|
1268
|
+
const reviewLabel = labels.find((l) => l.name.startsWith(REVIEW_LABEL_PREFIX));
|
|
1269
|
+
if (!reviewLabel) {
|
|
1270
|
+
return formatError({
|
|
1271
|
+
error: true,
|
|
1272
|
+
message: "Review draft missing link to original page (no jicon-review-* label found)",
|
|
1273
|
+
statusCode: 400,
|
|
1274
|
+
details: {
|
|
1275
|
+
labels: labels.map((l) => l.name),
|
|
1276
|
+
hint: "This draft may have been created manually. Use confluence_draft_create with pageId to create proper review drafts.",
|
|
1277
|
+
},
|
|
1278
|
+
});
|
|
1279
|
+
}
|
|
1280
|
+
const originalPageId = reviewLabel.name.replace(REVIEW_LABEL_PREFIX, '');
|
|
1281
|
+
// 4. Fetch original page to get current version
|
|
1282
|
+
let originalPage;
|
|
1283
|
+
try {
|
|
1284
|
+
originalPage = await client.getPage(originalPageId, ["version", "space"]);
|
|
1285
|
+
}
|
|
1286
|
+
catch (error) {
|
|
1287
|
+
return formatError({
|
|
1288
|
+
error: true,
|
|
1289
|
+
message: `Original page ${originalPageId} not found. It may have been deleted.`,
|
|
1290
|
+
statusCode: 404,
|
|
1291
|
+
details: {
|
|
1292
|
+
originalPageId,
|
|
1293
|
+
hint: "If the original page was deleted, use confluence_review_discard to remove this review draft.",
|
|
1294
|
+
},
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
// 5. Get the review draft content
|
|
1298
|
+
const reviewContent = reviewDraft.body?.storage?.value || "";
|
|
1299
|
+
// 6. Update original page with review content
|
|
1300
|
+
const updatedPage = await client.updatePage(originalPageId, originalPage.version?.number || 1, originalPage.title, // Keep original title (not the REVIEW prefix)
|
|
1301
|
+
reviewContent, false // Not a minor edit
|
|
1302
|
+
);
|
|
1303
|
+
// 7. Delete review draft
|
|
1304
|
+
await client.deleteDraft(args.reviewDraftId);
|
|
1305
|
+
// 8. Invalidate buffers
|
|
1306
|
+
contentBuffer.invalidateByMetadata({ resourceId: originalPageId });
|
|
1307
|
+
contentBuffer.invalidateByMetadata({ resourceId: args.reviewDraftId });
|
|
1308
|
+
// Build the page URL
|
|
1309
|
+
const baseUrl = client.getBaseUrl();
|
|
1310
|
+
const pageUrl = `${baseUrl}${updatedPage._links?.webui || `/pages/viewpage.action?pageId=${originalPageId}`}`;
|
|
1311
|
+
return formatSuccess({
|
|
1312
|
+
success: true,
|
|
1313
|
+
originalPageId,
|
|
1314
|
+
originalTitle: updatedPage.title,
|
|
1315
|
+
newVersion: updatedPage.version?.number,
|
|
1316
|
+
reviewDraftDeleted: args.reviewDraftId,
|
|
1317
|
+
viewUrl: pageUrl,
|
|
1318
|
+
message: `Changes from review draft applied to page "${updatedPage.title}" (version ${updatedPage.version?.number}). Review draft deleted.`,
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
catch (error) {
|
|
1322
|
+
return formatError(isApiError(error) ? error : new Error(String(error)));
|
|
1323
|
+
}
|
|
1324
|
+
},
|
|
1325
|
+
},
|
|
1326
|
+
confluence_review_discard: {
|
|
1327
|
+
description: `Discard a review draft without applying changes to the original page.
|
|
1328
|
+
|
|
1329
|
+
This tool:
|
|
1330
|
+
1. Validates the draft is a "[jicon-mcp REVIEW]" draft
|
|
1331
|
+
2. Deletes the review draft permanently
|
|
1332
|
+
3. Original page remains unchanged`,
|
|
1333
|
+
inputSchema: z.object({
|
|
1334
|
+
reviewDraftId: z.coerce.string().describe("ID of the [jicon-mcp REVIEW] draft to discard"),
|
|
1335
|
+
}),
|
|
1336
|
+
handler: async (args) => {
|
|
1337
|
+
const REVIEW_PREFIX = '[jicon-mcp REVIEW] ';
|
|
1338
|
+
try {
|
|
1339
|
+
// 1. Get review draft to validate
|
|
1340
|
+
const reviewDraft = await client.getDraft(args.reviewDraftId);
|
|
1341
|
+
// 2. Validate it's a review draft
|
|
1342
|
+
if (!reviewDraft.title.startsWith(REVIEW_PREFIX)) {
|
|
1343
|
+
return formatError({
|
|
1344
|
+
error: true,
|
|
1345
|
+
message: `Not a jicon review draft. Title must start with "${REVIEW_PREFIX}"`,
|
|
1346
|
+
statusCode: 400,
|
|
1347
|
+
details: {
|
|
1348
|
+
actualTitle: reviewDraft.title,
|
|
1349
|
+
hint: "Use confluence_draft_delete for non-review drafts",
|
|
1350
|
+
},
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
// 3. Delete review draft
|
|
1354
|
+
await client.deleteDraft(args.reviewDraftId);
|
|
1355
|
+
// 4. Invalidate buffer
|
|
1356
|
+
contentBuffer.invalidateByMetadata({ resourceId: args.reviewDraftId });
|
|
1357
|
+
return formatSuccess({
|
|
1358
|
+
success: true,
|
|
1359
|
+
discarded: true,
|
|
1360
|
+
reviewDraftId: args.reviewDraftId,
|
|
1361
|
+
originalTitle: reviewDraft.title.replace(REVIEW_PREFIX, ''),
|
|
1362
|
+
message: "Review draft discarded. Original page was not modified.",
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
catch (error) {
|
|
1366
|
+
return formatError(isApiError(error) ? error : new Error(String(error)));
|
|
1367
|
+
}
|
|
1368
|
+
},
|
|
1369
|
+
},
|
|
1370
|
+
confluence_review_list: {
|
|
1371
|
+
description: `List all "[jicon-mcp REVIEW]" drafts for cleanup or management.
|
|
1372
|
+
|
|
1373
|
+
Returns review drafts with their linked original page IDs.
|
|
1374
|
+
Use this to find abandoned review drafts or manage multiple review workflows.`,
|
|
1375
|
+
inputSchema: z.object({
|
|
1376
|
+
spaceKey: z.string().optional().describe("Filter by space key (optional)"),
|
|
1377
|
+
}),
|
|
1378
|
+
handler: async (args) => {
|
|
1379
|
+
const REVIEW_PREFIX = '[jicon-mcp REVIEW] ';
|
|
1380
|
+
const REVIEW_LABEL_PREFIX = 'jicon-review-';
|
|
1381
|
+
try {
|
|
1382
|
+
// 1. List user's drafts with labels expanded
|
|
1383
|
+
const drafts = await client.listUserDrafts(args.spaceKey, 500);
|
|
1384
|
+
// 2. Filter to review drafts only
|
|
1385
|
+
const reviewDrafts = drafts.results.filter((draft) => draft.title?.startsWith(REVIEW_PREFIX));
|
|
1386
|
+
// 3. Build response with original page info from labels
|
|
1387
|
+
const baseUrl = client.getBaseUrl();
|
|
1388
|
+
const reviews = reviewDrafts.map((draft) => {
|
|
1389
|
+
const labels = draft.metadata?.labels?.results || [];
|
|
1390
|
+
const reviewLabel = labels.find((l) => l.name.startsWith(REVIEW_LABEL_PREFIX));
|
|
1391
|
+
const originalPageId = reviewLabel?.name.replace(REVIEW_LABEL_PREFIX, '') || 'unknown';
|
|
1392
|
+
return {
|
|
1393
|
+
reviewDraftId: draft.id,
|
|
1394
|
+
title: draft.title,
|
|
1395
|
+
originalTitle: draft.title.replace(REVIEW_PREFIX, ''),
|
|
1396
|
+
originalPageId,
|
|
1397
|
+
spaceKey: draft.space?.key || '',
|
|
1398
|
+
spaceName: draft.space?.name || '',
|
|
1399
|
+
createdDate: draft.version?.when || '',
|
|
1400
|
+
editUrl: `${baseUrl}/pages/resumedraft.action?draftId=${draft.id}`,
|
|
1401
|
+
};
|
|
1402
|
+
});
|
|
1403
|
+
return formatSuccess({
|
|
1404
|
+
reviewDrafts: reviews,
|
|
1405
|
+
total: reviews.length,
|
|
1406
|
+
message: reviews.length > 0
|
|
1407
|
+
? `Found ${reviews.length} review draft(s). Use confluence_review_publish or confluence_review_discard to manage.`
|
|
1408
|
+
: "No review drafts found.",
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
catch (error) {
|
|
1412
|
+
return formatError(isApiError(error) ? error : new Error(String(error)));
|
|
1413
|
+
}
|
|
1414
|
+
},
|
|
1415
|
+
},
|
|
862
1416
|
};
|
|
863
1417
|
}
|
|
864
1418
|
//# sourceMappingURL=tools.js.map
|