mrvn-cli 0.5.11 → 0.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +174 -14
- package/dist/index.js.map +1 -1
- package/dist/marvin-serve.js +173 -13
- package/dist/marvin-serve.js.map +1 -1
- package/dist/marvin.js +174 -14
- package/dist/marvin.js.map +1 -1
- package/package.json +1 -1
package/dist/marvin-serve.js
CHANGED
|
@@ -15711,7 +15711,9 @@ function collectSprintSummaryData(store, sprintId) {
|
|
|
15711
15711
|
workFocus: focusTag ? focusTag.slice(6) : void 0,
|
|
15712
15712
|
aboutArtifact: about,
|
|
15713
15713
|
jiraKey: doc.frontmatter.jiraKey,
|
|
15714
|
-
jiraUrl: doc.frontmatter.jiraUrl
|
|
15714
|
+
jiraUrl: doc.frontmatter.jiraUrl,
|
|
15715
|
+
confluenceUrl: doc.frontmatter.confluenceUrl,
|
|
15716
|
+
confluenceTitle: doc.frontmatter.confluenceTitle
|
|
15715
15717
|
};
|
|
15716
15718
|
allItemsById.set(item.id, item);
|
|
15717
15719
|
if (about && sprintItemIds.has(about)) {
|
|
@@ -19026,11 +19028,14 @@ import { tool as tool20 } from "@anthropic-ai/claude-agent-sdk";
|
|
|
19026
19028
|
var JiraClient = class {
|
|
19027
19029
|
baseUrl;
|
|
19028
19030
|
baseUrlV3;
|
|
19031
|
+
confluenceBaseUrl;
|
|
19029
19032
|
authHeader;
|
|
19033
|
+
host;
|
|
19030
19034
|
constructor(config2) {
|
|
19031
|
-
|
|
19032
|
-
this.baseUrl = `https://${host}/rest/api/2`;
|
|
19033
|
-
this.baseUrlV3 = `https://${host}/rest/api/3`;
|
|
19035
|
+
this.host = config2.host.replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
19036
|
+
this.baseUrl = `https://${this.host}/rest/api/2`;
|
|
19037
|
+
this.baseUrlV3 = `https://${this.host}/rest/api/3`;
|
|
19038
|
+
this.confluenceBaseUrl = `https://${this.host}/wiki/api/v2`;
|
|
19034
19039
|
this.authHeader = "Basic " + Buffer.from(`${config2.email}:${config2.apiToken}`).toString("base64");
|
|
19035
19040
|
}
|
|
19036
19041
|
async request(path11, method = "GET", body) {
|
|
@@ -19118,6 +19123,30 @@ var JiraClient = class {
|
|
|
19118
19123
|
{ body }
|
|
19119
19124
|
);
|
|
19120
19125
|
}
|
|
19126
|
+
// --- Confluence methods ---
|
|
19127
|
+
async getConfluencePage(pageId) {
|
|
19128
|
+
return this.doRequest(
|
|
19129
|
+
`${this.confluenceBaseUrl}/pages/${encodeURIComponent(pageId)}?body-format=atlas_doc_format`,
|
|
19130
|
+
"GET"
|
|
19131
|
+
);
|
|
19132
|
+
}
|
|
19133
|
+
/**
|
|
19134
|
+
* Extract a Confluence page ID from various URL formats.
|
|
19135
|
+
* Returns null if the URL doesn't match any known pattern.
|
|
19136
|
+
*/
|
|
19137
|
+
static extractPageId(url2) {
|
|
19138
|
+
const pagesMatch = url2.match(/\/pages\/(\d+)/);
|
|
19139
|
+
if (pagesMatch) return pagesMatch[1];
|
|
19140
|
+
const paramMatch = url2.match(/[?&]pageId=(\d+)/);
|
|
19141
|
+
if (paramMatch) return paramMatch[1];
|
|
19142
|
+
return null;
|
|
19143
|
+
}
|
|
19144
|
+
/**
|
|
19145
|
+
* Build a web URL for a Confluence page.
|
|
19146
|
+
*/
|
|
19147
|
+
getConfluencePageUrl(pageId) {
|
|
19148
|
+
return `https://${this.host}/wiki/pages/viewpage.action?pageId=${pageId}`;
|
|
19149
|
+
}
|
|
19121
19150
|
};
|
|
19122
19151
|
function createJiraClient(jiraUserConfig) {
|
|
19123
19152
|
const host = jiraUserConfig?.host ?? process.env.JIRA_HOST;
|
|
@@ -20178,6 +20207,111 @@ function createJiraTools(store, projectConfig) {
|
|
|
20178
20207
|
};
|
|
20179
20208
|
}
|
|
20180
20209
|
),
|
|
20210
|
+
// --- Confluence tools ---
|
|
20211
|
+
tool20(
|
|
20212
|
+
"link_to_confluence",
|
|
20213
|
+
"Link a Confluence page to any Marvin artifact. Validates the page exists and fetches its title.",
|
|
20214
|
+
{
|
|
20215
|
+
artifactId: external_exports.string().describe("Marvin artifact ID (e.g. 'D-001', 'A-003', 'T-002')"),
|
|
20216
|
+
confluenceUrl: external_exports.string().describe("Confluence page URL")
|
|
20217
|
+
},
|
|
20218
|
+
async (args) => {
|
|
20219
|
+
const jira = createJiraClient(jiraUserConfig);
|
|
20220
|
+
if (!jira) return jiraNotConfiguredError();
|
|
20221
|
+
const artifact = store.get(args.artifactId);
|
|
20222
|
+
if (!artifact) {
|
|
20223
|
+
return {
|
|
20224
|
+
content: [
|
|
20225
|
+
{ type: "text", text: `Artifact ${args.artifactId} not found` }
|
|
20226
|
+
],
|
|
20227
|
+
isError: true
|
|
20228
|
+
};
|
|
20229
|
+
}
|
|
20230
|
+
const pageId = JiraClient.extractPageId(args.confluenceUrl);
|
|
20231
|
+
if (!pageId) {
|
|
20232
|
+
return {
|
|
20233
|
+
content: [
|
|
20234
|
+
{ type: "text", text: `Could not extract page ID from URL: ${args.confluenceUrl}` }
|
|
20235
|
+
],
|
|
20236
|
+
isError: true
|
|
20237
|
+
};
|
|
20238
|
+
}
|
|
20239
|
+
const page = await jira.client.getConfluencePage(pageId);
|
|
20240
|
+
const existingTags = artifact.frontmatter.tags ?? [];
|
|
20241
|
+
store.update(args.artifactId, {
|
|
20242
|
+
confluenceUrl: args.confluenceUrl,
|
|
20243
|
+
confluencePageId: pageId,
|
|
20244
|
+
confluenceTitle: page.title,
|
|
20245
|
+
tags: [...existingTags.filter((t) => !t.startsWith("confluence:")), `confluence:${page.title}`]
|
|
20246
|
+
});
|
|
20247
|
+
return {
|
|
20248
|
+
content: [
|
|
20249
|
+
{
|
|
20250
|
+
type: "text",
|
|
20251
|
+
text: `Linked ${args.artifactId} to Confluence page "${page.title}" (ID: ${pageId}).`
|
|
20252
|
+
}
|
|
20253
|
+
]
|
|
20254
|
+
};
|
|
20255
|
+
}
|
|
20256
|
+
),
|
|
20257
|
+
tool20(
|
|
20258
|
+
"read_confluence_page",
|
|
20259
|
+
"Read the content of a Confluence page by URL or page ID. Returns the page title, metadata, and body as plain text.",
|
|
20260
|
+
{
|
|
20261
|
+
pageUrl: external_exports.string().optional().describe("Confluence page URL"),
|
|
20262
|
+
pageId: external_exports.string().optional().describe("Confluence page ID (alternative to URL)")
|
|
20263
|
+
},
|
|
20264
|
+
async (args) => {
|
|
20265
|
+
const jira = createJiraClient(jiraUserConfig);
|
|
20266
|
+
if (!jira) return jiraNotConfiguredError();
|
|
20267
|
+
const resolvedId = args.pageId ?? (args.pageUrl ? JiraClient.extractPageId(args.pageUrl) : null);
|
|
20268
|
+
if (!resolvedId) {
|
|
20269
|
+
return {
|
|
20270
|
+
content: [
|
|
20271
|
+
{
|
|
20272
|
+
type: "text",
|
|
20273
|
+
text: "Provide either pageUrl or pageId. Could not extract page ID from the given URL."
|
|
20274
|
+
}
|
|
20275
|
+
],
|
|
20276
|
+
isError: true
|
|
20277
|
+
};
|
|
20278
|
+
}
|
|
20279
|
+
const page = await jira.client.getConfluencePage(resolvedId);
|
|
20280
|
+
let bodyText = "";
|
|
20281
|
+
if (page.body?.atlas_doc_format?.value) {
|
|
20282
|
+
try {
|
|
20283
|
+
const adf = JSON.parse(page.body.atlas_doc_format.value);
|
|
20284
|
+
bodyText = extractCommentText(adf);
|
|
20285
|
+
} catch {
|
|
20286
|
+
bodyText = page.body.atlas_doc_format.value;
|
|
20287
|
+
}
|
|
20288
|
+
}
|
|
20289
|
+
const parts = [
|
|
20290
|
+
`# ${page.title}`,
|
|
20291
|
+
"",
|
|
20292
|
+
`Page ID: ${page.id}`,
|
|
20293
|
+
`Status: ${page.status}`,
|
|
20294
|
+
`Version: ${page.version.number} (${page.version.createdAt.slice(0, 10)})`,
|
|
20295
|
+
"",
|
|
20296
|
+
"---",
|
|
20297
|
+
"",
|
|
20298
|
+
bodyText || "(empty page)"
|
|
20299
|
+
];
|
|
20300
|
+
const allDocs = store.registeredTypes.flatMap((t) => store.list({ type: t }));
|
|
20301
|
+
const linkedArtifacts = allDocs.filter(
|
|
20302
|
+
(d) => d.frontmatter.confluencePageId === resolvedId || d.frontmatter.confluenceUrl === args.pageUrl
|
|
20303
|
+
);
|
|
20304
|
+
if (linkedArtifacts.length > 0) {
|
|
20305
|
+
parts.push("");
|
|
20306
|
+
parts.push("---");
|
|
20307
|
+
parts.push(`Linked Marvin artifacts: ${linkedArtifacts.map((d) => d.frontmatter.id).join(", ")}`);
|
|
20308
|
+
}
|
|
20309
|
+
return {
|
|
20310
|
+
content: [{ type: "text", text: parts.join("\n") }]
|
|
20311
|
+
};
|
|
20312
|
+
},
|
|
20313
|
+
{ annotations: { readOnlyHint: true } }
|
|
20314
|
+
),
|
|
20181
20315
|
// --- Jira status fetch (read-only) ---
|
|
20182
20316
|
tool20(
|
|
20183
20317
|
"fetch_jira_status",
|
|
@@ -20488,6 +20622,8 @@ function formatIssueEntry(issue2) {
|
|
|
20488
20622
|
var COMMON_TOOLS = `**Available tools:**
|
|
20489
20623
|
- \`push_artifact_to_jira\` \u2014 create a Jira issue from any Marvin artifact and link it directly via \`jiraKey\` on the artifact.
|
|
20490
20624
|
- \`link_to_jira\` \u2014 link an existing Jira issue to any Marvin artifact (sets \`jiraKey\` directly on the artifact).
|
|
20625
|
+
- \`link_to_confluence\` \u2014 link a Confluence page to any Marvin artifact. Validates the page exists and fetches its title.
|
|
20626
|
+
- \`read_confluence_page\` \u2014 **read-only**: fetch and return the content of a Confluence page by URL or page ID. Use this to review Confluence content for updating tasks, generating contributions, or answering questions.
|
|
20491
20627
|
- \`fetch_jira_status\` \u2014 **read-only**: fetch current Jira status, subtask progress, and linked issues for Jira-linked actions/tasks. Returns proposed changes without applying them.
|
|
20492
20628
|
- \`fetch_jira_daily\` \u2014 **read-only**: fetch a daily/range summary of all Jira changes \u2014 status transitions, comments, linked Confluence pages, and cross-references with Marvin artifacts. Returns proposed actions (status updates, unlinked issues, question candidates, Confluence pages to review).
|
|
20493
20629
|
- \`fetch_jira_statuses\` \u2014 **read-only**: discover all Jira statuses in a project and show their Marvin mappings (mapped vs unmapped).
|
|
@@ -21188,11 +21324,30 @@ function formatDate(iso) {
|
|
|
21188
21324
|
function typeLabel(type) {
|
|
21189
21325
|
return type.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
21190
21326
|
}
|
|
21327
|
+
var JIRA_SVG = `<svg class="integration-icon" viewBox="0 0 256 256" width="14" height="14" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="jg1" x1="98.03%" y1="0.16%" x2="58.89%" y2="40.87%"><stop offset="18%" stop-color="#0052CC"/><stop offset="100%" stop-color="#2684FF"/></linearGradient><linearGradient id="jg2" x1="100.17%" y1="0.05%" x2="55.76%" y2="45.19%"><stop offset="18%" stop-color="#0052CC"/><stop offset="100%" stop-color="#2684FF"/></linearGradient></defs><path d="M244.658 0H121.707a55.502 55.502 0 0 0 55.502 55.502h22.649V77.37c.02 30.625 24.841 55.447 55.466 55.502V10.666C255.324 4.777 250.55 0 244.658 0z" fill="#2684FF"/><path d="M183.822 61.262H60.872c.019 30.625 24.84 55.447 55.466 55.502h22.649v21.868c.02 30.597 24.798 55.408 55.395 55.502V71.928c0-5.891-4.776-10.666-10.56-10.666z" fill="url(#jg1)"/><path d="M122.951 122.489H0c0 30.653 24.85 55.502 55.502 55.502h22.72v21.868c.02 30.597 24.798 55.408 55.396 55.502V133.155c0-5.891-4.776-10.666-10.667-10.666z" fill="url(#jg2)"/></svg>`;
|
|
21328
|
+
var CONFLUENCE_SVG = `<svg class="integration-icon" viewBox="0 0 256 246" width="14" height="14" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="cg1" x1="99.14%" y1="113.9%" x2="33.86%" y2="37.96%"><stop offset="18%" stop-color="#0052CC"/><stop offset="100%" stop-color="#2684FF"/></linearGradient><linearGradient id="cg2" x1="0.86%" y1="-13.9%" x2="66.14%" y2="62.04%"><stop offset="18%" stop-color="#0052CC"/><stop offset="100%" stop-color="#2684FF"/></linearGradient></defs><path d="M9.26 187.28c-3.14 5.06-6.71 10.98-9.26 15.53a7.84 7.84 0 0 0 2.83 10.72l67.58 40.48a7.85 7.85 0 0 0 10.72-2.63c2.14-3.54 5.01-8.25 8.15-13.41 22.18-36.47 44.67-32.02 85.83-13.41l68.59 31.05a7.85 7.85 0 0 0 10.42-3.94l29.24-66.24a7.85 7.85 0 0 0-3.84-10.32c-20.53-9.27-61.49-27.75-87.33-39.45-72.2-32.73-133.87-30.05-182.93 51.62z" fill="url(#cg1)"/><path d="M246.11 58.24c3.14-5.06 6.71-10.98 9.26-15.53a7.84 7.84 0 0 0-2.83-10.72L184.96 0a7.85 7.85 0 0 0-10.72 2.63c-2.14 3.54-5.01 8.25-8.15 13.41-22.18 36.47-44.67 32.02-85.83 13.41L12.37 -1.6a7.85 7.85 0 0 0-10.42 3.94L-27.29 68.58a7.85 7.85 0 0 0 3.84 10.32c20.53 9.27 61.49 27.75 87.33 39.45 72.2 32.73 133.87 30.05 182.23-60.11z" fill="url(#cg2)"/></svg>`;
|
|
21191
21329
|
function jiraIcon(jiraKey, jiraUrl) {
|
|
21192
21330
|
if (!jiraKey) return "";
|
|
21193
21331
|
const href = jiraUrl ?? "#";
|
|
21194
21332
|
const title = escapeHtml(jiraKey);
|
|
21195
|
-
return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener" title="Jira: ${title}" class="
|
|
21333
|
+
return `<a href="${escapeHtml(href)}" target="_blank" rel="noopener" title="Jira: ${title}" class="integration-link jira-link">${JIRA_SVG}</a>`;
|
|
21334
|
+
}
|
|
21335
|
+
function confluenceIcon(confluenceUrl, confluenceTitle) {
|
|
21336
|
+
if (!confluenceUrl) return "";
|
|
21337
|
+
const title = confluenceTitle ? escapeHtml(confluenceTitle) : "Confluence";
|
|
21338
|
+
return `<a href="${escapeHtml(confluenceUrl)}" target="_blank" rel="noopener" title="${title}" class="integration-link confluence-link">${CONFLUENCE_SVG}</a>`;
|
|
21339
|
+
}
|
|
21340
|
+
function integrationIcons(frontmatter) {
|
|
21341
|
+
const jira = jiraIcon(
|
|
21342
|
+
frontmatter.jiraKey,
|
|
21343
|
+
frontmatter.jiraUrl
|
|
21344
|
+
);
|
|
21345
|
+
const confluence = confluenceIcon(
|
|
21346
|
+
frontmatter.confluenceUrl,
|
|
21347
|
+
frontmatter.confluenceTitle
|
|
21348
|
+
);
|
|
21349
|
+
if (!jira && !confluence) return "";
|
|
21350
|
+
return `<span class="integration-icons">${jira}${confluence}</span>`;
|
|
21196
21351
|
}
|
|
21197
21352
|
function renderMarkdown(md) {
|
|
21198
21353
|
const lines = md.split("\n");
|
|
@@ -23059,17 +23214,22 @@ tr:hover td {
|
|
|
23059
23214
|
.owner-badge-dm { background: rgba(52, 211, 153, 0.18); color: #34d399; }
|
|
23060
23215
|
.owner-badge-other { background: rgba(139, 143, 164, 0.12); color: var(--text-dim); }
|
|
23061
23216
|
|
|
23062
|
-
/* Jira
|
|
23063
|
-
.
|
|
23217
|
+
/* Integration icons (Jira, Confluence) */
|
|
23218
|
+
.integration-icons {
|
|
23064
23219
|
display: inline-flex;
|
|
23065
23220
|
align-items: center;
|
|
23221
|
+
gap: 0.2rem;
|
|
23066
23222
|
vertical-align: middle;
|
|
23067
23223
|
margin-left: 0.35rem;
|
|
23224
|
+
}
|
|
23225
|
+
.integration-link {
|
|
23226
|
+
display: inline-flex;
|
|
23227
|
+
align-items: center;
|
|
23068
23228
|
opacity: 0.7;
|
|
23069
23229
|
transition: opacity 0.15s;
|
|
23070
23230
|
}
|
|
23071
|
-
.
|
|
23072
|
-
.
|
|
23231
|
+
.integration-link:hover { opacity: 1; }
|
|
23232
|
+
.integration-icon { vertical-align: middle; }
|
|
23073
23233
|
|
|
23074
23234
|
/* Group header rows (PO dashboard decisions/deps) */
|
|
23075
23235
|
.group-header-row td {
|
|
@@ -23095,7 +23255,7 @@ function documentsPage(data) {
|
|
|
23095
23255
|
(doc) => `
|
|
23096
23256
|
<tr>
|
|
23097
23257
|
<td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.id)}</a></td>
|
|
23098
|
-
<td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.title)}</a>${
|
|
23258
|
+
<td><a href="/docs/${data.type}/${doc.frontmatter.id}">${escapeHtml(doc.frontmatter.title)}</a>${integrationIcons(doc.frontmatter)}</td>
|
|
23099
23259
|
<td>${statusBadge(doc.frontmatter.status)}</td>
|
|
23100
23260
|
<td>${escapeHtml(doc.frontmatter.owner ?? "\u2014")}</td>
|
|
23101
23261
|
<td>${doc.frontmatter.priority ? `<span class="priority-${doc.frontmatter.priority.toLowerCase()}">${escapeHtml(doc.frontmatter.priority)}</span>` : "\u2014"}</td>
|
|
@@ -23184,7 +23344,7 @@ function documentDetailPage(doc) {
|
|
|
23184
23344
|
</div>
|
|
23185
23345
|
|
|
23186
23346
|
<div class="page-header">
|
|
23187
|
-
<h2>${escapeHtml(fm.title)}${
|
|
23347
|
+
<h2>${escapeHtml(fm.title)}${integrationIcons(fm)}</h2>
|
|
23188
23348
|
<div class="subtitle">${escapeHtml(fm.id)} · ${escapeHtml(label)}</div>
|
|
23189
23349
|
</div>
|
|
23190
23350
|
|
|
@@ -24615,7 +24775,7 @@ function renderItemRows(items, borderColor, showOwner, depth = 0) {
|
|
|
24615
24775
|
const row = `
|
|
24616
24776
|
<tr class="${classes.join(" ")}" style="--focus-color: ${borderColor}">
|
|
24617
24777
|
<td${indent}><a href="/docs/${escapeHtml(w.type)}/${escapeHtml(w.id)}">${escapeHtml(w.id)}</a></td>
|
|
24618
|
-
<td>${escapeHtml(w.title)}${jiraIcon(w.jiraKey, w.jiraUrl)}</td>
|
|
24778
|
+
<td>${escapeHtml(w.title)}${jiraIcon(w.jiraKey, w.jiraUrl)}${confluenceIcon(w.confluenceUrl, w.confluenceTitle)}</td>
|
|
24619
24779
|
${ownerCell}
|
|
24620
24780
|
<td>${statusBadge(w.status)}</td>
|
|
24621
24781
|
<td>${progressCell}</td>
|
|
@@ -26240,7 +26400,7 @@ function boardPage(data, basePath = "/board") {
|
|
|
26240
26400
|
<div class="board-card">
|
|
26241
26401
|
<a href="/docs/${doc.frontmatter.type}/${doc.frontmatter.id}">
|
|
26242
26402
|
<div class="bc-id">${escapeHtml(doc.frontmatter.id)}</div>
|
|
26243
|
-
<div class="bc-title">${escapeHtml(doc.frontmatter.title)}${
|
|
26403
|
+
<div class="bc-title">${escapeHtml(doc.frontmatter.title)}${integrationIcons(doc.frontmatter)}</div>
|
|
26244
26404
|
${doc.frontmatter.owner ? `<div class="bc-owner">${escapeHtml(doc.frontmatter.owner)}</div>` : ""}
|
|
26245
26405
|
</a>
|
|
26246
26406
|
</div>`
|